PWM舵机控制) q, ~+ t5 N( w5 D& G W L' C. x
1 V" y% |# W! D3 N! S: g
硬件清单- 180°舵机
- 270°舵机
- 360°舵机
- STM/GD32F103C8T6核心板
- 杜邦线
; i/ @; k( r1 g5 L4 l
1 i; n% z( f9 E, Z
硬件接线 W4 h7 H* R' E& p, _+ O
" P/ N0 J) [. R" m2 z+ z- Z9 ~+ g% Y/ B3 x
一、标准/连续旋转舵机
! z% Q6 o5 R1 U7 u. x6 ePWM舵机的控制原理基于脉宽调制技术。
9 o8 H, m& t+ |; I: b1、标准舵机(180°/270°)具有固定的角度范围 精确控制角度位置 8 g4 F# X+ V3 O0 ]
通过改变PWM信号来控制脉冲的高电平时间,从而来控制舵机旋转的角度2、连续旋转舵机(360°)
7 V" @- x7 r; o% ^% B2 B7 Y$ m3 a: f, U4 \9 ]
- 控制旋转速度和方向! o" e8 G0 v, z i
- 实现无限制的连续旋转/ p* f$ O$ @& {6 I, P; n2 Y
; |/ J' ?9 w$ S. |" c1 i
0 w2 }( l. p3 V. O5 A3 f通过改变PWM信号来控制脉冲的高电平时间,从而来控制舵机的速度和方向; Y# Y1 A o; w6 e
/ Z2 D9 r% B& f3 L2 |
二、舵机控制思路
7 V- h$ l: u @" A, G+ @, p控制信号以固定频率的方波形式发送给舵机;这个方波的周期通常为20ms,即50Hz的频率;
0 m: g1 T; W# ^' A. V# M在控制信号中,脉冲的高电平时间决定舵机旋转的角度或旋转速度和方向。7 @$ x7 K0 @4 M- w$ M" Q* |. H* ^' M
- e5 o, V# E6 Z0 R7 D- G/ |
- R; _2 x* G/ c
: r8 z; C5 h1 I8 ?. G: a
v- p$ Z0 }- Y6 _& S) E9 |+ _, s
三、PWM实现思路 1、时钟使能 - //TIM3时钟使能' R& V7 {! u2 ]: x
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);7 }: t1 r9 M( q# k! C4 E
- //使能GPIOB端口时钟和AFIO时钟
4 m% G) G5 R: x( p0 _; ~8 l - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
复制代码
1 v' w: x' O7 \) J* w; r2、配置GPIO为复用功能及时钟使能 - // 配置GPIO为复用功能0 ^% A) j, V* \( M
- GPIO_InitTypeDef GPIO_InitStructure;
& B. g, `/ u6 C; m5 `+ V" Q - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 设置要配置的引脚
3 d0 j; R$ @7 O$ D, [! P- D b, S2 n - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置引脚模式为复用推挽模式9 N9 U p6 Y1 X/ s7 w+ s
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚的速度
+ Q% W$ N% F: m( M. u. p - GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIO配置. W& J; b5 o6 b) e' l) n/ c+ K& H
4 T$ U2 U: i) A1 n. _6 W- // 禁用JTAG 启用SWD& |) N3 L0 I/ n7 V9 D
- GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);! d) ?4 o; z/ g
- // 把TIM3_CH1功能部分映射到PB4引脚上, T1 V3 \- T) p9 E! }
- GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
复制代码 ( a P. o9 {$ f, p& P4 D
由于使用的是PB4引脚,需要将TIM3_CH1功能部分映射到PB4引脚上 % O# C( U% S. C# I E4 T. h
3、配置定时器3为PWM模式 - // 配置TIM3为PWM模式2 F3 u( \" N f4 o/ \
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
0 [/ ~4 O$ I* r1 y - TIM_TimeBaseStructure.TIM_Period = 20000 - 1; // PWM周期,即计数周期20ms,PWM信号频率为50HZ
6 k+ l& R1 R- O$ m# k2 {+ s4 q - TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 预分频系数,定时器时钟频率为72MHz/72=1MHz
* F" Q+ X* O/ F1 m. e* q+ }& u - TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分频
- W: R& l+ g" R0 O# G - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数模式为向上计数/ b8 R2 p" L$ \8 {
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
复制代码
4 h B& R! l9 ` d4、配置TIM3的通道1为PWM输出 - // 配置TIM3的通道1为PWM输出
9 |7 U' s: f9 p6 [0 a$ H - TIM_OCInitTypeDef TIM_OCInitStructure;" Z6 s+ H( D. B! Z4 C: D! }
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1! n; f6 p/ c- V( X, M
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能0 U/ S5 @" ~& ~6 g, e
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性为高电平
* P* h7 E5 a: E3 \. L. g' ` - TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比9 D* x" l; Z3 }* X/ O" y2 D
- TIM_OC1Init(TIM3, &TIM_OCInitStructure);
: ~9 I6 b/ @, J' r! U" _, T - ' ~$ B) x6 V; b, k& T' u* {
- TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); // 开启OC1预加载寄存器& Q) E5 x% R3 b1 u0 g" f9 b* d. J
- TIM_ARRPreloadConfig(TIM3, ENABLE);// 使能自动重装载寄存器: e' h* s) D# C# y2 ]* X" M
- // 使能TIM3定时器
" m ~& t' @& [; M9 g/ V- e3 v4 v9 G - TIM_Cmd(TIM3, ENABLE);
复制代码 B( x& B- R" [( J3 l/ l# [
四、主要函数 此处代码以180°舵机演示 1、main.c - if(Data_Flag == 1)
# c) Y: [: l+ h! n* J$ v: _5 ]+ P - {& j( |( N9 N, E2 z& E. y( R
- int Servo_Degree = strtol(Data_Buffer, NULL, 10);//将数组中的数据转换成整数
5 k- T `3 P* k - printf("%s\n",Data_Buffer);//打印数组中的字符串. X, W) t5 @; a. L2 p! ]8 [2 p
- printf("舵机目前角度 = %d°\n",Servo_Degree);//打印转化后的数据
- z7 M* T2 v* v" Z# B - Degree_Conv_PWM(Servo_Degree);//控制舵机旋转角度
. j2 V& ]& G; f8 r5 ^# v/ h - Data_Flag = 0;//标志位重置
: Z) A1 _! M3 s/ u+ R# N - memset(Data_Buffer, 0, sizeof(Data_Buffer));//清除数组中的数据
# o/ ~' @$ t; T - }
复制代码
+ o8 m0 ]9 R( ^ L* e! J7 ^: |# C3 Z( n) \# u
使用轮询的方式判断是否接收到正确的数据,根据串口数据控制舵机运转
q/ R- V4 G+ R2、PWM.c - void Degree_Conv_PWM(int Servo_Degree)// 将接收的数据转换成PWM高电平脉宽
3 \: [4 N0 t/ H: x' K5 o% h* \1 C4 R0 k - {
( l& }9 ]- x# B# N8 f8 L6 N - float PWM_Time;
8 V. ~. m+ C/ G/ | q - PWM_Time = (2000.0 * (Servo_Degree / 180.0)) + 500;3 I) I1 }& T3 K$ z* _# q
- TIM_SetCompare1(TIM3, (int)PWM_Time);//定时器设置比较值函数和通道有关 时间参数单位是us& O3 _. F( d0 P# a# J/ m
- }
复制代码 2 \. b5 L" g" I6 b
$ X3 w( O& S% o k3 e9 V& ?使用强制类型转换是为了保证精度,最后传递给定时器一个整数比较值1 R" N' `: Y# s4 s" n2 C
3、Usart.c - int fputc(int ch, FILE *f)// printf()函数重定向
' \0 I! m5 P7 G/ V. ? - {+ p7 w) t2 Y, {
- // 发送一个字符到USART1
4 x0 z* g& h# \% C% x# h - USART_SendData(USART1, (uint16_t)ch);
/ p1 E: Y! y# X' T" X - // 等待发送完成& V2 n7 J/ c: {1 L/ Q
- while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
0 x& {. N6 S& a- P - return ch;
3 \! e& F# t# @4 ^/ w; L3 l - }
x! v' `, I2 [8 g. R0 q; F1 P
! _, x. S( @8 O; f8 w3 C' }- void USART1_IRQHandler(void)//串口中断函数/ e' T7 {1 v1 c( `$ z; d
- {0 Z+ q! W# B9 Z( D4 o9 ^+ a: {
- if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
( j9 M1 s& c. a/ v8 \ - {
: ^! K V o* r, Y( j* w - Rx_Buffer[Rx_Index] = USART_ReceiveData(USART1);// 从串口接收数据到缓冲区& t4 b- y* C4 C! m5 ]
- if(Rx_Buffer[Rx_Index] == '[/align][b][b][font=Tahoma][size=3][color=#000000][b][b][font=Tahoma][size=3][color=#000000]
6 ^: w3 O, d% q+ J - [/color][/size][/font][/b][/b]8 t% U C/ U; }7 w% z2 Q
- Usart1:串口中断接收以“$”开头,“#”结尾的数据[/color][/size][/font][/b][/b]
! V) }- W, `. x, R9 g0 l - [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, b$ y- u% C$ U0 i - {7 ?9 d# f+ Q# q0 [9 e
- SysTick->CTRL = 0;// SysTick控制寄存器清零,停止SysTick定时器" l% o& R0 U1 M1 Z
- SysTick->LOAD = 72*us - 1;// 设置SysTick加载寄存器的值,确定延时的时间
, c* g+ n1 @- ] D - SysTick->VAL = 0;// 将SysTick当前值寄存器清零
9 Q) ^. B5 ]: C- y% [* \0 ? - SysTick->CTRL = 5;// 设置SysTick控制寄存器,使其开始倒数计数,并启用SysTick定时器
5 N- F4 ~* s2 ^: x; d - while((SysTick->CTRL & 0x00010000) == 0);// 等待SysTick定时器倒数完成
+ u; N3 A5 a& ^2 K: r' x* j - SysTick->CTRL = 0;// 再次将SysTick控制寄存器清零,停止SysTick定时器$ o/ h: R1 A# z# i T
- }
复制代码 使用系统滴答定时器来控制延时
o# h" Q" e) u五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等8 x- M( K! V. d; A
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。 ) i2 w0 @4 n8 Y% E, s8 d
转载自: W-ML 如有侵权请联系删除
& u8 W0 `) d" V9 o( c3 T* c ! h; N% d' {1 \$ v6 `
* t" l9 s7 V3 G
: ?$ L2 Z6 o' r; h- D# P- Q3 k2 y)// 如果数据是以$开头则开始存储数据6 E# x" }0 L$ |& n; }+ D
{* c. f- e0 {5 _7 R+ R" b
memset(Rx_Buffer, 0, sizeof(Rx_Buffer));// 清空接收缓冲区 ~, ~0 e$ k9 ~9 E. ^- c% ~$ l: D6 O
Rx_Buffer[0] = '+ l; O( [& E# G7 o/ y
( I6 `3 I/ N3 ]. X# rUsart1:串口中断接收以“$”开头,“#”结尾的数据0 f5 [8 f6 q/ ~. ]: f) t5 G
4、SysTick.c - void Delay_us(uint16_t us)% B6 i& ?0 X' l+ h
- {
! m% L& h. M% g8 V& \4 Z - SysTick->CTRL = 0;// SysTick控制寄存器清零,停止SysTick定时器- j7 z2 W7 p' y) j) o, Q) C8 u9 f6 I
- SysTick->LOAD = 72*us - 1;// 设置SysTick加载寄存器的值,确定延时的时间) W: ]1 L* V2 w. y d I
- SysTick->VAL = 0;// 将SysTick当前值寄存器清零/ G$ e7 P# b* N
- SysTick->CTRL = 5;// 设置SysTick控制寄存器,使其开始倒数计数,并启用SysTick定时器" |$ p9 u) |- G. a) J4 `" X
- while((SysTick->CTRL & 0x00010000) == 0);// 等待SysTick定时器倒数完成' |5 f: Q# N9 H! v$ L
- SysTick->CTRL = 0;// 再次将SysTick控制寄存器清零,停止SysTick定时器
, @' ^4 A S# f9 I/ B9 N6 o - }
复制代码 使用系统滴答定时器来控制延时9 A: V# X( W8 ]+ x! G) W' y$ ]
五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等% q: W% }3 o( G! f3 ], s) p! z
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。
3 N' E2 V1 S% k) N
转载自: W-ML 如有侵权请联系删除
& R1 I( W) n0 | n : X4 H' `9 K* |5 N2 E5 Y! O
0 X' Y7 T7 R& b# A+ j! L
3 T3 W8 U2 H8 a+ \* Y( W;5 h; s" w( A1 ], e0 f
Rx_Index = 1;+ b( I" Z1 s0 ~+ b& D
}5 a# |! e e7 ?% h7 A
else if(Rx_Buffer[Rx_Index] == '#' && Rx_Buffer[0] == '
4 W, g [5 A7 P0 v
$ t0 L( [( z2 {5 F. ?Usart1:串口中断接收以“$”开头,“#”结尾的数据1 z- `8 V" Y( w( S" j* F
4、SysTick.c [ DISCUZ_CODE_7 ] 使用系统滴答定时器来控制延时& j% b7 i& G t4 z
五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等9 ~6 ^4 o; s5 V1 P) E
2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。 0 O7 \% S6 }" F* h
转载自: W-ML 如有侵权请联系删除4 J# k C) z7 Q+ I6 ^
3 Q+ n+ @; V! v$ O- b, J+ r) D* F" O
$ g+ }5 }* s+ J L
% o+ Q( M8 ?2 a+ p. h8 c& M- t1 `# p)// 如果数据是以#结尾则结束数据存储并打印数据信息
6 L" n6 R: Z& ?' f" n2 h {. r; [( n. B" |
printf("Received: %sundefined", Rx_Buffer);// 打印接收到的数据
. k0 a- q# Z4 b3 V7 Q memcpy(Data_Buffer, Rx_Buffer + 1, 3);// 使用memcpy函数进行数组内容复制
8 A0 r7 s; j: t K1 H: m+ g6 E7 T memset(Rx_Buffer, 0, sizeof(Rx_Buffer));// 清空接收缓冲区8 f) a' H' I4 B- m$ C8 l+ ?
Rx_Index = 0;// 重置索引! |8 Q0 o! X7 S& L4 s8 ?
Data_Flag = 1;// 数据标志位
, w: G+ Y* L& p6 R X3 Y }
! H0 l; u) n) L. }8 [ else( b3 p. _- ]+ u: h h7 [
{
. V6 w* f1 c a5 \# e Rx_Index++;// 增加索引,准备接收下一个字符
) G0 g' c _, w- W4 f& w$ q }4 J& O8 m1 a1 Z7 f5 k
}; Q0 Z+ O3 l3 T' ]( b
}[/code]% n9 F: d" Z2 A7 q" o' S
2 l9 O# |8 V, q( E
Usart1:串口中断接收以“$”开头,“#”结尾的数据
0 h {: ^" Y7 v7 W1 B5 L4、SysTick.c [ DISCUZ_CODE_7 ] 使用系统滴答定时器来控制延时
7 z; m0 X; W. Z3 n% y1 U! {+ H3 w五、实践 通过串口发送指令控制舵机(180°舵机演示) 1、UartAssist 注意:串口、波特率、以及数据格式等
) f8 F( ]9 r1 L2、现象 180°/270°舵机可以通过串口发送的指令控制舵机旋转的角度; 360°舵机可以通过串口发送的指令控制舵机旋转的方向和速度。
3 o$ A$ u g+ e
转载自: W-ML 如有侵权请联系删除
0 K) g9 Q' |, `* @/ g/ {/ e 3 v& [; }; e% P
3 u; w1 c9 K7 A) @* q+ b! l
) F9 g* _( V' F/ d7 {" ~) z, Z3 m
, v9 n: I; a' u5 f* T |