当前位置: 首页 > news >正文

衡山派开发板I2C扩展16路舵机控制:PCA9685模块驱动移植与RT-Thread实战

衡山派开发板I2C扩展16路舵机控制:PCA9685模块驱动移植与RT-Thread实战

最近在做一个机械臂项目,用衡山派开发板做主控,发现一个很头疼的问题:板子上的PWM引脚不够用了。舵机控制需要PWM信号,一个舵机就要占一个引脚,想做多自由度机器人,引脚资源立马捉襟见肘。

相信很多朋友在做机器人、机械臂或者多自由度项目时都遇到过类似问题。今天我就来分享一个非常实用的解决方案——通过I2C总线扩展16路PWM输出,使用PCA9685舵机驱动模块。这个模块特别适合衡山派开发板,只需要两个GPIO引脚(I2C的SCL和SDA),就能控制多达16个舵机,而且还能级联,最多可以控制992路PWM!

下面我就手把手教大家如何在RT-Thread操作系统下,把PCA9685的驱动移植到衡山派开发板上,实现精确的舵机角度控制。

1. PCA9685模块:你的多路PWM救星

1.1 模块简介与工作原理

PCA9685是一个I2C接口的16通道PWM控制器芯片。简单来说,它就像一个"PWM扩展器"——你的主控芯片通过I2C告诉PCA9685:"第0通道输出1ms高电平,19ms低电平",PCA9685就会忠实地执行,完全不需要主控芯片一直占用CPU资源去维持PWM波形。

这和传统的TLC5940系列有很大不同。传统方案需要主控不断发送数据来维持PWM输出,而PCA9685内置了时钟和PWM驱动器,你只需要设置一次,它就能自己维持输出,大大减轻了主控的负担。

模块关键特性:

  • 16路独立PWM输出:每路12位分辨率(4096级)
  • I2C接口:只需要SCL和SDA两根线
  • 可调频率:约1.6kHz可调,舵机常用50Hz
  • 宽电压兼容:3.3V~5V供电,可驱动5V舵机
  • 级联能力:通过地址选择引脚,最多可挂62个模块(992路PWM!)
  • 输出使能引脚:可快速禁用所有输出

1.2 硬件连接与引脚定义

模块的接线非常简单,只需要4根线:

引脚说明连接衡山派开发板
VCC电源正极(3.3V~5V)3.3V或5V电源
GND电源地GND
SCLI2C时钟线PE.14
SDAI2C数据线PE.12

注意:模块上已经内置了上拉电阻,所以不需要外接上拉电阻。如果你发现通信不稳定,可以尝试在SCL和SDA线上各加一个4.7kΩ的上拉电阻到VCC。

模块的舵机接口是标准的3针接口(信号、电源、地),可以直接插接舵机。每个通道最大输出电流约15mA,驱动舵机时需要外接电源。

2. 驱动移植:让PCA9685在RT-Thread上跑起来

2.1 获取驱动代码

首先需要下载驱动代码。驱动代码已经打包好,你可以在衡山派开发板的资料下载中心找到:

下载路径:资料下载中心 → 模块移植资料下载 → 找到"16路舵机驱动模块"的压缩包

下载后解压,把整个驱动文件夹复制到你的工程目录下:

\luban-lite\application\rt-thread\helloworld\user-bsp\

提示:如果你没有user-bsp这个文件夹,说明你还没有进行模块移植的前置配置。需要先按照官方文档完成必要的配置操作。

2.2 配置Kconfig文件

接下来需要修改Kconfig文件,让menuconfig能够识别我们的驱动模块。

用VSCode或其他编辑器打开:

application\rt-thread\helloworld\Kconfig

在文件的最后,#endif语句前面添加以下内容:

# 16路舵机驱动模块 source "application/rt-thread/helloworld/user-bsp/16ch-servo-drive-module/Kconfig"

2.3 使用menuconfig启用模块

现在进入env工具配置环境:

  1. 打开env工具:双击luban-lite文件夹下的win_env.bat

  2. 应用默认配置:输入以下命令选择衡山派开发板的默认配置

