概述
FPGA-UART接口实现
- 前言
- UART协议
- UART协议实现(verilog)
- 仿真
前言
UART接口协议是一种比较简单、非常常用的一种接口协议,使用它的场景很常见,是我们学习FPGA一定要会的接口协议;
UART协议
通用异步收发器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种串行、异步、全双工的通信协议,在嵌入式领域应用的非常广泛。其数据通信格式如下图:
LSB:
least significant bit 表示二进制数据的最低位。
MSB :
most significant bit 表示二进制数据的最高位。
起始位:
每开始一次通信时发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的开始。因为总线空闲时为高电平所以开始一次通信时先发送一个明显区别于空闲状态的信号即低电平。
数据位:
起始位之后就是我们所要传输的数据,数据位可以是5、6、7、8,9位等,构成一个字符(一般都是8位)。先发送最低位,最后发送最高位,使用低电平表示‘0’高电平表示‘1’完成数据位的传输。
奇偶校验位:
数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。
停止位:
它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备之间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟的机会。停止位个数越多,数据传输越稳定,但是数据传输速度也越慢。
空闲位:
UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平,表示当前线路上没有数据传输。
UART协议实现(verilog)
为了更加简单的描述串口协议,我们这里使用常用的1位起始位,1位停止位,8位数据位,无奇偶校验位,波特率9600(本次波特率以参数形式提供,如需测试其他波特率,计算后更改即可)。
UART发送端代码如下
module uart_tx
#(
parameter max_baud = 5208,
parameter max_samp = 2604
)
(
input wire sclk ,
input wire rst_n ,
input wire tx_data_en ,//接收完成标志
input wire[7:0] tx_data ,//接收数据
output reg uart_tx
);
reg tx_en ;
reg[7:0] tx_data_tmp ;
reg[12:0] cnt_baud ;//波特率
reg bit_flag ;
reg[3:0] bit_cnt ;
// parameter max_baud = 5208-1 ;
// parameter max_samp = 2604-1 ;
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
tx_data_tmp <= 0;
else if(tx_data_en==1)
tx_data_tmp <= tx_data;
//计数标志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
tx_en <= 0;
else if(tx_data_en==1)
tx_en <=1 ;
else if(bit_cnt==9 && bit_flag==1)
tx_en <= 0;
//波特率产生
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
cnt_baud <= 0;
else if(tx_en==0) ///****
cnt_baud <= 0;
else if(cnt_baud==max_baud)
cnt_baud <= 0;
else if(tx_en==1)
cnt_baud <= cnt_baud + 1'b1;
//bit标志产生
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
bit_flag <= 0;
else if(cnt_baud==max_samp)
bit_flag <= 1;
else
bit_flag <= 0;
//bit计数
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
bit_cnt <= 0;
else if(bit_cnt==9 && bit_flag==1)
bit_cnt <= 0;
else if(bit_flag==1)
bit_cnt <= bit_cnt + 1'b1;
//发送数据
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
uart_tx <= 0;
else if(bit_flag==1)
case(bit_cnt)
0:uart_tx <= 0; //起始位
1:uart_tx <= tx_data_tmp[0]; //先发低位
2:uart_tx <= tx_data_tmp[1];
3:uart_tx <= tx_data_tmp[2];
4:uart_tx <= tx_data_tmp[3];
5:uart_tx <= tx_data_tmp[4];
6:uart_tx <= tx_data_tmp[5];
7:uart_tx <= tx_data_tmp[6];
8:uart_tx <= tx_data_tmp[7];
9:uart_tx <= 1'b1; //停止位
default:uart_tx <= 1'b1;
endcase
endmodule
UART接收端代码如下
module uart_rx
#(
parameter max_baud = 5208,
parameter max_samp = 2604
)
(
input wire sclk , //50MHZ
input wire rst_n ,
input wire rx ,
output reg rx_data_en ,
output reg[7:0] rx_data
);
reg rx1 ;//延时一个时钟
reg rx2 ;//延时两个时钟
reg rx_reg ;//延时三个时钟
reg rx_en ;//计数标志
reg[12:0] cnt_baud ;//波特率
reg bit_flag ;
reg[3:0] bit_cnt ;
reg rx_data_en_tmp ;
reg[7:0] rx_data_tmp ;
//消除亚稳态
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx1 <= 0;
else
rx1 <= rx;
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx2 <= 0;
else
rx2 <= rx1;
//边沿检测
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_reg <= 0;
else
rx_reg <= rx2;
//产生计数标志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_en <= 0;
else if(rx2 == 0 && rx_reg == 1)
rx_en <= 1;
else if(bit_cnt==9)
rx_en <= 0;
//波特率产生
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
cnt_baud <= 0;
else if(cnt_baud==max_baud)
cnt_baud <= 0;
else if(rx_en==0)
cnt_baud <= 0;
else if(rx_en==1)
cnt_baud <= cnt_baud + 1'b1;
//采样产生bit标志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
bit_flag <= 0;
else if(cnt_baud==max_samp)
bit_flag <= 1;
else
bit_flag <= 0;
//bit计数
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
bit_cnt <= 0;
else if(bit_cnt==9)
bit_cnt <= 0;
else if(bit_flag==1)
bit_cnt <= bit_cnt + 1'b1;
//接收数据
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data_tmp <= 0;
else if(bit_flag==1 && bit_cnt!=0)
rx_data_tmp <= {rx_reg,rx_data_tmp[7:1]}; //右移
//传输完标志
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data_en_tmp <= 0;
else if(bit_cnt==8 && bit_flag==1)
rx_data_en_tmp <= 1;
else
rx_data_en_tmp <= 0;
//接收数据输出
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data <= 0;
else if(rx_data_en_tmp==1)
rx_data <= rx_data_tmp;
//接收数据标志输出
always @(posedge sclk or negedge rst_n)
if(rst_n==0)
rx_data_en <= 0;
else
rx_data_en <= rx_data_en_tmp;
endmodule
再写一个顶层文件将发送端和接收端例化(当然,使用时也可以直接使用发送代码和接收代码),代码如下
module uart_top(
input wire sclk ,//50MHZ
input wire rst_n ,
input wire rx_data ,
output wire tx_data
);
wire rx_flag ;
wire[7:0] data_net ;
uart_rx
#(
.max_baud (5208-1 ), //波特率 50M/9600 ~5208
.max_samp (2604-1 ) //在中间采样
)
uart_rx_inst(
.sclk (sclk ),
.rst_n (rst_n ),
.rx (rx_data ),
.rx_data_en (rx_flag ),
.rx_data (data_net )
);
uart_tx
#(
.max_baud (5208-1 ), //波特率 50M/9600 ~5208
.max_samp (2604-1 ) //在中间采样
)
uart_tx_inst(
.sclk (sclk ), //50MHZ
.rst_n (rst_n ),
.tx_data_en (rx_flag ),
.tx_data (data_net ),
.uart_tx (tx_data )
);
endmodule
这里实现UART的方式是通过计数器的方式,当然也可以通过其他的方式实现(比如状态机等),主要还是要理解UART协议,再理解的基础上,实现起来就会比较轻松。
仿真
给对应top写一个testbench(仿真激励),如下:
`timescale 1ns/1ns
module tb_uart_top() ;
reg sclk ;
reg rst_n ;
reg rx_data ;
wire tx_data ;
initial
begin
sclk = 0 ;
rst_n <= 0 ;
#20
rst_n <= 1 ;
end
always #5 sclk = ~sclk ; //50MHZ
initial
begin
rx_data <= 1 ;
#200
rx_byte() ;
end
// defparam uart_top_inst.uart_rx_inst.max_baud = 51;
// defparam uart_top_inst.uart_rx_inst.max_samp = 25;
// defparam uart_top_inst.uart_tx_inst.max_baud = 51;
// defparam uart_top_inst.uart_tx_inst.max_samp = 25;
task rx_bit(
input [7:0] data
);
integer i;
for(i=0;i<10;i=i+1)
begin
case(i)
0:rx_data <= 0 ;
1:rx_data <= data[0] ;
2:rx_data <= data[1] ;
3:rx_data <= data[2] ;
4:rx_data <= data[3] ;
5:rx_data <= data[4] ;
6:rx_data <= data[5] ;
7:rx_data <= data[6] ;
8:rx_data <= data[7] ;
9:rx_data <= 1'b1 ;
endcase
#53000;
end
endtask
task rx_byte();
integer j;
for(j=0;j<24;j=j+1)
rx_bit(j);
endtask
uart_top uart_top_inst(
.sclk (sclk ),
.rst_n (rst_n ),
.rx_data (rx_data ),
.tx_data (tx_data )
);
endmodule
这里通过task发送0-23到UART接收端,接收端接收到数据再通过发送端发送出去。
截取其中仿真波形如下:
以发送23(8‘h17)为例,从图上可以看出接收的二进制数为8‘b11101000,因为在仿真时(testbench)我们先发送的低位,所以需要将接收的数据倒置,即8’b00010111=8’h17,转化为十进制即为23。
仿真结果正确。
实际上本段代码可以直接拿来使用,下板测试通过。
若有相关问题可以互相讨论
最后
以上就是年轻大象为你收集整理的FPGA常用接口协议--UART的全部内容,希望文章能够帮你解决FPGA常用接口协议--UART所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复