RA4M2开发IOT(10)----集成LPS22DF气压计

RA4M2开发IOT.10--集成LPS22DF气压计

概述

本篇文章将延续现有 "动态显示 MEMS 数据" 的框架,在同一条 I²C 总线上新增 LPS22DF 数字气压计。

项目将具备 惯性 + 气压 的完整环境感知能力,并且借助涂鸦平台可快速把本地大气数据同步到云端,为室内气候监测、爬山/无人机高度预警等场景奠定基础。

最近在瑞萨RA的课程,需要样片的可以加qun申请:925643491。

视频教学

https://www.bilibili.com/video/BV1DjNmzyEuV

RA4M2开发IOT(10)----集成LPS22DF气压计

样品申请

https://www.wjx.top/vm/rCrkUrz.aspx

硬件准备

首先需要准备一个开发板,这里我准备的是自己绘制的开发板,需要的可以进行申请。

主控为R7FA4M2AD3CFL#AA0

同时添加RA4M2_IOT扩展版。

参考程序

https://github.com/CoreMaker-lab/RA4M2_IOT

https://gitee.com/CoreMaker/RA4M2_IOT

产品特性

LPS22DF是一款超紧凑型压阻绝对压力传感器,可用作数字输出气压计。LPS22DF相比前代产品具有更低的功耗和更小的压力噪声。

该器件包含传感元件和IC接口,该接口通过I²C、MIPI I3CSM或SPI接口实现传感元件与应用的通信,同时该器件也支持用于数据接口的广泛Vdd IO。检测绝对压力的传感元件由悬浮膜组成,采用ST开发的专门工艺进行制造。

LPS22DF采用全压塑孔LGA封装(HLGA)。可保证在-40 °C到+85 °C的温度范围都能工作。封装上有开孔,以便外部压力到达传感元件。

260-1260 hPa 的绝对压力范围,适用于多种气压应用。 最低电流消耗可达 1.7 μA,适合低功耗设备。 压力精度达 0.2 hPa,并具备 0.34 Pa 的低噪声和 0.45 Pa/°C 的温度补偿偏移。

通信模式

对于LPS22DF,可以使用IIC进行通讯。

最小系统图如下所示。

本文使用的板子原理图如下所示。

接入IOT板PCB如下所示。

速率

该模块支持的I2C速度为快速模式1M。

IIC属性配置

查看手册,可以得知LPS22DF的IIC地址为"1011100" 或者 "1011101",即0x5C或0x5D。

CS片选配置

LPS22DF可以通过CS管脚进行IIC或者SPI通讯切换


SA0地址设置

通过设置SA0管脚的高低电平可以改变模块的地址。


配置中断

这里的中断口对应P402。


在"New Stack"下选择Input > External IRQ (r_icu)。

模块配置如下所示。

● Name:g_external_irq4,这是该外部中断的名称。

● Channel:选择了4通道。

● Trigger:触发方式设置为Rising(上升沿触发),即信号上升时触发中断。

● Digital Filtering:未启用数字滤波(Not Supported)。

● Digital Filtering Sample Clock:由于数字滤波未启用,因此该项也未支持。

● Callback:指定了回调函数external_irq4_callback。当中断触发时,将调用此函数处理具体逻辑。

● Pin Interrupt Priority:设置为Priority 2,表示该中断的优先级为2。

● IRQ06:映射到引脚P402,即该中断信号通过引脚P000触发。

配置中断脚。

中断回调函数

● external_irq4_callback函数是外部中断的回调函数,当中断触发时,icu_irq_isr中断服务程序会调用此函数。

● lps22df_irq_flag变量在每次中断时赋值为1。

c 复制代码
bool lps22df_irq_flag =0;
/* Called from icu_irq_isr */
void external_irq4_callback (external_irq_callback_args_t * p_args)
{
    (void) p_args;
    lps22df_irq_flag = 1;
}

使能中断

在app_peripheral_init添加开启中断和初始化。

