freertos开发空气检测仪之串口驱动与外部设备(PM2.5模块)交互实例

感谢粉丝的关注,昨天进行年会,加上马上就要放假了,这两天没更新文章。今天这个空气检测仪项目学习继续进行。本篇文章带来的是freertos开发空气检测仪之串口驱动与外部设备(PM2.5模块)交互实例。

PM2.5模块原理图

在这里插入图片描述

这个是本次项目中使用的原理图,可以看到pm2.5传感器使用串口的方式进行数据的交互,而且自带了一个私有的PA6的io引脚操作。

调试USART0串口

根据我的经验进行调试。

/*
 * PM2.5传感器使用 USART0 (Tx: PA9, Rx: PA10)
 * 私有控制引脚: PA6
 */
static CalUartConfig 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
};
/* ------------------- 自定义 Init/Send 函数 ------------------- */

/* PM2.5 传感器私有初始化: 额外初始化 PA6 */
static int USART0_Dev_Init(struct UART_Device *pDev, uint32_t baudrate)
{
    /* 1. 调用通用初始化 */
    if (UART_Dev_Init(pDev, baudrate) != 0) return -1;
    
    /* 2. 初始化私有引脚 PA6 */
    rcu_periph_clock_enable(RCU_GPIOA);
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
    
    /* 默认拉高 (Enable) */
    gpio_bit_set(GPIOA, GPIO_PIN_6);
    
    return 0;
}

/* 2. 定义设备实例 */

struct UART_Device g_usart0_dev = {
    .name      = "usart0",
    .priv_cfg  = &g_usart0_cfg,
    .priv_data = NULL,
    .Init      = USART0_Dev_Init, /* 使用自定义 Init */
    .Send      = UART_Dev_Send,   /* 使用通用 Send */
    .Recv      = UART_Dev_Recv
};
/* 3. 注册函数 */
void Driver_UART_Init(void)
{
    UART_Device_Register(&g_wifi_uart_dev);
    UART_Device_Register(&g_uart3_dev);
    UART_Device_Register(&g_usart0_dev);
}
4.串口中断服务函数
void USART0_IRQHandler(void)
{
    uint8_t rx_byte;
    uint8_t status = CAL_UART_IRQHandler(&g_usart0_cfg, &rx_byte);
    
    if (status == 1) {
        UART_Dev_PushRxByte(&g_usart0_dev, rx_byte);
    }
}

依据我调试的经验,经过一番折腾,串口0的驱动就设计好了。下一步调试模块相关代码。

PM2.5模块抽象结构体

/* 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;

/* PM2.5 设备结构体 */
typedef struct Pm25Device {
    char *name;
    struct UART_Device *uart_dev; /* 底层使用的串口设备 */
    
    /* 接口函数 */
    int (*Init)(struct Pm25Device *pDev);
    int (*PowerControl)(struct Pm25Device *pDev, bool on);
    int (*ReadData)(struct Pm25Device *pDev, Pm25Data *pData);
    AirQualityLevel (*GetLevel)(uint16_t pm2p5Conc);
} Pm25Device;

/* 获取 PM2.5 设备实例 */
struct Pm25Device *GetPm25Device(void);
/* 静态实例 */
static struct Pm25Device g_pm25_dev = {
    .name = "PM2.5 Sensor",
    .uart_dev = NULL, /* Init时获取 */
    .Init = Pm25_Init,
    .PowerControl = Pm25_PowerControl,
    .ReadData = Pm25_ReadData,
    .GetLevel = Pm25_GetLevel
};
// 注册函数
struct Pm25Device *GetPm25Device(void)
{
    return &g_pm25_dev;
}

然后在对应的文件中,填充好如下对应的函数代码片段即可。

/* 内部私有函数声明 */
static int Pm25_Init(struct Pm25Device *pDev);
static int Pm25_PowerControl(struct Pm25Device *pDev, bool on);
static int Pm25_ReadData(struct Pm25Device *pDev, Pm25Data *pData);
static AirQualityLevel Pm25_GetLevel(uint16_t pm2p5Conc);

使用AI与参考现成资料进行了开发,将上述的函数片段完成,进行整合了下,下一步进行测试。

pm2.5模块测试

static void pm25_test_task(void *pvParameters)
{
    struct Pm25Device *pDev = GetPm25Device();
    Pm25Data data;
    
    if (pDev == NULL) {
        DBG_log("[PM2.5 TEST] Failed to get device!\n");
        vTaskDelete(NULL);
        return;
    }
    
    /* 初始化设备 */
    if (pDev->Init(pDev) != 0) {
        DBG_log("[PM2.5 TEST] Failed to init device!\n");
        vTaskDelete(NULL);
        return;
    }
    
    DBG_log("[PM2.5 TEST] Device initialized. Waiting for data...\n");
    
    while (1)
    {
        /* 尝试读取数据 */
        /* ReadData 内部会尝试从串口队列读取数据并进行拼包解析 */
        /* 返回 1 表示成功解析出一包数据 */
        if (pDev->ReadData(pDev, &data) == 1)
        {
            AirQualityLevel level = pDev->GetLevel(data.pm2p5Conc);
            char *levelStr;
            
            switch(level) {
                case AQI_EXCELLENT: levelStr = "Excellent"; break;
                case AQI_GOOD:      levelStr = "Good"; break;
                case AQI_MILD:      levelStr = "Mild"; break;
                case AQI_MODERATE:  levelStr = "Moderate"; break;
                case AQI_SEVERE:    levelStr = "Severe"; break;
                case AQI_TERRIBLE:  levelStr = "Terrible"; break;
                default:            levelStr = "Unknown"; break;
            }
            
            DBG_log("[PM2.5] PM2.5: %d ug/m3 (%s), PM1.0: %d, PM10: %d\n", 
                   data.pm2p5Conc, levelStr, data.pm1p0Conc, data.pm10Conc);
        }
        
        /* 
         * 延时策略:
         * PM2.5 传感器通常 1秒 发送一次数据。
         * 如果这里延时太久,串口缓冲区可能会溢出。
         * 如果延时太短,会空转浪费CPU。
         * 建议 100ms 轮询一次,足以处理 32字节 的数据包。
         */
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void pm25_test_start(void)
{
    /* 1. 初始化底层驱动 (Driver_UART_Init 已经被调用过吗?如果没有,需要调用) */
    /* 在 main.c 中应该已经调用了 Driver_UART_Init,这里为了保险起见,
       假设 main.c 会统一负责底层驱动初始化,或者在这里重复调用(如果支持幂等)*/
    /* Driver_UART_Init(); */ 
    
    /* 2. 创建测试任务 */
    /* 优先级建议:中等,不需要像串口接收那样高,但要比后台任务高 */
    xTaskCreate(pm25_test_task, "pm25_test", 512, NULL, 2, NULL);
}

实验现象

在这里插入图片描述

可以看到,本次项目使用的串口和外部设备就完成通讯交互的功能。

本文完!!

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