UART
1、UART简介
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)是一种异步收发传输器,其在数据发送时将并行数据转换成串行数据来传输,在数据接收时将接收到的串行数据转换成并行数据,可以实现全双工传输和接收。它包括了RS232、RS422 和RS485 等接口标准规范和总线标准规范。换句话说,UART 是异步串行通信的总称。而RS232、RS422 和RS485 等,是对应各种异步串行通信口的接口标准和总线标准,它们规定了通信口的电气特性、传输速率、连接特性和接口的机械特性等内容。
RS232、RS485在串口通信中,主要区别是电平的不同,其中UART通常使用TTL电平,下面介绍这几个存在的差异:
TTL全名是晶体管-晶体管逻辑集成电路(Transistor-Transistor Logic)
- 逻辑“1”:VCC=3.3V或5V;
- 逻辑“0”:VCC=0V;
RS232
- 逻辑“1”: -3V~-13V;
- 逻辑“0”: +3V~+13V;
例如,对于传输数据0x55 ,即二进制的 1010101,RS232和TTL的区别如下:

RS485是差分信号进行串行传输:
- 逻辑1以两线间的电压差为 +(2~6)v 表示;
- 逻辑0以两线间的电压差为 -(2~6)v 表示;
在工业通信中,使用RS485比较多,因为RS485是差分信号,可以抑制共模干扰,因此在恶劣的环境中拥有很好的抗干扰性,比较稳定
2、连接示意图

在UART的设计中,既可以发送,也可以接收数据(全双工)。但是在实际的设计中,常常只做其中一部分,要么做收部分,要么做发部分。如下图所示,只有收部分。
在设计中也可以做一个全双工的测试(回环测试,后面将采用这个方式验证UART的功能)。

3、数据包

UART数据包的格式(或称为“帧”),当没有数据传输时总线上保持高电平“1”,有数据传输时,拉低表示起始位,接着8个数据位,然后校验位,最后拉高表示停止位,并且进入空闲状态,等待下一次的数据传输。
起始位:起始信号一般由一个逻辑‘0’的数据位表示。
数据位:在起始位后,紧接着的是传输数据的主体内容,即有效数据。通常有效数据的长度7和8位长。
校验位:在有效数据之后,有一个可选的数据校验位。由于数据通信很容易受到外界干扰,导致数据传输出现偏差,因此,可以在传输过程中加上数据校验位来解决这个问题。校验方法有奇校验(odd),偶校验(even),0校验(space),1校验(mark)以及无校验(noparity)。
停止位:停止信号可由1、1.5或2个逻辑‘1’的数据位表示。

上图为B站UP主的时序图。他的FPGA时钟频率为50MHz,串口通信波特率: 115,200 bit/s。那么可以计算出每一个bit传输需要时钟周期数: 50000,000/115,200≈434 0.5个bit 传输需要时钟周期数434/2 =217
他这里设计了一个0.5bit是因为,他把每位数据分为两份,原本10bit的数据也就是20份0.5bit。这样处理可以实现在接收方采样时,在最中间采样,保证数据的准准确性。
图中红色数字3、5、7等,即表示数据的中间位置,3表示half_bit_period,也就是217*3,当计数到217*3时,就可以将RX的一位串行数据存储到data[0]。以此类推,可以实现8位数据的并行存储。
4、波特率
由于UART是异步通讯,通讯过程中没有时钟信号线,因此通讯双方需要约定好波特率,即每个码元的长度,以便对信号进行解码。它表示每秒传输二进制数据的位数。常用的波特率有9600、19200、35400、57600以及115200等。例如当波特率为9600,表示每秒可以传输9600个bit位数据(也就是传输一个bit位数据需要(1/9600≈104us)长的时间)。
5、UART设计
本实例设计的UART特性:(来自:小鱼学IC)
(1) 并没有支持奇偶检验,故没有奇偶校验位
(2) 并不支持数据位宽可配,数据位位宽固定为8bit。
(3) 支持输入时钟与波特率可配。
原理图如下,分两个大模块,一个数据接收控制模块(Receive_Control),一个数据发送控制模块(Send_Control):

