IIC总线是两线式串行半双工总线,是一种多主机的总线,IIC总线由串行数据线(SDA)和串行时钟线(SCL)构成,总线的控制权归控制时钟线(SCL)的器件所有,尽管串行总线并没有并行总线的吞吐能力,但它们只要很少的配线和IC连接管脚,IIC总线可以使快速器件和慢速器件通讯,传输速率标准模式下可达100Kbit/s,在快速模式下可达400Kbit/s,在高速模式下可达3.4Mbit/s,连接到总线上的接口数量只由总线电容400pF的限制决定。
1、开始信号和结束信号
开始信号:当时钟总线SCL为高电平时,数据线SDA由高电平向低电平跳变,表示数据传输开始;
结束信号:当SCL线为高电平时,SDA线从低电平向高电平跳变,表示数据传输结束。
注意:开始和结束信号都是由主器件产生的,在开始信号产生以后,总线则处于忙状态,其它器件不能再产生开始信号,主器件产生结束信号以后,退出主器件角色,经过一段时间,总线被认为是空闲的,这是其它器件就可以再产生开始信号,另外,SDA线上的数据必须在时钟的高电平周期保持稳定 数据线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。IIC总线发送器送到SDA线上的每个字节必须为8位长,传送时高位在前,低位在后。
IIC总线有些应用可以没有应答信号,只需要开始信号和结束信号即可。故此处实现的总线没有应答信号。
本实验是通过串口将数据发送给FPGA,然后FPGA再将数据通过IIC总线传给STM32,然后STM32通过串口将接收到的数据在另一台电脑上打印,程序经过验证,此处只讲解FPGA的IIC的实现。
首先,看数据的RTL图,首先采用异步复位,同步释放的复位方式,然后是和电脑通讯的串口模块RX:UU2,然后数据传输的FIFO中,最后IIC:UU4模块读取FIFO中的数据,并将其发送出去。
此处其他模块不再做详解,只介绍IIC模块,
IIC模块由时钟产生模块IIC_CLK:UU5和数据控制模块IIC_DATA:UU6构成,其中IIC_DATA模块在检测到FIFO中有数据时,将通过使能信号iic_start_flag启动时钟产生模块IIC_CLK,时钟产生模块IIC_CLK产生时钟iic_clk和iic_clk_dvi,其中iic_clk主要用于外部和内部时候在那个同步,iic_clk_dvi主要用于数据控制模块IIC_DATA当中将要发送的数据的变化。
下面是数据发送仿真图:
附上IIC模块代码:
IIC_CLK模块:
module IIC_CLK( clk,sys_rst_n, iic_clk, iic_start_flag, iic_clk_dvi, // iic_data_en ); input clk; input sys_rst_n;//系统复位信号 input iic_start_flag;//iic开始标志,高电平有效,且有效期间要维持高电平 output iic_clk_dvi;//记录iic时钟的3/4时钟节点,作为数据的变换标志,产生一个时钟的高电平,只是数据可以变化 //output iic_data_en;//表示已经开始数据传输 output iic_clk; //parameter clk_freq = 20_000_000;//20MHz parameter iic_freq = 50;//400Khz parameter iic_data_change = 38;//数据变化时间节点 reg [7:0] iic_clk_r; reg [7:0] iic_clk_dvi_r; //reg iic_data_en_r; //------------------------------------------------- //iic的时钟技计数 always@(posedge clk or negedge sys_rst_n) begin if(!sys_rst_n) begin iic_clk_r <= 8'b0; end else if(iic_start_flag) begin if(iic_clk_r > iic_freq) iic_clk_r = 8'd0; else iic_clk_r <= iic_clk_r + 1'b1; end else iic_clk_r = 8'd0; end //-------------------------------------------------- //数据变化节点的计数,数据传输开始标志 always@(posedge clk or negedge sys_rst_n) begin if(!sys_rst_n)begin iic_clk_dvi_r <= 8'b0; end else if(iic_start_flag) begin if(iic_clk_r > iic_data_change) iic_clk_dvi_r <= 8'b0; else iic_clk_dvi_r <= iic_clk_dvi_r + 1'b1; end else iic_clk_dvi_r <= 8'b0; end //------------------------------------------------- reg [3:0] i; always@(posedge clk or negedge sys_rst_n) if(!sys_rst_n) i <= 4'd0; else if(iic_clk_r == iic_freq) i <= i + 1'b1; else if(!iic_start_flag) i <= 4'd0; //------------------------------------------------- //数据传输开始标志 assign iic_clk = (iic_start_flag&&(i < 4'd9))?((iic_clk_r <= iic_freq/2)?1'b1:1'b0):1'b1;//当iic开始标志有效时,开始iic时钟翻转 assign iic_clk_dvi = (iic_start_flag)?((iic_clk_dvi_r == iic_data_change)?1'b1:1'b0):1'b0;//当iic开始标志有效时,开始iic数据变化计数 endmodule
IIC_DATA模块:
<span style="font-family:SimSun;"></span><pre name="code" class="csharp" style="font-size: 9pt;">module IIC_DATA( clk, sys_rst_n, iic_clk, iic_clk_dvi, // iic_data_en,//高电平时,表示已经可以开始发送数据 fifo_data, iic_data, iic_start_flag, rdusedw_sig, fifo_req ); input clk; input sys_rst_n; input iic_clk; input iic_clk_dvi; //input iic_data_en; input [7:0] fifo_data;//来自FIFO的数据 input [3:0]rdusedw_sig;//检测FIFO中的数据量 output fifo_req; output iic_data;//数据的输出,数据是串行输出,并且高位首先输出;作为输入时接受应答信号 output iic_start_flag;//数据输出开始标志 reg [7:0] fifo_data_r; reg fifo_req_r; //---------------------------------------------------------------- reg iic_clk_r1; reg iic_clk_r2; wire iic_clk_x; wire iic_clk_s; //---------------------------------------------------------------------- reg iic_start_flag_r; reg [3:0] rdusedw_sig_r; //---------------------------------------------------------------------- reg iic_data_r1; //reg [7:0] k; //---------------------------------------------------------------- reg [3:0] i; //----------------------------------------------------------------- reg [3:0] j; //--------------------------------------------------------------- reg iic_data_r2; //----------------------------------------------------------------- //读FIFO中的数据 always@(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin fifo_data_r <= 8'b0; fifo_req_r <= 1'b0; end // else if((rdusedw_sig !=4'd0)&&rec_ack)begin//当一个字节的数据发送完成之后再读取新的数据 else if(rdusedw_sig !=4'd0)begin fifo_req_r <= 1'b1; fifo_data_r <= fifo_data; end else begin fifo_req_r <= 1'b0; end //----------------------------------------------------------------- //捕捉IIC时钟的下降沿和上升沿 always@(posedge clk or negedge sys_rst_n) if(!sys_rst_n)begin iic_clk_r1 <= 1'b0; iic_clk_r2 <= 1'b0; end else begin iic_clk_r1 <= iic_clk; iic_clk_r2 <= iic_clk_r1; end //---------------------------------------------------------------------- //当FIFO中的数据不为空时,且在数据传送标志有效时(即要在数据总线空闲时),开始标志产生 always@(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin rdusedw_sig_r <= 4'd0; end else rdusedw_sig_r = rdusedw_sig; //----------------------------------------------------------------------- //产生开始标志位,并启动数据传输 reg [8:0] k; always @(posedge clk or negedge sys_rst_n) if(!sys_rst_n) k <= 9'd0; else if((k <= 9'd40)&&(j == 9)) k <= k + 1'b1; else k <= 9'd0; always@(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin iic_start_flag_r <= 1'b0; iic_data_r1 <= 1'b1; end else if((j == 4'd9)&&(k == 9'd40))begin iic_start_flag_r = 1'b0;//结束标志的产生 iic_data_r1 <= 1'b1; end else if(rdusedw_sig_r != 4'd0)begin iic_start_flag_r <= 1'b1; iic_data_r1 <= 1'b0; end else; //---------------------------------------------------------------------- //IIC时钟下降沿计数,用于发送数据 always@(posedge clk or negedge sys_rst_n) if(!sys_rst_n) i <= 4'd0; else if(iic_clk_x) i <= i + 1'b1;//在时钟的下降沿进行计数处理 // else if(i > 4'd9) i <= 4'd0; else if(!iic_start_flag) i <= 4'd0; //---------------------------------------------------------------------- //IIC时钟上升沿计数,用于发送数据 always@(posedge clk or negedge sys_rst_n) if(!sys_rst_n) j <= 4'd0; else if(iic_clk_s) j <= j + 1'b1;//在时钟的下降沿进行计数处理 // else if(j > 4'd10) j <= 4'd0; else if(!iic_start_flag) j <= 4'd0; else ; //------------------------------------------------------------------------- //开始正式发送数据 always@(posedge clk or negedge sys_rst_n) if(!sys_rst_n) begin iic_data_r2 <= 1'b1; end else if(iic_clk_dvi == 1)begin case (i) 4'd1:iic_data_r2 <= fifo_data_r[7]; 4'd2:iic_data_r2 <= fifo_data_r[6]; 4'd3:iic_data_r2 <= fifo_data_r[5]; 4'd4:iic_data_r2 <= fifo_data_r[4]; 4'd5:iic_data_r2 <= fifo_data_r[3]; 4'd6:iic_data_r2 <= fifo_data_r[2]; 4'd7:iic_data_r2 <= fifo_data_r[1]; 4'd8:iic_data_r2 <= fifo_data_r[0]; 4'd9:iic_data_r2 <= 1'b0; endcase end //下降沿和上升沿都要采集,并且要对比第九个,要求都为低电平,才有一次应答成功,此程序还没有完成结束动作 else ; //-------------------------------------------------------- assign fifo_req = fifo_req_r; ////-------------------------------------------------------- assign iic_clk_x = ~iic_clk_r1 & iic_clk_r2; // 捕捉IIC时钟的下降沿 assign iic_clk_s = iic_clk_r1 & ~iic_clk_r2; // 捕捉IIC时钟的上升沿 //-------------------------------------------------------- //assign rec_ack = rec_ack_r; //-------------------------------------------------------- assign iic_start_flag = iic_start_flag_r; assign iic_data = (iic_start_flag)?((i == 0)?iic_data_r1:iic_data_r2):1'b1; endmodule
</pre>