IIC原理及Linux应用空间IIC编程

IIC原理及Linux应用空间IIC编程

IIC就是master/slave模式,通过数据线和时钟线两根线实现主从设备通信,其传输熟虑标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。其主要由两根线组成是SDA、SCL、上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

1、IIC连线
  • VCC:提供电源
  • GND:接地
  • SDA:串联数据线,传输数据
  • SCL:串联时钟线,起控制作用
    在这里插入图片描述
2、IIC通信过程

IIC通信过程由开始、发送、响应、接收、结束五个部分构成。

1、(在发送、接收数据的时候)当SCL为高电平时,SDA线不允许变化(一变化就是开始和结束信号);当SCL线为低电平时,SDA线可以任意0、1变化。 
2、SCL为高电平期间SDA要保持稳定,这时可以对SDA进行采样,在SCL为低电平期间可以对SDA进行设置(即将传输0或者1)。

一个字节的数据是如何传输的,一共八位。Master会先将SCL拉低,然后在SCL为低的状态下将一个bit放到SDA上(比如要发送一个 0,Master就会通过拉低SDA来放好这个0),然后Master会把SCL拉高(释放),此时Slaver会立刻检测到SCL的变化,由此聪明的Slaver便知道Master已经将要发送的那个bit准备好了,Slaver便会在这个SCL的高电平期间尽快(Maser不会等你很久的哦)去读取一下SDA,嗯读到了一个0,Slaver就把这个0放到自己的移位寄存器中待后续处理。Master会在一个设定好的时间后把SCL再次拉低,然后在SCL为低电平期间把下一个bit放到SDA上,然后再把SCL拉高,然后Slaver在SCL的高电平期间再去读SDA…如此反复8次,一个Byte的传输便告结束。
在这里插入图片描述

3、IIC空闲状态

I2C总线的SDA和SCL两条信号同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

4、IIC开始、结束信号
开始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平。
停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。

在这里插入图片描述

5、IIC应答信号

发送方在发送完8bit数据后,在第9个时钟期间释放数据线,等待接收方的应答信号。接收方此时应给出应答或非应答信号,SDA被拉低表示为应答信号,SDA置高为NAK信号。应答信号由接收方发出(可能为主,也可能为从)。

6、IIC读写、地址

IIC设备地址位为8位,但是最低位为读写为。0代表写,1代表读,实际地址位为7位。7位I2C总线可以挂接127个不同地址的I2C设备,0号"设备"作为群呼地址。

7、IIC读写5中模式

