Funpack5-4 - 用FRDM-MCXN236设计智能电机控制
该项目使用了FRDM-MCXN236,实现了智能风机控制的设计,它的主要功能为:基于 FRDM-MCXN236 的智能电机控制器:串口屏/CAN/WIFI 三通道共用自研 0x8x 协议栈统一控制步进电机;飞梭调速、限位换向、倾倒联锁、断电记忆;上电自动回零+行程标定;MQTT 每秒遥测上报并支持网页图形化下发命令;SHT40 温湿度与自动背光;DPD 低功耗休眠与掉电走时。
标签
Funpack活动
电机驱动
RTOS
寒彬彬
更新2026-10-19
19

一、项目介绍

本项目基于 NXP FRDM-MCXN236 评估板实现一台智能电机控制器:串口屏本机控制为主,CAN 总线与 WIFI(MQTT 云端)远程控制为辅,三条命令通道共用同一协议栈与执行体;具备姿态联锁保护、断电记忆、上电自动回零与行程标定、环境温湿度采集与网络对时。

核心实现的功能:

  1. 多通道统一命令协议:添加0x8x 帧协议栈(connect 层),屏幕 UART / CAN / WIFI 三入口共用一套解析分发与执行体,新增命令只需协议表加一行。
  2. 电机控制:PWM 脉冲 + 方向 + 使能三线驱动步进驱动器(共阳极低有效),飞梭编码器调速、按键启停、限位自动换向、姿态倾倒联锁、断电记忆转速。
  3. 位置进度条:上电自动回零 + 行程自动测存 Flash,屏幕进度条实时镜像。
  4. 远程链路:CAN 经典帧 500Kbps(收 0x100);ESP32-AT 实现 WIFI 接入、NTP 对时、MQTT 遥测 1s 上行与命令下行(hex 文本载荷),并提供网页控制台图形化下发(MQTT over WebSocket)。
  5. 环境感知:SHT40 温湿度、FXLS8974 姿态参考归零与 50° 倾倒联锁、光敏 ADC 自动背光调光。
  6. 低功耗与存储:Deep Power Down 休眠(SW2/屏幕触摸唤醒);片内末扇区统一配置记录 + W25Q64 外挂 Flash。

二、硬件介绍

硬件

接口

说明

FRDM-MCXN236

—

Cortex-M33 150MHz,1MB Flash / 256KB SRAM

步进电机驱动器

STEP=P3_16(FlexPWM1) / DIR=P3_15 / EN=P3_17

共阳极接法,MCU 输出低 = 光耦导通 = 有效

飞梭编码器

P3_14/P1_3 + 按键 P1_2

GPIO 端口中断双沿 1x 解码

限位开关 ×2

P2_7 / P2_0

同端口中断位图分发

SHT40 温湿度

LPI2C5 (0x44)

CRC-8 校验,1s 周期

FXLS8974 姿态

LPI2C2

±2g 档 1024 LSB/g,12 位右对齐

W25Q64

LPSPI6 单线 SPI

JEDEC 0xEF4017

ESP32-AT

LPUART7 115200

stock AT 固件,MQTT 零移植

TJA1057 CAN 收发器

CAN1 500Kbps

MB0=Rx / MB1=Tx

串口屏(Nextion 风格)

LPUART2

帧尾 FF FF FF 指令风格

光敏电阻

ADC 通道

环境光采集,自动背光

三、方案框图与设计思路

image.png


设计思路:

  1. 四层分层:app(任务)→ drive(外接芯片协议:connect/esp_at/sht40/accel/w25q/app_cfg/motor)→ BSP(片上外设表驱动)→ SDK。铁律:电机操作只出现在 MotorTask,屏幕帧发送只出现在 InterfaceTask;跨任务只通过 g_fan 只读镜像、单字节命令缓存与事件组同步。
  2. 协议即架构:0x8x 帧(包头+参数+FF FF FF)统一三通道——UART 字节流逐字节 Connect_FeedByte,CAN/WIFI 整帧 Connect_FeedFrame(校验帧尾后同一分发路径)。
  3. 表驱动:协议命令表 s_cmdTable、GPIO 输入输出表、I2C/SPI/UART 通道表,均为"加一行扩一项"。
  4. 断电记忆:转速+姿态参考+行程合存片内末扇区同一条 36B 记录(4KB 擦除粒度下分存会互毁),读改写+累加和校验。

四、调试软件与软件流程

image.png

关键代码一:协议栈表驱动分发(drive/connect.c)——CAN/WIFI/屏幕三入口共用:

void Connect_FeedFrame(const uint8_t *frame, uint32_t len)
{
if ((len < CONNECT_FRAME_TAIL_LEN) ||
(frame[len-1] != 0xFF) || (frame[len-2] != 0xFF) || (frame[len-3] != 0xFF))
return; /* 帧尾校验 */
for (i = 0; i < CMD_NUM; i++) /* 遍历命令表 */
if ((frame[0] == s_cmdTable[i].cmd) && (len == s_cmdTable[i].len))
{ s_cmdTable[i].handler(frame, len); return; }
}

