—— 基于 FRDM-MCXN236 的多传感器采集系统
一、项目介绍
这个项目围绕 NXP 的 FRDM-MCXN236 开发板展开,做了一款能"感知—显示—记录"的多传感器系统。它通过 I2C 同时接入一个六轴姿态传感器和一个颜色传感器,把姿态数据和颜色数据周期性地采集出来,一边在板载 RGB 灯上做状态指示,一边通过串口发给电脑上的 VOFA+ 做实时可视化,并可以存成 CSV 做离线统计。从接线到写驱动再到上位机,整条链路完整走一遍,收获颇丰。
1.1 硬件介绍
控制器是 FRDM-MCXN236,主控芯片 MCX N236 是一颗 150MHz 的 Arm Cortex-M33 内核、带 1MB Flash,板载 MCU-Link 调试器(调试口 J10,同时提供一路虚拟串口)。选它一方面是这一系列和之前用过的 FRDM-MCXN947 同属 MCUXpresso 工具链,环境不用重装;另一方面引脚资源充足,方便同时挂多个 I2C 设备。
MPU6500 六轴姿态传感器:商家标称 MPU6050,但读它的 WHO_AM_I 寄存器返回 0x70,说明实际是 MPU6500(寄存器与 MPU6050 兼容,仅温度换算系数不同)。这颗芯片还自带一个片内温度传感器,可以顺带读出芯片温度。接在 Arduino 排针 J2 的 I2C 上(芯片内部 LPI2C5,SDA=P1_16,SCL=P1_17),地址 0x68。
TCS34725 颜色传感器:卖家标的是 GY-33,实际是一颗 TCS34725,可直读 RGBC 原始值。接在 mikroBUS 排针的 I2C 上(内部 LPI2C2,SDA=P4_0,SCL=P4_1),地址 0x29。之所以把它单独放到第二条总线,是因为两个模块挤在同一对排针上接线很别扭、又容易接错;分开后不仅排线清爽,地址也完全不冲突。
此外还用到板载 RGB 指示 LED(红 P4_18、绿 P4_19、蓝 P4_17,低电平点亮)和 LPUART4 串口(经 MCU-Link 虚拟串口,115200)。两个传感器都从板子的 3.3V 取电。

