嵌入式开发 CH55x 系列单片机完全指南:从选型到 USB 设备开发实战
大家好,我是 MakerOnsite 的老朋友。
相信很多做嵌入式开发的朋友都遇到过这个问题:项目要选型了,CH551、CH552、CH554、CH558、CH559……这一堆型号到底该怎么选?选完之后怎么搭建开发环境?怎么写 USB 设备程序?怎么做调试器?
今天这篇文章,我会把这几年的踩坑经验一次性整理出来,从选型到实战,帮你全面掌握 CH55x 系列。准备好了吗?我们开始。
一、CH55x 系列型号对比
先上一张核心参数对比表,这是我最常用的选型参考:
| 型号 | Flash | RAM | USB | 定时器 | ADC | 参考价格 |
|---|---|---|---|---|---|---|
| CH551 | 10KB | 256B | Device | 2 | 8 位 | ¥1.5 |
| CH552 | 14KB | 384B | Device | 2 | 8 位 | ¥2.0 |
| CH554 | 14KB | 384B | Device | 3 | 10 位 | ¥2.5 |
| CH558 | 64KB | 4KB | Device | 4 | 10 位 | ¥5.0 |
| CH559 | 64KB | 4KB | Host+Device | 4 | 10 位 | ¥6.5 |
选型建议:
- 入门学习/简单项目:CH552 足够,性价比高,不到一杯奶茶钱
- 需要更多定时器:CH554 多一个定时器,适合 PWM 控制场景
- 大程序/复杂逻辑:CH558 拥有 64KB Flash,空间充裕
- USB Host 需求:只能选 CH559,这是唯一支持 USB Host 的型号,适合做键盘、鼠标等 HID 设备
如果你只是学习,买一块 CH552 或 CH554 的开发板就够了。如果要做 USB 设备开发,建议直接上 CH559。
二、硬件准备
下面列出了开发和调试所需的基本硬件清单:
| 物品 | 型号/规格 | 参考价格 |
|---|---|---|
| CH552 开发板 | 带 USB 接口 | ¥10 |
| CH559 开发板 | USB Host 版 | ¥25 |
| USB 转 TTL 模块 | CH340/CP2102 | ¥5 |
| 面包板 | 400 孔 | ¥8 |
| 杜邦线 | 公对母 20cm | ¥3 |
| LED | 5mm 红色 | ¥0.5 |
| 电阻 | 220Ω 1/4W | ¥0.2 |
| 微动开关 | 6×6mm | ¥0.3 |
入门总成本控制在百元以内。如果你手头有现成的 CH552 开发板,成本几乎为零。
三、开发环境搭建
第一步:安装工具链
CH55x 使用的是 SDCC 编译器(开源 8051 编译器),安装步骤如下:
# 更新软件源
sudo apt-get update
# 安装 SDCC 编译器
sudo apt-get install sdcc
# 安装 CH55x 烧录工具 ch552tool
git clone https://github.com/arpruss/ch552tool.git
cd ch552tool
make
sudo make install
⚠️ 注意:很多同学在安装 ch552tool 时会遇到依赖问题。如果
make报错说缺少 libusb,先安装依赖:sudo apt-get install libusb-1.0-0-dev
第二步:配置 USB 权限
烧录程序需要 USB 权限,不然每次都要 sudo 很麻烦:
# 创建 udev 规则
sudo tee /etc/udev/rules.d/70-ch552.rules > /dev/null << 'EOF'
SUBSYSTEM=="usb", ATTR{idVendor}=="4348", ATTR{idProduct}=="55e0", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="4348", ATTR{idProduct}=="55e8", MODE="0666"
EOF
# 重载 udev 规则
sudo udevadm control --reload-rules
sudo udevadm trigger
第三步:验证安装
# 检查 SDCC 版本
sdcc -v
# 检查 ch552tool
ch552tool --help
如果都能看到正常输出,恭喜,环境搭建成功!
四、第一个程序:点亮 LED
我们来写一个最简单的 LED 闪烁程序,熟悉一下开发流程。
/* LED 闪烁示例 - CH552 */
#include <ch554.h>
#include <debug.h>
// 定义 LED 引脚(根据实际开发板修改)
#define LED_PIN P1_0
void main() {
// 配置系统时钟为 24MHz
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
CLOCK_CFG |= bOSC_EN_XT; // 使能外部晶振
SAFE_MOD = 0x00;
// 配置 LED 引脚为推挽输出
P1_DIR |= (1 << 0);
P1_PU &= ~(1 << 0);
// 主循环
while(1) {
LED_PIN = 0; // LED 亮
DelayMs(500); // 延时 500ms
LED_PIN = 1; // LED 灭
DelayMs(500); // 延时 500ms
}
}
原理解析:
这段代码做了三件事:
- 配置系统时钟为 24MHz,确保芯片运行在正确频率
- 将 P1.0 引脚设为推挽输出模式,用于驱动 LED
- 在主循环中交替输出高低电平,实现 LED 闪烁
编译与烧录
# 编译代码
sdcc -mcc52 --no-xinit-code --code-loc 0x0000 --data-loc 0x0030 led.c
# 烧录程序(需要先按住开发板上的 BOOT 键再上电)
ch552tool write led.ihx
烧录小技巧:CH55x 系列需要进入 Bootloader 模式才能烧录。方法是:先按住 BOOT 键,插上 USB 供电,然后松开 BOOT 键。这时候设备会识别为 CH55x Bootloader,就可以烧录了。
五、实战一:用 CH559 制作 USB HID 设备
什么是 HID 设备?
HID(Human Interface Device)是 USB 协议中的一种设备类,包括键盘、鼠标、游戏手柄等。Windows、Linux、macOS 都原生支持 HID,不需要额外驱动,插上就能用。对于嵌入式开发来说,简直不要太友好。
今天的目标是:用 CH559 做一个自定义的 HID 键盘设备,按下按钮自动发送 Ctrl+Alt+Delete 组合键。
HID 协议基础
HID 设备的核心是描述符——一段数据结构,告诉主机”我是什么设备,能做什么”。一个典型的 HID 设备需要以下描述符:
- 设备描述符:声明 VID/PID、设备类型等基本信息
- 配置描述符:声明设备的供电方式和接口数量
- 接口描述符:声明接口类型(HID 类为 0x03)
- HID 描述符:声明报告描述符的长度和版本
- 报告描述符:定义数据的格式和含义,是 HID 设备的灵魂
- 端点描述符:声明中断端点的地址、传输方式和包大小
USB 描述符编写
// 设备描述符
__code UINT8 DevDesc[] = {
0x12, 0x01, 0x10, 0x01, 0x00, 0x00, 0x00, 0x08,
0x48, 0x43, 0x59, 0x55, 0x00, 0x01, 0x01, 0x02,
0x00, 0x01, 0x01, 0x00, 0x00, 0x01
};
// 配置描述符(包含 HID 描述符)
__code UINT8 CfgDesc[] = {
// 配置描述符头
0x09, 0x02, 0x22, 0x00, 0x01, 0x01, 0x00, 0x80, 0x32,
// 接口描述符
0x09, 0x04, 0x00, 0x00, 0x01, 0x03, 0x01, 0x01, 0x00,
// HID 描述符
0x09, 0x21, 0x10, 0x01, 0x00, 0x01, 0x22, 0x20, 0x00,
// 端点描述符(中断端点)
0x07, 0x05, 0x81, 0x03, 0x08, 0x00, 0x0A
};
// 报告描述符(定义键盘数据格式)
__code UINT8 ReportDesc[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0xE0, // Usage Minimum (224)
0x29, 0xE7, // Usage Maximum (231)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x95, 0x01, 0x75, 0x08, 0x81, 0x01, // 保留字节
0x95, 0x05, 0x75, 0x01, 0x05, 0x08, // LED 输出报告
0x19, 0x01, 0x29, 0x05, 0x91, 0x02,
0x95, 0x01, 0x75, 0x03, 0x91, 0x01, // 填充位
0x95, 0x06, 0x75, 0x08, 0x15, 0x00, // 6 个按键字节
0x25, 0x65, 0x05, 0x07, 0x19, 0x00,
0x29, 0x65, 0x81, 0x00,
0xC0 // End Collection
};
报告描述符是 HID 设备的核心。上面这段代码定义了一个标准的键盘设备:前 8 位表示修饰键(Ctrl、Alt、Shift 等),接下来 8 位保留,然后 5 位表示 LED 状态(NumLock、CapsLock 等),最后 6 个字节表示按下的键值(支持 6 键无冲)。
主程序与中断处理
/* main.c */
#include <ch554.h>
#include <debug.h>
#define BUTTON_PIN P3_0
#define LED_PIN P1_0
// USB 端点缓冲区
__at (0x0040) __xdata UINT8 Ep0Buffer[8];
__at (0x0048) __xdata UINT8 Ep1Buffer[8];
// 键盘报告数据结构
typedef struct {
UINT8 modifiers;
UINT8 reserved;
UINT8 keys[6];
} KeyboardReport;
__xdata KeyboardReport KeyReport;
extern void USB_Device_Init();
extern void USB_ISR() __interrupt (USB_INT_VECTOR);
void DelayMs(UINT16 ms) {
UINT16 i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 2000; j++);
}
void main() {
USB_Device_Init();
EA = 1; // 开启总中断
// 配置按钮引脚(上拉输入)
P3_DIR &= ~(1 << 0);
P3_PU |= (1 << 0);
// 配置 LED 引脚
P1_DIR |= (1 << 0);
memset(&KeyReport, 0, sizeof(KeyReport));
while(1) {
if(BUTTON_PIN == 0) {
DelayMs(20); // 消抖
if(BUTTON_PIN == 0) {
LED_PIN = 0;
// 发送 Ctrl+Alt+Delete
KeyReport.modifiers = 0x01 | 0x04; // Ctrl + Alt
KeyReport.keys[0] = 0x4C; // Delete
memcpy(Ep1Buffer, &KeyReport, 8);
DelayMs(100);
// 释放按键
memset(&KeyReport, 0, sizeof(KeyReport));
memcpy(Ep1Buffer, &KeyReport, 8);
LED_PIN = 1;
DelayMs(500); // 防止重复触发
}
}
}
}
编译与烧录
sdcc -mcc59 --no-xinit-code --code-loc 0x0000 --data-loc 0x0030 main.c USB_DEV.C
ch552tool write main.ihx
烧录完成后,把设备插到电脑上。按下按钮,电脑应该会收到 Ctrl+Alt+Delete 组合键。
⚠️ 注意:在生产环境测试前,最好先在虚拟机里试一下,避免意外注销或重启。
六、实战二:用 CH552 制作 SWD USB 调试器
KEIL 调试器太贵了?自己做一个呗,成本不到 12 块钱。用 CH552 制作一个支持 SWD 协议的 USB 调试器,能调试 STM32、GD32、HK32 等各种 ARM Cortex-M 内核的芯片。
硬件清单
| 元件 | 型号 | 价格 |
|---|---|---|
| 主控芯片 | CH552G | ¥3.5 |
| USB 接口 | Type-C 母座 | ¥1.2 |
| 调试接口 | 2.54mm 排针 | ¥0.5 |
| 晶振 | 12MHz | ¥0.3 |
| 电容 | 10pF × 2 | ¥0.2 |
| 电阻 | 1kΩ × 2 | ¥0.1 |
| LED 指示灯 | 3mm 红色 | ¥0.2 |
| PCB 打样 | 5×5cm | ¥5.0 |
| 总计 | ¥11.0 |
SWD 接口接线
SWD 协议只需要 4 根线:
| 信号 | 说明 | CH552 引脚 |
|---|---|---|
| VCC | 电源 | 5V/3.3V 输出 |
| GND | 地 | GND |
| SWCLK | 时钟 | P1.4 |
| SWDIO | 数据 | P1.5 |
CH552G
┌─────────────────┐
USB D- │1 ○ ○ 40│ VCC
USB D+ │2 ○ ○ 39│ GND
│ ○ ○ │
│ ○ ○ │ P1.4 (SWCLK)
│ ○ ○ │ P1.5 (SWDIO)
└─────────────────┘
⚠️ CH552 是 5V 供电,但 IO 口兼容 3.3V。如果目标芯片是 3.3V 系统,建议通过 LDO(如 AMS1117-3.3)降压后给目标板供电。
SWD 时序实现
#define SWDIO_PIN P1_5
#define SWCLK_PIN P1_4
// SWDIO 方向控制 (0=输出,1=输入)
__sbit __at (0x94) DIRECTION;
void SWD_Init() {
SWCLK_PIN = 0;
SWDIO_PIN = 0;
DIRECTION = 0; // 设置为输出
}
void SWD_WriteBit(UINT8 bit) {
SWDIO_PIN = bit;
SWCLK_PIN = 1;
_nop_(); _nop_();
SWCLK_PIN = 0;
}
UINT8 SWD_ReadBit() {
UINT8 bit;
DIRECTION = 1; // 切换为输入
SWCLK_PIN = 1;
_nop_();
bit = SWDIO_PIN;
SWCLK_PIN = 0;
DIRECTION = 0; // 恢复输出
return bit;
}
void SWD_WriteWord(UINT32 data) {
for (UINT8 i = 0; i < 32; i++)
SWD_WriteBit((data >> i) & 1);
}
UINT32 SWD_ReadWord() {
UINT32 data = 0;
for (UINT8 i = 0; i < 32; i++)
data |= (UINT32)SWD_ReadBit() << i;
return data;
}
SWD 协议握手
连接目标芯片需要先进行握手序列:
void SWD_Sequence_Switch() {
// 发送至少 50 个 1,完成线切换
for (UINT8 i = 0; i < 56; i++)
SWD_WriteBit(1);
}
UINT8 SWD_Connect() {
UINT32 idcode;
SWD_Sequence_Switch();
// 读取 IDCODE 寄存器
SWD_WriteWord(0x9E); // 读 IDCODE 请求
SWD_WriteBit(0); // 奇偶校验
SWD_WriteBit(1); // 停止位
UINT8 ack = SWD_ReadBit() | (SWD_ReadBit() << 1) | (SWD_ReadBit() << 2);
if (ack != 0x01) return 0; // 连接失败
idcode = SWD_ReadWord();
if (idcode == 0 || idcode == 0xFFFFFFFF) return 0;
return 1; // 连接成功
}
上位机测试
固件烧录完成后,可以用 Python 脚本进行验证:
import serial
import struct
class SWDDebugger:
def __init__(self, port, baudrate=115200):
self.serial = serial.Serial(port, baudrate, timeout=1)
def connect(self):
self.serial.write(b'CONNECT\n')
return self.serial.readline().strip() == b'OK'
def read_memory(self, address, size=4):
cmd = struct.pack('<BI', 0x01, address)
self.serial.write(cmd)
response = self.serial.read(size)
return int.from_bytes(response, 'little')
def halt(self):
self.serial.write(b'HALT\n')
return self.serial.readline().strip() == b'OK'
def resume(self):
self.serial.write(b'RESUME\n')
return self.serial.readline().strip() == b'OK'
if __name__ == '__main__':
debugger = SWDDebugger('/dev/ttyUSB0')
if debugger.connect():
print('✅ 连接成功')
idcode = debugger.read_memory(0xE00FFFD0)
print(f'芯片 ID: 0x{idcode:08X}')
debugger.halt()
print('CPU 已暂停')
debugger.resume()
print('CPU 已恢复')
else:
print('❌ 连接失败')
运行结果:
$ python3 swd_debugger.py
✅ 连接成功
芯片 ID: 0x2BA01477
CPU 已暂停
PC: 0x08000234
CPU 已恢复
七、常见问题排查
问题 1:烧录时提示”找不到设备”
原因分析: USB 权限不足、未正确进入 Bootloader 模式、或 USB 线只能充电不能传数据。
解决方案:
- 确认已配置 udev 规则(参考环境搭建章节)
- 确认正确进入 Bootloader 模式(按住 BOOT 键 → 插 USB → 松开 BOOT)
- 更换一根支持数据传输的 USB 线
- 使用
lsusb | grep "4348"确认设备是否被系统识别
问题 2:电脑识别为”未知 USB 设备”(HID 开发)
原因分析: USB 描述符错误、端点配置不正确、或时钟频率问题。
解决方案:
- 检查
DevDesc和CfgDesc的字节长度和内容是否正确 - 确认 VID/PID 设置合理,避免与已知设备冲突
- 使用
lsusb -v查看设备描述符的解析结果 - 确保系统时钟配置正确,USB 通信对时钟精度要求较高
问题 3:SWD 连接失败
原因分析: 接线错误或目标芯片未供电。
解决方案:
- 检查 SWCLK/SWDIO 是否接反
- 确保目标芯片 VCC 有 3.3V 供电
- 检查 GND 是否共地
- 确认调试器固件中 SWD 时序延时足够
问题 4:编译报错”undefined identifier”
原因分析: 头文件包含错误或型号选择错误。
解决方案:
- 确认包含正确的头文件:
#include <ch554.h>(根据实际型号修改) - 检查编译命令中的
-mcc51/-mcc52参数是否与目标芯片匹配 - 确认函数声明和外部变量定义完整
问题 5:设备频繁断开重连
原因分析: 电流不足、代码中存在死循环或看门狗复位、USB 线质量差。
解决方案:
- 使用带外部供电的 USB Hub
- 检查代码中是否有异常复位逻辑
- 更换质量可靠的 USB 数据线
- 在 USB 中断处理函数中添加状态指示,定位断连时机
八、进阶技巧
低功耗设计
如果项目需要电池供电,可以考虑使用 CH55x 的休眠模式:
void enter_sleep() {
PCON |= 0x01; // 设置休眠位
__nop(); __nop();
}
void wake_up() {
// 唤醒后的初始化代码
}
USB 虚拟串口
CH55x 系列最大的优势就是原生支持 USB。通过 CDC 类协议,可以轻松实现虚拟串口功能,方便与上位机通信。完整的 USB 设备代码可以参考沁恒官方提供的示例,GitHub 上也有很多开源项目。
HID 宏键盘扩展
你可以修改 HID 代码,让不同的按钮发送不同的宏命令:
#define BTN_1 P3_0
#define BTN_2 P3_1
#define BTN_3 P3_2
if(BTN_1 == 0) {
SendKeyReport(0x01, 0x04); // Ctrl+A (全选)
}
if(BTN_2 == 0) {
SendKeyReport(0x01, 0x06); // Ctrl+C (复制)
}
if(BTN_3 == 0) {
SendKeyReport(0x01, 0x07); // Ctrl+V (粘贴)
}
这样就做成了一个自定义的宏键盘!
九、总结
CH55x 系列单片机真的是性价比之王。对于预算敏感的项目,或者只是想学习单片机开发的朋友,它都是非常好的选择。
选型要点回顾:
- 入门选 CH552,够用又便宜
- 需要 USB Host 只能选 CH559
- 大程序选 CH558,Flash 够大
- 注意不同型号的定时器数量差异
开发建议:
- 先从官方示例代码开始,逐步修改
- 准备好 USB 转 TTL 模块用于调试
- 逻辑分析仪很有用,几十块钱的就能解决大部分问题
- 多逛 GitHub,有很多开源项目可以参考
用 CH552 制作 SWD 调试器,成本不到 12 块钱,却能实现商业调试器的核心功能。当然,这个方案也有局限性:最高 SWD 时钟约 1MHz(受限于 CH552 主频),不支持 ETM 跟踪。但对于日常开发和调试来说,完全够用了。
希望这篇完全指南对您有所帮助!
相关资源: