国民技术
直播中

ouxiaolong

11年用户 470经验值
擅长:嵌入式技术 光电显示
私信 关注

【国民技术N32项目移植】红外测温枪项目移植

1 引言

红外体温枪测量高效而备受青睐,发展迅速。但目前市场上的红外体温计存在测量误差大、成本高等缺点,应用受到限制。因此本文提出了一种基于MLX90614的高精度便携式红外体温计。

红外测温传感器有很多,比如MLX90614、AMG8833,考虑到测温精度,本设计选择MLX90614。

MLX90614 是一款红外非接触温度计。TO-39金属封装里同时集成了红外感应热电堆探测器芯片和信号处理专用集成芯片。由于集成了低噪声放大器、17 位模数转换器和强大的数字信号处理单元,使得高精度和高分辨度的温度计得以实现。温度计具备出厂校准化,有数字 PWM 和 SMBus(系统管理总线)输出模式。续传送温度范围为-20-120 ˚C 的物体温度,其分辨率为 ±0.14 ˚C。

16755827207309uogst5xab

物体红外辐射能量的大小和波长的分布与其表面温度关系密切。因此,通过对物体自身红外辐射的测量,能准确地确定其表面温度,红外测温就是利用这一原理测量温度的。红外测温器由光学系统、光电探测器、信号放大器和信号处理及输出等部分组成。 光学系统汇聚其视场内的目标红外辐射能量,视场的大小由测温仪的光学零件及其位置确定。红外能量聚焦在光电探测器上并转变为相应的电信号。该信号经过放大器和信号处理威廉希尔官方网站 ,并按照仪器内的算法和目标发射率校正后转变为被测目标的温度值。

MLX90614 是由STATE MACHINE控制物体温度和环境温度的测量和计算。MLX81101 采集到的人体温度先与环境温度进行计算,然后经过低噪声低偏置增益可调斩波放大器放大,再经过ADC调制器转换成单一比特流,之后传输到 DSP 进行进一步处理,最后的结果通过 PWM 或 SMBus 模式进行输出。其中,通过 EEPROM配置的可编程有限脉冲响应数字滤波器和无限脉冲响应数字滤波器减少输入信号的带宽,以实现预期的信噪比和刷新率。有限脉冲响应数字滤波器的输出最终存储在内置 RAM 中。并将结果通过 PWM 或是SMBus模式输出。

该模块以81101热电元件作为红外感应部分。输出是被测物体温度(TO)与传感器自身温度(Ta)共同作用的结果,理想情况下热电元件的输出电压为:

Vir = A(To4 – Ta4)

其中温度单位均为 Kelvin, A 为元件的灵敏度常数。

目标温度和环境温度由 81101 内置的热电偶测定测量,从 81101 中输出的两路温度信号分别经内部 MLX90302 器件上高性能、低噪声的斩波稳态放大器放大再经一个 17-bit 的模数转换器( ADC)和强大的数字信号处理(DSP)单元后输出。

该系列模块的温度解析度可达 0.01°C,体积小巧,被测目标和环境温度能通过单通道输出,有两种输出方式: PWM 输出、可编程 SMBus 输出,适于多种应用环境,

通过 SMBus 编程可以更改模块 EEPROM 内的预设值并按照应用要求进行配置,并可以读出 EEPROM 内的配置信息;还可以读出模块 RAM 内温度等数据。

MLX90614 有适用于 3 伏和 5 伏电源操作的两种类型。由于 3 伏型其小于 2 毫安的电流消耗,它非常适用于手提装置和电池动力装置。为此,传感器也具有一个节能“休眠”模式,此时电流消耗可低于 2 毫安。对于 12 伏汽车电池直接供电的情况, 5 伏型包含的电子部件可与几个外部元件一起在较高电压下运行。

2 功能描述

红外体温枪测的核心功能如下:

  • 基于额头测定体温,采用额温转体温算法校准了体温值
  • OLED实时显示体温,并将体温存储在EEPROM中
  • 通过蜂鸣器和LED提醒温度测量,不同的LED对应不同的温度值
  • 历史温度值可通过串口输出(10组数据)

3 MLX90614传输协议

MLX90614 的 SMBus 协议如下:

1675582721421h0rmqvbpfg

3.1 读器件

读器件命令决定是读 RAM 或 EEPROM数据格式。

16755827220481vezdbtf1e

3.2 写器件

写器件命令决定是写 RAM 或 EEPROM数据格式。

1675582722371prniw7916r

3.3 数据传输时序

数据传输时序如下:

1675582722702de8fi1wdaf

PWM/SDA 上的数据在 SCL 变为低电平 300n 后即可改变,数据在 SCL 的上升沿被捕获。16 位数据分两次传输,每次传一个字节。每个字节都是按照高位(MSB)在前,低位(LSB)在后的格式传输,两个字节中间的第九个时钟是应答时钟。

4 方案设计

4.1 硬件设计

