FPGA串口收發(四):接收數據並轉發,間隔時間發送
// Description: 串口收發:串口接收數據,內部生成數據,串口間隔特定時間發送數據
// 串口接收數據:串行信號線 1101_1000 ,轉爲並行數據,取反截取低4位 傳遞給led,再傳遞給data_gen,
// 發送數據: 0.1ms生成/發送一位數據,發送字符串"==HELLO WORLD=" (對應ASCII碼),以及led值
1、源文件
uart_rx.v
uart_tx.v
uart_data_gen.v
uart_top.v
uart_data_gen.v
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Myminieye
// Engineer: Nill
//
// Create Date: 2019-08-20 14:36
// Design Name:
// Module Name: uart_data_gen
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 產生的數據,用於串口發送
// 字符串"====HELLO WORLD==="
// 每秒1s產生一個字符,有可能再加上串口接收的數據( write_max_num 比8'h14 大時)
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
`define UD #1
module uart_data_gen #(
parameter CLK_CNT_1S = 26'h0FA0 //定義參數,計數次數
//晶振40MHz,1s需要計數的 40M次 = 40*10^6 = 0x2625A00
//parameter clk_cnt = 26'h2625A00;
)
(
input clk,
input rst_n,
input [7:0] read_data,
input tx_busy,
input [7:0] write_max_num, //字符串長度,發送的最大字符數 8'h14(20個)
output reg [7:0] write_data, //生成的數據,傳出給到串口發送模塊 uart_tx
output reg write_en //開始生成數據,傳遞給uart_tx模塊,開啓串口發送狀態tx_en
);
//==========================================================================
//wire and reg 定義:信號與參數
//==========================================================================
reg [25:0] time_cnt=0; //計數次數,記到26'h2625A00
reg [ 7:0] data_num; //8位數字,位數
// 設置串口發射工作區間
reg work_en=0;
reg work_en_1d=0;
//==========================================================================
//內部信號觸發,生成數據使能 work_en :1s計時, 每秒產生一個字符
//==========================================================================
//計數遞增,與計數截止的if分開
always @(posedge clk)
begin
time_cnt <= `UD time_cnt + 26'd1;
end
//============ work_en使能,開始生成數據:內部計時1s, ============
//計數時間到,work_en生成數據使能,一個字符串/每秒
always @(posedge clk)
begin
if(time_cnt == CLK_CNT_1S) //計數到1s
//if(time_cnt == 5'd2048)
work_en <= `UD 1'b1; //開始生成數據
else if(data_num == write_max_num-1'b1)//第19個字符
work_en <= `UD 1'b0; //生成數據結束
end
//生成數據使能,打一拍:work_en_1d比work_en,慢一拍
//計時剛到1s,work_en爲1,work_en_1d爲0
always @(posedge clk)
begin
work_en_1d <= `UD work_en;
end
//==========================================================================
//外部信號觸發:串口發送模塊接觸忙碌,獲取tx_busy的下降沿
//==========================================================================
//===========發送寄存器,輸入狀態 tx_busy_f :忙->空閒============
//獲取tx_busy的下降沿,從忙變成不忙:1變成0
reg tx_busy_reg = 0;
wire tx_busy_f;
//發送忙狀態,打一拍
always @ (posedge clk)
tx_busy_reg <= `UD tx_busy;
//tx_busy_f,下降沿fall
assign tx_busy_f = (!tx_busy) && (tx_busy_reg);
//==========================================================================
//內部信號和外部信號,綜合觸發
//1、內部狀態,觸發產生數據動作:write_pluse 高有效,每秒產生一個字符
//2、外部狀態,write_pluse -> write_en -> 輸出,觸發串口發送
//==========================================================================
//===============觸發信號:產生數據 & 發送數據==================
/*
@功能1: 計時剛到1s,可以發送,字符串的下一個數據,給出觸發信號
(若發送狀態機沒走完,還是忙,等走完再發送)
@數據流1: work_en + work_en_1d -> write_pulse(data_gen) -> write_en(data_gen) -> tx_en(top) -> tx_pulse(uart_tx)
@功能2: (計時1s多)解除忙狀態,執行發送,字符串的下一個數據,給出觸發信號
@數據流2: tx_busy -> tx_busy_f -> write_pulse(data_gen) -> write_en(data_gen) -> tx_en(top) -> tx_pulse(uart_tx)
@功能3: 1s剛到,此時發送也解除了忙,執行發送,給出觸發信號
*/
//========1、 觸發產生數據動作:write_pluse 高有效=======
reg write_pluse;
always @ (posedge clk)
begin
if(!rst_n)
write_pluse <= `UD 1'b0;
else if(work_en) //此時,計時到了1s
begin
if(~work_en_1d || tx_busy_f)
//1、work_en_1d=0,剛到1s(上一刻還沒計數完,還是0),每1s發送
//2、tx_busy_f=1,早就到了1s,而且剛解除發送忙(下降沿)。 -> 相當於發送模塊這8位發完了,傳出tx_busy不忙,進入data_gen,又返回發送模塊,觸發下一組8位發送
write_pluse <= `UD 1'b1;
else
write_pluse <= `UD 1'b0;
/*// 把 或關係||拆開,換個寫法
if(~work_en_1d)
write_pluse <= `UD 1'b1;
else if ( tx_busy_f)
write_pluse <= `UD 1'b1;
else
write_pluse <= `UD 1'b0;
*/
end
else
write_pluse <= `UD 1'b0;
end
//==========2、開始生成數據狀態 write_en,高有效,輸出 ========
//打一拍,作爲發送觸發,反饋給輸出模塊
always @(posedge clk)
begin
write_en <= `UD write_pluse; //write_en比 write_pluse 慢一拍
end
//=================產生數據1:數據位遞增======================
//
always @ (posedge clk)
begin
if ( !rst_n )
data_num <= `UD 8'h0;
else if ( ~work_en & tx_busy_f )
//從0開始發送新的字符串:字符串(20個字符)發完了,並且發送狀態剛解除忙(下降沿)
data_num <= 8'h0; //源代碼爲 7'h0?
else if ( write_pluse )
//此字符串,發送下一個字符:計時剛到1s,或者1s多瞭解除了發送忙狀態
data_num <= data_num + 8'h1;
end
//================== 產生數據2:向外送出字符串 "====HELLO WORLD==="
// 字符的對應ASCII碼
// H:0x48 E:0x45 L:0x4C O:0x4F W:0x57 R:0x52 D:0x44
always @ (posedge clk)
begin
case(data_num)
8'h0 ,
8'h1 ,
8'h2 ,
8'h3 : write_data <= `UD 8'h3D;// ASCII code is =
8'h4 : write_data <= `UD 8'h48;// ASCII code is H
8'h5 : write_data <= `UD 8'h45;// ASCII code is E
8'h6 : write_data <= `UD 8'h4C;// ASCII code is L
8'h7 : write_data <= `UD 8'h4C;// ASCII code is L
8'h8 : write_data <= `UD 8'h4F;// ASCII code is 0
8'h9 : write_data <= `UD 8'h20;// ASCII code is
8'ha : write_data <= `UD 8'h57;// ASCII code is W
8'hb : write_data <= `UD 8'h4F;// ASCII code is O
8'hc : write_data <= `UD 8'h52;// ASCII code is R
8'hd : write_data <= `UD 8'h4C;// ASCII code is L
8'he : write_data <= `UD 8'h44;// ASCII code is D
8'hf ,
8'h10 ,
8'h11 : write_data <= `UD 8'h3D;// ASCII code is =
8'h12 : write_data <= `UD 8'h0d;//回車
8'h13 : write_data <= `UD 8'h0a;//換行
default: write_data <= `UD read_data;//默認態,數字保持不變,輸入數據轉發出去
//在top模塊中修改 write_max_num,比8'h14 大時,可以把串口接收的數據(給到led)再發出去
endcase
end
endmodule
uart_top.v
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Myminieye
// Engineer: Nill
//
// Create Date: 2019-08-20 14:36
// Design Name:
// Module Name: uart
// Project Name:
// Target Devices:
// Tool Versions:
// Description: 串口收發數據
// 串口接收的數據,取反,點亮LED燈:
// 高電平點亮, 發送數據爲 0xF0 或者 11110000
// 串口發送的數據,由data_gen生成,發送字符串"====HELLO WORLD===",每秒發一個字符,加換行回車共計19個
// 若 write_max_num比8'h14大,可以把串口接收的數據(給到led)再發出去
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
`define UD #1
module uart_top #(
parameter BPS_NUM = 16'd347, // 時鐘/波特率,1 bit位寬所需時鐘週期的個數
CLK_CNT_1S = 16'h0FA0 //1s發一次,40M次 = 40*10^6 = 0x2625A00
//0.1ms一次, 40M*10^(-4) = 40*10^6*10^(-4) = 40*100=4000
// parameter BPS_4800: 40MHz set 8333 ; 50MHz set 10417
// parameter BPS_9600: 40MHz set 4167 ; 50MHz set 5208
// parameter BPS_115200: 40MHz set 347; 50MHz set 434
)
(
//input ports
input clk,
input rst_n,
input uart_rx,
//output ports
output [3:0] led,
output uart_tx,
//仿真追加:傳遞以下變量
output [7:0] rx_data, //串口接收的數據
output rx_finish, //串口接收數據有效,接收完成拉高1個BPS
output [7:0] tx_data, //產生的數據(含接收的數據)
output tx_finish //串口發送數據結束標誌,8位數據發完,拉高一個BPS
);
//LED指示燈:BK12_W15,17,14,16 -> led[0],1,2,3(變量命名)-> LED4,3,2,1 (絲印從下到上)
//==========================================================================
//wire and reg 定義:信號與參數
//==========================================================================
wire tx_busy; //transmitter is free.
wire tx_en; //開啓串口發送,整個發送週期都拉高
wire [7:0] write_data;
assign tx_data = write_data;
reg [7:0] write_max_num = 8'h15;//發送字符串位數,20位
//在top模塊中修改 write_max_num,比8'h14 大時,可以把串口接收的數據(給到led)再發出去
reg [7:0] receive_data;
always @(posedge clk)
receive_data <= led ;
//==========================================================================
//調用top模塊
//==========================================================================
//產生數據,1s一個
uart_data_gen #(
.CLK_CNT_1S ( CLK_CNT_1S )
)
u_uart_data_gen(
.clk ( clk ),//input clk,
.rst_n ( rst_n ),//input rst_n,
.read_data ( receive_data ),//input [7:0] read_data,
.tx_busy ( tx_busy ),//input 輸入,串口發送忙狀態
.write_max_num( write_max_num),//input [7:0] write_max_num,
.write_data ( write_data ),//output reg [7:0] 生成的數據
.write_en ( tx_en ) //output reg 輸出,當前爲生成數據狀態
);
//串口發送
uart_tx #(
//.CLK_SYS ( SYS_CLK_FRE), //系統時鐘
.BPS_NUM ( BPS_NUM ) // 時鐘/波特率,1 bit位寬所需時鐘週期的個數
)
u_uart_tx(
.clk ( clk ),// input clk,
.tx_data ( tx_data ),// input [7:0] tx_data,
.tx_pulse ( tx_en ),// input 外部輸入,開始產生數據->開啓串口發送狀態
.uart_tx ( uart_tx ),// output reg uart_tx,
.tx_busy ( tx_busy ), // output 輸出,串口接收忙狀態
.tx_finish( tx_finish) // output //串口發送數據結束標誌,8位數據發完,拉高一個BPS
);
//串口接收
uart_rx #(
//.CLK_SYS ( SYS_CLK_FRE), //系統時鐘
.BPS_NUM ( BPS_NUM ) // 時鐘/波特率,1 bit位寬所需時鐘週期的個數
)
u_uart_rx (
.clk ( clk ),// input clk,
.uart_rx ( uart_rx ),// input uart_rx,
.rx_data ( rx_data ),// output reg [7:0] rx_data,
.rx_finish ( rx_finish ) // output 串口接收數據有效,接收完成拉高1個BPS
//.rx_end ( rx_end ) // output //接收到停止位,拉高1個clk,沒啥用
);
assign led = ~rx_data;//8位數據截斷高4位,只剩下低4位
endmodule
2、仿真文件 testbench
tb_uart_top.v
`timescale 1ns / 1ps
//////////////////////////SimulationCode:Test Bench/////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2020/05/21
// Design Name:
// Module Name: tb_uart_top
// Project Name: 串口收發的測試文件
// Target Devices:
// Tool Versions:
// Description: 串口收發:串口接收數據,內部生成數據,串口間隔特定時間發送數據
// 串口接收數據:串行信號線 1101_1000 ,轉爲並行數據,取反截取低4位 傳遞給led,再傳遞給data_gen,
// 發送數據: 0.1ms生成/發送一位數據,發送字符串"====HELLO WORLD===" (對應ASCII碼),以及led值
// Dependencies:
// 仿真週期 run 3ms
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
`define UD #1
module uart_top_tb;
//==========================================================================
//定義:信號與參數
//==========================================================================
reg sim_clk; //模擬時鐘信號
//reg tx_pulse; // active posedge
reg sim_rst_n;
// input to rx module
reg sim_uart_rx; //串口發送信號線
//output from rx module
wire [3:0] sim_led; //接收數據,取反,取低4位,傳給led
wire sim_uart_tx; //串口接收數據有效,接收完成拉高1個BPS
//================================
//仿真追加:將以下變量從下級模塊中傳出來
// uart_rx/tx -> uart_top -> uart_top_tb
//================================
wire rx_finish ; //串口接收數據結束,接收完成拉高1個BPS
reg rx_finish_reg;
wire tx_finish ; //串口發送數據結束,8位數據發完,拉高一個BPS
reg tx_finish_reg;
wire [7:0] rx_data ; //串口接收的數據(8位)
wire [7:0] tx_data ; //串口發送的數據(生成的字符串 + 接收的8位數據)
//時鐘參數
parameter SYS_CLK_FRE = 40_000_000; //系統頻率40MHz 40_000_000
parameter SYS_CLK_PERIOD = 1_000_000_000/SYS_CLK_FRE; //週期25ns
parameter RST_CYCLE = 5; //復位持續時間,clk時鐘週期數
parameter RST_TIME = RST_CYCLE * SYS_CLK_PERIOD; //復位時間:5個時鐘週期
//波特率參數
parameter BAUD_RATE = 115200; //串口波特率
parameter BAUD_RATE_PERIOD = 1_000_000_000/BAUD_RATE;
//波特率週期,0.104ms = 104us,1/9600 s = 1^9 /9600 ns = 4167 sim_clk
//波特率週期, 1/115200 = 8680 ns = 8.7us = 347 sim_clk
//波特率+時鐘參數
parameter [15:0] BPS_NUM = SYS_CLK_FRE / BAUD_RATE; //時鐘/波特率,用時鐘週期構造波特率週期
// BPS_NUM = 40_000_000/115200 = 347.22 = 16'd347
// 1 bit位寬所需時鐘週期的個數。最長的波特率計數,10417,二進制有14位,取16位
// parameter BPS_4800: 40MHz set 8333 ; 50MHz set 10417
// parameter BPS_9600: 40MHz set 4167 ; 50MHz set 5208
// parameter BPS_115200: 40MHz set 347; 50MHz set 434
//串口發送數據的週期
parameter CLK_CNT_1S = 16'h0FA0; //仿真改爲:0.1ms一次, 40M*10^(-4) = 40*10^6*10^(-4) =4000 = 0xFA0
//實際:1s發一次,40M次 = 40*10^6 = 0x2625A00 //CLK_CNT_1S = 26'h2625A00
//==========================================================================
//logic:邏輯信號初始化與判斷
//==========================================================================
//模擬系統時鐘:40MHz,25ns
always #((SYS_CLK_PERIOD+1)/2-1) sim_clk = ~sim_clk; //延時,電平翻轉
//若以rx_finish/tx_finish 觸發,則rx_data/tx_data還是上一刻的數據
//打一拍,延遲,rx_data/tx_data數值已改變
always @(posedge sim_clk) begin
rx_finish_reg <= `UD rx_finish;
tx_finish_reg <= `UD tx_finish;
end
always @(posedge rx_finish_reg)
$display("Note 2 :The rx_data 8'h%h has received.",rx_data); //命令行顯示:rx_data
always @(posedge tx_finish_reg)
$display("Note 4 :The rx_data 8'h%h has received.",tx_data); //命令行顯示:tx_data
/*
always @(posedge sim_clk) begin
if(tx_finish_reg && ~tx_finish) begin //接收數據完成(觸發1次)
sim_tx_data <= `UD tx_data;
$display("Note 4 :The tx_data 8'h%h has been sent.",sim_tx_data); //命令行顯示:tx_data
end
end
*/
//==========================================================================
//模擬:信號的輸入,顯示輸出結果
//==========================================================================
initial begin
//模擬復位信號:拉低一次
#0;
sim_clk = 1'b0;
sim_rst_n = 1'b0; //復位拉低,有效,
sim_uart_rx = 1'b1; //串口線,默認高,起始拉低
//rx_data <= `UD 8'h00; //rx_data 初始化,在 uart_rx中完成
//#RST_TIME; //延時:保持足夠長時間(至少5個clk)
#BAUD_RATE_PERIOD; //5個clk時間軸太短,仿真改爲1個BPS,更明顯
sim_rst_n = 1'b1; //解除復位
//==========================================================================
//模擬串口接收:串行信號輸入,轉化成並行數據,並顯示
//==========================================================================
sim_uart_rx = 1'b1; //串口發送線,默認拉高
repeat( BPS_NUM*1 ) @(posedge sim_clk); //循環347個時鐘週期,即一個波特率週期
//#BAUD_RATE_PERIOD; //直接延時,一個波特率週期
$display("Note 1 : Initialization complete. BAUD_RATE is %d",BAUD_RATE); //命令行顯示初始化完成,輸出BAUD_RATE
//串口:起始位
sim_uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
//串行數據,一位一位送入接收信號線:***從位0到位7***,依次發送
//測試數據爲8'hD8=8'b1101_1000
sim_uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
sim_uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
sim_uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
sim_uart_rx = 1'b1;
#BAUD_RATE_PERIOD;
sim_uart_rx = 1'b1;
#BAUD_RATE_PERIOD;
sim_uart_rx = 1'b0;
#BAUD_RATE_PERIOD;
sim_uart_rx = 1'b1;
#BAUD_RATE_PERIOD;
sim_uart_rx = 1'b1;
$display("Note 2 : The uart_rx 8'hD8 = 8'b1101_1000 has been sent."); //命令行顯示:串口信號線數據已發送
#BAUD_RATE_PERIOD;
//串口:結束位
sim_uart_rx = 1'b1;
#BAUD_RATE_PERIOD;
$display("Note 3 :*** The led data 8'h%h has received.",sim_led); //命令行顯示:sim_led
//命令行顯示:串口信號線接收已結束,顯示接收到的數據
//==========================================================================
//模擬串口發送:並行數據,串行輸出
//==========================================================================
//*** data_gen模塊生成字符串,以及串口接收的數據,依次從串口輸出***
repeat( BPS_NUM*1 ) @(posedge sim_clk); //循環347個時鐘週期,即一個波特率週期
//#BAUD_RATE_PERIOD; //直接延時,一個波特率週期
//傳遞 第一組數據:8位並行數據,一次性送入串口發送模塊
//sim_tx_data = rx_data; //串口接收數據,傳給串口發送
#BAUD_RATE_PERIOD;
//開啓觸發信號:串口發送
//tx_pulse = 1; //用 rx_en 代替 模擬tx_pulse,直接觸發
#BAUD_RATE_PERIOD; //rx_en 串口接收數據有效,接收完成拉高1個BPS
//結束觸發:串口發送
//tx_pulse = 0;
#BAUD_RATE_PERIOD;
//0.1ms發一次數據,20位有2ms = 250個 BPS
repeat( BPS_NUM*250 ) @(posedge sim_clk);//等待發送數據完成(觸發20次)
$stop; //結束仿真
end
//initial
//==========================================================================
//調用top模塊
//==========================================================================
uart_top #(
.BPS_NUM ( BPS_NUM ), // 時鐘/波特率,1 bit位寬所需時鐘週期的個數
.CLK_CNT_1S ( CLK_CNT_1S ) //串口發送數據的週期
)
sim_uart_top(
.clk (sim_clk),
.rst_n (sim_rst_n),
.uart_rx (sim_uart_rx),
.led (sim_led),
.uart_tx (sim_uart_tx),
//仿真追加:將以下變量從下級模塊中傳出來
// uart_rx/tx -> uart_top -> uart_top_tb
.rx_data (rx_data ),
.rx_finish(rx_finish),
.tx_data (tx_data ),
.tx_finish(tx_finish)
);
endmodule
//module uart_top_tb
//循環移位發送數據
/*
parameter uart_rx_data =
initial begin
//模擬復位信號:一次,低電平5個clk
#0;
sim_clk = 1'b0;
sim_rst_n = 1'b0; //復位拉低,有效,
sim_uart_rx = 1'b1; //串口發送線,默認拉高
#RST_TIME; //延時:保持足夠長時間(5個clk)
sim_rst_n = 1'b1; //解除復位
repeat( 10 ) begin
//串口起始信號:高電平到低電平(下降沿)
#BAUD_RATE_PERIOD;
sim_uart_rx = 1'b1; //時間延遲BPS_CNT ns, 檢查`timescale 1 ns/ 1 ps
#BAUD_RATE_PERIOD;
sim_uart_rx = 1'b0;
repeat(12560) @(posedge sim_clk);
end
end
//串口數據信號:從第0位到第7位, 第8位停止位
always @(posedge data_clk) begin
if( cnt == 4'h8 ) begin
cnt <= 1'b0;
sim_uart_rx = 1'b1; //第8位後,停止位也爲1
$display("Running testbench:Pass"); //在命令行顯示pass這串字符。
end
else begin
cnt <= cnt +1'b1;
sim_uart_rx <= uart_rx_data[cnt];
end
end
*/
3、仿真結果
ModelSim波形
忘了標記,接收數據取反,截取,最後一位 07,也輸出了
命令行:
生成數據代碼對比