IIC 通信协议 (二)

article/2025/6/25 7:35:19


目录

引言

子模块设计

思路

单字节 IIC 发送模块

思路

Verilog 源码

多字节发送控制模块

思路

Verilog 源码

仿真

思路

test bench

仿真结果

 

参考声明


 


引言

本篇博文承接前文,继续做 IIC 通信协议 FPGA实现相关的内容。用Verilog 编写一个 IIC 通信控制器,最后用 Microchip公司提供的 IIC 驱动器件的 Verilog 模型 辅助完成 仿真验证。



子模块设计

思路

此处的思路主要是将IIC复杂的通信步骤拆解,因为对器件进行一次读或者写操作,都需要如下步骤:

  1. 发送起始信号,征用总线;
  2. 发送器件ID,选择通信的从机;
  3. 发送要操作的寄存器地址;
  4. 发送/接收要写入/读出寄存器的数据;

因此为了简化设计的复杂度,将设计分为两大模块:

  1. 单字节发送模块
  2. 多字节发送控制模块

单字节 IIC 发送模块

思路

拟采用状态机的方法进行设计。此处仅对状态转换条件进行简述。更为详细的理解需要仔细阅读设计代码。

状态声明:

LP_ST_IDLE:空闲状态         
LP_ST_GEN_START:产生起始信号    
LP_ST_WR_DATA:写数据    
LP_ST_RD_DATA :读数据   
LP_ST_GEN_ACK :产生应答    
LP_ST_CHECK_ACK :检查器件发出的应答   
LP_ST_GEN_STOP:产生停止信号,释放总线

状态转移说明:

LP_ST_IDLE:

如果操作使能信号有效,依次判断 写起始信号,写数据,读数据哪个命令有效(有优先级)  如果对应的命令有效则跳转到相应的状态中。
LP_ST_GEN_START:

产生起始信号;产生完毕后,根据输入命令继续判断是否需要写/读寄存器,如果需要则跳转到对应的状态。
LP_ST_WR_DATA: 

写入数据;写数据完成后,检查从机是否给出响应;  
LP_ST_RD_DATA :

读出数据;读数据完毕后,根据命令给出响应;   
LP_ST_GEN_ACK:

产生应答;产生结束后,判断是否有结束的命令,如果有则进入结束状态。     
LP_ST_CHECK_ACK:

检查应答;检查结束后,判断是否有结束的命令,如果有则进入结束状态。         
LP_ST_GEN_STOP:

产生结束信号;产生完毕后,进入空闲状态;

此IIC发送接收的时序由计数器控制 发送 1比特数据计数器增加 4 .          

Verilog 源码

// | ===================================================---------------------------===================================================
// | ---------------------------------------------------     IIC 控制器 模块	   ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-24
// | 完成时间 : 2022-12-24
// | 作    者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// |			-1- IIC 通信速率支持:100KHz、400KHz、3400KHz
// |			-2- 
// | 			-3- 
// |
// | ================================= 		模块修改历史纪录 	  =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:module IIC_CTRL_MDL #(
// | ====================================  模块可重载参数声明  ==================================== 
parameter 				P_SYS_CLK_FREQ		=			32'd50_000_000,// 系统时钟频率 单位:Hz
parameter				P_IIC_CLK_FREQ 		=			32'd400_000    // IIC 时钟频率 单位:Hz
)(
// | ==================================== 模块输入输出端口声明 ==================================== 
input 												   	I_SYS_CLK   ,
input												   	I_SYS_RSTN  ,input 			[5:0]									I_CMD 	    ,
input 													I_OPR_EN    ,
input 			[7:0]									I_TX_DATA   ,
output 	reg		[7:0]									O_RX_DATA   ,output 	reg												O_OPR_DONE  ,
output 	reg												O_ACK       ,output 	reg												O_IIC_SCLK  ,
inout   	 											IO_IIC_SDAT);// | ====================================   模块内部参数声明   ====================================
// 状态编码 
localparam 				LP_ST_IDLE 			=			7'b0000_001;
localparam 				LP_ST_GEN_START		=			7'b0000_010;
localparam 				LP_ST_WR_DATA		=			7'b0000_100;
localparam 				LP_ST_RD_DATA		=			7'b0001_000;
localparam 				LP_ST_GEN_ACK 		=			7'b0010_000;
localparam 				LP_ST_CHECK_ACK		=			7'b0100_000;
localparam 				LP_ST_GEN_STOP 		=			7'b1000_000;// 命令字编码
localparam				LP_CMD_WR_REQ		=			6'b000_001;
localparam 				LP_CMD_RD_REQ		=			6'b000_010;
localparam 				LP_CMD_START_REQ    =			6'b000_100;
localparam				LP_CMD_STOP_REQ		=			6'b001_000;
localparam 				LP_CMD_ACK_REQ 		=			6'b010_000;
localparam 				LP_CMD_NOACK_REQ	=			6'b100_000; 	// IIC 时钟分频计数器最大值
localparam				LP_DIV_FREQ_CNT_MAX	=			P_SYS_CLK_FREQ/P_IIC_CLK_FREQ/4 ;// | ====================================   模块内部信号声明   ====================================
// 状态
reg 					[6:0]							R_STATE;
// 计数器
reg 				[$clog2(LP_DIV_FREQ_CNT_MAX)-1:0]	R_DIV_FREQ_CNT;
reg														R_DIV_FREQ_CNT_EN;
reg 					[4:0]							R_BIT_CNT;// IIC输出数据线
reg 													R_SDAT_VAL;
reg 													R_SDAT;// SCLK 脉冲(频率为 IIC 时钟 4 倍)
wire 													W_SCLK_PULSE_4;// | ====================================   模块内部逻辑设计   ====================================
always @ (posedge I_SYS_CLK)
beginif(~I_SYS_RSTN)beginR_DIV_FREQ_CNT <= {($clog2(LP_DIV_FREQ_CNT_MAX)){1'b0}};endelse if(R_DIV_FREQ_CNT_EN)beginif(R_DIV_FREQ_CNT == LP_DIV_FREQ_CNT_MAX-1)beginR_DIV_FREQ_CNT <= {($clog2(LP_DIV_FREQ_CNT_MAX)){1'b0}};endelsebeginR_DIV_FREQ_CNT <= R_DIV_FREQ_CNT + 1;end endelsebeginR_DIV_FREQ_CNT <= {($clog2(LP_DIV_FREQ_CNT_MAX)){1'b0}};end
endassign W_SCLK_PULSE_4 = (R_DIV_FREQ_CNT == LP_DIV_FREQ_CNT_MAX-1);assign IO_IIC_SDAT    = (R_SDAT_VAL & !R_SDAT) ? 1'b0 : 1'bz;always @ (posedge I_SYS_CLK)
beginif(~I_SYS_RSTN)beginR_STATE    		  <= LP_ST_IDLE;O_RX_DATA  		  <= 8'd0;O_OPR_DONE 		  <= 1'd0;O_ACK      		  <= 1'd0;O_IIC_SCLK 		  <= 1'd1;R_DIV_FREQ_CNT_EN <= 1'd0;R_BIT_CNT		  <= 5'd0;R_SDAT_VAL	      <= 1'd0;R_SDAT 		      <= 1'd1;endelsebegincase(R_STATE)LP_ST_IDLE :beginif(I_OPR_EN)beginR_DIV_FREQ_CNT_EN <= 1'b1;if(I_CMD & LP_CMD_START_REQ)beginR_STATE <= LP_ST_GEN_START;	endelse if(I_CMD & LP_CMD_WR_REQ)beginR_STATE <= LP_ST_WR_DATA; endelse if(I_CMD & LP_CMD_RD_REQ)beginR_STATE <= LP_ST_RD_DATA;endelsebeginR_STATE <= LP_ST_IDLE;endendelsebeginR_STATE    <= LP_ST_IDLE;R_DIV_FREQ_CNT_EN <= 1'b0;endR_BIT_CNT		  <= 5'd0;O_ACK      		  <= 1'd0;O_OPR_DONE        <= 1'b0;endLP_ST_GEN_START	:beginif(W_SCLK_PULSE_4)beginif(R_BIT_CNT == 3)beginR_BIT_CNT <= 0;endelsebeginR_BIT_CNT <= R_BIT_CNT + 1;endR_SDAT_VAL <= 1'b1;case(R_BIT_CNT)0:begin 	  R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end1:begin 	  R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end2:begin 	  R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b1;end3:begin 	  R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b0;enddefault:begin R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;endendcaseif(R_BIT_CNT == 3)beginif(I_CMD & LP_CMD_WR_REQ)beginR_STATE <= LP_ST_WR_DATA;endelse if(I_CMD & LP_CMD_RD_REQ)beginR_STATE <= LP_ST_RD_DATA;endelsebeginR_STATE <= LP_ST_GEN_START;endendendendLP_ST_WR_DATA	:beginif(W_SCLK_PULSE_4)beginif(&R_BIT_CNT)beginR_BIT_CNT <= 5'd0;endelsebeginR_BIT_CNT <= R_BIT_CNT + 1;endR_SDAT_VAL <= 1'b1;case(R_BIT_CNT)0,4,8,12,16,20,24,28 :begin	R_SDAT <= I_TX_DATA[3'd7-R_BIT_CNT[4:2]]; O_IIC_SCLK <= 1'b0;end 1,5,9,13,17,21,25,29 :begin	R_SDAT <= R_SDAT;O_IIC_SCLK <= 1'b1;end 2,6,10,14,18,22,26,30:begin	R_SDAT <= R_SDAT;O_IIC_SCLK <= 1'b1;end3,7,11,15,19,23,27,31:begin	R_SDAT <= R_SDAT;O_IIC_SCLK <= 1'b0;enddefault:			  begin	R_SDAT <= 1'b1;  O_IIC_SCLK <= 1'b1;endendcaseif(&R_BIT_CNT)beginR_STATE <= LP_ST_CHECK_ACK;endendendLP_ST_RD_DATA	:beginif(W_SCLK_PULSE_4)beginif(&R_BIT_CNT)beginR_BIT_CNT <= 5'd0;endelsebeginR_BIT_CNT <= R_BIT_CNT + 1;endR_SDAT_VAL <= 1'b0;case(R_BIT_CNT)0,4,8,12,16,20,24,28 :begin	O_IIC_SCLK <= 1'b0;end 1,5,9,13,17,21,25,29 :begin	O_IIC_SCLK <= 1'b1;end 2,6,10,14,18,22,26,30:begin	O_RX_DATA <= {O_RX_DATA[6:0],IO_IIC_SDAT};O_IIC_SCLK <= 1'b1;end3,7,11,15,19,23,27,31:begin	O_IIC_SCLK <= 1'b0;enddefault:begin	O_IIC_SCLK <= 1'b1;endendcaseif(&R_BIT_CNT)beginR_STATE <= LP_ST_GEN_ACK;endendendLP_ST_GEN_ACK 	:beginif(W_SCLK_PULSE_4)beginif(R_BIT_CNT == 3)beginR_BIT_CNT <= 0;endelsebeginR_BIT_CNT <= R_BIT_CNT + 1;endR_SDAT_VAL <= 1'b1;case(R_BIT_CNT)0:beginif(I_CMD & LP_CMD_ACK_REQ)R_SDAT <= 1'b0;else if(I_CMD & LP_CMD_NOACK_REQ)R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b0;end1:begin 	  R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end2:begin 	  R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b1;end3:begin 	  R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b0;enddefault:begin R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;endendcaseif(R_BIT_CNT == 3)beginif(I_CMD & LP_CMD_STOP_REQ)beginR_STATE <= LP_ST_GEN_STOP;endelsebeginR_STATE <= LP_ST_IDLE;O_OPR_DONE <= 1'b1;endendendendLP_ST_CHECK_ACK	:beginif(W_SCLK_PULSE_4)beginif(R_BIT_CNT == 3)beginR_BIT_CNT <= 0;endelsebeginR_BIT_CNT <= R_BIT_CNT + 1;endR_SDAT_VAL <= 1'b0;case(R_BIT_CNT)0:begin	 O_IIC_SCLK <= 1'b0;O_ACK <= 1'b0;end1:begin	 O_IIC_SCLK <= 1'b1;O_ACK <= 1'b0;end2:begin	 O_IIC_SCLK <= 1'b1;O_ACK <= IO_IIC_SDAT;end3:begin	 O_IIC_SCLK <= 1'b0;O_ACK <= O_ACK;enddefault:begin  O_IIC_SCLK <= 1'b1;O_ACK <= 1'b0;endendcaseif(R_BIT_CNT == 3)beginif(I_CMD & LP_CMD_STOP_REQ)beginR_STATE <= LP_ST_GEN_STOP;endelsebeginR_STATE <= LP_ST_IDLE;O_OPR_DONE <= 1'b1;endendendendLP_ST_GEN_STOP 	:beginif(W_SCLK_PULSE_4)beginif(R_BIT_CNT == 3)beginR_BIT_CNT <= 0;endelsebeginR_BIT_CNT <= R_BIT_CNT + 1;endR_SDAT_VAL <= 1'b1;case(R_BIT_CNT)0:begin	 O_IIC_SCLK <= 1'b0; R_SDAT<= 1'b0;end1:begin	 O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b0;end2:begin	 O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b1;end3:begin	 O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b1;enddefault:begin  O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b1;endendcaseif(R_BIT_CNT == 3)beginR_STATE <= LP_ST_IDLE;O_OPR_DONE <= 1'b1;endendend			endcaseend
endendmodule

多字节发送控制模块

思路

同样是利用状态机实现 IIC 读写;将读和写分别分为三步:

  1. 发起读/写(给写地址,写数据等)
  2. 等待读/写完成
  3. 读/写完成

每个读/写的状态内,利用计数器确定发送/读出操作步骤的先后顺序。因为每次读写过程都包含下面几个步骤:

  1. 发送起始信号,征用总线;
  2. 发送器件ID,选择通信的从机;
  3. 发送要操作的寄存器地址;
  4. 发送/接收要写入/读出寄存器的数据;

Verilog 源码

// | ===================================================---------------------------===================================================
// | ---------------------------------------------------     IIC 通信顶层 模块	   ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-24
// | 完成时间 : 2022-12-24
// | 作    者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// |			-1- IIC 通信速率支持:100KHz、400KHz、3400KHz
// |			-2- 
// | 			-3- 
// |
// | ================================= 		模块修改历史纪录 	  =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:module TOP_IIC_MDL#(
// | ====================================  模块可重载参数声明  ==================================== 
parameter 				P_SYS_CLK_FREQ		=			32'd50_000_000,// 系统时钟频率 单位:Hz
parameter				P_IIC_CLK_FREQ 		=			32'd400_000    // IIC 时钟频率 单位:Hz
)(
// | ==================================== 模块输入输出端口声明 ==================================== 
input 												   	I_SYS_CLK   ,
input												   	I_SYS_RSTN  ,input 													I_WR_REQ,
input 													I_RD_REQ,input 					[15:0]							I_ADDR,
input 													I_ADDR_MODE,// 1:16位地址;0:8位地址input 					[7:0]							I_WR_DATA,
output 		reg			[7:0]							O_RX_DATA,input 					[7:0]							I_DEVICE_ID,output		reg											O_RW_DONE,
output 		reg 										O_ACK,    output 													O_IIC_SCLK,
inout 													IO_IIC_SDAT);// | ====================================   模块内部参数声明   ====================================
// 命令字编码
localparam				LP_CMD_WR_REQ		=			6'b000_001;
localparam 				LP_CMD_RD_REQ		=			6'b000_010;
localparam 				LP_CMD_START_REQ    =			6'b000_100;
localparam				LP_CMD_STOP_REQ		=			6'b001_000;
localparam 				LP_CMD_ACK_REQ 		=			6'b010_000;
localparam 				LP_CMD_NOACK_REQ	=			6'b100_000; // 状态编码
localparam				LP_ST_IDLE 			=			7'b0000_001;
localparam				LP_ST_WR			=			7'b0000_010;
localparam 				LP_ST_WAIT_WR_DONE	=			7'b0000_100;
localparam				LP_ST_WR_DONE  		=			7'b0001_000;
localparam				LP_ST_RD			=			7'b0010_000;
localparam 				LP_ST_WAIT_RD_DONE	=			7'b0100_000;
localparam				LP_ST_RD_DONE  		=			7'b1000_000;// | ====================================   模块内部信号声明   ====================================
reg 					[2:0]							R_CNT;
reg 					[6:0]							R_STATE;reg 					[5:0]							R_CMD;
reg 													R_OPR_EN;
reg 					[7:0]							R_TX_DATA;
wire					[7:0]							W_RX_DATA;
wire 													W_OPR_DONE;
wire 													W_ACK;
wire 					[15:0]							W_ADDR;// | ====================================   模块内部逻辑设计   ====================================
assign 			W_ADDR     =     I_ADDR_MODE ? I_ADDR : {I_ADDR[7:0],I_ADDR[15:8]};always @ (posedge I_SYS_CLK)
beginif(~I_SYS_RSTN)beginR_CNT     <= 3'd0;R_STATE   <= LP_ST_IDLE;R_CMD     <= 6'd0;R_OPR_EN  <= 1'b0;R_TX_DATA <= 8'd0;O_RX_DATA <= 8'd0;O_RW_DONE <= 1'd0;O_ACK     <= 1'd0;endelsebegincase(R_STATE)LP_ST_IDLE :beginR_CNT <= 3'd0;O_ACK <= 1'b0;O_RW_DONE <= 1'b0;if(I_WR_REQ)beginR_STATE <= LP_ST_WR;endelse if(I_RD_REQ)beginR_STATE <= LP_ST_RD;endelsebeginR_STATE <= LP_ST_IDLE;endendLP_ST_WR:beginR_STATE <= LP_ST_WAIT_WR_DONE;case(R_CNT)3'd0:beginTASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_START_REQ,1'b1,I_DEVICE_ID);end3'd1:beginTASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[15:8]);end3'd2:beginTASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[7:0]);end3'd3:beginTASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_STOP_REQ,1'b1,I_WR_DATA);enddefault:beginendendcaseendLP_ST_WAIT_WR_DONE	:beginR_OPR_EN <= 1'b0;if(W_OPR_DONE)beginO_ACK <= O_ACK | W_ACK;case(R_CNT)3'd0:beginR_CNT <= 3'd1;R_STATE <= LP_ST_WR;end3'd1:beginif(I_ADDR_MODE)beginR_CNT <= 3'd2;endelsebeginR_CNT <= 3'd3;endR_STATE <= LP_ST_WR;end3'd2:beginR_CNT <= 3;R_STATE <= LP_ST_WR;end3'd3:beginR_STATE <= LP_ST_WR_DONE;enddefault:beginR_STATE <= LP_ST_IDLE;endendcaseendendLP_ST_WR_DONE :beginO_RW_DONE <= 1'b1;R_STATE   <= LP_ST_IDLE;endLP_ST_RD:beginR_STATE <= LP_ST_WAIT_RD_DONE;case(R_CNT)3'd0:beginTASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_START_REQ,1'b1,I_DEVICE_ID);end3'd1:beginTASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[15:8]);end3'd2:beginTASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[7:0]);end3'd3:beginTASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_START_REQ,1'b1,I_DEVICE_ID | 8'd1);end3'd4:beginTASK_RD_BYTE(LP_CMD_RD_REQ | LP_CMD_NOACK_REQ | LP_CMD_STOP_REQ,1'b1);enddefault://不必理会beginendendcaseendLP_ST_WAIT_RD_DONE	:beginR_OPR_EN <= 1'b0;if(W_OPR_DONE)beginif(R_CNT <= 3'd3)beginO_ACK <= O_ACK | W_ACK;endcase(R_CNT)3'd0:beginR_CNT <= 3'd1;R_STATE <= LP_ST_RD;end3'd1:beginif(I_ADDR_MODE)beginR_CNT <= 3'd2;endelsebeginR_CNT <= 3'd3;endR_STATE <= LP_ST_RD;end3'd2:beginR_CNT <= 3'd3;R_STATE <= LP_ST_RD;end3'd3:beginR_CNT <= 3'd4;R_STATE <= LP_ST_RD;end3'd4:beginR_STATE <= LP_ST_RD_DONE;enddefault:beginR_STATE <= LP_ST_IDLE;endendcaseendendLP_ST_RD_DONE :beginO_RW_DONE <= 1'b1;R_STATE <= LP_ST_IDLE;O_RX_DATA <= W_RX_DATA;end	default	:beginR_STATE <= LP_ST_IDLE;end		endcaseendend
// | ====================================   模块内部任务定义   ====================================
task TASK_WR_BYTE;input [5:0]      I_TASK_CMD;input 		     I_TASK_OPR_EN;input [7:0]	     I_TASK_WR_DATA;beginR_CMD    <= I_TASK_CMD;R_OPR_EN <= I_TASK_OPR_EN;R_TX_DATA<= I_TASK_WR_DATA; end
endtask task TASK_RD_BYTE;input [5:0]      I_TASK_CMD;input 		     I_TASK_OPR_EN;beginR_CMD    <= I_TASK_CMD;R_OPR_EN <= I_TASK_OPR_EN;end
endtask // | ====================================   模块内部模块例化   ====================================IIC_CTRL_MDL #(.P_SYS_CLK_FREQ(P_SYS_CLK_FREQ),.P_IIC_CLK_FREQ(P_IIC_CLK_FREQ)) INST_IIC_CTRL_MDL (.I_SYS_CLK   (I_SYS_CLK),.I_SYS_RSTN  (I_SYS_RSTN),.I_CMD       (R_CMD),.I_OPR_EN    (R_OPR_EN),.I_TX_DATA   (R_TX_DATA),.O_RX_DATA   (W_RX_DATA),.O_OPR_DONE  (W_OPR_DONE),.O_ACK       (W_ACK),.O_IIC_SCLK  (O_IIC_SCLK),.IO_IIC_SDAT (IO_IIC_SDAT));endmodule

