写在前面,希望记录一下自己动手做的事情,拿来主义永远是别人的
1、UART串口简介
串口顾名思义,是将字节数据以bit位进行串行传输,特点是线路简单,速度较慢。所以常用在工业控制,嵌入式等数据传输速度要求不高的场景中。
扩展:串行通信分两种:同步串行和异步串行通信
同步串行:通信双方在同一时钟控制下,同步传输数据
异步串行:通信双方使用各自时钟控制数据发送与接收过程
2、UART串口接线与数据与数据包格式
接线:UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。
串口接线
数据包格式: 在进行具体的串口设计之前,先了解串口通信协议。通常串口的一次发送或接收由四个部分组成:起始位S(“一般为逻辑‘0’)、数据位D0~D7(一般为6位~8位之间可变,数据低位在前)、校验位(奇校验、偶检验或不需要校验位)、停止位(通常为1位、1.5位、2位)。停止位必须为逻辑1。在一次串口通信过程中,数据接收与发送双方没有共享时钟,因此,双方必须协商好数据传输波特率。波特率即数据传输速率。根据双方协议好的传输速率,接收端即可对发送端的数据进行采样。常用的波特率有9600、19200、38400、57600以及115200等。当然更块的速度意味着对采样的要求更高,有可能误码率会逐渐提高。
通常对串口进行数据采样,采用更高频的时钟。这样做的目的是采用高频时钟来锁存低频时钟,减少数据的误码率,增加接收模块的自纠错能力。
具体的工作流程为:
发送端按照预先设定好的波特率,发送起始位(Start)+数据位(data)+奇偶校验位+结束位。其中,起始位为逻辑0,结束位为逻辑1,发送端在空闲状态为1。发送数据包格式如下图所示。
数据包格式
数据的奇偶校验位是可以选择的,如果不使用奇偶,那么就没有这个数据位,本设计中没有用到奇偶校验位。
3、设计
接收端通过检测电平‘1’到‘0’的跳变来确定一个数据包的开始。确定开始位接收完成之后,依次接收数据,使用更高的采样时钟,完成数据采集。接收完数据位后,继续接收奇偶校验位和停止位。
串口的接收与发送,其主要时序设计包括两个部分:1、波特率的产生时序;2、数据传输时序,包括接收与发送。
波特率产生时序设计:假设FPGA输入时钟100Mhz,为得到常用的波特率,仍然采用计数分频来得到。BAUD_DIV=100_000000/波特率。其中采样中心点为发送或接收时钟的中心点,即BAUD_DIV_CAP=100_000000/(2*波特率)。该部分在数据接收和发送部分均单独完成。如下图所示
方案图
数据接收模块:在设置好传输波特率的情况下,根据串口传输时序,进行解串。空闲状态时,接收 数据为逻辑高电平,等待起始位逻辑低电平的到来。当起始位到达后,由低位到高位,依次采集8位数据,并进行相应的解串,存入临时寄存器。接收有效数据完成后,判断结束位,接收完毕。
数据发送模块:设置发送使能信号和待发送的数据。通过计数器,表示10个数据发送的周期。这10个数据,依次为起始位+8位数据位+1位结束位,实现数据位的逐个发送。
RX模块:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2020/09/21 13:51:32 // Design Name: // Module Name: UART_RX // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module uart_rx_path( input clk_100m, input uart_rx_i, output [7:0] uart_rx_data_o, output uart_rx_done, output baud_bps_sim ); parameter [13:0] BAUD_DIV = 14'd13020; //9600bps ,50Mhz parameter [13:0] BAUD_DIV_MID= 14'd6510; //collection mid (*mark_debug="true"*) reg [13:0] baud_div =0; (*mark_debug="true"*) reg baud_bps =0; (*mark_debug="true"*) reg bps_start =0; always @(posedge clk_100m) begin if(baud_div==BAUD_DIV_MID)begin baud_bps<= 1'b1; baud_div<= baud_div +1'b1; end else if(baud_div<BAUD_DIV && bps_start)begin//当波特率启动时,计数器累加 baud_div <= baud_div +1'b1; baud_bps <= 0; end else begin baud_bps<=0; baud_div<=0; end end (*mark_debug="true"*) reg [4:0] uart_rx_i_r = 5'b11111; always @(posedge clk_100m) begin uart_rx_i_r <= {uart_rx_i_r[3:0],uart_rx_i}; end wire uart_rx_int = uart_rx_i_r[4]|uart_rx_i_r[3]|uart_rx_i_r[2]|uart_rx_i_r[1]|uart_rx_i_r[0]; (*mark_debug="true"*) reg[3:0] bit_num = 0; (*mark_debug="true"*) reg uart_rx_done_r =0; (*mark_debug="true"*) reg state =1'b0; (*mark_debug="true"*) reg [7:0] uart_rx_data_0_r0 = 0; //process reg [7:0] uart_rx_data_0_r1 = 0; //done always @(posedge clk_100m) begin uart_rx_done_r <= 1'b0; case (state) 1'b0: if(!uart_rx_int)begin bps_start<=1'b1; state<=1'b1; end 1'b1: if(baud_bps) begin bit_num<=bit_num+1'b1; if(bit_num<4'd9) uart_rx_data_0_r0[bit_num-1]<= uart_rx_i; //else //uart_rx_data_0_r0 <= uart_rx_data_0_r0; end else if (bit_num==4'd10)begin bit_num <= 0; uart_rx_done_r <= 1'b1; uart_rx_data_0_r1 <= uart_rx_data_0_r0; state<=1'b0;//进入状态0,再次循环检测 bps_start<=0; end default:; endcase end assign baud_bps_tb=baud_bps;//for simulation assign uart_rx_data_o=uart_rx_data_0_r1; assign uart_rx_done=uart_rx_done_r; endmodule
TX模块
module uart_tx_path( input clk_i, input [7:0] uart_tx_data_i, //待发送数据 input uart_tx_en_i, //发送发送使能信号 output uart_tx_o ); parameter BAUD_DIV = 14'd13020;//波特率时钟,9600bps,100Mhz/9600=10416,波特率可调 parameter BAUD_DIV_CAP = 14'd6510;//波特率时钟中间采样点,100Mhz/9600/2=d208,波特率可调 (*mark_debug="true"*)reg [13:0] baud_div=0; //波特率设置计数器 (*mark_debug="true"*)reg baud_bps=0; //数据发送点信号,高有效 reg [9:0] send_data=10'b1111111111;//待发送数据寄存器,1bit起始信号+8bit有效信号+1bit结束信号 (*mark_debug="true"*)reg [3:0] bit_num=0; //发送数据个数计数器 (*mark_debug="true"*)reg uart_send_flag=0; //数据发送标志位 (*mark_debug="true"*)reg uart_tx_o_r=1; //发送数据寄存器,初始状态位高 always@(posedge clk_i) begin if(baud_div==BAUD_DIV_CAP) //当波特率计数器计数到数据发送中点时,产生采样信号baud_bps,用来发送数据 begin baud_bps<=1'b1; baud_div<=baud_div+1'b1; end else if(baud_div<BAUD_DIV && uart_send_flag)//数据发送标志位有效期间,波特率计数器累加,以产生波特率时钟 begin baud_div<=baud_div+1'b1; baud_bps<=0; end else begin baud_bps<=0; baud_div<=0; end end always@(posedge clk_i) begin if(uart_tx_en_i) //接收数据发送使能信号时,产生数据发送标志信号 begin uart_send_flag<=1'b1; send_data<={1'b1,uart_tx_data_i,1'b0};//待发送数据寄存器装填,1bit起始信号0+8bit有效信号+1bit结束信号 end else if(bit_num==4'd10) //发送结束时候,清楚发送标志信号,并清楚待发送数据寄存器内部信号 begin uart_send_flag<=1'b0; send_data<=10'b1111_1111_11; end end always@(posedge clk_i) begin if(uart_send_flag) //发送有效时候 begin if(baud_bps)//检测发送点信号 begin if(bit_num<=4'd9) begin uart_tx_o_r<=send_data[bit_num]; //发送待发送寄存器内数据,从低位到高位 bit_num<=bit_num+1'b1; end end else if(bit_num==4'd10) bit_num<=4'd0; end else begin uart_tx_o_r<=1'b1; //空闲状态时,保持发送端位高电平,以备发送时候产生低电平信号 bit_num<=0; end end assign uart_tx_o=uart_tx_o_r; endmodule
TOP模块
module uart_top( input clk_i_p, input clk_i_n, //input clk_i, input uart_rx_i, output uart_tx_o ); wire [7:0] uart_rx_data_o; wire uart_rx_done; wire clk_i; IBUFDS IBUFDS_inst ( .O(clk_i), // 1-bit output: Buffer output .I(clk_i_p), // 1-bit input: Diff_p buffer input (connect directly to top-level port) .IB(clk_i_n) // 1-bit input: Diff_n buffer input (connect directly to top-level port) ); uart_rx_path uart_rx_path_u ( .clk_100m(clk_i), .uart_rx_i(uart_rx_i), .uart_rx_data_o(uart_rx_data_o), .uart_rx_done(uart_rx_done) ); uart_tx_path uart_tx_path_u ( .clk_i(clk_i), .uart_tx_data_i(uart_rx_data_o), .uart_tx_en_i(uart_rx_done), .uart_tx_o(uart_tx_o) ); endmodule
Test_bench(本文开发板是KCU105,用的125差分,在仿真时,将顶层差分时钟屏蔽,复原clk_i单端即可,波特率使用125Mhz/9600,改为100Mhz/9600即可)
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2020/09/21 13:55:20
// Design Name:
// Module Name: uart_top_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module uart_top_TB;
reg clk_i_p;
reg clk_i_n;
reg rst_n_i;
reg uart_rx_i;
wire [7:0] uart_tx_o;
uart_top u_uart_top
(
.clk_i_p (clk_i_p),
.clk_i_n (clk_i_n),
//.rst_n_i (rst_n_i),
.uart_rx_i (uart_rx_i),
.uart_tx_o (uart_tx_o)
);
initial
begin
clk_i_p = 0;
clk_i_n = 1;
rst_n_i = 0;
uart_rx_i = 1'b1;
// Wait 100 ns for global reset to finish
#96;
rst_n_i=1;
#104170
uart_rx_i = 1'b1;
#104170
uart_rx_i = 1'b0;//start
//1001_0101
#104170
uart_rx_i = 1'b1;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b1;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b1;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b1;
#104170
uart_rx_i = 1'b1;//stop
#808320
//00000101
uart_rx_i = 1'b0;//start
#104170
uart_rx_i = 1'b1;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b1;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b1;//stop
#808320
//10000100
uart_rx_i = 1'b0;//start
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b1;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b0;
#104170
uart_rx_i = 1'b1;
#104170
uart_rx_i = 1'b1;//stop
end
always
begin
#2 clk_i_p = ~clk_i_p;
#2 clk_i_n = ~clk_i_n;
end
endmodule
仿真发送的第一个数据是,10010101,,对应的Hex为95,第二个是00000101,hex为05。
4、综合布线前仿真如下:
接收模块正确
发送仿真ok
5、上板子调试
结果
测试结果
有误码情况:
波特率提高时,会有误码出现,后面有时间再来改善一下。
误码图:
原先的Kcu105自带USB转串口,但是实际过程中效果不理想(出现出厂自带的测试东西),所以我用的以前的PL2303这个USB转串口,外挂了两个Pin管脚,效果还行。