Funpack5-1 - 用NXP FRDM-MCXA346开发板实现Shell与电机控制
该项目使用了NXP FRDM-MCXA346开发板,实现了Shell与无刷电机控制的设计,它的主要功能为:串口模拟了Shell功能并用于控制LED和电机,电机也可控制LED状态。
标签
FOC
FRDM-MCXA346
Shell
DigiKey Funpack
小小洋洋
更新2026-03-02
同济大学
40

一、任务活动介绍

Funpack 活动是由硬禾科技联合 DigiKey 发起的“玩成功就全额退”活动。本期为第五季第 1 期,指定开发平台为 NXP 新品 FRDM-MCXA346 开发板,任务内容如下:

任务 1:串口通信

  • 基础题:通过 UART 串口输出字符串“Hello, DigiKey Funpack 5-1”【⭐️】
  • 进阶题:基于开发板并结合 AI 辅助实现一个带缓冲区的轻量级 Shell 交互系统,可读取、解析并执行用户输入命令;支持命令提示符显示、缓冲区管理与基本命令解析,例如通过指令控制板载 LED 的颜色与亮度【⭐️⭐️⭐️】

任务 2:ADC 数据采集

  • 基础题:使用高分辨率 ADC 采集电压并显示【⭐️】
  • 进阶题:联合电机驱动板实现三相无刷电机驱动【⭐️⭐️⭐️】

本项目完成了任务 1 的基础与进阶内容,以及任务 2 的进阶内容
在电机控制部分,未采用 ADC 采样方式,而是通过 I²C 读取角度传感器数据实现闭环控制。

image.png


二、项目描述

FRDM-MCXA346 开发板具备 180 MHz 主频、1 MB Flash 与 256 KB RAM,板载资源丰富,包含调试器、按键、RGB LED 及 Arduino 兼容扩展接口。

本项目围绕开发板与外设联调,依次实现以下功能:

  1. 串口输出与回显“Hello, DigiKey Funpack 5-1”
  2. 基于 Letter-Shell 实现带缓冲的命令解释器
  3. 通过串口命令控制 RGB LED 的开关与颜色
  4. 基于 SimpleFOC 完成无刷电机位置/速度/急停控制
  5. 将电机角度映射至 LED 颜色,实现交互联动效果

为实现上述功能,主要完成了以下工作:

  • 引入 FreeRTOS 并创建多任务系统
  • 移植 Letter-Shell,实现串口 Shell 与命令注册机制
  • 移植 SimpleFOC,完成 PWM 与时钟配置及电机控制
  • 建立 电机-传感器-LED 反馈联动链路


三、芯片选型

image.png

1)FRDM-MCXA346 开发板

  • Cortex-M33 内核,最高 180 MHz
  • 1 MB Flash + 256 KB RAM(含 8 KB ECC)
  • 双 FlexPWM、4×16 位 ADC、SmartDMA、MAU 数学加速器
  • 支持 CAN/I3C/SPI/I²C/UART/USB-Type-C
  • 板载 MCU-Link 调试器(CMSIS-DAP)
  • 兼容 Arduino / mikroBUS / PMOD 扩展生态
  • 适用于:工业控制、电机驱动、IoT 边缘计算

本项目实际使用的资源:
调试接口、UART、I²C、PWM、板载 RGB LED

2)AS5600 磁编码角度传感器

用于实时检测无刷电机转子角度,实现位置闭环控制。

3)DRV8313 三相无刷电机驱动芯片

提供三相驱动能力,并支持外部 PWM 控制。


四、方案框图与设计思路

项目基于 NXP SDK + FreeRTOS 架构开发。
系统初始化完成后创建两个核心任务:

  • Shell 任务:负责命令解析与用户交互
  • FOC 任务:执行电机控制与角度采样处理

二者通过 Shell 调用相关接口,实现 Hello/LED/电机 等控制指令。

image.png


五、电路设计

  1. 原理图
  1. 连线图
  • I²C → AS5600 角度传感器
  • PWM → DRV8313 三相驱动模块
  • 板载 RGB LED 作为状态与角度显示输出

image.png


六、软件设计与关键代码

  1. 软件设计流程

image.png


  1. 工程介绍

工程以官方 freertos_hello 示例为基础:

  • 启用必要外设与引脚
  • 移植 Letter-Shell 与 SimpleFOC
  • 注册自定义 Shell 命令

image.png

启用并配置了如下的一些引脚和外设。

image.png


  1. 关键代码

这里补充一些移植过程中,我觉得比较关键的代码,完整代码可以查看附件。

① Letter-Shell移植代码shell_port.c:这里使用了串口中断,并配置了循环缓冲区,用于更好地处理串口数据

Shell shell;
static char shellBuffer[512];