4.1.1 系统硬件连接

红外体温枪以N32FG4FR主控芯片组为核心,由红外测温模块、蜂鸣器报警模块、OLED显示模块构成,通过MLX90614红外传感器测距传感器获取温度。

1.png

4.1.2 MLX90614接口威廉希尔官方网站

MLX90614与MCU连接的硬件威廉希尔官方网站 如下图所示。SCL、PWM/SDA 管脚直接连接MCU 的普通 I/O 口即可,由于 MLX90614的输入输出接口是漏级开路(OD)结构,需要加上拉电阻。多个 MLX90614 可以用于一个系统中,每个 MLX90614 对应一个不同地址,通过地址的不同而访问不同的 MLX90614,最多可以达到 127 个。

167558272560131shm59ktk

通过 SMBus 将 MLX90614 的 RAM 与主控芯片N32G4FR连接起来,以读取其测得的温度。MLX90614 与N32 接口威廉希尔官方网站 如上图所示。MLX90614只有四个引脚,即电源端 VDD,接地端 VSS,时钟信号输入端 SCL 和数字信号输入输出端 SDA。VDD与 3.3V 电源相连进行供电,为了确保 SDA 和 SCL 能够在总线空闲时都处于高电平状态,威廉希尔官方网站 中设计了两个上拉电阻。为了使电流低于 1.3mA,阻值选用常用的 4.7K左右。

4.2 软件设计

4.2.1 软件架构设计

红外体温枪的软件基于FreeRTOS实现,其软件架构如下:

2.png

整个软件架构分为四层:Hardware、Driver、FreeRTOS Kernel、Application。

Hardware层:基础为外设有UART、I2C、GPIO,UART用于调试,I2C用于与外设通信。

Driver层:主要为上层应用提供驱动接口。

FreeRTOS Kernel层:FreeRTOS内容比较多,除了基础内核外,还包含了丰富的组件和第三方库,主

要包含以下组成部分:

  • 基础内核:包括不可裁剪的最小内核和可裁剪模块。最小内核包含任务管理、内存管理、中断管理、异常管理和系统时钟。可裁剪模块包括队列、信号量、互斥量、软件定时器、任务通知、事件组。
  • 内核增强:在内核基础功能之上,进一步提供增强功能,包括低功耗模式、cu的使用率、Trce事件跟踪、TCP/IP、CLI、POSIX等。
  • 协议栈:提供的一系列独立于FreeRTOS内的库,只和标准C库相关。比如MQTT、JSON、HTTP等。
  • 第三方组件:一般和具体应用场景相关的组件或者第三方提供的组件,比如GUI、为AWS IOT工特定的增值系服务等。

Application层:Application部分包含系统任务和用户自定义任务,用户自定义任务包含通信、调试、控制、算法等模块。“

整个软件的运行如下:

3.png

(1)硬件初始化

主要初始化按键、OLED、MLX90614等资源。

(2)OS初始化

该阶段主要进行OS任务启动

(3)任务运行阶段

  • FreeRTOS和中断会配合运行各个任务,主要操作如下:+
  • 中断会由用户或FreeRTOS内核触发,比如event/signal。
  • 任务间信号量、互斥量等完成某项需求相互间需要同步等需求。:
  • 任务需要与硬件外设进行打交道,或入或出。

4.2.2 MLX90614温度获取

多个 MLX90614 可以用于一个系统中,通过地址不同区分器件,器件默认的地址为 5AH,因此在多 MLX90614 系统中,需要给每个 MLX90614 分配一个不同的地址,在只有一个MLX90614 的系统中, MLX90614 识别地址 00h,即在单个 MLX90614 系统中,可以使用该地址访问它。

C:\Users\BruceOu\Desktop\11.png

发送和接收数据是以字节为单位进行的,程序流程如下图所示。每次发送一个字节(按位发送,发送 8 个位就是一个字节),然后就判断对方是否有应答,如果有应答,就接着发送下一个字节;如果没有应答,多次重发该字节,直到有应答,就接着发送下一个字节,如果多次重发后,仍然没有应答,就结束。接收数据时,每次接收一个字节(按位接收,接收 8 个位就是一个字节),然后向对方发送一个应答信号,然后就可以继续接收下一个字节。

1675582726847vu2eflrant

从MLX90614 种读出的数据是 16 位的,由高 8 位( DataH)和低 8 位( DataL)两部分组成,其中RAM地址07H单元存储的是TOBJ1数据,数据范围从0x27AD到 0x7FFF,表示的温度范围是-70.01℃到+382.19℃。

从MLX90614 中读出的数据(DataH:DataL)换算为温度数据(T,单位为℃ )如下所示:

T= (DataH:DataL)*0.02-273.15

例如:DataH:DataL=0x27AD,代入式中 T=-70.01℃

5 红外测温枪实现

5.1 MLX90614温度读取

MLX90614是基于I2C的,本设计是基于软件I2C,关于软件I2C可参看笔者的帖子,另外MLX90614的读写时序参看前文。

