目錄
- (一)工程目錄如圖:
- (二)main函數實現:
- (三)MLX90614測溫代碼實現
前面介紹了使用 STM32制作紅外測溫儀硬件設計,今天來說一下軟件的實現,具體的程序,完整的keil代碼我已經打包放在了這里 MLX90614紅外測溫儀軟件設計.rar
由于程序流程比較清晰,這里我就不把程序流程圖貼出來了,直接上代碼。
(一)工程目錄如圖:
(二)main函數實現:
/*******************************************************
*文件名: main.c
*作 者: 水枂:https://me.csdn.net/download/weixin_43839785
*生成日期: 2019/1/2
*最后修改:
*功能描述: 紅外溫度測量
********************************************************/
#include "varytypes.h"extern unsigned char image[];
extern char envirTemp[][32];
extern char objectTemp[][32];
extern char historyTemp_max[][32];
extern char historyTemp_min[][32];
extern unsigned char flag;
u16 volatile arr[2]={0,100};
int main(void)
{delay_init();//延時函數初始化NVIC_Configuration();//設置NVIC中斷分組0~4共5組 :2位搶占優先級,2位響應優先級 BlueTooth_Init();//藍牙初始化USART2_Init();//串口藍牙初始化LED_Init();//初始化LED端口PC13KEY_Init();//初始化按鍵端口PA0OLED_Init();//初始化oledOLED_Clear();//清屏RTCInit();//RTC時鐘初始化AT24CXX_Init();//AT24C04存儲初始化MLX_I2C_Init();//Mlx90614讀取初始化/****************************************
*溫度記錄值讀取
******************************************/
// arr[0]=AT24CXX_ReadOneByte(0);//max
// arr[1]=AT24CXX_ReadOneByte(1);//min/****************************************
*開機界面顯示
******************************************/OLED_ShowString(3,1,"welcome");OLED_DrawBMP(77, 1,128, 6,image);OLED_ShowString(3,4,"waiting.");delay_s(1);OLED_ShowString(3,4,"waiting..");delay_s(1);OLED_ShowString(3,4,"waiting...");delay_s(1);OLED_Clear();while(1){KEY_Scan();if(flag==0){OLED_ShowCHinese_Mul(3,1,5,objectTemp);display_temp(75,1,OBJ1TEMPADDR);OLED_ShowCHinese_Mul(3,5,5,envirTemp);display_temp(75,5,ENVITEMPADDR);}else if(flag==1){OLED_ShowCHinese_Mul(1,1,7,historyTemp_max);OLED_ShowNum(105,1,AT24CXX_ReadOneByte(0),3,16);OLED_ShowCHinese_Mul(1,5,7,historyTemp_min);OLED_ShowNum(105,5,AT24CXX_ReadOneByte(1),3,16);}}
}/****************************************************
*函數名 :display_temp
*功 能 :顯示采集回來轉換后的溫度
*參 數 : @ x,y :起點坐標 // @object: 是環境溫度還是物體溫度ENVITEMPADDR、OBJ1TEMPADDR、OBJ2TEMPADDR
*說 明 :Temperature data is T=(Data)*0.02-273.15運用了這個的轉換公式計算溫度
*****************************************************/
void display_temp(u8 x,u8 y,u8 object)
{u16 temp,T;u8 point_before,point_after;u8 point_after_one ,point_after_two;temp=I2C_ReadRAM(0x00,object);//0x00是器件的地址,由于只有一個用了0x00T=temp*2;if(T>=27315) //溫度:零上{T=T-27315;point_before=T/100;point_after=T-point_before*100;point_after_one=point_after/10;//為了小數點后顯示正常point_after_two=point_after%10;OLED_ShowNum(x,y,point_before,3,16);OLED_ShowString(x+24,y,".");OLED_ShowNum(x+32,y,point_after_one,1,16);OLED_ShowNum(x+40,y,point_after_two,1,16);if(object==OBJ1TEMPADDR){if(point_before>arr[0]){arr[0]=point_before;}//arr[0]存放溫度最高值if(point_before<arr[1]){arr[1]=point_before;}//arr[1]存放溫度最低值}}else//溫度:零下{T=27315-T;point_before=T/100;point_after=T-point_before*100;OLED_ShowString(x,y,"-");OLED_ShowNum(x+8,y,point_before,3,16);OLED_ShowString(x+32,y,".");OLED_ShowNum(x+40,y,point_after,2,16);} }
(三)MLX90614測溫代碼實現
關于MLX90614的文檔,可以直接百度,或者去淘寶賣家那要一份,MLX90614測溫代碼實現:
/**
******************************************************************************
*
* 基于STM32F103的MLX90614紅外溫度傳感器驅動程序
*
*******************************************************************************/
#include "mlx90614.h"/**
* @功能 I2C通信狀態改變后的延時
* @說明 無
* @參數 無
* @返回值 無
*/
void I2C_Delay(void)
{
delay_us(5);
}
/****************************************************************
*初始化MLX_IIC用的端口
****************************************************************/
void MLX_I2C_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);;//使能 GPIOB 時鐘//GPIOB6,B7初始化設置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHzGPIO_Init(GPIOB, &GPIO_InitStructure);//初始化MLX_IIC_SCL=1;MLX_IIC_SDA=1; }
/*******************************************************************************
* 函 數 名 : SDA_OUT
* 函數功能 : SDA輸出配置
* 輸 入 : 無
* 輸 出 : 無
*******************************************************************************/
void MLX_SDA_OUT(void)
{GPIO_InitTypeDef GPIO_InitStructure;//GPIOB9初始化設置GPIO_InitStructure.GPIO_Pin =GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHzGPIO_SetBits(GPIOB,GPIO_Pin_7); //上拉GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
}/*******************************************************************************
* 函 數 名 : SDA_IN
* 函數功能 : SDA輸入配置
* 輸 入 : 無
* 輸 出 : 無
*******************************************************************************/
void MLX_SDA_IN(void)
{GPIO_InitTypeDef GPIO_InitStructure;//GPIOB9初始化設置GPIO_InitStructure.GPIO_Pin =GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//輸入模式GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
}/**
* @功能 產生通訊開始信號
* @說明 MLX90614在SCK=1時,檢測到SDA由1到0表示通信開始
* @參數 無
* @返回值 無
*/
void I2C_Start(void)
{
MLX_SDA_OUT();
MLX_IIC_SDA=1;
MLX_IIC_SCL=1;
I2C_Delay();
MLX_IIC_SDA=0;
I2C_Delay();
MLX_IIC_SCL=0;
I2C_Delay();
}
/**
* @功能 產生通訊停止信號
* @說明 MLX90614在SCK=1時,檢測到SDA由0到1表示通信結束
* @參數 無
* @返回值 無
*/
void I2C_Stop(void)
{
MLX_SDA_OUT();
MLX_IIC_SDA=0;
MLX_IIC_SCL=0;
I2C_Delay();
MLX_IIC_SCL=1;
I2C_Delay();
MLX_IIC_SDA=1;
I2C_Delay();
}/*******************************************************************************
* 函 數 名 : IIC_Ack
* 函數功能 : 產生ACK應答
* 輸 入 : 無
* 輸 出 : 無
*******************************************************************************/
void I2C_Ack(void)
{MLX_IIC_SCL=0;MLX_SDA_OUT();MLX_IIC_SDA=0;delay_us(2);MLX_IIC_SCL=1;delay_us(5);MLX_IIC_SCL=0;
}/*******************************************************************************
* 函 數 名 : IIC_NAck
* 函數功能 : 產生NACK非應答
* 輸 入 : 無
* 輸 出 : 無
*******************************************************************************/
void I2C_NAck(void)
{MLX_IIC_SCL=0;MLX_SDA_OUT();MLX_IIC_SDA=1;delay_us(2);MLX_IIC_SCL=1;delay_us(5);MLX_IIC_SCL=0;
} /*******************************************************************************
* 函 數 名 : IIC_Wait_Ack
* 函數功能 : 等待應答信號到來
* 輸 入 : 無
* 輸 出 : 1,接收應答失敗0,接收應答成功
*******************************************************************************/
u8 I2C_Wait_Ack(void)
{u8 tempTime=0;MLX_SDA_IN(); //SDA設置為輸入 MLX_IIC_SDA=1;delay_us(1); MLX_IIC_SCL=1;delay_us(1); while(MLX_READ_SDA){tempTime++;if(tempTime>250){I2C_Stop();return 1;}}MLX_IIC_SCL=0;//時鐘輸出0 return 0;
} /**
* @功能 將MLX90614的工作模式從PWM模式切換到SMBus模式
* @說明 從PWM模式切換到SMBus的方法是將SCL保持至少1.44ms以上的低電平
* 如果PWM沒有使能就不需要發送請求命令
* @參數 無
* @返回值 無
*/
void PwmToSMBus(void)
{
MLX_IIC_SCL=0;
delay_us(1500); //大于1.44ms
MLX_IIC_SCL=1;
}
/**
* @功能 退出睡眠模式
* @說明 保持SCK高電平后,SDA持續至少33ms低電平,
* 在退出睡眠模式后需要間隔250ms(典型值)才輸出數據。
* @參數 無
* @返回值 無
*/
void Eixt_Sleep(void)
{
MLX_IIC_SCL=1;
MLX_IIC_SDA=1;
I2C_Delay();
MLX_IIC_SDA=0;
delay_ms(35); //大于33ms退出睡眠模式
MLX_IIC_SDA=1;
delay_ms(260); //大于250ms開始輸出數據
}
/**
* @功能 從RAM/EEPROM中讀取一個字節數據
* @說明 從MLX90614中的指定地址讀取一個字節數據,高位在前,低位在后
* @參數 ack_nack:主機應答信號
* @返回值 dat: 讀取的數據
*/
uint8_t I2C_ReadByte(uint8_t ack)
{u8 i,receive=0;MLX_SDA_IN();//SDA設置為輸入for(i=0;i<8;i++ ){MLX_IIC_SCL=0; delay_us(2);MLX_IIC_SCL=1;receive<<=1;if(MLX_READ_SDA)receive++; delay_us(1); } if (!ack)I2C_NAck();//發送nACKelseI2C_Ack(); //發送ACK return receive;
}
/**
* @功能 向EEPROM寫一個字節數據
* @說明 在寫完一個字節后檢測MLX6014是否發送了應答信號
* @參數 dat:需要發送的字節
* @返回值 s_ack:應答信號狀態
*/
uint8_t I2C_WriteByte(uint8_t dat)
{u8 t; uint8_t s_ack=0;MLX_SDA_OUT(); MLX_IIC_SCL=0;//拉低時鐘開始數據傳輸for(t=0;t<8;t++){ if((dat&0x80)>0) //0x80 1000 0000MLX_IIC_SDA=1;elseMLX_IIC_SDA=0;dat<<=1; delay_us(2); //對TEA5767這三個延時都是必須的MLX_IIC_SCL=1;delay_us(2); MLX_IIC_SCL=0; delay_us(2);}
if(I2C_Wait_Ack()) //高電平表示正確接收數據 (高?低??這個應該是低電平)
{
s_ack = ACK_FAIL;
}
else
{
s_ack = ACK_SUCCESS;
}
//delay_us(2*N);//修改的
//MLX_IIC_SCL=0;
//delay_us(4*N);
return s_ack;
}
/**
* @功能 讀MLX90614的RAM中內容
* @說明 主要讀取三個,環境溫度,物體溫度1,物體溫度2
* 器件從地址可以通過向EEPROM的SMBus地址0x0E中寫入來進行設定。
* @參數 saddr:從機地址,7位地址,任何MLX90614都會對0x00地址作出反應
* cmd:存放溫度的寄存器地址
* @返回值 Data:讀取出來的數值
*using Read Word: SA(write) - Command - SA(read) - LSByte - MSByte - PEC
*/
uint16_t I2C_ReadRAM(uint8_t saddr,uint8_t cmd)
{
uint16_t Data;
uint8_t DataL; //接收數據低字節
uint8_t DataH; //接收數據高字節
uint8_t PEC;
uint8_t retry = 10; //失敗重復次數
uint8_t s_ack = 0;
uint8_t Pecreg; //計算的PEC值
uint8_t buf[6]; //存儲已接收數據的緩存
MLX_IIC_SCL=0;
while(retry--)
{
I2C_Start(); //發送起始位
s_ack = I2C_WriteByte((saddr<<1)|WR); //發送從機地址和Wr位
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(RAM|cmd);
//發送命令,8位,RAM表示對RAM操作,cmd表示操作RAM的地址
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
I2C_Start(); //重新發送起始位
s_ack = I2C_WriteByte((saddr<<1)+1); //發送從機地址和Rd位
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
DataL = I2C_ReadByte(1); //讀數據低字節
DataH = I2C_ReadByte(1); //讀數據高字節
PEC = I2C_ReadByte(1); //讀數據PEC字節
// DataL=RX_byte(0); //
// DataH=RX_byte(0); //
// PEC=RX_byte(1);
I2C_Stop(); //發送停止位
buf[5]=(saddr<<1);
buf[4]=EEPROM|cmd;
buf[3]=(saddr<<1)|RD;
buf[2]=DataL;
buf[1]=DataH;
buf[0]=0;
Pecreg=PEC_Cal(buf,6); //調用計算 PEC 的函數
if(Pecreg == PEC)
{
break; //退出循環
}
}
else goto stop_rr;
}
else goto stop_rr;
}
else goto stop_rr;
stop_rr:
I2C_Stop(); //發送停止位,芯片接收失敗
}
PEC = PEC+1;
Data = (DataH<<8) + DataL;
return Data;
}
/**
* @功能 清除EEPROM指定單元的數據
* @說明 在向EEPROM中寫入數據之前必須先清除內存單元中的數據,也就是全部寫入0
* @參數 saddr:從機地址
cmd:發送命令
* @返回值 無
*/
void I2C_ClearEEPROM(uint8_t saddr,uint8_t cmd)
{
uint8_t retry = 10; //失敗重復次數
uint8_t s_ack = 0;
MLX_IIC_SCL=0;
while(retry--)
{
I2C_Start(); //發送起始位
s_ack = I2C_WriteByte((saddr<<1)|WR); //發送從機地址和Wr位
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(EEPROM|cmd);
//發送命令,8位 EPROM表示對RAM操作,cmd表示操作EEPROM的地址$MLX90614.C
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(0x00); //發送低字節
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(0x00); //發送高字節
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(0x6f); //發送PEC字節
if(s_ack == ACK_SUCCESS)
{
I2C_Stop(); //發送停止位
break; //退出循環
}
else goto stop_ce;
}
else goto stop_ce;
}
else goto stop_ce;
}
else goto stop_ce;
}
else goto stop_ce;
stop_ce:
I2C_Stop(); //發送停止位,芯片接收失敗
}
delay_ms(5); //擦除完成至少等待5ms
}
/**
* @功能 讀EEPROM指定單元的數據
* @說明 從指定從機讀取指定EEPROM地址的數據
* @參數 saddr:從機地址
cmd:讀取EEPROM地址
* @返回值 Data:讀取數據
*/
uint16_t I2C_ReadEEPROM(uint8_t saddr,uint8_t cmd)
{
uint8_t retry = 10;
uint8_t s_ack;
uint16_t Data;
uint8_t DataL; //接收數據低字節
uint8_t DataH; //接收數據高字節
uint8_t PEC; //接收的PEC值
uint8_t Pecreg; //計算的PEC值
uint8_t buf[6]; //存儲已接收數據的緩存
while(retry--)
{
I2C_Start(); //發送起始位
s_ack = I2C_WriteByte((saddr<<1)|WR); //發送從機地址和Wr位
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(EEPROM|cmd); //發送命令
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
I2C_Start(); //重新發送起始位
s_ack = I2C_WriteByte((saddr<<1)|RD); //發送從機地址和Rd位
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
DataL = I2C_ReadByte(1); //讀數據低字節
DataH = I2C_ReadByte(1); //讀數據高字節
PEC = I2C_ReadByte(1); //讀數據PEC字節
I2C_Stop(); //發送停止位
buf[5]=(saddr<<1);
buf[4]=EEPROM|cmd;
buf[3]=(saddr<<1)|RD;
buf[2]=DataL;
buf[1]=DataH;
buf[0]=0;
Pecreg=PEC_Cal(buf,6); //調用計算 PEC 的函數
if(Pecreg == PEC)
{
break;
}
}
else goto stop_re;
}
else goto stop_re;
}
else goto stop_re;
stop_re:
I2C_Stop();
}
Data = (DataH<<8) + DataL;
return Data;
}
/**
* @功能 寫EEPROM指定單元的數據
* @說明 在向EEPROM中寫入數據之前必須先清除內存單元中的數據,也就是全部寫入0
* @參數 saddr:要清除數據的內存單元
* @返回值 無
*/
void I2C_WriteEEPROM(uint8_t saddr,uint8_t cmd,uint8_t DataL,uint8_t DataH)
{
uint8_t retry = 10; //失敗重復次數
uint8_t s_ack = 0;
uint8_t Pecreg; //存儲計算所得PEC結果$MLX90614.C
uint8_t buf[6]; //存儲將要發送字節的緩沖器
buf[5]=0;
buf[4]=saddr<<1;
buf[3]=cmd;
buf[2]=DataL;
buf[1]=DataH;
buf[0]=0;
Pecreg=PEC_Cal(buf,6);
MLX_IIC_SCL=0;
while(retry--)
{
I2C_Start(); //發送起始位
s_ack = I2C_WriteByte((saddr<<1)|WR); //發送從機地址和Wr位
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(EEPROM|cmd); //發送命令
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(DataL); //發送低字節
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(DataH); //發送高字節
if(s_ack == ACK_SUCCESS)
{
s_ack = 0;
s_ack = I2C_WriteByte(Pecreg); //發送PEC碼
if(s_ack == ACK_SUCCESS)
{
I2C_Stop(); //發送停止位
break; //退出循環
}
else goto stop_we;
}
else goto stop_we;
}
else goto stop_we;
}
else goto stop_we;
}
else goto stop_we;
stop_we:
I2C_Stop();
}
delay_ms(5); //寫入之后等待5ms
}
/**
* @功能 計算PEC包裹校驗碼,根據接收的字節計算PEC碼
* @說明 計算傳入數據的PEC碼
* @參數 pec[]:傳入的數據
n:傳入數據個數
* @返回值 pec[0]:計算得到的PEC值
*/
uint8_t PEC_Cal(uint8_t pec[],uint16_t n)
{
unsigned char crc[6];
unsigned char Bitposition=47;
unsigned char shift;
unsigned char i;
unsigned char j;
unsigned char temp;
do{
crc[5]=0; //載入 CRC數值 0x000000000107
crc[4]=0;
crc[3]=0;
crc[2]=0;
crc[1]=0x01;
crc[0]=0x07;
Bitposition=47; //設置Bitposition的最大值為47
shift=0;
//在傳送的字節中找出第一個“1”
i=5; //設置最高標志位 (包裹字節標志)
j=0; //字節位標志,從最低位開始
while((pec[i]&(0x80>>j))==0 && (i>0))
{
Bitposition--;
if(j<7)
{
j++;
}
else
{
j=0x00;
i--;
}
}//while語句結束,并找出Bitposition中為“1”的最高位位置
shift=Bitposition-8;
//得到CRC數值將要左移/右移的數值“shift”
//對CRC數據左移“shift”位
while(shift)
{
for(i=5;i<0xFF;i--)
{
if((crc[i-1]&0x80) && (i>0))
//核對字節的最高位的下一位是否為"1"
{ //是 - 當前字節 + 1
temp=1; //否 - 當前字節 + 0
} //實現字節之間移動“1”
else
{
temp=0;
}
crc[i]<<=1;
crc[i]+=temp;
}
shift--;
}
//pec和crc之間進行異或計算
for(i=0;i<=5;i++)
{
pec[i]^=crc[i];
}
}while(Bitposition>8);
return pec[0]; //返回計算所得的crc數值
}
/**
* @功能 設定MLX90614器件地址
* @說明 器件從地址可以通過向EEPROM的SMBus地址0x0E中寫入來進行設定。
為了給從器件設定地址,必須先以0x00+Wr當作從地址開始,當主機
發送此命令,MLX90614總是會反饋并忽略掉內部芯片編碼信息。
向EEPROM寫入數據前需要清除原來的數據,就是向修改單元寫入0x0000
擦除之后需要等待5ms才可以重新寫入數據
修改地址時寫入的地址高字節MLX90614會忽略
修改之后需要重新將MLX90614的電源斷開重啟。
* @參數 soaddr:從機舊地址
snaddr:從機新地址
* @返回值 無
*/
void I2C_SetSlaveAddr(uint8_t soaddr,uint8_t snaddr)
{
// uint8_t cmd = EEPROM|SMBUSADDR;
// uint8_t DataL = snaddr;
// uint8_t DataH = 0x00;
// EEPROM_WRITE(snaddr,cmd,0x00,0x00);
// EEPROM_WRITE(snaddr,cmd,DataL,DataH);
}/*************************************************
*函數名 : CALTEMP()
*功 能 : 把讀回來的數據轉換為攝氏度
*說 明 : 從RAM里面讀出來的是一個比較大的數(可以用顯示屏打印出來看看)需要使用下面的公式把溫度轉換出來://Temperature data is T=(Data)*0.02-273.15
*參 數 : TEMP為要轉換的數據
*修改時間 : 2019/3/15***************************************************/
void CALTEMP(unsigned long int TEMP)
{unsigned long int T;unsigned int A, B;//unsigned int tempb;T=TEMP*2;if(T>=27315) //溫度:零上{T=T-27315;A=T/100;B=T-A*100;}else//溫度:零下{T=27315-T;A=T/100;B=T-A*100;}
}
程序這一部分不做過多的講解,關于測溫的功能都是按照官方提供的文檔進行編寫的,需要自己好好參悟,最后說一下,這個MLX90614測溫很容易受外界環境的干擾,需要自己寫算法,在硬件上進行改進才能得到更高的準確度。
本文章僅供學習交流用禁止用作商業用途,文中所有內容均為原創未經授權不得轉載
微信公眾號:zhjj0729
微博:文藝to青年
簡書:水枂