
1. 为什么我从 ARM 切到 RISC-V以及沁恒这套工具链到底香不香先说个有意思的事。我做嵌入式有些年头了早些年项目基本是 ARM Cortex-M 一统天下从 STM32 到 GD32再到 NXP换汤不换药。但这两年 RISC-V 的势头是真的明显尤其是沁恒微的 CH32V 系列价格、性能、外设丰富度都在线而且它的整个开发工具链居然是国产自研的 MounRiver Studio下面我统一叫 MRS这就让我一直想找机会好好折腾一把。这颗芯片我拿到手的第一感觉是它的定位非常“接地气”。CH32V 系列里面有 103、203、305、307 这些型号从入门级到带以太网、USB 高级外设的都有价格却比同规格 ARM 芯片低不少。更关键的是沁恒没有像某些厂家那样只给你一个 SDK 让你自己摸索而是直接提供了一整套 IDE、GCC 工具链、调试器和烧录工具全链路打通这对习惯了 ARM 生态里“A 家用 IDE、B 家编译链、C 家调试器”那种拼凑式开发的人来说简直是清流。但说实话刚上手 MRS 的时候我也踩了不少坑。和 Keil、IAR 这类老牌 IDE 比起来MRS 的界面和工程结构有自己的逻辑如果不搞清楚它的配置细节很容易在编译、下载、调试环节卡壳。尤其是我这次要做的蓝牙移植涉及到串口、中断、低功耗这些模块的协同工程配置如果一开始就乱了后面排查会让你怀疑人生。所以这篇文章我想把这阵子用 MRS 做沁恒 RISC-V 开发的经验完整梳理一遍。核心内容就两块第一是 MounRiver Studio 的工程配置把环境、编译链、调试器这些底子给你打牢第二是蓝牙移植的完整过程从选型到透传实现每一步都给你说明白。不管你是刚从 ARM 转过来还是第一次接触 RISC-V照着走应该都能跑通。顺便说一个很多人关心的问题ARM 和 RISC-V 到底怎么选我的观点是如果项目追求极致的生态成熟度和大量现成代码库ARM 依然稳妥但如果目标是成本敏感、想要自主可控、愿意花一点时间熟悉新工具链的量产产品RISC-V 尤其是沁恒这一套性价比确实很能打。2. MounRiver Studio 工程配置实战从拿到 IDE 到点亮一颗 LED1.1 MRS 的安装与自带 GCC 到底装到了哪里很多第一次用 MRS 的人都会问同一个问题MounRiver Studio 的 GCC 安装到了哪里因为它不像 Keil 那样在安装时报一个明确的工具链路径给你MRS 把编译器藏得比较深但这特殊问题其实特别简单只是入口不太好找。我用的是 Windows 版本默认安装路径一般在C:\MounRiver或者你自定义的目录下。装完之后你在这个目录里会看到一个MounRiver主文件夹里面有几个子目录eclipse、workspace、toolchain之类的。GCC 工具链就在toolchain目录下具体一点是C:\MounRiver\MounRiver\toolchain\RISC-V Embedded GCC\bin在这个bin目录里你会看到riscv-none-embed-gcc.exe、riscv-none-embed-objcopy.exe、riscv-none-embed-gdb.exe这些文件。对这套 GCC 是带riscv-none-embed-前缀的独立工具链它和你在 Linux 上装的主流 riscv64-unknown-elf-gcc 有点区别主要针对嵌入式裸机场景做了优化libc 用的是 newlib和 MRS 的链接脚本配套。提示如果你想在命令行里手动编一下某个.c文件测试编译参数可以直接把这个 bin 目录加进系统 PATH然后执行riscv-none-embed-gcc --version看到版本号就说明工具链没问题。装好 MRS 之后建议先打开一次 IDE让它自动初始化 workspace。第一次启动会慢一点别急着关等右下角的进度条跑完。初始化完成后你会看到欢迎页到此环境就算 ready 了。1.2 新建工程与芯片选型这一步千万别选错MRS 支持沁恒的多种 RISC-V 芯片系列新建工程的流程是File - New - MounRiver Project然后会弹出一个工程配置向导。这里面有几个关键选项我逐个说。第一是选择芯片系列和具体型号。我用的是 CH32V103C8T6 这块板子选型号时会看到一系列 CH32V103 的子型号C8T6、C8U6 等。这个必须和你的物理芯片完全一致因为它决定了默认的启动文件、链接脚本和 Flash/RAM 大小配置。选错型号最典型的后果是烧录后程序跑飞或者调试器报 Cannot access target 这类诡异错误。第二是选择标准外设库或者 LL 库版本。MRS 新建工程时会自动把对应的固件库拷到工程目录下比如Peripheral文件夹。这里我建议不要选空工程而是选带标准外设库的模板因为蓝牙移植要用到 GPIO、USART、中断这些东西自己从零写寄存器定义太折磨人标准外设库能省掉大量低级错误。第三是配置 RISC-V 内核的扩展选项。在工程属性里有一个地方可以设置-march和-mabi参数CH32V103 默认是-marchrv32imac -mabiilp32意思是用 RV32I 基础指令集 M 乘法除法 A 原子操作 C 压缩指令ABI 用 ilp32。这个默认配置一般不用动除非你做的是某些特殊优化。新建完工程后你会发现 MRS 已经自动生成了main.c、system_ch32v10x.c、startup_ch32v10x.s等文件。我强烈建议在动手写代码前先编译一次默认工程确认工具链和工程配置没问题再往下走。这一步能帮你把“环境问题”和“代码问题”分开后期排查会轻松很多。1.3 工程属性里的关键配置项调试器、优化级别、链接脚本当你写了几行代码开始编译时有几个配置项我得特别强调它们几乎决定了你后续会不会被各种奇奇怪怪的问题缠上。第一个是调试器配置。MRS 默认支持 WCH-Link 调试器如果你用的是官方评估板板载一般就是 WCH-Link。在Run - Debug Configurations - 目标工程 - Debugger选项里要确认调试器类型选的是 WCH-Link接口类型选 SWD速率可以先用默认值。这里最常出的问题是WinUSB 驱动没装好导致调试器连不上。解决办法是去 MRS 安装目录下找驱动或者用 Zadig 把 WCH-Link 的驱动切换到 WinUSB然后再试。第二个是编译优化级别。新建工程时默认可能是-O0适合调试。但工程发布时建议把优化调到-Os或者-O2因为 CH32V103 的 Flash 只有 64KB如果不优化稍微上点功能就爆 Flash。切换位置在Project Properties - C/C Build - Settings - GNU RISC-V Cross C Compiler - Optimization。这里有个经验之谈优化级别调高后如果代码里有未初始化的局部变量、未声明 volatile 的循环标志位很容易出现调试正常发布乱跑的诡异问题所以发布前一定要做一次完整的功能回归。第三个是链接脚本。MRS 会把.ld文件放在工程里比如Ld/CH32V103C8T6.ld。如果你需要自定义 Flash/RAM 分配比如把一段数据放在固定地址做 OTA就得自己改这个文件。不过对常规项目默认脚本就够用不建议新手乱动。配置到这里你已经可以编译一个空工程然后接上 WCH-Link 点下载看到板载 LED 闪烁或者在线调试断点生效了。工程配置这一步就算真正通关。3. 蓝牙移植前必须搞明白的几件事方案选型、协议栈与硬件连接1.1 先说大局你想要的“蓝牙移植”到底是哪条路线“蓝牙移植”这四个字在嵌入式圈子里其实有两种截然不同的意思。一种是在 MCU 上直接跑蓝牙协议栈把 BLE 协议栈集成进你的工程MCU 自己就是蓝牙主控常见于 Nordic nRF52 系列、泰凌微 TLSR 系列这些单芯片方案另一种是 MCU 通过串口和一颗蓝牙透传模组通信MCU 本身只做逻辑控制蓝牙协议栈在模组里跑你用 AT 指令去控制它。针对沁恒微的情况两条路线都可以走但实现难度和适用场景完全不同。先说路线一沁恒也有一些自带蓝牙功能的 RISC-V 芯片比如 CH582、CH592 这些它们内置了 BLE 协议栈SDK 里直接提供了蓝牙例程你可以在 MRS 里打开对应的工程来跑。这个路线的优点是集成度高、成本低、功耗好控制缺点是协议栈封装程度较高如果你想做比较特殊的蓝牙行为比如自定义 GATT 服务或者复杂的配对逻辑需要花时间读透协议栈源码。再说路线二用 CH32V103/CH32V307 这类不带蓝牙的通用 MCU 去接一颗单独的 BLE 透传模组。这种做法是我这篇文章主要想讲的因为它的“移植”属性最强——你的 MCU 主控代码基本不用大改只要把原来的串口通信目标从有线改成无线就行。蓝牙模组选型也比较灵活市面上一堆兼容 AT 指令的透传模组可选。1.2 蓝牙模组选型别只看价格这四个参数必须盯紧很多新手选蓝牙模组就盯着价格和尺寸结果做出来的产品连接不稳定、功耗高、传输速率上不去。以我做透传的经验选型至少要看这四点。第一是蓝牙版本和协议支持。现在主流是 BLE 4.2 或者 BLE 5.0BLE 5.0 在速率、广播间隔、连接稳定性上都有优势。如果你的产品是手机 APP 直接连尽量选 BLE 5.0 的模组兼容性好。第二是串口波特率。模组和 MCU 之间走的是 UART两边波特率必须一致。常见的有 9600、115200。有些模组默认波特率是 9600但我的经验是如果数据量大比如要传传感器波形或者音频流建议调到 115200否则 UART 会成为瓶颈。注意AT 指令配置完波特率后模组会保存设置下次上电就用新波特率调试时千万别忘了这件事。第三是透传模式的支持方式。好的透传模组会在 UART 收到数据后自动通过 BLE 发送出去不需要额外的控制指令但这个“自动”之间的延时各不相同。有的模组有 20ms 的打包延迟实时性差一些。如果你要做实时控制类应用这个参数很关键。第四是供电电压和电流。很多 BLE 模组在广播状态下峰值电流能到几十毫安如果 MCU 板子用的是 LDO 供电要确认 LDO 的带载能力够不够否则会出现蓝牙一连上就复位的现象。注意如果你用的是沁恒自家的蓝牙模组比如 WCH 的 BLE 系列MRS 的例程包里有对应的 AT 指令 demo可以直接参考。用第三方模组的话先确认模组厂家是否提供 MounRiver 能用的示例没有的话只要串口通信没问题AT 指令部分自己写也不难。1.3 硬件连接与电平匹配串口通信最容易翻车的环节硬件连接这块看着简单但我被坑过太多次了。CH32V103 的 UART 引脚电平是 3.3V大部分蓝牙模组也是 3.3V 电平理论上可以直连。但如果你买的模组模块上有板载电平转换芯片或者你用的是 5V 供电的 Arduino 兼容版本电平就可能不匹配。我的建议是第一查清楚模组的数据手册确认 IO 电平范围第二如果电平不匹配不要直接分压最好用双向电平转换模块第三TX 和 RX 要交叉连接也就是说 MCU 的 TX 接模组的 RXMCU 的 RX 接模组的 TX这个交叉接反的问题每隔几天就会有人在群里问一次。除了 UART 两线蓝牙模组的 EN 使能脚、STATE 状态脚也要接出来。EN 脚可以挂在 MCU 的 GPIO 上用来控制模组进入低功耗模式或者复位模组STATE 脚可以告诉 MCU 当前蓝牙是否已连接这个信号对做连接状态指示非常有用。接好线之后我建议不要立刻进 MCU 编程先用 USB 转 TTL 模块单独测一下模组发个 AT 指令看看返回是否正常。这样可以把“模组问题”和“MCU 代码问题”彻底隔离开后面出 bug 时你能直接排除一半变量。4. 从零到一实现蓝牙透传完整实操记录1.1 工程搭建在 MRS 里把 UART、GPIO 和中断先铺好现在进入正题把蓝牙透传跑起来。我的演示平台是 CH32V103C8T6 一颗 BLE 5.0 透传模组。CH32V103 有多个 USART我用的是 USART1引脚 PA9TX、PA10RX。代码基于 MRS 自带的标准外设库。第一步在 main.c 里先初始化 GPIO。USART1 的 TX 是推挽复用输出RX 是浮空输入这个不要搞反。我见过有人把 RX 也配成推挽复用结果串口完全收不到数据查了半天才发现是引脚模式错了。void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); }第二步配置 USART1波特率设成 1152008 数据位、1 停止位、无校验。这里特别提醒一下MRS 的库函数里USART_Init需要先获取 PCLK 时钟频率CH32V103 系统时钟默认是 72MHz如果你改过系统主频必须用RCC_GetClocksFreq获取实际值再传给初始化函数否则波特率会算错。void USART_Config(void) { USART_InitTypeDef USART_InitStructure {0}; USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); NVIC_EnableIRQ(USART1_IRQn); USART_Cmd(USART1, ENABLE); }这里我把接收中断打开了。蓝牙透传的本质就是“收到蓝牙发来的数据就转发给 MCUMCU 处理完再通过串口发回去”所以中断驱动的 UART 接收是必须的。轮询方式在数据量小的时候也能用但一旦数据帧一多主循环里轮询容易丢字节。第三步写中断服务函数。CH32V103 的 USART1 中断入口在startup文件里已经定义好叫USART1_IRQHandler你在 main.c 里实现这个函数就行不需要额外声明。中断里做的事很简单如果 RXNE 标志位置位把数据读出来放到一个环形缓冲区。#define RX_BUFF_SIZE 256 volatile uint8_t rx_buff[RX_BUFF_SIZE]; volatile uint16_t rx_head 0; volatile uint16_t rx_tail 0; void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t data USART_ReceiveData(USART1); rx_buff[rx_head] data; rx_head (rx_head 1) % RX_BUFF_SIZE; } }环形的写法是老生常谈了但确实实用。缓冲区大小 256 字节如果你的蓝牙数据包比较长可以加一倍代价只是多占 256 字节 RAM对 CH32V103 的 20KB RAM 来说完全可接受。1.2 主循环里的数据转发逻辑把有线串口变无线有了环形缓冲区和中断接收之后主循环的逻辑就非常清爽了。我做的事情是如果缓冲区里有数据就从缓冲区取出一个字节再通过 USART1 的轮询发送发出去。int main(void) { Delay_Init(); GPIO_Config(); USART_Config(); while (1) { if (rx_head ! rx_tail) { uint8_t ch rx_buff[rx_tail]; rx_tail (rx_tail 1) % RX_BUFF_SIZE; USART_SendData(USART1, ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } } }这段代码看着简单但里面藏着两个容易被忽略的点。第一发送时一定要等 TXE 标志位置位再发下一个字节否则你会看到数据丢字节或者乱码。第二中断里往缓冲区写主循环里从缓冲区读这种单生产者单消费者的场景不需要加锁但务必要把rx_head、rx_tail声明成 volatile否则编译器优化后可能会操作寄存器里的缓存值导致判断条件永远不成立。实际上做完这一步你的硬件无线透传已经通了手机装上任意一个 BLE 串口助手 APP连上模组发一长串字符给 MCUMCU 再从同一串口原样返回。这样一个基础环回测试能快速验证 GPIO、UART、中断、蓝牙模组四个环节是否全部正常。1.3 加一点实用的东西AT 指令控制模组参数与状态指示透传能跑通还只是第一步。实际项目里通常需要在设备端动态配置蓝牙模组的参数比如改蓝牙广播名、设置配对 PIN 码、查询连接状态。这些操作都是通过模组的 AT 指令集完成的。AT 指令本质上也是串口数据区别在于模组会把收到的数据当作指令解析而不是透传出去。所以你要在代码里区分“透传模式”和“AT 指令模式”。大部分透传模组有一个AT/EN引脚拉低或拉高就能在两种模式之间切换具体极性看模组说明书。我的一般做法是上电时先把 EN 引脚拉成 AT 模式发一串初始化 AT 指令比如设置波特率、设置广播间隔、恢复出厂设置然后再把 EN 切回透传模式。这样确保模组每次上电都处于预期状态。void BT_Init(void) { GPIO_WriteBit(GPIOB, GPIO_Pin_0, Bit_SET); // EN - AT mode Delay_Ms(50); USART_SendString(ATNAMEMyDevice\r\n); Delay_Ms(200); USART_SendString(ATBAUD115200\r\n); Delay_Ms(200); USART_SendString(ATRESET\r\n); Delay_Ms(500); GPIO_WriteBit(GPIOB, GPIO_Pin_0, Bit_RESET); // EN - transparent mode }这里有个非常容易踩的坑很多模组在 AT 模式下会对输入做回显也就是说你发的 AT 指令字符会被模组原样返回给你同时模组的 TX 也会接到 MCU 的 RX。如果你的 MCU 程序这时候正在按透传业务处理收到的数据就会把 AT 回显当成业务数据来处理。解决办法是在 AT 模式下屏蔽业务逻辑或者用 STATE 引脚判断当前是透传还是 AT 模式。1.4 编译、烧录与在线调试排雷指南编译烧录这一步MRS 和 Keil 体验有点不一样。MRS 默认按 F7 编译然后点虫子图标进入调试。烧录有两种方式直接点调试模式IDE 会先把固件下载到 Flash再停在 main 函数入口如果你只想下载不调试可以点 Run 旁边的下载按钮。调试器选 WCH-Link 后第一次连接可能需要装驱动。如果你在设备管理器里看到 WCH-Link 显示黄色感叹号说明驱动没装好去 MRS 安装目录的Driver文件夹下把 WCH-Link 驱动装上就行。在线调试时我建议开几个 Watch 窗口把rx_head、rx_tail加进去这样能很直观地看到数据有没有进缓冲区中断有没有触发。如果发现 rx_head 一直不变说明中断没进来优先查 NVIC 配置和 GPIO 复用时钟有没有打开。提示调试模式下如果不小心把程序跑死卡在某个中断里循环出不来可以先拔掉调试器按住板子上的复位键再重新上电让 MCU 进入出厂默认状态再重新连调试器。这个招数救了我好多次。5. 我踩过的坑和排查技巧实录MRS 与蓝牙移植避坑速查1.1 编译器路径与工具链相关GCC 找不到头文件怎么办MRS 自带 GCC 的好处是开箱即用坏处是如果你把工程拷贝到另一台电脑上或者 IDE 安装目录变了工程里的编译器路径可能失效。最常见的现象是编译报错说找不到stdio.h或者找不到ch32v10x.h。排查思路是这样的第一确认 MRS 安装路径没有中文和空格第二检查工程属性里C/C Build - Settings - Toolchains的路径是否指向当前电脑的 MRS 安装目录第三检查工程属性里的Include Paths是否包含了固件库的Core和Peripheral/inc目录。如果你是从网上下载的别人写的工程尤其要注意那个工程可能是在别人电脑上配置的里面可能写死了绝对路径。这种时候最好的办法不是去改路径而是新建一个 MRS 标准工程把你需要的源文件手动加进去。1.2 编译报错的经典案例链接脚本不匹配与内存溢出我刚开始在 CH32V103 上跑蓝牙透传的模板代码时遇到过region FLASH overflowed的报错。当时纳闷代码量不大怎么会 Flash 溢出后来发现是链接脚本选错了工程建立时芯片型号选的是 CH32V203链接脚本按 256KB Flash 分配但实际芯片只有 64KB编译出的固件超过了物理容量。解决方法是回到Project Properties - C/C Build - Settings - Linker看看-T参数指向的是哪个.ld文件确保它和实际芯片匹配。如果是你自己改过链接脚本先对比一下脚本里FLASH (rx) : ORIGIN 0x08000000, LENGTH 64K这段定义LENGTH 一定要和物理 Flash 大小一致。内存溢出还有一种情况是函数内定义了大数组。比如在中断里临时开了个uint8_t data[1024]这在栈上分配会瞬间把栈顶打穿。CH32V103 的默认栈大小在启动文件里定义一般是 2KB如果你用了大缓冲区建议把缓冲区定义成全局变量或者把栈调大。1.3 蓝牙连接不稳定从硬件到软件逐层排查蓝牙连接不稳定是透传方案里最常见的投诉。我遇到过几类典型现象写出来给你做个对照检查表。第一种现象手机能搜到设备但连接瞬间失败。这种大概率是广播参数配置太激进或者模组供电电流不够。先看供电用万用表测一下模组 VCC 在广播瞬间的电压如果跌到 3V 以下基本可以断定是供电不足换个大电流 LDO 或者加个 100uF 左右的电容。第二种现象连接后一段时间自动断开。这个优先检查模组的广播超时和连接超时参数有些模组默认连接超时是 5 秒如果手机和模组之间有短暂信号遮挡就会断。另外检查你的 MCU 程序里有没有主动往 UART 发异常数据比如上电瞬间引脚电平不确定产生了一堆乱码模组可能把乱码解析成 AT 指令导致异常复位。第三种现象数据传输偶尔丢字节。这种和蓝牙关系不大多半是 UART 波特率误差过大。CH32V103 和其他芯片不同它的 UART 波特率是从系统时钟分频得到的如果你的系统时钟是内部 HSI 而不是外部晶振频率精度不够会导致串口偶发丢字节。方案是改用外部晶振或者适当降低波特率。1.4 关于“工程发布”的经验之谈从 Debug 到 Release 的注意事项有一个热搜词叫“工程发布时如何配置数据库”我猜问的人可能是做上位机或者服务端开发的在嵌入式这边对应的概念其实是“发布版本的工程配置”。在我做嵌入式工程发布时有几点必须检查。第一编译优化级别从-O0改到-Os因为发布版的 Flash 占用必须尽量小第二关闭调试相关输出尤其是一些 SEGGER RTT、串口打印日志的宏定义如果开启会明显拖慢程序运行速度第三确认所有assert_param类的运行检查可以保留但不要影响实时性必要时在发布版里关掉第四一定要把生成的.hex或.bin文件归档同时记录对应的 GCC 版本、固件库版本、编译时间这些东西在后续量产和问题追溯时是关键信息。另外还有一个和“数据库”沾边的点如果你的产品需要保存蓝牙配对信息或者校准参数到 Flash建议在代码里做一个小型的 KV 存储区。沁恒的标准外设库里没有专门的 Flash 存储库你可以直接调用内部 Flash 的读写函数把参数放在固定地址。这里要说一下写 Flash 之前必须先擦除整个扇区而且操作期间最好不要进入中断否则可能出现擦写失败。6. 顺手再分享几个 MRS 提升效率的操作细节有一类小技巧单个看起来不起眼但组合起来能显著提高开发效率。第一个是 MRS 的代码模板功能在Window - Preferences - C/C - Editor - Templates里可以自定义快捷代码块比如main自动生成 GPIO 初始化框架非常省时间。第二个是查看寄存器状态。MRS 调试时 Peripherals 窗口非常有用可以实时查看每个外设的寄存器值包括 UART 的数据寄存器、状态寄存器、GPIO 的配置寄存器。排查问题时不用再去代码里翻寄存器定义直接在窗口里看 BIT 位的状态就一目了然了。第三个是编译后自动生成 Hex 文件。在Project Properties - C/C Build - Settings - Build Steps - Post-build steps里加上一句${cross_prefix}objcopy -O ihex ${BuildArtifactFileName} ${BuildArtifactFileBaseName}.hex之后每次编译都会自动生成 hex方便烧录和归档。第四个是关于 MRS 调试器的一个冷知识如果你用 WCH-Link 调试 CH32V103遇到“连接超时”或者“无效设备”的报错先试着重插一次 USB 线然后确认调试器是否进入了正确的目标芯片模式。WCH-Link 支持多种芯片系列有时上一次调试的是 307现在换个 103 芯片需要重新在 MRS 的调试配置里选一次目标芯片。最后再说一个我个人的体会RISC-V 的生态确实比 ARM 年轻资料相对分散出了问题有时候需要在官方论坛和 QQ 群里翻半天。但只要先把 MRS 的工程配置吃透把编译链、下载器、调试器这些底层的东西搞明白后面的开发体验和 ARM 其实差别不大。沁恒这条产品线的迭代速度也快尤其蓝牙这块SDK 和例程一直在更新用新版本 SDK 时注意看更新日志有时能直接省掉你自己造轮子的时间。如果你也是从 ARM 转过来想试试 RISC-V我的建议是别急着追求复杂功能先用一块 CH32V103 的板子把 GPIO、USART、中断、定时器这几个基础外设跑一遍再结合本文的蓝牙透传例子把它串成一个完整的小项目。这套流程走下来你对 MRS 的工程结构、RISC-V 的启动流程、沁恒的固件库风格都会有一个清晰的把握后续再上 CH32V203、CH32V307 这些更高性能的芯片基本就是顺手的事。