1.实验说明
在上位机中通过串口调试助手向 PotatoPie 开发板中发送串口数据,开发板将接收到的数据发送给上
位机,完成数据的回环,该例程通信框图如下图所示

通过 UART 可以与主机或其他外部设备进行串口通信。
UART 是一种通用的数据通信协议,也是异步串行通信口的总称, 在进行 UART 通信时可以配置传输速度、
数据格式等参数, 将数据通过串行通讯进行传输。
UART 通信协议如下图所示:

管脚说明
set_pin_assignment	{ rx }	{ LOCATION = P52; }
set_pin_assignment	{ clk_ref }	{ LOCATION = P128; IOSTANDARD = LVCMOS33; }
set_pin_assignment	{ i_rst }	{ LOCATION = P55; IOSTANDARD = LVCMOS33; PULLTYPE = PULLDOWN; }
set_pin_assignment	{ tx }	{ LOCATION = P57; }
- rx,UART接收管脚
 - clk_ref,参考时钟
 - i_rst,复位
 - tx,UART发送管脚
 
实验现象
连接 PotatoPie 开发板,用USB转TTL串口线连接到开发板的UART IO上,下载 bit 文件,在上位机中打开串口调试助手,波特率选择 115200,数据位宽 8bit,无校验位, 1bit 停止位,利用串口同时发送 1~9 hex 数据。

上位机接收到的数据与发送的数据一致, UART 数据回环传输正确。
实验原理
UART介绍
UART(Universal Asynchronous Receiver/Transmitter)是一种异步全双工串行通信协议,由Tx和Rx两根数据线组成,因为没有参考时钟信号,所以通信的双方必须约定串口波特率、数据位宽、奇偶校验位、停止位等配置参数,从而按照相同的速率进行通信。

起始位:先发出一个逻辑”0”信号,表示传输字符的开始;数据位:可以是5~8位逻辑”0”或”1”;如ASCII码(7位),扩展BCD码(8位);小端传输,即LSB先发,MSB后发;校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验);停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平(用于双方同步,停止位时间间隔越长,容错能力越强);空闲位:处于逻辑“1”状态,表示当前线路上没有数据传送;
↑图-1 起始位和停止位

↑图-2 数据位

↑传输“A”
上图是uart协议传输一个”A”字符通过示波器的uart解码而得到的波形示意图。根据此图来介绍一下uart的一些基本参数。 波特率:此参数容易和比特率混淆,其实他们是由区别的。但是我认为uart中的波特率就可以认为是比特率,即每秒传输的位数(bit)。一般选波特率都会有9600,19200,115200等选项。其实意思就是每秒传输这么多个比特位数(bit)。 起始位:先发出一个逻辑”0”的信号,表示传输数据的开始。 数据位:可以选择的值有5,6,7,8这四个值,可以传输这么多个值为0或者1的bit位。这个参数最好为8,因为如果此值为其他的值时当你传输的是ASCII值时一般解析肯定会出问题。理由很简单,一个ASCII字符值为8位,如果一帧的数据位为7,那么还有一位就是不确定的值,这样就会出错。 校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。就比如传输“A”(01000001)为例。 
1、当为奇数校验:”A”字符的8个bit位中有两个1,那么奇偶校验位为1才能满足1的个数为奇数(奇校验)。图-1的波形就是这种情况。 
2、当为偶数校验:”A”字符的8个bit位中有两个1,那么奇偶校验位为0才能满足1的个数为偶数(偶校验)。 
此位还可以去除,即不需要奇偶校验位。 停止位:它是一帧数据的结束标志。可以是1bit、1.5bit、2bit的空闲电平。可能大家会觉得很奇怪,怎么会有1.5位~没错,确实有的。所以我在生产此uart信号时用两个波形点来表示一个bit。这个可以不必深究。。。 空闲位:没有数据传输时线路上的电平状态。为逻辑1。 传输方向:即数据是从高位(MSB)开始传输还是从低位(LSB)开始传输。比如传输“A”如果是MSB那么就是01000001(如图-2),如果是LSB那么就是10000010(如下图的图-4) 
uart传输数据的顺序就是:刚开始传输一个起始位,接着传输数据位,接着传输校验位(可不需要此位),最后传输停止位。这样一帧的数据就传输完了。接下来接着像这样一直传送。在这里还要说一个参数。 帧间隔:即传送数据的帧与帧之间的间隔大小,可以以位为计量也可以用时间(知道波特率那么位数和时间可以换算)。比如传送”A”完后,这为一帧数据,再传”B”,那么A与B之间的间隔即为帧间隔。 

