超声波测距原理+ I3 p; t# v- q& y( r0 _6 O
利用HC-SR04超声波测距模块可以实现比较精确的直线测距,其测距原理图如下:! n+ g1 H8 C+ J( D
/ g5 T( l: p% o$ R
: \% |. y! F6 h
8 H5 K( I9 w4 QHC-SR04的一端发出超声波,接触到反射物后反射,被另一个端口接收到,所以只要知道发射和接收的时间差,就可以根据声波传播的速率算出HC-SR04和反射物直接的距离。
# G* Z6 Y( b; m) R所以实现超声波测距就需要俩个条件:
5 A5 ]) E5 c+ J# o2 k* u发射和接收的时间差
- i! w C1 ~1 [- D( g超声波传输的速率7 ~* Z# y+ B1 d0 G$ |: j
HC-SR04工作原理; h* b3 [5 P0 b
HC-SR04模块的电气参数如示:
k, x3 R3 C9 L. D
; T F. \ n. |2 x% M4 j
/ P- f* @( x# y% O8 ` O+ Z
0 A9 U& d* B* ?+ i, R" h
HC-SR04模块的实物图如示:0 d8 `2 u9 b/ e' c1 N" p! Q: y/ I
0 y' Q7 y! n* A$ ]2 S# P
4 x8 M, D- _) W) ]6 O
6 O6 x( T* Q2 ^; b6 g有四个引脚:
& a3 b% M2 T+ R; W/ T* [Vcc:+5V电源供电
/ B' M b2 Q+ D+ cTrig:输入触发信号(可以触发测距)
0 G+ Q3 x, x, o! TEcho:传出信号回响(可以传回时间差)/ j1 d3 g8 i$ n. l/ A9 g3 j! q, a
Gnd:接地/ U* O9 `0 o. P, _6 E& `
& ]" K1 N! p9 h2 q: e+ v# N用Trig和Echo引脚实现测距的流程:4 K! u: h+ J9 I! p! P- B
1.通过Trig输出一段至少10us的高电平(脉冲),触发一次测距,超声波在传输的过程中Echo一直输出高电平。
8 A0 [) ^2 c* T. M2.在Trig脉冲输出后,立即检测Echo引脚的电平,测出Echo高电平持续的时间t,t就是超声波在所测距离一个来回所需时间。
9 I1 g0 I2 B4 ?% q; Y. K' ]# L* O
* _: Y( v& e5 i h测距时序图如示:, j! z- w1 o, w/ d* n( O9 K
3 y. J, s; I, p& h) J
5 m" t% {! }' P/ o+ \; t
9 j7 z2 j4 Z2 `1 }0 B" a7 mSTM32实现驱动
& w# m7 ?4 n* V# Y( h i利用STM32驱动HC-SR04需要做好几个关键点:- ^7 j0 ^7 S; P( g/ _7 E
引脚的配置
' i. Q$ h4 v; E d7 t2 }$ U! A! h时序的控制 _ i; ~: L2 _ }$ I- r. q
时间差的测量: V$ ` H; Q+ F0 M+ \. E
" g8 R- a' O/ e% v下面来分开实现几个关键点/ Y- h9 K h4 j+ E6 k# U& y0 u8 |
$ {8 l' u) h8 E" Z' Z1.引脚的配置
% V! N2 p S& R$ L6 s- R! U+ O! @HC-SR04四个引脚,Vcc和Gnd直接接在开发板的电源上即可,主要是Trig和Echo引脚的配置,我选择了PB1连接Trig引脚、PB2连接Echo引脚。
9 r `. ~ v6 v7 L5 b) T因为要控制Trig输出电平,所以PB1引脚模式是推挽输出GPIO_Mode_Out_PP8 @ w5 V' w5 [9 b
Echo要检测高电平持续的时间,所以PB2引脚模式是浮空输入GPIO_Mode_IN_FLOATING( O7 c! O" V1 r2 q
相关的配置代码如下:) y/ u% Z6 X) r0 M |& F% E
- void SR04_GPIO_Init( void )
" M: E6 d3 h- h8 g" o1 m0 q - {
/ {4 v$ l" h. i" Y" t, h* I- m - GPIO_InitTypeDef GPIO_InitStruct;6 c& t/ @' o5 k
- RCC_APB2PeriphClockCmd( Trig_Clock |Echo_Clock , ENABLE );
" f, s$ L$ _% G1 l -
- [- Q$ |: L( G) k) }# q$ O9 M5 | - GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;6 n" P- M8 s: }' w* `
- GPIO_InitStruct.GPIO_Pin = Trig_PIN;
, ?3 @2 k; l( V: L: C$ j6 c7 ` - GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
; r3 t4 B' F$ V1 S( m! R t* u - GPIO_Init(Trig_PORT, &GPIO_InitStruct);
2 s; G% Y5 L( M( k! y" E9 E2 c% D -
) i1 `+ |# z0 n: j( i - GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
# t% H5 C \. F3 a2 M- k* U5 W - GPIO_InitStruct.GPIO_Pin = Echo_PIN;
' V, B1 a8 q7 x6 h4 d+ F; m$ t - GPIO_Init(Echo_PORT, &GPIO_InitStruct);
$ E* R* l( X$ L, a- O5 B1 o) z' _ - }
. Y" w* R% t9 a
复制代码 & t7 w: r6 ?+ F3 r
2.时序控制
7 [- E- M4 V1 i3 j+ _. @; xHC-SR04的时序是:先来一段10us的Trig高电平,接着接收一段Echo的高电平,伪代码如下:( C' f; i& `' h8 S. n6 u
- #define Trig_H GPIO_SetBits(GPIOB, GPIO_Pin_1)+ s) i; I8 j9 h. K9 e w
- #define Trig_L GPIO_ResetBits(GPIOB, GPIO_Pin_1)' q" M* ]2 u* m' a0 {
7 ~9 J8 U7 T. ^( b/ L1 @5 E- /* Trig给一个至少10us的高电平,超声波进行一次测距 */7 t Y. O- P7 V6 ~; {
- Trig_H;
: f% P0 E% N/ m0 { - Delay_us( 10 );
% I: C8 p' h m6 d - Trig_L;
1 _. F; T' U+ I' { - /* 等待Echo高电平 */
% O0 h2 J; q9 t" i
复制代码
* \ x& w% D& ?" Y3.时间差测量
8 W8 \7 n5 \) D f这个是最重要的一步,要测量Echo高电平持续的时间,因为光传播的速率是340m/s,而测距的范围大多是cm级别,所以相应Echo高电平持续的时间也就是us级别的。
: e- p, }1 V' n2 h4 R( t4 X所以,测量时间差的条件就比较苛刻,我是利用SysTick(系统计数器)的原理实现计时的。SysTick计数器原理是对通过SysTick_Config()函数配置每俩次中断之间的节拍数,也就是俩次中断之间的机器周期,我大概算出了,测出0.1cm距离的Echo高电平时间约为6um,而系统时钟的频率是72MHz,所以配置每俩次中断之间的节拍为432的时候,进入一次中断就代表0.1cm的距离,所以只需要记录进入中断的次数就可以算出距离。通过一个全局变量在中断函数中自增来记录中断次数。SysTick_Config函数源代码如下:
' v7 V; b% ^' B3 d* }- static __INLINE uint32_t SysTick_Config(uint32_t ticks)+ L; t6 W, W! F5 J5 n R* w
- { 7 ^/ c1 v. e4 J" \( J
- /* 判断ticks 是否超出装填值和重装值的最大值 */; V* D0 g+ B( y2 d* q1 b1 x
- if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
5 |* S* w9 f( ]+ { d - 2 W4 j5 x$ \. A# b! |
- /* 配置 装载寄存器 */ 0 o3 O$ ^) L; m( ~6 ?: c9 O; A/ R9 |6 `
- SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
) }3 |) E3 C8 s; h" K - /* 配置 内核中断的优先级,也是在NVIC中 */
d& A& Q2 ~+ d% `: S" s4 l2 v6 t - NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); " ^/ X! e2 T/ B/ j i2 I) d9 B
- /* 加载计数器的值 */
5 G3 D: ~$ [$ i; C% \2 O$ h - /* SysTick->VAL是当前数值寄存器的值 */. o- \, I7 X0 L+ m% J) |: w# h
- SysTick->VAL = 0;
2 }5 S( k4 }: p5 ?, [8 O7 X - 4 }+ u/ B9 ]3 R. |2 n8 o2 P
- /* CTRL是SysTick控制及状态寄存器:
- ]/ z: X( m2 Y, z - CLKSOURCE:位段2 时钟源选择,0=APB/8;1=APB APB即72MHz ]0 u, O y- S3 y2 C
- TICKINT: 位段1 当置为1时,计数器递减到0时会产生中断请求;当置为0时无动作9 s, B* F8 g4 s0 j
- ENABLE: 位段0 使能位,可以启动SysTick定时器*/. n Z0 j1 y5 L
- SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
|0 O8 b- P- N$ t - SysTick_CTRL_TICKINT_Msk | # P% m4 M, ^3 c1 \2 v |
- SysTick_CTRL_ENABLE_Msk; . J9 d' b+ _! b* U d! @9 y x
- return (0);
1 J) G6 q; d) o5 a2 I2 f* m6 f' _ - }3 ^% @: c0 o5 I6 Q8 r* k T3 k
D9 P2 `- w$ S# R, {' U
复制代码
1 M( F* l; V9 C2 A1 ySysTick的具体原理可以参考一下我之前的博客:SysTick原理& [. U- ~* ^2 D. E
8 G+ s( Q! Q+ a2 e, L
注意:SysTick_Config()函数执行完就开启了中断,所以必须在Echo为低电平后及时关闭中断,并且将记录中断的变量清零。
. B$ U: K1 l( ^( `6 s# B$ L! T# l
f- k% N; L7 K2 n) i中断函数如示:" r* i1 D( l' k5 |, k- P
- /* 用extern和volatile关键字修饰的 全局变量n */" l, h( S" t- _$ A. j: p( v s
- extern volatile uint32_t n;
; H7 m' j. Z/ k ] - 7 [$ B8 n3 {* q/ |- S* R$ f$ R
- void SysTick_Handler(void): Q3 f) E2 \9 n4 c/ @7 Y
- {) o/ o7 h. K# N0 S! v ^" I
- n++;( V& ~" W' j% L4 a4 ~
- }) e+ D8 b0 h1 B, {1 f1 Z
复制代码
. z+ _6 T& w0 [; ~+ X) l/ s关闭中断及清零n的代码如下:
) Z# \( a' r0 R2 [& Z- /* 本来的使能位取反 */
5 U' \+ I6 z1 z( p - SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;6 f0 {- Y! U3 [# S% `! D9 X! S
复制代码 + M" K4 i1 H4 ]; u& T) e) ^1 I, E
SysTick->CTRL寄存器的0位控制着中断的使能,具体情况在之前SysTick的博客中已做详细说明。
( E6 S. M) k: N3 y
% G' t4 A' z7 ~6 ?; X7 R) o' i i U4.如何将距离测出来( W3 q4 x9 m, g' Y; U+ i# ]: X
我在main函数中实现了距离的测量,并且通过串口打印函数将距离传到上位机,具体代码如示:! g1 t. Y. R: j D5 _
- int main(void)9 [3 w" W& v# a
- {
5 H" _% }, I2 ]" e
. Y# J7 N) ^8 S- h) n0 x% T; P) d- int i=1,q;3 d+ J% o" t* p( p& J
- float p;
2 h7 e9 g2 Y0 |( y5 }2 z1 E - /* HC-SR04模块引脚初始化 */5 W q1 P9 @! g
- SR04_GPIO_Init();
' L8 s1 K4 ^6 o7 D1 C - /* 串口相关配置 */: Z- O. a4 N# S
- GQ_UART_Config();
3 r2 ?" y; @* b/ l, ^& a - /* 打印调试信息 */' b% }0 [ N0 V% C' P
- printf("慢漫的测距实验\n ");
/ {* \' u' f' A/ @: r) [$ E - 3 t! v0 k( x2 t3 N; [5 V
- ' R' J. e% L9 }+ l. e& F) ~
- while( 1 )$ ^7 P( C8 d- I* i! \1 W9 A* j0 g
- {
7 s. g8 E) p8 r6 @8 h9 o& g - /* 每0.5s测一次距离 */
! ]0 B% |4 |- t5 V - Delay_ms( 500 );
6 J' i; o( _$ W; o -
# \, h6 s/ ^) m) Y - /* Trig给一个至少10us的高电平,超声波进行一次测距 */
, r' Z/ [( y8 ` - Trig_H;. J9 Z3 T5 G2 l, r
- Delay_us( 10 );; [ p F1 F G+ @" V' T
- Trig_L;5 ~/ u7 J1 D+ B3 W: a6 Z1 o: a9 C
- /* 等待Echo高电平 */* t2 l6 W5 {/ ]5 ^) k, c. S) I/ g: ]
- while( Echo_Value != 1 );
* r+ K7 M( \/ T% F - /* 打开中断,对Echo高电平时间计时 */. t' D+ x6 v4 L6 X
- /* 配置计数器的装载值是72*6=432,即一次中断6um,正好是超声波的0.1cm,所以中断次数n对应着n*0.1cm */
0 z( m" J! B* {6 \% T0 Y - /* SysTick_Config()中已经使能计数器了,所以无需再开启 */
# I* B1 O7 U6 ]6 L+ I - SysTick_Config( 432 );
6 g0 L5 E, R& s' [1 `, {3 B$ _1 H1 C - /* 等待直到Echo为低电平 */ g# t) t2 @- |9 I# u! i, W
- while(Echo_Value == 1);+ z3 s- r- w) [7 L- C, c6 v
- /* 关闭中断,通过参数n来取得距离参数 */! }9 \+ Q% V5 F" a; |
- /* 本来的使能位取反 */4 [2 g2 x: c% p, O& e3 M4 q
- SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;3 @ C/ }5 z9 a) v! p3 b
- /* p、q分别是距离的整数部分和小数部分 */) u' P( Z# j6 j; a3 g, K
- p=n/10;; }5 F _! O. r
- q=n%10;
, F/ s6 C4 ^6 r - /* 打印距离信息 */
' z; T! N! O0 q4 |2 T7 p - /* p-50时经过调试的,因为测量的距离和诸多因素有关,这个操作减小了误差 */
* {$ q1 g% ^4 g7 V0 S# i- y+ Q - printf("第%d次测量为:%.0f.%dcm\n",i,p-50,q); G7 @5 M# v) n9 p
- i++;6 A5 D3 U( s( L3 @. l( P* M
- /* 清零中断记录变量n */7 y8 \* s2 {: i& p2 l. R
- n=0;+ y+ ~. B: ^( ~8 `
- }+ C5 s2 }% s" C! W
- 9 h; V9 @8 d# Q6 h3 |& D$ ]
- }& S' d% m5 v- e p
复制代码
2 T# `; S- B% R/ R% a————————————————- ]1 q6 y8 v! w$ s/ ~3 i
版权声明:Aspirant-GQ
7 M' s) x* B+ z# R0 O1 b. f% e如有侵权请联系删除; F# N6 ]5 ^. e" x! u/ Q
5 v* f3 i1 }: ]/ a$ _; A9 O |