PCA9685-16 路舵机驱动模块
当你在一个项目中碰到了微控制器芯片的 PWM 输出引脚不够用的情况,那么这款 PCA968516 路舵机就能很快帮助您解决这个问题了。只要你的主控芯片具备了 I2C 通信,就能够让主控芯片和 PCA9685 通信,实现多个舵机的同时控制了。PCA9685 16 路舵机是一个采用 I2C 通信,内置了 PWM 驱动器和一个时钟,这个意味着,这将和 TLCG940 系列有很大不同,你不需要不断发送信号占用你的单片机。它是 5V 的兼容,这意味你还可以用 3.3V 单片机控制并且安全地驱动到 6V 输出(当你想要控制白色或蓝色指示灯用 3.4+ 正电压也是可以的)。地址选择引脚使你可以把 62 个驱动板挂在单个 l2C 总线上,总共有 992 路 PWM 输出,那将是非常庞大的资源,约 1.6Khz 可调频 PWM 输出,为步进电机准备输出 12 位分辨率,可配置的推拉输出或开路输出,输出使能引脚能够快速禁用所有输出。
模块来源
采购链接:
16 路 PWM Servo 舵机驱动板机器人控制器 IIC 接口驱动器模块 PCA9685
资料下载:
https://pan.baidu.com/s/1FjoAuJm387bxaZxS6g9HEg
提取码:8888
规格参数
输入电压: 3.3V~5V
额定电流: 15mA
控制方式: 串口
尺寸: 21(长)*21(宽)[单位:mm]
移植过程
我们的目标是在天空星 STM32F407 上能够控制多路舵机的功能。首先要获取资料,查看数据手册应如何实现,再移植至我们的工程。
查看资料
I2C 器件地址
PCA9685 是一个 I2C 从设备,有个设备 ID,或者叫从 地址。从地址是如下确定的:
Board 0: Address = 0×40 Offset = binary 00000 (默认)
Board 1: Address = 0×41 Offset = binary 00001 (A0 接上拉)
Board 2: Address = 0×42 Offset = binary 00010 (接上 A1 上拉)
Board 3: Address = 0×43 Offset = binary 00011 (A0 和 A1 上拉)
Board 4: Address = 0×44 Offset = binary 00100 (A2 上拉)
以此类推;
PCA9685 的 I2C 总线从地址如下图所示。为了节约电力,硬件可选地址引脚上没有内部上拉电阻,它们必须被拉高或拉低。但是我们使用的是模块,而模块上已经为我们接好了上拉电阻。
地址字节的最后一位定义要执行的操作。当设置为逻辑 1 时,将选择读操作,而逻辑 0 则选择写操作。
在原理图中,地址线全部接 0,所以 slave address 是 0x40。对应 Fig 4 上的位置,则为
则 IIC 地址是 0x80 ,写入时是 0x80,读取时是 0x81。
设置 PWM 频率
舵机控制所需的 PWM 周期为 20 ms. 在用 PCA9685 作为多舵机控制器时,需要将 其 PWM 输出周期设定为 20 ms,即 PWM 波的频率设定为 50 Hz,PCA9685 输出频率与振荡器有关,频率的设置值 refresh_rate 见下面的公式;
其中,EXTCLK 是 PCA9685 的内部时钟频率为 25Mhz;prescale 是要设置的频率,我们设置为 50Hz;
refresh_rate = 25,000,000 /( 4096 * ( 50 + 1 ))
refresh_rate = 25,000,000 / 4096 / (50 + 1)
refresh_rate = 6,103.52 / (50 + 1)
refresh_rate = 6,103.52 / 51
refresh_rate = 119.68
所以我们需要设置的值是 119.68,取整数就是 120。
需要注意的是,频率的更改只能在 PCA9685 芯片处于休眠状态下进行。
以下加粗字体是数据手册内容:
要使用 EXTCLK 引脚,该位必须按以下顺序设置:
- 在 mode1 中设置 SLEEP 位。这就关闭了内部振荡器,使芯片处于休眠状态。
- 将逻辑 1 写入 MODE1 中的 SLEEP 和 EXTCLK 位。这样就转换完成了。外部时钟可以在切换期间处于活动状态,因为设置了 SLEEP 位。
这个位是一个“粘性位”,也就是说,它不能通过写入逻辑 0 来清除。EXTCLK 位只能通过电源循环或软件重置来清除。
占空比或者脉冲宽度的设定
每个 PWM 引脚输出的开启时间和 PWM 的占空比可以通过 LEDn_ON 和 LEDn_OFF 寄存器独立控制。
每个 PWM 引脚输出将有两个 12 位寄存器。这些寄存器将由用户编程。两个寄存器都将保存从 0 到 4095 的值。一个 12 位寄存器将保存 ON 时间的值,另一个 12 位寄存器将保存 OFF 时间的值。将 ON 和 OFF 时间与 12 位计数器的值进行比较,该计数器将从 0000h 持续运行到 0FFFh(0 到 4095 十进制)。
ON 时间是可编程的,它是 PWM 输出 ON 的时间,OFF 时间也是可编程的,它是 PWM 输出 OFF 的时间。这样相移就完全可编程了。相移的分辨率为目标频率的 1 / 4096。表 7 列出了这些寄存器。
以下用一个例子说明如何计算要加载到这些寄存器中的值。
(假设使用 LED0 输出,(延时时间)+ (PWM 占空比)<=100%)
延迟时间 = 10%;PWM 占空比= 20% (LEDON 电平= 20%;LEDOFF 时间= 80%)。延迟时间= 10% = 4096 * 0.1 = 409.6 ~ 410,计数= 410(十进制) = 19Ah(十六进制)
因为计数器从 0 开始,到 4095 结束,我们将减去 1,所以延迟时间 = 199h 个数。
LED0_ON_H = 1h;LED0_ON_L = 99h (LED 开始打开后,这个延迟计数到 409)
LED 开机时间= 20% = 819.2 ~ 819 次
LED 关闭时间= 4CCh(十进制 410 + 819-1 = 1228)
LED0_OFF_H = 4h;LED0_OFF_L = CCh(此计数到 1228 后 LED 开始关闭)
整个周期为 4095, LED_ON 和 LED_OFF 2 个的设定值确定脉宽,在后面的代码里,LED_ON 设为 0, LED_OFF 就是脉宽了。 这里都用 2 位字节来表示。
相关地址表
这里只截图了需要的地址,分别是:
#define PCA_Addr 0x80 //IIC 地址
#define PCA_Model 0x00
#define LED0_ON_L 0x06
#define LED0_ON_H 0x07
#define LED0_OFF_L 0x08
#define LED0_OFF_H 0x09
#define PCA_Pre 0xFE //配置频率地址
引脚选择
移植至工程
移植步骤中的导入.c 和.h 文件与第二章的第 1 小节【DHT11 温湿度传感器】相同,只是将.c 和.h 文件更改为 bsp_pca9685.c 与 bsp_pca9685.h。这里不再过多讲述,移植完成后面修改相关代码。
在文件 bsp_pca9685.c 中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*
Change Logs:
* Date Author Notes
* 2024-03-22 LCKFB-LP first version
*/
#include "bsp_pca9685.h"
#include "stdio.h"
#include "board.h"
#include <math.h>
/******************************************************************
* 函 数 名 称:PCA9685_GPIO_Init
* 函 数 说 明:PCA9685的引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PCA9685_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_PCA9685_GPIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_SDA|GPIO_SCL;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(PORT_PCA9685, &GPIO_InitStructure);
}
/******************************************************************
* 函 数 名 称:IIC_Start
* 函 数 说 明:IIC起始时序
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Start(void)
{
SDA_OUT();
SDA(1);
delay_us(5);
SCL(1);
delay_us(5);
SDA(0);
delay_us(5);
SCL(0);
delay_us(5);
}
/******************************************************************
* 函 数 名 称:IIC_Stop
* 函 数 说 明:IIC停止信号
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(5);
SDA(1);
delay_us(5);
}
/******************************************************************
* 函 数 名 称:IIC_Send_Ack
* 函 数 说 明:主机发送应答或者非应答信号
* 函 数 形 参:0发送应答 1发送非应答
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void IIC_Send_Ack(unsigned char ack)
{
SDA_OUT();
SCL(0);
SDA(0);
delay_us(5);
if(!ack) SDA(0);
else SDA(1);
SCL(1);
delay_us(5);
SCL(0);
SDA(1);
}
/******************************************************************
* 函 数 名 称:I2C_WaitAck
* 函 数 说 明:等待从机应答
* 函 数 形 参:无
* 函 数 返 回:0有应答 1超时无应答
* 作 者:LC
* 备 注:无
******************************************************************/
unsigned char I2C_WaitAck(void)
{
char ack = 0;
unsigned char ack_flag = 10;
SCL(0);
SDA(1);
SDA_IN();
delay_us(5);
SCL(1);
delay_us(5);
while( (SDA_GET()==1) && ( ack_flag ) )
{
ack_flag--;
delay_us(5);
}
if( ack_flag <= 0 )
{
IIC_Stop();
return 1;
}
else
{
SCL(0);
SDA_OUT();
}
return ack;
}
/******************************************************************
* 函 数 名 称:Send_Byte
* 函 数 说 明:写入一个字节
* 函 数 形 参:dat要写人的数据
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void Send_Byte(uint8_t dat)
{
int i = 0;
SDA_OUT();
SCL(0);//拉低时钟开始数据传输
for( i = 0; i < 8; i++ )
{
SDA( (dat & 0x80) >> 7 );
delay_us(1);
SCL(1);
delay_us(5);
SCL(0);
delay_us(5);
dat<<=1;
}
}
/******************************************************************
* 函 数 名 称:Read_Byte
* 函 数 说 明:IIC读时序
* 函 数 形 参:无
* 函 数 返 回:读到的数据
* 作 者:LC
* 备 注:无
******************************************************************/
unsigned char Read_Byte(void)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
SCL(0);
delay_us(5);
SCL(1);
delay_us(5);
receive<<=1;
if( SDA_GET() )
{
receive|=1;
}
delay_us(5);
}
SCL(0);
return receive;
}
/******************************************************************
* 函 数 名 称:PCA9685_Write
* 函 数 说 明:向PCA9685写命令或数据
* 函 数 形 参:addr写入的寄存器地址 data写入的命令或数据
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PCA9685_Write(uint8_t addr,uint8_t data)
{
IIC_Start();
Send_Byte(PCA_Addr);
I2C_WaitAck();
Send_Byte(addr);
I2C_WaitAck();
Send_Byte(data);
I2C_WaitAck();
IIC_Stop();
}
/******************************************************************
* 函 数 名 称:PCA9685_Read
* 函 数 说 明:读取PCA9685数据
* 函 数 形 参:addr读取的寄存器地址
* 函 数 返 回:读取的数据
* 作 者:LC
* 备 注:无
******************************************************************/
uint8_t PCA9685_Read(uint8_t addr)
{
uint8_t data;
IIC_Start();
Send_Byte(PCA_Addr);
I2C_WaitAck();
Send_Byte(addr);
I2C_WaitAck();
IIC_Stop();
delay_us(10);
IIC_Start();
Send_Byte(PCA_Addr|0x01);
I2C_WaitAck();
data = Read_Byte();
IIC_Send_Ack(1);
IIC_Stop();
return data;
}
/******************************************************************
* 函 数 名 称:PCA9685_setPWM
* 函 数 说 明:设置第num个PWM引脚,on默认为0,控制舵机旋转off角度
* 函 数 形 参:num:设置第几个引脚输出,范围0~15
* on :默认为0
* off:舵机旋转角度,范围:0~180
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PCA9685_setPWM(uint8_t num,uint32_t on,uint32_t off)
{
IIC_Start();
Send_Byte(PCA_Addr);
I2C_WaitAck();
Send_Byte(LED0_ON_L+4*num);
I2C_WaitAck();
Send_Byte(on&0xFF);
I2C_WaitAck();
Send_Byte(on>>8);
I2C_WaitAck();
Send_Byte(off&0xFF);
I2C_WaitAck();
Send_Byte(off>>8);
I2C_WaitAck();
IIC_Stop();
}
/******************************************************************
* 函 数 名 称:PCA9685_setFreq
* 函 数 说 明:设置PCA9685的输出频率
* 函 数 形 参:freq
* 函 数 返 回:无
* 作 者:LC
* 备 注:
floor语法:
FLOOR(number, significance)
Number必需。要舍入的数值。
Significance必需。要舍入到的倍数。
说明
将参数 number 向下舍入(沿绝对值减小的方向)为最接近的 significance 的倍数。
如果任一参数为非数值型,则 FLOOR 将返回错误值 #VALUE!。
如果 number 的符号为正,且 significance 的符号为负,则 FLOOR 将返回错误值 #NUM!
示例
公式 说明 结果
FLOOR(3.7,2) 将 3.7 沿绝对值减小的方向向下舍入,使其等于最接近的 2 的倍数 2
FLOOR(-2.5, -2) 将 -2.5 沿绝对值减小的方向向下舍入,使其等于最接近的 -2 的倍数 -2
******************************************************************/
void PCA9685_setFreq(float freq)
{
uint8_t prescale,oldmode,newmode;
double prescaleval;
// freq *= 0.9; // Correct for overshoot in the frequency setting (see issue #11).
// PCA9685的内部时钟频率是25Mhz
// 公式: presale_Volue = round( 25000000/(4096 * update_rate) ) - 1
// round = floor(); floor是数学函数,需要导入 math.h 文件
// update_rate = freq;
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale = floor(prescaleval+0.5f);
//返回MODE1地址上的内容(保护其他内容)
oldmode = PCA9685_Read(PCA_Model);
//在MODE1中设置SLEEP位
newmode = (oldmode&0x7F)|0x10;
//将更改的MODE1的值写入MODE1地址,使芯片睡眠
PCA9685_Write(PCA_Model,newmode);
//写入我们计算的设置频率的值
//PCA_Pre = presale 地址是0xFE,可以数据手册里查找到
PCA9685_Write(PCA_Pre,prescale);
//重新复位
PCA9685_Write(PCA_Model,oldmode);
//等待复位完成
delay_1ms(5);
//设置MODE1寄存器开启自动递增
PCA9685_Write(PCA_Model,oldmode|0xa1);
}
//
/******************************************************************
* 函 数 名 称:setAngle
* 函 数 说 明:设置角度
* 函 数 形 参:num要设置的PWM引脚 angle设置的角度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void setAngle(uint8_t num,uint8_t angle)
{
uint32_t off = 0;
off = (uint32_t)(158+angle*2.2);
PCA9685_setPWM(num,0,off);
}
/******************************************************************
* 函 数 名 称:PCA9685_Init
* 函 数 说 明:PCA9685初始化,所有PWM输出频率配置与所有PWM引脚输出的舵机角度
* 函 数 形 参:hz设置的初始频率 angle设置的初始角度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void PCA9685_Init(float hz,uint8_t angle)
{
uint32_t off = 0;
PCA9685_GPIO_Init();
//在MODE1地址上写0x00
PCA9685_Write(PCA_Model,0x00); //这一步很关键,如果没有这一步PCA9685就不会正常工作。
// pwm.setPWMFreq(SERVO_FREQ)函数主要是设置PCA9685的输出频率,
// PCA9685的16路PWM输出频率是一致的,所以是不能实现不同引脚不同频率的。
// 下面是setPWMFreq函数的内容,主要是根据频率计算PRE_SCALE的值。
PCA9685_setFreq(hz);
//计算角度
off = (uint32_t)(145+angle*2.4);
//控制16个舵机输出off角度
PCA9685_setPWM(0,0,off);
PCA9685_setPWM(1,0,off);
PCA9685_setPWM(2,0,off);
PCA9685_setPWM(3,0,off);
PCA9685_setPWM(4,0,off);
PCA9685_setPWM(5,0,off);
PCA9685_setPWM(6,0,off);
PCA9685_setPWM(7,0,off);
PCA9685_setPWM(8,0,off);
PCA9685_setPWM(9,0,off);
PCA9685_setPWM(10,0,off);
PCA9685_setPWM(11,0,off);
PCA9685_setPWM(12,0,off);
PCA9685_setPWM(13,0,off);
PCA9685_setPWM(14,0,off);
PCA9685_setPWM(15,0,off);
delay_1ms(100);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
在文件 bsp_pca9685.h 中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*
Change Logs:
* Date Author Notes
* 2024-03-22 LCKFB-LP first version
*/
#ifndef _BSP_PCA9685_H_
#define _BSP_PCA9685_H_
#include "stm32f4xx.h"
//端口移植
#define RCC_PCA9685_GPIO RCC_AHB1Periph_GPIOA
#define PORT_PCA9685 GPIOA
#define GPIO_SDA GPIO_Pin_5
#define GPIO_SCL GPIO_Pin_6
//设置SDA输出模式
#define SDA_OUT() { \
GPIO_InitTypeDef GPIO_InitStructure; \
GPIO_InitStructure.GPIO_Pin = GPIO_SDA; \
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; \
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; \
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; \
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; \
GPIO_Init(GPIOA, &GPIO_InitStructure); \
}
//设置SDA输入模式
#define SDA_IN() { \
GPIO_InitTypeDef GPIO_InitStructure; \
GPIO_InitStructure.GPIO_Pin = GPIO_SDA; \
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; \
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; \
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; \
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; \
GPIO_Init(GPIOA, &GPIO_InitStructure); \
}
//获取SDA引脚的电平变化
#define SDA_GET() GPIO_ReadInputDataBit(GPIOA, GPIO_SDA)
//SDA与SCL输出
#define SDA(x) GPIO_WriteBit(GPIOA, GPIO_SDA, (x?Bit_SET:Bit_RESET) )
#define SCL(x) GPIO_WriteBit(GPIOA, GPIO_SCL, (x?Bit_SET:Bit_RESET) )
#define PCA_Addr 0x80 //IIC地址
#define PCA_Model 0x00
#define LED0_ON_L 0x06
#define LED0_ON_H 0x07
#define LED0_OFF_L 0x08
#define LED0_OFF_H 0x09
#define PCA_Pre 0xFE //配置频率地址
void PCA9685_Init(float hz,uint8_t angle);
void setAngle(uint8_t num,uint8_t angle);
void PCA9685_setFreq(float freq);
void PCA9685_setPWM(uint8_t num,uint32_t on,uint32_t off);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
移植验证
在自己工程中的 main 主函数中,编写如下。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*
Change Logs:
* Date Author Notes
* 2024-03-22 LCKFB-LP first version
*/
#include "board.h"
#include "bsp_uart.h"
#include <stdio.h>
#include "bsp_pca9685.h"
int main(void)
{
uint8_t i = 0;
board_init();
uart1_init(115200U);
printf("start\r\n");
PCA9685_Init(60,0); //PCA9685--16路舵机初始化 频率60Hz -- 0度
delay_ms(1000);
while(1)
{
i = ( i + 1 ) % 180;
setAngle(0,i);
delay_ms(50);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
移植现象:0 号接口的舵机从 0 度一直移动到 180 度后,又回到 0 度。
代码下载
链接在开发板介绍
章节的离线资料下载!!