FPGA开发——IIC实现简单的串口回环

一、概述

        在我们进行日常开发时,不管是进行MCU、单片机、还是FPGA,都会使用到IIC通信协议。采用串行总线可以简化系统硬件结构、减小系统体积、提高系统可靠性。常 用的串行总线有单总线(1-Wire Bus)、IICInter-Integrated Circuit)、SPISerial Peripheral Interface)等。 IIC 总线是 Phlips 公司推出的一种同步串行总线,是一种支持多主机多从机 系统、总线仲裁以及高低速器件同步功能的高性能串行总线技术。

        在前面学习串口通信的时候,我们实现了数据回环的设计,现在我们学到了IIC通信协议,我们也来进行一下简单的IIC数据回环的设计,所谓的IIC数据回环设计就是在使用我们设计出的串口的基础之上,编写IIC的主机和从机,按照IIC通信协议的要求,进行一个数据的交互,从而实现IIC的数据回环实现。今天这篇文章的目的就是为了让我们熟悉一下IIC的通信协议。

二、框架构造

1、总体系统框架

        在框图中可以看到,我们实现的思路就是通过串口接收和发送模块实现与PC机的交互,从而实现数据的回环,而IIC的作用就是通过对于接收模块接收到的数据进行处理,最后把数据传递给发送模块。        

2、IIC主机模块

图中我们把SDA分成了三根线,在代码端口定义中我们对于sda_oen,sda_out,sda_in进行了处理,最后只使用了一根线。

 

3、从机模块

因为我们在主机中对于SDA线进行了处理,所以对与从机来说,只需要一根线接收就行,只不过为了方便观察我们写出来了。

 

三、工程实现

1、IIC主机模块的代码编写

        新建一个iic_master.v文件,如下:这里的输入数据是来自串口接收模块接收到的数据

//---------<模块及端口声名>-------------------------------------------
module iic_master( 
    input				clk		,
    input				rst_n	,
    //IIC
    input		[7:0]   din		,
    input               send_en ,
    //iic_slave从机数据
    output		reg    	iic_scl	,
    inout		    	iic_sda	
);								 
//---------<参数定义>------------------------------------------------
parameter  CLK   =50_000_000 ;
parameter  SPEED =100_000;//传输速率
//---------<内部信号定义>--------------------------------------------
parameter      IDLE     =5'b000_01 ,
               START    =5'b000_10 ,
               TRANS    =5'b001_00 ,
               ACK      =5'b010_00 ,
               STOP     =5'b100_00 ;

parameter  CNT_MAX =CLK/SPEED;

reg             iic_sda_out;
reg             iic_sda_en ;
wire            iic_sda_in ;

reg     [9:0]  scl_cnt;//SCL时钟计数器
wire           add_scl_cnt;
wire           end_scl_cnt;

reg		[3:0]	cnt_bit	   ;
wire			add_cnt_bit;
wire			end_cnt_bit;

reg            iic_scl_r;
wire            nedge;   
reg     [4:0]   state_c ;//现态
reg     [4:0]   state_n ;//次态

wire    idle2start  ;
wire    start2trans ;
wire    trans2ack   ;
wire    ack2stop    ;
wire    stop2idle   ;

reg     [7:0] din_r;//输入数据缓存

//IIC_SDA描述
assign iic_sda =iic_sda_en ? iic_sda_out : 1'bz;
assign iic_sda_in =iic_sda;
//第一段:同步时序描述状态转移
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)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
                state_n = state_c; 
        end
        START : begin
            if(start2trans)
                state_n = TRANS;
            else
                state_n = state_c; 
        end
        TRANS : begin
            if(trans2ack)
                state_n = ACK;
            else
                state_n = state_c; 
        end
        ACK   : begin
            if(ack2stop)
                state_n = STOP;
            else
                state_n = state_c; 
        end
        STOP  : begin
            if(stop2idle)
                state_n = IDLE;
            else
                state_n = state_c; 
        end
        default : ;
    endcase
end

assign idle2start  = state_c == IDLE  && send_en;
assign start2trans = state_c == START && end_scl_cnt;
assign trans2ack   = state_c == TRANS && end_cnt_bit;
assign ack2stop    = state_c == ACK   && end_scl_cnt;
assign stop2idle   = state_c == STOP  && end_scl_cnt;

//第三段:描述状态输出(组合逻辑/时序逻辑)
//scl_cnt
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        scl_cnt <= 'd0;
    end 
    else if(add_scl_cnt)begin 
        if(end_scl_cnt)begin 
            scl_cnt <= 'd0;
        end
        else begin 
            scl_cnt <= scl_cnt + 1'b1;
        end 
    end
end 

