STM32F103系列单片机定时器介绍(三)
STM32F103系列单片机定时器介绍(三)
引言
上次我们讲解了基本定时器的功能框图,了解了基本定时器的底层构造,以及其中每一部分发挥的作用,分析了影子寄存器存在的意义,以及如何计算基本定时器的定时器时间。本次我将会向大家介绍如何使用基本定时器实现非阻塞式的超声波测距功能
模块介绍
基本信息
本次使用的超声波测距模块的型号为:HC-SR04,当前测距模块有四个引脚分别是:VCC、Trig(控制端)、Echo(接收端)、GND

测距原理
在超声波模块的触发端(Trig)输入10微秒以上的高电平,即可发射超声波,发射超声波之后,与接收到传回的超声波之前,接收端是高电平,因此程序可以从接收端(Echo)引脚的高电平持续时间计算出与被测物体的距离,测试距离=(高电平持续时间*声速)/2
时序图
超声波模块的时序图如下

结合当前时序图再次理解测距过程可得具体测距过程为:
-
单片机发送触发信号
单片机向超声波测距模块的
Trig引脚发送一个至少持续10us的高电平,这相当于向单片机发送起始信号,发送完当前高电平信号后超声波测距模块就会开始测距 -
模块发送测距脉冲
从时序图的第二行可以看到当超声波测距模块接收到单片机发送的
10us测距模块后就会开始测距操作,它会自动发送8个40KHz的超声波脉冲,连续发送8个脉冲是为了抗干扰 -
模块接收测距脉冲
当模块内部把8个
40KHz的脉冲发送后就会立刻把Echo引脚的电平拉高,随后超声波在空气中大致以340m/s的速度传播,当超声波撞击到前方的障碍物并发射回来被模块内部的接收器捕获到之后就会立刻将Echo引脚的电平再次拉低
计算公式推导
结合我们前面的分析和物理学的基本公式我们不难推出测距的单程距离公式为:
单程距离 = 声速 × 高电平总时间 2 单程距离 = \frac{声速 \times 高电平总时间}{2} 单程距离=2声速×高电平总时间
确定了计算公式后我们接下来需要统一单位:
- 时间单位:在测距过程中高低电平的持续时间往往都是微秒级别的
- 距离单位:在结果输出时我们希望的测量结果单位为厘米
- 声音传播速度:声音的传播速度在空气中大致为
340m/s
随后我们就可以进行单位换算操作:
-
换算长度:
340m=34000cm -
换算时间:
1s=1,000,000us所以声速转换后可以表示为:
声速 = 34 , 000 cm 1 , 000 , 000 uS = 0.034 cm/us 声速 = \frac{34,000 \text{ cm}}{1,000,000 \text{ uS}} = 0.034 \text{ cm/us} 声速=1,000,000 uS34,000 cm=0.034 cm/us
此时将换算好的声速代入第一步的基础公式中:
距离 ( c m ) = 0.034 cm/uS × 时间 ( u s ) 2 距离 (cm) = \frac{0.034 \text{ cm/uS} \times 时间 (us)}{2} 距离(cm)=20.034 cm/uS×时间(us)
距离 ( c m ) = 0.017 × 时间 ( u s ) 距离 (cm) = 0.017 \times 时间 (us) 距离(cm)=0.017×时间(us)
此时我们就得到了距离公式:距离 = 时间 * 0.017,但是在没有浮点数运算单元的
STM32F1中,处理带有小数的浮点数乘法是非常消耗CPU资源和时间的,为了减少对CPU的影响我们还可以在此基础上进行进一步的处理,总所周知乘以一个小数等于除以这个小数的倒数:1 0.017 ≈ 58 \frac{1}{0.017} \approx 58 0.0171≈58
所以最后的计算公式就转换为了
距离 ( c m ) = 时间 ( u S ) 58.0 距离 (cm) = \frac{时间 (uS)}{58.0} 距离(cm)=58.0时间(uS)
项目配置
本次项目是基于HAL库进行开发,因此项目的配置我们使用CubeMX软件进行一站式处理,基本配置这里不做过多介绍,主要介绍核心部分的配置
传感器引脚配置
当前我所使用的开发板中连接到超声波模块触发端的是引脚PF11,连接到超声波模块接收端的是引脚PF12,我们在CubeMX中配置引脚的模式如下:
PF11配置为推挽输出模式PF12配置为中断模式,中断触发模式为双边沿触发,初始状态为拉低状态

定时器配置
在本项目中我们使用的是基本定时器TIM6,具体配置如下

在当前项目中配置预分频器的值为71也就是设置计数器每1微秒自增一次,技术模式只有向上计数一种,自动重装载值只要大于60000即可,因此我们直接将自动重装载值设置为65535(自动重装载值得上限),下面的auto-reload preload是选择是否开启预加载模式,由于此次项目中并没有涉及到修改定时器底层配置的操作,只是用来简单计数,因此这里开不开影响不大。最下面的Trigger Event Selection,这个选项的作用是配置基本定时器的触发输出信号,本次项目中也并没有涉及到相关操作,因此我们也不需要进行相关配置
串口配置
为了方便后续通过串口输出距离信息,这里使能一个串口用于后续的数据发送