接收控制模块与发送控制模块内部都有一个波特率时钟产生模块(BuadRate_set),用于将电路输入时钟(clk)进行分频产生波特率时钟,用于接收和发送数据控制。

可以看到,BuadRate_set模块有一个enable控制信号,只有当enable信号为高时,BuadRate_set模块才工作。这是为了降低功耗。
- 在发送控制模块里面,只有发送数据的时候才拉高其相应的enable
- 在接收控制模块里面,只有检测到有数据发送进来的时候才拉高其相应的enable。
code
来源网址:UART(串口)收发模块设计- 02 (qq.com)
module BuadRate_set #( parameter CLK_Period=50000000,//the unit is Hz parameter Buad_Rate=9600 //the unit is bits/s )( input clk, input rst_n, input enable, output Buad_clk ); localparam DIV_PEREM=CLK_Period/Buad_Rate/2; reg[15:0] cnt; always @( posedge clk ) if( !rst_n ) cnt <= 16'b0000; else if( enable ) begin if( cnt != DIV_PEREM ) cnt <= cnt+1'b1; else cnt <= 16'h0000; end else cnt <= 16'h0000; reg DIV_clk; always @( posedge clk ) if( !rst_n || !enable ) DIV_clk <= 1'b1; else if(cnt==DIV_PEREM) DIV_clk <= ~DIV_clk; assign Buad_clk = DIV_clk; endmodule
module Send_Control #( parameter CLK_Period=50000000,//the unit is Hz parameter Buad_Rate=9600 //the unit is bits/s )( input clk, input rst_n, input tx_en, input [7:0] tx_data, output tx_finish, output uart_tx ); localparam IDLE=2'b00; localparam EN_TX=2'b10; localparam END_BIT=2'b11; wire Buad_clk; wire Buad_en; BuadRate_set #( .CLK_Period(CLK_Period),//the unit is Hz .Buad_Rate (Buad_Rate) //the unit is bits/s ) BuadRate_set_inst( .clk (clk ), .rst_n (rst_n ), .enable (Buad_en ), .Buad_clk(Buad_clk) ); reg [8:0] data_to_send; reg [1:0] state; reg [3:0] cnt; always @( posedge clk ) if( !rst_n ) state <= IDLE; else case(state) IDLE:if( tx_en ) state <= EN_TX; EN_TX: if( cnt=='d9 ) state <= END_BIT; END_BIT: if( cnt=='d0 ) state <= IDLE; default: state <= IDLE; endcase assign Buad_en = (state == EN_TX || state == END_BIT); always @( posedge Buad_clk or negedge rst_n) if( !rst_n ) cnt <= 4'b000; else begin if( cnt!=4'd9) cnt <= cnt + 1'b1; else cnt <= 4'b0000; end always @( posedge Buad_clk or negedge rst_n ) if( !rst_n ) data_to_send <= 8'hff; else if( cnt == 'd0 ) data_to_send <= {tx_data,1'b0}; else data_to_send <= {1'b1,data_to_send[8:1]}; assign uart_tx = (Buad_en)?data_to_send[0]:1'b1; assign tx_finish = (state == IDLE)? 1'b1:1'b0; endmodule
module Receive_Control #( parameter CLK_Period=50000000,//the unit is Hz parameter Buad_Rate=9600 //the unit is bits/s )( input clk, input rst_n, input uart_rx, output reg [7:0] rx_data, output rx_finish, output o_valid ); localparam IDLE=2'b00; localparam GET_DATA=2'b01; localparam END_BIT=2'b11; wire Buad_clk; wire rx_en; BuadRate_set #( .CLK_Period(CLK_Period),//the unit is Hz .Buad_Rate (Buad_Rate) //the unit is bits/s ) BuadRate_set_inst( .clk (clk ), .rst_n (rst_n ), .enable (rx_en ), .Buad_clk(Buad_clk) ); reg[5:0] get_start_bit; always @( posedge clk ) if( !rst_n ) get_start_bit <= 6'b111111; else get_start_bit <= {get_start_bit[4:0],uart_rx}; reg start; always @( posedge clk ) if( !rst_n ) start <= 1'b0; else if( state !=IDLE ) start <= 1'b0; else if( get_start_bit==6'b111000 ) start <= 1'b1; reg [1:0] state; reg [3:0] cnt; always @( negedge Buad_clk or negedge rst_n ) if( !rst_n ) state <= IDLE; else case( state ) IDLE: begin if( start ) state <= GET_DATA; end GET_DATA: begin if( cnt=='d7 ) state <= END_BIT; end END_BIT: begin state <= IDLE; end default: state <= IDLE; endcase assign rx_en =( ~(start==1'b0 && state==IDLE) )? 1'b1:1'b0; always @( negedge Buad_clk or negedge rst_n ) if( !rst_n ) rx_data <= 8'b0; else if( state == GET_DATA ) rx_data <= {uart_rx,rx_data[7:1]}; always @( negedge Buad_clk ) if( state!=GET_DATA ) cnt <= 'd0; else cnt <= cnt+1'b1; assign rx_finish=(state==END_BIT); reg finish_d; always @( posedge clk or negedge rst_n ) if( !rst_n ) finish_d<=1'b0; else finish_d<=rx_finish; assign o_valid = rx_finish&(~finish_d); endmodule
module UART #( parameter CLK_Period=50000000,//the unit is Hz parameter Buad_Rate=9600 //the unit is bits/s )( input clk, input rst_n, input [7:0] tx_data, input tx_en, output tx_finish, output uart_tx, input uart_rx, output [7:0] rx_data, output rx_finish ); Send_Control #( .CLK_Period(CLK_Period),//the unit is Hz .Buad_Rate (Buad_Rate) //the unit is bits/s ) send_control_inst ( .clk ( clk ), .rst_n ( rst_n ), .tx_en ( tx_en ), .tx_data ( tx_data ), .tx_finish ( tx_finish ), .uart_tx ( uart_tx ) ); Receive_Control #( .CLK_Period(CLK_Period),//the unit is Hz .Buad_Rate (Buad_Rate ) //the unit is bits/s ) Receive_inst ( .clk (clk ), .rst_n (rst_n ), .uart_rx(uart_rx), .rx_data(rx_data), .rx_finish(rx_finish), .o_valid(o_valid) ); endmodule
//UART回环顶层 //作者:DOVI666 //时间:24/1/5 module uart( input clk, input rst_n, input rx, output tx ); wire [7:0]data; wire rx_tx_en; parameter sysclk = 50_000_000; //系统时钟 parameter baud = 115200; //波特率 parameter half_period = sysclk/baud/2; //半周期计数.整周期计数为434,半周期为217 //***********uart_rx例化***********// uart_rx #( .sysclk(sysclk), .baud(baud), .half_period(half_period) )uart2_rx_inst( .clk(clk), .rst_n(rst_n), .data_in(rx), .data_out(data), .rx_finish(rx_tx_en) ); //***********uart_tx例化***********// uart_tx #( .sysclk(sysclk), .baud(baud), .half_period(half_period) )uart2_tx_inst( .clk(clk), .rst_n(rst_n), .data_in(data), .tx_en(rx_tx_en), .data_out(tx) ); endmodule
//UART接收模块 //作者:DOVI666 //时间:24/1/5 module uart2_rx( input clk, input rst_n, input data_in, output reg [7:0] data_out, output rx_finish ); reg data_in1; reg data_in2; //用作数据打两拍 parameter sysclk = 50_000_000; //系统时钟 parameter baud = 115200; //波特率 parameter half_period = sysclk/baud/2; //半周期计数.整周期计数为434,半周期为217 //整段数据1+8+1=10bit,则可分为20个半周期 wire start; reg [12:0]baud_cnt; //波特计数,最大4340 reg rx_flag; //开始接收串行数据标志位 //************打两拍,用于判断输入数据是否进入起始位***************// always@(posedge clk or negedge rst_n)begin if(!rst_n)begin data_in1 <= 1'b0; data_in2 <= 1'b0; end else begin data_in1 <= data_in; data_in2 <= data_in1; end end assign start = (~data_in)? (data_in2 ? 1'b1:1'b0) : 1'b0; //起始位标志,判断打两拍前后数据是否一致,in=0,in2=1 //不一致表示下降沿到来,开始起始位 //*******数据正式传输flag标志************************// always@(posedge clk or negedge rst_n)begin if(!rst_n) rx_flag <= 1'b0; else if(start) rx_flag <= 1'b1; else if((baud_cnt == 20*half_period) && (data_in ==1'b1)) rx_flag <= 1'b0; else rx_flag <= rx_flag; end //************波特率计数***************************// always@(posedge clk or negedge rst_n)begin if(!rst_n) baud_cnt <= 13'b0; else if(rx_flag) baud_cnt <= (baud_cnt < (20*half_period))? //只要小于20倍半周期,就会加1 (baud_cnt+1'b1): (baud_cnt <= 13'b0); //大于20倍就置零,表示10bit数据的结束 else baud_cnt <= 13'b0; end always@(posedge clk or negedge rst_n)begin if(!rst_n) data_out <= 8'b0; else if(rx_flag) begin case(baud_cnt) //根据时序图编写 3 *half_period : data_out[0] <= data_in; 5 *half_period : data_out[1] <= data_in; 7 *half_period : data_out[2] <= data_in; 9 *half_period : data_out[3] <= data_in; 11*half_period : data_out[4] <= data_in; 13*half_period : data_out[5] <= data_in; 15*half_period : data_out[6] <= data_in; 17*half_period : data_out[7] <= data_in; default : data_out <= data_out; endcase end else data_out <= data_out; end assign rx_finish = (baud_cnt == 20*half_period); //只要波特率记满,就表示一次数据传输完毕 //这里没有写(data_in == 1'b1),是因为若一直没传数据,那就一直为空闲状态 //所以就没有产生一次数据传输完毕的标志 endmodule
//UART发送模块 //作者:DOVI666 //时间:24/1/5 module uart2_tx( input clk, input rst_n, input [7:0] data_in, input tx_en, output reg data_out //output tx_finish ); parameter sysclk = 50_000_000; //系统时钟 parameter baud = 115200; //波特率 parameter half_period = sysclk/baud/2; //半周期计数.整周期计数为434,半周期为217 //整段数据1+8+1=10bit,则可分为20个半周期 reg [13:0]baud_cnt; reg tx_flag; reg [7:0]data_temp; //*************暂存接收到的数据***********************// always@(posedge clk or negedge rst_n)begin //因为rx部分只会又一个周期时间拉高rx_finish if(!rst_n) //过了这个周期,会拉低,若不及时存储收到的数据 data_temp <= 8'b0; //新数据会刷新寄存器,导致数据出错 else if(tx_en) data_temp <= data_in; else data_temp <= data_temp; end //************波特率计数***************************// always@(posedge clk or negedge rst_n)begin if(!rst_n) baud_cnt <= 13'b0; else if(tx_flag) baud_cnt <= (baud_cnt < (20*half_period))? //只要小于20倍半周期,就会加1 (baud_cnt+1'b1): (baud_cnt <= 13'b0); //大于20倍就置零,表示10bit数据的结束 else baud_cnt <= 13'b0; end always@(posedge clk or negedge rst_n)begin if(!rst_n) tx_flag <= 1'b0; else if(tx_en) tx_flag <= 1'b1; else if(baud_cnt == 20*half_period) tx_flag <= 1'b0; else tx_flag <= tx_flag; end always@(posedge clk or negedge rst_n)begin if(!rst_n) data_out <= 1'b1; else if(tx_flag)begin case(baud_cnt) //根据时序图编写 0 *half_period : data_out <= 1'b0; 2 *half_period : data_out <= data_temp[0]; 4 *half_period : data_out <= data_temp[1]; 6 *half_period : data_out <= data_temp[2]; 8 *half_period : data_out <= data_temp[3]; 10*half_period : data_out <= data_temp[4]; 12*half_period : data_out <= data_temp[5]; 14*half_period : data_out <= data_temp[6]; 16*half_period : data_out <= data_temp[7]; 18*half_period : data_out <= 1'b1; default : data_out <= data_out; endcase end else data_out <= data_out; end // assign tx_finish = (baud_cnt == 20*half_period); //只要波特率记满,就表示一次数据传输完毕 endmodule
6、上板验证
这里设计了一个实测设计(回环设计)(UART_TEST),功能如下:
串口助手(上位机)发什么就能收回什么
上电复位后,通过串口向上位机发送数据8'h21,然后只要收到上位机发送过来的数据,就将其返回给上位机。(8‘h21对应ASCⅡ码为 “ ! ” )