c 复制代码
    // 打开外部中断通道(用于接收 LPS22DF 的中断输出)
    fsp_err_t err = R_ICU_ExternalIrqOpen(&g_external_irq4_ctrl, &g_external_irq4_cfg);
    assert(FSP_SUCCESS == err);

    err = R_ICU_ExternalIrqEnable(&g_external_irq4_ctrl);
    assert(FSP_SUCCESS == err);

管脚初始化

使能SA0为低电平,配置模块地址。

在app_peripheral_init中添加LPS22DF的初始化。

c 复制代码
    //LPS22DF CS->1
    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_05_PIN_00, BSP_IO_LEVEL_HIGH);    
    //LPS22DF SA0->0
    R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_13, BSP_IO_LEVEL_LOW);   

参考程序

https://github.com/STMicroelectronics/lps22df-pid/

把对应驱动包导入到src文件夹。

添加头文件。

c 复制代码
#include "lps22df_reg.h"

添加预设定义。

c 复制代码
/* Extern variables ----------------------------------------------------------*/

/* Private functions ---------------------------------------------------------*/
/*
 *   WARNING:
 *   Functions declare in this section are defined at the end of this file
 *   and are strictly related to the hardware platform used.
 *
 */
static int32_t platform_write_lps22df(void *handle, uint8_t reg, const uint8_t *bufp,
                              uint16_t len);
static int32_t platform_read_lps22df(void *handle, uint8_t reg, uint8_t *bufp,
                             uint16_t len);
static void tx_com_lps22df( uint8_t *tx_buffer, uint16_t len );
static void platform_delay_lps22df(uint32_t ms);
static void platform_init_lps22df(void);
stmdev_ctx_t            dev_ctx_LPS22DF; // 设备上下文

在最下方添加LPS22DF的IIC读写函数。

c 复制代码
/*
 * @brief  Write generic device register (platform dependent)
 *
 * @param  handle    customizable argument. In this examples is used in
 *                   order to select the correct sensor bus handler.
 * @param  reg       register to write
 * @param  bufp      pointer to data to write in register reg
 * @param  len       number of consecutive register to write
 *
 */
static int32_t platform_write_lps22df(void *handle, uint8_t reg, const uint8_t *bufp,uint16_t len)
{
    R_SCI_I2C_SlaveAddressSet(&g_i2c2_ctrl, 0x5C, I2C_MASTER_ADDR_MODE_7BIT);
    assert(FSP_SUCCESS == err);
    // 创建一个足够大的缓冲区来包含寄存器地址和数据
    uint8_t data[len + 1];
    data[0] = reg; // 将寄存器地址放在数据的开始
    memcpy(&data[1], bufp, len); // 复制数据到缓冲区

    err = R_SCI_I2C_Write(&g_i2c2_ctrl, data, len+1, true);
    assert(FSP_SUCCESS == err);
    /* Since there is nothing else to do, block until Callback triggers*/
    //while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms)
    while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms>0)
    {
        R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MICROSECONDS);
        timeout_ms--;
        }
    if (I2C_MASTER_EVENT_ABORTED == i2c_event)
    {
        __BKPT(0);
    }
    /* Read data back from the I2C slave */
    i2c_event = I2C_MASTER_EVENT_ABORTED;
    timeout_ms           = 100000;
    return 0;
}


/*
 * @brief  Read generic device register (platform dependent)
 *
 * @param  handle    customizable argument. In this examples is used in
 *                   order to select the correct sensor bus handler.
 * @param  reg       register to read
 * @param  bufp      pointer to buffer that store the data read
 * @param  len       number of consecutive register to read
 *
 */
