通用异步收发传输器( Universal Asynchronous Receiver/Transmitter) ,通常称作UART。 UART 是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。 它包括了 RS232、 RS499、 RS423、 RS422 和 RS485 等接口标准规范和总线标准规范。
RS232 通信协议简介
1、 RS232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线,tx 是发送数据的线。
2、波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、 9600、 115200 等。
3、比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为“每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 单个调制状态对应的二进制位数。如果使用的是 9600 的波特率,其串口的比特率为: 9600Bps 1bit= 9600bps。
整体模块
设计并实现基于串口 RS232 的数据收、发模块,使用收、发模块,完成串口数据回环实验。
模块名称 | 功能描述 |
---|---|
uart_rx | 串口数据接收模块 |
uart_tx | 串口数据发送模块 |
rs232 | 顶层模块 |
消除亚稳态
FPGA接收RX信号可以通过一个或多个D触发器级联,这些触发器被快速时钟(sys_clk)驱动。第一个D触发器捕获到的信号可能会处于亚稳态,但通过级联的第二个或第三个D触发器,亚稳态的影响可以被最小化。这样,信号在离开最后一个触发器时应该是稳定的。
串口接收模块
该模块的功能是接收通过 PC 机上的串口调试助手发送的固定波特率的数据。
信号 | 位宽 | 类型 | 功能描述 |
---|---|---|---|
sys_clk | 1Bit | Input | 工作时钟,频率50MHz |
sys_rst_n | 1Bit | Input | 复位信号,低电平有效 |
rx | 1Bit | Input | 串口接收信号 |
po_data | 8Bit | Output | 串口接收后转成的8bit数据 |
po_data_flag | 1Bit | Output | 串口接收后转成的8bit数据有效标志信号 |
串口数据发送模块
该模块的功能是将 FPGA 中的数据以固定的波特率发送到 PC 机的串口调试助手并打印出来,串口发送模块按照串口的协议组装成帧,然后按照顺序一个比特一个比特将数据发送至 PC 机,而 FPGA 内部的数据往往都是并行的,需将其转化为串行数据发送。
信号 | 位宽 | 类型 | 功能描述 |
---|---|---|---|
sys_clk | 1Bit | Input | 工作时钟,频率50MHz |
pi_data | 8Bit | Input | 发要送的8bit并行数据 |
pi_data_flag | 1Bit | Input | 要发送的8bit并行数据有效标志信号 |
tx | 1Bit | Output | 串口发送信号 |
程序代码
rs232.v
module rs232
(
input sys_clk ,
input sys_rst_n,
input rx ,
output tx
);
wire [7:0] rx_data;
wire tx_flag;
uart_rx
#(
.UART_BPS (9600),
.CLK_FREQ (50_000_000)
)
uart_rx_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rx (rx ),
.po_data (rx_data ),
.po_flag (tx_flag )
);
uart_tx
#(
.UART_BPS (9600),
.CLK_FREQ (50_000_000)
)
uart_tx_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.pi_data (rx_data),
.pi_flag (tx_flag),
.tx (tx )
);
endmodule
tb_rs232.v
`timescale 1ns/1ns
module tb_rs232();
reg sys_clk;
reg sys_rst_n;
reg rx;
wire tx;
//初始化系统时钟、全局复位和输入信号
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
//调用任务rx_byte
initial
begin
#200
rx_byte();
end
//创建任务rx_byte,本次任务调用rx_bit任务,发送8次数据,分别为0~7
task rx_byte(); //因为不需要外部传递参数,所以括号中没有输入
integer j;
for(j = 0; j<8; j= j+1) //调用8次rx_bit任务,每次发送的值从0变化7
rx_bit(j);
endtask
//创建任务rx_bit,每次发送的数据有10位,data的值分别为0到7由j的值传递进来
task rx_bit
(
//传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
input [7:0] data
);
integer i;
for(i = 0; i<10; i= i+1)
begin
case(i)
0: rx <= 1'b0;
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7];
9: rx <= 1'b1;
endcase
#(5208*20);//每发送1位数据延时5208个时钟周期
end
endtask
rs232 rs232_inst
(
.sys_clk (sys_clk ),
.sys_rst_n(sys_rst_n),
.rx (rx),
.tx (tx)
);
endmodule
uart_rx.v
module uart_rx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire rx ,
output reg [7:0] po_data ,
output reg po_flag
);
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg start_flag ;
reg work_en ;
reg [15:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
reg [7:0] rx_data ;
reg rx_flag ;
//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;
//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;
//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;
//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
start_flag <= 1'b0;
else if((rx_reg3 == 1'b1) && (rx_reg2 == 1'b0) && (work_en == 1'b0))
start_flag <= 1'b1;
else
start_flag <= 1'b0;
//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(start_flag == 1'b1)
work_en <= 1'b1;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
work_en <= 1'b0;
else
work_en <= work_en;
//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 16'b0;
else if((baud_cnt == BAUD_CNT_MAX -1) || (work_en == 1'b0))
baud_cnt <= 16'd0;
else
baud_cnt <= baud_cnt + 1'b1;
//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX/2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
bit_cnt <= 4'b0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
//rx_data:输入数据进行移位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_data <= 8'b0;
else if((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8) && (bit_flag == 1'b1))
rx_data <= {rx_reg3,rx_data[7:1]};
//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_flag <= 1'b0;
else if((bit_cnt == 4'd8) && bit_flag == 1'b1)
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_data <= 8'b0;
else if(rx_flag == 1'b1)
po_data <= rx_data;
//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
endmodule
tb_uart_rx.v
`timescale 1ns/1ns
module tb_uart_rx();
reg sys_clk ;
reg sys_rst_n ;
reg rx ;
wire [7:0] po_data;
wire po_flag;
//初始化系统时钟、全局复位和输入信号
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
rx <= 1'b1;
#20
sys_rst_n <= 1'b1;
end
//模拟发送8次数据,分别为0~7
initial
#200
begin
rx_bit(8'd0); //任务的调用,任务名+括号中要传递进任务的参数
rx_bit(8'd1);
rx_bit(8'd2);
rx_bit(8'd3);
rx_bit(8'd4);
rx_bit(8'd5);
rx_bit(8'd6);
rx_bit(8'd7);
end
always #10 sys_clk = ~sys_clk;
//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task rx_bit
(
//传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
input [7:0] data
);
integer i;
for(i = 0; i<10; i= i+1)
begin
case(i)
0: rx <= 1'b0;
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7];
9: rx <= 1'b1;
endcase
#(5208*20);
end
endtask
uart_rx
#(
.UART_BPS (9600),
.CLK_FREQ (50_000_000)
)
uart_rx_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rx (rx ),
.po_data (po_data ),
.po_flag (po_flag )
);
endmodule
uart_tx.v
module uart_tx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [7:0] pi_data , //模块输入的8bit数据
input wire pi_flag , //并行数据有效标志信号
output reg tx
);
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
reg work_en;
reg [15:0] baud_cnt;
reg bit_flag;
reg [3:0] bit_cnt;
//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(pi_flag == 1'b1)
work_en <= 1'b1;
else if((bit_cnt == 4'd9) && (bit_flag == 1'b1))
work_en <= 1'b0;
//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 16'd0;
else if((work_en == 1'b0) || (baud_cnt == BAUD_CNT_MAX))
baud_cnt <= 16'd0;
else if(work_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == 16'd1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 1'b0;
else if((bit_cnt == 4'd9) && (bit_flag == 1'b1))
bit_cnt <= 1'b0;
else if((work_en == 1'b1) && (bit_flag == 1'b1))
bit_cnt <= bit_cnt +1'b1;
//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tx <= 1'b0;//空闲状态时为高电平
else if(bit_flag == 1'b1)
case (bit_cnt)
0 :tx <= 1'b0;
1 :tx <= pi_data[0];
2 :tx <= pi_data[1];
3 :tx <= pi_data[2];
4 :tx <= pi_data[3];
5 :tx <= pi_data[4];
6 :tx <= pi_data[5];
7 :tx <= pi_data[6];
8 :tx <= pi_data[7];
9 :tx <= 1'b1;
default:tx <= 1'b1;
endcase
endmodule
tb_uart_tx.v
`timescale 1ns/1ns
module tb_uart_tx();
reg sys_clk ;
reg sys_rst_n ;
reg [7:0] pi_data ;
reg pi_flag ;
wire tx;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
initial
begin
pi_data <= 8'd0;
pi_flag <= 1'b0;
#200
//数据0
pi_data <= 8'd0;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据1
pi_data <= 8'd1;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据2
pi_data <= 8'd2;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据3
pi_data <= 8'd3;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据4
pi_data <= 8'd4;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据5
pi_data <= 8'd5;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据6
pi_data <= 8'd6;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
#(5208*10*20)
//数据7
pi_data <= 8'd7;
pi_flag <= 1'b1;
#20
pi_flag <= 1'b0;
end
uart_tx
#(
.UART_BPS (9600),
.CLK_FREQ (50_000_000)
)
uart_tx_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.pi_data (pi_data),
.pi_flag (pi_flag),
.tx (tx )
);
endmodule
参考资料
[1] 野火《FPGA Verilog开发实战指南》:
[[野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开发板 — [野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开发板 文档 (embedfire.com)](https://doc.embedfire.com/fpga/altera/ep4ce10_pro/zh/latest/index.html)