果果小师弟 · 2020年07月11日

基于STM32的MLX90614人体红外测温枪

  今天分享一个项目是做一个红外测温的。这个东西网上都有现成的资料和代码,做起来不难。关于红外测温用的芯片是mlx90614。很巧的是“芯知识学堂”已经将他们的红外测温枪方案全部开源了出来。如果大家想自己做一个红外测温枪可以去看看他们的资料,自己尝试做着玩一下。
  那么在这里我就来写一写关于mlx90614红外测温的驱动代码和来说一说他的原理。主要是利用STM32F103C8T6单片机和mlx90614模块将测量的温度显示到0.96寸oled上面,如果你没有oled的话也可以用串口调试助手把温度信息打印出来。

一、所需材料

STM32F103C8T6最小系统板+mlx90614红外测温模块+0.96寸oled+一个jlink下载器。

二、mlx90614传感器介绍

  MLX90614 系列模块是一组通用的红外测温模块。在出厂前该模块已进行校验及线性化,具有非接触、体积小、精度高,成本低等优点。被测目标温度和环境温度能通过单通道输出,并有两种输出接口,适合于汽车空调、室内暖气、家用电器、手持设备以及医疗设备应用等。测温方式可分为接触式和非接触式,接触式测温只能测量被测物体与测温传感器达到热平衡后的温度,所以响应时间长,且极易受环境温度的影响;而红外测温是根据被测物体的红外辐射能量来确定物体的温度,不与被测物体接触,具有影响动被测物体温度分布场,温度分辨率高、响应速度快、测温范围广、不受测温上限的限制、稳定性好等特点,所以我们选择mlx90614来作为红外测温模块。
  单片机与mlx90614红外测温模块之间通信的方式是“类IIC”通信,意思就是通信方式跟IIC通信方式很像但又不是IIC,它有另外一个名字叫做SMBus。SMBus (System Management Bus)是 1995 年由 intel 公司提出的一种高效同步串行总线,SMBus 只有两根信号线:双向数据线和时钟信号线,容许 CPU 与各种外围接口器件以串行方式进行通信、交换信息,即可以提高传输速度也可以减小器件的资源占用,另外即使在没有SMBus 接口的单片机上也可利用软件进行模拟。

三、MLX90614工作原理


  MLX90614有MLX81101红外热电堆传感器和包括含有稳压电路、低噪声放大器、A/D转换器、DSP单元、脉宽调制电路及逻辑控制电路的MLX90302信号处理芯片构成。
  其工作原理为:红外热电堆传感器输出的温度信号经过内部低噪声、低失调的运算放大器(OPA)放大后经过A/D转换器(ADC)转换为17位数字信号通过可编程FIR及IIR低通数字滤波器(即DSP)处理后输出,输出结果存储在其内部RAM存储单元中。

  MLX90614中有两个存储器,分别为EEPROM和RAM。
  MLX90614中共有32个字长为16位的EEPROM存储单元,其地址为000H—01FH。
  EEPROM中所有的寄存器都是可以通过SMBus进行读取,但只有部分寄存器是可以进行改写的(地址为0x00, 0x01, 0x02, 0x03, 0x04, 0x05*,0x0E, 0x0F, 0x09)。可改写部分如左表所示。
在这里插入图片描述
  Tomax和Tomin是设定的测量物体温度上、下限,Ta范围即环境温度范围。
  其测量温度上限计算方法为:Tomax=100×(To MAX+273.15),通过计算将结果写入000H;温度下限计算方法与上限计算方法一样,将计算结果写入001H。
  MLX90614中总共有32个17位的RAM存储单元,用户不能通过RAM来写入数据,只能读取RAM中的部分存储单元读取16位存储数据。其采集的环境温度数据保存在地址006H存储单元中,采集的被测物体温度数据存储在007H存储单元中。因此运用存储在RAM地址中的数据,通过公式的计算,可以得到环境温度Ta及被测物体温度数据To。 在这里插入图片描述
  Ta和To既可通过SMBus读取RAM单元(分辨率0.02°C ,固定范围)输出,也可通过PWM数字模式输出(10位分辨率,范围可配置)。由于变电所测温温度范围与MLX90614出厂时校准的温度范围符合,因此可直接采用SMBus方式进行温度数据Ta和To的读取输出。
  Ta=RAM(006H)x0.02-273.15   To=RAM(007H)x0.02-273.15

