祝各位道友念头通达
GitHub Gitee 语雀 打赏

fpga 串口调试代码实现

发送一串字符, rx收到波形图如下

image
由上图可知, 串口接收rx线一直处于拉高状态, 起始一直处于高电平

搞懂波特率

搞懂串口,首先他不依靠时钟传输数据, 通过双方约束好的波特率, 计算发送一个 bit 的数据所花费的时间
通过下图所示可以查看出串口发送一个 bit 所需要 434 个时钟周期, 已知时钟为50MHZ
t(发送1bit) = (50MHZ/波特率)*20ns;
也可以这么理解波特率, 就是 一秒发送多少个bit, 115200 意思就是一秒可以发送 115200 bit 的数据
image

搞懂数据位,数据格式

发送AB, 一个字节

下图是以波特率115200 停止位1,数据位8,无奇偶校验下 发送的十六进制 AB 1010, 1011
发送工具使用的是 XCOM
image
收到的波形图如下
image

发送的AB: 1010.1011
收到的AB: 1101.0101

正好和发送的数据相反, 从发送端发送数据是顺序的, 用的是一根线,在接收端收到的就是反序的
image

发送ABCD, 两个字节

发现S计数器总共发送了两次, 数据位是八位, 发送两个字节需要发送两次
image
发送两次的波形图如下
image

发送十次波形图如下

image

FPGA 实现串口数据的接收

uart_recv

`timescale 1ns / 1ps

module Uart_recv(
    input sys_clk,
    input uart_rx,
    output reg [7:0] rdata   //接收到的串口数据
);

// 波特率位 115200, 计算出一个bit需要多久, 
// t(1bit) = (1*10^9)/115200 =  8680.55 ns
// c = 8680.55/20 = 434 次
parameter TIME_1BIT = 434;   //根据波特率计算出来的

parameter UART_DATA = 8;     //数据位

reg [8:0] rx_cnt;
reg [3:0] rx_dcnt;                              //接收数据位的大小, 数据位(8)
reg is_start;                                   //是否开始采数据 00,否, 01, 开始采集数据
                                                //监测 rx下降沿
always @(posedge sys_clk) begin
                                                //如果还没开始采集数据,则监测 uart_tx 的下降沿 
    if(!is_start) begin 
                                                //如果监测到下降沿
        if(!uart_rx) begin
            is_start <= 1'b1;                   //监测到下降沿了, 开始采集数据
            rx_dcnt <= 1'b0;
            rx_cnt <= 1'b0;                     //计数器置0
        end
    end else if(rx_cnt == TIME_1BIT) begin      //如果开始采集数据的话, 则判断累加数, 如果累加数满足 一个数据位发送的时间周期,则进行下一步
        if(rx_dcnt == UART_DATA) begin          //处理数据位, 当数据位为8时
            is_start <= 1'b0;                   //start标记第一个字节获取结束
            rdata <= 1'b0;                      //rdata 清零
        end else                                //如果累加的数据位小于8,则将rx边沿变化赋值于 rdata
            rdata[rx_dcnt] <= uart_rx;

        rx_dcnt <= rx_dcnt + 1'b1;              //累加
        rx_cnt <= 1'b0;                         //计数器清零
    end else begin                              //如果不满足一个周期,则累加数
        rx_cnt <= rx_cnt + 1'b1;
    end
end

//在线分析
//ila_0 ila_0 (
// 	.clk(sys_clk),         // input wire clk
// 	.probe0(uart_rx),      // input wire [0:0]  probe0
// 	.probe1(rdata),         // input wire [0:0]  probe1
//   .probe2(rx_dcnt)
//);
endmodule

分析截图
image

uart_send

`timescale 1ns / 1ps
module Uart_send(
    input sys_clk,
    input tx_val_enable,
    input [7:0] tx_data,
    output reg uart_tx,
    output reg tx_val_disable
);

parameter TIME_1BIT = 434;
parameter UART_DATA = 8;

reg [8:0] rx_cnt = 1'b0;                               //累加器, 计算一个bit传送所需要的时间
reg [3:0] rx_dcnt = 1'b0;                              //接收数据位的大小, 数据位(8)
reg is_start = 1'b0;                                   //记录是否第一次发送

initial begin
    tx_val_disable <= 1'b0;
    uart_tx <= 1'b1;
end

always @(posedge sys_clk) begin         
    if(tx_val_enable) begin                     //如果确认要发送的话, 开始发送 uart_tx
        if(!is_start) begin                     //如果是第一次发送数据的话
            is_start <= 1'b1;                   //标记已经开始发送数据
            rx_dcnt <= 1'b0;                    //初始化数据位计数器为 0
            rx_cnt <= 1'b0;                     //计时器清零
            uart_tx <= 1'b0;                    //先将边沿拉低,告知对方自己要开始发送数据
        end else if(rx_cnt == TIME_1BIT) begin  //计数器,计时 TIME_1BIT 次进行数据位的发送
            if(rx_dcnt == UART_DATA) begin      //当数据位计时器等于8bit的时候
                is_start <= 1'b0;               //是否第一次发送失能
            end else begin
                uart_tx <= tx_data[rx_dcnt];    //从下标0开始发送,发送 0~7
            end 
            
            if(rx_dcnt == UART_DATA - 1'b1) begin
                tx_val_disable <= 1'b1;         //输出失效状之后,还可以在发送 1bit
            end

            rx_dcnt <= rx_dcnt + 1'b1;          //数据位累加
            rx_cnt <= 1'b0;                     //计数器清零
        end else begin
            rx_cnt <= rx_cnt + 1'b1;
        end  
    end else begin
        tx_val_disable <= 1'b0;
    end
end

endmodule

场景练习 (楼梯灯)

  1. 按键, 灯亮一段时间(1000ms)
  2. 感光(串口模拟发送AB), 灯常亮
  3. 2的优先级高于1
  4. 灯灭或灯亮,状态切换的时候需要在串口输出状态值 F0/F1

模块分析

  1. 按键消抖
  2. 时钟延迟
  3. 边沿采样 (灯状态变化-rx)
  4. 锁存器 (按键值状态以及AB值的保留, 串口发送一次AB,即可让灯一直亮,如果发送非AB,则灯灭)
  5. 串口发送
  6. 串口接收

代码

UART_test (top)

点击查看代码
```verilog
`timescale 1ns / 1ps

module UART_test(
    input sys_clk,
    input sys_rst,
    input key,
    input uart_rx,  //串口接收线 
    output uart_tx, //串口发送线
    output reg led
);

wire rx_val_enable;   //接收有效数据使能
reg tx_val_enable;    //发送数据使能
wire tx_val_disable;  //发送使能失效

wire key_val_output;
reg key_val_input;

wire [7:0] rx_data;
reg [7:0] tx_data;

reg sys_clk_flag = 1'b0;                                //记录时钟变化
reg pre_led;                                            //记录上一次灯的状态

initial begin
    tx_val_enable = 1'b0;
    led = 1'b0;
    pre_led = 1'b0;
    tx_data = 1'b0;
    key_val_input = 1'b0;
end

always @(posedge sys_clk) begin
    key_val_input = key_val_output;
end

// 当按键按下的时候, 灯亮, 将状态发送至串口
// 当接收到指定格式命令字, 让灯亮, 将灯的状态发送至串口
key_debounce u_key_debounce(
    .sys_clk    (sys_clk),
    .sys_rst    (sys_rst),
    .key        (key),
    .key_val    (key_val_output)
);

wire led_flag;   //使能,灯亮, 否则 灯灭
// led 延迟亮一段时间, 然后将状态发送至串口
beep_control u_beep_control(
    .sys_clk    (sys_clk),
    .sys_rst    (sys_rst),
    .key_val    (key_val_input),
    .beep       (led_flag)
);

// 接收 命令字, 点亮灯, 然后将状态从 uart_tx 发送出去 
always @(posedge sys_clk) begin
    sys_clk_flag = ~sys_clk_flag;
    if ((rx_val_enable && rx_data == 8'hAB) || led_flag) begin        //监测rx使能的话, 则值有效
        led <= 1'b1;                                    //灯亮
    end else begin
        led <= 1'b0;
    end

    pre_led <= led;                                 
end

ila_0 ila_0 (
	.clk(sys_clk), // input wire clk
	.probe0(led), // input wire [0:0]  probe0  
	.probe1(pre_led), // input wire [0:0]  probe1 
	.probe2(tx_val_enable), // input wire [0:0]  probe2 
	.probe3(tx_val_disable) // input wire [0:0]  probe3
);

always @(posedge sys_clk) begin
    if(led != pre_led) begin
        tx_val_enable <= 1'b1;                                   
        tx_data <= 8'hF0 + led;                                 //如果发送使能, 则发送数据
    end else if(tx_val_disable == 1'b1) begin
        tx_val_enable <= 1'b0;
    end
end

parameter TIME_1BIT = 434; 
parameter UART_DATA = 8;    

Uart_recv #(
    .TIME_1BIT (TIME_1BIT),
    .UART_DATA (UART_DATA)
) u_uart_recv(
    .sys_clk    (sys_clk),
    .uart_rx    (uart_rx),
    .rx_val_enable (rx_val_enable),
    .rx_data      (rx_data)
);

Uart_send #(
    .TIME_1BIT (TIME_1BIT),
    .UART_DATA (UART_DATA)
) u_uart_send(
    .sys_clk    (sys_clk),
    .tx_val_enable (tx_val_enable),
    .tx_val_disable (tx_val_disable),
    .tx_data       (tx_data),
    .uart_tx       (uart_tx)
);

endmodule

key_debounce 按键消抖

点击查看代码
```verilog
`timescale 1ns / 1ps
module key_debounce(
    input sys_clk,
    input sys_rst,
    input key,
    output reg key_val
);

reg [19:0] cnt;                   //计时器
reg cur_key;
reg pre_key;                      //记录上一次按键的值

initial begin
    key_val = 1'b0;
end

parameter DELAY = 20'd100_0000;   //延时20ms, 计时 100_0000 次
parameter KEY_DOWN = 1'b0;        //按键按下的状态 

always @(posedge sys_clk or negedge sys_rst) begin
    if(!sys_rst) begin
        key_val <= 0;
        pre_key <= 0;
        cur_key <= 0;
        cnt <= 20'd0;
    end else if(key == KEY_DOWN) begin
        cur_key <= ~key;
        pre_key <= cur_key;
        if(pre_key == cur_key) 
            if(cnt > 0) 
                cnt <= cnt - 1'b1;
            else
                key_val <= 1;
        // else
        //     cnt <= 0;
    end else begin
        cur_key <= 1'b1;
        pre_key <= 1'b0;
        cnt <= DELAY;
        key_val <= 0;
    end

end

endmodule

beep_control 灯亮延迟

点击查看代码
```verilog
`timescale 1ns / 1ps
module beep_control(
    input sys_clk,
    input sys_rst,
    input key_val,
    output reg beep
);

initial begin
    beep = 1'b0;
end

parameter BEEP_DELAY = 32'd5000_0000;   // 用来延迟响应 1000ms
// parameter BEEP_DELAY = 32'd10;   // 用来延迟响应 10ms

reg [31:0] beep_cnt;

always @(posedge sys_clk or negedge sys_rst) begin
    if(!sys_rst) begin
        beep = 1'b0;
        beep_cnt <= 0;
    end else if(key_val) begin
        beep_cnt <= BEEP_DELAY;
        beep <= 1'b0;
    end else begin
        if(beep_cnt > 0) begin
            beep_cnt <= beep_cnt - 1'b1;
            beep <= 1'b1; 
        end else 
            beep <= 1'b0;
    end
end

endmodule

Uart_recv 串口接收

点击查看代码
```verilog
`timescale 1ns / 1ps

module Uart_recv(
    input sys_clk,
    input uart_rx,
    output reg rx_val_enable,
    output reg [7:0] rx_data   //接收到的串口数据
);

// 波特率位 115200, 计算出一个bit需要多久, 
// t(1bit) = (1*10^9)/115200 =  8680.55 ns
// c = 8680.55/20 = 434 次
parameter TIME_1BIT = 434;   //根据波特率计算出来的

parameter UART_DATA = 8;     //数据位

reg [7:0] rdata = 1'b0;
reg [8:0] rx_cnt = 1'b0;                               //累加器, 计算一个bit传送所需要的时间
reg [3:0] rx_dcnt = 1'b0;                              //接收数据位的大小, 数据位(8)
reg is_start = 1'b0;                                   //是否开始采数据 00,否, 01, 开始采集数据

initial begin
    rx_val_enable <= 1'b0;
    rx_data <= 1'b0;
end
                                                //监测 rx下降沿
always @(posedge sys_clk) begin
                                                //如果还没开始采集数据,则监测 uart_tx 的下降沿 
    if(!is_start) begin                         //让值使能
        if(!uart_rx) begin
            is_start <= 1'b1;                   //监测到下降沿了, 开始采集数据
            rx_dcnt <= 1'b0;                    //初始化数据位计数器为 0
            rx_cnt <= 1'b0;                     //计数器置0
        end
    end else if(rx_cnt == TIME_1BIT) begin      //如果开始采集数据的话, 则判断累加数, 如果累加数满足 一个数据位发送的时间周期,则进行下一步
        if(rx_dcnt == UART_DATA) begin          //处理数据位, 当数据位为8时
            is_start <= 1'b0;                   //start标记第一个字节获取结束
            rdata <= 1'b0;                      //rdata 清零
            rx_val_enable <= 1'b1;              //让值使能
            rx_data <= rdata;                   //将有效的值输出
        end else                                //如果累加的数据位小于8,则将rx边沿变化赋值于 rdata
            rdata[rx_dcnt] <= uart_rx;

        rx_dcnt <= rx_dcnt + 1'b1;              //累加
        rx_cnt <= 1'b0;                         //计数器清零
    end else begin                              //如果不满足一个周期,则累加数
        rx_cnt <= rx_cnt + 1'b1;                
    end
end

endmodule

Uart_send 串口发送

点击查看代码
```verilog
`timescale 1ns / 1ps
module Uart_send(
    input sys_clk,
    input tx_val_enable,
    input [7:0] tx_data,
    output reg uart_tx,
    output reg tx_val_disable
);

parameter TIME_1BIT = 434;
parameter UART_DATA = 8;

reg [8:0] rx_cnt = 1'b0;                               //累加器, 计算一个bit传送所需要的时间
reg [3:0] rx_dcnt = 1'b0;                              //接收数据位的大小, 数据位(8)
reg is_start = 1'b0;                                   //记录是否第一次发送

initial begin
    tx_val_disable <= 1'b0;
    uart_tx <= 1'b1;
end

always @(posedge sys_clk) begin         
    if(tx_val_enable) begin                     //如果确认要发送的话, 开始发送 uart_tx
        if(!is_start) begin                     //如果是第一次发送数据的话
            is_start <= 1'b1;                   //标记已经开始发送数据
            rx_dcnt <= 1'b0;                    //初始化数据位计数器为 0
            rx_cnt <= 1'b0;                     //计时器清零
            uart_tx <= 1'b0;                    //先将边沿拉低,告知对方自己要开始发送数据
        end else if(rx_cnt == TIME_1BIT) begin  //计数器,计时 TIME_1BIT 次进行数据位的发送
            if(rx_dcnt == UART_DATA) begin      //当数据位计时器等于8bit的时候
                is_start <= 1'b0;               //是否第一次发送失能
            end else begin
                uart_tx <= tx_data[rx_dcnt];    //从下标0开始发送,发送 0~7
            end 
            
            if(rx_dcnt == UART_DATA - 1'b1) begin
                tx_val_disable <= 1'b1;         //输出失效状之后,还可以在发送 1bit
            end

            rx_dcnt <= rx_dcnt + 1'b1;          //数据位累加
            rx_cnt <= 1'b0;                     //计数器清零
        end else begin
            rx_cnt <= rx_cnt + 1'b1;
        end  
    end else begin
        tx_val_disable <= 1'b0;
    end
end

endmodule

design之后

image

posted @ 2022-08-24 14:10  韩若明瞳  阅读(498)  评论(0)    收藏  举报