关键代码二:CAN 收帧快照喂栈(freertos_hello.c)——先快照再喂,防喂栈途中被 ISR 新帧覆盖:

if (g_canRxCount != s_canLastCnt) {   /* ISR 覆盖式缓冲 + 计数递增 */
s_canLastCnt = g_canRxCount;
uint8_t len = g_canRxLen;
for (i = 0; i < len; i++) frame[i] = g_canRxBuff[i];
Connect_FeedFrame(frame, len); /* 与屏幕同一执行体 */
}

关键代码三:WIFI 下行 hex 文本解码——MQTT 文本链路传裸二进制不可靠,约定 hex 字符串(如 "8501FFFFFF"),解码后喂同一协议栈(原缓冲就地解码,写指针恒落后读指针,零额外栈)。

关键代码四:上电自动回零 + 行程自动测存(app/motor_task.c,简化示意)——状态机三态 + 30s 超时保护 + 回零期冲突仲裁(SW/0x85 STOP 急停中止、START 拒绝、调速忽略):

switch (s_homing.state) {
case kHoming_ToLim1: /* 反转爬向限位1 */
Motor_SetSpeed(HOMING_LIM1_SPEED); /* = -MOTOR_MIN_HZ */
if (lim1Hit) {
Motor_SetSpeed(0);
s_homeBase = BSP_QDC_GetPosition(); /* 偏移法免硬件清零 */
s_homing.state = (g_travelSteps > 0) ? kHoming_Done : kHoming_ToLim2;
} /* 已标定直接完成,否则测行程 */
break;
case kHoming_ToLim2: /* 反向爬行测行程 */
if (lim2Hit) {
g_travelSteps = Homing_PosSteps(); /* (pos-homeBase) 换算步数 */
AppCfg_UpdateTravel(g_travelSteps); /* 行程落盘,重启直接读 */
s_homing.state = kHoming_Done;
}
break;
}

关键代码五:姿态联锁整数域判倒(drive/accel.c,简化示意)——软件参考归零 + 交叉相乘避浮点除法,cos²50°≈413/1000:

int32_t dot = a.x * g0.x + a.y * g0.y + a.z * g0.z;
if (dot <= 0) return true; /* 倒扣/跌落直接判倾斜 */
/* 柯西不等式保证 dot² ≤ |a|²·|g0|²,整数域乘积不溢出 */
return (int64_t)dot * dot * 1000
< (int64_t)mod2A * mod2G0 * 413; /* 夹角 >50° 判倒 */

关键代码六:MQTT 遥测发布(freertos_hello.c,简化示意)——×10 定点拼小数避开 lite printf 无 %f;空格键值对防 AT 逗号截断;负温用 %s%u 手动出符号(工程 %d 按无符号输出):

temp10 = sht40.temp * 10;                   /* ×10 定点 */
snprintf(json, sizeof(json),
"speed=%u meas=%u temp=%s%u.%u humi=%u.%u",
g_fan.speedHz, g_fan.measHz,
temp10 < 0 ? "-" : "", abs(temp10) / 10, abs(temp10) % 10,
humi10 / 10, humi10 % 10);
EspAt_MqttPublish(json); /* AT+MQTTPUB qos1,1s 拍 */

关键代码七:断电记忆读改写(drive/app_cfg.c,简化示意)——转速+姿态参考+行程合一条 36B 记录(4KB 擦除粒度分存互毁),累加和校验:



bool AppCfg_UpdateTravel(uint32_t steps)
{
(void)AppCfg_Load(&cfg); /* 读旧记录(失败用默认) */
cfg.travelSteps = steps; /* 只改目标字段 */
cfg.checksum = AppCfg_Csum(&cfg); /* 重算累加和 */
Flash_Erase(APP_CFG_SECTOR); /* 4KB 扇区擦除 */
return Flash_Program(APP_CFG_SECTOR, &cfg, sizeof(cfg));
}

五、功能展示与说明

屏幕主页

image.png

操作:上电约 4s 姿态参考就绪 → 按 SW 启动电机 → 旋转飞梭调速

现象:n6 转速数值实时跟随,j0 进度条镜像位置,时间/温湿度/背光同帧刷新;屏幕可下发休眠/设置时间/校准

说明:屏幕显示链路(同帧发送+死区节流)与本地控制链路(飞梭中断→20ms 拍消费)全通

串口日志

image.png

操作:上电观察调试口输出;CAN 工具发帧、MQTTX 发布命令后对照日志

