超声波测距在近几届蓝桥杯中几乎每年都考。这篇文章基于项目中的两版超声波驱动代码把PCA定时器的使用、40KHz发射时序、距离计算和温度补偿全部讲透。测距原理超声波模块HC-SR04的工作方式很简单发射端发出一串40KHz的超声波脉冲超声波碰到障碍物反射回来接收端检测到回波根据往返时间计算距离距离 声速 × 飞行时间 / 2 340 m/s × T / 2硬件连接TX发射P1.0RX接收P1.1PCA定时器STC15的PCA可编程计数器阵列本质上是一个16位定时器非常适合超声波的微秒级计时。CMOD 0x00; // PCA时钟源Fosc/1212T模式 CH CL 0; // 清零计数器 CR 1; // 启动PCA计时 CF 0; // PCA溢出标志在12MHz晶振、12T模式下每个PCA计数值 1us。16位最大计数65535对应最大计时约65ms可测距离约11m——远超超声波的实际量程2cm~4m。超声波驱动完整代码项目Driver/U_Wave.c中的实现#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计数值单位us12T模式下 所以 d PCA值 × 0.017 cm4) 返回值类型问题注意U_Wave_Data()返回unsigned char最大值255。这意味着距离超过255cm的测量结果会溢出截断。第16届省赛改进了这一点// 第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届省赛的补偿公式// 用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~5Vvoid 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线性关系。避障检测应用国赛代码中的避障逻辑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届省赛的运动状态检测更有意思——根据两次测距的差值判断前方物体是静止、徘徊还是跑动// 根据距离变化判断运动状态 if(D_Distance 5) Temp_Motion_State 0; // 静止 else if(D_Distance 10) Temp_Motion_State 1; // 徘徊 else Temp_Motion_State 2; // 跑动 // 状态变化锁定防止频繁切换 // 检测到状态变化后锁定3秒3秒内不允许再次变化常见问题问题原因解决始终返回0TX和RX接反了交换P1.0和P1.1读数不稳定声波多径反射多次测量取均值/中值测量偏大声速不准加温度补偿近距离2cm测不到发射和接收重叠加入最小距离判断while((RX1)(CF0))死循环接收引脚一直为高检查模块连接