I2C总线通讯协议中主机模块的FPGA实现
来源:互联网 发布:牯岭镇 知乎 编辑:程序博客网 时间:2024/04/26 12:40
本人FPGA小白,对FPGA比较感兴趣,前段时间跟某位同学讨论I2C总线通讯协议,我以前写过关于串口和SPI的通讯协议,还没有接触过I2C总线通讯协议。这次就抱着试试看心态,去了解了下I2C总线通讯协议。
结果就是,I2C通讯的复杂程度远超串口和SPI,我查找了一些关于I2C总线通讯的资料内容,比较不开心的是,各类资料对于I2C总线通讯协议的描述不尽相同,我选了其中的两篇我能接受的资料作为这次博客内容的基础,一篇是对I2C总线通讯的基本描述,一篇是其中关于协议通讯中ACK过程的描述,在接下来的内容中,会用到两篇资料中的内容,以下是两篇资料的链接:
1.I2C总线通讯协议及其原理
2.对I2C总线时序的一点理解以及ACK和NACK(NAK)
I2C总线通讯协议概述:
1.信号线描述
通讯协议为串行通讯协议,总共用到两根双向信号线,为SDA与SCL,其中SDA为数据线,SCL为时钟线;总线上通过上拉电阻接正电源,当总线空闲时,两根信号线均为高电平,连接到总线上的任一设备输出低电平时都会使总线拉低,表面两根信号线均为线“与”逻辑。
SCL为时钟线,为OD门,当上升沿时将数据输入到EEPROM中,下降沿时驱动EEPROM输出数据。
SDA也为OD门,输出与其他OD门或者OC门构成线与逻辑。
2.主从设备区分
协议区分主设备与从设备,每个从设备拥有对应于自己的7位地址码,前4位为器件类型,由厂家决定,而后三位则由用户自己定义。同一时间,主设备只能与一个从设备通讯,从设备挂载在总线上的数量由地址码位数以及总线最大电容400pf限制。
主设备在通讯中主要承担提供SCL时钟,控制信息读写流向,决定通讯的开始与结束的任务。
从设备则是提供和接收信息并于主机进行交互。
3.协议的简要内容
3.1 开始和结束信号
协议有自己的通讯帧,每帧都有开始信号与结束信号
开始信号:当SCL为高电平期间,将SDA信号从高电平拉低,则构成开始信号,此时总线将由空闲状态转为被占用状态,各从机将准备好从主机接收数据。
结束信号:当SCL为高电平期间,将SDA信号从低电平拉高,则构成结束信号,此时总线将被释放,从占用状态变为空闲状态,通讯结束。
在通讯过程中,SCL为高电平时,SDA均不允许发生变化,否则将会被视作开始或者结束信号,导致通讯出错。
3.2 ACK状态
ACK状态是I2C区别串口和SPI的一个很大的地方,当发送方发送8位数据后,在第9个SCL时钟上升沿之前需要释放总线(在我写的程序中,我选择在第9个时钟的下降沿),从接收方接收一个来自SDA的信号,该信号在第9个SCL时钟的高电平期间应该保持不变,对于接收方而言,若成功接收这8位数据,在总线释放期间,将SDA线拉低,表示ACK;若无法接收这8位数据,在总线释放期间,需要将SDA线拉高,表示NACK,通知发送方结束本次发送。
如果接收方是主设备,则在接收到从设备发来的最后一位数据后,在结束信号前,需要发送一个NACK状态,以通知从设备发送方释放总线,使主设备可以发送结束信号结束本次通讯。
3.3 I2C总线读写部分
I2C总线的读写过程有相同的部分也有各自区别,对于单个8位数据读写过程而言,从主机角度看,写过程需要写入3个8位数据,分别为从设备地址与写标志(7位从地址,写标志),从设备子寄存器地址(有的设备可能不需要),写入数据;而读过程需要写3个8位数据,读一个8位数据,分别为写从设备地址与写标志,写从设备子寄存器地址,写从设备地址与读标志,读出子寄存器数据。
3.3.1主设备向从设备写
如前述,主设备发送开始信号,接下来发送7位从设备地址和写标志信号(低电平),与之匹配的从机继续通讯过程,之后主机向匹配到的从机发送要写入的子寄存器地址,随后传送写入的数据,最后发送结束信号结束本次通讯。
更加具体的过程可以参看我给的链接一中的资料,这里直接上图:
3.3.2 主设备从从设备读
读过程第一次接触的时候感觉非常的怪异,就我个人的习惯看,我觉得应该只需要改变写过程中的读写标志信号和最后一个字节的信号流向就可以改变读写过程,可事实却不是这样,读过程远比写过程复杂的多。
读过程大致过程如下:
1)发送开始信号
2)发送7位从机地址和写标志信号,接收ACK信号
3)发送8位从机子寄存器地址,接收ACK信号
4)重启开始信号
5)发送7位从机地址和读标志信号,接收ACK信号
6)读出SDA上传过来的从机子寄存器数据,发送NACK信号
7)发送结束信号
4.主机模块FPGA实现与仿真
I2C通讯协议可以有很多种实现方法,可以对硬件的I2C电路控制编程实现,也可以用模拟GPIO的时序方法实现,在这里我用的就是后者,毕竟FPGA直接写接口就是模拟GPIO时序的方式。
软件版本是ISE14.7,仿真工具用的Moesim_10.1c,主要内容包括:
I2C_Master.v-----------I2C通信协议的通用主机读写模块
SI5338_Init.v------------通过I2C通信的方式初始化时钟芯片SI5338,完成将输入25MHz时钟倍频为50MHz的任务
I2C_Master_tb.v-------I2C_Master.v的testbench
`timescale 1ns / 1ps//////////////////////////////////////////////////////////////////////////////////// Company: // Engineer: // // Create Date: 22:40:45 11/20/2017 // Design Name: // Module Name: I2C_Master // Project Name: // Target Devices: // Tool versions: // Description: /* I2C总线通信协议通用模块:SCL SDA 开始信号:SCL高时,SDA拉低 结束信号:SCL高时,SDA拉高 SDA数据在SCL低电平时置位 模块中实际默认开始信号与结束信号在SCL高电平中间产生 SDA数据位改变在SCL低电平的中间产生 SCL时钟频率为200kHz 从机地址可调,模块既支持读也支持写,通过输入管脚控制*/// // Dependencies: //// Revision: // Revision 0.01 - File Created// Additional Comments: ////////////////////////////////////////////////////////////////////////////////////module I2C_Master(
//I2C
I_Clk_in,
I_Rst_n,O_SCL,IO_SDA,//control_sigI_Start, //一次读/写操作开始信号O_Done, //一次读/写操作结束信号I_R_W_SET, //读写控制信号,写为1,读为0I_Slave_Addr,//从机地址I_R_W_Data,//读写控制字16位I_R_W_Data[15:8]->reg_addr,I_R_W_Data[7:0]->W_data,读状态则可默认为7'b0 O_Data, //读到的数据,当O_Done拉高时数据有效O_Error //检测传输错误信号,当出现从机未响应,从机不能接收数据等情况时,拉高电平 );//I/OinputI_Clk_in;inputI_Rst_n;outputO_SCL;inoutIO_SDA;inputI_Start;outputO_Done;input [6:0] I_Slave_Addr;inputI_R_W_SET;input [15:0]I_R_W_Data;output [7:0] O_Data;output O_Error;/******时钟定位模块(测试时时钟为50MHz),定位SCL的高电平中心,与SCL的低电平中心,产生100kHz的SCL*******/parameter Start_Delay=9'd60;//开始时SDA拉低电平持续的时间,共用计数器下应小于SCL_HIGH2LOW-1parameter Stop_Delay=9'd150;//一次读/写结束后SDA拉高电平的时间,共用计数器下应小于SCL_HIGH2LOW-1parameter SCL_Period=9'd499;//测试板时钟为50MHz,100KHz为500个Clkparameter SCL_LOW_Dest=9'd374;//时钟判定高电平在前,低电平在后,低电平中央为3/4个周期,375个Clkparameter SCL_HIGH2LOW=9'd249;//电平翻转位置,1/2个SCL周期,250个Clkparameter ACK_Dect=9'd124; //SCL高电平中间位置,用于检测ACK信号reg [8:0]R_SCL_Cnt;reg R_SCL_En;assign O_SCL=(R_SCL_Cnt<=SCL_HIGH2LOW)?1'b1:1'b0;//SCL 时钟输出always @ (posedge I_Clk_in or negedge I_Rst_n)beginif (~I_Rst_n) begin R_SCL_Cnt<=9'b0; endelse begin if (R_SCL_En) if (R_SCL_Cnt==SCL_Period) R_SCL_Cnt<=9'b0; else R_SCL_Cnt<=R_SCL_Cnt+9'b1; else R_SCL_Cnt<=9'b0; endend/******SDA读写控制模块******/reg [5:0] R_State;reg R_SDA_I_O_SET;//SDA双向选择I/O口 1为输出,0为输入reg R_SDA_t; //SDA的输出端口reg O_Done; //结束信号reg [7:0] O_Data; //读到的数据reg O_Error;//传输错误指示信号/****状态定义*****/parameter Start=6'd0; //一次读写开始的状态parameter ReStart=6'd34; //读操作入口状态parameter Stop=6'd56; //发送停止位状态always @ (posedge I_Clk_in or negedge I_Rst_n)beginif (~I_Rst_n) begin R_SCL_En<=1'b0; //计数时钟停止 R_State<=6'd0; R_SDA_I_O_SET<=1'b1;//默认设置为输出管脚 R_SDA_t<=1'b1; //SDA输出默认拉高 O_Data<=8'b0; O_Done<=1'b0; O_Error<=1'b0; endelse begin if (I_Start) //当开始信号置高时表示I2C通信开始 begincase(R_State) Start: //启动位 begin R_SCL_En<=1'b1; O_Error<=1'b0;//每次重新下一次传输时,清除错误标志位 if (R_SCL_Cnt==Start_Delay) begin R_SDA_t<=1'b0; //SCL高电平时拉低 R_State<=R_State+6'd1; end else begin R_SDA_t<=1'b1; R_State<=R_State; end end 6'd1,6'd2,6'd3,6'd4,6'd5,6'd6,6'd7: //写入7位从机地址 begin if (R_SCL_Cnt==SCL_LOW_Dest)begin R_SDA_t<=I_Slave_Addr[6'd7-R_State];//从MSB-LSB写入输入端从机地址 R_State<=R_State+6'd1;end else R_State<=R_State; end 6'd8: //写入写标志(0) begin if (R_SCL_Cnt==SCL_LOW_Dest) beginR_SDA_t<=1'b0;R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd9: //ACK状态 begin if (R_SCL_Cnt==SCL_HIGH2LOW) //在第8个时钟的下降沿释放SDA beginR_SDA_I_O_SET<=1'b0;R_State<=R_State+6'd1; end elseR_State<=R_State; end 6'd10: //在第9个时钟高电平中心检测ACK信号是否为0,如果为1,则表示从机未应答,进入结束位 begin if (R_SCL_Cnt==ACK_Dect) begin O_Error<=IO_SDA; //检测从机是否响应 R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd11: begin if (R_SCL_Cnt==SCL_HIGH2LOW) //在第9个时钟的下降沿重新占用SDA,准备发送从机子寄存器地址 begin R_SDA_I_O_SET<=1'b1; R_State<=(O_Error)?Stop:(R_State+6'd1); R_SDA_t<=1'b0; endelse R_State<=R_State; end 6'd12,6'd13,6'd14,6'd15,6'd16,6'd17,6'd18,6'd19: //写入8位寄存器地址 begin if (R_SCL_Cnt==SCL_LOW_Dest)begin R_SDA_t<=I_R_W_Data[6'd27-R_State];//从MSB-LSB写入寄存器地址 I_R_W_Data[15:8] R_State<=R_State+6'd1;end else R_State<=R_State; end 6'd20: //ACK状态 begin if (R_SCL_Cnt==SCL_HIGH2LOW)//在第8个时钟的下降沿释放SDA begin R_SDA_I_O_SET<=1'b0; R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd21: //检测ACK beginif (R_SCL_Cnt==ACK_Dect) begin O_Error<=IO_SDA;//检测从机是否响应 R_State<=R_State+6'd1; endelse R_State<=R_State; end 6'd22: begin if (R_SCL_Cnt==SCL_HIGH2LOW) //在第9个时钟的下降沿重新占用SDA,区分接下来该发送数据还是读数据 begin R_SDA_I_O_SET<=1'b1; R_State<=(O_Error)?Stop:((I_R_W_SET)?(R_State+6'd1):ReStart); //从机状态 R_SDA_t<=(O_Error|I_R_W_SET)?1'b0:1'b1; //此处拉高SDA信号是为读状态重启开始信号做准备 end else R_State<=R_State; end 6'd23,6'd24,6'd25,6'd26,6'd27,6'd28,6'd29,6'd30://写入8位数据地址 beginif (R_SCL_Cnt==SCL_LOW_Dest) begin R_SDA_t<=I_R_W_Data[6'd30-R_State];//从MSB-LSB写入8位数据地址 R_State<=R_State+6'd1; endelse R_State<=R_State; end 6'd31: //ACK状态 begin if (R_SCL_Cnt==SCL_HIGH2LOW)//在第8个时钟的下降沿释放SDA begin R_SDA_I_O_SET<=1'b0; R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd32://检测ACK begin if (R_SCL_Cnt==ACK_Dect) begin O_Error<=IO_SDA;//检测从机是否响应 R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd33: begin if (R_SCL_Cnt==SCL_HIGH2LOW)//在第9个时钟的下降沿重新占用SDA,准备发送停止位 begin R_SDA_I_O_SET<=1'b1; R_SDA_t<=1'b0;//先拉低SDA信号 R_State<=Stop;//跳转到结束位发送状态 end else R_State<=R_State; end ReStart://主机读状态入口 初始时需要重启开始状态 begin if (R_SCL_Cnt==Start_Delay) begin R_SDA_t<=1'b0; //SCL高电平时拉低 R_State<=R_State+6'd1; end else begin R_SDA_t<=1'b1; R_State<=R_State; end end 6'd35,6'd36,6'd37,6'd38,6'd39,6'd40,6'd41://发送从机7位地址 begin if (R_SCL_Cnt==SCL_LOW_Dest) begin R_SDA_t<=I_Slave_Addr[6'd41-R_State];//从MSB-LSB写入输入端从机地址 R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd42://写入读标志(1) begin if (R_SCL_Cnt==SCL_LOW_Dest) begin R_SDA_t<=1'b1;//写入读地址标志 R_State<=R_State+6'd1; end else R_State<=R_State; end6'd43: //ACK状态 begin if (R_SCL_Cnt==SCL_HIGH2LOW)//在第8个时钟的下降沿释放SDA begin R_SDA_I_O_SET<=1'b0;R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd44://ACK检测 begin if (R_SCL_Cnt==ACK_Dect) begin O_Error<=IO_SDA; R_State<=R_State+6'd1; end else R_State<=R_State; end6'd45://之后需要一直读取数据,所以SDA总线这里需要保持输入状态 begin if (R_SCL_Cnt==SCL_HIGH2LOW)//在第9个时钟下降沿保持SDA总线的释放状态begin R_SDA_I_O_SET<=(O_Error)?1'b1:1'b0;//若前次ACK检测通过,则保持SDA总线释放状态,不 通过则占用SDA总线用来发送停止位 R_State<=(O_Error)?Stop:(R_State+6'd1); R_SDA_t<=1'b0; end else R_State<=R_State; end6'd46,6'd47,6'd48,6'd49,6'd50,6'd51,6'd52,6'd53://8个时钟信号高电平中间依次从SDA上读取数据 begin if (R_SCL_Cnt==ACK_Dect) begin O_Data<={O_Data[6:0],IO_SDA};//从MSB开始读入数据 R_State<=R_State+6'd1; end else R_State<=R_State; end6'd54://读入8位数据后,主机需要向外发送一个NACK信号 begin if (R_SCL_Cnt==SCL_HIGH2LOW) begin R_SDA_I_O_SET<=1'b1;//主机重新占用SDA R_SDA_t<=1'b1; R_State<=R_State+6'd1; end else R_State<=R_State; end6'd55://在第9个时钟下降沿持续占用总线,拉低SDA,开始发送结束位 begin if (R_SCL_Cnt==SCL_HIGH2LOW) begin R_SDA_t<=1'b0; R_State<=R_State+6'd1; end else R_State<=R_State; endStop: //发送停止位 begin if (R_SCL_Cnt==Stop_Delay) begin R_SDA_t<=1'b1; R_State<=R_State+6'd1; end else R_State<=R_State; end6'd57: //停止时钟,同时输出Done信号,表示一次读写操作完成 begin R_SCL_En<=1'b0; O_Done<=1'b1;//拉高Done信号 R_State<=R_State+6'd1; end6'd58: begin O_Done<=1'b0;//拉低Done信号 R_State<=Start; enddefault: begin R_SCL_En<=1'b0;//计数时钟停止 R_State<=6'd0; R_SDA_I_O_SET<=1'b1;//默认设置为输出管脚 R_SDA_t<=1'b1;//SDA输出默认拉高 O_Done<=1'b0; endendcase end else //开始信号无效时,回到初始设置 begin R_SCL_En<=1'b0; //计数时钟停止 R_State<=6'd0; R_SDA_I_O_SET<=1'b1;//默认设置为输出管脚 R_SDA_t<=1'b1; //SDA输出默认拉高 O_Done<=1'b0; end endend/*******配置三态门信号******/assign IO_SDA=(R_SDA_I_O_SET)?R_SDA_t:1'bz;endmodule
`timescale 1ns / 1ps////////////////////////////////////////////////////////////////////////////////// Company: // Engineer://// Create Date: 19:10:07 11/21/2017// Design Name: I2C_Master// Module Name: F:/verilog_demo/I2C_Bus/I2C_Master_tb.v// Project Name: I2C_Bus// Target Device: // Tool versions: // Description: //// Verilog Test Fixture created by ISE for module: I2C_Master//// Dependencies:// // Revision:// Revision 0.01 - File Created// Additional Comments:// ////////////////////////////////////////////////////////////////////////////////module I2C_Master_tb;// Inputsreg I_Clk_in;reg I_Rst_n;reg I_Start;reg I_R_W_SET;reg [6:0] I_Slave_Addr;reg [15:0] I_R_W_Data;// Outputswire O_SCL;wire O_Done;wire [7:0] O_Data; wire O_Error;// Bidirswire IO_SDA;// Instantiate the Unit Under Test (UUT)I2C_Master uut (.I_Clk_in(I_Clk_in), .I_Rst_n(I_Rst_n), .O_SCL(O_SCL), .IO_SDA(IO_SDA), .I_Start(I_Start), .O_Done(O_Done), .I_R_W_SET(I_R_W_SET), .I_Slave_Addr(I_Slave_Addr), .I_R_W_Data(I_R_W_Data), .O_Data(O_Data),.O_Error(O_Error));//重定义参数大小,减少仿真时间defparam uut.Start_Delay=8'd4;defparam uut.Stop_Delay=8'd4;defparam uut.SCL_Period=8'd19;defparam uut.SCL_LOW_Dest=8'd14;defparam uut.SCL_HIGH2LOW=8'd9;defparam uut.ACK_Dect=8'd5;initial begin// Initialize InputsI_Clk_in = 0;I_Rst_n = 0;I_Start = 0;I_R_W_SET=1'b1;I_Slave_Addr = 0;#3 I_Rst_n=1'b1; I_Start=1'b1; I_Slave_Addr=7'b010_1100;//从机地址 I_R_W_Data=16'b0010_0001_0010_0011;//I_R_W_Data[15:8]->reg addr I_R_W_Data[7:0]-> data end /***CLK***/always #1 I_Clk_in=~I_Clk_in;/****Test_Vector****/reg [6:0] R_State; reg [7:0] WR_Data; reg [7:0] RD_Data;//建立新的三态门reg SDA_t;reg SDA_SET;wire IO_SDA_S;assign IO_SDA_S=(SDA_SET)?SDA_t:1'bz;//两个三态门相连assign IO_SDA=IO_SDA_S;always @ (posedge O_SCL or negedge I_Rst_n)begin if (~I_Rst_n) begin R_State<=7'd0; WR_Data<=8'b0; RD_Data<=8'b0101_0110; SDA_SET<=1'b0; //默认为输入 SDA_t<=1'b0; endelse begin case(R_State) 7'b0,7'd1,7'd2,7'd3,7'd4,7'd5,7'd6: //接收从机地址和读写标志 R_State<=R_State+7'd1; 7'd7: //第8个脉冲周期 begin #20 SDA_SET<=1'b1;//三态转输出 SDA_t<=1'b0; R_State<=R_State+7'd1; end 7'd8://返回ACK信号 begin #20 SDA_SET<=1'b0; //三态门转输入 R_State<=R_State+7'd1; end 7'd9,7'd10,7'd11,7'd12,7'd13,7'd14,7'd15://接收寄存器地址 R_State<=R_State+7'd1; 7'd16: begin #20 SDA_SET<=1'b1;//三态转输出 SDA_t<=1'b0; R_State<=R_State+7'd1; end 7'd17: //返回ACK信号 begin #20 SDA_SET<=1'b0; //三态门转输入 R_State<=R_State+7'd1; end 7'd18,7'd19,7'd20,7'd21,7'd22,7'd23,7'd24://接收数据 begin WR_Data<={WR_Data[6:0],IO_SDA}; R_State<=R_State+7'd1; end 7'd25: begin WR_Data<={WR_Data[6:0],IO_SDA}; #20 SDA_SET<=1'b1;//三态转输出 SDA_t<=1'b0; //返回ACK信号 R_State<=R_State+7'd1; end 7'd26: //ACK begin #20 SDA_SET<=1'b0; //三态门转输入 R_State<=R_State+7'd1; end 7'd27: //终止位 begin #18 I_R_W_SET<=1'b0; //运行模式转为读,同时Start关掉 I_Start=1'b0; //关闭模块 #100 I_Start=1'b1; //Start开启 进行一次完整读过程 R_State<=R_State+7'd1; end 7'd28,7'd29,7'd30,7'd31,7'd32,7'd33,7'd34://接收从机地址 R_State<=R_State+7'd1; 7'd35: begin #20 SDA_SET<=1'b1;//三态转输出 SDA_t<=1'b0; R_State<=R_State+7'd1; end 7'd36: //返回ACK信号 begin #20 SDA_SET<=1'b0; //三态门转输入 R_State<=R_State+7'd1; end 7'd37,7'd38,7'd39,7'd40,7'd41,7'd42,7'd43: //接收读寄存器地址 R_State<=R_State+6'd1; 7'd44: //第8个脉冲 begin #20 SDA_SET<=1'b1;//三态转输出 SDA_t<=1'b0; R_State<=R_State+7'd1; end 7'd45://返回ACK信号 begin #20 SDA_SET<=1'b0;//三态转输入 R_State<=R_State+7'd1; end 7'd46: R_State<=R_State+7'd1; 7'd47,7'd48,7'd49,7'd50,7'd51,7'd52,7'd53://接收从机地址 R_State<=R_State+7'd1; 7'd54://第8个脉冲 begin#20 SDA_SET<=1'b1;//三态转输出 SDA_t<=1'b0;//返回ACK信号 R_State<=R_State+7'd1; end 7'd55://第9个脉冲 begin#30 SDA_t<=RD_Data[7]; R_State<=R_State+7'd1; end 7'd56,7'd57,7'd58,7'd59,7'd60,7'd61,7'd62://输出寄存器内数据 begin #30 SDA_t<=RD_Data[7'd62-R_State]; R_State<=R_State+7'd1; end 7'd63://第8个脉冲 begin#20 SDA_SET<=1'b0;//三态转输入 R_State<=R_State+7'd1; end 7'd64: //第9个脉冲 begin#20 R_State<=R_State+7'd1; end 7'd65://STOP begin #18 I_R_W_SET<=1'b1; //运行模式转为写,同时Start关掉 I_Start=1'b0; #100 I_Start=1'b1; //Start开启 进行一次NACK的写过程 R_State<=R_State+7'd1; end 7'd66,7'd67,7'd68,7'd69,7'd70,7'd71,7'd72://写入7位从机地址 R_State<=R_State+7'd1; 7'd73://第8个脉冲 begin#20 SDA_SET<=1'b1; SDA_t<=1'b1;//发送NACK信号 R_State<=R_State+7'd1; end 7'd74://第9个脉冲 begin #20 SDA_SET<=1'b0; R_State<=R_State+7'd1; end 7'd75://Stop begin #10 I_R_W_SET<=1'b1; //运行模式为写,同时Start关掉I_Start=1'b0; #100 R_State<=R_State;//结束仿真 enddefault: R_State<=6'b0;endcase endendendmodule
`timescale 1ns / 1ps//////////////////////////////////////////////////////////////////////////////////// Company: // Engineer: // // Create Date: 22:28:47 11/21/2017 // Design Name: // Module Name: SI5338_Init // Project Name: // Target Devices: // Tool versions: // Description: // 使用I2C总线协议控制SI5338时钟芯片的寄存器初始化,由于只需要写,不使用I2C_Master通用读写模块,太笨重了。// Dependencies: //// Revision: // Revision 0.01 - File Created// Additional Comments: ////////////////////////////////////////////////////////////////////////////////////module SI5338_Init( //I2C I_Clk_in, I_Rst_n, O_SCL, //SCL时钟线 IO_SDA, //SDA双向数据总线 // O_Init_Done //初始化完成指示位.初始化后输出一个高电平 );//I/Oinput I_Clk_in;input I_Rst_n;output O_SCL;inout IO_SDA;output O_Init_Done;/******时钟定位模块(测试时钟25MHz),定位SCL的高电平中心,与SCL的低电平中心,产生100kHz的SCL*******/parameter Start_Delay=8'd24;//开始时SDA拉低电平持续的时间,共用计数器下应小于SCL_HIGH2LOW-1parameter Stop_Delay=8'd99;//一次读/写结束后SDA拉高电平的时间,共用计数器下应小于SCL_HIGH2LOW-1parameter SCL_Period=8'd249;//测试板时钟为25MHz,100KHz为250个Clkparameter SCL_LOW_Dest=8'd187;//时钟判定高电平在前,低电平在后,低电平中央为3/4个周期,187.5个Clkparameter SCL_HIGH2LOW=8'd124;//电平翻转位置,1/2个SCL周期,125个Clkreg [7:0] R_SCL_Cnt;reg R_SCL_En;assign O_SCL=(R_SCL_Cnt<=SCL_HIGH2LOW)?1'b1:1'b0;//SCL 时钟输出always @ (posedge I_Clk_in or negedge I_Rst_n)beginif (~I_Rst_n) begin R_SCL_Cnt<=8'b0; endelse begin if (R_SCL_En) if (R_SCL_Cnt==SCL_Period) R_SCL_Cnt<=8'b0; else R_SCL_Cnt<=R_SCL_Cnt+8'b1; else R_SCL_Cnt<=8'b0; endend/***写入延迟计数器***/parameter T_Delay=8'd249;reg R_Delay_En;reg [7:0] R_Delay_Cnt;always @ (posedge I_Clk_in or negedge I_Rst_n)begin if (~I_Rst_n) begin R_Delay_Cnt<=8'd0; end else begin if (R_Delay_En) R_Delay_Cnt<=R_Delay_Cnt+8'd1; elseR_Delay_Cnt<=8'd0; endend/***I2C写模块***/parameter Slave_Addr=7'b111_0000; //从机地址reg [8:0] R_Word_Cnt; //保存写入的子寄存器个数reg [5:0] R_State;reg [7:0] R_Rom_Addr_Addr; //写入寄存器地址的地址reg [7:0] R_Rom_Data_Addr; //写入数据的地址reg R_I_O_SET; //SDA管脚输入输出选择线reg R_SDA_t; //三态门的输出管教reg O_Init_Done; //指示芯片内部寄存器是否初始化完成wire[6:0] W_Slave_Addr; //从机地址wire[7:0] W_Reg_Addr; //从机子寄存器地址wire[7:0] W_Reg_Data; //子寄存器数据assign W_Slave_Addr=Slave_Addr;always @ (posedge I_Clk_in or negedge I_Rst_n)begin if (~I_Rst_n) beginR_State<=6'b0;R_SCL_En<=1'b0;R_Delay_En<=1'b0;R_Word_Cnt<=5'b0;R_Rom_Addr_Addr<=8'b0;R_Rom_Data_Addr<=8'b0;R_SDA_t<=1'b1; //SDA默认拉高R_I_O_SET<=1'b1; //默认SDA为输出O_Init_Done<=1'b0; end else begin case (R_State) 6'd0: //启动位 begin R_SCL_En<=1'b1; //SCL时钟启动 R_Delay_En<=1'b0; //延迟时钟关闭 if (R_SCL_Cnt==Start_Delay)begin R_SDA_t<=1'b0; //启动位时,在SCL高电平拉低SDA R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd1,6'd2,6'd3,6'd4,6'd5,6'd6,6'd7: //写入从机7位地址 begin if (R_SCL_Cnt==SCL_LOW_Dest) begin R_SDA_t<=W_Slave_Addr[6'd7-R_State]; //从MSB-LSB顺序写入 R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd8: //写入写标志0 begin if (R_SCL_Cnt==SCL_LOW_Dest) begin R_SDA_t<=1'b0; //从MSB-LSB顺序写入
R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd9: //第8个时钟下降沿处开始(跳过)检测ACK begin if (R_SCL_Cnt==SCL_HIGH2LOW) begin R_I_O_SET<=1'b0; //SDA输出转输入 R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd10: //第9个时钟下降沿处停止检测ACK begin if (R_SCL_Cnt==SCL_HIGH2LOW) begin R_I_O_SET<=1'b1; //占用总线用于下次发送 R_SDA_t<=1'b0; R_State<=R_State+6'd1; endelse R_State<=R_State; end 6'd11,6'd12,6'd13,6'd14,6'd15,6'd16,6'd17,6'd18://写入从机子寄存器8位地址 begin if (R_SCL_Cnt==SCL_LOW_Dest) begin R_SDA_t<=W_Reg_Addr[6'd18-R_State]; //从MSB-LSB写入子寄存器地址 R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd19: //第8个时钟下降沿开始(跳过)检测ACKbegin if (R_SCL_Cnt==SCL_HIGH2LOW) begin R_I_O_SET<=1'b0; //SDA输出转输入 R_State<=R_State+6'd1; end else R_State<=R_State;end 6'd20: begin if (R_SCL_Cnt==SCL_HIGH2LOW) begin R_I_O_SET<=1'b1; //占用总线用于下次发送 R_SDA_t<=1'b0; R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd21,6'd22,6'd23,6'd24,6'd25,6'd26,6'd27,6'd28: //写入子寄存器数据 begin if (R_SCL_Cnt==SCL_LOW_Dest) begin R_SDA_t<=W_Reg_Data[6'd28-R_State]; //从MSB-LSB顺序写入子寄存器中数值 R_State<=R_State+6'd1; end elseR_State<=R_State; end 6'd29://第8个时钟下降沿开始(跳过)检测ACK begin if (R_SCL_Cnt==SCL_HIGH2LOW) begin R_I_O_SET<=1'b0; //SDA输出转输入 R_State<=R_State+6'd1;end else R_State<=R_State; end 6'd30: begin if (R_SCL_Cnt==SCL_HIGH2LOW) begin R_I_O_SET<=1'b1; //占用总线用于下次发送 R_SDA_t<=1'b0; R_State<=R_State+6'd1; end else R_State<=R_State; end 6'd31: //停止位 begin if (R_SCL_Cnt==Stop_Delay) begin R_SDA_t<=1'b1; //SCL高电平期间拉高SDA总线表示停止信号 R_SCL_En<=1'b0;//SCL时钟使能关闭 R_Delay_En<=1'b1;//由于数据写入从机寄存器需要时间,在结束位发送后,延迟一段时间再次开始下一次传输 R_State<=R_State+6'd1;//开始进入等待数据写入寄存器状态 end else R_State<=R_State; end 6'd32: begin if (R_Delay_Cnt==T_Delay) begin R_Word_Cnt<=R_Word_Cnt+8'd1; R_Delay_En<=1'b0; //定时时间到,则定时时钟停止 if (R_Word_Cnt==8'd252) //当全部寄存器写入时,不在返回初始状态,初始化完成标志拉高 begin R_State<=R_State; O_Init_Done<=1'b1; end else begin R_State<=6'd0; R_Rom_Addr_Addr<=R_Rom_Addr_Addr+8'd1; R_Rom_Data_Addr<=R_Rom_Data_Addr+8'd1; end end elseR_State<=R_State; end default: begin R_State<=6'b0; R_SCL_En<=1'b0; R_Delay_En<=1'b0; R_Word_Cnt<=5'b0; R_SDA_t<=1'b1; //SDA默认拉高 R_I_O_SET<=1'b1; //默认SDA为输出 O_Init_Done<=1'b0; end endcase endend/***三态门定义***/assign IO_SDA=(R_I_O_SET)?R_SDA_t:1'bz;/***SI5338数据模块***/ROM_Si5338_addr_252x8bit ROM_Reg_Addr ( .clka(I_Clk_in), // input clka .addra(R_Rom_Addr_Addr), // input [7 : 0] addra .douta(W_Reg_Addr) // output [7 : 0] douta);ROM_Si5338_data_252x8bit ROM_Reg_Data ( .clka(I_Clk_in), // input clka .addra(R_Rom_Data_Addr), // input [7 : 0] addra .douta(W_Reg_Data) // output [7 : 0] douta);endmodule
- I2C总线通讯协议中主机模块的FPGA实现
- I2C协议master设备的FPGA实现
- I2C串行总线协议的VHDL实现
- I2C总线协议的verilog实现
- PCI总线协议的FPGA实现及驱动设计
- Linux中编写自己的I2C总线模块
- 1.1 SATA主机协议的FPGA实现之准备工作
- 1.2 SATA主机协议的FPGA实现之物理层设计
- I2C总线驱动程序的实现
- 对I2C总线协议的一些理解
- I2C总线协议相关的函数详解
- 对I2C总线协议的一些理解
- I2C总线协议详解
- I2C总线协议(转载)
- I2C总线协议详解
- I2C串行总线协议
- i2c总线协议总结
- I2C总线协议
- ubuntu linux zip和unzip类命令详解
- Opencv显示创建Mat对象的七种方式
- MarkDown语法入门
- Scala集合
- SpringMvc学习(二)
- I2C总线通讯协议中主机模块的FPGA实现
- 刘汝佳p35,2-5(分数化小数)算法竞赛入门经典第二版
- JNI方法的静态注册和动态注册RegisterNatives
- 抽象类(abstract class)和接口(interface)分析
- 《Oracle 12c特性解读-容器数据库和灾备》金牌客座讲师主讲,进阶高级DBA必修
- Ubuntu (Debian) 编译安装Mariadb过程,亲测,有坑!
- VIM编辑器常用命令50例大全
- springMVC从上传的Excel文件中读取数据
- 传滴滴派驻ofo多名高管集体休假 小鸣单车CEO离职99%员工被裁