I2C协议及其Verilog实现
创始人
2025-05-30 06:55:28
0

I2C协议

  IIC 协议是三种最常用的串行通信协议(I2C,SPI,UART)之一,接口包含 SDA(串行数据线)和 SCL(串行时钟线),均为双向端口。I2C 仅使用两根信号线,极大地减少了连接线的数量,支持多主多从,且具有应答机制,因此在片间通信有较多的应用。

  I2C 主要包括四个状态:起始 START,数据传送 SEND,应答 ACK,停止 STOP。

在这里插入图片描述

  • 传输起始

  当 SCL 为高电平,SDA 出现下跳变时,标志着传输的起始。

  • 数据传输

  在传输数据位时,采用大端传输(即先传最高位 MSB),SDA 在SCL 低电平时改变,在 SCL=H 时,必须保持 SDA 稳定

  • 应答

  在传输完 8bit 数据后,Master 须释放 SDA ,Slave 接过 SDA 的控制权,给出应答信号 ACK,当 ACK=L 时,表示本字节数据传输有效。

  • 停止

  当 SCL 为高,SDA 出现上跳变时,标志着传输的结束。

  一次 I2C 传输可以传输多个字节,通常第一个字节为 I2C 设备地址 ADDR(7bit)和读写标志 R/W‾\rm{R/\overline W}R/W(1bit)。一个可能的 I2C 例子如下:

在这里插入图片描述

Verilog实现

  本博文所附代码实现了 I2C Master 以及 I2C Slave 的功能,未涉及具体设备的时序要求,但利用这两个模块可以很容易地实现更具体的功能。比如要实现上例的 I2C 设备写功能,则将 I2C_Master 模块的 data_num 设置为 3,然后在每次 data_req 时 wrdat 依次给出 {I2C_Device_Addr, 0}、Reg_Addr、Data 即可。

I2C Master

