1. 项目背景与参赛概况
去年参加的第十五届省级单片机竞赛是我大学生涯中最具挑战性的技术赛事之一。这场为期三天的封闭式比赛要求我们基于STM32F103C8T6开发板完成一个完整的物联网监测系统,包含传感器数据采集、无线传输和上位机显示三大模块。我们团队最终获得了二等奖,但在开发过程中踩过的坑、解决的问题比获奖本身更值得记录。
这次复盘主要针对比赛中几个关键技术点的实现方案、调试过程中遇到的典型问题以及后续改进思路。不同于普通实验项目,竞赛环境下的开发具有三个显著特点:一是所有代码必须从零开始手写(禁用库函数直接调用),二是外设资源严格受限(仅提供基础传感器模块),三是评分标准特别注重低功耗优化。这些限制条件让看似简单的功能实现起来充满挑战。
2. 硬件架构设计解析
2.1 核心器件选型考量
比赛提供的标准套件包含:
- 主控:STM32F103C8T6(72MHz Cortex-M3,64KB Flash)
- 传感器:DHT11温湿度、MQ-2烟雾、HC-SR04超声波
- 通信模块:ESP8266-01S WiFi模组
- 显示设备:0.96寸OLED(SSD1306驱动)
在资源受限条件下,我们做了以下关键决策:
- 放弃使用硬件I2C驱动OLED,改用软件模拟I2C
- 实测发现硬件I2C在调试阶段频繁死锁
- GPIO模拟方案虽然占用CPU但稳定性更好
- 烟雾传感器采用分时供电策略
- MQ-2预热电流达150mA,持续供电会导致电源波动
- 通过MOSFET控制每30秒供电5秒采集数据
2.2 低功耗设计实现
比赛评分细则中低功耗占比达30%,我们通过以下措施将系统平均电流控制在8mA以下:
c复制// 关键代码:外设分时供电控制
void PowerManage_Task(void)
{
static uint32_t tick;
if(HAL_GetTick() - tick > 30000) {
MQ2_POWER_ON();
osDelay(5000); // 预热5秒
Adc_GetMQ2Value();
MQ2_POWER_OFF();
tick = HAL_GetTick();
}
}
注意事项:使用MOSFET控制传感器电源时,务必在G极添加下拉电阻(我们最初因漏接导致上电瞬间误触发,浪费1小时排查)
3. 软件框架搭建
3.1 实时操作系统选型
在FreeRTOS和裸机之间,我们最终选择基于CMSIS-RTOS的简化调度方案,主要考虑:
- FreeRTOS完整版占用超过15KB ROM
- 自定义任务调度器仅需2KB空间
- 关键任务只有三个:
- 传感器数据采集(周期1s)
- WiFi数据传输(事件触发)
- OLED刷新(周期200ms)
任务优先级设置如下表:
| 任务名称 | 优先级 | 堆栈大小 | 执行周期 |
|---|---|---|---|
| Sensor | 2 | 512 | 1s |
| WiFi | 3 | 1024 | 事件触发 |
| Display | 1 | 256 | 200ms |
3.2 通信协议设计
由于ESP8266模块仅支持AT指令,我们设计了紧凑的二进制协议:
code复制[HEAD][LEN][TYPE][PAYLOAD][CRC]
0x55 1B 1B N 2B
协议特点:
- 包头固定0x55用于帧同步
- 长度字段包含TYPE和PAYLOAD
- 类型字段区分传感器数据类型
- CRC采用CRC-16/CCITT算法
踩坑记录:最初使用JSON格式传输,发现模块在发送超过100字节时频繁崩溃,改为二进制协议后稳定性大幅提升
4. 关键问题排查实录
4.1 超声波测距异常
比赛现场出现HC-SR04持续返回0值的问题,通过以下步骤解决:
- 用逻辑分析仪捕捉Trig和Echo信号
- 发现Echo脉宽偶尔超过60ms(超出定时器捕获范围)
- 修改定时器配置:
c复制htim3.Init.Period = 0xFFFF; // 原值20000
htim3.Init.Prescaler = 72-1; // 原值720-1
- 添加软件滤波:
c复制#define SAMPLE_NUM 5
uint32_t Get_Distance(void)
{
uint32_t sum = 0;
for(int i=0; i<SAMPLE_NUM; i++){
sum += Single_Measure();
osDelay(10);
}
return sum/SAMPLE_NUM;
}
4.2 WiFi断连问题
ESP8266在连续工作2小时后会出现断连,最终发现两个关键因素:
- 模块供电不足:在3.3V线路上添加470μF电容
- AT指令超时:修改重试机制
c复制int ESP_SendCmd(const char* cmd, uint32_t timeout)
{
for(int i=0; i<3; i++){ // 最大重试3次
if(ESP_OK == Send_AT(cmd, timeout)){
return 0;
}
osDelay(100);
ESP_HardwareReset(); // 第三次失败后硬件复位
}
return -1;
}
5. 性能优化技巧
5.1 内存管理策略
由于可用RAM仅20KB,我们采用以下优化手段:
- 使用共用体减少变量占用:
c复制typedef union {
struct {
float temp;
float humidity;
} env;
uint32_t gas;
} SensorData;
- 禁用标准库printf,改用精简版串口输出:
c复制void UART_Printf(const char *fmt, ...)
{
char buf[64]; // 固定小缓冲区
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 100);
va_end(args);
}
5.2 实时性保障
为确保关键任务及时响应:
- 在ADC采样期间关闭全局中断
- 减少DMA传输被中断的概率
- 使用定时器硬件触发采样
- 避免软件延时带来的抖动
c复制void ADC_Config(void)
{
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, 3);
}
6. 后续改进方向
通过赛后总结,我们认为系统还有三处可优化空间:
- 通信加密:当前协议未加密,可添加AES-128加密
- OTA升级:通过WiFi实现固件远程更新
- 动态功耗调整:根据环境数据变化率自适应调整采样频率
这次比赛最大的收获不是奖项,而是真正理解了嵌入式开发中"资源受限"的含义。在后来参与的商业项目中,这段经历让我特别注重代码的时空效率,这种思维方式比任何具体技术都更有价值。