【项目分享】基于STM32超声波避障小车
Y/ i9 a2 K2 e/ N" o9 n4 Q5 X. D3 M% u+ S
/ ]7 l* y( g/ X4 I, x不管是对于初学者还是对于一个玩过单片机的电子爱好者来说,或多或少都接触到过小车项目,今天给大家介绍的的一个项目基于STM32超声波避障小车。这也是我曾经的一个课设,在此开源分享给大家,全文5000多字,干货满满,加油读完,保证你收货多多7 A& x+ n# G7 y. o y2 o
$ v. h# G/ @3 X1 }' [! v
0 h- Y" f1 I/ I5 \% Q/ h4 A7 b7 |! q
& ~% f8 _5 _ F
处理器电路设计
! {& J+ y" N" U- H) m( g, S" J! V! m单片机是系统的CPU,是系统稳定、正常运行的重要前提,以下为单片机选型的两种方案:
' i4 Q6 b2 [2 p5 i# N(1)传统的8位单片机,是通过超大规模集成电路对其进行集成为一个独立芯片的控制器。内部组件包括CPU、随机存储器、只读存储器、I/O接口、中断系统、计时器、串口通讯、数模转换等。STC89C52单片机是最常见的51单片机,但是资源较少,精确度低,处理速度相比STM32单片机差很多。2 d- j4 r! ]# R) n! X
(2)使用目前市面上最常见的STM32单片机,STM32系列单片机可分为ARMCortex-M3内核体系结构的不同应用领域。它可分为STM32F1系列和STM32F4系列,STM32F1系列单片机时钟频率最高可达72米,在同一产品中性能最好。单片机的基本处理速度为36米,16位单片机的性能也很好。微晶片的内建快闪记忆体相当大,范围从32kb到512kb,可视需要选择。单个设备的功耗非常低,仅360mA,32位单片机产品的功耗最低,每兆赫只有0.5安培。特别值得一提的是,内接单晶片汇流排是一种Harvard架构,可执行速度高达1.25 DMIPS/MHz的指令。此芯片越来越多地被用作主要控制器。
- D7 j2 u- ~3 B* R 通过对单片机的资源和处理时间的速度我们采用选择STM32103C8T6为本系统主控芯片,程序下载是只需要一个JLINK就可以轻松完成。控制器模块电路如下所示:
) d0 J: N5 I: W* {3 ^- r s. G
+ ]. ^0 @5 M4 w8 `/ m' x
电源模块设计
; X# c2 b" b* O3 U4 X* B本设计采用锂电池供电, 模块的供电电压一般都为5V,同时超声波模块需要较大的电流才能正常工作,所以在降压的基础上也要保证足够大的输出电流。本设计采用可调输出版本,模块的输入电压范围广,输出电压在1.25V-35V内可调,电压转换效率高,输出纹波小。降压电路如下所示:
C3 P* a. ?2 m; z1 X
, \" ~0 z2 ?/ m
电机驱动模块设计
, j6 [! U. r6 E. i要完成转向是能够利用单片机实现的,然而单片机I0的带负载能力弱,因此我们选择了大功率放大器件TB6612FNG。TB6612FNG是采用MOSFET-H桥结构的双通道大电流电路输出,可以控制2个电机的驱动。相比普通的电机驱动,外围电路非常简单,只需要一个芯片和一个钽电容进行PWM输出滤波,系统尺寸小。PWM信号输入频率范围广,轻松满足本设计的需求。 * q" b$ |6 e5 Y" Q. M% N
1 c9 X5 g- I$ z# B: ?3 X电机驱动引脚表
! M' u( Y5 j! `' z- 1控制芯片:TB6612
0 c' y% v% q9 Q$ X% N - 2控制芯片数量:2
% ^5 _1 k8 B1 P0 i0 X0 V - 3 1号TB6612引脚分配:9 L0 }5 Y" E. R& }' y0 ?7 d( g
- 4 VM PWMA--------->TIM1_CH1(PA8)+ g6 |2 Y: z$ P
- 5 VCC AIN2--------->GPIOB_12
& }9 a( ~+ r4 \* J- S% j( ~0 o - 6 GND AIN1--------->GPIOB_13* i! q0 a" m8 Q
- 7 AO1 STBY--------->GPIOB_14& A! z" F& S& D1 k8 t
- 8 AO2 BIN1--------->GPIOB_15
$ f" R2 {6 L4 @5 z ~* o7 l - 9 BO2 BIN2--------->GPIOA_12
) e, P: H, ^- Y9 I8 w% s - 10 BO1 PWMB--------->TIM1_CH2(PA9)* _! E3 A5 r7 \) f& K& r
- 11 GND GND+ n) _$ v" ^0 N4 [0 @! i* r: a
- 12 2号TB6612引脚分配:3 S* O2 G+ o2 V' ]' L2 ^# A6 ^3 {6 J
- 13 VM PWMA--------->TIM1_CH3(PA10)
$ l6 t& R' ^% X9 V) R - 14 VCC AIN2--------->GPIOB_5+ J3 ^( Z$ M; S. b- Z; S
- 15 GND AIN1--------->GPIOB_6
2 D! G; Q; B S - 16 AO1 STBY--------->GPIOB_7
: K5 N, x9 i* o2 M - 17 AO2 BIN1--------->GPIOB_82 j* i9 U0 U8 U, ?3 r8 q6 k( M& @
- 18 BO2 BIN2--------->GPIOA_9
- J. h' `- {. i8 x$ L - 19 BO1 PWMB--------->TIM1_CH4(PA11)
- T' k8 {( M% G8 S0 b - 20 GND GND
O! h! I; o! K. K - 21真值表
; }$ |$ L( A; a9 q" {$ L' H8 I - 22 AIN1 0 1 0 1# P1 c' W7 e: j! P
- 23 AIN2 0 0 1 19 e5 n; L- _# L$ t9 U
- 24 BIN1 0 1 0 1
, o. R5 b( u. m: |, R) ] - 25 BIN2 0 0 1 1$ i/ B! B4 H3 y, Y5 F
- 26 停止 正转 反转 刹车
复制代码 8 I# @& e {6 u! w
8 v% o+ O% A3 O5 L: L: [/ p电机所用到的定时器配置
" H% `) l) {, f' J# q- <font size="3">1//初始化TIMX,设置TIMx的ARR,PSC
) z& s+ a$ ^+ Z& u: h+ ^6 U - 2//arr:自动重装载初值,psc为预分频值,两者配合控制定时器时钟的周期
' n$ U+ m% m! x - 3//定时器选择TIM12 F* z1 r% \' u& D" p' K* C
- 4static void TB6612_ADVANCE_TIM1_Mode_Config(TIM_TypeDef* TIMx,uint16_t arr,uint16_t psc,uint16_t duty)8 M( D+ o; @" p" V: T
- 5{
f: K2 l1 P. q& \0 V! I. P. v+ d - 6 //-----------------时基结构体初始化-------------------------/0 M! `; }- v1 p2 X* g
- 7 TIM_TimeBaseInitTypeDef TIM_TimeStructure;
5 ^- Z& F* E v5 I: j0 n- I - 8 /*开启定时器1时钟,即内部时钟CK_INT=72M*/
7 V- P- m }- X" [* C3 {- m: u: h - 9 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);$ {% j# X' C5 G% `& K: d/ a5 d% H
- 10 TIM_DeInit(TIMx);
- W; D: u0 K! D8 J, a4 u - 11 /*内部时钟作为计数器时钟,72MHZ*/
# @" F( g8 m3 e" N& Y - 12 TIM_InternalClockConfig(TIMx);
; [7 e" ?! o8 E( b# Y( o1 F' v - 13 /*自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断*/
: m7 I! q9 `* O% D2 P$ t - 14 TIM_TimeStructure.TIM_Period=arr;
: l* S7 o& y- X - 15 /*时钟预分频系数为71,则驱动计数器的时钟CK_CNT=CK_INT/(71+1)=1MHZ*/: M ^" f2 S+ q( d* b) S- [2 t1 r
- 16 TIM_TimeStructure.TIM_Prescaler=psc-1;
# r8 [; p4 r4 {) x - 17 /*设置时钟分割,TIM_CKD_DIV1=0,PWM波不延时*/6 J* k" N# q" N% B- j
- 18 TIM_TimeStructure.TIM_ClockDivision=TIM_CKD_DIV1;5 A( P/ }/ s+ l+ z
- 19 /*向上计数模式*/
/ z- a" ~# ?' c% e' E6 h& L3 ~ - 20 TIM_TimeStructure.TIM_CounterMode=TIM_CounterMode_Up;
4 r, g6 @4 D) h0 L - 21 /*重复计数器*/
3 M. b, k( T8 |; a+ L0 e9 n - 22 TIM_TimeStructure.TIM_RepetitionCounter=0;. f, ?% M p J/ h
- 23 /*初始化定时器*/2 s9 S! Y1 l8 N: k
- 24 TIM_TimeBaseInit(TIMx,&TIM_TimeStructure);% Y* u8 w/ Z |8 {0 ]1 g
- 25 /*使能ARR预装载寄存器(影子寄存器)*/
# H7 a8 b6 {; i8 L - 26 TIM_ARRPreloadConfig(TIMx,ENABLE);- n/ s1 I& C' G8 |( d* G; ^
- 27 //-----------------输出比较结构体初始化-----------------------/4 r$ F+ u, U# z; R
- 28 TIM_OCInitTypeDef TIM_OCInitStructure;4 d8 c! x0 U" J& ]) j
- 29 /*PWM模式设置,设置为PWM模式1*/% {- g, T, D' h1 L5 v
- 30 TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;! D. D. `6 F5 Y: ~* k
- 31 /*PWM输出使能相应的IO口输出信号*/
: y4 }, t1 o" b; p0 g1 Z( N# _ - 32 TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;5 `, O' i% Z& y
- 33 /*设置占空比大小,CCR1[15:0]: 捕获/比较通道1的值,若CC1通道配置为输出:CCR1包含了装入当前捕获/比较1寄存器的值(预装载值)。*/1 {& V) W9 y) E1 |
- 34 TIM_OCInitStructure.TIM_Pulse=duty;
& N4 e8 y. A D" w/ Q5 [. _ - 35 /*输出通道电平极性设置*/$ E/ p" v6 U4 M! p0 Z
- 36 TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
' Y% m. }; A$ v7 t7 H# \4 i - 37 /*初始化输出比较参数*/4 T& B. N% I f9 d3 x1 o$ h" \
- 38 TIM_OC1Init(TIMx,&TIM_OCInitStructure);//初始化TIM1 通道1
0 p% T; e" V0 p4 i- ^( W. U) { - 39 TIM_OC2Init(TIMx,&TIM_OCInitStructure);//初始化TIM1 通道2
6 }/ ^2 |2 N4 X, @) I2 L( ^ - 40 TIM_OC3Init(TIMx,&TIM_OCInitStructure);//初始化TIM1 通道3! e$ l4 `3 w8 x& I
- 41 TIM_OC4Init(TIMx,&TIM_OCInitStructure);//初始化TIM1 通道4+ l( E# q# J& f/ f6 [
- 42 /*自动重装载*/
+ @9 f* q6 x) O- P6 `7 k1 M - 43 TIM_OC1PreloadConfig(TIMx,TIM_OCPreload_Enable);
7 [6 f- O; e, x3 u. Z3 }5 x - 44 TIM_OC2PreloadConfig(TIMx,TIM_OCPreload_Enable);
! ?; P A8 i8 g: l - 45 TIM_OC3PreloadConfig(TIMx,TIM_OCPreload_Enable);
: G: M2 l3 B! ?; M+ v7 U0 j - 46 TIM_OC4PreloadConfig(TIMx,TIM_OCPreload_Enable);( W% O3 T" j; w6 v
- 47 /*使能计数器*/# R7 L3 p- j; _" L
- 48 TIM_Cmd(TIMx,ENABLE);- Z6 a- F) p% g, Z- f' U' @& M
- 49 /*主输出使能,如果设置了相应的使能位(TIMx_CCER寄存器的CCxE、CCxNE位),则开启OC和OCN输出。*/
& I: K# p2 v4 R/ m - 50 TIM_CtrlPWMOutputs(TIMx,ENABLE); ' b( k6 R1 ?" J0 z
- 51}4 p; J9 {1 Z, h/ ?0 K T
- 52//高级定时器输出通道初始化函数
7 v8 L+ z0 J2 G - 53static void TB6612_ADVANCE_TIM_Gpio_Config()
/ Z: S5 {# o& B2 l# n& G - 54{
7 p- {+ m8 n A( f& t - 55 GPIO_InitTypeDef GPIO_InitStruct;" ~. ?- m( Y) p' G+ S
- 56 /*----------通道1配置--------------*/! I% s( D; [7 L5 n6 i B
- 57 /*定时器1输出比较通道*/
+ r( ]5 z0 s) \( w' D' [ x4 x& Y( l5 G - 58 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
# N0 R$ K0 Q9 m+ j - 59 /*配置为复用推挽输出*/$ F# U" j& X4 m5 e, k7 O5 `2 C
- 60 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;! L0 q3 U% F9 P6 a4 x5 q8 K
- 61 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8;
: |$ s! D Q% ^2 q/ k8 h* K' l7 q - 62 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;, M h( Y# x% j: U% X9 P; |; O
- 63 GPIO_Init(GPIOA,&GPIO_InitStruct);
- Z6 t! g" g& x* ]6 ~; P - 64 /*-----------通道二配置-------------*/, E2 W' ` H1 @: f, }& o
- 65 /*定时器1输出比较通道*/1 z; { n/ h' |* i0 u4 {
- 66 /*配置为复用推挽输出*/! G2 N3 y7 E! O7 O0 B6 X5 M1 e
- 67 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;: W$ w/ U1 P6 B
- 68 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_11;% [: a( K6 ^2 Q2 g. Q7 x8 g
- 69 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;; R! U7 Y0 D i* U" N4 D
- 70 GPIO_Init(GPIOA,&GPIO_InitStruct);5 X% n% V+ y( D6 h' i4 R, e) P
- 71 /*-----------通道三配置-------------*/
2 ~ q- |4 j. X8 n: }7 w* Z! x - 72 /*定时器1输出比较通道*/
5 n3 F; Y) [2 p) z+ T( g - 73 /*配置为复用推挽输出*/( o+ G' l% c/ q' |0 W4 M$ J; C
- 74 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;" g6 j \% G9 O. s6 y
- 75 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
- Z! q8 c; R5 g - 76 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
3 Z! c+ \8 O8 B, V6 l - 77 GPIO_Init(GPIOA,&GPIO_InitStruct);" f( p6 C2 h% x+ \( m8 h
- 78 /*-----------通道四配置-------------*/
$ X+ t$ p: `/ \! y) f! F- M2 i - 79 /*定时器1输出比较通道*/# ?0 t- X; }' E
- 80 /*配置为复用推挽输出*/
- I6 p1 i, w# ] - 81 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
* X+ |, }0 P& q - 82 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;
8 r3 t4 q$ ^0 f1 m8 _3 y - 83 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
3 d ]' @. A' n1 \ - 84 GPIO_Init(GPIOA,&GPIO_InitStruct);1 I: x) C, ?( J' P; N
- 85}</font>
复制代码
. z* B% w+ B6 c% a
2 Y V) x0 t. M! y: R
( V5 F* Q$ c9 m, E# P 超声波模块2 n3 Q6 T: S- s
) F$ a0 b/ q1 V, v
: l! G- l ` a$ g; y5 _8 M. l; }) A% N
采用HC-SR04超声波模块,该芯片具有较高的集成度以及良好的稳定性,测度距离十分精确,十分稳定。供电电压为DC5V供电电流小于10mA,探测距离为0.010m-3.5m一共有四个引脚VCC(DC5V)、Triger(发射端)、Echo(接收端)、GND(地)。HC-SR04实物图如下:$ |9 z3 v: S6 u9 K; Y
8 C' J( M: d! U4 x9 T该模块是利用单片机的IO触发电平测距,单片机内部利用普通定时器产生一个高电平信号之后,超声波就可以自主发送频率为40khz的方波,然后等待信号的返回;若有信号返回,单片机IO口就立刻输出一高电平,利用高电平产生的时间可以计算小车与障碍物的距离。最终距离就是高电平持续时间乘以声音在空气中传播的速度再除以2,可以反复测量距离。
+ u& y; {. e# C s) M# y' {; \ 在程序开始首先初始化超声波,利用定时器并设置时基的自动重装载初值1000,psc为预分频值72,这样的话我们产生一次中断的时间是1ms,并设置抢占优先级0,子优先级3。HC_SR04_Echo引脚接收到高电平,打开定时器,且每1ms进入一次中断。在测量时首先让Trig发送一个大于10us的高电平,然后拉高HC_SR04_Trig,当Echo为0时打开定时器计时,当Echo为1时关闭定时器,通过公式计算距离。
3 q4 e( z4 S/ S# g6 X模块工作原理:
5 Q& O: v- ]/ e5 Q(1)单片机触发引脚,输出高电平信号;, y9 u! G& y K% w" |
(2)模块发送端自动发送特定频率的方波;
5 a- [% b9 |, }$ T! c, n(3)如果有信号返回,通过IO输出一高电平,高电平持续的时间就是超声波的发射时长;2 B1 c$ l, {9 | d
(4)测试距离=(高电平时间*声速(340M/S))/2。
R1 ^9 r# h# h! T$ V注意:在硬件操作上需要首先让模块地端先连接,否则会影响模块工作。测距时,被测物体的摆放不能太过于杂乱,否则会影响测试结果。2 x9 c* F+ M2 x9 Q1 Y
2 N" U/ D7 C* Z8 F4 ^2 w. _超声波重要代码(可参考)
% ]3 ?9 o4 v8 V- 1/* 获取接收到的高电平的时间(us*/
0 R6 D% I: D8 t0 C - 2uint32_t Get_HC_SR04_Time(void)
! H3 r) f9 T, l* Z5 N7 n+ F `! S - 3{
( [; ~, O `) [) |/ W - 4 uint32_t t=0;
9 p' ~, D% i2 w, N1 T. |- ]% } - 5 t=Acoustic_Distance_Count*1000;//us }8 X" M3 \( k" S
- 6 t+=TIM_GetCounter(TIM2);//获取us
) J/ x1 C2 G9 |. P- e3 \' C - 7 TIM2->CNT =0;
2 E5 R: Q1 Z# E* ]- b - 8 Acoustic_Distance_Count=0;6 f0 U6 {) u) I* g+ {7 l. J# o) @
- 9 Systic_Delay_us(100);% ~- H+ B1 q: d( I4 y- I1 a
- 10 return t;
0 t6 a- k2 r6 B7 H9 x - 11}5 d1 H' }6 d1 J0 C0 f
- 12/*获取距离*/
/ `" ^* N9 X8 C+ ^* D n - 13void Get_HC_SR04_Distance(void)
( G. [" l, ~3 H - 14{
9 M& F: I, |! S( k - 15 static uint16_t count=0;
+ m1 _! V* J1 @$ p D% l1 O; O9 m - 16 switch(count)& Z) A# N1 e$ ]$ n0 ~0 G
- 17 {
8 h2 F9 ?! m& j9 p/ V" j+ ? - 18 case 1:
+ X- @! ~/ ]. n! W& { - 19 {
6 S; S5 W/ p# H; X4 @% d$ Y - 20 GPIO_SetBits(Acoustic_Port,HC_SR04_Trig);//Trig发送一个大于10us的高电平
; S) j' K E! h6 G m' N - 21 }break;
n( j2 P% l, E; K - 22 y" Y9 j( H4 X+ x) G- y" N6 Z2 F
- 23 case 15:3 t" a, r; l* Y- f/ P( d
- 24 {
( x. }' j2 ^2 {; m - 25 count=0;9 H. ^/ t4 N) ~9 ^
- 26 GPIO_ResetBits(Acoustic_Port,HC_SR04_Trig);
$ w+ K: P/ f: g5 n - 27 while(GPIO_ReadInputDataBit(Acoustic_Port,HC_SR04_Echo)==0);//当Echo为0时打开定时器 计时+ T, O' ?7 r* ?' o, p
- 28 Open_Tim2();. N" v) m3 K3 [* b, b# e
- 29 while(GPIO_ReadInputDataBit(Acoustic_Port,HC_SR04_Echo)==1);//当Echo为0时打开定时器 计时+ X- l3 e9 m# i ~0 p0 J
- 30 Close_Tim2();1 U2 U6 S! y7 A- G, F
- 31 HC_SR04_Distance=(float)(Get_HC_SR04_Time()/5.78);
) Q9 K7 Q& Y( c4 Y9 T1 R' R8 L - 32" K# |' B6 M: I1 x8 Y
- 33 }break;8 y% Q, N2 t( a4 w* ~% x
- 34 default:break; `) t3 x3 S6 K4 g2 a5 t/ m
- 35 }
- J. s0 }. `! p4 g - 36 count++;
1 |7 ~. o" z* e( v - 37}
复制代码 1 X0 p& H- B" U( E" B( R3 w
! u3 u: F2 d) R6 K0 v$ I4 t- n' T$ h9 U" x+ C0 z; `2 W! a+ q
3 P- s/ Y& L! R9 B ( R4 i( y5 b: E y" ~6 B, Y+ [
舵机模块
' O2 n& V ?" z$ }9 R( `
- d* q9 |1 K" B! \+ l本系统使用的是SG90型号的舵机,舵机是一种常见的角度驱动器,本系统需要判断不同位置的障碍物可以且对转向的力度小。舵机可以理解为方向盘称,方向盘是一个常见的名字。它实际上是一个伺服马达。舵机实物图如下:
8 ^- ?3 |' s |2 L
' I/ g! `1 B4 c8 L- W, ]
舵机模块接口简单,舵机模块只有三个引脚。分别引引出了三根线左右两边是电源正负接口线,中间一根是PWM信号线直接连接单片机的控制引脚。通过控制单片机的引脚输出的脉冲宽度进而控制舵机旋转的角度。舵机每增加0.1ms 舵机对应增加9度。
( A4 l; S9 u0 J5 w; P0.5ms---------0
5 A& Y! ]' M1 n6 `1.0ms---------45 # i$ u) {, H( W3 S
1.5ms---------90
& g( ~. Z. j7 I/ I' t% |6 V2.0ms---------135- [8 L" b+ h% p* ?) F. y& ?- O
2.5ms-----------180
& W6 u9 z+ J6 H) ~2 X! S 20ms的时基脉冲,如果想让舵机转90度,就应该发生一个高电平持续时间为1.5ms,周期为20ms的方波,duty=1.5/20=7.5%。在这里设置定时器自动重装载寄存器arr的值为1000,所以当占空比为百分之75是,在程序中就要设置占空比为75/1000=7.5%, 这就是具体的算法。3 V2 y7 X& X" p# e* [/ _
舵机重要代码(可参考)( y+ ^/ S, P, `: Y& j/ b- c8 R" U
- 1/**PWM引脚初始化*/
- \0 K$ Y6 \1 I3 D& B - 2static void SERVO_Gpio_Init(void)
! ?" H* K. ^7 Z4 H; v4 J - 3{
. W5 p! m9 w) E1 }6 | - 4 GPIO_InitTypeDef GPIO_InitStruct;
8 C3 G3 Z6 {, q9 h4 _; t! H" C$ F; e - 5 /*----------通道2配置--------------*/
. T: E' m# `7 F- Q - 6 /*定时器3输出比较通道*/- W! K6 t1 N/ ~ C7 v
- 7 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
# E9 \. b$ r; e' A6 B - 8 /*配置为复用推挽输出*/
# l) v1 V0 v" }* v6 p6 s - 9 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
( L6 L, ?9 s ]2 h# S; h$ z - 10 GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
+ d" j" H! O8 M5 N% \8 e$ g - 11 GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
. _# i% [ Y" c - 12 GPIO_Init(GPIOA,&GPIO_InitStruct); A* { c) X. c3 U/ _& e
- 13}! I9 H0 c2 ?" z- F& l4 r
- 14//定时器3初始化,设置TIMx的ARR,PSC) H( p1 X! C3 _# k+ ^3 X8 ~
- 15//arr:自动重装载初值,psc为预分频值,两者配合控制定时器时钟的周期
. u$ b: Q% f) S1 O* p' C; V - 16static void SERVO_TIM_Config(TIM_TypeDef* TIMx,uint16_t arr,uint16_t psc,uint16_t duty)
% F8 Y+ F1 t; j" t b, f - 17{ L, N: f7 Y/ ^$ J. Y4 p, m. ]
- 18 //-----------------时基结构体初始化-------------------------/
: N. n, _, A0 m: f1 B - 19 TIM_TimeBaseInitTypeDef TIM_TimeStructure;
% s. E" x8 i7 J5 U) C9 r+ p) h. l9 Q% d - 20 /*开启定时器3时钟,即内部时钟CK_INT=72M*/3 E% F, t8 J( u4 c
- 21 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
& C, ^' i! b2 s3 M1 `1 z - 22 TIM_DeInit(TIMx);- R+ X7 r( @3 [6 w3 Q' s3 H
- 23 /*内部时钟作为计数器时钟,72MHZ*/& M: R% H; Q* d+ _, d; X
- 24 TIM_InternalClockConfig(TIMx);
; T8 W; s6 }: I9 Z2 j, Q - 25 /*自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断*/6 }! x7 d; ^/ V' \% S; ^
- 26 TIM_TimeStructure.TIM_Period=arr;//1000 当定时器从0计数到999,即1000次,为一个定时周期( R* Z: L( t6 t% ]* Q; M% M
- 27 /*时钟预分频系数为71,则驱动计数器的时钟CK_CNT=CK_INT/(1440-1+1)=0.05MHZ*/6 `# x+ T. ]) I1 Q! x3 n# B0 p) u
- 28 TIM_TimeStructure.TIM_Prescaler=psc-1;;//1400 //即定时器的频率为5KHZ
4 F) u" w3 X1 i9 y7 n" l; E2 p - 29 /*设置时钟分割,TIM_CKD_DIV1=0,PWM波不延时*/
9 K9 c* s2 v9 b6 Z - 30 TIM_TimeStructure.TIM_ClockDivision=TIM_CKD_DIV1;$ s ]- t* t! s& K9 l7 I0 s8 h6 L
- 31 /*向上计数模式*/
* e8 k& ]0 p% c* \ n- l- e - 32 TIM_TimeStructure.TIM_CounterMode=TIM_CounterMode_Up;
7 }3 o( W$ s. x- M6 N% K- j# | - 33 /*重复计数器*/$ @# j$ j1 _$ Y; y- e1 E
- 34 TIM_TimeStructure.TIM_RepetitionCounter=0;
; b" [' r2 S% X! k5 Q& l$ y$ f - 35 /*初始化定时器*/! ^ u( E, M0 v
- 36 TIM_TimeBaseInit(TIMx,&TIM_TimeStructure);
& A7 }, W: X- G - 37 /*使能ARR预装载寄存器(影子寄存器)*/: C) m. c" s3 p- n9 d
- 38 TIM_ARRPreloadConfig(TIMx,ENABLE);
& l( S+ }8 O$ ?0 G9 r& F2 G) r2 p - 39 //-----------------输出比较结构体初始化 开始-----------------------/; K6 [+ s/ l5 s* u
- 40 TIM_OCInitTypeDef TIM_OCInitStructure;) y4 p. T$ j S1 r
- 41 /*PWM模式设置,设置为PWM模式1*/
' {$ A+ X, K& O6 s' r% m X - 42 TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
6 }, N% E3 a+ U( T. F - 43 /*PWM输出使能相应的IO口输出信号*/. z1 H' G& I# ^- |8 U
- 44 TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;6 m4 u0 m3 P: e. f: v8 M
- 45 /*设置占空比大小,CCR1[15:0]: 捕获/比较通道1的值,若CC1通道配置为输出:CCR1包含了装入当前捕获/比较1寄存器的值(预装载值)。*/
+ \9 E! ` \, u( m' W7 g8 [3 X - 46 TIM_OCInitStructure.TIM_Pulse=duty; //占空比大小
, v; J c$ M# T$ d9 @ - 47 /*输出通道电平极性设置*/
! k* ~4 B4 T+ h# F6 l5 A - 48 TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;" q+ Q: B2 E( J$ Q- x! I& B, b
- 49 /*初始化输出比较参数*/) y5 W5 P1 H" d$ E0 I
- 50 TIM_OC2Init(TIMx,&TIM_OCInitStructure);
2 c. Q1 z0 Z2 k r. P1 w. E - 51 //-----------------输出比较结构体初始化 结束-----------------------/
( f& ]4 e- c* F3 `% l - 52 /*自动重装载*/8 ^& y! }7 U1 E8 ?% g
- 53 TIM_OC2PreloadConfig(TIMx,TIM_OCPreload_Enable);7 h( k5 U5 A- _2 }: a
- 54 /*使能计数器*/# e$ h4 W: U- l, g- b
- 55 TIM_Cmd(TIMx,ENABLE);
. x* z- H- L* W, [8 r& l - 56 /*主输出使能,如果设置了相应的使能位(TIMx_CCER寄存器的CCxE、CCxNE位),则开启OC和OCN输出。*/
8 p4 ?0 b2 t' n; A$ J3 ` - 57 TIM_CtrlPWMOutputs(TIMx,ENABLE);
9 S- w7 Z% b. \ - 58}
3 V# Y9 a( G4 e t, @: y/ \- Q - 59/*舵机PWM初始化
$ l% C7 X" J- F a7 X - 60 每增加0.1ms 舵机对应增加9度
4 X. J: v) j& S: H4 a( m4 _. g( w - 610.5ms---------0, M1 N/ H9 C* s
- 621.0ms---------45
4 ]; c s! P3 ~1 G. r- C - 631.5ms---------90
" D# B% D# D, T* T; N% S - 642.0ms---------135- Z4 J5 ?/ y) K, b/ Q/ ~. \3 l1 B
- 652.5ms-----------180
8 p) S/ r+ [; c5 ?( G - 662.1ms turn_left=150
6 z6 B* K) I7 z - 670.8ms turn_right=25
6 t, n& c. K0 b0 E* n" b1 U - 681.3ms turn_front=75
9 ]8 P7 e1 W! G. q6 S, I+ y - 6920ms的时基脉冲,如果想让舵机转90度,就应该发生一个高电平为1.5ms,周期为20ms的方波,duty=1.5/20=7.5% ,而定时器自动重装载寄存器arr的值为 1000 ,所以duty=75,时占空比为75/1000=7.5%. # S) q( ~* e& i* P- L, Q
- 70*/
9 l, E X. t# o+ i) F* K - 71void SERVO_Init(void)8 E7 q3 R9 E3 }/ M3 ]3 T) ^0 P+ o
- 72{7 [( `' T- K) l2 Z. W/ h+ q
- 73 SERVO_Gpio_Init();
- q/ _9 n9 t! `0 G2 z, C' J - 74 SERVO_TIM_Config(TIM3,1000,1440,turn_front);, ^' n4 n" l+ m9 N4 L5 N
- 75/** 我们把定时器设置自动重装载寄存器 arr 的值为 1000,设置时钟预分频器为 1440,则5 d0 D& Z! N/ Y; j4 h$ A# h2 r
- 76驱动计数器的时钟:CK_CNT = CK_INT / (1440-1+1)=0.05M,则计数器计数一次的时间等于:$ U/ ~9 ~$ D7 u
- 771/CK_CNT=20us,当计数器计数到 ARR 的值 1000 时,产生一次中断,则中断一次的时间
# |- j& A/ \$ R5 u) Q) C - 78为:1/CK_CNT*ARR=20ms。
) G3 U' l9 E! h8 F! q X3 x4 C - 79PWM 信号的频率 f = TIM_CLK/{(ARR+1)*(PSC+1)} TIM_CLK=72MHZ
6 m/ X0 N5 n0 n: P1 p8 V& j& g @. [7 ` - 80 = 72 000 000/(1000*1440)=5KHZ 7 j& a' e1 K" j6 i% E
- 81*/ 0 }0 z7 B- E6 N" q+ y9 t
- 82}
( N6 p6 ` [* i$ @ - 83/*舵机角度控制*/
4 {2 R% _+ t5 p% V$ M' u - 84void SERVO_Angle_Control(uint16_t Compare2)% A J% E2 F$ b+ _" B) u1 p* w
- 85{
* T! H* T" G: }. y. @ - 86 TIM_SetCompare2(TIM3,Compare2);$ M Z- K; H1 c8 H& s i' E7 R
- 87}
复制代码
; a3 H0 w" _+ {
' ?5 x, H* x; o+ W& o# t [7 c编码器模块
3 C7 d6 x# R9 X& T ' V0 G- x& c" N- o
调节小车前进的速度和避障快慢我们采用EC11旋转式编码器,可以用于光度、湿度、音量调节等参数的调节。EC11编码器的形状类似于电位器,中心有一个旋钮可以调节PWM信号,光电码盘利用光电转换原理输出三组方波脉冲。EC11编码器的实物图如下:
# \ v# R7 k: G5 c% d
+ X8 w9 q& B' R( J# h- D! j3 J& T3 b ( Z3 a# `: M# z, |& t+ Q* H+ a
OLED显示模块
: ~/ j; A+ _7 j( E: U; J9 X ; w$ V7 |( m# `, X5 W% ^
用来显示小车转速,以及左右编码器数值和电池电压等参数所用的是OLED显示模块,分辨率较高,而且功耗低,正常显示时仅0.06W,供电电压范围在3.3V-5V,有IIC和SPI两种通信协议可供选择。显示模块的亮度和对比度可以通过程序设置。由于它使用寿命长以及其他的优点,OLED更加适合小系统,本系统由于单片机引脚有限,不适合利用简单的LCD1602或者12864来显示,在多方对比之下OLED效果更好。OLED显示部分相对比较简单,大家参考中景园的例程就可以实现。. F2 E1 y+ S* E
6 \! V- {1 X8 Q5 ^. J9 M- p- F4 w# m `/ A* S. r( z
, _& w! ]6 h& P/ I0 b- g" H' h; i, D6 z N
文章出处:程序员小哈
1 X2 n7 C6 y8 ^! v) Z O |