/* ========== UART RX Interrupt Handler ========== */
// 中断接收串口数据,放入循环缓冲区中
/**
 * @brief UART RX interrupt handler
 * Stores received data in ring buffer
 */
void LPUART2_IRQHandler(void)
{
    uint8_t data;

    /* Check if RX data available */
    if (kLPUART_RxDataRegFullFlag & LPUART_GetStatusFlags(SHELL_UART))
    {
        data = LPUART_ReadByte(SHELL_UART);

        /* Store in ring buffer if space available */
        uint16_t nextHead = (rxHead + 1) % RING_BUFFER_SIZE;
        if (nextHead != rxTail)
        {
            rxRingBuf[rxHead] = data;
            rxHead = nextHead;
        }
        /* If buffer full, data is dropped */
    }

    SDK_ISR_EXIT_BARRIER;
}

/* ========== Shell Initialization ========== */
/**
 * @brief Initialize shell system (UART + task + buffer)
 * @return 0 on success, -1 on failure
 */
signed short shell_init(void)
{
    /* Initialize UART */
    shell_uart_init();

    /* Configure shell structure */
    shell.read = shellRead;
    shell.write = shellWrite;

    /* Initialize shell with buffer */
    shellInit(&shell, shellBuffer, sizeof(shellBuffer));

    /* Create shell task */
    if (xTaskCreate(shell_task,
                    "shell_task",
                    configMINIMAL_STACK_SIZE + 512,
                    NULL,
                    tskIDLE_PRIORITY + 3,
                    NULL) != pdPASS)
        return -1;

    return 0;
}

② FOC移植foc_port.c:初始化、获取时钟、三相PWM初始化。注意原有的从systick获取的时钟无效,这里使用了freertos的时钟数据。三相PWM配置为中心对称模式,并且设置了死区时间200ns。

* ========== FOC Hardware Initialization ========== */
/**
 * @brief Initialize SimpleFOC hardware and parameters
 */
void simple_foc_init(void)
{
    PRINTF("\r\n=== SimpleFOC init (MCXA346) ===\r\n");

    /* Initialize magnetic sensor */
    MagneticSensor_Init();
    delay_ms(10);
    M1_Disable; // Disable driver at startup for safety

    /* Initialize PWM for 3-phase output to DRV8313 */
    foc_pwm_init(20000); // 20kHz recommended

    /* Set FOC control parameters */
    voltage_power_supply = 12.0f;
    voltage_limit = 3.0f;
    velocity_limit = 20.0f;
    voltage_sensor_align = 3.0f;

    torque_controller = Type_voltage;
    controller = Type_angle;
    target = 0.0f;

    /* Initialize motor and FOC */
    Motor_init();
    Motor_initFOC();
    PID_init();

    PRINTF("Motor ready.\r\n");
}

/**
 * @brief SimpleFOC control task
 * Runs FOC loop at 1ms intervals, or LED control in angle mode
 */
static void simple_foc_task(void *arg)
{
    const TickType_t period = pdMS_TO_TICKS(1); // 1ms control period
    TickType_t tick = xTaskGetTickCount();      // Initialize baseline tick


    for (;;)
    {
        if (led_angle_mode) {
            // LED angle control mode: only read angle and update LEDs
            float angle = getAngle();
            update_leds_from_angle(angle);
        } else {
            // Normal FOC control
            loopFOC();                      // Clarke/Park/SVPWM/Current loop
            move(target);                   // Velocity/position controller (outer loop)
        }
        vTaskDelayUntil(&tick, period); // Fixed period, no jitter
    }
}

/**
 * @brief Get current time in microseconds
 * @return Current time in microseconds
 */
uint64_t micros(void)
{
    uint32_t tick = xTaskGetTickCount();
    uint32_t cycles = SysTick->VAL;


    return tick * 1000ULL +
           (SystemCoreClock / 1000U - cycles) / (SystemCoreClock / 1000000U);
}

/* ========== I2C/LPI2C Initialization ========== */
/**
 * @brief Initialize I2C for FOC sensor communication
 */
void FOC_I2C_Init(void)
{
    lpi2c_master_config_t config;


    LPI2C_MasterGetDefaultConfig(&config);
    config.baudRate_Hz = 1000000U; // Can be adjusted to 100k/400k as needed


    LPI2C_MasterInit(FOC_LPI2C_BASE, &config, FOC_LPI2C_CLOCK_FREQ);
}


/******************************************************************************/
// FOC PWM初始化
#define BOARD_PWM_BASEADDR (FLEXPWM0)
#define PWM_SRC_CLK_FREQ (CLOCK_GetFreq(kCLOCK_MainClk))
#define DEMO_PWM_CLOCK_DEVIDER (kPWM_Prescale_Divide_4)
#define DEMO_PWM_FAULT_LEVEL true

/*******************************************************************************
 * Code
 ******************************************************************************/
