FPGA学习之串口篇
前言
UART(Universal Asynchronous Receiver/Transmitter)中文名称为通用异步收发器,是一种全双工异步通信总线,俗称串口。串口在MCU中很常见,串口仅用两根信号线就可以实现全双工通信,是各类MCU和一些传感器的标配,在高端SOC芯片中通常用USB取代UART。
串口通信蕴含着最朴素的数字通信思想:通过协议规定收发双方的行为;通过约定动作获得收发同步;通过冗余实现错误检测。串口的传输格式如下:
串口在不发送数据时,数据信号线上总是呈现高电平状态,称为空闲状态。当有数据发送时, 信号线变成低电平,并持续一个波特的时间, 用于表示发送字符的开始,该位称为起始位。起始位之后,在信号线上依次出现待发送的每一位字符数据, 并且按照先低位后高位的顺序逐位发送。待发送的位数可以选择5位、6位、7位或8位,而数据位的后面可以加上一位奇偶校验位,也可以选择不加。最后传送的是停止位,一般选择1位、1.5位或2位。
二、UART发送代码
可以通过参数传递修改波特率。
module uart_tx #(
parameter Burd_rate = 9600 , // 波特率
parameter Clk_freq = 100_000_000 // 时钟频率
)(
input clk_in,
input rst_n,
input tx_flag,
input [7:0] tx_data,
output reg tx
);
localparam CNT_MAX = Clk_freq/Burd_rate;
reg [7:0] tx_data_reg;
reg [0:0] trans_en;
reg [13:0] burd_cnt;
reg bit_flag;
reg [3:0] bit_cnt;
// tx_flag为高时寄存tx_data
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
tx_data_reg <= 'h0;
else if(tx_flag)
tx_data_reg <= tx_data;
else
tx_data_reg <= tx_data_reg;
// 生成发送使能信号
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
trans_en <= 'h0;
else if(tx_flag)
trans_en <= 1'b1;
else if(bit_cnt == 4'd9 && bit_flag == 1'b1)
trans_en <= 1'b0;
// 累加波特计数器
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
burd_cnt <= 'h0;
else if((trans_en == 0) || (burd_cnt == CNT_MAX -1))
burd_cnt <= 'h0;
else if(trans_en)
burd_cnt <= burd_cnt + 1'b1;
// 生成比特发送使能信号
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
bit_flag <= 'b0;
else if(burd_cnt == 1)
bit_flag <= 1'b1;
else
bit_flag <= 'b0;
// 累加比特计数器
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
bit_cnt <= 'b0;
else if(bit_cnt == 4'd9 && bit_flag == 1'b1)
bit_cnt <= 'b0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
// 发送比特数据
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
tx <= 1'b1;
else if(bit_flag == 1'b1) begin
case(bit_cnt)
0: tx <= 1'b0;
1: tx <= tx_data_reg[0];
2: tx <= tx_data_reg[1];
3: tx <= tx_data_reg[2];
4: tx <= tx_data_reg[3];
5: tx <= tx_data_reg[4];
6: tx <= tx_data_reg[5];
7: tx <= tx_data_reg[6];
8: tx <= tx_data_reg[7];
9: tx <= 1'b1;
default:tx <= 1'b1;
endcase
end
endmodule
三、UART接收代码
可以通过参数传递修改波特率。
module uart_rx #(
parameter Burd_rate = 9600 , // 波特率
parameter Clk_freq = 100_000_000 // 时钟频率
)(
input clk_in,
input rst_n,
input rx,
output reg rx_flag,
output reg [7:0] rx_data
);
localparam CNT_MAX = Clk_freq/Burd_rate;
reg [2:0] rx_reg;
reg start_flag;
reg receive_en;
reg [13:0] buad_cnt;
reg [3:0] bit_cnt;
reg bit_flag;
reg [7:0] rx_data_reg;
reg [7:0] rx_data;
reg [0:0] rx_flag;
wire fall_edge;
// 提取 rx打拍信号的下降沿
assign fall_edge = (~rx_reg[1]) && rx_reg[2] ;
// 将rx打三拍
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
rx_reg <= 3'b000;
else
rx_reg <= {rx_reg[1],rx_reg[0],rx};
// 产生起始信号
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
start_flag <= 1'b0;
else if(fall_edge && (receive_en == 1'b0))
start_flag <= 1'b1;
else
start_flag <= 1'b0;
// 产生计数使能信号
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
receive_en <= 1'b0;
else if(start_flag == 1'b1 && bit_cnt == 4'b0)
receive_en <= 1'b1;
else if((bit_cnt == 4'd9) && (buad_cnt == CNT_MAX-1))
//else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
receive_en <= 1'b0;
else
receive_en <= receive_en;
// 波特计数器累加
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
buad_cnt <= 'h0;
else if(buad_cnt == CNT_MAX - 1 || receive_en == 1'b0)
buad_cnt <= 'h0;
else if(receive_en == 1'b1)
buad_cnt <= buad_cnt + 1'b1;
// 比特采样标志信号
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
bit_flag <= 1'b0;
else if(buad_cnt == CNT_MAX/2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
// 接收比特计数器累加
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
bit_cnt <= 4'b0;
else if(bit_cnt == 4'd9 && buad_cnt== CNT_MAX-1)
bit_cnt <= 4'b0;
else if(bit_flag )
bit_cnt <= bit_cnt + 1'b1;
// 接收数据移位,先接收的是低位,最后接收的是高位
always@(posedge clk_in or negedge rst_n)
if(!rst_n)
rx_data_reg <= 8'b0;
else if(bit_flag && bit_cnt > 4'd0)
rx_data_reg <= {rx_reg[2],rx_data_reg[7:1]};
else if(bit_cnt == 4'b0)
rx_data_reg <= 8'b0;
else
rx_data_reg <= rx_data_reg;
// 输出数据有效信号和八位数据
always@(posedge clk_in or negedge rst_n)
if(!rst_n) begin
rx_data <= 8'b0;
rx_flag <= 1'b0;
end
else if((bit_cnt == 4'd9) && (buad_cnt== CNT_MAX -1)) begin
rx_data <= rx_data_reg;
rx_flag <= 1'b1;
end
else begin
rx_data <= 8'b0;
rx_flag <= 1'b0;
end
endmodule
三、 总结
串口一次最多传输8位而不是更多位的原因在于收发双方并不是时钟同源的,并不像SPI系统有一个主时钟供主、从机共享。如果收发双方时钟偏差超过5%,则双方对同一数据序列采样经过20个时钟周期后,就会出现一个样点差异。 这种情况必然会导致数据统计错误。而UART的设计标准就是允许收发两端的频率偏差在10%以内,因此当接收8位数据后,收发双方的误差刚好控制在一位以内,对数据的采样不会出错,可正确通信。