前排提示一下:这些代码都是本人跟着野火的教程视频写的(与野火的例程会有出入),编程思路则由本人编写。
之前我们已经详细过了一遍高级定时器的功能框图,现在来简要说一下其他定时器。STM32 有三种定时器,定时器分类如下:
其中,高级定时器(定时、输出比较、输入捕获、互补输出) 的功能最齐全,其次是通用定时器(定时、输出比较、输入捕获),最后是基本定时器 (定时,只能向上计数)。
一、基本定时器——LED循环亮灭
【实现功能】通过基本定时器(TIM6、TIM7),使 LED 每隔 1000ms 反转一次工作模式,即实现循环亮灭的效果。
【基本思路】要产生 1000ms 的时间间隔,首先要想办法产生 1ms 的时间。时钟源的频率为 72MHz,想要得到 1MHz 的定时器频率,就必须使分频因子 PSC = 72 - 1,这样定时器频率就为 72 / (PSC+1) = 1MHz,定时器周期为 1/10^6 秒,那么只要重复1000次这样的周期就是 1ms,所以 Period(ARR) = 1000 - 1。因此,1ms 时间后将会产生一次更新事件。
通过更新事件产生一次中断,进而运行中断服务函数,那么我们只需在函数体内记录触发的次数即可,触发次数达1000次即说明过去了 1000 * 1ms = 1000ms = 1s 的时间。因此我们定义一个全局变量uint16_t time,来记录触发次数,达到 1000 次后,LED反转,同时变量清零。
【编程要点】
- 初始化时基结构体,并使能定时器;
- 初始化定时器的中断标志位,开启定时器中断;
- 配置中断优先级NVIC;
- 编写中断服务函数;
- 编写主函数。
以下为部分程序(使用基本定时器TIM6):
1. basic_tim.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #ifndef __BASIC_TIM_H #define __BASIC_TIM_H
#include "stm32f10x.h" #include "stm32f10x_tim.h" #include "misc.h"
#define BASIC_TIMx TIM6 #define BASIC_TIMx_CLK RCC_APB1Periph_TIM6 #define BASIC_TIM_Period (1000 - 1) #define BASIC_TIM_Prescaler (72 - 1) #define BASIC_TIMx_IRQn TIM6_IRQn #define BASIC_TIMx_IRQHandler TIM6_IRQHandler
void BASIC_TIM_Config(void); void BASIC_TIM_NVIC_Config(void);
#endif
|
2. basic_tim.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include "basic_tim.h"
void BASIC_TIM_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; RCC_APB1PeriphClockCmd(BASIC_TIMx_CLK, ENABLE); TIM_TimeBaseInitStructure.TIM_Prescaler = BASIC_TIM_Prescaler; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period = BASIC_TIM_Period; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(BASIC_TIMx, &TIM_TimeBaseInitStructure); TIM_ClearFlag(BASIC_TIMx, TIM_FLAG_Update); TIM_ITConfig(BASIC_TIMx, TIM_IT_Update, ENABLE); TIM_Cmd(BASIC_TIMx, ENABLE); }
void BASIC_TIM_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIMx_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
|
3. stm32f10x_it.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include "stm32f10x_it.h" #include "basic_tim.h"
extern volatile uint16_t time;
void BASIC_TIMx_IRQHandler(void) { if(TIM_GetITStatus(BASIC_TIMx, TIM_IT_Update) != RESET) { time++; TIM_ClearITPendingBit(BASIC_TIMx, TIM_IT_Update); } }
|
4. main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include "stm32f10x.h" #include "led.h" #include "basic_tim.h"
volatile uint16_t time = 0;
int main(void) { LED_Init(); BASIC_TIM_NVIC_Config(); BASIC_TIM_Config(); while(1) { if(time == 1000) { time = 0; LED0 = !LED0; } } }
|
二、通用定时器——测量脉宽
【实现功能】用户按下按键并松手,通用定时器(TIM2、TIM3、TIM4、TIM5)会测量这段按下去的时间是多少,并通过串口通信将测量结果显示在上位机屏幕上。
【基本思路】通过查看电路原理图(下图)可知,按键WK_UP正好与端口 PA0 中的TIM5_CH1相连,因此我们使用通用定时器 TIM5,并使能 PA0 这个端口。我们实现了这样一个功能:只要按下按键WK_UP,TIM5的通道1即能捕获输入信号。
按下按键,产生一段高电平;松开按键,产生低电平。这样连续的的按下又松开,就产生了方波信号。因此,我们要捕获的信号实际上是一段方波信号,我们要测量的脉宽实际上是一段高电平信号的延续时间,理解这一点很重要。
如何测量这样一段高电平信号的时间呢?我们先用朴素的语言理一下基本的思路:
- 先等待上升沿到来,一旦上升沿到来,意味着高电平信号来了,定时器就会立刻去捕获,这样就产生了一次边沿触发中断事件,此时我们可以在中断服务函数中命令定时器开始计数了。
- 由于我们按下按键的时长一般会比计数器的溢出时间要长,因此计数器会发生溢出,这样就产生了一次更新中断事件。需要注意的是,一次按下可能会产生不止一次溢出,即不止产生一次更新中断事件。
- 这时若松开按键,高电平瞬变为低电平,定时器就会触发下降沿中断事件,计数器此时可能未发生溢出,这时取出计数器的值 CCR 的值,通过计算,这样一次脉宽测量便结束了。
溢出一次所需的时间应设置多长呢?时钟源的频率为 72MHz,我们一般想要得到 1MHz 的定时器频率,就必须使分频因子 PSC = 72 - 1,这样定时器频率就为 72 / (PSC+1) = 1MHz,定时器周期为 1/10^6 秒。由于我们是测量脉宽的,所以计数器的重装载值 Period 直接设置成最大值即可,因为 Period(寄存器ARR)的最大值为 65535,所以我们设置 ARR = 65535,即65535 * 1 / 10^6 s 时间后将会产生一次更新事件。
我们用一张图来重新说明这里的原理:
如图所示,1处表示捕获到了上升沿,产生了边沿触发中断事件,2、3、4、5是更新中断事件,6处是捕获到了下降沿,一次测量结束。需要注意,1 ~ 2、2 ~ 3、3 ~ 4、4 ~ 5是总共4次从0到溢出的计数过程,所间隔的时间都是 65535 * 1 / 10^6 s。5~6是从0开始计数,还未溢出就捕获了下降沿,这时计数器计数到了 a (a < 65535,寄存器CCR的值为a)。所以,对于这张图,最终的脉宽时长为 (4 * 65535 * 1 / 10^6) + (a * 1 / 10^6) = (4 * 65535 + a) * (1 / 10^6) s。
结论:设溢出的次数为 x,最后一次计数器的值(CCR)为 a,则脉宽的测量公式为:(x * 65535 + a) * (1 / 10^6) s,即(x * 65535 + a) / (72MHz/(PSC+1)) s。 但在实际编程中,/ 运算的结果只是整数部分,因此可用 % 运算算出小数部分。
【编程要点】
- 使能GPIO:PA0;
- 初始化时基单元和输入捕获通道;
- 配置NVIC中断;
- 初始化定时器的中断标志位,开启定时器中断(共两个:更新事件和捕获事件);
- 编写中断服务函数的第一个内容:更新事件产生中断,溢出次数 x 加 1(即记录过去了多少次定时器周期);
- 编写中断服务函数的第二个内容:边沿触发产生中断,捕获到上升沿时开始计数,此时要将捕获方式改为下降沿捕获;捕获到下降沿时记录 CCR 的值,此时要将捕获方式改为上升沿捕获,为下一次捕获做准备;
- 编写主函数,用上面的公式计算测量结果,通过 USART 输出测量结果(记得在魔法棒里勾选使用 MicroLib)。
以下为部分代码,添加了本人注释。注意,为了记录上升沿、下降沿是否已经捕获过,以及CCR的值和溢出次数,我们自定义了一个结构体(其实也可以不弄结构体,定义成结构体的好处是可以使得相关变量的联系更加紧密):
1. general_tim.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #ifndef __GENERAL_TIM_H #define __GENERAL_TIM_H
#include "stm32f10x.h"
#define GENERAL_TIM TIM5 #define GENERAL_TIM_CLK RCC_APB1Periph_TIM5 #define GENERAL_TIM_PERIOD 0xFFFF #define GENERAL_TIM_PSC (72 - 1) #define GENERAL_TIM_RECNT 0
#define GENERAL_TIM_CH1_PORT_CLK RCC_APB2Periph_GPIOA #define GENERAL_TIM_CH1_PORT GPIOA #define GENERAL_TIM_CH1_PIN GPIO_Pin_0 #define GENERAL_TIM_CHANNEL_x TIM_Channel_1
#define GENERAL_TIM_IRQn TIM5_IRQn
void GENERAL_TIM_Init(void);
typedef struct { uint8_t Capture_FinishFlag; uint8_t Capture_StartFlag; uint16_t Capture_CcrValue; uint16_t Capture_Period; }TIM_ICUserValueTypeDef;
extern TIM_ICUserValueTypeDef TIM_ICUserValueStructure;
#endif
|
2. general_tim.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include "general_tim.h"
TIM_ICUserValueTypeDef TIM_ICUserValueStructure = {0,0,0,0};
static void GENERAL_TIM_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_PORT_CLK, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH1_PIN; GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure); }
static void GENERAL_TIM_MODE_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE); TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = GENERAL_TIM_PERIOD; TIM_TimeBaseStructure.TIM_Prescaler = GENERAL_TIM_PSC; TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure); TIM_ICInitStructure.TIM_Channel = GENERAL_TIM_CHANNEL_x; TIM_ICInitStructure.TIM_ICFilter = 0; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInit(GENERAL_TIM, &TIM_ICInitStructure); TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update | TIM_FLAG_CC1); TIM_ITConfig(GENERAL_TIM, TIM_IT_Update | TIM_IT_CC1, ENABLE); TIM_Cmd(GENERAL_TIM, ENABLE); }
static void GENERAL_TIM_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
void GENERAL_TIM_Init(void) { GENERAL_TIM_GPIO_Config(); GENERAL_TIM_MODE_Config(); GENERAL_TIM_NVIC_Config(); }
|
3. stm32f10x_it.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include "stm32f10x_it.h" #include "general_tim.h"
void TIM5_IRQHandler(void) { if ( TIM_GetITStatus(GENERAL_TIM, TIM_IT_Update) != RESET ) { TIM_ICUserValueStructure.Capture_Period++; TIM_ClearITPendingBit(GENERAL_TIM, TIM_IT_Update); } if( TIM_GetITStatus(GENERAL_TIM, TIM_IT_CC1) != RESET ) { if( TIM_ICUserValueStructure.Capture_StartFlag == 0 ) { TIM_SetCounter(GENERAL_TIM, 0); TIM_ICUserValueStructure.Capture_Period = 0; TIM_ICUserValueStructure.Capture_CcrValue = 0; TIM_OC1PolarityConfig(GENERAL_TIM, TIM_ICPolarity_Falling); TIM_ICUserValueStructure.Capture_StartFlag = 1; TIM_ICUserValueStructure.Capture_FinishFlag = 0; } else { TIM_ICUserValueStructure.Capture_CcrValue = TIM_GetCapture1(GENERAL_TIM); TIM_OC1PolarityConfig(GENERAL_TIM, TIM_ICPolarity_Rising); TIM_ICUserValueStructure.Capture_StartFlag = 0; TIM_ICUserValueStructure.Capture_FinishFlag = 1; } TIM_ClearITPendingBit(GENERAL_TIM, TIM_IT_CC1); } }
|
4. main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include "stm32f10x.h" #include "usart.h" #include "general_tim.h"
int main(void) { uint32_t time; uint32_t TIM_PscCLK = 72000000 / (GENERAL_TIM_PSC+1); USART_Config(); GENERAL_TIM_Init(); printf("开始测试!来!\n"); while(1) { if(TIM_ICUserValueStructure.Capture_FinishFlag == 1) { time = TIM_ICUserValueStructure.Capture_Period * (GENERAL_TIM_PERIOD+1) + (TIM_ICUserValueStructure.Capture_CcrValue+1); printf ( "\r\n测得高电平脉宽时间:%d.%d s\r\n", time / TIM_PscCLK, time % TIM_PscCLK ); TIM_ICUserValueStructure.Capture_FinishFlag = 0; } } }
|