IIC通信总结
由于通信协议一段时间不用后,会忘记一些细节,因此抽空总结出来,以便于自己和各位后续快速复习,刚好手上有个项目是使用IIC通信的,先从IIC通信开始总结吧
简介
IIC是一种需要两线时的半双工通信方式,用于连接微控制器和外围设备,它是由数据线 SDA 和时钟线 SCL 构成的串行总线,具有以下一些特点:
-
半双工通信:设备直接可以发送和接收,但是不能同时进行。
-
通信速率:常见的有100k和400k的速率,更高有3.4M和5M速率,但是很少用,一般不做讨论
-
可使用IO口模拟实现IIC:硬件IIC效率相较于软件IIC效率更高,传输速度更快,并且可以使用DMA,但是限制固定IO口,软件IIC则更加灵活,选取两个通用IO进行高低电平模拟即可。
-
实现多对多功能:可以同时挂载多个主机和多个从机设备,主机设备数量不限制,从机数量理论上为127(2^8)。
IIC通信协议的详解
-
大致原理:每一个器件都有一个对应的器件地址,主机通过发送器件地址找到对应的外围设备,然后发送需要进行读写的寄存器地址,然后如果是写,则主机继续发送需要写入的数据。如果是读则等待从机回应数据。
-
写数据:MCU–>开始信号–>从机器件地址(7位)+R/W(读为1,写为0)–>需要操作的寄存器地址–>需要写入的数据–>结束–>slave
-
读数据:MCU–>开始信号–>从机器件地址(7位)+R/w(读为1,写为0)–>需要操作的寄存器地址–>从机器件地址–>转入接收模式–>slave–>发送主机需要读取数据–>MCU–>发送结束信号–>结束–>Slave。
-
-
IIC通信的四种信号:
- 1.开始信号:SCL为高电平的时候,SDA从高电平到低电平跳变,开始传输数据
- 2.结束信号:SCL为高电平的时候,SDA从低电平到高电平跳变,结束传输数据
- 3.应答信号:接收数据的IC在接到8bit数据后,想发送数据的IC回一个低电平脉冲作为应答信号
- 4.数据信号:在一个时钟周期内,SCL为高电平时,SDA保持高,这表示该bit传输数据为1,SDA保持为低,则表示该bit传输数据为0
- 5.注意事项
- 1.只有起始信号是必须的,结束信号和应答信号都可以不存在。
-
时序图:
-
如下图,黄色为时钟线,粉色为数据线.传输两字节数据。主机发送特殊指令
- 1.MCU发送左边红色框中start信号
- 2.紧接着发送数据0001 1001(时钟线为高时,数据线为低则为0,数据线为高则为1),即发送数据0x19。
- 3.从机给一个低电平脉冲应答信号
- 4.从机给主机回1001 0001,即发送数据0x91。
- 5.主机未做应答信号
- 6.主机发送stop停止信号。
-
-
实例代码(以软件IIC为例)
-
1.开始信号:
// 开始总线(主设备 --> 从设备) static void I2C_Start(I2C_CONFIG_DEF *io_conf) { i2c_dat_output_init(io_conf);//IO口设置为输出模式 I2Cdat_H(io_conf); // 在SCL为高时SDA由1变为0开始总线 I2Cclk_H(io_conf); I2Cdat_L(io_conf); i2c_delay; I2Cclk_L(io_conf); // 钳住I2C总线,准备发送或接收数据 i2c_delay; }
-
2.结束信号:
// 结束总线(主设备 --> 从设备) static void I2C_Stop(I2C_CONFIG_DEF *io_conf) { i2c_dat_output_init(io_conf); I2Cdat_L(io_conf); // 在SCL为高时SDA由0变为1结束总线 I2Cclk_H(io_conf); i2c_delay; I2Cdat_H(io_conf); i2c_delay; }
-
3.发送应答信号:
// 设置应答位(主设备 --> 从设备) // ack: 0 = 确认, 1 = 不确认 static void I2C_Set_Ack(I2C_CONFIG_DEF *io_conf, unsigned char ack) { i2c_dat_output_init(io_conf); // SDA输出 I2Cclk_L(io_conf); if (ack) { I2Cdat_H(io_conf); } // 不确认,主设备告诉从设备,接收数据结束。 else { I2Cdat_L(io_conf); } // 确认,主设备每收到一个字节后都要发送确认 I2Cclk_H(io_conf); i2c_delay; I2Cclk_L(io_conf); I2Cdat_L(io_conf); }
-
4.接收应答信号:
// 接收应答位(主设备 <-- 从设备) // ret: 0 = 确认, 1 = 确认错误 static unsigned char I2C_Get_Ack(I2C_CONFIG_DEF *io_conf) { unsigned char ack; i2c_dat_input_init(io_conf); // SDA输入 I2Cdat_H(io_conf); // 8位发送完后释放数据线,准备接收应答位 I2Cclk_H(io_conf); i2c_delay; if (I2C_getdat(io_conf)) { ack = 1; } // 从设备无应答,说明器件损坏、忙状态或者不存在 else { ack = 0; } // 从设备正确应答 I2Cclk_L(io_conf); i2c_delay; return (ack); }
-
5.写入单个字节:
static void I2C_Write_8bits(I2C_CONFIG_DEF *io_conf, unsigned char dat) { unsigned char i; i2c_dat_output_init(io_conf); // SDA输出 for (i = 8; i; i--) { if (dat & 0x80) { I2Cdat_H(io_conf); } // 先发送最高位MSB else { I2Cdat_L(io_conf); } i2c_delay; I2Cclk_H(io_conf); // SCL为高时,保持数据稳定 i2c_delay; dat <<= 1; I2Cclk_L(io_conf); // SCL为低时,改变数据 } i2c_delay; }
-
6.读取单个字节:
static unsigned char I2C_Read_8bits(I2C_CONFIG_DEF *io_conf) { unsigned char i, dat; i2c_dat_input_init(io_conf); // 输入 dat = 0; // 初始化变量 for (i = 8; i; i--) { i2c_delay; I2Cclk_H(io_conf); dat <<= 1; // 先收到最高位 i2c_delay; if (I2C_getdat(io_conf)) dat++; I2Cclk_L(io_conf); } i2c_delay; return (dat); }
-
7.读写集成接口函数:
/*************************** ***** brief :对器件进行IIC的读写操作 ***** input :io_conf:软件I2C的IO结构体,包括了时钟和数据使用哪个IO口模拟 ***** WR_flag:读写操作,写是1,读是0 ***** ChipAddress:从机器件地址 ***** RegAddress:从机寄存器地址 ***** data:用于保存发送或者读取数据的指针 ***** data_number:需要发送或者读取的数据长度 ***** return :返回通信结果,SUCCESS表示通信成功,ERROR表示通信失败 ****************************/ u8 I2C_ReadWrite(I2C_CONFIG_DEF *io_conf, u8 WR_flag, u8 ChipAddress, u8 RegAddress, u8 *data, u8 data_number) { u8 temp; ChipAddress = ChipAddress << 1; I2C_Start(io_conf); // 开始总线 I2C_Write_8bits(io_conf, ChipAddress); // 写入地址 if (I2C_Get_Ack(io_conf) != 0) // 接收应答位 { I2C_Stop(io_conf); printf("I2C communication Error 0x%x\n", ChipAddress >> 1); return ERROR; } I2C_Write_8bits(io_conf, RegAddress); // 写入数据 if (I2C_Get_Ack(io_conf) != 0) // 接收应答位 { I2C_Stop(io_conf); printf("I2C communication Error 0x%x\n", ChipAddress >> 1); return ERROR; } if (WR_flag) { //循环发送 while (data_number--) { I2C_Write_8bits(io_conf, *data++); // 写入数据 if (I2C_Get_Ack(io_conf) != 0) // 接收应答位 { I2C_Stop(io_conf); printf("I2C communication Error 0x%x\n", ChipAddress >> 1); return ERROR; } } I2C_Stop(io_conf); // 结束总线 return SUCCESS; } else { I2C_Stop(io_conf); // 结束总线 I2C_Start(io_conf); // 开始总线 I2C_Write_8bits(io_conf, ChipAddress + 1); // 写入地址 if (I2C_Get_Ack(io_conf) != 0) // 接收应答位 { I2C_Stop(io_conf); printf("I2C communication Error 0x%x\n", ChipAddress >> 1); return ERROR; } while (data_number--) { // printf("data_number=%d\n",data_number); //最后一个char关闭ack if (data_number == 0) { temp = I2C_Read_8bits(io_conf); *data++ = temp; I2C_Set_Ack(io_conf, 1); } //等待接收完成 else { temp = I2C_Read_8bits(io_conf); *data++ = temp; I2C_Set_Ack(io_conf, 0); } } I2C_Stop(io_conf); // 结束总线 return SUCCESS; } }
-
-引用:
- 1.https://baijiahao.baidu.com/s?id=1732309316664193755&wfr=spider&for=pc