十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零开始学习嵌入式P45----I2C(上)

从零开始学习嵌入式P45----I2C(上) 从零开始学习嵌入式P45----I2C(上)串口可以让开发板和电脑交换字符I2C 则常用于板上芯片之间的通信。这一篇让 i.MX6ULL 的I2C1向一颗 EEPROM 写入六个字节。即使没有学过电路和通信时序也可以从“引脚为什么要上拉”一路读到“为什么向I2DR写一个字节后还要等待应答”。本文的例子使用i.MX6ULL-Mini 开发板把 UART4_TX_DATA 焊盘复用为 I2C1_SCL把 UART4_RX_DATA 焊盘复用为 I2C1_SDA外接一颗3.3 V 供电、7 位地址为0x50的 AT24C08A EEPROM。板上的 I2C1 两根线各有约 4.7 kΩ 的 3.3 V 上拉电阻外接模块若也带上拉要避免重复并联过多电阻。程序是裸机、主机模式、轮询传输不依赖操作系统也不启用 I2C 中断。本文给出完整的最小工程文件和预期总线字节无须先读其他文章才能理解或构建它。**接线先确认**开发板 I2C1_SCL 接 EEPROM SCLI2C1_SDA 接 EEPROM SDA双方 GND 相连EEPROM VCC 接与器件型号匹配的 3.3 VWP接 GND 才允许写入。本文按 AT24C08A 的A20、存储块 0 说明地址0x50A0/A1对这颗 8 Kbit 器件不参与硬件设备地址选择。板上有 I2C 上拉并不意味着板上一定装有地址0x50的 EEPROM。I2C 焊盘不能接到被 5 V 上拉的总线。一、I2C 究竟是什么I2CInter-Integrated Circuit也常写作 I²C有些工程文件把它写成 IIC是一种在短距离内让多颗芯片共享线路的串行总线。它只用两根信号线线名全称用途SCLSerial Clock给每一位数据提供时钟节拍SDASerial Data双向传送地址、数据和应答还要有共同的GND否则双方没有一致的电压参照。I2C 不是“只要两根线相连就能说话”双方还必须使用相容的供电和逻辑电平、上拉电阻、地址及总线速率。本例里 i.MX6ULL 是主机它发起通信、产生 SCL并选择要访问的从机。EEPROM 是从机只有看到自己的地址才回应。I2C 也允许总线上存在不止一个主机因此控制器有仲裁机制本例只接一个主机但程序仍检查“仲裁丢失”状态。I2C 是半双工的。SDA 可以向两个方向传但同一时刻只有一方负责当前数据位收发双方会在规定的时刻交换 SDA 的控制权。为什么必须是开漏和上拉普通推挽输出可以主动拉高也可以主动拉低。如果一颗芯片强推高电平另一颗芯片同时强拉低电平就会发生输出冲突。I2C 改用开漏输出芯片只能主动把线拉低要表达高电平就释放这根线由外部上拉电阻把它拉到 3.3 V。3.3 V │ 上拉电阻 │ SDA ────────────┼──────── i.MX6ULL SDA 输入/开漏输出 └──────── EEPROM SDA 输入/开漏输出 没人拉低SDA 1 任一设备拉低SDA 0同样的连接方式也用于 SCL。这种连接使“任意设备拉低”优先所以常称为线与。它还能支持从机把 SCL 暂时保持为低电平让主机放慢速度这叫时钟延展。上拉电阻并非越小越好太小会增大低电平时的电流太大又会让上升沿过慢。本文开发板上的 4.7 kΩ 是这个 3.3 V、短线实验的现成配置。**开漏与“把寄存器写成 1”不是一回事。**焊盘要配置开漏功能硬件在需要高电平时才会真正释放线路。本例后面的焊盘值是0x18B0其中 bit11ODE1。常见的0x10B0少了这一位不能把它照搬为规范的 I2C 焊盘设置。二、看懂一笔 I2C 传输总线空闲时上拉使 SCL、SDA 都为高电平。主机开始传输时在SCL 为高的时候让 SDA 从高变低形成START。结束时在SCL 为高的时候让 SDA 从低变高形成STOP。在普通数据位期间发送方主要在 SCL 低时改变 SDA接收方在 SCL 高时识别其值这样就不会把数据变化误判成 START 或 STOP。空闲高电平 ── START ── 8 个数据位 ── 第 9 个时钟ACK/NACK ── … ── STOP │ MSB 先发 │ SDA 高→低、SCL 高 接收方回应每发送8 位发送方要释放 SDA接收方在第 9 个时钟给出回应ACK 0接收方把 SDA 拉低表示这一字节已接收。NACK 1接收方不拉低 SDA。可能是没有对应设备、设备暂时不能接收或接收方有意结束读取。“数据字节发送完成”与“对方真的确认接收”是两件事。控制器的完成标志告诉软件第 9 个时钟结束了还要检查RXAK才知道对方回的是 ACK 还是 NACK。地址不是寄存器地址常用的 I2C7 位设备地址用于选中某颗芯片。传到总线上的第一个字节则是这 7 位左移一位再把最低位填成方向位0表示主机写1表示主机读。设备 7 位地址0x50 1010000₂ 写方向字节 (0x50 1) | 0 0xA0 10100000₂ 读方向字节 (0x50 1) | 1 0xA1 10100001₂因此代码传入0x50写进数据寄存器的第一个字节却是0xA0它们不是两个不同的设备地址。地址字节之后发出的0x00是EEPROM 内部存储位置也叫字地址含义与 7 位设备地址不同。这次写入的完整顺序是START → 0xA0设备地址 0x50 写方向 → EEPROM ACK → 0x00从存储块 0 的字地址 0 开始→ EEPROM ACK → 1 2 3 4 5 6 → 每字节后 EEPROM ACK → STOP字符1在总线上不是数值1而是 ASCII0x31。所以六个数据字节依次是31 32 33 34 35 36十六进制。若用逻辑分析仪查看解码结果应接近START A0 ACK 00 ACK 31 ACK … 36 ACK STOP。EEPROM 为什么不能无限连续写AT24C08A 是 8 Kbit即1024 字节的 EEPROM。它按 256 字节分为 4 个存储块块号占用设备地址中的两个位块内位置由后面的 8 位字地址给出。本文只写块 0 的0x000x05所以0x50加0x00足够定位。它一次页写最多容纳 16 个数据字节并且必须留在同一 16 字节页内。地址从页尾继续写时会回绕到同一页开头覆盖前面的数据而不会自动进入下一页。本例从0x00写 6 字节没有跨页。STOP 之后 EEPROM 还要进行内部非易失写入最长可到约10 ms总线 ACK 只表示字节被接受不能把它当成数据已经落入存储单元的证明。为避免反复消耗擦写寿命完整程序只执行一次写入。三、i.MX6ULL I2C1 从焊盘到寄存器同一个芯片焊盘可以承担多种功能。名称里虽然写着UART4选择ALT2后它们在本例承担的是 I2C1 功能与 UART4 通信无关。项目本例配置为什么需要UART4_TX_DATA 焊盘ALT2 SION→ I2C1_SCL把物理引脚接到控制器并始终打开输入路径UART4_RX_DATA 焊盘ALT2 SION→ I2C1_SDASDA 需要双向输入、输出SCL 输入选择0x020E05A4 1在多个可选 SCL 焊盘中选 UART4_TX_DATASDA 输入选择0x020E05A8 2在多个可选 SDA 焊盘中选 UART4_RX_DATA两个焊盘电气配置0x18B0在0x10B0的基础上设置ODE开漏位I2C1 时钟门控CCM_CCGR2[CG3] 11₂允许 I2C1 工作SION是Software Input On即使该复用功能看似在“输出”也强制打开焊盘输入路径让控制器能观察总线真实电平。I2C 要判断线是否被其他设备拉低所以两个焊盘都要保留输入路径。SION位是复用寄存器 bit4ALT2是低位的2合起来写0x12。1. 五个 16 位寄存器i.MX6ULL 的 I2C 外设寄存器要求按16 位半字访问。I2C1 的基址是0x021A0000本例用到的寄存器如下寄存器地址偏移作用IADR0x00本控制器作为从机时响应的地址本例是主机不用它选择 EEPROMIFDR0x04设置 SCL 分频I2CR0x08使能、主从选择、收发方向、START/STOPI2SR0x0C完成、总线忙、仲裁丢失、ACK 状态I2DR0x10写入要发的字节或读出接收的字节注意IADR不是 EEPROM 设备地址寄存器。主机要访问谁是把“7 位地址 读写位”作为普通的第一个发送字节写入I2DR。I2CR的关键位位名称本文里的含义bit7IEN1使能 I2C 控制器bit6IIEN1开硬件中断本例始终为0bit5MSTA0→1产生 START1→0产生 STOPbit4MTX1发送模式0接收模式bit3TXAK主机接收数据时回复 ACK 或 NACK写入实验不需要改它bit2RSTA不先 STOP直接产生重复 START本次写入不需要I2SR的关键位位名称程序如何使用bit7ICF一个字节及其应答时钟已经完成bit5IBB总线忙START 后为 1STOP 后回到 0bit4IAL仲裁丢失置位后不能继续假装自己是主机bit1IIF本次字节完成等事件发生即使不开硬件中断也会置位bit0RXAK读到0是 ACK读到1是 NACKIIF名字里有“中断”并不表示一定要写中断处理函数。只要IIEN0我们就可以在主循环轮询它。IIF、IAL都是写 0 清除不应以为“写 1 清除”。2. 时钟频率怎么确定I2C1 的传输节拍来自PERCLK不是 CPU 主频。为了让示例无需猜测前级时钟树状态初始化时把 PERCLK 的来源明确选为开发板的24 MHz 晶振分频字段设为 0实际除以 1。再将IFDR0x0F对应的 I2C 分频系数是240PERCLK 24 MHz / (0 1) 24 MHz 名义 SCL 24 MHz / 240 100 kHz焊盘和上拉使边沿需要一定时间真实线上测得的周期可能比这个名义值略长。100 kHz 与本例 3.3 V AT24C08A 的标准模式相匹配。另一种时钟组合是 PERCLK 为 66 MHz、IFDR0x15而0x15在分频表里代表÷640名义 SCL 约为103.1 kHz。IFDR字段不是直接写频率也不是把十六进制0x15当成“除以 21”。对于最高 100 kHz 的 3.3 V 器件本文不沿用那组稍高的名义速率。时钟来源与 IFDR 必须成套设置。四、把写入过程翻译成程序一笔主机写入可以按“每写一个字节就等待一次”来理解/* 核心顺序省略错误处理细节 */I2CRIEN|MTX|MSTA;/* 发送模式产生 START */I2DR(0x50U1)|0U;/* 0xA0选中 EEPROM方向为写 */wait_for_byte_and_ack();I2DR0x00U;/* EEPROM 块内字地址 */wait_for_byte_and_ack();I2DR1;/* 后续每个数据字节也一样 */wait_for_byte_and_ack();I2CRIEN;/* 清 MSTA产生 STOP */第一段让控制器掌握总线并送出 START第二段才真正开始发送地址字节。每次写I2DR后控制器负责把 8 位数据及第 9 个 ACK 时钟放到 SCL/SDA 上软件等IIF变为 1再检查ICF与RXAK。如果没有 EEPROM地址字节通常就会得到 NACK后续数据不能继续发。原型驱动常写成while ((I2SR IIF) 0) {}。线路断开、SCL 被外设一直拉低时它可能永远出不来。原型函数若返回void主程序也不知道是地址 NACK、数据 NACK 还是等待超时。完整示例改为有限轮询并返回错误码同时在失败时尝试释放总线。五、完整可构建的最小示例把下面四个文件放在同一个目录。代码使用寄存器物理地址假定前级启动程序已经完成 DDR 初始化并把裸机镜像装载到 DDR0x87800000后跳转到该地址CPU 在可访问外设物理地址的特权状态运行。本例不负责 i.MX6ULL 从 SD 卡上电所需的 IVT、DCD 和 DDR 初始化封装。所用启动地址、交叉编译器和装载条件都在这里写明代码本身不需要额外 SDK 头文件。1.main.c寄存器、初始化与写入#includestdint.h#includestddef.h#defineREG32(a)(*(volatileuint32_t*)(uintptr_t)(a))#defineREG16(a)(*(volatileuint16_t*)(uintptr_t)(a))#defineCCM_CSCMR1REG32(0x020C401CU)#defineCCM_CCGR1REG32(0x020C406CU)#defineCCM_CCGR2REG32(0x020C4070U)#defineMUX_UART4_TXREG32(0x020E00B4U)#defineMUX_UART4_RXREG32(0x020E00B8U)#definePAD_UART4_TXREG32(0x020E0340U)#definePAD_UART4_RXREG32(0x020E0344U)#defineSEL_I2C1_SCLREG32(0x020E05A4U)#defineSEL_I2C1_SDAREG32(0x020E05A8U)#defineI2C1_IFDRREG16(0x021A0004U)#defineI2C1_I2CRREG16(0x021A0008U)#defineI2C1_I2SRREG16(0x021A000CU)#defineI2C1_I2DRREG16(0x021A0010U)#defineIEN(1U7)#defineMSTA(1U5)#defineMTX(1U4)#defineICF(1U7)#defineIBB(1U5)#defineIAL(1U4)#defineIIF(1U1)#defineRXAK(1U0)#defineGPIO1_DRREG32(0x0209C000U)#defineGPIO1_GDIRREG32(0x0209C004U)#defineMUX_LEDREG32(0x020E0068U)#definePAD_LEDREG32(0x020E02F4U)enum{I2C_OK0,I2C_BUSY-1,I2C_TIMEOUT-2,I2C_NACK-3,I2C_ARBITRATION_LOST-4,I2C_BAD_LENGTH-5};/* 有限轮询防止硬件或接线异常时永远卡住。不是精确的毫秒计时器。 */staticintwait_bus_idle(void){for(uint32_tn0;n200000U;n){if((I2C1_I2SRIBB)0U)returnI2C_OK;}returnI2C_BUSY;}/* 写 I2DR 后调用等待一个字节传完并确认接收方应答。 */staticintwait_byte_ack(void){uint16_tstatus;for(uint32_tn0;n200000U;n){statusI2C1_I2SR;if((statusIAL)!0U){I2C1_I2SR0U;/* 写 0 清 IAL/IIF */returnI2C_ARBITRATION_LOST;}if((statusIIF)!0U){I2C1_I2SR0U;/* 为下一字节清除旧 IIF */if((statusICF)0U)returnI2C_TIMEOUT;if((statusRXAK)!0U)returnI2C_NACK;returnI2C_OK;}}returnI2C_TIMEOUT;}staticvoidi2c1_init(void){/* 本例尚未启动其他 PERCLK 外设选择 24 MHz 晶振分频为 1。 */CCM_CCGR2~(3U6);/* 临时关闭 I2C1 时钟门 */CCM_CSCMR1(CCM_CSCMR1~0x7FU)|(1U6);CCM_CCGR2|(3U6);/* CCGR2[CG3] 11 */MUX_UART4_TX0x12U;/* ALT2 SIONSCL */MUX_UART4_RX0x12U;/* ALT2 SIONSDA */SEL_I2C1_SCL1U;/* 输入来自 UART4_TX_DATA */SEL_I2C1_SDA2U;/* 输入来自 UART4_RX_DATA */PAD_UART4_TX0x18B0U;/* ODE1开漏 */PAD_UART4_RX0x18B0U;I2C1_I2CR0U;/* 停止控制器内部软件复位 */I2C1_IFDR0x0FU;/* 24 MHz / 240 100 kHz */I2C1_I2SR0U;/* 清旧 IIF、IAL */I2C1_I2CRIEN;/* 使能仍是空闲从机态 */}/* 只写 AT24C08A 存储块 0 中的一页本例从字地址 0 开始。 */staticinteeprom_write_page0(uint8_tword_addr,constuint8_t*data,size_tlength){intresult;if(dataNULL||length0U||length(16U-(word_addr0x0FU)))returnI2C_BAD_LENGTH;/* 不允许跨 16 字节页 */resultwait_bus_idle();if(result!I2C_OK)returnresult;I2C1_I2SR0U;/* 清旧标志 */I2C1_I2CRIEN|MTX|MSTA;/* START、主机发送 */I2C1_I2DR(uint16_t)(0x50U1);/* 7 位地址 0x50 写位 0 */resultwait_byte_ack();if(result!I2C_OK)gotostop;I2C1_I2DRword_addr;/* 块内字地址 */resultwait_byte_ack();if(result!I2C_OK)gotostop;for(size_ti0;ilength;i){I2C1_I2DRdata[i];resultwait_byte_ack();if(result!I2C_OK)gotostop;}stop:if(resultI2C_ARBITRATION_LOST){/* 已失去主机资格不能再假定清 MSTA 能产生本机 STOP。 */I2C1_I2CR0U;I2C1_I2SR0U;I2C1_I2CRIEN;returnresult;}I2C1_I2CRIEN;/* 清 MSTA产生 STOP */if(wait_bus_idle()!I2C_OKresultI2C_OK)resultI2C_BUSY;returnresult;}/* Mini 板 LED 接 GPIO1_IO03低电平点亮。 */staticvoidled_init(void){CCM_CCGR1|(3U26);/* GPIO1 时钟门 */MUX_LED5U;/* GPIO 模式 */PAD_LED0x10B0U;/* LED 不需要开漏 */GPIO1_DR|(1U3);/* 默认熄灭 */GPIO1_GDIR|(1U3);/* 输出 */}intmain(void){staticconstuint8_tmessage[]{1,2,3,4,5,6};intresult;led_init();i2c1_init();resulteeprom_write_page0(0x00U,message,sizeofmessage);if(resultI2C_OK)GPIO1_DR~(1U3);/* 全部字节 ACK 后LED 点亮 */for(;;);/* 只写一次不循环磨损 EEPROM */}这个文件前半段用REG16访问 I2C 控制器用REG32访问 CCM、IOMUXC、GPIO。volatile使每次硬件寄存器读写都保留下来如果去掉它编译器可能把“反复看 IIF 有没有变”优化成一次读取。它并不能代替超时、ACK 检查或总线仲裁。i2c1_init()先锁定时钟与焊盘再停用控制器、设分频、清旧标志最后打开IEN。这里选择晶振作为 PERCLK会影响所有使用 PERCLK 的模块所以这一设置只适用于本篇这种尚未启用其他 PERCLK 外设的独立程序。若它被放进已有系统必须统一管理整条 PERCLK 时钟路径。wait_byte_ack()先看IAL因为仲裁丢失时即使出现IIF也不能按“成功传完一个字节”处理。正常完成后它用保存下来的同一份status检查ICF和RXAK随后把IIF清零让下一字节的完成事件不会与上一字节混淆。这里的轮询次数是一个有界防卡死措施不是保证任何 CPU 频率下恰好等待多少毫秒如果总线被物理故障拉住软件返回错误也无法凭空把电平拉高。eeprom_write_page0()先检查数据长度保证不会在同一次传输里越过 16 字节页尾。然后按地址、字地址、数据的顺序写I2DR每一字节之后都等待 ACK。错误路径尽量产生 STOP若仲裁已经丢失则复位本机 I2C 控制器而不冒充仍然占有总线。函数返回0表示这次传输的字节被应答且 STOP 后总线空闲不代表 EEPROM 内部 10 ms 写入周期已经结束。LED 只是一个简单的成功信号全程 ACK 时点亮。LED 不亮可能是没有接 EEPROM、设备地址或WP不对、上拉/供电/接线错误、超时或仲裁丢失它本身不能把这些错误逐一显示。若使用逻辑分析仪观察 SDA/SCL 的字节和 ACK 最直接。2.start.S进入 C 程序前准备栈和 BSS.syntax unified .cpu cortex-a7 .arm .section .text.startup, ax, %progbits .balign 32 .global _start _start: ldr pc, reset_handler b fault_handler b fault_handler b fault_handler b fault_handler nop b fault_handler b fault_handler reset_handler: cpsid if cps #0x1f ldr sp, 0x84000000 ldr r0, __bss_start ldr r1, __bss_end mov r2, #0 1: cmp r0, r1 bhs 2f str r2, [r0], #4 b 1b 2: bl main 3: b 3b fault_handler: b fault_handler .ltorg .section .note.GNU-stack, , %progbits最前面的 8 个槽位按 ARM 异常向量的常见布局预留。本文不启用 IRQ因此先屏蔽 IRQ/FIQ切到 SYS 模式后设置栈指针再把未初始化的静态数据区 BSS 清零。message在只读数据区局部变量和函数调用仍需要栈。这里没有把VBAR显式指向向量表所以这些预留槽位不能当成已经配置完毕的完整异常处理系统正常路径不依赖它们。3.imx6ull.lds把代码链接到约定的装载地址ENTRY(_start) SECTIONS { . 0x87800000; .text : { KEEP(obj/start.o(.text.startup)) *(.text .text.*) } .rodata ALIGN(4) : { *(.rodata .rodata.*) } .ARM.extab ALIGN(4) : { *(.ARM.extab*) } .ARM.exidx ALIGN(4) : { *(.ARM.exidx*) } .data ALIGN(4) : { *(.data .data.*) } .bss ALIGN(4) (NOLOAD) : { __bss_start .; *(.bss .bss.*) *(COMMON) . ALIGN(4); __bss_end .; } }链接地址0x87800000必须与前级装载程序实际放置镜像的位置一致。NOLOAD表示 BSS 不占原始镜像内容交给start.S在运行前清零。更换装载位置时必须连同链接地址一起修改不能只改其中一边。4.Makefile编译并生成原始二进制.DEFAULT_GOAL : all CROSS_COMPILE ? arm-linux-gnueabihf- CC : $(CROSS_COMPILE)gcc OBJCOPY : $(CROSS_COMPILE)objcopy OBJDUMP : $(CROSS_COMPILE)objdump TARGET : i2c FLAGS : -stdc11 -Wall -Wextra -O0 -g -mcpucortex-a7 -marm \ -mfloat-abisoft -ffreestanding -fno-builtin \ -fno-stack-protector -fno-pie \ -fno-unwind-tables -fno-asynchronous-unwind-tables .PHONY: all clean all: $(TARGET).bin $(TARGET).dis obj: mkdir -p obj obj/start.o: start.S | obj $(CC) $(filter-out -stdc11,$(FLAGS)) -c $ -o $ obj/main.o: main.c | obj $(CC) $(FLAGS) -c $ -o $ $(TARGET).elf: obj/start.o obj/main.o imx6ull.lds $(CC) $(FLAGS) -static -nostdlib -Wl,-T,imx6ull.lds \ -Wl,-Map,$(TARGET).map -o $ obj/start.o obj/main.o -lgcc $(TARGET).bin: $(TARGET).elf $(OBJCOPY) -O binary $ $ $(TARGET).dis: $(TARGET).elf $(OBJDUMP) -D $ $ clean: rm -f obj/start.o obj/main.o $(TARGET).elf $(TARGET).bin \ $(TARGET).dis $(TARGET).map在安装 GNU ARM 交叉编译器与 GNU Make 的环境中执行make会得到i2c.bin、i2c.elf和反汇编文件i2c.dis。如果工具链前缀是arm-none-eabi-可用make CROSS_COMPILEarm-none-eabi-。i2c.bin是裸程序镜像不是包含 i.MX6ULL 上电引导头和 DDR 初始化信息的完整 SD 启动镜像执行它的前提已经在本节开头说明。六、如何判断结果与定位问题启动后程序尝试写入一次。若从地址、字地址到六个数据字节都得到 ACK且 STOP 后总线恢复空闲板上 LED 点亮。若 EEPROM 未连接LED 应保持熄灭这只是状态提示不能证明掉电后数据已保存。EEPROM 在 STOP 后最多仍可能需要约 10 ms 完成内部写入若要立即发起下一笔读/写应等待其完成或用设备地址反复探测 ACK。现象首先检查SCL、SDA 一直高LED 不亮I2C1 焊盘复用、输入选择、CCGR2 门控、程序是否真的运行发出0xA0后看到 NACKEEPROM 是否上电、GND 是否共地、A2 是否为 0、是否选对 7 位地址以及 EEPROM 是否仍在内部写入周期SDA/SCL 一直低是否短路、错误的 5 V/3.3 V 上拉、某个设备持续拉低、焊盘开漏设置地址 ACK、数据阶段 NACK线路与供电是否稳定、外设是否确实按这套写入时序工作页边界问题通常表现为内容回绕并非 NACK分析仪显示速率与计算不符PERCLK 是否真来自 24 MHzIFDR是否为0x0F上拉与线路电容是否使上升沿过慢LED 亮但随后读出的内容不对EEPROM 内部写入是否完成、WP是否为低电平、是否读同一存储块和字地址、是否跨页回绕WP禁止写入时仍可能应答总线字节读 EEPROM 时常见的“指定地址读”会先发START → 0xA0 → 字地址不发 STOP而是发重复 START接着发0xA1然后切换主机接收方向。最后一个数据字节后由主机发送 NACK再发 STOP。这说明0xA0和0xA1只是同一颗设备的写/读方向字节也说明RSTA、MTX、TXAK在读路径中各有用途本文写入程序只需要前一种方向。当你看到I2DR0xA0、随后IIF1且RXAK0可以把它直接翻译成一句话**主机把“我要写地址为 0x50 的设备”送上总线对方在第 9 个时钟回应了 ACK。**后面的字地址和六个数据字节重复同样的“发送—等待—检查”过程最后清除MSTA才构成完整的一次 I2C 写入。
返回列表