首先是MLX90614的GPIO初始化。

/**
  * [url=home.php?mod=space&uid=2666770]@Brief[/url]  Mlx90614传感器的初始化
  * [url=home.php?mod=space&uid=3142012]@param[/url]  soft_i2c_dev
  * @retval 返回值 0 表示正确, 返回1表示未探测到
  */
int8_t Dev_MLX90614_Init(ST_Soft_I2C_Dev *soft_i2c_dev)
{
    int8_t ucError = 0;

    // 初始化IIC接口
    Dev_Soft_I2C_Port_Init(soft_i2c_dev);

    return (ucError);
}

Dev_Soft_I2C_Port_Init()函数就是软件I2C的移植接口。

/**
  * @brief  I2C GPIO 初始化Port函数
  * @param  soft_i2c_dev
  * @retval None
  */
void Dev_Soft_I2C_Port_Init(ST_Soft_I2C_Dev *soft_i2c_dev)
{
#if (defined(STM32F1) || defined(STM32F4))
    GPIO_InitTypeDef GPIO_Initure;
#endif

#if defined(HAL_LIB)
#if (defined(STM32F1) || defined(STM32F4))
    /* 打开GPIO时钟 */
#if defined(GPIOA)
    if((soft_i2c_dev->scl_port == GPIOA) || (soft_i2c_dev->sda_port == GPIOA))
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();
    }
#endif
#if defined(GPIOB)
    if((soft_i2c_dev->scl_port == GPIOB) || (soft_i2c_dev->sda_port == GPIOB))
    {
        __HAL_RCC_GPIOB_CLK_ENABLE();
    }
#endif
#if defined(GPIOC)
    if((soft_i2c_dev->scl_port == GPIOC) || (soft_i2c_dev->sda_port == GPIOC))
    {
        __HAL_RCC_GPIOC_CLK_ENABLE();
    }
#endif
#if defined(GPIOD)
    if((soft_i2c_dev->scl_port == GPIOD) || (soft_i2c_dev->sda_port == GPIOD))
    {
        __HAL_RCC_GPIOD_CLK_ENABLE();
    }
#endif
#if defined(GPIOE)
    if((soft_i2c_dev->scl_port == GPIOE) || (soft_i2c_dev->sda_port == GPIOE))
    {
        __HAL_RCC_GPIOE_CLK_ENABLE();
    }
#endif
#if defined(GPIOF)
    if((soft_i2c_dev->scl_port == GPIOF) || (soft_i2c_dev->sda_port == GPIOF))
    {
        __HAL_RCC_GPIOF_CLK_ENABLE();
    }
#endif
#if defined(GPIOG)
    if((soft_i2c_dev->scl_port == GPIOG) || (soft_i2c_dev->sda_port == GPIOG))
    {
        __HAL_RCC_GPIOG_CLK_ENABLE();
    }
#endif
#if defined(GPIOH)
    if((soft_i2c_dev->scl_port == GPIOH) || (soft_i2c_dev->sda_port == GPIOH))
    {
        __HAL_RCC_GPIOH_CLK_ENABLE();
    }
#endif

    //初始化设置
    GPIO_Initure.Pin = soft_i2c_dev->scl_pin;
    GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出

    GPIO_Initure.Pull = GPIO_PULLUP; //上拉

#if defined(STM32F1)
    GPIO_Initure.Speed = GPIO_SPEED_FREQ_MEDIUM;
#elif defined(STM32F4)
    GPIO_Initure.Speed = GPIO_SPEED_FAST; //快速
#endif
    HAL_GPIO_Init(soft_i2c_dev->scl_port, &GPIO_Initure);

    GPIO_Initure.Pin = soft_i2c_dev->sda_pin;
    HAL_GPIO_Init(soft_i2c_dev->sda_port, &GPIO_Initure);
#endif

#elif defined(STD_LIB)
#if defined(STM32F1)
    /* 打开GPIO时钟 */
#if defined(GPIOA)
    if((soft_i2c_dev->scl_port == GPIOA) || (soft_i2c_dev->sda_port == GPIOA))
    {
        RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOA, ENABLE);
    }
#endif
#if defined(GPIOB)
    if((soft_i2c_dev->scl_port == GPIOB) || (soft_i2c_dev->sda_port == GPIOB))
    {
        RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOB, ENABLE);
    }
#endif
#if defined(GPIOC)
    if((soft_i2c_dev->scl_port == GPIOC) || (soft_i2c_dev->sda_port == GPIOC))
    {
        RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOC, ENABLE);
    }
#endif
#if defined(GPIOD)
    if((soft_i2c_dev->scl_port == GPIOD) || (soft_i2c_dev->sda_port == GPIOD))
    {
        RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOD, ENABLE);
    }
#endif
#if defined(GPIOE)
    if((soft_i2c_dev->scl_port == GPIOE) || (soft_i2c_dev->sda_port == GPIOE))
    {
        RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOE, ENABLE);
    }