https://blog.csdn.net/ctyqy2015301200079/article/details/83857641

  • 单字节写入 (BYTE WRITE)
    单片机向被控器E2PROM中写入单个字节数据,逻辑如下。
    首先单片机在SDA总线上产生start信号,接着产生7bit的地址信号以及1bit读写标记为,其中地址的前4bit为固定在被控器内部不可更改的序列,每一种类的器件共享一个4bit地址代码,后3bit为确定具体某个芯片的代码,那1bit读写标记位规定为高电平表示“读”、低电平表示“写”。被控器识别SDA总线上的7bit地址和1bit标记位,在发现和自己的地址匹配后向SDA上发送ACK,主控器收到ACK后再向SDA总线上产生具体的8bit地址,也就是要把字节发送到E2PROM的具体哪个位置去。被控器接收到地址字节后再次发送ACK。此时,主控器接收到ACK后会发送1字节的数据,被控器从SDA上依次逐bit读取该字节数据,之后向主控器发送ACK,主控器在接收到ACK后向总线发送stop信号,结束本轮数据传输。
    为方便描述,我们把7bit地址和1bit读写标记位称为“控制字节(Control Byte)”,把标记芯片内部地址的字节称为“字地址(Word Address)”,发送的数据就是Data。
    主控器向被控器写入单字节数据的总线信号示意如图所示。
    在这里插入图片描述
    此处有一点需要注意,那就是对A0、A1、A2这3个bit的理解和使用。
    上文中说到这3bit表示对总线上芯片地址的识别,在单主单从的模式中无需考虑,而Control Byte的后3bit正好是用来在SDA上寻找从器件的。这样一来似乎在单主单从模式下后3bit可以随意填写,而24C08内部的block却无法区分,因此上述描述不能自洽。而我在实践中得到的信息是这样的:对于24C08而言,Control Byte的后3bit中的最后2bit决定了片内block的选择,Control Byte的后3bit中的第1bit决定了芯片的选择,因此,一条I2C总线上最多只能挂载2片24C08芯片,共计8个block。同理,一条I2C总线上最多只能挂载4片24C04(每片有2个block)或8片24C02(每片有1个block)。
    集成I2C总线的E2PROM地址为的前4bit按规定都是1010,这4bit数据是固定在其内部无法改变的。因此,对于24C08来说,不妨假设地址位后3bit的第1bit都是0,那么其中4个block的7bit地址代码分别为:1010000、1010001、1010010和1010011,转换成16进制就是:0x50、0x51、0x52和0x53。
  • 页写入 (PAGE WRITE)
    单片机向被控器E2PROM中整页写入数据,逻辑如下。
    在被控器向主控器发送第3个ACK表明自己收到1字节收据后,主控器继续发送下一字节数据而不是stop,直到某一时刻主控器发送stop。
    虽然页(Page)的大小是16Byte,但是主控器连续发送的数据量不一定非得是一页的数据量,理论上可以连续发送任意多字节的数据。但是有一点需要注意,就是对E2PROM的写入是按页循环的,即当地址超出某页末尾后,下一个待写入字节的地址不会自动转入下一页,而是会回到本页的开头,这样该字节就会覆盖本页开头原有的那一字节数据。
    主控器向被控器整页写入数据的总线信号示意如图所示。
    在这里插入图片描述
  • 读取当前地址 (CURRENT ADDRESS READ)
    单片机读取被控器当前操作数据(读/写)的地址,逻辑如下。
    芯片24C28内部有一个地址计数器,记录了上一个数据访问的地址,不论是读取还是写入。因此,若上一个操作的地址是n,那么下一步执行读取当前地址所得到的数据就是n+1。当24C08收到Control Byte后,它在SDA总线上生成ACK信号以及8bit地址n+1,主控制器则在SDA上产生not ACK信号示意24C08停止继续传输,最后主控制器产生stop结束此次任务。
    主控器读取被控器当前地址的总线信号示意如图所示。
    在这里插入图片描述
  • 随机读取 (RANDOM READ)
    随机读取并不是真的“随机”,而是读取指定地址是的数据,逻辑如下。
    随机读取允许主控器读取被控器任意地址的数据。首先主控器发送start,接着发送Control Byte,注意此时最后一位续写标记bit值应为0,即表示“写”。在被控器应答ACK后,主控器将地址字节发送出去,当被控器再次应答ACK后,主控器再次发送一个start信号以结束“写”任务,并紧跟着发送Control Byte开始“读”任务,并注意这里Control Byte的最后1bit值应当为1,即表示“读”。当被控器第三次应答ACK后,此时被控器向主控器发送刚刚接收到的地址处的数据字节。主控器接收完毕后向被控器发送not ACK示意被控器停止继续传输,最后主控器产生stop信号结束任务。
    主控器随机读取被控器数据的总线信号示意如图所示。
    在这里插入图片描述
  • 顺序读取 (SEQUENTIAL READ)
    顺序读取是主控器读取某一特定地址及其之后的若干个数据,逻辑如下。
    顺序读取前面的逻辑和随机读取一致,直到最后一步:主控器在接收到数据后并不发送not ACK而是发送ACK,这样被控器继续向主控器发送数据,直到主控器产生not ACK为止,最后主控器会在发送not ACK之后发送stop结束任务。
    主控器顺序读取被控器数据的总线信号示意如图所示。
    在这里插入图片描述
    理论上被控器可以向主控器无限次发送数据,但是和PAGE WRITE的情形一样,连续读取也存在循环,只不过读取时逐block循环的,显然比PAGE WEITE的逐页循环大了很多。也就是说,在读取到当前block的最后一个字节的数据后,如果继续读取,那么不会读到下个block首地址的数据,而是会读到本block首地址的数据。

注意:其中写入第二个字节是word address是指定的写到具体那个位置去。跟随机读取是一样,指定读取位置。顺序读取和读取当前位置区别是,读取当前位置只读1个字节,而顺序读取是读取很多字节。

8、代码

流程:

  • i2c_open : 打开设备,获取文件描述符
  • i2c_init : 初始化设置7字节模式和从机地址
  • i2c_softreset : 设置软中断
  • i2c_get_temperture_humidity: 获取温度湿度
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <sys/stat.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <string.h>


#define SOFTRESET 0xfe
#define TRIGGER_TEMPERATURE 0xF3
#define TRIGGER_HUMIDITY 0xF5

