超声测距模块 HC-SR04P。工作电压3-5V,有效距离2-400cm,分辨率1mm。注意型号末尾的“P”:有另一个外观、接口、工作方式一样的型号 HC-SR04,但工作电压为5V。我这个模块 PCB正面印刷的是HC-SR04,反面印刷 HC-SR04P:
有4个引脚,VCC/GND供电,TRIG触发测距,ECHO接收测量结果。向TRIG给一个10us以上(规格书建议40-50us)的正脉冲触发信号,模块开始工作,发射超声波并接收回声。从发射到接收到回声的时间,以回响电平的形式从ECHO输出;电平持续时间等于声波往返模块到障碍物的时间:
常温下,声音在空气中的传播速度约343m/s = 3.43mm/10us = .8575mm/2.5us。初步想法是,启动一个Timer作为时间基,计数频率 2.5us = 400kHz,以满足10us触发信号延时和1mm测量分辨率的需求。接口方式,触发信号用GPIO output,回响信号用GPIO外部中断。如下图,回响信号接PC7(Arduino兼容Pin D9),其EXTI line编号为7:
使用基本Timer TIM6,时钟源为PCLK1 x 2=72MHz。设PSC=179,得计数频率 CK_CNT = 72MHz / (1+179) = 400kHz。设 ARR = 63999,则 UEV 频率为 400kHz /(1+63999) = 6.25Hz = 160ms。
计时及有关工具函数实现如下。全局变量 _App_timestamp 记录时间戳,在TIM6 UEV 中断回调函数中更新,因此其精度为160ms。获取当前时间戳时,将_App_timestamp 与 TIM6 CNT寄存器值相加,可达到 2.5us 精度:
typedef struct { uint32_t milli; uint16_t micro; } _App_Timestamp; volatile _App_Timestamp _App_timestamp = { 0, 0 }; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (&htim6 == htim) { _App_timestamp.milli += 160; // 160 ms/tick } } #define _App_GetDuration(from, to) ( 1000 * ((to).milli - (from).milli) + (to).micro - (from).micro ) void _App_GetTimestamp(_App_Timestamp *ts) { *ts = _App_timestamp; uint32_t micro = 5 * htim6.Instance->CNT / 2; ts->milli += (micro / 1000); ts->micro += (micro % 1000); } void _App_DelayMicro(uint32_t micro) { _App_Timestamp t1, t2; _App_GetTimestamp(&t1); for (;;) { _App_GetTimestamp(&t2); if (_App_GetDuration(t1, t2) >= micro) { break; } } }
ECHO Pin启用外部中断,在中断回调函数内记录上升沿和下降沿的时间戳:
typedef struct { uint8_t state; uint32_t duration; // in us _App_Timestamp _risingTs; } _App_Measure; volatile _App_Measure _App_measure = { 0 }; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (D9_ECHO_Pin == GPIO_Pin) { if (GPIO_PIN_SET == HAL_GPIO_ReadPin(D9_ECHO_GPIO_Port, D9_ECHO_Pin)) { // Rising edge _App_Timestamp now; _App_GetTimestamp(&now); _App_measure._risingTs = now; _App_measure.state = 0; } else { // Falling edge _App_Timestamp now; _App_GetTimestamp(&now); _App_measure.duration = _App_GetDuration(_App_measure._risingTs, now); _App_measure.state = 1; } } }
主循环如下。首先发送TRIG脉冲,时间设为20us。距离的计算注意将声波往返距离除以 2 才是到障碍物的距离:
void App_Loop() { _App_Trig(20); HAL_Delay(500); if (_App_measure.state) { uint32_t dur = _App_measure.duration; uint32_t dist = (uint32_t) (.343f * dur / 2); if (dist > 25 && dist < 5000) { char msg[80]; sprintf(msg, "%d mm\n", (int) dist); HAL_UART_Transmit(&huart2, (uint8_t *) msg, strlen(msg), HAL_MAX_DELAY); } } } void _App_Trig(uint32_t micro) { HAL_GPIO_WritePin(D8_TRIG_GPIO_Port, D8_TRIG_Pin, GPIO_PIN_SET); _App_DelayMicro(micro); HAL_GPIO_WritePin(D8_TRIG_GPIO_Port, D8_TRIG_Pin, GPIO_PIN_RESET); }
测得的距离从串口 USART2 输出,单位为mm: