2.2 STM32电机测速(正交或者霍尔编码器)
# B% W8 e) C( i) }; c2 q2.2.1 实现工具
% ~5 ~8 M, ~* k# i% c& qSTM32单片机、带编码器的直流减速电机、Keil5、(蓝牙、串口助手)调试用 ) j, y$ Z3 |1 N9 p7 k j6 x. Y- E
2.2.2 编码器原理
* ]% C: @) {9 e% Q4 }" }(1)编码器是什么?
! R9 }. Z s: {" t! `; ~$ {& c6 d
编码器是一种将角位移或者角速度转换成一串电数字脉冲的旋转式传感器。编码器又分为光电编码器和霍尔编码器。 , `" m& w+ y+ C [+ _
(2)编码器工作原理是什么? * q3 l1 T7 M' z. D1 v
霍尔编码器是有霍尔马盘和霍尔元件组成。霍尔马盘是在一定直径的圆板上等分的布置有不同的磁极。霍尔马盘与电动机同轴,电动机旋转时,霍尔元件检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。示意图如下:
5 ?6 |% U [" b% O
(3)带霍尔编码器的直流减速电机接线图
: M0 ^% M n2 {+ S5 y5 r
' M' ?& y5 S- [
(4)测速原理: # C2 q$ Y! `3 \5 B& P' v8 f
单位时间内,根据脉冲走过的距离计算电机实际速度,这里采用5ms定时器中断。 1 U5 E: n+ U" H7 Y6 b9 B1 i/ i
(5)采集数据方式:
" m0 Y1 _& y {+ a( \# ]
通常有两种方式,第一种软件技术直接采用外部中断进行采集,根据AB相位差的不同可以判断正负。第二种硬件技术直接使用定时器的编码器模式,这里采用第二种。也是大家常说的四倍频,提高测量精度的方法。 2 C# d' I9 \! U2 L
其实就是把AB相的上升沿和下降沿都采集而已,所以1变4。自己使用外部中断方式实现就比较占用资源了,所以不建议使用。 # R8 l) [3 F$ B5 _8 S1 n9 J0 _
(6)速度计算方法:
. S) N" S* r- z( h# T5 k7 g
这里计算的是真实的电机轮子的物理转速 电机转动一圈的脉冲数:num1 单位:个 单位时间:t 单位:秒 单位时间内捕获的脉冲变化数:num2 单位:个 (反应电机正反转) 电机轮子半径:r 单位:m 圆周率:pi 单位:无 速度:speed 单位: mm/s( T) q5 j+ ^' O. q
speed = 1000∗num 2∗(2∗pi∗r/num1)/ t speed = 1000*num2*(2*pi*r/num1)/tspeed=1000∗num2∗(2∗pi∗r/num1)/t
) j2 ^2 C* g+ Z2 Y8 [2 K 2.2.3 部分代码分享& S9 ~- q. |; P( f$ k: w+ R, k
& ^, N. q& c# L+ f6 w1 [, `
6 \) Z, E0 |& F& c% a/ m* H. e8 C: _4 L* F! q5 I2 b
主要流程: 将编码器AB相使用的引脚设置成定时器的编码器模式,我们根据TIMx->CNT寄存器数据的变化,计算出单位时间内,脉冲的变化值。然后在定时器中断服务函数中进行速度计算,然后将速度数据通过蓝牙发送到PC的串口助手,验证数据是否正确。 ! e& Y- o7 s9 o) o; t% ~& q, o5 W
(1)编码器引脚配置以及测速代码 D" c( ^8 m* A9 h# J* i
- #include "encoder.h"- x$ ]6 C8 n/ k
2 \" ]7 ~ G+ L! b, ]" s% e0 G$ D- /*************************************************************************** O' E5 T0 M! d; J; q0 B- s
- 函数功能:把TIM2初始化为编码器接口模式
% M; }7 ^! b4 A8 F* u/ ^5 @, Z - 入口参数:无
7 Y/ B8 t5 {/ H S+ }0 T4 n - 返回 值:无* ~ M' c$ n2 q" o- p0 E0 q' c
- **************************************************************************/
7 |8 H/ c5 L0 V- k" C \0 |. l$ e - void Encoder_Init_TIM2(void)8 x9 h) {# f2 k; u7 `( t9 T6 R
- {/ M9 b, V" t$ e9 A6 Q
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; / D% k3 Q4 Z+ u( F1 O1 p% G; A
- TIM_ICInitTypeDef TIM_ICInitStructure; $ M) F* n3 G# U# V P
- GPIO_InitTypeDef GPIO_InitStructure;
* @! B4 W' V3 D: T. R - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能定时器2的时钟
' `+ k" }+ t% M2 |- g/ u3 r - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
) \3 t' F8 H6 C) H- U1 l0 u. k - - T6 b* d- a5 U" x* R5 d
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; //端口配置( U! z4 u Q! c( t
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入6 k0 r# h8 G, p0 Y" y
- GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA( B% V; F4 q6 v/ L
- + B# j0 W! j; C# i6 a# ?
- TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);+ J& L2 q8 X7 v ]1 g' e
- TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频器
! Q2 T) `; B; B+ s. w7 V - TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值: b" _3 {2 F3 G! n, `- L% |% e1 ~; x: u
- TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择时钟分频:不分频3 k& @% o& o, p. A& s2 n; r
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数 5 J# r8 d. a; r$ M1 h
- TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);) k+ {+ F: l. w
-
4 O. T2 g4 q) w) n1 Q1 L; [6 }2 d# Z - TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3& S7 G% G; Q( X* k1 A
-
3 N6 G9 G3 t9 U0 W1 A. N5 N - TIM_ICStructInit(&TIM_ICInitStructure);+ ?% R: f4 q+ P' S
- TIM_ICInitStructure.TIM_ICFilter = 10;
# Y) K' V7 L$ p" X* m* ]9 k - TIM_ICInit(TIM2, &TIM_ICInitStructure);
5 q/ j+ y1 L3 J( d+ v! y - TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除TIM的更新标志位
, n( z/ H* q$ b3 M: ~$ Z* t - TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
; ]5 ^8 X; S/ V0 T# l+ D# v - //Reset counter
3 m( ~ `" A1 n9 U# K9 u5 K - TIM_SetCounter(TIM2,0);0 s+ Y8 U1 H* q; Q# o
- //===============================================
+ J4 y3 l! ~/ t2 M: s6 J# m - TIM2->CNT = 0x7fff;
" t* |; N5 M! M - //=============================================== _/ i( k; U9 u5 l* k% h. W e2 G
- TIM_Cmd(TIM2, ENABLE); / ?) L6 E i- I9 Q2 v+ e
- }& D( Y% C: V" E4 l* G
- /**************************************************************************
+ Z4 L% i9 R D' V ` - 函数功能:把TIM4初始化为编码器接口模式$ {0 w9 F5 @- U! n5 u: t
- 入口参数:无
" x0 z& e5 B; v: i! w+ J6 @: _ - 返回 值:无' }! n' [& _8 C5 [
- **************************************************************************/% z& X9 k. w; \' U
- void Encoder_Init_TIM4(void)
8 O7 E% \4 g. I: \# B, F) h - {" o2 }" v/ b G' J2 w8 _! ~
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; ; k8 |7 n9 q$ `9 G9 a+ }
- TIM_ICInitTypeDef TIM_ICInitStructure;
1 v$ s& \; s5 P - GPIO_InitTypeDef GPIO_InitStructure;
* d5 P8 Q# V' P3 D/ v - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能定时器4的时钟. Z! u# _# h' H" q0 M/ j3 [
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能PB端口时钟
^ G% U: z7 f( X8 w: }1 B3 } - 5 b p1 s2 r* B) v, W4 a( P* A+ n
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //端口配置& k. ?% x4 l& c% |( ~
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入, `( M3 x7 S3 J9 F' o
- GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB
" j( J/ m8 N4 S: h' y2 a. l - : Z$ i% }* F9 [% I9 {4 D
- TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);9 \% B) j, r, {+ S+ c
- TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器 * C, @8 d4 I1 p+ s2 H# N9 Q
- TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值
- ~5 B* v1 n3 T - TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择时钟分频:不分频; G' M# |6 z: O
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数 w9 u1 U7 }; t# W
- TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
$ P+ _7 B5 O7 {4 O& H. E - TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
4 E( B8 |% K' e- N" K - TIM_ICStructInit(&TIM_ICInitStructure);, p2 Z; Z4 N. q1 G( _0 x
- TIM_ICInitStructure.TIM_ICFilter = 10;& W# v# L0 E% U8 c5 |
- TIM_ICInit(TIM4, &TIM_ICInitStructure);
. ?* c2 ~* N& {1 {+ o - TIM_ClearFlag(TIM4, TIM_FLAG_Update); //清除TIM的更新标志位6 Z8 r: ~# z% ~ y+ H
- TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);% s, v( c8 N6 l6 ~ h* L" \ V) S1 D4 ^
- //Reset counter
) ?# P* r/ V+ V9 F - TIM_SetCounter(TIM4,0);
- H: f3 n! X3 ?# \0 l - //===============================================
4 w7 X: T7 \- H, L5 J5 l - TIM4->CNT = 0x7fff;
0 I. Q/ \. }* m - //===============================================! F% x7 y( x' T% ]# F
- TIM_Cmd(TIM4, ENABLE); ! T" v1 t4 {8 X0 _
- }: Z9 |- I2 g( |8 K$ v5 e! J
$ z. M# o6 N8 z' N# `- /**************************************************************************$ b8 i- i E, h
- 函数功能:读取编码器脉冲差值- r! i& N" N/ T+ u* C5 ~/ T' x
- 入口参数:TIM_TypeDef * TIMx
: g) A8 g# }! o$ S8 D - 返回 值:无+ J2 G5 J B% j k0 m
- **************************************************************************/
+ p' C4 x, B S3 p4 P+ U1 x - s16 getTIMx_DetaCnt(TIM_TypeDef * TIMx)# T' B; w" G7 j# C U A2 n' r
- {
5 ^% X7 {/ e/ [ - s16 cnt;+ K! Q; ?) O* [' `; A
- cnt = TIMx->CNT-0x7fff; //这一点默认认为,单位时间内编码器不会出现0x7fff的脉冲变化,事实上也是这样8 Q0 D' d+ f% o& j, I
- TIMx->CNT = 0x7fff;% D+ k9 n! |! T3 w6 Y! x
- return cnt;
; d+ ^# I, b' _1 ?2 O - }
1 N, T* q7 `0 O) _( @ - t0 T; ?7 Y+ R/ J' b/ Q2 f
- /**************************************************************************
' X- E/ n* V) ^. X4 h% s3 ~ - 函数功能:计算左右轮速6 x! J7 J+ {' W! T% j8 S
- 入口参数:int *leftSpeed,int *rightSpeed
1 j u4 q0 @4 }: m% s( K - 返回 值:无/ Z3 ]9 n/ u3 v
- //计算左右车轮线速度,正向速度为正值 ,反向速度为负值,速度为乘以1000之后的速度 mm/s
& A, q( r- u, Z( B' Z% N( p ~ - //一定时间内的编码器变化值*转化率(转化为直线上的距离m)*200s(5ms计算一次) 得到 m/s *1000转化为int数据, j$ J$ F+ p; p' b( w! }6 B, w
' ?/ A, s S' U. n- U- 一圈的脉冲数:& O6 f! @/ L' a/ L, h
- 左:1560
7 \: F8 ?) p4 h) ^% y - 右:1040/ A# ~3 B$ o* y8 y7 y
- 轮子半径:0.03m- B: D0 { r8 l1 S
- 轮子周长:2*pi*r1 ~) d& z" _& z/ G
- 一个脉冲的距离:
1 T. J# f) A p1 v - 左:0.000120830m7 W) K' h8 b+ s! N
- 右:0.000181245m
4 s0 G! N$ r# j - 速度分辨率:
/ |3 ?$ J& w3 y - 左:0.0120m/s 12.0mm/s* {$ o/ ?2 f: D! I; z( R: ]4 k
- 右:0.0181m/s 18.1mm/s
( z8 r ]* e8 ^3 W6 O - **************************************************************************/' q* j3 z" q2 W& J7 P8 u/ a
* ?. f0 q, a y. @7 |- void Get_Motor_Speed(int *leftSpeed,int *rightSpeed)
! W8 k' V3 \; | V$ \5 B- e - {
% M/ B1 c. y8 V0 `8 y9 D) j - static int leftWheelEncoderNow = 0;
# e9 _3 S1 a$ @6 O& u3 t% G1 f. t - static int rightWheelEncoderNow = 0;: o9 L# v3 k* p8 O
- static int leftWheelEncoderLast = 0;7 o% B- A+ N. S1 _3 L5 d/ B; c% d
- static int rightWheelEncoderLast = 0; * t6 G4 H' l9 A. ~9 }- j: k6 f
- ! ?+ J/ X( Q, l: z$ g" A. V
- //记录本次左右编码器数据
) [3 i( S% n6 m3 j - leftWheelEncoderNow += getTIMx_DetaCnt(TIM4);' y4 _# I' p; O; `0 c
- rightWheelEncoderNow+= getTIMx_DetaCnt(TIM2);7 N# u0 [( U$ S' X+ X) [: p4 F# w
- `$ e% c- I* y7 d! _
- //5ms测速
: k E0 f* W" i+ ] - *leftSpeed = (leftWheelEncoderNow - leftWheelEncoderLast)*1000*200*0.000120830; - o% }4 ~. | c) p0 N: e
- *rightSpeed = (rightWheelEncoderNow - rightWheelEncoderLast)*1000*200*0.000181245;
- x) ^6 z; ~4 P. {5 W$ ^1 a - 7 A {7 g @' X L& i
- //记录上次编码器数据
2 ?( C6 C, @. I, p1 J; r- C - leftWheelEncoderLast = leftWheelEncoderNow; 9 c. h1 K0 l: G! I% g% a3 N; F I
- rightWheelEncoderLast = rightWheelEncoderNow; 7 f. a j; E4 _- i) Z& r
- }
复制代码(2)main.c / ?6 A" d0 e' G5 `" T
- #include "sys.h"
( x* v# V d& u J+ M# B6 ] - 6 O- E- |3 M! O7 h& Q! R
- //====================自己加入的头文件===============================5 e) [* y) Q6 G3 ~4 ~0 N0 k
- #include "delay.h", _- z o5 V! x6 i1 N! c6 O
- #include "led.h"
: Y& {8 G' Y% H: ^) C- V - #include "encoder.h"/ C# u1 u4 q& m W' C
- #include "usart3.h": J; n7 b" j( F( i+ A
- #include "timer.h"$ e3 v1 O0 r& C C! `
- #include <stdio.h>
* a* b4 Z' j+ l, Q4 T, L - //===================================================================" d$ a' J6 _9 T+ J
- ! j" n2 u' ]) I( A; W
- int leftSpeedNow =0;: s& g+ u0 ?) m
- int rightSpeedNow =0;
/ i I L4 H# ^! v9 o1 @& o - - a% \. ]: {9 @ p6 C, _9 [
- int main(void)
/ q5 V. J! |' T - {
; V) J( j( o5 {# J8 q, X - Q( j0 w4 D7 s0 ?' o, \$ F
- GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);
7 O8 n0 s0 K3 K" c - GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//禁用JTAG 启用 SWD% A% d- ?. f6 ] S
- 0 R/ c, k A2 a: s5 }
- MY_NVIC_PriorityGroupConfig(2); //=====设置中断分组7 l3 `7 \& s) X
-
! X6 M1 J8 q3 p8 ^ - delay_init(); //=====延时函数初始化
/ P5 [5 u; `/ n) l: U - LED_Init(); //=====LED初始化 程序灯
( C6 T' E' _! u( t3 j1 `+ R -
/ r' j4 C6 j' r - usart3_init(9600); //=====串口3初始化 蓝牙 发送调试信息$ C& H6 |; \1 F' D
. d1 O; l- Q: Y5 b- Encoder_Init_TIM2(); //=====初始化编码器1接口2 e* T; m9 [# e' k# o2 b3 B* p& C+ s
- Encoder_Init_TIM4(); //=====初始化编码器2接口; c5 T5 B# [' G" T, t$ U' z
-
7 c5 [% K- b' u& c; _ - TIM3_Int_Init(50-1,7200-1); //=====定时器初始化 5ms一次中断! S( T, p) Y, ~$ L( [" `
- 9 \1 w. n$ n. W. z4 a) C5 N% V
- while(1)+ N) l% t% |$ \" M
- {
# ~$ ~, u ]% j# n - printf("L=%d,R=%d\r\n",leftSpeedNow,rightSpeedNow);
e& D8 l! u4 Y/ f+ [# h- L- W - delay_ms(15);
) E' W0 f, e! C3 d4 U5 h - }
: `; w' ^( O* r8 }4 |; u! l1 v - }( [+ z2 z$ G) v) x0 |& B
- + a8 u. m# ^: }+ ^
- //5ms 定时器中断服务函数
/ l6 B6 P3 m6 X1 [# }3 m( I
4 e4 z) {2 a9 i- void TIM3_IRQHandler(void) //TIM3中断
2 h( G4 B Q( \ - {* w8 a0 O, t' v% p0 A
- if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 : h; }' C4 i* w# }- d" h
- {. ]$ c" [7 E; y5 K! w' M
- TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx的中断待处理位:TIM 中断源
^" L( O5 Y8 d/ K - # i1 ?; f2 ^; I( C
- Get_Motor_Speed(&leftSpeedNow,&rightSpeedNow);
0 n' R- ^ e" |/ ^7 Q' y* ~ - Led_Flash(100);
" }/ t* @9 ]! s' h& { - }& y) Y5 K E( Q( W" j5 o9 T
- }
复制代码
3 I4 t) E2 T# G/ r; a
' g2 s, M/ n% s
% M% U7 W" x6 n, {" N. s, s
2.2.4 总结上面的一篇文章,我们实现的STM32单片机对直流减速电机的测速。再加上之前介绍的电机PWM控制,下一篇我们要结合前两篇的内容加上PID实现对电机速度的闭环控制。
' J1 F" a, ]8 {( G
3 F; i6 U- {3 r. v* z9 G% U" W4 {( d/ _: x9 j* q6 u- `- z
|
博主写的教程很清楚,我写了20多篇关于编码器测速+STM32CubeMX开发+GPIO、ADC采集+PID电机控制+openmv手把手开发的教程欢迎大家一起交流:这是文章:https://blog.csdn.net/qq_46187594/article/details/138110155?spm=1001.2014.3001.5502 这个是视频:https://www.bilibili.com/video/BV16x4y1M7EN/?spm_id_from=333.337.search-card.all.click