1. 项目概述
这个基于FreeRTOS的空气检测仪项目是我去年底完成的一个嵌入式系统开发实践。作为一个长期从事嵌入式开发的工程师,我一直想做一个能够整合多种传感器数据的便携式设备。这个项目不仅实现了基础的环境监测功能,还尝试了RTOS在多任务管理中的应用。
整套系统运行在STM32系列MCU上,通过FreeRTOS实现了温湿度监测、PM2.5检测、电池电量监控、RTC时间显示等功能的并行处理。特别值得一提的是,我在项目中采用了模块化设计思想,每个功能都封装成独立的任务,通过消息队列进行通信,这种架构在实际工业应用中非常实用。
2. 硬件架构设计
2.1 核心硬件选型
在选择硬件组件时,我主要考虑了以下几个因素:
- 低功耗需求(设备需要电池供电)
- 传感器精度要求
- 开发成本控制
- 未来可扩展性
最终确定的硬件配置如下:
| 组件类型 | 具体型号 | 关键参数 | 选择理由 |
|---|---|---|---|
| MCU | STM32F103C8T6 | 72MHz Cortex-M3, 64KB Flash, 20KB RAM | 性价比高,FreeRTOS支持完善 |
| 温湿度传感器 | SHT30 | ±2%RH精度,±0.2℃精度 | I2C接口,低功耗 |
| PM2.5传感器 | PMS5003 | 0-1000μg/m³测量范围 | 激光散射原理,精度高 |
| 显示屏 | 1.3寸OLED | 128x64分辨率,I2C接口 | 低功耗,显示效果好 |
| RTC芯片 | DS3231 | ±2ppm精度 | 内置温度补偿,走时精准 |
2.2 电源管理设计
由于是便携式设备,电源管理尤为重要。我设计了双电源供电方案:
- 锂电池供电(3.7V 18650电池)
- USB 5V输入充电
通过TPS61030升压芯片将电池电压稳定在3.3V,同时使用STM32内置的12位ADC监测电池电压。为了准确反映电量,我采用了分段线性化的方法进行电量估算:
code复制// 电池电量估算算法
uint8_t GetBatteryPercent(float voltage)
{
if(voltage > 4.1) return 100;
else if(voltage > 3.9) return 80 + (voltage-3.9)*100;
else if(voltage > 3.7) return 30 + (voltage-3.7)*250;
else if(voltage > 3.5) return 10 + (voltage-3.5)*100;
else return 0;
}
3. FreeRTOS任务设计
3.1 任务划分与优先级设置
在FreeRTOS中,我将系统功能划分为以下几个主要任务:
| 任务名称 | 优先级 | 堆栈大小 | 主要功能 |
|---|---|---|---|
| SensorTask | 3 | 512字 | 传感器数据采集 |
| DisplayTask | 2 | 1024字 | 界面显示更新 |
| KeyScanTask | 1 | 256字 | 按键扫描处理 |
| PowerTask | 2 | 256字 | 电源管理 |
| LogTask | 1 | 256字 | 日志记录 |
注意:FreeRTOS优先级数值越大优先级越高,我通常保留最高优先级(4)给关键系统任务
3.2 任务间通信机制
为了实现任务间的数据同步,我采用了多种FreeRTOS通信机制:
-
消息队列:用于传感器数据的传递
c复制xQueueHandle xSensorDataQueue; xSensorDataQueue = xQueueCreate(5, sizeof(SensorData_t)); -
信号量:用于显示刷新同步
c复制
xSemaphoreHandle xDisplaySemaphore; xDisplaySemaphore = xSemaphoreCreateBinary(); -
事件组:用于系统状态通知
c复制
EventGroupHandle_t xSystemEvents; xSystemEvents = xEventGroupCreate();
4. 关键功能实现细节
4.1 传感器数据采集
传感器采集任务运行周期为1秒,采用状态机模式处理不同传感器的读取时序:
c复制void vTaskSensor(void *pvParameters)
{
SensorState_t eState = SENSOR_INIT;
TickType_t xLastWakeTime = xTaskGetTickCount();
while(1) {
switch(eState) {
case SENSOR_INIT:
// 初始化传感器
break;
case SENSOR_READ_TEMP:
// 读取温湿度
break;
case SENSOR_READ_PM25:
// 读取PM2.5
break;
}
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
}
}
对于PM2.5传感器,我特别处理了数据校验问题。PMS5003采用串口通信,数据包格式为:
code复制0x42 0x4D + 28字节数据 + 2字节校验和
校验算法实现如下:
c复制uint16_t CalcPM25Checksum(uint8_t *data, uint8_t len)
{
uint16_t sum = 0;
for(uint8_t i=0; i<len; i++) {
sum += data[i];
}
return sum;
}
4.2 显示界面实现
显示系统采用了分层架构:
- 底层驱动层:封装OLED基本操作
- 中间件层:实现GUI组件(文本框、进度条等)
- 应用层:具体页面逻辑
页面切换通过状态机实现,核心数据结构如下:
c复制typedef struct {
uint8_t currentPage;
void (*PageInit[PAGE_COUNT])(void);
void (*PageUpdate[PAGE_COUNT])(void);
} PageManager_t;
页面切换动画我采用了简单的渐变动画,通过改变显示缓冲区的数据实现平滑过渡:
c复制void PageTransition(uint8_t *oldBuf, uint8_t *newBuf)
{
for(uint8_t i=0; i<8; i++) {
for(uint8_t j=0; j<128; j++) {
displayBuf[j+i*128] = (oldBuf[j+i*128] & mask) |
(newBuf[j+i*128] & ~mask);
}
OLED_Refresh();
vTaskDelay(pdMS_TO_TICKS(30));
}
}
5. 电源管理优化
5.1 低功耗设计
为了延长电池续航,我实施了以下优化措施:
-
动态频率调整:根据任务负载调整系统时钟
c复制void SetSystemClock(uint32_t freq) { RCC_ClocksTypeDef RCC_Clocks; SystemCoreClockUpdate(); RCC_GetClocksFreq(&RCC_Clocks); // 时钟配置代码... } -
传感器间歇工作:非必要时不唤醒传感器
c复制void PM25Sensor_Sleep(void) { UART_SendData(PM25_UART, SLEEP_CMD, sizeof(SLEEP_CMD)); } -
显示背光控制:无操作时降低亮度
c复制void OLED_SetContrast(uint8_t level) { I2C_WriteByte(OLED_ADDR, 0x81, level); }
5.2 电池电量监测
电池电量监测采用滑动平均滤波算法,减少电压波动带来的显示跳动:
c复制#define BATTERY_SAMPLES 5
static float batteryVoltages[BATTERY_SAMPLES];
static uint8_t batteryIndex = 0;
float GetFilteredBatteryVoltage(void)
{
float sum = 0;
for(uint8_t i=0; i<BATTERY_SAMPLES; i++) {
sum += batteryVoltages[i];
}
return sum / BATTERY_SAMPLES;
}
void UpdateBatteryVoltage(float newVoltage)
{
batteryVoltages[batteryIndex] = newVoltage;
batteryIndex = (batteryIndex + 1) % BATTERY_SAMPLES;
}
6. 开发中的经验总结
6.1 FreeRTOS使用心得
-
堆栈大小设置:初期经常遇到堆栈溢出问题,后来发现可以通过以下方法估算:
- 计算局部变量总大小
- 加上函数调用深度×每个函数的栈帧大小
- 乘以安全系数1.5
-
优先级设置原则:
- 实时性要求高的任务优先级高
- 执行时间短的任务优先级高
- 关键系统任务保留最高优先级
-
常见问题排查:
- 任务无法调度:检查vTaskStartScheduler()是否调用
- 消息队列满:增大队列长度或提高消费者任务优先级
- 系统卡死:检查是否有任务长时间占用CPU
6.2 硬件调试技巧
-
传感器数据异常:
- 先检查电源是否稳定
- 用逻辑分析仪抓取通信波形
- 确认时序是否符合规格书要求
-
显示问题排查:
c复制// 简单的显示测试模式 void TestOLED(void) { OLED_Clear(); OLED_DrawRectangle(0,0,127,63); OLED_Refresh(); while(1); } -
低功耗优化:
- 用电流表测量各模块工作电流
- 逐个关闭外设查找耗电大户
- 优化软件延时,使用RTOS的阻塞式延时
7. 项目扩展方向
虽然当前版本已经实现了基本功能,但还有不少可以改进的地方:
-
网络功能实现:计划添加ESP8266模块实现数据上传
- 采用AT指令方式连接WiFi
- 使用MQTT协议上传数据到服务器
- 实现OTA固件升级功能
-
数据存储功能:添加SPI Flash存储历史数据
c复制void SaveSensorData(SensorData_t data) { uint32_t addr = GetNextWriteAddr(); SPI_Flash_Write(addr, (uint8_t*)&data, sizeof(data)); } -
报警功能:当PM2.5超标时触发声光报警
c复制void CheckAirQuality(float pm25) { if(pm25 > 75.0) { Buzzer_On(); LED_Blink(500); } } -
外壳设计:3D打印定制外壳,提升产品完成度
在实际开发过程中,我发现FreeRTOS的任务划分需要根据具体应用场景灵活调整。初期我尝试为每个小功能创建独立任务,结果导致系统过于复杂。后来改为将相关性强的功能合并到同一任务中,通过状态机管理,系统稳定性明显提升。
另一个重要体会是,嵌入式开发中硬件和软件的调试往往需要交替进行。比如PM2.5传感器最初读数不稳定,后来发现是电源滤波电容不足导致的。因此建议在软件开发前,先用示波器确认各硬件模块工作正常。