超声波测距原理' r3 R, ^! J& T0 H5 F
利用HC-SR04超声波测距模块可以实现比较精确的直线测距,其测距原理图如下:* ~! u. Y' j) R, D1 Z9 H% R# S% k" Q: q
. j6 V! Q( `! d' z
" G% G$ z" P7 Q# A5 w' s
. \% I+ ~) v4 G! i w: Z( pHC-SR04的一端发出超声波,接触到反射物后反射,被另一个端口接收到,所以只要知道发射和接收的时间差,就可以根据声波传播的速率算出HC-SR04和反射物直接的距离。
( l9 y* `4 Q4 X2 d+ R所以实现超声波测距就需要俩个条件:
! v3 h8 u' \2 u6 z9 i. q2 Y& Z- S% B发射和接收的时间差, Y$ K3 F, q. z G
超声波传输的速率
# M# B7 Z% Y. eHC-SR04工作原理
) Z4 o& U% Y$ hHC-SR04模块的电气参数如示:
6 k8 H, r5 C+ v$ E( k: z( u3 h c, d. b( @. S. h
7 Y1 U% f3 G( w& T+ N/ a- _1 L3 `9 A! m/ e& n) K
HC-SR04模块的实物图如示:
1 v# S9 A3 C3 h8 ~. p
: Q8 J# a# @; |7 ^1 `4 v
. N* b' ~( T% {2 r. v$ ?& e/ D: P+ Z8 w: | `$ M
有四个引脚:
( u% T& N/ q& P2 T$ N$ y! C9 K7 G: }; pVcc:+5V电源供电
* T2 A3 ?+ M6 H+ T. Y U+ \% RTrig:输入触发信号(可以触发测距)
U. E6 t% Y9 }$ h1 y5 |Echo:传出信号回响(可以传回时间差)
, u+ n% G1 U2 k0 _Gnd:接地
, T8 u% T0 \$ W J I1 {
7 A, V, e+ c8 C% U+ Q, k1 X用Trig和Echo引脚实现测距的流程:
: M& Y- z0 V. B) ~: S1.通过Trig输出一段至少10us的高电平(脉冲),触发一次测距,超声波在传输的过程中Echo一直输出高电平。+ I, n8 r; L$ E7 |. i! H7 M
2.在Trig脉冲输出后,立即检测Echo引脚的电平,测出Echo高电平持续的时间t,t就是超声波在所测距离一个来回所需时间。 q' f; ~0 Q- Z0 _- m8 v4 C
: X4 w0 h2 C* ?
测距时序图如示:) M% y7 @- k4 C" K# }& p0 c
. r# n& {+ Y% n5 j
9 i7 ~3 [' @% v g/ ?, L' [2 N
G2 i: a: [- V& dSTM32实现驱动2 _ [( ?$ k) s* g& ~) X' `6 P V
利用STM32驱动HC-SR04需要做好几个关键点:
% C- x4 h0 h7 T+ i7 ^引脚的配置- a% m$ S4 q: ]6 i- Z
时序的控制
7 H* O* s% l. d( H时间差的测量3 _4 A7 \& h8 C2 S
3 k4 ]. e5 X; F7 V下面来分开实现几个关键点
' k! |: e' Q% k/ u9 W7 S0 ?5 ]- e1 ]* t# Z# S( M. R
1.引脚的配置
3 o4 X# Z5 A s* V+ A8 VHC-SR04四个引脚,Vcc和Gnd直接接在开发板的电源上即可,主要是Trig和Echo引脚的配置,我选择了PB1连接Trig引脚、PB2连接Echo引脚。0 U" L/ _. K" T! c4 m& _$ m5 U
因为要控制Trig输出电平,所以PB1引脚模式是推挽输出GPIO_Mode_Out_PP
. i' v% t, A! r0 l& q3 h: ]6 ]1 iEcho要检测高电平持续的时间,所以PB2引脚模式是浮空输入GPIO_Mode_IN_FLOATING( B4 A t, v2 ]! {( B
相关的配置代码如下:
; [5 ^% r+ @0 L- void SR04_GPIO_Init( void )
, u( o, F* F4 q! f8 z4 e. O - {
# O# e( T) H0 V M - GPIO_InitTypeDef GPIO_InitStruct;* Z! z. V( h% c. T
- RCC_APB2PeriphClockCmd( Trig_Clock |Echo_Clock , ENABLE );" }' q* n0 q4 O. @2 _# i9 h
-
+ W2 s% {3 L( [6 _& u8 K! { - GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
8 m1 e9 h3 S/ I) ]% r, [ - GPIO_InitStruct.GPIO_Pin = Trig_PIN;- B6 D( j+ m( D/ k' g+ J& l3 T
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;9 f2 |2 v9 E4 _/ r; \) U
- GPIO_Init(Trig_PORT, &GPIO_InitStruct);
* k0 [( F% G" w! [. V1 I4 x - 0 Q1 n. _: P6 k% O8 h
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
$ C0 p4 z \9 |, g0 W2 M - GPIO_InitStruct.GPIO_Pin = Echo_PIN;8 r) e0 L9 h2 j9 |
- GPIO_Init(Echo_PORT, &GPIO_InitStruct);" F5 Q! Q# U/ G! j) F" ` I$ l
- }6 F" c/ l; J6 l# D' E
复制代码 ; l3 X7 o/ Z1 s2 o) A# u
2.时序控制 P3 M( c9 O* n3 M& q$ _" ]& x
HC-SR04的时序是:先来一段10us的Trig高电平,接着接收一段Echo的高电平,伪代码如下:+ G9 N: M: T1 s. ~
- #define Trig_H GPIO_SetBits(GPIOB, GPIO_Pin_1)
# ]7 m% e N# B, d! [; E - #define Trig_L GPIO_ResetBits(GPIOB, GPIO_Pin_1)
$ N: K* E+ _+ }% ] - & a3 c$ X3 {$ k: q
- /* Trig给一个至少10us的高电平,超声波进行一次测距 */' D% ?4 a; e- ?. |/ F3 S
- Trig_H;
2 b+ o2 ~2 m8 c# T) N# J9 b - Delay_us( 10 );
3 f. v6 ?1 l$ u+ l' ] - Trig_L;' J" P6 r" d* W V. h! a
- /* 等待Echo高电平 */5 ]! h0 p- C: m- v b6 ?) Z9 A
复制代码
* F2 N2 k* P) e7 P3.时间差测量
+ z0 w' s- F, l这个是最重要的一步,要测量Echo高电平持续的时间,因为光传播的速率是340m/s,而测距的范围大多是cm级别,所以相应Echo高电平持续的时间也就是us级别的。
! v1 y7 I, Y7 P6 T所以,测量时间差的条件就比较苛刻,我是利用SysTick(系统计数器)的原理实现计时的。SysTick计数器原理是对通过SysTick_Config()函数配置每俩次中断之间的节拍数,也就是俩次中断之间的机器周期,我大概算出了,测出0.1cm距离的Echo高电平时间约为6um,而系统时钟的频率是72MHz,所以配置每俩次中断之间的节拍为432的时候,进入一次中断就代表0.1cm的距离,所以只需要记录进入中断的次数就可以算出距离。通过一个全局变量在中断函数中自增来记录中断次数。SysTick_Config函数源代码如下:* f0 H4 Q* h( D/ ^) b; ^: s' d
- static __INLINE uint32_t SysTick_Config(uint32_t ticks)
9 j' X0 p4 N9 J% R9 Z) W - { / P1 R0 @1 ]+ a7 J- }, T7 L C. \
- /* 判断ticks 是否超出装填值和重装值的最大值 */
9 ^5 t5 a Z/ I - if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); % c6 P1 k; P1 P/ t- Y
- 9 g0 N4 r7 @* t- [( o
- /* 配置 装载寄存器 */ 3 s+ q8 p/ }3 T
- SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; ; U5 H$ R$ u* d# x& D
- /* 配置 内核中断的优先级,也是在NVIC中 */ Z' h. x1 } ?4 s
- NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
% F0 {$ B5 O0 ]7 ~1 Z, y - /* 加载计数器的值 */9 A4 e2 \" s9 W, f
- /* SysTick->VAL是当前数值寄存器的值 */
% v1 Q# t% k. I: E - SysTick->VAL = 0;
( {$ J2 j" {1 s -
1 g/ Y, _& J/ m- s - /* CTRL是SysTick控制及状态寄存器:
! s. I8 a6 r1 i- [8 I: [ - CLKSOURCE:位段2 时钟源选择,0=APB/8;1=APB APB即72MHz
) C' A% Q% B/ L+ N - TICKINT: 位段1 当置为1时,计数器递减到0时会产生中断请求;当置为0时无动作7 Z- E1 `! p1 q, }5 p
- ENABLE: 位段0 使能位,可以启动SysTick定时器*/ J. o# b2 y! Z& Y' F" Q5 H
- SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | ; v# v6 u7 b I w4 V, S' I
- SysTick_CTRL_TICKINT_Msk |
/ r; S; ^. S! Q - SysTick_CTRL_ENABLE_Msk;
- p8 }# g$ G2 F3 u& H: f4 O - return (0); - S6 g1 @" k* T
- }
, z+ v, ~! G! W- }% ^* U$ f - 6 B6 @& k; Z' n3 X5 c' V
复制代码 , h) {+ h: w: K8 E+ {- Q4 n
SysTick的具体原理可以参考一下我之前的博客:SysTick原理
9 N: T+ y' |: F" d
/ v) T* D; N$ Z. ?注意:SysTick_Config()函数执行完就开启了中断,所以必须在Echo为低电平后及时关闭中断,并且将记录中断的变量清零。2 N& x- u& D' L$ r, y/ S) l: K
. m/ @4 l4 E- U; i; \中断函数如示:
5 k) c* y$ ]0 `( N- /* 用extern和volatile关键字修饰的 全局变量n */5 y% r2 N' `$ N, h
- extern volatile uint32_t n;, |8 U+ Y6 G/ r" ^# Y5 b
5 ~* ^' y, G- @* v% |, z1 }: F2 H- void SysTick_Handler(void)
5 j3 I% x5 { Q - {7 S: k. n& H2 g9 @; _
- n++;3 ]8 | b$ K, p/ U) g" V5 n* |4 A. d
- }
! w2 [* Z5 B. I! S. ]2 E5 B! Y, ^
复制代码 ; s9 b K& o8 h* F8 H9 D! v7 O
关闭中断及清零n的代码如下:: M9 W' W% M3 ^8 I$ Z% A
- /* 本来的使能位取反 */! t }+ U3 M8 A- A: l5 Z7 `
- SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;5 E4 M, H& s& }. u, g
复制代码 2 p! f) ~3 U8 o- E$ f
SysTick->CTRL寄存器的0位控制着中断的使能,具体情况在之前SysTick的博客中已做详细说明。
' K& i T3 `$ ^, y) M/ l
M6 P2 A( }) ~- P$ A2 A4.如何将距离测出来0 E! G! A7 A; }
我在main函数中实现了距离的测量,并且通过串口打印函数将距离传到上位机,具体代码如示:
+ p- X( F" Y, z0 z, P. i* J) z9 D- int main(void)$ @! Z; Q9 {; q) K
- {. _7 s9 R* |9 l' P- G! V
0 ^4 D/ W: Z% `% z1 b# b+ B- int i=1,q;% ~4 }1 S& e7 H% L. m4 P9 N, y
- float p;
) H* Q0 Y2 I* @" z ` - /* HC-SR04模块引脚初始化 */
4 Z: B' C& ~# l* H7 n7 F; { - SR04_GPIO_Init();" O. }: x }. L5 X
- /* 串口相关配置 */
- u0 ]( Y9 }0 s6 w: Q - GQ_UART_Config();/ ]7 D) ~4 e! J" w
- /* 打印调试信息 */3 L9 n% D" x# e6 T+ Y
- printf("慢漫的测距实验\n ");
7 B. x: z' T; x - ( Z# U/ H8 T1 [4 N0 @; N1 r- O
-
6 e# c9 q+ ]3 a: x; a& u) R3 J4 J - while( 1 )
" Z. e6 {8 Z8 i, Q - {+ E/ r: A8 X: i
- /* 每0.5s测一次距离 */
8 ^( l+ n& w# X! ?2 r# e% a. x M - Delay_ms( 500 );" v; q* W& F0 Y& M9 w, S4 ~
- # P- {0 U! d$ F* b$ y. s
- /* Trig给一个至少10us的高电平,超声波进行一次测距 */8 J" N5 b/ z+ ^9 E. t: U
- Trig_H;- A; b0 D/ W: v
- Delay_us( 10 );
3 G/ x7 q9 j$ y# v: ]+ p7 D - Trig_L;2 D$ ~3 u K1 Z+ o" a& @- z
- /* 等待Echo高电平 */
! N& F9 z" n! X7 n) S* R' G - while( Echo_Value != 1 );
6 M7 E; r8 i' g - /* 打开中断,对Echo高电平时间计时 */
3 H9 o+ s* `- ? U: k) s - /* 配置计数器的装载值是72*6=432,即一次中断6um,正好是超声波的0.1cm,所以中断次数n对应着n*0.1cm *// V8 U2 L3 p7 X+ o2 _( j1 u; [$ ?
- /* SysTick_Config()中已经使能计数器了,所以无需再开启 */
' j" u, @" q) I$ S3 B) m! E( n9 R8 S - SysTick_Config( 432 );. m; m. U) l$ W6 P# T* [
- /* 等待直到Echo为低电平 */
% y+ R0 B6 O0 c; w - while(Echo_Value == 1);
1 ]# T b) A% _3 q7 E - /* 关闭中断,通过参数n来取得距离参数 */4 V' @, P5 L5 j
- /* 本来的使能位取反 */
0 e- Z5 x9 _+ D1 _ - SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
% I+ x; m( i0 {; u/ u - /* p、q分别是距离的整数部分和小数部分 */3 g+ a9 D3 z9 C
- p=n/10;
9 t( A4 Q- r4 K2 [9 v; i. {2 X - q=n%10;
6 E8 l3 O9 u) Q3 u; m - /* 打印距离信息 */% z3 K+ u+ n2 I1 P4 P5 `" D0 e3 i
- /* p-50时经过调试的,因为测量的距离和诸多因素有关,这个操作减小了误差 */
8 b7 K$ o: p% ~! ^4 Y - printf("第%d次测量为:%.0f.%dcm\n",i,p-50,q);" k8 s$ W. h- y* E3 e: K
- i++;! b& m- _$ _0 v# w3 i- B4 S
- /* 清零中断记录变量n */
9 `$ F; s' B+ x. S; X- B - n=0;
3 |1 o% A: \' [# K7 p, t* y7 n' o! n - }
4 p0 O+ H6 Q2 `- t3 u3 W6 P4 n -
) _+ X& m: ^. X0 G: ? - }
2 e) @: p, u, j8 m+ y
复制代码
+ M8 c0 a0 n! l& `5 g4 d————————————————
3 B7 v" u, x7 e) |版权声明:Aspirant-GQ1 d2 v& W3 R. u5 U7 s
如有侵权请联系删除
) R" W8 k* l" U# g) l' H# W! {( `1 i; s' A
|