PWM舵机控制+ R) A; }: S$ _) W7 Y" P4 @8 M
8 {+ r) r! e" @
硬件清单- 180°舵机
- 270°舵机
- 360°舵机
- STM/GD32F103C8T6核心板
- 杜邦线' Z- B: r! I. w. |
: G1 N, `' h1 C- B6 x* `( O
硬件接线
" J3 g: z+ Y& u6 b9 T! ^
: y% V% T0 N% A: k/ e. ]0 g5 X9 A# |1 p* Q! u
一、标准/连续旋转舵机
2 A% k1 S; Z4 T/ B5 D% APWM舵机的控制原理基于脉宽调制技术。
) N# W! H% F. D1、标准舵机(180°/270°)具有固定的角度范围 精确控制角度位置 7 q. U; ~: {" D; _% l- f
通过改变PWM信号来控制脉冲的高电平时间,从而来控制舵机旋转的角度2、连续旋转舵机(360°)& D% ^9 Q; f' I! H2 c
6 Y* \5 h& q- m( V
- 控制旋转速度和方向0 R+ t0 q+ }* ~3 E
- 实现无限制的连续旋转8 `6 D# t4 v; |6 i9 n
) B5 V' k- I8 E- g. R) A
3 W0 M5 d! p a' j8 v. h* L通过改变PWM信号来控制脉冲的高电平时间,从而来控制舵机的速度和方向. c O7 N% E1 d1 z* Z) w, s
# d1 q, L% X V; e二、舵机控制思路/ w! e* e& ?$ l# Q
控制信号以固定频率的方波形式发送给舵机;这个方波的周期通常为20ms,即50Hz的频率;
4 [* B. _7 d8 e/ z& }在控制信号中,脉冲的高电平时间决定舵机旋转的角度或旋转速度和方向。
- r( Q, f. g- X0 G# z
7 k* G+ l( V/ A+ x4 |5 I
3 K. u2 B: N/ e8 J
. H9 E# n# Q! ]& u4 l
: R0 J# ^9 {& G# C/ X三、PWM实现思路 1、时钟使能 - //TIM3时钟使能
8 \4 G' Y+ }% ~( _ - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);: A4 M' o3 r- y
- //使能GPIOB端口时钟和AFIO时钟6 k, N, T% m( z# J& {
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
复制代码 5 f$ O# L1 O, T* g# L
2、配置GPIO为复用功能及时钟使能 - // 配置GPIO为复用功能9 J" d, e1 M+ H, u r
- GPIO_InitTypeDef GPIO_InitStructure;
" d% d: M: Q) v! C) p - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 设置要配置的引脚8 E1 D3 C( N1 F+ y% i
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置引脚模式为复用推挽模式. Y; O/ M; [/ ?' T
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚的速度! l k& _& D' Q5 b# K/ |. N8 u
- GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIO配置( u( J( y* F/ b2 a+ Q: u }: J" o. a
" m2 N; g0 `- |& f- // 禁用JTAG 启用SWD
" \# p5 ], M8 P - GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);% ~1 f' }3 j9 r! W! O- O
- // 把TIM3_CH1功能部分映射到PB4引脚上" y5 z9 s* t* c' ?
- GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
复制代码
+ ` \& @" j+ r9 C由于使用的是PB4引脚,需要将TIM3_CH1功能部分映射到PB4引脚上
( ] b0 I- |' @! ?7 _
3、配置定时器3为PWM模式 - // 配置TIM3为PWM模式7 a7 u* O ^1 ]( ~( u
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;# K }# \9 p$ ?( ~5 J) T1 T: S
- TIM_TimeBaseStructure.TIM_Period = 20000 - 1; // PWM周期,即计数周期20ms,PWM信号频率为50HZ
( N; y. G# E# U( h- A) C7 ?6 {0 G) F - TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 预分频系数,定时器时钟频率为72MHz/72=1MHz. I# B J, f1 N8 B* ^7 K
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分频/ J/ v/ e& B7 Q! q
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数模式为向上计数% t6 H7 R) d+ k; X- w
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
复制代码
2 N5 }7 h: p X. m* y2 \2 g9 D4、配置TIM3的通道1为PWM输出 - // 配置TIM3的通道1为PWM输出
! ^) G* |5 G0 j% z1 q0 l - TIM_OCInitTypeDef TIM_OCInitStructure;
. l$ ]% L3 t( M q9 H - TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1
1 ]1 X: [' @. R" W& \1 {( v - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
7 C$ D# ?' q) K1 k/ M# C% U7 W) L7 v1 I - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性为高电平; e* m' ]- g+ u
- TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比' i0 X3 e, A; w# }
- TIM_OC1Init(TIM3, &TIM_OCInitStructure);
& ^+ `) L, V" L1 b# v/ B
0 m6 Y- l% _1 Y/ v- TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); // 开启OC1预加载寄存器
6 [# [- u& u1 X/ z& Z! h* H0 u' I( e - TIM_ARRPreloadConfig(TIM3, ENABLE);// 使能自动重装载寄存器
& S% y( P* g% Y# N2 f) { - // 使能TIM3定时器
2 R4 {9 h9 a8 X+ o3 g - TIM_Cmd(TIM3, ENABLE);
复制代码 6 y4 q4 ]& B( P! y, \3 Y, s" k
四、主要函数 此处代码以180°舵机演示 1、main.c - if(Data_Flag == 1)( _* ?/ X, x7 R$ i8 m: \1 j
- {( S" S/ u6 K5 A0 C" M
- int Servo_Degree = strtol(Data_Buffer, NULL, 10);//将数组中的数据转换成整数 x7 ?7 G% R9 k# k0 F/ l
- printf("%s\n",Data_Buffer);//打印数组中的字符串
6 O6 s/ E: o/ j4 D7 y( ]9 V$ H - printf("舵机目前角度 = %d°\n",Servo_Degree);//打印转化后的数据
# k% \7 k& f( l# L1 X0 F5 x - Degree_Conv_PWM(Servo_Degree);//控制舵机旋转角度
9 c7 D6 i# y* g" B' O - Data_Flag = 0;//标志位重置8 n0 w* i! ]0 ]0 l5 g/ e
- memset(Data_Buffer, 0, sizeof(Data_Buffer));//清除数组中的数据
# q, x* ~( Y9 x/ j2 M - }
复制代码 $ E5 c+ Y) C. \9 a; U' T" O6 j( D6 a
6 J7 n/ E3 \) k使用轮询的方式判断是否接收到正确的数据,根据串口数据控制舵机运转1 m, T x: K- v4 E* }
2、PWM.c - void Degree_Conv_PWM(int Servo_Degree)// 将接收的数据转换成PWM高电平脉宽3 y# q& Q+ r( `( T; E' V
- {
. G+ G R1 ~' w2 d& N - float PWM_Time;
& O, H: h* |8 s: o* I - PWM_Time = (2000.0 * (Servo_Degree / 180.0)) + 500;6 w% Q# F6 _/ p8 _' t. g2 {
- TIM_SetCompare1(TIM3, (int)PWM_Time);//定时器设置比较值函数和通道有关 时间参数单位是us
) k/ q+ S6 M# o- S+ `5 ] P7 M - }
复制代码
2 A8 {1 a4 B5 x% V g+ l, z1 W
4 U% h5 _9 w1 L/ y, I! Y使用强制类型转换是为了保证精度,最后传递给定时器一个整数比较值1 v& V, d% S. p0 X7 u1 S$ [
3、Usart.c - int fputc(int ch, FILE *f)// printf()函数重定向" x1 t/ i8 Z2 x
- {* P9 t* \7 a1 k# F1 J0 s
- // 发送一个字符到USART1( d5 N0 l V. o8 b/ e7 l- Y s
- USART_SendData(USART1, (uint16_t)ch);
' d3 [* l6 B. h/ T: l" `" f - // 等待发送完成1 I. {3 i) j3 E) ^/ O
- while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);8 }, a, P; _$ g' t- J H+ X
- return ch;+ O r/ v+ b; g+ }5 J7 e( D2 V
- }( ^3 ]. x( E; F, o
. w1 C, L& R G* s" s4 {) q- void USART1_IRQHandler(void)//串口中断函数
2 t2 }0 Y {, s* {+ b I; ] - {4 D. F v# d7 h; g' U2 L- A+ d
- if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)4 C* x. N" a# g* S. W
- {& D9 J4 U. O: [, T: o, s% V. J
- Rx_Buffer[Rx_Index] = USART_ReceiveData(USART1);// 从串口接收数据到缓冲区( b! ], R8 q5 T1 Y" Z( B
- if(Rx_Buffer[Rx_Index] == '[/align][b][b][font=Tahoma][size=3][color=#000000][b][b][font=Tahoma][size=3][color=#000000], b+ J% P3 {4 X
- [/color][/size][/font][/b][/b]
( X! @- K2 C0 M - Usart1:串口中断接收以“$”开头,“#”结尾的数据[/color][/size][/font][/b][/b]
7 Q2 x# B, l" m4 q: u2 k - [align=left][b][b][b][b][font=Tahoma][size=3][color=#ff8c00]4、SysTick.c[/color][/size][/font][/b][/b][/b][/b][/align][align=left][code]void Delay_us(uint16_t us)6 O- S/ s& @" l9 Y3 L0 n! f
- {. W' j& a# d7 M% s6 e" Y' x
- SysTick->CTRL = 0;// SysTick控制寄存器清零,停止SysTick定时器
" v% b M2 _1 \0 y( v - SysTick->LOAD = 72*us - 1;// 设置SysTick加载寄存器的值,确定延时的时间) x" E8 P4 O! f, O0 r+ {3 a
- SysTick->VAL = 0;// 将SysTick当前值寄存器清零
! @/ z: j1 F$ J# `! u3 E - SysTick->CTRL = 5;// 设置SysTick控制寄存器,使其开始倒数计数,并启用SysTick定时器 {6 l' y' @8 `6 h5 e
- while((SysTick->CTRL & 0x00010000) == 0);// 等待SysTick定时器倒数完成
& D0 H2 J( n, O3 V% ~ - SysTick->CTRL = 0;// 再次将SysTick控制寄存器清零,停止SysTick定时器
( C+ o7 G" K5 b' m; | - }
复制代码 使用系统滴答定时器来控制延时
% G: W% g" U& W2 B& E1 }& ]五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等
7 Y+ p0 \8 R. u2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。 $ x# D$ L0 x/ ]7 g' _/ o
转载自: W-ML 如有侵权请联系删除0 b W6 j R/ |/ P
1 Y4 ?# y7 }. } t3 `( M
' i0 S" Q! }2 k5 m0 k
7 _$ u. U+ d& V/ Y1 E
)// 如果数据是以$开头则开始存储数据
1 ?/ A- h! }8 E8 \ {* Y, z$ D# h$ F, {# D
memset(Rx_Buffer, 0, sizeof(Rx_Buffer));// 清空接收缓冲区
- j- r3 B8 I5 w* _4 u" v' D3 l Rx_Buffer[0] = '7 M8 f0 n9 s- b& c9 f6 D2 v1 k5 N
& y5 O7 O) E3 s
Usart1:串口中断接收以“$”开头,“#”结尾的数据. }9 |+ M4 q2 {- S1 E3 _
4、SysTick.c - void Delay_us(uint16_t us)
( j) ?8 K$ \4 P9 y# q - {
3 S. r" y0 V: R - SysTick->CTRL = 0;// SysTick控制寄存器清零,停止SysTick定时器
, m \( M4 v! v/ P - SysTick->LOAD = 72*us - 1;// 设置SysTick加载寄存器的值,确定延时的时间
5 J7 H) [ l0 w - SysTick->VAL = 0;// 将SysTick当前值寄存器清零
! _) b# S& B. m- X+ h) B0 w9 Y3 ^ - SysTick->CTRL = 5;// 设置SysTick控制寄存器,使其开始倒数计数,并启用SysTick定时器) x3 f8 m* g! x& x
- while((SysTick->CTRL & 0x00010000) == 0);// 等待SysTick定时器倒数完成1 Q3 A! g; T8 M: H e1 B( R
- SysTick->CTRL = 0;// 再次将SysTick控制寄存器清零,停止SysTick定时器
8 H6 f, m3 w1 C+ l+ j - }
复制代码 使用系统滴答定时器来控制延时 O. d+ P$ ]( z$ v
五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等3 a5 s: j" i; d9 s( ]/ D! c
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。
. J+ u1 N% I1 |3 P1 G
转载自: W-ML 如有侵权请联系删除- }5 s) y M% y, k5 \7 R
* e S. [1 J( V/ F: ~7 B; L
, k5 ^1 _# ]3 [
9 V1 u8 i% `# m; h6 P. B;
2 [. ]. f( R7 J4 D' y Rx_Index = 1;
) m2 P( F; G1 C+ l% m6 {- g }: s/ a; o7 d( h& H5 \; M
else if(Rx_Buffer[Rx_Index] == '#' && Rx_Buffer[0] == ', I& M6 ?! I: a2 y+ x; ]! o
9 X1 r) }: }) CUsart1:串口中断接收以“$”开头,“#”结尾的数据
+ ]: x: a7 v6 J4、SysTick.c [ DISCUZ_CODE_7 ] 使用系统滴答定时器来控制延时) ^$ `& }0 q8 M' v* O9 Z
五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等0 e6 ] {" c! `/ g! Y b
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。 ; ^& r- s2 l6 X# {$ d v
转载自: W-ML 如有侵权请联系删除/ _) `3 U' L- B- L' A y) M9 ?
- O8 X2 p; y: o( o* s) J( Q& _" P
6 e u: K: v" u) n, |$ g
7 ^2 D8 h5 y: Z' H- q% ~)// 如果数据是以#结尾则结束数据存储并打印数据信息
# q. }: V5 p+ i {% P& E* x0 R; z2 e1 w
printf("Received: %sundefined", Rx_Buffer);// 打印接收到的数据$ n( x2 K+ x1 n" U
memcpy(Data_Buffer, Rx_Buffer + 1, 3);// 使用memcpy函数进行数组内容复制
+ \/ f8 G# K) y1 {* P* W: L memset(Rx_Buffer, 0, sizeof(Rx_Buffer));// 清空接收缓冲区
( l7 l1 x5 f' y8 @7 R" W) m& p/ a Rx_Index = 0;// 重置索引2 [, T" m2 `7 I L0 \
Data_Flag = 1;// 数据标志位" ~* I; I i+ C
}
( Q3 X9 D; w) _$ {% j9 b1 F else
! N7 E( p" W* `$ e {% R: Q4 L. z6 u, o" g n
Rx_Index++;// 增加索引,准备接收下一个字符) d a( o& H/ T* z
}
" w& z N$ t4 d% d }
6 }1 f' n8 y) \% C1 p}[/code]
; V( f( W/ l1 |2 k2 t1 u5 R) \" N: g1 Q# g. p. x: S
Usart1:串口中断接收以“$”开头,“#”结尾的数据# U& w$ k0 d: e3 j/ e6 y
4、SysTick.c [ DISCUZ_CODE_7 ] 使用系统滴答定时器来控制延时
2 U, t, F( F; C; \( C五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等
* l' w5 ?( b; Z6 E t* ?) y2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。
0 {& Q% |$ n& ~; c" c& u' c* A" R
转载自: W-ML 如有侵权请联系删除1 M& c- D1 o. p% s
; k' l1 l2 Q. }) Z0 d$ Q: ^
; G3 _2 s5 {* g
8 B6 `% `2 i, c1 B C( d
+ M- e% \. e0 [3 _8 K/ Y' j |