目录一. I2C 简介1.1 I2C 介绍二. I2C 基本时序与基本知识2.1 起始位2.2 停止位2.3 数据传输2.4 应答信号 ACK2.5 对从机地址的规定三. I2C 主机与从机之间的通信时序3.1 写一个字节时序3.2 读一个字节时序3.3 写多个字节时序3.4 读多个字节时序四. IMX6ULL 中与 I2C 相关的寄存器4.1 I2Cx_IFDR 分频器寄存器4.2 I2Cx_I2CR I2C控制寄存器4.3 I2Cx_I2SR I2C状态寄存器4.4 I2Cx_I2DR 数据输入输出寄存器五. 使用I2C协议与AT24C02进行通信5.1 I2C 初始化代码5.2 I2C 主机写AT24C02示例代码1、流程图2、示例代码5.3 I2C主机读AT24C02示例代码1、流程图2、示例代码六. 使用I2C协议与 LM75A 进行通信6.1 LM75A 芯片简介6.2 LM75A 的主要参数6.3 LM75A 的设备地址6.4 LM75A 的寄存器地址6.5 LM75A 的时序6.6 读取温度示例代码I2C 是最常用的通信接口众多的传感器都会提供 I2C 接口来和主控相连比如陀螺仪、 加速度计、触摸屏、温度传感器等等。所以 I2C 是必须掌握的一. I2C 简介串行同步半双工1.1 I2C 介绍IICInter-Integrated Circuit又称I2C是是IICBus简称所以中文应该叫集成电路总线。是飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展而来的一种同步串行半双工通信总线方式。该总线允许同时连接多个设备(芯片)。每块芯片在总线上拥有特定的地址I2C 是很常见的一种总线协议I2C 是 NXP 公司设计的I2C 使用两条线在主机和从机之间进行数据通信。一条是SCL(Serial Clock)(串行时钟线)另外一条是SDA(Serial Data)(串行数据线)这两条数据线需要接上拉电阻目的是为了总线空闲的时候 SCL 和 SDA 处于高电平。I2C 总线标准模式下速度可以达到100Kbit/s快速模式下可以达到400Kbis/sI2C 是支持多从机的也就是一个 I2C 控制器下可以挂多个 I2C 从设备这些不同的 I2C 从设备有不同的器件地址这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C 设备了一个 I2C 总线连接多个 I2C 设备如图所示图中 SDA 和 SCL 这两根线必须要接一个上拉电阻范围是4.7K - 10K之间一般是4.7K。其余的 I2C 从 器件都挂接到 SDA 和 SCL 这两根线上这样就可以通过 SDA 和 SCL 这两根线来访问多个 I2C 设备二. I2C 基本时序与基本知识2.1 起始位也就是 I2C 通信起始标志通过这个起始位就可以告诉I2C 从机要开始进行 I2C 通信了。在 SCL 为高电平的时候SDA 出现下降沿就表示为起始位2.2 停止位停止位就是停止 I2C 通信的标志位和起始位的功能相反。在 SCL 位高电平的时候SDA 出现上升沿就表示为停止位2.3 数据传输发送数据MSB高位先行原则规定当时钟线 SCL 高电平时数据线 SDA 要保持稳定因为 I2C 接收方要采样数据当时钟线 SCL 为低电平时数据线 SDA 信号可改变I2C发送方发送数据2.4 应答信号 ACK当 I2C 主机发送完 8 位数据以后会将数据线 SDA 设置为输入状态等待 I2C 从机应答 ACK也就是等到 I2C 从机告诉主机它接收到数据了。应答信号是由从机发出的主机需要提供应答信号所需的时钟主机发送完 8 位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信号表示通信成功否则表示通信失败2.5 对从机地址的规定从机地址起始信号后的第一个字节发送即为从机地址总共八位2^8 256 --- 可得 I2C 总线上可挂载 256 个从机但是规定前七个bit作为从机地址在2^7 128 --- 总共可挂载 128 个从机最后一个 bit 位来表示数据的流向位0主机发给从机低电平写1从机发给主机高电平读总结七位从机地址最多可挂载2^7 128个从机但是I2C会空留出一个即127个从机九位从机地址最多2^9 - 1 个从机三. I2C 主机与从机之间的通信时序此节以 AT24C02 (EEPROM) 芯片为例来说明主机与从机使用 I2C 总线进行通信的时序关于AT24C0x系列芯片的说明AT24C02A、AT24C04A、AT24C08A 这些芯片都是EEPROMAT24C02A容量2K (256 x 8)注意这里的 2K 表示的是 bit而不是字节 字节 2 K / 8 256 字节AT24C04A容量4K (512 x 8)AT24C08A容量8K (1024 x 8)芯片图设备地址此芯片的 A2 - A0 默认接地故芯片地址为1010 0000 0xA0设备的寄存器地址由于使用的是 AT24C02A 容量为 256 字节所以内部的寄存器地址为0x00 - 0xFF可以对其进行单字节的写操作也可以对其进行 页 的写操作AT24C02A一页 八字节3.1 写一个字节时序主机通过 I2C 总线与从机之间进行通信的写时序1)、开始信号主机发送起始信号通信时起始信号总是由主机发起2)、发送 I2C 设备地址每个 I2C 器件都有一个设备地址通过发送具体的设备地址来决 定访问哪个 I2C 器件。这是一个 8 位的数据其中高 7 位是设备地址最后 1 位是读写位为 1 的话表示这是一个读操作为 0 的话表示这是一个写操作。也就是低电平写高电平读3)、读写位I2C 器件地址后面跟着一个读写位为 0 表示写操作为 1 表示读操作4)、ACK 应答从机向主机回复ACK应答信号5)、寄存器地址主机向从机发送要写入数据的寄存器地址6)、ACK 应答从机向主机回复ACK应答信号7)、主机向从机发送要写入寄存器的数据8)、ACK 应答从机向主机回复ACK应答信号9)、结束标志主机发送 STOP 结束标志3.2 读一个字节时序主机通过 I2C 总线与从机之间进行通信的读时序I2C 单字节读时序分为4 大步第一步是发送设备地址第二步是发送要读取的寄存器地址第三步重新发送设备地址最后一步就是 I2C 从器件输出要读取的寄存器值。1)、主机发送起始信号2)、主机发送要读取的I2C 从设备地址 一位写位低电平。3)、读写控制位因为是向 I2C 从设备发送数据因此是写信号---注意第一次发送的是写信号因为要先发送一次寄存器地址4)、从机向主机发送的 ACK 应答信号。5)、主机发送要读取的寄存器地址。6)、从机向主机发送的 ACK 应答信号。7)、主机重新发送 START 信号--- 注意这里主机会重新发送一次起始信号8)、重新发送要读取的I2C 从机设备地址 一位读位高电平9)、读写控制位这里是读信号表示接下来是从 I2C 从设备里面读取数据。10)、从机向主机发送的 ACK 应答信号。11)、主机从 I2C 从机器件里面读取数据。12)、主机向从机回复 NO ACK 信号表示读取完成不需要从机再发送数据了。13)、主机发出 STOP 信号停止 I2C 通信3.3 写多个字节时序I2C 多字节写时序与单字节的写时序差不多分为四大步第一步是发送设备地址与写位并等待从机的ACK应答第二步是发送要读取的寄存器地址并等待ACK应答第三步就是主机不断的向从机发送要写的数据并且等待从机的ACK应答最后主机向从机发送停止位 STOP 即可3.4 读多个字节时序I2C 多字节读时序与单字节读时序也差不多分为五大步第一步是发送7位的设备地址 一位写位低电平并且等待从机的 ACK 应答第二步是主机向从机发送要读取的寄存器地址并且等待从机的 ACK 应答第三步重新发送起始位并且重新发送7位的设备地址 一位读位并且等待从机的 ACK 应答第四步就是从机不断的向主机发送要读取的数据主机不断的向从机回复 ACK 应答第五步就是主机向从机回复 NO ACK 信号(高电平)表示读取完成不需要从机再发送 ACK 信号了并且主机发送一个结束位 STOP四. IMX6ULL 中与 I2C 相关的寄存器IMX6ULL 总共有 4 个 I2C 控制器IMX6ULL提供了 4 个 I2C 外设通过这四个 I2C 外设即可完成与 I2C 从器件进行通信IMX6ULL的 I2C 外设特性如下①、与标准 I2C 总线兼容。②、多主机运行③、软件可编程的 64 中不同的串行时钟序列。④、软件可选择的应答位。⑤、开始/结束信号生成和检测。⑥、重复开始信号生成。⑦、确认位生成。⑧、总线忙检测4.1 I2Cx_IFDR 分频器寄存器寄存器 I2Cx_IFDR 只有IC(bit5:0)这个位用来设置 I2C 的波特率I2C 的时钟源可以选择IPG_CLK_ROOT 66MHz通过设置 IC 位既可以得到想要的I2C 波特率IC 位可选的设置 如图所示不像其他外设的分频设置一样可以随意设置图中列出了 IC 的所有可选值。比如现在 I2C 的时钟源为 66MHz要设置 I2C 的波特率为100KHz(标准模式)那么 IC 就可以设置为0x15也就是 640 分频。66000000/640103.125KHz≈100KHz4.2 I2Cx_I2CR I2C控制寄存器IEN(bit7)I2C 使能位1 使能 I2C0 关闭 I2C设置波特率之前最好将该位清零设置好波特率之后再置1IIEN(bit6)I2C 中断使能位1 使能 I2C 中断0 关闭 I2C 中断MSTA(bit5)Master/Slave mode select bit主从模式选择位设置 IIC 工作在主模式还是从模式1 工作在主模式0 工作在从模式注意作为主机时启动通信前将此位置1之后会发送一个起始位。在发送过程中向此位清零会立即产生一个停止位MTX(bit4)Transmit/Receive mode select bit传输方向选择位用来设置是进行发送还是接收0 接收1 发送注意发送从机地址时此位总是1接收数据时此位为0修改此位并不会对数据流向位带来任何影响TXAK(bit3)Transmit acknowledge enable传输应答位使能0 发送 ACK 信号1 发送 NO ACK 信号关于这一位有两种说法-----------------------说法一------------------------两种情况主机作为接收方0发送ACK---主机还要继续接收请从机继续发送下一个字节1发送NO ACK---主机不再接收传输结束主机作为发送方时此位不生效-----------------------说法二------------------------在发送数据时表明是否检测应答接收数据时表明是否回应应答主机作为接收方0发送ACK1发送 NO ACK主机作为发送方0检测从机的ACK---注意这里只是检测并不阻塞1检测从机的NO ACKRSTA(bit2)Repeat start重复开始信号为 1 的话产生一个重新开始信号此位用来重发起始信号4.3 I2Cx_I2SR I2C状态寄存器ICF(bit7)Data transferring bit数据传输状态位0表示数据正在传输1表示数据传输完成用于判断数据是否正在传输只读用法等待之前的数据传输完成while( (I2C1-I2SR (1 ICF)) 0 );IAAS(bit6)1表示 I2C 地址也就是 I2Cx_IADR 寄存器中的地址是从设备地址0表示没有地址只读IBB(bit5)I2C bus busy bitI2C 总线忙标志位0 表示 I2C 总线空闲1 表示 I2C 总线忙注意发送完停止位以后该位清零启动传输前根据此位判断总线是否空闲完全由硬件自动置 1、自动清零并且是只读位IAL(bit4)Arbitration lost仲裁丢失位1表示发生仲裁丢失----一般用在多主机情况下0无仲裁丢失注意在启动通信前需要将 IAL 位清零SRW(bit2)Slave read/write从机读写状态位当 I2C 作为从机的时候使用此位用来表明主机发送给从机 的是读还是写命令0 表示主机要向从机写数据1 表示主机要从从机读取数据IIF(bit1)I2C interruptI2C 中断挂起标志位1 表示有中断挂起0无中断挂起此位需要软件清零注意在启动通信前需要将 IIF 位清零此位在通信过程中十分重要无论接收还是发送数据前都需要将此位清零之后开始传输数据直到此位置位。RXAK(bit0)Received acknowledge应答信号标志位0 表示接收到 ACK 应答信号1 表示检 测到 NO ACK 信号4.4 I2Cx_I2DR 数据输入输出寄存器DATA发送时无论发送设备地址还是数据都将要发生的内容写到这个寄存器中当要发送的数据写入后I2C控制器就开始传输数据传输完毕后(收到ACK或NACK)就会使IIF(12Cx_I2SR[bit1])置位对于接收数据来说当从该寄存器读走一个字节后 I2C 控制器就开始传输数据直到IIF置位因此在读取过程中第一个读到的字节是无效的五. 使用I2C协议与AT24C02进行通信5.1 I2C 初始化代码IMX6ULL中与 I2C 相关的原理图1、可以看到 I2C1 的 SCL 与 SDL 分别接在了核心板的 UART4_TXD 与 UART4_RXD 上故在初始化时要对这两个引脚进行初始化2、注意可以看到在引脚复用时第二个参数传了“1”要求强制打开引脚的硬件输入通路SION 1SION0PAD 输入通路关闭I2C 外设无法采样外部真实引脚电平SION1强制打开 PAD 输入通路I2C 控制器可以采集总线电平识别 ACK、仲裁、起始 / 停止信号原因I2C 控制器硬件必须时刻采样引脚上真实物理电平例如主机发送完 8bit 数据释放 SDA等待从机拉低 SDA 产生 ACK 应答。 此时芯片内部 I2C 外设需要读取外部 PAD 电平判断从机有没有回复应答IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1); IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);3、在配置引脚的电气属性时要设置引脚上拉电阻4、在设置 I2C 的波特率时一定要先失能设置好波特率后再使能5、示例代码如下//初始化I2C void i2c_init(void) { //1、配置引脚复用 IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1); //UART4_RX引脚复用为I2C的数据引脚 IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1); //UART4_TX引脚复用为I2C的时钟引脚 //2、配置引脚电器属性 IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0xF0B0); //I2C一定要加上拉电阻保证空闲时两条线的电平为高电平 IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0xF0B0); //3、设置波特率 100KHz I2C1-I2CR ~(1 7); //先失能 I2C1-IFDR 0x15; //设置640分频接近660分频即可 I2C1-I2CR | (1 7); //使能 }5.2 I2C 主机写AT24C02示例代码1、流程图2、示例代码//I2C写函数 void i2c_write(I2C_Type *base, unsigned char dev_address, unsigned char reg_address, const unsigned char *data, int len) { //1、清除仲裁标志位 base-I2SR ~(1 IAL); //2、清除中断标志位 base-I2SR ~(1 IIF); //3、等待之前的数据传输完成 while( (base-I2SR (1 ICF)) 0 ); //4、主机通信前置位此位发送一个起始位 base-I2CR | (1 MSTA); //5、发送从机地址时此为总是置1发送写位 base-I2CR | (1 MTX); //6、等待从机ACK信号 base-I2CR ~(1 TXAK); //只用了这一次来等待从机的 ACK 当主机作为发送方时此为不生效 //7、清零 IIF 位 base-I2SR ~(1 IIF); //8、发送从机地址 base-I2DR dev_address 1; //9、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //10、清零 IIF 位 base-I2SR ~(1 IIF); //11、发送寄存器地址 base-I2DR reg_address; //12、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //13、发送 len 字节数据 while(len--) { //清零 IIF 位 base-I2SR ~(1 IIF); base-I2DR *data; //等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); } //14、发送结束标志 STOP base-I2CR ~(1 MSTA); //15、等待总线空闲 while( (base-I2SR (1 IBB)) ! 0 ) { delay_us(100); } }5.3 I2C主机读AT24C02示例代码1、流程图2、示例代码注意1、这里主机读取从机数据前要进行假读一次原因是读写时共用一个I2DR里面可能会有上一次写的残留数据2、在发送从机地址时若从机地址是0xA0则在传参时需要传递0x50因为从机地址是七位还有一位读写位的影响从机地址在函数中需要进行左移故要注意传参//I2C 读函数 void i2c_read(I2C_Type *base, unsigned char dev_address, unsigned char reg_address, unsigned char *data, int len) { //1、清除仲裁标志位此位一般多主机使用 base-I2SR ~(1 IAL); //2、清除中断标志位 base-I2SR ~(1 IIF); //3、等待之前的数据传输完成 while( (base-I2SR (1 ICF)) 0 ); //4、主机通信前置位此位发送一个起始位 base-I2CR | (1 MSTA); //5、主机模式写模式 base-I2CR | (1 MTX); //6、等待从机ACK信号表示去检测从机的 ACK 应答 base-I2CR ~(1 TXAK); //只用了这一次来等待从机的 ACK 当主机作为发送方时此为不生效 //7、清零 IIF 位 base-I2SR ~(1 IIF); //8、发送从机地址 一位写位 base-I2DR dev_address 1; //9、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //10、清零 IIF 位不管是接收到 ACK还是发送 ACK 都会引起 IIF 位置位故要清除此位 base-I2SR ~(1 IIF); //11、发送寄存器地址 base-I2DR reg_address; //12、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //13、主机重新发送起始位 base-I2CR | (1 RSTA); //14、清零 IIF 位 base-I2SR ~(1 IIF); //15、发送从机地址 一位读位 base-I2DR dev_address 1 | 1; //16、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //17、改变主机模式到读模式接收数据 base-I2CR ~(1 MTX); //18、清零 IIF 位 base-I2SR ~(1 IIF); //19、判断要读取的数据长度是否是1Y发送 NO ACK if(len 1) { //置位 TXAK 发送 NO ACK base-I2CR | (1 TXAK); } //20、假读一次读出上一次写进的残留数据 *data base-I2DR; //21、循环读取 while(len-- ! 0) { //等待主机回复 ACK表示主机读取到一字节数据 while( (base-I2SR (1 IIF)) 0 ); //清零 IIF 位 base-I2SR ~(1 IIF); if(len 0) { //清零 MSTA发送停止位 base-I2CR ~(1 MSTA); //清零 TXAK以后通信等待的是 ACK。修改为 ACK base-I2CR ~(1 TXAK); //等待总线空闲 while( (base-I2SR (1 IBB)) ! 0 ) { delay_us(100); } } else if(len 1) { //表示是最后一个字节数据 //置位 TXAK发送NO ACK base-I2CR | (1 TXAK); } //读取数据 *data base-I2DR; } }六. 使用I2C协议与 LM75A 进行通信6.1 LM75A 芯片简介LM75A是一款数字温度传感器采集的温度格式是双字节并且低七位无效如下图所示芯片引脚定义如下图所示6.2 LM75A 的主要参数传感器的参数工作电压2.7 - 5.5v之间故在与 IMX6ULL 进行通信时接3.3 v即可精度25 - 100℃±2℃55 - 125℃±3℃测量范围在精度是±2℃下-25 - 100℃在精度是±3℃下-55 - 125℃分辨率0.5 ℃最高位表示符号位6.3 LM75A 的设备地址芯片手册中对 LM75A 从机地址做了如下描述LM75A 在 I2C 总线上作为从设备运行因此 SCL 线是输入端LM75A 不会生成时钟信号而 SDA 线则是双向串行数据通路。根据 I2C 总线规范LM75A 具有 7 位的从设备地址。从机地址的四个最重要的位是直接固化在 LM75A 中的其值为“1001”。地址的三个最不重要的位分配给了引脚 A2 至 A0并通过将这些引脚连接到地低电平0或连接到 VS高电平1来设置。本次示例此芯片的地址为1001 000在传参时0x48 1 即可6.4 LM75A 的寄存器地址在芯片手册中可知温度寄存器的地址是0x00并且是只读温度寄存器产品ID寄存器6.5 LM75A 的时序就是 I2C 的读时序6.6 读取温度示例代码注意1、这里进行了浮点数的运行故在启动代码中需要增加使能FPU单元的函数不然无法进行浮点数的运算示例代码如下_enable_fpu: // 1. 设置CPACR寄存器使能FPU访问 mrc p15, 0, r0, c1, c0, 2 // 读取CPACR orr r0, r0, #(0xF 20) // 设置CP10和CP11为完全访问 mcr p15, 0, r0, c1, c0, 2 // 写回CPACR // 2. 使能FPU mov r0, #0x40000000 // 设置FPEXC的EN位 vmsr fpexc, r0 // 写入FPEXC // 3. 配置FPSCR mov r0, #0x00000000 // 清除所有标志位 vmsr fpscr, r0 // 写入FPSCR bx lr // 返回2、在移植的 stdio 中功能不全无法使用 “%f” 进行打印小数故这里要使用“定点数”的方法来打印小数温度//温度传感器采集数据 void get_lm75_temperature(void) { unsigned char buff[2] { 0 }; float f 0; short s 0; int k 0, n 0, m 0; i2c_read(I2C1, 0x48, 0, buff, 2); //MSB 原则先发高位后发低位 s | buff[0] 8; s | buff[1]; //LM75芯片规定Temperature Register 的第七位为无效数据故要右移七位 s 7; //乘分辨率0.5计算出最终温度 f 0.5 * s; //移植的stdio库功能不全无法打印浮点数故采用定点数的方法打印 k f * 10; n k / 10; //小数点左边的数 m k % 10; //小数点右边的数 //输出 printf(temperature is %d.%d\n,n,m); }结果
ARM---I2C协议时序详解以及IMX6ULL使用I2C协议与AT24C02,LM75A芯片通信详解与示例代码
目录一. I2C 简介1.1 I2C 介绍二. I2C 基本时序与基本知识2.1 起始位2.2 停止位2.3 数据传输2.4 应答信号 ACK2.5 对从机地址的规定三. I2C 主机与从机之间的通信时序3.1 写一个字节时序3.2 读一个字节时序3.3 写多个字节时序3.4 读多个字节时序四. IMX6ULL 中与 I2C 相关的寄存器4.1 I2Cx_IFDR 分频器寄存器4.2 I2Cx_I2CR I2C控制寄存器4.3 I2Cx_I2SR I2C状态寄存器4.4 I2Cx_I2DR 数据输入输出寄存器五. 使用I2C协议与AT24C02进行通信5.1 I2C 初始化代码5.2 I2C 主机写AT24C02示例代码1、流程图2、示例代码5.3 I2C主机读AT24C02示例代码1、流程图2、示例代码六. 使用I2C协议与 LM75A 进行通信6.1 LM75A 芯片简介6.2 LM75A 的主要参数6.3 LM75A 的设备地址6.4 LM75A 的寄存器地址6.5 LM75A 的时序6.6 读取温度示例代码I2C 是最常用的通信接口众多的传感器都会提供 I2C 接口来和主控相连比如陀螺仪、 加速度计、触摸屏、温度传感器等等。所以 I2C 是必须掌握的一. I2C 简介串行同步半双工1.1 I2C 介绍IICInter-Integrated Circuit又称I2C是是IICBus简称所以中文应该叫集成电路总线。是飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展而来的一种同步串行半双工通信总线方式。该总线允许同时连接多个设备(芯片)。每块芯片在总线上拥有特定的地址I2C 是很常见的一种总线协议I2C 是 NXP 公司设计的I2C 使用两条线在主机和从机之间进行数据通信。一条是SCL(Serial Clock)(串行时钟线)另外一条是SDA(Serial Data)(串行数据线)这两条数据线需要接上拉电阻目的是为了总线空闲的时候 SCL 和 SDA 处于高电平。I2C 总线标准模式下速度可以达到100Kbit/s快速模式下可以达到400Kbis/sI2C 是支持多从机的也就是一个 I2C 控制器下可以挂多个 I2C 从设备这些不同的 I2C 从设备有不同的器件地址这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C 设备了一个 I2C 总线连接多个 I2C 设备如图所示图中 SDA 和 SCL 这两根线必须要接一个上拉电阻范围是4.7K - 10K之间一般是4.7K。其余的 I2C 从 器件都挂接到 SDA 和 SCL 这两根线上这样就可以通过 SDA 和 SCL 这两根线来访问多个 I2C 设备二. I2C 基本时序与基本知识2.1 起始位也就是 I2C 通信起始标志通过这个起始位就可以告诉I2C 从机要开始进行 I2C 通信了。在 SCL 为高电平的时候SDA 出现下降沿就表示为起始位2.2 停止位停止位就是停止 I2C 通信的标志位和起始位的功能相反。在 SCL 位高电平的时候SDA 出现上升沿就表示为停止位2.3 数据传输发送数据MSB高位先行原则规定当时钟线 SCL 高电平时数据线 SDA 要保持稳定因为 I2C 接收方要采样数据当时钟线 SCL 为低电平时数据线 SDA 信号可改变I2C发送方发送数据2.4 应答信号 ACK当 I2C 主机发送完 8 位数据以后会将数据线 SDA 设置为输入状态等待 I2C 从机应答 ACK也就是等到 I2C 从机告诉主机它接收到数据了。应答信号是由从机发出的主机需要提供应答信号所需的时钟主机发送完 8 位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信号表示通信成功否则表示通信失败2.5 对从机地址的规定从机地址起始信号后的第一个字节发送即为从机地址总共八位2^8 256 --- 可得 I2C 总线上可挂载 256 个从机但是规定前七个bit作为从机地址在2^7 128 --- 总共可挂载 128 个从机最后一个 bit 位来表示数据的流向位0主机发给从机低电平写1从机发给主机高电平读总结七位从机地址最多可挂载2^7 128个从机但是I2C会空留出一个即127个从机九位从机地址最多2^9 - 1 个从机三. I2C 主机与从机之间的通信时序此节以 AT24C02 (EEPROM) 芯片为例来说明主机与从机使用 I2C 总线进行通信的时序关于AT24C0x系列芯片的说明AT24C02A、AT24C04A、AT24C08A 这些芯片都是EEPROMAT24C02A容量2K (256 x 8)注意这里的 2K 表示的是 bit而不是字节 字节 2 K / 8 256 字节AT24C04A容量4K (512 x 8)AT24C08A容量8K (1024 x 8)芯片图设备地址此芯片的 A2 - A0 默认接地故芯片地址为1010 0000 0xA0设备的寄存器地址由于使用的是 AT24C02A 容量为 256 字节所以内部的寄存器地址为0x00 - 0xFF可以对其进行单字节的写操作也可以对其进行 页 的写操作AT24C02A一页 八字节3.1 写一个字节时序主机通过 I2C 总线与从机之间进行通信的写时序1)、开始信号主机发送起始信号通信时起始信号总是由主机发起2)、发送 I2C 设备地址每个 I2C 器件都有一个设备地址通过发送具体的设备地址来决 定访问哪个 I2C 器件。这是一个 8 位的数据其中高 7 位是设备地址最后 1 位是读写位为 1 的话表示这是一个读操作为 0 的话表示这是一个写操作。也就是低电平写高电平读3)、读写位I2C 器件地址后面跟着一个读写位为 0 表示写操作为 1 表示读操作4)、ACK 应答从机向主机回复ACK应答信号5)、寄存器地址主机向从机发送要写入数据的寄存器地址6)、ACK 应答从机向主机回复ACK应答信号7)、主机向从机发送要写入寄存器的数据8)、ACK 应答从机向主机回复ACK应答信号9)、结束标志主机发送 STOP 结束标志3.2 读一个字节时序主机通过 I2C 总线与从机之间进行通信的读时序I2C 单字节读时序分为4 大步第一步是发送设备地址第二步是发送要读取的寄存器地址第三步重新发送设备地址最后一步就是 I2C 从器件输出要读取的寄存器值。1)、主机发送起始信号2)、主机发送要读取的I2C 从设备地址 一位写位低电平。3)、读写控制位因为是向 I2C 从设备发送数据因此是写信号---注意第一次发送的是写信号因为要先发送一次寄存器地址4)、从机向主机发送的 ACK 应答信号。5)、主机发送要读取的寄存器地址。6)、从机向主机发送的 ACK 应答信号。7)、主机重新发送 START 信号--- 注意这里主机会重新发送一次起始信号8)、重新发送要读取的I2C 从机设备地址 一位读位高电平9)、读写控制位这里是读信号表示接下来是从 I2C 从设备里面读取数据。10)、从机向主机发送的 ACK 应答信号。11)、主机从 I2C 从机器件里面读取数据。12)、主机向从机回复 NO ACK 信号表示读取完成不需要从机再发送数据了。13)、主机发出 STOP 信号停止 I2C 通信3.3 写多个字节时序I2C 多字节写时序与单字节的写时序差不多分为四大步第一步是发送设备地址与写位并等待从机的ACK应答第二步是发送要读取的寄存器地址并等待ACK应答第三步就是主机不断的向从机发送要写的数据并且等待从机的ACK应答最后主机向从机发送停止位 STOP 即可3.4 读多个字节时序I2C 多字节读时序与单字节读时序也差不多分为五大步第一步是发送7位的设备地址 一位写位低电平并且等待从机的 ACK 应答第二步是主机向从机发送要读取的寄存器地址并且等待从机的 ACK 应答第三步重新发送起始位并且重新发送7位的设备地址 一位读位并且等待从机的 ACK 应答第四步就是从机不断的向主机发送要读取的数据主机不断的向从机回复 ACK 应答第五步就是主机向从机回复 NO ACK 信号(高电平)表示读取完成不需要从机再发送 ACK 信号了并且主机发送一个结束位 STOP四. IMX6ULL 中与 I2C 相关的寄存器IMX6ULL 总共有 4 个 I2C 控制器IMX6ULL提供了 4 个 I2C 外设通过这四个 I2C 外设即可完成与 I2C 从器件进行通信IMX6ULL的 I2C 外设特性如下①、与标准 I2C 总线兼容。②、多主机运行③、软件可编程的 64 中不同的串行时钟序列。④、软件可选择的应答位。⑤、开始/结束信号生成和检测。⑥、重复开始信号生成。⑦、确认位生成。⑧、总线忙检测4.1 I2Cx_IFDR 分频器寄存器寄存器 I2Cx_IFDR 只有IC(bit5:0)这个位用来设置 I2C 的波特率I2C 的时钟源可以选择IPG_CLK_ROOT 66MHz通过设置 IC 位既可以得到想要的I2C 波特率IC 位可选的设置 如图所示不像其他外设的分频设置一样可以随意设置图中列出了 IC 的所有可选值。比如现在 I2C 的时钟源为 66MHz要设置 I2C 的波特率为100KHz(标准模式)那么 IC 就可以设置为0x15也就是 640 分频。66000000/640103.125KHz≈100KHz4.2 I2Cx_I2CR I2C控制寄存器IEN(bit7)I2C 使能位1 使能 I2C0 关闭 I2C设置波特率之前最好将该位清零设置好波特率之后再置1IIEN(bit6)I2C 中断使能位1 使能 I2C 中断0 关闭 I2C 中断MSTA(bit5)Master/Slave mode select bit主从模式选择位设置 IIC 工作在主模式还是从模式1 工作在主模式0 工作在从模式注意作为主机时启动通信前将此位置1之后会发送一个起始位。在发送过程中向此位清零会立即产生一个停止位MTX(bit4)Transmit/Receive mode select bit传输方向选择位用来设置是进行发送还是接收0 接收1 发送注意发送从机地址时此位总是1接收数据时此位为0修改此位并不会对数据流向位带来任何影响TXAK(bit3)Transmit acknowledge enable传输应答位使能0 发送 ACK 信号1 发送 NO ACK 信号关于这一位有两种说法-----------------------说法一------------------------两种情况主机作为接收方0发送ACK---主机还要继续接收请从机继续发送下一个字节1发送NO ACK---主机不再接收传输结束主机作为发送方时此位不生效-----------------------说法二------------------------在发送数据时表明是否检测应答接收数据时表明是否回应应答主机作为接收方0发送ACK1发送 NO ACK主机作为发送方0检测从机的ACK---注意这里只是检测并不阻塞1检测从机的NO ACKRSTA(bit2)Repeat start重复开始信号为 1 的话产生一个重新开始信号此位用来重发起始信号4.3 I2Cx_I2SR I2C状态寄存器ICF(bit7)Data transferring bit数据传输状态位0表示数据正在传输1表示数据传输完成用于判断数据是否正在传输只读用法等待之前的数据传输完成while( (I2C1-I2SR (1 ICF)) 0 );IAAS(bit6)1表示 I2C 地址也就是 I2Cx_IADR 寄存器中的地址是从设备地址0表示没有地址只读IBB(bit5)I2C bus busy bitI2C 总线忙标志位0 表示 I2C 总线空闲1 表示 I2C 总线忙注意发送完停止位以后该位清零启动传输前根据此位判断总线是否空闲完全由硬件自动置 1、自动清零并且是只读位IAL(bit4)Arbitration lost仲裁丢失位1表示发生仲裁丢失----一般用在多主机情况下0无仲裁丢失注意在启动通信前需要将 IAL 位清零SRW(bit2)Slave read/write从机读写状态位当 I2C 作为从机的时候使用此位用来表明主机发送给从机 的是读还是写命令0 表示主机要向从机写数据1 表示主机要从从机读取数据IIF(bit1)I2C interruptI2C 中断挂起标志位1 表示有中断挂起0无中断挂起此位需要软件清零注意在启动通信前需要将 IIF 位清零此位在通信过程中十分重要无论接收还是发送数据前都需要将此位清零之后开始传输数据直到此位置位。RXAK(bit0)Received acknowledge应答信号标志位0 表示接收到 ACK 应答信号1 表示检 测到 NO ACK 信号4.4 I2Cx_I2DR 数据输入输出寄存器DATA发送时无论发送设备地址还是数据都将要发生的内容写到这个寄存器中当要发送的数据写入后I2C控制器就开始传输数据传输完毕后(收到ACK或NACK)就会使IIF(12Cx_I2SR[bit1])置位对于接收数据来说当从该寄存器读走一个字节后 I2C 控制器就开始传输数据直到IIF置位因此在读取过程中第一个读到的字节是无效的五. 使用I2C协议与AT24C02进行通信5.1 I2C 初始化代码IMX6ULL中与 I2C 相关的原理图1、可以看到 I2C1 的 SCL 与 SDL 分别接在了核心板的 UART4_TXD 与 UART4_RXD 上故在初始化时要对这两个引脚进行初始化2、注意可以看到在引脚复用时第二个参数传了“1”要求强制打开引脚的硬件输入通路SION 1SION0PAD 输入通路关闭I2C 外设无法采样外部真实引脚电平SION1强制打开 PAD 输入通路I2C 控制器可以采集总线电平识别 ACK、仲裁、起始 / 停止信号原因I2C 控制器硬件必须时刻采样引脚上真实物理电平例如主机发送完 8bit 数据释放 SDA等待从机拉低 SDA 产生 ACK 应答。 此时芯片内部 I2C 外设需要读取外部 PAD 电平判断从机有没有回复应答IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1); IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);3、在配置引脚的电气属性时要设置引脚上拉电阻4、在设置 I2C 的波特率时一定要先失能设置好波特率后再使能5、示例代码如下//初始化I2C void i2c_init(void) { //1、配置引脚复用 IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1); //UART4_RX引脚复用为I2C的数据引脚 IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1); //UART4_TX引脚复用为I2C的时钟引脚 //2、配置引脚电器属性 IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0xF0B0); //I2C一定要加上拉电阻保证空闲时两条线的电平为高电平 IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0xF0B0); //3、设置波特率 100KHz I2C1-I2CR ~(1 7); //先失能 I2C1-IFDR 0x15; //设置640分频接近660分频即可 I2C1-I2CR | (1 7); //使能 }5.2 I2C 主机写AT24C02示例代码1、流程图2、示例代码//I2C写函数 void i2c_write(I2C_Type *base, unsigned char dev_address, unsigned char reg_address, const unsigned char *data, int len) { //1、清除仲裁标志位 base-I2SR ~(1 IAL); //2、清除中断标志位 base-I2SR ~(1 IIF); //3、等待之前的数据传输完成 while( (base-I2SR (1 ICF)) 0 ); //4、主机通信前置位此位发送一个起始位 base-I2CR | (1 MSTA); //5、发送从机地址时此为总是置1发送写位 base-I2CR | (1 MTX); //6、等待从机ACK信号 base-I2CR ~(1 TXAK); //只用了这一次来等待从机的 ACK 当主机作为发送方时此为不生效 //7、清零 IIF 位 base-I2SR ~(1 IIF); //8、发送从机地址 base-I2DR dev_address 1; //9、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //10、清零 IIF 位 base-I2SR ~(1 IIF); //11、发送寄存器地址 base-I2DR reg_address; //12、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //13、发送 len 字节数据 while(len--) { //清零 IIF 位 base-I2SR ~(1 IIF); base-I2DR *data; //等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); } //14、发送结束标志 STOP base-I2CR ~(1 MSTA); //15、等待总线空闲 while( (base-I2SR (1 IBB)) ! 0 ) { delay_us(100); } }5.3 I2C主机读AT24C02示例代码1、流程图2、示例代码注意1、这里主机读取从机数据前要进行假读一次原因是读写时共用一个I2DR里面可能会有上一次写的残留数据2、在发送从机地址时若从机地址是0xA0则在传参时需要传递0x50因为从机地址是七位还有一位读写位的影响从机地址在函数中需要进行左移故要注意传参//I2C 读函数 void i2c_read(I2C_Type *base, unsigned char dev_address, unsigned char reg_address, unsigned char *data, int len) { //1、清除仲裁标志位此位一般多主机使用 base-I2SR ~(1 IAL); //2、清除中断标志位 base-I2SR ~(1 IIF); //3、等待之前的数据传输完成 while( (base-I2SR (1 ICF)) 0 ); //4、主机通信前置位此位发送一个起始位 base-I2CR | (1 MSTA); //5、主机模式写模式 base-I2CR | (1 MTX); //6、等待从机ACK信号表示去检测从机的 ACK 应答 base-I2CR ~(1 TXAK); //只用了这一次来等待从机的 ACK 当主机作为发送方时此为不生效 //7、清零 IIF 位 base-I2SR ~(1 IIF); //8、发送从机地址 一位写位 base-I2DR dev_address 1; //9、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //10、清零 IIF 位不管是接收到 ACK还是发送 ACK 都会引起 IIF 位置位故要清除此位 base-I2SR ~(1 IIF); //11、发送寄存器地址 base-I2DR reg_address; //12、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //13、主机重新发送起始位 base-I2CR | (1 RSTA); //14、清零 IIF 位 base-I2SR ~(1 IIF); //15、发送从机地址 一位读位 base-I2DR dev_address 1 | 1; //16、等待从机回复 ACK while( (base-I2SR (1 IIF)) 0 ); //17、改变主机模式到读模式接收数据 base-I2CR ~(1 MTX); //18、清零 IIF 位 base-I2SR ~(1 IIF); //19、判断要读取的数据长度是否是1Y发送 NO ACK if(len 1) { //置位 TXAK 发送 NO ACK base-I2CR | (1 TXAK); } //20、假读一次读出上一次写进的残留数据 *data base-I2DR; //21、循环读取 while(len-- ! 0) { //等待主机回复 ACK表示主机读取到一字节数据 while( (base-I2SR (1 IIF)) 0 ); //清零 IIF 位 base-I2SR ~(1 IIF); if(len 0) { //清零 MSTA发送停止位 base-I2CR ~(1 MSTA); //清零 TXAK以后通信等待的是 ACK。修改为 ACK base-I2CR ~(1 TXAK); //等待总线空闲 while( (base-I2SR (1 IBB)) ! 0 ) { delay_us(100); } } else if(len 1) { //表示是最后一个字节数据 //置位 TXAK发送NO ACK base-I2CR | (1 TXAK); } //读取数据 *data base-I2DR; } }六. 使用I2C协议与 LM75A 进行通信6.1 LM75A 芯片简介LM75A是一款数字温度传感器采集的温度格式是双字节并且低七位无效如下图所示芯片引脚定义如下图所示6.2 LM75A 的主要参数传感器的参数工作电压2.7 - 5.5v之间故在与 IMX6ULL 进行通信时接3.3 v即可精度25 - 100℃±2℃55 - 125℃±3℃测量范围在精度是±2℃下-25 - 100℃在精度是±3℃下-55 - 125℃分辨率0.5 ℃最高位表示符号位6.3 LM75A 的设备地址芯片手册中对 LM75A 从机地址做了如下描述LM75A 在 I2C 总线上作为从设备运行因此 SCL 线是输入端LM75A 不会生成时钟信号而 SDA 线则是双向串行数据通路。根据 I2C 总线规范LM75A 具有 7 位的从设备地址。从机地址的四个最重要的位是直接固化在 LM75A 中的其值为“1001”。地址的三个最不重要的位分配给了引脚 A2 至 A0并通过将这些引脚连接到地低电平0或连接到 VS高电平1来设置。本次示例此芯片的地址为1001 000在传参时0x48 1 即可6.4 LM75A 的寄存器地址在芯片手册中可知温度寄存器的地址是0x00并且是只读温度寄存器产品ID寄存器6.5 LM75A 的时序就是 I2C 的读时序6.6 读取温度示例代码注意1、这里进行了浮点数的运行故在启动代码中需要增加使能FPU单元的函数不然无法进行浮点数的运算示例代码如下_enable_fpu: // 1. 设置CPACR寄存器使能FPU访问 mrc p15, 0, r0, c1, c0, 2 // 读取CPACR orr r0, r0, #(0xF 20) // 设置CP10和CP11为完全访问 mcr p15, 0, r0, c1, c0, 2 // 写回CPACR // 2. 使能FPU mov r0, #0x40000000 // 设置FPEXC的EN位 vmsr fpexc, r0 // 写入FPEXC // 3. 配置FPSCR mov r0, #0x00000000 // 清除所有标志位 vmsr fpscr, r0 // 写入FPSCR bx lr // 返回2、在移植的 stdio 中功能不全无法使用 “%f” 进行打印小数故这里要使用“定点数”的方法来打印小数温度//温度传感器采集数据 void get_lm75_temperature(void) { unsigned char buff[2] { 0 }; float f 0; short s 0; int k 0, n 0, m 0; i2c_read(I2C1, 0x48, 0, buff, 2); //MSB 原则先发高位后发低位 s | buff[0] 8; s | buff[1]; //LM75芯片规定Temperature Register 的第七位为无效数据故要右移七位 s 7; //乘分辨率0.5计算出最终温度 f 0.5 * s; //移植的stdio库功能不全无法打印浮点数故采用定点数的方法打印 k f * 10; n k / 10; //小数点左边的数 m k % 10; //小数点右边的数 //输出 printf(temperature is %d.%d\n,n,m); }结果