static void PWM_DRV_Init3PhPwm(uint32_t pwm_freq_hz)
{
    uint16_t deadTimeVal;
    pwm_signal_param_t pwmSignal[2];
    uint32_t pwmSourceClockInHz;
    uint32_t pwmFrequencyInHz = pwm_freq_hz;

    pwmSourceClockInHz = PWM_SRC_CLK_FREQ;

    /* Set deadtime count, we set this to about 200ns */
    deadTimeVal = ((uint64_t)pwmSourceClockInHz * 200) / 1000000000;

// 省略中间代码

    /*********** PWMA_SM1 - phase B configuration, setup PWM A channel only ************/
#ifdef DEMO_PWM_CLOCK_DEVIDER
    PWM_SetupPwm(BOARD_PWM_BASEADDR, kPWM_Module_1, pwmSignal, 1, kPWM_SignedCenterAligned, pwmFrequencyInHz,
                 pwmSourceClockInHz / (1 << DEMO_PWM_CLOCK_DEVIDER));
#else
    PWM_SetupPwm(BOARD_PWM_BASEADDR, kPWM_Module_1, pwmSignal, 1, kPWM_SignedCenterAligned, pwmFrequencyInHz,
                 pwmSourceClockInHz);
#endif


    /*********** PWMA_SM2 - phase C configuration, setup PWM A channel only ************/
#ifdef DEMO_PWM_CLOCK_DEVIDER
    PWM_SetupPwm(BOARD_PWM_BASEADDR, kPWM_Module_2, pwmSignal, 1, kPWM_SignedCenterAligned, pwmFrequencyInHz,
                 pwmSourceClockInHz / (1 << DEMO_PWM_CLOCK_DEVIDER));
#else
    PWM_SetupPwm(BOARD_PWM_BASEADDR, kPWM_Module_2, pwmSignal, 1, kPWM_SignedCenterAligned, pwmFrequencyInHz,
                 pwmSourceClockInHz);
#endif
}

③ LED随电机角度控制foc_port.c:虽然我很想使用PWM控制LED的亮度,但是由于三个LED的引脚,无法在PWM0或1的A或B通道,部分都在X通道。其中只有A和B通道才能输出PWM,因此没办法实现亮度的控制。所以,这里只是将电机分为了12个区域,对应区域开关LED。

/**
 * @brief Control LEDs based on motor angle
 * Maps angle to 6 regions for RGB LED control
 */
void update_leds_from_angle(float angle_rad)
{
    // Convert radians to degrees
    float angle = angle_rad * 180.0f / 3.141592653589793f;


    // Normalize angle to 0-360 degrees
    while (angle < 0) angle += 360.0f;
    while (angle >= 360.0f) angle -= 360.0f;


    int r = 0, g = 0, b = 0;


    // 6 regions: 3 main + 3 intermediate
    if (angle < 30.0f) {
        // Region 1: Red only
        r = 1; g = 0; b = 0;
    } else if (angle < 60.0f) {
        // Region 1-2: Red + Green
        r = 1; g = 1; b = 0;
    } else if (angle < 90.0f) {
        // Region 2: Green only
        r = 0; g = 1; b = 0;
    } else if (angle < 120.0f) {
        // Region 2-3: Green + Blue
        r = 0; g = 1; b = 1;
    } else if (angle < 150.0f) {
        // Region 3: Blue only
        r = 0; g = 0; b = 1;
    } else if (angle < 180.0f) {
        // Region 3-1: Blue + Red
        r = 1; g = 0; b = 1;
    } else if (angle < 210.0f) {
        // Region 1: Red only (continued)
        r = 1; g = 0; b = 0;
    } else if (angle < 240.0f) {
        // Region 1-2: Red + Green (continued)
        r = 1; g = 1; b = 0;
    } else if (angle < 270.0f) {
        // Region 2: Green only (continued)
        r = 0; g = 1; b = 0;
    } else if (angle < 300.0f) {
        // Region 2-3: Green + Blue (continued)
        r = 0; g = 1; b = 1;
    } else if (angle < 330.0f) {
        // Region 3: Blue only (continued)
        r = 0; g = 0; b = 1;
    } else {
        // Region 3-1: Blue + Red (continued)
        r = 1; g = 0; b = 1;
    }


    // Set LEDs (assuming active low)
    if (r) GPIO_PortClear(BOARD_LED_RED_GPIO, 1u << BOARD_LED_RED_GPIO_PIN);
    else   GPIO_PortSet(BOARD_LED_RED_GPIO, 1u << BOARD_LED_RED_GPIO_PIN);


    if (g) GPIO_PortClear(BOARD_LED_GREEN_GPIO, 1u << BOARD_LED_GREEN_GPIO_PIN);
    else   GPIO_PortSet(BOARD_LED_GREEN_GPIO, 1u << BOARD_LED_GREEN_GPIO_PIN);


    if (b) GPIO_PortClear(BOARD_LED_BLUE_GPIO, 1u << BOARD_LED_BLUE_GPIO_PIN);
    else   GPIO_PortSet(BOARD_LED_BLUE_GPIO, 1u << BOARD_LED_BLUE_GPIO_PIN);
}

