嵌入式STM32F103毕设项目实战:从传感器采集到低功耗通信的完整链路实现
最近在帮学弟学妹们看嵌入式毕设,发现一个挺普遍的现象:很多基于STM32F103的项目,功能实现得比较“碎”。比如,单独驱动个DHT11温湿度传感器能行,串口打印数据也没问题,但一旦要把传感器数据通过无线模块发出去,还得兼顾低功耗,代码就变得一团乱麻,各种全局变量满天飞,while(1)里塞满了延时和标志位检查。
这其实反映了从“单片机实验”到“嵌入式系统设计”的思维跨越。今天,我就以“温湿度监测+LoRa无线传输”这个非常典型的毕设场景为例,分享一套在STM32F103C8T6这类资源受限的Cortex-M3平台上,构建一个完整、可维护、可演示的毕设项目的实战思路。

1. 痛点分析:为什么你的毕设代码看起来“不专业”?
在动手之前,我们先梳理一下常见问题,这能帮助我们明确设计目标:
- 功能碎片化,耦合度高:传感器读取、数据处理、通信发送的代码全部堆在
main.c里,修改任何一部分都可能引发连锁错误。 - 没有功耗管理:设备需要电池供电时,代码依然在
while(1)里空转,电量消耗极快,不符合物联网设备的基本要求。 - 代码不可维护:大量使用魔法数字(如
0x48),硬件相关的引脚定义散落在各个.c文件,任务调度全靠delay_ms和标志位,可读性差。 - 缺乏健壮性考量:通信失败怎么办?传感器偶尔读取出错怎么处理?程序跑飞了如何自救?这些在实际产品中必须考虑的问题,在毕设中常被忽略。
- 可演示性差:上电后只有一个LED在闪,无法直观展示数据流和系统状态,不利于答辩展示。
2. 技术选型:为你的项目选择合适的技术栈
针对“温湿度+LoRa上传”的需求,我们来做个简单的选型对比:
操作系统 vs 裸机
- 裸机开发(前后台系统):适合逻辑简单、对实时性要求不苛刻的小项目。优点是资源占用极小,启动快。难点在于需要自己规划好状态机,管理好各任务的时间片,复杂度稍高就容易变成“面条代码”。
- FreeRTOS:即使是在只有64KB Flash的STM32F103C8T6上,裁剪后的FreeRTOS内核也完全能跑起来。它提供了任务调度、队列、信号量等机制,能让你以更清晰的方式组织代码。例如,可以创建“Sensor_Task”负责采集,“Comm_Task”负责发送。对于毕设而言,我强烈建议尝试使用FreeRTOS,这不仅能大幅提升代码结构,更是你简历上的一个亮点。
通信模块选型
- ESP8266/ESP32 WiFi:优势是直接接入互联网,方便,资料多。劣势是功耗较高(即使深度睡眠),对电池供电不友好,且在某些无WiFi覆盖的场合(如农田、仓库)不适用。
- LoRa模块(如SX1278):优势是传输距离远(可达数公里),功耗极低,非常适合野外、楼宇等远距离低速率数据传输场景。劣势是需要额外的网关来接入网络,速率慢。对于需要体现“低功耗”和“远距离”特性的毕设,LoRa是更出彩的选择。
本例我们确定的技术栈为:STM32F103C8T6 + FreeRTOS + DHT11 + LoRa模块(AT指令版)。使用AT指令版的LoRa模块可以简化射频部分的开发,让我们更专注于系统集成。
3. 核心实现细节:构建完整的数据链路
一个健壮的系统需要分层设计。我们可以规划为:硬件驱动层、数据处理层、任务应用层。
3.1 DHT11单总线驱动编写
DHT11的时序要求严格,必须用微秒级延时。在FreeRTOS中,直接使用vTaskDelay()不行,因为它最小单位是Tick(通常1ms)。我们需要一个精准的微秒延时函数,通常用SysTick或定时器实现。
// dht11.h - 集中管理硬件定义和接口
#ifndef __DHT11_H
#define __DHT11_H
#include “stm32f10x.h”
// 引脚定义,修改这里即可适配不同硬件连接
#define DHT11_GPIO_PORT GPIOB
#define DHT11_GPIO_PIN GPIO_Pin_12
#define DHT11_RCC_APB2Periph RCC_APB2Periph_GPIOB
// 函数声明
void DHT11_Init(void);
uint8_t DHT11_Read_Data(uint8_t *temperature, uint8_t *humidity);
#endif
// dht11.c - 关键时序实现
#include “dht11.h”
#include “delay.h” // 需要实现一个us级延时库
static void DHT11_IO_Out(void) { /* 配置为推挽输出 */ }
static void DHT11_IO_In(void) { /* 配置为浮空输入 */ }
static uint8_t DHT11_Read_Bit(void) {
// 等待低电平结束
while(DHT11_DATA_IN() == 0);
// 延时40us后检测引脚电平,高则为‘1’,低则为‘0’
delay_us(40);
if(DHT11_DATA_IN() == 1) return 1;
else return 0;
}
uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi) {
uint8_t buf[5] = {0};
uint8_t i, j;
// 主机发起开始信号
DHT11_IO_Out();
DHT11_DATA_OUT(0);
delay_ms(18); // 至少18ms低电平
DHT11_DATA_OUT(1);
delay_us(30);
// ... 省略等待从机响应和40位数据读取的代码 ...
// 校验和数据
if(buf[4] == (buf[0]+buf[1]+buf[2]+buf[3])) {
*humi = buf[0];
*temp = buf[2];
return 0; // 成功
}
return 1; // 校验失败
}
3.2 低功耗与RTC唤醒
要实现低功耗,就不能让MCU一直全速运行。我们可以让系统大部分时间处于STOP模式(功耗极低),然后通过RTC(实时时钟)定时唤醒。
- 配置RTC:使用外部低速晶振LSE(32.768kHz)为RTC提供时钟,精度高且功耗低。
- 配置唤醒中断:设置RTC的唤醒周期(例如每10秒一次)。
- 进入STOP模式:在采集并发送完数据后,关闭外设时钟,调用
PWR_EnterSTOPMode()进入停止模式。 - 唤醒处理:RTC唤醒中断发生后,系统会继续执行中断服务程序,然后回到主循环。注意:从STOP模式唤醒后,系统时钟会重置为HSI,需要重新配置系统时钟。
// power_mgr.c 节选
void Enter_LowPower_Mode(void) {
// 1. 挂起FreeRTOS调度器,防止任务切换
vTaskSuspendAll();
// 2. 关闭不需要的外设时钟 (如GPIO, USART等)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, DISABLE);
// 3. 配置所有未使用的IO为模拟输入以省电
GPIO_Analog_Config();
// 4. 进入STOP模式(可通过RTC或外部中断唤醒)
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
// 5. 唤醒后,恢复系统时钟(HSE/PLL)
SystemClock_Config();
// 6. 重新初始化必要的外设
USART1_Init();
// 7. 恢复FreeRTOS调度器
xTaskResumeAll();
}
3.3 USART与LoRa模块的AT指令通信
AT指令通信的关键在于稳定性和超时处理。绝不能因为一次没收到回复就死等。
// lora_driver.c 节选
#define LORA_UART USART1
#define LORA_RECV_TIMEOUT_MS 2000 // 接收超时2秒
// 发送AT指令并等待预期响应
uint8_t LoRa_Send_AT_Cmd(const char *cmd, const char *expect_resp, uint16_t timeout_ms) {
uint8_t recv_buf[256] = {0};
uint16_t recv_len = 0;
uint32_t start_tick = xTaskGetTickCount();
// 清空接收缓冲区
UART_ClearRxBuffer(LORA_UART);
// 发送指令
printf(“%s\r\n”, cmd); // 假设printf重定向到LORA_UART
// 循环读取,直到收到预期响应或超时
while((xTaskGetTickCount() - start_tick) < pdMS_TO_TICKS(timeout_ms)) {
if(UART_ReceiveString(LORA_UART, recv_buf, &recv_len)) {
if(strstr((char*)recv_buf, expect_resp) != NULL) {
return 0; // 成功
}
}
vTaskDelay(pdMS_TO_TICKS(10)); // 让出CPU,避免忙等
}
return 1; // 超时失败
}
// 在任务中调用
void LoRa_Send_Data_Task(void *pvParameters) {
uint8_t temp, humi;
char send_buf[64] = {0};
// 初始化LoRa模块
if(LoRa_Send_AT_Cmd(“AT+CFG=433500000,5,0,0,7,0,0,0,0,3000,8,8”, “OK”, 3000) != 0) {
// 初始化失败,可以重试几次或记录错误
LOG_ERROR(“LoRa Init Failed!”);
}
while(1) {
// 等待传感器数据就绪信号量
if(xSemaphoreTake(xSemaphore_SensorReady, portMAX_DELAY) == pdTRUE) {
// 读取全局变量中的传感器数据(需注意互斥访问)
temp = g_current_temp;
humi = g_current_humi;
// 组装JSON格式数据
snprintf(send_buf, sizeof(send_buf),
“{\“temp\”: %d, \“humi\”: %d}”, temp, humi);
// 发送数据 (假设模块支持直接发送)
LoRa_Send_AT_Cmd(send_buf, “OK”, 2000);
}
}
}
4. 性能与安全性:让项目从“能用”到“可靠”
- ADC采样抗干扰:如果使用STM32内部的ADC测量电池电压,需要在采样通道并联一个0.1uF的滤波电容,软件上可以采用多次采样取平均值的算法。
- 通信重传机制:上述
LoRa_Send_AT_Cmd函数已经有了超时判断。我们可以在此基础上封装一个带重试的发送函数,失败3次后再上报错误。 - Flash写保护:如果需要记录历史数据到片内Flash,在写操作前一定要先解锁Flash,写完立即上锁,防止程序跑飞误擦写。同时,注意Flash的寿命(约1万次擦写),需要做均衡磨损处理。
- 看门狗:独立看门狗IWDG和窗口看门狗WWDG都配上。IWDG用于防止程序死锁,喂狗任务放在低优先级。WWDG用于防止程序跑飞,喂狗放在关键循环中。切记:在进入STOP模式前,看门狗必须被暂停或妥善处理,否则会复位。
5. 生产环境避坑指南(硬件篇)
这些是实验室开发容易忽略,但实际做产品时必须考虑的问题:
- 晶振负载电容匹配:STM32的8MHz外部晶振(HSE)两脚对地需要接两个20pF左右的负载电容。电容值不匹配会导致晶振不起振或频率不准。最好参考芯片数据手册和晶振厂家推荐值。
- JTAG/SWD引脚复用冲突:PA13, PA14, PA15, PB3, PB4这些调试引脚,在上电后默认是JTAG功能。如果你要用它们作为普通GPIO(比如控制LED),必须在程序一开始就禁用JTAG,仅使能SWD(占用引脚少)。
// 在SystemInit()之后,main()最开始调用 void JTAG_Disable(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); // 禁用JTAG,保留SWD } - 电源去耦:每个芯片的VCC和GND之间,尽可能靠近引脚的地方,都要并联一个0.1uF的陶瓷电容和一个10uF的钽电容,用于滤除高频和低频噪声。
- 信号完整性:LoRa模块的射频天线接口要严格按照手册设计匹配电路,天线周围要净空,不要走其他信号线。

总结与展望
通过以上步骤,我们搭建了一个结构清晰、低功耗、健壮的STM32F103毕设项目框架。它不仅仅实现了功能,更展示了你对嵌入式系统设计的多方面思考。
这个项目还有很大的扩展空间,非常适合作为你深入学习的起点:
- 扩展为多节点网络:可以再做一个相同的节点,让它们将数据发送到同一个LoRa网关,网关再通过4G或以太网上传到云服务器。你可以研究一下简单的LoRaWAN协议或自定一个轻量级的TDMA(时分多址)协议来避免无线冲突。
- 加入OTA升级功能:将Flash划分为Bootloader区和Application区。Bootloader通过串口或LoRa接收新的固件包,校验后写入Application区,实现远程无线升级。这是物联网设备的必备技能。
- 接入云平台:在网关端,可以将数据转发到阿里云IoT、腾讯云IoT或ThingsBoard等开源平台,实现数据可视化。
嵌入式开发最有魅力的地方在于,你能亲眼看到自己写的代码如何与物理世界互动。建议你对照这个思路,亲手复现一遍,过程中遇到的每一个错误和解决过程,都是你宝贵的经验。祝你毕设顺利,收获满满!
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)