STM32定时器原理与应用实战
STM32定时器原理与应用实战
一、定时器基本操作原理
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),外部触发的输入信号:可以统计外部触发信号 ;
③ ITR0ITR3(Internal Trigger03),内部触发信号:内部触发信号来自其他定时器的“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交替显示。
四、总结
定时器在单片机开发过程中具有相对重要的作用,合理配置相关的定时寄存器,能够为我们提供精确的时间控制和计数功能。