教程 
蓝桥杯单片机超声波到底要发几个周期脉冲?

网上的蓝桥杯教程(模块)中,超声波模块大多发送八个周期的40khz方波. 然而在实际操作中,会显得非常不稳定.

在一次ai debug会话中,ai给出了以下发送超声波代码:

1
2
3
4
5
6
7
void Transmit() {
    EA=0;
    TX=1;
    Delay12us();
    Delay12us();  
    TX=0;
 }

这段脉冲的周期远低于八个周期,但出乎意料的,这段代码运行的很稳定.

于是,为了求证,我在4T平台找到蓝桥杯官方给出的超声波代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned char Dist_Meas(void)
{
unsigned char ucNum = 10;

TX = 0;
CL = 0xf4; CH = 0xff; // PCA 重载值,每 12μs 溢出一次
CR = 1; // 启动 PCA
while (ucNum--) // 翻转 10 次 = 5 个完整 40kHz 周期
{
while (!CF); // 等 PCA 溢出
TX ^= 1; // 翻转 TX
CL = 0xf4; CH = 0xff;
CF = 0;
}
CR = 0; // 关闭 PCA

CL = 0; CH = 0; // PCA 清零
CR = 1; // 启动
while(RX && !CF); // ← 等 RX 下降沿(和原始代码一样!)
CR = 0;
return ((CH<<8)+CL)*0.017; // 同 t*17/1000
}

它一共翻转了ucNum次电平,翻转了10次,也就是5个周期的40khz方波.

以上似乎已经说明:8个周期的方波对于蓝桥杯的超声波模块来说过于多了,以至于可能出现RX 的放大/比较电路在发射期间被串扰饱和的情况。导致等脉冲发完,换能器还在机械余震,RX 继续振荡,造成干扰.

8周期的 8051 超声波例程最早来自某个开发板,那个板的接收电路增益低,需要 8 个脉冲才能让回波高于比较器阈值. 蓝桥杯竞赛板的模拟前端比那些开发板好,1个脉冲就够了.

验证

考虑从稳定性和准确性两个方面。

测量方法

拿一本书,使得其距离超声波传感器上沿距离为15cm、35cm、55cm,在不同的距离下,改变发射周期 (1i111 \le i \le 11) 并记录数码管显示数据.

公式部分

在每次测量中,间隔500ms更新一次数码管显示距离,一共产生五次数据,计算平均值和标准差 (N=5N=5)

平均值计算公式为:

μ=1Ni=1Nxi\mu = \frac{1}{N} \sum_{i=1}^{N} x_i

标准差计算公式为:

σ=1Ni=1N(xiμ)2\sigma = \sqrt{\frac{1}{N} \sum_{i=1}^{N} (x_i - \mu)^2}

其中,标准差用于衡量稳定性,平均值作如下运算用于衡量准确性

d误差=μd实际距离d_{误差} = |\mu - d_{实际距离}|

代码部分

通过改变按键增减waveNum的值来改变发射次数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include <REGX52.H>
#include <INTRINS.H>

sfr AUXR = 0x8E;
sfr CL = 0xE9;
sfr CCAP0L = 0xEA;
sfr CCON = 0xD8;
sbit CF = CCON^7;
sbit CR = CCON^6;
sbit CCF0 = CCON^0;
sfr CH = 0xF9;
sfr CCAP0H = 0xFA;
sfr CMOD = 0xD9;
sfr CCAPM0 = 0xDA;
sfr T2H = 0xD6;
sfr T2L = 0xD7;
sbit TX = P1^0;
sbit RX = P1^1;
unsigned char segData[8] = {10, 10, 10, 10, 10, 10, 10, 10};
unsigned value=1000;
unsigned char code segTable[18] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0xFF,
0xBF, /*11 -*/
0xC7, /*12 L*/
0x89, /*13 H*/
0x8E, /*14 F*/
0x8C, /*15 P*/
0x86, /*16 E*/
0x88, /*17 A*/
};
void PCA_Init(void) //1毫秒@12.000MHz
{
CCON = 0;
CL = 0;
CH = 0;
CMOD = 0x00;//禁止溢出中断,设置时钟源
CCAP0L = value;
CCAP0H = value>>8;
CCAPM0 = 0x49; //设置为16定时器模式
value += 1000;
CR = 1; //开始工作
EA = 1;
}
void Init() {
P2 = (P2&0x1F)|0xA0;
P0_4=0; P0_6=0;
P2 = (P2&0x1F)|0x80;
P0=0xFF;
P2 = (P2&0x1F);
}

bit runFlag=0; unsigned char waveNum=9;
void Delay12us(void) //@12.000MHz
{
unsigned char data i;

_nop_();
_nop_();
i = 33;
while (--i);
}

