??歡迎來到FPGA專欄~串口發(fā)送模塊
- ☆* o(≧▽≦)o *☆嗨~我是小夏與酒??
- ?博客主頁:小夏與酒的博客
- ??該系列文章專欄:FPGA學習之旅
- 文章作者技術和水平有限,如果文中出現(xiàn)錯誤,希望大家能指正??
- ?? 歡迎大家關注! ??
![]()
一、效果演示
1.1 演示
??發(fā)送測試:
??issp調(diào)試測試:
數(shù)據(jù)調(diào)試:
調(diào)試數(shù)據(jù)發(fā)送:
1.2 串口發(fā)送模塊完整代碼(可直接使用)
??模塊端口介紹:
信號名稱 | 功能描述 |
---|---|
Clk | 系統(tǒng)時鐘50MHz |
Rst_n | 系統(tǒng)復位信號 |
data_byte | 待傳輸?shù)?bit數(shù)據(jù) |
send_en | 發(fā)送使能信號 |
baud_set | 波特率設置信號 |
uart_tx | 串口信號輸出 |
Tx_Done | 發(fā)送結束信號,輸出一個時鐘周期高電平 |
uart_state | 發(fā)送狀態(tài),處于發(fā)送狀態(tài)時為1 |
??baud_set值與波特率對應關系:
baud_set | 波特率 |
---|---|
000 | 9600 |
001 | 19200 |
010 | 38400 |
011 | 57600 |
100 | 115200 |
uart_byte_tx.v:
//
//模塊名稱:串口發(fā)送模塊
//
module uart_byte_tx(
input Clk,
input Rst_n,
input [7:0] data_byte,
input send_en,
input [2:0] baud_set,
output reg uart_tx,
output reg Tx_Done,
output reg uart_state
);
reg bps_clk;//波特率時鐘
reg [15:0]div_cnt;//分頻計數(shù)器
reg [15:0]bps_DR;//分頻計數(shù)最大值
reg [3:0]bps_cnt;//波特率計數(shù)時鐘
//定義數(shù)據(jù)的起始位和停止位
localparam START_BIT = 1'b0;
localparam STOP_BIT = 1'b1;
reg [7:0]r_data_byte;//數(shù)據(jù)寄存器
//--------<uart狀態(tài)模塊>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt == 4'd11)//bps_cnt計數(shù)達到11次,即發(fā)送結束
uart_state <= 1'b0;
else
uart_state <= uart_state;
end
//--------<使能分頻計數(shù)模塊>-------
assign en_cnt = uart_state;
//--------<寄存待發(fā)送的數(shù)據(jù),使數(shù)據(jù)保持穩(wěn)定>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
r_data_byte <= 8'd0;
else if(send_en)
r_data_byte <= data_byte;
else
r_data_byte <= r_data_byte;
end
//--------<波特率查找表>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
bps_DR <= 16'd5207;
else begin
case(baud_set)
0:bps_DR <= 16'd5207;
1:bps_DR <= 16'd2603;
2:bps_DR <= 16'd1301;
3:bps_DR <= 16'd867;
4:bps_DR <= 16'd433;
default:bps_DR <= 16'd5207;
endcase
end
end
//--------<Div_Cnt模塊>--------
//得到不同計數(shù)周期的計數(shù)器
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
div_cnt <= 16'd0;
else if(en_cnt)begin
if(div_cnt == bps_DR)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 16'd0;
end
//--------<bps_clk信號的產(chǎn)生>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
bps_clk <= 1'b0;
else if(div_cnt == 16'd1)
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
end
//--------<bps_cnt計數(shù)模塊>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
bps_cnt <= 4'd0;
else if(bps_cnt == 4'd11)//clr信號
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt + 1'b1;
else
bps_cnt <= bps_cnt;
end
//--------<Tx_Done模塊>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Tx_Done <= 1'b0;
else if(bps_cnt == 4'd11)
Tx_Done <= 1'b1;
else
Tx_Done <= 1'b0;
end
//--------<數(shù)據(jù)位輸出模塊-10選1多路器>--------
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
uart_tx <= 1'b1;
else begin
case(bps_cnt)
0:uart_tx <= 1'b1;
1:uart_tx <= START_BIT;
2:uart_tx <= r_data_byte[0];
3:uart_tx <= r_data_byte[1];
4:uart_tx <= r_data_byte[2];
5:uart_tx <= r_data_byte[3];
6:uart_tx <= r_data_byte[4];
7:uart_tx <= r_data_byte[5];
8:uart_tx <= r_data_byte[6];
9:uart_tx <= r_data_byte[7];
10:uart_tx <= STOP_BIT;
default:uart_tx <= 1'b1;
endcase
end
end
endmodule
二、串口發(fā)送時序
串口發(fā)送一個字節(jié)數(shù)據(jù)的時序圖參考如下:
根據(jù)上圖,一個字節(jié)想要成功發(fā)送完成,需要計數(shù)11次
波特率時鐘的高電平;而且一個完整的字節(jié)發(fā)送需要10位數(shù)據(jù)位,包括1個起始位,8個數(shù)據(jù)位和1個停止位。
需要注意,在串口未發(fā)送數(shù)據(jù)時(波特率時鐘未使能時),串口輸出高電平;在第一個波特率時鐘高電平到來時,開始發(fā)送起始位。
??波特率計算:
baud_set | 波特率bps | 周期 | 分頻計數(shù)值 | 50M系統(tǒng)時鐘計數(shù)值 |
---|---|---|---|---|
0 | 9600 | 104167ns | 104167/System_clk_period | 5208-1 |
1 | 19200 | 52083ns | 52083/System_clk_period | 2604-1 |
2 | 38400 | 26041ns | 26041/System_clk_period | 1302-1 |
3 | 57600 | 17361ns | 17361/System_clk_period | 868-1 |
4 | 115200 | 8680ns | 8680/System_clk_period | 434-1 |
三、模塊設計與代碼詳解
根據(jù)小梅哥FPGA設計的電路圖進行代碼編寫:
代碼理解主要參考前文中代碼的模塊注釋和串口發(fā)送時序圖。
在此展示簡單的測試激勵文件:
uart_byte_tx_tb.v:
`timescale 1ns/1ns
`define clock_period 20
module uart_byte_tx_tb;
reg Clk;
reg Rst_n;
reg [7:0]data_byte;
reg send_en;
reg [2:0]baud_set;
wire uart_tx;
wire Tx_Done;
wire uart_state;
uart_byte_tx Uuart_byte_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.data_byte(data_byte),
.send_en(send_en),
.baud_set(baud_set),
.uart_tx(uart_tx),
.Tx_Done(Tx_Done),
.uart_state(uart_state)
);
initial Clk = 1;
always#(`clock_period / 2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
data_byte = 8'd0;
send_en = 1'b0;
baud_set = 3'd4;
#(`clock_period * 20 + 1);
Rst_n = 1'b1;
#(`clock_period * 50);
data_byte = 8'haa;
send_en = 1'b1;
#(`clock_period * 1);
send_en = 1'b0;
@(posedge Tx_Done)
#(`clock_period * 5000);
data_byte = 8'hff;
send_en = 1'b1;
#(`clock_period * 1);
send_en = 1'b0;
@(posedge Tx_Done)
#(`clock_period * 5000);
$stop;
end
endmodule
仿真結果:
Tx_Done信號的正確輸出:
四、按鍵控制串口發(fā)送數(shù)據(jù)
先看整體的RTL視圖來理解設計思路:
按鍵信號經(jīng)過KeyFilter按鍵消抖模塊之后的有效信號用來控制串口發(fā)送模塊的發(fā)送使能;HEX8模塊和74HC595模塊用來驅動三線制數(shù)碼管顯示數(shù)據(jù);issp模塊用來從電腦端調(diào)試數(shù)據(jù);uart_byte_tx模塊用來將調(diào)試數(shù)據(jù)通過串口發(fā)送給電腦。
??按鍵消抖模塊的詳細講解:【FPGA零基礎學習之旅#10】按鍵消抖模塊設計與驗證(一段式狀態(tài)機實現(xiàn))。
??HEX8模塊的詳細講解:【FPGA零基礎學習之旅#11】數(shù)碼管動態(tài)掃描。
??74HC595模塊的詳細講解:【FPGA零基礎學習之旅#12】三線制數(shù)碼管驅動(74HC595)串行移位寄存器驅動。
??issp ip核的創(chuàng)建和使用包含在文章【FPGA零基礎學習之旅#11】數(shù)碼管動態(tài)掃描中。
在此給出頂層模塊,其余模塊見文末:
uart_byte_tx_top.v:
module uart_byte_tx_top(
input Clk, //50M
input Rst_n,
input key_in,
output SH_CP, //shift clock
output ST_CP, //latch data clock
output DS, //shift serial data
output uart_tx,
output led
);
wire [7:0] sel;//數(shù)碼管位選(選擇當前要顯示的數(shù)碼管)
wire [7:0] seg;//數(shù)碼管段選(當前要顯示的內(nèi)容)
wire [7:0] data_byte;
wire key_flag;
wire key_state;
issp issp(
.probe(),
.source(data_byte)
);
KeyFilter KeyFilter(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
//通過按鍵狀態(tài)使能串口發(fā)送模塊,按鍵按下時,串口發(fā)送模塊使能
assign send_en = key_flag & (!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(),
.uart_state(led)
);
//由于串口目前只發(fā)送一個字節(jié)數(shù)據(jù),只占用2個數(shù)碼管,所以其余數(shù)碼管顯示0
HEX8 HEX8(
.Clk(Clk),
.Rst_n(Rst_n),
.En(1'b1),
.disp_data({24'h0,data_byte}),
.sel(sel),
.seg(seg)
);
m74HC595_Driver m74HC595_Driver(
.Clk(Clk),
.Rst_n(Rst_n),
.Data({seg,sel}),
.S_EN(1'b1),
.SH_CP(SH_CP),
.ST_CP(ST_CP),
.DS(DS)
);
endmodule
程序配置完成之后的數(shù)碼管顯示和led的點亮:
使用issp更改數(shù)據(jù),數(shù)碼管顯示數(shù)據(jù)并通過按鍵發(fā)送:
??修改為11:
??修改為AF:
本案例的其余模塊在此部分列出:
KeyFilter.v:
//
//模塊:按鍵消抖模塊
//key_state:輸出消抖之后按鍵的狀態(tài)
//key_flag:按鍵消抖結束時產(chǎn)生一個時鐘周期的高電平脈沖
//
module KeyFilter(
input Clk,
input Rst_n,
input key_in,
output reg key_flag,
output reg key_state
);
//按鍵的四個狀態(tài)
localparam
IDLE = 4'b0001,
FILTER1 = 4'b0010,
DOWN = 4'b0100,
FILTER2 = 4'b1000;
//狀態(tài)寄存器
reg [3:0] curr_st;
//邊沿檢測輸出上升沿或下降沿
wire pedge;
wire nedge;
//計數(shù)寄存器
reg [19:0]cnt;
//使能計數(shù)寄存器
reg en_cnt;
//計數(shù)滿標志信號
reg cnt_full;//計數(shù)滿寄存器
//------<邊沿檢測電路的實現(xiàn)>------
//邊沿檢測電路寄存器
reg key_tmp0;
reg key_tmp1;
//邊沿檢測
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
end
assign nedge = (!key_tmp0) & (key_tmp1);
assign pedge = (key_tmp0) & (!key_tmp1);
//------<狀態(tài)機主程序>------
//狀態(tài)機主程序
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(curr_st)
IDLE:begin
key_flag <= 1'b0;
if(nedge)begin
curr_st <= FILTER1;
en_cnt <= 1'b1;
end
else
curr_st <= IDLE;
end
FILTER1:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else if(pedge)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER1;
end
DOWN:begin
key_flag <= 1'b0;
if(pedge)begin
curr_st <= FILTER2;
en_cnt <= 1'b1;
end
else
curr_st <= DOWN;
end
FILTER2:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b1;
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else if(nedge)begin
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER2;
end
default:begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
end
//------<20ms計數(shù)器>------
//20ms計數(shù)器
//Clk 50_000_000Hz
//一個時鐘周期為20ns
//需要計數(shù)20_000_000 / 20 = 1_000_000次
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
endmodule
HEX8.v:
module HEX8(
input Clk, //50M
input Rst_n, //復位
input En, //數(shù)碼管顯示使能
input [31:0] disp_data, //8 × 4 = 32(8個數(shù)碼管,數(shù)據(jù)格式為hex,總共輸32位)
output reg [7:0] seg, //數(shù)碼管段選
output [7:0] sel //數(shù)碼管位選(數(shù)碼管選擇)
);
reg [7:0]sel_r;
//----------<分頻器>----------
reg [14:0]divider_cnt;//25000-1
reg clk_1K;
reg [3:0]data_tmp;//待顯示數(shù)據(jù)緩存
//1KHz分頻計數(shù)器
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
divider_cnt <= 15'd0;
else if(!En)
divider_cnt <= 15'd0;
else if(divider_cnt == 24999)
divider_cnt <= 15'd0;
else
divider_cnt <= divider_cnt + 1'b1;
end
//1KHz掃描時鐘
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
clk_1K <= 1'b0;
else if(divider_cnt == 24999)
clk_1K <= ~clk_1K;
else
clk_1K <= clk_1K;
end
//----------<6位循環(huán)移位寄存器>-----------
always@(posedge clk_1K or negedge Rst_n)begin
if(!Rst_n)
sel_r <= 8'b0000_0001;
else if(sel_r == 8'b1000_0000)
sel_r <= 8'b0000_0001;
else
sel_r <= sel_r << 1;
end
//----------<6選1多路器>----------
always@(*)begin
case(sel_r)
8'b0000_0001:data_tmp = disp_data[3:0];
8'b0000_0010:data_tmp = disp_data[7:4];
8'b0000_0100:data_tmp = disp_data[11:8];
8'b0000_1000:data_tmp = disp_data[15:12];
8'b0001_0000:data_tmp = disp_data[19:16];
8'b0010_0000:data_tmp = disp_data[23:20];
8'b0100_0000:data_tmp = disp_data[27:24];
8'b1000_0000:data_tmp = disp_data[31:28];
default:data_tmp = 4'b0000;
endcase
end
//----------<LUT>----------
always@(*)begin
case(data_tmp)
4'h0:seg = 8'hc0;
4'h1:seg = 8'hf9;
4'h2:seg = 8'ha4;
4'h3:seg = 8'hb0;
4'h4:seg = 8'h99;
4'h5:seg = 8'h92;
4'h6:seg = 8'h82;
4'h7:seg = 8'hf8;
4'h8:seg = 8'h80;
4'h9:seg = 8'h90;
4'ha:seg = 8'h88;
4'hb:seg = 8'h83;
4'hc:seg = 8'hc6;
4'hd:seg = 8'ha1;
4'he:seg = 8'h86;
4'hf:seg = 8'h8e;
endcase
end
//----------<2選1多路器>----------
assign sel = (En)?(sel_r):8'b1111_1111;
endmodule
m74HC595_Driver.v:
module m74HC595_Driver(
Clk,
Rst_n,
Data,
S_EN,
SH_CP,
ST_CP,
DS
);
parameter DATA_WIDTH = 16;
input Clk;
input Rst_n;
input [DATA_WIDTH-1 : 0] Data; //data to send
input S_EN; //send en
output reg SH_CP; //shift clock
output reg ST_CP; //latch data clock
output reg DS; //shift serial data
parameter CNT_MAX = 4;
reg [15:0] divider_cnt;//分頻計數(shù)器
wire sck_pluse;
reg [4:0]SHCP_EDGE_CNT;//SH_CP EDGE counter
reg [15:0]r_data;
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
r_data <= 16'd0;
else if(S_EN)
r_data <= Data;
else
r_data <= r_data;
end
//clock divide
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
divider_cnt <= 16'd0;
else if(divider_cnt == CNT_MAX)
divider_cnt <= 16'd0;
else
divider_cnt <= divider_cnt + 1'b1;
end
assign sck_pluse = (divider_cnt == CNT_MAX);
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
SHCP_EDGE_CNT <= 5'd0;
else if(sck_pluse)begin
if(SHCP_EDGE_CNT == 5'd31)
SHCP_EDGE_CNT <= 5'd0;
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'b1;
end
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
SH_CP <= 1'b0;
ST_CP <= 1'b0;
DS <= 1'b0;
end
else begin
case(SHCP_EDGE_CNT)
5'd0: begin SH_CP <= 1'b0; ST_CP <= 1'b1; DS <= r_data[15]; end
5'd1: begin SH_CP <= 1'b1; ST_CP <= 1'b0;end
5'd2: begin SH_CP <= 1'b0; DS <= r_data[14];end
5'd3: begin SH_CP <= 1'b1; end
5'd4: begin SH_CP <= 1'b0; DS <= r_data[13];end
5'd5: begin SH_CP <= 1'b1; end
5'd6: begin SH_CP <= 1'b0; DS <= r_data[12];end
5'd7: begin SH_CP <= 1'b1; end
5'd8: begin SH_CP <= 1'b0; DS <= r_data[11];end
5'd9: begin SH_CP <= 1'b1; end
5'd10:begin SH_CP <= 1'b0; DS <= r_data[10];end
5'd11:begin SH_CP <= 1'b1; end
5'd12:begin SH_CP <= 1'b0; DS <= r_data[9];end
5'd13:begin SH_CP <= 1'b1; end
5'd14:begin SH_CP <= 1'b0; DS <= r_data[8];end
5'd15:begin SH_CP <= 1'b1; end
5'd16:begin SH_CP <= 1'b0; DS <= r_data[7];end
5'd17:begin SH_CP <= 1'b1; end
5'd18:begin SH_CP <= 1'b0; DS <= r_data[6];end
5'd19:begin SH_CP <= 1'b1; end
5'd20:begin SH_CP <= 1'b0; DS <= r_data[5];end
5'd21:begin SH_CP <= 1'b1; end
5'd22:begin SH_CP <= 1'b0; DS <= r_data[4];end
5'd23:begin SH_CP <= 1'b1; end
5'd24:begin SH_CP <= 1'b0; DS <= r_data[3];end
5'd25:begin SH_CP <= 1'b1; end
5'd26:begin SH_CP <= 1'b0; DS <= r_data[2];end
5'd27:begin SH_CP <= 1'b1; end
5'd28:begin SH_CP <= 1'b0; DS <= r_data[1];end
5'd29:begin SH_CP <= 1'b1; end
5'd30:begin SH_CP <= 1'b0; DS <= r_data[0];end
5'd31:begin SH_CP <= 1'b1; end
default:begin SH_CP <= 1'b0;ST_CP <= 1'b0;DS <= 1'b0; end
endcase
end
end
endmodule
文章來源:http://www.zghlxwxcb.cn/news/detail-707040.html
??結尾文章來源地址http://www.zghlxwxcb.cn/news/detail-707040.html
- ?? 感謝您的支持和鼓勵! ????
- ??您可能感興趣的內(nèi)容:
- 【FPGA零基礎學習之旅#11】數(shù)碼管動態(tài)掃描
- 【Python】串口通信-與FPGA、藍牙模塊實現(xiàn)串口通信(Python+FPGA)
- 【Arduino TinyGo】【最新】使用Go語言編寫Arduino-環(huán)境搭建和點亮LED燈
- 【全網(wǎng)首發(fā)開源教程】【Labview機器人仿真與控制】Labview與Solidworks多路支配關系-四足爬行機器人仿真與控制
![]()
到了這里,關于【FPGA零基礎學習之旅#13】串口發(fā)送模塊設計與驗證的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!