④ Shell函数、命令注册freertos_hello.c:这里简单附上几个函数和命令,例如hello、角度控制、速度控制、led设置。

/* ========== Shell hello 函数 ========== */
void hello(void)
{
    PRINTF("Hello, DigiKey Funpack 5-1\r\n");
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), hello, hello, Hello DigiKey Funpack 5 - 1);

/* ============================================================
 * angle —— Query / Set target angle
 * ========================================================== */


/**
 * @brief Set or query motor angle target
 * Usage: angle [target_degrees]
 */
void angle(int argc, void *argv[])
{
    float current_angle = getAngle();


    if (argc == 1)
    {
        PRINTF("Current angle = %.2f deg, Target = %.2f deg\r\n", current_angle, target);
        return;
    }


    if (argc == 2)
    {
        const char *arg = (const char *)argv[1];
        target = strtof(arg, NULL);
        controller = Type_angle;
        PRINTF("Set angle target = %.2f deg\r\n", target);
        return;
    }


    PRINTF("Usage: angle [target_degrees]\r\n");
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN),
                 angle, angle, angle[target]);


/* ============================================================
 * vel —— Query / Set velocity target
 * ========================================================== */


/**
 * @brief Set or query motor velocity target
 * Usage: vel [target_rad_per_sec]
 */
void vel(int argc, void *argv[])
{
    if (argc == 1)
    {
        PRINTF("Velocity target = %.2f rad/s\r\n", target);
        return;
    }


    target = strtof((char *)argv[1], NULL);
    controller = Type_velocity;
    PRINTF("Set velocity target = %.2f rad/s\r\n", target);
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN),
                 vel, vel, vel[rad / s]);


七、实物演示

  1. 使用USB连接上开发板,通过MobaXterm创建一个串口的Session进行连接。
  2. 上电之后,会输出FOC初始化的调试信息,期间电机会旋转小的角度进行校准。校准完成之后,会显示输入密码(123456),成功后即可进入shell交互。

image.png

  1. 输入help查看所有的指令,输入hello可以返回Hello, DigiKey Funpack 5-1,输入angle获取角度。

image.png

  1. 输入led查看相关指令,并通过例如led on, led off, led rgb 1 1 0等,控制led的开关

image.png

image.png

  1. 输入angle查看角度,带上数字为目标角度,可以看到电机旋转;或者输入vel控制旋转速度;以及输入stop停止电机。

image.png

image.png

  1. 输入ledmode on,即可开启电机控制led颜色函数。具体效果可以从视频中查看。


八、难点与解决

本次项目遇到了很多问题。虽然基础题目很简单,但是看到开发板的性能如何强悍,决定完成进阶功能,为此遇到了如下问题并解决:

  1. 难点:Letter-Shell中不能够注册自己的函数,并且IDE中无法修改链接文件。

解决:需要在项目属性中,将该项目的链接文件管理功能关闭,即可修改链接文件。随后,在链接文件的text部分,补充上代码。

  1. 难点:在无刷电机控制中,PWM始终无法输出

解决:官方案例中提供的PWM工程,就是完成三相输出的PWM,为此,修改其中的频率、占空比函数接口完成PWM输出。

  1. 难点:移植的SimpleFOC无法正常工作,电机抖动厉害。

解决:移植过程中,其中的获取系统时钟的函数SysTick(),因为开发板也包括该函数所以没报错,但实际上无输出。为此,修改时钟为freertos的即可。

  1. 难点:Letter-Shell和FOC的任务,由于实时性要求高,两个任务难以在Freertos中同时工作,即使修改任务优先级也不可以。

解决:在Freertos的配置中,将其频率从200Hz修改为5000Hz,即可平衡两个任务,其中FOC任务以1kHz运行,Letter-Shell任务以低优先级循环运行即可。

  1. 难点:一直想要通过PWM控制板载LED的亮度,但是一直无法实现,最多只能控制BLUE LED的亮度。

解决:最后发现是因为AB通道才能输出PWM,X通道只能用于捕获,因此无法控制亮度。


九、总结

  1. 通过本次项目实践,系统掌握了 FRDM-MCXA346 的外设配置流程以及 FreeRTOS 多任务开发方法;
  2. 完成了 Letter-Shell 与 SimpleFOC 的移植与调试,并对移植过程中可能出现的时序、时钟与外设冲突问题形成了完整的认识与经验积累。
  3. 最后感谢主办方给予的机会!
附件下载
frdmmcxa346_freertos_hello.zip
工程源码
团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号