33.7.2.2. 软件分析¶
宏定义
代码清单:高级定时器-9 宏定义¶
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/************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 我们这里默认使用TIM5
#define GENERAL_TIM TIM5
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM5
#define GENERAL_TIM_Period 0XFFFF
#define GENERAL_TIM_Prescaler (72-1)
// TIM 输入捕获通道GPIO相关宏定义
#define GENERAL_TIM_CH1_GPIO_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_IT_CCx TIM_IT_CC1
#define GENERAL_TIM_IRQ TIM5_IRQn
#define GENERAL_TIM_INT_FUN TIM5_IRQHandler
// 获取捕获寄存器值函数宏定义
#define GENERAL_TIM_GetCapturex_FUN TIM_GetCapture1
// 捕获信号极性函数宏定义
#define GENERAL_TIM_OCxPolarityConfig_FUN TIM_OC1PolarityConfig
// 测量的起始边沿
#define GENERAL_TIM_STRAT_ICPolarity TIM_ICPolarity_Rising
// 测量的结束边沿
#define GENERAL_TIM_END_ICPolarity TIM_ICPolarity_Falling
使用宏定义非常方便程序升级、移植。有关具体每个宏的含义看注释即可。
定时器复用功能引脚初始化
代码清单:高级定时器-10 定时器复用功能引脚初始化¶
1
2
3
4
5
6
7
8
9
10static void GENERAL_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 输入捕获通道 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
}
GENERAL_TIM_GPIO_Config()函数初始化了定时器用到的相关的GPIO,当使用不同的GPIO的时候,
只需要修改头文件里面的宏定义即可,而不需要修改这个函数。
定时器模式配置
代码清单:高级定时器-11 定时器模式配置¶
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
43static void GENERAL_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_PERIOD;
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_PSC;
// 时钟分频因子 ,配置死区时间时需要用到
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
/*--------------------输入捕获结构体初始化-------------------*/
TIM_ICInitTypeDef TIM_ICInitStructure;
// 配置输入捕获的通道,需要根据具体的GPIO来配置
TIM_ICInitStructure.TIM_Channel = GENERAL_TIM_CHANNEL_x;
// 输入捕获信号的极性配置
TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;
// 输入通道和捕获通道的映射关系,有直连和非直连两种
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
// 输入的需要被捕获的信号的分频系数
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
// 输入的需要被捕获的信号的滤波系数
TIM_ICInitStructure.TIM_ICFilter = 0;
// 定时器输入捕获初始化
TIM_ICInit(GENERAL_TIM, &TIM_ICInitStructure);
// 清除更新和捕获中断标志位
TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update|GENERAL_TIM_IT_CCx);
// 开启更新和捕获中断
TIM_ITConfig (GENERAL_TIM, TIM_IT_Update | GENERAL_TIM_IT_CCx, ENABLE );
// 使能计数器
TIM_Cmd(GENERAL_TIM, ENABLE);
}
ADVANCE_TIM_Mode_Config()函数中初始化了两个结构体,有关这两个结构体成员的具体含义可参考“定时器初始化结构体详解”小节,剩下的程序参考注释阅读即可。
在初始化时基结构体的周期和时钟分频因子这两个成员时,我们使用了两个宏GENERAL_TIM_PERIOD和GENERAL_TIM_PSC。GENERAL_TIM_PERIOD配置的是ARR寄存器的值,决定了计数器一个周期的计数时间,默认我们配置为0XFFFF,即最大。GENERAL_TIM_PSC配
置的是分频因子,默认配置为72-1,则可以计算出计数器的计数周期为(GENERAL_TIM_PSC+1)/72M =
1US。所以输入捕获能捕获的最小的时间为1us,最长的时间为1us*(0Xffff+1)=65536us=65.536ms,当超过这个计数周期的时候,就会产生中断,然后在中断里面做额外的处理,需要记录好产生了多少次更新中断,最后把这个更新时间加入到脉宽的时间里面。
中断优先级配置函数
// 中断优先级配置
static void GENERAL_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM_IRQ ;
// 设置主优先级为 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
因为只有一个中断源,所以优先级可以随便配置。
中断服务函数
void GENERAL_TIM_INT_FUN(void)
{
// 当要被捕获的信号的周期大于定时器的最长定时时,定时器就会溢出,产生更新中断
// 这个时候我们需要把这个最长的定时周期加到捕获信号的时间里面去
if ( TIM_GetITStatus ( GENERAL_TIM, TIM_IT_Update) != RESET ) {
TIM_ICUserValueStructure.Capture_Period ++;
TIM_ClearITPendingBit ( GENERAL_TIM, TIM_FLAG_Update );
}
// 上升沿捕获中断
if ( TIM_GetITStatus (GENERAL_TIM, GENERAL_TIM_IT_CCx ) != RESET) {
// 第一次捕获
if ( TIM_ICUserValueStructure.Capture_StartFlag == 0 ) {
// 计数器清0
TIM_SetCounter ( GENERAL_TIM, 0 );
// 自动重装载寄存器更新标志清0
TIM_ICUserValueStructure.Capture_Period = 0;
// 存捕获比较寄存器的值的变量的值清0
TIM_ICUserValueStructure.Capture_CcrValue = 0;
// 当第一次捕获到上升沿之后,就把捕获边沿配置为下降沿
GENERAL_TIM_OCxPolarityConfig_FUN(GENERAL_TIM, TIM_ICPolarity_Falling);
// 开始捕获标准置1
TIM_ICUserValueStructure.Capture_StartFlag = 1;
}
// 下降沿捕获中断
else { // 第二次捕获
// 获取捕获比较寄存器的值,这个值就是捕获到的高电平的时间的值
TIM_ICUserValueStructure.Capture_CcrValue =
GENERAL_TIM_GetCapturex_FUN (GENERAL_TIM);
// 当第二次捕获到下降沿之后,就把捕获边沿配置为上升沿,好开启新的一轮捕获
GENERAL_TIM_OCxPolarityConfig_FUN(GENERAL_TIM, TIM_ICPolarity_Rising);
// 开始捕获标志清0
TIM_ICUserValueStructure.Capture_StartFlag = 0;
// 捕获完成标志置1
TIM_ICUserValueStructure.Capture_FinishFlag = 1;
}
TIM_ClearITPendingBit (GENERAL_TIM,GENERAL_TIM_IT_CCx);
}
}
在GENERAL_TIM_Mode_Config()函数中,我们配置输入捕获的起始边沿为GENERAL_TIM_STRAT_ICPolarity,这是一个宏,默认配置为上升沿。
我们的按键默认是接GND,当按键按下的时候会被拉高,这个时候这个由低到高的上升沿会被捕获到,这是第一次捕获,此时我们把计数器清0,
开始计数,同时把捕获边沿改成下降沿捕获。当第二次进入中断服务函数的时候,说明捕获到下降沿,这个时候表示脉宽捕获完毕,我们读取捕获寄存器的值即可,
然后我们就可以通过这个值算出脉宽的时间。最后把捕获编译配置为上升沿,为的是下一次捕获。如果脉宽的时间超过了计数器的最大计数时间,
那么就会产生更新中断,我们需要做额外的处理,即产生了多少次更新中断记录下来,最后在算脉宽的时间的时候把这个更新的时间加进去即可。
中断服务函数里面用到的捕获结束标志位、捕获开始标志位、捕获寄存器的值和自动重装载更新标志这几个成员是在一个结构体里面定义,
具体声明见 代码清单:高级定时器-12。其中声明是在bsp_GeneralTim.h这个头文件中,定义和初始化则在bsp_GeneralTim.c文件。
代码清单:高级定时器-12 定时器输入捕获用户自定义变量结构体声明¶
1
2
3
4
5
6
7// 定时器输入捕获用户自定义变量结构体声明
typedef struct {
uint8_t Capture_FinishFlag; // 捕获结束标志位
uint8_t Capture_StartFlag; // 捕获开始标志位
uint16_t Capture_CcrValue; // 捕获寄存器的值
uint16_t Capture_Period; // 自动重装载寄存器更新标志
} TIM_ICUserValueTypeDef;
主函数
代码清单:高级定时器-13 main函数¶
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
29int main(void)
{
uint32_t time;
// TIM 计数器的驱动时钟
uint32_t TIM_PscCLK = 72000000 / (GENERAL_TIM_PSC+1);
/* 串口初始化 */
USART_Config();
/* 定时器初始化 */
GENERAL_TIM_Init();
printf ( "\r\n野火 STM32 输入捕获实验\r\n" );
printf ( "\r\n按下K1,测试K1按下的时间\r\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;
}
}
}
主函数很简单,主要是一些初始化,然后在一个while循环中打印测量的脉宽时间。在计算的时候,
记得把周期GENERAL_TIMPERIOD和Capture_CcrValue的值都加1后再运算,因为计数器是从开始计数的。