PWM舵机控制 M- }& h# _4 a- @, k
" N% p9 H6 ]" O3 ^4 `
硬件清单- 180°舵机
- 270°舵机
- 360°舵机
- STM/GD32F103C8T6核心板
- 杜邦线5 s9 C% Q" X8 E. n' `! Y; T
* t- q$ I1 ]* Q. E6 D1 T/ Z- m
硬件接线 1 H8 Y. G2 R$ h2 N. d7 m
: F; E9 r6 }6 I& q b& x6 `+ ^1 a7 h- a( z
一、标准/连续旋转舵机
, a7 z2 }) D8 q- H% U( l) V, H8 N2 bPWM舵机的控制原理基于脉宽调制技术。
- N* E# k- z- g f8 w \4 k1、标准舵机(180°/270°)具有固定的角度范围 精确控制角度位置
6 x0 l# w( w$ ? 通过改变PWM信号来控制脉冲的高电平时间,从而来控制舵机旋转的角度2、连续旋转舵机(360°)* R: R% W: ?5 p& o f
' A, C5 X- D g- 控制旋转速度和方向
1 \- W1 S' x! n - 实现无限制的连续旋转' w4 C* ]% q+ z' {
; g8 Y' ]0 Y! ^/ Z7 m- y
1 ^. h" t- d x) n* b
通过改变PWM信号来控制脉冲的高电平时间,从而来控制舵机的速度和方向' b! D1 {7 \3 |3 m( Y* w: ?: |
4 d+ M7 ]2 S/ m3 v4 h7 ]
二、舵机控制思路4 V+ v- `# F4 [* @
控制信号以固定频率的方波形式发送给舵机;这个方波的周期通常为20ms,即50Hz的频率;, L; Z: m% |3 L: F$ F& q$ t
在控制信号中,脉冲的高电平时间决定舵机旋转的角度或旋转速度和方向。
" S% N" a% \4 y7 n" r2 p0 P; s6 A: ]# ~8 ]
2 D8 z* h: {9 A0 p* P( X7 s# P
: I+ R' L/ U4 o3 e) J* X
2 j! P$ L: b$ u* l5 y2 w
三、PWM实现思路 1、时钟使能 - //TIM3时钟使能
w' p+ m- H! n3 m& U5 p" ^ - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
- k$ {. |# ~# F+ v - //使能GPIOB端口时钟和AFIO时钟
% K6 G7 F7 Y7 ~0 A1 H, m - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
复制代码
# g8 S# J t: R) e, z9 P) Y2、配置GPIO为复用功能及时钟使能 - // 配置GPIO为复用功能
; l. M$ c# f9 G! ~8 \2 ^; z2 Z - GPIO_InitTypeDef GPIO_InitStructure;- w& I8 f6 K' D- E. K
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 设置要配置的引脚
+ R. O# B2 E+ }- [" ]6 S - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置引脚模式为复用推挽模式
3 ^1 e! Z- d( O. o) G r6 Q b - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚的速度
: h# r5 [5 O& v - GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIO配置3 }. o" Y g3 }% l. [; Z
- 3 N( I7 k! v' t/ c: ^
- // 禁用JTAG 启用SWD) a7 X& A U- \# c
- GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);; W2 A( k. Z& w: S* R
- // 把TIM3_CH1功能部分映射到PB4引脚上8 N* A' {( S; R7 Z
- GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
复制代码
0 ?7 O* ]" l0 y由于使用的是PB4引脚,需要将TIM3_CH1功能部分映射到PB4引脚上
" b8 F6 P5 E9 x! _4 T
3、配置定时器3为PWM模式 - // 配置TIM3为PWM模式
6 O1 }8 m# o% @. H$ }- X - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
" a+ v9 k# m0 x2 I+ Z - TIM_TimeBaseStructure.TIM_Period = 20000 - 1; // PWM周期,即计数周期20ms,PWM信号频率为50HZ: ^! g; I1 l; U/ z2 k. k
- TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 预分频系数,定时器时钟频率为72MHz/72=1MHz
6 D' q0 r9 A/ X - TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分频
8 K# U/ o$ ~# v3 r' P9 z/ w& K - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数模式为向上计数* K/ k; }/ Z7 r S+ ?: I' B8 _% r
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
复制代码 ; |% \8 T0 l- ?1 y$ o$ q
4、配置TIM3的通道1为PWM输出 - // 配置TIM3的通道1为PWM输出; S0 G2 @8 X( G7 b- \) B3 Q
- TIM_OCInitTypeDef TIM_OCInitStructure;$ B( |) Y |, q0 I( g; ~( a9 H
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1- J2 T5 _: X' @8 R5 e/ T5 ?1 Y! d
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
+ H, {$ ^, T, s( l/ W7 F3 e - TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性为高电平. f; t( X* i6 V5 i6 z' S* }
- TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比! C0 v/ P3 T' V: F$ z
- TIM_OC1Init(TIM3, &TIM_OCInitStructure);
/ F4 z+ ], W& G. b } - / Q2 x0 A! y n5 ~$ \- {5 u
- TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); // 开启OC1预加载寄存器" ?! ~) e. x8 P5 t& B! d3 [
- TIM_ARRPreloadConfig(TIM3, ENABLE);// 使能自动重装载寄存器 H9 P6 z3 Q: l, L+ \- u u! ^* i# s: B
- // 使能TIM3定时器5 V5 M$ a/ I$ z7 x' ?# S ]
- TIM_Cmd(TIM3, ENABLE);
复制代码 6 Y" Q8 w! g1 x7 |, Y+ [4 Z
四、主要函数 此处代码以180°舵机演示 1、main.c - if(Data_Flag == 1)
) x3 w/ h7 _! S% A. w- f! H7 ]) c - {
' {: k( v3 Q: n$ n - int Servo_Degree = strtol(Data_Buffer, NULL, 10);//将数组中的数据转换成整数
: N+ ?% O4 U: v" j8 d3 D - printf("%s\n",Data_Buffer);//打印数组中的字符串
) |- r# {' r4 k7 X) r7 n, O - printf("舵机目前角度 = %d°\n",Servo_Degree);//打印转化后的数据; i) |! ~5 E' o7 S# R
- Degree_Conv_PWM(Servo_Degree);//控制舵机旋转角度
+ K4 D* }; z; p& F4 F. ^ - Data_Flag = 0;//标志位重置4 r) u. M# o% v( ~% C) J4 c" ]+ J
- memset(Data_Buffer, 0, sizeof(Data_Buffer));//清除数组中的数据, D$ S, p/ x! i) o
- }
复制代码
; K8 u6 y4 x5 W# n$ k6 m: f% ?2 _; P( V# _3 \9 g: J2 w2 q
使用轮询的方式判断是否接收到正确的数据,根据串口数据控制舵机运转
) }( E, M I% z! n! ~6 p2、PWM.c - void Degree_Conv_PWM(int Servo_Degree)// 将接收的数据转换成PWM高电平脉宽
O; W" v* Z+ n$ }( z8 W - {! h% Y& V& d; L7 U6 r# D
- float PWM_Time;0 M* G+ G: s$ D" ]- a
- PWM_Time = (2000.0 * (Servo_Degree / 180.0)) + 500;: M1 D" p4 y* c) n
- TIM_SetCompare1(TIM3, (int)PWM_Time);//定时器设置比较值函数和通道有关 时间参数单位是us
3 r( @! X' B! W: w+ O - }
复制代码
! L) f! U) h* f% e' t5 T
/ _7 n1 ~: |: q y' |; D8 Z使用强制类型转换是为了保证精度,最后传递给定时器一个整数比较值
" k% Y2 S8 D2 ?3、Usart.c - int fputc(int ch, FILE *f)// printf()函数重定向9 A; [% R2 O( w
- {
2 ]! y$ _5 X- J# I5 U- s2 K - // 发送一个字符到USART10 J8 k0 L4 p; v2 L1 U9 j
- USART_SendData(USART1, (uint16_t)ch);
4 h& D. O; s0 l- R - // 等待发送完成8 p9 w6 |7 a$ `3 @, i6 M
- while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
4 I1 L9 N+ D0 [ ?: n: U | - return ch;" R4 n Y/ S% x: u. @% X
- }* {/ R3 O* r9 j: U
- 0 B# V& K. [; d+ A! ^
- void USART1_IRQHandler(void)//串口中断函数! l, ]( ~" D# y8 e0 F& g. L) K
- {* e. V# H0 r9 i
- if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET), P: f5 e3 i% U. v, d: N% x
- {
/ v( w5 g+ z4 X- } - Rx_Buffer[Rx_Index] = USART_ReceiveData(USART1);// 从串口接收数据到缓冲区4 z6 G# j/ K6 o" W0 T
- if(Rx_Buffer[Rx_Index] == '[/align][b][b][font=Tahoma][size=3][color=#000000][b][b][font=Tahoma][size=3][color=#000000]
I' O% V+ P: W i( B. b - [/color][/size][/font][/b][/b]
1 b: S$ Z" Q% r( |% o. e - Usart1:串口中断接收以“$”开头,“#”结尾的数据[/color][/size][/font][/b][/b]9 [$ k, o; k6 d# C: ]
- [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)
) ?% @, ?- H) o R0 C/ U - {# r) B% M! o6 \1 ]- F6 c1 d9 L
- SysTick->CTRL = 0;// SysTick控制寄存器清零,停止SysTick定时器
: g! H. c9 {) ~- t, b" Q" P - SysTick->LOAD = 72*us - 1;// 设置SysTick加载寄存器的值,确定延时的时间
. P0 @" f8 O+ M# P" m/ F - SysTick->VAL = 0;// 将SysTick当前值寄存器清零
0 l% I/ Q. Q) |, n( c3 ^ - SysTick->CTRL = 5;// 设置SysTick控制寄存器,使其开始倒数计数,并启用SysTick定时器
5 M- j( K4 f9 v- A0 @( e - while((SysTick->CTRL & 0x00010000) == 0);// 等待SysTick定时器倒数完成9 I+ w4 _8 a: U3 b
- SysTick->CTRL = 0;// 再次将SysTick控制寄存器清零,停止SysTick定时器
- u8 i( j6 m1 G7 ]7 J - }
复制代码 使用系统滴答定时器来控制延时( K' B% V4 O% A% J$ L5 ~4 _
五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等8 [ [4 S) E+ t# r f4 U/ n: k
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。 + z+ E3 I9 ]* B: V
转载自: W-ML 如有侵权请联系删除
+ w c [. J9 w. A0 g2 \2 p ; ?% @. A' v' x1 m( a$ h/ H
* o% u2 g% e# C1 m
0 f$ O& S, ^4 K0 x% [5 t)// 如果数据是以$开头则开始存储数据
8 F' Z3 U) \' ]5 s {3 N! @ Z- Z! N/ B, l
memset(Rx_Buffer, 0, sizeof(Rx_Buffer));// 清空接收缓冲区
( ?2 C( k2 ~5 @+ f. G4 z4 x Rx_Buffer[0] = '
- I. z% b3 [" ?/ x! x$ o3 p" ~9 ]3 ~+ d4 H- u9 Q2 F, `
Usart1:串口中断接收以“$”开头,“#”结尾的数据3 S3 Y% m; @8 ~0 S" q- Z
4、SysTick.c - void Delay_us(uint16_t us)! O. ` f- j" r
- {( @% g, Q+ N0 `6 ~% l. t
- SysTick->CTRL = 0;// SysTick控制寄存器清零,停止SysTick定时器3 h6 B2 m* I% U q0 W
- SysTick->LOAD = 72*us - 1;// 设置SysTick加载寄存器的值,确定延时的时间
8 f+ n* m k! L - SysTick->VAL = 0;// 将SysTick当前值寄存器清零
4 X/ D3 B2 h8 ] - SysTick->CTRL = 5;// 设置SysTick控制寄存器,使其开始倒数计数,并启用SysTick定时器 s0 @5 D% f/ n- j) R/ O+ _; p* ~" B
- while((SysTick->CTRL & 0x00010000) == 0);// 等待SysTick定时器倒数完成% O( z1 K4 N' a$ x3 r) V
- SysTick->CTRL = 0;// 再次将SysTick控制寄存器清零,停止SysTick定时器+ d! \9 U; _8 f
- }
复制代码 使用系统滴答定时器来控制延时, H D: U( a- f: }) R, c' v
五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等8 A5 c* i2 M P
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。
' G3 j1 u, Z/ h* o1 n9 G4 C$ l; ~
转载自: W-ML 如有侵权请联系删除
. w1 X, ?, T: C# E* R . s- |, ? f8 v5 U. Q0 G. U3 y
0 S N( P/ g0 x+ i
# Q% V+ {9 L& m0 A( s;
! h( \1 l$ D9 g. y7 t' F Rx_Index = 1;1 |* K$ A* ~( _' w& \' |1 g8 Q
}' @+ z J% M* ]; b6 t" J
else if(Rx_Buffer[Rx_Index] == '#' && Rx_Buffer[0] == '5 i$ C5 w& T) t5 ?+ {# r" B, I
+ O) E7 Y ~/ z" b5 a8 g4 LUsart1:串口中断接收以“$”开头,“#”结尾的数据
1 ~" y( l. x6 c; ~5 w- n+ I1 w4、SysTick.c [ DISCUZ_CODE_7 ] 使用系统滴答定时器来控制延时
! L1 {) U# E4 W( R l" Q( @五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等
0 ?* A; Z9 r" z% @9 p& U7 }! D4 o2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。 1 G$ W4 Y' C7 [7 R% R
转载自: W-ML 如有侵权请联系删除
9 f$ M6 n. G9 j9 l5 | 8 i/ w: e. t* U L1 a0 D
# ~6 H2 @) ]8 D9 |! t/ ]! `3 j2 m
2 g& E' M8 k: ?9 _% b
)// 如果数据是以#结尾则结束数据存储并打印数据信息+ f: T3 _- [" j7 `1 y1 |
{
" K% z S8 w! R$ m printf("Received: %sundefined", Rx_Buffer);// 打印接收到的数据
5 ?+ J5 J) }4 @4 z, J7 t memcpy(Data_Buffer, Rx_Buffer + 1, 3);// 使用memcpy函数进行数组内容复制& a" F5 w$ U- ?) p3 K9 S
memset(Rx_Buffer, 0, sizeof(Rx_Buffer));// 清空接收缓冲区
+ y4 A1 h; y7 S, d* y Rx_Index = 0;// 重置索引
6 u6 ^! h3 u* L( A Data_Flag = 1;// 数据标志位
; N6 W& J* b, O) S- U! U }7 o, T1 @" c# n1 S' l: e
else4 Y! y1 m3 |$ a
{
1 X9 N* h, _ f+ j6 G Rx_Index++;// 增加索引,准备接收下一个字符
u& }: W, R7 I$ _' y* i2 H }! |: q9 e6 s3 D; K- f1 h
}
8 t, _; @% k& K. I ^}[/code]
% Q/ f0 P- d" c! b: e; Z1 D
6 P* Q }! `5 R! X8 K( v5 fUsart1:串口中断接收以“$”开头,“#”结尾的数据
' _. Y) d" x* d' ]: |5 P; U$ b8 ]4、SysTick.c [ DISCUZ_CODE_7 ] 使用系统滴答定时器来控制延时
8 p% u! B; N$ N% ?3 K2 q4 V U4 ~1 Y& k五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等# L1 }- i8 i! _1 W
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。
7 O& y/ J; k/ {- b P/ ] j3 B
转载自: W-ML 如有侵权请联系删除) g9 V8 }% j( c" b' v
. c6 Y1 [5 b6 U# }- \; Y' e 6 ~. w- R9 L# G* I
3 Z) o1 G2 W# ~" n% k4 a3 D
0 z) O9 k/ u. a4 T6 W |