/* * file         : I2C_Master.v* author       : 今朝无言* Lab		    : WHU-EIS-LMSWE* date		    : 2023-03-18* version      : v1.0* description  : i2c master*/
module I2C_Master(
input				clk,		//clk 是 SCL 的 4 倍
input				rst_n,input		[7:0]	data_num,
input				tx_en,		//上升沿有效output	reg			data_req,
input		[7:0]	wrdat,output	reg			tx_done,
output	reg			tx_err,		//发送失败inout				SDA,		//串行数据线
inout				SCL			//串行时钟线
);
// START D7 D6 ... D1 D0 ACK STOPlocalparam IDLE		= 8'h01;
localparam START	= 8'h02;	//发送起始,SCL=H期间,SDA=D
localparam SEND		= 8'h04;	//数据发送,注意在此期间,SDA在SCL=H时必须保持稳定,在SCL=L时才允许SDA改变
localparam WAIT		= 8'h08;
localparam ACK		= 8'h10;	//等待Slave的ACK,期待SDA=L & 请求下一个数据
localparam STOP		= 8'h20;	//发送结束,SCL=H期间,SDA=Rreg 	[7:0]	state		= IDLE;
reg 	[7:0]	next_state	= IDLE;reg		[1:0]	clk_cnt		= 2'd0;
reg				send;			//是否处于发送状态(SDA有效,包括START、SEND、STOP阶段,而ACK阶段应释放SDA线)reg		[7:0]	wrdat_buf;
reg		[3:0]	bit_cnt;		//发送的第几位reg		[7:0]	data_num_buf;
reg		[7:0]	data_cnt;		//已发送数据的个数reg				SDA_buf;
reg				SCL_buf;assign	SDA		= (send)? SDA_buf : 1'bz;
assign	SCL		= (~tx_done)? SCL_buf : 1'bz;always @(posedge clk) begin		//SCL时钟分为4段,方便时序控制clk_cnt		<= clk_cnt + 1'b1;		//clk_cnt:	0	1	2	3//SCL	 :	L	H	H	Lcase(clk_cnt)2'd0: beginSCL_buf <= 1'b0;end2'd1: beginSCL_buf <= 1'b1;end2'd2: beginSCL_buf <= 1'b1;end2'd3: beginSCL_buf <= 1'b0;enddefault: beginSCL_buf <= 1'b0;endendcase
endwire			tx_pe;			//tx_en的上升沿
reg				tx_en_d0;
reg				tx_en_d1;assign	tx_pe	= tx_en_d0 & (~tx_en_d1);always @(posedge clk) begintx_en_d0		<= tx_en;tx_en_d1		<= tx_en_d0;
endreg		start_flag;always @(posedge clk or negedge rst_n) beginif(~rst_n) begintx_done		<= 1'b1;wrdat_buf	<= 8'd0;endelse beginif(tx_pe && tx_done) beginstart_flag	<= 1;					//准备发送标志endelse if(clk_cnt==1) beginstart_flag	<= 0;endif(start_flag && clk_cnt==1) begin		//模块工作状态信号tx_done		<= 1'b0;endelse if(next_state == IDLE) begintx_done		<= 1'b1;endelse begintx_done		<= tx_done;endif(tx_pe && tx_done) begindata_num_buf	<= data_num;		//data_num缓冲endelse begindata_num_buf	<= data_num_buf;endif(data_req) beginwrdat_buf	<= wrdat;				//数据缓冲endelse beginwrdat_buf	<= wrdat_buf;endend
endalways @(posedge clk) begincase(next_state)START, SEND, WAIT, STOP: beginsend	<= 1'b1;enddefault: beginsend	<= 1'b0;endendcase
end//--------------------------- State Machine -------------------------------
always @(posedge clk or negedge rst_n) beginif(~rst_n) beginnext_state	<= IDLE;state		<= IDLE;endelse begincase(next_state)IDLE, START, SEND, WAIT, ACK: beginif(clk_cnt == 3) beginstate	<= next_state;endelse beginstate	<= state;endendSTOP: beginif(clk_cnt == 2) beginstate	<= next_state;endelse beginstate	<= state;endenddefault: beginstate	<= IDLE;endendcaseend
endalways @(*) begin					//状态转移case(state)IDLE: beginif(~tx_done) beginnext_state	<= START;endelse beginnext_state	<= IDLE;endendSTART: beginnext_state	<= SEND;endSEND: beginif(bit_cnt == 4'd15) beginnext_state	<= WAIT;endelse beginnext_state	<= SEND;endendWAIT: beginnext_state	<= ACK;endACK: beginif(data_cnt <= data_num_buf) beginnext_state	<= SEND;endelse beginnext_state	<= STOP;endendSTOP: beginnext_state	<= IDLE;enddefault: beginnext_state	<= IDLE;endendcase
endalways @(posedge clk) begincase(next_state)IDLE: beginSDA_buf		<= 1'b1;bit_cnt		<= 4'd0;data_req	<= 1'b0;data_cnt	<= 8'd0;endSTART: beginbit_cnt		<= 4'd7;if(clk_cnt == 2) begin		//SCL=H,SDA=DSDA_buf		<= 1'b0;endif(clk_cnt == 2) begindata_cnt	<= data_cnt + 1'b1;data_req	<= 1'b1;endelse begindata_req	<= 1'b0;endendSEND: begintx_err		<= 1'b0;if(clk_cnt == 0) begin		//SCL=H期间SDA=wrdat,SCL=L期间改变SDASDA_buf		<= wrdat_buf[bit_cnt];bit_cnt		<= bit_cnt - 1'b1;endendWAIT: beginSDA_buf			<= SDA_buf;endACK: beginbit_cnt			<= 4'd7;SDA_buf			<= 1'b0;if(clk_cnt == 1) beginif(SDA == 1'b0) begin	//检查SDA线,是否有Slave的ACK(SDA=L)tx_err	<= 1'b0;endelse begintx_err	<= 1'b1;endendif(data_cnt < data_num_buf && clk_cnt == 2) begindata_req	<= 1'b1;endelse begindata_req	<= 1'b0;endif(clk_cnt == 2) begindata_cnt	<= data_cnt + 1'b1;endendSTOP: beginif(clk_cnt == 2) begin		//SCL=H,SDA=RSDA_buf		<= 1'b1;endenddefault: beginSDA_buf		<= 1'b1;endendcase
endendmodule

