一、关于i2c协议概述

I2C总线协议只需要2根信号线即可完成数据的传输,这两根线分别是时钟线SCL和信号线SDA。I2C线上有且只有1个主设备Master和若干个从设备Slave,区别Master和Slave的标准是SCL,即谁是SCL的提供者,谁就是Master,而与SDA无关。这点尤其需要注意,发送SDA不能作为区别Master和Slave的标准。

关于I2C总线再作以下说明:
1-两条总线SDA和SCL都必须接上拉电阻,这是为了确保两条总线在空闲时都是高电平,上拉电阻的经典取值是10kΩ;
2-I2C总线上可以挂载多个主机和多个从机,但是同一时间只允许1个主机和1个从机进行通信;
3-总线上每一个设备都有一个独立的地址,通过该地址实现通信;
4-总线上的设备是“线与”的关系,即任一设备的管脚输出低电平都可以将该管脚所在总线的电平拉低,线与关系是时钟同步和总线仲裁的硬件基础;
5-I2C传输速度有标准速度模式(SS Mode)、快速模式(FS Mode)和高速模式(HS Mode)三种,数据传输速率分别为100kbps、400kbps和3.4Mbps。

协议层:协议层规定了通讯的起始停止信号、数据有效性、响应、总线仲裁、时钟同步、地址广播等内容。

1、总线空闲与信号起始终止
   I2C协议规定SDA和SCL都为高电平时总线空闲(not busy)。

I2C协议规定SCL保持高电平、SDA由高变低为起始信号(start),所有命令和数据的传输必须以起始信号为首。

I2C协议规定SCL保持高电平、SDA由低变高为终止信号(stop)。所有命令和数据的传输必须以终止信号为尾。

2、数据有效
I2C协议规定在总线上出现起始信号start后,若SCL在高电平期间SDA保持电平不变,则SDA的状态表示有效数据(data valid)。在传输数据时SDA的改变必须只能发生在SCL为
低电平期间,每一bit数据有1个时钟脉冲时长。

3、应答和非应答
   I2C协议规定每个被寻址设备在接收1字节数据后都必须向发送字节的设备发送应答(ACK)信号,确认的器件必须在应答时钟脉冲期间下拉SDA线,使得SDA线在应答相关时钟脉冲
SCL为高电平期间稳定为低电平。

I2C协议规定与ACK信号相反的信号为非应答(not ACK)信号。在主器件从从器件中读取数据时,主器件必须在读取的最后1字节数据后在SDA总线上产生not ACK信号以示意从器
件停止发送数据。not ACK信号是在SCL为高电平期间保持SDA也为高电平。

4、地址广播
地址广播是I2C协议规定的寻址方式。它是指主设备在产生start信号后,各个从设备开始关注总线SDA信号,此时主设备在总线上生成需接受/发送数据的从设备的
地址(Address),相当于向总线上所有从设备广播了这一地址。每个从设备将总线上的地址与自己的地址相对比,不一致的退出接收,一致的继续接收,直到8bit地址数据
广播完毕,仍然留下的那一个从设备就是主设备的寻址目标。

5、总线仲裁解决的是多个主设备竞争使用同一总线的问题。

假设主控器1要发送的数据DATA1为“101 ……”;主控器2要发送的数据DATA2为“1001 ……”总线被启动后两个主控器在每发送一个数据位时都要对自己的输出电平进行检测,
只要检测的电平与自己发出的电平一致,他们就会继续占用总线。在这种情况下总线还是得不到仲裁。当主控器1发送第3位数据“1”时(主控器2发送“0” ),由于“线与”的
结果SDA上的电平为“0”,这样当主控器1检测自己的输出电平时,就会测到一个与自身不相符的“0”电平。这时主控器1只好放弃对总线的控制权;因此主控器2就成为总线的
唯一主宰者。(实例来自博客)

从中可以得出:参与仲裁的所有主控器都不会丢失数据;参与仲裁的所有主控器没有固定的优先级别,而是遵循低电平优先的原则。

6、时钟同步

时钟同步是用来解决中控器和被控器的数据传输速率不相同的问题。

被控器可以通过将SCL主动拉低并延长其低电平时间的方法来通知主控器,当主控器在准备下一次传送时发现SCL为低电平,就会等待,直至被控器完成操作并释放SCL线的
控制控制权。这样,主控器实际上受到被控器的时钟同步控制。由此可见,SCL线上的低电平是由时钟低电平最长的器件决定,高电平的时间由高电平时间最短的器件决定。

需要说明的是,不管是总线仲裁还是时钟同步,它们得以实现的基础是SDA总线的“线与”性质,而这是由I2C总线独特的IO结构决定的。另外,总线仲裁和时钟同步之间并
不存在特定的先后关系,它们往往同时发生。

二、GPIO模拟I2C协议的C代码实现

下面将用C语言实现上面所描述的I2C总线协议的各个动作,并将这些分散的动作整合起来实现字节的读写操作。

1、首先需要定义2个IO口以连接两根总线SDA和SC,例如我这里使用250和251,然后定义i2c速率,即delay的值,这里设置为10k,即udelay(100);

