一、前置知识
串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
UART 是一种采用异步串行通信方式的通用异步收发传输器(Universal Asynchronous Receiver Transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
协议层数据格式:
物理层接口标准:
使用DB9接口标准。
USB接口类型:
二、原理分析
数据环回实验:
约定通信规则:数据位为 8 位,停止位为 1 位,无校验位,波特率为 115200bps
需要分别编写三个模块:数据接收模块、数据发送模块、环回控制模块。
串口接收:
- sys_clk:系统时钟。
- clk_cnt:系统时钟计数器,记录434个时钟(50_000_000/115200),表示在115200波特率下传输一位数据所需要的时间。
- rx_cnt:每当clk_cnt计数到434,表示传输一位数据,rx_cnt加一。
- uart_rxd:PC端上位机传输到FPGA的串行数据,FPGA接收。
- start_flag:串口接收开始标志位,判断接收到的数据是否为起始标志。采用两时钟延迟采集uart_rxd下降沿,uart_rxd传输开始后产生一个上升沿。
- rx_flag:串口接收标志位,拉高表示正在进行串口接收。以start_flag开始,以rx_cnt==9为止,即接收到的第9位停止位不需要,接收到第8位有效位就完成一轮数据接收。
- uart_done:串口数据接收完成标志。
- uart_data:串口接收到的数据,传给数据环回模块处理。
三、代码编写
3.1 数据接收模块
```verilog module uart_recv( input sys_clk, input sys_rst_n, input uart_rxd, output reg [7:0] uart_data, output reg uart_done );
// 判断uart_rxd起始位的下降沿 wire start_flag; reg uart_rxd_d0; // 当前状态 reg uart_rxd_d1; // 上一个状态 reg rx_flag; reg [3:0] rx_cnt; reg [8:0] clk_cnt; reg [7:0] rx_data;
parameter SYS_FREQ = 50_000_000; parameter UART_BPS = 115200; localparam UART_CNT = SYS_FREQ/UART_BPS;
assign start_flag = ~uart_rxd_d0 & uart_rxd_d1;
// 判断uart_rxd起始位的下降沿 always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin uart_rxd_d0 <= 1’b0; uart_rxd_d1 <= 1’b0; end else begin uart_rxd_d0 <= uart_rxd; uart_rxd_d1 <= uart_rxd_d0; end end
// 处理接收标志位rx_flag always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) rx_flag <= 1’b0; else if(start_flag==1’b1) rx_flag <= 1’b1; else if(rx_cnt==4’d9) rx_flag <= 1’b0; else rx_flag <= rx_flag; end
// 处理时钟计数器clk_cnt always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) clk_cnt <= 9’d0; else if(rx_flag) begin if(clk_cnt < UART_CNT-1) clk_cnt <= clk_cnt + 1’b1; else clk_cnt <= 9’d0; end else clk_cnt <= 9’d0; end
// 处理接收数据寄存器rx_cnt always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) rx_cnt <= 4’d0; else if(rx_flag) begin if(clk_cnt == UART_CNT-1) rx_cnt <= rx_cnt + 1’b1; else rx_cnt <= rx_cnt; end else rx_cnt <= 4’d0; end
// 处理数据寄存器rx_data always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) rx_data <= 8’d0; else if(rx_flag) begin if(clk_cnt == UART_CNT/2) begin case (rx_cnt) 4’h1: rx_data[0] <= uart_rxd_d1; 4’h2: rx_data[1] <= uart_rxd_d1; 4’h3: rx_data[2] <= uart_rxd_d1; 4’h4: rx_data[3] <= uart_rxd_d1; 4’h5: rx_data[4] <= uart_rxd_d1; 4’h6: rx_data[5] <= uart_rxd_d1; 4’h7: rx_data[6] <= uart_rxd_d1; 4’h8: rx_data[7] <= uart_rxd_d1; default: ; endcase end else rx_data <= rx_data; end else rx_data <= 8’d0; end
// 处理输出的数据uart_data和标志位uart_done always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin uart_data <= 8’d0; uart_done <= 1’b0; end else if(rx_cnt == 4’d9) begin uart_data <= rx_data; uart_done <= 1’b1; end else begin uart_data <= 8’d0; uart_done <= 1’b0; end end
endmodule
<a name="Zrt4V"></a>
### 3.2 数据发送模块
```verilog
module uart_send(
input sys_clk,
input sys_rst_n,
input uart_en,
input [7:0] uart_din,
output reg uart_txd,
output uart_tx_busy
);
wire en_flag;
reg uart_en_d0;
reg uart_en_d1;
reg tx_flag;
reg [7:0] tx_data;
reg [3:0] tx_cnt;
reg [8:0] clk_cnt;
parameter SYS_FREQ = 50_000_000;
parameter UART_BPS = 115200;
localparam UART_CNT = SYS_FREQ/UART_BPS;
assign uart_tx_busy = tx_flag;
assign en_flag = uart_en_d0 & ~uart_en_d1; // 抓取uart_en上升沿
// 判断uart_rxd起始位的下降沿
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else begin
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
// 处理发送标志位tx_flag和发送数据寄存器tx_data
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else if (en_flag) begin
tx_flag <= 1'b1;
tx_data <= tx_data;
end
else if ((tx_cnt==4'd9) && (clk_cnt==UART_CNT-UART_CNT/16)) begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end
// 处理时钟计数器clk_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
clk_cnt <= 9'd0;
else if(tx_flag) begin
if(clk_cnt < UART_CNT-1)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 9'd0;
end
else
clk_cnt <= 9'd0;
end
// 处理发送数据寄存器tx_cnt
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
tx_cnt <= 4'd0;
else if(tx_flag) begin
if(clk_cnt == UART_CNT-1)
tx_cnt <= tx_cnt + 1'b1;
else
tx_cnt <= tx_cnt;
end
else
tx_cnt <= 4'd0;
end
// 数据并转串
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
uart_txd <= 1'b1; // 低电平数据开始传输
else if(tx_flag) begin
case(tx_cnt)
4'd0: uart_txd <= 1'b0;
4'd1: uart_txd <= tx_data[0];
4'd2: uart_txd <= tx_data[1];
4'd3: uart_txd <= tx_data[2];
4'd4: uart_txd <= tx_data[3];
4'd5: uart_txd <= tx_data[4];
4'd6: uart_txd <= tx_data[5];
4'd7: uart_txd <= tx_data[6];
4'd8: uart_txd <= tx_data[7];
4'd9: uart_txd <= 1'b1;
default:;
endcase
end
else
uart_txd <= 1'b1;
end
endmodule
3.3 数据环回模块
module uart_loop(
input sys_clk,
input sys_rst_n,
input recv_done,
input [7:0] recv_data,
input send_busy,
output reg send_en,
output reg [7:0] send_data
);
wire recv_done_flag;
reg recv_done_d0;
reg recv_done_d1;
reg send_ready;
assign recv_done_flag = recv_done_d0 & ~recv_done_d1; // 抓取 recv_done 上升沿
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
recv_done_d0 <= 1'b0;
recv_done_d1 <= 1'b0;
end
else begin
recv_done_d0 <= recv_done;
recv_done_d1 <= recv_done_d0;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
send_en <= 1'b0;
send_data <= 8'd0;
send_ready <= 1'b0;
end
else begin
if(recv_done_flag) begin
send_en <= 1'b0;
send_data <= recv_data;
send_ready <= 1'b1;
end
else if(send_ready && ~send_busy) begin
send_en <= 1'b1;
send_ready <= 1'b0;
end
end
end
endmodule
3.4 顶层模块
module uart_loopback_top(
input sys_clk,
input sys_rst_n,
input uart_rxd,
output uart_txd
);
wire [7:0] uart_recv_data;
wire [7:0] uart_send_data;
wire uart_done;
wire uart_en;
wire uart_tx_busy;
uart_recv u_uart_recv(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.uart_rxd (uart_rxd ),
.uart_data (uart_recv_data),
.uart_done (uart_done )
);
uart_send u_uart_send(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.uart_en (uart_en ),
.uart_din (uart_send_data),
.uart_txd (uart_txd ),
.uart_tx_busy (uart_tx_busy )
);
uart_loop u_uart_loop(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.recv_done (uart_done ),
.recv_data (uart_recv_data),
.send_busy (uart_tx_busy ),
.send_en (uart_en ),
.send_data (uart_send_data)
);
endmodule
3.5 引脚
package require ::quartus::project
#system clock:50Mhz
set_location_assignment PIN_M2 -to sys_clk
#system reset
set_location_assignment PIN_M1 -to sys_rst_n
#USB UART
set_location_assignment PIN_N5 -to uart_rxd
set_location_assignment PIN_M7 -to uart_txd
#RS232 UART
#set_location_assignment PIN_B8 -to uart_rxd
#set_location_assignment PIN_C3 -to uart_txd
#RS485
#set_location_assignment PIN_B8 -to rs485_uart_rxd
#set_location_assignment PIN_C3 -to rs485_uart_txd
四、测试
能接收到数据,但接收到的数据都是空字符。。
检查一下代码,在数据发送模块里,没有将环回模块传过来的数据进行处理,一直在获取空数据0,所以会在串口调试助手显示空白符(commit:aeee75b):
修改如下:
// 处理发送标志位tx_flag和发送数据寄存器tx_data
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else if (en_flag) begin
tx_flag <= 1'b1;
tx_data <= uart_din; // 准备发送数据,寄存器存储待发送的数据
end
else if ((tx_cnt==4'd9) && (clk_cnt==UART_CNT-UART_CNT/16)) begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end