一、项目介绍:
本项目基于 NXP FRDM-MCXN236 开发板,通过 I2C 总线连接 SHT20 温湿度传感器,实现周期性环境数据采集,并通过调试串口输出采集结果,同时用板载 RGB LED指示系统运行状态,采集刷新周期为 1 秒(可调整)。
项目完整实现了活动要求的四项功能:
- 通过 I2C 读取传感器数据:使用 LPI2C2 主机,按 SHT2x 协议(保持模式 + CRC‑8 校验)读取温度与湿度;
- 周期采集环境信息:主循环以固定 1000ms 节拍采集;
- 通过串口输出采集结果:LPUART4 @115200,逐行打印
[T=..C RH=..%]; - 使用 LED 指示系统运行状态:绿色 LED 每次成功采集翻转一次(心跳),红色 LED 在采集失败时闪烁报警,且数据刷新周期不大于 1 秒。
实测运行效果(串口原文):
[BOOT] FRDM-MCXN236 SHT20 environmental monitor
[T=28.01C RH=61.02%] seq=67
[T=28.01C RH=61.05%] seq=68
[T=28.02C RH=60.99%] seq=70
即采集到约 28.0 ℃、61 %RH 的室内环境数据,稳定运行、刷新周期 1 秒。
二、使用到的硬件介绍:
类别 | 型号 / 说明 |
|---|---|
控制器(开发板) | NXP FRDM-MCXN236 |
传感器 | SHT20 温湿度传感器模块(I2C 接口,3.3V 供电,7 位地址 0x40) |
调试器 | 板载 MCU‑Link |
线材 | USB Type‑C 数据线;杜邦线若干 |
开发板用到的资源
- LPI2C2(LP_FLEXCOMM2):I2C 主机,100 kHz,PIO4_0 = SDA、PIO4_1 = SCL,时钟源 FRO12M(12 MHz);
- LPUART4(LP_FLEXCOMM4):调试串口,115200‑8‑N‑1,PIO1_8 = TX / PIO1_9 = RX;
- GPIO4:板载 RGB LED,PIO4_18 = 红、PIO4_19 = 绿(低电平点亮,可用 PIO4_17 蓝);
- SysTick:1 ms 定时中断,作为软件毫秒时基;
- MCU‑Link:下载调试 + 虚拟串口。
硬件接线(mikroBUS J5)
FRDM‑MCXN236 的 mikroBUS 插座把板载传感器 I2C 直接引出,接线非常方便:
SHT20 模块 | mikroBUS | MCU 引脚 / 说明 |
|---|---|---|
VCC | J6‑7(+3.3V) | 3.3V |
GND | J5‑8 / J6‑8 | GN |
SDA | J5‑6 | PIO4_0(LPI2C2_SDA) |
SCL | J5‑5 | PIO4_1(LPI2C2_SCL) |
三、方案框图和设计思路介绍

设计思路
整体采用“分层 + 轮询”的裸机方案,共分三层:
- 驱动层(
sht20.c/.h):只关心 SHT20 协议,对上层提供SHT20_Init()、SHT20_ReadTemperature()、SHT20_ReadHumidity()、SHT20_ReadBoth()四个接口。 - 板级初始化层(
pin_mux.c、peripherals.c/.h、hardware_init.c):负责引脚复用、
时钟(FRO12M → FLEXCOMM2/4)、LPI2C2 主机初始化、SysTick 时基、调试串口初始化。 - 应用层(
led_blinky.c主程序):以 1 秒为节拍调用驱动,组织“采集 → 串口输出 → LED 指示”。
关键设计取舍:
- 协议模式:SHT2x 支持“保持模式(hold master,0xE3/0xE5)”与“非保持模式(0xF3/0xF5)”。
本项目采用保持模式:主机一次传输即可完成“发命令 + 重复起始 + 读回 3 字节”,
测量期间由传感器拉低 SCL 自动等待,实现最简洁,也便于 CRC 校验。 - 数据完整性:每次测量读回
MSB, LSB, CRC三字节,用 CRC‑8(多项式 0x31)校验,避免误读。 - 周期节拍:用 SysTick 累加毫秒计数
g_ms,主循环把下一次采样对齐到固定节拍,
保证实际输出周期稳定为 1 秒(满足“不大于 1 秒”)。 - 状态指示:绿=心跳/正常,红=错误,语义清晰,便于无串口时快速判断系统是否在跑。
- 健壮性:加入 I2C 总线恢复(详见第六节),解决“总线被从机占用卡死”的经典问题。
四、软件流程图