static inline void msleep(unsigned long ms);
int i2c_open();
int i2c_init(int fd);
int i2c_softreset(int fd);
int i2c_get_temperature_humidity(int fd);

int main(){
	int	fd;
	double	temp;	
	fd = i2c_open();
	if(fd < 0){
		printf("open dev/i2c-1 is faild %s\n",strerror(errno));
	}
	i2c_init(fd);
	i2c_softreset(fd);
	i2c_get_temperature_humidity(fd);
}

static inline  void msleep(unsigned long ms)
{
 struct timespec cSleep;
 unsigned long ulTmp;
 
 cSleep.tv_sec = ms / 1000;
 if (cSleep.tv_sec == 0)
 { 
 ulTmp = ms * 10000;
 cSleep.tv_nsec = ulTmp * 100;
 } 
 else
 { 
 cSleep.tv_nsec = 0;
 } 
 nanosleep(&cSleep, 0);
}

int i2c_open(){
	int	fd;
	fd = open("/dev/i2c-1",O_RDWR);
	if(fd < 0 ){
		printf("i2c device open faild : %s\n", strerror(errno));
		return -1;
	}	
	return fd;
}


int i2c_init(int fd){
	if(fd < 0 ){
		printf("fd is wrong\n");
		return -1;
	}
	if(ioctl(fd, I2C_TENBIT, 0) < 0){
		printf("set bitaddr is faild [%s]\n",strerror(errno));
		return -1;
	}
	if(ioctl(fd, I2C_SLAVE, 0x40) < 0){
		printf("set slave is faild [%s]\n",strerror(errno));
		return -1;
	}

}

int i2c_softreset(int fd){
	struct i2c_msg		msg;
	struct i2c_rdwr_ioctl_data	data;

	uint8_t	buf[2];
	msg.addr = 0x40;
	msg.flags = 0;
	msg.len = 1;
	msg.buf = buf;
	msg.buf[0] = SOFTRESET;

	data.nmsgs = 1;
	data.msgs = &msg;

	if(ioctl(fd, I2C_RDWR, &data) < 0){
		printf("resert is faild [%s]\n", strerror(errno));
		return -1;
	}
	msleep(50);
	return 0;
}

int i2c_get_temperature_humidity(int fd){
	struct i2c_msg	msg;
	struct	i2c_rdwr_ioctl_data	data;
	
	uint8_t	buf[4];
	msg.addr = 0x40;
	msg.flags = 0;
	msg.len = 1;
	msg.buf = buf;
	msg.buf[0] = TRIGGER_TEMPERATURE;
	
	data.nmsgs = 1;
	data.msgs = &msg;	
	
	if(ioctl(fd, I2C_RDWR, &data) < 0){
		printf("write is faild : %s\n",strerror(errno));
		return -1;
	}
	msleep(65); //There must wait 65ms at least
	/*------------------------*/
	/*    get temperature     */
	/*------------------------*/
	memset(buf,0, sizeof(buf));
	msg.addr = 0x40;
	msg.flags =1 ;
	msg.len = 3;
	msg.buf = buf;
	
	data.nmsgs = 1;
	data.msgs = &msg;
	if(ioctl(fd, I2C_RDWR, &data) < 0){
		printf("read is faild : %s\n", strerror(errno));
	}
	double temp =  175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
	printf("temperature is [%.2f℃]\n",temp);

	/*-------------------------*/
	/*	get humidity	   */
	/*-------------------------*/
	msg.flags = 0;
	msg.len = 1;
	msg.addr = 0x40;
	msg.buf[0] = TRIGGER_HUMIDITY;

	data.nmsgs = 1;
	data.msgs = &msg;
	if(ioctl(fd, I2C_RDWR, &data) < 0){
		printf("write humidity is faild [%s]\n",strerror(errno));
		return -1;
	}
	msleep(22); // There must be waiting 22ms
	memset(buf, 0, sizeof(buf));	
	msg.addr = 0x40;
	msg.len = 3;
	msg.buf = buf;
	msg.flags = I2C_M_RD;

	data.nmsgs = 1;
	data.msgs = &msg;

	if(ioctl(fd, I2C_RDWR, &data) < 0){
		printf("read humdity is faild [%s]\n",strerror(errno));
		return -1;
	}
	double humidity = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;
	printf("humidity is [%.2f%]\n", humidity);
	return 0;
}