我们可以通过以下两个命令来查看确认自己需要使用的gpio的数字是多少(其他平台可能节点不同,不过名称基本上是pinmux-pins),注意结合硬件原理图或者跟硬件工程师确认:
cat /d/pinctrl/pinctrl/pinmux-pins 或者 cat /sys/kernel/debug/pinctrl/pinctrl/pinmux-pins

cat /sys/kernel/debug/gpio //查看GPIO当前的申请使用情况和当前的电平状态

#define DELAY_TIME_HXD 100 //设置i2c clk, hxd019大概要求10k左右
#define GPIO_SDA 250
#define GPIO_SCL 251

2、初始化gpio模拟的i2c通讯SDA和SCL, 拉高SDA和SCL,初始化的效果是SDA和SCL总线上全部呈现高电平

static int gpio_i2c_init(void) 
{
    int ret;

	if(hxd019d_dbg_en == 1)
		printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);

	if (!gpio_is_valid(GPIO_SDA)) {
		printk("GPIO_SDA: %d is invalid\n", GPIO_SDA);
		return -ENODEV;
	}
    ret = gpio_request(GPIO_SDA,"GPIO_SDA");
    if(ret < 0)
    {
        printk("request GPIO_SDA error\n");
        return ret;
    }
	if (!gpio_is_valid(GPIO_SCL)) {
		printk("GPIO_SCL: %d is invalid\n", GPIO_SCL);
		return -ENODEV;
	}
    ret = gpio_request(GPIO_SCL,"GPIO_SCL");
    if(ret < 0)
    {    
        printk("request GPIO_SCL error\n");
        return ret;
    }


	if (!gpio_is_valid(GPIO_BUSY)) {
		printk("GPIO_BUSY: %d is invalid\n", GPIO_BUSY);
		return -ENODEV;
	}
    ret = gpio_request(GPIO_BUSY,"GPIO_BUSY");
    if(ret < 0)
    {    
        printk("request GPIO_BUSY error\n");
        return ret;
    }
	
	ret = gpio_direction_input(GPIO_BUSY); // set GPIO_BUSY as input IO
    if(ret < 0)
    {
        printk("set GPIO_BUSY direction fail");
        return ret;
    }

	gpio_direction_output(GPIO_SDA, 1);
	gpio_direction_output(GPIO_SCL, 1);

    return 0;

}

3、起始信号,用GPIO模拟起始信号,SCL保持高电平、SDA由高变低。

static int iic_i2cstart_hxd019(void) 
{
	if(hxd019d_dbg_en == 1)
		printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);

	gpio_direction_output(GPIO_SDA, 1);
	gpio_direction_output(GPIO_SCL, 1);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SDA, 0);
	udelay(DELAY_TIME_HXD);

	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SCL, 0);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	return 0;

}

4、终止信号:用GPIO模拟起始信号,SCL保持高电平、SDA由低变高。

static int iic_i2cstop_hxd019(void) 
{
	if(hxd019d_dbg_en == 1)
		printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);

	gpio_direction_output(GPIO_SDA, 0);
	gpio_direction_output(GPIO_SCL, 0);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SCL, 1);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SDA, 1);
	udelay(DELAY_TIME_HXD);

	return 0;

}

5、主控读取ack应答信号,即主控平台读取ACK对应的IO口处的电平。

uint8_t GetACKSign_hxd019(void)  //主控器读取ACK对应的IO口处的电平
{
	uint8_t ACKSign;

	if(hxd019d_dbg_en == 1)
		printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);

	gpio_direction_input(GPIO_SDA);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SCL, 1);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	ACKSign = gpio_get_value(GPIO_SDA);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SCL, 0);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	return ACKSign;
}

6、主控发送ack信号,即主控平台向ACK对应的IO口发送低电平。

void SendACKSign_hxd019(void) //主控器发送ACK
{
	if(hxd019d_dbg_en == 1)
		printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);

	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	gpio_direction_output(GPIO_SDA, 0);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SCL, 1);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SCL, 0);	
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);
	return ;
}

7、主控发送not ACK信号,即主控平台向ACK对应的IO口发送高电平。

void SendNoACKSign_hxd019(void) //主控器发送not ack
{
	if(hxd019d_dbg_en == 1)
		printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);

	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SCL, 1);
	udelay(DELAY_TIME_HXD);
	udelay(DELAY_TIME_HXD);

	gpio_direction_output(GPIO_SCL, 0);

	return ;
}

8、单字节读操作,它的逻辑是初始化—主控器发送起始信号—主控器逐bit读取SDA线上信号。

static int iic_I2CReadData_hxd019(uint8_t* pbData,int num) //单字节读操作
{
	uint8_t readdata = 0;
	int i=8;

	gpio_direction_input(GPIO_SDA);
	while (i--)
	{
		readdata <<= 1;

		gpio_direction_output(GPIO_SCL, 1);
		udelay(DELAY_TIME_HXD);

		readdata |= gpio_get_value(GPIO_SDA);

		gpio_direction_output(GPIO_SCL, 0);
		udelay(DELAY_TIME_HXD);
		udelay(DELAY_TIME_HXD);
	}

		gpio_direction_output(GPIO_SCL, 0);
		udelay(DELAY_TIME_HXD);

	*pbData = readdata;
	if(num >= (ReadBytesTotal -1))
		SendNoACKSign_hxd019();
	else
		SendACKSign_hxd019();

		udelay(DELAY_TIME_HXD);
		udelay(DELAY_TIME_HXD);

	return 0;
}