五、关键代码介绍
(1)SHT20 读取(保持模式 + CRC‑8 校验)——sht20.c
/* 发送命令并紧接重复起始读取:START + addr(W) + cmd + RESTART + addr(R) + data */
static status_t SHT20_ReadWithCommand(uint8_t cmd, uint8_t *data, uint32_t dataSize)
{
lpi2c_master_transfer_t xfer = {0};
xfer.slaveAddress = SHT20_I2C_ADDR; /* 0x40 */
xfer.direction = kLPI2C_Read;
xfer.subaddress = cmd; /* 0xE3/0xE5(保持模式) */
xfer.subaddressSize = 1U;
xfer.data = data;
xfer.dataSize = dataSize; /* 3 字节:MSB, LSB, CRC */
xfer.flags = kLPI2C_TransferDefaultFlag;
return LPI2C_MasterTransferBlocking(SHT20_I2C_BASE, &xfer);
}
/* CRC-8:多项式 0x31(x^8+x^5+x^4+1),初值 0x00(SHT2x 数据手册) */
static uint8_t SHT20_Crc8(const uint8_t *data, uint32_t len)
{
uint8_t crc = 0x00U, bit; uint32_t i;
for (i = 0U; i < len; i++) {
crc ^= data[i];
for (bit = 0U; bit < 8U; bit++)
crc = (0u != (crc & 0x80U)) ? (uint8_t)((crc << 1U) ^ 0x31U) : (uint8_t)(crc << 1U);
}
return crc;
}
温湿度换算(数据手册公式):
/* T(°C) = -46.85 + 175.72 * S / 2^16 */
*temperature = -46.85f + 175.72f * ((float)raw / 65536.0f);
/* RH(%) = -6.0 + 125.0 * S / 2^16(限幅 0~100) */
rh = -6.0f + 125.0f * ((float)raw / 65536.0f);
(2)周期采集 + 串口输出 + LED 指示——led_blinky.c
int main(void)
{
BOARD_InitHardware(); /* 引脚->时钟->LPI2C2/SysTick->调试串口 */
PRINTF("\r\n[BOOT] FRDM-MCXN236 SHT20 environmental monitor\r\n");
LED_RED_OFF(); LED_GREEN_OFF();
if (SHT20_Init() != kStatus_Success) { PRINTF("[ERR] SHT20 init failed\r\n"); LED_RED_ON(); }
while (1) {
status = SHT20_ReadBoth(&temperature, &humidity);
if (status == kStatus_Success) {
PRINTF("[T=%.2fC RH=%.2f%%] seq=%u\r\n", (double)temperature, (double)humidity, seq);
LED_RED_OFF(); LED_GREEN_TOGGLE(); /* 绿灯心跳 */
} else {
PRINTF("[ERR] sensor read failed (status=%d)\r\n", (int)status);
LED_GREEN_OFF(); LED_RED_TOGGLE(); /* 红灯闪烁 */
if (++failCount >= 5U) { BOARD_I2C_Recover(); failCount = 0U; } /* 总线恢复 */
}
/* 对齐到固定 1s 节拍 */
nextTick += 1000U;
while ((int32_t)(g_ms - nextTick) < 0) { }
}
}
(3)I2C 总线恢复(稳健性关键)——peripherals.c
/* 9 个 SCL 时钟 + 一个 STOP,使占用总线的从机释放 SDA */
static void LPI2C2_ReleaseBus(void) {
/* P4_0/P4_1 临时切 GPIO:SCL 输出、SDA 输入(保留上拉) */
...
for (i = 0U; i < 9U; i++) { GPIO_PinWrite(GPIO4,1,0); delay; GPIO_PinWrite(GPIO4,1,1); delay; }
/* STOP:SCL 高时 SDA 由低变高 */
...
/* 恢复 I2C 复用 */
}
void BOARD_I2C_Recover(void) { LPI2C2_ReleaseBus(); LPI2C_MasterInit(LPI2C2, &cfg, 12000000U); }
六、功能展示图及说明
- 串口输出

- LED 指示:正常运行时绿色 LED 每秒翻转(心跳);读取失败时红色 LED 闪烁。

