本文用的单片机是STM32F103C8T6,超声波测距模块是HC-SR04,显示测距结果用的是0.96寸OLED屏模块。 7 m9 f) g0 k; s9 J4 K5 h2 \8 s
效果展示下图中小于10cm时的显示结果有点问题,代码已修复并更新
- x" D2 H* P( B1 t1 c7 G8 d$ w
. }5 {0 L" l# Q5 d
修复后的结果: 2 j0 o- }- ?; U
" S! t9 G, Y, r* E5 e% k" k
6 [: e; \- N, D, o) `7 P c
HC-SR04硬件概述HC-SR04超声波距离传感器的核心是两个超声波传感器。一个用作发射器,将电信号转换为40 KHz超声波脉冲。接收器监听发射的脉冲。如果接收到它们,它将产生一个输出脉冲,其宽度可用于确定脉冲传播的距离。就是如此简单! 该传感器体积小,易于在任何机器人项目中使用,并提供2厘米至400厘米(约1英寸至13英尺)之间出色的非接触范围检测,精度为3mm。 8 B U0 E+ f' U0 ~+ r2 q
, A Z& s+ l+ H8 Y
7 Z2 b' p* M1 f, C# H4 L- F8 o
7 B! f/ ]0 x& h; X' y7 JHC-SR04超声波传感器引脚
5 p# T' m' Y: g6 ~& w& z4 X( W5 P让我们看一下它的引脚排列。 VCC 是HC-SR04超声波距离传感器的电源,我们连接了5V的供电。 Trig (Trigger) 引脚用于触发超声波脉冲,下面例程中用的GPIOB5,所以连接STM32的GPIOB5。 Echo 回声当接收到反射信号时,引脚产生一个脉冲。脉冲的长度与检测发射信号所需的时间成正比,下面例程中用的GPIOB6,所以连接STM32的GPIOB6。 GND 应该连接到STM32的地。 5 Y+ M' r' d8 j$ Q0 [. m
HC-SR0如何工作?当持续时间至少为10 µS(10微秒)的脉冲施加到触发引脚时,一切就开始了。响应于此,传感器以40 KHz发射八个脉冲的声音脉冲。这种8脉冲模式使设备的“超声特征”变得独一无二,从而使接收器能够将发射模式与环境超声噪声区分开。 八个超声波脉冲通过空气传播,远离发射器。同时,回声引脚变为高电平,开始形成回声信号的开始。 如果这些脉冲没有被反射回来,则回波信号将在38毫秒(38毫秒)后超时并返回低电平。因此38 ms的脉冲表示在传感器范围内没有阻塞。 * J5 S3 {) c2 W" E @. ]6 A
如果这些脉冲被反射回去,则在收到信号后,Echo引脚就会变低。这会产生一个脉冲,其宽度在150 µS至25 mS之间变化,具体取决于接收信号所花费时间。
/ S6 |6 q0 Y- }/ v) u4 e
8 q* d2 P& n2 C+ J. Q
HC-SR04的时序图如下: ! W; m6 @0 w. W5 o
0 c& [' L' {# s: ? K( w
然后,将接收到的脉冲的宽度用于计算到反射物体的距离。这可以通过我们在初中学到的简单的距离-速度-时间方程来解决。 距离=速度x时间 3 e+ F% w: g6 M4 w. h; a- ]
接线将HC-SR04和0.96寸OLED屏连接到STM32。
% I$ ]: X& z! L 9 y* U/ D D) A" b0 q0 w+ C H
! R" n/ O: S. T7 N9 e' b4 Q0 d
温度对距离测量的影响尽管HC-SR04对于我们的大多数项目来说都相当准确,例如入侵者检测或接近警报;但是有时候您可能想设计一种要在户外或在异常炎热或寒冷的环境中使用的设备。在这种情况下,您可能要考虑到空气中的声速随温度,气压和湿度而变化的事实。 由于声音因素进入HC-SR04距离计算的速度,因此可能会影响我们的读数。如果已知温度(°C)和湿度,请考虑以下公式: 声速 m/s = 331.4 +(0.606 * 温度)+(0.0124 * 湿度)
8 x7 q3 H7 i; `7 W程序我是用的的ST标准库写的程序,文章中放出主要的程序。
( `; E7 V; T r9 Pmain.c - #include "stm32f10x.h" // Device header- _- K4 x; A1 s, F+ h Z6 Y
- #include "Delay.h"' P/ D- J" L K- ~
- #include "OLED.h"
) v2 P6 w; d9 F, y - #include "Timer.h"
7 [9 |9 G" Y# w5 a1 K* k% L - #include "HCSR04.h"
4 s: k5 N4 H4 {# L* v* Q6 U - ' |* P3 K" u7 _& T+ k0 D
- uint64_t numlen(uint64_t num)//计算数字的长度2 Q/ }5 J# R% ~ i N
- {
" Z* K# n6 } O: \4 q6 r. g - uint64_t len = 1; // 初始长度为1
! E5 G' J d+ j6 l! E - for(; num > 9; ++len) // 判断num是否大于9,否则长度+1
: C* P$ c5 Q, {+ V2 L( K4 ~$ c' t - num /= 10; // 使用除法进行运算,直到num小于1, D/ K+ N3 O1 p' s4 {
- return len; // 返回长度的值1 Y2 M# a3 |8 ^1 G6 O+ n
- }
+ _' J* H1 F; t, Z - # B& Q* C& |/ r; ? y
- int main(void)) g' `* R; M% }3 I2 D' |" O( I" v
- {
- J+ _( B3 x% K' g8 f - OLED_Init(); //初始化OLED屏* N. t: s) R, y6 a: g) |
- Timer_Init(); //初始化定时器) K: x: C. a: |/ p! c
- HC_SR04_Init(); //初始化超声波测距模块
+ K% t5 w8 K! q& X. ^ -
; n3 H2 |) J: G - OLED_ShowString(1, 1, "Distance:"); //OLED屏输出字符串
0 y- \: }. B1 } -
* x9 X! e8 J( W; x7 T7 j$ l - while (1)
H2 J" t8 T9 ^; f- S0 a - {
/ A1 B0 Z6 s# F5 l& h - int Distance_mm=sonar_mm(); //获取距离测量结果,单位毫米(mm)
# J" k" @" P5 Q; H - int Distance_m=Distance_mm/1000; //转换为米(m)为单位,将整数部分放入Distance_m
: V" {4 O7 Q" y1 r" { - int Distance_m_p=Distance_mm%1000; //转换为米(m)为单位,将小数部分放入Distance_m_p
8 F( @4 g, f; K4 ` - OLED_Clear_Part(2,1,16); //将OLDE屏第2行清屏
! k/ h2 C: X9 v" V3 x7 m7 G/ S - OLED_ShowNum(2, 1,Distance_m,numlen(Distance_m)); //显示测量结果的整数部分' j, A" ^3 B }$ Q5 P2 D0 ]' O( U
- OLED_ShowChar(2, 1+numlen(Distance_m), '.'); //显示小数点
2 Y/ x u( B% m- Q: G% f4 \ h- [ - if(Distance_m_p<100){ //判断是否小于100毫米
9 Q; F4 H3 C) p6 v - OLED_ShowChar(2, 1+numlen(Distance_m)+1,'0'); //因为单位是米,所以小于10cm时要加0( M% [' a' ]8 n; I9 ~7 y
- OLED_ShowNum(2, 1+numlen(Distance_m)+2,Distance_m_p,numlen(Distance_m_p)); //显示测量结果的小数部分
) L ~) t! g6 y5 P7 _9 ~ - OLED_ShowChar(2, 1+numlen(Distance_m)+2+numlen(Distance_m_p), 'm'); //显示单位
+ G$ S4 I( I! n7 |* Z - }else // https://blog.zeruns.tech
# m7 ~/ U1 @3 y) ]9 B: R8 x6 I - {
6 ?, B' T% ~3 P, S - OLED_ShowNum(2, 1+numlen(Distance_m)+1,Distance_m_p,numlen(Distance_m_p)); //显示测量结果的小数部分
7 D* t1 z( W" s# p1 g' P1 U6 j - OLED_ShowChar(2, 1+numlen(Distance_m)+1+numlen(Distance_m_p), 'm'); //显示单位
+ \6 _6 u' r: A* |. [ - }
) r* e3 U0 q" W5 W0 D - OLED_Clear_Part(3,1,16); //将OLDE屏第3行清屏
1 n9 I' y8 }. p% g! H- f6 @ - OLED_ShowNum(3, 1,Distance_mm,numlen(Distance_mm)); //显示单位为毫米的距离结果
8 Y0 L' W& K/ f - OLED_ShowString(3, 1 + numlen(Distance_mm), "mm");/ d5 H# \6 m; r0 l+ a
- Delay_ms(300); //延时300毫秒
; u3 g1 U- k+ j$ X* I - 9 r0 v( y* L9 c. d3 S5 w
- }% D4 Z' m; i- t3 I6 {' W( e2 m7 F
- }
复制代码 . r$ I0 `2 j D! N8 V: o9 a
Timer.c - #include "stm32f10x.h" // Device header
7 Y$ P% e, g' D% X9 @ - //blog.zeruns.tech
0 ~; N% [+ r2 ]. l0 h6 i$ h - void Timer_Init(void)
0 ~% ?8 S" o V, O- W: `. w% C - {
& r& ]0 _$ J6 c - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //启用TIM3时钟
& C# m: L8 @9 [0 r g - ) u1 h) u$ s5 g1 R
- TIM_InternalClockConfig(TIM3); //设置TIM3使用内部时钟
; `, ~5 Z( u) N- X2 I; t - / `4 R" y* q# v) k' k! K% g
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体,配置定时器
$ w( `( K# k6 E+ Y - TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置1分频(不分频). F$ c+ F1 |8 a
- TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置计数模式为向上计数
7 d( E# d4 @1 ? - TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //设置最大计数值,达到最大值触发更新事件,因为从0开始计数,所以计数10次是10-1,每10微秒触发一次0 |/ ?5 b9 l# j( Q. Y
- TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //设置时钟预分频,72-1就是每 时钟频率(72Mhz)/72=1000000 个时钟周期计数器加1,每1微秒+1. s" W- e7 n! Q
- TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器(高级定时器才有,所以设置0)( z3 O! k- K* w
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //初始化TIM3定时器
9 C+ ]/ ?& X N' D7 c' L -
- U0 s5 j" X0 v" g3 c" b: F - TIM_ClearFlag(TIM3, TIM_FLAG_Update); //清除更新中断标志位' s, r- d* K K8 U, O: n% L
- TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); //开启更新中断
( j! j7 Y- \, D -
- r- s# ~- A% j# s2 `( h - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组
9 P* j# U" G! c3 M -
; P8 U# d1 b; T& T$ |) o2 | - NVIC_InitTypeDef NVIC_InitStructure; //定义结构体,配置中断优先级 V3 {% K5 `/ B+ Y
- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //指定中断通道3 `, |7 k1 Z6 W# F! F
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //中断使能: H# [4 N) q: }* N, J& u8 P: a0 h4 P
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //设置抢占优先级6 V* q7 C! {7 u8 K- |
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //设置响应优先级
8 d6 E; p6 b6 H; ~" A# N - NVIC_Init(&NVIC_InitStructure); // https://blog.zeruns.tech
1 _5 M% T) a# ^5 c -
2 w n/ @* z( f* ] - TIM_Cmd(TIM3, ENABLE); //开启定时器
; O4 U/ ]6 h0 y3 J - }2 X) f* B6 E% Q% e: ^% ~! t& X
- 1 T' q d$ h( u# s7 {+ ~# }
- /*
2 x& }- h/ s, y5 `5 q1 |) T4 ] - void TIM3_IRQHandler(void) //更新中断函数
: B3 z3 w& w# K( i - {
3 F! o3 ?2 M# C' U* ?& ^, s - if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) //获取TIM3定时器的更新中断标志位
% f( @2 Y) t; |. C( g8 V. `/ O% N - {4 }& G. J" L0 \" b
-
0 U2 B# O+ H- {% E - TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除更新中断标志位
% Z1 H5 ^$ I) n, n8 }* ^$ L - }
) _% P- ?+ Y+ [( g' n$ ?& _1 v' U2 X# M - }*/
复制代码
. }3 |5 p0 G. b* J3 P) y5 I# vTimer.h - #ifndef __TIMER_H
7 i# M& W+ g0 z - #define __TIMER_H
# S0 \' u$ n H( ~' x' Y - 4 n- R% g* Q- i
- void Timer_Init(void);9 j* d+ ?7 Z2 G- |: b3 c
( C0 h' \1 W4 i9 N* i4 \- #endif
复制代码 8 p% S$ o. ~2 f% ]+ ^. c
HCSR04.c - #include "stm32f10x.h"
: W4 H& G; l1 T, [8 z8 C' o" { - #include "Delay.h"% V6 w7 r* M( X( i: K6 z$ p
! J6 I# W) \! s- /*
4 t: U5 p% e& J) x+ C/ } - 我的博客:blog.zeruns.tech
5 V1 n7 N& V# o0 l" G4 {6 ?) n - 具体使用说明请到我博客看
4 x; y. U# H7 y8 B" { - */
7 h- W/ |; \ B0 T" U) u
5 [' \, @& h; ]- N7 |4 n- {" l: v( M9 p
- #define Echo GPIO_Pin_6 //HC-SR04模块的Echo脚接GPIOB6& W! v5 C6 v8 C( h9 ?8 R
- #define Trig GPIO_Pin_5 //HC-SR04模块的Trig脚接GPIOB5
6 X0 S7 B0 G9 z% m ^. u' G5 T - 7 i+ O* B0 L7 e1 I$ c: s! N
- uint64_t time=0; //声明变量,用来计时
) y5 q# c+ B- ]9 C" a( D& n - uint64_t time_end=0; //声明变量,存储回波信号时间% B9 J' p% L; N4 B X/ a b
- 4 a# Q4 U4 h4 B+ f! Z, o3 u) p7 ^3 G% |
- void HC_SR04_Init(void)8 C s- d. \, ~; q0 f/ H6 y
- {
- _0 g$ `- B4 d9 m2 D7 T; W! { - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //启用GPIOB的外设时钟 & ?8 r; ^: g2 R! K
- GPIO_InitTypeDef GPIO_InitStructure; //定义结构体; U4 I, D; h+ T# a$ F! N
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置GPIO口为推挽输出8 `/ v, {" B; I( v
- GPIO_InitStructure.GPIO_Pin = Trig; //设置GPIO口5
9 p8 o7 V$ w' C3 i: N3 I - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置GPIO口速度50Mhz) {& \ m6 E. R) f% F6 L
- GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化GPIOB! w0 L2 u: F9 Y7 O+ U8 W" y3 N( [
-
1 b) K& W! O' n- }2 }& U/ X* n - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //设置GPIO口为下拉输入模式
% X1 b5 C' ]- K4 m - GPIO_InitStructure.GPIO_Pin = Echo; //设置GPIO口6
, p/ }% S. {$ ]" M - GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化GPIOB& ? D) O B4 ]
- GPIO_WriteBit(GPIOB,GPIO_Pin_5,0); //输出低电平
1 X" S4 J3 Y; R. n - Delay_us(15); //延时15微秒& v4 }6 n$ g+ a
- }
. d1 m9 D3 [+ R" r4 g1 _
9 Y y1 F) Y# [- int16_t sonar_mm(void) //测距并返回单位为毫米的距离结果" l. P+ V" V7 a5 @
- {4 E7 ]2 {- C+ n; r0 I- i8 N0 k
- uint32_t Distance,Distance_mm = 0;
3 ]1 {. K( \0 c; B+ f, ^9 L - GPIO_WriteBit(GPIOB,Trig,1); //输出高电平7 V; a$ s( X. Y/ ?
- Delay_us(15); //延时15微秒* k e$ h7 I: J% ~3 {
- GPIO_WriteBit(GPIOB,Trig,0); //输出低电平/ w3 i4 r0 |0 D# ] p
- while(GPIO_ReadInputDataBit(GPIOB,Echo)==0); //等待低电平结束
5 c; K! G# c. r - time=0; //计时清零
e2 }) A; s, t- d - while(GPIO_ReadInputDataBit(GPIOB,Echo)==1); //等待高电平结束
& s. n! v, v: J9 h/ P6 C7 y - time_end=time; //记录结束时的时间8 ~( B' F7 h. d$ F8 F3 |
- if(time_end/100<38) //判断是否小于38毫秒,大于38毫秒的就是超时,直接调到下面返回0/ L$ [+ |% m4 j" }- a! R
- {! v# r3 T/ ] w$ ~4 c* V2 c7 p) A
- Distance=(time_end*346)/2; //计算距离,25°C空气中的音速为346m/s
) U- ^) q; T; ?5 K - Distance_mm=Distance/100; //因为上面的time_end的单位是10微秒,所以要得出单位为毫米的距离结果,还得除以100* ]/ |0 f. z5 {; W
- }! Y1 C, G- h5 `7 C c( a
- return Distance_mm; //返回测距结果
/ N/ `2 q( t, ^$ X - }
2 m0 i) U$ }. O. C7 q
% ?. H$ _; `/ H0 Y' E- float sonar(void) //测距并返回单位为米的距离结果3 m2 k7 E% Z$ d9 W& h T$ Y1 r
- {8 p6 t/ M; ?# f4 J; O$ i
- uint32_t Distance,Distance_mm = 0;
" B5 q" S' p- h4 q1 s8 ^$ } - float Distance_m=0;5 C- G2 r- u$ I% ]6 t1 p/ @ h* X
- GPIO_WriteBit(GPIOB,Trig,1); //输出高电平; V( I5 v' {0 C0 U% @* l" i8 C: f
- Delay_us(15);
# I9 M G, W( Y7 d2 v, X - GPIO_WriteBit(GPIOB,Trig,0); //输出低电平
7 ]- ^8 E4 {% U& l9 t# x - while(GPIO_ReadInputDataBit(GPIOB,Echo)==0);
2 L) p7 U& F$ h8 H2 Z3 T/ x - time=0;( D) X0 R0 H8 q) r7 G
- while(GPIO_ReadInputDataBit(GPIOB,Echo)==1);4 `$ ~8 M7 Q# J
- time_end=time;4 `/ Q( w. f4 D, f* S8 J
- if(time_end/100<38)
9 d5 k4 N. k8 \8 f [0 | - {. v, j! ]: C8 f0 V
- Distance=(time_end*346)/2;& ^/ G/ ]- _7 s4 |) i
- Distance_mm=Distance/100;9 F3 z% a4 m% ^, v# m E
- Distance_m=Distance_mm/1000;4 J5 g; ?8 d$ n3 I1 M7 U
- }
r1 `/ ]: W& {. t - return Distance_m;1 m% P4 h, [5 Z- c0 e0 e, g
- }( T8 t5 ?( @- {5 `& p
- 2 Y! B' ~" m! R+ H1 e5 l
- void TIM3_IRQHandler(void) //更新中断函数,用来计时,每10微秒变量time加1
: c5 D( W, ~/ }9 x - {
+ F+ r2 g5 O/ R: I( } - if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) //获取TIM3定时器的更新中断标志位
" Q$ ~, I% n; n# D# V6 _3 v - {
: t' \) Y5 @3 y" H0 H1 A) G" }4 { - time++;
& ?1 G K4 g+ i7 I - TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除更新中断标志位, V3 {3 S/ v H$ a7 R
- }
! P, x7 \6 A6 R) [$ V/ c% q2 ^ - }
复制代码 q% u, _0 G8 X! ~* o1 b" r
HCSR04.h - #ifndef __HCSR04_H
, o" a) i# C }4 Y. Y - #define __HCSR04_H
' p4 k% @1 q/ r+ F - - P3 l8 ^4 e4 r* ]3 w" k
- void HC_SR04_Init(void);8 O+ K7 o y/ Q K2 e3 O% v
- int16_t sonar_mm(void);. _8 T& g2 p* q, U- ?( x
- float sonar(void); s4 w, H2 m! W5 V' F2 T
- - \+ x- ?4 j1 `5 @
- #endif
$ W6 v/ n6 e2 R# E
复制代码 # e, d$ {% \, R1 r1 b
: {3 d6 H- x7 n
转载自: zeruns
/ B- y, b; N8 o9 V# P; g |