前言% U1 E F# c* X! W" u5 E
原来做的差速小车是基于Arduino控制的,感觉有些简单,也有些基础,Arduino方便简单的同时,可操作性感觉也少了很多,所以想将控制器换成STM32,然后将树莓派作为上位机,STM32作为下位机,通过树莓派和STM32进行通讯,实现对差速移动小车的控制,本人也是寒假期间初学STM32,也是奔着应用去的,所以对于STM32编程原理方面可能不太精通,这里偏重于记录应用层面的知识。0 x4 N5 y1 Y- ~+ r( M9 i% X
. ]- y( R: l/ j k
一、PWM调速原理
T/ n& b$ H- n$ l% P1 j6 V直流电机驱动是最简单的,给电机通上电就能转,根据电机的公式:( I6 n4 H+ g! C, W' v
- s( I& O6 m( g! L8 ~
* \1 t8 ?+ Q6 r/ ^+ j$ m
可知:当提高电压时,反电势升高,进而转速升高,电压与转速大致有如图所示的关系
" @! X Q/ I5 j1 M9 S5 p1 H+ k4 ]
* y1 _( A0 b V2 W1 K
- G% v- i, l* ^9 f' `- W2 q! b
. L. L/ b9 t0 n; {" ^所以我们只要控制给电机通电的电压即可控制电机的转速,但是在实际的控制中,控制直流电机需要通过H桥控制电机的正反转,如图,当T1和T4二极管导通时,有粉色通路;当T2和T3二极管导通时,有蓝色通路,这样我们就可以实现弱电控制强电,通过二极管的通断来控制电机的转向。! Q& H, v* v- ~1 \
3 B$ |$ y0 y X# K) M/ N( d
但是这样电机通电时电压就是Us,我们如果想自由的控制Us的电压值基本是不能实现的,因为电机是接到单片机的引脚上的,引脚的供电电压值是确定的,我们就要使用控制二极管的通断时间对电机的转速进行控制,即PWM控制。
- K! O6 _. H- M0 ^& u( I/ g( z* O9 a/ I( b
图中的D1~D4二极管为续流的作用,因为电机中有绕组,在断电后,电感的电流不能瞬时变为0,所以在断电后电流沿棕色和绿色的通路放点。3 c# C* {; T% I4 O8 x
4 n+ V4 g1 O# S6 R
' r$ f2 y0 I) @. ~9 j
+ s# F2 H* R" O/ N
: T. r- P* R5 p- f
& ?8 O+ @+ I% @( ^ 在一个周期内,我们通过控制通电的时间就可以调控平均电压,而平均电压的高低直接控制电机的转速,通电时间/周期,就可以得到占空比,我们也就是通过控制电机的占空比来控制电机的转速的。
8 r- m4 P$ i' i5 ~
' U2 H) V- R0 n( R% I3 F6 h
i" n2 i6 q. ^2 J9 u- R
8 l o0 i, k5 B, l& M) L# O# f" T- k 在实际应用过程中,我们不用自己搭建H桥,而是使用电机驱动板(如:L298N)对直流电机进行驱动,L298N内搭载两个H桥电路,可以实现对两个电机的转向和转速进行控制。, n, i, @9 C0 y9 W: v& [ f' k
" f# ?2 }" h" C, p. N ^) k8 ^. D& w
% U; N+ n; S0 w0 l# f
' i6 K. d( m1 l! } 这是淘宝商家提供的电机驱动板控制表,将IN1~4接到单片机的引脚,我们就可以通过引脚输出PWM控制信号,对直流电机进行控制。
0 }. ~) O2 X! i: @' ?4 G0 l I/ y9 S' I/ x
二、STM32编程实现9 Z3 y+ c3 x5 r( J' ~6 G+ Z( X
在STM32中如果想输出PWM信号,需要借助定时器,通过定时器的捕获/比较通道的PWM输出' S; E; R% V v3 |( U8 K
! X0 k m/ I! l2 W: |' U# |) C
3 K& a1 x p7 U: \- F7 f
. D! {" t- N3 t- ?9 l& ~
当我们对定时器设置了预装载值arr和比较值ccr后,可以通过配置PWM模式,使定时器CNT计数值超过ccr后产生有效信号,并通过配置相应寄存器设置有效信号是1还是0,而配置PWM的输出方式,具体原理信息可以参考原子哥的视频,也可以参考中文参考手册的14.4.7内容。. a% P! h% _+ Q" B( v R
# l$ e, k! ?! `
- ^# O$ C" W$ R3 L7 b9 F5 X
/ h6 N. m7 X( V) e 在程序中,我们使用库函数进行配置,配置步骤如下:
$ L/ A0 S' J# N% @* D2 I& ~. \1.使能定时器和相关外设引脚时钟 :RCC_APBxPeriphClockCmd()& s# x" \4 L9 V# G1 G9 K+ w
2.配置IO口为复用输出模式(查手册8.1.11)配置成相应的模式(复用推挽输出). y* W, |/ A D
3.初始化定时器:TIM_TimeBaseInit()4 \' k/ A3 G/ c& B6 x6 d
4.初始化TIM2 Channe1234 PWM模式:TIM_OCxInit() W6 b+ t$ y4 r' `0 h
5.使能OCx通道的预装载寄存器:TIM_OC1PreloadConfig()
, i N7 O8 A; z$ I+ g8 Z6.使能时钟:TIM_Cmd()
$ [( o- }' d) y; s! a+ L( ], v7 I7.在主函数中配置占空比进行调速:TIM_SetCompare1()
3 }, ~& h2 g v: v' r
& B' ?1 k% k; S1 b5 O+ ~- #include "sys.h"
0 s2 w5 n" y M: i( p4 S
' K! Y) K# m1 ?% v$ w& O% r- /********************
5 M; U9 f/ n. S* u - 功能:通用时钟2用来产生通道1234四路PWM信号9 e. x. y* p7 G8 |* {) v; y* e
- 函数:TIM2_PWM_Init(u16 arr,u16 psc)5 ^# P; T+ d6 k" f
- 作者:K.Fire( E: W" Y! s- O0 h. k: i
- 日期:2022.01.30
" f2 ?; |. H% F0 N* K - 引脚:PA0 PA1 PA2 PA37 B# G! ?- U( Q& ?
- 参数:arr:自动重装值 psc:时钟预分频数
& A- U) i5 y) l# W0 w - *****************/: |& t7 X/ j9 O+ |2 n4 h9 B
- 2 a$ w' g4 p5 ~) Z3 M
- void TIM2_PWM_Init(u16 arr,u16 psc) t: _8 B) c# P3 |
- {
( F3 A: C- H$ o6 s. L1 h9 s. J - GPIO_InitTypeDef GPIO_InitStructure;7 r' n) w) q+ z8 F
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
: n0 I9 X, ~% S0 R; `. Q5 f% S$ }& L - TIM_OCInitTypeDef TIM_OCInitStructure;: @, Y3 h7 v. j& | X9 c
-
]8 g i* K2 `0 o$ E- u6 W - //使能GPIOA外设模块时钟 4 n" X6 U2 U9 A' m# w x
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
4 }/ {! H! O& }/ M! z - 0 k: o5 _. j7 T( W( P$ M
- //设置引脚为复用输出功能,输出TIM2 CH1的PWM脉冲波形 GPIOA.0/ u r2 b2 P+ ^1 r
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM2_CH1" x% ~3 r+ U. B: M2 f n
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出$ b8 Q9 Y- E4 J* u" t% I
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;6 i+ S5 [4 m2 V' K
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO: {" D6 M. {. n" H1 e
-
7 }3 }% h' U/ z8 {& v4 J1 _/ ?, ] - //设置引脚为复用输出功能,输出TIM2 CH2的PWM脉冲波形 GPIOA.1 Z6 t$ y3 H; k8 Z' r
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //TIM2_CH24 J5 z$ l* \1 \5 q Q+ [
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO. D# `' u. w) p' r* F/ s8 ^8 _
- + `- m* \/ \' z8 o! ]/ I
- //设置引脚为复用输出功能,输出TIM2 CH3的PWM脉冲波形 GPIOA.2
( Y, F+ Q* e0 a9 Q: w) |! ~ - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //TIM2_CH3
! W8 u, D2 L9 _% v - GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO, f, R' v! ]) I, U0 z- C$ b/ s
- ; d: D% J" L+ {$ v+ {7 z+ }
- //设置引脚为复用输出功能,输出TIM2 CH4的PWM脉冲波形 GPIOA.3
+ X" v* E* p: r, q - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //TIM2_CH4
( }" R# \0 ~" w0 v& k6 R' ~- } - GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO3 i4 q7 M. I4 S% s0 t
4 O- f6 \# U/ x& O, }- //使能定时器2时钟& l% f% K/ v7 _2 R; S
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
' o0 j4 i1 o8 m y+ x) N4 B8 ~& y E - //初始化TIM2 w8 l/ }6 W0 S: w: [# F- v
- TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值1 A, D: p1 y6 s0 J5 Y( L ~7 \
- TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 2 C3 t" w9 {0 \$ j Z0 F
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim9 h9 I# A9 g6 Y8 k! z6 X3 f
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式4 `+ @6 A4 L9 d- U. V* y( y$ l# k
- TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
/ f" s0 D/ d1 t0 ~% S2 Z- _ - , m( M( a0 X( _, I a" F
- //初始化TIM2 Channe1234 PWM模式
# h/ U9 u- b( R2 I - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2# a1 N% s5 T. w# t* E
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
8 t; l) a3 ^8 I& Y5 B- q+ M6 R - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
1 j- h2 Y. T9 z1 D( m3 B. r - 0 z# K, |& M- A' o9 `* y/ ]# b
- TIM_OC1Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC1
. ^* w5 G' ` H( v# g - TIM_OC2Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC2
2 u+ ]" u* Q: f5 e- y3 u9 q) [ - TIM_OC3Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC3) ], `+ ~. S; c& t2 a7 N- W. [ _
- TIM_OC4Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC4
" H6 j0 H+ O6 L: ^/ y9 X. ]$ V
9 c1 L+ f+ T$ s. ^/ A2 x- TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR1上的预装载寄存器
A+ I# C( @6 Z7 V - TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR2上的预装载寄存器" q- y0 K) _6 {& U& T
- TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR3上的预装载寄存器
* u) L) X- Q, [5 M7 ~+ ?7 p - TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR4上的预装载寄存器
) r. |6 h3 o) n% ~+ G1 d
# p2 y5 [( d! N# I- TIM_Cmd(TIM2, ENABLE); //使能TIM22 X! y# x' Y5 V4 ?4 x
- 3 I# N3 R3 I/ B
- }
* H$ z! h2 X/ i
复制代码 & A2 }% X5 \. r) s3 _" H
总结
" S+ C! r. V! d8 b; }1 o2 k原理部分我讲的不清晰,我也是看原子哥的视频学的,大家可以通过传送门去B站看原子哥的视频,先更新PWM调速,因为这一部分我测试成功了,等我编码器来了在对编码器和蓝牙进行测试。
1 y+ [# M' D3 g* R1 [! }+ r+ b0 [5 j( w
. w6 j8 ]! k+ m) ?
9 }' S! G7 L: O4 y9 l3 X
|