- 实测数据:室内环境下温度约 28.0 ℃、相对湿度约 61 %RH,数据连续稳定。
七、项目中遇到的难题及解决方法
难题 1:I2C 总线“卡死”,所有传输返回 900(BUSY)——最耗时的一个坑
- 现象:程序一上电,读写 SHT20 始终返回
status=900(kStatus_LPI2C_Busy)。为定位问题,我把工程里 NXP 官方的板载加速度计例程(读 FXLS8974,同一 LPI2C2 总线)也烧进去,同样失败;说明总线硬件本身完好。 - 排查:用软件回读寄存器确认引脚配置正确(
PCR=Alt2+上拉、开漏正确),并把 SDA 临时切成 GPIO 输入观察,发现 SDA 一直被拉低(关掉主机、切成输入仍为低,推挽驱动高也拉不起来),因此判断是总线被某个从机占用。 - 根因:I2C 传输是“起始→地址→数据→停止”的完整序列;当主机在一次传输未完成时被复位
(调试时反复烧录/复位极易发生),从机收不到停止条件,便继续占用总线、把 SDA 拉低,之后所有传输都判“总线忙”。这是 I2C 的经典“总线挂死”,与传感器和接线无关。 - 解决:加入总线恢复——把 SCL 临时切为 GPIO,发送 9 个时钟脉冲 + 一个 STOP强制从机释放总线,再重新初始化 I2C 主机;并在连续 5 次读取失败时自动重做一次。加入后立即恢复正常读数。
- 预防:任何可能中途复位主机的操作后先做总线恢复;对 I2C 读写加超时与重试。
难题 2:SysTick 时基与实际时钟不符
- 现象:配置工具按 48 MHz 生成的 SysTick 是 10 ms,但工程实际用 FRO12M(12 MHz)启动,实际中断约 40 ms,时基不准。
- 解决:按实际时钟重算——
SYSTICK0_CLK_FREQ = 12000000、SYSTICK0_TICKS = 12000(1 ms)。
难题 3:串口完全没有输出
- 现象:固件已运行,但串口无任何输出。
- 根因:原始
led_blinky示例不使用串口,pin_mux.c里没有配置 LPUART4 的引脚。 - 解决:在引脚配置中补上
PIO1_8/PIO1_9(FC4,Alt2),并确保初始化调试串口。
难题 4:新增引脚组不生效
- 现象:I2C 引脚配好了却不工作。
- 根因:
hardware_init.c调用的是BOARD_InitPins(),而新增的引脚组挂在BOARD_InitBootPins()下。 - 解决:改为调用
BOARD_InitBootPins()。
难题 5:浮点不打印小数
- 现象:
PRINTF("%.2f", x)输出不正确。 - 根因:SDK 调试串口默认不开启浮点打印。
- 解决:在
CMakeLists.txt中加入-DPRINTF_FLOAT_ENABLE=1(及PRINTF_ADVANCED_ENABLE=1)。
八、心得体会
这次活动让我完整走了一遍“从需求到真机跑通”的嵌入式开发流程,收获主要体现在:
第一,“现象 ≠ 根因”。一开始 I2C 一直报“总线忙”,我第一反应是接线或传感器坏了,但通过“换一个已知良好的例程(板载加速度计)对比”这一关键实验,排除了硬件与自身代码,才把方向锁定到“总线状态被破坏”。这让我体会到:遇到问题要用实验去二分、去隔离变量,而不是凭直觉猜。
第二,协议细节决定成败。SHT20 的保持/非保持模式、CRC 多项式、状态位清零、温湿度换算式等,任何一个弄错都会得到错误数据;我通过对照数据手册逐条实现并校验最终读数稳定合理。这说明“对着手册写驱动”比“抄一段能用就行”可靠得多。
第三,稳健性同样重要。能读到数据只是第一步,还要考虑异常(拔线、复位、总线卡死)时系统不崩、能自愈、能提示。因此我加上了总线恢复与失败计数重试,让系统更“抗造”。接下来我打算继续完善:把采集数据接入显示屏做实时曲线、增加按键交互与参数配置、并把 I2C 访问改为带超时/中断的方式,进一步降低 CPU 占用。感谢 NXP 与电子森林提供这次机会,也感谢 DigiKey 的支持,让我在真实硬件上完成了第一个完整的 I2C 传感器项目。