FPGA入門 —— FPGA UART 串口通信
串口簡介
UART 通用異步收發(fā)傳輸器( Universal Asynchronous Receiver/Transmitter) ,通常稱作 UART。 UART 是一種通用的數(shù)據(jù)通信協(xié)議,也是異步串行通信口(串口)的總稱,它在發(fā)送數(shù)據(jù)時將并行數(shù)據(jù)轉(zhuǎn)換成串行數(shù)據(jù)來傳輸,在接收數(shù)據(jù)時將接收到的串行數(shù)據(jù)轉(zhuǎn)換成并行數(shù)據(jù)。 它包括了ch340、 RS232、 RS499、 RS423、 RS422 和 RS485 等接口標準規(guī)范和總線標準規(guī)范
串口作為常用的三大低速總線(UART、 SPI、 IIC)之一,在設(shè)計眾多通信接口和調(diào)試時占有重要地位。但 UART 和 SPI、 IIC 不同的是,它是異步通信接口,異步通信中的接收方并不知道數(shù)據(jù)什么時候會到達,所以雙方收發(fā)端都要有各自的時鐘,在數(shù)據(jù)傳輸過程中是不需要時鐘的,發(fā)送方發(fā)送的時間間隔可以不均勻,接受方是在數(shù)據(jù)的起始位和停止位的幫助下實現(xiàn)信息同步的。而 SPI、 IIC 是同步通信接口,同步通信中雙方使用頻率一致的時鐘,在數(shù)據(jù)傳輸過程中時鐘伴隨著數(shù)據(jù)一起傳輸,發(fā)送方和接收方使用的時鐘都是由主機提供的
UART 通信只有兩根信號線,一根是發(fā)送數(shù)據(jù)端口線叫 tx(Transmitter),一根是接收數(shù)據(jù)端口線叫 rx(Receiver),對于 PC 來說它的 tx 要和對于 FPGA 來說的 rx 連接,同樣 PC 的 rx 要和 FPGA 的 tx 連接,如果是兩個 tx 或者兩個 rx 連接那數(shù)據(jù)就不能正常被發(fā)送出去和接收到,所以不要弄混,記住 rx 和 tx 都是相對自身主體來講的。UART 可以實現(xiàn)全雙工,即可以同時進行發(fā)送數(shù)據(jù)和接收數(shù)據(jù)
串口回環(huán)模塊如下圖所示,使用電腦上位機串口模塊完成對 FPGA 串口模塊的環(huán)回實驗

串口時序
下面我們來看一下 RS232 協(xié)議:
-
RS232 是 UART 的一種,沒有時鐘線,只有兩根數(shù)據(jù)線,分別是 rx 和 tx,這兩根線都是 1bit 位寬的。其中 rx 是接收數(shù)據(jù)的線, tx 是發(fā)送數(shù)據(jù)的線
-
rx 位寬為 1bit, PC 機通過串口調(diào)試助手往 FPGA 發(fā) 8bit 數(shù)據(jù)時, FPGA 通過串口線rx 一位一位地接收,從最低位到最高位依次接收,最后在 FPGA 里面位拼接成 8 比特數(shù)據(jù),也就是我們常說的串轉(zhuǎn)并
-
tx 位寬為 1bit, FPGA 通過串口往 PC 機發(fā) 8bit 數(shù)據(jù)時, FPGA 把 8bit 數(shù)據(jù)通過 tx線一位一位的傳給 PC 機,從最低位到最高位依次發(fā)送,最后上位機通過串口助手按照RS232 協(xié)議把這一位一位的數(shù)據(jù)位拼接成 8bit 數(shù)據(jù),并行數(shù)據(jù)轉(zhuǎn)換成串行數(shù)據(jù)進行發(fā)送
-
串口數(shù)據(jù)的發(fā)送與接收是基于幀結(jié)構(gòu)的,即一幀一幀的發(fā)送與接收數(shù)據(jù)。每一幀除了中間包含 8bit 有效數(shù)據(jù)外,還在每一幀的開頭都必須有一個起始位,且固定為 0;在每一幀的結(jié)束時也必須有一個停止位,且固定為 1,即最基本的幀結(jié)構(gòu)(不包括校驗等)有10bit。在不發(fā)送或者不接收數(shù)據(jù)的情況下, rx 和 tx 處于空閑狀態(tài),此時 rx 和 tx 線都保持高電平,如果有數(shù)據(jù)幀傳輸時,首先會有一個起始位,然后是 8bit 的數(shù)據(jù)位,接著有 1bit的停止位,然后 rx 和 tx 繼續(xù)進入空閑狀態(tài),然后等待下一次的數(shù)據(jù)傳輸
rs232時序圖入下圖所示:

-
波特率:在信息傳輸通道中,攜帶數(shù)據(jù)信息的信號單元叫碼元(因為串口是 1bit 進行傳輸?shù)?,所以其碼元就是代表一個二進制數(shù)), 每秒鐘通過信號傳輸?shù)拇a元數(shù)稱為碼元的傳輸速率,簡稱波特率,常用符號“Baud”表示,其單位為“波特每秒(Bps)”。串口常見的波特率有 4800、9600、 115200 等
-
比特率:每秒鐘通信信道傳輸?shù)男畔⒘糠Q為位傳輸速率,簡稱比特率,其單位為“每秒比特數(shù)(bps)”。比特率可由波特率計算得出,公式為:比特率=波特率 * 單個調(diào)制狀態(tài)對應(yīng)的二進制位數(shù)。如果使用的是 115200 的波特率,其串口的比特率為: 115200Bps *1bit= 115200bps
-
由計算得串口發(fā)送或者接收 1bit 數(shù)據(jù)的時間為一個波特,即 1/9600 秒,如果用 50MHz(周期為 20ns)的系統(tǒng)時鐘來計數(shù),需要計數(shù)的個數(shù)為 cnt = (1s * 10^9)ns /115200bit)ns / 20ns ≈434 個系統(tǒng)時鐘周期,即每個 bit 數(shù)據(jù)之間的間隔要在 50MHz 的時鐘頻率下計數(shù) 434 次
串口模塊設(shè)計
我們先繪制一下串口回環(huán)的整個模塊圖:

由整個回環(huán)模塊圖,我們可以看出在整個回環(huán)實驗中,我們需要再設(shè)計兩個模塊,一個接收模塊和一個發(fā)送模塊。
接收模塊
先看一下接收模塊的波形圖:

