前言
UART(通用异步收发传输器)是一种串行通信协议,用于在电子设备之间进行数据传输。RS232是UART协议的一种常见实现标准,广泛应用于计算机和外围设备之间的通信。它定义了串行数据的传输格式和电气特性,以确保不同设备之间的兼容性和可靠性。RS232协议采用异步串行通信方式,通过数据线将并行数据转换为串行数据进行传输,接收端再将串行数据恢复为并行数据。RS232的标准包括信号电平、连接器类型和数据格式等规范,确保了设备之间的正确通信。它的常见应用包括计算机串口、调制解调器、打印机等设备的连接。
在实际应用中,实现RS232通信需要配置合适的硬件和软件,并进行详细的验证测试。硬件方面,通常需要RS232接口电路和相应的连接线;软件方面,则需要配置数据传输参数(如波特率、数据位、停止位、校验位)和进行通信协议的编程。验证过程包括测试通信的稳定性、数据传输的准确性以及处理异常情况的能力。
本篇内容将探讨RS232通信的实现方法和验证步骤,旨在提供对UART通信协议的深入理解和实践经验,以支持在实际应用中的有效部署和故障排除。
正文
一、UART通信实现与验证(RS232)
1.项目需求
基于FPGA,设计验证UART通信的RS232接口数据收发实验。
2.技术介绍
常用的三种低速数据总线:UART,SPI,IIC,
UART 通用异步收发传输器,全双工通信,UART是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。包括RS232、 RS499、 RS423、 RS422和RS485等接口标准规范和总线标准规范。
SPI 串行外围接口,是一种用于串行数据传输的协议,用于微控制器和外围设备之间的通信。是一种同步、全双工的串行通信协议,主要用于微控制器和外围设备之间的数据传输。它通过四条主要信号线(MOSI、MISO、SCK和SS)实现主从设备之间的通信,允许在全双工模式下进行高效的数据交换。SPI协议支持灵活的时钟极性和相位设置,广泛应用于各种电子设备中。
IIC 是一种双线制的串行通信协议,通过数据线(SDA)和时钟线(SCL)实现主从设备之间的数据交换。它支持多个主设备和从设备,采用地址分配来选择从设备,适用于短距离、低速的数据传输,广泛应用于各种电子设备中。
RS232传输距离近,速度慢,但是电子设备普遍留有该接口方便通信,使用两根信号线进行通信,节省资源。
PC机通过串口调试助手向FPGA发送8Bit数据,数据串行发送(从低到高),在FPGA内部拼凑为8Bit数据,FPGA通过串口串行发送数据给串口(从低到高),完成串口通信。基于帧结构(数据包),起始位+8Bit数据+停止位;完成一个帧结构(一包数据)(10bit),tx与rx在空闲状态下拉高。(串口每次只发送1Bit数据)
这里介绍几个知识点,本实验会遇到与之相关问题:
亚稳态:实际信号跳变不是立刻跳变,有个渐变过程,在时钟信号下进行检测,如果时钟检测时刻刚好在这个渐变过程,会产生毛刺或震荡,在下个检测时钟到来之前,震荡结束后信号01不定.消除方法:使用多级寄存器(打拍)。
波特率:码元(携带数据系统的信号单元,在UART RS232中表示一个二进制数)传输速率即波特率,单位bps。
波特 :串口接收或发送1bit数据的时间,例9600bps对应1/9600s
比特率:每秒中通信信道传输的信息量,位传输速率简称比特率,单位bit/s。
比特率=波特率*每个调制状态对应的二进制数
例:9600bps的UART RS232对应比特率=9600bps*1=9600bit/s
单比特信号从高速时钟域同步到低速时钟域,使用打拍方式(计数器)会有数据漏采,常使用脉冲同步或握手信号进行数据同步,多比特数据进行传输时先要进行格雷码转换,或使用fifo或ram进行数据同步。
3.顶层架构
4.端口描述
clk | 时钟接口(50Mhz) |
rst_n | 复位按键(低电平有效) |
rx | 数据接收接口 |
tx | 数据发送接口 |
二、代码验证
uart_rx模块:数据接收模块,进行数据读取和转化
module uart_rx(
input clk ,
input rst_n ,
input rx ,
output reg[7:0]po_data , //接收到的数据
output reg po_flag //数据输出有效
);
parameter uart_btl ='d9600 ;//串口波特率
parameter clk_shuj ='d50_000_000 ;//时钟频率
parameter cnt_max =clk_shuj/uart_btl;
reg reg1,reg2,reg3 ;//打排延迟周期,消除亚稳态
reg flag ;//uart工作信号(uart工作时在工作状态切换后产生一个时钟周期高电平)
reg en ;//uart工作使能标志信号(uart工作时在工作状态切换后的下一个时钟周期开始一直拉高,直到检测到停止信号)
reg [15:0] cnt ;//每比特数据持续时钟周期计数器
reg [3 :0] bit_cnt ;//数据个数计数器
reg bit_flag ;//每bit数据接收有效信号
reg [7 :0] rx_data ;//存储输入数据
reg rx_flag ;//输入数据并位结束信号
always@(posedge clk,negedge rst_n)//数据打排1
begin
if(rst_n == 0)
reg1 <= 1'b1;
else
reg1 <= rx;
end
always@(posedge clk,negedge rst_n)//数据打排2
begin
if(rst_n == 0)
reg2 <= 1'b1;
else
reg2 <= reg1;
end
always@(posedge clk,negedge rst_n)//数据打排3
begin
if(rst_n == 0)
reg3 <= 1'b1;
else
reg3 <= reg2;
end
always@(posedge clk,negedge rst_n)//uart工作信号赋值
begin
if(rst_n == 0)
flag <= 1'b0;
else
if((reg2 == 1'b0)&&(reg3 == 1'b1)&&(en == 1'b0))
flag <= 1'b1;
else
flag <= 1'b0;
end
always@(posedge clk,negedge rst_n)//uart工作使能标志信号赋值
begin
if(rst_n == 0)
en <= 1'b0;
else
if(flag == 1'b1)
en <= 1'b1;
else
if((bit_cnt == 4'd8)&&(bit_flag == 1'b1))
en <= 1'b0;
else
en <= en;
end
always@(posedge clk,negedge rst_n)//每比特数据持续时钟周期计数器驱动逻辑
begin
if(rst_n == 0)
cnt <= 16'd0;
else
if((cnt == cnt_max - 1)||(en == 1'b0))
cnt <= 16'd0;
else
cnt = cnt + 1'b1;
end
always@(posedge clk,negedge rst_n)//每比特数据持续时钟周期计数器驱动逻辑
begin
if(rst_n == 0)
bit_flag <= 1'b0;
else
if(cnt == cnt_max/2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end
always@(posedge clk,negedge rst_n)//数据个数计数器驱动逻辑
begin
if(rst_n == 0)
bit_cnt <= 4'd0;
else
if((bit_cnt == 4'd8)&&(bit_flag == 1'b1))
bit_cnt <= 4'd0;
else
if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
always@(posedge clk,negedge rst_n)//接收数据并位
begin
if(rst_n == 0)
rx_data <= 8'd0;
else
if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
rx_data <= {reg3,rx_data[7:1]};
end
always@(posedge clk,negedge rst_n)//输入数据并位结束信号
begin
if(rst_n == 0)
rx_flag <= 1'b0;
else
if((bit_cnt == 4'd8)&&(bit_flag == 1'b1))
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
end
always@(posedge clk,negedge rst_n)//输出数据传递
begin
if(rst_n == 0)
po_data <= 8'd0;
else
if(rx_flag == 1'b1)
po_data <= rx_data;
else
po_data <= po_data;
end
always@(posedge clk,negedge rst_n)//输出使能
begin
if(rst_n == 0)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
end
endmodule
uart_tx模块:数据传出模块
module uart_tx(
input clk ,
input rst_n ,
input [7:0] pi_data ,
input pi_flag ,
output reg tx
);
parameter uart_btl ='d9600 ;//串口波特率
parameter clk_shuj ='d50_000_000 ;//时钟频率
parameter cnt_max =clk_shuj/uart_btl;
reg en ;
reg [15:0] cnt ;//每bit数据传输完成计数器
reg flag ;//
reg [3 :0] bit_cnt ;//bit计数器
always@(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
en <= 1'b0;
else
if(pi_flag == 1'b1)
en <= 1'b1;
else
if((bit_cnt == 4'd9)&&(flag == 1'b1))
en <= 1'b0;
else
en <= en;
end
always@(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
cnt <= 16'd0;
else
if((en == 1'b0)||(cnt == cnt_max - 1'b1))
cnt <= 16'd0;
else
if(en == 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= cnt;
end
always@(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
flag <= 1'b0;
else
if(cnt == 16'd1)
flag <= 1'b1;
else
flag <= 1'b0;
end
always@(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
bit_cnt <= 4'd0;
else
if((bit_cnt == 4'd9)&&(flag == 1'b1))
bit_cnt <= 4'd0;
else
if((en == 1'b1)&&(flag == 1'b1))
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
always@(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
tx <= 1'b1;
else
if(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
end
endmodule
顶层连线
module uart_rs232(
input clk ,
input rst_n ,
input rx ,
output tx
);
wire [7:0] pi_data ;
wire pi_flag ;
uart_tx uart_tx_inst(
.clk (clk ) ,
.rst_n (rst_n ) ,
.pi_data(pi_data) ,
.pi_flag(pi_flag) ,
.tx (tx )
);
uart_rx uart_rx_inst(
.clk (clk ),
.rst_n (rst_n ),
.rx (rx ),
.po_data (pi_data ), //接收到的数据
.po_flag (pi_flag )//数据输出有效
);
endmodule
仿真代码
`timescale 1ns/1ps
module uart_rs232_tb;
reg clk ;
reg rst_n ;
reg rx ;
wire tx ;
uart_rs232 uart_rs232_inst(
.clk (clk ),
.rst_n (rst_n ),
.rx (rx ),
.tx (tx )
);
initial clk = 1'b1;
always #10 clk = ~clk;
initial begin
rst_n = 1'b0;
rx = 1'b1;
#40
rst_n = 1'b1;
#200
re_byte();
#1000000
$stop;
end
//赋值函数
task rx_bit(
input [7:0]data
);
integer i;
for(i = 0;i < 10; i = i + 1)//循环9次
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
task re_byte();
integer j;
for(j = 0;j < 8;j = j + 1)
rx_bit(j);//传递0——7,8个数据
endtask
endmodule
三、仿真验证
运行仿真,对信号进行分组,调出中间变量进行分析:
如上图,数据可以正常传出,放大波形图进行数据验证:传入数据01234567,第一个flag信号(接收数据有效信号)即data[0],下图中黄线位置,
具体数据可以观察下图位置,选中flag,选中如图图标。
第一个flag信号(接收数据有效信号)即data[0]=0,上图中黄线位置.第2个flag信号(接收数据有效信号)即data[1]=1,下图中黄线位置。
第八个flag信号(接收数据有效信号)即data[7]=7下图中黄线位置
可以看到数据传入正常,验证数据输出,可以在仿真文件中调用两个顶层文件,在代码基础上将tx接入代第二个顶层代码的rx位置,观察在第二个中数据是否正确被解析。这里不做验证。