赞
踩
1、RS232 通信协议简介
1、RS232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线,tx 是发送数据的线。
2、rx 位宽为 1bit,PC 机通过串口调试助手往 FPGA 发 8bit 数据时,FPGA 通过串口线rx 一位一位地接收,从最低位到最高位依次接收,最后在 FPGA 里面位拼接成 8 比特数据。
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 帧结构。
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 的数据后,再接收一个波特时间的停止位。
2、串口通信程序设计
实验完成PC端通过串口助手发送数据,FPGA接收数据后在发送给PC端,顶层模块框图如下图所示,主要是包含了两个模块,分别是发送和接收模块,分别编写两个模块的代码来进行仿真验证。
接收模块如下图所示,信号出入端口有时钟、复位和串行数据输入。数据输出端口有串行数据输出和标志位。
串口发送模块的时序图如下图所示,这边主要是参照野火的教程,详情可以下载野火的文档。
根据时序图编写我们的verilog代码,数据接收模块的代码如下所示。
module uart_rx #( parameter freq = 'd50_000_000, parameter baud = 'd9600 ) ( input clk, input rst_n, input rx, output reg out_flag, output reg [7:0] out_data ); parameter BAUD_MAX=freq/baud; reg rx_reg1; reg rx_reg2; reg rx_reg3; reg start_flag; reg work_en; reg [3:0] bit_cnt; reg bit_flag; reg [12:0] baud_cnt; reg [7:0] rx_data; reg rx_flag; always @(posedge clk or negedge rst_n) begin if(~rst_n)begin rx_reg1<=1'b1; end else begin rx_reg1<=rx; end end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin rx_reg2<=1'b1; end else begin rx_reg2<=rx_reg1; end end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin rx_reg3<=1'b1; end else begin rx_reg3<=rx_reg2; end end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin start_flag<=1'b0; end else if(rx_reg2==1'b0 && rx_reg3==1'b1 && work_en==1'b0)begin start_flag<=1'b1; end else start_flag<=1'b0; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin work_en<=1'b0; end else if(start_flag==1'b1)begin work_en<=1'b1; end else if(bit_cnt==4'd8 && bit_flag)begin work_en<=1'b0; end end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin baud_cnt<=13'd0; end else if(baud_cnt == BAUD_MAX-1'b1 || work_en==1'b0)begin baud_cnt<=13'd0; end else baud_cnt<=baud_cnt+1'b1; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin bit_flag<=1'b0; end else if(baud_cnt==BAUD_MAX/2'd2-1'b1)begin bit_flag<=1'b1; end else bit_flag<=1'b0; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin bit_cnt<=4'd0; end else if(bit_flag==1'b1 & bit_cnt==4'd8)begin bit_cnt<=4'd0; end else if( bit_flag==1'b1)begin bit_cnt<=bit_cnt+1'b1; end else bit_cnt<=bit_cnt; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin rx_data<=8'd0; end else if(bit_flag==1'b1 && bit_cnt>=4'd1 && bit_cnt<=4'd8)begin rx_data<={rx_reg3,rx_data[7:1]}; end else rx_data<=rx_data; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin rx_flag<=1'b0; end else if(bit_flag==1'b1 & bit_cnt==4'd8)begin rx_flag<=1'b1; end else rx_flag<=1'b0; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin out_data<=8'd0; end else if(rx_flag==1'b1)begin out_data<=rx_data; end else out_data<=out_data; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin out_flag<=1'b0; end else if(rx_flag==1'b1)begin out_flag<=rx_flag; end else out_flag<=1'b0; end endmodule
编写串口接收模块的仿真测试代码如下。
//~ `New testbench `timescale 1ns / 1ps module tb_uart_rx; // uart_rx Parameters parameter PERIOD = 10 ; parameter freq = 'd50_000_0; parameter baud = 'd9600 ; parameter BAUD_MAX = freq/baud ; // uart_rx Inputs reg clk = 0 ; reg rst_n = 0 ; reg rx = 0 ; // uart_rx Outputs wire out_flag ; wire [7:0] out_data ; initial begin forever #(PERIOD/2) clk=~clk; end initial begin #(PERIOD*2) rst_n = 1; end uart_rx #( .freq ( freq ), .baud ( baud ) ) u_uart_rx ( .clk ( clk ), .rst_n ( rst_n ), .rx ( rx ), .out_flag ( out_flag ), .out_data ( out_data [7:0] ) ); initial begin #(PERIOD*10); rx_bit(8'd0); rx_bit(8'd1); rx_bit(8'd2); rx_bit(8'd3); 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 #(PERIOD*52); end endtask endmodule
最后的仿真结果图如下图所示,可以看到仿真结果接收模块接收8位的串行数据,接收完一个字节后,输出一个标志位信号和8位长度的数据。
接下来是发送模块,发送模块主要包括时钟、复位、输入的并行数据、标志位、输出的串行数据。
时序图如下图所示,具体代码讲解可以参照野火文档。
串口发送模块的代码如下图所示。
module uart_tx #( parameter freq = 'd50_000_000, parameter baud = 'd9600 ) ( input clk, input rst_n, input [7:0] in_data, input in_flag, output reg tx ); parameter BAUD_MAX=freq/baud; reg work_en; reg bit_flag; reg [3:0] bit_cnt; reg [12:0] baud_cnt; always @(posedge clk or negedge rst_n) begin if(~rst_n)begin work_en<=1'b0; end else if(in_flag==1'b1)begin work_en<=1'b1; end else if(bit_cnt==4'd9 && bit_flag==1'b1)begin work_en<=1'b0; end end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin baud_cnt<=13'd0; end else if(baud_cnt == BAUD_MAX-1'b1 || work_en==1'b0)begin baud_cnt<=13'd0; end else baud_cnt<=baud_cnt+1'b1; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin bit_flag<=1'b0; end else if(baud_cnt==13'd1)begin bit_flag<=1'b1; end else bit_flag<=1'b0; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin bit_cnt<=4'd0; end else if(bit_cnt==4'd9 && bit_flag==1'b1)begin bit_cnt<=4'd0; end else if(bit_flag==1'b1)begin bit_cnt<=bit_cnt+1'b1; end else bit_cnt<=bit_cnt; end always @(posedge clk or negedge rst_n) begin if(~rst_n)begin tx<=1'b1; end else if(bit_flag==1'b1)begin case(bit_cnt) 4'd0:tx<=1'b0; 4'd1:tx<=in_data[0]; 4'd2:tx<=in_data[1]; 4'd3:tx<=in_data[2]; 4'd4:tx<=in_data[3]; 4'd5:tx<=in_data[4]; 4'd6:tx<=in_data[5]; 4'd7:tx<=in_data[6]; 4'd8:tx<=in_data[7]; 4'd9:tx<=1'b1; default:tx<=1'b1; endcase end end endmodule
串口发送模块的测试代码如下图所示。
`timescale 1ns / 1ps module tb_uart_tx; // uart_tx Parameters parameter PERIOD = 10 ; parameter freq = 'd50_000_0; parameter baud = 'd9600 ; parameter BAUD_MAX = freq/baud ; // uart_tx Inputs reg clk = 0 ; reg rst_n = 0 ; reg [7:0] in_data = 0 ; reg in_flag = 0 ; // uart_tx Outputs wire tx ; initial begin forever #(PERIOD/2) clk=~clk; end initial begin #(PERIOD*2) rst_n = 1; end uart_tx #( .freq ( freq ), .baud ( baud )) u_uart_tx ( .clk ( clk ), .rst_n ( rst_n ), .in_data ( in_data [7:0] ), .in_flag ( in_flag ), .tx ( tx ) ); initial begin in_data<=8'd0; in_flag<=1'b0; #(PERIOD*10) in_data<=8'd0; in_flag<=1'b1; #(PERIOD) in_flag<=1'b0; #(PERIOD*52*10) in_data<=8'd1; in_flag<=1'b1; #(PERIOD) in_flag<=1'b0; #(PERIOD*52*10) in_data<=8'd2; in_flag<=1'b1; #(PERIOD) in_flag<=1'b0; #(PERIOD*52*10) in_data<=8'd3; in_flag<=1'b1; #(PERIOD) in_flag<=1'b0; end endmodule
串口发送模块的仿真图如下图所示,可以看到发送模块接收到并行数据和标志位后将数据转换为串行数据通过tx端口发送回PC端口。
3、上板验证
这边打开串口助手,设置号波特率、数据位、停止位等参数后打开串口。发送数据后在上面屏幕看到从FPGA端发送回来的数据,串口通信成功。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。