STM32定时器应用指南
STM32-01-认识单片机
STM32-02-基础知识
STM32-03-HAL库
STM32-04-时钟树
STM32-05-SYSTEM文件夹
STM32-06-GPIO
STM32-07-外部中断
STM32-08-串口
STM32-09-IWDG和WWDG
文章目录
一、STM32 基础定时器
STM32定时器分类:
STM32定时器特性表:
主要功能:
定时器类型 | 主要功能 |
---|---|
基本定时器 | 没有输入输出通道,常用作时基,即定时功能 |
通用定时器 | 具有多路独立通道,可用于输入捕获/输出比较,也可用作时基 |
高级定时器 | 除具备通用定时器所有功能外,还具备带死区控制的互补信号输出、刹车输入等功能(可用于电机控制、数字电源设计等) |
1. 基本定时器简介
STM32F103有两个基本定时器TIM6
和TIM7
,它们的功能完全相同,资源是完全独立的,可以同时使用。
主要特性:
1~65536
,用于对计数器时钟频率进行分频;定时原理:
2. 基本定时器框图
影子寄存器: 实际起作用的寄存器,用户不可直接访问。举个例子:我们可以把预分频系数写入预分频器寄存器(TIMx_PSC
),但是预分频器寄存器只是起到缓存数据的作用,只有等到更新事件发生时,预分频器寄存器的值才会被自动写入其影子寄存器中,这时才真正起作用。
自动重载寄存器:自动重载寄存器及其影子寄存器的作用在于自动重载寄存器是否具有缓冲作用还受到ARPE
位的控制。
更新事件的产生:
- 软件产生,将
TIMx_EGR
寄存器的位UG
置 1,产生更新事件后,硬件会自动将UG
位清零。 - 硬件产生,基本定时器的计数器(
CNT
)是一个递增的计数器,当寄存器(TIMx_CR1
)的CEN
位置 1,即使能定时器,每来一个CK_CNT脉冲,TIMx_CNT
的值就会递增加1。当TIMx_CNT
值与TIMx_ARR
的设定值相等时,TIMx_CNT
的值就会被自动清零并且会生成更新事件(如果开启相应的功能,就会产生DMA请求、产生中断信号或者触发DAC同步电路),然后下一个 CK_CNT脉冲到来,TIMx_CNT
的值就会递增加1,如此循环。在此过程中,TIMx_CNT
等于TIMx_ARR
时,我们称之为定时器溢出,因为是递增计数,故而又称为定时器上溢。定时器溢出就伴随着更新事件的发生。
计数模式与溢出条件:
递增计数模式实例说明:
递减计数模式实例说明:
中心对齐计数模式实例说明:
3. 基本定时器相关寄存器
控制寄存器 1(TIMx_CR1
)
DMA/中断使能寄存器(TIMx_DIER
)
状态寄存器(TIMx_SR
)
计数器(TIMx_CNT
)
预分频器(TIMx_PSC
)
自动重装载寄存器(TIMx_ARR
)
4. 定时器溢出时间计算
5. 基本定时器中断配置步骤
-
配置定时器基础工作参数
HAL_TIM_Base_Init()
-
定时器基础MSP初始化
HAL_TIM_Base_MspInit()
-
使能更新中断并启动计数器
HAL_TIM_Base_Start_IT()
-
设置优先级,使能中断
HAL_NVIC_SetPriority() HAL_NVIC_EnableIRQ()
-
编写中断服务函数
TIMx_IRQHandler() HAL_TIM_IRQHandler()
-
编写定时器更新中断回调函数
HAL_TIM_PeriodElapsedCallback()
程序流程
6. 代码实现
功能:使用定时器中断实现LED灯闪烁.
定时器中断初始化
void btim_timx_int_init(uint16_t psc, uint16_t per)
{
g_timx_handle.Instance = TIM6;
g_timx_handle.Init.Prescaler = psc;
g_timx_handle.Init.Period = per;
HAL_TIM_Base_Init(&g_timx_handle);
HAL_TIM_Base_Start_IT(&g_timx_handle);
}
定时器基础MSP初始化函数
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
__HAL_RCC_TIM6_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM6_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM6_IRQn);
}
}
定时器6中断服务函数
void TIM6_IRQHandler()
{
HAL_TIM_IRQHandler(&g_timx_handle);
}
定时器溢出中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
LED0_TOGGLE();
}
}
主函数
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/EXTI/exti.h"
#include "./BSP/WDG/wdg.h"
#include "./BSP/TIMER/btim.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
btim_timx_int_init(7199, 4999);
while(1)
{
}
}
二、STM32通用定时器
1. 通用定时器简介
- 通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。
- 它适用于多种场合,包括测量输入信号的脉冲宽度(输入捕获)或者产生输出波形(输出比较和PWM)。
- 使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。
- 每个定时器都是完全独立的,没有互相共享任何资源,并且可以一起同步操作。
主要特性
16位递增、递减、中心对齐计数器(计数值:0~65535)
16位预分频器(分频系数:1~65536)
4个独立通道:输入捕获,输出比较,PWM生成,单脉冲模式输出
使用外部信号控制定时器且可实现多个定时器互连的同步电路
支持针对定位的增量编码器和霍尔传感器电路
触发输入作为外部时钟或者按周期的电流管理
2. 通用定时器框图
1. 时钟源
计数器时钟选择类型 | 设置方法 |
---|---|
内部时钟(CK_INT) | 设置TIMx_SMCR的SMS=000 |
外部时钟模式1:外部输入引脚(TIx) | 设置TIMx_SMCR的SMS=111 |
外部时钟模式2:外部触发输入(ETR) | 设置TIMx_SMCR的ECE=1 |
内部触发输入(ITRx) |
1. 内部时钟(CK_INT
)
2. 外部时钟模式1(TI1, TI2)
3. 外部时钟模式2(ETR)
4. 内部触发输入(ITRx)
2. 控制器
控制器包括:触发控制器,从模式控制器,编码器接口
触发控制器:用来提供触发信号给别的外设
从模式控制器:可以控制计数器复位、启动、递增/递减、计数
编码器接口:针对编码器计数
3. 时基单元
4. 输入捕获
TIMx_CH1
~TIMx_CH4
表示定时器的4个通道,4个通道独立工作。IO端口通过复用功能与这些通道相连。配置好IO端口的复用功能后,将需要测量的信号输入到相应的 IO端口,输入捕获部分可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常见的测量有:测量输入信号的脉冲宽度、测量PWM输入信号的频率和占空比等。
5. 捕获/比较(公共)
以通道1为例,对应CCR1
寄存器,CC1G
位可以产生软件捕获事件,那么硬件捕获事件如何产生的?以通道1输入为例,CC1S[1:0]=01,即IC1映射到TI1上;CC1E位置1,使能输入捕获;比如不滤波、不分频,ICF[3:0]=00,ICPS[1:0]=00;比如检测上升沿,CC1P位置0:接着就是等待测量信号的上升沿到来。当上升沿到来时,IC1PS信号就会触发输入捕获事件发生,计数器的值就会被锁存到捕获/比较影子寄存器里。当CCR1寄存器没有被进行读操作的时候,捕获/比较影子寄存器里的值就会锁存到CCR1寄存器中,那么程序员就可以读取CCR1寄存器,得到计数器的计数值。检测下降沿同理。
6. 输出比较
首先程序员写CCR1寄存器,即写入比较值。这个比较值需要转移到对应的捕获比较影子寄存器后才会真正生效。什么条件下才能转移?compare transfer旁边的与门,需要满足三个条件:CCR1不在写入操作期间、CC1S[1:0]=0配置为输出、OC1PE位置 0(或者OC1PE位置1,并且需要发生更新事件,这个更新事件可以软件产生或者硬件产生)。当CCR1寄存器的值转移到其影子寄存器后,新的值就会和计数器的值进行比较,它们的比较结果将会通过输出比较影响定时器的输出。
3. 通用定时器相关寄存器
控制寄存器1(TIMx_CR1
)
从模式控制寄存器(TIMx_SMCR
)
DMA/中断使能寄存器(TIMx_DIER
)
状态寄存器(TIMx_SR
)
计数寄存器(TIMx_CNT
)
预分频寄存器(TIMx_PSC
)
自动重载寄存器(TIMx_ARR
)
三、 通用定时器输出PWM波
通用定时器输出模式
PWM,即脉冲宽度调制,是利用微处理器的数字输出对模拟电路进行控制的一种非常有效的技术。让定时器产生PWM,在计数器频率固定时,PWM频率自动重载寄存器(TIMx_ARR)的值决定,其占空比由捕获/比较寄存器(TIMx_CCRx)的值决定。
PWM模式
PWM控制寄存器
捕获/比较模式寄存器1/2(TIMx_CCMR1/2
)
捕获/比较使能寄存器(TIMx_CCER
)
捕获/比较寄存器(TIMx_CCR1/2/3/4
)
1. 通用定时器PWM输出实验配置步骤
-
配置定时器基础工作参数
HAL_TIM_PWM_Init()
-
定时器PWM输出MSP初始化
HAL_TIM_PWM_MspInit()
-
配置PWM模式/比较值等
HAL_TIM_PWM_ConfigChannel()
-
使能输出并启动计数器
HAL_TIM_PWM_Start()
-
修改比较值控制占空比(可选)
__HAL_TIM_SET_COMPARE()
-
使能通道预装载(可选)
__HAL_TIM_ENABLE_OCxPRELOAD()
相关库函数介绍
函数 | 主要寄存器 | 主要功能 |
---|---|---|
HAL_TIM_PWM_Init() | CR1、ARR、PSC | 初始化定时器基础参数 |
HAL_TIM_PWM_MspInit() | 无 | 存放NVIC、CLOCK、GPIO初始化代码 |
HAL_TIM_PWM_ConfigChannel() | CCMRx、CCRx、CCER | 配置PWM模式、比较值、输出极性等 |
HAL_TIM_PWM_Start() | CCER、CR1 | 使能输出比较并启动计数器 |
__HAL_TIM_SET_COMPARE() | CCRx | 修改比较值 |
__HAL_TIM_ENABLE_OCxPRELOAD() | CCER | 使能通道预装载 |
2. 代码实现
功能:使用TM3通道2(由PB5复用)输出PWM,PB5引脚连接了LED0,从而实现PWM输出控制LED0亮度。
程序流程:
通用定时器PWM输出初始化函数
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_pwm_chy;
g_timx_pwm_chy_handle.Instance = TIM3; //定时器选择
g_timx_pwm_chy_handle.Init.Prescaler = psc; //定时器分频
g_timx_pwm_chy_handle.Init.Period = arr;
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; //定时器计数模式
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle); //初始化PWM
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; //模式选择PWM1
timx_oc_pwm_chy.Pulse = arr/2; //占空比为50%
timx_oc_pwm_chy.OCNPolarity = TIM_OCNPOLARITY_LOW; //输出比较极性为低
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, TIM_CHANNEL_2); //配置定时器3通道2
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_2); //开启PWM通道
}
定时器输出PWM MSP初始化函数
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_TIM3_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_5;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &gpio_init_struct);
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_REMAP_TIM3_PARTIAL(); //设置重映射,把TIM3通道2映射到PB5
}
}
主函数
int main(void)
{
uint16_t ledrpwmval = 0;
uint8_t dir = 1;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_pwm_chy_init(500 - 1, 72 - 1);
while(1)
{
delay_ms(10);
if(dir) ledrpwmval++;
else ledrpwmval--;
if(ledrpwmval > 300) dir = 0;
if(ledrpwmval == 0) dir = 1;
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, TIM_CHANNEL_2, ledrpwmval);
}
}
四、通用定时器输入捕获实验
1. 输入捕获测量脉冲宽度
测量方法:假定工作在递增计数模式,首先设置通道x为上升沿捕获,这样在t1时刻上升沿到来时,就会发生捕获事件(打开捕获中断,捕获事件发生时会触发捕获中断)。在捕获中断里将计数器值清零,并设置通道x为下降沿捕获,这样t2时刻下降沿到来时,就会发生捕获事件和捕获中断。捕获事件发生时,计数器的值会被锁存到捕获/比较寄存器中。在捕获中断内,读取捕获/比较寄存器就可以获取高电平脉冲时间,计数器计数的个数,从而可以算出高电平脉冲的时间(假定计数器没有溢出)。
但是在t1到t2时间段,定时器可能会产生N次溢出,需要对定时器溢出做相应的处理,防止高电平太长,导致测量出错。在t1到t2时间内,假定定时器溢出N次,那么高电平时间内,计数器计数的个数计算方法为:N * (ARR + 1) + CCRx2
,CCRx2表示t2时间点,捕获/比较寄存器的值。经过计算得到高电平脉宽时间内计数器计数个数后,用这个个数乘以计数器的计数周期,就可以得到高电平持续的时间。就是输入捕获测量高电平脉宽时间的整个过程。
STM32F103的定时器除了TM6和TM7,其他定时器都有输入捕获功能。输入捕获,简单的说就是通过检测TIMx_CHy
上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)时,会发生捕获事件,将当前定时器的值(TIMx_CNT
)锁存到对应通道的捕获/比较寄存器(TIMx_CCRy
)里,完成一次捕获。同时还可以配置捕获事件发生时是否触发捕获中断/DMA。另外还要考虑测量的过程中是否可能发生定时器溢出,如果可能溢出,还要做溢出处理。
2. 输入捕获实验相关寄存器
捕获/比较模式寄存器1/2(TIMx_CCMR1/2
)
捕获/比较使能寄存器(TIMx_CCER
)
3. 通用定时器输入捕获配置步骤
-
配置定时器基础工作参数
HAL_TIM_IC_Init()
-
定时器输入捕获MSP初始化
HAL_TIM_IC_MspInit()
-
配置输入通道映射、捕获边沿等
HAL_TIM_IC_ConfigChannel()
-
设置优先级,使能中断
HAL_NVIC_SetPriority() HAL_NVIC_EnableIRQ()
-
使能定时器更新中断
__HAL_TIM_ENABLE_IT()
-
使能捕获、捕获中断及计数器
HAL_TIM_IC_Start_IT()
-
编写中断服务函数
TIMx_IRQHandler() HAL_TIM_IRQHandler()
-
编写更新中断和捕获回调函数
HAL_TIM_PeriodElapsedCallback() HAL_TIM_IC_CaptureCallback()
相关函数的功能
函数 | 主要寄存器 | 主要功能 |
---|---|---|
HAL_TIM_IC_Init() | CR1、ARR、PSC | 初始化定时器基础参数 |
HAL_TIM_IC_MspInit() | 无 | 存放NVIC、CLOCK、GPIO初始化代码 |
HAL_TIM_IC_ConfigChannel() | CCMRx、CCER | 配置通道映射、捕获边沿、分频、滤波等 |
__HAL_TIM_ENABLE_IT() | DIER | 使能更新中断等 |
HAL_TIM_IC_Start_IT() | CCER、DIER、CR1 | 使能输入捕获、捕获中断并启动计数器 |
HAL_TIM_IRQHandler() | SR | 定时器中断处理公用函数,处理各种中断 |
HAL_TIM_PeriodElapsedCallback() | 无 | 定时器更新中断回调函数,由用户重定义 |
HAL_TIM_IC_CaptureCallback() | 无 | 定时器输入捕获回调函数,由用户重定义 |
4. 代码实现
功能
1、使用TM5CH1来做输入捕获,捕获PA0上的高电平脉宽,并将脉宽时间通过串口打印出来,然后通过按WKUP按键,模拟输入高电平,例程中能测试的最长高电平脉宽时间为: 4194303us.
2、LED0闪烁指示程序运行。
通用定时器TIM5通道1输入捕获初始化函数
void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
{
TIM_IC_InitTypeDef timx_ic_cap_chy = {0};
//初始化定时器参数
g_timx_cap_chy_handle.Instance = TIM5; //定时器5的基地址
g_timx_cap_chy_handle.Init.Prescaler = psc; //定时器的分频系数
g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; //递增计数模式
g_timx_cap_chy_handle.Init.Period = arr; //自动重装载值
HAL_TIM_IC_Init(&g_timx_cap_chy_handle); //初始化
//配置通道映射、捕获边沿、分频、滤波等
timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING; //配置捕获极性为上升沿
timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI; //映射到通道1
timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1; //不进行分频
timx_ic_cap_chy.ICFilter = 0; //不进行滤波
HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy, TIM_CHANNEL_1); //初始化
__HAL_TIM_ENABLE_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE); //使能更新中断
HAL_TIM_IC_Start_IT(&g_timx_cap_chy_handle, TIM_CHANNEL_1); //开始捕获TIM5的通道1
}
通用定时器输入捕获初始化函数
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM5) //判断定时器是否正确
{
GPIO_InitTypeDef gpio_init_struct; //GPIO配置结构体
__HAL_RCC_TIM5_CLK_ENABLE(); //使能定时器时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟
gpio_init_struct.Pin = GPIO_PIN_0; //配置引脚号
gpio_init_struct.Mode = GPIO_MODE_AF_PP; //配置复用推挽输出
gpio_init_struct.Pull = GPIO_PULLDOWN; //配置下拉模式
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; //配置输出速度
HAL_GPIO_Init(GPIOA, &gpio_init_struct); //初始化
HAL_NVIC_SetPriority(TIM5_IRQn, 1, 3); //设置中断优先级
HAL_NVIC_EnableIRQ(TIM5_IRQn); //使能TIM5中断
}
}
定时器TIM5中断服务函数
//定时器5中断服务函数
void TIM5_IRQHandler(void)
{
//HAL库中断公共处理函数
HAL_TIM_IRQHandler(&g_timx_cap_chy_handle);
}
定时器输入捕获中断处理回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM5)
{
if((g_timxchy_cap_sta & 0x80) == 0) //未成功捕获
{
if(g_timxchy_cap_sta & 0x40) //捕获下降沿
{
g_timxchy_cap_sta |= 0x80; //位7置1 代表成功捕获
g_timxchy_cap_val = HAL_TIM_ReadCapturedValue(&g_timx_cap_chy_handle, TIM_CHANNEL_1); //保存当前计数器的值
TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1); //失能通道
TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); //使能通道1为上降沿捕获
}
else //捕获上升沿
{
g_timxchy_cap_sta = 0;
g_timxchy_cap_val = 0;
g_timxchy_cap_sta |= 0x40;
__HAL_TIM_DISABLE(&g_timx_cap_chy_handle); //失能定时器5
__HAL_TIM_SET_COUNTER(&g_timx_cap_chy_handle, 0); //计数器的值清0
TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1); //失能通道
TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); //使能通道1为下降沿捕获
__HAL_TIM_ENABLE(&g_timx_cap_chy_handle); //使能定时器5
}
}
}
}
定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM5)
{
if((g_timxchy_cap_sta & 0x80) == 0) //未成功捕获
{
if(g_timxchy_cap_sta & 0x40) //已经成功捕获
{
if((g_timxchy_cap_sta & 0X3F) == 0x3f) //高电平时间过长
{
TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1); //失能通道
TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); //使能通道1为下降沿捕获
g_timxchy_cap_sta |= 0x80; //标记成功捕获一次
g_timxchy_cap_val = 0xFFFF;
}
else //累积定时器溢出次数
{
g_timxchy_cap_sta++;
}
}
}
}
if(htim->Instance == TIM6)
{
LED0_TOGGLE();
}
if(htim->Instance == TIM7)
{
LED1_TOGGLE();
}
}
主函数
int main(void)
{
uint32_t temp = 0;
uint8_t t= 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_cap_chy_init(0xFFFF, 72 - 1); //1MHz
while(1)
{
if(g_timxchy_cap_sta & 0x80)
{
temp = g_timxchy_cap_sta & 0x3F;
temp = temp * 65536; //溢出时间总和
temp += g_timxchy_cap_val; //溢出的总时间
printf("HIGH:%d us\r\n", temp);
g_timxchy_cap_sta = 0; //开启下一次捕获
}
t++;
if(t > 20)
{
t = 0;
LED0_TOGGLE();
}
delay_ms(10);
}
}
主程序运行流程
中断程序运行流程
五、通用定时器脉冲计数实验
1. 脉冲计数框图
脉冲计数实验使用的时钟源是外部时钟模式1,外部输入引脚(TIx)作为定时器的时钟源。实验中使用WK_UP按键按下产生的高电平脉冲作为定时器的计数器时钟,每按下一次按键产生一次高电平脉冲,计数器的值加一。
外部时钟模式1的外部输入引脚只能是通道1或者通道2对应的IO,通道3或者通道4不可以。以通道1输入为例,外部时钟源信号通过通道1输入后,TI1分别要经过滤波器、边沿检测器后,来到TI1FP1,被触发输入选择器选择为触发源,接着来到从模式控制器。从模式选择为外部时钟模式1,这时候外部时钟源信号就会到达时基单元的预分频器,后面就是经过分频后就作为计数器的计数时钟。
2. 相关寄存器
捕获/比较模式寄存器1/2(TIMx_CCMR1/2
)
与输入捕获配置相同
捕获/比较使能寄存器(TIMx_CCER
)
与输入捕获配置相同
从模式控制寄存器(TIMx_SMCR
)
3. 通用定时器脉冲计数配置步骤
-
配置定时器基础工作参数
HAL_TIM_IC_Init()
-
定时器输入捕获MSP初始化
HAL_TIM_IC_MspInit()
-
配置定时器从模式等
HAL_TIM_SlaveConfigSynchro()
-
使能输入捕获并启动计数器
HAL_TIM_IC_Start()
-
获取计数器的值
__HAL_TIM_GET_COUNTER()
-
设置计数器的值
__HAL_TIM_SET_COUNTER()
相关库函数
函数 | 主要寄存器 | 主要功能 |
---|---|---|
HAL_TIM_IC_Init() | CR1、ARR、PSC | 初始化定时器基础参数 |
HAL_TIM_IC_MspInit() | 无 | 存放NVIC、CLOCK、GPIO初始化代码 |
HAL_TIM_SlaveConfigSynchro() | SMCR、CCMRx、CCER | 配置定时器从模式、触发选择、分频、滤波等 |
HAL_TIM_IC_Start() | CCER、CR1 | 使能输入捕获、启动计数器 |
__HAL_TIM_GET_COUNTER() | CNT | 获取计数器当前值 |
__HAL_TIM_SET_COUNTER() | CNT | 设置计数器的值 |
4. 代码实现
功能:
使用TM2CH1做输入捕获,我们将捕获PA0上的高电平脉宽,并将脉宽进行计数,通过串口打印出来。大家可以通过按WKUP按键,输入高电平脉冲,通过按KEY0重设当前计数。 LED0闪烁,提示程序运行。
通用定时器脉冲计数初始化函数
void gtim_timx_cnt_chy_init(uint16_t psc)
{
TIM_SlaveConfigTypeDef timx_slave_config = {0};
g_timx_cnt_chy_handle.Instance = TIM2; //定时器选择
g_timx_cnt_chy_handle.Init.Prescaler = psc; //定时器分频
g_timx_cnt_chy_handle.Init.Period = 65535;
g_timx_cnt_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; //定时器计数模式
HAL_TIM_IC_Init(&g_timx_cnt_chy_handle); //初始化PWM
timx_slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
timx_slave_config.InputTrigger = TIM_TS_TI1F_ED;
timx_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
timx_slave_config.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchro(&g_timx_cnt_chy_handle, &timx_slave_config);
HAL_TIM_IC_Start(&g_timx_cnt_chy_handle, TIM_CHANNEL_1);
}
主函数
int main(void)
{
uint16_t curcnt; //存储当前的计数值
uint16_t oldcnt; //存储旧的计数值
uint8_t key; //按键的键值
uint8_t t; //延时时间
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init();
gtim_timx_cnt_chy_init(0);
while(1)
{
key = key_scan(0);
if(key == 1)
{
__HAL_TIM_SET_COUNTER(&g_timx_cnt_chy_handle, 0);
}
curcnt = __HAL_TIM_GET_COUNTER(&g_timx_cnt_chy_handle);
if(oldcnt != curcnt)
{
oldcnt = curcnt;
printf("CNT:%d\r\n", oldcnt);
}
t++;
if(t > 20)
{
t = 0;
LED0_TOGGLE();
}
delay_ms(10);
}
}
六、高级定时器输出指定个数PWM实验
1. 高级定时器框图
-
重复计数器
在基本定时器和通用定时器中,定时器发生上溢或者下溢时,会直接生成更新事件。但有重复计数器的定时器并不完全这样,在定时器每次发生上溢或下溢时,重复计数器的值会减一,当重复计数器的值为0时,再发生一次上溢或者下溢才会生成定时器更新事件。如果我们设置重复计数器寄存器的
RCR
的值为N
,那么更新事件将在定时器发生N+1
次上溢或下溢时发生。 -
输出比较
与通用定时器相比,高级定时器多了带死区控制的互补输出功能。通道1、2、3都有互补输出通道,
DTG
是死区发生器,死区时间由DTG[7:0]
位来配置。如果不使用互补通道和死区时间控制,那么高级定时器TIM1和TIM8和通用定时器的输出比较部分使用方法基本一样,只是要注意MOE
位得置1定时器才能输出。 -
断路功能
断路功能也称刹车功能,一般用于控制电机的刹车。F1系列有一个断路通道,断路源可以是刹车输入引脚(
TIMx_BKIN
),也可以是一个时钟失败事件。时钟失败事件由复位时钟控制器中的时钟安全系统产生。系统复位后,断路功能默认被禁止,MOE
位为低。
2. 重复计数器特性
-
计数器每次上溢或下溢都能使重复计数器减1,减到0时,再发生一次溢出就会产生更新事件。
-
如果设置RCR为N,更新事件将在N+1次溢出时发生。
3. 相关寄存器
控制寄存器1(TIMx_CR1
)
捕获/比较模式寄存器1/2(TIMx_CCMR1/2
)
捕获/比较使能寄存器(TIMx_CCER
)
事件产生寄存器(TIMx_EGR
)
重复计数器寄存器(TIMx_RCR
)
捕获/比较寄存器1/2/3/4(TIMx_CCR1/2/3/4
)
断路和死区寄存器(TIMx_BDTR
)
4. 高级定时器输出指定个数PWM实验配置步骤
-
配置定时器基础工作参数
HAL_TIM_PWM_Init()
-
定时器PWM输出MSP初始化
HAL_TIM_PWM_MspInit()
-
配置PWM模式/比较值等
HAL_TIM_PWM_ConfigChannel()
-
设置优先级,使能中断
HAL_NVIC_SetPriority() HAL_NVIC_EnableIRQ()
-
使能定时器更新中断
__HAL_TIM_ENABLE_IT()
-
使能输出、主输出、计数器
HAL_TIM_PWM_Start()
-
编写中断服务函数
TIMx_IRQHandler() HAL_TIM_IRQHandler()
-
编写更新中断回调函数
HAL_TIM_PeriodElapsedCallback()
具体函数功能
函数 | 主要寄存器 | 主要功能 |
---|---|---|
HAL_TIM_PWM_Init() | CR1、ARR、PSC | 初始化定时器基础参数 |
HAL_TIM_PWM_MspInit() | 无 | 存放NVIC、CLOCK、GPIO初始化代码 |
HAL_TIM_PWM_ConfigChannel() | CCMRx、CCRx、CCER | 配置PWM模式、比较值、输出极性等 |
__HAL_TIM_ENABLE_IT() | CCER | 使能更新中断等 |
HAL_TIM_PWM_Start() | CCER、CR1 | 使能输出、主输出、启动计数器 |
HAL_TIM_IRQHandler() | SR | 定时器中断处理公用函数,处理各种中断 |
HAL_TIM_PeriodElapsedCallback() | 无 | 定时器更新中断回调函数,由用户重定义 |
HAL_TIM_GenerateEvent() | EGR | 通过软件产生事件 |
__HAL_TIM_ENABLE() | CR1 | 启动计数器 |
5. 代码实现
功能:
通过TIM8CH1(由PC6复用)输出PWM,然后为了指示PWM的输出情况,我们用杜邦线将PC6和PE5引脚的排针连接起来,从而实现PWM输出控制LED1(硬件已连接在PPE5引脚上)的亮灭。注意的点是:PE5要设置成浮空输入,避免引脚冲突,我们在main函数中设置好了,请看源码。上电默认输出5个PWM波,连接好杜邦线后可以看见LED1亮灭五次。之后按一下按键KEYO,就会输出5个PWM波控制LED1亮灭五次。LED0闪烁提示系统正在运行。
高级定时器输出指定个数PWM初始化
void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_npwm_chy = {0};
g_timx_npwm_chy_handle.Instance = TIM8;
g_timx_npwm_chy_handle.Init.Prescaler = psc;
g_timx_npwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数模式
g_timx_npwm_chy_handle.Init.Period = arr;
g_timx_npwm_chy_handle.Init.RepetitionCounter = 0; //重复计数器初始值
g_timx_npwm_chy_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /*使能TIMx_ARR进行缓冲 */
HAL_TIM_PWM_Init(&g_timx_npwm_chy_handle);
timx_oc_npwm_chy.OCMode = TIM_OCMODE_PWM1; //使用PWM1工作模式
timx_oc_npwm_chy.Pulse = arr / 2; //占空比设置为50%
timx_oc_npwm_chy.OCPolarity = TIM_OCPOLARITY_HIGH; //高电平有效
HAL_TIM_PWM_ConfigChannel(&g_timx_npwm_chy_handle, &timx_oc_npwm_chy, TIM_CHANNEL_1); //进行初始化
__HAL_TIM_ENABLE_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE); //允许更新中断
HAL_TIM_PWM_Start(&g_timx_npwm_chy_handle, TIM_CHANNEL_1); //开启对应的PWM通道
}
高级定时器输出指定个数PWM MSP回调函数
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM8)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_TIM8_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_6;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
HAL_NVIC_SetPriority(TIM8_UP_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM8_UP_IRQn);
}
}
高级定时器设置PWM个数函数
void atim_timx_npwm_chy_set(uint8_t npwm)
{
if(npwm == 0) return;
g_npwm_remain = npwm; //保存脉冲个数
HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE); //产生一次软件更新事件,在中断里面处理脉冲输出
__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); //使能定时器TIM8
}
定时器8中断服务函数
void TIM8_UP_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_npwm_chy_handle);
}
定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM8)
{
if(g_npwm_remain)
{
TIM8->RCR = g_npwm_remain - 1;
HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE);
__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle);
g_npwm_remain = 0;
}
else
{
TIM8->CR1 &= ~(1 << 0);
}
}
}
主函数
void LED_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = GPIO_PIN_5;
gpio_init_struct.Mode = GPIO_MODE_INPUT; //设置为输入,防止冲突(PC6)
gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOE, &gpio_init_struct);
}
int main(void)
{
uint8_t t = 0;
uint8_t key = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
// usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init();
LED_init(); //需要重新配置PE5为输入模式防止冲突
atim_timx_npwm_chy_init(5000 -1, 7200 - 1);
atim_timx_npwm_chy_set(5);
while(1)
{
key = key_scan(0);
if(key == 4)
{
atim_timx_npwm_chy_set(6);
}
t++;
if(t > 50)
{
t = 0;
LED0_TOGGLE();
}
delay_ms(10);
}
}
七、高级定时器输出比较模式实验
1. 输出比较模式功能
输出比较模式下翻转功能作用是:当计数器的值等于捕获/比较寄存器影子寄存器的值时, OC1REF
发生翻转,进而控制通道输出(OCx
)翻转。通过翻转功能实现输出PWM的具体原理如下:PWM频率由自动重载寄存器(TIMx_ARR
)的值决定,在这个过程中,只要自动重载寄存器的值不变,那么PWM占空比就固定为50%。可以通过捕获/比较寄存器(TIMx_CCRx
)的值改变PWM的相位。
2. 相关寄存器
控制寄存器1(TIMx_CR1
)
捕获/比较模式寄存器1/2(TIMx_CCMR1/2
)
捕获/比较使能寄存器(TIMx_CCER
)
捕获/比较寄存器1/2/3/4(TIMx_CCR1/2/3/4
)
TIM1/TIM8断路和死区寄存器(TIMx_BDTR
)
3. 高级定时器输出比较模式实验配置步骤
-
配置定时器基础工作参数
HAL_TIM_OC_Init()
-
定时器输出比较MSP初始化
HAL_TIM_OC_MspInit()
-
配置输出比较模式等
HAL_TIM_OC_ConfigChannel()
-
使能通道预装载
__HAL_TIM_ENABLE_OCxPRELOAD()
-
使能输出、主输出、计数器
HAL_TIM_OC_Start()
-
修改捕获/比较寄存器的值
__HAL_TIM_SET_COMPARE()
具体函数功能
函数 | 主要寄存器 | 主要功能 |
---|---|---|
HAL_TIM_OC_Init() | CR1、ARR、PSC | 初始化定时器基础参数 |
HAL_TIM_OC_MspInit | 无 | 存放NVIC、CLOCK、GPIO初始化代码 |
HAL_TIM_OC_ConfigChannel() | CCMRx、CCRx、CCER | 设置输出比较模式、比较值、输出极性等 |
__HAL_TIM_ENABLE_OCxPRELOAD() | CCMRx | 使能通道预装载 |
HAL_TIM_OC_Start() | CR1、CCER、BDTR | 使能输出比较、主输出、启动计数器 |
__HAL_TIM_SET_COMPARE() | CCRx | 修改捕获/比较寄存器的值 |
4. 代码实现
功能:
使用输出比较模式的翻转功能,通过定时器8的4路通道输出占空比固定为50%、相位分别是25%、50%、75%和100%的PWM。
高级定时器输出比较初始化
void atim_timx_comp_pwm_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_comp_pwm = {0};
g_timx_comp_pwm_handle.Instance = TIM8;
g_timx_comp_pwm_handle.Init.Prescaler = psc;
g_timx_comp_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数模式
g_timx_comp_pwm_handle.Init.Period = arr;
g_timx_comp_pwm_handle.Init.RepetitionCounter = 0; //重复计数器初始值
HAL_TIM_PWM_Init(&g_timx_comp_pwm_handle);
timx_oc_comp_pwm.OCMode = TIM_OCMODE_TOGGLE; //模式选择翻转
timx_oc_comp_pwm.OCPolarity = TIM_OCPOLARITY_HIGH; //极性选择高电平
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_1);
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_2);
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_3);
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_4);
//使能通道预装载
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
//使能输出、主输出、计数器
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
}
定时器输出比较MSP函数
void HAL_TIM_OC_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM8)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_TIM8_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
gpio_init_struct.Mode = GPIO_MODE_AF_PP; //推挽复用
gpio_init_struct.Pin = GPIO_PIN_6;
gpio_init_struct.Pull = GPIO_NOPULL; //没有上下拉
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_7;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_8;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_9;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
}
}
主函数
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init();
atim_timx_comp_pwm_init(1000 - 1, 72 - 1);
__HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_1, 250 - 1);
__HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_2, 500 - 1);
__HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_3, 750 - 1);
__HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_3, 1000 - 1);
while(1)
{
t++;
if(t > 50)
{
t = 0;
LED0_TOGGLE();
}
delay_ms(10);
}
}
程序运行结果
八、高级定时器互补输出带死区控制实验
1. 互补死区的理解