四、IIC协议原理


IIC主从机之间通讯步骤如下:
1.主机发送一个起始信号通知各从机就位。
2.主机发送从机地址和读写标志位(写标志位为0,读标志位为1)
从机地址和读写标志位一共占用8位,地址占用高7位,读写标志位占用最低位,
3从机给主机回复响应(ACK)
4.如果是写模式,主机发送一字节数据等待从机响应,主机收到响应之后如果还有数据要发就继续发送第二段数据等待响应……直到发送完成;
如果是读模式,此时主机STM32读取从机发来的数据,并给从机响应,如果从机还有数据要发送(接着汇报第二段),主机接着读取然后发送响应给从机…
5.主机给从机一个停止信号

1.写时序


  首先主机发送一个起始位,然后在发送从机的地址0x00和写标志位0,一共是8位。发送这8位之后,主机等待从机的响应,如过从机发送的是应答信号,主机就继续向从机发送一个字节的数据,同理再一次等待从机的响应,主机收到响应之后如果还有数据要发就继续发送第二段数据等待响应…直到发送完成;
写所用到的函数有:
1.void SMBus_StartBit(void);----主机发送一个起始位

  1. u8 SMBus_ SendByte (u8 ack_nack)--- 主机发送从机的地址和写标志位
  2. SMBus_ReceiveBit(ack_nack);-- 从机发送的是应答信号
  3. u8 SMBus_SendByte(u8 ack_nack)—主机发送从机的一个字节的数据

5.void SMBus_StopBit(void)-- ----主机发送一个停止位

2.读时序


  首先主机发送一个起始位,然后在发送从机的地址0x00和读标志位1,一共是8位。发送这8位之后,主机等待从机的响应,如过从机发送的是应答信号,从机就向主机发送一个字节的数据。读时序就是主机从从机读数据,反过来就是从机向主机发送数据,同理,从机在发送完一个字节的数据之后也要向主机询问是否继续发送,如果主机让继续发送也就是发送了应答信号,那么从机就继续发送数据,每次发送完之后都要询问是否要继续发送,直到主机发送了非应答信号,从机才停止发送数据,最后主机再发送一个停止信号即可。
读所用到的函数有:
1.void SMBus_StartBit(void);---主机发送一个起始位

  1. u8 SMBus_ SendByte (u8 ack_nack)—主机发送从机的地址和写标志位
  2. SMBus_ReceiveBit(ack_nack);-- 从机发送的是应答信号
  3. u8 SMBus_ReceiveByte (u8 ack_nack)--从机向主机发送的一个字节的数据

5.void SMBus_StopBit(void)-- ----主机发送一个停止位

3.通信过程

  • 1.起始信号--在时钟线SCL高电平期间数据线SDA发生下降沿跳变产生起始信号
  • 2.应答信号--在时钟线SCL为高电平期间数据线SDA保持低电平为应答信号
  • 3.非应答信号--在时钟线SCL为高电平期间数据线SDA保持高电平为非应答信号
  • 4.结束信号--在时钟线SCL高电平期间数据线SDA发生上升沿跳变产生停止信号
  • 5.数据信号--在数据传输期间,时钟线 SCL 为高电平期间,如果数据线 SDA 为高电平则代表二进制1,同理,时钟线 SCL 为高电平期间,如果数据线 SDA 为低电平则代表二进制 0。
  • 6.上面1号框是SDA 数据有效期,2号框是数据改变期。

五、程序编写

1.起始信号与停止信号

void SMBus_StartBit(void)
{
    SMBUS_SDA_H();    // 首先拉高数据线
    SMBus_Delay(5);    // 延时几微妙
    SMBUS_SCK_H();  // 拉高时钟线
    SMBus_Delay(5);    // 延时几微妙
    SMBUS_SDA_L();  // 拉低数据线
    SMBus_Delay(5);    // 延时几微妙
    //在SCK=1时,检测到SDA由1到0表示通信开始(下降沿)
    SMBUS_SCK_L();    // 拉低时钟线
    SMBus_Delay(5);    // 延时几微妙
}
void SMBus_StopBit(void)
{
    SMBUS_SCK_L();   // 拉低时钟线
    SMBus_Delay(5);     // 延时几微妙
    SMBUS_SDA_L();   // 拉低数据线
    SMBus_Delay(5);     // 延时几微妙
    SMBUS_SCK_H();   // 拉高时钟线
    SMBus_Delay(5);     // 延时几微妙
    SMBUS_SDA_H();    // 拉高数据线
}

