12.2.I2C寻址模式上一节介绍的是I2C每一位信号的时序流程而I2C通信在字节级的传输中也有固定的时序要求。I2C通信的起始信号(Start)后首先要发送一个从机的地址这个地址一共有7位紧跟着的第8位是数据方向位(R/W)“0”表示接下来要发送数据写‘“1”表示接下来是请求数据读。打电话的时候当拨通电话接听方捡起电话肯定要回一个“喂”这就是告诉拨电话的人这边有人了。同理这个第九位ACK实际上起到的就是这样一个作用。当发送完了这7位地址和1位方向后如果发送的这个地址确实存在那么这个地址的器件应该回应一个ACK拉低SDA即输出“0”如果不存在就没“人”回应NACKSDA将保持高电平即“1”。写一个简单的程序访问一下Kingst51开发板上的EEPROM的地址另外再写一个不存在的地址看看它们是否能回一个ACK来了解和确认一下这个问题。Kingst51开发板上的EEPROM器件型号是24C02在24C02的数据手册3.6节中可查到24C02的7位地址中其中高4位是0b1010低3位的地址取决于具体电路的设计,由芯片的A2、A1、A0这3个引脚的实际电平决定来看一下电路图如图12-4所示。图12-4 24C02原理图从图12-4可以看出A2、A1、A0都是接的GND也就是说都是0因此24C02的7位地址实际上是二进制的0b1010000也就是0x50。用I2C的协议来寻址0x50另外再寻址一个不存在的地址0x62寻址完毕后通过逻辑分析仪观察一下两个地址是否回复ACK。/*****************************main.c文件程序源代码******************************/#include reg52.h#include intrins.h#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}sbit I2C_SCL P3^7;sbit I2C_SDA P3^6;bit I2CAddressing(unsigned char addr);void main(){I2CAddressing(0x50); //查询地址为0x50的器件I2CAddressing(0x62); //查询地址为0x62的器件while (1);}/* 产生总线起始信号 */void I2CStart(){I2C_SDA 1; //首先确保SDA、SCL都是高电平I2C_SCL 1;I2CDelay();I2C_SDA 0; //先拉低SDAI2CDelay();I2C_SCL 0; //再拉低SCL}/* 产生总线停止信号 */void I2CStop(){I2C_SCL 0; //首先确保SDA、SCL都是低电平I2C_SDA 0;I2CDelay();I2C_SCL 1; //先拉高SCLI2CDelay();I2C_SDA 1; //再拉高SDAI2CDelay();}/* I2C总线写操作dat-待写入字节返回值-从机应答位的值 */bit I2CWrite(unsigned char dat){bit ack; //用于暂存应答位的值unsigned char mask; //用于探测字节内某一位值的掩码变量for (mask0x80; mask!0; mask1) //从高位到低位依次进行{if ((maskdat) 0) //该位的值输出到SDA上I2C_SDA 0;elseI2C_SDA 1;I2CDelay();I2C_SCL 1; //拉高SCLI2CDelay();I2C_SCL 0; //再拉低SCL完成一个位周期}I2C_SDA 1; //8位数据发送完后主机释放SDA以检测从机应答I2CDelay();I2C_SCL 1; //拉高SCLack I2C_SDA; //读取此时的SDA值即为从机的应答值I2CDelay();I2C_SCL 0; //再拉低SCL完成应答位并保持住总线return ack; //返回从机应答值}/* I2C寻址函数即检查地址为addr的器件是否存在返回值-从器件应答值 */bit I2CAddressing(unsigned char addr){bit ack;I2CStart(); //产生起始位即启动一次总线操作ack I2CWrite(addr1); //器件地址需左移一位因寻址命令的最低位//为读写位用于表示之后的操作是读或写I2CStop(); //不需进行后续读写而直接停止本次总线操作return ack;}前面的章节中已经提到利用库函数_nop_()可以进行精确延时一个_nop_()的时间就是一个机器周期这个库函数包含在intrins.h这个文件中如果要使用这个库函数只需要在程序最开始和包含reg52.h一样includeintrins.h之后程序中就可以使用这个库函数了。还有一点要提一下I2C通信分为低速模式100kbit/s、快速模式400kbit/s和高速模式3.4Mbit/s。因为所有的I2C器件都支持低速但却未必支持另外两种速度所以作为通用的I2C程序选择100k这个速率也就是说实际程序产生的时序必须小于等于100k的时序参数很明显也就是要求SCL的高低电平持续时间都不短于5us因此在时序函数中通过插入I2CDelay()这个总线延时函数它实际上就是4个NOP指令用define在文件开头做了定义加上改变SCL值语句本身占用的至少一个周期来达到这个速度限制。如果以后需要提高速度那么只需要减小这里的总线延时时间即可。此外学习一个发送数据的技巧就是I2C通信时如何将一个字节的数据发送出去。注意函数I2CWrite中用的for循环的技巧。for (mask0x80; mask!0; mask1)由于I2C通信是从高位开始发送数据所以先从最高位开始0x80和dat进行按位与运算从而得知dat第7位是0还是1然后右移一位也就是变成了用0x40和dat按位与运算得到第6位是0还是1一直到第0位结束最终通过if语句把dat的8位数据依次发送了出去。使用Kingst LA5016逻辑分析仪将抓到的波形显示出来并且用过I2C的协议解码器将协议解析出来如图12-5所示。从图上可以看出第一个字节发的是0x50回复了一个ACK第二个字节发了一个0x62但是出现的是NAK说明这个地址没有产生应答。图12-5 逻辑分析仪抓取I2C地址在逻辑分析仪的I2C协议设置中有三种地址格式显示方式也就是目前市面上各种资料对I2C协议地址定义的方式。如图12-6所示。图12-6 I2C地址显示格式前边讲I2C发送的第一个字节是7位地址加一位读写位但是有些资料直接将读写位归结到I2C的地址也有的资料将7位地址位认为是高7位以开发板的0x50地址和0x62地址为例即地址二进制0b1010 000写的时候是0b1010 0000读的时候是0b1010 0001。方式18-bit包含读/写位0x50地址对应这种方式写地址为0xA0;读地址为0xA1。方式28-bit读/写位显示为0即写地址和读地址都是0xA0。方式37-bit本教材采用的方式写地址和读地址都是0x50。