前言
1 I" h/ `$ Y+ n! M5 ~$ t原来做的差速小车是基于Arduino控制的,感觉有些简单,也有些基础,Arduino方便简单的同时,可操作性感觉也少了很多,所以想将控制器换成STM32,然后将树莓派作为上位机,STM32作为下位机,通过树莓派和STM32进行通讯,实现对差速移动小车的控制,本人也是寒假期间初学STM32,也是奔着应用去的,所以对于STM32编程原理方面可能不太精通,这里偏重于记录应用层面的知识。
) U& K0 P* s+ t# F% y
: Q* M5 r) Q v5 }一、PWM调速原理
8 X$ ?. S u& M# A: N直流电机驱动是最简单的,给电机通上电就能转,根据电机的公式:6 ~: V/ o0 q ], p+ q7 e
& T9 A5 j3 d* E8 N
6 B, [; a! r# z2 l, O- E8 c可知:当提高电压时,反电势升高,进而转速升高,电压与转速大致有如图所示的关系5 w4 c8 w0 _0 A$ J% v$ N2 j
. x' p5 Y f9 E3 A8 c s
! X* g3 V: T" Y4 i
, I6 v# C G# p- D, K所以我们只要控制给电机通电的电压即可控制电机的转速,但是在实际的控制中,控制直流电机需要通过H桥控制电机的正反转,如图,当T1和T4二极管导通时,有粉色通路;当T2和T3二极管导通时,有蓝色通路,这样我们就可以实现弱电控制强电,通过二极管的通断来控制电机的转向。1 G0 ^" e( u) @" B! H7 u
$ R e+ O( D' r2 n但是这样电机通电时电压就是Us,我们如果想自由的控制Us的电压值基本是不能实现的,因为电机是接到单片机的引脚上的,引脚的供电电压值是确定的,我们就要使用控制二极管的通断时间对电机的转速进行控制,即PWM控制。2 z! s( Q* X& w4 t/ I; y) ]. T
, Q, \; }. ` J6 C7 o
图中的D1~D4二极管为续流的作用,因为电机中有绕组,在断电后,电感的电流不能瞬时变为0,所以在断电后电流沿棕色和绿色的通路放点。
0 T/ ~! V2 ~) h$ u* U _; j2 O3 p9 m2 I" D
x: }- D' ?: g; T% _
5 n5 `/ S. B5 y1 _9 n
4 Y5 B, M7 H( J0 b2 }0 t
% S( R: v- F) [2 G+ P: A$ X; @ 在一个周期内,我们通过控制通电的时间就可以调控平均电压,而平均电压的高低直接控制电机的转速,通电时间/周期,就可以得到占空比,我们也就是通过控制电机的占空比来控制电机的转速的。: B# s3 G( Q& ^5 u
) J7 E: h9 n. Z) Q; h- D
2 N( @8 S# {) q S8 y* r/ n
( _, v) c- B0 k5 e3 L
在实际应用过程中,我们不用自己搭建H桥,而是使用电机驱动板(如:L298N)对直流电机进行驱动,L298N内搭载两个H桥电路,可以实现对两个电机的转向和转速进行控制。
, k) U) \" I4 h9 u! A0 V
+ o5 S* S/ {* r9 R2 r- Z; J
* b" r# O0 H: Q+ a: x4 A2 I0 V3 R( a- N! @
这是淘宝商家提供的电机驱动板控制表,将IN1~4接到单片机的引脚,我们就可以通过引脚输出PWM控制信号,对直流电机进行控制。
* h* o9 Z8 B( w: P$ J$ B1 I0 P" l8 k' c4 ?
二、STM32编程实现
! U6 ?, n, M; W* F在STM32中如果想输出PWM信号,需要借助定时器,通过定时器的捕获/比较通道的PWM输出
0 @8 L x$ c) Z h9 L/ s) G' t3 k4 {& }# W
8 S% R- y7 D1 Y9 F" R3 @: _5 N, \) [
9 w; N) @8 {: h" m# P 当我们对定时器设置了预装载值arr和比较值ccr后,可以通过配置PWM模式,使定时器CNT计数值超过ccr后产生有效信号,并通过配置相应寄存器设置有效信号是1还是0,而配置PWM的输出方式,具体原理信息可以参考原子哥的视频,也可以参考中文参考手册的14.4.7内容。6 H u! Y' A8 v4 d, w: n0 m
u/ ]6 B% x9 w# N
* ]6 M# b4 D( V& F. L. K# i" A# ]' P' n5 j# N6 Z
在程序中,我们使用库函数进行配置,配置步骤如下:2 j. \% M& `# ~' i H0 q
1.使能定时器和相关外设引脚时钟 :RCC_APBxPeriphClockCmd()
; `) e; g' A- [0 D2.配置IO口为复用输出模式(查手册8.1.11)配置成相应的模式(复用推挽输出)
; e5 D1 W3 g2 R' i3 K3.初始化定时器:TIM_TimeBaseInit()
t. X& j/ l+ W7 V3 y- _( V4.初始化TIM2 Channe1234 PWM模式:TIM_OCxInit(). [! `. P" ^, V) H
5.使能OCx通道的预装载寄存器:TIM_OC1PreloadConfig()7 O" ~1 y- W: n
6.使能时钟:TIM_Cmd()1 W3 g2 {) D1 @1 a( j5 r$ z* S2 W
7.在主函数中配置占空比进行调速:TIM_SetCompare1()
3 W% e; ?0 z4 M
1 P( F# x) [; _. Q. t- #include "sys.h"- P- N6 U& k+ a
- 1 v0 r7 {4 \) V9 u
- /********************
: p1 O8 Z+ ]% w( [7 \0 C: s* v - 功能:通用时钟2用来产生通道1234四路PWM信号% T' r- L9 F' y4 ~' Q1 x1 p' p7 j
- 函数:TIM2_PWM_Init(u16 arr,u16 psc). V9 O' D* f6 c* A' B4 o0 w
- 作者:K.Fire; n& ^# L6 P$ o- a+ W; U' M0 `; G
- 日期:2022.01.305 T1 z1 T. @: | L! g- O k2 k
- 引脚:PA0 PA1 PA2 PA3# v; W9 M5 y7 ^" R
- 参数:arr:自动重装值 psc:时钟预分频数/ j( i' ~5 S; }
- *****************/
! x5 u8 k; t- B' G" J5 e - / v' g# Z% `! @, T4 F
- void TIM2_PWM_Init(u16 arr,u16 psc)
) |- q7 Y$ a* b5 `6 u9 N" E - { 2 z/ I' z7 S- Q2 T! C; f
- GPIO_InitTypeDef GPIO_InitStructure;
$ _& V3 P( Z6 a3 }. C; K6 w - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
0 s, s f. m( o - TIM_OCInitTypeDef TIM_OCInitStructure;
$ V- U# m/ ~' S4 l7 ?- n6 @* T - * h$ [! n4 O! j6 ?6 [% B0 `
- //使能GPIOA外设模块时钟 * G" t9 H3 D8 v5 _/ }' g X3 n8 Y
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
; u' P7 Q* A1 i' a9 G1 w+ w7 K
& a* s( ^( ?3 |( u- //设置引脚为复用输出功能,输出TIM2 CH1的PWM脉冲波形 GPIOA.0( z3 T4 x7 @6 V9 s1 P* V1 Y' h; _' a: L
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM2_CH1
& _1 ~! C w: H. P% c - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
, l2 t& A- e6 F8 N; N2 v" [ - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;! s6 ^! P" L7 i) i/ E5 e
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
9 w' b) x H# b -
9 f) U2 Q q* s& Q: a# F% c' Q( v7 q - //设置引脚为复用输出功能,输出TIM2 CH2的PWM脉冲波形 GPIOA.1
/ o* v; w3 @' d, j& f T - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //TIM2_CH2
4 W9 H8 Z9 k7 _8 N7 X, U0 v1 I5 P - GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO* d4 j8 `+ k, h% m; H1 d& P8 X7 U
-
' H7 ~, W' T3 p - //设置引脚为复用输出功能,输出TIM2 CH3的PWM脉冲波形 GPIOA.2
# `/ ?2 E. ~3 M) d( Q' {' O - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //TIM2_CH3, i, n5 B7 W: }* i4 a
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
, z% y) n( f @, V- o& |2 ^" A - 0 q2 ?$ m) b4 g" K
- //设置引脚为复用输出功能,输出TIM2 CH4的PWM脉冲波形 GPIOA.3
. P5 ?9 @) r P9 F" I- N' Y: a% ^1 I - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //TIM2_CH4 n; ^ y6 o8 o* |
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO- K4 }, z; e" g; R+ A# j1 [5 v
- ) i' L1 h" J z
- //使能定时器2时钟
- f# Q& L: x. s" c. s! D! i9 k1 M - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); " L* b+ Z! H D7 T! \+ ^
- //初始化TIM2
% R' M8 w7 K& J8 R+ `3 D - TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值0 |& C/ U" F' O( M
- TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
( ~6 y1 z& ~( P, _+ G - TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
' m: n. E" A$ I3 | _1 `2 W. ] - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式7 M+ b* E3 H, E; ], @# z( B
- TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
7 {# D+ S6 I- r5 L# b - 8 A" M% T( n8 Z( t
- //初始化TIM2 Channe1234 PWM模式
/ _1 e8 V0 r1 F! Y; s - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式22 B; Y' ^4 d' ~1 _1 i5 w
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能7 M, A0 f6 B5 E5 R- b4 U
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
! j. x5 F* a0 m' t) _ -
1 { x. {9 b7 h! @. t n0 Y4 w+ N - TIM_OC1Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC1& l: t8 A B/ Q8 u, n
- TIM_OC2Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC2+ B* \& m, P0 b% q: u6 x e' ^) I- \
- TIM_OC3Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC31 ~$ j9 A( i H2 A6 C& m7 K! a& F# K
- TIM_OC4Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC4
$ m. L( p& A7 p" } B: b+ P
1 l+ B# m, f1 o1 o* M: A- TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR1上的预装载寄存器, t/ E8 q b3 [8 [
- TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR2上的预装载寄存器
4 q" X1 C1 N$ p+ w! y' A, z: `- Z - TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR3上的预装载寄存器# J6 y% M! p3 T7 Q" l
- TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR4上的预装载寄存器
& G1 A4 ]! k0 i% @% j% [0 D; b( X" v
. x7 o5 Z# @( g& c, a y: S: {; H- TIM_Cmd(TIM2, ENABLE); //使能TIM2
6 m/ k4 D! w7 G9 P. G/ l8 { - + C6 i8 M' u3 g6 p
- }9 o" ]) t7 j8 V# ^$ A/ _
复制代码 ' d- a; m5 {5 o* a
总结
- c U& l7 X3 ?' V/ R r K原理部分我讲的不清晰,我也是看原子哥的视频学的,大家可以通过传送门去B站看原子哥的视频,先更新PWM调速,因为这一部分我测试成功了,等我编码器来了在对编码器和蓝牙进行测试。, Z, L, S8 t- }% S
+ M" p" [6 H- ^+ X6 x! D9 e7 ]0 s
K" t# C! g, G5 ?; e" ^& F7 @1 T( o: ?+ `& [9 O
|