PWM舵机控制( c! I$ b* I* q) ^1 q- X& I8 f
& `# m" F) Y: l' Z硬件清单- 180°舵机
- 270°舵机
- 360°舵机
- STM/GD32F103C8T6核心板
- 杜邦线* s5 t/ W3 A' t8 u( ~
: U- u* s8 r# V2 K9 F, y
硬件接线 6 l1 C/ z! [# p: }5 o3 F
5 o1 |7 P- V# O- @3 F+ |
% z% y% h" f& U" K( C8 Q一、标准/连续旋转舵机! P: m5 b1 R, w7 K4 J
PWM舵机的控制原理基于脉宽调制技术。6 h+ D" l q4 l4 e3 `
1、标准舵机(180°/270°)具有固定的角度范围 精确控制角度位置 , Y2 v: W4 ]( d7 L" I
通过改变PWM信号来控制脉冲的高电平时间,从而来控制舵机旋转的角度2、连续旋转舵机(360°)$ g! l% ^0 b2 V4 m
, q* o, J0 R+ H* W
- 控制旋转速度和方向, i9 A3 D7 A$ \1 R; B
- 实现无限制的连续旋转
: M0 d+ r3 p. u9 n
7 Y% n# E7 T6 `' g& ^" p" U. U" {' R
通过改变PWM信号来控制脉冲的高电平时间,从而来控制舵机的速度和方向4 G' Z) o( h0 q9 Q
+ G4 r+ N+ J) r8 O# X9 n$ t二、舵机控制思路8 p! i3 p J/ s, c$ B% q
控制信号以固定频率的方波形式发送给舵机;这个方波的周期通常为20ms,即50Hz的频率;
# Z9 e! O' q+ L' }在控制信号中,脉冲的高电平时间决定舵机旋转的角度或旋转速度和方向。
; ]$ G) }" Q7 l$ U; |1 ]9 V1 O- N. a( W' Y0 ]: `1 k1 n' ]" P. T
$ l' I. \/ a w, S# q) w o2 t
0 {9 ]# c) j @6 x6 w
K' X0 X7 C; F# u- C- J' N三、PWM实现思路 1、时钟使能 - //TIM3时钟使能
* c! s& k" Y, S& F - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
7 B! D4 K7 v8 g9 i8 [+ V& N% ` - //使能GPIOB端口时钟和AFIO时钟
3 h9 V, f, T" r8 {9 n - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
复制代码 $ [+ i) \& D" ?
2、配置GPIO为复用功能及时钟使能 - // 配置GPIO为复用功能
: C# A- Z' [9 M7 u, o O+ X" ~ - GPIO_InitTypeDef GPIO_InitStructure;
5 t9 p9 {0 ?5 D - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 设置要配置的引脚5 }! Z9 |' v* @ k
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置引脚模式为复用推挽模式7 e M: w2 V6 V S
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚的速度$ Q: W3 E' }( `1 p
- GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIO配置
+ T9 Z9 W. Y) s: a# c9 C - ( h8 O; E6 |8 u: M8 n
- // 禁用JTAG 启用SWD
9 S5 ^5 Q B4 Y" q1 N+ W$ z - GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); k- }5 h1 B- W& j( d/ i
- // 把TIM3_CH1功能部分映射到PB4引脚上8 G; N1 Z# P D! ?$ `# w5 g
- GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
复制代码 4 j/ q6 K t* a2 Q
由于使用的是PB4引脚,需要将TIM3_CH1功能部分映射到PB4引脚上 1 ^! S: ?* v% ~
3、配置定时器3为PWM模式 - // 配置TIM3为PWM模式
T( Z) H9 n- E, H% i - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;4 ~+ \1 O! ?! T
- TIM_TimeBaseStructure.TIM_Period = 20000 - 1; // PWM周期,即计数周期20ms,PWM信号频率为50HZ6 e) ~$ ^2 e. Q) B/ J |5 ^
- TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 预分频系数,定时器时钟频率为72MHz/72=1MHz
3 ^6 j+ W- E9 K/ F - TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分频
6 R3 l b9 J- S H4 {) e# f - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数模式为向上计数
8 r( _4 T9 A- u2 T1 _ - TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
复制代码 3 Q5 B! `2 w4 @: @5 X$ ~
4、配置TIM3的通道1为PWM输出 - // 配置TIM3的通道1为PWM输出
# q: k( N; W7 p& T- q* W - TIM_OCInitTypeDef TIM_OCInitStructure;( i' [/ D2 {8 J: i
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1
" v; e$ w. w% H6 o3 x - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
' |! L0 D8 W5 D+ ^* W1 ^ - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性为高电平
- v# U. E. P! e' c3 D8 |! R7 [9 L3 e - TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比
: S/ A& ^" x- z - TIM_OC1Init(TIM3, &TIM_OCInitStructure);7 h& Z( x$ o& F" x. [+ n9 k
* e; S- x0 k% K3 e2 p( _- TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); // 开启OC1预加载寄存器
4 ]! c0 w2 B0 J B+ H$ |; \ - TIM_ARRPreloadConfig(TIM3, ENABLE);// 使能自动重装载寄存器2 H3 A3 v: i1 h+ J" b) R
- // 使能TIM3定时器0 z$ @# `2 I% O% M- l# s! Z% `! A) F3 |% w
- TIM_Cmd(TIM3, ENABLE);
复制代码
, ?9 ~7 H s L( d& ^* i四、主要函数 此处代码以180°舵机演示 1、main.c - if(Data_Flag == 1), t7 y* ]1 j; s# A! O) W7 c
- {
. G* }, `+ H) x/ z. V - int Servo_Degree = strtol(Data_Buffer, NULL, 10);//将数组中的数据转换成整数
/ B+ J; ]4 s* K - printf("%s\n",Data_Buffer);//打印数组中的字符串/ n9 ~9 [- V J. R) c
- printf("舵机目前角度 = %d°\n",Servo_Degree);//打印转化后的数据
) L) w2 S! p: ?8 S% d - Degree_Conv_PWM(Servo_Degree);//控制舵机旋转角度7 m/ }( q/ d2 X: o
- Data_Flag = 0;//标志位重置3 {& x& t' C8 i3 Z. Y
- memset(Data_Buffer, 0, sizeof(Data_Buffer));//清除数组中的数据; d8 g! C; m" U. j
- }
复制代码 - i. L) E& R5 w$ @0 v5 U% ]
8 O) U9 f, L1 {# t# \. D
使用轮询的方式判断是否接收到正确的数据,根据串口数据控制舵机运转- d l: L5 [. e6 g; f0 L4 F6 z
2、PWM.c - void Degree_Conv_PWM(int Servo_Degree)// 将接收的数据转换成PWM高电平脉宽
9 d7 W; p% @, `- G) w( x1 j - {
9 I- J( ?1 p& l - float PWM_Time;
. N7 b0 }0 B9 ~) M( c- p - PWM_Time = (2000.0 * (Servo_Degree / 180.0)) + 500;6 A! k4 y. D$ L- ~7 P, n
- TIM_SetCompare1(TIM3, (int)PWM_Time);//定时器设置比较值函数和通道有关 时间参数单位是us, r5 Y! _8 T7 I
- }
复制代码
1 f3 r4 t$ N/ }3 {9 ~8 ~) [, j! A. H3 E
使用强制类型转换是为了保证精度,最后传递给定时器一个整数比较值8 [% }7 m/ ]# U* t2 j
3、Usart.c - int fputc(int ch, FILE *f)// printf()函数重定向
6 g1 \( {# K7 U& Z - {
" D9 X8 e& J- X4 ]! v1 ]; J% W - // 发送一个字符到USART1
- C; _/ e8 E) {) F) k - USART_SendData(USART1, (uint16_t)ch);& }4 Q5 P% P# {* k3 F! [* `
- // 等待发送完成
6 V# s: C6 J2 @# G: D3 v5 X: I - while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
: K$ A, Y! n9 x1 u0 i - return ch;
2 y9 f# h' J8 D - }
/ T, N! B1 @- ?9 [5 a
/ e! J$ S+ V$ \6 x# g- L- void USART1_IRQHandler(void)//串口中断函数/ M) i' L u' d5 H T6 X
- {6 } L. r7 ]* s& m; Y, y$ P
- if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
# T5 ~5 |# v2 Z) x; E }6 I3 G - {! g* L$ D7 ^( K0 L+ M' d% Q
- Rx_Buffer[Rx_Index] = USART_ReceiveData(USART1);// 从串口接收数据到缓冲区
3 }8 a& \/ c& h9 W+ E - if(Rx_Buffer[Rx_Index] == '[/align][b][b][font=Tahoma][size=3][color=#000000][b][b][font=Tahoma][size=3][color=#000000]
- y& b" {0 w' w& Q - [/color][/size][/font][/b][/b]
- i9 ~$ N C6 F" C - Usart1:串口中断接收以“$”开头,“#”结尾的数据[/color][/size][/font][/b][/b]
, @) B0 T2 D" Y# y - [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 ^4 G8 z2 L& X8 c
- {
: [4 f% ~- h7 H% k$ x( G8 H, P - SysTick->CTRL = 0;// SysTick控制寄存器清零,停止SysTick定时器
9 C; a6 ~, F" a - SysTick->LOAD = 72*us - 1;// 设置SysTick加载寄存器的值,确定延时的时间8 R( @. s/ G' c
- SysTick->VAL = 0;// 将SysTick当前值寄存器清零
2 T4 r Q6 j1 L' N0 L4 f - SysTick->CTRL = 5;// 设置SysTick控制寄存器,使其开始倒数计数,并启用SysTick定时器
" T* `. ?, F$ e; z# ?! f8 H2 N - while((SysTick->CTRL & 0x00010000) == 0);// 等待SysTick定时器倒数完成, D# Y8 G9 [' z8 _8 g) |3 L! h
- SysTick->CTRL = 0;// 再次将SysTick控制寄存器清零,停止SysTick定时器* g3 N8 i# P* b
- }
复制代码 使用系统滴答定时器来控制延时
) [. I/ C' Y: k r, ^五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等" i8 X+ l( C- C! t+ F) ]) r' R
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。 5 z4 s0 f4 m6 E4 l) ~ B% ~0 g/ H
转载自: W-ML 如有侵权请联系删除6 D5 a9 i+ l" @ J" G4 \6 T
3 r: }: t/ v) U4 Z7 R
) ?1 O9 F4 }. H' x3 j9 ^+ c# w! s# P! h
)// 如果数据是以$开头则开始存储数据
8 Y( n" ~% x" ?9 {9 b9 _ r {
7 {0 M7 ^* Y3 }, k5 z+ }; ~ memset(Rx_Buffer, 0, sizeof(Rx_Buffer));// 清空接收缓冲区2 u. F* `. J, J: q$ P
Rx_Buffer[0] = '! C* j) I0 ?9 D3 q, t& Y
1 A, ^: l7 S! K1 d) k, \Usart1:串口中断接收以“$”开头,“#”结尾的数据
0 M) |+ X/ M+ ^7 x; v3 P, x, H- ?* b% y0 X4、SysTick.c - void Delay_us(uint16_t us)) v# j9 @& j0 M( t$ W; D6 w, a
- {+ K4 S9 w9 ]) p2 S- N
- SysTick->CTRL = 0;// SysTick控制寄存器清零,停止SysTick定时器$ d9 `; A; O% M
- SysTick->LOAD = 72*us - 1;// 设置SysTick加载寄存器的值,确定延时的时间6 R4 V: E# z! d2 h( e8 U
- SysTick->VAL = 0;// 将SysTick当前值寄存器清零
. d! v* R, m+ K8 D" } - SysTick->CTRL = 5;// 设置SysTick控制寄存器,使其开始倒数计数,并启用SysTick定时器, h$ b& o3 d. K6 e
- while((SysTick->CTRL & 0x00010000) == 0);// 等待SysTick定时器倒数完成. n" S& ]! Q: {0 h9 y _( s
- SysTick->CTRL = 0;// 再次将SysTick控制寄存器清零,停止SysTick定时器
* l3 h/ v0 S: ]5 N: u - }
复制代码 使用系统滴答定时器来控制延时
. x6 [+ ?4 y6 E* t五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等4 B1 j8 \% v1 v- n6 n% n5 e
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。 4 @: ~6 `/ X# J5 K! ^
转载自: W-ML 如有侵权请联系删除
' D: Q- a7 V& ^3 j2 Z4 [ 0 y; }! w. S& O4 z k9 n
: W! i5 p. [: @. ]. R# X0 ^" b! d
: x7 t# C- T8 l4 _8 U
;
: D7 v7 T t- x2 N Rx_Index = 1;
( q% j. a7 z# _8 U, ^/ R }
$ @ c6 J$ r5 L. v; z else if(Rx_Buffer[Rx_Index] == '#' && Rx_Buffer[0] == '5 |5 g3 j8 A6 W3 U: H- K+ J
- L8 W) O7 ^' A
Usart1:串口中断接收以“$”开头,“#”结尾的数据
+ V$ ^; N) ^! y4 j( }4、SysTick.c [ DISCUZ_CODE_7 ] 使用系统滴答定时器来控制延时
+ N& i7 ^9 m# n* v, K五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等
, O+ ?: w v& {, O2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。 ' u! t2 k/ [, u) w$ V
转载自: W-ML 如有侵权请联系删除* W" C4 @" v! P \" N3 X7 }0 ?% A
N# S2 i# e; W
1 u: t" c" D. n: S1 Q7 H
: S u8 O' M) D* z. [3 o)// 如果数据是以#结尾则结束数据存储并打印数据信息$ h3 ~! k9 P, Y+ k" u* g
{
3 b8 o+ j# _8 Y/ z6 u printf("Received: %sundefined", Rx_Buffer);// 打印接收到的数据7 a! u% P. y' U- ^( p# K
memcpy(Data_Buffer, Rx_Buffer + 1, 3);// 使用memcpy函数进行数组内容复制1 L8 s2 _$ v, |( {
memset(Rx_Buffer, 0, sizeof(Rx_Buffer));// 清空接收缓冲区
! { N- B7 Y- T9 Y Rx_Index = 0;// 重置索引" u0 S" H/ p/ W8 r; g0 u) L
Data_Flag = 1;// 数据标志位
6 d W1 N1 u/ }4 H1 s. ` }
* j! G5 U; P. I6 ~& I& i+ x else* |! O( K0 I O) E9 [" Q
{
& s( a* C- \. M3 a Rx_Index++;// 增加索引,准备接收下一个字符' p' Q3 r6 l$ d! P7 g
}
/ `" j3 D$ O. `+ A! b: Q" f }
8 {: m! G" t) L}[/code], [/ ?4 A( E* _- g" M- Q! B7 Z
! I2 s5 l! M6 B& ~; q: M
Usart1:串口中断接收以“$”开头,“#”结尾的数据
( W# L5 Y1 }3 E) d0 S. @4、SysTick.c [ DISCUZ_CODE_7 ] 使用系统滴答定时器来控制延时
3 Z: C: m7 L& g* ]6 S" ~* C# {- |8 v五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等' Q! ]' ]. R1 v9 d- L7 Z
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。
5 }$ N+ e' A% O/ U9 B5 Y3 `
转载自: W-ML 如有侵权请联系删除
: [ h1 Q% P- f- L: H0 y
5 K- G4 ?2 o+ G- n
8 A/ U" O. v8 B- P. Z5 g% p, c0 V. B2 j! m
6 ?; ?% q T- f3 ^5 C4 w$ V2 t3 F
|