基于FPGA的UART回环设计
文章目录
一. UART介绍
UART(Universal Asynchronous Receiver/Transmitter),即通用异步收发传输器,它是一种通用的串行、异步通信总线,有两条数据线,可以实现全双工的发送与接收。
UART因为是异步通信,所以不包括同步信号中的时钟线,其两条数据数据线TX和rRX分别用来发送和接收数据。UART负责处理串行端口与发送接收数据总线见的串并转换,并规定了数据包格式,TX和RX采用相同的数据包格式和波特率就可以进行数据传输。
二. 协议原理
发送端UART将要发送的数据组装成一个数据包通过TX端口串行将数据发送出去,接收端UART通过其RX端口串行对数据包的数据进行采集,从数据包中恢复出发送端发送的数据。
UART的数据包包括1个开始位、5到9位数据(取决于UART设置),1个奇偶校验位,1或者2个停止位。
开始位:UART数据传输线空闲时保持高电平,开始发送数据时,发送端UART将数据线从高拉到低一个时钟周期(其长短根据波特率决定)。当接收端UART探测到下降沿时,开始根据波特率读取数据。
数据位:数据位中为要传输的真是数据,如果使用奇偶校验位,数据帧中可包含5到8位数据,如果不适用奇偶校验位的话,数据帧中最长则可包含9位数据。
奇偶校验位:奇偶校验位用于在接收端UART来显示传输过程中是否有数据被改变。在传输过程中,位数据可能因为电磁辐射、不匹配的波特率、或者长距离传输等原因被改变。当接收UART读取完数据帧后,计算其中1的个数,并且检查1的总个数是奇数还是偶数。如果奇偶校验位是0,则数据帧中1的个数应该是偶数个,如果奇偶校验位是1,则数据帧中1的个数则应该是奇数个。当奇偶校验位的值与数据位帧相匹配时,则UART认为传输中没有错误(具有局限)。但是如果奇偶校验位是0,数据帧的1的总个数是奇数;或者奇偶校验位是1,数据帧中1的共个数是偶数,则UART则会知道帧数据已经被改变。
停止位:为了表示传输包的结束,发送端UART将数据传输线从低电平拉到高电平至少1到2个数据位宽。
前面已经对数据包/数据发送时序进行了介绍,下面在对波特率以及与其容易混淆的比特率的基础知识进行补充。
波特率定义是:每秒钟传送的符号(码元)数量,单位波特(Baud、B,即symbol/s)。
比特率定义是:每秒钟传送的位(比特)数量,基本单位是比特每秒(bps,bit per second)。.
从两者的定义可以看出要想知道波特率和比特率的关系,就首先的弄清楚码元数量和比特数量的关系。此处引用参考资料2中的解释:把通信系统中码元类比为公共交通车辆,例如公交车、地铁、的士……。通信系统所传输的比特数量类比为出行的人,则比特率为出行人口流动速度,波特率就是发车率。就像例子中提及的公交车、地铁、的士可以搭乘不同数量的出行人员一样,不同码元也可以用不同位数的比特表示。码元所需要的比特位数,由码元支持的状态数量确定。
UART中每个码元就是一位数据,存在两种状态,也即波特率单位变为每秒位数,与比特率定义相同,可以理解为此处的波特率就是比特率,本次采用的波特率为9600bps、14400bps、38400bps、115200bps。
三. 基本计数器实现
本次采用可调节波特率设置
always @(*) begin
case(sw)
2'b00: baud = 5208; //9600波特率
2'b01: baud = 3472; //14400波特率
2'b10: baud = 1302; //38400波特率
2'b11: baud = 434; //115200波特率
default: baud = 5208;
endcase
end
当sw开关拨动到不同位置时,可设置不同的波特率
1. 计数器设计
波特率计数器设计,为简单的基础计数器,最大数为可设置的调节数
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
else begin
cnt <= cnt;
end
end
assign end_cnt = add_cnt && (cnt == baud-1)||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1));
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
add_cnt <= 1'b0;
end
else if(rx_start)begin
add_cnt <= 1'b1;
end
else if(end_cnt_bit||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1)))begin
add_cnt <= 1'b0;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_r <= 2'b00;
end
else begin
rx_r <= {rx_r[0],rx};
end
end
assign rx_start = ~rx_r [0] & rx_r [1];
其中,add_cnt为波特率计数器开始计数的标志,当输入的数据起始位到来,即检测其下降沿rx_r,波特率计数器开始计数。
end_cnt为波特率计数器结束的标志,当计到最大的波特率时停止,或者在10bit即停止位时(本设计由于硬件问题,停止位为半个波特率的周期),计数器停止计数。
比特计数器设计
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = end_cnt ;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 4'd11-1;
开始计数标志为波特率计数器结束,当计到最大bit数,即11个bit时停止。
2. 数据接收
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_data_r <= 11'b11111111111;
end
else if(cnt == (baud - 1)>>2)begin
rx_data_r[cnt_bit] <= rx;
end
end
由于uart收发数据为串行数据,这里要将串行数据转为并行数据,设置数据寄存器rx_data_r,按照bit计数器将数据存入寄存器
assign rx_check = ~^ rx_data_r[8 :1];
奇偶校验位处理,本设计位奇校验rx_check为第十位奇偶校验位,对前8bit数据位进行按位异或运算,所得结果为0则代表数据位有偶数个,1则代表为奇数个,但本设计为奇校验,对所得数再取反,才使得所有数据加校验位为奇数个1。
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_data <= 8'hff;
end
else if((cnt == (baud - 1)>>1)&&cnt_bit == 4'd9)begin
if((rx_check == rx_data_r[9]))begin
rx_data <= rx_data_r[8:1];
end
else begin
rx_data <=8'hff;
end
end
end
将数据位的8bit数据存入寄存器,方便传出和后续使用。
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_done <= 1'b0;
end
else if(end_cnt_bit)begin
rx_done <= 1'b1;
end
else begin
rx_done <= 1'b0;
end
end
rx停止信号,方便tx发数据以及后续的处理。
3. tx发送设计
波特率计数器与bit计数器与rx相同,这里不再赘述。
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data<=11'b11111111111;
end
else if(tx_start)begin
data<={1'b1,tx_check,tx_data,1'b0};
end
else begin
data<=data;
end
end
发出数据的处理,uart为低位先发,先发0起始位,后接所发的数据1-8bit的tx_data为rx数据寄存器传入的数据,后接校验位tx_check(与rx中同理,对tx_data按位取异或再取反),后接停止位1.
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx<=1'b1;
end
else if(end_cnt)begin
tx<=data[cnt_bit];
end
else if(end_cnt_bit)begin
tx<=1'b1;
end
else begin
tx<=tx;
end
end
uart数据为串行数据,将缓存的data数据挨个发出。
5. 主要代码
uart_rx.v
module uart_rx (
input clk,
input rst_n,
input rx,
input [1:0] sw,
output reg [7:0] rx_data,
output reg rx_done
);
wire rx_check;
//比特率计数器
reg [12:0] cnt;
reg add_cnt;
wire end_cnt;
//bit计数器
reg [3:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
//下降沿检测
reg [1:0] rx_r;
wire rx_start;
reg [10:0] rx_data_r;
reg [12:0] baud;
always @(*) begin
case(sw)
2'b00: baud = 5208; //9600波特率
2'b01: baud = 3472; //14400波特率
2'b10: baud = 1302; //38400波特率
2'b11: baud = 434; //115200波特率
default: baud = 5208;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
else begin
cnt <= cnt;
end
end
assign end_cnt = add_cnt && (cnt == baud-1)||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1));
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
add_cnt <= 1'b0;
end
else if(rx_start)begin
add_cnt <= 1'b1;
end
else if(end_cnt_bit||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1)))begin
add_cnt <= 1'b0;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_r <= 2'b00;
end
else begin
rx_r <= {rx_r[0],rx};
end
end
assign rx_start = ~rx_r [0] & rx_r [1];
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = end_cnt ;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 4'd11-1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_data_r <= 11'b11111111111;
end
else if(cnt == (baud - 1)>>2)begin
rx_data_r[cnt_bit] <= rx;
end
end
assign rx_check = ~^ rx_data_r[8 :1];
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_data <= 8'hff;
end
else if((cnt == (baud - 1)>>1)&&cnt_bit == 4'd9)begin
if((rx_check == rx_data_r[9]))begin
rx_data <= rx_data_r[8:1];
end
else begin
rx_data <=8'hff;
end
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_done <= 1'b0;
end
else if(end_cnt_bit)begin
rx_done <= 1'b1;
end
else begin
rx_done <= 1'b0;
end
end
endmodule
uart_tx.v
module uart_tx (
input clk,
input rst_n,
input [7:0] tx_data,
input tx_start,
input [1:0] sw,
output reg tx,
output tx_done
);
//奇偶校验位
wire tx_check;
//波特率计数器参数
reg [12:0] cnt;
reg add_cnt;
wire end_cnt;
//bit计数器参数
reg [3:0] cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;
reg [12:0] baud;
reg [10:0] data;//数据寄存器
always @(*) begin
case(sw[1:0])
2'b00: baud = 5208; //9600波特率
2'b01: baud = 3472; //14400波特率
2'b10: baud = 1302; //38400波特率
2'b11: baud = 434; //115200波特率
default: baud = 5208;
endcase
end
//波特率计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
else begin
cnt <= cnt;
end
end
assign end_cnt = add_cnt && (cnt == baud-1)||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1));
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
add_cnt <= 1'b0;
end
else if(tx_start)begin
add_cnt <= 1'b1;
end
else if(end_cnt_bit||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1)))begin
add_cnt <= 1'b0;
end
end
//bit计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = end_cnt;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 4'd11-1;
assign tx_check = ~^tx_data;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data<=11'b11111111111;
end
else if(tx_start)begin
data<={1'b1,tx_check,tx_data,1'b0}; //数据加启示位
end
else begin
data<=data;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx<=1'b1;
end
else if(end_cnt)begin
tx<=data[cnt_bit];
end
else if(end_cnt_bit)begin
tx<=1'b1;
end
else begin
tx<=tx;
end
end
assign tx_done = end_cnt_bit;
endmodule
led.v
module led (
input clk,
input rst_n,
input [1:0] sw,
input [7:0] data,
output reg [2:0] led
);
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
led[1:0] <= 2'b00;
end
else begin
case(sw)
2'b00: led[1:0] <= 2'b00;
2'b10: led[1:0] <= 2'b01;
2'b01: led[1:0] <= 2'b10;
2'b11: led[1:0] <= 2'b11;
default: led[1:0] <= 2'b00;
endcase
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
led[2] <= 1'b0;
end
else if(data == 8'h80)begin
led[2] <= 1'b1;
end
else if(data == 8'hb3)begin
led[2] <= 1'b0;
end
else begin
led[2] <= led[2];
end
end
endmodule
uart_loop.v
module uart_loop (
input clk,
input rst_n,
input rx,
input [1:0] sw,
output [2:0] led,
output tx
);
wire [7:0] data;
wire tx_done;
uart_rx inst_uart_rx (
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.sw(sw),
.rx_data(data),
.rx_done(rx_done)
);
uart_tx inst_uart_tx (
.clk(clk),
.rst_n(rst_n),
.tx_data(data),
.tx_start(rx_done),
.sw(sw),
.tx(tx),
.tx_done(tx_done)
);
led inst_led (
.clk(clk),
.rst_n(rst_n),
.sw(sw),
.data(data),
.led(led)
);
endmodule
四. 状态机实现
1. rx接收模块
设置波特率部分与上文相同不在赘述。
以下为状态机部分
//状态机1段
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
状态机基本逻辑
//状态机2段
always @(*) begin
case(state_c)
IDLE: state_n = (IDLE_2_START)? START : state_c;
START: begin state_n = (START_2_DATA)? DATA : state_c; bit_num = 4'd0 ;end
DATA: begin state_n = (DATA_2_STOP )? STOP : state_c; bit_num = 4'd8 ;end
STOP: begin state_n = (STOP_2_IDLE )? IDLE : state_c; bit_num = 4'd0 ;end
default: begin state_n = IDLE;bit_num = 4'd0; end
endcase
end
本次设置状态有四个,分别为空闲IDLE,起始位START,数据位DATA,停止位STOP。各个跳转条件为***2***,即为 ***to ***(例如IDLE_to_START即写为IDLE_2_START)。
//状态机3段
assign IDLE_2_START = rx_start;
assign START_2_DATA = end_cnt_bit;
assign DATA_2_STOP = end_cnt_bit;
assign STOP_2_IDLE = end_cnt_bit;
对跳转条件进行描述,其中rx_start即为下降沿检测,检测到下降沿到来时,状态进行跳转,起始位到来,之后每个状态跳转都基于bit计数器,bit计数器结束时跳转状态。
波特率计数器与上文相同,但开始与结束条件有所改变。
assign end_cnt = add_cnt && ((cnt == baud-1)||( (cnt == ((baud-1)>>1)&& state_c == STOP)));
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
add_cnt <= 1'b0;
end
else if(state_c == IDLE)begin
if(rx_start)begin
add_cnt <= 1'b1;
end
else begin
add_cnt <= 1'b0;
end
end
else if(START_2_DATA||DATA_2_STOP||STOP_2_IDLE)begin
add_cnt <= 1'b1;
end
else if((cnt == (baud-1)>>1)&& state_c == STOP)begin
add_cnt <= 1'b0;
end
end
正常情况下,记到当前所设波特率的最大值是停止,或当状态为STOP且记到半个波特率周期时停止。
当状态为空闲IDLE且rx_start为真,开始起始位的波特率计数。
当跳转条件成立,也将add_cnt拉高,开始计数。
当记到当前所设波特率的最大值是停止,或当状态为STOP且记到半个波特率周期时,将add_cnt拉低,不计数。
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_num;
bit计数器主体无改变,只有停止信号改为状态机中所设置的bit_num。
rx_data_r数据缓存与rx_check停止位写法与上文形同
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
rx_data <= 8'hff;
end
else if(end_cnt_bit && state_c == DATA)begin
if((rx_check == rx_data_r[8]))begin
rx_data <= rx_data_r[7:0];
end
else begin
rx_data <=8'hff;
end
end
end
assign rx_done = STOP_2_IDLE && state_c == STOP;
当状态处于DATA时且数据计到停止位,将八位数据位的数据赋给数据寄存器,传给tx发送模块。
状态从停止位STOP跳转到IDLE空闲位时,代表一次数据的接收完成,将完成信号传出。
2. tx发送模块
三段状态机、波特率计数器、bit计数器、下降沿、校验位都与rx写法相同不在赘述。
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
data_r <=9'b111111111;
end
else if(tx_start)begin
data_r <= {tx_check,tx_data};
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx <= 1'b1;
end
else begin
case(state_c)
IDLE: tx <= 1'b1;
START: tx <= 1'b0;
DATA: tx <= data_r[cnt_bit];
STOP: tx <= 1'b1;
default: tx <= 1'b1;
endcase
end
end
tx_start为rx收数据的停止信号,此时进行数据的发送操作。
将所收数据与校验位进行拼接,组成新的数据,再进行并串转换,IDLE状态下发送1,即为空闲,START发送0
为起始位,接下来依次发送寄存器data_r的数据,最后发送停止位1。
五. 实现效果
电脑向FPGA发送hello,FPGA接收到并返回电脑hello
sw[0]与sw[1]都为1,同时led[2]与led[3]亮起,对应波特率为115200,数据传输正确。
sw[0]为0,sw[1]为1,led[2]量led[3]灭,对应波特率为38400,
数据传输正确。
电脑向FPGA发送“开”,led[0]亮起
发送关led[0]熄灭。
六. 总结
本次对uart的回环设计用到了两种设计方法,分别为简单的计数器堆叠,和用状态机实现,前者实现较为简单且更容易理解,后者能够清晰的看到各种状态之间的逻辑,本人更偏向于前者。两种方法所实现的效果相同。