FPGA自学8——UART功能使用

        UART是单片机开发中最常用的串行通信接口之一,今天我用FPGA实现UART通信,感受一下FPGA与单片机之间的区别。

        本次实验的功能:上位机通过USB转TTL工具向FPGA主板发送数据,主板收到后将数据返回到上位机。

1、UART接收模块 

//串口接收模块
module  urat_recv   (
    input			  sys_clk,                  //系统时钟
    input             sys_rst_n,                //系统复位,低电平有效
    
    input             uart_rxd,                 //UART接收端口,这里代表的就是硬件RX引脚
    output  reg       uart_done,                //接收一帧数据完成标志信号
    output  reg [7:0] uart_data                 //接收的数据
    );
//parameter可用作在顶层模块中例化底层模块时传递参数的接口,
 
parameter  CLK_FREQ = 50000000;                 //系统时钟频率
parameter  UART_BPS = 9600;                     //串口波特率

//localparam的作用域仅仅限于当前module,不能作为参数传递的接口。
localparam  BPS_CNT=CLK_FREQ/UART_BPS; //为得到指定波特率,需要对系统时钟计数BPS_CNT次

reg uart_rxd_d0;
reg uart_rxd_d1;   

reg [15:0]  clk_cnt;            //系统时钟计数器
reg [3:0]   rx_cnt;             //接收数据计数器
reg         rx_flag;            //接收过程标志信号
reg [7:0]   rxdata;             //接收数据缓存

wire    start_flag;             //进入接收过程标志

//边沿检测的一个时钟周期脉冲信号
assign  start_flag = uart_rxd_d1 & (~uart_rxd_d0);
 
always  @(posedge   sys_clk or negedge sys_rst_n)begin//系统时钟的上升沿触发  或者复位信号触发此always块
    if(!sys_rst_n)  begin  
        uart_rxd_d0<=1'b0; //复位时清零
        uart_rxd_d1<=1'b0; //复位时清零
    end
    else begin
        uart_rxd_d0  <= uart_rxd;     //寄存  uart_rxd与实际电路连接,其连接的是uart的RX接口           
        uart_rxd_d1  <= uart_rxd_d0;
    end 
end  

//当脉冲信号start_flag(边沿检测)到达时,进入接收过程   
always @(posedge sys_clk or negedge sys_rst_n) begin     
     if(!sys_rst_n)    
       rx_flag=1'b0;    
    else begin
    if(start_flag) //上一个模块控制的变量
            rx_flag<=1;    //进入接收过程
        else if((rx_cnt==4'd9/*9是8个数据尾+1个停止位*/)&&(clk_cnt==BPS_CNT/2/*当接收到第9个bit时,在波特率周期的中间将接收标志置零*/))  
                //clk_cnt:是波特率计数   rx_cnt:表示当前接收到第几个数据
            rx_flag<=0;
        else
            rx_flag<=rx_flag;    //保持原有值
    end 
end

//进入接收过程后,启动系统时钟计数器与接收数据计数器
always  @(posedge   sys_clk or negedge sys_rst_n)begin//系统时钟的上升沿触发  或者复位信号触发此always块
     if(!sys_rst_n)  begin  
        clk_cnt<=16'b0; //复位时清零
        rx_cnt<=4'b0; //复位时清零
    end
    else if(rx_flag)//上一个模块控制的变量,表示当前处于接收状态
         begin
            if(clk_cnt<BPS_CNT-1)begin//波特率计数
                clk_cnt<=clk_cnt+1;//波特率接着计数
                rx_cnt<=rx_cnt;//还没有接收到新的bit,计数保持
            end   
            else begin
                clk_cnt<=16'd0;//波特率周期时间到,计数清零
                rx_cnt<=rx_cnt+1;                
            end
         end
         else begin
            clk_cnt<=16'b0; //复位时清零
            rx_cnt<=4'b0; //复位时清零
         end
end 

//将由外部接收到的数据放到uart接收缓存中
always @(posedge   sys_clk or negedge sys_rst_n)begin//系统时钟的上升沿触发  或者复位信号触发此always块
    if ( !sys_rst_n)  
        rxdata <= 8'd0;                  //复位则清空缓存  
    else if(rx_flag)begin                //当前是接收状态
            if(clk_cnt==BPS_CNT/2)begin  //波特率周期一般的时候
                case (rx_cnt)            //接收到第几个bit
                 4'd1 : rxdata[0] <= uart_rxd_d1;   //寄存数据位最低位
                 4'd2 : rxdata[1] <= uart_rxd_d1;
                 4'd3 : rxdata[2] <= uart_rxd_d1;
                 4'd4 : rxdata[3] <= uart_rxd_d1;
                 4'd5 : rxdata[4] <= uart_rxd_d1;
                 4'd6 : rxdata[5] <= uart_rxd_d1;
                 4'd7 : rxdata[6] <= uart_rxd_d1;
                 4'd8 : rxdata[7] <= uart_rxd_d1;   //寄存数据位最高位
                 default:;  
                 endcase
            end
            else    
                rx_flag<=rx_flag;           //保持 
         end
     else
        rxdata<=8'd0;
end

always@(posedge   sys_clk or negedge sys_rst_n)begin//系统时钟的上升沿触发  或者复位信号触发此always块
     if (!sys_rst_n) begin
        uart_data <= 8'd0;                               
        uart_done <= 1'b0;
     end
     else if(rx_cnt==4'd9)begin      //接收数据计数器计数到停止位时   
          uart_data <= rxdata;      //数据由此模块输出
          uart_done <= 1'b1;        //此模块输出的接收完成标志
     end 
     else begin
         uart_data <= 8'd0;         //没有接收完成,则清零                             
         uart_done <= 1'b0; 
     end

end
endmodule

         单片机开发时只需要将波特率、数据位、停止位等参数配置给现有的功能模块上即可,但FPGA开发时,每个功能都是硬件层面的东西。例如:

  •         串口波特率:FPAG开发时,串口波特率是通过系统时钟频率计数得到的,bps周期=系统时钟频率 / bps,然后再通过计数得到每个bit之间的周期。
  •         数据位、停止位:在FPGA中就是程序控制上的东西,程序设计时可以任意设计数据位停止位(奇葩做法),但是通信双方的设备要保持一直。

         这里程序设计时的一个技巧,在纯软件开发的单片机是体会不到的:

         上图中UART接收端其实就是硬件上的信号,表示芯片在硬件电路上检测到了数据线上的数据,对应现实中就是调试软件点了下发数据操作。

         上图中的操作就是从硬件上产生一个数据接收的标志,与单片机的串口接收中断类似。

 2、UART发送模块 

//发送模块
module  uart_send(
    input	      sys_clk,                  //系统时钟
    input         sys_rst_n,                //系统复位,低电平有效
    
    input         uart_en,                  //发送使能信号
    input  [7:0]  uart_din,                 //待发送数据
    output  reg   uart_txd                  //UART发送端口
);

parameter  CLK_FREQ = 50000000;             //系统时钟频率
parameter  UART_BPS = 9600;                 //串口波特率
localparam BPS_CNT  = CLK_FREQ/UART_BPS;    //为得到指定波特率,对系统时钟计数BPS_CNT次

reg         uart_en_d0;
reg         uart_en_d1;
reg [15:0]  clk_cnt;        //系统时钟计数器  计数bps
reg [3:0]   tx_cnt;         //发送数据计数器bit
reg         tx_flag;        //发送过程标志信号
reg [7:0]   tx_data;        //发送数据缓存
  
wire       en_flag;

//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
assign en_flag = (~uart_en_d1) & uart_en_d0;

//对发送使能信号uart_en延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
        uart_en_d0 <= 1'b0;                                  
        uart_en_d1 <= 1'b0;
    end                                                      
    else begin                                               
        uart_en_d0 <= uart_en;                               
        uart_en_d1 <= uart_en_d0;                            
    end
end
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程          
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin                                  
        tx_flag <= 1'b0;
        tx_data <= 8'd0;
    end 
    else if (en_flag) begin                 //检测到发送使能上升沿                      
            tx_flag <= 1'b1;                //进入发送过程,标志位tx_flag拉高
            tx_data <= uart_din;            //寄存待发送的数据
        end
        else 
        if ((tx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
        begin                               //计数到停止位中间时,停止发送过程
            tx_flag <= 1'b0;                //发送过程结束,标志位tx_flag拉低
            tx_data <= 8'd0;
        end
        else begin
            tx_flag <= tx_flag;
            tx_data <= tx_data;
        end 
end

//进入发送过程后,启动系统时钟计数器与发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin                             
        clk_cnt <= 16'd0;                                  
        tx_cnt  <= 4'd0;
    end                                                      
    else if (tx_flag) begin                 //处于发送过程
        if (clk_cnt < BPS_CNT - 1) begin
            clk_cnt <= clk_cnt + 1'b1;
            tx_cnt  <= tx_cnt;
        end
        else begin
            clk_cnt <= 16'd0;               //对系统时钟计数达一个波特率周期后清零
            tx_cnt  <= tx_cnt + 1'b1;       //此时发送数据计数器加1
        end
    end
    else begin                              //发送过程结束
        clk_cnt <= 16'd0;
        tx_cnt  <= 4'd0;
    end
end

//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin        
    if (!sys_rst_n)  
        uart_txd <= 1'b1;        
    else if (tx_flag)
        case(tx_cnt)
            4'd0: uart_txd <= 1'b0;         //起始位 
            4'd1: uart_txd <= tx_data[0];   //数据位最低位
            4'd2: uart_txd <= tx_data[1];
            4'd3: uart_txd <= tx_data[2];
            4'd4: uart_txd <= tx_data[3];
            4'd5: uart_txd <= tx_data[4];
            4'd6: uart_txd <= tx_data[5];
            4'd7: uart_txd <= tx_data[6];
            4'd8: uart_txd <= tx_data[7];   //数据位最高位
            4'd9: uart_txd <= 1'b1;         //停止位
            default: ;
        endcase
    else 
        uart_txd <= 1'b1;                   //空闲时发送端口为高电平
end


endmodule

         uart_done:  接收完成标志,高电平表示接收完成,是信号发出的位置
         uart_TX_en: 发送使能标志,高电平开始接收
         urat_recv_init 实例化时将   uart_done   与  uart_TX_en连接到了一起
         urat_send_init 实例化时又将 uart_TX_en  与  uart_en连接到了一起
        这么做实现的功能是:当接收完成时产生一个高电平表示可以发送,发送端检测到这个高电平开始发送数据  

        顶层模块将接收模块 和 发送模块相关信号 连接在一起

串口发送模块同样有一个边沿检测的过程,与接收不同的是这里检测的是一个上升沿

 

3、顶层模块

        FPGA开发中每个项目都有一个顶层代码,来实例化底层的驱动。        

//顶层模块
module uart_top(
    input   sys_clk,        //外部50M时钟
    input   sys_rst_n,      //外部复位信号,低有效
    
    input   uart_rxd,       //UART接收端口
    output  uart_txd        //UART发送端口
);
parameter   CLK_FREQ=50000000;   //定义系统时钟频率
parameter   UART_BPS=115200;     //定义串口波特率

wire    uart_TX_en;             //串口发送使能
wire    [7:0] uart_TX_data;     //串口发送数据缓存 
wire    clk_1ms        ;        //1ms定时器      

//实例化底层接收
urat_recv #(
    .CLK_FREQ       (CLK_FREQ),     //设置系统时钟频率     
    .UART_BPS       (UART_BPS)      //设置串口接收波特率   
    )//这里是将顶层模块值传递给底层模块 
urat_recv_init(
    .sys_clk        (sys_clk),      //系统时钟
    .sys_rst_n      (sys_rst_n),    //复位信号
    
    .uart_rxd       (uart_rxd),     //UART接收端口
    .uart_done      (uart_TX_en),   //底层接收完成标志 赋值给 顶层 发送 使能标志
    .uart_data      (uart_TX_data)  //底层接收到的数据放到 串口发送数据缓存 中  
) ; //底层模块数据  连接到顶层
  
// uart_done:  接收完成标志,高电平表示接收完成,是信号发出的位置
// uart_TX_en: 发送使能标志,高电平开始接收
// urat_recv_init 实例化时将   uart_done   与  uart_TX_en连接到了一起
// urat_send_init 实例化时又将 uart_TX_en  与  uart_en连接到了一起
// 这么做实现的功能是:当接收完成时产生一个高电平表示可以发送,发送端检测到这个高电平开始发送数据  

uart_send #(                          //串口发送模块
    .CLK_FREQ       (CLK_FREQ),       //设置系统时钟频率
    .UART_BPS       (UART_BPS))       //设置串口发送波特率
urat_send_init(                 
    .sys_clk        (sys_clk),
    .sys_rst_n      (sys_rst_n),
     
    .uart_en        (uart_TX_en),
    .uart_din       (uart_TX_data),
    .uart_txd       (uart_txd)
    );

endmodule

        一般在顶层模块会定义本工程使用的所有硬件资源,然后分配给各个底层模块;

        各个模块直接的数据传输通过底层实例化来进行连接

5、实验验证 

USB 转TTL工具与电路板相连 

打开调试工具,验证实验结果

 

Guess you like

Origin blog.csdn.net/qq_34301282/article/details/120885188