🎉欢迎来到FPGA专栏~串口发送字符串
🎉 目录-串口发送字符串
一、效果演示
🥝发送Hello:
🥝发送数字字符并自增1:
🥝发送数字字符复位后从1开始发送:
二、代码编写
✨注:本篇文章需要使用到按键消抖模块 和串口发送模块(1byte) :
按键消抖模块: 【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现)。
串口发送模块: 【FPGA零基础学习之旅#13】串口发送模块设计与验证。
首先展示整体代码和RTL视图。
代码:uart_string_tx_top.v
c
module uart_string_tx_top(
input Clk,
input Rst_n,
input key_in,
output uart_tx,
output led
);
reg send_en;
reg [7:0]data_byte;
reg [2:0]cnt;
wire Tx_Done;
wire key_flag;
wire key_state;
localparam
byte1 = "H",
byte2 = "E",
byte3 = "L",
byte4 = "L",
byte5 = "O",
byte6 = "\n";
KeyFilter KeyFilter(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
uart_byte_tx uart_byte_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.data_byte(data_byte),
.send_en(send_en),
.baud_set(3'd0),
.uart_tx(uart_tx),
.Tx_Done(Tx_Done),
.uart_state(led)
);
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 1'b0;
else if(Tx_Done)
cnt <= cnt + 1'b1;
else if(key_flag & !key_state)
cnt <= 1'b0;
else
;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
send_en <= 1'b0;
else if(key_flag & !key_state)
send_en <= 1'b1;
else if(Tx_Done & (cnt < 3'd5))
send_en <= 1'b1;
else
send_en <= 1'b0;
end
always@(*)begin
case(cnt)
3'd0:data_byte = byte1;
3'd1:data_byte = byte2;
3'd2:data_byte = byte3;
3'd3:data_byte = byte4;
3'd4:data_byte = byte5;
3'd5:data_byte = byte6;
default:data_byte = 0;
endcase
end
endmodule
RTL视图:
🔸设计思路:
使用前文的串口发送模块(FPGA零基础学习之旅#13】串口发送模块设计与验证)一次只能发送1byte的数据,为了发送多比特的字符串数据,我们将字符串按照byte流发送出去即可。
🔸代码详解:
用于判断串口发送模块已发送完1byte数据:
c
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 1'b0;
else if(Tx_Done)
cnt <= cnt + 1'b1;
else if(key_flag & !key_state)
cnt <= 1'b0;
else
;
end
用于开启或关闭串口发送模块的使能信号:
c
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
send_en <= 1'b0;
else if(key_flag & !key_state)
send_en <= 1'b1;
else if(Tx_Done & (cnt < 3'd5))
send_en <= 1'b1;
else
send_en <= 1'b0;
end
通过查找表的方式将byte流发送出去:
c
always@(*)begin
case(cnt)
3'd0:data_byte = byte1;
3'd1:data_byte = byte2;
3'd2:data_byte = byte3;
3'd3:data_byte = byte4;
3'd4:data_byte = byte5;
3'd5:data_byte = byte6;
default:data_byte = 0;
endcase
end
测试激励文件:
c
`timescale 1ns/1ns
`define clock_period 20
module uart_string_tx_top_tb;
reg Clk;
reg Rst_n;
reg press;
wire key_in;
wire uart_tx;
wire led;
uart_string_tx_top uart_string_tx_top0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.uart_tx(uart_tx),
.led(led)
);
key_model key_model(
.press(press),
.key(key_in)
);
initial Clk = 1;
always#(`clock_period / 2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
press = 0;
#(`clock_period*20 + 1);
Rst_n = 1'b1;
#(`clock_period*20 + 1);
press = 1;
#(`clock_period*20 + 1);
press = 0;
wait(uart_string_tx_top0.Tx_Done &(uart_string_tx_top0.cnt == 3'd5));
#(`clock_period*2000000 + 1);
#(`clock_period*20 + 1);
press = 1;
#(`clock_period*20 + 1);
press = 0;
wait(uart_string_tx_top0.Tx_Done &(uart_string_tx_top0.cnt == 3'd5));
#(`clock_period*2000000 + 1);
$stop;
end
endmodule
其中,仿真模型key_model:
c
`timescale 1ns/1ns
module key_model(press,key);
input press;
output reg key;
reg [15:0]myrand;
initial begin
key = 1'b1;
end
always@(posedge press)
press_key;
task press_key;
begin
//50次随机时间按下抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key = ~key;
end
key = 0;
#25_000_000;//按下稳定
//50次随机时间释放抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key = ~key;
end
key = 1;
#25_000_000;//释放稳定
end
endtask
endmodule
关于上述仿真模型的基础讲解,见文章:【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现)。
仿真结果:
三、封装为模块
将uart_string_tx_top中发送字符串"Hello"代码的部分封装为一个模块,只需要把设计部分中使用到的输入输出信号整理好即可。
Str_Hello.v:
c
module Str_Hello(
input Clk,
input Rst_n,
input Tx_Done,
input key_flag,
input key_state,
output reg send_en,
output reg [7:0] data_byte
);
reg [2:0]cnt;
localparam
byte1 = "H",
byte2 = "E",
byte3 = "L",
byte4 = "L",
byte5 = "O",
byte6 = "\n";
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 1'b0;
else if(Tx_Done)
cnt <= cnt + 1'b1;
else if(key_flag & !key_state)
cnt <= 1'b0;
else
;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
send_en <= 1'b0;
else if(key_flag & !key_state)
send_en <= 1'b1;
else if(Tx_Done & (cnt < 3'd5))
send_en <= 1'b1;
else
send_en <= 1'b0;
end
always@(*)begin
case(cnt)
3'd0:data_byte = byte1;
3'd1:data_byte = byte2;
3'd2:data_byte = byte3;
3'd3:data_byte = byte4;
3'd4:data_byte = byte5;
3'd5:data_byte = byte6;
default:data_byte = 0;
endcase
end
endmodule
module Str_Hello的RTL视图:
将该模块例化到顶层模块中:
c
module uart_string_tx_top(
input Clk,
input Rst_n,
input key_in,
output uart_tx,
output led
);
wire send_en;
wire [7:0]data_byte;
wire Tx_Done;
wire key_flag;
wire key_state;
KeyFilter KeyFilter(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
uart_byte_tx uart_byte_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.data_byte(data_byte),
.send_en(send_en),
.baud_set(3'd0),
.uart_tx(uart_tx),
.Tx_Done(Tx_Done),
.uart_state(led)
);
Str_Hello Str_Hello0(
.Clk(Clk),
.Rst_n(Rst_n),
.Tx_Done(Tx_Done),
.send_en(send_en),
.key_flag(key_flag),
.key_state(key_state),
.data_byte(data_byte)
);
endmodule
顶层模块的RTL视图:
四、其余项目
以使用串口发送模块发送字符串的思路,编写一个模块:
按下一次按键,串口发送字符"0";再按下一次按键,串口发送字符"1";... ;再按下一次按键,串口发送字符"9"。且每一个数字字符为一行。
🔸实现思路:
当接收到一次按键信号之后,串口发送模块依次发送数字字符和一个换行符;同时,再接收到一次按键信号的同时,内部的计数器开始计数,一次按键信号获取后计数器增加1,用于判断发送的数字字符的大小。
🔸实现效果:
🔸先看RTL视图 来理解思路:
通过RTL视图,可以看到串口发送模块的Tx_Done信号是作为反馈信号输入给Num_Adder模块的,该信号即用于判断一个byte数据发送的完成。当一个数字字符发送完成并返回Tx_Done信号之后,需要继续发送一个换行符。
module Num_Adder.v:
c
module Num_Adder(
input Clk,
input Rst_n,
input key_flag,
input key_state,
input Tx_Done,
output reg send_en,
output reg [7:0] Num_byte
);
reg [3:0]cnt;
reg [1:0]cnt_N;
reg [7:0]Num_byte_r;
//--------<发送数据的增加模块>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 4'd0;
else if(key_flag & !key_state)
cnt <= cnt + 1'd1;
else if(cnt == 4'd10)
cnt <= 4'd0;
else
cnt <= cnt;
end
//--------<数据查找表>--------
always@(*)begin
case(cnt)
4'd1:Num_byte_r = "0";
4'd2:Num_byte_r = "1";
4'd3:Num_byte_r = "2";
4'd4:Num_byte_r = "3";
4'd5:Num_byte_r = "4";
4'd6:Num_byte_r = "5";
4'd7:Num_byte_r = "6";
4'd8:Num_byte_r = "7";
4'd9:Num_byte_r = "8";
4'd10:Num_byte_r = "9";
default:Num_byte_r = 0;
endcase
end
//--------<发送"\n"的计数器>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt_N <= 2'd0;
else if(Tx_Done)
cnt_N <= cnt_N + 1'b1;
else if(key_flag & !key_state)
cnt_N <= 2'd0;
else
;
end
//--------<发送"\n">--------
always@(*)begin
case(cnt_N)
2'd0:Num_byte = Num_byte_r;
2'd1:Num_byte = "\n";
default:Num_byte = 0;
endcase
end
//--------<发送模块使能信号的处理>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
send_en <= 1'b0;
else if(key_flag & !key_state)
send_en <= 1'b1;
else if(Tx_Done & (cnt_N < 2'd2))
send_en <= 1'b1;
else
send_en <= 1'b0;
end
endmodule
module uart_NumAdder_tx_top.v:
c
module uart_NumAdder_tx_top(
input Clk,
input Rst_n,
input key_in,
output uart_tx,
output led
);
wire [7:0]data_byte;
wire key_flag;
wire key_state;
wire Tx_Done;
wire send_en;
KeyFilter KeyFilter(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
Num_Adder Num_Adder0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_flag(key_flag),
.key_state(key_state),
.Tx_Done(Tx_Done),
.send_en(send_en),
.Num_byte(data_byte)
);
uart_byte_tx uart_byte_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.data_byte(data_byte),
.send_en(send_en),
.baud_set(3'd0),
.uart_tx(uart_tx),
.Tx_Done(Tx_Done),
.uart_state(led)
);
endmodule
五、后记
我在我的每篇文章中都几乎放上设计的RTL视图,因为观察RTL视图,也是一种简单的debug方法。
在设计串口发送字符串的逻辑框架时,发现仿真一直出不来结果,直到我看了一眼RTL视图:
看了之后才发现是例化模块的时候,引脚绑定的大小写不一致导致的。
🧸结尾