正点原子P15在PL端的uart电路参考,PS端uart和PL端一致,这里不做重复,uart电路由电脑端进行供电,即uart和主芯片之间除利用uart_tx和uart_rx通信外是独立的。
从上图中可以看到,FPGA芯片的PL_UART1_TX连接到CH340的RXD管脚,FPGA芯片的PL_UART1_RX连接到 CH340 的 TXD 管脚,CH340 的 PL_CH340_P/N 分别连接到开发板的 USB Type-C 插座 D+/-上。CH340 具体的工作原理是:发送的时候把 FPGA 的 PL_UART1_TX 管脚送出的信号由 TTL 电平转换为 USB 差分电平并送到 USB Type-C 插座,接收的时候则把 USB Type-C 插座送来的 USB 差分电平转换为 TTL 电平送给 FPGA 的 PL_UART1_RX 管脚,同时我们通过 USB 数据线将 USB Type-C 插座 D+/-直连到 PC 上相应的 USB 口的 D+/-,当然也包括电源和地,这样就完成了 USB 串口通信的硬件准备工作。
UART的英文全称是 Universal Asynchronous Receiver/Transmitter,即通用异步收发器,串口是串行接口的简称,两者组合起来就是通用异步串行通信接口,它包括了 RS232、RS499、RS423、RS422 和 RS485 等接口标准规范和总线标准规范,因此串口广泛应用于嵌入式、工业控制等领域。
通信通常分为串行通信和并行通信。串行通信的的特点:一是节省传输线,大大降低了使用成本,二是数据传送速度慢,这一点在大位宽的数据传输上尤为明显。综上可知,串行通信主要应用于长距离、低速率的通信场合。而并行用的通信线多、成本高,故不宜进行远距离通信,因此并行通信一般用于近距离的通信,通常传输距离小于 30 米。
串行通信一般有 2 种通信方式:同步串行通信(synchronized serial communication)和异步串行通信(asynchronous serial communication)。同步串行通信需要通信双方在同一时钟的控制下同步传输数据;异步串行通信是指具有不规则数据段传送特性的串行数据传输。在常见的通信总线协议中,I2C,SPI 属于同步通信而 UART 属于异步通信。同步通信的通信双方必须先建立同步,即双方的时钟要调整到同一个频率,收发双方不停地发送和接收连续的同步比特流。异步通信在发送字符时,发送端可以在任意时刻开始发送字符,所以,在 UART 通信中,数据起始位和停止位是必不可少的。
UART 是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。UART 串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。对于 PC 来说它的 TX 要和对于 FPGA 来说的 RX 连接,同样 PC 的 RX 要和 FPGA 的 TX 连接,如果是两个 TX 或者两个 RX 连接那数据就不能正常被发送出去或者接收到,所以这里大家不要弄混。
UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位,如下图所示。
起始位:当不传输数据时,UART 数据传输线通常保持高电压电平。若要开始数据传输,发送 UART会将传输线从高电平拉到低电平并保持 1 个波特率周期。当接收 UART 检测到高到低电压跃迁时,便开始以波特率对应的频率读取数据帧中的位。
数据帧:数据帧包含所传输的实际数据。如果使用奇偶校验位,数据帧长度可以是 5 位到 8 位。如果不使用奇偶校验位,数据帧长度可以是 10 位。在大多数情况下,数据以最低有效位优先方式发送。
奇偶校验:奇偶性描述数字是偶数还是奇数。通过奇偶校验位,接收 UART 判断传输期间是否有数据发生改变。电磁辐射、不一致的波特率或长距离数据传输都可能改变数据位。接收 UART 读取数据帧后,将计数值为 1 的位,检查总数是偶数还是奇数。如果奇偶校验位为 0(偶数奇偶校验),则数据帧中的 1 或逻辑高位总计应为偶数。如果奇偶校验位为 1(奇数奇偶校验),则数据帧中的 1 或逻辑高位总计应为奇数。当奇偶校验位与数据匹配时,UART 认为传输未出错。但是,如果奇偶校验位为 0,而总和为奇数,或者奇偶校验位为 1,而总和为偶数,则 UART 认为数据帧中的位已改变。
停止位:为了表示数据包结束,发送 UART 将数据传输线从低电压驱动到高电压并保持 1 到 2 位时间。UART 通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为 5、6、7、8 位,其中 8 位数据位是最常用的,在实际应用中一般都选择 8 位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择 1 位(默认),1.5 或 2 位。串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是 bps(位/秒),常用的波特率有 9600、19200、38400、57600 以及 115200 等。
在设置好数据格式及传输速率之后,UART 负责完成数据的串并转换,而信号的传输则由外部驱动电路实现。电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准有 RS232、RS422、RS485 等,它们定义了接口不同的电气特性,如 RS-232 是单端输入输出,而 RS-422/485 为差分输入输出等。
RS232以前最常用的是DP9接口,如下图所示,但该接口体积较大,不适合开发板,现大多数都是用USB转串口实现uart串口通信。即最开始的USB转串口通信电路结构。
通常在FPGA与串口的通信模块中,主要形式为下图,外围接口电路包含时钟,rx,tx,以及复位rst,其中rst_n表示低电平复位。rx_done表示一组数据包接收完成,而rx_data表示接收到的数据包,通常我们在PC端会在数据包发送时进行多数据包编码,并在接收端即FPGA中编写解码模块对接收到的数据包进行命令帧解析,并在后续中编写对应的执行模块以实现不同的效果。
下图为最简单一种形式,即实现串口的回传模式,将接收到的信息原封不动的传回去。
其中uart接收模块代码如下:
34 至 45 行是一个经典的边沿检测电路,通过检测串口接收端 uart_rxd 的下降沿来捕获起始位。一旦检测到起始位,输出一个时钟周期的脉冲 start_en,并进入串口接收过程。串口接收状态用 rx_flag 来标志,rx_flag 为高标志着串口接收过程正在进行,此时启动系统时钟计数器 clk_cnt 与接收数据计数器 rx_cnt。
由第 13 行的公式 BAUD_CNT_MAX = CLK_FREQ/UART_BPS 可知,BAUD_CNT_MAX 为当前波特率下,串口传输一位所需要的系统时钟周期个数。因此 baud_cnt 从零计数到 BAUD_CNT_MAX-1 时,串口刚好完成一位数据的传输。由于接收数据计数器 rx_cnt 在每次 baud_cnt 计数到 BAUD_CNT_MAX-1 时加 1,因此由 rx_cnt 的值可以判断串口当前传输的是第几位数据。
第 89 行至第 111 行就是根据 baud_cnt 的值将 uart 接收端口的数据寄存到接收数据寄存器对应的数据位,从而实现接收数据的串并转换。其中第 93 行选择 baud_cnt 计数至(BAUD_CNT_MAX/2)-1 时寄存接收端口数据,是因为计数到数据中间时的采样结果最稳定。
程序中需要额外注意的地方是串口接收过程结束条件的判定,由第 54 行可知,在计数到停止位中间时,标志位 rx_flag 就已经拉低。这样做是因为虽然此时一帧数据传输还没有完成(停止位只传送到一半),但是数据位已经寄存完毕。而在连续接收数据时,提前半个波特率周期结束接收过程可以为检测下一帧数据的起始位留出充足的时间。
当检测到串口接收端 uart_rxd 的下降沿后,在整个接收过程中 rx_flag 保持为高电平,同时 rx_cnt 对串口数据进行计数。当 rx_cnt 计数到 9 时,且 baud_cnt 计数到停止位的中间时,说明串口数据接收完成,此时拉高 uart_rx_done 信号,同时将接收到的数据赋值给 uart_rx_data。
点击查看代码
1 module uart_rx(
2 input clk , //系统时钟
3 input rst_n , //系统复位,低有效
4
5 input uart_rxd , //UART 接收端口
6 output reg uart_rx_done, //UART 接收完成信号
7 output reg [7:0] uart_rx_data //UART 接收到的数据
8 );
9
10 //parameter define
11 parameter CLK_FREQ = 100000000; //系统时钟频率
12 parameter UART_BPS = 115200 ; //串口波特率
13 localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次
14
15 //reg define
16 reg uart_rxd_d0;
17 reg uart_rxd_d1;
18 reg uart_rxd_d2;
19 reg rx_flag ; //接收过程标志信号
20 reg [3:0 ] rx_cnt ; //接收数据计数器
21 reg [15:0] baud_cnt ; //波特率计数器
22 reg [7:0 ] rx_data_t ; //接收数据寄存器
23
24 //wire define
25 wire start_en;
26
27 //*****************************************************
28 //** main code
29 //*****************************************************
30 //捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
31 assign start_en = uart_rxd_d2 & (~uart_rxd_d1) & (~rx_flag);
32
33 //针对异步信号的同步处理
34 always @(posedge clk or negedge rst_n) begin
35 if(!rst_n) begin
36 uart_rxd_d0 <= 1'b0;
37 uart_rxd_d1 <= 1'b0;
38 uart_rxd_d2 <= 1'b0;
39 end
40 else begin
41 uart_rxd_d0 <= uart_rxd;
42 uart_rxd_d1 <= uart_rxd_d0;
43 uart_rxd_d2 <= uart_rxd_d1;
44 end
45 end
46
47 //给接收标志赋值
48 always @(posedge clk or negedge rst_n) begin
49 if(!rst_n)
50 rx_flag <= 1'b0;
51 else if(start_en) //检测到起始位
52 rx_flag <= 1'b1; //接收过程中,标志信号 rx_flag 拉高
53 //在停止位一半的时候,即接收过程结束,标志信号 rx_flag 拉低
54 else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1'b1))
55 rx_flag <= 1'b0;
56 else
57 rx_flag <= rx_flag;
58 end
59
60 //波特率的计数器赋值
61 always @(posedge clk or negedge rst_n) begin
62 if(!rst_n)
63 baud_cnt <= 16'd0;
64 else if(rx_flag) begin //处于接收过程时,波特率计数器(baud_cnt)进行循环计数
65 if(baud_cnt < BAUD_CNT_MAX - 1'b1)
66 baud_cnt <= baud_cnt + 16'b1;
67 else
68 baud_cnt <= 16'd0; //计数达到一个波特率周期后清零
69 end
70 else
71 baud_cnt <= 16'd0; //接收过程结束时计数器清零
72 end
73
74 //对接收数据计数器(rx_cnt)进行赋值
75 always @(posedge clk or negedge rst_n) begin
76 if(!rst_n)
77 rx_cnt <= 4'd0;
78 else if(rx_flag) begin //处于接收过程时 rx_cnt 才进行计数
79 if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时
80 rx_cnt <= rx_cnt + 1'b1; //接收数据计数器加 1
81 else
82 rx_cnt <= rx_cnt;
83 end
84 else
85 rx_cnt <= 4'd0; //接收过程结束时计数器清零
86 end
87
88 //根据 rx_cnt 来寄存 rxd 端口的数据
89 always @(posedge clk or negedge rst_n) begin
90 if(!rst_n)
91 rx_data_t <= 8'b0;
92 else if(rx_flag) begin //系统处于接收过程时
93 if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin //判断 baud_cnt 是否计数到数据位的中间
94 case(rx_cnt)
95 4'd1 : rx_data_t[0] <= uart_rxd_d2; //寄存数据的最低位
96 4'd2 : rx_data_t[1] <= uart_rxd_d2;
97 4'd3 : rx_data_t[2] <= uart_rxd_d2;
98 4'd4 : rx_data_t[3] <= uart_rxd_d2;
99 4'd5 : rx_data_t[4] <= uart_rxd_d2;
100 4'd6 : rx_data_t[5] <= uart_rxd_d2;
101 4'd7 : rx_data_t[6] <= uart_rxd_d2;
102 4'd8 : rx_data_t[7] <= uart_rxd_d2; //寄存数据的高低位
103 default : ;
104 endcase
105 end
106 else
107 rx_data_t <= rx_data_t;
108 end
109 else
110 rx_data_t <= 8'b0;
111 end
112
113 //给接收完成信号和接收到的数据赋值
114 always @(posedge clk or negedge rst_n) begin
115 if(!rst_n) begin
116 uart_rx_done <= 1'b0;
117 uart_rx_data <= 8'b0;
118 end
119 //当接收数据计数器计数到停止位,且 baud_cnt 计数到停止位的中间时
120 else if(rx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin
121 uart_rx_done <= 1'b1 ; //拉高接收完成信号
122 uart_rx_data <= rx_data_t; //并对 UART 接收到的数据进行赋值
123 end
124 else begin
125 uart_rx_done <= 1'b0;
126 uart_rx_data <= uart_rx_data;
127 end
128 end
129
130 endmodule
串口发送模块代码如下:
为了确保环回实验的成功,在程序的 36 行我们将 uart_tx_busy 提前 1/16 个停止位拉低。尽管串口发送数据只是接收数据的反过程,理论上在传输的时间上是一致的,但是考虑到我们模块里计算波特率会有较小的偏差,并且串口对端的通信设备(如电脑等)收发数据的波特率同样可能会出现较小的偏差,因此为了确保环回实验的成功,这里将发送模块的停止位略微提前结束。需要说明的是,较小偏差的波特率在串口通信时是允许的,同样可以保证数据可靠稳定的传输。
点击查看代码
1 module uart_tx(
2 input clk , //系统时钟
3 input rst_n , //系统复位,低有效
4 input uart_tx_en , //UART 的发送使能
5 input [7:0] uart_tx_data, //UART 要发送的数据
6 output reg uart_txd , //UART 发送端口
7 output reg uart_tx_busy //发送忙状态信号
8 );
9
10 //parameter define
11 parameter CLK_FREQ = 100000000; //系统时钟频率
12 parameter UART_BPS = 115200 ; //串口波特率
13 localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次
14
15 //reg define
16 reg [7:0] tx_data_t; //发送数据寄存器
17 reg [3:0] tx_cnt ; //发送数据计数器
18 reg [15:0] baud_cnt ; //波特率计数器
19
20 //*****************************************************
21 //** main code
22 //*****************************************************
23
24 //当 uart_tx_en 为高时,寄存输入的并行数据,并拉高 BUSY 信号
25 always @(posedge clk or negedge rst_n) begin
26 if(!rst_n) begin
27 tx_data_t <= 8'b0;
28 uart_tx_busy <= 1'b0;
29 end
30 //发送使能时,寄存要发送的数据,并拉高 BUSY 信号
31 else if(uart_tx_en) begin
32 tx_data_t <= uart_tx_data;
33 uart_tx_busy <= 1'b1;
34 end
35 //当计数到停止位结束时,停止发送过程
36 else if(tx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16) begin
37 tx_data_t <= 8'b0; //清空发送数据寄存器
38 uart_tx_busy <= 1'b0; //并拉低 BUSY 信号
39 end
40 else begin
41 tx_data_t <= tx_data_t;
42 uart_tx_busy <= uart_tx_busy;
43 end
44 end
45
46 //波特率的计数器赋值
47 always @(posedge clk or negedge rst_n) begin
48 if(!rst_n)
49 baud_cnt <= 16'd0;
50 //当处于发送过程时,波特率计数器(baud_cnt)进行循环计数
51 else if(uart_tx_busy) begin
52 if(baud_cnt < BAUD_CNT_MAX - 1'b1)
53 baud_cnt <= baud_cnt + 16'b1;
54 else
55 baud_cnt <= 16'd0; //计数达到一个波特率周期后清零
56 end
57 else
58 baud_cnt <= 16'd0; //发送过程结束时计数器清零
59 end
60
61 //tx_cnt 进行赋值
62 always @(posedge clk or negedge rst_n) begin
63 if(!rst_n)
64 tx_cnt <= 4'd0;
65 else if(uart_tx_busy) begin //处于发送过程时 tx_cnt 才进行计数
66 if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时
67 tx_cnt <= tx_cnt + 1'b1; //发送数据计数器加 1
68 else
69 tx_cnt <= tx_cnt;
70 end
71 else
72 tx_cnt <= 4'd0; //发送过程结束时计数器清零
73 end
74
75 //根据 tx_cnt 来给 uart 发送端口赋值
76 always @(posedge clk or negedge rst_n) begin
77 if(!rst_n)
78 uart_txd <= 1'b1;
79 else if(uart_tx_busy) begin
80 case(tx_cnt)
81 4'd0 : uart_txd <= 1'b0 ; //起始位
82 4'd1 : uart_txd <= tx_data_t[0]; //数据位最低位
83 4'd2 : uart_txd <= tx_data_t[1];
84 4'd3 : uart_txd <= tx_data_t[2];
85 4'd4 : uart_txd <= tx_data_t[3];
86 4'd5 : uart_txd <= tx_data_t[4];
87 4'd6 : uart_txd <= tx_data_t[5];
88 4'd7 : uart_txd <= tx_data_t[6];
89 4'd8 : uart_txd <= tx_data_t[7]; //数据位最高位
90 4'd9 : uart_txd <= 1'b1 ; //停止位
91 default : uart_txd <= 1'b1;
92 endcase
93 end
94 else
95 uart_txd <= 1'b1; //空闲时发送端口为高电平
96 end
97
98 endmodule
最后顶层代码和测试代码如下:
点击查看代码
1 module uart_loopback(
2 input sys_clk_p, //系统差分输入时钟
3 input sys_clk_n, //系统差分输入时钟
4 input sys_rst_n, //系外部复位信号,低有效
5
6 //UART 端口
7 input uart_rxd , //UART 接收端口
8 output uart_txd //UART 发送端口
9 );
10
11 //parameter define
12 parameter CLK_FREQ = 100000000; //定义系统时钟频率
13 parameter UART_BPS = 115200 ; //定义串口波特率
14
15 //wire define
16 wire sys_clk ; //单端系统时钟
17 wire uart_rx_done; //UART 接收完成信号
18 wire [7:0] uart_rx_data; //UART 接收数据
19
20 //*****************************************************
21 //** main code
22 //*****************************************************
23
24 //转换差分信号
25 IBUFDS diff_clock
26 (
27 .I (sys_clk_p), //系统差分输入时钟
28 .IB(sys_clk_n), //系统差分输入时钟
29 .O (sys_clk) //输出系统时钟
30 );
31
32 //串口接收模块
33 uart_rx #(
34 .CLK_FREQ (CLK_FREQ),
35 .UART_BPS (UART_BPS)
36 )
37 u_uart_rx(
38 .clk (sys_clk ),
39 .rst_n (sys_rst_n ),
40 .uart_rxd (uart_rxd ),
41 .uart_rx_done (uart_rx_done),
42 .uart_rx_data (uart_rx_data)
43 );
44
45 //串口发送模块
46 uart_tx #(
47 .CLK_FREQ (CLK_FREQ),
48 .UART_BPS (UART_BPS)
49 )
50 u_uart_tx(
51 .clk (sys_clk ),
52 .rst_n (sys_rst_n ),
53 .uart_tx_en (uart_rx_done),
54 .uart_tx_data (uart_rx_data),
55 .uart_txd (uart_txd ),
56 .uart_tx_busy ( )
57 );
58
59 endmodule
点击查看代码
1 `timescale 1ns/1ns //仿真的单位/仿真的精度
2
3 module tb_uart_loopback();
4
5 //reg define
6 reg sys_clk_p; //差分时钟
7 reg sys_clk_n; //差分时钟
8 reg sys_rst_n; //复位信号
9 reg uart_rxd ; //UART 接收端口
10
11 //wire define
12 wire uart_txd ; //UART 发送端口
13
14 //*****************************************************
15 //** main code
16 //*****************************************************
17
18 //发送 8'h55 8'b0101_0101
19 initial begin
20 sys_clk_p <= 1'b0;
21 sys_clk_n <= 1'b1;
22 sys_rst_n <= 1'b0;
23 uart_rxd <= 1'b1;
24 #200
25 sys_rst_n <= 1'b1;
26 #1000
27 uart_rxd <= 1'b0; //起始位
28 #8680
29 uart_rxd <= 1'b1; //D0
30 #8680
31 uart_rxd <= 1'b0; //D1
32 #8680
33 uart_rxd <= 1'b1; //D2
34 #8680
35 uart_rxd <= 1'b0; //D3
36 #8680
37 uart_rxd <= 1'b1; //D4
38 #8680
39 uart_rxd <= 1'b0; //D5
40 #8680
41 uart_rxd <= 1'b1; //D6
42 #8680
43 uart_rxd <= 1'b0; //D7
44 #8680
45 uart_rxd <= 1'b1; //停止位
46 #8680
47 uart_rxd <= 1'b1; //空闲状态
48 end
49
50 //100Mhz 的时钟,周期则为 1/100Mhz=10ns,所以每 5ns,电平取反一次
51 always #5 sys_clk_p = ~ sys_clk_p;
52 always #5 sys_clk_n = ~ sys_clk_n;
53
54 //例化顶层模块
55 uart_loopback u_uart_loopback(
56 .sys_clk_p (sys_clk_p),
57 .sys_clk_n (sys_clk_n),
58 .sys_rst_n (sys_rst_n),
59 .uart_rxd (uart_rxd ),
60 .uart_txd (uart_txd )
61 );
62
63 endmodule