#endif
#if defined(GPIOF)
    if((soft_i2c_dev->scl_port == GPIOF) || (soft_i2c_dev->sda_port == GPIOF))
    {
        RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOF, ENABLE);
    }
#endif
#if defined(GPIOG)
    if((soft_i2c_dev->scl_port == GPIOG) || (soft_i2c_dev->sda_port == GPIOG))
    {
        RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOG, ENABLE);
    }
#endif

    //初始化设置
    GPIO_Initure.GPIO_Pin = soft_i2c_dev->scl_pin;
    GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_OD; //推挽输出

    GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz; //快速
    GPIO_Init(soft_i2c_dev->scl_port, &GPIO_Initure);

    GPIO_Initure.GPIO_Pin = soft_i2c_dev->sda_pin;
    GPIO_Init(soft_i2c_dev->sda_port, &GPIO_Initure);

#elif defined(STM32F4)
    /* 打开GPIO时钟 */
#if defined(GPIOA)
    if((soft_i2c_dev->scl_port == GPIOA) || (soft_i2c_dev->sda_port == GPIOA))
    {
        RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOA, ENABLE);
    }
#endif
#if defined(GPIOB)
    if((soft_i2c_dev->scl_port == GPIOB) || (soft_i2c_dev->sda_port == GPIOB))
    {
        RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOB, ENABLE);
    }
#endif
#if defined(GPIOC)
    if((soft_i2c_dev->scl_port == GPIOC) || (soft_i2c_dev->sda_port == GPIOC))
    {
        RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOC, ENABLE);
    }
#endif
#if defined(GPIOD)
    if((soft_i2c_dev->scl_port == GPIOD) || (soft_i2c_dev->sda_port == GPIOD))
    {
        RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOD, ENABLE);
    }
#endif
#if defined(GPIOE)
    if((soft_i2c_dev->scl_port == GPIOE) || (soft_i2c_dev->sda_port == GPIOE))
    {
        RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOE, ENABLE);
    }
#endif
#if defined(GPIOF)
    if((soft_i2c_dev->scl_port == GPIOF) || (soft_i2c_dev->sda_port == GPIOF))
    {
        RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOF, ENABLE);
    }
#endif
#if defined(GPIOG)
    if((soft_i2c_dev->scl_port == GPIOG) || (soft_i2c_dev->sda_port == GPIOG))
    {
        RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOG, ENABLE);
    }
#endif
#if defined(GPIOH)
    if((soft_i2c_dev->scl_port == GPIOH) || (soft_i2c_dev->sda_port == GPIOH))
    {
        RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOH, ENABLE);
    }
#endif

    //初始化设置
    GPIO_Initure.GPIO_Pin = soft_i2c_dev->scl_pin;
    GPIO_Initure.GPIO_Mode = GPIO_Mode_OUT; //推挽输出
    GPIO_Initure.GPIO_OType = GPIO_OType_PP;

    GPIO_Initure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz; //快速
    GPIO_Init(soft_i2c_dev->scl_port, &GPIO_Initure);

    GPIO_Initure.GPIO_Pin = soft_i2c_dev->sda_pin;
    GPIO_Init(soft_i2c_dev->sda_port, &GPIO_Initure);
#endif
#endif

#if defined(N32G4FR)
    GPIO_InitType GPIO_Initure;

    /* 打开GPIO时钟 */
#if defined(GPIOA)
    if((soft_i2c_dev->scl_port == GPIOA) || (soft_i2c_dev->sda_port == GPIOA))
    {
        RCC_EnableAPB2PeriphClk ( RCC_APB2_PERIPH_GPIOA, ENABLE);
    }
#endif
#if defined(GPIOB)
    if((soft_i2c_dev->scl_port == GPIOB) || (soft_i2c_dev->sda_port == GPIOB))
    {
        RCC_EnableAPB2PeriphClk ( RCC_APB2_PERIPH_GPIOB, ENABLE);
    }
#endif
#if defined(GPIOC)
    if((soft_i2c_dev->scl_port == GPIOC) || (soft_i2c_dev->sda_port == GPIOC))
    {
        RCC_EnableAPB2PeriphClk ( RCC_APB2_PERIPH_GPIOC, ENABLE);
    }
#endif
#if defined(GPIOD)
    if((soft_i2c_dev->scl_port == GPIOD) || (soft_i2c_dev->sda_port == GPIOD))
    {
        RCC_EnableAPB2PeriphClk ( RCC_APB2_PERIPH_GPIOD, ENABLE);
    }
#endif
#if defined(GPIOE)
    if((soft_i2c_dev->scl_port == GPIOE) || (soft_i2c_dev->sda_port == GPIOE))
    {
        RCC_EnableAPB2PeriphClk ( RCC_APB2_PERIPH_GPIOE, ENABLE);
    }
