1. 项目概述
这个项目实现了一个基于PCA9685芯片的16通道舵机控制系统,通过STM32作为主控芯片,配合电脑串口通信,实现了用键盘控制多个舵机角度的功能。我在机器人关节控制项目中多次使用这种方案,它特别适合需要精确控制多个舵机协同工作的场景。
PCA9685是一款I2C接口的16通道PWM控制器芯片,每个通道都能独立输出12位分辨率(4096级)的PWM信号。结合STM32的强大处理能力和电脑的直观操作界面,这个系统可以灵活应用于机械臂控制、机器人行走、动画模型驱动等各种需要多自由度精确控制的场合。
2. 硬件设计与选型
2.1 核心器件选型解析
PCA9685 PWM控制器:
- 16通道独立PWM输出
- 12位分辨率(0.24°精度@180°舵机)
- 支持高达1.6kHz的PWM频率
- I2C接口,地址可配置(最多62个设备级联)
提示:PCA9685的VCC建议使用5V供电,与舵机电源一致,避免电平转换问题。
STM32主控选择:
我推荐使用STM32F103C8T6(蓝色药丸开发板):
- 72MHz主频,性能足够
- 多个USART接口
- 丰富的I2C资源
- 价格低廉,开发资源丰富
舵机选型建议:
- 标准180°模拟舵机(如SG90)
- 工作电压5-6V
- 注意总电流需求(多舵机需大功率电源)
2.2 硬件连接方案
完整接线示意图:
code复制[电脑USB] --(串口)--> [STM32] --(I2C)--> [PCA9685] --> [舵机1-16]
关键连接细节:
-
STM32与PCA9685:
- SCL -> PB6(I2C1)
- SDA -> PB7(I2C1)
- VCC -> 5V
- GND -> 共地
-
串口连接:
- STM32 USART1(PA9/PA10)接USB转TTL模块
- 波特率建议115200
-
舵机连接:
- 每个PCA9685输出通道接一个舵机信号线(黄色/白色)
- 舵机电源独立供电,注意电容滤波
3. 软件实现细节
3.1 STM32固件开发
开发环境配置:
- 使用STM32CubeMX初始化项目
- 开启I2C1(标准模式,100kHz)
- 配置USART1(115200,8N1)
- 生成MDK-ARM工程
PCA9685驱动关键代码:
c复制#define PCA9685_ADDR 0x40 // 默认地址
void PCA9685_Init(void) {
uint8_t buf[2];
// 复位芯片
buf[0] = 0x00; // MODE1寄存器
buf[1] = 0x10; // SLEEP位
HAL_I2C_Master_Transmit(&hi2c1, PCA9685_ADDR, buf, 2, 100);
HAL_Delay(10);
// 设置PWM频率为50Hz(适用于多数舵机)
buf[0] = 0xFE; // PRE_SCALE寄存器
buf[1] = 121; // 计算公式:osc_clock/(4096*update_rate)-1
HAL_I2C_Master_Transmit(&hi2c1, PCA9685_ADDR, buf, 2, 100);
// 退出睡眠模式
buf[0] = 0x00;
buf[1] = 0x80; // RESTART位
HAL_I2C_Master_Transmit(&hi2c1, PCA9685_ADDR, buf, 2, 100);
}
void SetServoAngle(uint8_t channel, uint16_t angle) {
uint16_t pulse = (uint16_t)(angle * (2000.0 / 180.0) + 500);
uint8_t buf[5];
buf[0] = 0x06 + 4 * channel; // LED0_ON_L寄存器
// 设置PWM占空比
buf[1] = 0x00; // ON时间低8位(固定0)
buf[2] = 0x00; // ON时间高8位
buf[3] = pulse & 0xFF; // OFF时间低8位
buf[4] = pulse >> 8; // OFF时间高8位
HAL_I2C_Master_Transmit(&hi2c1, PCA9685_ADDR, buf, 5, 100);
}
3.2 串口通信协议设计
我采用简单的ASCII协议,便于调试:
code复制C<channel>A<angle>\n
示例:
- "C0A90\n" -> 通道0设为90°
- "C15A180\n" -> 通道15设为180°
串口中断处理代码:
c复制char rxBuffer[16];
uint8_t rxIndex = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(rxBuffer[rxIndex] == '\n') {
ProcessCommand(rxBuffer, rxIndex);
rxIndex = 0;
} else {
rxIndex++;
if(rxIndex >= sizeof(rxBuffer)) rxIndex = 0;
}
HAL_UART_Receive_IT(huart, &rxBuffer[rxIndex], 1);
}
3.3 电脑端控制程序
Python实现示例(PySerial + Keyboard):
python复制import serial
import keyboard
ser = serial.Serial('COM3', 115200, timeout=1)
# 通道映射:按键->(通道, 步进角度)
key_map = {
'1': (0, 5), '2': (1, 5), '3': (2, 5),
'q': (0, -5), 'w': (1, -5), 'e': (2, -5),
# 可扩展更多按键和通道
}
current_angles = [90] * 16 # 初始位置
def send_command(channel, angle):
cmd = f"C{channel}A{angle}\n".encode()
ser.write(cmd)
current_angles[channel] = angle
while True:
for key, (channel, step) in key_map.items():
if keyboard.is_pressed(key):
new_angle = current_angles[channel] + step
new_angle = max(0, min(180, new_angle)) # 限幅
send_command(channel, new_angle)
while keyboard.is_pressed(key): pass # 防连按
4. 系统调试与优化
4.1 常见问题排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 舵机无反应 | I2C通信失败 | 检查地址、接线,用逻辑分析仪抓包 |
| 舵机抖动 | 电源不足 | 增加电容,使用独立5V 3A以上电源 |
| 角度不准 | 脉宽计算错误 | 校准500-2500μs对应0-180° |
| 串口数据丢失 | 波特率不匹配 | 检查两端波特率设置 |
| 多舵机不同步 | 刷新率过低 | 调整PCA9685的update_rate |
4.2 性能优化技巧
-
PWM频率选择:
- 标准舵机:50Hz(默认)
- 数码舵机:可尝试100-300Hz
- 使用
PCA9685_SetPWMFreq()函数调整
-
运动平滑处理:
c复制void SmoothMove(uint8_t channel, uint16_t target, uint16_t steps) {
uint16_t current = GetCurrentAngle(channel);
float delta = (float)(target - current) / steps;
for(int i=0; i<steps; i++) {
current += delta;
SetServoAngle(channel, current);
HAL_Delay(20);
}
}
- 多通道同步控制:
c复制void SetMultiServos(uint8_t channels[], uint16_t angles[], uint8_t count) {
uint8_t buf[1 + count*5];
buf[0] = 0xFA; // ALL_LED_ON_L寄存器
for(int i=0; i<count; i++) {
uint16_t pulse = (uint16_t)(angles[i] * (2000.0/180.0) + 500);
buf[1+5*i] = 0x00; // ON_L
buf[2+5*i] = 0x00; // ON_H
buf[3+5*i] = pulse & 0xFF; // OFF_L
buf[4+5*i] = pulse >> 8; // OFF_H
}
HAL_I2C_Master_Transmit(&hi2c1, PCA9685_ADDR, buf, sizeof(buf), 100);
}
5. 应用场景扩展
5.1 机械臂控制方案
通过定义动作组,可以实现复杂轨迹:
python复制actions = [
{"name": "抓取", "steps": [
{0:90, 1:45, 2:30},
{0:120, 1:60, 2:45},
{0:150, 1:90, 2:60}
]},
# 更多动作...
]
def play_action(action):
for step in action["steps"]:
for channel, angle in step.items():
send_command(channel, angle)
time.sleep(0.5)
5.2 电脑端GUI增强
使用PyQt创建控制面板:
python复制from PyQt5.QtWidgets import (QApplication, QSlider, QLabel,
QVBoxLayout, QWidget)
class ServoControl(QWidget):
def __init__(self, channel):
super().__init__()
self.channel = channel
self.slider = QSlider(Qt.Horizontal)
self.slider.setRange(0, 180)
self.slider.valueChanged.connect(self.update_angle)
layout = QVBoxLayout()
layout.addWidget(QLabel(f"通道 {channel}"))
layout.addWidget(self.slider)
self.setLayout(layout)
def update_angle(self, value):
send_command(self.channel, value)
5.3 多设备级联方案
PCA9685支持最多62个设备级联(16×62=992通道):
-
硬件修改:
- 每个PCA9685的A0-A5引脚设置不同地址
- 共用SCL/SDA线,电源并联
-
软件适配:
c复制void SetServoAngle(uint8_t device, uint8_t channel, uint16_t angle) {
uint8_t addr = 0x40 | (device & 0x3F); // 计算设备地址
// ...其余代码与单设备相同...
}
我在一个大型动画项目中成功应用了32个PCA9685级联(512通道),控制整个场景的机械装置同步运动。关键是要做好电源管理和信号隔离,每个PCA9685组最好有独立的电源稳压和I2C缓冲器。
