活动开发板介绍
本次活动使用的开发板是恩智浦的FRDM-MCXA346开发板,这块开发板搭载了MCX A346 MCU,该MCU基于Arm® Cortex®-M33内核,运行频率高达180MHz,1MB闪存,256KB RAM,带8KB的纠错码(ECC),同时具有双FlexPWM、4组16位ADC、专用MAU数学加速器以及SmartDMA,开发板正反面如下


开发板器件框图如下,可以看到开发板有着丰富的外设接口,还有用户按键和RGB LED可以验证GPIO和PWM

任务分析
本次活动我选择的是任务1:串口通信的进阶题目,使用MCXA346开发板并用AI辅助实现一个带缓冲区的shell,核心是实现一个能读取、解析并执行用户输入命令的简易命令行解释器,并管理好输入缓冲区。基本要求:程序需能显示命令提示符(如 ysh > $),并循环接收用户输入(\n换行符为断句符号)。管理好输入缓冲区,妥善处理字符,并能进行基本的命令解析。例如:实现简单的指令控制板载的LED的颜色和亮度。
该任务可以拆分为两个任务
RGB LED的PWM控制
根据开发板原理图,可以看到RGB LED的RGB分别对应P3_18、P3_19、P3_21三个引脚,为了调整LED亮度,采用的是引脚的CTIMER功能

串口模拟shell指令输入和分析
串口模拟shell的重点在于串口交互,既需要产生类似于SHELL>>这样提示一行指令开始的内容,又需要分析指令,包括正确读取指令并分析,对不正确的指令有识别纠错的功能
目标实现功能
基于以上这些分析,可以设置芯片引脚如下,其中LPUART2用于串口接收和发送,CTIMER2用于时间比较,从而用于产生对应占空比的PWM

