串口系列知识分享:
(1)串口通信实现-串口发送
(2)串口通信发送多字节数据
(3)串口通信实现-串口接收
(4)UART 通信-使用VIO进行板级验证
(5)串口接收-控制LED闪烁
(6)使用串口发送实现ACX720开发板时钟显示
(7)串口发送+RAM+VGA传图

前言

此文介绍uart串口串口接收-控制LED的verilog实现和testbench的编写,仿真环境为vivado 2018.3。


提示:以下是本篇文章正文内容,下面案例可供参考

一、实现原理

【设计思路】:
1.uart_cmd模块:将uart_byte_rx的输出rx_data和rx_done作为uart_cmd的输入,将led控制模块的输入作为uart_cmd的输出,将两个模块连接起来。

2.协议制定:ctrl[7:0]与Time[31:0]的值怎么取,使用串口接收模块可能得到多个数据,那么哪些数据是我们所需要的Ctrl和Time呢?于是制定如下协议:

0x55 0xA5 | Time[7:0] Time[15:8] Time[23:16] Time[31:14] Ctrl[7:0] | 0xF0
  • 1

当检测到第一个字节为8’h55,第二个字节为8‘hA5,第八个字节为8’hF0时将接收到的3-6字节作为TIme值,7字节作为ctrl值,这样能实现对LED等闪烁的控制。

3.协议判断:当每接收到8个字节时如何通过代码判断是否符合上述协议?

reg [7:0] data_str [7:0];
    always@(posedge Clk)
    if(rx_done)begin
        data_str[7] <= #1 rx_data;
        data_str[6] <= #1 data_str[7];
        data_str[5] <= #1 data_str[6];
        data_str[4] <= #1 data_str[5];
        data_str[3] <= #1 data_str[4];
        data_str[2] <= #1 data_str[3];
        data_str[1] <= #1 data_str[2];
        data_str[0] <= #1 data_str[1];        
    end


always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) begin
        ctrl <= #1 0;
        time_set <= #1 0;
    end else if(r_rx_done)begin
        if((data_str[0] == 8'h55) && (data_str[1] == 8'hA5) && (data_str[7] == 8'hF0))begin
            time_set[7:0] <= #1 data_str[2];
            time_set[15:8] <= #1 data_str[3];
            time_set[23:16] <= #1 data_str[4];
            time_set[31:24] <= #1 data_str[5];
            ctrl <= #1 data_str[6];
        end
    end
 

使用8字节的reg用作存储接收到的data,每次循环右移。将接收到的data与设定好的协议作比较,取出Time和Ctrl。

二、顶层模块

module uart_rx_ctrl_led(
    Clk,
    Reset_n,
    Led,
    uart_rx
);

    input Clk;
    input Reset_n;
    output Led;
    input uart_rx;
    
    wire [7:0]ctrl;
    wire [31:0]time_set;
    wire [7:0]rx_data;
    wire rx_done;
    
    counter_led_4 counter_led(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Ctrl(ctrl),
        .Time(time_set),
        .Led(Led)
    ); 

    uart_cmd uart_cmd(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .rx_data(rx_data),
        .rx_done(rx_done),
        .ctrl(ctrl),
        .time_set(time_set)
    );
    
    uart_byte_rx uart_byte_rx(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Baud_Set(4),
        .uart_rx(uart_rx),
        .Data(rx_data),
        .Rx_Done(rx_done)  
    ); 

endmodule
 

顶层模块用于例化下面三个模块,进行连接。

三、uart_cmd模块

代码如下(示例):

`timescale 1ns/1ns

module uart_cmd(
    Clk,
    Reset_n,
    rx_data,
    rx_done,
    ctrl,
    time_set
);
    
    input Clk;
    input Reset_n;
    input [7:0]rx_data;
    input rx_done;
    output reg[7:0]ctrl;
    output reg[31:0]time_set;
    
    reg [7:0] data_str [7:0];
    always@(posedge Clk)
    if(rx_done)begin
        data_str[7] <=  rx_data;
        data_str[6] <=  data_str[7];
        data_str[5] <=  data_str[6];
        data_str[4] <=  data_str[5];
        data_str[3] <=  data_str[4];
        data_str[2] <=  data_str[3];
        data_str[1] <=  data_str[2];
        data_str[0] <=  data_str[1];        
    end
    
    reg r_rx_done;   
    //延迟一个时钟周期,当rx_done检测为1时,并没有做到8个字节全赋值,所以可以使用延迟的方法
    always@(posedge Clk)
        r_rx_done <= rx_done;
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) begin
        ctrl <= #1 0;
        time_set <= #1 0;
    end else if(r_rx_done)begin
        if((data_str[0] == 8'h55) && (data_str[1] == 8'hA5) && (data_str[7] == 8'hF0))begin
            time_set[7:0] <= #1 data_str[2];
            time_set[15:8] <= #1 data_str[3];
            time_set[23:16] <= #1 data_str[4];
            time_set[31:24] <= #1 data_str[5];
            ctrl <= #1 data_str[6];
        end
    end    
    
endmodule


将上述代码注释处删除后会出现:


时间延迟后,可以明显看出在时钟上升沿,rx_done=1时,还没有完全赋值。于是让rx_done晚一拍。

四、uart_byte_rx模块

代码如下(示例):

`timescale 1ns/1ns

module uart_byte_rx(
    Clk,
    Reset_n,
    Baud_Set,
    uart_rx,
    Data,
    Rx_Done,  
);

   input Clk;
   input Reset_n;
   input [2:0]Baud_Set;
   input uart_rx;
   output reg[7:0]Data; 
   output reg Rx_Done;
   
   reg [1:0]uart_rx_r;
   always@(posedge Clk)begin
        uart_rx_r[0] <= #1 uart_rx;
        uart_rx_r[1] <= #1 uart_rx_r[0] ;
   end

    wire pedge_uart_rx;
//    assign pedge_uart_rx = ((uart_rx_r[1] == 0) && (uart_rx_r[0] == 1));
    assign pedge_uart_rx = (uart_rx_r == 2'b01);
    wire nedge_uart_rx;
//    assign nedge_uart_rx = ((uart_rx_r[1] == 1) && (uart_rx_r[0] == 0));  
    assign nedge_uart_rx = (uart_rx_r == 2'b10);  
 
    reg [8:0]  Bps_DR;
    always@(*)
        case(Baud_Set)
            0:Bps_DR = 1000000000/9600/16/20 - 1;
            1:Bps_DR = 1000000000/19200/16/20 - 1;
            2:Bps_DR = 1000000000/38400/16/20 - 1;
            3:Bps_DR = 1000000000/57600/16/20 - 1;
            4:Bps_DR = 1000000000/115200/16/20 - 1;
            default:Bps_DR = 1000000000/9600/16/20 - 1;
        endcase
        
    wire bps_clk_16x;
    assign bps_clk_16x = (div_cnt == Bps_DR / 2);

    reg RX_EN;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) 
        RX_EN <=  0;
    else if(nedge_uart_rx)
        RX_EN <=  1;
    else if(Rx_Done || (sta_bit >= 4))
        RX_EN <= 0;
        
    reg [8:0]div_cnt;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)    
        div_cnt <=  0;
    else if(RX_EN)begin
        if(div_cnt == Bps_DR)
            div_cnt <=  0;
        else
            div_cnt <=  div_cnt + 1'b1;
    end
    else
        div_cnt <=  0;

    reg [7:0]bps_cnt;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) 
        bps_cnt <=  0;
    else if(RX_EN)begin
        if(bps_clk_16x)begin
            if(bps_cnt == 159)
                bps_cnt <=  0;
            else
                bps_cnt <=  bps_cnt + 1'b1;
        end
        else
            bps_cnt <=  bps_cnt;
     end
     else
        bps_cnt <=  0;
       
    //reg width name number/depth
    reg[2:0]r_data[7:0];
    reg [2:0]sta_bit;
    reg [2:0]sto_bit;
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) begin
        sta_bit <=  0;
        sto_bit <=  0;
        r_data[0] <=  0;
        r_data[1] <=  0;
        r_data[2] <=  0;
        r_data[3] <=  0;
        r_data[4] <=  0;
        r_data[5] <=  0;
        r_data[6] <=  0;
        r_data[7] <=  0;
    end
    else if(bps_clk_16x)begin
        case(bps_cnt)
            0:begin
                sta_bit <=  0;
                sto_bit <=  0;
                r_data[0] <=  0;
                r_data[1] <=  0;
                r_data[2] <=  0;
                r_data[3] <=  0;
                r_data[4] <=  0;
                r_data[5] <=  0;
                r_data[6] <=  0;
                r_data[7] <=  0;
            end
            5,6,7,8,9,10,11:sta_bit <= #1 sta_bit + uart_rx;
            21,22,23,24,25,26,27: r_data[0] <=  r_data[0] + uart_rx;
            37,38,39,40,41,42,43: r_data[1] <=  r_data[1] + uart_rx;
            53,54,55,56,57,58,59: r_data[2] <=  r_data[2] + uart_rx;
            69,70,71,72,73,74,75: r_data[3] <=  r_data[3] + uart_rx;
            85,86,87,88,89,90,91: r_data[4] <=  r_data[4] + uart_rx;
            101,102,103,104,105,106,107: r_data[5] <=  r_data[5] + uart_rx;
            117,118,119,120,121,122,123: r_data[6] <=  r_data[6] + uart_rx;
            133,134,135,136,137,138,139: r_data[7] <=  r_data[7] + uart_rx;
            149,150,151,152,153,154,155: sto_bit <=  sto_bit + uart_rx;
            default:;
        endcase
    end
        
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) 
        Data <= #1 0;        
    else if(bps_clk_16x && (bps_cnt == 159))begin
        Data[0] <=  (r_data[0] >= 4)?1'b1:1'b0;
        Data[1] <=  (r_data[1] >= 4)?1'b1:1'b0;
        Data[2] <=  (r_data[2] >= 4)?1'b1:1'b0;
        Data[3] <=  (r_data[3] >= 4)?1'b1:1'b0;
        Data[4] <=  (r_data[4] >= 4)?1'b1:1'b0;
        Data[5] <=  (r_data[5] >= 4)?1'b1:1'b0;
        Data[6] <=  (r_data[6] >= 4)?1'b1:1'b0;
        Data[7] <=  (r_data[7] >= 4)?1'b1:1'b0;
    end 
    
//    always@(posedge Clk or negedge Reset_n)
//    if(!Reset_n) 
//        Data <= #1 0;        
//    else if(bps_clk_16x && (bps_cnt == 159))begin
//        Data[0] <= #1 r_data[0][2];
//        Data[1] <= #1 r_data[1][2];
//        Data[2] <= #1 r_data[2][2];
//        Data[3] <= #1 r_data[3][2];
//        Data[4] <= #1 r_data[4][2];
//        Data[5] <= #1 r_data[5][2];
//        Data[6] <= #1 r_data[6][2];
//        Data[7] <= #1 r_data[7][2];
//    end 

    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) 
        Rx_Done <=  0;
    else if((div_cnt == Bps_DR/2) && (bps_cnt == 159))
        Rx_Done <=  1;
    else
        Rx_Done <=  0; 
        
endmodule

五、counter_led_4模块

module counter_led_4(
    Clk,
    Reset_n,
    Ctrl,
    Time,
    Led
);
    input Clk;
    input Reset_n;
    input [7:0]Ctrl;
    input [31:0]Time;
    output reg Led;

    reg [31:0]counter;
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        counter <=  0;
    else if(Time - 1 <=  counter)
        counter <=  0;
    else
        counter <=  counter + 1'b1;
    
    reg [2:0]counter2;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n) 
        counter2 <=  0; 
    else if(  Time - 1 <=  counter)
        counter2 <=  counter2 + 1'b1;

    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        Led <=  0;
    else case(counter2)
        0:Led <=  Ctrl[0];
        1:Led <=  Ctrl[1];
        2:Led <=  Ctrl[2];
        3:Led <=  Ctrl[3];
        4:Led <=  Ctrl[4];
        5:Led <=  Ctrl[5];
        6:Led <=  Ctrl[6];
        7:Led <=  Ctrl[7];
        default:Led <=  Led;
    endcase
            
endmodule

【注】:上述代码中写成Time - 1 <= counter而不是counter = Time - 1的原因:

上电的时候, counter ==0-1发现不满足,就 couter 一直自加,直到自加到32’ hFFFFFFFFFFFFFFFF '才会清零。←由于实际板级运行的时候,当我们的 time 值更新时(25000000), counter 的值已经大于该值,所以无法通过计数比较的方式清零, 能一直自加下去,直到32位计满了,溢出清零,然后才能正常的循环技术清零。于是写成Time - 1 <= counter能解决这个问题,且这样写利大于弊。

【附件:】链接:https://pan.baidu.com/s/1nIBn_n_6DYezlvRN4OfkCQ?pwd=h7ub
提取码:h7ub