前置介绍
超声波测距模块规格手册




在代码中需要对 33ms 进行处理, 在配置的时候, 用定时器进行计数测量这个脉冲的时间.
其是从 0 计数到 ARR , 那么这个计数的时间就不要低于 33ms, 才能保证如果是最远距离也不会导致 ARR 溢出重新计数, 那么就没有办法去计算时间了.
所以在频率配置上要特别注意, 每一个完整的技术周期要超过这个时间.
超声波测距模块其用法即为:
- 将 TRIG 引脚接入 MCU 的一个输出引脚
- 输出引脚输出一个 10us 的高电平, 超声波测距模块收到信号开始工作, 发出超声波再收到超声波
- 接入定时器, 通过 输入捕获模式 捕获回响信号的高电平和低电平, 即可得到高电平的宽度.
- 计算高电平区间计数数量, 与频率共同计算得到高电平时间
- 通过距离推算公式, 得到超声波测距的距离.
TIM-输入捕获之超声波测距

实验关键点: 如何捕获上升沿和下降沿?

假设使用定时器通道 2 做输入捕获, 虽然在选择输入捕获模式的时候, 可以将上升沿和下降沿都进行计数, 但是没办法在上升沿和下降沿都进行捕获.
所以就要用到 TI2FP2 做为 直接捕获 (比如捕获上升沿部分), 再用 通道 1 的 TI1FP2 也就是 间接捕获 (捕获信号下降沿部分)
即: 使用两个通道, 来传输一个信号.
以为我们从框图中能够看到, 通道一和通道二是 直接捕获 和 间接捕获 相互的, 而通道三和通道四则是直接和间接相互的. 那么我们就可以检测. 将下降沿的读数减去上升沿的读数, 就能得到高电平期间的计数.
TIM-超声波测距所用函数

项目配置
GPIO

PA15 设置为 GPIO_Output
User Label 设置为 CS100A_TRIG
TIM2

先将 Channel2 设置为 Input Capture direct mode (直接输入捕获模式)
再将 Channel1 设置为 Input Capture indirect mode (间接输入捕获模式)
两个通道的 IC Selection 因为上面已经选择了直接和间接, 所以这里就只有一个选项, 默认即可

直接通道选择捕获上升沿, 间接通道选择捕获下降沿.
Prescaler 修改为 72 - 1.
Counter Period 默认即可, 注意: 不要小于测距模块 33 ms 的返回时间
使能 auto-reload preload .
指令周期 - CPU工作的基本单元, CPU从内存中取出一条指令并执行完成所需要的全部时间
此处为语雀内容卡片,点击链接查看:https://www.yuque.com/heartacheboy/opk4mi/egfnxkwmgp9yw97u
代码部分
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h> // 使用 printf 函数
#include <string.h> // 使用 strncmp
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint32_t start_time = 0; // 记录超声波回响开始时间 (单位: 定时器计数值)
uint32_t end_time = 0; // 记录超声波回响结束时间 (单位: 定时器计数值)
float pulse_width = 0.0f; // 计算出的超声波高电平脉宽时间 (单位: 微秒 s)
float distance = 0.0f; // 根据脉宽计算出的距离值 (单位: 厘米 cm)
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/**
* @brief 发送超声波 TRIG 触发信号
* @note 输出一个大约 10us 的高电平, 用于启动超声波测距
* @retval 无
*/
void CS100A_TRIG_Start(void)
{
// 设置 TRIG 引脚为高电平, 开始发出超声波脉冲
HAL_GPIO_WritePin(GPIOA, CS100A_TRIG_Pin, GPIO_PIN_SET);
// 因为延迟的最小单位是 ms, 所以这里使用软延迟实现.
// 内核时钟频率是 72MHz, 也就是每执行 72个指令周期, 才会消耗掉 1us,
// 而一个 for 循环是八个指令周期, 操作九次才能够达到 8 * 9 = 72, 消耗 1us,
// 而我们需要 10us 的高电平, 就需要用 9 * 10 次
// 简单延时, 用于产生大约 10us 的脉冲宽度 (72)
for (uint16_t i = 0; i < 90; i++);
// 设置 TRIG 引脚为低电平, 完成触发
HAL_GPIO_WritePin(GPIOA, CS100A_TRIG_Pin, GPIO_PIN_RESET);
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 清零定时器计数器 CNT
__HAL_TIM_SET_COUNTER(&htim2, 0);
// 启动输入捕获功能 (通道1为间接, 捕获下降沿; 通道2为直接, 捕获上升沿)
// 注意: 必须在触发前先开启捕获, 否则肯呢个错过回拨信号
// 捕获下降沿
HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_1);
// 捕获上升沿
HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_2);
// 发送超声波触发信号
CS100A_TRIG_Start();
// 检测通道二所捕获的上升沿标志位 (等待回波信号上升沿捕获完成, 即开始计时)
while(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC2) == RESET);
// 代表检测到了回波信号的高电平 (清除上升沿中断标志位)
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_CC2);
// 读取通道2捕获的上升沿时间 (起始时间) (从对应的htim中读取捕获到的值)
start_time = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2);
// 等待回波信号下降沿捕获完成, 即停止计时
// 下降沿是由通道一, 的间接捕获来完成的, 所以这里要检测通道一的CC1状态
while(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC1) == RESET);
// 代表检测到了回波信号的高电平 (清除上升沿中断标志位)
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_CC1);
// 读取通道1捕获的下降沿时间 (结束时间)
end_time = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
// 停止捕获 (避免干扰下次采样)
// 停止捕获下降沿
HAL_TIM_IC_Stop(&htim2, TIM_CHANNEL_1);
// 停止捕获上升沿
HAL_TIM_IC_Stop(&htim2, TIM_CHANNEL_2);
// 计算计数差
uint32_t delta = end_time - start_time;
// 换算成脉宽 (单位: s)
// 因为是 1MHz 所以是一秒记一百万个数.
pulse_width = (float)delta * 1e-6f;
// 根据声速换算成距离 (单位: m)
distance = pulse_width * 340.0f / 2.0f;
printf("当前距离: %.2f m\r\n", distance);
// 500ms 测量一次
// 当测量完成后, 就会回到上面, 重新清零定时器计数器, 再一次开启输入捕获, 捕获上升沿和下降沿
// 计算中间的计数值, 转换脉宽, 转换距离等.
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE BEGIN 4 */
/**
* @brief 重定向 printf 的输出到串口
* @param ch: 要发送的字符
* @param f: 文件指针 (标准库要求的参数, 一般不使用)
* @retval 返回发送的字符
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/* USER CODE END 4 */
程序现象

移动超声波测距模块 或者 改变用手遮挡超声波测距模块的距离, 可以看到输出距离发生变化.