↑图-3

↑图-4
上两图和下两图传送的数据和波特率都是一样的,但是有几个参数是故意设置反了从而形成对比。有助于更深入的理解UART。

实现原理
实现原理就不讲了吧,就是用IO模拟上一切中的串口波形,跟单片机一个思路,只不过FPGA可以更准确的实现UART所需的波形,波特率也能做到更高。
代码说明
代码层次结构如下:

- sys_pll,用于由板载的10M生成25M时钟。
 - uart_rx,UART的接收模块。
 - uart_tx,UART的发送模块。
 
design_top_wrapper
module  design_top_wrapper(
    input     wire    clk_ref     ,
input i_rst,
    input     wire    rx          ,
 
    output    wire    tx
);
 
//wire sys_clk;
//wire clko;
//EF2_PHY_OSCDIV inst(
//	.rstn(1),
//	.stdby(0),
//	.div(7'b000_0100),
//	.clko(clko));
//EF2_LOGIC_BUFG BUFG_inst(
//.o(sys_clk),
//.i(clko)
//);	
//reg [7:0] cnt_rst;
//always @(posedge sys_clk) if (!cnt_rst[7]) cnt_rst <= cnt_rst + 1'b1;
//wire sys_rst_n = cnt_rst[7];
wire sys_clk;
reg [15:0] cnt_rst;
reg pll_rst;
reg [2:0] i_rst_sync;
always @(posedge clk_ref) i_rst_sync <= {i_rst_sync[1:0], i_rst};
always @(posedge clk_ref, posedge i_rst_sync[2]) begin
	if (i_rst_sync[2]) begin
		cnt_rst <= 16'd1;	
		pll_rst <= 1;
	end
	else begin
		if (!cnt_rst[15])
			cnt_rst <= cnt_rst << 1;
		pll_rst <= ~cnt_rst[15];
	end
end
wire sys_rst_n;
sys_pll u_sys_pll(.refclk(clk_ref),
		.reset(pll_rst),
		.stdby(0),
		.extlock(sys_rst_n),
		.clk0_out(sys_clk));    
 
wire    [7:0]   rx_data     ;
wire            rx_flag     ;
uart_rx
#(
    .UART_BPS ( 115200    ),
    .CLK_FREQ ( 58_470_000)
)
uart_rx_inst
(
    .sys_clk  ( sys_clk   ),
    .sys_rst_n( sys_rst_n ),
    .rx       ( rx        ),
  
    .out_data ( rx_data   ),
    .out_flag ( rx_flag   )
);
 
uart_tx
#(
    .UART_BPS ( 115200     ),
    .CLK_FREQ ( 58_470_000 )
)
uart_tx_inst
(
    .sys_clk  ( sys_clk   ),
    .sys_rst_n( sys_rst_n ),
    .in_data  ( rx_data   ),
    .in_flag  ( rx_flag   ),
    .tx       ( tx        )
);  
 
endmodule
可以看到在顶层PLL提供时钟,UART_TX提供发送,UART_RX进行接收,其中有一个rx_flag信号由UART_RX模块输出的,用于告知UART_TX进行发送,以完成环回。
sys_pll
是一个IP,不做代码分析。输入10M,输出25M,没什么可说的,关于IP配置可以参考软件教程。


UART_RX
模块端口
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]    out_data    ,
    output  reg            out_flag    
);
- sys_clk,25M时钟
 - sys_rst_n,复位
 - rx,接收脚
 - out_data,输出接收到的数据
 - out_flag,输出接收到有效数据的信号
 