9、单字节写操作,它的逻辑是初始化—主控器发送起始信号—主控器逐bit往SDA线上写数据。

static uint8_t iic_I2CWriteData_hxd019(uint8_t bData) //单字节写操作
{
	uint8_t Data_Bit,ACKSign;
	int i = 0;

	if(hxd019d_dbg_en == 1)
		printk("hxd019d: %s, line(%d)\n", __func__, __LINE__);

	gpio_direction_output(GPIO_SCL, 0);
	udelay(DELAY_TIME_HXD);
						    
	for(i = 7;i >= 0; i--)
	{
		udelay(DELAY_TIME_HXD);

		Data_Bit= (bData >> i) & 0x01;

		if(Data_Bit)
			gpio_direction_output(GPIO_SDA, 1);
		else
			gpio_direction_output(GPIO_SDA, 0);

		udelay(DELAY_TIME_HXD);
		gpio_direction_output(GPIO_SCL, 1);
		udelay(DELAY_TIME_HXD);
		gpio_direction_output(GPIO_SCL, 0);
	}
	ACKSign=GetACKSign_hxd019();
	return ACKSign;		
	
	
}

10、主控检查是否接收到ACK,主控器在发完第1个地址字节后,按规定被控期需要向主控器回复一个ACK信号,
主控器如果能在总线上检测到这个ACK,就继续向被控器传送数据,否则视为本次数据传送失败。

uint8_t i2c_ack_check(uint8_t ctrl_byte)
{
	iic_i2cstart_hxd019();
	iic_I2CWriteData_hxd019(ctrl_byte);
	if(GetACKSign_hxd019() == 0)
	{

		// time delay here is not necessary, just to make waveforms more readable
		udelay(DELAY_TIME_HXD);

		gpio_direction_input(GPIO_SDA);		// set SDA as input
		gpio_direction_input(GPIO_SCL);		// set SCL as input
		return 0;
	}
	else
	{
		// time delay here is to save computing resource
		udelay(DELAY_TIME_HXD);
		return 1;
	}
}

11、i2c读写寄存器操作

int gpio_analog_i2c_read(unsigned char I2cRegAddr)//读寄存器操作
{
	int i = 100;
	//spin_lock(&a_lock);
	unsigned char bValue = 0;
    unsigned char devAddr = 0;

	if(hxd019d_dbg_en == 1)
		printk("hxd019d: ------%s(%d)-------\n", __func__, __LINE__);

    devAddr = (chipSlaveAddr << 1);
    iic_i2copen_hxd019();
    udelay(DELAY_TIME_HXD);
    iic_i2cstart_hxd019();
    udelay(DELAY_TIME_HXD);
    iic_I2CWriteData_hxd019(devAddr);
    udelay(DELAY_TIME_HXD);
    iic_I2CWriteData_hxd019(I2cRegAddr);
    udelay(DELAY_TIME_HXD);
    iic_i2cstart_hxd019();
    udelay(DELAY_TIME_HXD);
    iic_I2CWriteData_hxd019((devAddr | 0x1));
    udelay(DELAY_TIME_HXD);
    iic_I2CReadData_hxd019(&bValue,i);
    udelay(DELAY_TIME_HXD);
    iic_i2cstop_hxd019();
    udelay(DELAY_TIME_HXD);
    iic_i2cclose_hxd019();
    udelay(DELAY_TIME_HXD);
	//spin_unlock(&a_lock);
	
	return bValue;	
	
}

int gpio_analog_i2c_write(unsigned char I2cRegAddr,unsigned char data) //写寄存器操作
{

	//spin_lock(&a_lock);
    unsigned char devAddr = 0;

	if(hxd019d_dbg_en == 1)
		printk("hxd019d: ------%s(%d)-------\n", __func__, __LINE__);

    devAddr = chipSlaveAddr << 1;
    iic_i2copen_hxd019();
    udelay(DELAY_TIME_HXD);
    iic_i2cstart_hxd019();
    udelay(DELAY_TIME_HXD);
    iic_I2CWriteData_hxd019(devAddr);
    udelay(DELAY_TIME_HXD);
    iic_I2CWriteData_hxd019(I2cRegAddr);
    udelay(DELAY_TIME_HXD);
    //iic_i2cstart();
    //udelay(DELAY_TIME);
    //iic_I2CWriteData((devAddr | 0x1));
    //udelay(DELAY_TIME);
    //iic_I2CReadData(&bValue);
    iic_I2CWriteData_hxd019(data);
    udelay(DELAY_TIME_HXD);
    iic_i2cstop_hxd019();
    udelay(DELAY_TIME_HXD);
    iic_i2cclose_hxd019();
    udelay(DELAY_TIME_HXD);
	//spin_unlock(&a_lock);
	
	return 0;	
	
}
Logo

更多推荐