一、IIC总线原理

IIC是一种同步半双工串行(MSB)高位先行通信总线,由飞利浦公司开发,广泛用于短距离低速通信(如传感器、EEPROM、OLED 等外设)。

1.物理层

两根信号线:SDA数据线和SCL时钟线。IIC的接线方式SCL接SCL,SDA接SDA,IIC总线上必须外接两个上拉电阻(通常 4.7kΩ),用于在总线空闲时将总线拉高,实现 "线与" 逻辑(多主设备低电平优先)。IO需要设置为开漏输出
时钟线的作用:用于控制数据线上的数据的有效性。时钟线为低电平时,数据无效。时钟线为高电平时,数据有效。
支持多主多从,通过从设备地址区分通信对象。内置硬件应答(ACK)处理,无需手动控制。
通信速率:主流实用速率是100kHz(标准模式)和 400kHz(快速模式)

2.协议层

核心信号与操作:起始信号、停止信号、应答信号、非应答信号、等待信号、发送数据、接受数据。

核心信号(起始、停止、重复起始)

起始信号时序:SCL 为高电平时,SDA 从高电平跳变为低电平(高→低)。
停止信号时序:SCL 为高电平时,SDA 从低电平跳变为高电平(低→高)。
重复起始信号时序:与起始信号完全相同(SCL 高时 SDA 高→低),但它出现在一次通信过程中。
重复起始信号作用:主设备在不释放总线的情况下切换通信方向,如从 “写” 切换到 “读”,避免其他主设备抢占总线。

数据传输规则
(1)数据有效性

SCL 为高电平时,SDA 的电平必须稳定为高或者低,此时数据有效;SCL 为低电平时,SDA 可以跳变,准备下一位数据。

(2)应答机制(ACK/NACK)

每传输8位后,接收方必须在第9个时钟周期回应 “应答”,确认数据已接收:
ACK(应答):接收方拉低 SDA,低电平,表示已正确接收字节。
NACK(非应答):接收方释放 SDA,高电平(由上拉电阻维持),表示未接收、接收错误或无需继续发送。

(3)一句话总结

高时钟看状态(起始 / 停止 / 应答),低时钟换数据

主设备与从设备的对话
1.主设备写从设备

向从设备的某个寄存器写入数据

① 主设备发送起始信号(S),启动通信。
② 主设备发送从设备写地址(7 位地址 + 0),等待从设备应答。
③ 从设备地址匹配,回应ACK
④ 主设备发送寄存器地址(如 0x05),等待从设备应答。
⑤ 从设备回应ACK
⑥ 主设备发送要写入的数据(如 0xAB),等待从设备应答。
⑦ 从设备回应ACK(表示数据接收成功)。
⑧ 主设备发送停止信号(P),结束通信。

2.主设备读从设备

读取从设备某个寄存器的数据

① 主设备发送起始信号(S),启动通信。
② 主设备发送从设备写地址(先告诉从设备 “要读哪个寄存器”),等待 ACK。
③ 从设备回应ACK
④ 主设备发送目标寄存器地址(如 0x05),等待 ACK。
⑤ 从设备回应ACK
⑥ 主设备发送重复起始信号(Sr)(不释放总线,切换到读模式)。
⑦ 主设备发送从设备读地址(7 位地址 + 1),等待 ACK。
⑧ 从设备回应ACK,开始向主设备发送数据。
⑨ 主设备接收数据后,发送NACK(表示 “这是最后一个字节,无需继续发送”)。
⑩ 主设备发送停止信号(P),结束通信。

二、编写代码实战

基于STM32F4标准库、软件模拟I2C总线驱动SHT20温湿度传感器的完整实现,适配了 FreeRTOS 实时操作系统。

1.底层IIC驱动

#include "drv_iic.h"
#include "drv_systick.h"