I2C Slave

/* * file         : I2C_Slave.v* author       : 今朝无言* Lab		    : WHU-EIS-LMSWE* date		    : 2023-03-18* version      : v1.0* description  : i2c slave*/
module I2C_Slave(
input				clk,				//须远高于SCL(4倍以上)output	reg	[7:0]	rddat,
output				rx_done,
output				data_valid,inout				SDA,				//串行数据线
input				SCL					//串行时钟线
);reg				isACK		= 1'b0;		//接过SDA线控制权,给出应答信号ACK=L
reg		[7:0]	rddat_tmp;
reg				busy;assign	SDA			= isACK? 1'b0 : 1'bz;
assign	rx_done		= ~busy;
assign	data_valid	= (cnt==9)? 1 : 0;always @(SDA) beginif(~SDA & SCL) begin				// SCL=H,SDA=D,接收起始busy	<= 1'b1;endelse if(SDA & SCL) begin			// SCL=H,SDA=R,接收结束busy	<= 1'b0;endelse beginbusy	<= busy;end
endreg				SCL_d0;
wire			SCL_pe;
wire			SCL_ne;reg				SDA_d0;
wire			SDA_pe;reg				busy_d0;
wire			busy_pe;assign	SCL_pe	= SCL & (~SCL_d0);
assign	SCL_ne	= (~SCL) & SCL_d0;
assign	SDA_pe	= SDA & (~SDA_d0);
assign	busy_pe	= busy & (~busy_d0);always @(posedge clk) beginSCL_d0		<= SCL;SDA_d0		<= SDA;busy_d0		<= busy;
endreg		[3:0]	cnt;always @(posedge clk) beginif(busy_pe) begincnt			<= 4'd0;endelse if(SCL_pe) begincnt			<= cnt + 1'b1;endelse if(SCL_ne && cnt == 9) begincnt			<= 0;endelse begincnt			<= cnt;endif(busy_pe) beginrddat_tmp	<= 8'd0;endelse if(SCL_pe && cnt < 8) beginrddat_tmp[7-cnt]	<= SDA;endelse beginrddat_tmp	<= rddat_tmp;endif(SCL_pe && cnt == 8) beginrddat	<= rddat_tmp;endelse beginrddat	<= rddat;endif(busy_pe) beginisACK		<= 1'b0;endelse if(SCL_ne && cnt == 8) beginisACK		<= 1'b1;endelse if(SCL_ne && cnt == 9) beginisACK		<= 1'b0;endelse beginisACK		<= isACK;end
endendmodule

test bench

