概述
串口通信之RS232
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作 UART。UART 是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。它包括了 RS232、RS499、RS423、RS422 和 RS485 等接口标准规范和总线标准规范。
本博客记录串口 RS232 的学习经验,基于 RS232 的串口收、发功能模块,并完成串口数据回环实验。
文章目录
- 串口通信之RS232
- 一、理论学习
- 1.串口简介
- 2.串口(RS232)优势
- 3.RS232信号线
- 4.RS232通信协议简介
- 二、串口接收模块(rx)
- 1.模块框图
- 2.波形设计
- 3.设计代码
- 4.仿真代码
- 5.仿真结果
- 三、串口发送模块(tx)
- 1.模块框图
- 2.仿真波形
- 3.设计代码
- 4.仿真代码
- 5.仿真结果
- 四、回环测试(loopback)
- 1.顶层代码
- 2.仿真代码
- 3.仿真波形
一、理论学习
1.串口简介
串口作为常用的三大低速总线(UART、SPI、IIC)之一,在设计众多通信接口和调试时占有重要地位。但 UART 和 SPI、IIC 不同的是,它是异步通信接口,异步通信中的接收方并不知道数据什么时候会到达,所以双方收发端都要有各自的时钟,在数据传输过程中是不需要时钟的,发送方发送的时间间隔可以不均匀,接受方是在数据的起始位和停止位的帮助下实现信息同步的。而 SPI、IIC 是同步通信接口(后面的章节会做详细介绍),同步通信中双方使用频率一致的时钟,在数据传输过程中时钟伴随着数据一起传输,发送方和接收方使用的时钟都是由主机提供的。
UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫 rx(Receiver),如下图所示,对于 PC 来说它的 tx 要和对于 FPGA 来说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接,如果是两个 tx 或者两个 rx 连接那数据就不能正常被发送出去和接收到,所以不要弄混,记住 rx 和 tx 都是相对自身主体来讲的。UART 可以实现全双工,即可以同时进行发送数据和接收数据。
2.串口(RS232)优势
串口 RS232 传输数据的距离虽然不远,传输速率也相对较慢,但是串口依然被广泛的用于电路系统的设计中,串口的好处主要表现在以下几个方面:
1、很多传感器芯片或 CPU 都带有串口功能,目的是在使用一些传感器或 CPU 时可以通过串口进行调试,十分方便;
2、在较为复杂的高速数据接口和数据链路集合的系统中往往联合调试比较困难,可以先使用串口将数据链路部分验证后,再把串口换成高速数据接口。如在做以太网相关的项目时,可以在调试时先使用串口把整个数据链路调通,然后再把串口换成以太网的接口;
3、串口的数据线一共就两根,也没有时钟线,节省了大量的管脚资源。
3.RS232信号线
在最初的应用中,RS-232 串口标准常用于计算机、路由与调制调解器(MODEN,俗称“猫”)之间的通讯,在这种通讯系统中,设备被分为数据终端设备 DTE(计算机、路由)和数据通讯设备 DCE(调制调解器)。在旧式的台式计算机中一般会有 RS-232 标准的 COM 口(也称 DB9 接口),见下图 。
其中接线口以针式引出信号线的称为公头,以孔式引出信号线的称为母头。在计算机中一般引出公头接口,而在调制调解器设备中引出的一般为母头,使用上图中的串口线即可把它与计算机连接起来。通讯时,串口线中传输的信号使用 RS-232 标准调制。在各种应用场合下, DB9 接口中的公头及母头的各个引脚的标准信号线接法见下图 。
串口线中的 RTS、CTS、DSR、DTR 及 DCD 信号,使用逻辑 1 表示信号有效,逻辑 0表示信号无效。例如,当计算机端控制 DTR 信号线表示为逻辑 1 时,它是为了告知远端的调制调解器,本机已准备好接收数据, 0 则表示还没准备就绪。但在目前的其它工业控制使用的串口通讯中,一般只使用 RXD、TXD 以及 GND 三条信号线,直接传输数据信号,所以我们只需关注这几个信号线。
4.RS232通信协议简介
1.帧结构:串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除了中间包含 8bit 有效数据外,还在每一帧的开头都必须有一个起始位,且固定为 0;在每一帧的结束时也必须有一个停止位,且固定为 1,即最基本的帧结构(不包括校验等)有10bit。在不发送或者不接收数据的情况下,rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输。如下图所示为一个最基本的 RS232 帧结构。
2.波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、9600、115200 等,本文中我们选用9600的波特率。
3.比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为“每秒比特数(bps)”。比特率可由波特率计算得出,**公式为:比特率=波特率 * 单个调制状态对应的二进制位数。**如果使用的是 9600 的波特率,其串口的比特率为:9600Bps * 1bit= 9600bps。
4.由计算得串口发送或者接收 1bit 数据的时间为一个波特,即 1/9600 秒,如果用50MHz(周期为 20ns)的系统时钟来计数,需要计数的个数为 cnt = (1s * 10^9)ns / 9600bit)ns / 20ns ≈ 5208 个系统时钟周期,即每个 bit 数据之间的间隔要在 50MHz 的时钟频率下计数 5208 次。
二、串口接收模块(rx)
串口接收模块的功能是接收通过 PC 机上的串口调试助手发送的固定波特率的数据,串口接收模块按照串口的协议准确接收串行数据,解析提取有用数据后需将其转化为并行数据,因为并行数据在 FPGA 内部传输的效率更高,转化为并行数据后同时产生一个数据有效信号标志信号伴随着并行的有效数据一同输出。
1.模块框图
输入信号:系统时钟、系统复位、按位输入的串行数据。
输出信号:8位的并行数据、数据有效的标志性信号。
2.波形设计
<1> rx_reg1/rx_reg2 :先打两拍,进行同步,解决跨时钟域的亚稳态问题。
<2> rx_reg3/start_nedge:再打一拍,利用rx_reg2和rx_reg3来产生下降沿信号start_nedge。
<3>work_en: rx 信号本身就是 1bit 的,如果在判断第一个下降沿后,后面帧中的数据还可能会有下降沿出现,那我们会又产生一个start_nedge 标志信号,这样就出现了误判断,所以通过work_en来锁定此区域避免误判。work_en在start_nedge产生后拉高,在bit_flag和bit_cnt=8时拉低。
<4>baud_cnt:计数器,计 5208 个数的计数器依次接收 10 个比特的数据,计数器每计 5208 个数就接收一个新比特的数据。
<5>bit_flag:baud_cnt 计数器在计数值为 0 到 5207 期间都是数据有效的时刻,那我们该什么时候取数据呢?理论上讲,在数据变化的地方取数是不稳定的,所以我们选择当 baud_cnt 计数器计数到 2603,即中间位置时取数最稳定。
<6>bit_cnt:产生一个用于计数该时刻接收的数据是第几个比特的 bit_cnt 计数器。
<7>rx_data/rx_flag:移位和移位完成信号。
<8>po_data/po_flag:在rx_flag的指示下,完成po_data和po_flag。
很重要的知识 |
注: 单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态(如:将慢速时钟域(PC 机中的波特率)系统中的 rx 信号同步到快速时钟域(FPGA 中的 sys_clk)系统中,所使用的方法叫电平同步,俗称“打两拍法”)。第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为 70%~80%左右,第二级寄存器可以稳定输出的概率为 99%左右,后面再多加寄存器的级数改善效果就不明显了,所以数据进来后一般选择打两拍即可。
另外单比特信号从快速时钟域同步到慢速时钟域还仅仅使用打两拍的方式会漏采数据,所以往往使用脉冲同步法或的握手信号法;而多比特信号跨时钟域需要进行格雷码编码(多比特顺序数才可以)后才能进行打两拍的处理,或者通过使用 FIFO、RAM 来处理数据与时钟同步的问题。
亚稳态振荡时间 Tmet 关系到后级寄存器的采集稳定问题,Tmet 影响因素包括:器件的生产工艺、温度、环境以及寄存器采集到亚稳态里稳定态的时刻等。甚至某些特定条件,如干扰、辐射等都会造成 Tmet 增长。
3.设计代码
module uart_rx(
input sys_clk,
input sys_rst_n,
input rx,
output reg [7:0] po_data,
output reg po_flag
);
reg rx_reg1;
reg rx_reg2;
reg rx_reg3;
reg start_nedge;
reg work_en;
reg [12:0] baud_cnt;
reg bit_flag;
reg [3:0] bit_cnt;
reg [7:0] rx_data;
reg rx_flag;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_reg1 <= 0;
else
rx_reg1 <= rx;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_reg2 <= 0;
else
rx_reg2 <= rx_reg1;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_reg3 <= 0;
else
rx_reg3 <= rx_reg2;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
start_nedge <= 0;
else if(work_en)
start_nedge <= 0;
else if((rx_reg2 == 0) && (rx_reg3 == 1))
start_nedge <= 1;
else
start_nedge <= 0;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
work_en <= 0;
else if(start_nedge)
work_en <= 1;
else if((bit_flag) && (bit_cnt == 8))
work_en <= 0;
else
work_en <= work_en;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
baud_cnt <= 0;
else if(work_en)begin
if(baud_cnt >= 5207)
baud_cnt <= 0;
else
baud_cnt <= baud_cnt + 1;
end
else
baud_cnt <= 0;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bit_flag <= 0;
else if(baud_cnt == 2603)
bit_flag <= 1;
else
bit_flag <= 0;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bit_cnt <= 0;
else if(bit_flag) begin
if(bit_cnt == 8)
bit_cnt <= 0;
else
bit_cnt <= bit_cnt + 1;
end
else
bit_cnt <= bit_cnt;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_data <= 8'h00;
else if((bit_cnt >= 1) && (bit_flag))
rx_data <= {rx_reg3, rx_data[7:1]};
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_flag <= 0;
else if((bit_flag) && (bit_cnt == 8))
rx_flag <= 1;
else
rx_flag <= 0;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
po_data <= 8'h00;
else if(rx_flag)
po_data <= rx_data;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
po_flag <= 0;
else if(rx_flag)
po_flag <= 1;
else
po_flag <= 0;
endmodule
4.仿真代码
`timescale 1ns / 1ns
module uart_rx_tb();
reg sys_clk;
reg sys_rst_n;
reg rx;
wire [7:0] po_data;
wire po_flag;
initial sys_clk = 0;
always #10 sys_clk = !sys_clk;
initial begin
sys_rst_n = 0;
rx = 1;
#20;
sys_rst_n = 1;
#200;
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);
#200;
$stop;
end
uart_rx uart_rx_inst(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rx (rx),
.po_data (po_data),
.po_flag (po_flag)
);
task rx_bit(
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
endmodule
5.仿真结果
三、串口发送模块(tx)
1.模块框图
整个模块也必须用到时序逻辑,所以先设计好时钟 sys_clk 和复位 sys_rst_n 两个输入信号,其次是 FPGA 要发送的 8bits 有用数据 pi_data 和伴随数据有效的标志信号 pi_flag。其次是相对于 FPGA 的 tx 端发送至 PC 机中的 1bit 输出信号。
2.仿真波形
大致与接收模块思路相同,需要单独注意的是:
<1>work_en:为baud_cnt计数的使能信号,当检测到数据有效标志信号 pi_flag 为高电平时拉高工作使能信号 work_en;当bit_cnt 计数器计数到 9 且 bit_flag 信号有效时停止位就可以被发送出去了,此时就不再需要 baud_cnt 计数器计数了,就可以把 work_en 信号拉低了
<2>bit_flag/bit_cnt:当baud_cnt 计数器的计数值为 1的时候作为发送数据的点。
3.设计代码
module uart_tx(
input sys_clk,
input sys_rst_n,
input [7:0] pi_data,
input pi_flag,
output reg tx
);
reg work_en;
reg [12:0] baud_cnt;
reg bit_flag;
reg [3:0] bit_cnt;
// work_en PART
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
work_en <= 0;
else if(pi_flag)
work_en <= 1;
else if((bit_flag) && (bit_cnt == 9))
work_en <= 0;
else
work_en <= work_en;
// baud_cnt PART
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
baud_cnt <= 0;
else if(work_en)begin
if(baud_cnt == 5207)
baud_cnt <= 0;
else
baud_cnt <= baud_cnt + 1;
end
else
baud_cnt <= 0;
// bit_flag PART
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bit_flag <= 0;
else if(baud_cnt == 1)
bit_flag <= 1;
else
bit_flag <= 0;
// bit_cnt PART
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bit_cnt <= 0;
else if(bit_flag)begin
if((bit_cnt >= 9) && (bit_flag))
bit_cnt <= 0;
else
bit_cnt <= bit_cnt + 1;
end
//tx PART
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
tx <= 1;
else if (bit_flag)begin
case(bit_cnt)
0:tx <= 0;
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;
endcase
end
else
tx <= tx;
endmodule
4.仿真代码
`timescale 1ns / 1ns
module uart_tx_tb();
reg sys_clk;
reg sys_rst_n;
reg [7:0] pi_data;
reg pi_flag;
wire tx;
uart_tx uart_tx_inst(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.pi_data (pi_data),
.pi_flag (pi_flag),
.tx (tx)
);
initial sys_clk = 0;
always #10 sys_clk = !sys_clk;
initial begin
sys_rst_n = 0;
pi_data = 8'b1111_1111;
pi_flag = 0;
#20;
sys_rst_n = 1;
#200;
//发送数据0~7 中间有一段大延迟进行测试
//发送0
pi_data <= 8'd0;
pi_flag <= 1;
#20;
pi_flag <= 0;
#(5208*20*10 - 20);
//发送1
pi_data <= 8'd1;
pi_flag <= 1;
#20;
pi_flag <= 0;
#(5208*20*10 - 20);
//发送2
pi_data <= 8'd2;
pi_flag <= 1;
#20;
pi_flag <= 0;
#(5208*20*10 - 20);
//发送3
pi_data <= 8'd3;
pi_flag <= 1;
#20;
pi_flag <= 0;
#(5208*20*10 - 20);
//延迟测试
#(5208*20*9*2);
//发送4
pi_data <= 8'd4;
pi_flag <= 1;
#20;
pi_flag <= 0;
#(5208*20*10 - 20);
//发送5
pi_data <= 8'd5;
pi_flag <= 1;
#20;
pi_flag <= 0;
#(5208*20*10 - 20);
//发送6
pi_data <= 8'd6;
pi_flag <= 1;
#20;
pi_flag <= 0;
#(5208*20*10 - 20);
//发送7
pi_data <= 8'd7;
pi_flag <= 1;
#20;
pi_flag <= 0;
#(5208*20*10 - 20);
#2000;
$stop;
end
endmodule
5.仿真结果
四、回环测试(loopback)
我们通过 loopback 测试(回环测试)来验证设计模块的正确性。所谓 loopback 测试就是发送端发送什么数据,接收端就接收什么数据,这也是非常常用的一种测试手段,如果 loopback 测试成功,则说明从数据发送端到数据接收端之间的数据链路是正常的,以此来验证数据链路的畅通。
1.顶层代码
module uart_RS232(
input sys_clk,
input sys_rst_n,
input rx,
output tx
);
wire [7:0] po_data;
wire po_flag;
uart_rx uart_rx_inst(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rx (rx),
.po_data (po_data),
.po_flag (po_flag)
);
uart_tx uart_tx_inst(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.pi_data (po_data),
.pi_flag (po_flag),
.tx (tx)
);
endmodule
2.仿真代码
`timescale 1ns / 1ns
module uart_RS232_tb();
reg sys_clk;
reg sys_rst_n;
reg rx;
wire tx;
uart_RS232 uart_RS232_inst(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rx (rx),
.tx (tx)
);
initial begin
sys_clk <= 1;
sys_rst_n <= 0;
rx <= 1;
#20;
sys_rst_n <= 1;
end
initial begin
#200;
rx_byte();
#2000;
$stop;
end
always #10 sys_clk = !sys_clk;
task rx_byte();
integer j;
for(j=0;j<8;j=j+1)
rx_bit(j);
endtask
task rx_bit(
input [7:0] data
);
integer i;
for(i=0;i<10;i=i+1)begin
case(i)
0: rx <= 0;
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;
endcase
#(5208*20);
end
endtask
endmodule
3.仿真波形
参考文档:野火串口RS232
最后
以上就是爱笑裙子为你收集整理的【边学边记_06】——串口通信之RS232串口通信之RS232的全部内容,希望文章能够帮你解决【边学边记_06】——串口通信之RS232串口通信之RS232所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复