STM32F103输入捕获–按键
文章目录
一、输入捕获
输入捕获是定时器的一个应用,用于
- 波形高低电平的时间的计算
- 波形频率的计算
输入捕获的步骤为
- GPIO初始化(GPIO时钟使能)
- TIMx初始化(TIMx时钟使能)
- TIM_IC初始化
- 中断初始化
- 中断函数的处理
其中最为重要的就是中断函数的处理。
二、定义初始化所需结构体
- GPIO结构体
- TIMx结构体
- TIM_IC结构体(捕获的结构体)
- NVIC中断结构体
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_ICInitTypeDef TIM5_ICInitStructure;
三、时钟使能
GPIO | APB2 |
---|---|
TIMx | APB1 |
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
四、GPIO初始化
// GPIOA0初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
此处使用下拉输入是因为此实验用于测试高电平的时间。
那为什么IO口是PA0呢?
4.1 IO与TIMx
可根据芯片的数据去查询,由于本次实验使用TIM5,所以以下为IO与TIM5的关系
TxCx(某定时器的某一频道) | 默认IO | 重映射IO |
---|---|---|
T5C1 | PA0 | None |
T5C2 | PA1 | None |
T5C3 | PA2 | None |
T5C4 | PA3 | None |
五、定时器初始化
// TIM5初始化
TIM_TimeBaseInitStructure.TIM_Period = 0xFFFF; // 定时器最大数字
TIM_TimeBaseInitStructure.TIM_Prescaler = (72-1); // 时基
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0; // 分频系数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数方式
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
难点为TIM_Period(arr)与TIM_Prescaler(psc)和定时器时间的关系
T
=
(
a
r
r
+
1
)
×
(
p
s
c
+
1
)
72
×
1
0
6
(
s
)
T = \frac{(arr+1)\times(psc+1)}{72 \times 10^6}(s)
T=72×106(arr+1)×(psc+1)(s)
T b a s e = ( p s c + 1 ) 72 × 1 0 6 ( s ) T_{base} =\frac{(psc+1)}{72 \times 10^6}(s) Tbase=72×106(psc+1)(s)
如假设 p s c = ( 72 − 1 ) psc=(72-1) psc=(72−1)
则 T b a s e = 1 0 − 6 s T_{base} = 10^-6s Tbase=10−6s 即 1 u s 1us 1us
定时器里的数字为多少即此时的时间为多少us。(后续在超声波的应用中会详细讲解,此文主要讲解输入捕获部分的内容)
六、定时器捕获初始化
// 输入捕获初始化
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; // 频道1 -- PA0
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 映射到TI1
TIM5_ICInitStructure.TIM_ICPrescaler = 0; // 不分频
TIM5_ICInitStructure.TIM_ICFilter = 0; // 不滤波
TIM_ICInit(TIM5,&TIM5_ICInitStructure);
在4.1的表格中明确写明了IO口与channel之间的关系,由于GPIO定义的是PA0,所以相对应的,捕获通道要定义为Channel1。
七、中断初始化
// 中断初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; // TIM5中断函数
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 先占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 从优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE); // TIM5中断允许
TIM_Cmd(TIM5,ENABLE); // 启动定时器
八、中断函数
难点在于如何在中断函数里去获取高电平的时间
8.1 进入中断的条件
首先明确进入中断的条件为:
- 定时器溢出中断
- 捕获发生中断
以上两个条件满足其一均可以进入中断函数,因此需要在中断函数里对其进行区分。
8.2 高电平捕获的流程
1、捕获到高电平,进入中断,将捕获条件改为下降沿。
2、捕获到低电平,一次高电平捕获完成,将捕获条件改为上升沿。
由此可见,就是在高低电平之间进行一个来回的交替
8.3 时间的计算
1、捕获到高电平后开始计时
- 若是溢出,则溢出次数+1
- 若是超出最大值,则为最大值,不再变化
2、捕获到低电平时计时结束
8.4 伪代码
需要定义的变量
变量 | 含义 |
---|---|
f_up=1 | 发生上升沿捕获(1:发生;0:未发生) |
f_cap=0 | 发生捕获,开始计时(1:开始计时;0:不开始计时) |
finall=0 | 是否完成一次捕获(1:完成;0:未完成) |
num=0 | 溢出次数 |
val=0 | 捕获完成后定时器的值 |
中断函数
如果:finall == 1
不执行任何代码,直接返回,代表完成捕获
如果捕获标志位!=RESET ;(即代表发生捕获并进入中断)
默认如果f_up==1; 即发生上升沿捕获
定时器数字=0,num=0,val=0
开始计时,f_cap=0(允许计时)
同时改为下降沿捕获(f_up=0,定时器捕获配置)
否则(表示捕获到了下降沿)
val = 定时器内的数字
finall = 1;完成一次捕获
f_cap=0;关闭计时
同时改为下降沿捕获(f_up=1,定时器捕获配置)
如果 定时器溢出标志位!=RESET;(代表发生了定时器溢出中断)
如果 cap==1;允许计时
如果 num超出最大值
num = 最大值
否则
num++ ;溢出次数+1
清除溢出与捕获更新标志;
主函数:
如果finall==1;完成了一次捕获
t i m e = n u m × a r r + v a l time = num\times arr + val time=num×arr+val (us)
finall = 0;开启下一次捕获
8.5 可执行代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
u8 f_up = 1; // 判断上升捕获
u8 f_cap = 0; // 是否发生捕获
u8 finish = 0; // 捕获完成
u16 num = 0; // 定时器溢出次数
u16 val = 0; // 定时器值
void caputer_init()
{
GPIO_InitTypeDef GPIO_Cap_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_ICInitTypeDef TIM5_ICInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
// GPIOA0初始化
GPIO_Cap_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Cap_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Cap_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Cap_InitStructure);
// TIM5初始化
TIM_TimeBaseInitStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseInitStructure.TIM_Prescaler = (72-1);
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
// 中断初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 输入捕获初始化
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM5_ICInitStructure.TIM_ICPrescaler = 0;
TIM5_ICInitStructure.TIM_ICFilter = 0;
TIM_ICInit(TIM5,&TIM5_ICInitStructure);
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);
TIM_Cmd(TIM5,ENABLE); // 开定时器
}
void TIM5_IRQHandler(void)
{
if(finish) // 完成一次捕获,不进入任何中断处理,返回主函数处理数据
return;
// 如果发生捕获
if(TIM_GetITStatus(TIM5,TIM_IT_CC1)!=RESET)
{
printf("cap\r\n");
// 是否为上升捕获
if(f_up) // 上升沿
{
printf("up\r\n");
val = 0;
num = 0;
TIM_SetCounter(TIM5,0); // 定时器数字为0开始计算高电平时间
f_cap = 1; // 打开进入计时中断
// 将捕获切换为下降沿
f_up =0;
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);
}
else // 下降沿
{
printf("down\r\n");
// 完成了一次捕获
finish = 1;
// 获取时间
val = TIM_GetCapture1(TIM5);
// 关闭溢出允许
f_cap = 0;
// 将捕获切换为上升沿
f_up = 1;
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);
}
}
// 如果发生计时溢出中断
if(TIM_GetITStatus(TIM5,TIM_IT_Update)!=RESET)
{
if(f_cap) // 开始计时已打开
{
// 超出最大值
if(num == 0xFFFF)
{
num = 0xFFFF;
}
else
// 记录溢出次数
num++;
printf("num = %d\r\n",num);
}
}
TIM_ClearITPendingBit(TIM5,TIM_IT_Update|TIM_IT_CC1); // 清除中断更新标志
}
int main(void)
{
float num_f; // 定义变量,必须放在最前面
u32 time=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(); //
uart_init(115200);
delay_ms(30);
caputer_init(); // 捕获初始化
// 主循环函数
while(1)
{
if(finish) // 一次捕获完成
{
time = num * 65536;
time += val;
printf("HIGH_time = %d us\r\n",time);
finish = 0; // 捕获完成
}
}
}
九、结果展示
十、存在的问题
我是用按键实现的输入捕获,但是执行若干次后程序会崩溃,必须要reset重启才可以用。
(注:若是无法输出的朋友,按一下复位按键即可。)
若是那位大神知道错误在哪儿,还请不吝赐教!!!我也还在秃头思考ing。