`timescale 1ns/100psmodule I2C_tb();reg		clk_100M	= 1'b1;
always #5 beginclk_100M	<= ~clk_100M;
endreg		rst_n		= 1'b1;wire	SDA;
wire	SCL;pullup(SDA);	//pullup、pulldown设置上下拉
pullup(SCL);reg		[7:0]	data_num;
reg				tx_en;reg		[7:0]	wrdat		= 8'd0;
wire			data_req;wire			tx_done;
wire			tx_err;wire	[7:0]	rddat;
wire			rx_done;
wire			data_valid;I2C_Master I2C_Master(.clk		(clk_100M),		//clk 是 SCL 的 4 倍.rst_n		(rst_n),.data_num	(data_num),.tx_en		(tx_en),		//上升沿有效.data_req	(data_req),.wrdat		(wrdat),.tx_done	(tx_done),.tx_err		(tx_err),		//发送失败.SDA		(SDA),			//串行数据线.SCL		(SCL)			//串行时钟线
);I2C_Slave I2C_Slave(.clk		(clk_100M),		//须远高于SCL(4倍).rddat		(rddat),.rx_done	(rx_done),.data_valid	(data_valid),.SDA		(SDA),			//串行数据线.SCL		(SCL)			//串行时钟线
);// wrdat
always @(posedge data_req) beginwrdat	<= wrdat + 1'b1;
endinitial begintx_en	<= 1'b0;#100;send(8'd5);#10000;send(8'd13);wait(rx_done);#1000;$stop;
end//启动发送,发送num个数据
task send;input	[7:0]	num;beginwait(rx_done);data_num	<= num;tx_en		<= 1'b1;#100;tx_en	<= 1'b0;end
endtaskendmodule

仿真结果

在这里插入图片描述

相关内容

热门资讯

银行、消金公司助贷余额增速不得... 近日,中国证券报记者从多位业内人士处独家获悉,5月以来,多地金融监管部门对部分中小银行、消金公司下达...
朱鸿接任陈航,担任钉钉科技有限... 消费日报-今朝新闻讯 天眼查显示,6月23日,钉钉科技有限公司发生工商变更,陈航卸任法定代表人、董事...
3日累跌超20%,德创环保:公... 6月25日, 德创环保(603177.SH)公告,公司股票于2026年6月23日、6月24日和6月2...
北京发布2026年第七轮拟供商... 央广网北京6月25日消息(记者门庭婷)6月25日,北京市规划和自然资源委员会网站发布了2026年第七...
开放麦 | 启明创投胡奇:从A... “2026年,创投圈的浪潮再次翻涌:AI从技术概念走进产业深水区,硬科技创业从“小众赛道” 变成“主...
腾讯孙忠怀:在行业转身处 6月24日,2026腾讯视频年度发布在上海举行。腾讯公司副总裁、腾讯在线视频董事长孙忠怀以《在行业转...
加息,突变!美联储,重磅传来!... 美联储政策路径突生变数。 美国商务部经济分析局最新公布的数据显示,5月个人消费支出(PCE)物价指数...
6月合肥上门收金必看!5步避坑... 2026年6月,合肥黄金市场持续高位运行,不少市民翻出家里闲置的旧金饰、投资金条想变现,上门回收因为...
潮汕女富豪挂帅后加码液冷!祥鑫... 潮汕女强人,带着百亿公司加码液冷散热。 6月24日晚间,祥鑫科技(002965.SZ)公告称,公司董...
马斯克向太空要电,GobiX ... 一场关于「去哪里找电」的全球竞赛,正在朝两个方向展开。 作者|周永亮 编辑| 郑玄 「太空光伏是不是...
原料药行业陷入周期低谷 有药企... 每经记者|许立波 每经编辑|魏文艺 “过完年到现在,我们整个团队每个月都在出差,跑遍了亚非拉、欧美市...
家门口筛查白内障!永顺泽家镇暖... 大众卫生报·新湖南客户端6月25日讯(通讯员 彭雪姣)为切实解决辖区老年性白内障患者异地就医奔波、就...
终于等到!油价马上再大跌,这个... 点击添加图片描述(最多60个字) 编辑 各位车主朋友,好消息接二连三! 继6月18日油价大幅下调...
丈量出海新路 世界酒庄影响力指... 长期以来,全球酒庄评价体系由西方机构主导,且大多局限于单一酒种、单一评价维度,这一局面正逐渐被打破。...
峰瑞资本创始合伙人李丰:从资本... “2026年,创投圈的浪潮再次翻涌:AI从技术概念走进产业深水区,硬科技创业从“小众赛道” 变成“主...
原创 A... 迈向成熟,还有茁壮成长的机会。 作者 | 方璐 编辑丨于婞 来源 | 野马财经 2026年6月21日...
为企业解锁出海新通道!亚太中小... 6月24日下午,作为2026年APEC中小企业工商论坛的重要组成部分,亚太中小企业国际化合作发展论坛...
君赛生物港股IPO,增聘兴证国... 跟丰宜科技一样,正冲刺港股IPO的上海君赛生物股份有限公司(简称“君赛生物”)增聘一位整体协调人。 ...
圣邦股份明日上市:暗盘涨24%... 雷递网 雷建平 6月25日 圣邦微电子(北京)股份有限公司(简称:“圣邦股份”,股票代码:“0366...
科技“吃肉”,券商跟着“喝汤”... 当科技持续成为市场核心主线,押中硬科技项目的券商也成为被追逐的焦点。 6月24日,半导体零部件概念股...