|
CH55x 系列单片机完全指南:从选型到 USB 设备开发实战

CH55x 系列单片机完全指南:从选型到 USB 设备开发实战

大家好,我是 MakerOnsite 的老朋友。

相信很多做嵌入式开发的朋友都遇到过这个问题:项目要选型了,CH551、CH552、CH554、CH558、CH559……这一堆型号到底该怎么选?选完之后怎么搭建开发环境?怎么写 USB 设备程序?怎么做调试器?

今天这篇文章,我会把这几年的踩坑经验一次性整理出来,从选型到实战,帮你全面掌握 CH55x 系列。准备好了吗?我们开始。

一、CH55x 系列型号对比

先上一张核心参数对比表,这是我最常用的选型参考:

型号FlashRAMUSB定时器ADC参考价格
CH55110KB256BDevice28 位¥1.5
CH55214KB384BDevice28 位¥2.0
CH55414KB384BDevice310 位¥2.5
CH55864KB4KBDevice410 位¥5.0
CH55964KB4KBHost+Device410 位¥6.5

选型建议:

  1. 入门学习/简单项目:CH552 足够,性价比高,不到一杯奶茶钱
  2. 需要更多定时器:CH554 多一个定时器,适合 PWM 控制场景
  3. 大程序/复杂逻辑:CH558 拥有 64KB Flash,空间充裕
  4. 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
LED5mm 红色¥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
    }
}

原理解析:

这段代码做了三件事:

  1. 配置系统时钟为 24MHz,确保芯片运行在正确频率
  2. 将 P1.0 引脚设为推挽输出模式,用于驱动 LED
  3. 在主循环中交替输出高低电平,实现 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 设备需要以下描述符:

  1. 设备描述符:声明 VID/PID、设备类型等基本信息
  2. 配置描述符:声明设备的供电方式和接口数量
  3. 接口描述符:声明接口类型(HID 类为 0x03)
  4. HID 描述符:声明报告描述符的长度和版本
  5. 报告描述符:定义数据的格式和含义,是 HID 设备的灵魂
  6. 端点描述符:声明中断端点的地址、传输方式和包大小

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 输出
GNDGND
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 线只能充电不能传数据。

解决方案:

  1. 确认已配置 udev 规则(参考环境搭建章节)
  2. 确认正确进入 Bootloader 模式(按住 BOOT 键 → 插 USB → 松开 BOOT)
  3. 更换一根支持数据传输的 USB 线
  4. 使用 lsusb | grep "4348" 确认设备是否被系统识别

问题 2:电脑识别为”未知 USB 设备”(HID 开发)

原因分析: USB 描述符错误、端点配置不正确、或时钟频率问题。

解决方案:

  1. 检查 DevDescCfgDesc 的字节长度和内容是否正确
  2. 确认 VID/PID 设置合理,避免与已知设备冲突
  3. 使用 lsusb -v 查看设备描述符的解析结果
  4. 确保系统时钟配置正确,USB 通信对时钟精度要求较高

问题 3:SWD 连接失败

原因分析: 接线错误或目标芯片未供电。

解决方案:

  1. 检查 SWCLK/SWDIO 是否接反
  2. 确保目标芯片 VCC 有 3.3V 供电
  3. 检查 GND 是否共地
  4. 确认调试器固件中 SWD 时序延时足够

问题 4:编译报错”undefined identifier”

原因分析: 头文件包含错误或型号选择错误。

解决方案:

  1. 确认包含正确的头文件:#include <ch554.h>(根据实际型号修改)
  2. 检查编译命令中的 -mcc51/-mcc52 参数是否与目标芯片匹配
  3. 确认函数声明和外部变量定义完整

问题 5:设备频繁断开重连

原因分析: 电流不足、代码中存在死循环或看门狗复位、USB 线质量差。

解决方案:

  1. 使用带外部供电的 USB Hub
  2. 检查代码中是否有异常复位逻辑
  3. 更换质量可靠的 USB 数据线
  4. 在 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 跟踪。但对于日常开发和调试来说,完全够用了。

希望这篇完全指南对您有所帮助!


相关资源: