草庐IT

【STM32篇】驱动MXL90614红外测温模块

这可不是猴 2023-06-02 原文

本次实验使用的测温模块型号GY-906-DCC模块,测距为10cm左右。

一、简介

MLX90614 是一款红外非接触温度计。TO-39 金属封装里同时集成了红外感应热电堆探测器芯片和信处理专用集成芯片。

由于集成了低噪声放大器、17 位模数转换器和强大的数字信号处理单元,使得高精度和高分辨度的温计得以实现。温度计具备出厂校准化,有数字 PWM 和 SMBus(系统管理总线)输出模式。

作为标准,配置为 10 位的 PWM 输出格式用于连续传送温度范围为-20…120 ˚C 的物体温度,其分辨率为 0.14 ˚C。POR 默认模式是 SMBus 输出格式。

图1.典型应用电路图

(一)引脚定义和描述

图2.顶视图

引脚名

功能

VSS

地。 金属罐也连接到该引脚上。

SCL/Vz

两线通信协议的串行时钟信号。该引脚上的 5.7V 齐纳二极管用于连接外部双极晶体管

以实现对器件高电源电压 8 …16V 的应用。

PWM/SDA

数字信号输入 / 输出。正常模式下该引脚以脉宽调制方式输出测量的物体温度。

SMBus 兼容模式下自动配置为开漏 NMOS。

VDD

外部电源电压。

(二)MLX90614 存储器

EEPROM

EEPROM 里只有限定数目的地址是允许客户改写的。整个 EEPROM 可通过 SMBus 接口读和写。

图3.EEPROM

MLX90614 的 EEPROM 有 32 个 16 位存储单元,其中存储单元 Tomax,Tomin,Ta 分别是用户物体温度上下限和环境温度范围,PWMCTRL 是 PWM 配置寄存器。

MLX90614 可通过 PWM 或是 SMBus 兼容接口读取,由 EEPROM 配置选择 PWM 输出模式。 (出厂默认为SMBus) PWM 输出有两种可编程的格式,单个和双重数据传送,提供单线读取两个温度的功能。(双重可以选择物体或是物体和环境温度) PWM 周期通过片上振荡器驱动并可编程。

RAM

RAM 中不能写入数据,只能进行读取,并且只有有限数目是客户感兴趣的。

图4.RAM

MLX90614 的RAM有 32 个 17 位存储单元,其中TA,TOBJ1,TOBJ2是环境温度和物体温度,在SMBus方式下,可以从这几个存储单元读出环境和被测物体的温度。

命令 Command

图5.命令码

xxxxx 代表要读取/写入的内存地址的 低5 位。

(三)两线协议

SMBus 接口为两线协议,允许主控器件 (MD) 和一个或是一个以上的从动器件 (SD) 通信。系统在给定的时刻只有一个主控器件。MLX90614 只作为从动器件使用。

使用模拟IIC实现SMBus通信。

图6.IIC通信协议

起始信号和停止信号

图7.起始信号和停止信号

起始信号:在 SCL 线是高电平时 SDA 线从高电平向低电平切换。

停止信号:SCL 是高电平时 SDA 线由低电平向高电平切换。

起始和停止条件一般由主机产生 总线在起始条件后被认为处于忙的状态 在停止条件的某段时间后总线被认为再次处于空闲状态。如果产生重复起始 Sr 条件而不产生停止条件 总线会一直处于忙的状态。

//起始信号
void MLX906_START_Sign(void)
{
    MLX906_SDA_OUT();//输出模式
    MLX906_SDA_H;
    MLX906_SCL_H;
    Delay_us(2);
    MLX906_SDA_L;
    Delay_us(2);
    MLX906_SCL_L;//钳住总线,准备开始数据传输
    Delay_us(1);
}
//停止信号
void MLX906_STOP_Sign(void)
{
    MLX906_SDA_OUT();//输出模式
    MLX906_SCL_L;
    MLX906_SDA_L;
    Delay_us(2);
    MLX906_SCL_H;
    MLX906_SDA_H;
    Delay_us(1);
}

应答信号和非应答信号

图8.应答信号

SCL在高电平期间SDA始终处于低电平,需要在传输完毕一个字节后发送。

//主机应答(ACK信号)
void MLX906_Ack(void)
{ 
    MLX906_SDA_OUT();//输出模式
    MLX906_SDA_L;
    MLX906_SCL_L;
    Delay_us(2);
    MLX906_SCL_H;
    Delay_us(2);
    MLX906_SCL_L;
}

图9.非应答信号

SCL在高电平期间SDA始终处于高电平,需要在传输完毕一个字节后发送。

//不产生ACK应答信号
void MLX906_Nack(void)
{
    MLX906_SDA_OUT();//输出模式
    MLX906_SCL_L;
    MLX906_SDA_H;
    Delay_us(2);
    MLX906_SCL_H;
    Delay_us(2);
    MLX906_SCL_L;
    Delay_us(2);
}

响应

图10.IIC总线的响应

数据传输必须带响应。相关的响应时钟脉冲由主机产生。在响应的时钟脉冲期间,发送器释放SDA线(高)。

在响应的时钟脉冲期间 ,接收器必须将SDA 线拉低 ,使它在这个时钟脉冲的高电平期间保持稳定的低电平。

uint8_t MLX906_Wait_Ack(void)
{
    MLX906_SDA_IN();//输入模式
    uint8_t utime=0;
    MLX906_SDA_H;
    Delay_us(1);
    MLX906_SCL_H;
    Delay_us(1);
    while(MLX906_READ_SDA())
    {
        utime++;
        if(utime>=255)
        {
            MLX906_STOP_Sign();
            return 1;//非应答信号
        }
    }
    MLX906_SCL_L;
    return 0;//应答信号
}

数据传输

在数据传输过程中,数据线SDA需在时钟线SCL为高电平时保持稳定,在低电平期间可修改或读取数据线电平状态。

发送到 SDA 线上的每个字节必须为8位 ,每次传输可以发送的字节数量不受限制 ,每个字节后必须跟一个响应位。

发送数据:

//发送一个字节数据
uint8_t MLX906_SendByte(uint8_t byte1)
{
    uint8_t i,byte;
    byte=byte1;
    MLX906_SDA_OUT();//输出模式
    for(i=0;i<8;i++)//高位先发
    {
        if(byte&0x80)
        {
            MLX906_SDA_H;
        }
        else
        {
            MLX906_SDA_L;
        }
        MLX906_SCL_H;
        Delay_us(2);
        MLX906_SCL_L;
        Delay_us(2);
        byte<<=1;
    }
    MLX906_SDA_H;//释放总线
    return MLX906_Wait_Ack();//等待响应
}

读数据:

//读取一个字节
uint8_t MLX906_ReadByte(void)
{
    uint8_t byte=0;
    uint8_t i;
    MLX906_SDA_IN();//输入模式
    for(i=0;i<8;i++)
    {
        MLX906_SCL_H;//SCL高电平时读取数据
        if(MLX906_READ_SDA())
        {
            byte|= 1<<(7-i);
        }
        MLX906_SCL_L;//下降沿时从机修改数据
        Delay_us(2);
    }
    MLX906_Nack();
    return byte;
}

二、总线协议

图11.SMBus 数据包组成

在 SD 接收到每个 8 位数据后,会回复 ACK/NACK 信息。当 MD 初始化通信,将首先发送受控地址,只有能识别该地址的 SD 会确认,其它的会保持沉默。如果 SD 未确认其中的任意字节,MD 应停止通信并重新发送信息。NACK 也会在 PEC 接收后出现,这意味着在接收的信息有错误并且 MD 应重新发送信息。

PEC 的计算结果是基于 除 START,REPEATED START,STOP,ACK, 和 NACK 位外的所有位。 PEC 是 CRC-8 的多项式 aX8+X2+X1+1。每个字节的最高有效位首先传送。

(一)读数据

图12.SMBus读数据格式

(二)软件流程

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

图13.读数据流程图

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

//读内存
uint16_t MLX906_ReadMemory(void)
{
    uint8_t Pec,PecReg,ErrorCounter;
    uint8_t TempL=0;
    uint8_t TempH=0;
    uint8_t arr[6];
    ErrorCounter=0;
    do
    {
        ErrorCounter++;
        if(ErrorCounter==10)
        {
            return 0;
        }            
        MLX906_START_Sign();//起始信号
        if(MLX906_SendByte(0x00))//发送MLX90614地址
            continue;
        if(MLX906_SendByte(0x07))//发送读MLX90614 RAM地址
            continue;
        MLX906_START_Sign();//重新启动
        if(MLX906_SendByte(0x01))//发送数据采集命令
            continue;
        TempL=MLX906_ReadByte();//读取地位数据
        TempH=MLX906_ReadByte();//读取高位数据
        Pec=MLX906_ReadByte();//读取校验位
        MLX906_STOP_Sign();//停止信号
        arr[5]= 0x00;
        arr[4]= 0x07;
        arr[3]= 0x01;
        arr[2]= TempL;
        arr[1]= TempH;
        arr[0]= 0;
        PecReg=CRC_Calculation(arr);//计算CRC校验
    }while(PecReg!=Pec);
    return (uint16_t)((TempH<<8)|TempL);
}

(三)环境温度和物体温度的计算

环境温度Ta

物体温度To

//读温度
float MLX906_Read_Temp(void)
{
    return (float )MLX906_ReadMemory()*0.02-273.15;
}

三、实验结果

手腕温度测量(未校准),可能有所误差。

图14.手腕温度测量

mian函数中轮询打印。

图15.UART读取温度数据

四、附件

mlx906.h

#ifndef _MLX906_H_
#define _MLX906_H_

#include "stm32f10x.h"
#include "systick.h"

/*
引脚连接:

    SCL  时钟线  - PB10
    SDA     数据线  - PB11

*/
#define MLX906_SCL_H GPIO_WriteBit(GPIOB,GPIO_Pin_10,Bit_SET)
#define MLX906_SCL_L GPIO_WriteBit(GPIOB,GPIO_Pin_10,Bit_RESET)
#define MLX906_SDA_H GPIO_WriteBit(GPIOB,GPIO_Pin_11,Bit_SET)
#define MLX906_SDA_L GPIO_WriteBit(GPIOB,GPIO_Pin_11,Bit_RESET)
#define MLX906_READ_SDA() GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)

//配置SDA数据线的输入输出模式
#define MLX906_SDA_OUT() ?????//输出模式 自己配置哦
#define MLX906_SDA_IN()  ?????//输入模式

//此处为笔者小设计中才用到的,不用管
#define MLX906_STATE_READ 1
#define MLX906_STATE_UNREAD 0
#define ANOMALY_TEMP 37     //体温异常

extern float MLX906_Temp;
extern uint8_t  MLX906_Temp_State;

void MLX906_Init(void);
float MLX906_Read_Temp(void);

#endif

mlx906.c


#include "mlx906.h"

//MLX初始化函数
void MLX906_Init(void)
{
    //初始化引脚
    GPIO_InitTypeDef mlx906_gpio_init;
    mlx906_gpio_init.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;
    mlx906_gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
    mlx906_gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&mlx906_gpio_init);
    
    MLX906_SCL_H;//拉高引脚电平
    MLX906_SDA_H;
}

/*    IIC   时序    */

//起始信号
void MLX906_START_Sign(void)
{
    MLX906_SDA_OUT();//输出模式
    MLX906_SDA_H;
    MLX906_SCL_H;
    Delay_us(2);
    MLX906_SDA_L;
    Delay_us(2);
    MLX906_SCL_L;//钳住总线,准备开始数据传输
    Delay_us(1);
}
//停止信号
void MLX906_STOP_Sign(void)
{
    MLX906_SDA_OUT();//输出模式
    MLX906_SCL_L;
    MLX906_SDA_L;
    Delay_us(2);
    MLX906_SCL_H;
    MLX906_SDA_H;
    Delay_us(1);
}
uint8_t MLX906_Wait_Ack(void)
{
    MLX906_SDA_IN();//输入模式
    uint8_t utime=0;
    MLX906_SDA_H;
    Delay_us(1);
    MLX906_SCL_H;
    Delay_us(1);
    while(MLX906_READ_SDA())
    {
        utime++;
        if(utime>=255)
        {
            MLX906_STOP_Sign();
            return 1;//非应答信号
        }
    }
    MLX906_SCL_L;
    return 0;//应答信号
}
//主机应答(ACK信号)
void MLX906_Ack(void)
{
    
    MLX906_SDA_OUT();//输出模式
    MLX906_SDA_L;
    MLX906_SCL_L;
    Delay_us(2);
    MLX906_SCL_H;
    Delay_us(2);
    MLX906_SCL_L;
}
//不产生ACK应答信号
void MLX906_Nack(void)
{
    MLX906_SDA_OUT();//输出模式
    MLX906_SCL_L;
    MLX906_SDA_H;
    Delay_us(2);
    MLX906_SCL_H;
    Delay_us(2);
    MLX906_SCL_L;
    Delay_us(2);
}

//读取一个字节
uint8_t MLX906_ReadByte(void)
{
    uint8_t byte=0;
    uint8_t i;
    MLX906_SDA_IN();//输入模式
    for(i=0;i<8;i++)
    {
        MLX906_SCL_H;//SCL高电平时读取数据
        if(MLX906_READ_SDA())
        {
            byte|= 1<<(7-i);
        }
        MLX906_SCL_L;//下降沿时从机修改数据
        Delay_us(2);
    }
    MLX906_Nack();
    return byte;
}
//发送一个字节数据
uint8_t MLX906_SendByte(uint8_t byte1)
{
    uint8_t i,byte;
    byte=byte1;
    MLX906_SDA_OUT();//输出模式
    for(i=0;i<8;i++)//高位先发
    {
        if(byte&0x80)
        {
            MLX906_SDA_H;
        }
        else
        {
            MLX906_SDA_L;
        }
        MLX906_SCL_H;
        Delay_us(2);
        MLX906_SCL_L;
        Delay_us(2);
        byte<<=1;
    }
    MLX906_SDA_H;//释放总线
    return MLX906_Wait_Ack();//等待响应
}

//crc校验
uint8_t CRC_Calculation(uint8_t pec[])
{
    uint8_t crc[6];//存放多项式
    uint8_t BitPosition = 47;
    uint8_t shift;
    uint8_t i,j,temp;
    do
    {
        crc[5]=0;
        crc[4]=0;
        crc[3]=0;
        crc[2]=0;
        crc[1]=0x01;
        crc[0]=0x07;
        BitPosition = 47;
        shift = 0;
        i=5;
        j=0;
        while((pec[i]&(0x80>>j))==0 && i>0)
        {
            BitPosition--;
            if(j<7)
            {
                j++;
            }
            else
            {
                j= 0x00;
                i--;
            }
        }
        shift= BitPosition-8;
        while(shift)
        {
            for(i=5;i<0xFF;i--)
            {
                if((crc[i-1]&0x80)&&(i>0))
                {
                    temp=1;
                }
                else
                {
                    temp=0;
                }
                crc[i]<<=1;
                crc[i]+=temp;
            }
            shift--;
        }
        for(i=0;i<=5;i++)
        {
            pec[i]^=crc[i];
        }
    }while(BitPosition>8);
    return pec[0];
}
//读内存
uint16_t MLX906_ReadMemory(void)
{
    uint8_t Pec,PecReg,ErrorCounter;
    uint8_t TempL=0;
    uint8_t TempH=0;
    uint8_t arr[6];
    ErrorCounter=0;
    do
    {
        ErrorCounter++;
        if(ErrorCounter==10)
        {
            return 0;
        }            
        MLX906_START_Sign();//起始信号
        if(MLX906_SendByte(0x00))//发送MLX90614地址
            continue;
        if(MLX906_SendByte(0x07))//发送读MLX90614 RAM地址
            continue;
        MLX906_START_Sign();//重新启动
        if(MLX906_SendByte(0x01))//发送数据采集命令
            continue;
        TempL=MLX906_ReadByte();//读取地位数据
        TempH=MLX906_ReadByte();//读取高位数据
        Pec=MLX906_ReadByte();//读取校验位
        MLX906_STOP_Sign();//停止信号
        arr[5]= 0x00;
        arr[4]= 0x07;
        arr[3]= 0x01;
        arr[2]= TempL;
        arr[1]= TempH;
        arr[0]= 0;
        PecReg=CRC_Calculation(arr);//计算CRC校验
    }while(PecReg!=Pec);
    return (uint16_t)((TempH<<8)|TempL);
}
//读温度
float MLX906_Read_Temp(void)
{
    return (float )MLX906_ReadMemory()*0.02-273.15;
}

注:GPIO时钟已在main.c文件中做了相关配置。

SDA数据线的输入输出模式配置 由 读者自行配置。

望有助于各位理解,如有侵权,请联系删除。

2023/1/7

有关【STM32篇】驱动MXL90614红外测温模块的更多相关文章

  1. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  2. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  3. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  4. ruby - 当使用::指定模块时,为什么 Ruby 不在更高范围内查找类? - 2

    我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or

  5. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

  6. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  7. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  8. ruby-on-rails - 如何在 Ruby on Rails 中实现由 JSF 2.0 (Primefaces) 驱动的 UI 魔法 - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。问题1)我想知道ruby​​onrails是否有功能类似于primefaces的gem。我问的原因是如果您使用primefaces(http://www.primefaces.org/showcase-labs/ui/home.jsf),开发人员无需担心javascript或jquery的东西。据我所知,JSF是一个规范,基于规范的各种可用实现,prim

  9. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  10. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

随机推荐