scons --apply-def=d13x_JLC_rt-thread_helloworld_defconfig
  1. 进入menuconfig
scons --menuconfig
  1. 启用模块
    • 进入Porting code using the LCKFB module菜单
    • 找到Using 16-channel servo driver module选项
    • Y键选中(前面会出现*号)
    • 按左右方向键选择<Save>保存配置,然后退出

2.4 编译与烧录

保存配置后,开始编译工程:

scons -j16

这里的-j16表示使用16个线程并行编译,编译速度更快。你可以根据自己电脑的CPU核心数调整这个数字。

编译完成后,在以下路径会生成镜像文件:

\luban-lite\output\d13x_JLC_rt-thread_helloworld\images\d13x_JLC_v1.0.0.img

用烧录工具将这个镜像烧录到开发板即可。

3. 代码深度解析:理解PCA9685的驱动原理

3.1 I2C地址配置

PCA9685是一个I2C从设备,每个设备都有一个唯一的地址。地址由A0、A1、A2三个引脚的电平决定:

A2A1A0地址(7位)写地址读地址
0000x400x800x81
0010x410x820x83
0100x420x840x85
..................

我们使用的模块默认A0、A1、A2都接地,所以地址是0x40。在I2C通信时,写地址是0x80(地址左移1位,最低位写0),读地址是0x81(地址左移1位,最低位读1)。

在代码中,这个地址定义在bsp_pca9685.c文件开头:

#define PCA_Addr 0x80 // IIC写地址

3.2 PWM频率设置:为什么是50Hz?

舵机控制需要20ms的PWM周期,也就是50Hz的频率。PCA9685的输出频率通过一个预分频器(prescale)来设置。

计算公式来自数据手册:

prescale = round(25,000,000 / (4096 × freq)) - 1

其中:

  • 25,000,000是PCA9685的内部时钟频率(25MHz)
  • 4096是12位计数器的最大值(0-4095)
  • freq是我们要设置的PWM频率

对于50Hz的舵机控制频率:

prescale = round(25,000,000 / (4096 × 50)) - 1 = round(25,000,000 / 204,800) - 1 = round(122.07) - 1 = 122 - 1 = 121

但在实际代码中,我们用的是这个公式:

prescaleval = 25000000; prescaleval /= 4096; prescaleval /= freq; prescaleval -= 1; prescale = floor(prescaleval + 0.5f);

这里有个重要的注意事项:修改频率必须在芯片休眠状态下进行。所以设置频率的步骤是:

  1. 读取MODE1寄存器的当前值
  2. 设置SLEEP位,让芯片进入休眠
  3. 写入预分频值
  4. 清除SLEEP位,唤醒芯片
  5. 等待5ms让芯片稳定

对应的代码实现:

void PCA9685_setFreq(float freq) { uint8_t prescale, oldmode, newmode; double prescaleval; // 计算预分频值 prescaleval = 25000000; prescaleval /= 4096; prescaleval /= freq; prescaleval -= 1; prescale = floor(prescaleval + 0.5f); // 保存当前模式,设置休眠位 oldmode = PCA9685_Read(PCA_Model); newmode = (oldmode & 0x7F) | 0x10; // 设置SLEEP位 PCA9685_Write(PCA_Model, newmode); // 进入休眠 // 写入频率设置 PCA9685_Write(PCA_Pre, prescale); // 0xFE是预分频寄存器地址 // 恢复模式,唤醒芯片 PCA9685_Write(PCA_Model, oldmode); delay_ms(5); // 设置自动递增模式 PCA9685_Write(PCA_Model, oldmode | 0xa1); }

3.3 舵机角度控制:脉宽计算

舵机的控制原理是通过PWM脉冲的宽度来控制角度。标准舵机的控制信号是:

  • 0.5ms脉宽 → 0度
  • 1.5ms脉宽 → 90度
  • 2.5ms脉宽 → 180度

在20ms周期(50Hz)下,这些脉宽对应的占空比是:

  • 0.5ms / 20ms = 2.5%
  • 1.5ms / 20ms = 7.5%
  • 2.5ms / 20ms = 12.5%

PCA9685使用12位计数器(0-4095),所以我们需要把时间转换成计数值:

计数值 = 脉宽(秒) × 频率(Hz) × 4096

对于50Hz频率:

  • 0.5ms脉宽:0.0005 × 50 × 4096 = 102.4 ≈ 102
  • 1.5ms脉宽:0.0015 × 50 × 4096 = 307.2 ≈ 307
  • 2.5ms脉宽:0.0025 × 50 × 4096 = 512

但在实际代码中,作者使用了经验公式。在PCA9685_SetAngle函数中:

int PCA9685_SetAngle(uint8_t Num, uint8_t Angle) { uint32_t off = 0; off = (uint32_t)(158 + Angle * 2.2); PCA9685_SetPWM(Num, 0, off); return RT_EOK; }

这个公式158 + Angle * 2.2是经过实测校准的。我们来验证一下:

  • 0度时:158 + 0 × 2.2 = 158
  • 90度时:158 + 90 × 2.2 = 158 + 198 = 356
  • 180度时:158 + 180 × 2.2 = 158 + 396 = 554

对应的脉宽:

  • 158对应约0.77ms(158/4096×20ms)
  • 356对应约1.74ms
  • 554对应约2.70ms

这个范围覆盖了大部分舵机的可调范围。如果你用的舵机不同,可能需要调整这个公式。

3.4 关键寄存器说明

PCA9685有几个重要的寄存器需要了解:

寄存器地址名称说明
0x00MODE1模式寄存器1,控制芯片工作模式
0x06LED0_ON_L通道0开启时间低字节
0x07LED0_ON_H通道0开启时间高字节
0x08LED0_OFF_L通道0关闭时间低字节
0x09LED0_OFF_H通道0关闭时间高字节
0xFEPRE_SCALE预分频寄存器,设置PWM频率

每个通道有4个寄存器(ON_L、ON_H、OFF_L、OFF_H),16个通道就是64个寄存器。寄存器地址是连续的,所以通道n的寄存器地址是:

  • LEDn_ON_L = 0x06 + 4×n
  • LEDn_ON_H = 0x07 + 4×n
  • LEDn_OFF_L = 0x08 + 4×n
  • LEDn_OFF_H = 0x09 + 4×n

4. 实战应用:创建舵机控制线程

4.1 测试代码解析

驱动包中提供了一个测试程序test_16ch_servo_drive_module.c,这个文件创建了一个线程来控制舵机。我们来看看关键部分:

// 线程入口函数 static void pca9685_thread_entry(void *param) { int ret = 0; int i = 0; int while_count = 1; /* PCA9685初始化,设置频率60Hz,初始角度0度 */ ret = PCA9685_Init(60, 0); if(ret != RT_EOK) { LOG_E("failed to PCA9685_Init !!"); return; } rt_thread_mdelay(1000); // 等待1秒让舵机稳定 rt_kprintf("Start Loop !!\n"); while(while_count++) { i = (i + 1) % 180; // 角度从0循环到179 PCA9685_SetAngle(0, i); // 控制第0通道 if(while_count >= 5000) { while_count = 1; rt_kprintf("\n输入[test_exit_pca9685_driver_module]命令退出\n"); rt_thread_mdelay(2000); } rt_thread_mdelay(50); // 每50ms改变一次角度 } }

这个线程做了几件事:

  1. 初始化PCA9685,设置PWM频率为60Hz(注意不是50Hz),所有舵机初始角度为0度
  2. 进入循环,让第0通道的舵机从0度逐步增加到179度,然后回到0度重新开始
  3. 每5000次循环提示用户如何退出

4.2 命令导出与使用

代码最后导出了两个命令:

// 启动PCA9685线程 MSH_CMD_EXPORT(test_pca9685_driver_module, run PCA9685 16 ch driver module); // 退出PCA9685线程 MSH_CMD_EXPORT(test_exit_pca9685_driver_module, quit PCA9685);

在RT-Thread的Finsh命令行中,你可以:

  • 输入test_pca9685_driver_module启动舵机控制
  • 输入test_exit_pca9685_driver_module停止控制

提示:输入命令时按Tab键可以自动补全命令名。

4.3 串口调试验证

将开发板通过USB转TTL模块连接到电脑,使用串口调试工具(如Putty、SecureCRT等)连接,波特率设置为115200。

上电后,在串口终端输入:

test_pca9685_driver_module

你会看到舵机开始缓慢转动,从0度转到180度,然后回到0度重新开始。同时终端会输出"Start Loop !!"的提示信息。

5. 实际项目中的使用技巧

5.1 多舵机同时控制

在实际项目中,我们往往需要同时控制多个舵机。修改测试代码,让多个舵机协同工作:

// 控制多个舵机做不同动作 for(int channel = 0; channel < 16; channel++) { // 每个舵机从不同角度开始 int angle = (channel * 10) % 180; PCA9685_SetAngle(channel, angle); }

5.2 角度平滑移动

直接设置角度会让舵机"跳"到目标位置,不够平滑。我们可以实现一个平滑移动函数:

void smooth_move(uint8_t channel, uint8_t target_angle, uint8_t speed) { uint8_t current_angle = get_current_angle(channel); // 需要自己记录当前角度 if(current_angle < target_angle) { for(uint8_t i = current_angle; i <= target_angle; i += speed) { PCA9685_SetAngle(channel, i); rt_thread_mdelay(20); // 每20ms移动一小步 } } else { for(uint8_t i = current_angle; i >= target_angle; i -= speed) { PCA9685_SetAngle(channel, i); rt_thread_mdelay(20); } } // 最后确保到达目标位置 PCA9685_SetAngle(channel, target_angle); }

5.3 处理不同品牌的舵机

不同品牌、不同型号的舵机,脉宽范围可能不同。常见的几种:

  • 标准舵机:0.5ms~2.5ms(0-180度)
  • 数码舵机:0.5ms~2.5ms,但中间位置可能是1.5ms
  • 特殊舵机:有些是0.9ms~2.1ms,或者0.6ms~2.4ms

你可以在PCA9685_SetAngle函数中修改计算公式:

// 对于0.9ms~2.1ms的舵机 off = (uint32_t)(184 + Angle * 1.33); // 0.9ms~2.1ms // 对于0.6ms~2.4ms的舵机 off = (uint32_t)(123 + Angle * 2.4); // 0.6ms~2.4ms

5.4 电源注意事项

舵机在转动时电流很大,特别是多个舵机同时转动时。务必注意:

  1. 独立供电:不要用开发板的3.3V或5V直接给多个舵机供电
  2. 电源滤波:舵机电源端要加大的电解电容(如1000μF)滤波
  3. 共地:舵机电源地和开发板电源地一定要连接在一起
  4. 电流估算:小型舵机空载约100-200mA,带负载可能达到500mA-1A

我一般会用单独的5V/3A以上的开关电源给舵机供电,开发板只提供控制信号。

6. 常见问题与调试技巧

6.1 舵机不转动或抖动

可能原因1:电源不足

  • 现象:舵机不转,或者转到某个位置就卡住
  • 解决:用万用表测量舵机供电电压,转动时电压不应低于4.8V

可能原因2:脉宽范围不对

  • 现象:舵机只在一个小范围内转动
  • 解决:调整PCA9685_SetAngle函数中的计算公式

可能原因3:频率设置错误

  • 现象:舵机发热但不转动
  • 解决:确认PWM频率设置为50Hz(舵机标准)或60Hz(代码中用的)

6.2 I2C通信失败

可能原因1:地址错误

  • 现象:完全没反应
  • 解决:用逻辑分析仪或示波器抓取I2C波形,确认地址是否正确

可能原因2:上拉电阻问题

  • 现象:偶尔通信成功,大部分时间失败
  • 解决:在SCL和SDA线上各加4.7kΩ上拉电阻到3.3V

可能原因3:时序问题

  • 现象:在特定角度通信失败
  • 解决:调整I2C时序中的延时,特别是delay_us(5)这些地方

6.3 多个模块级联

如果需要控制超过16个舵机,可以级联多个PCA9685模块。每个模块的A0、A1、A2引脚设置不同的电平,对应不同的I2C地址。

接线时注意:

  1. 所有模块的VCC、GND、SCL、SDA并联
  2. 每个模块的A0、A1、A2设置不同
  3. 在代码中根据地址分别初始化每个模块
// 初始化多个模块 PCA9685_Init_Addr(0x80, 60, 0); // 地址0x40的模块 PCA9685_Init_Addr(0x82, 60, 0); // 地址0x41的模块 PCA9685_Init_Addr(0x84, 60, 0); // 地址0x42的模块

你需要修改驱动,增加带地址参数的初始化函数。

通过PCA9685模块,我们成功解决了衡山派开发板PWM引脚不足的问题。现在只需要两个GPIO引脚,就能控制16个甚至更多的舵机,这对于机器人、机械臂、云台等需要多自由度控制的项目来说,简直是神器。

在实际使用中,我建议先把单个舵机调通,理解脉宽和角度的对应关系,然后再扩展到多个舵机。电源问题一定要重视,很多奇怪的问题都是电源引起的。如果遇到问题,先用逻辑分析仪抓一下I2C波形,往往能快速定位问题所在。

这个方案我在好几个机器人项目中都用过,稳定性很好。希望这篇教程能帮你快速上手PCA9685,让你的多舵机项目顺利进行。

http://www.jsqmd.com/news/494072/

相关文章:

  • LangFlow+向量数据库实战:打造具备记忆能力的智能问答系统
  • 基于深度学习的学生上课行为检测(YOLOv12/v11/v8/v5模型+数据集)(源码+lw+部署文档+讲解等)
  • 颠覆性文字转CAD技术:Zoo Text-to-CAD UI让创意设计零门槛实现
  • ChatTTS音色推荐实战:如何构建高保真语音合成系统
  • VSCode侧边栏与状态栏全解析:从Git管理到编码效率提升
  • 从驱动到界面:基于I.MX6ULL与Qt的车载信息娱乐系统全栈实践
  • 3个提升效率的AI提示词框架:让大模型交互更简单
  • Delphi实战:FireDAC与uniDAC高效连接PostgreSQL的配置指南
  • Star 4.4k 开源 OpenClaw 桌面客户端
  • 基于SpringBoot的Java毕设畜牧业系统:新手入门实战与避坑指南
  • YimMenu技术指南:从问题解决到高级应用的完整方案
  • PP-DocLayoutV3应用案例:自动分析论文版面,快速提取图表和标题
  • 用Python验证高等数学公式:手把手实现定积分对称性检验
  • Spring_couplet_generation助力乡村振兴:为乡村文旅定制AI文化内容
  • MissionPlanner地面控制站实战指南:从安装到飞行的全流程掌握
  • ModelScope模型列表深度使用指南:如何根据场景选择最适合的API模型
  • CodeWarrior 5.2与USBDM下载器:高效烧录程序的完整指南
  • YimMenu:GTA V游戏体验增强与安全防护全方案
  • 2026年比较好的政府媒资管理系统公司推荐:政府媒资管理系统行业公司推荐 - 品牌宣传支持者
  • WPF DataGrid控件进阶应用:从基础绑定到高级交互全解析
  • VCS编译选项深度解析:-debug_access和-debug_region对Verdi波形可视化的影响
  • I2C总线协议详解:从标准模式到超速模式的实战指南(NXP UM10204中文版解析)
  • YOLOv8实战:从零构建高精度竹签计数模型(保姆级教程)
  • 智能虚拟试衣技术解决方案:ComfyUI-IDM-VTON实现与应用指南
  • 零基础玩转MissionPlanner:从安装到飞行的无人机地面站实战指南
  • i茅台自动化决策系统:从人工操作到智能管理的效率优化方案
  • VibeVoice Pro GPU算力优化指南:RTX 3090上实现高吞吐低延迟语音生成
  • JDE:从特征金字塔到损失平衡,剖析实时多目标跟踪的联合学习之道
  • SquareLine Studio汉化版安装与激活全攻略(附一个月免费激活码)
  • QWEN-AUDIOGPU算力优化教程:BFloat16推理+动态显存回收实操