至此项目的核心组件的配置操作就大体完成了
代码编写
状态机与全局变量
由于项目的整体架构搭建过程在上一篇文章中已经完整的介绍过,链接如下
文章链接
因此接下来直接快进到编写代码的过程,首先我们需要在bsp.c文件中定义对应的状态机枚举,其中枚举类型变量的内容如下
typedef enum{
STATE_IDLE = 0, //空闲态
STATE_WAIT_RISING, //等待回波上升沿
STATE_MEASURE, //正在测量(等待下降沿)
STATE_DATA_READY, //数据就绪
STATE_ERROR, //测量超时(错误状态)
}State_t;
当前枚举类型中保存了整个超声波测距过程中的完整状态,这个枚举,接下来我们需要创建对应的全局变量来保存系统的状态信息,我们定义了如下三个全局变量
extern volatile State_t g_state; //存储当前的系统状态
extern volatile uint32_t g_time_count; //存储高电平持续的时间(单位为微秒)
extern float g_distance; //存储最后计算得出的距离(单位为厘米)
触发测量函数
接下来我们会在传感器的驱动目录下的源文件中编写一个用于发送Trig信号的函数,这个函数的作用是触发超声波测距操作,具体代码如下:
void Sensor_Trigger(void)
{
//只有在空闲状态下才触发测距
if (g_state == STATE_IDLE)
{
//只有在接收引脚为低电平时才触发测距
if (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) == GPIO_PIN_RESET)
{
//向触发引脚发送10us以上的高电平
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
delay_us(20);
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
//切换全局状态进入等待接收端上升沿状态
g_state = STATE_WAIT_RISING;
}
}
}
当前函数的主要作用为触发测距操作并切换系统状态,测距触发完成后等待接收端接收到高电平
中断处理
我们需要通过HAL库中的中断回调函数来处理EXTI外部中断和TIM6溢出中断,将下面的两端代码分别放到测距模块对应文件中和定时器模块对应文件中
-
外部中断回调函数(由
HAL_GPIO_EXTI_IRQHandler底层调用)void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { //判断触发中断的是否为接收端引脚 if (GPIO_Pin == ECHO_Pin) { //读取当前引脚电平判断是上升沿触发还是下降沿触发 if (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) == GPIO_PIN_SET) { //上升沿触发,说明此时超声波刚刚反射回来 if (g_state == STATE_WAIT_RISING) { //将TIM6的计数值清零 __HAL_TIM_SET_COUNTER(&htim6, 0); //开启TIM6计时器 HAL_TIM_Base_Start_IT(&htim6); //切换全局状态,进入测量状态 g_state = STATE_MEASURE; } } else { //下降沿触发说明此时发送出去超声波已经全部发射回来了 if (g_state == STATE_MEASURE) { //关闭TIM6定时器 HAL_TIM_Base_Stop_IT(&htim6); //读取高电平的持续时间 g_time_count = __HAL_TIM_GET_COUNTER(&htim6); //切换全局状态,此时已经测量完毕 g_state = STATE_DATA_READY; } } } } -
基本定时器更新中断回调函数(超时保护)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { //判断是不是TIM6发生了溢出 if (htim->Instance == TIM6) { //停止定时器 HAL_TIM_Base_Stop_IT(htim); //更新系统状态,防止系统卡死在测量状态 g_state = STATE_ERROR; } }
主循环逻辑
接下来就是同一处理系统状态和测量数据,相关内容我们放在bsp.c文件中的bsp_loop中实现,并在main.c中的while循环中进行调用,主要内容如下
void bsp_loop()
{
//定义起始时间信息
static uint32_t last_trigger_time = 0;
//根据超声波传感器官方建议:测量周期应不小于60ms
if (HAL_GetTick() - last_trigger_time > 60)
{
Sensor_Trigger();
//更新时间信息
last_trigger_time = HAL_GetTick();
}
//检测是否有测量完成的数据
if (g_state == STATE_DATA_READY)
{
g_distance = g_time_count / 58.0f;
printf("distance: %f\n", g_distance);
//处理完毕后切换为空闲状态,等待下一次测距
g_state = STATE_IDLE;
}
//处理超时异常
if (g_state == STATE_ERROR)
{
printf("error\n");
// 处理完毕后切换为空闲状态,等待下一次测距
g_state = STATE_IDLE;
}
}
主函数调用
上述代码编写完成后接下来我们就需要在主函数中进行调用,我们在主函数顶部引入相关头文件后直接在while循环中调用相关函数即可,while循环内容如下
while (1)
{
bsp_loop();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
结果检测
代码编写完成后我们编译没有报错,接下来进行烧录操作,代码烧录后在上位机软件中打开对应窗口就可以看到对应的串口输出

至此利用基本定时器实现非阻塞式超声波测距的程序就编写完成了
更多推荐
所有评论(0)