通常的机械臂都是由多路舵机组成,我使用的是某宝上(并不)常见的五自由度机械臂。尽管商家称它为六自由度。
4 n1 @& G( A# k2 B$ I9 D这里使用STM32F407VGT6的6路PWM输出通道来控制6个舵机的运动,树莓派(上位机)通过USB转TTL模块与STM32进行串口通讯
: F4 s; c( T3 ]& }! T PWM舵机控制原理+ s7 l; G" T v2 N
1 U1 y% y W' s' t标准的 PWM 舵机有三条控制线,分别为:电源、地及信号线。" g2 ?& u: @9 m# }( i/ A
6 [. J4 L3 Y* V1 Y/ A
! B0 F e0 Z% z5 U D+ C: ~
市面上大多数180°舵机需要的PWM波周期通常为20ms,高电平接收时间通常为0.5 ~ 2.5ms,对应舵机旋转角度为0 ~ 180°。用PWM波控制舵机时,只需将时钟的周期设置为20ms(50Hz),并且通过改变比较值pulse改变PWM波的高电平时间来控制舵机旋转的角度.
; Q+ r4 X/ O" S% L/ a/ {2 {" D) D6 G1 K STM32CubeMx主要配置1 ?# R# K7 |/ s- r* {; }
对于STM32,我使用的是STM32CubeIDE + Mx 进行开发。时钟框图为默认配置,如图。 , V; t& ^, Y$ }" ~/ [3 j1 U% q1 U
$ z d! n- l4 q
/ m! x/ F' x- k0 v4 b
TIMER+ u/ _. ^% B* a2 R( ^
由于需要控制6个舵机,我选择了TIM3(4路PWM输出)和TIM9(2路PWM输出)。
8 a! e4 y/ b9 I S1 c) X bPWM输出的频率由时钟APB2决定,由时钟框图可知此处的APB2频率为16MHz;PWM波频率计算公式为: W = APB2 / (PSC + 1)(ARR + 1) 其中PSC为分频系数,ARR为自动重装载值。这里我将PSE设置为39,ARR设置为7999。
2 \' X4 d3 a1 h% g4 f8 {
A0 Q$ Z, M4 J7 t, i% G
+ T* S3 X! C( z3 C! X注意,这里的计数模式(Counter Mode)不同时,会导致接下来比较值值相同时舵机的旋转方向不同。
由于PWM波高电平时间需控制在0.5 ~ 2.5ms内,所以各PWM通道的比较值(pulse)必须控制在200 ~ 1000之内; (8000 x 0.5 / 20 = 200)(8000 x 2.5 / 20 = 1000) 当舵机旋转角度为90°时,pulse值应为600。这里我将各个PWM输出通道的比较值均设置为600。
: n7 ~3 W9 R5 S
7 P9 L/ ?5 h5 R( b! x由于我手头上的机械臂中的一个舵机用于控制机械爪,其为90°舵机,所以在配置控制该舵机的PWM通道时,比较值范围仅能为200 ~ 600,中间值为400我使用的单片机为STM32F407VGT6,其TIM3和TIM9所对应的PWM输出通道分别为PA6, PA7, PB0, PB1和PE5, PE6。
# [% ]* i2 L2 R) z+ y/ b* k这里只需将这些IO口设置为复用推挽输出以及上拉即可。
% g! f2 G* u8 o0 O
0 |* R U- l# Q* L0 M8 ?3 [7 a
8 s5 j7 l8 C" _& U 串口配置6 U$ s; t( i& k: B( ~7 e9 ^; y
这里我选择USART_2用作串口通讯,其Tx与Rx分别对应为PA2、PA3。
; R7 u" L8 @- P2 t; i( E4 Z
串口的配置如图所示。 9 v1 m4 \% D3 l$ g$ y; C
* t: V: X# A$ Y- ?
5 E: Z ^" r3 n- s/ Z
这里使用异步通信模式,注意波特率等参数需与上位机相匹配。
8 I, w1 M2 P0 ~/ H & P7 ]( o Y+ u/ Z. ]3 i5 Q
1 X' ?0 S" _1 U3 e5 ~5 W6 N
PA2与PA3均设置为复用推挽输出,上拉。
' J [0 ^) r; d) t7 U4 u 中断控制1 V+ g' Y. p3 u1 U' s$ H3 [
由于这里我只使用了串口接收中断,所以NVIC这里可以不用配置;若STM32除了控制机械臂外还有其余任务,则可能需要配置NVIC。 1 x; A2 O0 p! t( d7 c. Q
STM32CubeIDE代码实现! U& ]1 X! E! o7 ]8 J' E9 u
在STM32CubuMx配置完毕并生成工程之后,我们的主要任务只有设计上位机与单片机的通讯协议,以及完成串口接收中断函数即可。 9 i) }5 O* d5 ^, s
通讯协议设计1 o' ]8 ]* c9 O) ~* ^7 \1 ~6 \
我初步的设计为串口发送一次数据,控制单个舵机的角度;所以发送的数据中需要包括舵机的编号以及舵机的目标角度(或者由角度转换而成的比较值)。 在这里我没有设计通讯头以及对舵机转速的控制,因为这里我的串口只用于传输控制舵机的指令,以及我使用的是小型舵机,其转速并不是很快,可以满足我的要求,无需控制其速度。 设计思路如下:
+ P2 z) w2 r2 b4 R! z$ F0 F$ @上位机每次发送16位的数据(2个uint8_t类型,串口只能发送串口),舵机编号为0~5,发送的pulse值为 (200 ~ 1000) - 200,将舵机编号数值乘以1000加上pulse值减去200得到一个uint16_t类型的数值;再将其转换为十六进制进行发送。 例:对3号舵机进行操作,角度为45°。 数据处理:
9 p0 E1 p% G- w3 x 1000 + (45° / 180°) x 800 = 3200;# O3 Q1 F7 |1 z2 j4 F
3200转换为十六进制为:0x0C80。 STM32进行逆向操作得到原数据。
; X8 m! l, t# E STM32代码实现由于串口发送的是两个uint8_t类型的数据,所以在解码之前还需将两个uint8_t类型数据转换为一个uint16_t类型的数据。 在打开USART2的接收中断时,设置为接受到两个uint8_t类型的数据进入接收中断,以便于在接收中断处理函数中进行数据处理。 9 d# P, B' T& P: E1 t
- uint8_t pulse[2];, A5 n6 t1 g4 ]! A, e
- HAL_UART_Receive_IT(&huart2, pulse, sizeof(pulse));
复制代码 " F* {% r& Q) D
) x- x) Y' O4 _. W4 X. b( S
串口接受中断函数代码如下:
9 @' D8 k; M) ?! P4 x9 E+ \& J
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
) ], a6 p" o5 E9 q" M& [ - {$ Z+ D- d* v e) H
- if(huart->Instance == USART2)
: l' y6 K( D7 ^4 v+ j0 Z% l J - {
6 [! }7 T7 D) ~ - uint8_t rec0, rec1;1 @3 c& @! H4 T7 n& _6 ]# N- e* ~
) t7 D: j) b% U( M2 G3 l. M- // 获取接收到的两个字节的数据. ~3 Q; ]# P% \8 Z
- rec0 = *((huart->pRxBuffPtr) - 2);
& {. A! W# ^* R1 x0 W5 s2 T+ f, @) w' b/ W - rec1 = *((huart->pRxBuffPtr) - 1);2 t3 s! j2 @" \9 m
# {) y' [& u$ }% g" I! W- uint16_t numPart[2], armControl, arm targetPulse;: N- F, {0 W9 R+ b- ?
- 2 ]. M: K2 q2 ?0 h5 B0 x: s
- // 通过移位与强制转换得到uint16_t类型数据
; E7 o; B' b% w6 J3 k - numPart[0] = (uint16_t) rec0;
& ^2 I# P9 |/ s5 |% h - numPart[1] = (uint16_t) rec1;
! ?/ y4 Y& E7 S - armControl = ((numPart[0] << 8) | numPart[1]);
2 }+ o+ v/ ?2 T- A$ u - 7 Y4 U+ h8 u4 _
- // 逆向解码5 [4 ~5 X& ^* [ f) [8 W
- arm = armControl / 1000; // 舵机编号2 C$ |4 A6 W# t4 T0 a( t+ H) J l
- targetPulse = armControl % 1000 + 200; // 目标比较值* B! K, L& o' }8 Y6 g0 W' v1 j
% o( b7 I; n& v4 J& q% _( t- // 通过switch语句改变指定PWM通道的比较值
' }5 k$ o. i3 U3 B - switch(arm)1 j7 l! o5 i% @ {# v% U
- {
# B7 y9 I" l! i) S - case 1:7 Y2 J7 r" ~! T9 y& P
- __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, targetPulse);; T6 G* J' \# a- `8 C+ Q' S! E7 e
- break;8 x6 [. x% D4 |0 `2 _* y
- 9 `7 r. {+ Z5 i& v# `0 B
- case 2:6 f! `( R: j, V) D
- __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, targetPulse);
: M* C, M) ]# ?% b7 ~$ Z P - break;, x, t5 B0 Y; h% U- C) t* R
- 3 k0 H ^7 S+ H% ~ h
- case 3:
' t9 m. ?+ c7 } - __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, targetPulse);
2 o! W1 Q: G2 w9 c, d3 {7 V6 j7 V - break;
( V$ @0 n1 p' S- P: m( V7 k- ]
% H8 n0 K+ g9 m2 L* d0 Q/ x- case 4:
9 d2 P4 T/ B7 Y" g - __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_4, targetPulse);6 t, h8 p- B# I/ U, F
- break;& O1 w% c- y' H- k
- : C% a. \0 D1 m6 y7 s
- case 5:) I) m: F; n3 d, O; E" X
- __HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_1, targetPulse);/ i% B4 n( [- F$ N9 q- l( d
- break;
& r: ]' d8 T% J, q v% P0 Z - ) t5 z# c) F+ B. Q, z2 l4 C* Z2 ]! J
- case 6:
' j L6 i- x) {7 a3 }* Q' \4 @ - __HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_2, targetPulse);+ W+ \3 @) U/ [, e! [
- break;7 X, Y) E# U, T6 _, O' L
- }8 F: l0 x: A6 S8 x7 @3 |1 k
- }5 [- V' O2 o# N0 H2 o7 W, h& i! s
- }" X. }, @) M1 l$ S6 v) r
复制代码
* ^7 T) ^+ z' `. ]! l7 z7 v0 s. Y在进入while循环之前还需将PWM通道使能;
3 P; D# Z5 ?7 I) ~& A/ g+ b. R; B8 @- HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);/ T! K. J. @) |2 Y/ F
- HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);: {; ]2 G, S9 Z5 l3 |
- HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
- b2 t/ F i2 d7 U9 t - HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
4 b: Q% w: L" n" O - HAL_TIM_PWM_Start(&htim9, TIM_CHANNEL_1);
V, [, G' n7 i1 _+ J# K - HAL_TIM_PWM_Start(&htim9, TIM_CHANNEL_2);
复制代码 9 R/ r* F+ n9 x4 \
) f/ f; R. _: c8 W! [1 D: W1 m) \5 h
并且在USART2_IRQHandler函数中使能串口接收中断: 0 p% a7 K' d( s7 V
- void USART2_IRQHandler(void)
5 P+ f, u! g1 @* f - {' [& s% C, _6 |; i
- /* USER CODE BEGIN USART2_IRQn 0 */2 [4 ^) U6 M& ~7 L7 O4 k
- " {) g6 d; S% v8 F; _" E( A
- /* USER CODE END USART2_IRQn 0 */
$ t0 J' J3 \# q4 {+ D1 R/ ^+ @ - HAL_UART_IRQHandler(&huart2);
3 R5 S* I' U: b7 b' {6 I - /* USER CODE BEGIN USART2_IRQn 1 */
! {& \. y( Z7 f% m - HAL_UART_Receive_IT(&huart2, pulse, sizeof(pulse));
. D4 b6 `8 _$ z) _6 I3 ? - /* USER CODE END USART2_IRQn 1 */* X: |8 G9 u7 f5 k
- }
4 u( d" `+ p/ {/ T! w& j2 C
复制代码 : {1 X p2 d% l
6 V1 c/ k9 e% m5 N3 P测试
1 G5 ~0 ^' f5 y+ T8 @% @4 r在测试时我使用的环境是安装有Ubuntu 18.04的树莓派,串口调试工具为CuteCom;由5V锂电池为单片机与舵机(机械臂)供电,每次手动发送两个字节的数据,十进制转十六进制在科学计算器上进行。机械臂的运动符合预期。 2 l- \) \8 q4 c' x$ g
经过测试,同时发送12个字节的数据,机械臂也能够正常运行。 ! B9 p. E4 S. J
这里需要注意,在接线时,舵机的地线必须与单片机共地。 $ i9 F3 Y5 s- T3 g! o/ X! t' L% n( F
之后就可以将上位机串口发送的代码写入C++或者Python程序,通过上位机控制机械臂。
. r7 A9 Y. B" [5 W: k7 C" ^1 t$ \+ H! J& X# x8 n
|