串口是嵌入式设备的重要数据传输手段,在基础串口的功能中,实现串口的波特率控制、停止位、校验、数据接收、数据发送即为串口的基础功能,为测试这些基础功能,最简单的实验就是数据回环测试,即将接收到的数据原样回发。
硬件原理
在正点原子开发板中,串口可转到RS232接口或RS485接口,当前需要实现TTL串口功能,故以RS232为例进行实验。
原理图如下: 串口的发送核接受对应FPGA的M15和K14脚。
设计目标
本次设计目标是基础的串口回传功能,FPGA将串口接收到的内容原样发送出来。 从实现原理出发,顶层模块的输入为时钟、复位、串口输入引脚;顶层模块的输出为串口输出引脚; 下层模块分别需要:串口接收模块,数据回传控制模块,串口发送模块。
串口接收模块uart_recv 输入: 时钟、复位、串口输入引脚 输出: 数据、接收完成标记 功能: 接收串口数据,接收完成后接收完成标记输出一个时钟周期的脉冲,脉冲输出完成后,重置接收状态。
回传控制模块uart_loop 输入: 时钟、复位、数据接收、接收完成标记、发送状态标记 输出: 数据、发送使能 功能: 接收完成一个串口数据,待串口输出状态为空闲时,将接收到的串口数据通过串口发送模块提供的接口发送出去。
串口发送模块uart_send 输入: 时钟、复位、数据接收、发送使能 输出: 串口输出、发送状态标记 功能: 将接收到的数据完整的发送出去,并根据发送状态控制发送状态标记位。
一个串口数据的示意图如下所示:
综上所述,模块框架如下所示:(ila_0模块是虚拟逻辑分析仪,用来检查模块间的数据传输情况)
代码编辑
Verilog语言的代码包括以下几个部分: 一个用于例化子模块的顶层模块; 串口发送模块; 串口接收模块; 回传控制模块。
顶层模块相关代码
顶层模块代码(uart_loopback_top.v):
`timescale 1ns / 1ps
module uart_loopback_top(
input sys_clk,
input sys_rst_n,
input uart_rxd,
output uart_txd
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200;
wire uart_recv_done;
wire [7:0] uart_recv_data;
wire uart_send_en;
wire [7:0] uart_send_data;
wire uart_tx_busy;
uart_recv #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS))
u_uart_recv(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_done (uart_recv_done),
.uart_data (uart_recv_data)
);
uart_send #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS))
u_uart_send(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_en (uart_send_en),
.uart_din (uart_send_data),
.uart_tx_busy (uart_tx_busy),
.uart_txd (uart_txd)
);
uart_loop u_uart_loop(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.recv_done (uart_recv_done),
.recv_data (uart_recv_data),
.tx_busy (uart_tx_busy),
.send_en (uart_send_en),
.send_data (uart_send_data)
);
ila_0 ila_0 (
.clk(sys_clk),
.probe0(uart_recv_data),
.probe1(uart_send_data)
);
endmodule
约束文件(uary_loopback_top.xdc):
create_clock -period 20.000 -name clk [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN K14 IOSTANDARD LVCMOS33} [get_ports uart_rxd]
set_property -dict {PACKAGE_PIN M15 IOSTANDARD LVCMOS33} [get_ports uart_txd]
下层功能模块代码
串口发送功能(uart_send.v):
`timescale 1ns / 1ps
module uart_send(
input sys_clk,
input sys_rst_n,
input uart_en,
input [7:0] uart_din,
output uart_tx_busy,
output reg uart_txd
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 9600;
localparam BPS_CNT = CLK_FREQ/UART_BPS;
reg uart_en_d0;
reg uart_en_d1;
reg [15:0] clk_cnt;
reg [ 3:0] tx_cnt;
reg tx_flag;
reg [ 7:0] tx_data;
wire en_flag;
assign uart_tx_busy = tx_flag;
assign en_flag = (~uart_en_d1) & uart_en_d0;
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
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 == BPS_CNT -(BPS_CNT/16))) begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_cnt <= 16'd0;
else if (tx_flag) begin
if (clk_cnt < BPS_CNT - 1)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 16'd0;
end
else
clk_cnt <= 16'd0;
end
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 == BPS_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)
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
else
uart_txd <= 1'b1;
end
endmodule
串口接收模块(uaer_recv.v):
`timescale 1ns / 1ps
module uart_recv(
input sys_clk,
input sys_rst_n,
input uart_rxd,
output reg uart_done,
output reg [7:0] uart_data
);
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 9600;
localparam BPS_CNT = CLK_FREQ/UART_BPS;
reg uart_rxd_d0;
reg uart_rxd_d1;
reg [15:0] clk_cnt;
reg [ 3:0] rx_cnt;
reg rx_flag;
reg [ 7:0] rxdata;
wire start_flag;
assign start_flag = uart_rxd_d1 & (~uart_rxd_d0);
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
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
rx_flag <= 1'b0;
else begin
if(start_flag)
rx_flag <= 1'b1;
else if((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))
rx_flag <= 1'b0;
else
rx_flag <= rx_flag;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_cnt <= 16'd0;
else if ( rx_flag ) begin
if (clk_cnt < BPS_CNT - 1)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 16'd0;
end
else
clk_cnt <= 16'd0;
end
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 == BPS_CNT - 1)
rx_cnt <= rx_cnt + 1'b1;
else
rx_cnt <= rx_cnt;
end
else
rx_cnt <= 4'd0;
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if ( !sys_rst_n)
rxdata <= 8'd0;
else if(rx_flag)
if (clk_cnt == BPS_CNT/2) begin
case ( rx_cnt )
4'd1 : rxdata[0] <= uart_rxd_d1;
4'd2 : rxdata[1] <= uart_rxd_d1;
4'd3 : rxdata[2] <= uart_rxd_d1;
4'd4 : rxdata[3] <= uart_rxd_d1;
4'd5 : rxdata[4] <= uart_rxd_d1;
4'd6 : rxdata[5] <= uart_rxd_d1;
4'd7 : rxdata[6] <= uart_rxd_d1;
4'd8 : rxdata[7] <= uart_rxd_d1;
default:;
endcase
end
else
rxdata <= rxdata;
else
rxdata <= 8'd0;
end
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 <= rxdata;
uart_done <= 1'b1;
end
else begin
uart_data <= 8'd0;
uart_done <= 1'b0;
end
end
endmodule
串口回传模块(uart_loop.v):
`timescale 1ns / 1ps
module uart_loop(
input sys_clk,
input sys_rst_n,
input recv_done,
input [7:0] recv_data,
input tx_busy,
output reg send_en,
output reg [7:0] send_data
);
reg recv_done_d0;
reg recv_done_d1;
reg tx_ready;
wire recv_done_flag;
assign recv_done_flag = (~recv_done_d1) & recv_done_d0;
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
tx_ready <= 1'b0;
send_en <= 1'b0;
send_data <= 8'd0;
end
else begin
if(recv_done_flag)begin
tx_ready <= 1'b1;
send_en <= 1'b0;
send_data <= recv_data;
end
else if(tx_ready && (~tx_busy)) begin
tx_ready <= 1'b0;
send_en <= 1'b1;
end
end
end
endmodule
实验结果
上述代码编辑完成后即可进行测试,使用仿真器连接PC和开发板、使用RS232转USB的串口转换线连接PC和开发板RS232接口,RS232转USB的线如下图所示:
此时将开发板上电,使用串口调试助手发送数据,波特率设置为115200bps,8位数据位、1位停止位、无校验。 下图中红色部分位发送的数据、黑色部分位接收的数据; 由图可知,串口的回传测试成功!
|