协议)
IIC (Inter‑Integrated Circuit) 集成电路总线文章目录IIC (Inter‑Integrated Circuit) 集成电路总线一、IIC 总线概述二、物理层三、设备寻址71 字节格式四、设备角色与功能主设备从设备五、通信速率六、协议层6.1 起始信号 停止信号6.2 数据位传输规则6.3 ACK 应答信号6.4 三种工作模式6.5 多主机与总线仲裁七、IIC 核心小结八、Verilog 实现 IIC 协议摘要IICInter‑Integrated Circuit是一种两线式双向串行总线仅需 SDA数据线与 SCL时钟线两根信号线配合外部上拉电阻即可实现主从设备间的板内短距离通信。本文从物理层、设备寻址7 位地址 1 位读写位、设备角色、通信速率与协议层起始/停止信号、数据位传输、ACK 应答、多主机仲裁逐层讲解 IIC 核心机制并给出基于三段式状态机的 Verilog 完整实现与仿真代码帮助读者从原理到代码全面掌握 IIC 协议。一、IIC 总线概述IICInter‑Integrated Circuit集成电路总线主要用于连接微控制器与各类外围器件实现芯片与芯片之间的通信。可以在 CPU 与被控 IC、IC 与 IC 之间完成双向数据传输高速 IIC 总线速率可达 400kbps 以上。适用场景主控制器与从器件的主从通信适合小数据量、短距离板内通信约束特性任意时刻总线只允许存在一个有效主机从机与从机之间不能直接交换数据必须经过主机转发。二、物理层IIC 是两线式双向串行总线只需要两根信号线SDASerial Data串行数据线负责传输实际收发的数据。SCLSerial Clock Line串行时钟线由主机产生时钟控制数据收发时序。总线硬件要求SDA、SCL 两根信号线必须外接上拉电阻阻值根据通信速率、总线容性负载选择常用取值3.3kΩ ~ 10kΩ。总线空闲时两根线被上拉电阻拉为高电平器件内部为开漏输出结构依靠高阻态实现线与逻辑。总线上挂载的每一个 IIC 从设备都拥有唯一设备地址主机依靠地址区分不同从机保证访问准确。三、设备寻址71 字节格式IIC 读写寻址字节一共 8bit7bit从机设备地址 1bit读写方向位bit7~bit17 位从机设备地址用来寻找总线上目标从机bit0读写方向位0主机向从机写数据1主机从从机读数据注意IIC 器件内部 IO 为开漏结构总线空闲时 SDA、SCL 处于高阻态依靠外部上拉电阻拉高电平。四、设备角色与功能主设备产生 SCL 时钟信号产生起始信号、停止信号发起总线访问、发送寻址字节从设备检测总线上的设备地址匹配自身地址后响应检测起始位、停止位配合主机完成应答与数据收发IIC 支持多主机模式multimastering一条总线可以挂多个主机重点同一时间只允许一个主机掌握总线控制权。多主机场景依靠总线仲裁机制避免多个主机同时发送信号造成冲突。五、通信速率标准模式最高100kHz快速模式最高400kHz超高速模式最高3.4MHz通信属性同步、串行、半双工同步通信时序完全跟随 SCL 时钟串行数据按 bit 逐位传输半双工同一时刻只能单向传输不能同时收发。六、协议层6.1 起始信号 停止信号起始信号 (S)SCL 为高电平时SDA 由高电平跳变到低电平表示通信开始。口诀sda先拉低scl后拉低。停止信号 §SCL 为高电平时SDA 由低电平跳变到高电平表示通信结束总线回到空闲状态。口诀sda后拉高scl先拉高。只有 SCL 处于高电平期间SDA 的跳变才代表起始 / 停止SCL 低电平时允许 SDA 改变电平用来传输数据 bit。6.2 数据位传输规则SCL 高电平阶段SDA 电平必须保持稳定接收端采样 SDA 得到数据SCL 低电平阶段允许 SDA 电平翻转准备下一位数据。每传输 1bit 数据对应一个 SCL 时钟周期。口诀低电平切换数据高电平保持。6.3 ACK 应答信号每发送完 8bit1 字节数据之后接收方需要拉低 SDA 产生应答 ACKACK应答接收方将 SDA 拉低告诉发送方接收正常NACK非应答SDA 保持高电平代表无应答通常表示从机未收到、结束读操作。6.4 三种工作模式写模式开始信号 →主机发从机地址 写位 → 从机 ACK → 主机发送寄存器地址/或者直接从头开始写数据 → 从机 ACK → 主机发送数据 → 从机 ACK… → 停止信号读模式开始信号 → 主机发从机地址 读位 → 从机ACK → 主机读取数据变现为不工作高阻态通过上拉电阻输入数据→ 主机ACK → 读取下一个数据 →主机ACK…→主机NACK→停止信号先写后读模式开始信号 → 主机发从机地址 写位 → 指定寄存器或先从头写入数据→…→从机ACK → 重新发起起始→发送从机地址 读位 → 从机 ACK → 从机输出数据 → 主机ACK…→ 主机 NACK → 发出停止信号结束读取。6.5 多主机与总线仲裁多个主机同时抢占总线时仲裁逻辑主机一边发送数据一边实时监听 SDA 总线电平如果自己输出高电平但总线被别的主机拉为低电平代表仲裁失败该主机立刻停止发送让出总线等待总线空闲后重新发起通信。七、IIC 核心小结硬件SDASCL开漏输出必须外部上拉电阻空闲总线为高电平寻址7 位设备地址 1 位读写位从机之间不能直接通信信号起始、停止、应答 ACK/NACKSCL 高电平采样数据SCL低电平SDA转换支持多主机但同一时刻仅有一个主机有效依靠总线仲裁解决冲突半双工同步串行适合板内短距离小数据外设OLED、DS18B20、EEPROM 等。八、Verilog 实现 IIC 协议说明为本篇初稿实现支持 IIC 写、读、组合读写重复起始内部采用三段式状态机使用三态门处理 SDA/SCL 双向端口。module top ( input wire clk , input wire rst_n , input wire start_iic ,//协议开始信号 input wire worr ,//方向信号 input wire [7:0] sendnum ,//器件规定主机的写入数据量 input wire [7:0] recvnum ,//规定主机的读出数据量 input wire [7:0] data_i_iic ,//外部给到的数据 output wire [7:0] data_o_iic ,//从机里面读到、输出的数据 output wire done_w ,//完成一次写数据的握手信号 output wire done_r ,//完成一次读数据的握手信号 output wire done_iic ,//完成一次iic通信的握手信号 output wire wrong ,//报警信号 inout wire scl ,//时钟总线 inout wire sda //数据总线 ); //参数 parameter sysclk 50_000_000 ,//系统时钟 iiclk 100_000 ,//iic速率 delay sysclk/iiclk ,//发一比特所用周期数10us MID delay/2 ,//5us Q_MID delay/4 ,//2.5us TQ_MID MID Q_MID ,//12.5us ID_W 8b1010_0000 ,//设备ID方向 ID_R 8b1010_0001 ;//设备ID方向 //状态定义 localparam IDLE d1 , START1 d2 , IDW d3 , ACK1 d4 , WDATA d5 , ACK2 d6 , STOP d7 , IDR d8 , ACK3 d9 , RDATA d10 , ACK4 d11 , START2 d12 ; reg [3:0] state_c,state_n; //状态转移 wire IDLE_START1 , START1_IDW , IDW_ACK1 , ACK1_WDATA , WDATA_ACK2 , ACK2_WDATA , ACK2_STOP ,//第一种工作模式IDLE - START1 - IDW - ACK1 - WDATA -- ACK2 -STOP -ILDE START1_IDR , IDR_ACK3 , ACK3_RDATA , RDATA_ACK4 , ACK4_RDATA , ACK4_STOP ,//第二种工作模式IDLE - START1 - IDR - ACK3 - RDATA -- ACK4 -STOP -ILDE ACK2_START2 , START2_IDR ,//第三种工作方式IDLE - START1 - IDW - ACK1 - WDATA -- ACK2 - IDR - ACK3 - RDATA -- ACK4 -STOP -ILDE ACK1_STOP ,//从机错误应答情况 ACK3_STOP ,//从机错误应答情况 STOP_IDLE ; //内部信号 //计数器 reg [9:0] cnt_time ;//时间计数 reg [3:0] cnt_bit ;//比特计数 //寄存信号 reg [1:0] start_reg ;//协议开始两个位宽是为了后续根据需求选择长短信号 reg worr_reg ;//工作模式1/2选择 reg [7:0] sendnum_reg ; reg [7:0] recvnum_reg ; reg [7:0] data_reg ; //数据量计数 reg [7:0] sendcnt ;//发送计数 reg [7:0] recvcnt ;//接收计数 //数据暂存和应答信号 reg [7:0] data_temp ; reg ack_flag ;//应答信号 //三态门信号 reg scl_en ; reg scl_out ; wire scl_in ; reg sda_en ; reg sda_out ; wire sda_in ; //三态门 assign scl scl_en ? scl_out : 1bz; assign scl_in scl; assign sda sda_en ? sda_out : 1bz; assign sda_in sda; //输入数据寄存 always (posedge clk or negedge rst_n) begin if (rst_n 0) begin start_reg 2b00; end else begin start_reg {start_reg[0],start_iic}; end end //状态机第一段 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_START1) ? START1: IDLE ; START1 : state_n (START1_IDW ) ? IDW : (START1_IDR) ? IDR : START1; IDW : state_n (IDW_ACK1 ) ? ACK1 : IDW ; ACK1 : state_n (ACK1_STOP ) ? STOP : (ACK1_WDATA) ? WDATA : ACK1; WDATA : state_n (WDATA_ACK2 ) ? ACK2 : WDATA ; ACK2 : state_n (ACK2_STOP ) ? STOP : (ACK2_START2)?START2 :(ACK2_WDATA ) ? WDATA : ACK2; STOP : state_n (STOP_IDLE ) ? IDLE : STOP ; IDR : state_n (IDR_ACK3 ) ? ACK3 : IDR ; ACK3 : state_n (ACK3_STOP ) ? STOP : (ACK3_RDATA ) ? RDATA : ACK3; RDATA : state_n (RDATA_ACK4 ) ? ACK4 : RDATA ; ACK4 : state_n (ACK4_STOP ) ? STOP : (ACK4_RDATA ) ? RDATA : ACK4; START2 : state_n (START2_IDR ) ? IDR : START2 ; default: state_n IDLE; endcase end end assign IDLE_START1 (state_c IDLE ) start_reg 2b01; assign START1_IDW (state_c START1) cnt_time delay Q_MID - 1 worr_reg 0; assign START1_IDR (state_c START1) cnt_time delay Q_MID - 1 worr_reg 1; assign IDW_ACK1 (state_c IDW ) cnt_time delay - 1 cnt_bit 7; assign ACK1_STOP (state_c ACK1 ) cnt_time delay - 1 ack_flag 1; assign ACK1_WDATA (state_c ACK1 ) cnt_time delay - 1 ack_flag 0; assign WDATA_ACK2 (state_c WDATA ) cnt_time delay - 1 cnt_bit 7; assign ACK2_STOP (state_c ACK2 ) cnt_time delay - 1 (sendcnt sendnum recvnum 0 ack_flag 0)||ack_flag 1; assign ACK2_WDATA (state_c ACK2 ) cnt_time delay - 1 sendcnt sendnum ack_flag 0; assign IDR_ACK3 (state_c IDR ) cnt_time delay - 1 cnt_bit 7; assign ACK3_STOP (state_c ACK3 ) cnt_time delay - 1 ack_flag 1; assign ACK3_RDATA (state_c ACK3 ) cnt_time delay - 1 ack_flag 0; assign RDATA_ACK4 (state_c RDATA ) cnt_time delay - 1 cnt_bit 7; assign ACK4_STOP (state_c ACK4 ) cnt_time delay - 1 recvcnt recvnum; assign ACK2_START2 (state_c ACK2 ) cnt_time delay - 1 sendcnt sendnum recvnum ! 0; assign ACK4_RDATA (state_c ACK4 ) cnt_time delay - 1 recvcnt recvnum; assign START2_IDR (state_c START2) cnt_time delay Q_MID - 1 ; assign STOP_IDLE (state_c STOP ) cnt_time delay Q_MID - 1 ; //状态机第三段 always (posedge clk or negedge rst_n) begin if (rst_n 0) begin //计数 cnt_time 0; cnt_bit 0; //寄存 worr_reg 0; sendnum_reg 0; recvnum_reg 0; data_reg 0; //数据量 sendcnt 0; recvcnt 0; data_temp 0; ack_flag 0; //三态门 scl_en 0; sda_en 0; scl_out 0; sda_out 0; end else begin case (state_c) IDLE :begin//寄存信号主机不工作 //计数 cnt_time 0; cnt_bit 0; //寄存 worr_reg worr; sendnum_reg sendnum; recvnum_reg recvnum; data_reg data_i_iic; //数据量计数 sendcnt 0; recvcnt 0; data_temp 0; ack_flag 0; //三态门 scl_en 0; sda_en 0; scl_out 1;//象征为高阻但实际上不会被输出 sda_out 1;//象征为高阻但实际上不会被输出 end START1,START2:begin//开始时序主机工作sda先拉低scl后拉低 //计数 if (cnt_time delay Q_MID - 1) begin//计数12.5us cnt_time 0; end else begin cnt_time cnt_time 1; end cnt_bit 0; //三态门 scl_en 1; sda_en 1; if (cnt_time delay) begin scl_out 0; end else begin scl_out 1; end if (cnt_time MID) begin sda_out 0; end else begin sda_out 1; end end IDW :begin//主机工作发送从机地址 //计数 if (cnt_time delay - 1) begin//周期计数 cnt_time 0; end else begin cnt_time cnt_time 1; end if (cnt_time delay - 1) begin if (cnt_bit 7) begin cnt_bit 0; end else begin cnt_bit cnt_bit 1; end end //三态门 scl_en 1; sda_en 1; if (cnt_time Q_MID cnt_time TQ_MID-1) begin//两边高中间低 scl_out 1; end else begin scl_out 0; end if (cnt_time 1) begin sda_out ID_W[7 - cnt_bit];//高位优先 end end ACK1,ACK3:begin//采集应答信号、维持时钟输出 //计数 if (cnt_time delay - 1) begin//周期计数 cnt_time 0; end else begin cnt_time cnt_time 1; end cnt_bit 0; //寄存 if (cnt_time MID) begin//采样点 ack_flag sda_in; end //三态门 scl_en 1; sda_en 0; if (cnt_time Q_MID cnt_time TQ_MID-1) begin//两边高中间低 scl_out 1; end else begin scl_out 0; end sda_out 1; end WDATA :begin//主机工作写数据、数据量计数保持 //计数 if (cnt_time delay - 1) begin//周期计数 cnt_time 0; end else begin cnt_time cnt_time 1; end if (cnt_time delay - 1) begin if (cnt_bit 7) begin cnt_bit 0; end else begin cnt_bit cnt_bit 1; end end //三态门 scl_en 1; sda_en 1; if (cnt_time Q_MID cnt_time TQ_MID-1) begin//两边高中间低 scl_out 1; end else begin scl_out 0; end if (cnt_time 0) begin sda_out data_reg[7 - cnt_bit];//高位优先 end end ACK2 :begin//采集应答信号、维持时钟输出、对数据量进行计数 //计数 if (cnt_time delay - 1) begin//周期计数 cnt_time 0; end else begin cnt_time cnt_time 1; end cnt_bit 0; //寄存 if (cnt_time 0) begin//完成一次数据写入计数1 sendcnt sendcnt 1; end //更新数据 data_reg data_i_iic; if (cnt_time MID) begin//采样点 ack_flag sda_in; end //三态门 scl_en 1; sda_en 0; if (cnt_time Q_MID cnt_time TQ_MID-1) begin//两边高中间低 scl_out 1; end else begin scl_out 0; end sda_out 1; end STOP :begin//停止时序主机工作sda后拉高scl先拉高 //计数 if (cnt_time delay Q_MID - 1) begin//计数12.5us cnt_time 0; end else begin cnt_time cnt_time 1; end cnt_bit 0; //三态门 scl_en 1; sda_en 1; if (cnt_time Q_MID) begin scl_out 1; end else begin scl_out 0; end if (cnt_time TQ_MID) begin sda_out 1; end else begin sda_out 0; end end IDR :begin//主机工作发送从机地址 //计数 if (cnt_time delay - 1) begin//周期计数 cnt_time 0; end else begin cnt_time cnt_time 1; end if (cnt_time delay - 1) begin if (cnt_bit 7) begin cnt_bit 0; end else begin cnt_bit cnt_bit 1; end end //三态门 scl_en 1; sda_en 1; if (cnt_time Q_MID cnt_time TQ_MID-1) begin//两边高中间低 scl_out 1; end else begin scl_out 0; end if (cnt_time 1) begin sda_out ID_R[7 - cnt_bit];//高位优先 end end RDATA :begin//从机工作主机读数据、数据量计数保持 //计数 if (cnt_time delay - 1) begin//周期计数 cnt_time 0; end else begin cnt_time cnt_time 1; end if (cnt_time delay - 1) begin if (cnt_bit 7) begin cnt_bit 0; end else begin cnt_bit cnt_bit 1; end end if (cnt_time MID) begin data_temp[7-cnt_bit] sda_in; end //三态门 scl_en 1; sda_en 0; if (cnt_time Q_MID cnt_time TQ_MID-1) begin//两边高中间低 scl_out 1; end else begin scl_out 0; end sda_out 1; end ACK4 :begin//发出应答信号 //计数 if (cnt_time delay - 1) begin//周期计数 cnt_time 0; end else begin cnt_time cnt_time 1; end cnt_bit 0; //寄存 if (cnt_time 0) begin//完成一次数据写入计数1 recvcnt recvcnt 1; end //三态门 scl_en 1; sda_en 1; if (cnt_time Q_MID cnt_time TQ_MID-1) begin//两边高中间低 scl_out 1; end else begin scl_out 0; end if (recvcnt recvnum) begin sda_out 1; end else begin sda_out 0; end end default: begin //计数 cnt_time 0; cnt_bit 0; //寄存 worr_reg 0; sendnum_reg 0; recvnum_reg 0; data_reg 0; //数据量 sendcnt 0; recvcnt 0; data_temp 0; ack_flag 0; //三态门 scl_en 0; sda_en 0; scl_out 0; sda_out 0; end endcase end end //输出信号 assign data_o_iic (state_c ACK4) ? data_temp : 0; assign done_r (state_c ACK4 cnt_time MID) ? 1 : 0; assign done_w (state_c ACK2 cnt_time MID) ? 1 : 0; assign done_iic (state_c STOP cnt_time MID) ? 1 : 0; assign wrong (state_c STOP cnt_time MID (sendcnt!sendnum || recvcnt!recvnum )); endmodule以cmd命令控制模式来写/******************************************************************* 通用的IIC_cmd形式接口、将iic的几种读写方式分为六种情况 单次cmd执行一种情况多个cmd组合成一次完整的icc通信 ********************************************************************/ module iic_cmd #( parameter SYS_CLOCK 50_000_000,//系统时钟频率 IIC_CLOCK 100_000 //IIC传输速率 )( input clk , input rst_n , //读写控制上层控制 input start_en ,//读/写使能信号,长信号、拉高时进行单字节的读写 input [3:0] cmd ,//命令信号 cmd[0]:起始 cmd[1]:写 cmd[2]:读 cmd[3]:停止信号 带起始信号的写4‘b0011 input [7:0] wr_data ,//写入的数据ID、地址、数据 output reg [7:0] rd_data ,//读出的数据 output trans_done,//一帧字节数据传输完成 output reg slave_ack ,//从机应答信号 //从机、iic接口 inout scl , inout sda ); //参数 localparam MAX_CNT_SCL SYS_CLOCK/IIC_CLOCK,//iic工作时钟周期 CHANGE_TIME (MAX_CNT_SCL2) ,//四分之一串行时钟周期 SAMPLE_TIME (MAX_CNT_SCL1) (MAX_CNT_SCL2);//四分之三串行时钟周期 //命令 localparam CMD_START 4b0001, CMD_WRITE 4b0010, CMD_READ 4b0100, CMD_STOP 4b1000; //状态定义 localparam IDLE 7b000_0001, START 7b000_0010, WR_DATA 7b000_0100, RD_DATA 7b000_1000, R_ACK 7b001_0000,//接收ack S_ACK 7b010_0000,//发送ack STOP 7b100_0000; reg [6:0] state_c,state_n; //状态转移 //六种路径 //写1.带起始的写idle-start-wr_data-r_ack-idle //2.普通的写idle-wr_data-r_ack-idle //3.带停止的写:idle-wr_data-r_ack-stop-idle //读1.带起始的读idle-start-rd_data-s_ack-idle //2.普通的读idle-rd_data-s_ack-idle //3.带停止的读:idle-rd_data-s_ack-stop-idle wire idle2start , idle2wr_data , idle2rd_data , start2wr_data, start2rd_data, wr_data2r_ack, r_ack2idle , r_ack2stop , rd_data2s_ack, s_ack2idle , s_ack2stop , stop2idle ; //内部信号 //三态门信号 reg scl_en ; reg scl_out ; wire scl_in ; reg sda_en ; reg sda_out ; wire sda_in ; //串行时钟计数器 reg [9:0] cnt_scl ; wire add_cnt_scl; wire end_cnt_scl; //读写数据比特计数器 reg [2:0] cnt_bit ; wire add_cnt_bit; wire end_cnt_bit; //---------状态机------------------------------------------------- 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 case(state_c) IDLE : begin if(idle2start) //带起始信号的传输优先级高于其它传输 state_n START; else if(idle2wr_data) state_n WR_DATA; else if(idle2rd_data) state_n RD_DATA; else state_n state_c; end START : begin if(start2wr_data) state_n WR_DATA; else if(start2rd_data) state_n RD_DATA; else state_n state_c; end WR_DATA : begin if(wr_data2r_ack) state_n R_ACK; else state_n state_c; end RD_DATA : begin if(rd_data2s_ack) state_n S_ACK; else state_n state_c; end R_ACK : begin if(r_ack2idle) state_n IDLE; else if(r_ack2stop) state_n STOP; else state_n state_c; end S_ACK : begin if(s_ack2idle) state_n IDLE; else if(s_ack2stop) state_n STOP; else state_n state_c; end STOP : begin if(stop2idle) state_n IDLE; else state_n state_c; end default : state_n IDLE; endcase end assign idle2start (state_c IDLE) start_en cmd[0];//(cmd CMD_START) assign idle2wr_data (state_c IDLE) start_en cmd[1]; assign idle2rd_data (state_c IDLE) start_en cmd[2]; assign start2wr_data (state_c START) end_cnt_scl cmd[1]; assign start2rd_data (state_c START) end_cnt_scl cmd[2]; assign wr_data2r_ack (state_c WR_DATA) end_cnt_bit; assign r_ack2idle (state_c R_ACK) end_cnt_scl cmd[3] 1b0; assign r_ack2stop (state_c R_ACK) end_cnt_scl cmd[3] 1b1; assign rd_data2s_ack (state_c RD_DATA) end_cnt_bit; assign s_ack2idle (state_c S_ACK) end_cnt_scl cmd[3] 1b0; assign s_ack2stop (state_c S_ACK) end_cnt_scl cmd[3] 1b1; assign stop2idle (state_c STOP) end_cnt_scl; //---------cnt_scl------------------------------------------------- always (posedge clk or negedge rst_n)begin if(rst_n 0)begin cnt_scl d0; end else if(add_cnt_scl)begin if(end_cnt_scl)begin cnt_scl d0; end else begin cnt_scl cnt_scl 1b1; end end end assign add_cnt_scl state_c ! IDLE; assign end_cnt_scl add_cnt_scl cnt_scl MAX_CNT_SCL - 1; //---------cnt_bit------------------------------------------------- always (posedge clk or negedge rst_n)begin if(rst_n 0)begin cnt_bit d0; end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit d0; end else begin cnt_bit cnt_bit 1b1; end end end assign add_cnt_bit (state_c WR_DATA || state_c RD_DATA) end_cnt_scl; assign end_cnt_bit add_cnt_bit cnt_bit 8 - 1; //---------scl_out scl_out ------------------------------------------------- //三态门 assign scl scl_en ? scl_out : 1bz; assign scl_in scl; always (posedge clk or negedge rst_n)begin if(rst_n 0)begin scl_out 1b1; end else if(state_c ! IDLE)begin if(cnt_scl (MAX_CNT_SCL1))begin scl_out 1b0; scl_en 1b1; end else begin scl_out 1b1; scl_en 1b1; end end else begin scl_en 1b0; scl_out 1b1; end end //---------sda_out sda_en rd_data------------------------------------ //三态门 assign sda sda_en ? sda_out : 1bz; assign sda_in sda; always (posedge clk or negedge rst_n)begin if(rst_n 0)begin sda_out 1b1; sda_en 1b0; rd_data 8d0; slave_ack 1b0; end else begin case (state_c) IDLE : begin sda_out 1b1; sda_en 1b0; end START : begin sda_en 1b1; if(cnt_scl SAMPLE_TIME) sda_out 1b1; else sda_out 1b0; end WR_DATA : begin sda_en 1b1; if(cnt_scl CHANGE_TIME - 1) //并转串 MSB sda_out wr_data[7-cnt_bit]; else sda_out sda_out; end RD_DATA : begin sda_out 1b0; sda_en 1b0; if(cnt_scl SAMPLE_TIME - 1) rd_data[7-cnt_bit] sda_in; else rd_data rd_data; end R_ACK : begin sda_out 1b0; sda_en 1b0; // slave_ack (cnt_scl SAMPLE_TIME - 1) ? sda_in : slave_ack; if(cnt_scl SAMPLE_TIME - 1) slave_ack sda_in; else slave_ack slave_ack; end S_ACK : begin sda_en 1b1; // sda_out (cnt_scl CHANGE_TIME cmd[3]) ? 1b1 : 1b0; if(cnt_scl CHANGE_TIME) sda_out cmd[3] ? 1b1 : 1b0; else sda_out sda_out; end STOP : begin sda_en 1b1; if(cnt_scl CHANGE_TIME) sda_out 1b0; else if(cnt_scl SAMPLE_TIME) sda_out 1b1; end default: begin sda_en 1b0; sda_out 1b0; rd_data rd_data; end endcase end end //---------trans_done------------------------------------------------- assign trans_done r_ack2idle | s_ack2idle | stop2idle; endmodule仿真代码和波形module tb (); reg clk ; reg rst_n ; reg start_iic ; reg [7:0] data_i_iic ; wire [7:0] data_o_iic ; wire done_w ; wire done_r ; wire done_iic ; wire wrong ; tri0 scl ; tri0 sda ; initial begin clk 0; rst_n 0; start_iic 0; #20 rst_n 1; #100 start_iic 1; #100 start_iic 0; end always #10 clk ~clk; always (posedge clk or negedge rst_n) begin if (rst_n 0) begin data_i_iic 8h22; end else if(done_w)begin data_i_iic data_i_iic 1; end end top top_inst( .clk (clk ), .rst_n (rst_n ), .start_iic (start_iic ), .worr (0 ), .sendnum (4 ), .recvnum (3 ), .data_i_iic (data_i_iic), .data_o_iic (data_o_iic), .done_w (done_w ), .done_r (done_r ), .done_iic (done_iic ), .wrong (wrong ), .scl (scl ), .sda (sda ) ); endmodule