超声波测距原理
+ E( L% e( d2 C, ~* a& l利用HC-SR04超声波测距模块可以实现比较精确的直线测距,其测距原理图如下:
* `, v) ]1 h7 R! P- \( \0 b" E& s) Y! Y1 R
( C1 @5 y Y* t8 i0 Z# s
$ G" h$ x2 Q, j* w7 Q
HC-SR04的一端发出超声波,接触到反射物后反射,被另一个端口接收到,所以只要知道发射和接收的时间差,就可以根据声波传播的速率算出HC-SR04和反射物直接的距离。2 f' n8 b$ K+ b
所以实现超声波测距就需要俩个条件:* ? H% W" v; z* s" u8 w+ E2 u$ r% [
发射和接收的时间差
/ F; y3 N5 r1 r2 ^% I超声波传输的速率' _6 U" D% E) Z5 M6 n0 Z) _8 M
HC-SR04工作原理
3 r+ Y9 S2 a0 K, ~" eHC-SR04模块的电气参数如示:
4 c" K7 C0 n' ^: t0 O
. d5 C; D: D9 }0 C* C% L! N1 d. q
- ?& R" j- `! B7 w5 m
" ]" b: t( I6 b2 Y+ v/ T
HC-SR04模块的实物图如示:* u* [+ L6 k, V) [& R3 o h
, n/ V2 k& |3 i1 O. Q# c9 F$ l) ?
4 ^2 J- C- J1 b. M+ T7 E& y4 k6 S
2 p7 }: ?. _$ S3 f# {
有四个引脚:
, C4 ?$ F- _0 n7 }& f0 CVcc:+5V电源供电
+ }, c2 E3 K6 X9 z0 Z7 ETrig:输入触发信号(可以触发测距)
. ^" ^6 Z" h" B" s: k& E2 W% AEcho:传出信号回响(可以传回时间差)
# [4 e/ D9 g& y4 ^4 wGnd:接地
* m; E2 [- p& x/ |: F) A: w6 k# y' \5 R0 x7 _8 g
用Trig和Echo引脚实现测距的流程:2 \& k( o1 y, {6 j+ t
1.通过Trig输出一段至少10us的高电平(脉冲),触发一次测距,超声波在传输的过程中Echo一直输出高电平。" e2 J3 w, ?; s8 C4 m1 y
2.在Trig脉冲输出后,立即检测Echo引脚的电平,测出Echo高电平持续的时间t,t就是超声波在所测距离一个来回所需时间。 ?. _5 D2 t" `& ~8 l* {
8 t# }/ x' U0 Y' K测距时序图如示:
; V% w( u N7 m7 q; b% |; L+ U8 A0 v5 a6 h- G
! `/ V& E# ^4 U
- h1 y/ t; @; B% V
STM32实现驱动: L# d9 k: ]% l7 _2 X
利用STM32驱动HC-SR04需要做好几个关键点:
6 a$ \1 Z1 B1 m! d7 j2 G引脚的配置
1 D0 B; x9 J) z6 v `时序的控制
: I, R) ~& e" j1 W* K时间差的测量
$ M0 c3 O1 T( ]% {
) j4 u" t, v8 a/ ~ |' T1 y下面来分开实现几个关键点
) N3 _" J7 w2 ^- ^& R5 R. O3 S# T6 N3 Q" k
1.引脚的配置
7 J: x g0 G5 h& F2 w" Z4 T2 Q! j, e$ qHC-SR04四个引脚,Vcc和Gnd直接接在开发板的电源上即可,主要是Trig和Echo引脚的配置,我选择了PB1连接Trig引脚、PB2连接Echo引脚。% h1 C/ J4 x. e( W& X# P
因为要控制Trig输出电平,所以PB1引脚模式是推挽输出GPIO_Mode_Out_PP
4 z7 p# H Y, m3 t' b3 TEcho要检测高电平持续的时间,所以PB2引脚模式是浮空输入GPIO_Mode_IN_FLOATING
6 h9 b; f: Q3 e# ?; x" I/ K0 O相关的配置代码如下:* C% ?0 O5 R! D) f; F
- void SR04_GPIO_Init( void )
3 u" L. m0 D! n0 a& z+ }% Z: z - {+ b3 Q8 ]7 o. p$ e$ i
- GPIO_InitTypeDef GPIO_InitStruct;
6 U7 ^3 e# t0 |2 P' {0 o9 x* n; j - RCC_APB2PeriphClockCmd( Trig_Clock |Echo_Clock , ENABLE );
+ y: N$ e/ w! T - " i1 \ t$ ~' r( S
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;) `0 b1 y1 U/ l" _ ?
- GPIO_InitStruct.GPIO_Pin = Trig_PIN;. H" n6 ?5 h; [, \- u% ^
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;& \% G& b3 {2 U, Y% G" T/ x
- GPIO_Init(Trig_PORT, &GPIO_InitStruct);
4 A s6 ~ K U; E' {5 y -
1 Z; E1 ]: C. ]3 A) Z$ |- J0 k - GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
2 j! f% j$ [+ r4 O6 P% i - GPIO_InitStruct.GPIO_Pin = Echo_PIN;
6 R: s" d4 M/ r* k - GPIO_Init(Echo_PORT, &GPIO_InitStruct);5 g, S6 H- h: _
- } b3 c, y8 `3 r- r% U/ w) E4 z: _
复制代码 * K. Y& B3 J; L! R) ~# A
2.时序控制
3 }) ]' U" |* C- q# aHC-SR04的时序是:先来一段10us的Trig高电平,接着接收一段Echo的高电平,伪代码如下:" e& x9 \7 y7 F, o- n# W2 L
- #define Trig_H GPIO_SetBits(GPIOB, GPIO_Pin_1)
. f' E1 ?( V- b) }8 f - #define Trig_L GPIO_ResetBits(GPIOB, GPIO_Pin_1). x |7 ~7 H- i+ q' R: \
4 i9 }9 U% `8 B/ h+ _- /* Trig给一个至少10us的高电平,超声波进行一次测距 */# J9 e4 u1 E- ?
- Trig_H;1 w& }0 @2 g% D- ^+ W, ? @0 l
- Delay_us( 10 );. O) L, @! l4 B) j" k+ I h1 z, {
- Trig_L;
7 i T5 P0 K! I- ?% V8 J% `7 [ - /* 等待Echo高电平 */
- C3 g" ~' A" F% A0 F E+ L+ ?
复制代码
: S' y- y% ]: U( X. x3.时间差测量/ _7 }4 i9 v/ O* Q& O# B1 g
这个是最重要的一步,要测量Echo高电平持续的时间,因为光传播的速率是340m/s,而测距的范围大多是cm级别,所以相应Echo高电平持续的时间也就是us级别的。
2 C3 S4 H% @6 N2 f: R所以,测量时间差的条件就比较苛刻,我是利用SysTick(系统计数器)的原理实现计时的。SysTick计数器原理是对通过SysTick_Config()函数配置每俩次中断之间的节拍数,也就是俩次中断之间的机器周期,我大概算出了,测出0.1cm距离的Echo高电平时间约为6um,而系统时钟的频率是72MHz,所以配置每俩次中断之间的节拍为432的时候,进入一次中断就代表0.1cm的距离,所以只需要记录进入中断的次数就可以算出距离。通过一个全局变量在中断函数中自增来记录中断次数。SysTick_Config函数源代码如下:9 X+ G! s+ N- v }/ E
- static __INLINE uint32_t SysTick_Config(uint32_t ticks)
. O0 e# R$ p1 `$ i$ J6 C/ V - { 3 i1 y+ S& ^1 `7 @( S
- /* 判断ticks 是否超出装填值和重装值的最大值 */* J Y7 k/ P0 N2 _
- if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); + g% J/ Q7 X4 v- j: c" v
-
" c+ n x' ]7 {' E0 x - /* 配置 装载寄存器 */ $ ]) L8 ?$ G- }1 b$ c/ A( R) C
- SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
' k) @% B4 K+ x: d$ n - /* 配置 内核中断的优先级,也是在NVIC中 */
+ L3 H4 D7 e x - NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
) k* r- P8 M9 H2 L( H* O - /* 加载计数器的值 */
s) y6 {/ i1 L$ Y; w: x$ n - /* SysTick->VAL是当前数值寄存器的值 */
- i! @2 k# B# }; M: G; r7 H - SysTick->VAL = 0;
4 _. z2 t0 T3 ^. d4 ~: v6 y - + U- U8 N2 E* s H( N: \5 L
- /* CTRL是SysTick控制及状态寄存器:
) E9 [) B% e' Y( s1 d" x. K - CLKSOURCE:位段2 时钟源选择,0=APB/8;1=APB APB即72MHz( D# R. x g4 x* R/ j8 l
- TICKINT: 位段1 当置为1时,计数器递减到0时会产生中断请求;当置为0时无动作( B/ h8 W S. n! `7 ]3 ^0 t
- ENABLE: 位段0 使能位,可以启动SysTick定时器*/7 I3 T& \3 I3 S3 X! D
- SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | 1 V! W2 h& ^& M& u J8 ]( d7 Z1 D
- SysTick_CTRL_TICKINT_Msk |
0 P. B/ Z' s/ Z/ u - SysTick_CTRL_ENABLE_Msk; 0 }4 W2 s' g1 Z6 h: e
- return (0);
) V6 ~' X: H, C - }1 {8 b: a/ q0 F- J; j; t
- # t+ w9 q& {) S& n" j
复制代码
9 J \- T% S" @SysTick的具体原理可以参考一下我之前的博客:SysTick原理
% {5 b' k, Q/ S' Z: {8 Q0 U6 W( g+ C1 U8 |7 d8 ]
注意:SysTick_Config()函数执行完就开启了中断,所以必须在Echo为低电平后及时关闭中断,并且将记录中断的变量清零。& N7 }6 }$ A) G# o$ j8 s8 |
$ `* L% S# O+ G' [) L8 h中断函数如示:" J2 Q9 ~7 m* `# ^) s3 h9 l
- /* 用extern和volatile关键字修饰的 全局变量n */1 v- _6 O1 o8 Y% f0 w n
- extern volatile uint32_t n;4 ^0 V+ f4 `( J: V
- 4 L. f9 i3 h2 l6 p4 i3 f; i# p( e
- void SysTick_Handler(void)
6 D x" U2 O+ I. _% ?2 x0 Z - {
% U7 d( R5 R, Z8 A& m2 B - n++;
4 L* }+ a( ?% M2 G S: }! N - }
) n' t) X y$ |6 x" t0 \3 f; p/ R
复制代码
0 ]4 H( l7 c) |! ?关闭中断及清零n的代码如下:2 v- D% w3 Z. j6 U
- /* 本来的使能位取反 */1 s; u# M7 m( C+ `
- SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
( a2 q) w% H" T; M x
复制代码
7 `, g# `: U4 P; p USysTick->CTRL寄存器的0位控制着中断的使能,具体情况在之前SysTick的博客中已做详细说明。& C' r9 S8 z, g3 [& s" |& ?
& b$ Z( _: t1 p) c1 e5 I
4.如何将距离测出来
8 x+ z9 K& W$ o& s" t& b0 l我在main函数中实现了距离的测量,并且通过串口打印函数将距离传到上位机,具体代码如示:' C; m5 T; b* O( f j# ^5 C
- int main(void)
1 w1 ~; v" c! K1 g - {
3 _) G! s t- N. W# ] - 3 \6 c; Y3 W$ j! \) B9 O
- int i=1,q;
) J& G- b& G" L* I2 x# | - float p;
) `1 `4 S; D L2 | - /* HC-SR04模块引脚初始化 */
# R/ O. u+ c3 L/ Q8 m5 t" O - SR04_GPIO_Init();' H f; w8 r( [% X/ E
- /* 串口相关配置 */! c! ~# ?3 O& b; M+ m8 U1 a6 z
- GQ_UART_Config();
7 Y% \' c! z" S! V - /* 打印调试信息 */
6 i; A8 z) H( k8 W" @ - printf("慢漫的测距实验\n ");
5 _0 c; i8 K0 T. }. ` - ( [6 C0 U w& Z) A
- 2 h. j3 B. \/ f: _0 B
- while( 1 )6 L& L7 _- Y) C+ r1 W4 z( s8 e' P
- {4 {0 R. w: j; j
- /* 每0.5s测一次距离 */: Y* N; [8 ]+ [0 _
- Delay_ms( 500 );
8 T1 Z: G5 } ^4 w -
( F1 w) @' ], b/ s - /* Trig给一个至少10us的高电平,超声波进行一次测距 */& h, y( i' f5 Y) _0 W$ O9 d' S- Q
- Trig_H;
* v3 ^' H% e2 b' p# l& s$ k, ? - Delay_us( 10 );+ c7 g. A: p( f g' V6 u2 f
- Trig_L;
2 g$ S$ @ e f F0 f, l+ ]6 s - /* 等待Echo高电平 */$ q7 D9 y( W1 P, y
- while( Echo_Value != 1 );) a/ m; z2 W# Q! u9 {
- /* 打开中断,对Echo高电平时间计时 */4 g1 x7 v3 E7 Y+ s$ e
- /* 配置计数器的装载值是72*6=432,即一次中断6um,正好是超声波的0.1cm,所以中断次数n对应着n*0.1cm */
) X; _' M4 P: e4 m7 \7 D. P8 C - /* SysTick_Config()中已经使能计数器了,所以无需再开启 */$ `* U! [, ]9 `( u: O
- SysTick_Config( 432 );
& A: c$ B; x1 J3 s5 _ - /* 等待直到Echo为低电平 */5 v9 q( M0 }4 ?$ s6 h
- while(Echo_Value == 1);1 C. p' K, M2 R- g, G
- /* 关闭中断,通过参数n来取得距离参数 */
1 w9 b: |/ ^+ N7 A - /* 本来的使能位取反 */5 L9 k; ~0 N% Z0 Q/ }/ q/ G
- SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
: x$ g) j7 _( T- j% h. s - /* p、q分别是距离的整数部分和小数部分 */( d! Q! U% T: B2 r* G/ p$ j: A
- p=n/10;& C5 H( z( V1 G. T+ x+ O
- q=n%10;
/ S; C2 T" U8 s7 y# L" f! | - /* 打印距离信息 */
# V8 ?9 A: }% q1 ~' X! e V - /* p-50时经过调试的,因为测量的距离和诸多因素有关,这个操作减小了误差 */6 N( B& h( u6 k/ {1 D3 N" |$ I
- printf("第%d次测量为:%.0f.%dcm\n",i,p-50,q);# x3 H! m% ]' H
- i++;
( @9 I- m- L& W" K+ B - /* 清零中断记录变量n */) V2 C# i2 }) ~7 e
- n=0;! V7 E c7 H( a9 ?. o- _2 n* S# U2 ~
- }- X/ L$ a8 }- @( u6 X- k
-
. D, `7 Q! _5 z/ a. o8 f - }
$ F/ K! D) p# F% E. E% a; M! \
复制代码
" ~$ c. t$ i5 s3 B4 V————————————————
3 I' J9 z; }5 Y6 i( c, e$ Y1 `版权声明:Aspirant-GQ
- f7 H! x; U5 t ]& } j: E5 o0 m$ \如有侵权请联系删除
8 u* e6 {+ {$ J$ d3 X% F
& H. ~( e$ c! n& v' v |