輸入信號為系統(tǒng)時鐘、復(fù)位信號以及接收信號;為了消除跨時鐘域所帶來的的亞穩(wěn)態(tài)現(xiàn)象,需要對 rx 信號進行打 3 拍操作,跨時鐘域處理推薦是 3 拍,當然有些人覺得 2 拍足矣,只要驗證沒問題,這個你隨意。
進行完打拍操作后,需要取第三拍的下降沿操作,也就是 ~rx_reg2 & rx_reg3 ,至于為何這樣,可以參考邊沿檢測部分。
得到 start_flag 信號之后,就可以進行一個 rx 的接收了,本次接收模塊使用的波特率為 115200,系統(tǒng)時鐘為 50M,所以波特率為 50_000_000/115200=434,當然這里波特率也可以根據(jù)需要進行修改
為了每位數(shù)據(jù)的接收的穩(wěn)定,所以可以數(shù)據(jù)的標志信號可以在波特率信號的中間位置進行拉高。然后將每 bit 的數(shù)據(jù)都拼接到我們接收數(shù)據(jù)的最高位,以此類推,接收到的第8位數(shù)據(jù),就拼接在了 rx_data 的最高位,最先接收的數(shù)據(jù)就存儲在了 rx_data 的最低位
接收完8位數(shù)據(jù)之后,我們的接收標志信號拉高,表示已經(jīng)完成一次8位數(shù)據(jù)的接收。在接收完成標志位拉高后,將我們寄存的串轉(zhuǎn)并的數(shù)據(jù)賦值 po_data,同樣 po_flag 拉高一個周期
根據(jù)波形圖與文字分析,可以編寫代碼如下:
UART_recv.v
module UART_recv
#(
parameter CLK = 26'd50000000 , // 時鐘頻率
parameter BAUD = 17'd115200 // 波特率
)
(
input wire clk ,
input wire rstn ,
input wire UART_rx ,
output reg flag_out , // 數(shù)據(jù)接收完成標志位,既發(fā)送開始標志位
output reg [7 : 0] data_out // 接收的數(shù)據(jù)
);
localparam Baud_Clk = CLK/BAUD ; // 傳輸每個 Baud 需要的時鐘數(shù)
reg rx_en ; // 接收使能
reg start_flag ; // 開始接收標志
reg flag_rx ; // 接收標志位,半個時鐘周期為 1 ,用于判斷數(shù)據(jù)已經(jīng)全部接收完成
reg flag_bit ; // 比特標志位,采用下降沿發(fā)送
reg rx_reg1 ; // 接收寄存器1,同步打拍(打一拍延時一個時鐘周期)
reg rx_reg2 ; // 接收寄存器2
reg rx_reg3 ; // 接收寄存器3,取第三拍下降沿進行邊緣檢測
reg [8 : 0] cnt_baud ; // 波特率計數(shù)器
reg [7 : 0] data_rx ; // 接收數(shù)據(jù)寄存器
reg [3 : 0] cnt_bit ; // 比特計數(shù)器
// 打三拍
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
rx_reg1 <= 1'b1;
rx_reg2 <= 1'b1;
rx_reg3 <= 1'b1;
end
else begin
rx_reg1 <= UART_rx;
rx_reg2 <= rx_reg1;
rx_reg3 <= rx_reg2;
end
end
// 檢測第三拍下降沿,用作數(shù)據(jù)接收信號
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
start_flag <= 1'b0;
end
// 判斷第三拍下降沿
else if(rx_reg3 && ~rx_reg2) begin
start_flag <= 1'b1;
end
else begin
start_flag <= 1'b0;
end
end
// 接收使能
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
rx_en <= 1'b0;
end
// 下降沿接收
else if(start_flag == 1'b1) begin
rx_en <= 1'b1;
end
// 接收完成,輸入只需要判斷到數(shù)據(jù)位最后一位,輸出則需要判斷完整輸出
else if(cnt_bit == 4'd8 && flag_bit == 1'b1) begin
rx_en <= 1'b0;
end
end
// 波特計數(shù)器
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt_baud <= 9'd0;
end
// 傳輸完成所有波特或者使能失效,表示接收結(jié)束
else if(cnt_baud == Baud_Clk - 1'b1 || rx_en == 1'b0) begin
cnt_baud <= 9'd0;
end
// 只有輸入使能才能計數(shù)
else if(rx_en == 1'b1) begin
cnt_baud <= cnt_baud + 9'd1;
end
end
// 比特標志位
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
flag_bit <= 1'b0;
end
// 半個周期反轉(zhuǎn)一次,輸入一個bit需要兩個時鐘周期,輸出需要三個
else if(cnt_baud == Baud_Clk/2 - 1'b1) begin
flag_bit <= 1'b1;
end
else begin
flag_bit <= 1'b0;
end
end
// 比特計數(shù)器
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt_bit <= 4'd0;
end
// 輸入判斷完成
else if(cnt_bit == 4'd8 && flag_bit == 1'b1) begin
cnt_bit <= 4'd0;
end
// 前面判斷了輸入使能失效,無法進行波特計數(shù)
else if(flag_bit == 1'b1) begin
cnt_bit <= cnt_bit + 4'd1;
end
end
// 接收數(shù)據(jù)
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
data_rx <= 8'd0;
end
// 只要開始接收,就開始存儲數(shù)據(jù),1-8為數(shù)據(jù)位,解析輸入數(shù)據(jù),從最低位向最高位輸入
else if(cnt_bit >= 4'd1 && cnt_bit <= 4'd8 && flag_bit == 1'b1) begin
data_rx <= {rx_reg3,data_rx[7:1]};
end
end
// 接收標志位
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
flag_rx <= 1'b0;
end
// 數(shù)據(jù)接收完成,輸出半個時鐘周期的 1 用于數(shù)據(jù)轉(zhuǎn)存,將收到的數(shù)據(jù)再次發(fā)送出去
else if(cnt_bit == 4'd8 && flag_bit == 1'b1) begin
flag_rx <= 1'b1;
end
else begin
flag_rx <= 1'b0;
end
end
// 接收的串轉(zhuǎn)并數(shù)據(jù)
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
data_out <= 8'd0;
end
// 判斷數(shù)據(jù)已經(jīng)全部接收完成
else if(flag_rx == 1'b1) begin
data_out <= data_rx;
end
end
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
flag_out <= 1'b0;
end
else begin
flag_out <= flag_rx;
end
end
endmodule //UART_recv
編寫接收模塊仿真代碼:
tb_uart_rx.v:
`timescale 1ns / 1ns
module tb_uart_rx();
reg sys_clk ;
reg sys_rst_n ;
reg rx ;
wire [7:0] po_data ;
wire po_flag ;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#201
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
//模擬發(fā)送8次數(shù)據(jù),分別為0~7
initial begin
#200
rx_bit(8'd8); //任務(wù)的調(diào)用,任務(wù)名+括號中要傳遞進任務(wù)的參數(shù)
rx_bit(8'd1);
rx_bit(8'd2);
rx_bit(8'd3);
rx_bit(8'd4);
rx_bit(8'd5);
rx_bit(8'd6);
rx_bit(8'd7);
end
//定義一個名為rx_bit的任務(wù)
//任務(wù)以task開頭,后面緊跟的是任務(wù)名,調(diào)用時使用
task rx_bit(
//傳遞到任務(wù)中的參數(shù),調(diào)用任務(wù)的適合從外部傳進來一個8位的值
input [7:0] data
);
integer i; //定義一個常量
for(i=0; i<10; i=i+1) begin
case(i)
0: rx <= 1'b0;
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7];
9: rx <= 1'b1;
endcase
#(434*20); //每發(fā)送1位數(shù)據(jù)時434個時鐘周期
end
endtask
uart_rx
#(
.BAUD_MAX ('d115_200 ),
.CLK_MAX ('d50_000_000 )
)
uart_rx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.rx (rx ),
.po_data (po_data ),
.po_flag (po_flag )
);
endmodule
打拍取沿及接收使能部分波形:

波特計數(shù)器以及比特標志位(434/2拉高)波形:

數(shù)據(jù)接收端以及數(shù)據(jù)接收完成標志位波形:

發(fā)送模塊
同理按照時序圖繪制發(fā)送模塊的波形圖:

因為設(shè)計一個串口回環(huán),發(fā)送和接收模塊的波特率是相同的,所以并沒有隊 pi_flag 信號進行打 3 拍處理。在接收信號拉高之后, tx_en 拉高,進入發(fā)送模式。
同樣波特計數(shù)器為 434,bit_flag 每次拉高,bit_cnt 計一個數(shù),第 0 位為起始位,拉低,然后進行數(shù)據(jù)的傳送,當計數(shù)到第九位時,使 tx 信號拉高,表示停止位。
同樣根據(jù)波形圖可以編寫出發(fā)送模塊的代碼:
UART_send.v:
module UART_send
#(
parameter CLK = 26'd50000000 , // 時鐘頻率
parameter BAUD = 17'd115200 // 波特率
)
(
input wire clk ,
input wire rstn ,
input wire [7 : 0] data_in , // 需要發(fā)送的數(shù)據(jù)
input wire flag_in , // 數(shù)據(jù)接收標志位,既發(fā)送標志位
output reg UART_tx // 串口輸出位
);
localparam Baud_Clk = CLK/BAUD ; // 傳輸每個 Baud 需要的時鐘數(shù)
reg tx_en ; // 發(fā)送使能
reg flag_bit ; // 比特標志位,采用下降沿發(fā)送
reg [8 : 0] cnt_baud ; // 波特率計數(shù)器
reg [3 : 0] cnt_bit ; // 比特計數(shù)器
// 發(fā)送使能
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
tx_en <= 1'b0;
end
// 已經(jīng)發(fā)送了十位 bit 并且到達下一個下降沿,輸入只需要判斷到數(shù)據(jù)位最后一位,輸出則需要判斷完整輸出
else if(cnt_bit == 4'd9 && flag_bit == 1'b1) begin
tx_en <= 1'b0;
end
else if(flag_in == 1'b1) begin
tx_en <= 1'b1;
end
end
// 波特計數(shù)器
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt_baud <= 9'd0;
end
// 傳輸完成所有波特或者使能失效,表示發(fā)送結(jié)束
else if(cnt_baud == Baud_Clk - 1'b1 || tx_en == 1'b0) begin
cnt_baud <= 9'd0;
end
else begin
cnt_baud <= cnt_baud + 9'd1;
end
end
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
flag_bit <= 1'b0;
end
// 只有剛開始發(fā)送的一瞬間會產(chǎn)生一個時鐘周期上升沿和下降沿
else if(cnt_baud == 9'd1) begin
flag_bit <= 1'b1;
end
else begin
flag_bit <= 1'b0;
end
end
// 計數(shù)10分有效數(shù)據(jù)位
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt_bit <= 4'd0;
end
// 已經(jīng)發(fā)送了十位 bit 并且到達下一個下降沿
else if(cnt_bit == 4'd9 && flag_bit == 1'b1) begin
cnt_bit <= 4'd0;
end
// 使能有效,下降沿發(fā)送數(shù)據(jù)
else if(flag_bit == 1'b1 && tx_en == 1'b1) begin
cnt_bit <= cnt_bit + 4'd1;
end
else begin
cnt_bit <= cnt_bit;
end
end
// 滿足 RS232 協(xié)議 起始位為 0,停止位為 1,并按位輸出
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
UART_tx <= 1'd1;
end
// 下降沿發(fā)送數(shù)據(jù)
else if(flag_bit == 1'b1) begin
case (cnt_bit)
0: UART_tx <= 1'd0 ;
1: UART_tx <= data_in[0] ;
2: UART_tx <= data_in[1] ;
3: UART_tx <= data_in[2] ;
4: UART_tx <= data_in[3] ;
5: UART_tx <= data_in[4] ;
6: UART_tx <= data_in[5] ;
7: UART_tx <= data_in[6] ;
8: UART_tx <= data_in[7] ;
9: UART_tx <= 1'd1 ;
default: UART_tx <= 1'd1 ;
endcase
end
end
endmodule //UART_send
同樣進行發(fā)送代碼的仿真編寫:
tb_uart_tx.v:
`timescale 1ns / 1ns
module tb_uart_tx();
reg sys_clk ;
reg sys_rst_n ;
reg [7:0] pi_data ;
reg pi_flag ;
wire tx ;
initial begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
#201
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
initial begin
pi_flag <= 1'b0;
pi_data <= 8'd0;
#401
pi_data <= 8'd0;
pi_flag <= 1'd1;
#20
pi_flag <= 1'd0;
#(434*10*20)
pi_data <= 8'd15;
pi_flag <= 1'd1;
#20
pi_flag <= 1'd0;
#(434*10*20)
pi_data <= 8'd2;
pi_flag <= 1'd1;
#20
pi_flag <= 1'd0;
#(434*10*20)
pi_data <= 8'd3;
pi_flag <= 1'd1;
#20
pi_flag <= 1'd0;
#(434*10*20)
pi_data <= 8'd4;
pi_flag <= 1'd1;
#20
pi_flag <= 1'd0;
#(434*10*20)
pi_data <= 8'd5;
pi_flag <= 1'd1;
#20
pi_flag <= 1'd0;
#(434*10*20)
pi_data <= 8'd6;
pi_flag <= 1'd1;
#20
pi_flag <= 1'd0;
#(434*10*20)
pi_data <= 8'd7;
pi_flag <= 1'd1;
#20
pi_flag <= 1'd0;
end
uart_tx
#(
.BAUD_MAX('d115_200 ) ,
.CLK_MAX ('d50_000_000 )
)uart_tx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.pi_data (pi_data ),
.pi_flag (pi_flag ),
.tx (tx )
);
endmodule
先看一下發(fā)送開始標志位波形:

延遲一個時鐘周期,發(fā)送使能拉高:

波特計數(shù)器等于 1 是,比特標志位拉高,比特位數(shù)加 1:

如接收到的數(shù)據(jù)為 7,發(fā)送端從最低位 1110_0000 發(fā)送數(shù)據(jù) 7,注意這里的順序是先發(fā)送最低位,最后一位為最高位
例化回環(huán)模塊
最后將我們的兩個模塊例化到一起:
UART.v:
module UART (
input wire clk ,
input wire rstn ,
input wire UART_rx ,
output wire UART_tx
);
localparam CLK_50MHz = 26'd50000000 ; // 時鐘頻率
localparam BAUD = 17'd115200 ; // 波特率
wire [7:0] data ;
wire flag ;
UART_send
#(
.CLK (CLK_50MHz ),// 時鐘頻率
.BAUD (BAUD ) // 波特率
)
UART_send_init(
.clk (clk ),
.rstn (rstn ),
.data_in (data ), // 需要發(fā)送的數(shù)據(jù)
.flag_in (flag ), // 數(shù)據(jù)接收標志位,既發(fā)送標志位
.UART_tx (UART_tx ) // 串口輸出位
);
UART_recv
#(
.CLK (CLK_50MHz ), // 時鐘頻率
.BAUD (BAUD ) // 波特率
)
UART_recv_init(
.clk (clk ),
.rstn (rstn ),
.UART_rx (UART_rx ),
.flag_out (flag ), // 數(shù)據(jù)接收完成標志位,既發(fā)送開始標志位,半個時鐘周期為 1 ,用于判斷數(shù)據(jù)已經(jīng)全部接收完成
.data_out (data ) // 接收的數(shù)據(jù)
);
endmodule //UART
燒錄效果演示

串口發(fā)送多字節(jié)數(shù)據(jù)
這里我們采用發(fā)送多字節(jié)的方式進行發(fā)送,前面我們已經(jīng)介紹了 FPGA 如何使用超聲波并顯示在數(shù)碼管上,這里講超聲波距離數(shù)據(jù)通過串口發(fā)送到上位機,這里超聲波距離保留三位小數(shù),格式如下:xxx.xxxcm
這里我們通過數(shù)據(jù)處理后,轉(zhuǎn)為 ASCII,然后我們每次發(fā)送一位,按照我們上面給定的格式,整個數(shù)據(jù)共有 9 位,有由于我們需要每分鐘發(fā)送一次數(shù)據(jù),所以我們需要每分鐘發(fā)送 10 個數(shù)據(jù)
有些朋友可能會問,我們只有 9 位數(shù)據(jù),為什么要發(fā)送 10 位?
這是因為我們由于要使用 python 程序接收數(shù)據(jù),所以我們需要在數(shù)據(jù)最后發(fā)送換行表示一次數(shù)據(jù)發(fā)送完成
數(shù)據(jù)處理代碼如下:
UART_driver.v:
module UART_driver (
input wire clk ,
input wire rstn ,
input wire [18:0] data_in ,
input wire UART_rx ,
output wire UART_tx
);
localparam CLK_50MHz = 26'd50000000 ; // 時鐘頻率
localparam BAUD = 17'd115200 ; // 波特率
reg [7:0] data ;
wire flag ;
wire tx_done ;
wire flag_0 ; // 未啟動超聲波
reg [25:0] cnt_clk ;
reg [3:0] xcnt ;
reg [71: 0] data_out ; // 最終發(fā)送的數(shù)據(jù)
reg [3:0] cm_hund ;//100cm
reg [3:0] cm_ten ;//10cm
reg [3:0] cm_unit ;//1cm
reg [3:0] point_1 ;//1mm
reg [3:0] point_2 ;//0.1mm
reg [3:0] point_3 ;//0.01mm
localparam
byte0 = "0",
byte1 = "1",
byte2 = "2",
byte3 = "3",
byte4 = "4",
byte5 = "5",
byte6 = "6",
byte7 = "7",
byte8 = "8",
byte9 = "9",
byte10 = "\n";
always@(posedge clk or negedge rstn) begin
if(!rstn)
xcnt <= 0;
else if(tx_done)
xcnt <= xcnt + 1'd1;
else if(xcnt == 10)
xcnt <= 0;
end
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt_clk <= 0;
end
else if(flag) begin
cnt_clk <= 0;
end
else begin
cnt_clk = cnt_clk + 1;
end
end
assign flag = cnt_clk == CLK_50MHz/11 - 1; // 一秒鐘發(fā)送所有數(shù)據(jù)
assign flag_0 = cm_hund == 0 && cm_ten == 0 && cm_unit == 0 && point_1 == 0 && point_2 == 0 && point_3 == 0;
always @(posedge clk or negedge rstn)begin
if(!rstn)begin
cm_hund <= 'd0;
cm_ten <= 'd0;
cm_unit <= 'd0;
point_1 <= 'd0;
point_2 <= 'd0;
point_3 <= 'd0;
end
else begin
cm_hund <= data_in % 10;
cm_ten <= data_in / 10 ** 1 % 10;
cm_unit <= data_in / 10 ** 2 % 10;
point_1 <= data_in / 10 ** 3 % 10;
point_2 <= data_in / 10 ** 4 % 10;
point_3 <= data_in / 10 ** 5 % 10;
end
end
always @(*) begin
case (xcnt)
0 : data = hex_data(point_3);
1 : data = hex_data(point_2);
2 : data = hex_data(point_1);
3 : data = "." ;
4 : data = hex_data(cm_unit);
5 : data = hex_data(cm_ten) ;
6 : data = hex_data(cm_hund);
7 : data = "c" ;
8 : data = "m" ;
9 : data = "\n" ;
default: data = 6'h30;
endcase
end
// 函數(shù),4位輸入,7位輸出,判斷要輸出的數(shù)字
function [7:0] hex_data; //函數(shù)不含時序邏輯相關(guān)
input [03:00] data_i;//至少一個輸入
begin
case(data_i)
4'd0:hex_data = 6'h30;
4'd1:hex_data = 6'h31;
4'd2:hex_data = 6'h32;
4'd3:hex_data = 6'h33;
4'd4:hex_data = 6'h34;
4'd5:hex_data = 6'h35;
4'd6:hex_data = 6'h36;
4'd7:hex_data = 6'h37;
4'd8:hex_data = 6'h38;
4'd9:hex_data = 6'h39;
default:hex_data = 6'h30;
endcase
end
endfunction
UART_send
#(
.CLK (CLK_50MHz ),// 時鐘頻率
.BAUD (BAUD ) // 波特率
)
UART_send_init(
.clk (clk ),
.rstn (rstn ),
.data_in (data ), // 需要發(fā)送的數(shù)據(jù)
.flag_in (flag ), // 數(shù)據(jù)接收標志位,既發(fā)送標志位
.UART_tx (UART_tx ), // 串口輸出位
.tx_done (tx_done )
);
endmodule //UART
python 接收串口多字節(jié)數(shù)據(jù)
這里我們選擇 python 語言作為上位機數(shù)據(jù)處理語言
開始的時候,我們運行數(shù)據(jù)處理程序,但是并不知道板子上已經(jīng)發(fā)送到哪一位數(shù)據(jù),所以我們需要循環(huán)過濾掉第一次接收到的數(shù)據(jù):
while True:
if ser.in_waiting:
data = ser.read(ser.in_waiting)
if str(data,encoding="utf-8") == '\n':
break
然后我們就可以循環(huán)接收串口數(shù)據(jù):
while True:
if ser.in_waiting: # 如果串口接收到了數(shù)據(jù)
data = ser.read(ser.in_waiting) # 讀取所有可用的數(shù)據(jù)
num_chars = len(data) # 獲取收到的字符個數(shù)
num_all = num_all + num_chars
if num_all <= 7:
char = char + str(data , encoding = "utf-8")
elif num_all == 10:
# distance = float(char)
if char != "No Data":
distance = float(char)
# print(char[0:7])
print("距離:",distance,"cm")
char = ""
num_all = 0
# print(f"收到 {num_chars} 個字符:{data}")
# print(data)
ser.close() # 關(guān)閉串口
完整 python 代碼如下:文章來源:http://www.zghlxwxcb.cn/news/detail-703939.html
import serial
ser = serial.Serial('COM22', 115200) # 假設(shè)您的串口是 COM1,波特率為 9600
ser.flushInput() # 清空輸入緩沖區(qū)
num_all = 0
char = ""
while True:
if ser.in_waiting:
data = ser.read(ser.in_waiting)
if str(data,encoding="utf-8") == '\n':
break
while True:
if ser.in_waiting: # 如果串口接收到了數(shù)據(jù)
data = ser.read(ser.in_waiting) # 讀取所有可用的數(shù)據(jù)
num_chars = len(data) # 獲取收到的字符個數(shù)
num_all = num_all + num_chars
if num_all <= 7:
char = char + str(data , encoding = "utf-8")
elif num_all == 10:
# distance = float(char)
if char != "No Data":
distance = float(char)
# print(char[0:7])
print("距離:",distance,"cm")
char = ""
num_all = 0
# print(f"收到 {num_chars} 個字符:{data}")
# print(data)
ser.close() # 關(guān)閉串口
燒錄效果演示文章來源地址http://www.zghlxwxcb.cn/news/detail-703939.html
到了這里,關(guān)于FPGA入門 —— FPGA UART 串口通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!