这里我们对温湿度的获取的步骤。我们对代码进行讲解。

  • 1

    struct i2c_msg	msg;
    struct	i2c_rdwr_ioctl_data	data;
    

    首先定义结构体,i2c_msg结构体如下:

    struct i2c_msg {
    	__u16 addr;					/* 从机地址			*/
    	__u16 flags;				/* 标志位,指定进行的操作 */
    #define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
    #define I2C_M_RD		0x0001	/* read data, from slave to master */
    #define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
    #define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
    	__u16 len;		/* msg length(单位为字节,需要注意)				*/
    	__u8 *buf;		/* pointer to msg data			*/
    };
    

    i2c_rdwr_ioctl_data结构体如下:

    struct i2c_rdwr_ioctl_data {
    	struct i2c_msg __user *msgs;	/* 指向i2c_msg的指针 */
    	__u32 nmsgs;			/* 定义msg的个数*/
    };
    
  • 2

    uint8_t	buf[4];
    

    这个是设置4个字节大小的数组。

  • 3
    首先像从机写数据,告诉从机我要获取温度了。

    msg.addr = 0x40; /* set slave address*/
    msg.flags = 0; /*write*/
    msg.len = 1; /*send len is one byte */
    msg.buf = buf; /*Give buf to buf of structure*/
    msg.buf[0] = TRIGGER_TEMPERATURE; /* send msg 0xf3*/
    
    data.nmsgs = 1; /* msg number is one*/
    data.msgs = &msg;	 /*Point to msg*/
    
    if(ioctl(fd, I2C_RDWR, &data) < 0){ /* send to kernel*/
    	printf("write is faild : %s\n",strerror(errno));
    	return -1;
    }
    

    里面有发送的数据,0xf3就是要获取温度的指令代码在这里插入图片描述

  • 4

    msleep(65); //There must wait 65ms at least
    

    在第三步在给从机发送想要获取温度之后,我们需要等待至少65ms,因为这个时间是给从机获取温度的时间,如果没有这一句话,从机没有时间采集温度,那怎么获取温度呢?可以看到下面这个图片,温度获取最小值是66 最大是85,我代码是65是因为我测试了65 也可以获取到温度,但是64就不行了,也就是说采集温度最小等待65ms。
    在这里插入图片描述

  • 5

    /*------------------------*/
    /*    get temperature     */
    /*------------------------*/
    	memset(buf,0, sizeof(buf)); /*清空buf*/
    	msg.addr = 0x40; /*从机地址*/
    	msg.flags =1 ; /* 标志位1代表读取数据 */
    	msg.len = 3; /*读取的长度位3*/
    	msg.buf = buf; /*读取的数据放在buf中*/
    	
    	data.nmsgs = 1; /*只有一个msg*/
    	data.msgs = &msg; /*指向msg*/
    	if(ioctl(fd, I2C_RDWR, &data) < 0){ /*告诉内核我要干什么,给他执行*/
    		printf("read is faild : %s\n", strerror(errno));
    	}
    	double temp =  175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
    	printf("temperature is [%.2f℃]\n",temp);
    

    这是在发送给从机写我要获取温度之后,在从机中读取数据的过程,我们必须先写数据告诉从机我们要干什么,才能读取数据。

  • 6

    /*-------------------------*/
    /*	get humidity	   */
    /*-------------------------*/
    msg.flags = 0;
    msg.len = 1;
    msg.addr = 0x40;
    msg.buf[0] = TRIGGER_HUMIDITY;
    
    data.nmsgs = 1;
    data.msgs = &msg;
    if(ioctl(fd, I2C_RDWR, &data) < 0){
    	printf("write humidity is faild [%s]\n",strerror(errno));
    	return -1;
    }
    msleep(22); // There must be waiting 22ms
    memset(buf, 0, sizeof(buf));	
    msg.addr = 0x40;
    msg.len = 3;
    msg.buf = buf;
    msg.flags = I2C_M_RD;
    
    data.nmsgs = 1;
    data.msgs = &msg;
    
    if(ioctl(fd, I2C_RDWR, &data) < 0){
    	printf("read humdity is faild [%s]\n",strerror(errno));
    	return -1;
    }
    double humidity = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;
    printf("humidity is [%.2f%]\n", humidity);
    return 0;
    

    同样的过程获取湿度也是先清空buf,然后给从机写数据告诉从机我要获取湿度,然后等待22毫秒,让从机采集湿度信息,最后再向从机发送一个请求读取湿度信息的包,然后读取出来就完事。

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值