2.发送一个字节

u8 SMBus_SendByte(u8 Tx_buffer)
{
    u8    Bit_counter;
    u8    Ack_bit;
    u8    bit_out;
    for(Bit_counter=8; Bit_counter; Bit_counter--)
    {
        if (Tx_buffer&0x80)//如果最高位为1
        {
            bit_out=1;   // 把最高位置1
        }
        else  //如果最高位为0
        {
            bit_out=0;  // 把最高位置0
        }
        SMBus_SendBit(bit_out);    // 把最高位发送出去
        Tx_buffer<<=1;// 左移一位把最高位移出去等待下一个最高位,循环8次,每次都发最高位,就可把一个字节发出去了
    }
    Ack_bit=SMBus_ReceiveBit();    // Get acknowledgment bit
    return    Ack_bit;
}

  发送一个字节也就是8个bit位,我们的做法就是循环8次,每次将最高位发送出去。如果最高位为1,我们将这一个字节Tx_buffer和0x80(10000000)进行"与运算",把最高位置为1。如果最高位为0,就把最高位置为0,再通过 SMBus_SendBit(bit_out); 把最高位发送出去,之后再通过Tx_buffer<<=1;左移一位把最高位移出去等待下一个最高位,循环8次,每次都发最高位,就可把一个字节发出去了。这里是SMBus发送一个字节,我们知道在从机发送完一个字节之后,主机要返回一个应答信号告诉从机是否继续发送下一个字节,所以这里我们 使用Ack_bit=SMBus_ReceiveBit();这条语句来告诉从机书否还要继续发送下一个字节,如果返回的是0就继续发送,如果返回的是1就停止发送。

3.接收一个字节

u8 SMBus_ReceiveByte(u8 ack_nack)
{
    u8     RX_buffer;
    u8    Bit_Counter;
    for(Bit_Counter=8; Bit_Counter; Bit_Counter--)
    {
        if(SMBus_ReceiveBit())// Get a bit from the SDA line
        {
            RX_buffer <<= 1;// If the bit is HIGH save 1  in RX_buffer
            RX_buffer |=0x01;//如果Ack_bit=1,把收到应答信号1与0000 0001 进行或运算,确保为1
        }
        else
        {
            RX_buffer <<= 1;// If the bit is LOW save 0 in RX_buffer
            RX_buffer &=0xfe;//如果Ack_bit=1,把收到应答信号0与1111 1110 进行与运算,确保为0
        }
    }
    SMBus_SendBit(ack_nack);//把应答信号发出去,如果0,就进行下一次通信,如果为1,就拜拜了。
    return RX_buffer;
}

  从SMBus上接收一个字节也就是8位,我们也是用一个for循环将8各一次接受过来。首先问我们通过if函数来判断SMBus_ReceiveBit()是否是收到了应答信号,如果收到了的应答信号也就是1,我们就将收到的数据左移1位RX_buffer <<= 1;如果左移一位之后空出的那一位数据位1然后就与0x01也就是0000 0001 进行"或运算",确保为1。如果左移一位之后空出的那一位数据位0然后就与0xfe也就是1111 1110 进行"或运算",确保为0。最后SMBus把应答信号发出去,如果0,就进行下一次通信,如果为1,就拜拜了,就不接受数据了。

4.数据校验

u8 PEC_Calculation(u8 pec[])
{
    u8     crc[6];//存放多项式
    u8    BitPosition=47;//存放所有数据最高位,6*8=48 最高位就是47位
    u8    shift;
    u8    i;
    u8    j;
    u8    temp;
    do
    {    //Load pattern value 0x00 00 00 00 01 07
        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];
}

  这个数据校验是很重要的,目的就是来判断检测你采集的数据是否是是正确的。

  也就是我们程序会把PEC的数据通过SA_W、Command、SA_R把LSByte、MSByte读出来。然后我们自己再写一个校验的函数来判断读到的PEC数据是否正确,如果正确了才能进行下一步的判断,如果不正确的话就继续读,直到正确为止。如何才能知道自己读到的数据是否正确呢?这就需要自己单独建立一个C工程,把这个函数u8 PEC_Calculation(u8 pec[])放到里面运行一遍,看看自己输入的数据时候跟自己要得到数据数据是否一样就可以了。也就是把0x00、0x3a、0xd2、0xb5、0x07、0xb4放到这个函数运行,最终返回出来的数据是否是0x30就可以了,如果是就说明数据校验正确,如果不是就需要该函数,至于为啥返回的是0x30请看上面的时序表。

