ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

IIC——EEPROM的多字节读写驱动

2026/9/1 16:18:17 拓冰建站 浏览量
IIC——EEPROM的多字节读写驱动 IIC——EEPROM的多字节读写驱动摘要本文在 24LC04 单字节 EEPROM 驱动基础上讲解 EEPROM 页写、顺序多字节读原理针对 UART 与 IIC 速率不匹配问题引入 FIFO 做跨模块数据缓冲将控制模块拆分为 ctrl_w 写控制、ctrl_r 读控制实现多字节页写与顺序多字节读包含仿真、串口上板实测、逻辑抓包重点说明页回卷、FIFO 一拍延迟、多字节读 ACK/NACK、顺序读无地址循环等工程坑点。## 一、前置知识1.1 EEPROM读写模式回顾EEPROM常见工作模式单字节写、页多字节写、当前地址读、随机地址读、顺序多字节读。上一篇博客实现了24LC04的单字节读写驱动单字节写每写入1字节就要等待一次3‑5ms芯片内部烧写周期批量写入时效率很低。本篇实现多字节页写、顺序多字节读提升批量数据读写效率。24LC04硬件页参数页大小为16字节。页回卷坑点当写入数据跨越页边界时EEPROM硬件会发生地址回卷新数据会覆盖当前页起始位置上层业务逻辑需要做地址边界判断避免页回卷导致数据错乱。多字节页写时序起始S → 从机写地址(7bit写0) → ACK → 存储子地址 → ACK → 数据1‑ACK → 数据2‑ACK …… → 数据N‑ACK → Stop关键点只有发送Stop停止信号之后EEPROM才会执行一次3‑5ms内部编程整批多字节只需要一次烧写周期。顺序多字节读时序执行随机地址读前置操作起始S从机写地址ACK目标存储地址ACK重复起始Sr发送从机读地址ACK每读取1个字节主机回复ACK通知从机继续输出下一字节读到最后1字节主机输出NACK发送Stop结束读操作。1.2 为什么引入FIFO缓冲工程存在跨模块速率不匹配问题UART上位机串口波特率慢批量数据逐字节缓慢下发IIC主机需要连续收发多字节单字节版本只用普通寄存器只能保存1字节无法缓存批量数据。解决方案写方向FIFO缓存UART下发的多字节待写数据IIC写完1字节之后从FIFO取出下一个字节继续发送读方向FIFO缓存IIC从EEPROM读出的多字节IIC通信结束后再逐字节送给tx模块回传给PCFIFO实现UART与IIC时序解耦屏蔽两边速率差异。1.3 复用自定义通信帧格式沿用之前串口指令帧格式0xFE worr sendnum recvnum addr [data1,data2…datan] 0xEE0xFE帧头worr0写 /1读sendnumIIC主机发送总字节数器件地址存储地址N个写数据recvnumIIC主机接收总字节数多字节读时代表读取字节数量addrEEPROM操作起始地址data1~datan多组待写入数据多字节写时0xEE帧尾二、模块划分对比单字节版本底层iic、rx、tx模块逻辑完全不变新增FIFO控制模块拆分为写控制ctrl_w、读控制ctrl_r两个独立模块。整体模块架构rx模块接收上位机串口帧输出data_rx[7:0]并行字节、done_rx单字节有效脉冲。ctrl_w写控制模块帧解析状态机解析帧头、读写标志、收发字节数、起始地址多字节写时串口收到的批量数据写入写FIFO检测帧尾后启动IIC传输IIC每完成1字节写done_w就从FIFO读出下一个字节送给IIC模块。iic模块复用上一篇IIC主机代码支持起始、停止、重复起始Sr、应答检测。ctrl_r读控制模块IIC每读出1字节done_r把数据写入读FIFOIIC整组通信结束done_iicFIFO内缓存的数据依次读出输出给tx模块。tx模块接收ctrl_r输出数据串行发送回PC串口助手。top顶层模块完成全部子模块、FIFO IP核的例化与信号互联。模块接口简要说明ctrl_w写控制模块输入clk、rst_n、rx输出data_rx/done_rxiic返回握手信号done_iic、done_w输出start_iicIIC启动信号、worr读写方向、sendnum发送字节数、recvnum接收字节数、data_i_iic送给IIC的待发送数据内部8bit×256同步FIFO缓存串口下发的批量写数据ctrl_r读控制模块输入clk、rst_n、iic读出数据data_o_iic、done_r单字节读完成、done_txtx发送握手、done_iicIIC整组通信结束输出data_out[7:0]给到tx模块、rd_done数据有效脉冲内部8bit×256同步FIFO缓存EEPROM读取的批量数据FIFO配置说明使用Quartus同步FIFO IP核位宽8bit深度256aclr高电平异步清零。三、代码实现说明iic、rx、tx模块与单字节工程保持一致本篇仅展示新增/修改的控制模块FIFO为工程IP核。ctrl_w 写控制模块//接收rx的并行数据单字节一个一个的来所以在接收的过程中以done_rx信号作为条件 //可以用状态机来划分各节点接收信号 //rx:fe worr send recv addr data ee/传输的帧结构 //引入fifo匹配速率 module ctrl_w( input wire clk , input wire rst_n , input wire [7:0] data_rx ,//rx发送过来的单个指令 input wire done_rx ,//数据有效信号 input wire done_iic ,//数据更新结束 input wire done_w ,//用于区分地址和数据 input wire wrong ,//报警信号,清空fifo output wire start_iic ,//iic的协议开始信号 output wire worr ,//读写方向信号 output wire [7:0] sendnum ,//写数据量 output wire [7:0] recvnum ,//读数据量 output wire [7:0] data_i_iic //写入的地址数据 ); //参数、因为变化条件都是由done_rx来控制不需要额外的时间参数iic的速率远远大于rx体现为写慢读快 //状态定义 localparam IDLE 1 , WORR 2 , SENDNUM 3 , RECVNUM 4 , ADDR 5 , DATA 6 , STOP 7 ; reg [3:0] state_c,state_n; //状态转移 wire IDLE_WORR , WORR_SENDNUM , SENDNUM_RECVNUM , RECVNUM_ADDR , ADDR_DATA , ADDR_STOP , DATA_STOP , STOP_IDLE ; //内部信号寄存信号在状态机内扮演接收data_rx的角色 reg worr_reg ; reg [7:0] sendnum_reg ; reg [7:0] recvnum_reg ; // reg [7:0] data_reg ; reg [7:0] addr_reg ; reg [7:0] data_temp; //fifo8*256 //写 reg wr_en ;//写使能 reg [7:0] data_fifo ;//fifo的输入数据 //读 reg rd_en ;//读使能 wire [7:0] q ;//读出的数据,和读使能相差一周期 reg rd_en_r;//与q对齐 //握手信号 wire empty ;//空信号 wire full ;//满信号 wire [7:0] usedw ;//数据量 //状态机第一段 always (posedge clk or negedge rst_n) begin if (rst_n 0) begin state_c IDLE; end else begin state_c state_n; end end //状态机第二段 always (*) begin if (rst_n 0) begin state_n IDLE; end else begin case (state_c) IDLE : state_n (IDLE_WORR ) ? WORR : state_c ; WORR : state_n (WORR_SENDNUM ) ? SENDNUM : state_c ; SENDNUM : state_n (SENDNUM_RECVNUM ) ? RECVNUM : state_c ; RECVNUM : state_n (RECVNUM_ADDR ) ? ADDR : state_c ; ADDR : state_n (ADDR_STOP ) ? STOP :(ADDR_DATA) ? DATA : state_c ; DATA : state_n (DATA_STOP ) ? STOP : state_c ; STOP : state_n (STOP_IDLE ) ? IDLE : state_c ; default : state_n (IDLE); endcase end end //状态转移条件 assign IDLE_WORR (state_c IDLE ) done_rx data_rx 8hfe; assign WORR_SENDNUM (state_c WORR ) done_rx; assign SENDNUM_RECVNUM (state_c SENDNUM ) done_rx; assign RECVNUM_ADDR (state_c RECVNUM ) done_rx; assign ADDR_DATA (state_c ADDR ) done_rx; assign ADDR_STOP (state_c ADDR ) done_rx data_rx 8hee; assign DATA_STOP (state_c DATA ) done_rx data_rx 8hee;//检测到包尾就停止fifo的写入跳出状态 assign STOP_IDLE (state_c STOP ) done_iic; //状态机第三段 always (posedge clk or negedge rst_n) begin if (rst_n 0) begin worr_reg 0; sendnum_reg 0; recvnum_reg 0; addr_reg 0; data_fifo 0; wr_en 0; end else begin case (state_c) IDLE :begin worr_reg 0; sendnum_reg 0; recvnum_reg 0; addr_reg 0; end WORR : worr_reg done_rx ? data_rx : worr_reg ; SENDNUM : sendnum_reg done_rx ? data_rx : sendnum_reg; RECVNUM : recvnum_reg done_rx ? data_rx : recvnum_reg; ADDR : addr_reg done_rx ? data_rx : addr_reg ; DATA :begin//在多字节写的过程中rx数据帧传多个数据以done_rx为条件来将数据逐一写入取代了data_reg if (done_rx data_rx ! 8hee) begin wr_en 1; data_fifo data_rx; end else begin wr_en 0; data_fifo 0; end end STOP :begin data_fifo 0; wr_en 0; end default : begin worr_reg 0; sendnum_reg 0; recvnum_reg 0; addr_reg 0; data_fifo 0; wr_en 0; end endcase end end //fifo的控制读出 always (posedge clk or negedge rst_n) if(rst_n 0) begin rd_en 0; end else if(done_w) begin rd_en 1; end else begin rd_en 0; end //与q对齐 always (posedge clk or negedge rst_n) if(rst_n 0) begin rd_en_r 0; end else begin rd_en_r rd_en; end //数据暂存(换为fifo实现多数据暂存) always (posedge clk or negedge rst_n) begin if (rst_n 0) begin data_temp 0; end else if(start_iic)begin data_temp addr_reg; end else if (rd_en_r) begin//也就在down_w后两个周期才变化在ack2里面已经更新完毕 data_temp q;//数据更新变化,写完一次data_reg给一次还有存在必要q也由done_W控制输出 end end //输出信号 assign start_iic (DATA_STOP || ADDR_STOP) ; assign worr worr_reg ; assign sendnum sendnum_reg ; assign recvnum recvnum_reg ; assign data_i_iic data_temp; //同步fifo 8*256 可以一次性读完 fifo fifo_inst_w ( .aclr (~rst_n || wrong),//保险 .clock (clk ), .data (data_fifo),//done_rx .rdreq (rd_en ),//done_w .wrreq (wr_en ),//done_rx .empty (empty ), .full (full ), .q (q ), .usedw (usedw )//作为数据存储的停止条件 ); endmodule功能解析串口帧FIFO缓存多字节待写数据IIC每写完一字节从FIFO取数实现多字节页写。ctrl_r 读控制模块module ctrl_r ( input wire clk , input wire rst_n , input wire [7:0] data_o_iic ,//iic的输出数据也就是读出的 input wire done_r ,//完成一次读数据的握手信号 input wire done_tx ,//读数据控制读使能的信号 input wire done_iic ,//数据寄存fifo结束 output wire [7:0] data_out , output wire rd_done ); //fifo8*256 //写 reg wr_en ;//写使能 reg [7:0] data_fifo ;//fifo的输入数据 //读 reg rd_en ;//读使能 wire [7:0] q ;//读出的数据,和读使能相差一周期 reg rd_en_r;//与q对齐 //握手信号 wire empty ;//空信号 wire full ;//满信号 wire [7:0] usedw ;//数据量 //写 always (posedge clk or negedge rst_n) begin if (rst_n 0) begin wr_en 0; data_fifo 0; end else if(done_r) begin wr_en 1; data_fifo data_o_iic; end else begin wr_en 0; data_fifo 0; end end //读 always (posedge clk or negedge rst_n) begin if (rst_n 0) begin rd_en 0; end else if(empty0 (done_iic||done_tx)) begin rd_en 1; end else begin rd_en 0; end end //与q对齐 always (posedge clk or negedge rst_n) if(rst_n 0) begin rd_en_r 0; end else begin rd_en_r rd_en; end //输出信号 assign data_out q; assign rd_done rd_en_r; //同步fifo 8*256 可以一次性读完 fifo fifo_inst_r ( .aclr (~rst_n ), .clock (clk ), .data (data_fifo),//done_r .rdreq (rd_en ),//done_tx .wrreq (wr_en ),//done_r .empty (empty ), .full (full ), .q (q ), .usedw (usedw )//作为数据存储的停止条件 ); endmodule功能IIC读取的多字节存入读FIFOIIC通信结束后将缓存数据输出给tx模块上传上位机。四、仿真波形仿真代码与单字节类似仿真重点观测点串口帧解析状态机完整走完IDLE→WORR→SENDNUM→RECVNUM→ADDR→DATA→STOP写FIFOrx的done_rx脉冲下批量写入数据IIC的done_w脉冲触发FIFO读连续输出待写字节IIC时序多字节页写连续多组数据字节只在最后产生Stop信号多字节读重复起始Sr主机连续回复ACK读到最后1字节主机输出NACK读FIFOIIC读出数据存入FIFOIIC通信结束后FIFO逐字节输出给tx。仿真波形五、抓包与上板验证串口助手实测现象从串口截图可以看到顺序读回传的数据这里有一个非常关键的顺序读硬件特性可以从0号起始地址一次性读取256个字节查看整块EEPROM全部存储内容。不能从非0的其他地址直接读取256字节。原因本驱动实现的是硬件顺序读没有地址循环逻辑读到器件最大地址后后续读取会出错不会自动绕回0地址。截图示例发送指令FE 00 01 FF 00 EE串口接收区回吐出大量HEX字节大部分为EEPROM默认上电值0xFF。工程踩坑总结页回卷问题24LC04页大小16字节一批写指令数据不能跨页否则会覆盖页起始地址上层需要增加地址边界判断。写周期3‑5ms多字节写同样要等待内部烧写Stop之后EEPROM不会响应IIC优化方向ctrl_w增加写后延时计数器写完阻塞新指令或者轮询ACK。FIFO边界注意FIFOfull/empty握手防止写满溢出、读空读错数据rd_en与输出q存在一拍延迟需要rd_en_r做信号对齐。多字节读前面所有字节主机回复ACK只有最后1字节主机输出NACK如果全部回复ACKEEPROM会持续输出数据不会停止。顺序读硬件限制没有地址循环读取机制。只有起始地址为0时读取256字节才是完整整块如果起始地址不为0读取256字节会读到地址边界之后产生错误数据。重复起始Sr多字节读的前置随机地址设置必须使用重复起始不能中间发Stop。开发流程优先Modelsim仿真观察FIFO读写、IIC完整时序确认无误后再上板测试。## 相关文章推荐‑ IIC——EEPROM 的单字节写入、当前地址读、随机地址读简易驱动‑ verilog实现IIC集成电路总线协议‑ uart回环加入fifo——多模块间的时序分析