资讯动态

RA-Eco-RA4M2-100PIN-V2.0的USB 串口加上一个开源 Shell(MicroShell 移植实录)

发布时间:2026/10/2 2:27:07 来源:尧图企业网站定制
文章目录0. 写在前面1. 环境与目标2. 第一个坑本机 FSP 根本不带外设驱动源码现象根因决策3. 寄存器级 SCI9 驱动115200 8N1关键参数模块时钟sci9_uart.c节选完整见文末目录4. 拉取真正的开源 MicroShell而不是手写拉取方式裸机裁剪 ush_config.hCMake 加 include 路径5. MicroShell 与 SCI9 对接I/O 接口初始化描述符注册命令6. 第二个坑提示符乱码 ▒▒AF▒ ▒▒现象根因读库源码确认修复一行7. 第三个坑输入慢、丢字符输入 help 只回显 h现象根因修复三件套8. 编译与烧录编译关键RASC_EXE_PATH烧录9. 最终目录结构10. 总结在这里插入图片描述参考关键词Renesas RA4M2、FSP、RASC、寄存器级 UART、MicroShell、CH340、嵌入式命令行平台RA-Eco-RA4M2-100PIN-V2.0 开发板 / R7FA4M2AD3CFPCortex-M330. 写在前面调试嵌入式项目时最顺手的往往不是仿真器而是一个能随时敲命令的串口 shell看点状态、点个灯、读个寄存器比反复改代码烧录快得多。本篇文章记录我把一块RA-Eco-RA4M2-100PIN-V2.0开发板的CH340 USB 串口接芯片的 SCI9P109/P110 引脚接上一个真正的开源 shell 组件 —— MicroShellmarcinbor85/microshellMIT License的完整过程。过程中踩了三个真实的坑逐一解决后最终效果[RA4M2 /]$ help Commands: help - show this help led n 0|1 - set LED n(0/1/2) on/off echo text - print text back info - board / shell info reset - reset shell [RA4M2 /]$ led 0 1 led 0 on [RA4M2 /]$ info RA-Eco-RA4M2-100PIN-V2.0 MCU: R7FA4M2AD3CFP (Cortex-M33) Shell: MicroShell (marcinbor85/microshell, MIT) UART: CH340 - SCI9 (P109 TX / P110 RX), 115200 8N1下面按环境 → 第一个坑 → 拉库 → 对接 → 第二/三个坑 → 编译烧录的顺序展开。1. 环境与目标项目内容开发板RA-Eco-RA4M2-100PIN-V2.0MCUR7FA4M2AD3CFPCortex-M33200 MHz PLLICLK 100 MHzFSP 版本6.6.0工具链Renesas arm-llvmclang 22.1.0构建器Ninja~/.renesas/platform/ninja-build/ninja.exe代码生成RASCsc_v2026-07_fsp_v6.6.0/eclipse/rasc.exe串口CH340USB 口 A / 主机 COM3MCU 侧 SCI9P109TXD9、P110RXD9Shell开源 MicroShellmarcinbor85/microshellMIT目标把 CH340 串口接一个 shell支持回显、退格、Tab 补全能注册自己的命令点灯、看板子信息等。为什么选 MicroShell它是纯 C、零依赖、面向裸机的轻量 shell提供 Tab 补全、命令树、VT100 样式且 API 简洁一个read/ 一个write回调 ush_init/ush_service非常契合资源受限的 MCU。2. 第一个坑本机 FSP 根本不带外设驱动源码现象最初我按常规思路在 RASCFSP 配置器里给 SCI9 挂一个uart_on_sci_uart模块生成hal_data.h然后调R_SCI_UART_Open / Write / Read。结果编译直接挂ra_gen/hal_data.h:7:10: fatal error: r_sci_uart.h file not found不止一个文件报错main.c、hal_entry.c、hal_data.c全挂。根因排查后发现一个关键事实本机安装的 FSP 只包含了工具rasc.exe、各种生成器并不包含ra/fsp/src下各外设的驱动源码如r_sci_uart.c / r_uart_api.h。也就是说RASC 能生成引用r_sci_uart.h的hal_data.h但链接阶段找不到驱动实现源码根本不在机器上VSCode 的 RA 扩展也没有把 FSP 源码打包进来。决策放弃 FSP 的uart_on_sci_uart栈改为直接操作 SCI9 寄存器自己写一个最小串口驱动。好处零外部依赖只用到 FSP 的bsp寄存器定义R_SCI9、R_BSP_MODULE_START宏和r_ioport代码量很小便于移植到任何 Renesas RA 系列编译 100% 可控不依赖缺失的驱动文件。顺手把configuration.xml里的uart_on_sci_uart.0模块删掉保留 P109/P110 → SCI9 的引脚复用配置RASC 会重新生成进ra_gen/pin_data.c。3. 寄存器级 SCI9 驱动115200 8N1关键参数SCI9 基地址0x40118900结构体R_SCI0_Type对所有通道共用通过R_SCI9访问定义在R7FA4M2AD.h。PCLKB 50 MHzXTAL 24M → PLL(/3×25)200M → PCLKB/4。异步模式波特率波特率 PCLKB / (16 × (BRR 1))SMR.CKS0、SEMR.ABCS/ABCSE/BGDM0。50e6 / (16 × 27) 115740 Hz→BRR 26误差0.47% 1%合规。模块时钟SCI9 对应MSTPCRB 第 22 位FSP 宏R_BSP_MODULE_START(FSP_IP_SCI, 9)内部会自动处理 PRCR 写保护清掉该位开启时钟。sci9_uart.c节选完整见文末目录#includehal_data.h/* R_SCI9, R_BSP_MODULE_START, FSP_IP_SCI */#includestdint.h#defineSCI9_BRR26U/* PCLKB50MHz 下的 115200 波特率 */voidsci9_init(void){R_BSP_MODULE_START(FSP_IP_SCI,9);/* 开 SCI9 模块时钟 */R_SCI9-SCR0x00U;/* 先禁止收发 */R_SCI9-SMR0x00U;/* 异步 8N1CKS0, CHR0(8位), PE0, STOP0 */R_SCI9-SCMR0xF2U;/* 异步模式固定值 */R_SCI9-SEMR0x00U;/* 波特率除数因子 16 */R_SCI9-BRRSCI9_BRR;/* 波特率 */__NOP();/* 等待至少 1 个总线周期 */R_SCI9-SCR(1U5)|(1U4);/* 使能发送 TE、接收 RE轮询不开中断 */}voidsci9_putc(charc){while(0U(R_SCI9-SSR(1U7)));/* 等 TDRE */R_SCI9-TDR(uint8_t)c;while(0U(R_SCI9-SSR(1U2)));/* 等 TEND整帧发完 */}boolsci9_getc_poll(char*out){if(R_SCI9-SSR(1U6)){/* SSR.RDRF 有数据 */*out(char)R_SCI9-RDR;/* 读 RDR 自动清 RDRF */returntrue;}returnfalse;}这个驱动最初就是上面的裸版本。后面为了解决输入丢字符又加了软件接收环形缓冲和 SysTick 节拍见第 7 节。4. 拉取真正的开源 MicroShell而不是手写这里要特别诚实地说一个插曲第一版我给的是一份MicroShell 风格的手写等价实现并误称无外网所以内置。那其实不是开源组件。后来用户指出后我拉取了真实的 MicroShell 源码替换掉。下面记录的也是真实库的接法。拉取方式构建机直连github.com不通但codeload.github.com可达直接下载整包最可靠比 WebFetch 拼凑准curl-sLhttps://codeload.github.com/marcinbor85/microshell/tar.gz/refs/heads/main\-omicroshell.tar.gztar-xzfmicroshell.tar.gz把src/inc/*.h、src/src/*.c、src/src/commands/*.c、LICENSE直接vendor进工程的src/microshell/逐字节真实源码未改写库本身。裸机裁剪ush_config.hMicroShell 默认带cat/cd/echo/help/ls/pwd/xxd等内置命令且断言用到fprintf/exit。裸机环境没有这些于是新增src/microshell/config/ush_config.h裁剪/* 禁用全部内置命令改用本项目自注册命令 */#defineUSH_CONFIG_ENABLE_COMMAND_CAT0#defineUSH_CONFIG_ENABLE_COMMAND_CD0#defineUSH_CONFIG_ENABLE_COMMAND_ECHO0#defineUSH_CONFIG_ENABLE_COMMAND_HELP0#defineUSH_CONFIG_ENABLE_COMMAND_LS0#defineUSH_CONFIG_ENABLE_COMMAND_PWD0#defineUSH_CONFIG_ENABLE_COMMAND_XXD0#defineUSH_CONFIG_ENABLE_FEATURE_COMMANDS1#defineUSH_CONFIG_ENABLE_FEATURE_AUTOCOMPLETE1/* Tab 补全 */#defineUSH_CONFIG_ENABLE_FEATURE_SHELL_STYLES1/* VT100 样式 *//* 裸机无 fprintf/exit断言改为空操作 */#defineUSH_ASSERT(cond)((void)(cond))CMake 加 include 路径GeneratedSrc.cmakeRASC 自动生成会用GLOB_RECURSE src/*.c自动收录.c所以库文件无需手动登记。但需要把src/microshell和src/microshell/inc加入 include 搜索路径。注意这段要在用户自己的CMakeLists.txt里加不要写进GeneratedSrc.cmakeRASC 重生成会覆盖它# CMakeLists.txt 末尾 target_include_directories(${PROJECT_NAME}.elf PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/microshell ${CMAKE_CURRENT_SOURCE_DIR}/src/microshell/inc )5. MicroShell 与 SCI9 对接MicroShell 的 I/O 模型非常干净用户提供一个ush_io_interface里面有两个回调——read有字符返回 1 并填*ch否则返回 0和write发送 1 字节。这正好对应我们驱动的sci9_getc_poll/sci9_putc。I/O 接口staticintshell_io_read_char(structush_object*self,char*ch){(void)self;returnsci9_getc_poll(ch)?1:0;/* 非阻塞有则 1无则 0 */}staticintshell_io_write_char(structush_object*self,charch){(void)self;sci9_putc(ch);return1;}staticconststructush_io_interfaceg_shell_io{.readshell_io_read_char,.writeshell_io_write_char,};初始化描述符staticconststructush_descriptorg_shell_desc{.iog_shell_io,.input_bufferg_in_buf,.input_buffer_sizesizeof(g_in_buf),.output_bufferg_out_buf,.output_buffer_sizesizeof(g_out_buf),.path_max_length128,.hostnameRA4M2,.execNULL,};注册命令命令是一条ush_file_descriptor数组通过ush_commands_add()挂到全局命名空间与官方 example 的commands_register()等价staticconststructush_file_descriptorg_my_cmds[]{{.namehelp,.descriptionshow help,.execcmd_help_exec},{.nameled,.descriptiontoggle LED,.execcmd_led_exec},{.nameecho,.descriptionecho text,.execcmd_echo_exec},{.nameinfo,.descriptionboard info,.execcmd_info_exec},{.namereset,.descriptionreset shell,.execcmd_reset_exec},};命令回调函数里用 FSP 的R_IOPORT_PinWrite点灯、ush_print回显即可。例如ledstaticvoidcmd_led_exec(structush_object*self,structush_file_descriptorconst*file,intargc,char*argv[]){(void)file;if(argc3){ush_print(self,usage: led 0|1|2 0|1);return;}intidxargv[1][0]-0;inton(argv[2][0]1)?1:0;bsp_io_port_pin_tpin;if(idx0)pinBSP_IO_PORT_00_PIN_02;elseif(idx1)pinBSP_IO_PORT_04_PIN_04;elseif(idx2)pinBSP_IO_PORT_04_PIN_05;else{ush_print(self,led index must be 0/1/2);return;}R_IOPORT_PinWrite(g_ioport_ctrl,pin,on?BSP_IO_LEVEL_HIGH:BSP_IO_LEVEL_LOW);charmsg[24];(void)snprintf(msg,sizeof(msg),led %d %s,idx,on?on:off);ush_print(self,msg);}6. 第二个坑提示符乱码▒▒AF▒ ▒▒现象串口终端上提示符变成了[RA4M2 ▒▒AF▒ ▒▒]$ uShell 0.1.0 [RA4M2 ▒▒AF▒ ▒▒]$横幅uShell 0.1.0是库正常打印的欢迎信息不是乱码但提示符中间那串▒▒AF▒ ▒▒是乱码。根因读库源码确认MicroShell 的提示符格式是[hostname path]$其中path来自self-current_node-pathush_prompt.c。调用链ush_init()内部先把self-root NULL再调ush_reset()打印横幅ush_commands_add()只把命令节点挂到self-commands链表供按名匹配命令不会建立 root 树真正设置self-root、self-current_node并给节点path赋值的是ush_node_mount()官方 example 里fs_mount→root_mount就是干这个的。我只调了ush_commands_add没挂根节点 →root/current_node始终是 NULL → 提示符读current_node-path解引用 NULL读到未初始化内存 → 乱码。修复一行在ush_initush_commands_add之后补一个挂载空根节点ush_init(g_ush,g_shell_desc);ush_commands_add(g_ush,g_my_cmd_node,g_my_cmds,sizeof(g_my_cmds)/sizeof(g_my_cmds[0]));/* 关键挂载根节点建立合法的 root/current_node消除提示符乱码 */ush_node_mount(g_ush,/,g_shell_root,NULL,0);修复后提示符变成干净的[RA4M2 /]$。命令解析不受影响ush_file_find_by_name先遍历self-commands就是ush_commands_add注册的链表按名匹配挂载的根节点仅用于提示符路径显示。7. 第三个坑输入慢、丢字符输入 help 只回显 h现象连续输入help只回显了h后面的elp不见了整体感觉输入很慢。根因最初hal_entry.c的主循环里有3 段阻塞延时R_IOPORT_PinWrite(...);R_BSP_SoftwareDelay(500,BSP_DELAY_UNITS_MILLISECONDS);R_IOPORT_PinWrite(...);R_BSP_SoftwareDelay(500,BSP_DELAY_UNITS_MILLISECONDS);R_IOPORT_PinWrite(...);R_BSP_SoftwareDelay(500,BSP_DELAY_UNITS_MILLISECONDS);uart_shell_poll();共1.5 秒阻塞期间uart_shell_poll()完全不被调用。而 SCI9 接收寄存器只有1 字节115200 波特下每字符约 87µs——阻塞期间新字符写入会把旧字符冲掉overrun所以help只来得及回显第 1 个字符。修复三件套① 主循环改成非阻塞LED 流水用真实毫秒计时每轮都轮询串口staticuint32_tled_t00;staticintled_step0;while(1){uint32_tnowboard_millis();if((now-led_t0)500U){led_t0now;/* 三灯轮流亮 */R_IOPORT_PinWrite(g_ioport_ctrl,BSP_IO_PORT_00_PIN_02,led_step0?BSP_IO_LEVEL_HIGH:BSP_IO_LEVEL_LOW);R_IOPORT_PinWrite(g_ioport_ctrl,BSP_IO_PORT_04_PIN_04,led_step1?BSP_IO_LEVEL_HIGH:BSP_IO_LEVEL_LOW);R_IOPORT_PinWrite(g_ioport_ctrl,BSP_IO_PORT_04_PIN_05,led_step2?BSP_IO_LEVEL_HIGH:BSP_IO_LEVEL_LOW);led_step(led_step1)%3;}uart_shell_poll();/* 每轮都处理串口零延迟 */}② 基于 SysTick 的 1ms 节拍不触发中断纯轮询供计时#defineICLK_HZ100000000UL/* ICLK 100 MHz */voidboard_tick_init(void){SysTick-LOAD(ICLK_HZ/1000UL)-1UL;SysTick-VAL0UL;SysTick-CTRLSysTick_CTRL_CLKSOURCE_Msk|SysTick_CTRL_ENABLE_Msk;}uint32_tboard_millis(void){if(SysTick-CTRLSysTick_CTRL_COUNTFLAG_Msk)g_ms;returng_ms;}③ 软件接收环形缓冲64 字节每轮主循环先把硬件RDR里所有已就绪字节搬进软件缓冲sci9_getc_poll改为从缓冲取字节。即使主循环偶发被占用硬件 1 字节寄存器也不会被新字符冲掉。#defineRX_RING_SZ64Ustaticuint8_tg_rx_ring[RX_RING_SZ];staticuint16_tg_rx_head0,g_rx_tail0;voidsci9_rx_drain(void){while(R_SCI9-SSR(1U6)){/* RDRF 有数据 */uint8_tb(uint8_t)R_SCI9-RDR;uint16_tnext(g_rx_head1U)%RX_RING_SZ;g_rx_ring[g_rx_head]b;g_rx_headnext;}}boolsci9_getc_poll(char*out){if(g_rx_head!g_rx_tail){*out(char)g_rx_ring[g_rx_tail];g_rx_tail(g_rx_tail1U)%RX_RING_SZ;returntrue;}returnfalse;}主循环每轮先sci9_rx_drain()再ush_service()voiduart_shell_poll(void){sci9_rx_drain();/* 硬件字节 - 软件环 */while(ush_service(g_ush)){;}/* 驱动 MicroShell 状态机 */}关于硬件 FIFORA4M2 的 SCI9 其实自带硬件接收 FIFOFCR.FMFRDRL但 FIFO 模式下发送必须改写到FTDRL且TDRE变成TDFE当前无法在板子上实测改错会拖累整个 shell。软件环形缓冲已彻底解决丢字符问题硬件 FIFO 留作后续可选优化。8. 编译与烧录编译关键RASC_EXE_PATHNinja 在重生成build.ninja时会触发 CMake 重配置而Config.cmake要求RASC_EXE_PATH变量否则报错RASC_EXE_PATH variable is not set!。把它缓存进CMakeCache.txt即可只需首次cmake-Bbuild/Debug-S.\-DRASC_EXE_PATHC:/Renesas/RA/sc_v2026-07_fsp_v6.6.0/eclipse/rasc.exe之后直接RASC_EXE_PATHC:/Renesas/RA/sc_v2026-07_fsp_v6.6.0/eclipse/rasc.exe\ninja-Cbuild/Debugcmake 不在 PATH 也没关系build.ninja已记录它上次用的路径ninja 会自动重跑 cmake。编译产出build/Debug/RA4M2_Hello.hex约 41 KB。MicroShell 自身代码有几条-Wsign-conversion警告非 fatal二十九个库源文件全部编译链接通过。烧录用Renesas Flash ProgrammerRFP烧录RA4M2_Hello.hexRFP 会自动识别型号无需手动选串口终端以115200 8N1打开 CH340主机 COM3上电即可看到[RA4M2 /]$提示符。9. 最终目录结构RA4M2_Hello/ ├── CMakeLists.txt # 末尾新增 MicroShell 的 include 路径 ├── configuration.xml # 已移除 uart_on_sci_uart 模块保留 P109/P110-SCI9 引脚 ├── src/ │ ├── hal_entry.c # 非阻塞主循环 每轮 uart_shell_poll() │ ├── sci9_uart.c/.h # 寄存器级 SCI9 驱动 接收环 SysTick 节拍 │ ├── uart_shell.c/.h # MicroShell I/O 接口 命令 初始化 │ └── microshell/ # 开源 MicroShellvendorMIT │ ├── config/ush_config.h # 裸机裁剪配置 │ ├── inc/ # 库头文件 │ ├── src/ # 库实现ush.c / ush_node_mount.c / ... │ └── LICENSE └── build/Debug/RA4M2_Hello.hex10. 总结问题根因解法编译报r_sci_uart.h not found本机 FSP 不含外设驱动源码放弃 FSP UART 栈写寄存器级 SCI9 驱动提示符乱码▒▒AF▒ ▒▒只ush_commands_add没挂根节点current_node为 NULL补ush_node_mount(ush, /, ...)输入慢/丢字符主循环 1.5s 阻塞延时1 字节硬件寄存器 overrun非阻塞主循环 SysTick 节拍 64 字节软件接收环三个坑都和FSP 环境不完整 裸机 shell 的特殊时序有关解决思路可以复用到任何 RA 系列 串口 shell 的场景。寄存器级 SCI9 驱动是通用代码后续要加日志输出、AT 指令解析等都可直接复用sci9_uart.c。参考MicroShell 仓库https://github.com/marcinbor85/microshellMIT LicenseRenesas RA Flexible Software Package (FSP) 文档RA4M2 硬件手册SCI异步串行接口章、MSTP 模块停止控制章

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