assign add_scl_cnt = state_c!=IDLE ;
assign end_scl_cnt = add_scl_cnt && scl_cnt ==CNT_MAX-1 ;
//IIC_SCL时钟
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)
       iic_scl  <=1'b1;
    else if(state_c !=IDLE && scl_cnt<=CNT_MAX/2-1)
       iic_scl <= 1'b0;
    else
       iic_scl <=1'b1;
end    

//bit计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)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 + 1'b1;
        end 
    end
end 

assign add_cnt_bit =(state_c ==TRANS) && end_scl_cnt ;
assign end_cnt_bit = add_cnt_bit && cnt_bit ==8-1 ;

//din_r
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        din_r <= 'd0;
    end 
    else if(send_en)begin 
        din_r <= din;
    end 
end

 always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        iic_sda_en <= 'd0;
        iic_sda_out <= 'd1;
    end 
    else begin
        case (state_c)
            IDLE  :begin//修改
                iic_sda_en <= 'd0;
                iic_sda_out <= 1'b1;
            end
            START :begin
                iic_sda_en <= 1'b1;
                if((iic_scl==1) && (scl_cnt>=(CNT_MAX/2+CNT_MAX/4)))
                    iic_sda_out <= 1'b0;
                else
                    iic_sda_out <= 1'b1;
            end
            TRANS :begin
                    iic_sda_en <= 1'b1;
                    if((iic_scl==0) && (scl_cnt <=CNT_MAX>>2))
                        iic_sda_out <= din_r[7-cnt_bit];               
            end
            ACK   :begin
                iic_sda_en <= 1'b0;
                iic_sda_out <= 1'bz;
            end
            STOP  :begin
                iic_sda_en <= 1'b1;
                if(scl_cnt<=CNT_MAX>>2)
                    iic_sda_out <= 1'b0;
                else if((iic_scl==1) && (scl_cnt>=(CNT_MAX/2+CNT_MAX/4)))
                    iic_sda_out <= 1'b1;
            end
            default:;
        endcase
    end
 end   
endmodule 


2、从机模块代码的编写

这里和主机模块一样也是新建一个iic_slave.v文件,如下:

    
//---------<模块及端口声名>-------------------------------------------
module iic_slave( 
    input				clk		,
    input				rst_n	,
    //IIC
    input				iic_scl	,
    inout		        iic_sda	,
    //uart_tx
    output	reg	[7:0]	dout	,//串口发送模块输入数据
    output	reg     	dout_vld //串口发送模块输入数据标志位
);								 
//---------<参数定义>------------------------------------------------
parameter  CLK   =50_000_000 ;
parameter  SPEED =100_000;//传输速率
//---------<内部信号定义>--------------------------------------------
parameter      IDLE     =5'b000_01 ,
               START    =5'b000_10 ,
               TRANS    =5'b001_00 ,
               ACK      =5'b010_00 ,
               STOP     =5'b100_00 ;

parameter  CNT_MAX =CLK/SPEED;

reg iic_scl_r1;//同步打拍
reg iic_scl_r2;
reg iic_sda_r1;
reg iic_sda_r2;

wire     sda_nedge;//sda信号检测
wire     sda_pedge;

wire     scl_nedge;//scl信号检测
wire     scl_pedge;

reg         iic_sda_en;//对于sda线进行一个描述(三台门)
reg         iic_sda_out;
wire        iic_sda_in;

reg   [3:0]  cnt_bit;
wire         add_cnt_bit;
wire         end_cnt_bit;

reg     [9:0]  scl_cnt;//SCL时钟计数器
wire           add_scl_cnt;
wire           end_scl_cnt;   

reg     [4:0]   state_c ;//现态
reg     [4:0]   state_n ;//次态

wire    idle2start  ;
wire    start2trans ;
wire    trans2ack   ;
wire    ack2stop    ;
wire    stop2idle   ;

reg     [7:0] dout_r;//输出数据寄存器
//sda信号描述
assign  iic_sda = iic_sda_en ? iic_sda_out :1'bz;
assign  iic_sda_in = iic_sda;
//第一段:同步时序描述状态转移
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)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
                state_n<= state_c;     
        end
        START : begin
            if(start2trans)
                state_n<=TRANS   ;
            else
                state_n<= state_c;     
        end
        TRANS : begin
            if(trans2ack)
                state_n<=ACK   ;
            else
                state_n<= state_c;     
        end
        ACK   : begin
            if(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    ) && (iic_scl_r2 && sda_nedge);
assign start2trans = (state_c == START   ) && (scl_nedge);
assign trans2ack   = (state_c == TRANS   ) && (end_cnt_bit);
assign ack2stop    = (state_c == ACK     ) && (scl_nedge);
assign stop2idle   = (state_c == STOP    ) && (iic_scl_r2 && sda_pedge);

//第三段:描述状态输出(组合逻辑/时序逻辑) 
//对于scl输入信号进行打拍
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        iic_scl_r1 <= 1'b1;
        iic_scl_r2 <= 1'b1;
    end 
    else begin 
        iic_scl_r1 <= iic_scl;
        iic_scl_r2 <= iic_scl_r1;
    end 
end 
assign scl_nedge= ~iic_scl_r1 && iic_scl_r2 ;
assign scl_pedge= iic_scl_r1 && ~iic_scl_r2 ;
//对于sda输入信号进行打拍
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        iic_sda_r1 <= 1'b1;
        iic_sda_r2 <= 1'b1;
    end 
    else begin 
        iic_sda_r1 <= iic_sda;
        iic_sda_r2 <= iic_sda_r1;
    end 
end 
assign sda_nedge= ~iic_sda_r1 && iic_sda_r2 ;
assign sda_pedge= iic_sda_r1 && ~iic_sda_r2 ;

//bit计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)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 + 1'b1;
        end 
    end
end 

assign add_cnt_bit =(state_c ==TRANS) && scl_nedge ;
assign end_cnt_bit = add_cnt_bit && cnt_bit ==8-1 ;

//sda_en、sda_out
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        iic_sda_en <= 'd0;
        iic_sda_out <= 'd1;
    end 
    else if(state_c==ACK)begin
        iic_sda_en <= 1'b1;
        iic_sda_out <= 1'b0;
    end
    else begin
        iic_sda_en  <= 'd0;
        iic_sda_out <= 'd0;
    end
end   

//数据类型转换,串转并
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        dout_r <= 'd0;
    end 
    else if(state_c==TRANS  && iic_scl_r2)begin 
        dout_r[7-cnt_bit] <=iic_sda_r2;
    end 
end 
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        dout <= 'd0;
    end  
    else if(state_c==STOP)begin 
       dout <=dout_r; 
    end 
end

always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        dout_vld <= 'd0;
    end  
    else begin 
       dout_vld <=stop2idle; 
    end 
end
endmodule 

关于串口接收和发送模块的代码在前面的文章中已经写过了,如果不知道怎么写的小伙伴直接去前面文章里面找就行。

3、顶层文件 的编写

通过顶层文件将串口接收模块和IIC主从机模块连接起来,从而实现数据回环。

新建top.v文件,如下:

module top (
    input       clk     ,
    input       rst_n   ,
    input       rx      ,
    output      tx          
);
wire  [7:0] rx_dout;
wire        rx_dout_vld;
wire            busy;
wire           iic_scl;
wire           iic_sda;
wire    [7:0]  tx_din    ;
wire           tx_din_vld;
uart_rx uart_rx_inst(
    /*input            */ .clk	     (clk        ),
    /*input            */ .rst_n     (rst_n	 ),        
    /*input            */ .din_rx    (rx     ),
    /*output  reg[7:0] */ .dout_data (rx_dout  ),
    /*output  reg      */ .dout_flag (rx_dout_vld  )
);

iic_master iic_master_inst( 
    /*input				  */.clk		(clk    ),
    /*input				  */.rst_n	    (rst_n  ),
    /*input       [7:0]   */.din    (rx_dout),
    /*input               */.send_en    (rx_dout_vld),//开始传输的使能信号
    /*output  reg         */.iic_scl    (iic_scl),
    /*inout               */.iic_sda    (iic_sda)
);		

iic_slave u_iic_slave( 
    /*input				  */.clk		    (clk    ),
    /*input				  */.rst_n	        (rst_n  ),
    /*input               */.iic_scl        (iic_scl),
    /*inout               */.iic_sda        (iic_sda),
    /*output   reg  [7:0] */.dout	        (tx_din    ),
    /*output   reg        */.dout_vld       (tx_din_vld)  
);		

uart_tx uart_tx_inst(
    /*input        */ .clk        (clk      ) ,
    /*input        */ .rst_n      (rst_n    ) ,
    /*input   [7:0]*/ .tx_din     (tx_din     ) ,//进入发送模块准备发送的数据
    /*input        */ .tx_din_vld (tx_din_vld && ~busy    ) ,//要发送数据的有效信号
    /*output  reg  */ .tx_dout    (tx       ) ,//串行发送出去的数据
    /*output  reg  */ .busy       (busy   )  //发送一字节完成信号
);
endmodule

4、IIC测试文件的编写

因为串口我们前面已经写了并且测试过,所以这里我们只需要进行IIC的主机和从机进行一个仿真就可以得出结果,只要在这里数据能够正常交互,IIC的数据回环就没啥问题。