void Transmit() {
unsigned char i;
EA=0;
for(i = 0; i < waveNum; i++) {
TX = 1;Delay12us();TX = 0;Delay12us();
}
EA=1;
}
unsigned GetDistance() {
unsigned t=0;

TMOD &= 0x0f;
TL1 = 0x00;
TH1 = 0x00;
TF1 = 0;
TR1 = 0;

Transmit();
TR1 = 1;
while(RX==0 && TF1==0);
while(RX==1 && TF1==0); // ← 等 RX 变低(真实回波到达)
TR1 = 0;

if(TF1) { TF1=0; return 0; }
t = (TH1<<8) | TL1;
return t*0.017;
}
void main() {
Init();
PCA_Init();
while(1) {
static unsigned char cnt=0;
unsigned char i;
unsigned temp;
if(runFlag) {
cnt++;
runFlag=0;
segData[0]=waveNum/10;
segData[1]=waveNum%10;
if(cnt>=10) {
cnt=0;
temp = GetDistance();
segData[3]=segData[4]=segData[5]=segData[6]=segData[7]=10;
i=7; segData[i]=0;
while(temp) {
segData[i--]=temp%10;
temp /= 10;
}
}
}
}
}
sfr P4 = 0xC0;
sbit COL1 = P4^4;sbit COL2 = P4^2;sbit COL3 = P3^5;
sbit ROW3 = P3^2;sbit ROW4 = P3^3;
unsigned char ScanKeyboard(){
unsigned char key=0;
COL1=0; COL2=1; COL3=1;
if(!ROW3) key=5;
if(!ROW4) key=4;

COL1=1; COL2=0; COL3=1;
if(!ROW3) key=9;
if(!ROW4) key=8;

COL1=1; COL2=1; COL3=0;
if(!ROW3) key=13;
if(!ROW4) key=12;
return key;
}

void ProKey() {
static unsigned char preKey=0;
unsigned char key=ScanKeyboard();
if(key==0 && preKey!=0) {
if(preKey==4) {
if(waveNum-1>=0) waveNum--;
}
if(preKey==5) {
if(waveNum+1<=11) waveNum++;
}
}
preKey=key;
}
void DisplaySingleDigit(unsigned char addr) {
P2 = (P2 & 0x1F) | 0xE0;
P0 = 0xFF;
P2 = (P2 & 0x1F) | 0xC0;
P0 = (1<<addr);
P2 = (P2 & 0x1F) | 0xE0;
P0 = segTable[segData[addr]];
P2 = (P2 & 0x1F);
}
void PCA_Routine(void) interrupt 7 {
static unsigned char clkCnt=0, segCnt=0;
CCF0 = 0;
CCAP0L = value;
CCAP0H = value>>8;
if(clkCnt>=100) {clkCnt=0;runFlag=1;ProKey();}
if(segCnt>=8) segCnt=0;
DisplaySingleDigit(segCnt);
clkCnt++; if(clkCnt&0x01)segCnt++;
value += 1000;
}

数据

测量得以下数据(主包手比较抖,距离偏差为±1cm\pm 1 \text{cm}):

目标距离 发射周期数 1 2 3 4 5
15cm 1 15 15 15 15 15
3 14 14 14 14 14
5 14 13 13 14 13
7 5 6 7 13 4
9 6 5 5 5 5
35cm 1 36 36 36 36 36
3 34 34 34 34 34
5 34 34 34 34 34
7 6 5 5 5 5
9 4 7 4 5 4
55cm 1 56 56 56 56 56
3 55 54 54 55 55
5 54 55 7 55 55
7 6 5 6 5 4
9 4 4 4 4 4

计算得:

目标距离 (cm) 发射周期数 均值 标准差 误差
15 1 15.0 0.00 0.0
3 14.0 0.00 1.0
5 13.4 0.49 1.6
7 7.0 3.16 8.0
9 5.2 0.40 9.8
35 1 36.0 0.00 1.0
3 34.0 0.00 1.0
5 34.0 0.00 1.0
7 5.2 0.40 29.8
9 4.8 1.17 30.2
55 1 56.0 0.00 1.0
3 54.6 0.49 0.4
5 45.2 19.10 9.8
7 5.2 0.75 49.8
9 4.0 0.00 51.0

可视化

a Number of emission cycles Standard deviation (cm) Measurement variability vs. emission cycles 0 5 10 15 20 1 3 5 7 9 Target 15 cm Target 35 cm Target 55 cm b Number of emission cycles Absolute error (cm) Measurement error vs. emission cycles 0 10 20 30 40 50 60 1 3 5 7 9 Target 15 cm Target 35 cm Target 55 cm

结论

总的来说,短距离时,waveNum[1,5][1,5]这个区间内,超声波测量值基本准确. 这个检验还是有不严谨的地方,测量次数太少且未去除极端数据(去除极端数据的话,waveNum为5也可用).

补充

此外,长距离时,则需要更长的发射周期. 但是受串扰影响,代码需要修改:在Transmit()后硬编码几个Delay12us()或添加while(RX==1&&TF1==0);while(RX==0&&TF1==0);while(RX==1&&TF1==0);.

若有错误,欢迎指出.

本文作者: Genkaim

本文链接: https://www.genkaim.top/posts/abb27f10

打赏博主😘

bilibili发电⚡
Alipay (移动端)