一、超声波测距基本原理- r7 i/ O$ q. }) T* ?: w
超声波测距的原理非常简单,超声波发生器在某一时刻发出一个超声波信号,当这个超声波信号遇到被测物体后会反射回来,被超声波接收器接收到。这样只要计算出从发出超声波信号到接收到返回信号所用的时间,就可以计算出超声波发生器与反射物体的距离。
' Z7 d6 \: }! ?/ G8 G3 R/ h& ^& d/ d3 @
距离的计算公::d=s/2=(c*t)/2
5 ]7 n3 x+ E n5 r' I- T: U* a: p+ ~0 I4 W
其中 d 为被测物与测距器的距离,s 为声波的来回路程,c 为声波,t 为声波来回所用的时间。
0 I2 ~: p: B) H t4 O; i
: S- j$ p, k; F/ s由于超声波也是一种声波,其声速 c 与温度有关,在不同温度下的超声波声速不同。在使用时,如果温度变化不大,则可认为声速是基本不变的。如果测距精度要求很高,则应通过温度补偿的方法加以校正。声速校正后,只要测得超声波往返的时间,即可求得距离。不同外温度下的超声波声速表如下
3 ?' D1 E7 r! t4 X+ F1 K( @* @& d; |7 Y, {2 ]+ B. G0 X
# w' g+ W$ _1 S8 H) o不同温度下超声波声速表
/ Z' Y8 e5 p( \4 u8 g( q {) h0 u) Y/ b* I
补充说明0 [# X, G, n) Q
最近在问答区发现有小伙伴疑惑为什么在计算距离时是根据Echo输出的高电平持续时间来计算距离。这里简单解释一下,仅供参考
c+ W6 x$ h0 {4 X' _1. 实际超声波测距的原理就是上面介绍的,记录的时间是从超声波发出到回波被接收的时间,然后这个时间除以2,再乘超声波的传播速度得到距离。超声波测距模块实际也是这么做的。模块内部会记录超声波发出的时刻,在接收到回波后会立刻以高电平的形式从Echo引脚输出高电平,高电平的持续时间就是超声波从发出到被接收到的时间间隔。这么做是模块为了方便单片机处理,快速得到超声波从发出到被接收到的时间间隔。2 {* ^, E6 f; x- Z9 W
5 x8 K% U- d% Y/ E2. 实际大家可以自己测试一下,初始化一个定时器,从给Trig引脚10us高电平结束时刻开启定时器,到Echo引脚接收到高电平的时刻停止计时,用这个时间来计算距离,实际和用高电平持续时间来计算距离得到的结果基本是相同的。9 G. i% Q" p( s# w2 \
4 Y: s9 Q5 x3 I3. 给Trig引脚10us高电平并不是说只发送10us的超声波。10us的高电平是为了触发模块工作。10us高电平结束后,模块会发送一个8个40KHz的方波。
& e: d- \! S3 Z2 c A/ B
2 D, |5 b$ V' `5 | q6 R- c+ Q
. B1 C+ h/ b: V/ u# K; ~4 Q0 ?( W( g二、超声波传感器简介
. H _+ b1 O% c# a% r总体上讲,超声波发生器可以分为两大类:一类是用电气方式产生超声波类是用机械方式产生超声波。电气方式包括压电型、磁致伸缩型和电动型等:机械方式有加尔统笛、液哨和气流旋笛等。他们所产生的超声波的频率、功率和声波特性各不相同,因而用途也各不相同。目前较为常用的是压电式超声波发生。
2 y) Y; ]3 T. e J6 U& g7 N D5 @4 x( b$ J, N1 d
压电式超声波发生器实际上是利用压电晶体的谐振来工作的。它有两个压电晶片和一个共振板。当它的两极外加脉冲信号,其频率等于压电晶片的固有振荡频率时,压电晶片将会发生共振,并带动共振板振动,便产生超声波。反之,如果两极间未外加电压,当共振板接收到超声波时,将压迫压电晶片作振动,将机械能转换为电信号,这时它就成为超声波接收器了。这里介绍的是一个超声波测距模块——HC-SR04。. K+ b7 @0 n4 B& Q+ v1 {
! C" k4 Y7 t# Y5 S' r' ] c# o2 g0 }) n& Y8 }
HC-SR04 9 W1 p9 C) K& o9 ]( `& S- v
: a# ]/ j& ?) I6 B
9 }" Z% N* J- F, A
三、HC-SR04测距实现思路
& n, _0 N( F( D1 U, ^) D这里就不再针对HC-SR04模块的原理和电路做详细介绍了,直接介绍利用该模块实现测距的思路。该模块有四个引脚。* P7 I0 ?! v/ [% |. [
• VCC —— 通常是5V供电7 k7 {1 H# b% b
• GND —— 地" T9 O" }: O1 h* X D+ a8 J% u
• Trig —— 给该引脚大于10us的高电平,超声波发射头会发送一个超声波信号
4 ?3 Q8 h7 ~/ _• Echo —— 该引脚在接收到返回的超声波信号后会变为高电平. L$ a" @' j- c" S
) A3 M9 V2 d# f, F) V# P3 r
Echo接收到的高电平持续时间即为超声波一个来回所用的时间,利用该时间除以2再乘上光速,即可得到测量距离。! Z0 F. l( J8 l% w
% ^/ w0 J; M$ C% H% u8 C% ?3 ]# ~0 j/ G! [
四、超声波测距程序实现
* L4 A+ M: L9 p1 I4.1 HC-SR04初始化程序5 ?: Q7 f# l4 M6 ^. |1 D- [. V W* D
HC-SR04初始化程序主要包括两部分,一部分是初始化HC-SR04的GPIO,Trig引脚设置为推挽式输出,Echo引脚设置为浮空输入。另一部分是初始化TIM2。初始化TIM2之后,没有立即使能,而是等到Echo接收到高电平的时刻,开启TIM2。
7 H; h% d4 l$ L, u* U" S2 L- /*+ T7 J: G5 R! [
- *==============================================================================. l7 @5 Z1 ^* a% T
- *函数名称:Drv_Hcsr04_Init
4 m7 _, g2 t( _2 x, @8 | - *函数功能:初始化HC-SR04
$ `9 Y S- a# }# h" n; L2 o - *输入参数:无8 K' _! ~$ g' k" M9 w% a
- *返回值:无
3 N9 p9 U- m- ? - *备 注:初始化HC-SR04引脚的同时,初始化了TIM2,用来记录高电平持续时间% d1 R4 W) R- L) G; U% w0 @) ]
- 初始化完TIM2后,没有使能,当Echo收到高电平后使能
, n, u4 N! s2 K# e6 V6 n - *==============================================================================4 a# C: V d4 @ l$ m3 \0 {% ^( G
- */
1 i' ~( W+ `! k5 }! U) g9 G# S - void Drv_Hcsr04_Init (void) // Hc-sr04初始化% o8 B$ W* ^% K% j( m- U/ g
- {
6 b! r* W( [. b1 D8 i4 G - // 结构体定义
$ {6 t$ ]) E, v1 i2 \* j; q* J' o - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 生成用于定时器设置的结构体
8 e4 s( }$ t X. b8 z( d - GPIO_InitTypeDef GPIO_InitStructure; // GPIO结构体
% f! Q b5 _2 M4 L! o - NVIC_InitTypeDef NVIC_InitStructure; // NVIC结构体# t F' e7 o, K' a+ X U, \, j+ W
- RCC_APB2PeriphClockCmd(HCSR04_CLK, ENABLE); // 使能GPIO时钟
4 v2 q' @2 {" r- B& h - , q9 z( Y# ?$ z$ u/ l. X
- // GPIO初始化
% t! Q* d J" ~& k - GPIO_InitStructure.GPIO_Pin =HCSR04_TRIG; // 发送电平引脚
6 @7 j6 Y. ^* m/ h# |) a X4 W - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
% C& d+ M' q% V6 p" U - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽式输出 . b3 H, r" w, i; ^
- GPIO_Init(HCSR04_PORT, &GPIO_InitStructure);
2 C4 @$ H j2 W' q; q2 B - GPIO_ResetBits(HCSR04_PORT,HCSR04_TRIG);
$ ]/ S) e& Y! N - 6 h( a% n2 d( A. e$ X& L; V3 e$ {
- GPIO_InitStructure.GPIO_Pin = HCSR04_ECHO; // 返回电平引脚
" w& @( f. {6 E% `- G - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
" {7 |& O3 e' @ - GPIO_Init(HCSR04_PORT, &GPIO_InitStructure); / K$ U' Y g5 Y. ?- x
- GPIO_ResetBits(HCSR04_PORT,HCSR04_ECHO);
+ O! ?2 o0 q5 l - # ^ p5 {+ u, c( y$ J) l5 U
- // 定时器初始化 使用基本定时器TIM2
$ C2 X- E2 @4 \! t# G9 [ - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能对应RCC时钟
8 O5 M/ Q ]6 n. _0 _; d( t9 { - // 配置定时器基础结构体 5 J% e* O/ X7 \% N, |! U& ]$ O( {
- TIM_DeInit(TIM2); * L: W" o0 {. `+ @( H% U. ]0 W
- TIM_TimeBaseStructure.TIM_Period = (1000-1); // 设置在下一个更新事件装入活动的自动重装载寄存器周期的值(计数到1000为1ms ) # [& u5 w# L9 t( C% l" r! x/ ~' Z
- TIM_TimeBaseStructure.TIM_Prescaler =(72-1); // 设置用来作为TIMx时钟频率除数的预分频值 1M的计数频率 1US计数
r! ]3 R; K, [$ q - TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 不分频 ) u' o) j1 U7 ~' s
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // TIM向上计数模式 - q Y. O6 S) \' _
- TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位 ' Y; E( E" K; e
-
& y. X L# ~6 M5 b- z - TIM_ClearFlag(TIM2, TIM_FLAG_Update); // 清除更新中断,免得一打开中断立即产生中断
# z( N* g8 Z3 G# e: x, L1 V8 [1 b - TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); // 打开定时器更新中断
" U8 B& Z/ M! ]! j# K5 S8 }' j3 ? - . M, f) ~2 M% t U: {4 m, J
- // NVIC配置
: Y: a0 l: n; p - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
8 l |' r9 \/ G* Q* o -
% z6 i% f& f. V' x5 f, X - NVIC_InitStructure.NVIC_IRQChannel =TIM2_IRQn; // 选择定时器2中断 % q& m* y7 D% `
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占式中断优先级设置为0
8 e4 F- m$ _! r$ n- Q - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 响应式中断优先级设置为0
4 }' R; z" t6 i- T9 O9 A- a/ h - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断 ( U* X- p- }( w
- NVIC_Init(&NVIC_InitStructure); 3 O- }7 d) y& B6 u1 g" C8 s# S
-
0 ~3 `2 {! f: g - TIM_Cmd(TIM2,DISABLE);
$ Y- e" t% v1 I; \ - }
复制代码
' s! d' [. i+ D7 X1 D+ H* v, }4.2 TIM开关程序
1 @! F5 Y; h7 A. c- /*
& y' G* f; F+ M - *==============================================================================
5 [ B0 i. E; B; Y/ g - *函数名称:Drv_Hcsr04_OpenTimerForHc
. Z" Y; e; {% f; p: [ - *函数功能:打开定时器% X/ Q( y" l" ]
- *输入参数:无
" v4 g6 s8 d% L - *返回值:无* ]3 P8 V' B7 M
- *备 注:无: W0 N/ s, M" D, ]2 B
- *==============================================================================
) B$ Z; U; y, F3 A# o# G& j5 |% A - */# Q* }& r" t- Q- S3 r$ U
- void Drv_Hcsr04_OpenTimerForHc (void) // 打开定时器 4 K9 o+ Z: S' ]* T$ G
- {
* O8 d* ~' v+ S$ F9 Y j - TIM_SetCounter(TIM2,0); // 清除计数
! _6 n4 S$ g7 h - gMsHcCount = 0;
! q/ }. ~9 O( v, C - TIM_Cmd(TIM2, ENABLE); // 使能TIMx外设
+ u; X& }' R" Q& [' ^ - }
. Y$ u1 A s: M9 W6 H' A* f' f - /*& I ?1 V, g8 _% U0 F$ w$ y
- *==============================================================================. ^4 R3 r ~* E
- *函数名称:Drv_Hcsr04_CloseTimerForHc ? W9 R- P, V) w/ E" ]) t
- *函数功能:关闭定时器5 F/ k; ?3 ]8 }. L; w7 F
- *输入参数:无
; }/ ~6 A4 M7 a' k - *返回值:无 m k- ^: `: i- ^, ]2 R0 i
- *备 注:无
0 U, o8 T7 t7 ?' _( X+ t, B, B - *==============================================================================3 \2 R$ d( U! N' B ~# O
- */
. d7 Q- Q9 z5 t0 s8 v9 B1 e - void Drv_Hcsr04_CloseTimerForHc (void) // 关闭定时器
6 ]7 K1 b+ l( p! N: B - { ' N: Q3 }& F* Z5 O2 ^, A
- TIM_Cmd(TIM2, DISABLE); // 使能TIMx外设 $ m5 Y" W5 x( h8 V7 C5 m# r. W
- }
复制代码
+ o+ J2 l) G& i定时器中断服务函数如下* U0 U/ r5 h$ V+ O: c7 j$ N6 N
- /*
" h1 T7 o, A1 b8 Y7 f$ Q - *==============================================================================
0 O: K }; D, C {, j3 ]3 I( \ - *函数名称:TIM2_IRQHandler9 P* o2 K! V4 i
- *函数功能:定时器2中断服务程序 9 a0 `7 M `: e2 J8 x6 E
- *输入参数:无
2 f! [: c( {1 J - *返回值:无& g3 C: |2 x0 U: B- W
- *备 注:无9 q ~& N# K$ o/ u4 }0 C
- *==============================================================================
9 m6 V5 m3 T. B5 v - */5 s' I5 \& @, J- L
- void TIM2_IRQHandler (void) // TIM2中断 : L! W) |% ?, j
- { 4 a; r1 |, {( X3 i4 p
- if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) // 检查TIM2更新中断发生与否 9 E' M0 a4 n/ i0 |, H4 B
- {
5 ]! m! W+ R' F9 b) l - TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除TIMx更新中断标志 7 j4 d+ e [0 _! ?# L* j
- gMsHcCount++; & ]/ E$ C& x$ z) L0 U
- }
! V0 U5 z: U5 s5 _% q5 |- z$ b - }
复制代码
+ G7 Y6 O8 a" B5 L0 ?% l( n4.3 获取定时时间. C% Z l# i; ^3 G6 {+ t' w/ u
- /*8 u; {( t0 ^/ T% c2 y
- *==============================================================================
2 I8 L4 y8 E0 s3 r t4 M: T - *函数名称:Drv_Hcsr04_GetEchoTimer+ _3 J+ ^4 D. y& F
- *函数功能:获取定时器定时时间
; U/ g( V' U7 T9 V - *输入参数:无
8 g, G% j$ N' j' {" S% O/ m& J - *返回值:无. B& }( j7 a+ x1 Z5 I
- *备 注:无+ ]+ z0 ?& Z9 p$ W
- *==============================================================================1 k; e$ i2 j2 c& n0 i
- */3 y0 N! J2 c$ p1 T; _, k
- u32 Drv_Hcsr04_GetEchoTimer (void)
) \. r7 ?7 W1 O! M/ F% v% Q - {
7 v5 ]' {# @# e4 X* P6 H - u32 t = 0; 3 l7 p c3 y; t( X: Z0 a
- t = gMsHcCount * 1000; // 得到MS
0 }3 w& ~% A! U' N - t += TIM_GetCounter(TIM2); // 得到US
2 W( q7 s0 e T5 y: M1 r& C8 u9 ~ - TIM2 -> CNT = 0; // 将TIM2计数寄存器的计数值清零
$ Q8 ]) W# y8 I1 F: s d - delay_ms(50);
" L6 U5 y0 E; ]$ a3 b! }% }. v - return t; - ]7 B: }" W2 R5 \
- }
复制代码
5 b7 u l# \ _: n$ A& G" G4.4 计算测量距离
: [- [2 } r. v- /*
; F# s/ v4 a7 @ - *==============================================================================. n1 k" f. w! }5 c8 p
- *函数名称:Med_Hcsr04_GetLength
6 r3 ^; @3 ?1 g, z9 Z+ q - *函数功能:获取测量距离
; t2 ^& f& B$ X" l( e" R - *输入参数:无
# g, t7 ]+ b3 v3 Y8 Z - *返回值:无
" ^! |! b6 Z9 V5 i7 N \, w, m - *备 注:一次获取超声波测距数据 两次测距之间需要相隔一段时间,隔断回响信号
1 W, T3 I; N) H" i, w - 为了消除余震的影响,取五次数据的平均值进行加权滤波
- M* ]) D1 l. ?0 T - *==============================================================================! K2 d+ A- @# O$ J# C5 R9 g
- */
7 @+ ^# m- w3 P. Z - float Med_Hcsr04_GetLength (void ) D' k% }; b0 F$ G2 e
- { ; |/ k/ O6 ]: V, B- I
- u32 t = 0; 5 t0 B/ M" `( Q/ Z0 F# y9 u# H) t! F
- int i = 0; 0 u& o5 t/ ]' P
- float lengthTemp = 0;
# c! r5 C8 h$ {8 g* J - float sum = 0; # j' F5 a3 W! | I1 f# F1 Z
- while(i!=5)
8 _% m) _! w$ J3 Y' K! w! z* W - {
; h3 `% r5 w' q/ R1 E/ r - TRIG_Send = 1; // 发送口高电平输出 # C9 B% ~$ n. }" A9 \! e* r+ o8 s5 G
- delay_us(20);
- ?0 w1 b4 r6 J# n/ B9 ` - TRIG_Send = 0;
, {* A6 j: d& j - while(ECHO_Reci == 0); // 等待接收口高电平输出 $ v+ Q" g4 L) H
- Drv_Hcsr04_OpenTimerForHc(); //打开定时器 9 B, D7 A1 S9 ^
- i = i + 1; ; h. {. X, R, S& n7 [" g3 e
- while(ECHO_Reci == 1);
3 i/ M3 ^5 x0 T8 n: g - Drv_Hcsr04_CloseTimerForHc(); // 关闭定时器
1 h2 _# U3 p& W. U2 d - t = Drv_Hcsr04_GetEchoTimer(); // 获取时间,分辨率为1us+ J) X0 i, ~) D2 b5 a- G
- lengthTemp = ((float)t/58.0); // cm & t) }6 u4 o, W' B5 @9 {
- sum = lengthTemp + sum ; 8 J: F$ \+ J8 f6 u+ K
- } 6 A/ N& C z1 G% `1 T2 ^: k3 q
- lengthTemp = sum/5.0; % Z. U" u' \& |6 E& s
- return lengthTemp; . x8 H( \! W7 d" u" \7 a5 ^
- }
复制代码 / _. \/ v: E& |6 w
4.5 宏定义
- g& l- h, a) m# L- #define HCSR04_PORT GPIOB // 定义IO口- D2 m" W6 A" H9 c# t
- #define HCSR04_CLK RCC_APB2Periph_GPIOB // 开启GPIO时钟
0 i1 |- z d' n) z7 D - #define HCSR04_TRIG GPIO_Pin_8 // 定义Trig对应引脚
; F2 r% X* N# g- H - #define HCSR04_ECHO GPIO_Pin_9 // 定义Echo对应引脚+ o( ^+ O% o# N M/ i- ?
-
) H- ~! W; G; x9 R3 _% ^+ p - #define TRIG_Send PBout(8) // 将TRIG_Send映射到PB89 l1 A7 a( f& R
- #define ECHO_Reci PBin(9) // 将ECHO_Reci映射到PB9: j# G/ d3 Y: Y5 N1 r: o
$ B% W' ~- N2 B/ e- void Drv_Hcsr04_Init(void); // Hc-sr04初始化
& I* y4 u* O! P# J& y) q; _$ O - void Drv_Hcsr04_OpenTimerForHc (void); //打开定时器- Y* m- }; ]$ ^+ Z7 ]
- void Drv_Hcsr04_CloseTimerForHc (void); //关闭定时器
6 J9 c0 z. r. P/ K8 [; Q9 E5 ~ - u32 Drv_Hcsr04_GetEchoTimer(void); // 获取定时器时间
复制代码 # r& y! K3 t! b Z
* Z3 z& v2 l" d1 L! k: o/ k$ ^
五、应用实例
3 y2 B$ I ?, p5 z G0 A利用串口打印距离信息,main函数如下
* I, M2 i7 w6 h' Y2 H9 U- float gDistance = 0; //定义获取返回距离变量
2 C( Z: r, v& |' A - 0 |4 e+ Z% }( Z, i/ q! Y
- int main(void)* i6 I8 ]2 P n4 I
- {
; K# i. \1 y" Y- v" B& W - Med_Mcu_Iint(); // 系统初始化
3 A5 X# L* X3 Q - ) R ]* j$ C+ d8 ^0 i' t
- while(1)& s* Q1 S: R V7 Q/ y
- {3 |+ M+ K' h1 k" H, x/ z
- gDistance = Med_Hcsr04_GetLength(); //获取返回距离
; E" \9 t1 S$ g+ x - printf ("距离为:%.3f cm\n",gDistance); //串口打印返回距离
8 Q3 w' M) p b+ D6 Q -
+ F( Z+ A6 j: q3 ]1 h1 j - delay_ms(500); //延时500ms = 0.5s8 n% T b9 c- q4 ^! e# t
- }
, L* G0 }# U5 N - }
复制代码 * Q3 z. D" d; {8 @
六、拓展应用4 p6 G, b6 C4 O2 s7 z+ a
超声波测距比较常用的,比如利用超声波测距模块实现智能车的自动避障,这个在后续实战项目系列中会有,在此就不再详细介绍了。主要思路就是根据HC-SR04测得的与障碍物的距离,来决定是否要停止或转弯,以及往哪边转弯。
" e h1 y6 ^' \2 N2 b3 h% [/ W: T% r( m$ s) Z% C2 J
转载自:二土电子2 {1 h. j; H% K1 H7 M- \
如有侵权请联系删除( H+ N3 ]" |) C. i/ R4 l7 a3 G. w$ Q
& G7 X* b( w3 @0 P
0 X. Q1 H0 R7 d! S% g* X$ H |