前言
(完整代码在文末,包括仿真文件与设计文件,通过仿真与板级验证)本文利用verilog语言实现uart串口异步通信,FPGA接收串口发来的数据,并将接收到的数据通过tx端发送到PC端,在PC端串口打印显示数据 开发板:SF-AT7 软件平台:Vivado 2016.2
一、Uart串口通信
- uart串口通信是一种异步串行全双工通信方式,tx端用于数据发送,rx端用于数据接收。信号线空闲时为高电平。
- 由于是异步通信方式,数据发送会包装成数据帧的形式发送,帧格式为:1个起始位(0)、8个数据位(用户数据)、1个奇偶校验位(用于简单的纠错以保证传输的可靠性)、1和2个停止位(1),其中奇偶校验位不是必须的。下图为帧格式结构。
- 那么该如何检测到数据发送呢?可以注意到数据帧格式中第一个bit是低电平,当FPGA的rx端检测到信号线上有下降沿产生时,表示有数据传送过来,根据预先设置好的波特率对数据接收接收,由于数据是串行从低位到高位传输,接收到的数据暂时存储在寄存器中,待接收完1字节的数据,通过串并转换保存接收到的数据。
- 发送时通过tx信号线按照设置好的波特率将数据发送出去,数据发送仍然要按照数据帧格式发送,即先发送1bit的低电平,再从低位到高位发送数据。
二、串口异步通信实现
1.程序框图
FPGA实时检测uart_rx信号是否有数据,若接收到数据,你把接收到的数据通过uart_tx发送给PC端。 我们知道串口数据传输在设置的波特率下进行,因此需要有专门的波特率产生单元,且发送与接收分别对应一个波特率产生单元,将波特率设置包装成一个模块,分别在发送与接收端例化该模块即可,这是两个独立的硬件资源,属于逻辑复制,而并非资源共享。 整个程序具体实现过程就是有一个顶层模块,在顶层模块中例化波特率设置模块、串口发送与接收处理模块。
speed_setting u2_speed_rx(
.clk(clk_25m),
.rst_n(sys_rst_n),
.bps_start(bps_start1),
.clk_bps(clk_bps1)
);
my_uart_rx_q u3_my_uart_rx(
.clk(clk_25m),
.rst_n(sys_rst_n),
.uart_rx(uart_rx),
.rx_data(rx_data),
.rx_int(rx_int),
.clk_bps(clk_bps1),
.bps_start(bps_start1)
);
speed_setting u4_speed_tx(
.clk(clk_25m),
.rst_n(sys_rst_n),
.bps_start(bps_start2),
.clk_bps(clk_bps2)
);
my_uart_tx_q u5_my_uart_tx(
.clk(clk_25m),
.rst_n(sys_rst_n),
.rx_data(rx_data),
.rx_int(rx_int),
.uart_tx(uart_tx),
.clk_bps(clk_bps2),
.bps_start(bps_start2)
);
2.波特率设置模块
- 前面说过波特率设置本质是一个计数器,常用的波特率如9600、115200等是指1s內传输数据的个数,1/9600就是一个周期所对应的时长。以25MHz为例,一个周期是40ns ,那么计数周期就是:1/9600/40ns*1000000000(注意单位统一),设置一个计数器,当计数次数达到9600bps时,标志位有一个周期的高脉冲信号,该标志位信号用来控制tx端传送数据位的切换或者rx端接收数据位的切换。
- 根据上面的分析结果,利用verilog语言编程实现,可以用一个always块来实现计数器,另一个always块实现标志位置位。为了保证数据采集更加准备,我们选择在数据传送中间采样,即计数器记到9600一半的时候进行数据采样。
- 针对接收端,uart端口rx端接收到数据总线的信号。总线上数据都是以字节的形式传输,而uart通信协议中规定数据是串行接收的,因此接收时要进行串并转换,串并转换的速率由波特率决定。接收到信号后波特率设置模块开始计数,定时产生维持一个周期高电平的采样信号。
代码如下:
module speed_setting(
input clk,
input rst_n,
input bps_start,
output clk_bps
);
`define BPS_9600
`define CLK_PERIORD 40
`define BPS_SET 9600
`define BPS_PARA (1_000_000_000/`CLK_PERIORD/`BPS_SET)
`define BPS_PARA_2 (`BPS_PARA/2)
reg[12:0] cnt;
reg clk_bps_r;
reg[2:0] uart_ctrl;
always@(posedge clk or negedge rst_n)begin
if(!rst_n) cnt <= 13'd0 ;
else if(bps_start)begin
if(cnt < BPS_PARA) cnt <= cnt + 13'd1 ;
else cnt <= 13'd0 ;
end
else cnt <= 13'd0 ;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n) clk_bps_r <= 1'b0 ;
else if(cnt == BPS_PARA_2) clk_bps_r <= 1'b1 ;
else clk_bps_r <= 1'b0 ;
end
assign clk_bps = clk_bps_r ;
endmodule
3.串口发送控制模块
该模块实现对UART接收信号uart_rx进行解码,并实现串并转换,并将数据保存在【rx_data[7:0]】中。具体程序内部实现框图如下:
- 下降沿检测
通过检测信号【uart_rx】信号下降沿。来判断是否有信号发送过来。边沿检测一般会利用多个锁存器,所存uart_rx的状态,根据前后状态的逻辑运算结果进行判断,如果前一个状态是高电平,最新的状态为低电平,则表示出现了下降沿。
module my_uart_rx(
clk,rst_n,
uart_rx,rx_data,rx_int,
clk_bps,bps_start
);
input clk;
input rst_n;
input uart_rx;
input clk_bps;
output bps_start;
output[7:0] rx_data;
output rx_int;
reg uart_rx0,uart_rx1,uart_rx2,uart_rx3;
wire neg_uart_rx;
always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
uart_rx0 <= 1'b0;
uart_rx1 <= 1'b0;
uart_rx2 <= 1'b0;
uart_rx3 <= 1'b0;
end
else begin
uart_rx0 <= uart_rx;
uart_rx1 <= uart_rx0;
uart_rx2 <= uart_rx1;
uart_rx3 <= uart_rx2;
end
assign neg_uart_rx = uart_rx3 & uart_rx2 & ~uart_rx1 & ~uart_rx0;
-
波特率控制模块 该模块因为信号下降沿的到来,使得【bps_start】信号置为1,启动波特率设置模块。 -
计数采样模块 由于数据从低位到高位串行传输,所以要先用一个寄存器暂存数据,且第一位是标识位,不应该将其存储在寄存器中,采用【num】从0开始计数,计数到1时在每个波特率高电平有效的一个周期内从低位到高位保存数据。
always @ (posedge clk or negedge rst_n)
if(!rst_n) begin
rx_temp_data <= 8'd0;
num <= 4'd0;
rx_data_r <= 8'd0;
end
else if(rx_int) begin
if(clk_bps) begin
num <= num+1'b1;
case (num)
4'd1: rx_temp_data[0] <= uart_rx;
4'd2: rx_temp_data[1] <= uart_rx;
4'd3: rx_temp_data[2] <= uart_rx;
4'd4: rx_temp_data[3] <= uart_rx;
4'd5: rx_temp_data[4] <= uart_rx;
4'd6: rx_temp_data[5] <= uart_rx;
4'd7: rx_temp_data[6] <= uart_rx;
4'd8: rx_temp_data[7] <= uart_rx;
default: ;
endcase
end
else if(num == 4'd9) begin
num <= 4'd0;
rx_data_r <= rx_temp_data;
end
end
assign rx_data = rx_data_r;
4.串口发送控制模块
uart接收端接收数据完成后,通过【uart_tx】端将接收到的数据【rx_data】f发送出去。程序流程框图如下: 注意:因为要完全按照帧格式发送数据,所以要先发送一个波特率周期的低电平,在从低到高传输数据位。
reg uart_tx_r ;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
num <= 4'd0 ;
uart_tx_r <= 1'b1;
end
else if(tx_en)begin
if(clk_bps)begin
num <= num + 4'd1 ;
case(num)
4'd0 : uart_tx_r <= 1'b0 ;
4'd1 : uart_tx_r <= tx_data[0] ;
4'd2 : uart_tx_r <= tx_data[1] ;
4'd3 : uart_tx_r <= tx_data[2] ;
4'd4 : uart_tx_r <= tx_data[3] ;
4'd5 : uart_tx_r <= tx_data[4] ;
4'd6 : uart_tx_r <= tx_data[5] ;
4'd7 : uart_tx_r <= tx_data[6] ;
4'd8 : uart_tx_r <= tx_data[7] ;
4'd9 : uart_tx_r <= 1'b1 ;
default: uart_tx_r <= 1'b1;
endcase
end
else if(num == 4'd10)begin
num <= 4'd0 ;
end
end
end
assign uart_tx = uart_tx_r;
三、结果
1、仿真结果
- 仿真波形可以看到,uart_rx端将信号【8‘haa】接收过来,且是在每个bps周期的中间接收,然后再通过【uart_tx】端发送出去,在每个【clk_bps】有效的一个时钟周期内从低位到高位传输接收到的数据
2、板级调试结果
- 利用串口调试工具,发送8bit数据【aa】,可以看到接收端成功接收到该数据。
设计文件与仿真文件
https://pan.baidu.com/s/1dnoyo84pEtMlk5fSQdaqSA 提取码:p4q1
|