I2C基础知识参考:
嵌入式常见接口协议总结_路溪非溪的博客-CSDN博客
电路图
扩展的I2C接口,可以连接支持I2C的设备。常见于传感器等。
参考手册
目前大部分MCU都带有IIC总线接口,STM32F1也不例外。但是这里我们不使用STM32F1的硬件IIC,而是通过软件模拟。原因是ST为了规避飞利浦IIC专利问题,将STM32的硬件IIC设计的比较复杂,且稳定性不怎么好。
用软件模拟IIC,最大的好处就是方便移植, 同一个代码兼容所有MCU,任何一个单片机只要有IO口,就可以很快的移植过去,而且不需要特定的IO口。而硬件IIC,则换一款 MCU,基本上就得重新搞一次,移植是比较麻烦的,这是我们推荐使用软件模拟IIC的另外一个原因。自行阅读参考手册,大概了解下STM32的IIC。
此处通过SHT30的使用来学习IIC协议。
电路图如下:
注意,这里的引脚连接没有连到硬件IIC上。
所以,就算想用硬件IIC也用不起来。
STN30
STN30是一款温湿度传感器芯片,关键性能如下:
Wide supply voltage range, from 2.4 to 5.5 V
I2C Interface with communication speeds up to 1MHz and two user selectable addresses Typical accuracy of 2%RH and 0.3°C
The sensor shows best performance when operated within recommended normal temperature and humidity range of 5 – 60 °C and 20 – 80 %RH
饮片引脚如下:
设备地址:
上面的原理图中,ADDR接了地,所以这里的地址是0x44.
为了提高准确度,该芯片采用多次测量取均值的策略。所以,当重复度越高的时候,所用的时间会越长。
更多自行阅读手册。
GPIO配置
因为是用软件I2C,所以只用配置对应的两个GPIO口,即PG11和PG12。
要注意的一点是:
我们在配置时,需要将端口配置成输出或者输入,但是,在I2C工作过程中,既需要输出,又需要输入,通过半双工来完成整个过程。
那怎么办呢?怎么能让一个IO口既是输出也是输入呢?
当引脚配置成输出,并且是开漏模式时,引脚对输入的数据也能进行读取。
不过,读取时需要先输出一个高电平,关闭内部的NMOS管,才能作为输入使用。
这就是STM32的准双向口。
关键代码
I2C.c
/* Includes ------------------------------------------------------------------*/ #include "MyApplication.h" /* Private define-------------------------------------------------------------*/ //置位与清零SCL管脚 #define SET_SCL HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_SET) #define CLR_SCL HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_RESET) //置位与清零SDA管脚 #define SET_SDA HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_SET) #define CLR_SDA HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_RESET) //读SDA管脚状态 #define READ_SDA HAL_GPIO_ReadPin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin) /* Private variables----------------------------------------------------------*/ void Init(void); //I2C初始化 void Start(void); //I2C起始信号 void Stop(void); //I2C停止信号 ACK_Value_t Write_Byte(uint8_t); //I2C写字节 uint8_t Read_Byte (ACK_Value_t); //I2C读字节 /* Public variables-----------------------------------------------------------*/ I2C_Soft_t I2C_Soft = { Init, Start, Stop, Write_Byte, Read_Byte }; /* Private function prototypes------------------------------------------------*/ static void I2C_Delay_us(uint8_t); /* * @name Init * @brief I2C初始化 * @param None * @retval None */ static void Init(void) { SET_SCL; SET_SDA; } /* * @name Start * @brief I2C起始信号 * @param None * @retval None */ static void Start(void) { //SCL为高电平,SDA的下降沿为I2C起始信号 SET_SDA; SET_SCL; I2C_Delay_us(1); CLR_SDA; I2C_Delay_us(10); CLR_SCL; I2C_Delay_us(1); } /* * @name Stop * @brief I2C停止信号 * @param None * @retval None */ static void Stop(void) { //SCL为高电平,SDA的上升沿为I2C停止信号 CLR_SDA; SET_SCL; I2C_Delay_us(1); I2C_Delay_us(10); SET_SDA; } /* * @name Write_Byte * @brief I2C写字节 * @param WR_Byte -> 待写入数据 * @retval ACK_Value_t -> 从机应答值 */ static ACK_Value_t Write_Byte(uint8_t WR_Byte) { uint8_t i; ACK_Value_t ACK_Rspond; //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据 //数据按8位传输,高位在前,利用for循环逐个接收 for(i=0;i<8;i++) { //SCL清零,主机SDA准备数据 CLR_SCL; I2C_Delay_us(1); if((WR_Byte&BIT7) == BIT7) { SET_SDA; } else { CLR_SDA; } I2C_Delay_us(1); //SCL置高,传输数据 SET_SCL; I2C_Delay_us(10); //准备发送下一比特位 WR_Byte <<= 1; } CLR_SCL; //释放SDA,等待从机应答 SET_SDA; I2C_Delay_us(1); SET_SCL; I2C_Delay_us(10); ACK_Rspond = (ACK_Value_t)READ_SDA; CLR_SCL; I2C_Delay_us(1); //返回从机的应答信号 return ACK_Rspond; } /* * @name Write_Byte * @brief I2C写字节 * @param ACK_Value -> 主机回应值 * @retval 从机返回值 */ static uint8_t Read_Byte(ACK_Value_t ACK_Value) { uint8_t RD_Byte = 0,i; 接收数据 //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据 //数据按8位传输,高位在前,利用for循环逐个接收 for(i=0;i<8;i++) { //准备接收下一比特位 RD_Byte <<= 1; //SCL清零,从机SDA准备数据 CLR_SCL; I2C_Delay_us(10); //SCL置高,获取数据 SET_SCL; I2C_Delay_us(10); RD_Byte |= READ_SDA; } //SCL清零,主机准备应答信号 CLR_SCL; I2C_Delay_us(1); //主机发送应答信号 if(ACK_Value == ACK) { CLR_SDA; } else { SET_SDA; } I2C_Delay_us(1); SET_SCL; I2C_Delay_us(10); //Note: //释放SDA数据线 //SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号 CLR_SCL; SET_SDA; I2C_Delay_us(1); //返回数据 return RD_Byte; } /* * @name I2C_Delay * @brief I2C延时 * @param None * @retval None */ static void I2C_Delay_us(uint8_t us) { uint8_t i = 0; //通过示波器测量进行校准 while(us--) { for(i=0;i<7;i++); } } /******************************************************** End Of File ********************************************************/
SHT30.c
/* Includes ------------------------------------------------------------------*/ #include "MyApplication.h" /* Private define-------------------------------------------------------------*/ /* Private variables----------------------------------------------------------*/ void Measure_Period_Mode(void); //周期测量模式 /* Public variables-----------------------------------------------------------*/ SHT30_t SHT30 = { 0.0, 0, Measure_Period_Mode }; /* Private function prototypes------------------------------------------------*/ static uint8_t CRC_8(uint8_t *,uint8_t); /* * @name Measure_Period_Mode * @brief 周期测量模式 * @param None * @retval None */ static void Measure_Period_Mode(void) { uint8_t temp_array[6] = {0}; uint16_t temp_uint = 0; float temp_float = 0; //启动周期性测量 I2C_Soft.Start(); I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD); I2C_Soft.Write_Byte(0x27); //High repeat , mps = 10 I2C_Soft.Write_Byte(0x37); Timer6.SHT30_Measure_Timeout = 0; //发送接收数据命令 do { if(Timer6.SHT30_Measure_Timeout >= TIMER6_2S) //2s内没获取到数据,退出等待 break; I2C_Soft.Start(); I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD); I2C_Soft.Write_Byte(0xE0); I2C_Soft.Write_Byte(0x00); I2C_Soft.Start(); } while(I2C_Soft.Write_Byte(SHT30_ADDR | Read_CMD) ==NACK); //开始接收测量数据,并计算 if(Timer6.SHT30_Measure_Timeout < TIMER6_2S) { temp_array[0] = I2C_Soft.Read_Byte(ACK); temp_array[1] = I2C_Soft.Read_Byte(ACK); temp_array[2] = I2C_Soft.Read_Byte(ACK); temp_array[3] = I2C_Soft.Read_Byte(ACK); temp_array[4] = I2C_Soft.Read_Byte(ACK); temp_array[5] = I2C_Soft.Read_Byte(NACK); I2C_Soft.Stop(); //计算温度,精度0.1 if(CRC_8(temp_array,2) == temp_array[2]) //CRC-8 校验 { temp_uint = temp_array[0]*256+temp_array[1]; temp_float = ((float)temp_uint)*0.267032-4500; SHT30.fTemperature = temp_float*0.01; } //计算湿度,精度1%RH if(CRC_8(&temp_array[3],2) == temp_array[5]) //CRC-8 校验 { temp_uint = temp_array[3]*256+temp_array[4]; temp_float = ((float)temp_uint)*0.152590; temp_float = temp_float*0.01; SHT30.ucHumidity = (unsigned char)temp_float; } } } /* * @name CRC_8 * @brief CRC-8校验 * @param Crc_ptr -> 校验数据首地址 LEN -> 校验数据长度 * @retval CRC_Value -> 校验值 */ static uint8_t CRC_8(uint8_t *Crc_ptr,uint8_t LEN) { uint8_t CRC_Value = 0xFF; uint8_t i = 0,j = 0; for(i=0;i<LEN;i++) { CRC_Value ^= *(Crc_ptr+i); for(j=0;j<8;j++) { if(CRC_Value & 0x80) CRC_Value = (CRC_Value << 1) ^ 0x31; else CRC_Value = (CRC_Value << 1); } } return CRC_Value; } /******************************************************** End Of File ********************************************************/