在本章中,我们将 使用 TIM3的定时器中断来控制 DS1的翻转,在主函数用 DS0的翻转
来提示程序正在运行。本章,我们选择难度适中的通用定时器来介绍,本章将 分为如下几个部
分:
12.1 STM32通用定时器简介
12.2 硬件设计
12.3 软件设计
12.4下载 验证
12.1 STM32通用定时器简介
STM32的通用定时器是一个通过可编程预分频器( PSC)驱动的 16位自动装载计数器( CNT
构成。
STM32的通用定时器可以被用于:测量输入信号的脉冲长度 (输入捕获 )或者产生输出波形 (输出比较和 PWM)等。
使用定时器预分频器和 RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。 STM32的每个通用定时器是完全独立的,没有互相共享的任何资源。
STM3的通用 TIMx (TIM2、 TIM3、 TIM4和 TIM5)定时器功能包括:
1)16位向上、向下、向上 /向下自动装载计数器( TIMx_CNT)。
2)16位可编程 (可以实时修改 )预分频器 (TIMx_PSC),计数器时钟频率的分频系数为 1
65535之间的任意数值。
3. 4个独立通道( TIMx_CH1~4),这些通道可以用来作为
A.输入捕获
B.输出比较
C PWM生成 (边缘或中间对齐模式 )
D.单脉冲模式输出
4)可使用外部信号 TIMx_ETR)控制定时器和定时器互连(可以用 1个定时器控制另外
一个定时器)的同步电路。
5)如下事件发生时产生中断 /DMA
A.更新:计数器向上溢出 /向下溢出,计数器初始化 (通过软件或者内部 /外部触发 )
B.触发事件 (计数器启动、停止、初始化或者由内部 /外部触发计数 )
C.输入捕获
D.输出比较
E.支持针对定位的增量 (正交 )编码器和霍尔传感器电路
F.触发输入作为外部时钟或者按周期的电流管理
输入捕获(Input Capture)
输入捕获模式允许定时器在外部信号的特定边沿(如上升沿或下降沿)发生时,捕获并记录当前的计数值。这个功能非常适合于测量周期性信号的参数,如 PWM 信号的频率和占空比。
输出比较(Output Compare)和 PWM
输出比较模式用于控制定时器的输出信号(OCx)根据计数器的值来改变状态。具体的配置是通过寄存器 CCMRx(捕获/比较模式寄存器)中的 OCxM2:0位来实现的。
PWM(脉冲宽度调制)
PWM(脉冲宽度调制)是一种通过控制脉冲信号的宽度来模拟模拟信号的技术。PWM 信号由一系列脉冲组成,每个脉冲的宽度可以变化,从而模拟不同的电压或电流水平。PWM 信号的两个主要参数是周期(Period)和占空比(Duty Cycle)。
边缘对齐模式(Edge-Aligned Mode)
在边缘对齐模式下,计数器可以配置为向上计数或向下计数。PWM 信号的生成方式如下:
-
向上计数:计数器从 0 递增到自动重载值(ARR),然后重新从 0 开始计数。
-
向下计数:计数器从自动重载值(ARR)递减到 0,然后重新从自动重载值开始计数。
在这种模式下,PWM 信号的占空比由比较寄存器(CCR)的值决定。当计数器的值与比较寄存器的值匹配时,输出引脚的电平会发生变化。
中间对齐模式(Center-Aligned Mode)
在中间对齐模式下,计数器既向上计数又向下计数。具体行为如下:
-
向上/向下计数:计数器从 0 递增到自动重载值(ARR),然后从自动重载值递减到 0,再重新从 0 开始计数。
-
PWM 生成:在这种模式下,PWM 信号的占空比由比较寄存器(CCR)的值决定。当计数器的值与比较寄存器的值匹配时,输出引脚的电平会发生变化。
中间对齐模式的特点是所有通道的脉宽中点都对齐在计数器计数至计数器初始值(CNTIN)的时刻
单脉冲模式输出
单脉冲模式输出是指定时器在接收到触发信号后,生成一个脉宽可控的单次脉冲。这种模式常用于需要精确控制脉冲宽度和脉冲间隔的场合
1.首先是控制寄存器(TIMx_CR1),
最低位(位 0),也就是计数器使能位,该位必须置 1,才能让定时器开始计数。
2. DMA/中断使能寄存器( TIMx_DIER)。
最低位是更新中断允许位
3.预分频寄存器(TIMx_PSC)
用于对时钟进行分频,并提供给计数器,作为计数器的时钟。
这里,定时器的时钟来源有 4个:
1 内部时钟( CK_INT
2 外部时钟模式 1:外部输入脚 TIx
3 外部时钟模式 2:外部触发输入 ETR
4 内部触发输入( ITRx):使用 A定时器作为 B定时器的预分频器( A为 B提供时钟)。4.TIMx_CNT寄存器,该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值
5.自动重装载寄存器( TIMx_ARR),与控制寄存器(TIMx_CR1)的apre位有关
6.状态寄存器(TIMx_SR)。该寄存器用来标记当前与定时器相关的各种事件 /中断是否发生。
这一章 ,我们将使用定时器产生中断,然后在中断服务函数里面翻转 DS1上的电 平 ,来指
示定时器中断的产生。接下来我们以通用定时器 TIM3为实例,来说明要经过哪些步骤,才能
达到这个要求,并产生中断。 这里我们就对每个步骤通过库函数的实现方式来描述。首先要提
到的是,定时器相关的库函数主要集中在 HAL库 文件 stm32f1xx_hal_tim.h和
stm32f1xx_hal_tim.c文件中。
定时器配置步骤如下:
1.TIM3时钟使能
__HAL_RCC_TIM3_CLK_ENABLE(); //使能 TIM3时钟
2.初始化定时器参数,设置自动重装值,分频系数,计数方式等
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
typedef struct
{
TIM_TypeDef *Instance;
TIM_Base_InitTypeDef Init;
HAL_TIM_ActiveChannel Channel;
DMA_HandleTypeDef *hdma[7];
HAL_LockTypeDef Lock;
__IO HAL_TIM_StateTypeDef State;
}TIM_HandleTypeDef;
typedef struct
{
uint32_t Prescaler; //预分频系数
uint32_t CounterMode; //计数方式
uint32_t Period; //自动装载值ARR uint32_t ClockDivision;
//时钟分频因子,也就是定时器时钟频率 CK_INT与数字滤波器所使用的采样时钟之间的分频比 。
uint32_t RepetitionCounter;
} TIM_Base_InitTypeDef;
定时器初始化范例如下:
TIM_HandleTypeDef TIM3_Handler; //定时器句柄
TIM3_Handler.Instance=TIM3; //通用定时器 3 TIM3_Handler.Init.Prescaler= 7199; //分频系数
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数器
TIM3_Handler.Init.Period=4999; //自动装载值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
HAL_TIM_Base_Init(&TIM3_Handler);
3.使能定时器更新中断,使能定时器
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);//使能句 柄指定的定时器更新中断
__HAL_TIM_DISABLE_IT (htim, TIM_IT_UPDATE);//关闭句柄指定的定时器更新中断
__HAL_TIM_ENABLE(htim);//使能句柄 htim指定的定时器
__HAL_TIM_DISABLE(htim);//关闭句柄 htim指定的定时器
4.TIM3中断优先级设置
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);
5.编写中断服务函数
首先,中断服务函数是不变的,定时器3的中断服务函数为:
TIM3_IRQHandler();
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
比如定时器更新中断回调函数为:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
跟串口中断回调函数一样,我们只需要重写该函数即可。对于其他类型中断,
HAL库同样提供了几个不同的回调函数,这里我们列出常用的几个回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//更新中断
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);//输出比较
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);//输入捕获
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);//触发中断
12.2 硬件设计
本实验用到的硬件资源有:
1) 指示灯 DS0和 DS1
2) 定时器 TIM3
本章 将通过 TIM3的中断来控制 DS1的亮灭, DS0和 DS1的电路在前面已经有介绍了。而
TIM3属于 STM32的内部资源,只需要软件设置即可正常工作。
12.3软件设计
timer.c文件 代码 如下
#include "timer.h" #include "led.h" TIM_HandleTypeDef TIM3_Handler; //定时器句柄
//通用定时器 3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法 :Tout=((arr+1)*(psc+1))/Ft us. //Ft=定时器工作频率 ,单位 :Mhz //这里使用的是定时器 3! void TIM3_Init(u16 arr,u16 psc) { TIM3_Handler.Instance=TIM3; //通用定时器 3 TIM3_Handler.Init.Prescaler=psc; //分频系数
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数器
TIM3_Handler.Init.Period=arr; //自动装载值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
HAL_TIM_Base_Init(&TIM3_Handler); HAL_TIM_Base_Start_IT(&TIM3_Handler); //使能定时器 3和定时器 3更新中断: TIM_IT_UPDATE } //定时器底册驱动,开 启时钟,设置中断优先级
//此函数会被 HAL_TIM_Base_Init()函数调用
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) { if(htim-->Instance==TIM3) { __HAL_RCC_TIM3_CLK_ENABLE(); //使能 TIM3时钟
HAL_NVIC_SetPriority(TIM3_IRQn,1,3); //设置中断优先级,抢占优先级 1,子优先级 3 HAL_NVIC_EnableIRQ(TIM3_IRQn); //开启 ITM3中断
} } //定时器 3中断服务函数
void TIM3_IRQHandler(void) { HAL_TIM_IRQHandler(&TIM3_Handler); } //回调函数,定时器中断服务函数调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim==(&TIM3_Handler)) { LED1=!LED1; //LED1反转
} }
该文件下包含一个中断服务函数和一个定时器
3中断 初始化函数,中断服务函数比较简单,
在每次中断后,判断 TIM3的中断类型,如果中断类型正确,则执行 LED1 DS1)的取反。
TIM3_Int_Init函数就是执行我们上面介绍的那 5个步骤,使得 TIM3开始工作,并开启中
断。该函数的 2个参数用来设置 TIM3的溢出时间。因为我们在 Stm32_Clock_Init函数里面已
经初始化 APB1的时钟为 2分频,所以 APB1的时 钟为 36M 而从 STM32F1的内部时钟树图
(图 5.2.2.1)得知:当 APB1的时钟分频数为 1的时候, TIM2~7的时钟为 APB1的时钟,而
如果 APB1的时钟分频数不为 1,那么 TIM2~7的时钟频率将为 APB1时钟的两倍。因此, TIM3的时钟为 72M,再根据我们设计的 arr和 psc的值,就可以计算中断时间了。计算公式如下:
Tout= ((arr+1)*(psc+1))/Tclk
其中:
Tclk TIM3的 输入时钟频率 (单位为 Mhz)。
Tout TIM3溢出时间(单位为 us)。
我们将
timer.c文件保存,然后加入到 HARDWARE组下。接下来,在 timer.h文件里,我
们输入如下代码:
#ifndef __TIMER_H #define __TIMER_H #include "sys.h" extern TIM_HandleTypeDef TIM3_Handler; //定时器句柄
void TIM3_Int_Init(u16 arr,u16 psc); #endif
此部分代码十分简单,这里不做介绍。
最后,我们
在主程序里面输入 如下 代码
int main(void) { HAL_Init(); //初始化 HAL库
Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟 ,72M delay_init(72); //初始化延时函数
uart_init(115200); //初始化串口
LED_Init(); //初始化 LED KEY_Init(); //初始化按键
TIM3_Init(5000 1,7200 1); //定时器 3初始化,定时器时钟为 72M
//分频系数为 7200 1,所以定时器 3的频率为 72M/7200=10K,自动重装载为 5000 1
//那么定时器周期就是 500ms
while(1)
{
LED0=!LED0; delay_ms(200); } }
这里的代码和之前大同小异,此段代码对
TIM3进行初始化之后,进入死循环等待 TIM3溢出中断,当 TIM3_CNT的值等于 TIM3_ARR的值的时候,就会产生 TIM3的更新中断,然
后在中断里面取反 LED1 TIM3_CNT再从 0 开始计数。
这里定时器定时时长
500ms是这样计算出来的,定时器的时钟为 72Mhz,分频系数为 7200,所以分频后的计数频率为 72Mhz/7200=10KHz,然后计数到 5000,所以时长为 5000/10000=0.5s
也就是 500ms。
12.4 下载验证
在完成软件设计之后,我们将编译好的文件下载到开发板上,观看其运行结果
是否与我们编写的一致。如果没有错误,我们将看 DS0不停闪烁(每 400ms闪烁一次),而 DS1也是不停的闪烁,但是闪烁时间较 DS0慢( 1s一次)。