简介:这份资源面向物联网与嵌入式开发初学者及电子竞赛选手,围绕MSP430G2553超低功耗微控制器,给出温度与光照强度采集并无线传输的完整工程实现,解决环境监测中传感器驱动、数据显示与点对点通信的整合问题。压缩包共43个文件、约78KB,以C源文件与头文件承载主程序及DS18B20、LCD驱动,配合CCS工程配置、链接命令文件、编译中间产物与map映射文件,构成可直接导入调试的发送端与接收端双工程。已有320人学习下载,读者可从中获得DS18B20单总线测温、光照信号采集处理、12864液晶实时显示以及无线收发模块编码解码的完整参考代码,并借助工程配置与映射文件理解MSP430开发流程,适合作为课程设计或小型物联网项目的起步模板。
1. 一块 LaunchPad 板子,把温度和光照送进无线链路
手头有一块 MSP430G2553 LaunchPad,板载仿真器、两颗 LED、一个按键,价格便宜到可以随便造,但片上资源也紧得让人清醒:16 位 RISC 内核、512 字节 RAM、16KB Flash,没有硬件浮点,没有现成的无线外设。这个项目的定位就是在这种极限条件下把温度和光照两路模拟量采上来,再通过无线链路发出去——听起来简单,真正动手才会发现每一字节 RAM 都要算着花。
它适合两类人:一类是刚接触 MSP430 系列、想找一个完整闭环练手的嵌入式新手,从 ADC 采样、串口打印一路做到无线收发;另一类是手上已有无线模块、需要把低功耗节点快速搭起来的开发者,可以直接拿这套采集与发送逻辑当骨架。温度、光照这两个量选得很务实,一个走片内温度传感器,一个走外接光敏电阻,覆盖了内部通道和外部通道两种 ADC 用法。
2. 系统架构与硬件选型:两路模拟量怎么变成一帧无线数据
2.1 整体链路拆解
整个系统按数据流可以切成四段:传感段、采集段、处理段、传输段。传感段是片内温度传感器加一个光敏电阻分压电路;采集段是 MSP430G2553 内部的 10 位 ADC10,通过多路复用器在两个通道之间切换;处理段是 CPU 把原始码值换算成温度和光照的工程量,再打包成一帧带帧头、通道号、数据、校验的字节流;传输段交给外接的无线串口模块,用 UART 把帧发出去。
这样切分的好处是每段都能单独验证。传感器可以用万用表量分压点电压,ADC 可以用调试器看 ADC10MEM 寄存器,串口可以用 USB-TTL 直接抓,无线模块可以先用两块板子对发固定字符串确认通路。任何一段出问题都不会牵连其他段,排查时不用面对一个黑匣子。
选型上有几个关键决定。主控用 MSP430G2553 而不是更常见的 STM32,核心原因是低功耗场景下 MSP430 的待机电流确实低,而且 LaunchPad 自带仿真器省掉一个调试器成本。ADC 用片内 ADC10 而不是外挂芯片,是因为 10 位精度对温度和光照这种慢变量足够,省掉一路 SPI/I2C 布线。无线部分没有用片内方案(G2553 本身也没有),而是走 UART 透传模块,这样无线协议栈的复杂度被模块厂商吃掉了,主控只管发字节。
2.2 引脚分配与电气连接
引脚分配要在动手焊之前定死,后面改起来很烦。下面这张表是我一般会先画出来的分配方案:
| 功能 | 引脚 | 说明 |
|---|---|---|
| 片内温度传感器 | ADC10 内部通道 10 | 无需外部引脚,靠 INCH_10 选择 |
| 光照分压点 | P1.1 / A1 | 光敏电阻与固定电阻分压后接入 |
| UART TX | P1.2 | 接无线模块 RX |
| UART RX | P1.3 | 接无线模块 TX |
| 采样指示 LED | P1.0 | 每次采样翻转,方便肉眼确认节拍 |
| 参考电压 | 内部 1.5V 或 VCC | 由 REFON 与 SREF 决定 |
光照那一路的电路很朴素:光敏电阻一端接 VCC,另一端接固定电阻到地,中点进 P1.1。光照越强光敏电阻阻值越小,中点电压越高,ADC 读数越大。固定电阻的取值要跟光敏电阻的实际阻值范围匹配,常见做法是先拿万用表在目标光照范围内量一遍光敏电阻的阻值,取几何中值附近的电阻,这样分压点不会一直贴着 0 或 VCC,ADC 的有效位数才用得满。
提示:G2553 的 ADC10 参考电压可以选内部 1.5V 或 2.5V,也可以直接用 VCC。用内部参考时记得给 REFON 置位并等参考稳定,否则前几次采样会飘。
2.3 时钟与低功耗策略
G2553 默认的 DCO 大约 1MHz,够用。ADC10 的采样时钟可以独立选,用 SMCLK 或 ADC10OSC 都行,慢变量场景下用 ADC10OSC 更省事,不用去配分频。低功耗这块,如果只是做采集发送,没必要上复杂的 LPM 状态机,主循环里采完就发、发完进 LPM3 等定时器唤醒,已经能把平均电流压下来。真正要抠功耗的时候再考虑关 ADC、关参考源、把 UART 也挂起。
3. 采集与换算:ADC10 两通道切换的代码拆解
3.1 ADC10 初始化
初始化这段是整个采集的地基,配置错了后面全是玄学。核心是选通道序列、选参考、选采样保持时间。
#include <msp430g2553.h> // 采样结果缓存,两路各一个 volatile unsigned int adc_temp_raw = 0; volatile unsigned int adc_light_raw = 0; void adc10_init(void) { // 参考电压选内部 1.5V,参考源打开 ADC10CTL0 = SREF_1 + REFON + REF2_5V + ADC10SHT_3 + ADC10ON; // 采样保持时间拉长,慢变量不需要高速,长一点更稳 // 时钟源用 ADC10OSC,分频 1 ADC10CTL1 = INCH_10 + ADC10DIV_0 + ADC10SSEL_0 + CONSEQ_0; // 使能 ADC,等待参考稳定 __delay_cycles(5000); }逐项说清楚。SREF_1表示参考电压取内部参考,配合REFON打开参考源;REF2_5V选 2.5V 档,如果光照分压点电压范围较小,也可以换成 1.5V 档提高分辨率。ADC10SHT_3是最长的采样保持时间,慢变量场景下多等几个时钟周期换来的是读数稳定,这笔账划算。INCH_10是片内温度传感器通道,CONSEQ_0表示单通道单次转换,每次换通道前重新写ADC10CTL1的 INCH 位即可。__delay_cycles(5000)是等内部参考建立,少了这一步前几次转换结果会明显偏低。
3.2 两通道轮询采样
通道切换的关键是每次转换前改INCH位,然后重新触发。下面这个函数把两路都采一遍:
unsigned int adc10_read_channel(unsigned char channel) { // 改通道号,保留其他配置位 ADC10CTL1 = (ADC10CTL1 & 0xF000) | (channel << 12); // 使能转换,软件触发 ADC10CTL0 |= ENC + ADC10SC; // 等待转换完成 while (ADC10CTL1 & ADC10BUSY) ; return ADC10MEM; } void sample_both(void) { adc_temp_raw = adc10_read_channel(10); // 片内温度通道 adc_light_raw = adc10_read_channel(1); // A1,光照分压 }(ADC10CTL1 & 0xF000) | (channel << 12)这个写法是保留高 4 位配置、只改 INCH 字段,避免每次切通道把时钟源和分频也一起冲掉。ENC + ADC10SC是使能转换加软件启动,两个位要一起置。while (ADC10CTL1 & ADC10BUSY)是等忙标志清零,别用固定延时替代,不同通道的转换时间不完全一样。返回的ADC10MEM是 10 位右对齐结果,范围 0 到 1023。
3.3 原始码值换算成工程量
片内温度传感器的换算公式在器件手册里有给,G2553 这一代大致是每摄氏度对应固定的 ADC 码值偏移,常见做法是用两点校准:常温下读一个值,再用手捂热读一个值,反推斜率和截距。光照没有绝对单位,一般就报相对百分比或者直接报原始码值,标定时用照度计对一遍。
// 温度换算:用两点校准得到的斜率与截距 // 假设校准后 slope = 0.35 摄氏度/码值,offset 为截距 float raw_to_celsius(unsigned int raw) { return (float)raw * 0.35f - 50.0f; } // 光照换算成 0~100 的相对百分比 unsigned char raw_to_light_percent(unsigned int raw) { unsigned long pct = (unsigned long)raw * 100UL / 1023UL; return (unsigned char)pct; }raw_to_celsius里的 0.35 和 -50 是占位值,实际必须自己校准后替换,直接抄手册典型值误差可能有好几度。raw_to_light_percent用unsigned long做中间量是为了避免 16 位乘法溢出,raw最大 1023,乘 100 会到 102300,超过 16 位范围,这一步翻车过的人不少。百分比只是相对量,真要物理照度还得靠照度计标定曲线。
4. 无线发送与帧格式:UART 透传模块怎么用才不丢包
4.1 帧结构设计
无线透传模块本质上就是把 UART 字节流原样搬到空中,所以帧格式得自己定。一个够用又不啰嗦的帧长这样:
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头 | 2 字节 | 固定 0xAA 0x55,用于接收端对齐 |
| 通道号 | 1 字节 | 0x01 温度,0x02 光照 |
| 数据高字节 | 1 字节 | 工程量定点表示的高 8 位 |
| 数据低字节 | 1 字节 | 低 8 位 |
| 校验 | 1 字节 | 前面所有字节异或 |
| 帧尾 | 1 字节 | 固定 0x0D |
帧头用两个字节而不是一个,是为了降低误同步概率;校验用异或够简单,接收端算一遍对不上就丢帧。数据用定点表示(比如温度乘 10 存成整数)而不是直接发 float,是因为 float 在 16 位机上占 4 字节且收发两端字节序容易对不上,定点省事。
4.2 UART 初始化与发送函数
G2553 的 USCI_A0 配成 9600 8N1,跟大多数透传模块的出厂设置对齐:
void uart_init(void) { // P1.2 设为 TX,P1.3 设为 RX P1SEL |= BIT2 + BIT3; P1SEL2 |= BIT2 + BIT3; // 复位 USCI 后配置 UCA0CTL1 |= UCSWRST; UCA0CTL1 |= UCSSEL_2; // SMCLK 作时钟源 UCA0BR0 = 104; // 1MHz / 9600 ≈ 104 UCA0BR1 = 0; UCA0MCTL = UCBRS_1; // 调制位,减少波特率误差 UCA0CTL1 &= ~UCSWRST; // 释放复位 } void uart_send_byte(unsigned char b) { while (!(IFG2 & UCA0TXIFG)) ; UCA0TXBUF = b; } void send_frame(unsigned char ch, unsigned int value) { unsigned char buf[7]; buf[0] = 0xAA; buf[1] = 0x55; buf[2] = ch; buf[3] = (value >> 8) & 0xFF; buf[4] = value & 0xFF; buf[5] = buf[0] ^ buf[1] ^ buf[2] ^ buf[3] ^ buf[4]; buf[6] = 0x0D; for (unsigned char i = 0; i < 7; i++) uart_send_byte(buf[i]); }UCA0BR0 = 104是按 1MHz SMCLK 算的,如果 DCO 校准到别的频率,这个值要重算,波特率对不上接收端全是乱码。UCBRS_1是调制位,用来补偿整数分频带来的误差,9600 下这个值比较合适。uart_send_byte里等UCA0TXIFG是必须的,不等直接写UCA0TXBUF会覆盖还没发出去的字节,表现就是偶尔丢一个字节,这种问题抓起来很费劲。send_frame里先拼完整帧再逐字节发,避免边算校验边发导致时序错乱。
4.3 接收端对齐与校验
接收端如果也是 MSP430,思路是状态机:等 0xAA,再等 0x55,然后按字段收满,最后算校验。用中断收比轮询稳,UCA0RXIFG触发后把字节塞进环形缓冲,主循环里再解析。校验对不上就整帧丢弃,重新找帧头,不要试图修补,修补逻辑往往比丢帧更容易引入新 bug。
注意:透传模块的空速和串口波特率是两回事,模块配置工具里那两个参数别搞混,串口对上了空速不匹配照样收不到。
5. 避坑与排查:那些让读数飘、让帧丢失的坑
5.1 温度读数一直偏高或偏低
现象是串口打出来的温度跟室温差好几度,而且换一块板子数值又不一样。原因多半是直接用了手册典型斜率没做校准,片内温度传感器本身有批次离散,参考电压的实际值也有偏差。解决办法是两点校准:把板子放在已知温度环境里读一个码值,再换一个温度点读第二个,解出斜率和截距写进换算函数。校准后误差能压到 1 摄氏度以内。
5.2 光照通道读数贴着 0 或 1023 不动
现象是光照 ADC 值要么接近 0 要么接近满量程,中间几乎没变化。原因是分压电阻取值跟光敏电阻的实际阻值范围不匹配,分压点电压一直落在参考电压的两端。解决办法是先断开 ADC,用万用表量目标光照范围内分压点的电压,再反推合适的固定电阻;如果范围仍然偏窄,把参考电压从 2.5V 换成 1.5V 档,相当于把 ADC 的量化台阶变小。
5.3 无线端偶尔丢帧
现象是接收端大部分帧正常,但每隔一段时间少一帧。原因通常是发送端没等UCA0TXIFG就写下一个字节,或者接收端解析时把帧头字节当数据吃掉了。解决办法是发送侧严格等发送标志,接收侧用状态机而不是简单按固定长度切片,帧头对不上就回退一个字节重新找。另外透传模块在空速低的时候有发送缓冲,连续快发会溢出,帧间留几毫秒间隔更稳。
5.4 参考电压没稳定就采样
现象是上电后前几次温度读数明显偏低,过一会儿才正常。原因是REFON置位后内部参考需要建立时间,代码里没等就直接转换。解决办法是在初始化末尾加足够的延时(几千个时钟周期),或者先做一次丢弃转换,把第一次结果扔掉不用。
5.5 波特率误差导致乱码
现象是接收端能收到字节但全是乱码,或者偶尔对偶尔错。原因是 DCO 实际频率跟标称值有偏差,UCA0BR0按 1MHz 算出来的分频在偏差下误差超标。解决办法是用UCBRS调制位补偿,或者干脆用外部 32.768kHz 晶振做 UART 时钟源,低频下分频误差小得多。G2553 LaunchPad 上那颗晶振就是干这个用的。
6. 二次开发:把这套骨架改成多节点采集网络
这套代码最值钱的地方不是温度光照本身,而是「采样—换算—打包—发送」这条链路的骨架。要往二次开发走,第一步是把帧格式里的通道号扩展成节点号加通道号,让一个接收端能区分多个发送节点。具体做法是给每个节点烧录时写入一个 1 字节的 ID,放在帧的第 3 字节,接收端按 ID 分表存储。
第二步是把主循环改成定时唤醒。用 Timer_A 配一个比如 2 秒的周期中断,中断里置标志,主循环看到标志才采一次发一次,其余时间进 LPM3。这样平均电流能从毫安级降到几十微安级,电池供电才撑得住。改造时注意 ADC 和 UART 在低功耗模式下要重新使能,别唤醒后忘了开。
第三步是接收端。如果接收端也用 G2553,环形缓冲加状态机解析就够;如果要接电脑,用 USB-TTL 把透传模块接到上位机,上位机按同样的帧格式解析即可。验证方法很直接:先让节点发固定值(比如温度恒发 250),确认接收端解析正确,再换回真实采样,这样能把协议问题和采集问题分开。
| 改造方向 | 改动点 | 验证方式 |
|---|---|---|
| 多节点 | 帧加节点 ID,接收端分表 | 两个节点同时发,看是否串数据 |
| 低功耗 | Timer_A 周期唤醒 + LPM3 | 万用表测平均电流 |
| 上位机 | USB-TTL + 帧解析脚本 | 固定值先跑通再换真实数据 |
有个习惯我后来一直保持:任何无线链路改造,都先用固定值把协议跑通,再接入真实传感器。早年我图省事直接上真实数据调,结果丢帧和读数飘混在一起,查了一整天才发现是两个独立问题叠着。从那以后每次改帧格式,我都强制先发固定值走一遍全链路,确认协议没问题再动采集部分。希望帮到你。
本文还有配套的精品资源,点击获取