串口發送模塊筆記
首先時完成以後的效果如圖:
目錄:
一級目錄
二級目錄
三級目錄
一、分析
觀看了小梅哥的講解和設計思路以後我決定自己重新寫一下串口發送模塊;
串口發送模塊的原理比較簡單,只需要考慮波特率和發送的數據長度就可以了。
如上圖所示,一般的串口模塊就是以8bit一個字節的長度爲單位發送數據的,每個數據的開頭都由低電平爲開始,高電平爲結束。那麼開始結束的兩個bit加上數據的8bit,每發送一次數據至少處理10bit信號。
從整體上先設計邏輯電路,首先需要一個基本的分頻計數器,來產生9600Hz的數據脈衝,其次需要一個計數器,計數頻率9600Hz,計數每滿10就從新計數。
二、設計每個部分的代碼
1、5207計數器
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag5207 <= 0;
else if(conter == 13'd5207)
flag5207 <= 1'b1;
else
flag5207 <= 1'b0;
end
爲最基本的計數器分頻,產生9600Hz的信號提供給其他部分
2、數據比較產生使能信號
always@(posedge clk or negedge rst_n)
begin
if (!rst_n)
conter <= 0;
else if(conter == 13'd5207)
conter <= 0;
else
conter <= conter +1'b1;
end
爲了後面分頻給後面的邏輯電路,讓5207計數器每次循環計數到1時就產生一個高電平的使能信號給下面的電路。
3、11進制計數器
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
con <= 0;
else if(flag5207 == 1'b1)
begin
if(con == 4'd11)
con <= 0;
else
con <= con +1'b1;
end
else
con <= con;
end
爲了發送一個字節數據需要循環發送起始信號、數據段、結束信號,加在一起一共需要10bit,最後1個bit需要保持高電平防止信號不穩定
4、數據選擇器
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
TX <= 1'b0;
else
begin
case(con)
0:TX <= 1'b0;
1:TX <= 1'b1; //bit0
2:TX <= 1'b0; //bit1
3:TX <= 1'b1; //bit2
4:TX <= 1'b0; //bit3
5:TX <= 1'b1; //bit4
6:TX <= 1'b0; //bit5
7:TX <= 1'b1; //bit6
8:TX <= 1'b0; //bit7
default: TX <= 1'b1;
endcase
end
end
來產生最後的輸出信號,需要接收11進制計數器的數據,選擇發送從0到11的數據。分別代表了數據起始位和數據位以及數結束位。
三、基礎部分的全部代碼:
這裏我實現了基礎功能,下載到開發板裏以後只能一直循環發送數據“0x55”,轉換成ASIC就是“u”。也只以9600這一種波特率發送。
module main(
//input
clk,//模塊的時鐘 50MHz
rst_n, //系統復位信號
//output
TX, //串口輸出信號
conter, //內部計數器
con, //內部計數器
);
input clk;
input rst_n;
output TX;
output [13:0] conter;
output [4:0]con;
wire clk;
wire rst_n;
reg [13:0] conter;
reg [4:0]con;
reg TX;
reg flag5207;
//爲最基本的計數器分頻,產生9600Hz的信號提供給其他部分
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag5207 <= 0;
else if(conter == 13'd5207)
flag5207 <= 1'b1;
else
flag5207 <= 1'b0;
end
//爲了後面分頻給後面的邏輯電路,讓5207計數器每次循環計數到1時就產生一個高電平的使能信號給下面的電路。
always@(posedge clk or negedge rst_n)
begin
if (!rst_n)
conter <= 0;
else if(conter == 13'd5207)
conter <= 0;
else
conter <= conter +1'b1;
end
//11進制計數器爲了發送一個字節數據需要循環發送起始信號、數據段、結束信號,
//加在一起一共需要10bit,最後1個bit需要保持高電平防止信號不穩定
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
con <= 0;
else if(flag5207 == 1'b1)
begin
if(con == 4'd13)
con <= 0;
else
con <= con +1'b1;
end
else
con <= con;
end
//來產生最後的輸出信號,需要接收11進制計數器的數據,
//選擇發送從0到11的數據。分別代表了數據起始位和數據位以及數結束位
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
TX <= 1'b0;
else
begin
case(con)
0:TX <= 1'b0;
1:TX <= 1'b1; //bit0
2:TX <= 1'b0; //bit1
3:TX <= 1'b1; //bit2
4:TX <= 1'b0; //bit3
5:TX <= 1'b1; //bit4
6:TX <= 1'b0; //bit5
7:TX <= 1'b1; //bit6
8:TX <= 1'b0; //bit7
default: TX <= 1'b1;
endcase
end
end
endmodule
四、測試文件
`timescale 1ns/1ns
module main_testbench;
reg clk;
reg rst_n;
wire TX;
wire [13:0] conter;
wire [4:0] con;
main main(
.clk(clk),
.rst_n(rst_n),
.TX(TX),
.conter(conter),
.con(con)
);
always #10 clk =~ clk;
initial
begin
clk = 1;
rst_n = 1'b0;
#100
rst_n = 1'b1;
end
endmodule
五、完善以後的發送模塊RTL
在原來的基礎上增加了可以選擇的波特率、模塊的控制使能信號、發送一個字節的完成信號。
下面是我改進以後的代碼,可以在115200波特率的情況下向串口發送“hello word!”
1、頂層文件:
main.v
module main(
clk,
rst_n,
TX
);
input clk;
input rst_n;
output TX;
wire clk;
wire rst_n;
wire TX;
wire tx_down;
reg [8:0]data;
reg [3:0]flag;
urat_tx urat_tx(
//input
.clk(clk),//模塊的時鐘 50MHz
.rst_n(rst_n), //系統復位信號
.set_Baud_rate(4'd4),//波特率大小設置
.data(data), //要發送的一個字節數據
.en(1'b1),
//output
.TX(TX), //串口輸出信號
.tx_down(tx_down) //發送完成標誌,每完成一次發送就產生一段時間高電平
);
always@(posedge tx_down or negedge rst_n)
begin
if (!rst_n)
flag <=0;
else
flag <= flag + 1'd1;
end
always@(posedge tx_down or negedge rst_n)
begin
if(!rst_n)
data <= 0;
else
case(flag)
0: data <= 8'h68; //h
1: data <= 8'h65; //e
2: data <= 8'h6c; //l
3: data <= 8'h6c; //l
4: data <= 8'h6f; //o
5: data <= 8'h20; //空格
6: data <= 8'h77; //w
7: data <= 8'h6f; //o
8: data <= 8'h72; //r
9: data <= 8'h64; //d
10: data <= 8'h21; //!
11: data <= 8'h0d; //回車 \r
12: data <= 8'h0a; //換行 \n
default : data <= 8'h00;
endcase
end
endmodule
2、串口發送文件:
urat_tx.v
module urat_tx(
//input
clk,//模塊的時鐘 50MHz
rst_n, //系統復位信號
set_Baud_rate,//波特率大小設置
data, //要發送的一個字節數據
en, //模塊工作的使能信號
//output
TX, //串口輸出信號
tx_down //發送完成標誌,每完成一次發送就產生一段高脈衝
);
input clk; //模塊的時鐘 50MHz
input rst_n; //系統復位信號
input [8:0] data; //要發送的一個字節數據
input [3:0] set_Baud_rate; //波特率大小設置
input en; //模塊工作的使能信號
output TX; //串口輸出信號
output tx_down; //發送完成標誌,每完成一次發送就產生一段高脈衝
wire clk;
wire rst_n;
wire en;
reg TX;
reg tx_down;
//模塊內部的寄存器
reg flag5207;
reg [8:0] rev_data;
reg [13:0] conter;
reg [4:0]con;
reg [13:0] Baud_rate; //波特率
//localparam Baud_rate=433;//設置波特率
//接收設置的波特率大小
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
Baud_rate <= 5207;
else
case (set_Baud_rate)
0: Baud_rate <= 5207; //9600
1: Baud_rate <= 2603;
2: Baud_rate <= 1301;
3: Baud_rate <= 867;
4: Baud_rate <= 433; //115200
default: Baud_rate <= 5207;
endcase
end
//接收外來的要發送的數據;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rev_data <= 8'd0;
else
rev_data <= data;
end
//爲最基本的計數器分頻,產生例如9600Hz的信號提供給其他部分
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag5207 <= 0;
else if(en)//增加使能信號控制
begin
if(conter == Baud_rate)
flag5207 <= 1'b1;
else
flag5207 <= 1'b0;
end
else
flag5207 <= 1'b0;//如果不使能相當於給復位了 使能:en=1;
end
//爲了後面分頻給後面的邏輯電路,讓5207計數器每次循環計數到1時就產生一個高電平的使能信號給下面的電路。
always@(posedge clk or negedge rst_n)
begin
if (!rst_n)
conter <= 0;
else if(conter == Baud_rate)
conter <= 0;
else
conter <= conter +1'b1;
end
//11進制計數器爲了發送一個字節數據需要循環發送起始信號、數據段、結束信號,
//加在一起一共需要10bit,最後1個bit需要保持高電平防止信號不穩定
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
con <= 0;
else if(flag5207 == 1'b1)
begin
if(con == 4'd13)
con <= 0;
else
con <= con +1'b1;
end
else
con <= con;
end
//來產生最後的輸出信號,需要接收11進制計數器的數據,
//選擇發送從0到11的數據。分別代表了數據起始位和數據位以及數結束位
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
TX <= 1'b0;
else
begin
case(con)
0:TX <= 1'b0;
1:TX <= rev_data[0]; //bit0
2:TX <= rev_data[1]; //bit1
3:TX <= rev_data[2]; //bit2
4:TX <= rev_data[3]; //bit3
5:TX <= rev_data[4]; //bit4
6:TX <= rev_data[5]; //bit5
7:TX <= rev_data[6]; //bit6
8:TX <= rev_data[7]; //bit7
default: TX <= 1'b1;
endcase
end
end
//發送一個字節完成的標誌位,高電平
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
tx_down <=0;
else if(con == 4'd13)
tx_down <= 1'b1;
else
tx_down <= 1'b0;
end
endmodule
注意
串口要發送換行符就必須發送\r\n兩個數據,並不是直接發送這四個字符就可以實現換行的目的,而是需要根據 ASCII碼 的值來發送,這裏我主要參考了這兩位大神的教程: