13. 串口接收模块的项目应用案例

1.使用串口来控制LED灯工作状态

使用串口发送指令到FPGA开发板,来控制第7课中第4个实验的开发板上的LED灯的工作状态。

LED灯的工作状态:让LED灯按指定的亮灭模式亮灭,亮灭模式未知,由用户指定,8个变化状态为一个循环,每个变化状态的时间值可以根据不同的应用场景来选择。

1.1 原理如下

1.2 任务简化为:

如何使用串口接收8个字节的数据,判断并输出其中的数据。

2. 写设计代码,仿真代码并仿真

2.1 设计代码(重要)

1.在完成uart_cmd解析数据模块时,要掌握移位寄存器的使用,以及延后一拍判定的方法

2.修改了uart_byte_rx1串口接收模块代码,修改了rx_data八位数据接收条件,原先是发送了在出现tx_done信号后得到输出八位数据,这样会导致我们在后续模块通过tx_done信号接收数据时接收的是前一个八位数据。现在改为了tx_done信号出现时,八位数据已准备输出了。

3.修改了counter_led灯模块代码

cpp 复制代码
module uart_rx_ctrl_led(
    clk,
    rstn,
    uart_rx,
    blaud_set,
    led
);

    input clk;
    input rstn;
    input uart_rx;
    input [2:0]blaud_set;
    output led;
    
    wire [7:0]data;
    wire rx_done;
    wire [7:0] ctrl;
    wire [31:0] times;
    
    uart_byte_rx1 uart_byte_rx1_inst(
        .clk(clk),
        .rstn(rstn),
        .blaud_set(blaud_set),
        .uart_rx(uart_rx),
        .data(data),
        .rx_done(rx_done)
    );
    
    uart_cmd uart_cmd_inst(
        .clk(clk),
        .rstn(rstn),
        .rx_data(data),
        .rx_done(rx_done),
        .ctrl(ctrl),
        .times(times)
    );
        
    counter_led4 counter_led4_inst(
        .clk(clk),
        .rstn(rstn),
        .ctrl(ctrl),
        .times(times),
        .led(led)
    );
    
endmodule
cpp 复制代码
module uart_byte_rx1(
    clk,
    rstn,
    blaud_set,
    uart_rx,
    data,
    rx_done
);

    input clk;
    input rstn;
    input [2:0]blaud_set;
    input uart_rx;
    output reg [7:0] data;
    output rx_done;

    reg [8:0] bps_dr;
    always@(*)
        case(blaud_set)
            0:bps_dr = 1000000000/9600/16/20;
            1:bps_dr = 1000000000/19200/16/20;
            2:bps_dr = 1000000000/38400/16/20;
            3:bps_dr = 1000000000/57600/16/20;
            4:bps_dr = 1000000000/115200/16/20;
            default : bps_dr = 1000000000/9600/16/20;
        endcase
        
    //边沿信号检测
    reg [1:0] uart_rx_r; //用两位寄存器分别存储两个时间沿的uart_rx信号
    always@(posedge clk) begin
        uart_rx_r[0] <= uart_rx;
        uart_rx_r[1] <= uart_rx_r[0];
    end
    
    //将两位寄存器的值直接通过导线输出进行判断(不需要再使用寄存器)
    wire nedge_uart_rx;  //掌握一下这个方法,之前一直使用的是寄存器
    //法一:
    //assign nedge_uart_rx = ((uart_rx_r[0] == 0)&&(uart_rx_r == 1));
    //法二:
    assign nedge_uart_rx = (uart_rx_r == 2'b10);
    
    reg rx_en;
    always@(posedge clk or negedge rstn)
    if(!rstn)
        rx_en <= 0;
    else if(nedge_uart_rx)
        rx_en <= 1;
    else if(rx_done)
        rx_en <= 0;
    
    
    //周期计数器
    reg [8:0] div_cnt;
    always@(posedge clk or negedge rstn)
    if(!rstn)
        div_cnt <= 0;
    else if(rx_en) begin
        if(div_cnt == bps_dr - 1)
            div_cnt <= 0;
        else
            div_cnt <= div_cnt + 1'd1;
    end
    else
        div_cnt <= 0;
    
    
    wire [3:0]bps_clk_16x; //(一定要记得加位宽)采样信号,这种写法很灵活
    assign bps_clk_16x = bps_dr/2; //采样每一段的中点值,同时也可以用它来计数。
    
    //发送一字节的数据有需要十个数据位,每位数据有16个小段供采样,共160
    reg [7:0]bps_cnt;
    always@(posedge clk or negedge rstn)
    if(!rstn)
        bps_cnt <= 0;
    else if(rx_en) begin
        if(bps_cnt == 159)
            bps_cnt <= 0;
        else if(div_cnt ==bps_clk_16x)
            bps_cnt <= bps_cnt + 1'd1; 
    end
    else  
        bps_cnt <= 0;
    
    reg[2:0] r_data[7:0];//二维数据,代表八个r_data,每个r_data有3位寄存器存储数值。
    reg[2:0] sta_data;
    reg[2:0] sto_data;
    always@(posedge clk or negedge rstn)
    if(!rstn)begin
        sta_data <= 0;
        sto_data <= 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(div_cnt == bps_clk_16x - 1)
    case(bps_cnt) //下面合在一起的写法是允许的
        0:begin
        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_data <= sta_data + 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_data <= sto_data + uart_rx;
        default:;
    endcase        
    
    reg rx_done;
    always@(posedge clk or negedge rstn)
    if(!rstn)
        rx_done <= 0;
    else if(bps_cnt == 159) begin
        rx_done <= 1;
    end
    else
        rx_done <= 0;
    
    //数据接收完成后赋值给data输出
    always@(posedge clk or negedge rstn)
    if(!rstn)
        data <= 0;
    else if(bps_cnt == 159)begin //修改了之前的判定条件(重要)
        data[0] <= (r_data[0] >= 4 ) ? 1 : 0;
        data[1] <= (r_data[1] >= 4 ) ? 1 : 0;
        data[2] <= (r_data[2] >= 4 ) ? 1 : 0;
        data[3] <= (r_data[3] >= 4 ) ? 1 : 0;
        data[4] <= (r_data[4] >= 4 ) ? 1 : 0;
        data[5] <= (r_data[5] >= 4 ) ? 1 : 0;
        data[6] <= (r_data[6] >= 4 ) ? 1 : 0;
        data[7] <= (r_data[7] >= 4 ) ? 1 : 0;
    end
    
     // data[1] <= r_data[1][2]
    
    // 0:3'd000
    // 1:3'd001
    // 2:3'd010
    
    // 4:3'd100
    // 5:3'd101
    // 6:3'd110
    // 7:3'd111 利用第3位的区别给data赋值
    
endmodule
cpp 复制代码
module uart_cmd(
    clk,
    rstn,
    rx_data,
    rx_done,
    ctrl,
    times
);

    input clk;
    input rstn;
    input [7:0]rx_data;
    input rx_done;
    output reg[7:0]ctrl;
    output reg[31:0]times;

    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;
//    always@(posedge clk)
//    if(rx_done)
//        r_rx_done <= rx_done;
    reg r_rx_done;
    always@(posedge clk)
    if(rx_done)
        r_rx_done <= 1;
    else 
        r_rx_done <= 0;

    always@(posedge clk or negedge rstn)
    if(!rstn)begin
        times <= 0;
        ctrl <= 0;
    end
    else 
    if(r_rx_done)
        if((data_str[0] == 8'h55) && (data_str[1] == 8'hA5) && (data_str[7] == 8'hF0))begin
            times[7:0] <= data_str[2];
            times[15:8] <= data_str[3];
            times[23:16] <= data_str[4];
            times[31:24] <= data_str[5];
            ctrl[7:0] <= data_str[6];
        end
    else begin
        ctrl <= ctrl;
        times <= times;
    end
endmodule
cpp 复制代码
module counter_led4(
	clk,
	rstn,
	ctrl,
	times,
	led
);
	
	input clk;
	input rstn;
	input [7:0] ctrl; 
	input [31:0] times; 
	output reg led;
	
	reg[31:0] counter;
	always@(posedge clk or negedge rstn)
	if(!rstn)
		counter <= 0;
//为了防止times为0时减1会得到非常到的数值导致归0时间很长(重要)
	else if(counter >= times - 1'd1) 
		counter <= 0;
	else
		counter <= counter + 1'd1;
    
    reg [2:0]counter2;
    always@(posedge clk or negedge rstn)
	if(!rstn)
		counter2 <= 0;
	else if(counter == times - 1'd1)
	   counter2 <= counter2 + 1'd1;
		
		
	always@(posedge clk or negedge rstn)
	if(!rstn)  
	    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

2.2 仿真代码

cpp 复制代码
`timescale 1ns / 1ps

module uart_rx_ctrl_led_tb();

    reg clk;
    reg rstn;
    reg uart_rx;
    wire led;
    
    wire [2:0]blaud_set;
    assign blaud_set = 3'd4;

    uart_rx_ctrl_led uart_rx_ctrl_led_inst(
        .clk(clk),
        .rstn(rstn),
        .uart_rx(uart_rx),
        .blaud_set(blaud_set),
        .led(led)
    );
    
    initial clk = 1;
    always #10 clk = ~clk;
    
    initial begin
        rstn = 0;
        uart_rx = 1;
        #201;
        rstn = 1;
        #200;
        uart_tx_byte(8'h55);
        #90000;
        uart_tx_byte(8'ha5);
        #90000;
        uart_tx_byte(8'h9a);
        #90000;
        uart_tx_byte(8'h78);
        #90000;
        uart_tx_byte(8'h56);
        #90000;
        uart_tx_byte(8'h34);
        #90000;
        uart_tx_byte(8'h21);
        #90000;
        uart_tx_byte(8'hf0);
        #90000;
        $stop;

    end
    
    
    task uart_tx_byte;
        input [7:0] tx_data;
        begin
            uart_rx = 1;
            #20;
            uart_rx = 0;
            #8680;
            uart_rx = tx_data[0];
            #8680;
            uart_rx = tx_data[1];
            #8680;
            uart_rx = tx_data[2];
            #8680;
            uart_rx = tx_data[3];
            #8680;
            uart_rx = tx_data[4];
            #8680;
            uart_rx = tx_data[5];
            #8680;
            uart_rx = tx_data[6];
            #8680;
            uart_rx = tx_data[7];
        end
    endtask
    



endmodule

仿真波形

3. 调试(重要)

3.1 tx_done信号到来,data_str[]正确接收到了数据且符合协议,但times和ctrl却仍未0,并未产生正确输出。

通过波形分析原因

通过代码找分析原因

3.2 模块接口给错导致的错误

3.3 counter计数判定条件需要修改

  1. 上板验证(通过串口调试助手发送数据)
相关推荐
Mephisto.java1 分钟前
【大数据学习 | kafka高级部分】kafka中的选举机制
大数据·学习·kafka
Yawesh_best15 分钟前
思源笔记轻松连接本地Ollama大语言模型,开启AI写作新体验!
笔记·语言模型·ai写作
南宫生29 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
武子康2 小时前
大数据-212 数据挖掘 机器学习理论 - 无监督学习算法 KMeans 基本原理 簇内误差平方和
大数据·人工智能·学习·算法·机器学习·数据挖掘
CXDNW2 小时前
【网络面试篇】HTTP(2)(笔记)——http、https、http1.1、http2.0
网络·笔记·http·面试·https·http2.0
使者大牙2 小时前
【大语言模型学习笔记】第一篇:LLM大规模语言模型介绍
笔记·学习·语言模型
ssf-yasuo2 小时前
SPIRE: Semantic Prompt-Driven Image Restoration 论文阅读笔记
论文阅读·笔记·prompt
天行健PLUS2 小时前
【经验分享】六西格玛管理培训适合哪些人参加?
经验分享
As977_2 小时前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
ajsbxi2 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet