文章目录
文中如果有不正确或者不恰当之处,欢迎指正。感谢!
1. RS232 通信接口标准
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)是一种异步收发传输器,其在数据发送时将并行数据转换成串行数据来传输,在数据接收时将接收到的串行数据转换成并行数据,可以实现全双工传输和接收。它包括了 RS232、RS449、RS423、RS422 和 RS485 等接口标准规范和总线标准规范。换句话说,UART 是异步串行通信的总称。而 RS232、RS449、RS423、RS422 和 RS485 等,是对应各种异步串行通信口的接口标准和总线标准,它们规定了通信口的电气特性、传输速率、连接特性和接口的机械特性等内容。
本文重点阐述RS-232 串行数据通信的接口标准,及其使用Verilog HDL语言实现。RS-232的DB9 接口的针脚定义如下图所示:
引脚 | 简写 | 功能 |
---|---|---|
Pin1 | DCD | 调制解调器通知电脑有载波被侦测到 |
Pin2 | RXD | 接收数据 |
Pin3 | TXD | 发送数据 |
Pin4 | DTR | 电脑告诉调制解调器可以进行传输 |
Pin5 | GND | 地线 |
Pin6 | DSR | 调制解调器告诉电脑一切准备就绪 |
Pin7 | RTS | 电脑要求调制解调器将数据提交; |
Pin8 | CTS | 调制解调器通知电脑可以传数据过来; |
Pin9 | R1 | 调制解调器通知电脑有电话进来。 |
2. UART 关键参数及时序图
UART 通信在使用前需要做多项设置,最常见的设置包括数据位数、波特率大小、奇偶校验类型和停止位数。
关键参数 | 时序 |
---|---|
数据位(Data bits) | 该参数定义单个 UART 数据传输在开始到停止期间发送的数据位数。可选择为:5、6、7 或者 8(默认) |
波特率(Baud) | 是指从一设备发到另一设备的波特率,即每秒钟可以通信的数据比特个数。典型的波特率有 300, 1200, 2400, 9600, 19200, 115200 等。一般通信两端设备都要设为相同的波特率,但有些设备也可设置为自动检测波特率。 |
奇偶校验类型(Parity Type) | 是用来验证数据的正确性。 |
停止位(Stop bits) | 在每个字节的数据位发送完成之后,发送停止位,来标志着一次数据传输完成,同时用来帮助接受信号方硬件重同步。可选择为:1(默认)、1.5 或者 2 位。 |
按照一个完整的字节包括一位起始位、8 位数据位、一位停止位即总共十位数据来算, 要想完整的实现这十位数据的发送,就需要 11 个波特率时钟脉冲,第 1 个脉冲标记一次传输的起始,第 11 个脉冲标记一次传输的结束,如下图所示:
BPS_CLK 信号的第一个上升沿到来时,字节发送模块开始发送起始位,接下来的 2 到 9个上升沿,发送 8 个数据位,第 10 个上升沿到第 11 个上升沿为停止位的发送。
3. UART 异步串行通信发送模块设计与实现
基于上述原理,本文要实现的串口发送模块整体框图,如下图所示,其接口下表所示。
接口 | 功能 |
---|---|
Send_En | 发送使能信号 |
Data_Byte | 待传输 8bit 数 |
Baud_Set | 波特率设置信号 |
Clk | 系统时钟 |
Rst_n | 系统复位信号 |
Rs232_Tx | Rs232 信号输出 |
Tx_down | 发送结束信号,一个时钟周期高电平 |
UART_state | 发送状态,处于发送状态时为 1 |
在uart模块中通常包括五部分
1.波特率设置模块
2.波特率时钟产生模块
3.波特率计数模块
4.数据发送模块
5.控制信号模块
3.1 波特率设置模块
波特率是 UART 通信中需要设置的参数之一。需要波特率周期的计数值bps_DR计算的计算公式如下
"
sclk为系统时钟的频率;bps为波特率
例如波特率为9600的计数值:bps_DR=50000000/9600=5208
波特率 | 50M 系统时钟计数值 |
---|---|
9600 | 5208 |
19200 | 2604 |
38400 | 1302 |
57600 | 868 |
115200 | 434 |
波特率设置模块如下图所示:
3.1.1 Verilog代码实现
reg [15:0] bps_DR;//分频计数最大值
always@ (posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_DR<=16'd5208;
else
begin
case(Baud_Set)
3'b000: bps_DR<=16'd5208;
3'b001: bps_DR<=16'd2604;
3'b010: bps_DR<=16'd1302;
3'b011: bps_DR<=16'd868;
3'b100: bps_DR<=16'd434;
default: bps_DR<=16'd5208;
endcase
end
3.2 波特率时钟产生模块
该模块用于产生分频时钟,分频时钟的高电平为1个系统周期,低电平为(bps_DR-1)系统周期。
3.2.1 波特率时钟Verilog代码实现
reg [15:0] Div_Cnt;
reg en_cnt;
reg bps_clk;
//分频计数
always@ (posedge Clk or negedge Rst_n)
if(!Rst_n)
Div_Cnt<=16'd0;
else
if(en_cnt)
begin
if(Div_Cnt==bps_DR)
Div_Cnt<=16'd0;
else
Div_Cnt<=Div_Cnt+1'b1;
end
else
Div_Cnt<=16'd0;
//分频时钟
always@ (posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_clk<=1'b0;
else
if(Div_Cnt==16'd1)
bps_clk<=1'b1;
else
bps_clk<=1'b0;
3.3 波特率计数模块
对产生的波特率时钟进行进行计数。波特率计数模块模块的结构图如下:
3.3.1 波特率计数模块verilog代码
reg [3:0] bps_cnt_q;
reg clr;
always@ (posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_cnt_q<=4'd0;
else
if(clr)
bps_cnt_q<=4'd0;
else
if(bps_clk)
bps_cnt_q<=bps_cnt_q+1'b1;
else
bps_cnt_q<=bps_cnt_q;
3.4 数据发送模块
数据发送模块相等于一个并行数据转化为串行数据。此处采用状态机控制,状态图如下:
3.4.1数据发送模块Verilog代码实现
localparam IDLE=5'b00000;
localparam STAET_BIT=5'b00001;
localparam B0=5'b00011;
localparam B1=5'b00010;
localparam B2=5'b00110;
localparam B3=5'b00111;
localparam B4=5'b00101;
localparam B5=5'b00100;
localparam B6=5'b01100;
localparam B7=5'b01101;
localparam STOP_BIT=5'b01111;
reg [5:0] state;
reg Rx232_Tx;
reg [7:0] r_date_byte;
reg Tx_down;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
state<=IDLE;
else
case(state)
IDLE: if(bps_cnt_q==4'd1)
state<=STAET_BIT;
STAET_BIT: if(bps_cnt_q==4'd2)
state<=B1;
B1: if(bps_cnt_q==4'd3)
state<=B2;
B2: if(bps_cnt_q==4'd4)
state<=B3;
B3: if(bps_cnt_q==4'd5)
state<=B4;
B4: if(bps_cnt_q==4'd6)
state<=B5;
B5: if(bps_cnt_q==4'd7)
state<=B6;
B6: if(bps_cnt_q==4'd8)
state<=B7;
B7: if(bps_cnt_q==4'd9)
state<=B8;
B8: if(bps_cnt_q==4'd10)
state<=STOP_BIT;
STOP_BIT: if(bps_cnt_q==4'd11)
state<=IDLE;
default:state<=IDLE;
endcase
always@(posedge Clk or negedge Rst_n)
if(Rst_n)
Rx232_Tx<=1'd1;
else
if(State==IDLE)
Rx232_Tx<=1'd1;
else
if(state==STAET_BIT && bps_cnt_q==4'd1)
Rx232_Tx<=1'd0;
else
if(state==B1 && bps_cnt_q==4'd2)
RX232_Tx<=r_date_byte[0];
else
if(state==B2 && bps_cnt_q==4'd3)
RX232_Tx<=r_date_byte[1];
else
if(state==B3 && bps_cnt_q==4'd4)
RX232_Tx<=r_date_byte[2];
else
if(state==B4 && bps_cnt_q==4'd5)
RX232_Tx<=r_date_byte[3];
else
if(state==B5 && bps_cnt_q==4'd6)
RX232_Tx<=r_date_byte[4];
else
if(state==B6 && bps_cnt_q==4'd7)
RX232_Tx<=r_date_byte[5];
else
if(state==B7 && bps_cnt_q==4'd8)
RX232_Tx<=r_date_byte[6];
else
if(state==B8 && bps_cnt_q==4'd9)
RX232_Tx<=r_date_byte[7];
else
if(state==STOP_BIT && bps_cnt_q==4'd10)
RX232_Tx<=1'B1;
else
if(state==IDLE && bps_cnt_q==4'd11)
RX232_Tx<=1'B1;
always@(posedge Clk or negedge Rst_n)
if(Rst_n)
Tx_down<=1'd0;
else
if(state==STOP_BIT && bps_cnt_q==4'd10)
Tx_down<=1'd1;
else
Tx_down<=1'd0;
3.5 控制信号模块
控制信号是当Send_En给一个高脉冲,UART模块中可以产生一个使UART模块工作的使能信号。结构图如下图所示:
3.5.1 控制信号模块Verilog代码实现
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
UART_state<=1'B0;
else
if(Send_En)
UART_state<=1'B1;
else
if(Tx_down)
UART_state<=1'B0;
else
UART_state<=UART_state;
4. Verilog实现UART模块
以下代码已经在FPGA上硬件验证过
module uart_tx(
input wire Clk,
input wire Rst_n,
input wire [2:0] Baud_Set,
input wire Send_En,
input wire [7:0] date_byte,
output reg RX232_Tx,
output reg Tx_down,
output reg UART_state
);
localparam IDLE=5'b00000;
localparam STAET_BIT=5'b00001;
localparam B1=5'b00011;
localparam B2=5'b00010;
localparam B3=5'b00110;
localparam B4=5'b00111;
localparam B5=5'b00101;
localparam B6=5'b00100;
localparam B7=5'b01100;
localparam B8=5'b01101;
localparam STOP_BIT=5'b01111;
reg [5:0] state;
reg [7:0] r_date_byte;
reg [15:0] bps_DR;//分频计数最大值
reg [15:0] Div_Cnt;
reg bps_clk;
reg [3:0] bps_cnt_q;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
UART_state<=1'B0;
else
if(Send_En)
UART_state<=1'B1;
else
if(Tx_down)
UART_state<=1'B0;
else
UART_state<=UART_state;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Tx_down<=1'd0;
else
if(state==STOP_BIT && bps_cnt_q==4'd11)
Tx_down<=1'd1;
else
Tx_down<=1'd0;
always@ (posedge Clk or negedge Rst_n)
if(!Rst_n)
r_date_byte<=8'd0;
else
if(UART_state)
r_date_byte<=date_byte;
else
r_date_byte<=r_date_byte;
always@ (posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_DR<=16'd5208;
else
begin
case(Baud_Set)
3'b000: bps_DR<=16'd5208;
3'b001: bps_DR<=16'd2604;
3'b010: bps_DR<=16'd1302;
3'b011: bps_DR<=16'd868;
3'b100: bps_DR<=16'd434;
default: bps_DR<=16'd5208;
endcase
end
//分频计数
always@ (posedge Clk or negedge Rst_n)
if(!Rst_n)
Div_Cnt<=16'd0;
else
if(UART_state)
begin
if(Div_Cnt==bps_DR)
Div_Cnt<=16'd0;
else
Div_Cnt<=Div_Cnt+1'b1;
end
else
Div_Cnt<=16'd0;
//产生分频时钟
always@ (posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_clk<=1'b0;
else
if(Div_Cnt==16'd1)
bps_clk<=1'b1;
else
bps_clk<=1'b0;
//波特率计数
always@ (posedge Clk or negedge Rst_n)
if(!Rst_n)
bps_cnt_q<=4'd0;
else
if(Tx_down)
bps_cnt_q<=4'd0;
else
if(bps_clk)
bps_cnt_q<=bps_cnt_q+1'b1;
else
bps_cnt_q<=bps_cnt_q;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
state<=IDLE;
else
case(state)
IDLE: if(bps_cnt_q==4'd1)
state<=STAET_BIT;
STAET_BIT: if(bps_cnt_q==4'd2)
state<=B1;
B1: if(bps_cnt_q==4'd3)
state<=B2;
B2: if(bps_cnt_q==4'd4)
state<=B3;
B3: if(bps_cnt_q==4'd5)
state<=B4;
B4: if(bps_cnt_q==4'd6)
state<=B5;
B5: if(bps_cnt_q==4'd7)
state<=B6;
B6: if(bps_cnt_q==4'd8)
state<=B7;
B7: if(bps_cnt_q==4'd9)
state<=B8;
B8: if(bps_cnt_q==4'd10)
state<=STOP_BIT;
STOP_BIT: if(bps_cnt_q==4'd11)
state<=IDLE;
default:state<=IDLE;
endcase
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
RX232_Tx<=1'd1;
else
if(state==IDLE)
RX232_Tx<=1'd1;
else
if(state==STAET_BIT && bps_cnt_q==4'd1)
RX232_Tx<=1'd0;
else
if(state==B1 && bps_cnt_q==4'd2)
RX232_Tx<=r_date_byte[0];
else
if(state==B2 && bps_cnt_q==4'd3)
RX232_Tx<=r_date_byte[1];
else
if(state==B3 && bps_cnt_q==4'd4)
RX232_Tx<=r_date_byte[2];
else
if(state==B4 && bps_cnt_q==4'd5)
RX232_Tx<=r_date_byte[3];
else
if(state==B5 && bps_cnt_q==4'd6)
RX232_Tx<=r_date_byte[4];
else
if(state==B6 && bps_cnt_q==4'd7)
RX232_Tx<=r_date_byte[5];
else
if(state==B7 && bps_cnt_q==4'd8)
RX232_Tx<=r_date_byte[6];
else
if(state==B8 && bps_cnt_q==4'd9)
RX232_Tx<=r_date_byte[7];
else
if(state==STOP_BIT && bps_cnt_q==4'd10)
RX232_Tx<=1'B1;
else
if(state==IDLE && bps_cnt_q==4'd11)
RX232_Tx<=1'B1;
endmodule
5. testbench
`timescale 1ns/1ps
`define Clk_period 20
module uart_tx_tb();
reg Clk ;
reg Rst_n ;
reg [2:0] Baud_Set ;
reg Send_En ;
reg [7:0] date_byte ;
wire RX232_Tx ;
wire Tx_down ;
wire UART_state ;
uart_tx U1(
.Clk(Clk),
.Rst_n(Rst_n),
.Baud_Set(Baud_Set),
.Send_En(Send_En),
.date_byte(date_byte),
.RX232_Tx(RX232_Tx),
.Tx_down(Tx_down),
.UART_state(UART_state)
);
initial Clk=1;
always #(`Clk_period/2) Clk=~Clk;
initial
begin
Rst_n=0;
Send_En=0;
Baud_Set=3'b000;
date_byte=8'haa;
#(`Clk_period*20+1);
Rst_n=1;
Send_En=1;
#(`Clk_period);
Send_En=0;
#(`Clk_period*100000);
Send_En=1;
date_byte=8'h55;
#(`Clk_period);
Send_En=0;
#(`Clk_period*100000);
$stop;
end
endmodule
6.仿真结果
https://www.bilibili.com/video/BV1oE411Y7uv?p=12 小梅哥教学视频_UART串口发送
https://www.bilibili.com/video/BV1j441117a9?p=4 尤恺元教学视频_状态机