STM32模拟I2C协议获取MLX90615红外温度传感器测温数据(Open Drain管脚配置)
STM32的GPIO可配置管脚Open Drain输出模式有两个功能:
- 内部上拉可以设置,所以对于I2C访问速度不是特别高,不需要外部I2C上拉电阻;
- 虽然是Open Drain输出管脚可以直接读取管脚的电平状态,就像读取输入管脚一样,无需将输出管脚切换成输入管脚。
MLX90615是无接触红外温度传感器DAA医疗级高精度型号,也有不同温度测量距离的型号,适合不同场景产品的应用。MLX90615可以采用PWM方式或者I2C数据获取的方法是模拟I2C实现方法。MLX90615内部有300KΩ需要弱上拉STM32侧内部上拉可以更好地保证访问速度。否则会出现时序抖动错误。
MLX90615是一积较小的红外温度测温模块MLX与以下不同:
- 管脚排列顺序不同
- 从PWM进入I2C控制时间长度因模式而异,MLX90614需要大于1.44ms,MLX90615需要大于39ms
- 除了默认0地址访问外,还有另一个默认访问地址,MLX90614是0x5A, 而MLX90615是0x5B
- 访问温度数据存放的寄存器地址不同,MLX90614是0x07, 而MLX90615是0x27
硬件连接
低成本的STM32F030F4P6.读取开发板作为控制器MLX90615温度数据。连接关系如下:
软件工程配置
这里采用STM32CUBEIDE开发环境和HAL库。首先建立STM32F030F4P工程和配置时钟。如果在测试中发现HCLK高于4MHz(无论是外部时钟源还是内部时钟源)I2C即时序无法保证时序功能读取错误数据。推测低端芯片的锁相环电路性能较差,HCLK高频抖动相对较大。因此将HCLK频率配置为4MHz。另外在STM32F3和STM32F如果4系列进行相同的代码测试,则没有HCLK频率配置引起的问题也说明是STM32F0系列性能问题。 然后配置PA5和PA6作为Open Drain输出带上拉,默认为高电平输出: 然后配置USART1用于串口数据输出: 保存并生成初始工程代码。 FLASH比较小的MCU需要设置“size优化编译模式,避免编译代码占用空间超过FLASH最大空间 STM32 region `FLASH‘ overflowed by xxx bytes 问题解决。
软件工程代码
需要在代码中使用HAL工程微秒延时,HAL库工程微秒延迟实现原理参考STM32 HAL us delay(微秒延迟)指令延迟的实现和优化。
代码里I2C_Init()初始化函数用于保证MLX90615进入I2C控制模式,然后在while连续读取循环中的温度并串口输出。
MLX在90615的标准文档中有一个阅读时间序列bug,多画一个周期时钟,包括时钟周期中读取的温度数据、计算后的校准值和发送的PEC这个时钟周期应该去掉,读时序和时钟周期说明如下图所示: 而MLX在90614的规范文档中,读时序不多,MLX90614阅读时间如下:
PEC是MLX90615发出的CRC-8校验字节,MCU前五个字节内容可以在侧面做CRC-计算,得到CRC-8计算校验字节,和MLX90615发出的CRC-8验证字节比较,判断传输和接收是否正确。因此,设计是针对MLX90615读操作的CRC-8验证函数如下:
uint8_t PY_CRC_MLX90615_READ(uint8_t daddr, uint8_t Raddr, uint8_t dl, uint8_t dh) {
//Written by Pegasus Yu 2022/02/22 uint64_t cdata = 0; //Computed total data uint16_t data_t = 0; //Process data of CRC computing uint16_t crc_poly = 0x0107; //X^8 X^2 X^1 1 total 9 effective bits. Computed total data shall be compensated 8-bit '0' before CRC computing from 9-1=8. uint16_t index_t = 47; ///bit shifting index for initial '1' searching uint16_t index = 47; //bit shifting index for CRC computing uint8_t rec = 0; //bit number needed to be compensated for next CRC computing cdata |= (((uint64_t)daddr)<<40); //device write address
cdata |= (((uint64_t)Raddr)<<32); //register access address
cdata |= (((uint64_t)(daddr+1))<<24); //device read address
cdata |= (((uint64_t)dl)<<16); //data LSB
cdata |= (((uint64_t)dh)<<8); //data HSB
//8-bit '0' compensated into cdata so cdata involves 48 bits stored in 64-bit format.
while(index_t>0)
{
if( (cdata>>index_t)&1 )
{
index = index_t;
index_t = 0;
data_t |= (cdata>>(index-8));
{
data_t = data_t ^ crc_poly;
}
while(index!=0xffff)
{
if ((data_t>>7)&1) rec = 1;
else if ((data_t>>6)&1) rec = 2;
else if ((data_t>>5)&1) rec = 3;
else if ((data_t>>4)&1) rec = 4;
else if ((data_t>>3)&1) rec = 5;
else if ((data_t>>2)&1) rec = 6;
else if ((data_t>>1)&1) rec = 7;
else if ((data_t>>0)&1) rec = 8;
else rec = 9; ///
if((index-8)<rec)
{
data_t = data_t<<(index-8);
index = 0xffff;
}
else
{
for(uint8_t i=1;i<=rec;i++)
{
data_t = (data_t<<1)|((cdata>>(index-8-i))&1) ;
}
if(rec!= 9)
{
data_t = data_t ^ crc_poly;
index -= rec;
}
else
{
data_t = 0;
index_t = index-8-1;
index = 0xffff;
}
}
}
}
else
{
index_t--;
if(index_t<8) break;
}
}
return (uint8_t)data_t;
}
代码设计上,通过串口将温度数据的高字节和低字节输出,可以对高字节和低字节按照公式计算,得到浮点格式的温度数据。主要的实现代码(main.c)如下:
/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2022 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** *Written by Pegasus Yu in 2022 */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ //us delay functions float usDelayBase; void PY_usDelayTest(void) { uint32_t firstms, secondms; uint32_t counter = 0; firstms = HAL_GetTick()+1; secondms = firstms+1; while(uwTick!=firstms) ; while(uwTick!=secondms) counter++; usDelayBase = ((float)counter)/1000; } void PY_Delay_us_t(uint32_t Delay) { uint32_t delayReg; uint32_t usNum = (uint32_t)(Delay*usDelayBase); delayReg = 0; while(delayReg!=usNum) delayReg++; } void PY_usDelayOptimize(void) { uint32_t firstms, secondms; float coe = 1.0; firstms = HAL_GetTick(); PY_Delay_us_t(1000000) ; secondms = HAL_GetTick(); coe = ((float)1000)/(secondms-firstms); usDelayBase = coe*usDelayBase; } void PY_Delay_us(uint32_t Delay) { uint32_t delayReg; uint32_t msNum = Delay/1000; uint32_t usNum = (uint32_t)((Delay%1000)*usDelayBase); if(msNum>0) HAL_Delay(msNum); delayReg = 0; while(delayReg!=usNum) delayReg++; } //MLX90615 I2C access protocol #define us_num 20 //available #define SCL_OUT_H HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET) #define SCL_OUT_L HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET) #define SDA_OUT_H HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET) #define SDA_OUT_L HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET) #define SDA_IN HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) void I2C_Init(void) { SDA_OUT_H; SCL_OUT_L; PY_Delay_us_t(50000) ; //to enable i2c if previous mode PWM SCL_OUT_H; SDA_OUT_H; PY_Delay_us_t(2000000) ; } void I2C_Start(void) { PY_Delay_us_t(us_num) ; SDA_OUT_H; SCL_OUT_H; PY_Delay_us_t(us_num/2) ; SDA_OUT_L; PY_Delay_us_t(us_num/2) ; SCL_OUT_L; } void I2C_Stop(void) { SCL_OUT_L; PY_Delay_us_t(us_num) ; SDA_OUT_L; PY_Delay_us_t(us_num) ; SCL_OUT_H; PY_Delay_us_t(us_num) ; SDA_OUT_H; PY_Delay_us_t(us_num) ; } void I2C_Write_Ack(void) { PY_Delay_us_t(us_num/2) ; SDA_OUT_L; PY_Delay_us_t(us_num/2) ; SCL_OUT_H; PY_Delay_us_t(us_num) ; SCL_OUT_L; SDA_OUT_H; } uint8_t I2C_Read_Ack(void) { uint8_t status=0; SCL_OUT_L; PY_Delay_us_t(us_num/2) ; SDA_OUT_H; PY_Delay_us_t(us_num/2) ; status = SDA_IN; SCL_OUT_H; PY_Delay_us_t(us_num) ; SCL_OUT_L; SDA_OUT_L; return status; } void I2C_Send_Byte(uint8_t txd){ for(uint8_t i=0;i<8;i++) { PY_Delay_us_t(us_num/2) ; if((txd&0x80)>>7) SDA_OUT_H; else SDA_OUT_L; txd<<=1; PY_Delay_us_t(us_num/2) ; SCL_OUT_H; PY_Delay_us_t(us_num) ; SCL_OUT_L; } SDA_OUT_L; } uint8_t I2C_Read_Byte(unsigned char rdack) { uint8_t rxd=0; for(uint8_t i=0;i<8;i++ ) { SCL_OUT_L; PY_Delay_us_t(us_num/2) ; SDA_OUT_H; PY_Delay_us_t(us_num/2) ; SCL_OUT_H; rxd<<=1; if(SDA_IN) rxd++; PY_Delay_us_t(us_num) ; } SCL_OUT_L; SDA_OUT_H; if (rdack) I2C_Write_Ack(); return rxd; } uint8_t PY_CRC_MLX90615_READ(uint8_t daddr, uint8_t Raddr, uint8_t dl, uint8_t dh) { //Written by Pegasus Yu 2022/02/22 uint64_t cdata = 0; //Computed total data uint16_t data_t = 0; //Process data of CRC computing uint16_t crc_poly = 0x0107; //X^8+X^2+X^1+1 total 9 effective bits. Computed total data shall be compensated 8-bit '0' before CRC computing from 9-1=8. uint16_t index_t = 47; ///bit shifting index for initial '1' searching uint16_t index = 47; //bit shifting index for CRC computing uint8_t rec = 0; //bit number needed to be compensated for next CRC computing cdata |= (((uint64_t)daddr)<<40); //device write address cdata |= (((uint64_t)Raddr)<<32); //register access address cdata |= (((uint64_t)(daddr+1))<<24); //device read address cdata |= (((uint64_t)dl)<<16); //data LSB cdata |= (((uint64_t)dh)<<8); //data HSB //8-bit '0' compensated into cdata so cdata involves 48 bits stored in 64-bit format. while(index_t>0) { if( (cdata>>index_t)&1 ) { index = index_t; index_t = 0; data_t |= (cdata>>(index-8)); { data_t = data_t ^ crc_poly; } while(index!=0xffff) { if ((data_t>>7)&1) rec = 1; else if ((data_t>>6)&1) rec = 2; else if ((data_t>>5)&1) rec = 3; else if ((data_t>>4)&1) rec = 4; else if ((data_t>>3)&1) rec = 5; else if ((data_t>>2)&1) rec = 6; else if ((data_t>>1)&1) rec = 7; else if ((data_t>>0)&1) rec = 8; else rec = 9; /// if((index-8)<rec) { data_t = data_t<<(index-8); index = 0xffff; } else { for(uint8_t i=1;i<=rec;i++) { data_t = (data_t<<1)|((cdata>>(index-8-i))&1) ; } if(rec!= 9) { data_t = data_t ^ crc_poly; index -= rec; } else { data_t = 0; index_t = index-8-1; index = 0xffff; } } } } else { index_t--; if(index_t<8) break; } } return (uint8_t)data_t; } uint32_t Get_Temp_DATA( uint8_t ReaAd) { uint8_t Pecreg = 0; uint8_t DataL = 0 ,DataH = 0; uint32_t Result = 0; uint8_t daddr = 0x00; //0x00 or 0xB6(0x5B<<1) for MLX90615 default device address I2C_Start(); I2C_Send_Byte(daddr); I2C_Read_Ack(); //I2C_Write_Ack(); I2C_Send_Byte(ReaAd); //I2C_Write_Ack(); I2C_Read_Ack(); /* Don't add this erroneous clock timing, placed here just for reminder, from spec. PY_Delay_us_t(us_num) ; SCL_OUT_H; PY_Delay_us_t(us_num) ; SCL_OUT_L; */ I2C_Start(); I2C_Send_Byte(daddr+1); I2C_Read_Ack(); DataL=I2C_Read_Byte(1); DataH=I2C_Read_Byte(1); Pecreg=I2C_Read_Byte(1); I2C_Stop(); Result |= (((uint32_t)DataH)<<24); Result |= (((uint32_t)DataL)<<16); Result |= (((uint32_t)Pecreg)<<8); Result |= PY_CRC_MLX90615_READ(daddr, ReaAd, DataL, DataH); return Result; } /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ UART_HandleTypeDef huart1; /* USER CODE BEGIN PV */