定义波特率计数器的最大计数值
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS;
对rx管脚上输入的信号进行打拍同步
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;
捕获UART起始条件,
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        start_flag <= 1'b0;
    else if((rx_reg3 == 1'b1)&&(rx_reg2 == 1'b0)&&(work_en == 1'b0))
        start_flag <= 1'b1;
    else
        start_flag <= 1'b0;
 
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else if(start_flag == 1'b1)
        work_en <= 1'b1;
    else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1))
        work_en <= 1'b0;
    else
        work_en <= work_en;
因为UART的起始条件是一个低脉冲,所以(rx_reg3 == 1'b1)&&(rx_reg2 == 1'b0)判定为下降沿时start_flag <= 1'b1;表示传输启动,同时work_en <= 1'b1;表示已在进行一次8bit UART传输。
(bit_cnt == 4'd8)&&(bit_flag == 1'b1) 为8bit传输完成判断。
always@(posedge sys_clk or negedge sys_rst_n)
    if((sys_rst_n == 1'b0))
        baud_cnt <= 16'd0; 
    else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(work_en == 1'b0))
        baud_cnt <= 16'd0; 
    else
        baud_cnt <= baud_cnt + 1'b1;
上面代码用于产生波特率计时,后面的bit_flag用于标记一个计时满。
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'd0; 
    else if((bit_flag == 1'b1)&&(bit_cnt == 4'd8))
        bit_cnt <= 4'd0;
    else if(bit_flag == 1'b1)
        bit_cnt <= bit_cnt + 1'b1;
上面代码表示进行接收bit的计数。
else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1))
        rx_flag <= 1'b1;
rx_flag这个用于判定完成一个8bit接收,跟worker_on一样的逻辑。
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数据的采样和移位组装成8bit.由于UART先传你位,所以是左移。
always@(posedge sys_clk or negedge sys_rst_n)
    if((sys_rst_n == 1'b0))
        out_data <= 8'b0;
    else if(rx_flag == 1'b1)
        out_data <= rx_data;
 
always@(posedge sys_clk or negedge sys_rst_n)
    if((sys_rst_n == 1'b0))
        out_flag <= 1'b0; 
    else
        out_flag <= rx_flag;
将rx_data, out_flag寄存后输出给UART_TX模块使用。
UART_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]  in_data     , 
input        wire          in_flag     ,
 
output       reg          tx           
);
in_data, 要发送的数据
in_flag,启动发送的标志位
tx,UART发送管脚
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else if(in_flag == 1'b1)
        work_en <= 1'b1;
    else if((bit_cnt == 4'd9)&&(bit_flag == 1'b1))
        work_en <= 1'b0;
    else
        work_en <= work_en;
work_en表示正在进行传输,根据in_flag有效置1表示正在传输,
(bit_cnt == 4'd9)&&(bit_flag == 1'b1),表示传输完9位则传输结束,work_en置0。
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        baud_cnt <= 16'd0;
    else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(work_en == 1'b0))
        baud_cnt <= 16'd0;
    else if(work_en == 1'b1)
        baud_cnt <= baud_cnt + 1'b1;
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else if(baud_cnt == 16'd1)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;
第一段是波特率延时计数器,bit_flag表示一个波特率延时已达。
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;
对bit位进行计数。
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        tx <= 1'b1;  
    else if(bit_flag == 1'b1)
        begin
            case(bit_cnt)
                0:  tx <= 1'b0;     
                1:  tx <= in_data[0];
                2:  tx <= in_data[1];
                3:  tx <= in_data[2];
                4:  tx <= in_data[3];
                5:  tx <= in_data[4];
                6:  tx <= in_data[5];
                7:  tx <= in_data[6];
                8:  tx <= in_data[7];
                9:  tx <= 1'b1;        
                default: tx = 1'b1;
            endcase
        end
选择输出数据,每次延时到达,bitcnt计数器变化就输出一位,这段代码其实用移位操作更好。
可以看到UART_TX的代码要简单很多。
上面代码中UART_TX UART_RX各自维护自己的延时计数器,其实没有必要,可以共用。