5.读取温度函数

u16 SMBus_ReadMemory(u8 slaveAddress, u8 command)
{
    u16 data;
    u8 Pec;    
    u8 DataL=0;
    u8 DataH=0;    
    u8 arr[6];
    u8 PecReg;
    u8 ErrorCounter;
    ErrorCounter=0x00;// Initialising of ErrorCounter
    slaveAddress <<= 1;    //2-7位表示从机地址 从机地址左移一位,把读写位空出来    
    do
    {
repeat:
        SMBus_StopBit();
        --ErrorCounter;    
        if(!ErrorCounter) //ErrorCounter=0?
        {
            break;    //如果为0就跳出do-while{}循环
        }
        SMBus_StartBit();
        if(SMBus_SendByte(slaveAddress))//发送从机地址最低位Wr=0表示接下来写命令
        {
            goto    repeat; 
        }
        if(SMBus_SendByte(command))//发送命令
        {
            goto    repeat;
        }
        SMBus_StartBit();
        if(SMBus_SendByte(slaveAddress+1))    //发送从机地址+1最低位Rd=1表示接下来读数据
        {
            goto    repeat; 
        }
        DataL = SMBus_ReceiveByte(ACK);    //读低位数据保存到DataL 
        DataH = SMBus_ReceiveByte(ACK); //读高位数据保存到DataH
        Pec = SMBus_ReceiveByte(NACK);    //读校验数据保存到Pec  
        SMBus_StopBit();
        arr[5] = slaveAddress;    
        arr[4] = command;
        arr[3] = slaveAddress+1;
        arr[2] = DataL;    
        arr[1] = DataH;    
        arr[0] = 0;    
        PecReg=PEC_Calculation(arr);//Calculate CRC 数据校验
    }
    while(PecReg != Pec);
    data = (DataH<<8) | DataL;
    return data;
}

  这个函数的输入参数就IIC设备的从机地址和command寄存器地址。首先从机地址左移一位,把读写位空出来,因为不知道最开始是啥状态,我们就需要发送一个停止信号,因为ErrorCounter=0x00;那么进行"--"操作之后就不等于0,就进行下面的操作。接下来就发送起始信号,发送从机设备地址,如果发送的从机地址正确,就接着发送操作从机地址的command这个地址,因为这里报存着温度数据。然后重新发起一个起始信号,开始读数据,把温度数据的低位DataL高位DataH 和PEC数据读出来,再进行PEC数据校验,判断读到的PEC数据和校验得到的PEC数据是否相等,相等的话就跳出循环,通过将高位数据左移8位再与低8位进行按位或就得到了最终的数据data。

6.得到最终温度值

float SMBus_ReadTemp(void)
{   
    float temp;
    temp = SMBus_ReadMemory(0x00, 0x07)*0.02-273.15;
    return temp;
}

  通过数据手册我们知道将最终读取的数据*0.02-273.15就会得到最终的温度实际值,通过串口打印或者oled显示就可以得到传感器读取的温度了。

六、CRC­8校验原理

1.模2除法

  模2除法与算术除法类似,但每一位除的结果不影响其它位,即不向上一位借位,所以实际上就是异或。在循环冗余校验码(CRC)的计算中有应用到模2除法。CRC校验中有两个关键点,一是预先确定一个发送端和接收端都用来作为除数的二进制比特串(或多项式),可以随机选择,也可以使用国际标准,但是最高位和最低位必须为1;二是把原始帧与上面计算出的除数进行模2除法运算,计算出CRC码。
  

2.具体步骤

  1. 选择合适的多项式,确定除数。
  2. 看选定多项式的二进制位数,然后将要发送的数据上面加上这个位数­1位的0,然后用得到的数据以模2除法的方式除上面确定的除数,得到的余数就是该数的CRC校验码。注意,余数的位数一定只比除数位数少一位,也就是CRC校验码位数比除数位数少一位,如果前面位是0也不能省略。

3.实例1

