1 基于STC15F2K60S2的超声波测距代码
单片机型号说明:IAP15F2K61S2
新建工程时单片机型号选择STC15F2K60S2
超声波测距模块的工作原理及应用
使用定时器产生超声波发射频率
1.1 基本注意事项
1.1.1 跳线帽接法
蓝桥杯单片机开发板 J13跳线帽 选择I/O模式
超声波和数码管之间的两个跳线帽接法:横着接(1-3)(2-4),J2两个跳线帽接到左边是超声波,接到右边是红外通信模块
本开发板用到的芯片是 CX20106A芯片
1.1.2 晶振设置
说明STC15单片机默认晶振频率是11.0592MHz
历年赛题 一般要求选择12MHz
我们只需要在STC-ISP里设置一下即可
1.2 板载超声波工作原理
1.2.1 原理总结
只需要在Trig管脚(TX管脚)发送8个40KHz的超声波脉冲(方波信号),驱动超声波发送探头工作,然后检测回波信号。当检测到回波信号后,通过Echo管脚(RX管脚)输出
根据Echo管脚输出高电平的持续时间可以继续距离值
即距离值为:(高电平时间*340m/s)/2
1.2.2 超声波代码思路
- 发送8个40KHz的超声波信号
- 检测接收管脚高电平时间
- 根据时间计算距离值
1.3 STC15单片机代码部分
1.3.1 定时器0&定时器1初始化
定时器0用作基准定时
定时器1用作超声波距离检测,定时器1不需要中断
1. void Timer0_Init(void) //1毫秒 @12.000MHz @16位自动重载模式 2. { 3. AUXR |= 0x80; //定时器时钟1T模式 4. TMOD &= 0xF0; //设置定时器模式 5. TL0 = 0x20; //设置定时初始值 6. TH0 = 0xD1; //设置定时初始值 7. TF0 = 0; //清除TF0标志 8. TR0 = 1; //定时器0开始计时 9. ET0 = 1; 10. EA = 1; 11. } 12. 13. void Timer1_Init(void) 14. { 15. AUXR |= 0x40; //定时器时钟1T模式 16. TMOD &= 0x0F; //设置定时器模式 17. }
1.3.2 超声波ultrasonic.c ultrasonic.h文件配置
这里先粘贴超声波模块的代码,具体计算过程放在1.3.3距离计算讲解
1. #include "ultrasonic.h" 2. 3. unsigned char Ultrasonic_Time_Flag; //200ms置1 4. 5. /* 6. TX引脚发送40KHz方波信号驱动超声波发送探头 7. 1/40KHz = 0.000025s = 0.025ms = 25us 8. 使用软件延时注意RC振荡器频率 9. */ 10. void Ultrasonic_Delay25us() //@12.000MHz 超声波专用的延时函数 11. { 12. unsigned char i; 13. 14. _nop_(); 15. _nop_(); 16. i = 72; 17. while (--i); 18. } 19. void Send_wave(void) //发送40KHz的方波信号函数 20. { 21. unsigned char i; 22. for (i = 0; i < 8; i ++ ) 23. { 24. TX = 1; Ultrasonic_Delay25us(); 25. // somenop;somenop;somenop;somenop;somenop;somenop;somenop;somenop;somenop;somenop; 26. TX = 0; Ultrasonic_Delay25us(); 27. // somenop;somenop;somenop;somenop;somenop;somenop;somenop;somenop;somenop;somenop; 28. } 29. } 30. 31. //超声波测距函数 32. float Ultrasonic_Ranging(void) 33. { 34. unsigned int Ranging_Time; //测距的时间 35. float Distance; 36. 37. if (1 == Ultrasonic_Time_Flag) //200ms接受一次数据 38. { 39. Ultrasonic_Time_Flag = 0; //200ms时间标志位置0 40. Send_wave(); //发送方波信号 41. TR1 = 1; //启动计时 42. while ((1 == RX) && (0 == TF1)); //等待收到脉冲 43. TR1 = 0; //关闭计时 44. 45. if (1 == TF1) //发生溢出 46. { 47. TF1 = 0; 48. Distance = 99; //报错误数据 49. } 50. else 51. { 52. //Ranging_Time = (TH1<<8) | TL1; 53. Ranging_Time = TH1*256 + TL1; 54. Distance = Ranging_Time*0.017; //0.017 @12MHz 和 0.18446 @11.0592MHz 55. Distance /= 12.0; //STC15单片机运行速度是51单片机的12倍,这里要除以12.0 56. if (Distance >= 99) Distance = 99; //限幅 57. } 58. TH1 = 0; //复位清零 59. TL1 = 0; 60. } 61. return Distance; 62. } 63. 64. //定时器0中断服务函数 65. void Timer0_Rountine(void) interrupt 1 66. { 67. static unsigned int T0Count0; 68. 69. T0Count0++; 70. if(T0Count0 >= 200) //200ms 71. { 72. //执行操作 73. Ultrasonic_Time_Flag = 1; 74. T0Count0 = 0; //软件复位 75. } 76. }
1. //ultrasonic.h 文件 2. 3. #ifndef __ULTRASONIC_H__ 4. #define __ULTRASONIC_H__ 5. 6. #include <STC15F2K60S2.H> 7. #include "intrins.h" 8. 9. #define somenop {_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_(); _nop_();} 10. 11. sbit TX = P1^0; //发射引脚 12. sbit RX = P1^1; //接收引脚 13. 14. float Ultrasonic_Ranging(void); 15. 16. #endif
关于ultrasonic.c文件的一些解释说明
- Ultrasonic_Delay25us函数是Send_wave的内部调用函数,就是生成一个40KHz方波信号的函数
- 超声波测距函数,带float类型返回值,直接调用Ultrasonic_Ranging函数返回float类型的距离
- 注意Distance /= 12.0; 不能写Distance /= 12; 因为Distance是float类型!!!
- 定时器0中断服务函数放在本文件中,因为方便使用200ms的标志位Ultrasonic_Time_Flag
1.3.3 距离计算
计算公式:s = v*t
- s:距离
- v:声音的传播速度
- t:传播时间!
对于51单片机@12.000MHz晶振而言,1个时钟周期 = 1 /(时钟源频率) = 1/12,000,000
1个机器周期 = 12个时钟周期
所以一个机器周期的时间就是 1 / 12,000,000 * 12 s=12/12,000,000= 1/1,000,000s
一个机器周期定时器计数加一
这里讲清楚了定时器计数加一的时间, 所以我们只需要获得定时器1的计数次数就能计算出超声波模块的传播时间,进而求的距离
高电平计数次数:TH1 * 256 + TL1
超声波模块计时时间 t =(TH1 * 256 + TL1)* 12 / 12000000
由于这个计数时间是双程距离(一个来回的距离),所以时间应该除以2
t = (TH1 * 256 + TL1)* 12 / 12000000 / 2
声速 = 340m/s,s = v*t 单位是米(m),最后乘以100,将单位换算成厘米(cm)
t = (TH1 * 256 + TL1)* 12 / 12000000 / 2
∴ 距离 = (TH1 * 256 + TL1)* 12 / 12000000 / 2 * 340 * 100(cm)
∴距离 = (TH1 * 256 + TL1)*0.017 (cm)
上述式子 用橙色标记的数据是一个机器周期
注意:
如果STC15单片机没用使用AUXR寄存器,使用12T模式,那么蓝桥杯单片机开发板运行速度和51单片机一致,到这里距离计算就ok了,可以直接拿到数码管上显示。
!!!
但是我们配置的定时器0使用AUXR寄存器,配置为1T模式,所以蓝桥杯单片机开发板运行速度是51单片机的12倍,所以最后要修正一下距离数据,最后将距离数据除以12.0,就得到真正的距离了
对于@11.0592MHz的晶振而言
一个机器周期时间就是12/11.0592MHz = 12/11059200s,一个机器周期定时器计数加一
所以用超声波模块计时时间 t = (TH1*256+TL1)*12/11.0595/1000/1000
由于这个计数时间是双程距离(来回),所以时间要/2t = (TH1*256+TL1)*12/11.0595/1000/1000/2
声速340m/s ,s = v*t 单位是m,最后乘以100 单位化成cm
将后面计算简化得0.01844618(TH1*256+TL1)*12/11.0595/1000/1000/2*340*100 (cm)
=(TH1*256+TL1)*1.085/58
=(TH1*256+TL1)*0.01844618
1.3.4 数码管
由于本人懒得一批,直接copy之前写过的数码管代码
1. #include "smg.h" 2. 3. //关闭 蜂鸣器继电器LED数码管 4. void All_Init(void) 5. { 6. P2 = 0xA0; // 1010 0000 7. P0 = 0x00; //off蜂鸣器继电器 8. 9. P2 = 0x80; // 1000 0000 10. P0 = 0xFF; //offLED 11. 12. P2 = 0xC0; //使能锁存器U8 1100 0000 13. P0 = 0xFF; //选择所有数码管 14. P2 = 0xFF; //使能锁存器U7 1111 1111 15. P0 = 0xFF; //关闭所有数码管 16. } 17. 18. //15单片机延时函数 @11.0592MHz 19. void Delay_ms(int xms) 20. { 21. unsigned char i, j; 22. while(xms--){ 23. _nop_(); _nop_(); _nop_(); 24. i = 11; j = 190; 25. do{ 26. while (--j); 27. } while (--i); 28. } 29. } 30. 31. /*共阳极码表 ABCDEF都是大写*/ 32. //0 1 2 3 4 5 6 7 8 9 A B C D E F - 灭 33. unsigned char NixieTable[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x80, 0xC6, 0xC0, 0x86, 0x8E, 0xBF, 0xFF, \ 34. 0xC0 & 0x7F, \ 35. 0xF9 & 0x7F, \ 36. 0xA4 & 0x7F, \ 37. 0xB0 & 0x7F, \ 38. 0x99 & 0x7F, \ 39. 0x92 & 0x7F, \ 40. 0x82 & 0x7F, \ 41. 0xF8 & 0x7F, \ 42. 0x80 & 0x7F, \ 43. 0x90 & 0x7F, \ 44. 0xC1 //U 45. }; 46. 47. //如果想显示小数点,就 &0x7F 48. 49. void Nixie(unsigned char location, unsigned char number) 50. { 51. P2 = 0xC0; //Y6=0;Y6C=1; 使能锁存器U8 P27 = 1; P26 = 1; P25 = 0; 52. switch(location)//选中LED位置,位选 53. { 54. case 1:P0 = 0x01;break; 55. case 2:P0 = 0x02;break; 56. case 3:P0 = 0x04;break; 57. case 4:P0 = 0x08;break; 58. case 5:P0 = 0x10;break; 59. case 6:P0 = 0x20;break; 60. case 7:P0 = 0x40;break; 61. case 8:P0 = 0x80;break; 62. } 63. P2 = 0xFF; //Y7=0;Y7C=1; 使能锁存器U7 P27 = 1; P26 = 1; P25 = 1; 64. P0=NixieTable[number];//数字 65. Delay_ms(1); 66. P0=0xFF;//消影清零 67. } 68. 69. /*数码管显示浮点数,支持0-1000以内的浮点数*/ 70. void SMG_ShowFloatNum(float num) 71. { 72. 73. unsigned char Sep_Num[8]; //一次存放 千百十个... 74. long temp; 75. 76. if (num >= 0) //正数 77. temp = (long)(num*1000); 78. else 79. temp = (long)(-num*1000); 80. 81. 82. Sep_Num[0] = temp / 1000000 % 10; //千位 83. Sep_Num[1] = temp / 100000 % 10; //百位 84. Sep_Num[2] = temp / 10000 % 10; //十位 85. Sep_Num[3] = temp / 1000 % 10; //个位 86. Sep_Num[4] = temp / 100 % 10; //小数点后第一位 87. Sep_Num[5] = temp / 10 % 10; //小数点后第二位 88. Sep_Num[6] = temp % 10; //小数点后第三位 89. 90. 91. Nixie(4, Sep_Num[3] + 18); //下标+18代表显示小数点 92. Nixie(5, Sep_Num[4]); 93. Nixie(6, Sep_Num[5]); 94. Nixie(7, Sep_Num[6]); 95. 96. if (num >=10 && num < 100 ) {Nixie(3, Sep_Num[2]);} //显示十位 97. else if (num >=100 && num < 1000 ) {Nixie(3, Sep_Num[2]); Nixie(2, Sep_Num[1]);} //显示十位百位 98. else if (num >=1000 && num <= 9999) {Nixie(3, Sep_Num[2]); Nixie(2, Sep_Num[1]); Nixie(1, Sep_Num[0]);} //显示十位百位千位 99. else if (num > -100 && num <= -10 ) {Nixie(3, Sep_Num[2]); Nixie(2,16);} //显示十位,并且显示负号 100. else if (num > -1000 && num <= -100) {Nixie(3, Sep_Num[2]); Nixie(2, Sep_Num[1]); Nixie(1,16);} //显示十位百位,并且显示负号 101. else if (num >= -9999 && num <=-1000) {Nixie(3, Sep_Num[2]); Nixie(2, Sep_Num[1]); Nixie(1, Sep_Num[0]);} //显示十位百位千位,但是数码管不够了后续想显示出来,就把数码管显示的内容整体往后挪动一个位置即可 102. else if (num > -10 && num < 0 ) {Nixie(3,16);} //大于-10小于0的负数 103. }
1. #ifndef __SMG_H__ 2. #define __SMG_H__ 3. 4. #include <STC15F2K60S2.H> 5. #include "intrins.h" 6. 7. #define OFF_CODE 17 //数码管熄灭码 8. 9. void All_Init(void); //关闭 蜂鸣器继电器LED数码管 10. void Delay_ms(int xms); //15单片机延时函数 @11.0592MHz 11. 12. extern unsigned char NixieTable[]; 13. void Nixie(unsigned char location, unsigned char number); 14. void SMG_ShowFloatNum(float num); 15. 16. #endif
1.3.5 主函数
1. /* 2. 实验名称:超声波测距实验 3. 开发平台:STC15F2K60S2 @12.000MHz 4. */ 5. 6. #include <STC15F2K60S2.H> 7. #include "smg.h" 8. #include "ultrasonic.h" 9. #include "Timer0.h" 10. #include "Timer1.h" 11. 12. float distance; 13. 14. void main() 15. { 16. All_Init(); 17. Timer0_Init(); //基准定时1ms @12.000MHz @16位自动重载模式 18. Timer1_Init(); //仅初始化定时器1,不需要 计时、中断 19. 20. while(1) 21. { 22. distance = Ultrasonic_Ranging(); 23. SMG_ShowFloatNum(distance); 24. } 25. }
2 缺陷
2.1 传播速度
声音在空气中传播的影响因素有许多
- 声音的传播速度跟介质的反抗平衡力有关,反抗平衡力就是当物质的某个分子偏离其平衡位置时,其周围的分子就要把它挤回到平衡位置上,而反抗平衡力越大,声音就传播的越快。水的反抗平衡力要比空气的大,而铁的反抗平衡力又比水的大。
- 声音在空气中的传播速度还与压强有关。
- 声音在空气中的速度随温度的变化而变化,温度每上升/下降5℃,声音的速度上升/下降3m/s。声音在空气中的传播速度是:空气(15℃)340m/s ,空气(25℃)346m/s 。
- 声音的传播速度随物质的坚韧性的增大而增加,物质的密度减小而减少。一般情况下,同温度下,固体传声最快,液体次之,气体最慢;而在同种介质中,温度越低声速越慢。
- 声音的传播与阻力有关。声音会因外界物质的阻挡而发生折射,例如人面对群山呼喊,就可以听得到自己的回声。
声音在空气中的传播速度=340m/s,受这么多因素影响,声音的传播速度不一定是标准的340m/s,但是代码中用的就是340这个数据,所以测量距离并不是非常准确
2.2 代码
所测的距离经常超出范围,原因是直接输出所测的距离,解决方案:优化输出距离的数据,用简单的滤波即可
数码管不稳定,能看到闪烁,可以使用定时器刷新数码管,或者采集数据时间更长一点(大概500ms采集一次?)这里留个疑问
3 超声波距离报警器
使用超声波模块测距,数码管显示距离,蜂鸣器根据距离长短发出不同频率鸣叫
距离越远鸣叫时间越长(频率越小),距离越近鸣叫时间越短(频率越大)
代码有一些小修改
main.c里的float distance;变量拿到ultrasonic.c中定义,然后在.h文件中externa外部声明出去,这样方便数据调用
在ultrasonic.c里声明两个变量
1. unsigned int Buzzer_Time_Flag = 1000; //蜂鸣器报警时间长短标志位 2. unsigned char Buzzer_Alarm_Flag; //蜂鸣器报警标志位
定时器0中断服务函数有修改
1. //定时器0中断服务函数 2. void Timer0_Rountine(void) interrupt 1 3. { 4. static unsigned int T0Count0, Buzzer_Count; 5. 6. T0Count0++; 7. if(T0Count0 >= 200) //200ms 8. { 9. //执行操作 10. Ultrasonic_Time_Flag = 1; 11. T0Count0 = 0; //软件复位 12. } 13. 14. Buzzer_Count++; 15. if(Buzzer_Count >= Buzzer_Time_Flag) //大于等于时间标志位(大概是**ms) 16. { 17. Buzzer_Alarm_Flag = !Buzzer_Alarm_Flag; //鸣叫标志位状态反转,根据这个状态判断是否鸣叫 18. Buzzer_Count = 0; 19. } 20. }
编写蜂鸣器函数
1. //蜂鸣器 开启P06 =1,关闭P06 = 0; 2. void Buzzer_Alarm(void) 3. { 4. //将main.c的distance变量拿到本文件中,然后外部声明出去,方便这里和主函数的数据调用 5. if (distance) { Buzzer_Time_Flag = distance*10; } //鸣叫时间 = 距离*10,实现了距离越近,鸣叫时间越短 6. 7. P2 = 0xA0; // 1010 0000 选中 8. if (1 == Buzzer_Alarm_Flag) //鸣叫 9. { 10. // P06 = 1; 11. P0 = 0x40; //0100 0000 12. } 13. else if((0 == Buzzer_Alarm_Flag)) //不鸣叫 14. { 15. // P06 = 0; 16. P0 = 0x00; 17. } 18. }
控制蜂鸣器最好控制整个P0口,但是P06也能控制
如果没有障碍物,数据就溢出,数码管默认显示99.000,蜂鸣器默认以0.5Hz的频率鸣叫
声明void Buzzer_Alarm(void)函数,在while(1)里调用即可实现功能
4 超声波模块(HC-SR04)代码(51单片机)
更新中...