新建一个iic_tb.v文件,如下:

`timescale 1ns/1ps
    
module iic_tb();

//激励信号定义 
    reg				tb_clk  	;
    reg				tb_rst_n	;
    reg     [7:0]   data_in     ;
    reg             send_en     ;
    reg             sda_in      ;

//内部信号定义	 
    wire	        iic_scl     ;
    wire            iic_sda     ;
//输出信号定义
    wire    [7:0]   r_data      ;
    wire            r_data_vld  ;

    pullup(iic_sda);

//时钟周期参数定义	
    parameter		CLOCK_CYCLE = 20;   

//模块例化
    iic_master iic_master_inst( 
    /*input				  */.clk	    (tb_clk     ),
    /*input				  */.rst_n	    (tb_rst_n   ),
    /*input       [7:0]   */.din		(data_in    ),
    /*input               */.send_en    (send_en    ),//开始传输的使能信号
    /*output  reg         */.iic_scl    (iic_scl    ),
    /*inout               */.iic_sda    (iic_sda    )
);	

    iic_slave iic_slave_inst( 
    /*input				  */.clk		    (tb_clk     ),
    /*input				  */.rst_n	        (tb_rst_n   ),
    //iic_master()
    /*input               */.iic_scl        (iic_scl    ),
                            .iic_sda        (iic_sda    ),
    //uart_tx()
    /*output   reg        */.dout         (r_data     ),
    /*output   reg        */.dout_vld     (r_data_vld )
);	

//产生时钟
    initial 		tb_clk = 1'b0;
    always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;

//产生激励
    initial  begin 
        tb_rst_n = 1'b1;
        data_in = 0;
        send_en = 0;
        sda_in = 1'b1;
        #(CLOCK_CYCLE*2);
        tb_rst_n = 1'b0;
        #(CLOCK_CYCLE*20);
        tb_rst_n = 1'b1;

        #3;
        repeat(10)begin
            data_in = {$random}%256;
            send_en = 1'b1;
            #CLOCK_CYCLE;
            send_en = 1'b0;
            wait(iic_master_inst.stop2idle);
            #500;
        end
        

        #2000;
        $stop;


    end

endmodule 



                                            

5、波形仿真

通过观察IIC仿真波形图中的输入和输出数据结果可知,两者是一样的,说明我们设计的IIC主机和从机模块没有问题。

 

6、下板验证

通过串口调试助手我们可以看到数据正常交互。设计完成。

IIC(Inter-Integrated Circuit)是一种串行通信协议,常用于连接芯片和传感器之间的通信。在该实验中,我们将使用两个FPGA开发板,一个作为IIC主设备,另一个作为IIC从设备,通过数据回环测试IIC通信。 材料: - 两个FPGA开发板 - USB数据线 - IIC从设备 步骤: 1. 首先,连接一个FPGA开发板到电脑上,并使用Vivado创建一个新的工程。添加一个MicroBlaze处理器和IIC核。 2. 配置IIC核,将其设置为主设备。设置时钟,数据率和其他参数。为IIC核添加一个状态机,并配置状态机以发送和接收数据。将IIC核的输出连接到MicroBlaze的输入,并将MicroBlaze的输出连接到IIC核的输入。 3. 然后,连接另一个FPGA开发板,使用Vivado创建另一个新工程。添加一个MicroBlaze处理器和IIC核。 4. 配置IIC核,将其设置为从设备。设置时钟,数据率和其他参数。为IIC核添加一个状态机,并配置状态机以发送和接收数据。将IIC核的输出连接到MicroBlaze的输入,并将MicroBlaze的输出连接到IIC核的输入。 5. 将IIC从设备连接到IIC主设备的IIC总线上。 6. 在IIC主设备的MicroBlaze代码中,编写一个简单的程序,向IIC从设备发送数据并等待接收回来的数据。在IIC从设备的MicroBlaze代码中,编写一个简单的程序,接收来自IIC主设备的数据并回传给IIC主设备。 7. 上传并运行IIC主设备和IIC从设备的代码。 8. 如果一切正常,IIC主设备应该能够发送数据到IIC从设备,然后等待IIC从设备回传数据。收到数据后,IIC主设备应该能够将接收到的数据打印出来。 9. 可以通过更改发送的数据来测试IIC通信。如果一切正常,IIC从设备应该能够正确接收并回传数据。 总结: 在该实验中,我们使用了两个FPGA开发板,一个作为IIC主设备,另一个作为IIC从设备,通过数据回环测试IIC通信。该实验可以帮助我们了解IIC通信协议,并为将来的项目奠定基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

电子小芯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值