1.2 功能概览
- 通过两条 I2C 总线,分别读取六轴姿态数据和颜色数据;
- 以 10ms(100Hz)为周期采集,对加速度/角速度做一阶低通滤波;
- 由加速度和陀螺积分解算出横滚、俯仰、偏航三个姿态角;
- 读取姿态传感器片内的温度数据;
- 通过串口按 justfloat 协议输出 10 个通道的数据;
- 上位机 VOFA+ 用 3D 立方体、仪表盘实时显示姿态与温度;
- 加速度幅值超过 1.5g 时,板载红灯点亮约 1 秒作为报警;
- 对颜色做识别,并用板载 RGB 灯显示识别到的颜色(串口发
1开启、发0关闭); - 用上位机脚本把数据存成 CSV,并统计各通道的最小值/最大值/均值/标准差。
1.3 设计思路
整体设计可以概括为"一个时间基准 + 一个非阻塞主循环"。在 SysTick 中断里维护一个毫秒计数,主循环每次都只判断"到点没有",到点才做一次采集与处理,既简单又稳定。采集周期定在 10ms,既能满足实时性,也留足了余量。
软件上做了分层:最底层 i2c_bus 封装两条 I2C 的初始化和寄存器读写;中间 sensor(MPU)和 color_gy33(颜色)是两个传感器驱动;上层 app_env 负责调度、滤波、解算、报警和输出;led_status 单独负责灯。数据流向是:采集 → 一阶低通滤波 → 姿态解算 → 阈值报警判断 → 按 justfloat 打包发串口。输出特意选了 justfloat,一是省带宽,二是直接传二进制浮点,绕开了单片机打印浮点数比较麻烦的问题。
二、功能实现
2.1 软件结构
程序启动后先做板级初始化(引脚、时钟、调试串口),再依次初始化两个传感器和 I2C 总线,然后进入主循环。主循环里先非阻塞地读一次串口命令,再判断是否到 10ms:到点则采集姿态、读取颜色,做滤波与解算、报警判断和输出,最后刷新 LED。
void SysTick_Handler(void) { s_ms_tick++; } /* 1ms 时间基准 */
void APP_Task(void)
{
static uint32_t last = 0;
uint32_t now = APP_Millis();
APP_PollCommand(); /* 处理串口命令 */
if ((uint32_t)(now - last) >= APP_SAMPLE_PERIOD_MS) { /* 到 10ms 了 */
last = now;
/* 采集 -> 滤波 -> 解算 -> 报警 -> 输出 */
}
/* LED 指示 */
}
2.2 实现过程
2.2.1 I2C 总线与寄存器读写
两条总线共用一套"读/写寄存器"函数。底层用 LPI2C 的阻塞式传输,把要访问的寄存器作为 subaddress 发出去,后面跟数据即可(读就是重复起始后读回):
status_t I2C_ReadRegs(i2c_bus_t bus, uint16_t addr, uint8_t reg, uint8_t *buf, size_t len)
{
lpi2c_master_transfer_t x = {0};
x.slaveAddress = addr; x.direction = kLPI2C_Read;
x.subaddress = reg; x.subaddressSize = 1U;
x.data = buf; x.dataSize = len;
x.flags = kLPI2C_TransferDefaultFlag;
return LPI2C_MasterTransferBlocking(I2C_Instance(bus), &x);
}
2.2.2 姿态传感器读取与姿态解算
从寄存器 0x3B 起一次连读 14 个字节,包含加速度三轴、温度、陀螺三轴(均为大端 16 位),再按 ±2g / ±250°/s 的量程换算。姿态角里,横滚和俯仰由重力方向用 atan2 算出(稳定不漂移),偏航则由陀螺 Z 轴积分,并在开机时静止标定一次零偏:
I2C_ReadRegs(I2C_BUS_A, s_addr, 0x3B, raw, sizeof(raw)); /* 连读 14 字节 */
int16_t ax = (int16_t)((raw[0] << 8) | raw[1]); /* 大端 */
out->accel_g[0] = (float)ax / 16384.0f; /* ±2g */
out->gyro_dps[2] = (float)gz / 131.0f; /* ±250°/s */
*roll = atan2f(ay, az) * 57.29578f;
*pitch = atan2f(-ax, sqrtf(ay * ay + az * az)) * 57.29578f;
s_yaw_deg += (gz - s_gyro_z_bias) * (APP_SAMPLE_PERIOD_MS / 1000.0f);
2.2.3 温度采集
温度来自姿态传感器片内的温度传感器——它的寄存器和加速度、陀螺是挨着的,所以读姿态的时候就顺手一起读回来了:一次连读的 14 个字节里,第 7、8 字节正是温度(TEMP_OUT,大端 16 位有符号)。换算上,MPU6050 是 raw/340 + 36.53,而 MPU6500/9250 是 raw/333.87 + 21.0,两者差好几度,所以程序按读到的 WHO_AM_I 自动选用对应的系数:
int16_t tt = (int16_t)((raw[6] << 8) | raw[7]); /* 温度,大端 */
out->temperature_c = (float)tt / s_temp_lsb + s_temp_off; /* 系数按芯片选择 */
2.2.4 数据输出:justfloat 协议
数据按 justfloat 打包:先写 10 个通道的小端浮点数组,再补一个固定的 4 字节帧尾。上位机靠这个帧尾来切分每一帧:
static const uint8_t tail[4] = {0x00, 0x00, 0x80, 0x7F}; /* 小端 +Inf,帧尾 */
LPUART_WriteBlocking(LPUART4, (const uint8_t *)v, n * sizeof(float));
LPUART_WriteBlocking(LPUART4, tail, sizeof(tail));
2.2.5 颜色识别
读回 RGBC(Clear/Red/Green/Blue)后,先按 Clear 归一化,再乘白平衡增益补偿通道灵敏度差异;然后用"饱和度"判黑/白,其余按主通道判颜色——这样白光略偏绿也不会被误判成绿色:
float rf = ((float)red * 255.0f / clear) * APP_TCS_WB_R; /* 白平衡 */
uint32_t sat = ((uint32_t)(mx - mn) * 100U) / mx;
if (sat < 35U) return GY33_COLOR_WHITE; /* 三通道接近 -> 白/灰 */
if (mx == r) return GY33_COLOR_RED;
2.2.6 串口命令与 LED 指示
主循环里非阻塞地读串口,收到字符 1 就把 RGB 灯切到"显示颜色",收到 0 切回"姿态状态灯"。默认模式下,绿灯常亮表示正常、红灯亮 1 秒表示加速度报警、蓝灯闪烁是心跳:
while ((LPUART_GetStatusFlags(LPUART4) & kLPUART_RxDataRegFullFlag) != 0U) {
uint8_t b = LPUART_ReadByte(LPUART4);
if (b == '1') s_led_mode = APP_LED_MODE_COLOR;
else if (b == '0') s_led_mode = APP_LED_MODE_MPU;
}
三、功能演示
上电后系统自动以 10ms 周期采集。在电脑上用 VOFA+ 打开对应串口(justfloat 引擎,115200),可以看到 3D 立方体、仪表盘和数据通道。轻轻倾斜或转动开发板,屏幕上的立方体就跟着一起转;旁边的仪表盘分别显示合成加速度和温度,数值随动作实时跳动,静止下来后很快稳定,只剩小数末位轻微跳动——这正是前面那步低通滤波在起作用。

报警功能:把板子猛地一晃,合成加速度超过 1.5g,板载红灯就会亮起约一秒,这一秒里其它灯熄灭,一眼就能看出"动作太大"。平时不动时是绿灯常亮、蓝灯慢闪。
颜色识别:把不同颜色的物体放到颜色传感器前,识别到红色时数据通道 R 值明显升高、颜色分类位随之变化。通过串口发送 1 切到颜色模式后,板载 RGB 灯直接显示当前识别到的颜色——对着红色物体就亮红、对绿色亮绿、对白色三色全亮;发 0 切回姿态状态灯。所有数据还可同时用 Python 脚本存成 CSV,退出时自动打印各通道的最小值、最大值、均值和标准差,以及报警出现的次数。



四、总结与展望
这套系统目前已能稳定地采集姿态与颜色、实时可视化、在加速度超阈值时报警,并把数据记录下来做统计,基本达到了预期目标。整个过程中也踩了不少坑:一开始以为传感器是 MPU6050,读 WHO_AM_I 才发现是 0x70 的 MPU6500;颜色模块卖家说是 GY-33、地址该是 0x5A,程序扫总线却扫到 0x29;还有白色老被识别成绿色、接线太挤需要另开一条 I2C 总线……这些问题一个个排查下来,反倒让我对 I2C 时序、寄存器读写和"遇到问题先看数据手册"这些原本很抽象的东西,第一次有了真切的体会。
如果继续做下去,我想再接一些别的传感器,把颜色识别调得更准,或者让上位机反过来给下位机下发参数。总的来说,这个项目让我把从硬件接线、写驱动,到上位机可视化的整条链路完整地走了一遍,受益良多。