
简介这份资源面向物联网与嵌入式开发初学者及电子竞赛选手围绕MSP430G2553超低功耗微控制器给出温度与光照强度采集并无线传输的完整工程实现解决环境监测中传感器驱动、数据显示与点对点通信的落地问题。压缩包共43个文件、约78KB以C源文件与头文件为核心配合CCS工程配置、链接命令文件、编译中间产物及makefile等构建脚本完整保留发送端与接收端两套工程结构便于直接导入调试。已有320人学习下载可作为低功耗传感节点设计的参考范例。读者可从中获得DS18B20单总线测温、光照信号采集处理、12864液晶实时显示以及无线收发模块编码解码的代码框架并理解工程目录组织与编译配置思路适合对照学习传感器应用、LCD驱动与无线通信的整合方法。1. 从一块 LaunchPad 说起温度与光照如何无线跑起来手头有一块 MSP430G2553 LaunchPad板载一颗 NTC 热敏电阻和一颗光敏电阻很多人拿到手只跑过官方例程点个灯就搁置了。这个项目把它往前推了一步采集温度与光照两路模拟量本地做换算再通过无线模块把数据发出去接收端还原成可读数值。整套东西跑在 3.3V、16MHz 的入门级 MCU 上资源紧到每一字节 RAM 都要算着用正好是理解「传感器采集 ADC 换算 串口无线透传」完整链路的合适样本。适合刚接触 MSP430 想做一个能出数据的小系统的人也适合需要给课程设计或小型监测节点找参考实现的人。下面按「它怎么组织、怎么跑、怎么改」拆开讲。2. 系统架构与硬件链路两路模拟量怎么走到无线端2.1 整体数据流与模块划分这套系统的数据流很直白但每一段都有需要留意的细节。MSP430G2553 内部 ADC10 依次采样两路通道一路接板载 NTC 分压点一路接光敏电阻分压点。采样结果经过换算变成温度值和光照相对强度再按约定格式拼成一帧字符串从 UART 发往无线模块无线模块以透传方式把字节流送到接收端。接收端可以是另一块同型号板子也可以是接在电脑上的 USB 转串口。模块划分上我一般拆成四层硬件抽象层负责 ADC 通道选择与引脚配置采集换算层负责把原始码值转成物理量帧组装层负责拼包与校验传输层负责 UART 收发。这样拆的好处是换传感器只动换算层换无线模块只动传输层互不牵连。MSP430G2553 只有 512 字节 RAM、16KB Flash分层不能太细函数调用层级要控制否则栈容易吃紧。硬件连接上NTC 与光敏电阻都采用「上拉电阻 传感器」的分压结构分压中点接 ADC 输入引脚。选哪个引脚要看具体封装20 脚版本常用 P1.0 到 P1.7 中的模拟输入通道代码里通过ADC10AE0寄存器把对应引脚设为模拟功能。无线模块的 TXD 接 MCU 的 RXD、RXD 接 MCU 的 TXD供电统一 3.3V注意模块峰值电流可能超过 LaunchPad 板载稳压的输出能力独立供电更稳。2.2 时钟与 ADC10 配置ADC10 的采样精度和时钟源强相关。MSP430G2553 默认 DCO 约 1MHz做串口通信时通常把 DCO 校准到 1MHz 或 8MHz再用 SMCLK 分频给 ADC10 做采样时钟。采样保持时间要覆盖信号源内阻NTC 分压点内阻偏高时采样时间设短了读数会明显偏低这是很常见的翻车点。// ADC10 初始化双通道单次转换SMCLK/4 作为采样时钟 void ADC10_Init(void) { ADC10CTL0 0; // 先清零避免残留配置 ADC10CTL1 INCH_0 CONSEQ_0; // 通道 A0单通道单次 ADC10CTL0 SREF_0 ADC10SHT_3 // 参考电压 VCC/GND采样保持 64 个时钟 ADC10ON ADC10IE; // 打开 ADC允许转换完成中断 ADC10AE0 | BIT0 | BIT1; // P1.0、P1.1 设为模拟输入 }SREF_0表示参考电压取 VCC 与 GND3.3V 供电时满量程约 3.3V10 位分辨率对应约 3.2mV 每码。ADC10SHT_3是较长的采样保持时间配合高内阻分压点更稳。CONSEQ_0是单通道单次模式两路采集靠切换INCH_x分两次做比序列模式好控制也便于在两路之间插入换算逻辑。中断里只做「取结果、置标志」换算放到主循环避免中断里做浮点运算拖长响应。2.3 温度与光照的换算模型NTC 的阻值随温度非线性变化工程上常用两种做法查表加线性插值或者用 Steinhart-Hart 方程。MSP430G2553 没有硬件浮点跑浮点库会吃掉不少 Flash我一般优先用定点查表。先由 ADC 码值反推 NTC 阻值再在预生成的阻值-温度表里二分查找并线性插值。// 由 ADC 码值反推 NTC 阻值再查表得温度单位 0.1 摄氏度 // R_UP 为上拉电阻阻值ADC_FULL 为 10 位满量程 1023 int16_t ADC_to_Temp_x10(uint16_t adc) { uint32_t r_ntc; if (adc 0) return -400; // 防除零返回下限 // 分压公式V_adc/Vcc R_ntc/(R_ntcR_UP) 反解 r_ntc ((uint32_t)R_UP * adc) / (ADC_FULL - adc); return NTC_Lookup_x10(r_ntc); // 查表 线性插值 }R_UP的取值直接影响灵敏度区间上拉阻值接近被测温区中点对应的 NTC 阻值时该区间分辨率最好。光照一路不需要绝对照度用 ADC 码值归一化成 0 到 100 的相对强度即可公式是light adc * 100 / 1023注意先乘后除避免整数截断丢精度。如果确实要标定成勒克斯得用标准光源做多点拟合这一步在资源受限的板子上通常放到上位机做。2.4 帧格式与无线透传无线模块工作在透传模式时MCU 发什么字节它就发什么字节所以帧格式完全由软件约定。我习惯用「帧头 长度 载荷 校验」的定长结构接收端按帧头同步校验失败丢帧重等。载荷里温度用有符号 16 位、光照用无符号 8 位比发 ASCII 字符串省一半带宽也省 MCU 的格式化开销。// 组帧0xAA 0x55 | len | temp_h temp_l | light | checksum uint8_t Build_Frame(uint8_t *buf, int16_t temp_x10, uint8_t light) { uint8_t i, sum 0; buf[0] 0xAA; buf[1] 0x55; buf[2] 4; // 载荷长度 buf[3] (uint8_t)(temp_x10 8); buf[4] (uint8_t)(temp_x10 0xFF); buf[5] light; for (i 0; i 6; i) sum buf[i]; // 简单累加和校验 buf[6] sum; return 7; // 返回帧总长 }校验用累加和足够应付短帧和低干扰环境如果现场电磁环境复杂换成 CRC8 更稳代价是多几十字节 Flash。发送节奏上不要连续猛发模块从发射切到接收需要时间帧间隔留 10ms 以上比较保险。接收端解析时先找0xAA 0x55再核对长度和校验任何一步不符就整体后移一字节重新同步这套状态机写法在串口通信里很通用。3. 从零跑通工程搭建、烧录与联调步骤3.1 开发环境与工程骨架工具链用 TI 官方的 Code Composer Studio 或开源的 MSP430 GCC 都行前者集成调试器体验顺后者命令行友好、便于脚本化。新建工程时选对目标型号 MSP430G2553链接文件用芯片自带的 16KB Flash、512B RAM 配置。工程目录我一般这样组织main.c放主循环和初始化adc.c/h放采集换算uart.c/h放串口收发frame.c/h放组帧解析sensor_table.h放 NTC 查表数据。文件不多但边界清楚后面换传感器不用翻整个工程。# 以 MSP430 GCC 为例的编译与烧录命令骨架 msp430-elf-gcc -mmcumsp430g2553 -Os -o build/app.elf \ main.c adc.c uart.c frame.c msp430-elf-objcopy -O ihex build/app.elf build/app.hex # 烧录工具按手头调试器选择命令行参数以工具实际帮助为准-Os是必须的默认优化级别下代码体积容易顶到 Flash 上限。编译报「region RAM overflow」时优先查大数组和浮点库引用把查表数据加const放到 Flash 里能立刻缓解。烧录前确认调试器的目标电压设置与板子一致供电不足时会出现「找不到目标」的假故障。3.2 串口与无线模块的参数对齐UART 配置要和无线模块的出厂串口参数一致常见是 9600 波特率、8 数据位、无校验、1 停止位。DCO 校准误差会直接体现为波特率偏差1MHz DCO 下 9600 波特率的误差要控制在 2% 以内否则接收端会间歇性收到乱码。校准值存在芯片信息段里代码里读取校准常量比手写魔数可靠。// UART 初始化9600 8N1使用校准常量设置波特率 void UART_Init(void) { DCOCTL 0; BCSCTL1 CALBC1_1MHZ; // 1MHz 校准值 DCOCTL CALDCO_1MHZ; P1SEL | BIT1 | BIT2; // P1.1 RXD, P1.2 TXD P1SEL2 | BIT1 | BIT2; UCA0CTL1 | UCSSEL_2; // SMCLK 作为时钟源 UCA0BR0 104; // 1MHz/9600 ≈ 104 UCA0BR1 0; UCA0MCTL UCBRS_1; // 调制位减小误差 UCA0CTL1 ~UCSWRST; // 释放复位开始工作 }UCA0BR0/BR1是整数分频UCA0MCTL里的调制位补偿小数部分这两个值要按实际时钟频率重算不能照抄。无线模块上电后先给几百毫秒稳定时间再发数据模块还没初始化完就发前几帧必然丢这是联调时最常见的「以为代码有问题其实是时序」的情况。3.3 联调顺序与验证方法联调不要一上来就两端全接按「单板采集 → 有线串口 → 无线透传」三步走。第一步只烧采集板用调试器看 ADC 结果寄存器手捏 NTC 看码值是否单调变化遮光看光照通道是否响应。第二步把采集板 UART 直接接 USB 转串口用串口助手看帧内容确认帧头、长度、校验都对。第三步才接无线模块两端参数一致后再看接收端输出。验证温度换算是否可信用冰水混合物和室温两个点粗校即可偏差超过 2 摄氏度就回头查上拉阻值和采样时间。光照通道没有绝对基准用手电筒由远及近照看数值是否平滑上升、有无跳变跳变多半是采样保持时间不足或电源纹波。接收端解析建议先写一个上位机小脚本把收到的字节流按帧格式打印出来比盯着串口助手的十六进制窗口猜要快得多。4. 核心代码拆解主循环、中断与低功耗的配合4.1 主循环的状态机写法资源受限的板子上主循环用状态机比用阻塞延时清晰得多。采集、换算、发送三个阶段各占一个状态ADC 中断只置标志主循环轮询标志推进状态。这样既不会在中断里做重活也不会因为某一步耗时把整个节奏拖乱。typedef enum { ST_IDLE, ST_SAMPLE_T, ST_SAMPLE_L, ST_SEND } app_state_t; int main(void) { WDTCTL WDTPW WDTHOLD; // 关看门狗调试期先关 ADC10_Init(); UART_Init(); __enable_interrupt(); app_state_t st ST_SAMPLE_T; int16_t temp_x10 0; uint8_t light 0; while (1) { switch (st) { case ST_SAMPLE_T: ADC10CTL1 (ADC10CTL1 ~INCH_15) | INCH_0; ADC10CTL0 | ENC ADC10SC; // 启动 A0 转换 st ST_SAMPLE_L; break; case ST_SAMPLE_L: if (adc_done) { // 中断置位 adc_done 0; temp_x10 ADC_to_Temp_x10(ADC10MEM); ADC10CTL1 (ADC10CTL1 ~INCH_15) | INCH_1; ADC10CTL0 | ENC ADC10SC; // 启动 A1 转换 st ST_SEND; } break; case ST_SEND: if (adc_done) { adc_done 0; light (uint8_t)((uint32_t)ADC10MEM * 100 / 1023); uint8_t f[8]; uint8_t n Build_Frame(f, temp_x10, light); UART_Send(f, n); __delay_cycles(20000); // 帧间隔给无线模块切换时间 st ST_SAMPLE_T; } break; } } }ADC10CTL1的通道位要先清再置直接或运算会残留旧通道。__delay_cycles的参数按实际主频折算1MHz 下 20000 约 20ms。状态机里没有阻塞等待后续要加按键或低功耗模式插入新状态即可不用重构主循环。4.2 中断服务与标志传递ADC10 中断只做一件事清中断标志、置软件标志。共享变量加volatile否则优化器可能把它缓存进寄存器导致主循环永远等不到。volatile uint8_t adc_done 0; #pragma vectorADC10_VECTOR __interrupt void ADC10_ISR(void) { adc_done 1; // 仅置标志换算留给主循环 }中断里不做乘除和查表是因为这些操作耗时不确定会拉长中断响应影响串口接收的实时性。如果后续加了 UART 接收中断两个中断的优先级要心里有数ADC 转换完成和串口收字节撞在一起时先保证串口不丢字节。4.3 低功耗模式的取舍电池供电场景下采集完进 LPM3 等定时器唤醒是常规做法。但透传无线模块本身静态电流不小MCU 省下的那点电流可能被模块吃掉是否值得上低功耗要看整机预算。我的做法是先测整机平均电流再决定 MCU 侧优化到什么程度别为了低功耗而低功耗把调试复杂度拉上去。// 采集间隙进入 LPM3由定时器 A 周期唤醒 TA0CCR0 32768 - 1; // ACLK 32.768kHz约 1 秒 TA0CCTL0 CCIE; TA0CTL TASSEL_1 MC_1; // ACLK增计数模式 __bis_SR_register(LPM3_bits GIE); // 进低功耗等中断唤醒进 LPM3 前要确认所有外设都处于可唤醒状态UART 在 LPM3 下时钟可能停掉需要改用 LPM0 或给 UART 单独保留时钟。这类细节不实测很容易踩后面避坑章节会展开。5. 避坑与排查五条现场踩出来的记录5.1 温度读数整体偏低十几度现象是手捏 NTC 温度只涨一点点室温读数比实际低一截。原因是采样保持时间设得太短NTC 分压点内阻高采样电容没充到位就被采样了。解决是把ADC10SHT_x调到较长档位必要时在分压点并一个小电容做电荷缓冲改完读数立刻回到合理区间。5.2 串口助手收到周期性乱码现象是每隔几帧出现一串乱码帧头也对不上。原因多半是 DCO 未用校准常量、波特率偏差超限或者无线模块上电稳定时间不够。解决是改用CALBC1_1MHZ/CALDCO_1MHZ校准值重算UCA0BR0与调制位并在初始化后加几百毫秒延时再发首帧。5.3 接收端偶发丢帧且校验失败现象是大部分帧正常偶尔校验不过。原因是发送端帧间隔太短无线模块从发射切接收没完成或者两端供电地电位不一致引入干扰。解决是把帧间隔拉到 10ms 以上两端共地校验从累加和升级为 CRC8 观察是否仍失败以此区分是时序问题还是干扰问题。5.4 程序跑一段时间后复位现象是运行几分钟后莫名重启看门狗没开也会发生。原因是栈溢出或数组越界512 字节 RAM 经不起大局部数组。解决是把大数组改static或加const放 Flash检查Build_Frame的缓冲区长度是否够 7 字节编译时打开栈使用量检查。5.5 低功耗模式下串口收不到数据现象是加了 LPM3 后接收端再无输出。原因是 LPM3 关闭了 SMCLKUART 失去时钟源。解决是改用 LPM0 保留 SMCLK或把 UART 时钟切到 ACLK 并确认模块支持该波特率改完用电流表确认平均电流确实下降再收工。6. 二次开发换传感器、改协议与上位机对接拿到这套代码后最常见的改造方向有三个。换传感器时只动换算层比如把 NTC 换成数字温度芯片就在ADC_to_Temp_x10同级新增一个读取函数主循环状态机里替换调用即可帧格式不用变。改协议时只动frame.c把累加和换成 CRC8、把定长帧换成带转义的变长帧接收端同步改解析状态机两端一起升级别只改一端。对接上位机时最省事的做法是保留二进制帧、在上位机侧解析而不是让 MCU 拼 ASCII后者会多占 Flash 还拖慢发送节奏。改造点涉及文件验证方法更换温度传感器adc.c / sensor_table.h冰水与室温两点比对校验升级为 CRC8frame.c人为注入错帧看是否丢弃增加第三路采集main.c 状态机串口助手看帧长与字段上位机解析脚本独立脚本与串口助手原始字节比对有一个习惯我后来一直保持每次改完协议先用固定测试向量跑一遍组帧和解析确认字节级一致再上硬件。曾经因为只改了发送端校验、忘了同步接收端排查了大半天才发现是两端版本不一致这种错在资源受限项目里特别费时间。从那以后我每次动帧格式都强制走一遍「测试向量 → 双端联调 → 现场复测」的流程希望帮到你。本文还有配套的精品资源点击获取