目录
一、定时器基本操作原理
STM32 系列单片机有系统定时器(SysTick)、还有 2 个高级定时器 TIM1 和 TIM8,4 个 通用定时器 TIM2/3/4/5,2 个基本定时器 TIM6 和 TIM7。就功能方面,高级定时器>通用定 时器>基本定时器。理解了高级定时器,就能理解其他两种定时器。
1.定时器就是计数器
定时器最基础的功能是“计数”。一下框图是“高级定时器”:
看②,它是一个 16位的计数器:每进来一个时钟(CK_CNT),里面的计数值“CNT counter”就累加,可以从 0一直累加到0xFFFF。如果时钟频率是 72MHz,那么从 0累加到 0xFFFF的时间是 65535/72=910us。
对于计数器①,它的时钟来源有多种选择,如下图所示:
① 内部时钟:用作普通的定时器,比如系统定时器 ;
② ETR(External Trigger Input),外部触发的输入信号:可以统计外部触发信号 ;
③ ITR0~ITR3(Internal Trigger0~3),内部触发信号:内部触发信号来自其他定时器的“TRGO”信号,用于定时器的级联
④ TIMx_CH1/2/3/4:也可以作为外部输入信号 。
2.定时器时钟源
STM32F103C8定时器的时钟源可以是内部时钟(HSI)或外部时钟(HSE),具体取决于系统设计和配置。在大多数情况下,默认使用内部高速时钟(HSI)作为定时器的时钟源。
通用定时器有4种时钟源: ①内部时钟(CK_INT) ②外部时钟模式1:外部输入引脚(TIx),x=1,2(即只能来自于通道 1 或者通道 2) ③外部时钟模式2:外部触发输入(ETR) ④内部触发时钟:使用一个定时器作为另一定时器的预分频器
定时器时钟 TIMxCLK,即内部时钟 CK_INT,经 APB1预分频器后分频提供,如果APB1 预分频系数等于 1,则频率不变, 否则频率乘以 2,库函数中 APB1预分频的系数是2,即 PCLK1=36M,所以定时器时钟 TIMxCLK=36*2=72M。
参考此博客:STM32 第20讲 通用定时器(简介/框图/时钟源)_stm32定时器通道及引脚图-CSDN博客
3.PWM的概念
我们经常使用定时器来实现 PWM脉冲输出。PWM(Pulse Width Modulation)简称脉宽调制,使用定时器输出如下图所示的波形:
T 和 T1 都可以调整,频率 f=1/T,占空比 duty = T1/T。占空比是一个脉冲周期内, 高电平的时间与整个周期时间的比例,取值为 0%~100%。
PWM 的控制,通常使用定时器来实现。定时器里有这几个寄存器: ① CNT:计数寄存器,定时器每接收到一个时钟,CNT 值就加一 ② ARR(Auto Reload Register,自动加载寄存器):当 CNT 累加到 ARR 里的值后,CNT 就 从 0 开始,并且 GPIO 引脚的电平值反转 ③ CCR(Capture/Compare Register,捕获/比较寄存器):当 CNT 累加到 CRR 的值时, GPIO 引脚的电平值就反转 。
假设 CNT 值不断累加,这几个寄存器的值、GPIO 的电平,关系如下图所示:
二、高精度计时
1.Systick高精度计时
driver_timer.c
#include "driver_timer.h" #include "stm32f1xx_hal.h" #include "cmsis_version.h" /********************************************************************** * 函数名称: udelay * 功能描述: us级别的延时函数(复制rt-thread的代码) * 输入参数: us - 延时多少us * 输出参数: 无 * 返 回 值: 无 * 修改日期 版本号 修改人 修改内容 * ----------------------------------------------- * 2024/7/24 V1.0 大柯基 创建 ***********************************************************************/ void udelay(int us) { uint32_t told = SysTick -> VAL; uint32_t tnow; uint32_t load = SysTick ->LOAD; /* LOAD + 1 个时钟对应1ms * n us对应 n*(load + 1)/1000 个时钟 */ uint32_t ticks = us*(load +1)/1000; uint32_t cnt = 0; while(1) { tnow = SysTick -> VAL; if(told >= tnow) cnt += told - tnow; else cnt += told + told + 1 - tnow; /*刷新told 便于下次计算*/ told = tnow; if(cnt >= ticks) break; } } /********************************************************************** * 函数名称: mdelay * 功能描述: ms级别的延时函数 * 输入参数: ms - 延时多少ms * 输出参数: 无 * 返 回 值: 无 * 修改日期 版本号 修改人 修改内容 * ----------------------------------------------- * 2024/7/24 V1.0 大柯基 创建 ***********************************************************************/ void mdelay(int ms) { for(int i=0; i< ms; i++){ udelay(1000); } } /********************************************************************** * 函数名称: system_get_ns * 功能描述: 获得系统时间(单位ns) * 输入参数: 无 * 输出参数: 无 * 返 回 值: 系统时间(单位ns) * 修改日期 版本号 修改人 修改内容 * ----------------------------------------------- * 2024/7/24 V1.0 大柯基 创建 ***********************************************************************/ uint64_t system_get_ns(void) { uint64_t ns = HAL_GetTick(); ns = ns*1000000; uint32_t tnow = SysTick ->VAL; uint32_t load = SysTick ->LOAD; uint64_t cnt; cnt = load + 1 - tnow; ns += cnt * 1000000 / (load+1); return ns; }
2.定时器高精度计时
driver_timer.c
#include "driver_timer.h" #include "stm32f1xx_hal.h" #include "cmsis_version.h" //#define USE_SYSTICK extern TIM_HandleTypeDef htim4; /********************************************************************** * 函数名称: udelay * 功能描述: us级别的延时函数(复制rt-thread的代码) * 输入参数: us - 延时多少us * 输出参数: 无 * 返 回 值: 无 * 修改日期 版本号 修改人 修改内容 * ----------------------------------------------- * 2024/7/24 V1.0 大柯基 创建 ***********************************************************************/ void udelay(int us) { #ifdef USE_SYSTICK uint32_t told = SysTick -> VAL; uint32_t tnow; uint32_t load = SysTick ->LOAD; /* LOAD + 1 个时钟对应1ms * n us对应 n*(load + 1)/1000 个时钟 */ uint32_t ticks = us*(load +1)/1000; uint32_t cnt = 0; while(1) { tnow = SysTick -> VAL; if(told >= tnow) cnt += told - tnow; else cnt += told + told + 1 - tnow; /*刷新told 便于下次计算*/ told = tnow; if(cnt >= ticks) break; } #else uint32_t told = __HAL_TIM_GET_COUNTER(&htim4); uint32_t tnow; uint32_t load = __HAL_TIM_GET_AUTORELOAD(&htim4); /* LOAD + 1 个时钟对应1ms * n us对应 n*(load + 1)/1000 个时钟 */ uint32_t ticks = us*(load +1)/1000; uint32_t cnt = 0; while(1) { tnow = __HAL_TIM_GET_COUNTER(&htim4); if(tnow >= told) cnt += tnow - told; else cnt += load + 1 - told + tnow; /*刷新told 便于下次计算*/ told = tnow; if(cnt >= ticks) break; } #endif } /********************************************************************** * 函数名称: mdelay * 功能描述: ms级别的延时函数 * 输入参数: ms - 延时多少ms * 输出参数: 无 * 返 回 值: 无 * 修改日期 版本号 修改人 修改内容 * ----------------------------------------------- * 2024/7/24 V1.0 大柯基 创建 ***********************************************************************/ void mdelay(int ms) { for(int i=0; i< ms; i++){ udelay(1000); } } /********************************************************************** * 函数名称: system_get_ns * 功能描述: 获得系统时间(单位ns) * 输入参数: 无 * 输出参数: 无 * 返 回 值: 系统时间(单位ns) * 修改日期 版本号 修改人 修改内容 * ----------------------------------------------- * 2024/7/24 V1.0 大柯基 创建 ***********************************************************************/ uint64_t system_get_ns(void) { #ifdef USE_SYSTICK uint64_t ns = HAL_GetTick(); ns = ns*1000000; uint32_t tnow = SysTick ->VAL; uint32_t load = SysTick ->LOAD; uint64_t cnt; cnt = load + 1 - tnow; ns += cnt * 1000000 / (load+1); return ns; #else uint64_t ns = HAL_GetTick(); ns = ns*1000000; uint32_t tnow = __HAL_TIM_GET_COUNTER(&htim4); uint32_t load = __HAL_TIM_GET_AUTORELOAD(&htim4); uint64_t cnt; cnt = tnow; ns += cnt * 1000000 / (load+1); return ns; #endif }
三、使用PWM控制三色灯
driver_color_oled.c
#include "driver_color_led.h" #include "stm32f1xx_hal.h" #include "cmsis_version.h" extern TIM_HandleTypeDef htim2; #define COLOR_LED_R TIM_CHANNEL_3 #define COLOR_LED_G TIM_CHANNEL_1 #define COLOR_LED_B TIM_CHANNEL_2 void ColorLED_Init(void) { //MX_TIM2_Init(); HAL_TIM_PWM_Start(&htim2, COLOR_LED_R); HAL_TIM_PWM_Start(&htim2, COLOR_LED_G); HAL_TIM_PWM_Start(&htim2, COLOR_LED_B); } void ColorLED_SetColor(uint32_t color) { TIM_OC_InitTypeDef sConfigOC_R = {0}; TIM_OC_InitTypeDef sConfigOC_G = {0}; TIM_OC_InitTypeDef sConfigOC_B = {0}; sConfigOC_R.OCMode = TIM_OCMODE_PWM1; sConfigOC_R.Pulse = ((color >> 16) & 0xff)*1999/255; /* 最大值1999 */ sConfigOC_R.OCPolarity = TIM_OCPOLARITY_LOW; sConfigOC_R.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC_R, COLOR_LED_R) != HAL_OK) { Error_Handler(); } sConfigOC_G.OCMode = TIM_OCMODE_PWM1; sConfigOC_G.Pulse = ((color >> 8) & 0xff)*1999/255; /* 最大值1999 */ sConfigOC_G.OCPolarity = TIM_OCPOLARITY_LOW; sConfigOC_G.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC_G, COLOR_LED_G) != HAL_OK) { Error_Handler(); } sConfigOC_B.OCMode = TIM_OCMODE_PWM1; sConfigOC_B.Pulse = ((color >> 0) & 0xff)*1999/255; /* 最大值1999 */ sConfigOC_B.OCPolarity = TIM_OCPOLARITY_LOW; sConfigOC_B.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC_B, COLOR_LED_B) != HAL_OK) { Error_Handler(); } }
main.c
ColorLED_Init(); ColorLED_SetColor(0x00ff0000); HAL_Delay(1000); ColorLED_SetColor(0x00ff00); HAL_Delay(1000); ColorLED_SetColor(0x00ff); HAL_Delay(1000);
实验现象:全彩LED灯红绿蓝延迟1s交替显示。
四、总结
定时器在单片机开发过程中具有相对重要的作用,合理配置相关的定时寄存器,能够为我们提供精确的时间控制和计数功能。