项目介绍
本项目选用来自NXP的板卡(FRDM-MCXA346)连接RGB灯,设计一个简易shell,通过串口与用户进行交互,并根据用户输入的命令控制板载 RGB LED 的颜色与亮度。
硬件介绍

FRDM-MCXA346板卡基于MCX A346微控制器,其采用Arm Cortex-M33内核,最高运行频率达180MHz,内置高达1MB的闪存和256KB RAM(其中包含8KB纠错码ECC),并集成了乘法累加单元(MAU)与SmartDMA,支持控制器局域网灵活数据速率(CAN-FD)、低功耗通用异步收发器(LPUART)、低功耗串行外设接口(LPSPI)、低功耗内部集成电路(LPI2C)等多种通信接口,同时配备DMA控制器和低压差稳压器(LDO)。板上设有一颗用户RGB LED(D1)以及电源/MCU-Link状态指示灯;板载MCU-Link调试器支持CMSIS-DAP,并提供虚拟串口(VCOM)功能,默认连接至LPUART2以实现打印调试。该硬件适用于工业自动化、电机控制以及IoT边缘计算领域,并通过兼容Arduino®的接头提供了灵活的扩展选项。
方案设计
整体方案设计分为四个核心模块:
- 用户接口,包括uart接收和发送
- 命令行交互模块 (LPUART)
- 通信接口:使用低功耗通用异步收发器作为与上位机的通信接口。配置为标准波特率,8位数据位,无校验,1位停止位。
- 数据接收:采用中断驱动的接收方式。当 LPUART 接收到新数据时,触发中断服务程序。中断服务程序的任务是尽快将接收到的字节存入一个环形缓冲区,然后退出中断。这种设计避免了在中断中进行耗时操作,保证了系统的实时响应能力。
- 数据处理:在主循环中,轮询检查环形缓冲区是否有新数据。若有,则取出数据进行处理,实现中断与主任务的解耦。
- 用户界面:程序启动后,通过 LPUART 发送欢迎信息和命令提示符
ysh > $,引导用户输入。用户的输入字符会被实时回显到终端,并支持退格键删除,提供基本的编辑功能。
- 命令解析与执行模块
- 命令构建:主循环从环形缓冲区中取出字符,并将其逐个存入一个命令行缓冲区(
cmdLine)。当检测到回车符时,表示一条命令输入完毕。 - 命令解析:使用标准 C 库函数
strtok()对完整的命令行字符串进行分割,以空格为分隔符,将其解析为命令名(argv[0])和参数列表(argv[1],argv[2], ...)。 - 命令分发:设计一个命令表,它是一个结构体数组,每个结构体包含命令名字符串和一个指向对应处理函数的指针。遍历此表,匹配命令名,找到后调用相应的函数指针来执行命令。
- 命令构建:主循环从环形缓冲区中取出字符,并将其逐个存入一个命令行缓冲区(
- LED 控制模块 (CTIMER PWM)
- 硬件驱动:使用标准计数器/定时器(CTIMER)外设来生成脉宽调制(PWM)信号。PWM 是控制 LED 亮度的理想方式,通过改变信号的占空比(Duty Cycle)即可精确调节 LED 的平均功率,从而控制其亮度。
- 通道映射:将 CTIMER 的三个匹配输出通道(Match Output)分别配置到控制 RGB LED 的三个引脚上。通过设置不同通道的 PWM 占空比,可以混合出各种颜色。
- 命令实现:
led命令处理函数cmd_led负责解析led命令的参数(如red on,green off,blue brightness 50),并根据参数调用 CTIMER 驱动库函数来更新对应通道的 PWM 占空比,从而实现对 LED 颜色和亮度的实时控制。
硬件连接
本项目的核心控制对象,其三个颜色通道分别连接到 MCXA346 的特定 GPIO 引脚。通过 MCUXpresso Pins Tool 配置工具,这些引脚已被复用为 CTIMER 的 PWM 输出功能。
- 引脚连接:
- 红色 (RED) LED: 连接到 PIO3_18,对应 CTIMER 的 Match 0 输出 (
CTIMER_MAT0_OUT)。 - 绿色 (GREEN) LED: 连接到 PIO3_19,对应 CTIMER 的 Match 1 输出 (
CTIMER_MAT1_OUT)。 - 蓝色 (BLUE) LED: 连接到 PIO3_21,对应 CTIMER 的 Match 3 输出 (
CTIMER_MAT3_OUT)。
- 红色 (RED) LED: 连接到 PIO3_18,对应 CTIMER 的 Match 0 输出 (
设计思路
本项目成功实现了一个功能虽简但结构完整的嵌入式命令行 Shell。它通过串口接收用户指令,能够实时解析并执行,最终实现了对 MCXA346 开发板上 RGB LED 的灵活控制。
核心设计思路如下:
- 事件驱动与轮询结合:采用中断来处理异步的串口数据接收事件,确保了数据输入的即时性和无丢失。而命令的解析和执行则放在主循环中轮询标志位(
cmdPending)来完成。这种“中断收数据,主循环处理”的模式是嵌入式系统中经典的协作方式,它既保证了对外部事件的快速响应,又避免了在中断服务程序中执行复杂逻辑,维持了系统的稳定性。 - 模块化与可扩展性:代码结构清晰,将不同的功能(串口通信、命令处理、硬件控制)封装在各自的函数中。特别是命令的实现采用了“命令表驱动”的设计,使得添加新功能变得异常简单。例如,若要增加一个读取板载温度传感器的
temp命令,只需编写cmd_temp函数并将其注册到cmdTable数组中,整个 Shell 框架无需任何改动。 - 资源的高效利用:
- 环形缓冲区:作为中断和主循环之间的数据交换媒介,环形缓冲区有效地解决了生产者(ISR)和消费者(主循环)速度不匹配的问题,防止了数据丢失,是实现带缓冲输入的关键。
- CTIMER PWM:利用硬件定时器生成 PWM 信号来控制 LED 亮度,相比于使用软件延时(会阻塞 CPU),这种方式几乎不占用 CPU 时间,使得 CPU 可以去执行其他任务,如处理更复杂的命令。
- 友好的用户交互:Shell 提供了命令提示符、字符回显和退格删除等基本功能,为用户提供了一个类似于标准终端的操作体验,增强了可用性。
核心代码

1. main 函数负责完成所有硬件的初始化工作,然后进入一个无限循环。循环体内主要做两件事:
- 不断检查
rxIndex和lastRxIndex是否相等,如果不等,说明环形缓冲区中有新字符,于是取出并交给process_char处理。 - 检查
cmdPending标志位。如果为 1,说明process_char已接收到一条完整命令,于是清零标志位,调用execute_cmd执行,并打印新的命令提示符。
int main(void)
{
// ...硬件与外设初始化...
BOARD_InitHardware();
CTIMER_Init(...); // 初始化CTIMER用于PWM
LPUART_Init(...); // 初始化LPUART
send_string((char *)g_tipString);
send_string(PROMPT);
// 使能LPUART接收中断
LPUART_EnableInterrupts(DEMO_LPUART, kLPUART_RxDataRegFullInterruptEnable);
EnableIRQ(DEMO_LPUART_IRQn);
while (1)
{
// 检查环形缓冲区是否有新数据
if (rxIndex != lastRxIndex)
{
uint8_t c = demoRingBuffer[lastRxIndex];
lastRxIndex = (lastRxIndex + 1) % DEMO_RING_BUFFER_SIZE;
process_char(c); // 处理字符
}
// 检查是否有待执行的命令
if (cmdPending)
{
cmdPending = 0;
execute_cmd(cmdBuffer); // 执行命令
send_string(PROMPT); // 打印下一次的提示符
}
}
}
2.UART 中断服务程序
一旦硬件触发中断,就读取一个字节的数据,然后将其放入 demoRingBuffer 的 rxIndex 位置,并更新 rxIndex 指针。代码中的 txIndex 在此应用中未被用作发送索引,而是隐含地与主循环中的 lastRxIndex 构成了读写指针,用于判断缓冲区是否已满。
void DEMO_LPUART_IRQHandler(void)
{
uint8_t data;
if ((kLPUART_RxDataRegFullFlag) & LPUART_GetStatusFlags(DEMO_LPUART))
{
data = LPUART_ReadByte(DEMO_LPUART);
// 环形缓冲区不满则存入
if (((rxIndex + 1) % DEMO_RING_BUFFER_SIZE) != txIndex) // txIndex在这里实际是读指针lastRxIndex
{
demoRingBuffer[rxIndex] = data;
rxIndex = (rxIndex + 1) % DEMO_RING_BUFFER_SIZE;
}
}
SDK_ISR_EXIT_BARRIER;
}
3.命令解析与分发 execute_cmd
接收一个完整的命令行字符串,使用 strtok 将其分解为独立的单词,去 cmdTable 中查找。如果找到匹配项,就通过函数指针调用对应的处理函数,并将 argc 和 argv 传递过去,供其进一步处理参数。
static void execute_cmd(char *cmdLine)
{
char *argv[8];
int argc = 0;
char *token;
// 使用strtok以空格分割命令行
token = strtok(cmdLine, " ");
while (token != NULL && argc < 8)
{
argv[argc++] = token;
token = strtok(NULL, " ");
}
if (argc == 0) return;
// 遍历命令表查找匹配项
for (int i = 0; i < CMD_COUNT; i++)
{
if (strcmp(argv[0], cmdTable[i].name) == 0)
{
if (cmdTable[i].func != NULL)
cmdTable[i].func(argc, argv); // 调用命令处理函数
return;
}
}
send_string("Unknown command: ...\r\n"); // 未找到命令
}
3.cmd_led 命令实现
根据第二个参数判断是控制颜色还是亮度。如果是颜色("red", "green", "blue"),则根据第三个参数 (argv[2]) "on" 或 "off" 来设置 PWM 占空比。on 设置为 50% 亮度,off 设置为 0% 亮度。如果是 "brightness",则将第三个参数用 atoi 转换为数字,并计算出对应的 PWM 占空比值。最后,将计算出的新 PWM 参数应用到 CTIMER 硬件,LED 状态立刻发生改变。
static void cmd_led(int argc, char **argv)
{
// ... 变量定义 ...
if (argc == 3) // 严格要求3个参数, e.g., "led red on"
{
char *target = argv[1];
char *action = argv[2];
uint8_t CTIMER_MAT_OUT;
bool is_color_cmd = true;
// 确定要控制哪个颜色的LED
if(strcmp(target, "red") == 0) CTIMER_MAT_OUT = CTIMER_MAT0_OUT;
else if(strcmp(target, "green") == 0) CTIMER_MAT_OUT = CTIMER_MAT1_OUT;
else if(strcmp(target, "blue") == 0) CTIMER_MAT_OUT = CTIMER_MAT3_OUT;
else is_color_cmd = false;
if (is_color_cmd) {
// 处理 on/off
if(strcmp(action, "on") == 0) {
CTIMER_GetPwmPeriodValue(20000, 50, timerClock); // 50%亮度
} else if(strcmp(action, "off") == 0) {
CTIMER_GetPwmPeriodValue(20000, 100, timerClock); // 0%亮度 (占空比100%表示高电平时间为0,LED灭)
} else {
send_string("Usage: led <color> <on|off>\r\n");
return;
}
CTIMER_SetupPwmPeriod(CTIMER, CTIMER_MAT_PWM_PERIOD_CHANNEL, CTIMER_MAT_OUT, g_pwmPeriod, g_pulsePeriod, false);
}
// 处理 brightness
else if(strcmp(target, "brightness") == 0) {
// 注意:这里的实现逻辑有一个特点,它会调节上一次选中的那个LED灯的亮度
// 一个更健壮的设计是要求 "led <color> brightness <value>"
duty = atoi(action);
if (duty <= 100) {
// 占空比是反向的,100-duty
CTIMER_GetPwmPeriodValue(20000, 100 - duty, timerClock);
// 此处的 CTIMER_MAT_OUT 是上一次命令设置的静态变量值
CTIMER_SetupPwmPeriod(CTIMER, CTIMER_MAT_PWM_PERIOD_CHANNEL, last_selected_mat_out, g_pwmPeriod, g_pulsePeriod, false);
}
} else {
send_string("Usage: led <red|green|blue> <on|off> or led brightness <0-100>\r\n");
}
}
else
{
send_string("Invalid arguments for led command.\r\n");
}
}
功能演示
基础功能演示,串口打印“Hello, DigiKey Funpack 5-1”。

进阶功能演示,shell基本功能,可以控制红绿蓝三种不同颜色的灯光开灭,并控制亮度。

具体演示内容可观看演示视频 。
总结
完成本次任务学习到了很多新知识,对该板卡sdk的架构有了一定的了解,并且能在示例代码的基础上进行修改。本次完成的任务还可以继续改进,比如增加shell的用户登录与退出功能,键盘上下键自动填充历史命令功能,所有命令查看功能。
