FPGA学习笔记之串口收发回环实验
这只是笔者的学习笔记,笔者也是为了学习,顺便分享给大家,里面有很多不足的地方,望大家指出来,笔者好修稿!!!谢谢
“串口”—就是“串行接口”的简称,就是采用串行通信方式的接口。
一,uart串口的简介
首先,我们要明白个概念,通信的方式分为串行通信和并行通信;这里我们要说的是串行通信,串行通信分为两种:同步串行通信方式和异步串行通信方式。同步串行通信是指双方必须在同一时钟的作用下来进行数据的传输,因此便要多加一根线;而异步串行通信则是需要双方不需要加时钟线,双方约定个时钟来传输数据。
uart是一种采用异步串行传输方式的通用异步收发传输器(universal asynchronous receiver-transmitter);他的原理也就是并串转换,在发送时,将数据从并行转换成串行传输出去;在接收时,将串行数据转换成并行数据。因此uart串口只需要两根信号线,一根用于发送数据,一根用于接收数据。
uart的协议层
UART作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。
其中各位的意义如下:
起始位:先发出一个逻辑”0”的信号,表示传输字符的开始。
数据位:传输的二进制的数据,一般为8位。
奇偶校验位:这个位的作用时检查数据传输有没有误。
停止位:它是一个字符数据的结束标志。
而本次实验数据位采用的是8位数据,并且不用奇偶效验位,所以一共为10位。
还有一个非常重要的概念——波特率bps,波特率反应的是数据传输的速率,也就是每秒传输二进制的位数,单位是bps(位/秒);一般有9600,115200等等越大传输的越快。
uart的物理层
uart采用的接口标准有许多,比如RS232,RS422,RS485等。RS232是最常用的接口标准,RS232 接口标准出现较早, 可实现全双工工作方式,即数据发送和接收可以同时进行。在传输距离较短时(不超过 15m) , RS232 是串行通信最常用的接口标准,本章主要介绍针对 RS-232 标准的 UART 串口通信。
RS-232 标准的串口最常见的接口类型为 DB9, 样式如图所示,工业控制领域中用到的工控机一般都配备多个串口, 很多老式台式机也都配有串口。但是笔记本电脑以及较新一点的台式机都没有串口,它们一般通过 USB 转串口线(图 14.1.3)来实现与外部设备的串口通信。
DB9 接口定义以及各引脚功能说明如图 所示,我们一般只用到其中的2 (RXD)、 3(TXD) 、5(GND) 引脚,其他引脚在普通串口模式下一般不使用.
二,试验任务
本次实验的任务是设计一个uart串口环回收发系统,只做仿真,暂不做板上验证。
三,程序设计
设计的思路如下框图:
uart接收模块
时序图:
sys_clk系统时钟频率;
uart_rxd串口接收的数据输入;
start_flag检测开始位的信号标志,检测开始位的下降沿就拉高;
rx_flag数据的有效段的标志;
clk_cnt系统时钟计数器;
rx_cnt波特率周期的计数器;
uart_done传输完成的标志,拉高;
uart_data串转并的数据,接收一帧的数据;
不多说,直接上代码!!!
//uart接收模块
module uart_receive(
input sys_clk,//系统时钟
input sys_rst_n,//系统复位,低电平有效
input uart_rxd,//串口接收的二进制数据
output reg [7:0] uart_data,//串口接收模块并转串输出的8位数据
output reg uart_done//接收完的标志,拉高
);
parameter CLK_FRED = 50_000_000;//时钟周期频率
parameter UART_BPS = 115200;//波特率
parameter BPS_CNT = CLK_FRED/UART_BPS;//波特率周期
reg uart_rxd_reg0;//定义两个寄存器
reg uart_rxd_reg1;
wire start_flag;//开始的标志,检测下降沿的标志
reg rx_flag;//数据有效段的标志,拉高为有效
reg [3:0] rx_cnt;//波特率周期的计数器
reg [15:0] clk_cnt;//系统时钟的计数器
reg [7:0] rxdata;//输出数据的寄存器
//这就是一个边缘检测的功能,检测数据的低电平,开始位,数据传输开始就拉高start_flag
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_rxd_reg0 <= 1'b1;
uart_rxd_reg1 <= 1'b1;
end
else begin
uart_rxd_reg0 <= uart_rxd;
uart_rxd_reg1 <= uart_rxd_reg0;
end
end
assign start_flag = (uart_rxd_reg1)&(~uart_rxd_reg0);
//根据start_flag传输开始的标志,设置传输的有效段的范围,即从开始位到结束位都拉高rx_flag,至于为甚后面是波特率周期的一办,是为了给一帧数据留一个缓冲的时间;
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_flag <= 0;
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 <= 0;
else begin
if(rx_flag)begin
if(clk_cnt < BPS_CNT - 1)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 0;
end
else
clk_cnt <= 0;
end
end
//波特率周期计数器,每当系统时钟计数器计数到一个波特率周期时,波特率周期计数器就加一
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_cnt <= 0;
else begin
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 <= 0;
end
end
//串行数据转换为并行数据,至于为什么是计数到波特率周期的一半,是为了采样中间的值,这样采样的正确性高
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rxdata <= 0;
else begin
if(rx_flag)begin
if(clk_cnt == BPS_CNT/2)begin
case(rx_cnt)
4'd1: rxdata[0] <= uart_rxd_reg1;
4'd2: rxdata[1] <= uart_rxd_reg1;
4'd3: rxdata[2] <= uart_rxd_reg1;
4'd4: rxdata[3] <= uart_rxd_reg1;
4'd5: rxdata[4] <= uart_rxd_reg1;
4'd6: rxdata[5] <= uart_rxd_reg1;
4'd7: rxdata[6] <= uart_rxd_reg1;
4'd8: rxdata[7] <= uart_rxd_reg1;
default:;
endcase
end
else
rxdata <= rxdata;
end
else
rxdata <= 0;
end
end
//最后将寄存的输出数据传给输出,并拉高输出完的标志位uart_done
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_data <= 0;
uart_done <= 0;
end
else begin
if(rx_cnt == 4'd9)begin
uart_data <= rxdata;
uart_done <= 1'b1;
end
else begin
uart_data <= 0;
uart_done <= 0;
end
end
end
endmodule
uart发送模块
时序图:
这里各信号的含义以及代码的具体含义我就不介绍了,详情参考上面的接收模块!!!
uart_tx_busy这个信号是表示发送模块正在发送数据的繁忙信号!!!
上代码!!!
//uart发送模块
module uart_send(
input sys_clk,
input sys_rst_n,
input [7:0] uart_din,
input uart_en,
output uart_tx_busy,
output reg uart_txd
);
parameter CLK_FRED = 50_000_000;
parameter UART_BPS = 115200;
parameter BPS_CNT = CLK_FRED/UART_BPS;
reg uart_en_reg0;
reg uart_en_reg1;
wire en_flag;
reg tx_flag;
reg [7:0] txdata;
reg [3:0] tx_cnt;
reg [15:0] clk_cnt;
assign uart_tx_busy = tx_flag;
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_en_reg0 <= 0;
uart_en_reg1 <= 0;
end
else begin
uart_en_reg0 <= uart_en;
uart_en_reg1 <= uart_en_reg0;
end
end
assign en_flag = (~uart_en_reg1 & uart_en_reg0);
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
txdata <= 0;
tx_flag <= 0;
end
else begin
if(en_flag)begin
tx_flag <= 1'b1;
txdata <= uart_din;
end
else if(tx_cnt == 4'd9 && clk_cnt == BPS_CNT - 1'b1)begin
txdata <= 0;
tx_flag <= 0;
end
else begin
tx_flag <= tx_flag;
txdata <= txdata;
end
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
clk_cnt <= 0;
else begin
if(tx_flag)begin
if(clk_cnt == BPS_CNT-1'b1)
clk_cnt <= 0;
else
clk_cnt <= clk_cnt + 1'b1;
end
else
clk_cnt <= 0;
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
tx_cnt <= 0;
else begin
if(tx_flag)begin
if(clk_cnt == BPS_CNT-1'b1)
tx_cnt <= tx_cnt + 1'b1;
else
tx_cnt <= tx_cnt;
end
else
tx_cnt <= 0;
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_txd <= 1'b1;
else begin
case(tx_cnt)
4'd0 : uart_txd <= 1'b0;
4'd1 : uart_txd <= txdata[0];
4'd2 : uart_txd <= txdata[1];
4'd3 : uart_txd <= txdata[2];
4'd4 : uart_txd <= txdata[3];
4'd5 : uart_txd <= txdata[4];
4'd6 : uart_txd <= txdata[5];
4'd7 : uart_txd <= txdata[6];
4'd8 : uart_txd <= txdata[7];
4'd9 : uart_txd <= 1'b1;
default:;
endcase
end
end
endmodule
环回模块
环回模块的作用是将接收数据缓一下再发送到发送模块,防止直接使用接收模块和发送模块产生的停止位的时间不够而引起数据传输有问题!!!
module uart_loop(
input sys_clk,//系统时钟
input sys_rst_n,//系统复位,低电平有效
input [7:0] recv_data,//接收的数据
input recv_done,//接收数据完的标志
input tx_busy,//数据发送模块繁忙
output reg [7:0] send_data,//发送数据
output reg send_en // 发送使能
);
reg recv_done_reg0;
reg recv_done_reg1;
wire recv_done_flag;//检测接收标志上升沿的标志
reg tx_ready;//发送准备信号
//这就是个检测接收数据使能上升沿的边缘检测模块
assign recv_done_flag = (~recv_done_reg1 & recv_done_reg0);
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
recv_done_reg0 <= 0;
recv_done_reg1 <= 0;
end
else begin
recv_done_reg0 <= recv_done;
recv_done_reg1 <= recv_done_reg0;
end
end
//只有当数据接收完,才会准备把输入的数据输出给输出口,并且只有当发送准备标志开始时并且发送模块传来不繁忙时才会传输
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
tx_ready <= 0;
send_data <= 0;
send_en <= 0;
end
else begin
if(recv_done_flag)begin
tx_ready <= 1'b1;
send_data <= recv_data;
send_en <= 0;
end
else if((tx_ready)&(~tx_busy))begin
tx_ready <= 0;
send_en <= 1'b1;
end
end
end
endmodule
顶层模块
顶层模块就只是例化各个子模块而已
module top_uart_loopback(
input sys_clk,
input sys_rst_n,
input uart_rxd,
output uart_txd
);
parameter CLK_FRED = 50_000_000;
parameter UART_BPS = 115200;
wire [7:0] uart_recv_data;
wire uart_recv_done;
wire [7:0] uart_send_data;
wire uart_send_en;
wire uart_tx_busy;
uart_receive #(
.CLK_FRED (CLK_FRED),
.UART_BPS (UART_BPS)
)
u_uart_receive(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_data (uart_recv_data),
.uart_done (uart_recv_done)
);
//
uart_send #(
.CLK_FRED (CLK_FRED),
.UART_BPS (UART_BPS)
)
u_uart_send(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_din (uart_send_data),
.uart_en (uart_send_en),
.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_data (uart_recv_data),
.recv_done (uart_recv_done),
.tx_busy (uart_tx_busy),
.send_data (uart_send_data),
.send_en (uart_send_en)
);
endmodule
功能仿真
`timescale 1ns / 1ps
//
module tb_uart_loopback();
reg sys_clk;
reg sys_rst_n;
reg uart_rxd;
wire uart_txd;
top_uart_loopback u_top_uart_loopback(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_txd (uart_txd)
);
initial begin
sys_rst_n = 0;
#200
sys_rst_n = 1;
end
initial begin
sys_clk = 0;
forever #10 sys_clk = ~sys_clk;
end
parameter CLK_FRED = 50_000_000;
parameter UART_BPS = 115200;
parameter BPS_CNT = CLK_FRED/UART_BPS;
reg [3:0] i;
reg [15:0] cnt0;
reg [3:0] cnt1;
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt0 <= 0;
else begin
if(cnt0 == BPS_CNT - 1)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1'b1;
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt1 <= 0;
else begin
if(cnt0 == BPS_CNT - 1)
cnt1 <= cnt1 + 1'b1;
else
cnt1 <= cnt1;
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
i <= 0;
uart_rxd <= 1'b1;
end
else begin
case(i)
0: i <= i + 1;
1: begin
if(cnt1 == 4'd1)begin
uart_rxd <= 1'b0;
i <= i + 1;
end
end
2: begin
if(cnt1 == 4'd2)begin
uart_rxd <= 1'b1;
i <= i + 1;
end
end
3: begin
if(cnt1 == 4'd3)begin
uart_rxd <= 1'b1;
i <= i + 1;
end
end
4: begin
if(cnt1 == 4'd4)begin
uart_rxd <= 1'b0;
i <= i + 1;
end
end
5: begin
if(cnt1 == 4'd5)begin
uart_rxd <= 1'b0;
i <= i + 1;
end
end
6: begin
if(cnt1 == 4'd6)begin
uart_rxd <= 1'b1;
i <= i + 1;
end
end
7: begin
if(cnt1 == 4'd7)begin
uart_rxd <= 1'b1;
i <= i + 1;
end
end
8: begin
if(cnt1 == 4'd8)begin
uart_rxd <= 1'b0;
i <= i + 1;
end
end
9: begin
if(cnt1 == 4'd9)begin
uart_rxd <= 1'b0;
i <= i + 1;
end
end
10: begin
if(cnt1 == 4'd10)begin
uart_rxd <= 1'b1;
i <= i + 1;
end
end
11: i<= 0;
default:;
endcase
end
end
endmodule
到此结束了,因为笔者比较匆忙,而是没写过写过几次,见谅!