/*
函数名:iic_init()
函数参数:无
函数功能:初始化软件模拟iic的gpio引脚
函数返回值:无
其他:
*/
void iic_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;//
	GPIO_InitStruct.GPIO_OType=GPIO_OType_OD;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void sda_in(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void sda_out(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
	GPIO_InitStruct.GPIO_OType=GPIO_OType_OD;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void iic_start(void)
{
	SCL_L;
	sda_out();
	SDA_H;	
	SCL_H;
	delay_us(5);
	SDA_L;
	delay_us(5);
	SCL_L;
}
void iic_stop(void)
{
	SCL_L;
	sda_out();
	SDA_L;	
	SCL_H;
	delay_us(5);
	SDA_H;
	delay_us(5);
}
void send_ACK(void)
{
	SCL_L;
	sda_out();
	SDA_L;
	SCL_H;
	delay_us(5);
	SCL_L;
}
void send_NOACK(void)
{
	SCL_L;
	sda_out();
	SDA_H;
	SCL_H;
	delay_us(5);
	SCL_L;
}
int wait_ACK(void)
{
	uint16_t time_out=0;
	SCL_L;
	sda_in();
	SCL_H;
	while(SDA_READ)
	{
		time_out++;
		if(time_out>300)
		{
			iic_stop();
			return NOACK;
		}
	}
	SCL_L;
	return ACK;
}
void send_data(uint8_t data)
{
	uint8_t i=0;
	SCL_L;
	sda_out();
	for(i;i<8;i++)
	{
		if(data & 0x80)
		{
			SDA_H;
		}
		else
		{
			SDA_L;
		}
		SCL_H;
		delay_us(1);
		SCL_L;
		delay_us(1);
		data<<=1;
	}

}
uint8_t read_data(uint8_t ack_flag)
{
	uint8_t r_data=0,i=0;
	SCL_L;
	sda_in();
	for(i;i<8;i++)
	{	
		r_data<<=1;
		SCL_H;
		if(SDA_READ)
		{
			r_data|=0x01;
		}
		delay_us(1);
		SCL_L;
		delay_us(1);
	}
	if(ack_flag==ACK)
	{
		send_ACK();
	}
	else
	{
		send_NOACK();
	}
	SCL_L;
	return r_data;
}

2.SHT20应用驱动


#include "FreeRTOS.h"  
#include "drv_sht20.h"
#include "task.h"   

sht20_val sht20_data;
void sht20_init(void)
{
	iic_init();
}
uint8_t sht20_reset(void)
{
	iic_start();
	send_data(0x80);
	if(wait_ACK() != ACK)
	{
		iic_stop();
		return NOACK;
	}
	send_data(0xfe);
	if(wait_ACK() != ACK)
	{
		iic_stop();
		return NOACK;
	}
	iic_stop();
	return ACK;
}
uint16_t sht20_read(uint8_t cmd)
{
	uint16_t r_data=0;
	uint8_t temp[2];
	iic_start();
	send_data(0x80);//写地址
	if(wait_ACK() != ACK)
	{
		iic_stop();
		return NOACK;
	}
	
	send_data(cmd);
	if(wait_ACK() != ACK)
	{
		iic_stop();
		return NOACK;
	}
	
	iic_start();
	send_data(0x81);//读地址
	if(wait_ACK() != ACK)
	{
		iic_stop();
		return NOACK;
	}
	vTaskDelay(120 / portTICK_PERIOD_MS);
	temp[0] = read_data(ACK);
	temp[1] = read_data(NOACK);
	iic_stop();
	r_data =(temp[0]<<8|temp[1]);
	return r_data;
}
void sht20_getdata(void)
{
	uint16_t tmp=0;
	tmp = sht20_read(0xe5);//选择湿度
	sht20_data.hum	= -6+125*((float)tmp/65536);
	//printf("hum=%.2f\r\n",sht20_data.hum);

	uint16_t temperature_raw = sht20_read(0xe3);
	sht20_data.temperature = -46.85 + 175.72 * ((float)temperature_raw / 65536);
	//printf("Temperature: %.2f\r\n", sht20_data.temperature);

}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