超声波测距模块——PCA定时器驱动、温度补偿与避障实现

超声波测距在近几届蓝桥杯中几乎每年都考。这篇文章基于项目中的两版超声波驱动代码,把PCA定时器的使用、40KHz发射时序、距离计算和温度补偿全部讲透。

测距原理

超声波模块(HC-SR04)的工作方式很简单:

  1. 发射端发出一串40KHz的超声波脉冲

  2. 超声波碰到障碍物反射回来

  3. 接收端检测到回波

  4. 根据往返时间计算距离

    距离 = 声速 × 飞行时间 / 2
    = 340 m/s × T / 2

硬件连接:

  • TX(发射):P1.0
  • RX(接收):P1.1

PCA定时器

STC15的PCA(可编程计数器阵列)本质上是一个16位定时器,非常适合超声波的微秒级计时。

cs 复制代码
CMOD = 0x00;    // PCA时钟源:Fosc/12(12T模式)
CH = CL = 0;    // 清零计数器
CR = 1;         // 启动PCA计时
CF = 0;         // PCA溢出标志

在12MHz晶振、12T模式下,每个PCA计数值 = 1us。16位最大计数65535,对应最大计时约65ms,可测距离约11m------远超超声波的实际量程(2cm~4m)。


超声波驱动完整代码

项目 Driver/U_Wave.c 中的实现:

cs 复制代码
#include <STC15F2K60S2.H>
#include <intrins.h>

sbit RX = P1^1;    // 接收
sbit TX = P1^0;    // 发射

/* 12us延时函数 */
void Delay12us(void) {    // @12.000MHz
    unsigned char i;
    _nop_();
    i = 3;
    while(--i);
}

/* 发射8个40KHz超声波脉冲 */
void U_Wave_Init() {
    unsigned char i;
    EA = 0;                    // 关中断!保护发射时序
    for(i = 0; i < 8; i++) {
        TX = 1;
        Delay12us();           // 高电平12us
        TX = 0;
        Delay12us();           // 低电平12us
    }
    EA = 1;
}

/* 完整测距函数 */
unsigned char U_Wave_Data() {
    unsigned long int U_Wave_Time;

    CMOD = 0x00;
    CH = CL = 0;                // 清零PCA

    U_Wave_Init();               // 发射超声波

    CR = 1;                     // 启动PCA
    while((RX == 1) && (CF == 0));  // 等待回波或溢出
    CR = 0;                     // 停止PCA

    if(CF == 0) {               // 未溢出 = 正常测距
        U_Wave_Time = (CH << 8) | CL;
        return U_Wave_Time * 0.017;  // 距离(cm) = 时间(us) × 0.017
    } else {                    // 溢出 = 超出量程
        CF = 0;
        return 0;
    }
}

几个关键细节

1) 为什么发射时关中断?

40KHz超声波的周期是25us,半周期12.5us。代码用12us延时,已经非常接近。如果发射过程中Timer1中断触发,中断服务函数里会执行数码管扫描(大约需要几十微秒),这就破坏了发射脉冲的宽度,导致发出的不是40KHz的信号,测距会出错。

2) 为什么是8个脉冲?

8个脉冲只是经验值。理论上越多越好(接收端更容易识别),但太多会增加发射时间,而且需要精确的时序控制。8个脉冲(约192us)是硬件手册推荐的配置。

3) 距离计算公式推导

复制代码
声速 V = 340 m/s = 0.034 cm/us
距离 d = V × t / 2 = 0.034 × t / 2 = 0.017 × t

其中 t 是PCA计数值(单位:us,12T模式下)
所以 d = PCA值 × 0.017 cm

4) 返回值类型问题

注意 U_Wave_Data() 返回 unsigned char,最大值255。这意味着距离超过255cm的测量结果会溢出截断。第16届省赛改进了这一点:

cs 复制代码
// 第16届省赛版:超量程返回255(区别于正常值0)
unsigned char Sonic_Read(void) {
    unsigned int time;
    CMOD = 0x00;
    CH = CL = 0x00;
    CR = 1;
    Sonic_Init();
    while((RX == 1) && (CF == 0));
    CR = 0;
    if(CF == 0) {
        time = CH << 8 | CL;
        return (time * 0.017);
    } else {
        CF = 0;
        return 255;       // 返回255表示超量程
    }
}

虽然返回类型仍然是 unsigned char,但用255作为超量程标志,与正常的0距离区分开了。实际使用时,如果收到255就知道这次测距失败了。