static int32_t platform_read_lps22df(void *handle, uint8_t reg, uint8_t *bufp,uint16_t len)
{
    R_SCI_I2C_SlaveAddressSet(&g_i2c2_ctrl, 0x5C, I2C_MASTER_ADDR_MODE_7BIT);
    assert(FSP_SUCCESS == err);
    err = R_SCI_I2C_Write(&g_i2c2_ctrl, &reg, 1, true);
    assert(FSP_SUCCESS == err);
    /* Since there is nothing else to do, block until Callback triggers*/
    //while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms)
    while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms>0)
    {
        R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MICROSECONDS);
        timeout_ms--;
        }
    if (I2C_MASTER_EVENT_ABORTED == i2c_event)
    {
        __BKPT(0);
        }
    /* Read data back from the I2C slave */
    i2c_event = I2C_MASTER_EVENT_ABORTED;
    timeout_ms           = 100000;

    /* Read data from I2C slave */
    err = R_SCI_I2C_Read(&g_i2c2_ctrl, bufp, len, false);
    assert(FSP_SUCCESS == err);
    while ((I2C_MASTER_EVENT_RX_COMPLETE != i2c_event) && timeout_ms)
    {
        R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MILLISECONDS);
        timeout_ms--;
    }
    if (I2C_MASTER_EVENT_ABORTED == i2c_event)
    {
        __BKPT(0);
    }

    i2c_event = I2C_MASTER_EVENT_ABORTED;
    timeout_ms           = 100000;
  return 0;
}


/*
 * @brief  platform specific delay (platform dependent)
 *
 * @param  ms        delay in ms
 *
 */
static void platform_delay_lps22df(uint32_t ms)
{
    R_BSP_SoftwareDelay(ms, BSP_DELAY_UNITS_MILLISECONDS);
}

LPS22DF初始化

sensor_lps22df_init函数负责把 ST LPS22DF 气压/温度传感器接入到现有 I²C 总线,并配置为低噪声测量模式 。

c 复制代码
/* ---------------------------------------------------------------------------
 * @brief  初始化 LPS22DF 数字气压计(气压 + 片内温度)
 *         - 共用 I²C 总线(SENSOR_BUS)
 *         - 配置为 10 Hz / 256 次平均 / ODR÷9 低通
 *         - 打开 DRDY 中断,MCU 可用外部中断捕获新数据
 * --------------------------------------------------------------------------*/
static void sensor_lps22df_init(void)
{
    /* 1. 局部缓冲/结构体 -------------------------------------------------- */
    lps22df_pin_int_route_t int_route;     // 中断路由寄存器镜像
    lps22df_bus_mode_t      bus_mode;      // 接口&滤波选项
    lps22df_id_t            id;            // WHO_AM_I 读取结果
    lps22df_md_t            md;            // 模式配置
    int                     ret;           // ST 驱动函数返回值

    /* 2. 绑定底层读/写/延时 ------------------------------------------------ */
    dev_ctx_LPS22DF.write_reg = platform_write_lps22df;  // I²C 写函数
    dev_ctx_LPS22DF.read_reg  = platform_read_lps22df;   // I²C 读函数
    dev_ctx_LPS22DF.mdelay    = platform_delay_lps22df;  // 毫秒延时
    dev_ctx_LPS22DF.handle    = &SENSOR_BUS;             // I²C 句柄

    /* 3. 读取并校验设备 ID ------------------------------------------------- */
    lps22df_id_get(&dev_ctx_LPS22DF, &id);
    printf("LPS22DF_ID=0x%x, whoamI=0x%x\r\n", LPS22DF_ID, id.whoami);
    if (id.whoami != LPS22DF_ID)   // 若连线或地址错误,停在此处
        while (1);

    /* 4. 先 Boot -> 再软复位 ------------------------------------------------ */
    ret = lps22df_init_set(&dev_ctx_LPS22DF, LPS22DF_BOOT);   if (ret) while (1);
    ret = lps22df_init_set(&dev_ctx_LPS22DF, LPS22DF_RESET);  if (ret) while (1);

    /* 5. 启用驱动推荐配置:BDU=1 / IF_INC=1 ------------------------------- */
    lps22df_init_set(&dev_ctx_LPS22DF, LPS22DF_DRV_RDY);

    /* 6. 选择接口 + 滤波模式 ---------------------------------------------- */
    bus_mode.filter    = LPS22DF_FILTER_AUTO;  // 写寄存器自动关 LPF,之后再恢复
    bus_mode.interface = LPS22DF_SEL_BY_HW;    // 由 SEL 脚决定 I²C / SPI
    lps22df_bus_mode_set(&dev_ctx_LPS22DF, &bus_mode);

    /* 7. 设置测量输出速率 & 滤波/平均参数 --------------------------------- */
    md.odr = LPS22DF_10Hz;            // ODR = 10 Hz
    md.avg = LPS22DF_256_AVG;         // 256 次平均,降低噪声
    md.lpf = LPS22DF_LPF_ODR_DIV_9;   // 低通截止 ≈ 1.1 Hz
    lps22df_mode_set(&dev_ctx_LPS22DF, &md);

    /* 8. 使能气压/温度 DRDY 中断 ------------------------------------------- */
    lps22df_pin_int_route_get(&dev_ctx_LPS22DF, &int_route); // 读默认
    int_route.drdy_pres = PROPERTY_ENABLE;   // 数据就绪 → INT/DRDY 脚
    lps22df_pin_int_route_set(&dev_ctx_LPS22DF, &int_route);

    /* 现在 LPS22DF 已在 10 Hz 低噪声模式开始工作,可在主循环
       通过中断或轮询读取气压 (hPa) 与温度 (°C)。*/
  }