#endif
#if defined(GPIOF)
    if((soft_i2c_dev->scl_port == GPIOF) || (soft_i2c_dev->sda_port == GPIOF))
    {
        RCC_EnableAPB2PeriphClk ( RCC_APB2_PERIPH_GPIOF, ENABLE);
    }
#endif
#if defined(GPIOG)
    if((soft_i2c_dev->scl_port == GPIOG) || (soft_i2c_dev->sda_port == GPIOG))
    {
        RCC_EnableAPB2PeriphClk ( RCC_APB2_PERIPH_GPIOG, ENABLE);
    }
#endif

    //初始化设置
    GPIO_Initure.Pin = soft_i2c_dev->scl_pin;
    GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_OD;  /* 输出 */

    GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz; //快速
    GPIO_InitPeripheral(soft_i2c_dev->scl_port, &GPIO_Initure);

    GPIO_Initure.Pin = soft_i2c_dev->sda_pin;
    GPIO_InitPeripheral(soft_i2c_dev->sda_port, &GPIO_Initure);
#endif
}

接下来就是温度读取。

/**
  * @brief  Mlx90614读取温度值
  * @param  soft_i2c_dev, devAddr, regAddr
  * @retval 温度值
  */
int Dev_MLX90614_ReadTemp(ST_Soft_I2C_Dev *soft_i2c_dev, uint8_t devAddr, uint8_t regAddr) 
{
    uint16_t data;

    if( 0 == Dev_MLX90614_ReadReg(soft_i2c_dev, devAddr, regAddr, &data))
    {
        return data;
    }
    else
    {
        return MLX90614_TEMP_READ_ERR_CODE;
    }
}

根据MLX90614的读写时序,其核心函数如下。

/**
  * @brief  Mlx90614读寄存器
  * @param  soft_i2c_dev
  * @retval 返回值 0 表示正确, 返回1表示未探测到
  */
int8_t Dev_MLX90614_ReadReg(ST_Soft_I2C_Dev *soft_i2c_dev, uint8_t devAddr, uint8_t regAddr,uint16_t *pReadData)
{
    uint8_t ucAck = 0;
    uint8_t ValBuf[6] = {0};
    uint8_t prcRegVal = 0;
    
    ValBuf[0] = devAddr << 1;
    ValBuf[1] = regAddr;
    ValBuf[2] = (devAddr << 1) | 0x01;

    Dev_Soft_I2C_Start(soft_i2c_dev);
    Dev_Soft_I2C_Send_Byte(soft_i2c_dev, ValBuf[0]);
    ucAck = Dev_Soft_I2C_Wait_Ack(soft_i2c_dev);
    if(ucAck)
    {
        /* 如果LM75X,没有应答 */
        goto cmd_fail;    /* 器件无应答 */
    }
    Dev_Soft_I2C_Send_Byte(soft_i2c_dev, ValBuf[1]);
    ucAck = Dev_Soft_I2C_Wait_Ack(soft_i2c_dev);
    if(ucAck)
    {
        /* 如果LM75X,没有应答 */
        goto cmd_fail;    /* 器件无应答 */
    }

    Dev_Soft_I2C_Start(soft_i2c_dev);
    Dev_Soft_I2C_Send_Byte(soft_i2c_dev, ValBuf[2]);
    ucAck =  Dev_Soft_I2C_Wait_Ack(soft_i2c_dev);
    if(ucAck)
    {
        /* 如果LM75X,没有应答 */
        goto cmd_fail;    /* 器件无应答 */
    }

    ValBuf[3] =  Dev_Soft_I2C_Read_Byte(soft_i2c_dev, 1);
    ValBuf[4] =  Dev_Soft_I2C_Read_Byte(soft_i2c_dev, 1);
    ValBuf[5] =  Dev_Soft_I2C_Read_Byte(soft_i2c_dev, 0);

    /* 发送I2C总线停止信号 */
    Dev_Soft_I2C_Stop(soft_i2c_dev);

    prcRegVal = CRC8_Calc(ValBuf,5);
    if(prcRegVal == ValBuf[5])
    {
        *pReadData = (ValBuf[4] << 8) + ValBuf[3];
        return 0;    /* 执行成功 */
    }
    else
    {
        return -2;  //校验不正确
    }
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
    /* 发送I2C总线停止信号 */
    Dev_Soft_I2C_Stop(soft_i2c_dev);
    return -1;
}

最后在Temperature任务中调用Dev_MLX90614_ReadTemp()函数不断读取温度值。

5.2 MLX90614体温矫正

非接触式红外温度传感器测额温或是体表温度和实际体温是有一定的差异的。因此采用了迈来芯原厂提供的额温转体温参考算法来提高测温精度,以补偿后的温度作为人体体温输出。其核心算法如下:

/**
  * @brief  额温转体温算法
  * @param  ta为芯片内部温度,tf为额温
  * @retval tbody为体温
  */
float TF_to_Tbody(float ta, float tf)
{
    float tf_low, tf_high = 0;
    float tbody = 0;

    if(ta <= TA_LEVEL)
    {
        tf_low  = 32.66f + 0.186f*(ta-TA_LEVEL);
        tf_high = 34.84f + 0.148f*(ta-TA_LEVEL);
    }
    else
    {
        tf_low  = 32.66f + 0.086f*(ta-TA_LEVEL);
        tf_high = 34.84f + 0.1f*(ta-TA_LEVEL);
    }

    if((tf_low <= tf) && (tf <= tf_high))
    {
        //normal
        tbody = 36.3f + 0.5f/(tf_high - tf_low)*(tf - tf_low);
    }
    else if(tf > tf_high)
    {
        tbody = 36.8f + (0.829321f + 0.002364f*ta)*(tf-tf_high);
    }
    else if(tf < tf_low)
    {
        tbody =  36.3f + (0.551658f + 0.021525f*ta)*(tf-tf_low);
    }
    
    return tbody;
}

5.3 红外测温枪顶层应用实现

关于OLED、按键等具体实现就赘述了,整个工程的代码结构如下。

1675582727158of6ksp5z24

只需关注三个目录:User、BSP、Dev_driver。

  • User为主函数和任务的入口。
  • BSP的平台的外设驱动。
  • Dev_driver为设备抽象层,将一个个外设看成一个个设备,与平台无关,方便用户移植。

这里只讲解红外测温枪顶层应用实现。

首先看下主函数。

/**
  * @brief  main
  * @param  None
  * @retval 
  */
int main(void)
{
    BSP_Init();  //初始化

    //创建开始任务
    System_Init();

    vTaskStartScheduler();  //开启任务调度

    return 0; 
}

主函数中首先是硬件初始化。

/**
  * @brief  BSP_Init,板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @param  None
  * @retval None
  */
void BSP_Init(void)
{
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );

    /*LED init*/
    BSP_LED_Init(&BSP_LED_Dev0);
    BSP_LED_Init(&BSP_LED_Dev1);
    BSP_LED_Init(&BSP_LED_Dev2);

    /*Buzzer init*/
    BSP_Buzzer_Init(&BSP_Buzzer_Dev0);

    // OLED Init
    Dev_OLED_Init(&OLED_Dev);

    //MLX90614 Init
    Dev_MLX90614_Init(&MLX90614_Dev);
    Dev_MLX90614_ReadTemp(&MLX90614_Dev, MLX90614_ADDR, MLX90614_TAMB);

    /* USART1 配置模式为 115200 8-N-1,中断接收 */
    BSP_USART_Init(&BSP_USART_Dev0, 115200, 2, 1);

    /* I2C 外设初(AT24C02)始化*/
    Dev_EEPROM_Init(&EEPROM_Dev, EEPROM_DEV_ADDR);

    //key init
    BSP_Key_Init(&BSP_Key_Dev0, KEY_MODE_EXTI, 2, 0);
    BSP_Key_Init(&BSP_Key_Dev1, KEY_MODE_EXTI, 2, 1);

    /*PB4复用Jtag*/
    /* 使能(开启)引脚对应IO端口时钟,因为引脚默认是JTAG功能,现在需要配置为其他功能,这里需要使能复用时钟 */  
    RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO, ENABLE);
  
    /* Disable the Serial Wire Jtag Debug Port*/
    GPIO_ConfigPinRemap(GPIO_RMP_SW_JTAG_NO_NJTRST, ENABLE);
}

任务初始化代码以及信号量、消息队列等初始化代码如下。

/**
  * @brief  System_Init
  * @param  None
  * @retval None
  */
void System_Init(void)
{    
    /* 创建 Queue */
    Key_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
                             (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
    if (NULL != Key_Queue)
    {
#if defined(USING_DEBUG)
        printf("Create Key Queue succeeded\r\n");
#endif
    }

    TempState_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
                                   (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
    if (NULL != TempState_Queue)
    {
#if defined(USING_DEBUG)
        printf("Create TempState Queue succeeded\r\n");
#endif
    }

    /* 创建 BinarySem */
    Temp_BinarySem_Handle = xSemaphoreCreateBinary();
    if (NULL != Temp_BinarySem_Handle)
    {
#if defined(USING_DEBUG)
        printf("Create temperature BinarySem succeeded\r\r\n");
#endif
    }

    EEPROM_BinarySem_Handle = xSemaphoreCreateBinary();
    if (NULL != EEPROM_BinarySem_Handle)
    {
#if defined(USING_DEBUG)
        printf("Create EEPROM BinarySem succeeded\r\r\n");
#endif
    }

    //创建LED任务
    xTaskCreate((TaskFunction_t )LED_Task,
                (const char*    )"LED_Task",
                (uint16_t       )LED_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )LED_TASK_PRIO,
                (TaskHandle_t*  )&LED_Task_Handler);

    //创建Temperature任务
    xTaskCreate((TaskFunction_t )Temperature_Task,
                (const char*    )"Temperature_Task",
                (uint16_t       )TEMPERATURE_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )TEMPERATURE_TASK_PRIO,
                (TaskHandle_t*  )&Temperature_Task_Handler);

    //创建KEY任务
    xTaskCreate((TaskFunction_t )Key_Task,
                (const char*    )"Key_Task",
                (uint16_t       )KEY_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )KEY_TASK_PRIO,
                (TaskHandle_t*  )&Key_Task_Handler);

    //创建EEPROM任务
    xTaskCreate((TaskFunction_t )EEPROM_Task,
                (const char*    )"EEPROM_Task",
                (uint16_t       )EEPROM_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )EEPROM_TASK_PRIO,
                (TaskHandle_t*  )&EEPROM_Task_Handler);
}