仿真

思路

利用和支持 IIC 协议的器件完成验证。Microchip 官网提供了多种自产器件的 Verilog 模型。网址如下:

Verilog Modelsicon-default.png?t=M85Bhttps://www.microchip.com/doclisting/TechDoc.aspx?type=Verilog此处我们选择一个支持IIC通信的存储器,

将该模型添加到设计文件中,在test bench中例化使用,具体见如下代码:

test bench

// |------------------------------ ================================== ------------------------------
// |==============================  	 IIC顶层模块——测试文件  	  ==============================
// |------------------------------ ================================== ------------------------------
// | 创建时间 : 2022-12-25
// | 完成时间 : 2022-12-25
// | 作    者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :测试功能模块:TOP_IIC_MDL
// | 			辅助模块:M24LC04B
// |
// |------------------------------------------------------------------------------------------------
// |----------------------------------------  模块修改历史  ----------------------------------------
// | 修改日期:
// | 修改作者:
// | 修改注解:`timescale 1ns / 1psmodule TB_TOP_IIC_MDL();
// | ====================================  模块可重载参数声明  ==================================== 
parameter 				P_SYS_CLK_FREQ		=			32'd50_000_000;// 系统时钟频率 单位:Hz
parameter				P_IIC_CLK_FREQ 		=			32'd400_000   ;// IIC 时钟频率 单位:Hz// | ==================================== 模块输入输出端口声明 ==================================== 
reg 												   	I_SYS_CLK   ;
reg												   		I_SYS_RSTN  ;reg 													I_WR_REQ;
reg 													I_RD_REQ;reg 					[15:0]							I_ADDR;
reg 													I_ADDR_MODE;// 1:16位地址;0:8位地址reg 					[7:0]							I_WR_DATA;
wire 					[7:0]							O_RX_DATA;reg 					[7:0]							I_DEVICE_ID;wire													O_RW_DONE;
wire			 										O_ACK;    wire 													O_IIC_SCLK;
wire 													IO_IIC_SDAT;// 双向总线配置上拉
pullup PUP(IO_IIC_SDAT);
// | ======================================= 产生测试激励 =======================================
initial I_SYS_CLK = 1'b0;
always #10 I_SYS_CLK = ~I_SYS_CLK;initial
beginI_SYS_RSTN = 1'b0;I_WR_REQ   = 0;I_RD_REQ   = 0;I_ADDR     = 0;I_ADDR_MODE= 0;I_WR_DATA  = 0;I_DEVICE_ID= 0;#(20*100+1);I_SYS_RSTN = 1;#(20*100);TASK_WR_1_BYTE(8'hA0,8'h0A,8'hD1);TASK_WR_1_BYTE(8'hA0,8'h0B,8'hD2);TASK_WR_1_BYTE(8'hA0,8'h0C,8'hD3);TASK_WR_1_BYTE(8'hA0,8'h0F,8'hD4);TASK_RD_1_BYTE(8'hA0,8'h0A);TASK_RD_1_BYTE(8'hA0,8'h0B);TASK_RD_1_BYTE(8'hA0,8'h0C);TASK_RD_1_BYTE(8'hA0,8'h0F);$finish;
end// | ====================================   模块内部任务定义   ====================================
task TASK_WR_1_BYTE;input 	[7:0]	I_TASK_DEVICE_ID;input	[7:0]	I_TASK_ADDR;input 	[7:0]	I_TASK_WR_DATA;beginI_ADDR      = {8'd0,I_TASK_ADDR};I_DEVICE_ID = I_TASK_DEVICE_ID;I_ADDR_MODE = 1'b0;I_WR_DATA   = I_TASK_WR_DATA;I_WR_REQ    = 1'b1;#20;I_WR_REQ    = 1'b0;@(posedge O_RW_DONE);#200;end
endtask task TASK_RD_1_BYTE;input 	[7:0]	I_TASK_DEVICE_ID;input	[7:0]	I_TASK_ADDR;beginI_ADDR      = {8'd0,I_TASK_ADDR};I_DEVICE_ID = I_TASK_DEVICE_ID;I_ADDR_MODE = 1'b0;I_RD_REQ    = 1'b1;#20;I_RD_REQ    = 1'b0;@(posedge O_RW_DONE);#200;end
endtask // | ====================================== 待测试模块例化 ========================================TOP_IIC_MDL #(.P_SYS_CLK_FREQ(P_SYS_CLK_FREQ),.P_IIC_CLK_FREQ(P_IIC_CLK_FREQ)) INST_TOP_IIC_MDL (.I_SYS_CLK   (I_SYS_CLK),.I_SYS_RSTN  (I_SYS_RSTN),.I_WR_REQ    (I_WR_REQ),.I_RD_REQ    (I_RD_REQ),.I_ADDR      (I_ADDR),.I_ADDR_MODE (I_ADDR_MODE),.I_WR_DATA   (I_WR_DATA),.O_RX_DATA   (O_RX_DATA),.I_DEVICE_ID (I_DEVICE_ID),.O_RW_DONE   (O_RW_DONE),.O_ACK       (O_ACK),.O_IIC_SCLK  (O_IIC_SCLK),.IO_IIC_SDAT (IO_IIC_SDAT));M24LC04B INST_M24LC04B (.A0(1'b0), .A1(1'b0),.A2(1'b0), .WP(1'b0), .SDA(IO_IIC_SDAT), .SCL(O_IIC_SCLK), .RESET(~I_SYS_RSTN));endmodule

仿真结果

 

 

参考声明

【1】芯路恒开发板教程


http://chatgpt.dhexx.cn/article/6UrLoUWO.shtml

相关文章

IIC通信协议(硬件实现IIC通信详解I)

IIC通信协议 什么是IIC协议协议层起始信号和停止信号数据的有效性 什么是IIC协议 I2C&#xff08;Inter-Integrated Circuit&#xff09;通讯协议是由 Phiilps 公司开发的两线式串行总线&#xff0c;用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准…

详解通信协议之IIC通信协议

详解通信协议之IIC通信协议 本文结合AT24C02对IIC通信协议原理进行了描述。 IIC通信协议(以AT24C02为例) IIC通讯协议(Inter&#xff0d;Integrated Circuit)是由 Philips 公司开发双向同步半双工串行总线&#xff0c;只需要两根线(SDA、SCL)即可在连接于总线上的器件之间传…

IIC(I2C)通信协议详解

简介 I2C 是飞利浦公司设计的&#xff0c;一种很常见的总线协议&#xff0c; I2C 使用两条线在主控制器和从机之间进行数据通信。一条是 SCL(串行时钟线)&#xff0c;另外一条是SDA(串行数据线)&#xff0c;这两条数据线需要接上拉电阻&#xff0c;总线空闲的时候 SCL 和 SDA …

IIC 通信协议

一、IIC 通信协议介绍 IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线&#xff0c;用于连接微控制器及其外围设备。 如今主要在服务器管理中使用&#xff0c;其中包括单个组件状态的通信。例如管理员可对各个组件进行查询&#xff0c;以管理系统…

三大通信协议(二):IIC通信协议

目录 1. 概念2. 硬件连接3. 数据传输协议3.1 开始信号3.2 地址位3.3 读写位&#xff08;R/W&#xff09;3.4 应答位&#xff08;ACK / NACK&#xff09;3.5 数据位&#xff08;8Bit&#xff09;3.6 停止信号 4. 软件编写4.1 初始化4.2 开始信号4.3 IIC发送一个字节数据4.4 IIC读…

IIC通信协议总结

&#xff08;1&#xff09;概述 I2C(Inter-Integrated Circuit BUS) 集成电路总线&#xff0c;该总线由NXP&#xff08;原PHILIPS&#xff09;公司设计&#xff0c;多用于主控制器和从器件间的主从通信&#xff0c;在小数据量场合使用&#xff0c;传输距离短&#xff0c;任意时…

IIC通信协议,搞懂这篇就够了

注&#xff1a;公众号后台发送 “IIC” 即可获取基于STM32上实现软件模拟IIC的完整代码。 I2C(IIC)属于两线式串行总线&#xff0c;由飞利浦公司开发用于微控制器(MCU)和外围设备(从设备)进行通信的一种总线&#xff0c;属于一主多从(一个主设备(Master)&#xff0c;多个从设备…

IIC通讯协议

一、简介 IIC&#xff08;Inter-integerted Circuit&#xff09;集成电路总线&#xff0c;该通信协议由NXP&#xff08;原PHILIPS&#xff09;公司设计&#xff0c;多用于主控制器和从器件间的主从通信&#xff0c;在小数据量场景下使用&#xff0c;传输距离短&#xff0c;任意…

搜狗搜索正式接入微信公众号

搜狗搜索正式接入微信公众号 6月9日中午消息&#xff0c;搜狗搜索今日宣布正式接入微信公众号数据。用户将在搜狗搜索页面查询和浏览微信公众号文章。 据悉&#xff0c;在搜狗搜索框中输入微信公众号关键词&#xff0c;便可在搜索结果页中查看相关微信公众号的文章列表&#xf…

Python爬虫 selenium自动化 利用搜狗搜索爬取微信公众号文章信息

少年最好的地方就是:虽然嘴上说要放弃,心底却总会憋着一口气。——刘同 文章目录 一、需求和网页分析二、selenium爬虫一、需求和网页分析 URL:https://weixin.sogou.com/ 你是否有特别喜欢的一些公众号文章,比如说关于网易云热评的。 我那么多遗憾,那么多期盼,你知道…

RSS订阅微信公众号初探-feed43

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

python爬虫爬取微信公众号历史文章链接

一、最近公司有了要爬取微信公众号文章链接的需求&#xff0c;之前最初接触爬公众号文章的时候&#xff0c;用的是搜狗微信&#xff0c;在这个上面可以搜到相关的微信公众号文章&#xff0c;但是这些链接是有时效性的&#xff0c;第二天链接就打不开了&#xff08;不知道现在是…

python抓取搜狗微信公众号文章

初学python&#xff0c;抓取搜狗微信公众号文章存入mysql mysql表&#xff1a; 代码&#xff1a; import requests import json import re import pymysql# 创建连接 conn pymysql.connect(host你的数据库地址, port端口, user用户名, passwd密码, db数据库名称, charsetut…

搜狗微信文章采集php,[造轮子]爬取搜狗微信公众号文章

背景&#xff1a;想做一个公众号文章资源APP&#xff0c;发现搜狗有搜索公众号文章功能&#xff0c;果断开撸。http://wxiread.com (用CMS搭了个简易的站)。 Step1.分析栏目及接口 搜狗分了20个栏目&#xff0c;分别是 热门,推荐,段子手,养生堂,私房话… 对应地址从 /pcindex/p…

微信公众号的“温柔一刀”,搜狗微信搜索部分功能将被下线

作为搜狗的单一最大股东&#xff0c;腾讯与搜狗之间的联系向来就很紧密&#xff0c;其中包括搜搜被并入搜狗、微信公众号的搜索功能划分给搜狗、腾讯新闻、QQ浏览器等腾讯系产品的搜索功能全部换成搜狗。腾讯对搜狗的支持是显而易见的&#xff0c;但若有利益冲突的情况下&#…

【scrapy爬虫】最新sogou搜狗搜索 机智操作绕过反爬验证码(搜狗微信公众号文章同理)

前情提要 此代码使用scrapy框架爬取特定“关键词”下的搜狗常规搜索结果&#xff0c;保存到同级目录下csv文件。并非爬取微信公众号文章&#xff0c;但是绕过验证码的原理相同。如有错误&#xff0c;希望大家指正。 URL结构 https://www.sogou.com/web?query{关键词}&p…

利用搜狗抓取微信公众号文章

微信一直是一个自己玩的小圈子&#xff0c;前段时间搜狗推出的微信搜索带来了一丝曙光。搜狗搜索推出了内容搜索和公众号搜索两种&#xff0c;利用后者可以抓取微信公众号的最新内容&#xff0c;看了下还是比较及时的。 每个公众号都有一个openid&#xff0c;最早可以直接利用…

html语言判断水仙花数,水仙花数判断讲解

今天要讲解的这道题是 水仙花数判断 题目要求 本题要求实现一个函数&#xff0c;判断任一整数是否为水仙花数(必须是三位数&#xff0c;且数的每一位上数字的立方和与数本身相等)。例如1531^35^33^3112527153&#xff0c;而1或155则不是水仙花数。如果是水仙花数&#xff0c;则…

水仙花数的求解思路

目录 水仙花数输入一个数&#xff0c;求解从0到这个数的所有水仙花数 解题思路源代码&#xff1a;注意事项 水仙花数 在以前的博客之中写过求解水仙花数的博客 详情见下面链接&#xff1a; 求解水仙花数 在这里就主要写求解水仙花数的思路 输入一个数&#xff0c;求解从0到这…

计算水仙花数有哪些

计算水仙花数有哪些 一、介绍水仙花数二、使用while循环计算水仙花数1.源代码2.运行结果 三、使用for循环计算水仙花数1.执行代码部分2.运行结果3.源代码 一、介绍水仙花数 水仙花数指的是三位整数中每个数的立方之和等于本身的数 二、使用while循环计算水仙花数 1.源代码 …