1. 准备工作
硬件准备
首先需要准备一个开发板,这里我准备的是STM32L4的开发板(BearPi):
超声波模块使用HC-SR04,如图:
该模块的四个引脚说明如下表:
超声波模块测距原理
如图,超声波模块发出超声波信号,如果前方碰到障碍物,则被障碍物反射回来,模块收到回来的超声波后,即可计算出与障碍物的距离:
HR-SC04模块工作原理
HR-SC04模块的时序图如下:
根据该时序图,可以看出一次测距的流程如下:
触发信号由Trig引脚输入,10us的高电平即可触发一次测距;
触发后,距离信息由Echo引脚输出,该输出信号的高电平时间与检测距离成比例;
在整个测距过程中,最重要的是Echo引脚输出信号高电平的时间,可以使用STM32硬件定时器得到时长,这个时间为超声波从发射到接收的时长,由声速计算出传播距离,再取1/2,计算出当前距离障碍物的距离。
软件准备
需要安装好Keil - MDK及芯片对应的包,以便编译和下载生成的代码。
2.生成MDK工程
选择芯片型号
打开STM32CubeMX,打开MCU选择器:
搜索并选中芯片STM32L431RCT6:
配置时钟源
如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;
如果使用默认内部时钟(HSI),这一步可以略过;
这里我都使用外部时钟:
配置GPIO
配置两个普通的GPIO,一个配置为输出模式,用于连接模块的Trig引脚,触发一次测距,这里我配置为PB8,另一个配置为输入模式(浮空),用于连接模块的Echo引脚,接收echo信号,这里我配置为PB9。
配置通用定时器TIM2
知识小卡片——STM32L431的定时器
STM32L431xx 系列有 1 个高级定时器(TIM1), 3 个通用定时器(TIM2、TIM15、TIM16),两个基本定时器(TIM6、TIM7),还有两个低功耗定时器(LPTIM1、LPTIM2)。
STM32L431 的通用 TIMx (TIM2、TIM15、TIM16)定时器功能包括:
16 位(TIM15,TIM16)/32 位(TIM2)向上、向下、向上/向下自动装载计数器,注意:
TIM15、TIM16 只支持向上(递增)计数方式;
16 位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为 1~65535 之间的任
意数值。
4 个独立通道(TIMx_CH1~4, 其中 TIM15 最多 2 个通道, TIM16 最多 1 个
通道),这些通道可以用来作为:
输入捕获
输出比较
PWM 生成(边缘或中间对齐模式)
单脉冲模式输出
可使用外部信号控制定时器和定时器互连的同步电路。
如下事件发生时产生中断/DMA:
更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
输入捕获
输出比较
知识小卡片结束啦~
接下来开始配置TIM2,首先选择TIM2,我们的目的是每1us计数一次 ,所以定时器的频率需要在1Mhz, 时钟源选择内部时钟:
接下来是对TIM2的参数设置,参照数据手册中的RCC时钟树,TIM2内部时钟来源是PCLK1 = 80Mhz,,所以预分频系数设置为80,即可得到1Mhz的计数频率。
计数周期设为60000,实际测试情况下,大约60000us对应的实际距离是10m(实际中也用不到这么大):
配置串口
开发板板载了一个CH340换串口,连接到USART1。
接下来开始配置USART1:
配置时钟树
STM32L4的最高主频到80M,所以配置PLL,最后使HCLK = 80Mhz即可:
生成工程设置
代码生成设置
最后设置生成独立的初始化文件:
生成代码
点击GENERATE CODE即可生成MDK-V5工程:
3. 在MDK中编写、编译、下载用户代码
1. printf重定向
2. 添加us级延时函数
使用时注意根据自己的处理器主频修改:
3. 编写驱动
因为超声波模块往往需要多个,所以采用面向对象的思想设计,将每个模块封装为一个设备对象。
HC_SR04.h
/**
* @Copyright (c) 2021,mculover666 All rights reserved
* @filename HC_SR04.c
* @breif Drive HC_SR04 based on stm32 tim peripheral
* @changelog v1.0 mculover666 2021/5/9
* v1.1 mculover666 2021/5/12 add object design
*/
#ifndef _HC_SR04_H_
#define _HC_SR04_H_
#include “stm32f1xx.h”
#include “core_delay.h”
typedef struct hc_sr04_device_st {
GPIO_TypeDef *trig_port;
uint16_t trig_pin;
GPIO_TypeDef *echo_port;
uint16_t echo_pin;
TIM_HandleTypeDef *tim; //us级硬件定时器
double distance; //测算距离
} hc_sr04_device_t;
/* us级延时函数 */
#define HC_SR04_Delay_Us(n) CPU_TS_Tmr_Delay_US(n)
void HC_SR04_Init(hc_sr04_device_t *hc_sr04_device);
int HC_SR04_Measure(hc_sr04_device_t *hc_sr04_device);
#endif /* _HC_SR04_H_ */
HC_SR04.c
/**
* @Copyright (c) 2021,mculover666 All rights reserved
* @filename HC_SR04.c
* @breif Drive HC_SR04 based on stm32 tim peripheral
* @changelog v1.0 mculover666 2021/5/9
* v1.1 mculover666 2021/5/12 add object design
*/
#include “HC_SR04.h”
#include 《stdio.h》
/**
* @brief pend the mutex to protect tim.
* @param none
* @return none
* @note it will be need if you use rtos
*/
static void HC_SRO4_Mutex_Pend()
{
//add your code here
}
/**
* @brief post the mutex to protect tim.
* @param none
* @return none
* @note it will be need if you use rtos
*/
static void HC_SRO4_Mutex_Post()
{
//add your code here
}
/**
* @brief hc_sr04_device object initialization.
* @param hc_sr04_device the pointer of the hc_sr04_device_t object
* @return none
*/
void HC_SR04_Init(hc_sr04_device_t *hc_sr04_device)
{
// the gpio and tim is initialized in main
}
/**
* @brief Send trig signal.
* @param hc_sr04_device the pointer of the hc_sr04_device_t object
* @return none
*/
static void HC_SR04_Start(hc_sr04_device_t *hc_sr04_device)
{
/* output high level */
HAL_GPIO_WritePin(hc_sr04_device-》trig_port, hc_sr04_device-》trig_pin, GPIO_PIN_SET);
/* maintain high level at least 10us */
CPU_TS_Tmr_Delay_US(10);
/* resume low level */
HAL_GPIO_WritePin(hc_sr04_device-》trig_port, hc_sr04_device-》trig_pin, GPIO_PIN_RESET);
}
/**
* @brief Measure the high level time of the echo signal.
* @param hc_sr04_device the pointer of the hc_sr04_device_t object
* @return errcode
* @retval 0 success
* @retval -1 fail
*/
int HC_SR04_Measure(hc_sr04_device_t *hc_sr04_device)
{
uint32_t tick_us;
HC_SRO4_Mutex_Pend();
HC_SR04_Start(hc_sr04_device);
__HAL_TIM_SetCounter(hc_sr04_device-》tim, 0);
/* waitting for start of the high level through echo pin */
while (HAL_GPIO_ReadPin(hc_sr04_device-》echo_port, hc_sr04_device-》echo_pin) == GPIO_PIN_RESET);
/* start the tim and enable the interrupt */
HAL_TIM_Base_Start(hc_sr04_device-》tim);
/* waitting for end of the high level through echo pin */
while (HAL_GPIO_ReadPin(hc_sr04_device-》echo_port, hc_sr04_device-》echo_pin) == GPIO_PIN_SET);
/* stop the tim */
HAL_TIM_Base_Stop(hc_sr04_device-》tim);
/* get the time of high level */
tick_us = __HAL_TIM_GetCounter(hc_sr04_device-》tim);
/* calc distance in unit cm */
hc_sr04_device-》distance = (double)(tick_us/1000000.0) * 340.0 / 2.0 *100.0;
HC_SRO4_Mutex_Post();
return 0;
}
测试结果
在main.c中包含驱动头文件:
#include 《stdio.h》
#include “HC_SR04.h”
在main函数中创建设备对象:
/* USER CODE BEGIN 1 */
int ret;
hc_sr04_device_t hc_sr04_device1;
/* USER CODE END 1 */
编写测试代码:
/* USER CODE BEGIN 2 */
printf(“HC-SR04 Module Test By Mculover666rn”);
hc_sr04_device1.trig_port = GPIOB;
hc_sr04_device1.trig_pin = GPIO_PIN_8;
hc_sr04_device1.echo_port = GPIOB;
hc_sr04_device1.echo_pin = GPIO_PIN_9;
hc_sr04_device1.tim = &htim2;
HC_SR04_Init(&hc_sr04_device1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
ret = HC_SR04_Measure(&hc_sr04_device1);
if (ret 《 0) {
printf(“measure failrn”);
}
printf(“distance:%.2f cmrn”, hc_sr04_device1.distance);
HAL_Delay(1000);
}
/* USER CODE END 3 */
测试结果为:
1. 准备工作
硬件准备
首先需要准备一个开发板,这里我准备的是STM32L4的开发板(BearPi):
超声波模块使用HC-SR04,如图:
该模块的四个引脚说明如下表:
超声波模块测距原理
如图,超声波模块发出超声波信号,如果前方碰到障碍物,则被障碍物反射回来,模块收到回来的超声波后,即可计算出与障碍物的距离:
HR-SC04模块工作原理
HR-SC04模块的时序图如下:
根据该时序图,可以看出一次测距的流程如下:
触发信号由Trig引脚输入,10us的高电平即可触发一次测距;
触发后,距离信息由Echo引脚输出,该输出信号的高电平时间与检测距离成比例;
在整个测距过程中,最重要的是Echo引脚输出信号高电平的时间,可以使用STM32硬件定时器得到时长,这个时间为超声波从发射到接收的时长,由声速计算出传播距离,再取1/2,计算出当前距离障碍物的距离。
软件准备
需要安装好Keil - MDK及芯片对应的包,以便编译和下载生成的代码。
2.生成MDK工程
选择芯片型号
打开STM32CubeMX,打开MCU选择器:
搜索并选中芯片STM32L431RCT6:
配置时钟源
如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;
如果使用默认内部时钟(HSI),这一步可以略过;
这里我都使用外部时钟:
配置GPIO
配置两个普通的GPIO,一个配置为输出模式,用于连接模块的Trig引脚,触发一次测距,这里我配置为PB8,另一个配置为输入模式(浮空),用于连接模块的Echo引脚,接收echo信号,这里我配置为PB9。
配置通用定时器TIM2
知识小卡片——STM32L431的定时器
STM32L431xx 系列有 1 个高级定时器(TIM1), 3 个通用定时器(TIM2、TIM15、TIM16),两个基本定时器(TIM6、TIM7),还有两个低功耗定时器(LPTIM1、LPTIM2)。
STM32L431 的通用 TIMx (TIM2、TIM15、TIM16)定时器功能包括:
16 位(TIM15,TIM16)/32 位(TIM2)向上、向下、向上/向下自动装载计数器,注意:
TIM15、TIM16 只支持向上(递增)计数方式;
16 位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为 1~65535 之间的任
意数值。
4 个独立通道(TIMx_CH1~4, 其中 TIM15 最多 2 个通道, TIM16 最多 1 个
通道),这些通道可以用来作为:
输入捕获
输出比较
PWM 生成(边缘或中间对齐模式)
单脉冲模式输出
可使用外部信号控制定时器和定时器互连的同步电路。
如下事件发生时产生中断/DMA:
更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
输入捕获
输出比较
知识小卡片结束啦~
接下来开始配置TIM2,首先选择TIM2,我们的目的是每1us计数一次 ,所以定时器的频率需要在1Mhz, 时钟源选择内部时钟:
接下来是对TIM2的参数设置,参照数据手册中的RCC时钟树,TIM2内部时钟来源是PCLK1 = 80Mhz,,所以预分频系数设置为80,即可得到1Mhz的计数频率。
计数周期设为60000,实际测试情况下,大约60000us对应的实际距离是10m(实际中也用不到这么大):
配置串口
开发板板载了一个CH340换串口,连接到USART1。
接下来开始配置USART1:
配置时钟树
STM32L4的最高主频到80M,所以配置PLL,最后使HCLK = 80Mhz即可:
生成工程设置
代码生成设置
最后设置生成独立的初始化文件:
生成代码
点击GENERATE CODE即可生成MDK-V5工程:
3. 在MDK中编写、编译、下载用户代码
1. printf重定向
2. 添加us级延时函数
使用时注意根据自己的处理器主频修改:
3. 编写驱动
因为超声波模块往往需要多个,所以采用面向对象的思想设计,将每个模块封装为一个设备对象。
HC_SR04.h
/**
* @Copyright (c) 2021,mculover666 All rights reserved
* @filename HC_SR04.c
* @breif Drive HC_SR04 based on stm32 tim peripheral
* @changelog v1.0 mculover666 2021/5/9
* v1.1 mculover666 2021/5/12 add object design
*/
#ifndef _HC_SR04_H_
#define _HC_SR04_H_
#include “stm32f1xx.h”
#include “core_delay.h”
typedef struct hc_sr04_device_st {
GPIO_TypeDef *trig_port;
uint16_t trig_pin;
GPIO_TypeDef *echo_port;
uint16_t echo_pin;
TIM_HandleTypeDef *tim; //us级硬件定时器
double distance; //测算距离
} hc_sr04_device_t;
/* us级延时函数 */
#define HC_SR04_Delay_Us(n) CPU_TS_Tmr_Delay_US(n)
void HC_SR04_Init(hc_sr04_device_t *hc_sr04_device);
int HC_SR04_Measure(hc_sr04_device_t *hc_sr04_device);
#endif /* _HC_SR04_H_ */
HC_SR04.c
/**
* @Copyright (c) 2021,mculover666 All rights reserved
* @filename HC_SR04.c
* @breif Drive HC_SR04 based on stm32 tim peripheral
* @changelog v1.0 mculover666 2021/5/9
* v1.1 mculover666 2021/5/12 add object design
*/
#include “HC_SR04.h”
#include 《stdio.h》
/**
* @brief pend the mutex to protect tim.
* @param none
* @return none
* @note it will be need if you use rtos
*/
static void HC_SRO4_Mutex_Pend()
{
//add your code here
}
/**
* @brief post the mutex to protect tim.
* @param none
* @return none
* @note it will be need if you use rtos
*/
static void HC_SRO4_Mutex_Post()
{
//add your code here
}
/**
* @brief hc_sr04_device object initialization.
* @param hc_sr04_device the pointer of the hc_sr04_device_t object
* @return none
*/
void HC_SR04_Init(hc_sr04_device_t *hc_sr04_device)
{
// the gpio and tim is initialized in main
}
/**
* @brief Send trig signal.
* @param hc_sr04_device the pointer of the hc_sr04_device_t object
* @return none
*/
static void HC_SR04_Start(hc_sr04_device_t *hc_sr04_device)
{
/* output high level */
HAL_GPIO_WritePin(hc_sr04_device-》trig_port, hc_sr04_device-》trig_pin, GPIO_PIN_SET);
/* maintain high level at least 10us */
CPU_TS_Tmr_Delay_US(10);
/* resume low level */
HAL_GPIO_WritePin(hc_sr04_device-》trig_port, hc_sr04_device-》trig_pin, GPIO_PIN_RESET);
}
/**
* @brief Measure the high level time of the echo signal.
* @param hc_sr04_device the pointer of the hc_sr04_device_t object
* @return errcode
* @retval 0 success
* @retval -1 fail
*/
int HC_SR04_Measure(hc_sr04_device_t *hc_sr04_device)
{
uint32_t tick_us;
HC_SRO4_Mutex_Pend();
HC_SR04_Start(hc_sr04_device);
__HAL_TIM_SetCounter(hc_sr04_device-》tim, 0);
/* waitting for start of the high level through echo pin */
while (HAL_GPIO_ReadPin(hc_sr04_device-》echo_port, hc_sr04_device-》echo_pin) == GPIO_PIN_RESET);
/* start the tim and enable the interrupt */
HAL_TIM_Base_Start(hc_sr04_device-》tim);
/* waitting for end of the high level through echo pin */
while (HAL_GPIO_ReadPin(hc_sr04_device-》echo_port, hc_sr04_device-》echo_pin) == GPIO_PIN_SET);
/* stop the tim */
HAL_TIM_Base_Stop(hc_sr04_device-》tim);
/* get the time of high level */
tick_us = __HAL_TIM_GetCounter(hc_sr04_device-》tim);
/* calc distance in unit cm */
hc_sr04_device-》distance = (double)(tick_us/1000000.0) * 340.0 / 2.0 *100.0;
HC_SRO4_Mutex_Post();
return 0;
}
测试结果
在main.c中包含驱动头文件:
#include 《stdio.h》
#include “HC_SR04.h”
在main函数中创建设备对象:
/* USER CODE BEGIN 1 */
int ret;
hc_sr04_device_t hc_sr04_device1;
/* USER CODE END 1 */
编写测试代码:
/* USER CODE BEGIN 2 */
printf(“HC-SR04 Module Test By Mculover666rn”);
hc_sr04_device1.trig_port = GPIOB;
hc_sr04_device1.trig_pin = GPIO_PIN_8;
hc_sr04_device1.echo_port = GPIOB;
hc_sr04_device1.echo_pin = GPIO_PIN_9;
hc_sr04_device1.tim = &htim2;
HC_SR04_Init(&hc_sr04_device1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
ret = HC_SR04_Measure(&hc_sr04_device1);
if (ret 《 0) {
printf(“measure failrn”);
}
printf(“distance:%.2f cmrn”, hc_sr04_device1.distance);
HAL_Delay(1000);
}
/* USER CODE END 3 */
测试结果为:
举报