前言: STM32定时器输入捕获简介
STM32的输入捕获可以用于捕获脉宽, 测量时间 . 例如超声波测距模块就是需要用输入捕获功能, 通过测量输入脉冲的高电平脉宽 , 从而计算出测量物体的距离 ;
定时器PWM工作模式上篇博客讲过了, 上篇是输出PWM, 本篇是要输入, 即外面的信号送给单片机的引脚, 然后单片机测量出脉宽 ;
注: 下面根据正点原子的标准库函数教程分析, 并用CubeMX完成配置以及HAL库函数编程
如图所示 : 以测量高电平脉宽为例, 我们先设置定时器通道为上升沿捕获, 到1的时候触发定时器计数, 然后立刻设置为下降沿捕获, 到2的时候就捕获到下降沿, 再记录输入捕获寄存器的值, 两个时间差就是高电平时长tH;
需要注意的是, 在tH这段高电平时间内, 是由很多个向上计数的脉冲来计数的。在这里面计数可能溢出N多次; 下面是原子的库函数指南pdf里面讲解的图
在tH这段高电平里面, 可能有多个向上计数的脉冲, 而那个三角向上计数脉冲也可能溢出多次。就是利用这N多个向上计数的脉冲来计算tH的值的。 ARR的值是我们自己设定的,所以可以知道溢出一次是多长时间, 每溢出一次, 都给溢出次数加一。 溢出次数以及检测高低电平的数据记录在自己设定的一个变量里面
N*ARR + CCRx2即为CNT计数次数, 从而就可以算出计数时间, 算出高电平时长
N: 溢出次数 ARR: 溢出一次的时间 在一个tH内,溢出的次数不一定正好是整数, 所以用记录下CCRx2的值, 用以补充, 这样tH的值就更精确了
这是一个八位的变量,可以将其看做8位寄存器,不同的位储存不同的数据
5.1 操作简介
通过信号发生器给单片机对应引脚输入一个给定频率和占空比的矩形波信号, 单片机通过输入捕获测量出高电平时长; 通过串口发送至PC端的串口调试助手查看测量的脉宽是否准确
5.2 STM32CubeMX配置初始化+IAR编程
Step1 : Cube配置
(1) 新建工程
RCC和SYS配置, 时钟树配置都同前面一样; 还要用串口打印数据, 测试用。
RCC和SYS配置
USART1串口1配置(按照串口那一篇配置串口即可–>串口通信 )
注: 详细解释转至串口通信
时钟树配置
(2)TIM5参数配置
使用TIM5的通道一(TIM5_CH1)接收外部输入的信号。配置如下
注: 上一篇介绍了如何计算定时器溢出时间,这里溢出时间为1us 点击查看—>定时器中断及定时器产生PWM
使能TIM5中断(要在中断里面计数高电平脉宽)
NVIC设置(同样可以查看上一篇看详细讲解NVIC配置以及中断分组详解)
(3) 工程配置(Project Manager)
注 : 高级设置默认即可
(4) 生成代码(Generate Code)
Step2 : Keil/IAR编程
(1)重定向printf函数(重定向之后我们才可以使用printf函数将调试信息打印到串口调试助手) 下面是串口通信那一篇博客写的话,直接搬到这里:
在学习C语言的时候, 大家肯定都用过printf这个函数, printf可以将指定字符打印到电脑的显示器上;
但是, 单片机要使用这个就要把他打印的方向改一下, 不是打印在电脑的命令行中, 而是打印到串口里面,传输到串口调试助手. 因此我们需要重定向printf函数;
重定向后我们要将调试信息打印到USART1中, 需要对printf所依赖的打印函数fputc()重定向 .
在usart.c里面添加重定向代码
以后这段代码直接抄就好了, copy下来用
/* USER CODE BEGIN 0 */
#include "stdio.h"
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int _io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__*/
// 重定向C语言中的printf函数
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
/* USER CODE END 0 */
(2) 查看一下定时器相关代码学习
打开之后跟正点原子的标准库函数写的代码对比一下, 又利于自己理解CubeMX配置的机理, 以后会更得心应手, 慢慢的自己就可以一直标准库函数用HAL库写了。
这张图左边是CubeMX配置后自动生成的代码, 蓝色框框里面就是对应的CubeMX里面的配置; 右侧是原子的标准库例程代码, 可以对比一下, 增强理解
tips:CSDN只能上传不超过5M的图片, 所以这个图片经过了压缩 , 放大看可以看清晰一点。
(3) 编写中断部分函数
因为要在中断中捕获上升沿和下降沿, 所以主要代码写在中断服务函数里面
下图是计数中断
TIM5CH1_CAPTURE_STA虽然是我们定义的一个变量,但可以把它看做是一个8位的寄存器
下图是捕获中断
在HAL_TIM_PeriodElapsedCallback()回调函数中用以处理计数次数和时间; 在HAL_TIM_IC_CaptureCallback()回调函数负责处理捕获到的上升沿和下降沿,
并随着捕获到上升沿而更改为下降沿捕获, 随着捕获到下降沿而更改定时器为上升沿捕获.
下面是完整代码:
/* USER CODE BEGIN 1 */
/* bit7 捕获完成标识 bit6 捕获到高电平标识 bit5~0 捕获高电平后定时器溢出的次数 */
uint8_t TIM5CH1_CAPTURE_STA = 0; // 输入捕获状态
uint32_t TIM5CH1_CAPTURE_VAL; // 输入捕获值(TIM2/TIM5是32位的定时器所以这里定义为uint32_t)
// 中断服务函数里面会自动调用这个回调函数 这个是定时器更新中断中处理的函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM5) // 判断是定时器5发生中断
{
if ((TIM5CH1_CAPTURE_STA & 0x80) == 0) // 还未成功捕获
{
if (TIM5CH1_CAPTURE_STA & 0x40) // 捕获到高电平
{
if ( (TIM5CH1_CAPTURE_STA & 0x3f) == 0x3f ) // 如果高电平太长 做溢出处理
{
TIM5CH1_CAPTURE_STA |= 0x80; // 标记成功捕获了一次
TIM5CH1_CAPTURE_VAL = 0xffffffff;
}
else
{
TIM5CH1_CAPTURE_STA++; // 若没有溢出, 就只让TIM5CH1_CAPTURE_STA自加就ok
}
}
}
}
}
// 定时器输入捕获中断处理回调函数,该函数在 HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim) 中会被调用
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if ( (TIM5CH1_CAPTURE_STA & 0x80) == 0 ) // 还未成功捕获
{
if (TIM5CH1_CAPTURE_STA & 0x40) // 捕获到一个下降沿
{
TIM5CH1_CAPTURE_STA |= 0x80; // 标记成功捕获到一次高电平脉宽
TIM5CH1_CAPTURE_VAL = HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_1); // 获取当前的捕获值. 即CCRx2
TIM_RESET_CAPTUREPOLARITY(&htim5, TIM_CHANNEL_1); // 清除原来的设置
TIM_SET_CAPTUREPOLARITY(&htim5, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); // 配置TIM5通道1上升沿捕获
}
else
{
TIM5CH1_CAPTURE_STA = 0; // 清空自定义的状态寄存器
TIM5CH1_CAPTURE_VAL = 0; // 清空捕获值
TIM5CH1_CAPTURE_STA |= 0x40;// 标记捕获到了上升沿
__HAL_TIM_DISABLE(&htim5); //关闭定时器5
__HAL_TIM_SET_COUNTER(&htim5,0);
TIM_RESET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1); //一定要先清除原来的设置!!
TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);//定时器5通道1设置为下降沿捕获
__HAL_TIM_ENABLE(&htim5);//使能定时器5
}
}
}
/* USER CODE END 1 */
=
tips:每句话都有注释, 不要一看到密密麻麻代码就不看了, 看一下并不是很难理解。 也不要因为看到全是大写字母的函数或者变量而犯怵, 静下心来用两分钟看一看很容易看懂
还有一个问题:就是这里为什么用HAL_TIM_PeriodElapsedCallback而不是其他的callback呢? 原因在IRQ_Handler函数里面。
(4) 主函数程序(main.c)
首先使能定时器中断、同时定义一个变量备用:
/* USER CODE BEGIN 2 */
HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1); // 开启输入捕获中断
__HAL_TIM_ENABLE_IT(&htim5,TIM_IT_UPDATE); //使能更新中断
long long temp = 0; // 定义一个变量用以存储捕获到的时间 long long型是为了防止数据溢出
/* USER CODE END 2 */
在while(1)循环测量数据并打印
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(10);
// 信号发生器输入信号 串口打印高电平时长 ms
if (TIM5CH1_CAPTURE_STA & 0x80) // 如果捕获完成
{
temp = TIM5CH1_CAPTURE_STA & 0x3f;
temp *= 0xffffffff; // Total Overflow Time(总的溢出时间)
temp += TIM5CH1_CAPTURE_VAL; // Get Total High Level Time(获取总的高电平时长)
printf("HIGH: %lld msrn", temp/1000); // Print Total High Level Time(打印总的高电平时长)
TIM5CH1_CAPTURE_STA = 0; // Clear Capture State , Open The Next Capture(清除捕获状态,打开下一次捕获)
}
}
/* USER CODE END 3 */
(5) 至此程序就完成了.
2020年4月10日2020年4月10日20:53:30
== 补充说明:==
输入捕获这一节 有个学弟反应有点问题,如果用检测按键输入,按下时间太久了就会显示4194ms ; 类比到我这里的操作就是,信号发生器产生的波形频率很低的时候,就达到了测量上限,高电平最多4194ms;
这是因为原子的例程有一个时间上限 在于0x3f那个地方,0x3F就限制了时间的上限;下面这个是他给我的截图;
后面返校之后我会修改代码,完善这个地方
在主函数里面, TIM5CH1_CAPTURE_STA & 0x80的意思是判断有没有捕获到高电平 用TIM5CH1_CAPTURE_STA和0x80相与, 从而判断TIM5CH1_CAPTURE_STA的6位是否为1, 进而判断出是否捕获到高电平; 下面的一些涉及到相与的操作也都类似, 把一个变量看做一个寄存器, 把0x80、 0xffffffff等转换为二进制就好判断了, 在演草纸上画一下就很清楚
(6) 编译下载
上述代码都是之前经过测试的,但是当前由于疫情,没有条件展现结果, 如果有人用了这些代码并测试,有什么问题的话可以下面评论告知,感激不尽。效果展示会在后期补上
上一篇:STM32CubeMX 配置STM32F407 实现HAL库延时微妙方案
下一篇:第六节:STM32基于HAL库的IIC通信
推荐阅读最新更新时间:2024-11-17 04:27