接下来看看几个任务的实现。

/**
  * @brief  LED Task
  * @param  parameter
  * @retval None
  */
void LED_Task(void* parameter)
{
    uint32_t r_queue; /* 定义一个接收消息的变量 */
    while (1)
    {
        xQueueReceive( TempState_Queue, /* 消息队列的句柄 */
                       &r_queue, /* 发送的消息内容 */
                       portMAX_DELAY); /* 等待时间 一直等 */

        switch(r_queue)
        {
            case TEMP_LOW:
                BSP_Buzzer_On(&BSP_Buzzer_Dev0);
                BSP_LED_On(&BSP_LED_Dev1);
                vTaskDelay(500);   /* 延时500个tick */
                BSP_Buzzer_Off(&BSP_Buzzer_Dev0);
                BSP_LED_Off(&BSP_LED_Dev1);
                break;
            case TEMP_NORMAL:
                BSP_Buzzer_On(&BSP_Buzzer_Dev0);
                BSP_LED_On(&BSP_LED_Dev2);
                vTaskDelay(500);   /* 延时500个tick */
                BSP_Buzzer_Off(&BSP_Buzzer_Dev0);
                BSP_LED_Off(&BSP_LED_Dev2);
                break;
            case TEMP_HIGH:
                BSP_Buzzer_On(&BSP_Buzzer_Dev0);
                BSP_LED_On(&BSP_LED_Dev0);
                vTaskDelay(500);   /* 延时500个tick */
                BSP_Buzzer_Off(&BSP_Buzzer_Dev0);
                BSP_LED_Off(&BSP_LED_Dev0);
            default:
                break;
        }
    }
}

#define EEPROM_SAVE_TEMP_SIZE   40

/**
  * @brief  Temperature Task
  * @param  parameter
  * @retval None
  */
void Temperature_Task(void* parameter)
{
    uint8_t writeBuff[2];
    uint32_t index = 0;
    int unDataTemp;
    float fSensorTemp = 0;
    float fTemperature = 0;
    uint8_t fTempString[16] = {0};
    uint32_t unTempState = 0;

    while (1)
    {
        //获取二值信号量 xSemaphore,没获取到则一直等待
        xSemaphoreTake(Temp_BinarySem_Handle,/* 二值信号量句柄 */
                                 portMAX_DELAY); /* 等待时间 */
        unDataTemp = Dev_MLX90614_ReadTemp(&MLX90614_Dev, MLX90614_ADDR, MLX90614_TOBJ1);
        fTemperature = unDataTemp*0.02 - 273.15;
        //printf("unDataTemp = %x \r\n", unDataTemp);
        if(index > ((EEPROM_SAVE_TEMP_SIZE >> 1) - 1))
        {
            index = 0;
        }
        writeBuff[0] = (uint16_t)unDataTemp & 0xFF;
        writeBuff[1] = (uint16_t)unDataTemp >> 8;
        Dev_EEPROM_WriteBytes(&EEPROM_Dev, EEPROM_DEV_ADDR, writeBuff, index, 2);

        index += 2;
        vTaskDelay(5); 
        unDataTemp = Dev_MLX90614_ReadTemp(&MLX90614_Dev, MLX90614_ADDR, MLX90614_TAMB);
        fSensorTemp = unDataTemp*0.02 - 273.15;
        //printf("MLX90614 sensor:%.2f ℃\r\n",fSensorTemp);
        writeBuff[0] = (uint16_t)unDataTemp & 0xFF;
        writeBuff[1] = (uint16_t)unDataTemp >> 8;
        Dev_EEPROM_WriteBytes(&EEPROM_Dev, EEPROM_DEV_ADDR, writeBuff, index, 2);
        index += 2;

        fTemperature = TF_to_Tbody(fSensorTemp, fTemperature);
        if((fTemperature - 38.0f) >= 0.001f)
        {
            unTempState = TEMP_HIGH;
        }
        else if((fTemperature - 35.5f) >= 0.001f)
        {
            unTempState = TEMP_NORMAL;
        }
        else
        {
            unTempState = TEMP_LOW;
        }
        printf("Temperature : %.2f ℃\r\n",fTemperature);
        xQueueSend(TempState_Queue, &unTempState, 0);//释放信号量
        Dev_OLED_CLS(&OLED_Dev);//清屏
        sprintf((char *)fTempString,"Temp: %.2f C",fTemperature);
        Dev_OLED_ShowStr(&OLED_Dev, 5,5,(unsigned char*)fTempString, 2);
        vTaskDelay(50);   /* 延时1000个tick */
    }
}