现象:自检打印 SHT40 OK / ACCEL OK / CAN OK / W25Q64 OK JEDEC 0xEF4017;CAN 收帧 [CAN] rx ... 85 01 FF FF FF;WIFI 下行 [WIFI] cmd: 8501FFFFFF + Fan: start (0x85)

说明:外设自检与三通道命令入口日志判据齐全,联调可追溯

MQTTX 遥测与下行

image.png

操作:订阅 mcxn236/fan/telemetry;向 mcxn236/fan/cmd 发布 "8501FFFFFF"

现象:每秒一条 speed=300 meas=298 temp=28.3 humi=36.3(meas 为编码器实测,与目标转速互证闭环);发布后电机远程开机

说明:MQTT 上行/下行全链路打通,hex 文本载荷兼容 AT 行解析

DPD 深度休眠

电机控制器演示_动图.gif

操作:屏幕/远程下发 81 01 → 按 SW2 或触摸屏幕唤醒

现象:进深度掉电,唤醒打印 Wakeup source(bit4=SW2 / bit6=屏幕 RX),RTC 时间连续(VBAT 走时)

说明:最低功耗模式 + 双唤醒源 + 掉电走时

网页控制电机

image.png

操作:浏览器打开 tools/web_screen.html(双击即用)或 python tools/web_screen.py 起 :8000

现象:页顶显示 MQTT 在线,ON/OFF/speed±/校准/休眠按钮即下发对应 0x8x 帧(帧尾自动补全),遥测 1s 刷新,收发均记日志区

说明:MQTT over WebSocket 图形化远程控制,与串口屏同一套协议栈,作 MQTTX 图形化替代

六、难题与解决方法

  1. W25Q64 ROM API 死等挂死:排查定论 MCXN236 无 FlexSPI 控制器(家族共用头文件误导),改 LPSPI6 单线 SPI 重写驱动。
  2. WIFI "no response" 假死:ESP32 实际在线;真因是 RX 空闲期电平跌落被解码成 0x00 假帧污染行缓冲。修复 = 收行过滤 0x00(AT 文本协议不含 0x00,安全)。
  3. MQTT 上行三连坑:PUBRAW 两段式与公共 broker 失步自延续 → 弃用改 MQTTPUB 单命令;qos0 被拒 → 改 qos1;载荷裸逗号被当 AT 参数分隔符 → 改空格分隔键值对,终局零 ERROR。
  4. 加速度计三连修:ACTIVE 置位只写数据没写寄存器地址(一直待机出零);数据 12 位右对齐被 ÷16;倾倒判据交叉相乘 NUM/DEN 乘反恒判倾斜。全部复烧数据实锤结案。
  5. 工程 lite printf %d 按无符号输出:负数手动 %s%u 出符号。
  6. PINT 不可用:PINTSEL 只覆盖 GPIO0/1,飞梭/限位引脚在 GPIO2/3 → 全改 GPIO 端口中断位图分发。
  7. 存储互毁:转速+参考+行程合存同一条记录,避免 4KB 擦除粒度分存互覆盖。
  8. 协议栈抽取:0x8x 解析从屏幕任务抽成独立层后,CAN/WIFI 接入各只改一处收帧路径,执行体单份。

七、心得体会

第一,协议先行、分层落地。项目最大的一次重构是把 0x8x 解析从屏幕任务抽成独立 connect 层——当时只花了一个下午,却让 CAN/WIFI 两条新链路各改十几行就接入,执行体全程单份。教训是:多通道命令类项目,协议栈应该从第一天就独立成层,而不是等第二、第三个接口出现再抽。

第二,联调判读必须以"外部事实"为准。两次复烧出现"板上跑旧固件",串口日志甚至出现过字符乱序;此后定下铁律:判读以 broker 端收包(MQTTX)、CAN 工具回读、原始日志文件为准,且复烧后先核对启动日志参数与最新代码一致。这比任何"看起来对"都可靠。

第三,数据手册细节不能想当然。加速度计三连修(写寄存器漏地址、12 位右对齐、判据乘反)每一处都"看起来能跑",只有静止重力值 ≈1024 LSB、模长守恒这类物理约束才能实锤。传感器类调试要设计自校验判据,而不是肉眼估。

第四,根因优先,拒绝表层补丁。WIFI 假死排查一周,最终是一个 0x00 假帧;若当时选择"收到乱码就重启"的补丁,问题只会以更诡异的形式回来。交叉相乘乘反那次,是靠柯西不等式推出"恒真必错"才定位的——数学证明在调试里同样是有力工具。

第五,嵌入式是软硬件一体的工程。共阳极低有效、LDO 供电能力、光耦导通极性,这些电路层事实决定了代码写法;反过来代码里的极性宏又保护了接线变更时的可读性。遇到"外设不工作"先量电再读码,顺序不能反。


附件下载
interface.zip
屏幕代码
VS_RTC.zip
MCU代码
web_screen.html
网页代码
团队介绍
无
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号