RS232通信协议简介
1、RS232是UART的一种,没有时钟线,只有两根数据线,分别是rx和tx,这两根线都是1bit位宽的。其中rx是接受数据的线,tx是发送数据的线。
2、rx的位宽为1bit,PC机通过串口调试助手往FPGA发送8bit数据时,FPGA通过串口线rx一位一位的接收数据,从最低位到最高位依次接收,最后在FPGA里面位拼接成8bit数据。
3、tx 位宽为 1bit,FPGA 通过串口往 PC 机发 8bit 数据时,FPGA 把 8bit 数据通过 tx 线一位一位的传给 PC 机,从最低位到最高位依次发送,最后上位机通过串口助手按照 RS232 协议把这一位一位的数据位拼接成 8bit 数据。
4、串口数据的发送与接收是基于帧结构的,即一帧一帧的发送与接收数据。每一帧除了中间包含 8bit 有效数据外,还在每一帧的开头都必须有一个起始位,且固定为 0;在每一帧的结束时也必须有一个停止位,且固定为 1,即最基本的帧结构(不包括校验等)有 10bit。在不发送或者不接收数据的情况下,rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持 高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit 的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输。如下图所示 为一个最基本的 RS232 帧结构。
PS:Rx:一共是发送十位数据,但是第一位和第十位分别是0和1,中间八位任意,只需要对中间八位进行传输即可
5、波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进 行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元 的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、9600、115200 等,我们选用 9600 的波特率进行串口的编写与仿真。
6、比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为 “每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 * 单个调制状态对应的二进制位数。如果使用的是 9600 的波特率,其串口的比特率为:9600Bps * 1bit= 9600bps。
7、由计算得串口发送或者接收 1bit 数据的时间为一个波特,即 1/9600 秒,如果用 50MHz(周期为 20ns)的系统时钟来计数,需要计数的个数为 cnt = (1s * 10^9)ns / 9600bit)ns / 20ns ≈ 5208 个系统时钟周期,即每个 bit 数据之间的间隔要在 50MHz 的时钟 频率下计数 5208 次
8、上位机通过串口发 8bit 数据时,会自动在发 8 位有效数据前发一个波特时间的起始位,也会自动在发完 8 位有效数据后发一个停止位。同理,串口助手接收上位机发送的数据前,必须检测到一个波特时间的起始位才能开始接收数据,接收完 8bit 的数据后,再接收一个波特时间的停止位。
程序设计
1、顶层模块框图如下
可以看到,顶层的框图一共包含两个模块,分别是接收和发送模块。
2、接收模块
接收模块的作用是将从PC输入的串行数据转换为并行数据 。
一帧数据一共有10bit,其中起始位和停止位分别为0和1,接收模块只接收中间八位数据,将中间8位的串行数据转化为并行,然后把8位的并行数据输入到发送模块。
接收模块的框图如下图所示:
接收模块波形图如下:
对波形图的一些理解: 接收的Rx信号需要同步到系统时钟sys_clk下,即单比特信号从低速时钟域同步到快速时钟域,通常使用的方法都是打两拍。但为何要同步三次呢?因为start_flag需要提取一个下降沿,而第一个同步的时钟是一个不稳定的信号,所以需要用第二个和第三个同步后的来提取下降沿。
根据波形图写出代码如下:
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 boud_cnt_MAX = clk_freq/uart_bps ;
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg start_flag ;
reg work_en ;
reg [13:0] boud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
reg [7:0] rx_data ;
reg rx_flag ;
//regesiter delay, two clock time
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
rx_reg1<=1'b1;
else
rx_reg1<=rx;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
rx_reg2<=1'b1;
else
rx_reg2<=rx_reg1;
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_flag
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
start_flag<=1'b0;
else if((~rx_reg2)&&(rx_reg3))
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(bit_cnt==4'd8&&bit_flag==1'b1)
work_en<=1'b0;
else if(start_flag==1'b1)
work_en<=1'b1;
else
work_en<=work_en;
//boud_cnt
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
boud_cnt<=14'd0;
else if((boud_cnt==boud_cnt_MAX-1'b1)||(work_en==1'b0))
boud_cnt<=14'd0;
else if(work_en==1'b1)
boud_cnt<=boud_cnt+1'b1;
//bit_flag
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
bit_flag<=1'b0;
else if(boud_cnt==boud_cnt_MAX/2-1'b1)
bit_flag<=1'b1;
else
bit_flag<=1'b0;
//bit_cnt
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
bit_cnt<=1'b0;
else if(bit_cnt==4'd8&&bit_flag==1'b1)
bit_cnt<=1'b0;
else if(bit_flag==1'b1)
bit_cnt<=bit_cnt+1'b1;
else
bit_cnt<=bit_cnt;
//rx_data[7:0]
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
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
rx_flag<=1'b0;
else if(bit_flag==1'b1&&bit_cnt==4'd8)
rx_flag<=1'b1;
else
rx_flag<=1'b0;
//po_data
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
po_data<=8'd0;
else if(rx_flag==1'b1)
po_data<=rx_data;
//po_flag
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
po_flag<=1'b0;
else if(rx_flag==1'b1)
po_flag<=1'b1;
else
po_flag<=1'b0;
endmodule
接收模块仿真代码如下:
`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;
#20
sys_rst_n<=1'b1;
end
always #10 sys_clk=~sys_clk;
initial
begin
#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);
end
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
uart_rx inst
(
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.rx (rx) ,
.po_data (po_data) ,
.po_flag (po_flag)
);
endmodule
Vivado仿真波形图
整体波形如下
对仿真波形图的理解: 在空闲状态下,Rx保持高电平,当检测到低电平时,波特计数器开始计数,当波特计数器计数到最大值的时候,则开始传输数据,一个8bit的数据传输完成后,po_data才开始输出。
3、发送模块
发送模块的作用的接收来自接收模块的八位并行数据,然后将这八位并行数据转换位串行数据,一位一位的发送出去 。
一帧共有10bit,第一位和最后一位分别位0和1,来自接收模块的八位数据在中间。
模块设计
我们将串口接收模块取名为 uart_tx,根据功能简介我们对整个设计要求有了大致的了解,其中设计的关键点是如何将串并行数据转化为串行数据并发送出去,也就是按照顺序将并行数据发送至 PC 机上。FPGA 发送的串行数据同样没有时钟,所以要和 PC 机接约定好使用相同的波特率,一个一个地发送比特,为了后面做串口的回环测试我们仍选择使用 9600bps的波特率
模块的Visio框图如下:
发送模块的波形图如下:
ps:注意发送同样是10bit的数据,当第一个bit_flag=1的时候开始发送起始位0,当bit_flag=9的时候发送停止位1,其余时间Tx保持空闲状态。
根据波形图可以写出如下代码:
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 ,
input wire pi_data_flag ,
output reg tx
);
localparam boud_cnt_max=clk_freq/uart_bps;
reg work_en ;
reg [12:0] boud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
work_en<=1'b0;
else if((bit_cnt==4'd9)&&(bit_flag==1'b1))
work_en<=1'b0;
else if(pi_data_flag==1'b1)
work_en<=1'b1;
else
work_en<=work_en;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
boud_cnt<=13'd0;
else if((boud_cnt==boud_cnt_max-1'b1)||work_en==1'b0)
boud_cnt<=13'd0;
else if(work_en==1'b1)
boud_cnt<=boud_cnt+1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
bit_flag<=1'b0;
else if(boud_cnt==13'd1)
bit_flag<=1'b1;
else
bit_flag<=1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
bit_cnt<=4'd0;
else if((bit_cnt==4'd9)&&(bit_flag==1'b1))
bit_cnt<=4'd0;
else if((bit_flag==1'b1)&&(work_en==1'b1))
bit_cnt<=bit_cnt+1'b1;
else
bit_cnt<=bit_cnt;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n==1'b0)
tx<=1'b1;
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
发送模块的仿真代码如下:
`timescale 1ns / 1ns
module tb_uart_tx();
reg sys_clk ;
reg sys_rst_n ;
reg [7:0] pi_data ;
reg pi_data_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'b0;
pi_data_flag<=1'b0;
#200
//transport the data 0
pi_data<=8'd0;
pi_data_flag<=1'b1;
#20
pi_data_flag<=1'b0;
#(5208*20*10);
//transport the data 1
pi_data<=8'd1;
pi_data_flag<=1'b1;
#20
pi_data_flag<=1'b0;
#(5208*20*10);
//transport the data 2
pi_data<=8'd2;
pi_data_flag<=1'b1;
#20
pi_data_flag<=1'b0;
#(5208*20*10);
//transport the data 3
pi_data<=8'd3;
pi_data_flag<=1'b1;
#20
pi_data_flag<=1'b0;
#(5208*20*10);
//transport the data 4
pi_data<=8'd4;
pi_data_flag<=1'b1;
#20
pi_data_flag<=1'b0;
#(5208*20*10);
//transport the data 5
pi_data<=8'd5;
pi_data_flag<=1'b1;
#20
pi_data_flag<=1'b0;
#(5208*20*10);
//transport the data 6
pi_data<=8'd6;
pi_data_flag<=1'b1;
#20
pi_data_flag<=1'b0;
#(5208*20*10);
//transport the data 7
pi_data<=8'd7;
pi_data_flag<=1'b1;
#20
pi_data_flag<=1'b0;
#(5208*20*10);
end
uart_tx inst
(
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.pi_data (pi_data) ,
.pi_data_flag (pi_data_flag) ,
.tx (tx)
);
endmodule
Vivado 仿真波形如下:
顶层模块
顶层模块代码如下:
module top_rs232(
input wire sys_clk ,
input wire sys_rst_n ,
input wire rx ,
output wire tx
);
parameter uart_bps = 14'd9600;
parameter clk_freq = 26'd50_000_000;
wire [7:0] po_data;
wire po_flag;
uart_rx
#(
.uart_bps(uart_bps),
.clk_freq(clk_freq)
)
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_bps(uart_bps),
.clk_freq(clk_freq)
)
uart_tx_inst1
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.pi_data (po_data),
.pi_data_flag (po_flag),
.tx (tx)
);
endmodule
仿真测试代码如下:
`timescale 1ns / 1ns
module tb_top_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;
initial
begin
#200
rx_byte();
end
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<=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
top_rs232 inst(
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.rx (rx) ,
.tx (tx)
);
endmodule
Vivado仿真波形图如下: