UART串口通信
- uart概述
uart全称叫做异步收发传传输器,能够将多bit数据变为一位一位的进行传输,uart作为一种通用串行数据总线,用于异步通信,可实现全双工接发。RS232是uart的一种,是比较常见的一种串行通讯接口,用于PC机和外部板级进行通信。 - RS232通信协议的理解
1.RS232有两根位宽为1bit的数据线,分别为rx和tx,rx用于接收数据,tx用于发送数据。
2.rx作为接收线,当上位机通过串口向FPGA发送8位的数据的时候,从数据的最低位到最高位依次发送,FPGA会从rx线一位一位的接收到数据然后拼接还原成原来的8bit数据。同理,当FPGA向上位机发送数据的时候,也是从最低位到最高位,上位机通过tx线接收到每个bit数据后拼接还原成8bit数据。
3.当rx/tx处于空闲状态时,都处于高电平状态。当开始需要发送数据的时候,数据线会进入一个起始状态即低电平状态,接着是8bit的数据位,数据结束后会有一个停止位,停止位为高电平。若后面数据传送,则会进入空闲态;如果继续收发数据的话,停止位后面会另外接上起始位即0电平。如下图:
4.关于波特率:即串口通信时的速率,单位时间内载波变化的次数,这里选用的是9600Bd,即发送一比特数据需要的时间为 1/9600 秒。串口收发内容包含着1bit起始位+8位数据位+1bit停止位,每个bit都需要1/9600s。上位机在发送数据的时候,在发送8bit数据之前自动发送1bit的起始位,当8bit数据位发送完成的时候后自动发送1bit的停止位。同理上位机在接收数据时也是如此。
5.板卡的时钟是50mhz,因为每个bit收发的时间是1/9600s,用该时钟来计数时需要cnt=(1/9600)/20=5208这么多个时钟才能传输该bit,即每个bit需要5208个系统时钟之后再传输下个bit。 - RS232回环测试
测试的原理其实很简单,上位机通过串口助手把数据通过rx发送到FPGA中,然后在通过tx传回到上位机,当回传的数据和发送的数据一致时,证明uart收发是没有问题的。 - 顶层框图的设计:
根据上面这个框图,需要设计出uart_rx和uart_tx两个部分,单独验证完每个模块之后再例化到顶层中完成回环测试。 - 根据时序写出对应的代码,uart_rx和uart_tx,然后例化到顶层文件top_uart中。
uart_rx代码:
module uart_rx(
input wire clk,
input wire rst_n,
input wire rx,
output wire po_flag,
output wire [7:0] po_data
);
parameter CNT_BAUD_END = 5207;// (1/9600)s/20ns
parameter CNT_BAUD_END_HALF = 2603;
reg rx1,rx2,rx3;
reg rx_flag;
reg [12:0] cnt_baud;
reg bit_flag;
reg [3:0] bit_cnt;
reg [7:0] po_data_r;
reg po_flag_r;
assign po_data = po_data_r;
assign po_flag = po_flag_r;
always @(posedge clk) begin
{rx3,rx2,rx1} <= {rx2,rx1,rx};
end
// rx_flag
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
rx_flag <= 1'b0;
end
else if (bit_cnt == 'd8 && bit_flag == 1'b1 && rx_flag == 1'b1) begin
rx_flag <= 1'b0;
end
else if (rx3 == 1'b1 && rx2 ==1'b0) begin
rx_flag <= 1'b1;
end
end
// cnt_baud
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
cnt_baud <= 'd0;
end
else if (rx_flag == 1'b0) begin
cnt_baud <= 'd0;
end
else if (rx_flag == 1'b1 && cnt_baud == CNT_BAUD_END) begin
cnt_baud <= 'd0;
end
else if (rx_flag == 1'b1) begin
cnt_baud <= cnt_baud + 1'b1;
end
end
// bit_flag
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
bit_flag <= 1'b0;
end
else if (rx_flag == 1'b1 && cnt_baud == CNT_BAUD_END_HALF) begin
bit_flag <= 1'b1;
end
else begin
bit_flag <= 1'b0;
end
end
// bit_cnt
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
bit_cnt <= 'd0;
end
else if (rx_flag == 1'b1 && bit_flag == 1'b1 && bit_cnt == 'd8) begin
bit_cnt <= 'd0;
end
else if (rx_flag == 1'b1 && bit_flag == 1'b1) begin
bit_cnt <= bit_cnt + 1'b1;
end
end
// po_data_r
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
po_data_r <= 'd0;
end
else if (bit_cnt >= 'd1 && bit_flag == 1'b1) begin
po_data_r <= {rx3,po_data_r[7:1]};
end
end
// po_flag_r
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
po_flag_r <= 1'b0;
end
else if (bit_cnt == 'd8 && bit_flag == 1'b1 && rx_flag == 1'b1) begin
po_flag_r <= 1'b1;
end
else begin
po_flag_r <=1'b0;
end
end
endmodule
uart_tx代码:
module uart_tx(
input wire clk,
input wire rst_n,
input wire pi_flag,
input wire [7:0] pi_data,
output wire tx
);
parameter CNT_BAUD_END = 5207;// (1/9600)s/20ns
reg [7:0] data_reg;
reg tx_flag;
reg [12:0] cnt_baud;
reg bit_flag;
reg [3:0] bit_cnt;
reg tx_r;
reg [7:0] shift_reg;
assign tx = tx_r;
// data_reg
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
data_reg <= 'd0;
end
else if (pi_flag == 1'b1) begin
data_reg <= pi_data;
end
end
// tx_flag
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
tx_flag <= 1'b0;
end
else if (cnt_baud == CNT_BAUD_END && bit_flag == 1'b1 && bit_cnt == 'd8) begin
tx_flag <= 1'b0;
end
else if (pi_flag == 1'b1) begin
tx_flag <= 1'b1;
end
end
// cnt_baud
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
cnt_baud <= 'd0;
end
else if (tx_flag == 1'b1 && cnt_baud == CNT_BAUD_END) begin
cnt_baud <= 'd0;
end
else if (tx_flag == 1'b1) begin
cnt_baud <= cnt_baud + 1'b1;
end
end
// bit_flag
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
bit_flag <= 1'b0;
end
else if (tx_flag == 1'b1 && cnt_baud == CNT_BAUD_END-1) begin
bit_flag <= 1'b1;
end
else begin
bit_flag <= 1'b0;
end
end
// bit_cnt
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
bit_cnt <= 'd0;
end
else if (bit_flag == 1'b1 && bit_cnt == 'd8) begin
bit_cnt <= 'd0;
end
else if (bit_flag == 1'b1) begin
bit_cnt <= bit_cnt +1'b1;
end
end
// tx_r
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
tx_r <= 1'b1;
end
else if (pi_flag == 1'b1) begin
tx_r <= 1'b0;
end
else if (bit_cnt <= 'd7 && bit_flag == 1'b1) begin
tx_r <= data_reg[bit_cnt];
end
else if (bit_cnt == 'd8 && bit_flag == 1'b1) begin
tx_r <= 1'b1;
end
end
endmodule
- 写tb文件完成验证。tb的内容主体有三个部分:时钟部分,复位信号,rx数据的产生方式。这里重点记住rx数据是怎么产生的。自己先写一个数据文件,深度为16,位宽为8,然后进行读取。用$readmemh把数据读取到自己定义的变量中去,然后用两个task分别读取深度和宽度。读取深度的task中就有嵌套读取宽度的task。从而达到把写好的8位数据,一位一位的传到rx中,实现并转串。task部分代码如下:
task rd_byte();
integer i;
for(i=0;i<16;i=i+1)begin
rd_bit(mem[i]);
end
endtask
task rd_bit(input [7:0] data);
integer i;
for(i=0;i<10;i=i+1)begin
case(i)
0:rx = 0;
1:rx = data[i-1];
2:rx = data[i-1];
3:rx = data[i-1];
4:rx = data[i-1];
5:rx = data[i-1];
6:rx = data[i-1];
7:rx = data[i-1];
8:rx = data[i-1];
9:rx = 1;
endcase
#104160;
end
endtask
- 完成以上部分后可以在modelsim中进行验证,若tx中的数据和rx中的是对应,再检查uart_rx和uart_tx部分无误后即可,若有错误,定位到错误到错误点。按照路径进行检查。
- 最后一部分则是进行板级验,连接好开发板后,进入ise生bit文件下载到板卡中,打开友善串口助手,把数据位宽和波特率设置好之后,发送数据,最终反馈回来相同的数据。如下: