最近在做一个基于FreeRTOS的空气质量检测仪项目,核心功能是通过串口与PM2.5传感器模块进行通信,获取空气中的颗粒物浓度数据。这个项目涉及STM32单片机开发、FreeRTOS实时操作系统应用、串口通信协议解析等多个嵌入式开发关键技术点。
PM2.5传感器模块通常采用串口通信方式,会定期发送包含颗粒物浓度数据的报文。我们的任务是在STM32平台上实现:
这个项目看似简单,但实际开发中会遇到不少坑,比如串口数据丢失、报文解析错误、任务优先级设置不当等问题。下面我就详细分享整个开发过程的关键技术和经验。
我们选用的是市面上常见的激光式PM2.5传感器,具有以下特点:
这类传感器内部采用激光散射原理,通过测量颗粒物对激光的散射强度来计算浓度,具有响应快、精度较高的特点。
传感器与STM32的连接方式如下:
code复制PM2.5传感器 STM32F103
VCC ---- 5V
GND ---- GND
TX ---- PA10 (USART0_RX)
RX ---- PA9 (USART0_TX)
EN ---- PA6 (控制引脚)
特别需要注意的是:
基于FreeRTOS的软件架构分为三层:
code复制[硬件驱动层]
|- USART驱动
|- GPIO驱动
[设备抽象层]
|- PM2.5设备封装
|- 初始化
|- 数据读取
|- 电源控制
[应用层]
|- 数据采集任务
|- 数据显示任务
|- 系统监控任务
为方便数据处理,定义了以下核心数据结构:
c复制/* PM2.5 数据结构 */
typedef struct {
uint16_t pm1p0Conc; // PM1.0浓度(ug/m3)
uint16_t pm2p5Conc; // PM2.5浓度(ug/m3)
uint16_t pm10Conc; // PM10浓度(ug/m3)
uint16_t pm0p3Num; // >0.3um颗粒数
uint16_t pm0p5Num; // >0.5um颗粒数
uint16_t pm1p0Num; // >1.0um颗粒数
uint16_t pm2p5Num; // >2.5um颗粒数
uint16_t pm5p0Num; // >5.0um颗粒数
uint16_t pm10Num; // >10um颗粒数
} Pm25Data;
/* 空气质量等级 */
typedef enum {
AQI_EXCELLENT, // 优
AQI_GOOD, // 良
AQI_MILD, // 轻度污染
AQI_MODERATE, // 中度污染
AQI_SEVERE, // 重度污染
AQI_TERRIBLE, // 严重污染
AQI_UNKNOWN
} AirQualityLevel;
USART0的初始化配置如下:
c复制static UartConfig g_usart0_cfg = {
.usart_periph = USART0,
.usart_clk = RCU_USART0,
.tx_gpio_port = GPIOA,
.tx_gpio_pin = GPIO_PIN_9,
.tx_gpio_clk = RCU_GPIOA,
.rx_gpio_port = GPIOA,
.rx_gpio_pin = GPIO_PIN_10,
.rx_gpio_clk = RCU_GPIOA,
.irq_n = USART0_IRQn,
.irq_pre = 12,
.irq_sub = 0,
.use_dma_rx = 0 // 不使用DMA接收
};
配置要点:
c复制void USART0_IRQHandler(void)
{
uint8_t rx_byte;
uint8_t status = UART_IRQHandler(&g_usart0_cfg, &rx_byte);
if (status == 1) { // 成功接收到一个字节
UART_Dev_PushRxByte(&g_usart0_dev, rx_byte);
}
}
关键点:
c复制int UART_Dev_Send(struct UART_Device *pDev, uint8_t *data, uint16_t len)
{
// 检查参数
if (pDev == NULL || data == NULL || len == 0) {
return -1;
}
// 等待上次发送完成
while (usart_flag_get(pDev->priv_cfg->usart_periph, USART_FLAG_TBE) == RESET);
// 逐个字节发送
for (uint16_t i = 0; i < len; i++) {
usart_data_transmit(pDev->priv_cfg->usart_periph, data[i]);
while (usart_flag_get(pDev->priv_cfg->usart_periph, USART_FLAG_TBE) == RESET);
}
return 0;
}
c复制static int Pm25_Init(struct Pm25Device *pDev)
{
// 获取底层串口设备
pDev->uart_dev = GetUARTDevice("usart0");
if (pDev->uart_dev == NULL) {
return -1;
}
// 初始化串口
if (pDev->uart_dev->Init(pDev->uart_dev, 9600) != 0) {
return -2;
}
// 初始化控制引脚PA6
rcu_periph_clock_enable(RCU_GPIOA);
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
gpio_bit_set(GPIOA, GPIO_PIN_6); // 默认使能
return 0;
}
PM2.5传感器数据包格式通常为:
解析函数实现:
c复制static int Pm25_ReadData(struct Pm25Device *pDev, Pm25Data *pData)
{
static uint8_t rx_buf[32];
static uint8_t state = 0;
static uint8_t index = 0;
static uint8_t checksum = 0;
uint8_t byte;
while (UART_Dev_PopRxByte(pDev->uart_dev, &byte) == 0) {
switch (state) {
case 0: // 等待第一个帧头
if (byte == 0xAA) state = 1;
break;
case 1: // 等待第二个帧头
if (byte == 0xC0) {
state = 2;
index = 0;
checksum = 0xAA + 0xC0;
} else {
state = 0;
}
break;
case 2: // 接收数据
rx_buf[index++] = byte;
checksum += byte;
if (index >= 16) { // 接收完数据区
state = 3;
}
break;
case 3: // 校验
if (checksum == byte) { // 校验通过
// 解析数据
pData->pm1p0Conc = (rx_buf[0] << 8) | rx_buf[1];
pData->pm2p5Conc = (rx_buf[2] << 8) | rx_buf[3];
pData->pm10Conc = (rx_buf[4] << 8) | rx_buf[5];
// 其他数据类似解析...
return 1; // 成功解析一包数据
}
state = 0;
break;
}
}
return 0; // 未解析到完整数据包
}
c复制static AirQualityLevel Pm25_GetLevel(uint16_t pm2p5Conc)
{
if (pm2p5Conc <= 35) {
return AQI_EXCELLENT;
} else if (pm2p5Conc <= 75) {
return AQI_GOOD;
} else if (pm2p5Conc <= 115) {
return AQI_MILD;
} else if (pm2p5Conc <= 150) {
return AQI_MODERATE;
} else if (pm2p5Conc <= 250) {
return AQI_SEVERE;
} else {
return AQI_TERRIBLE;
}
}
c复制static void pm25_task(void *pvParameters)
{
struct Pm25Device *pDev = GetPm25Device();
Pm25Data data;
// 初始化设备
if (pDev->Init(pDev) != 0) {
vTaskDelete(NULL);
return;
}
while (1) {
// 尝试读取数据
if (pDev->ReadData(pDev, &data) == 1) {
// 获取空气质量等级
AirQualityLevel level = pDev->GetLevel(data.pm2p5Conc);
// 这里可以将数据发送到消息队列供其他任务使用
// 或者直接处理显示等
// 调试输出
printf("PM2.5: %d ug/m3, Level: %d\n",
data.pm2p5Conc, level);
}
// 适当延时,避免占用太多CPU
vTaskDelay(pdMS_TO_TICKS(100));
}
}
合理的任务优先级设置对系统稳定性很重要:
现象:偶尔会丢失部分数据包
解决方法:
现象:偶尔会解析出错误的数据
解决方法:
现象:长时间运行后系统卡死
解决方法:
这个项目虽然基础,但涵盖了嵌入式开发的多个关键技术点。在实际开发中,我遇到了不少问题,比如串口数据丢失、任务优先级设置不当等,通过不断调试和优化最终都得到了解决。对于初学者来说,建议重点关注以下几点:
掌握了这些核心要点,就能开发出稳定可靠的嵌入式应用了。