温度补偿

声速不是恒定的,它随温度变化:

复制代码
V(T) = 331.4 + 0.6 × T(°C)

0°C:  331.4 m/s
20°C: 343.4 m/s
40°C: 355.4 m/s

温度从0°C到40°C,声速差了约7%,对测距精度的影响非常大。

第16届省赛的补偿公式:

cs 复制代码
// 用DS18B20读到的温度修正测距结果
distance = (Sonic_Read() / 340.0) * (330 + 0.6 * temperature);

推导过程

复制代码
超声波模块内部用固定声速340m/s计算:
  raw_distance = time × 340 / 2 = time × 0.017

实际距离:
  real_distance = time × V(T) / 2 = time × (331.4 + 0.6T) / 2

所以:
  real_distance = raw_distance × V(T) / 340
                = (raw_distance / 340) × (331.4 + 0.6T)
                ≈ (raw_distance / 340) × (330 + 0.6T)  ← 简化版

DAC同步输出

第16届省赛还把测距结果映射到DAC输出(1~5V):

cs 复制代码
void DAC_Transition(unsigned int distance) {
    if(distance <= 20)
        DA_Output(1);         // 20cm以下 → 1V
    else if(distance >= 100)
        DA_Output(5);         // 100cm以上 → 5V
    else
        DA_Output(((distance - 20) / 20.0) + 1);  // 20~100cm线性映射
}

20cm→1V, 40cm→2V, 60cm→3V, 80cm→4V, 100cm→5V,线性关系。


避障检测应用

国赛代码中的避障逻辑:

cs 复制代码
void Get_Distance() {
    unsigned char temp;
    temp = U_Wave_Data();
    Distance_Now = temp;

    if(Distance_Now < 30) {        // 距离小于30cm
        Barrier_Clean_Flag = 0;    // 有障碍物
        if(Running_Mode == 2)
            Running_Mode = 1;      // 运行 → 暂停
    } else {
        Barrier_Clean_Flag = 1;    // 障碍物已清除
    }
}

第16届省赛的运动状态检测更有意思------根据两次测距的差值判断前方物体是静止、徘徊还是跑动:

cs 复制代码
// 根据距离变化判断运动状态
if(D_Distance < 5)
    Temp_Motion_State = 0;    // 静止
else if(D_Distance < 10)
    Temp_Motion_State = 1;    // 徘徊
else
    Temp_Motion_State = 2;    // 跑动

// 状态变化锁定(防止频繁切换)
// 检测到状态变化后锁定3秒,3秒内不允许再次变化

常见问题

问题 原因 解决
始终返回0 TX和RX接反了 交换P1.0和P1.1
读数不稳定 声波多径反射 多次测量取均值/中值
测量偏大 声速不准 加温度补偿
近距离(<2cm)测不到 发射和接收重叠 加入最小距离判断
while((RX==1)&&(CF==0)) 死循环 接收引脚一直为高 检查模块连接
相关推荐
Evand J1 个月前
【无人机编队控制程序4】复杂障碍环境下多无人机编队避障(人工势场法APF)与协同控制,MATLAB仿真例程
开发语言·matlab·无人机·控制·apf·避障
NQBJT1 个月前
双轮足导盲机器人:多传感融合与全局-局部分层导航系统设计
c++·esp32·openmv·避障·导盲·轮足
Evand J3 个月前
【MATLAB复现RRT(快速随机树)算法】用于二维平面上的无人车路径规划与避障,含性能分析与可视化
算法·matlab·平面·无人车·rrt·避障
沉在嵌入式的鱼3 个月前
温度嵌入式软件算法补偿方案及步骤
stm32·单片机·算法·温度传感器·温度补偿
Eric.Lee20214 个月前
SLAM 路径规划的安全走廊实现
python·机器人·ros·路径规划·避障·安全走廊
boss-dog5 个月前
curobo——CUDA加速的机器人库
机器人·轨迹规划·避障·curobo
BreezeJuvenile7 个月前
外设模块学习(8)——HC-SR04超声波模块(STM32)
stm32·单片机·嵌入式硬件·学习·超声波测距模块·hc-sr04
MocapLeader9 个月前
IROS 2025 多智能体深度强化学习算法实现Crazyflie无人机在复杂环境中协同追逐
无人机·集群·控制·导航·协同·轨迹规划·避障
云卓SKYDROID1 年前
无人机精准降落与避障模块技术解析
人工智能·无人机·航电系统·科普·避障·云卓科技·降落模块