module UART_TEST( input clk, input rst_n, input uart_rx, output uart_tx ); wire rx_finish; wire tx_finish; wire [7:0] rx_data; reg [7:0] tx_data; always @( posedge clk ) if( !rst_n ) tx_data <= 8'h21; else if( rx_finish ) tx_data <= rx_data; reg tx_en; reg tx_cnt; always @( posedge clk or negedge rst_n ) if( !rst_n ) begin tx_en<=1'b0; tx_cnt <=1'b0; end else if( !tx_cnt ) begin tx_en<=1'b1;//rx_finish; tx_cnt <= 1'b1; end else tx_en<=rx_finish; UART #( .CLK_Period(50000000),//the unit is Hz .Buad_Rate(9600) //the unit is bits/s ) UART_INST ( .clk ( clk ), .rst_n ( rst_n ), .tx_data ( tx_data ), .tx_en ( tx_en ), .tx_finish ( tx_finish ), .uart_tx ( uart_tx ), .uart_rx ( uart_rx ), .rx_data ( rx_data ), .rx_finish ( rx_finish ) ); endmodule
这里使用了友善串口调试助手,选择正确端口,调整号对应的串口参数,点击开始即可连接串口。
需要注意的是,开发板上的串口连接使用的是USB,需要现在出售的很多电脑、笔记本都没有串口,所以开发板都提供了usb2uart这个接口。既可以供也可以当串口使用,对于ax301这个开发板,使用的驱动可以从这里下载:
https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads

由图可知,本设计成功。
7、UART——FIFO——数码管
系统框图:

功能:在电脑中,通过串口调试助手,发送数据到FPGA,FPGA中uart_rx模块接收数据,并存入fifo中缓存,通过数码管显示当前发送的数据(0123456789ABCDEF)。uart_rx模块是上文写的模块。fifo模块是在quartus软件中调用的fifo模块,seg数码管显示模块内部有1kHZ分频。

以上是RTL视图。下方展示数码管模块显示代码与顶层设计代码(源自:咸鱼IC),
seg
//====================================================================== // --- 名称 : seg_display // --- 作者 : xianyu_FPGA // --- 日期 : 2018-11-10 // --- 描述 : 数码管动态显示模块,改自小梅哥FPGA //====================================================================== module seg //---------------------<端口声明>--------------------------------------- ( input clk , //时钟,50Mhz input rst_n , //复位,低电平有效 input en , //数码管显示使能 input [ 7:0] data , //输入数据 output [ 5:0] seg_sel , //数码管位选 output reg [ 7:0] seg_data //数码管段选,即内容显示 ); //---------------------<信号定义>--------------------------------------- reg [14:0] cnt ; wire add_cnt ; wire end_cnt ; reg clk_1k ; reg [ 5:0] seg_sel_r ; reg [ 3:0] data_tmp ; //---------------------------------------------------------------------- //-- 1k分频,扫描一个数目管时间为1ms //---------------------------------------------------------------------- //计数 always @(posedge clk or negedge rst_n)begin if(!rst_n) cnt <= 0; else if(add_cnt)begin if(end_cnt) cnt <= 0; else cnt <= cnt + 1'b1; end else cnt <= cnt; end assign add_cnt = en; //en为1才允许计数 assign end_cnt = add_cnt && cnt==25000-1; //分频 always @(posedge clk or negedge rst_n)begin if(!rst_n) clk_1k <= 0; else if(end_cnt) clk_1k <= ~clk_1k; else clk_1k <= clk_1k; end //---------------------------------------------------------------------- //-- 数码管扫描,8位循环扫描,频率为1k //---------------------------------------------------------------------- always @(posedge clk_1k or negedge rst_n)begin if(!rst_n) seg_sel_r <= 6'b11_1110; else if(seg_sel==6'b01_1111) seg_sel_r <= 6'b11_1110; else seg_sel_r <= (seg_sel << 1) + 1'b1; end //---------------------------------------------------------------------- //-- 位选,不同计数对应不同位选编码,也对应分割的不同数据 //---------------------------------------------------------------------- always @(*)begin case(seg_sel_r) 8'b11_1110: data_tmp = data[ 3: 0] ; // 位1 8'b11_1101: data_tmp = data[ 7: 4] ; // 位2 8'b11_1011: data_tmp = data[ 3: 0] ; // 位3 8'b11_0111: data_tmp = data[ 7: 4] ; // 位4 8'b10_1111: data_tmp = data[ 3: 0] ; // 位5 8'b01_1111: data_tmp = data[ 7: 4] ; // 位6 default: data_tmp = 4'b0000 ; endcase end assign seg_sel = en ? seg_sel_r : 6'b11_1111; // 位选使能 //---------------------------------------------------------------------- //-- 段选,将不同分割数据进行段选编码 //---------------------------------------------------------------------- always @(*)begin case(data_tmp) 4'h0: seg_data = 8'b1100_0000; 4'h1: seg_data = 8'b1111_1001; 4'h2: seg_data = 8'b1010_0100; 4'h3: seg_data = 8'b1011_0000; 4'h4: seg_data = 8'b1001_1001; 4'h5: seg_data = 8'b1001_0010; 4'h6: seg_data = 8'b1000_0010; 4'h7: seg_data = 8'b1111_1000; 4'h8: seg_data = 8'b1000_0000; 4'h9: seg_data = 8'b1001_0000; 4'ha: seg_data = 8'b1000_1000; 4'hb: seg_data = 8'b1000_0011; 4'hc: seg_data = 8'b1100_0110; 4'hd: seg_data = 8'b1010_0001; 4'he: seg_data = 8'b1000_0110; 4'hf: seg_data = 8'b1011_1111; default:seg_data = 8'b1111_1111; endcase end endmodule
module uart_fifo_seg( input clk, input rst_n, input data_in, output [5:0]sel, output [7:0]seg ); wire [7:0]data_out; wire wrreq; wire [7:0]hex_data; wire a=1; seg seg_inst( .clk(clk), //时钟,50Mhz .rst_n(rst_n), //复位,低电平有效 .en(a), //数码管显示使能 .data(hex_data), //输入数据 .seg_sel(sel), //数码管位选 .seg_data(seg)//数码管段选,即内容显示 ); uart_rx uart_rx_inst( .clk(clk), .rst_n(rst_n), .data_in(data_in), .data_out(data_out), .rx_finish(wrreq) ); fifo fifo_inst ( .aclr ( ~rst_n ), //这里的fifo是高电平清零,所以rst_n需要取反 .clock (clk), .data (data_out), .rdreq (a), .wrreq (wrreq), .q (hex_data) ); endmodule
参考:
博客园:咸鱼IC
微信公众号:小鱼学IC
B站:UID:491502261

浙公网安备 33010602011771号