CH1输出黄色的PWM,互补通道CH1N输出绿色的PWM,两个信号恰好是相反的,CH1的PWM为高电平期 间,CH1N的PWM则是低电平,反之亦然,这就是互补输出。
带死区控制的互补输出
CH1输出的PWM和CH1N输出的PWM在高低电平转换期间,插入了一段时间才实现互补输出。这段时间称为死区时间。上图箭头所指的区域就是死区时间,两段时间相同。
死区互补控制H桥
死区时间计算
2. 相关寄存器
控制寄存器1(TIMx_CR1
)
捕获/比较模式寄存器1/2(TIMx_CCMR1/2
)
捕获/比较使能寄存器(TIMx_CCER
)
捕获/比较寄存器1/2/3/4(TIMx_CCR1/2/3/4
)
TIM1/TIM8断路和死区寄存器(TIMx_BDTR
)
对应的结构框图
刹车断路功能
发生刹车后:
3. 高级定时器互补输出带死区控制实验配置步骤
-
配置定时器基础工作参数
HAL_TIM_PWM_Init()
-
定时器PWM输出MSP初始化
HAL_TIM_PWM_MspInit()
-
配置PWM模式/比较值等
HAL_TIM_PWM_ConfigChannel()
-
配置刹车功能、死区时间等
HAL_TIMEx_ConfigBreakDeadTime()
-
使能输出、主输出、计数器
HAL_TIM_PWM_Start()
-
使能互补输出、主输出、计数器
HAL_TIMEx_PWMN_Start()
相关函数功能
函数 | 主要寄存器 | 主要功能 |
---|---|---|
HAL_TIM_PWM_Init() | CR1、ARR、PSC | 初始化定时器基础参数 |
HAL_TIM_PWM_MspInit() | 无 | 存放NVIC、CLOCK、GPIO初始化代码 |
HAL_TIM_PWM_ConfigChannel() | CCMRx、CCRx、CCER | 配置PWM模式、比较值、输出极性等 |
HAL_TIMEx_ConfigBreakDeadTime() | BDTR | 配置刹车功能、死区时间等 |
HAL_TIM_PWM_Start() | CCER、CR1 | 使能输出、主输出、启动计数器 |
HAL_TIMEx_PWMN_Start() | CCER、CR1 | 使能互补输出、主输出、启动计数器 |
4. 代码实现
功能:
- 利用TIM1_CH1(PE9)输出70%占空比的PWM波,它的互补输出通道(PE8)则是输出30%占空比的PWM波。
- 刹车功能,当给刹车输入引脚(PE15)输入高电平时,进行刹车,即PE8和PE9停止输出PWM波。
- LED0闪烁指示程序运行。
高级定时器互补输出初始化函数
void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_cplm_pwm = {0};
g_timx_cplm_pwm_handle.Instance = TIM1; //定时器选择
g_timx_cplm_pwm_handle.Init.Prescaler = psc; //定时器分频
g_timx_cplm_pwm_handle.Init.Period = arr;
g_timx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; //定时器计数模式
g_timx_comp_pwm_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4; //CKD[1:0] = 10
HAL_TIM_PWM_Init(&g_timx_cplm_pwm_handle); //初始化PWM
timx_oc_cplm_pwm.OCMode = TIM_OCMODE_PWM1; //模式选择PWM模式1
timx_oc_cplm_pwm.OCPolarity = TIM_OCPOLARITY_HIGH; //OC1高电平有效
timx_oc_cplm_pwm.OCNPolarity = TIM_OCPOLARITY_HIGH; //OC1N高电平有效
timx_oc_cplm_pwm.OCIdleState = TIM_OCIDLESTATE_RESET; //当MOE = 0, OC1 = 1;
timx_oc_cplm_pwm.OCNIdleState = TIM_OCNIDLESTATE_RESET;//当MOE = 0, OC1x = 1;
HAL_TIM_PWM_ConfigChannel(&g_timx_cplm_pwm_handle, &timx_oc_cplm_pwm, TIM_CHANNEL_1);
//设置死区参数
g_sbreak_dead_time_config.OffStateRunMode = TIM_OSSR_DISABLE; //运行模式下的关闭状态选择
g_sbreak_dead_time_config.OffStateIDLEMode = TIM_OSSI_DISABLE; //空闲模式下的关闭状态选择
g_sbreak_dead_time_config.LockLevel = TIM_LOCKLEVEL_OFF; //寄存器锁定配置
g_sbreak_dead_time_config.BreakState = TIM_BREAK_ENABLE; //断路输入使能控制
g_sbreak_dead_time_config.BreakPolarity = TIM_BREAKPOLARITY_HIGH; //断路输入极性
g_sbreak_dead_time_config.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE; //自动恢复输出使能控制
HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, &g_sbreak_dead_time_config);
HAL_TIM_PWM_Start(&g_timx_cplm_pwm_handle, TIM_CHANNEL_1); //启动定时器的正常输出
HAL_TIMEx_PWMN_Start(&g_timx_cplm_pwm_handle, TIM_CHANNEL_1); //启动定时器的互补输出
}
定时器设置输出比较值死区时间
void atim_timx_cplm_pwm_set(uint16_t ccr, uint8_t dtg)
{
//设置比较寄存器的值
__HAL_TIM_SET_COMPARE(&g_timx_cplm_pwm_handle, TIM_CHANNEL_1, ccr);
//单独进行死区时间的设置
g_sbreak_dead_time_config.DeadTime = dtg;
HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, &g_sbreak_dead_time_config);
}
主函数
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init();
//设置1khz的周期
atim_timx_cplm_pwm_init(1000 - 1, 72 - 1);
//设置TIM捕获比较寄存器值,从而控制占空比为70%
atim_timx_cplm_pwm_set(700 - 1, 100);
while(1)
{
t++;
if(t > 50)
{
t = 0;
LED0_TOGGLE();
}
delay_ms(10);
}
}
程序运行流程
程序运行结果
九、高级定时器PWM输入模式实验
1. PWM输入工作原理
- 确定定时器的时钟源:本代码使用内部时钟(CK_INT),时钟频率为72MHz,计数频率确定了测量精度。
- 确定PWM输入的通道:PWM输入模式下测量PWM,PWM信号输入只能从通道1或通道2输入。
- 确定IC1和IC2的捕获边沿:以通道 1(CH1)输入 PWM 为例,一般设置 IC1 捕获边沿为上升沿捕获,IC2 捕获边沿为下降沿捕获。
- 选择触发输入信号:选择TI1FP1或TI2FP2。
- 从模式选择:复位模式,在出现所选触发输入(TRGI)上升沿时,重新初始化计数器并生成一个寄存器更新事件。
- 读取一个PWM周期内计数器的计数个数,以及高电平期间计数个数,再结合计数器的计数周期,最终通过计算得到输入的PWM周期和占空比等参数。
2. 输入模式时序图
假设计数器的计数频率是72MHz,那我们就可以计算出PWM的周期、频率和占空比等参数。由计数器的计数频率为72Hz,可以得到计数器计一个数的时间是13.8ns(即测量的精度是13.8ns)。知道了测量精度,再来计算PWM的周期,PWM周期=(4+1)*(1/72000000)=69.4ns,那么PWM的频率就是14.4MHz。占空比= (2+1)/(4+1)=3/5(即占空比为60%)。
3. 相关寄存器
从模式控制寄存器(TIMx_SMCR
)
捕获/比较模式寄存器1/2(TIMx_CCMR1/2
)
捕获/比较使能寄存器(TIMx_CCER
)
捕获/比较寄存器1/2/3/4(TIMx_CCR1/2/3/4
)
DMA中断使能寄存器(TIMx_DIER
)
4. 高级定时器PWM输入模式实验配置步骤
-
配置定时器基础工作参数
HAL_TIM_IC_Init()
-
定时器捕获输入MSP初始化
HAL_TIM_IC_MspInit()
-
配置IC1/2映射、捕获边沿等
HAL_TIM_IC_ConfigChannel()
-
配置从模式,触发源等
HAL_TIM_SlaveConfigSynchro()
-
设置优先级,使能中断
HAL_NVIC_SetPriority() HAL_NVIC_EnableIRQ()
-
使能捕获、捕获中断及计数器
HAL_TIM_IC_Start_IT() HAL_TIM_IC_Start()
-
编写中断服务函数
TIMx_IRQHandler()
-
编写输入捕获回调函数
HAL_TIM_IC_CaptureCallback()
函数具体功能
函数 | 主要寄存器 | 主要功能 |
---|---|---|
HAL_TIM_IC_Init() | CR1、ARR、PSC | 初始化定时器基础参数 |
HAL_TIM_IC_MspInit() | 无 | 存放NVIC、CLOCK、GPIO初始化代码 |
HAL_TIM_IC_ConfigChannel() | CCMRx、CCER | 配置通道映射、捕获边沿、分频、滤波等 |
HAL_TIM_SlaveConfigSynchro() | SMCR、CCER | 配置从模式、触发源、触发边沿等 |
HAL_TIM_IC_Start_IT() | CCER、DIER、CR1 | 使能输入捕获、捕获中断并启动计数器 |
HAL_TIM_IRQHandler() | SR | 定时器中断处理公用函数,处理各种中断 |
HAL_TIM_IC_CaptureCallback() | 无 | 定时器输入捕获回调函数,由用户重定义 |
4. 代码实现
功能:
首先通过TM3_CH2(PB5)输出PWM波。然后把PB5输出的PWM波用杜邦线接入PC6(定时器8通道1),最后通过串口打印PWM波的脉宽和频率等信息。通过LED1闪烁来提示程序正在运行。
通用定时器PWM输出初始化函数
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_pwm_chy;
g_timx_pwm_chy_handle.Instance = TIM3;
g_timx_pwm_chy_handle.Init.Prescaler = psc;
g_timx_pwm_chy_handle.Init.Period = arr;
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;
timx_oc_pwm_chy.Pulse = arr / 2;
timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW;
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_2);
}
定时器输出PWM MSP初始化函数
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_TIM3_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_5;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 推挽复用 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GPIOB, &gpio_init_struct);
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_REMAP_TIM3_PARTIAL();
}
}
PWM输入模式初始化函数
void atim_timx_pwmin_chy_init(void)
{
TIM_SlaveConfigTypeDef slave_config = {0};
TIM_IC_InitTypeDef tim_ic_pwmin_chy = {0};
g_timx_pwmin_chy_handle.Instance = TIM8; /* 定时器8 */
g_timx_pwmin_chy_handle.Init.Prescaler = 0; /* 定时器预分频系数 */
g_timx_pwmin_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_pwmin_chy_handle.Init.Period = 65535; /* 自动重装载值 */
HAL_TIM_IC_Init(&g_timx_pwmin_chy_handle);
/* 从模式配置,IT1触发更新 */
slave_config.SlaveMode = TIM_SLAVEMODE_RESET; /* 从模式:复位模式 */
slave_config.InputTrigger = TIM_TS_TI1FP1; /* 定时器输入触发源:TI1FP1 */
slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING; /* 上升沿检测 */
slave_config.TriggerFilter = 0; /* 不滤波 */
HAL_TIM_SlaveConfigSynchro(&g_timx_pwmin_chy_handle, &slave_config);
/* IC1捕获:上升沿触发TI1FP1 */
tim_ic_pwmin_chy.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿检测 */
tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_DIRECTTI; /* 选择输入端IC1映射到TI1 */
tim_ic_pwmin_chy.ICPrescaler = TIM_ICPSC_DIV1; /* 不分频 */
tim_ic_pwmin_chy.ICFilter = 0; /* 不滤波 */
HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_1);
/* IC2捕获:下降沿触发TI1FP2 */
tim_ic_pwmin_chy.ICPolarity = TIM_ICPOLARITY_FALLING; /* 下降沿检测 */
tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_INDIRECTTI; /* 选择输入端IC2映射到TI1 */
HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_2);
HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1);
HAL_TIM_IC_Start(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2);
}
定时器输入捕获MSP初始化函数
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM8)
{
GPIO_InitTypeDef gpio_init_struct = {0};
__HAL_RCC_TIM8_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_6;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLDOWN;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOC, &gpio_init_struct);
/* TIM1/TIM8有独立的输入捕获中断服务函数 */
HAL_NVIC_SetPriority(TIM8_CC_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM8_CC_IRQn);
}
}
定时器8输入捕获中断服务函数
void TIM8_CC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_pwmin_chy_handle); /* 定时器共用处理函数 */
}
PWM输入模式重新启动捕获函数
void atim_timx_pwmin_chy_restart(void)
{
sys_intx_disable(); /* 关闭中断 */
g_timxchy_pwmin_sta = 0; /* 清零状态,重新开始检测 */
g_timxchy_pwmin_hval=0;
g_timxchy_pwmin_cval=0;
sys_intx_enable(); /* 打开中断 */
}
定时器输入捕获中断处理回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM8)
{
if(g_timxchy_pwmin_sta == 0)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
g_timxchy_pwmin_hval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2) + 1 + 1;
g_timxchy_pwmin_cval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1) + 1 + 1;
g_timxchy_pwmin_sta = 1;
}
}
}
}
主函数
int main(void)
{
uint8_t t = 0;
double ht, ct, f, tpsc;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_pwm_chy_init(10 - 1, 72 - 1);
TIM3->CCR2 = 3;
atim_timx_pwmin_chy_init();
while (1)
{
delay_ms(10);
t++;
if (t >= 20) /* 每200ms输出一次结果,并闪烁LED0,提示程序运行 */
{
if (g_timxchy_pwmin_sta) /* 捕获了一次数据 */
{
printf("\r\n"); /* 输出空,另起一行 */
printf("PWM PSC :%d\r\n", g_timxchy_pwmin_psc); /* 打印分频系数 */
printf("PWM Hight:%d\r\n", g_timxchy_pwmin_hval); /* 打印高电平脉宽 */
printf("PWM Cycle:%d\r\n", g_timxchy_pwmin_cval); /* 打印周期 */
tpsc = ((double)g_timxchy_pwmin_psc + 1) / 72; /* 得到PWM采样时钟周期时间 */
ht = g_timxchy_pwmin_hval * tpsc; /* 计算高电平时间 */
ct = g_timxchy_pwmin_cval * tpsc; /* 计算周期长度 */
f = (1 / ct) * 1000000; /* 计算频率 */
printf("PWM Hight time:%.3fus\r\n", ht); /* 打印高电平脉宽长度 */
printf("PWM Cycle time:%.3fus\r\n", ct); /* 打印周期时间长度 */
printf("PWM Frequency :%.3fHz\r\n", f); /* 打印频率 */
atim_timx_pwmin_chy_restart(); /* 重启PWM输入检测 */
}
LED1_TOGGLE(); /* LED1(GREEN)闪烁 */
t = 0;
}
}
}
声明:资料来源(战舰STM32F103ZET6开发板资源包)
- Cortex-M3权威指南(中文).pdf
- STM32F10xxx参考手册_V10(中文版).pdf
- STM32F103 战舰开发指南V1.3.pdf
- STM32F103ZET6(中文版).pdf
- 战舰V4 硬件参考手册_V1.0.pdf
作者:turbosqi