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是一款温湿度传感器芯片,关键性能如下:
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.
为了提高准确度,该芯片采用多次测量取均值的策略。所以,当重复度越高的时候,所用的时间会越长。
更多自行阅读手册。
因为是用软件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 = 10I2C_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.1if(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%RHif(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