现假设我们使用的多项式为:G(X) =X^ 8 + X^ 2+X^ 1+1,要求出0x1A的CRC­8校验码。下面是具体的计算过程:
1、 将多项式转化为二进制序列,由G(X) = X^ 8 + X^ 2+X^ 1+1可知二进制一种有9位,第8位、第2位、第1位和第0位分别为1,则序列为100000111。
2、原来要计算的数据为1 1010,多项式的最高次为8,则在数据的后面加上8位0,数据变为110100000 0000,然后使用模2除法除以除数100000111,最终得到的除不尽的余数,变为我们要求的CRC­8结果。

3、最后除不尽的余数为0x46,所以0x1A按多项式G(X) = X8+X2+X+1计算得到的CRC­8码为0x46。

4.示例2


也就是求0xb4 0x07 0xb5 0xd2 0x3a 的CRC­8校验码是否是0x30

  1. 将多项式转化为二进制序列,由G(X) = X^8+X^2+X^1+1可知二进制一种有9位,第8位、第2位、第1位和第0位分别为1,则序列为100000111。
  2. 原来要计算的数据为b4 07 b5 d2 3a转换为2进制就是
    1011010000000111101101011101001000111010

,多项式的最高次为8,则在数据的后面加上8位0,数据变为
101101000000011110110101110100100011101000000000,然后使用模2除法除以除数100000111,最终得到的除不尽的余数,变为我们要求的CRC­8结果。

5.读取温度函数

u16 SMBus_ReadMemory(u8 slaveAddress, u8 command)
{
    u16 data;
    u8 Pec;    
    u8 DataL=0;
    u8 DataH=0;    
    u8 arr[6];
    u8 PecReg;
    u8 ErrorCounter;
    ErrorCounter=0x00;// Initialising of ErrorCounter
    slaveAddress <<= 1;    //2-7位表示从机地址 从机地址左移一位,把读写位空出来    
    do
    {
repeat:
        SMBus_StopBit();
        --ErrorCounter;    
        if(!ErrorCounter) //ErrorCounter=0?
        {
            break;    //如果为0就跳出do-while{}循环
        }
        SMBus_StartBit();
        if(SMBus_SendByte(slaveAddress))//发送从机地址最低位Wr=0表示接下来写命令
        {
            goto    repeat; 
        }
        if(SMBus_SendByte(command))//发送命令
        {
            goto    repeat;
        }
        SMBus_StartBit();
        if(SMBus_SendByte(slaveAddress+1))    //发送从机地址+1最低位Rd=1表示接下来读数据
        {
            goto    repeat; 
        }
        DataL = SMBus_ReceiveByte(ACK);    //读低位数据保存到DataL 
        DataH = SMBus_ReceiveByte(ACK); //读高位数据保存到DataH
        Pec = SMBus_ReceiveByte(NACK);    //读校验数据保存到Pec  
        SMBus_StopBit();
        arr[5] = slaveAddress;    
        arr[4] = command;
        arr[3] = slaveAddress+1;
        arr[2] = DataL;    
        arr[1] = DataH;    
        arr[0] = 0;    
        PecReg=PEC_Calculation(arr);//Calculate CRC 数据校验
    }
    while(PecReg != Pec);
    data = (DataH<<8) | DataL;
    return data;
}

  这个函数的输入参数就IIC设备的从机地址和command寄存器地址。首先从机地址左移一位,把读写位空出来,因为不知道最开始是啥状态,我们就需要发送一个停止信号,因为ErrorCounter=0x00;那么进行"--"操作之后就不等于0,就进行下面的操作。接下来就发送起始信号,发送从机设备地址,如果发送的从机地址正确,就接着发送操作从机地址的command这个地址,因为这里报存着温度数据。然后重新发起一个起始信号,开始读数据,把温度数据的低位DataL高位DataH 和PEC数据读出来,再进行PEC数据校验,判断读到的PEC数据和校验得到的PEC数据是否相等,相等的话就跳出循环,通过将高位数据左移8位再与低8位进行按位或就得到了最终的数据data。

6.得到最终温度值

float SMBus_ReadTemp(void)
{   
    float temp;
    temp = SMBus_ReadMemory(0x00, 0x07)*0.02-273.15;
    return temp;
}

  通过数据手册我们知道将【最终读取的数据*0.02-273.15】就会得到最终的温度实际值,通过串口打印或者oled显示就可以得到传感器读取的温度了。
公众号后台回复:【红外测温】,即可免费获取红外测温程序源码。

推荐阅读
关注数
1537
内容数
45
专注嵌入式软硬件开发。公众号:果果小师弟
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息