本次任务我将创建两条shell指令,分别用于控制RGB LED亮度和RGB LED呼吸灯速率
- RGB LED亮度调节,ledset <R> <G> <B>,每个参数输入0-100的整数作为占空比,调节输出到RGB三盏LED灯的PWM占空比从而调整亮度
- RGB LED呼吸灯速率调节,ledbreath <R> <G> <B>,每个参数输入0-5的整数作为呼吸速度等级,调节输出到RGB三盏LED灯的PWM占空比变化速率从而调整呼吸灯速率
代码功能实现
PWM波形的产生
由于P3_18、P3_19、P3_21三个引脚并不能够使用FlexPWM,因此需要借助ctimer功能实现
在头文件中加入fsl_ctimer.h用于导入ctimer
#include "fsl_ctimer.h"
接着宏定义一些ctimer相关的参数,并设定PWM的默认频率为200Hz
#define RED_LED_CTIMER CTIMER2
#define GREEN_LED_CTIMER CTIMER2
#define BLUE_LED_CTIMER CTIMER2
#define LED_PWM_PERIOD_CH kCTIMER_Match_2
#define RED_LED_PWM_DUTY_CH kCTIMER_Match_0
#define GREEN_LED_PWM_DUTY_CH kCTIMER_Match_1
#define BLUE_LED_PWM_DUTY_CH kCTIMER_Match_3
#define DEFAULT_PWM_FREQ (200U)
设置ctimer时钟初始化函数
static void Clocks_InitForCtimer2(void)
{
CLOCK_SetClockDiv(kCLOCK_DivCTIMER2, 1U);
CLOCK_AttachClk(kFRO_HF_to_CTIMER2);
RESET_ReleasePeripheralReset(kCTIMER2_RST_SHIFT_RSTn);
}
设置RGB三盏LED灯引脚的初始化函数
static void LedPwm_Init(void)
{
ctimer_config_t cfg;
Clocks_InitForCtimer2();
CTIMER_GetDefaultConfig(&cfg);
CTIMER_Init(RED_LED_CTIMER, &cfg);
uint8_t duty = 0U;
status_t status;
status = CTIMER_SetupPwmPeriod(
RED_LED_CTIMER,
LED_PWM_PERIOD_CH,
RED_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
duty,
false
);
if(status != kStatus_Success) {
PRINTF("Red LED SetupPwmPeriod failed: %d\r\n", status);
while(1) {
__NOP();
}
}
status = CTIMER_SetupPwmPeriod(
GREEN_LED_CTIMER,
LED_PWM_PERIOD_CH,
GREEN_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
duty,
false
);
if(status != kStatus_Success) {
PRINTF("Green LED SetupPwmPeriod failed: %d\r\n", status);
while(1) {
__NOP();
}
}
status = CTIMER_SetupPwmPeriod(
BLUE_LED_CTIMER,
LED_PWM_PERIOD_CH,
BLUE_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
duty,
false
);
if(status != kStatus_Success) {
PRINTF("Blue LED SetupPwmPeriod failed: %d\r\n", status);
while(1) {
__NOP();
}
}
CTIMER_StartTimer(RED_LED_CTIMER);
CTIMER_StartTimer(GREEN_LED_CTIMER);
CTIMER_StartTimer(BLUE_LED_CTIMER);
}
设置亮度调节函数,传参为RGB三盏LED灯的PWM占空比
void Led_SetBrightness(uint8_t brightnessPercent_r, uint8_t brightnessPercent_g, uint8_t brightnessPercent_b)
{
if (brightnessPercent_r > 100U) {
brightnessPercent_r = 100U;
}
if (brightnessPercent_g > 100U) {
brightnessPercent_g = 100U;
}
if (brightnessPercent_b > 100U) {
brightnessPercent_b = 100U;
}
CTIMER_SetupPwmPeriod(
RED_LED_CTIMER,
LED_PWM_PERIOD_CH,
RED_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
brightnessPercent_r,
false
);
CTIMER_SetupPwmPeriod(
GREEN_LED_CTIMER,
LED_PWM_PERIOD_CH,
GREEN_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
brightnessPercent_g,
false
);
CTIMER_SetupPwmPeriod(
BLUE_LED_CTIMER,
LED_PWM_PERIOD_CH,
BLUE_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
brightnessPercent_b,
false
);
}
shell内容的创建
为了使用shell功能需要引入fsl_shell.h头文件
#include "fsl_shell.h"
定义需要创建的shell功能的句柄(功能后续介绍),并设置shell功能的帮助信息,同时引入shell_handle_t和serial_handle_t两个变量用于shell指令的操作
/*******************************************************************************
* Prototypes
******************************************************************************/
/* SHELL command handlers */
static shell_status_t ledset_handler(shell_handle_t shellHandle, int32_t argc, char **argv);
static shell_status_t ledbreath_handler(shell_handle_t shellHandle, int32_t argc, char **argv);
/*******************************************************************************
* Variables
******************************************************************************/
SHELL_COMMAND_DEFINE(ledset, "\r\n\"ledset <R> <G> <B>\": Set RGB-LED brightness\r\n", ledset_handler, 3);
SHELL_COMMAND_DEFINE(ledbreath, "\r\n\"ledbreath <R> <G> <B>\": Set RGB-LED breath speed\r\n", ledbreath_handler, 3);
SDK_ALIGN(static uint8_t s_shellHandleBuffer[SHELL_HANDLE_SIZE], 4);
static shell_handle_t s_shellHandle;
extern serial_handle_t g_serialHandle;
在主函数中初始化shell并创建ledset和ledbreath两个shell功能
/* Init SHELL */
s_shellHandle = &s_shellHandleBuffer[0];
SHELL_Init(s_shellHandle, g_serialHandle, "SHELL>> ");
SHELL_RegisterCommand(s_shellHandle, SHELL_COMMAND(ledset));
SHELL_RegisterCommand(s_shellHandle, SHELL_COMMAND(ledbreath));
while (1)
{
#if !(defined(SHELL_NON_BLOCKING_MODE) && (SHELL_NON_BLOCKING_MODE > 0U))
SHELL_Task(s_shellHandle);
#endif
}
shell功能的创建
我们目标创建两条指令:ledset用于调整RGB三盏LED灯的亮度,ledbreath用于调整三盏LED灯的呼吸速率
ledset的实现
首先进行传参分析,参数不符合(数目不对以及不符合0-100的整数的)产生报警并返回,接着分析三个传参,将它们作为RGB三盏LED灯的占空比,调用函数Led_SetBrightness实现LED灯亮度的调节
static shell_status_t ledset_handler(shell_handle_t shellHandle, int32_t argc, char **argv)
{
uint8_t rgb[3] = {0};
breathFlag = false;
// 入参判断
if (argc != 4)
{
SHELL_Printf("Usage: ledset <R> <G> <B>\r\n");
return kStatus_SHELL_Success;
}
for(int i = 0; i < 3; i++) {
char *endp = NULL;
unsigned long v = strtoul(argv[i + 1], &endp, 0);
// 判断参数是否合规
if (argv[i + 1][0] == '\0' || *endp != '\0' || v > 100UL)
{
SHELL_Printf("Invalid %c value: %s (expect 0..100 integer)\r\n", "RGB"[i], argv[i + 1]);
return kStatus_SHELL_Success;
}
rgb[i] = (uint8_t)v;
}
Led_SetBrightness(rgb[0], rgb[1], rgb[2]);
return kStatus_SHELL_Success;
}
ledbreath的实现
为了实现呼吸灯,最简单的方案是写一个死循环,让占空比往复加减即可,然而为了能不断向shell输入内容,我们并不希望产生死循环。
在学习STM32的时候,我们知道有个概念叫做滴答定时器,这是说系统每个毫秒会进入一次中断。同理的,在FRDM-MCXA346上也可以使用这个思路,利用滴答定时器的中断进行计数,利用计数来实现周期的循环。
在主函数中初始化滴答定时器,设置其中断时间为每个毫秒
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock / 1000U);
定义一些全局变量,其中ms用于毫秒计数,breathFlag用于判断当前指令是否为ledbreath,rgb_speed用于存储当前RGB三盏LED灯呼吸灯速率等级
uint32_t ms = 0;
bool breathFlag = false;
uint8_t rgb_speed[3] = {0};
滴答定时器中断回调函数的实现需要在board.c中书写,在board.c中添加以下内容
将上述的全局变量作为这个c文件的外部变量,并设置PWM的相关参数
extern uint32_t ms;
extern bool breathFlag;
extern uint8_t rgb_speed[3];
uint8_t step_r = 0;
uint8_t step_g = 0;
uint8_t step_b = 0;
uint8_t duty_r = 0;
uint8_t duty_g = 0;
uint8_t duty_b = 0;
bool flag_r = true;
bool flag_g = true;
bool flag_b = true;
在board.c中设置一个函数,用于实现呼吸灯速率等级到真实步进值的映射
static uint8_t Speed2Step(uint8_t Speed)
{
switch(Speed) {
case 0:
return 0;
case 1:
return 1;
case 2:
return 2;
case 3:
return 4;
case 4:
return 5;
case 5:
return 10;
}
}
定义滴答定时器的中断回调函数,每50ms进行一次PWM占空比的更新,SysTick_Handler在工程文件最初的源代码中是一个弱定义的函数,在board.c中对其进行了重写
void SysTick_Handler(void)
{
if(!breathFlag) {
return;
}
if(ms++ < 50) {
return;
}
ms = 0;
step_r = Speed2Step(rgb_speed[0]);
step_g = Speed2Step(rgb_speed[1]);
step_b = Speed2Step(rgb_speed[2]);
if(flag_r) {
if(duty_r < 100U) {
duty_r += step_r;
} else {
flag_r = false;
}
} else {
if(duty_r > 0U) {
duty_r -= step_r;
} else {
flag_r = true;
}
}
if(flag_g) {
if(duty_g < 100U) {
duty_g += step_g;
} else {
flag_g = false;
}
} else {
if(duty_g > 0U) {
duty_g -= step_g;
} else {
flag_g = true;
}
}
if(flag_b) {
if(duty_b < 100U) {
duty_b += step_b;
} else {
flag_b = false;
}
} else {
if(duty_b > 0U) {
duty_b -= step_b;
} else {
flag_b = true;
}
}
Led_SetBrightness(duty_r, duty_g, duty_b);
}
在主文件中,ledbreath的句柄函数判断了入参合法性,并将入参传入了全局变量rgb_speed,同时将呼吸灯在滴答定时器内启用其逻辑的标志位breathFlag标记为true。如果使用ledset,那么breathFlag这个标志位就会变为false
static shell_status_t ledbreath_handler(shell_handle_t shellHandle, int32_t argc, char **argv)
{
// 入参判断
if (argc != 4)
{
SHELL_Printf("Usage: ledbreath <R> <G> <B>\r\n");
return kStatus_SHELL_Success;
}
for(int i = 0; i < 3; i++) {
char *endp = NULL;
unsigned long v = strtoul(argv[i + 1], &endp, 0);
// 判断参数是否合规
if (argv[i + 1][0] == '\0' || *endp != '\0' || v > 5UL)
{
SHELL_Printf("Invalid %c value: %s (expect 0, 1, 2, 3, 4, 5)\r\n", "RGB"[i], argv[i + 1]);
return kStatus_SHELL_Success;
}
rgb_speed[i] = (uint8_t)v;
}
breathFlag = true;
return kStatus_SHELL_Success;
}
代码汇总
主要的代码汇总如下
主文件RGBShell.c代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "app.h"
#include "board.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "fsl_debug_console.h"
#include "fsl_ctimer.h"
#include "fsl_clock.h"
#include "fsl_reset.h"
#include "fsl_component_serial_manager.h"
#include "fsl_shell.h"
#include "RGBShell.h"
/*******************************************************************************
* Definitions
******************************************************************************/
#define RED_LED_CTIMER CTIMER2
#define GREEN_LED_CTIMER CTIMER2
#define BLUE_LED_CTIMER CTIMER2
#define LED_PWM_PERIOD_CH kCTIMER_Match_2
#define RED_LED_PWM_DUTY_CH kCTIMER_Match_0
#define GREEN_LED_PWM_DUTY_CH kCTIMER_Match_1
#define BLUE_LED_PWM_DUTY_CH kCTIMER_Match_3
#define DEFAULT_PWM_FREQ (200U)
#define SHELL_Printf PRINTF
/*******************************************************************************
* Prototypes
******************************************************************************/
/* SHELL command handlers */
static shell_status_t ledset_handler(shell_handle_t shellHandle, int32_t argc, char **argv);
static shell_status_t ledbreath_handler(shell_handle_t shellHandle, int32_t argc, char **argv);
/*******************************************************************************
* Variables
******************************************************************************/
SHELL_COMMAND_DEFINE(ledset, "\r\n\"ledset <R> <G> <B>\": Set RGB-LED brightness\r\n", ledset_handler, 3);
SHELL_COMMAND_DEFINE(ledbreath, "\r\n\"ledbreath <R> <G> <B>\": Set RGB-LED breath speed\r\n", ledbreath_handler, 3);
SDK_ALIGN(static uint8_t s_shellHandleBuffer[SHELL_HANDLE_SIZE], 4);
static shell_handle_t s_shellHandle;
extern serial_handle_t g_serialHandle;
uint32_t ms = 0;
bool breathFlag = false;
uint8_t rgb_speed[3] = {0};
/*******************************************************************************
* Code
******************************************************************************/
static void Clocks_InitForCtimer2(void)
{
CLOCK_SetClockDiv(kCLOCK_DivCTIMER2, 1U);
CLOCK_AttachClk(kFRO_HF_to_CTIMER2);
RESET_ReleasePeripheralReset(kCTIMER2_RST_SHIFT_RSTn);
}
static void LedPwm_Init(void)
{
ctimer_config_t cfg;
Clocks_InitForCtimer2();
CTIMER_GetDefaultConfig(&cfg);
CTIMER_Init(RED_LED_CTIMER, &cfg);
uint8_t duty = 0U;
status_t status;
status = CTIMER_SetupPwmPeriod(
RED_LED_CTIMER,
LED_PWM_PERIOD_CH,
RED_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
duty,
false
);
if(status != kStatus_Success) {
PRINTF("Red LED SetupPwmPeriod failed: %d\r\n", status);
while(1) {
__NOP();
}
}
status = CTIMER_SetupPwmPeriod(
GREEN_LED_CTIMER,
LED_PWM_PERIOD_CH,
GREEN_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
duty,
false
);
if(status != kStatus_Success) {
PRINTF("Green LED SetupPwmPeriod failed: %d\r\n", status);
while(1) {
__NOP();
}
}
status = CTIMER_SetupPwmPeriod(
BLUE_LED_CTIMER,
LED_PWM_PERIOD_CH,
BLUE_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
duty,
false
);
if(status != kStatus_Success) {
PRINTF("Blue LED SetupPwmPeriod failed: %d\r\n", status);
while(1) {
__NOP();
}
}
CTIMER_StartTimer(RED_LED_CTIMER);
CTIMER_StartTimer(GREEN_LED_CTIMER);
CTIMER_StartTimer(BLUE_LED_CTIMER);
}
void Led_SetBrightness(uint8_t brightnessPercent_r, uint8_t brightnessPercent_g, uint8_t brightnessPercent_b)
{
if (brightnessPercent_r > 100U) {
brightnessPercent_r = 100U;
}
if (brightnessPercent_g > 100U) {
brightnessPercent_g = 100U;
}
if (brightnessPercent_b > 100U) {
brightnessPercent_b = 100U;
}
CTIMER_SetupPwmPeriod(
RED_LED_CTIMER,
LED_PWM_PERIOD_CH,
RED_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
brightnessPercent_r,
false
);
CTIMER_SetupPwmPeriod(
GREEN_LED_CTIMER,
LED_PWM_PERIOD_CH,
GREEN_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
brightnessPercent_g,
false
);
CTIMER_SetupPwmPeriod(
BLUE_LED_CTIMER,
LED_PWM_PERIOD_CH,
BLUE_LED_PWM_DUTY_CH,
DEFAULT_PWM_FREQ,
brightnessPercent_b,
false
);
}
static shell_status_t ledset_handler(shell_handle_t shellHandle, int32_t argc, char **argv)
{
uint8_t rgb[3] = {0};
breathFlag = false;
// 入参判断
if (argc != 4)
{
SHELL_Printf("Usage: ledset <R> <G> <B>\r\n");
return kStatus_SHELL_Success;
}
for(int i = 0; i < 3; i++) {
char *endp = NULL;
unsigned long v = strtoul(argv[i + 1], &endp, 0);
// 判断参数是否合规
if (argv[i + 1][0] == '\0' || *endp != '\0' || v > 100UL)
{
SHELL_Printf("Invalid %c value: %s (expect 0..100 integer)\r\n", "RGB"[i], argv[i + 1]);
return kStatus_SHELL_Success;
}
rgb[i] = (uint8_t)v;
}
Led_SetBrightness(rgb[0], rgb[1], rgb[2]);
return kStatus_SHELL_Success;
}
static shell_status_t ledbreath_handler(shell_handle_t shellHandle, int32_t argc, char **argv)
{
// 入参判断
if (argc != 4)
{
SHELL_Printf("Usage: ledbreath <R> <G> <B>\r\n");
return kStatus_SHELL_Success;
}
for(int i = 0; i < 3; i++) {
char *endp = NULL;
unsigned long v = strtoul(argv[i + 1], &endp, 0);
// 判断参数是否合规
if (argv[i + 1][0] == '\0' || *endp != '\0' || v > 5UL)
{
SHELL_Printf("Invalid %c value: %s (expect 0, 1, 2, 3, 4, 5)\r\n", "RGB"[i], argv[i + 1]);
return kStatus_SHELL_Success;
}
rgb_speed[i] = (uint8_t)v;
}
breathFlag = true;
return kStatus_SHELL_Success;
}
int main(void)
{
BOARD_InitHardware();
LedPwm_Init();
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock / 1000U);
/* Init SHELL */
s_shellHandle = &s_shellHandleBuffer[0];
SHELL_Init(s_shellHandle, g_serialHandle, "SHELL>> ");
SHELL_RegisterCommand(s_shellHandle, SHELL_COMMAND(ledset));
SHELL_RegisterCommand(s_shellHandle, SHELL_COMMAND(ledbreath));
while (1)
{
#if !(defined(SHELL_NON_BLOCKING_MODE) && (SHELL_NON_BLOCKING_MODE > 0U))
SHELL_Task(s_shellHandle);
#endif
}
}
board.c中与中断回调实现呼吸灯效果的代码
/*******************************************************************************
* Variables
******************************************************************************/
extern uint32_t ms;
extern bool breathFlag;
extern uint8_t rgb_speed[3];
uint8_t step_r = 0;
uint8_t step_g = 0;
uint8_t step_b = 0;
uint8_t duty_r = 0;
uint8_t duty_g = 0;
uint8_t duty_b = 0;
bool flag_r = true;
bool flag_g = true;
bool flag_b = true;
/*******************************************************************************
* Code
******************************************************************************/
static uint8_t Speed2Step(uint8_t Speed)
{
switch(Speed) {
case 0:
return 0;
case 1:
return 1;
case 2:
return 2;
case 3:
return 4;
case 4:
return 5;
case 5:
return 10;
}
}
void SysTick_Handler(void)
{
if(!breathFlag) {
return;
}
if(ms++ < 50) {
return;
}
ms = 0;
step_r = Speed2Step(rgb_speed[0]);
step_g = Speed2Step(rgb_speed[1]);
step_b = Speed2Step(rgb_speed[2]);
if(flag_r) {
if(duty_r < 100U) {
duty_r += step_r;
} else {
flag_r = false;
}
} else {
if(duty_r > 0U) {
duty_r -= step_r;
} else {
flag_r = true;
}
}
if(flag_g) {
if(duty_g < 100U) {
duty_g += step_g;
} else {
flag_g = false;
}
} else {
if(duty_g > 0U) {
duty_g -= step_g;
} else {
flag_g = true;
}
}
if(flag_b) {
if(duty_b < 100U) {
duty_b += step_b;
} else {
flag_b = false;
}
} else {
if(duty_b > 0U) {
duty_b -= step_b;
} else {
flag_b = true;
}
}
Led_SetBrightness(duty_r, duty_g, duty_b);
}
完整代码可以参见附件代码文件RGBShell
演示效果
将代码烧录进入芯片,使用ledset调整输入到每盏灯的PWM的占空比,如果占空比超过了100,那么就会产生告警

SHELL>> ledset 10 0 0的实现效果

SHELL>> ledset 50 0 0的实现效果

SHELL>> ledset 10 20 30的实现效果

使用ledbreath调整输入到每盏灯的PWM占空比的变化速率,如果速率等级不为0-5,那么就会产生告警

SHELL>> ledbreath 2 0 0的实现效果

SHELL>> ledbreath 4 0 0的实现效果

SHELL>> ledbreath 1 2 3的实现效果

更加具体的实现效果可以参见我的b站视频
心得总结
再次感谢得捷电子和硬禾科技对本次活动的支持。恩智浦的产品并不是我所常用的,其开发思路也与我目前掌握的一些MCU的开发方式有些差异,但是通过本次活动,我对恩智浦的MCU的相关开发流程有了一定的理解,尤其是其PWM的ctimer生成方案以及shell功能的实现。并且在这个过程中,我作为一名恩智浦产品的初学者,使用GitHub copilot这个AI工具解答了不少我在基本配置代码书写过程中产生的一系列疑问,这使得我对一款全新产品的认识速度在AI的帮助下相较之前有所提升。