目录
- 简介
- 接口与引脚
- 通信协议
- 亚稳态
- RS232接收模块
- 模块框图
- 时序波形
- RTL 代码
- 易错点
- Testbench 代码
- 仿真
- RS232发送模块
- 模块框图
- 时序波形
- RTL 代码
- Testbench 代码
- 仿真
简介
- UART:Universal Asynchronous Receiver/Transmitter,异步串行通信接口。发送数据时并行转串行,接收数据时串行转并行。
- RS232:UART包括多种接口标准规范和总线标准规范,RS232为其一,还有RS499、RS423、RS422、RS485等。
接口与引脚
重点关注RXD、TXD即可。
通信协议
-
帧结构:包含8bit有效数据和起始位、停止位。空闲状态是高电平,起始位低电平,停止位高电平。低位数据先发,后发高位。
假设我要传输字符“1”,对应的ASCII码值是0x31,即“0011 0001” ,低位先发,那么我的这帧数据波形为 0 1000 1100 1 -
波特率:假设波特率为9600Bps,传输一个码元(一个码元在这里代表一个二进制数,就是1bit数据)需要1/9600秒。假设系统时钟50MHz(周期为20ns),那么传输1bit数据所需的时间为 (1/9600)/(20*e-9) = 5208.33 ≈ 5208 个时钟周期。
亚稳态
-
产生原因:
触发器采集输入信号时需要一定的建立时间和保持时间,如果在这一期间输入信号发生变化,那么触发器将无法稳定输出,导致输出信号在高低电平间快速振荡,即进入亚稳态。 -
不良影响:
如果处于亚稳态的信号直接接入组合逻辑电路,会导致亚稳态在整个系统中传递,从而影响整个电路的运行,导致不稳定。 -
解决方案:
单比特信号可以采用“打两拍”,多比特信号可以采用异步FIFO、格雷码、握手。
RS232接收模块
模块框图
输入输出描述:
- 时钟:50MHz,每10ns翻转一次
- 复位:高电平异步复位
- RX:接收的串口数据
- DATA:处理后的数据,并行输出
- FLAG:置高时代表已处理好一帧数据,只维持一个时钟周期
时序波形
示意时序图,以9600波特率为例。
时序图分析:
- RX_REG
- RX_REG1:是将 RX 同步到时钟信号上。
- RX_REG2、3:打两拍,避免亚稳态。
- START_FLAG 标志数据接收的开始、WORK_EN 标志数据接收的状态
- START_FLAG:这里使用的是组合方式赋值,通过 RX_REG2、3 检测 RX_REG 的下降沿。由于 RX_REG 的数据位中也有可能存在下降沿,所以 START_FLAG 置高的前提条件还应有 WORK_EN 为 0 。
- WORK_EN:WORK_EN 通过判断 START_FLAG 的状态而置高,通过判断 BIT_CNT、BIT_FLAG 的状态而置低,其余时刻保持不变。
- BAUD_CNT、BIT_FLAG
- BAUD_CNT:BAUD_CNT 只在 WORK_EN 为高时计数
- BIT_FLAG:在每比特数据传输时的中间时刻拉高
- RX_DATA
由于是低位数据先发,所以 RX_REG 的数据存在 RX_DATA 的高位,每次更新时 RX_DATA 右移,实现数据的串行转并行。RX_DATA 的值在 BAUD_CNT 为 1-8 并且 BIT_FLAG 为高时采集 RX_REG。
RTL 代码
module RS232_RX
#(
parameter UART_BPS = 'd9600,
parameter CLK_FREQ = 'd50_000_000
)
(
input wire clk,
input wire rst,
input wire rx,
output reg [7:0] data,
output reg flag
);
parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;
reg rx_reg1;
reg rx_reg2;
reg rx_reg3;
wire start_flag;
reg work_en;
reg [16:0] baud_cnt;
reg bit_flag;
reg [3:0] bit_cnt;
reg [7:0] rx_data;
reg data_flag;
// 赋值rx_reg1
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;
end
// 赋值rx_reg2
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;
end
// 赋值rx_reg3
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;
end
// 赋值start_flag
assign start_flag = ((rx_reg2 == 1'b0) && (rx_reg3 == 1'b1));
// 赋值work_en
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
work_en <= 1'b0;
else if(start_flag == 1'b1)
work_en <= 1'b1;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd8))
work_en <= 1'b0;
else
work_en <= work_en;
end
// 赋值baud_cnt
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
baud_cnt <= 17'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
baud_cnt <= 17'b0;
else
baud_cnt <= baud_cnt + 1'b1;
end
// 赋值bit_flag
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX / 2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end
// 赋值bit_cnt
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
bit_cnt <= 4'b0;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd8))
bit_cnt <= 4'b0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
// 赋值rx_data
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
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]};
else
rx_data <= rx_data;
end
// 赋值data_flag
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
data_flag <= 1'b0;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd8))
data_flag <= 1'b1;
else
data_flag <= 1'b0;
end
// 赋值data
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
data <= 8'd0;
else if(data_flag == 1'b1)
data <= rx_data;
else
data <= data;
end
// 赋值flag
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
flag <= 1'b0;
else
flag <= data_flag;
end
endmodule
易错点
如果数据是多位的,赋值时需要注意其位宽大小。如果一个多位宽的数据赋值为“1’b0”,这将只赋值给这个数据的最低位,其他位将保留原值,与本意相悖。
Testbench 代码
`timescale 1ns / 1ps
module tb_RS232_RX();
reg clk;
reg rst;
reg rx;
wire [7:0] data;
wire flag;
// 初始化clk和rst
initial begin
clk <= 1'b0;
rst <= 1'b1;
#60;
rst <= 1'b0;
end
always #10 clk = ~clk;
// 初始化rx
initial begin
rx <= 1'b1;
#100;
rx_bit(8'b1001_0001);
rx_bit(8'b0101_0010);
rx_bit(8'b1110_0101);
rx_bit(8'b0110_1000);
rx_bit(8'b0110_1011);
rx_bit(8'b0011_0110);
rx_bit(8'b0001_1110);
end
// rx_bit函数,生成串行数据
task rx_bit(
input [7:0] rx_data
);
integer i;
for(i = 0; i < 10; i = i + 1) begin
case(i)
0: rx <= 1'b0;
1: rx <= rx_data[0];
2: rx <= rx_data[1];
3: rx <= rx_data[2];
4: rx <= rx_data[3];
5: rx <= rx_data[4];
6: rx <= rx_data[5];
7: rx <= rx_data[6];
8: rx <= rx_data[7];
9: rx <= 1'b1;
endcase
#(5208*20);
end
endtask
// 实例化模块
RS232_RX #(
.UART_BPS(9600),
.CLK_FREQ(50_000_000)
)tb_RS232_RX
(
.clk (clk ),
.rst (rst ),
.rx (rx ),
.data (data ),
.flag (flag )
);
endmodule
仿真
RS232发送模块
模块框图
输入输出描述:
- 时钟:50MHz,每10ns翻转一次
- 复位:高电平异步复位
- DATA:待发送的并行数据
- FLAG:数据标志位
- TX:输出处理后的串行数据
时序波形
示意时序图,以9600波特率为例。
时序图分析:
- WORK_EN:WORK_EN 通过判断 START_FLAG 的状态而置高,通过判断 BIT_CNT、BIT_FLAG 的状态而置低,其余时刻保持不变。
- BAUD_CNT、BIT_FLAG
- BAUD_CNT:BAUD_CNT 只在 WORK_EN 为高时计数
- BIT_FLAG:在 BAUD_CNT 计数为1时刻拉高
RTL 代码
module RS232_TX
#(
parameter UART_BPS = 'd9600,
parameter CLK_FREQ = 'd50_000_000
)
(
input wire clk,
input wire rst,
input wire [7:0] data,
input wire flag,
output reg tx
);
parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;
reg work_en;
reg [16:0] baud_cnt;
reg bit_flag;
reg [3:0] bit_cnt;
// 赋值work_en
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
work_en <= 1'b0;
else if(flag == 1'b1)
work_en <= 1'b1;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd10))
work_en <= 1'b0;
else
work_en <= work_en;
end
// 赋值baud_cnt
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
baud_cnt <= 17'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1'b1) || (work_en == 1'b0))
baud_cnt <= 17'b0;
else
baud_cnt <= baud_cnt + 1'b1;
end
// 赋值bit_flag
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
bit_flag <= 1'b0;
else if(baud_cnt == 17'b1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end
// 赋值bit_cnt
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
bit_cnt <= 4'b0;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd10))
bit_cnt <= 4'b0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
// 赋值tx
always@(posedge clk or posedge rst) begin
if(rst == 1'b1)
tx <= 1'b1;
else begin
case(bit_cnt)
4'd1: tx <= 1'b0;
4'd2: tx <= data[0];
4'd3: tx <= data[1];
4'd4: tx <= data[2];
4'd5: tx <= data[3];
4'd6: tx <= data[4];
4'd7: tx <= data[5];
4'd8: tx <= data[6];
4'd9: tx <= data[7];
4'd10: tx <= 1'b1;
default: tx <= 1'b1;
endcase
end
end
endmodule
Testbench 代码
`timescale 1ns / 1ps
module tb_RS232_TX();
reg clk;
reg rst;
reg [7:0] data;
reg flag;
wire tx;
// 初始化clk和rst
initial begin
clk <= 1'b0;
rst <= 1'b1;
#60;
rst <= 1'b0;
end
always #10 clk = ~clk;
// 初始化data和flag
initial begin
data <= 8'd0;
flag <= 1'd0;
#200;
// 数据1
data <= 8'b0010_1011;
flag <= 1'd1;
#20;
flag <= 1'd0;
#(5208*12*20);
// 数据2
data <= 8'b0111_0101;
flag <= 1'd1;
#20;
flag <= 1'd0;
#(5208*12*20);
// 数据3
data <= 8'b1010_0001;
flag <= 1'd1;
#20;
flag <= 1'd0;
#(5208*12*20);
end
// 实例化模块
RS232_TX #(
.UART_BPS(9600),
.CLK_FREQ(50_000_000)
)tb_RS232_TX
(
.clk (clk),
.rst (rst),
.data (data),
.flag (flag),
.tx (tx)
);
endmodule