在主程序中添加初始化。

c 复制代码
    // 初始化 LPS22DF 传感器,配置速率和模式
    sensor_lps22df_init();

界面初始化

在OLED_switch添加LPS22DF界面初始化。

c 复制代码
static void OLED_switch(void)
{
    if (g_oled_clear)                     // ← 仅当需切屏时进入
    {
        g_oled_clear = 0;                 // 标记已完成清屏
        OLED_Clear();                     // ① 清显存并刷新,黑场一次

        switch (g_oled_page)              // ② 根据当前页写"标签"
        {
            /* ---------- Page-0 : Tuya 模组 ---------- */
            case 0:
                OLED_ShowString(0,  0, (u8 *)"TUYA",       16,1);
                OLED_ShowString(0, 16, (u8 *)"WIFI MODE:", 16,1);
                OLED_ShowString(0, 32, (u8 *)"AP MODE:",   16,1);
                break;

            /* ---------- Page-1 : LSM6DSV16X ---------- */
            case 1:
                OLED_ShowString(0,  0, (u8 *)"MEMS_LSM6DSV16X", 12,1);
                OLED_ShowString(0, 12, (u8 *)"TAP:",   12,1);
                OLED_ShowString(60,12, (u8 *)"TEMP:",  12,1);
                OLED_ShowString(0, 24, (u8 *)"X(mg):", 12,1);
                OLED_ShowString(0, 36, (u8 *)"Y(mg):", 12,1);
                OLED_ShowString(0, 48, (u8 *)"Z(mg):", 12,1);
                break;

            /* ---------- Page-2 : LPS22DF 气压计 ------- */
            case 2:
                OLED_ShowString(0,  0, (u8 *)"MEMS_LPS22DF", 16,1);
                OLED_ShowString(0, 16, (u8 *)"Pressure:", 16,1);
                OLED_ShowString(0, 32, (u8 *)"Temp:",   16,1);
                break;
        }
        OLED_Refresh();                   // ③ 推送骨架到屏幕
    }
}

中断回调函数

lps22df_read_data_drdy函数是气压计 LPS22DF 的 DRDY 中断读取函数。

由于没有不需要上报涂鸦,所以2中的wifi_Update和g_tuya_up_data注释掉了。

两个变量推送到 OLED 第 2 页或打包成 Tuya DP 上报云端。

c 复制代码
/******************************************************************************
 * @brief  通过 DRDY 中断读取 LPS22DF 气压计数据
 *         - 由外部中断服务程序置位 lps22df_irq_flag
 *         - 进入后读取最新气压 / 温度(若数据已就绪)
 *         - 同时触发涂鸦 DP 上报计时器
 ******************************************************************************/
double lps22df_data_pressure=0.0f;
double lps22df_data_temp=0.0f;
static void lps22df_read_data_drdy(void)
{
    /* -------- 1. 检测由 EXTI 产生的数据就绪标志 ------------------ */
    if (lps22df_irq_flag)                    // INT/DRDY 低电平到来
    {
        lps22df_irq_flag = false;            // 先清除本地标志

        /* -------- 2. 触发涂鸦数据上报计时 ----------------------- */
//        wifi_Update   = 1;                   // 下轮主循环立刻刷新 DP
//        g_tuya_up_data = 2000;               // 2 s 后再次上报 (计数器复位)

        /* -------- 3. 查询 LPS22DF 数据就绪标志 ------------------ */
        lps22df_all_sources_t all_src;
        lps22df_all_sources_get(&dev_ctx_LPS22DF, &all_src);

        /* -------- 4. 仅在有新数据时读取压强/温度 ---------------- */
        if (all_src.drdy_pres || all_src.drdy_temp)
        {
            static lps22df_data_t data;      // 静态减少栈开销
            lps22df_data_get(&dev_ctx_LPS22DF, &data);
            lps22df_data_pressure = data.pressure.hpa;
            lps22df_data_temp = data.heat.deg_c;
//            printf("pressure [hPa]: %6.2f  temperature [degC]: %6.2f\r\n",
//                   data.pressure.hpa, data.heat.deg_c);
            /* 此处可再把 data 填入 OLED/Page-2 或涂鸦 DP */
        }
    }
}

之后在主程序中调用。

c 复制代码
        // 处理 LPS22DF事件,(例如气压,温度)
        lps22df_read_data_drdy();

气压数据显示

OLED_lps22df_MEMS() 是 OLED 第 2 页面(气压计页)的动态刷新函数。

● 通过 g_tuya_num 计数器,每 100 ms 刷新一次,既保证数据实时,又减轻 I²C 负担。

● 调用全局缓存 lps22df_data_pressure 与 lps22df_data_temp(由 DRDY 中断读取函数更新),把 气压 以 "xxxx.xx hPa",温度 以 "±xxx.xx °C" 的格式显示在屏幕指定坐标。

● 采用分段写字符的方式(整数->小数点->小数),避免 sprintf 带来的堆栈及时间开销。

● 最后执行 OLED_Refresh() 将修改后的显存块写回驱动 IC,实现无闪烁、平滑的实时数据展示。

c 复制代码
/* ------------ Page-2 : MEMS_lps22df -------------------------- */
static void OLED_lps22df_MEMS(void)
{
    /* ---------- 1. 节流判断:每 1 ms 主循环 +1,满 100 ≈ 100 ms ---------- */
    if(g_tuya_num<100)
    g_tuya_num++;
    else
    {
        g_tuya_num=0;
        uint32_t t100 = 0;// 小数两位 ×100 的临时变量
        OLED_ShowNum  (72, 16,(uint32_t)lps22df_data_pressure, 4, 16, 1);// 整数位
        /*   小数点 '.'       */
        OLED_ShowChar (104, 16, '.',16, 1);
        /*   小数部分:xx     */
        t100 = (uint32_t)(lps22df_data_pressure * 100);
        OLED_ShowNum  (112, 16,(uint32_t)t100%100, 2, 16, 1);

        /*   正负号 */
        if(lps22df_data_temp<0)
            OLED_ShowChar (72, 32, '-',16, 1);
        else
            OLED_ShowChar (72, 32, '+',16, 1);
        OLED_ShowNum (80, 32,(uint32_t)fabs(lps22df_data_temp),3,16, 1);// 整数位
        /*   小数点 '.'       */
        OLED_ShowChar (104, 32, '.',16, 1);
        /*   小数部分:xx     */
        t100=(uint32_t)(fabs(lps22df_data_temp)*100);
        OLED_ShowNum (112, 32,(uint32_t)t100%100,2,16, 1);
        OLED_Refresh();
    }
}

在主程序中添加OLED界面显示。

c 复制代码
        if(g_oled_page==0) /* Page-0 : Tuya 状态页 */
            OLED_DrawPage_TUYA();// 显示 Wi-Fi / AP 配网信息
        else if(g_oled_page==1)/* Page-1 : LSM6DSV16X 状态页 */
            OLED_DrawPage_MEMS();// 显示单/双击、温度、XYZ 加速度
        else if(g_oled_page==2)/* Page-2 : LPS22DF 状态页 */
            OLED_lps22df_MEMS();// 显示气压、温度