/**
  * @brief  Key Task
  * @param  parameter
  * @retval None
  */
void Key_Task(void* parameter)
{
    BaseType_t xReturn = pdPASS;
    uint32_t r_queue; /* 定义一个接收消息的变量 */
    while (1)
    {
        xQueueReceive( Key_Queue, /* 消息队列的句柄 */
                       &r_queue, /* 发送的消息内容 */
                       portMAX_DELAY); /* 等待时间 一直等 */
        switch(r_queue)
        {
            case KEY1_QUEUE_VALUE:
                xReturn = xSemaphoreGive(Temp_BinarySem_Handle );//给出二值信号量
                if ( xReturn == pdTRUE )
                {
#if defined(USING_DEBUG)
                    printf("Send Temp_BinarySem_Handle succeeded\r\n");
#endif
                }
                else
                {
#if defined(USING_DEBUG)
                    printf("Send Temp_BinarySem_Handle faled\r\n");
#endif
                }
                break;
            case KEY2_QUEUE_VALUE:
                xReturn = xSemaphoreGive(EEPROM_BinarySem_Handle );//给出二值信号量
                if ( xReturn == pdTRUE )
                {
#if defined(USING_DEBUG)
                    printf("Send EEPROM_BinarySem_Handle succeeded\r\n");
#endif
                }
                else
                {
#if defined(USING_DEBUG)
                    printf("Send EEPROM_BinarySem_Handle faled\r\n");
#endif
                }
                break;
            default:
                break;
        }
        vTaskDelay(50);  /* 延时50个tick */
    }
}

/**
  * @brief  EEPROM Task
  * @param  parameter
  * @retval None
  */
void EEPROM_Task(void* parameter)
{
    uint8_t readBuff[EEPROM_SAVE_TEMP_SIZE];
    uint32_t i;
    uint16_t unDataTemp;
    float fTemperature = 0;
    float fSensorTemp = 0;
    while(1)
    {
        //获取二值信号量 xSemaphore,没获取到则一直等待
        xSemaphoreTake(EEPROM_BinarySem_Handle,/* 二值信号量句柄 */
                       portMAX_DELAY); /* 等待时间 */

        Dev_EEPROM_ReadBytes(&EEPROM_Dev, EEPROM_DEV_ADDR, readBuff, 0, EEPROM_SAVE_TEMP_SIZE);
        printf("History Temperature:\r\n");
        for(i=0; i < EEPROM_SAVE_TEMP_SIZE/4; i=i+4)
        {
            unDataTemp = readBuff[i] + (readBuff[i+1] << 8);
            fTemperature = unDataTemp*0.02 - 273.15;
            fSensorTemp = readBuff[i+2] + (readBuff[i+3] << 8);
            fTemperature = unDataTemp*0.02 - 273.15;
            fTemperature = TF_to_Tbody(fSensorTemp, fTemperature);
            printf("%.2f, ", fTemperature);
        }

        printf("\r\n");
        vTaskDelay(50);  /* 延时50个tick */
    }
}

按键KEY1用于控制温度测量,按键KEY2用于导数历史数据,通过串口输出。OLED会实时显示温度值。

不同的温度值,不同的LED会闪烁,代表不同的温度等级,红灯表示发烧,绿灯表示体温正常,蓝灯表示低温。

6 功能演示

按下按键KEY1,蜂鸣器会发出滴答声,OLED会显示人体温度,同时回通过串口输出温度值。

1675582727549jvk0cy2l7l

另外在测温的同时还会将温度值存到AT24C02中,这里存了10组值,当按下KEY2,则会输出10组温度值。

1675582727907bzm6cfx3bi

C:\Users\BruceOu\Documents\Tencent Files\270139773\FileRecv\MobileFile\1675581205832.jpg

7 总结

1.测试结果

经过上述方案的检测,能满足预期要求,测温误差在正常范围内。

2.主要误差分析

当测量距离增加时,由于红外温度测量系统的视场不变,如果被测物体不能填满视场,那么系统检测到的来自被测物体的红外辐射能量就会减少,从而导致测量精度降低。另一方面,当测量距离增加时,在大气吸收的影响下,温度测量系统接收的辐射将减少,导致测温系统产生误差。因此推荐距离为2-4cm。

Note:红外体温枪用到了很多外设,相关的原理和使用方法请参看笔者的帖子,这里重点讲解MLX90614的实现。

更多回帖

发帖
×
20
完善资料,
赚取积分