【正点原子FPGA连载】第十六章 UART串口通信实验--摘自【正点原子】开拓者 FPGA 开发指南
本帖最后由 正点原子 于 2020-10-23 11:13 编辑1)实验平台:正点原子开拓者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=579749209820
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-281143-1-1.html
4)本实例源码下载:
5)对正点原子FPGA感兴趣的同学可以加群讨论:712557122点击加入:
6)关注正点原子公众号,获取最新资料更新:
http://www.openedv.com/data/attachment/forum/201905/17/203429z6c3os33t8albi33.png
第十六章 UART串口通信实验
串口是“串行接口”的简称,即采用串行通信方式的接口。串行通信将数据字节分成一位一位的形式在一条数据线上逐个传送,其特点是通信线路简单,但传输速度较慢。因此串口广泛应用于嵌入式、工业控制等领域中对数据传输速度要求不高的场合。本章我们将使用FPGA开发板上的UART串口完成上位机与FPGA的通信。
本章包括以下几个部分:
1
1.1 UART串口简介
1.2 实验任务
1.3 硬件设计
1.4 程序设计
1.5 下载验证
1.1 UART串口简介
串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。UART在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图 16.1.1所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时,对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数。
图 16.1.1 异步串行通信数据格式
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接口标准出现较早,可实现全双工工作方式,即数据发送和接收可以同时进行。在传输距离较短时(不超过15m),RS232是串行通信最常用的接口标准,本章主要介绍针对RS-232标准的UART串口通信。
RS-232标准的串口最常见的接口类型为DB9,样式如图 16.1.2所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点的台式机都没有串口,它们一般通过USB转串口线(图 16.1.3)来实现与外部设备的串口通信。
图 16.1.2 DB9接口
图 16.1.3 USB串口线
DB9接口定义以及各引脚功能说明如图 16.1.4所示,我们一般只用到其中的2(RXD)、3(TXD)、5(GND)引脚,其他引脚在普通串口模式下一般不使用,如果大家想了解,可以自行百度下。
图 16.1.4 DB9接口定义
1.2 实验任务
本节实验任务是上位机通过串口调试助手发送数据给FPGA,FPGA通过串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。
1.3 硬件设计
我们的开拓者FPGA开发板上有两种串口,一种是DB9接口类型的RS232串口,另一种是USB串口。
RS232串口部分的原理图如图 16.3.1所示。由于FPGA串口输入输出引脚为TTL电平,用3.3V代表逻辑“1”,0V代表逻辑“0”;而计算机串口采用RS-232电平,它是负逻辑电平,即-15V~-5V代表逻辑“1”,+5V~+15V代表逻辑“0”。因此当计算机与FPGA通信时,需要加电平转换芯片SP3232,实现RS232电平与TTL电平的转换。
图 16.3.1 RS232串口
这里需要注意的是,由上图可知,SP3232芯片端口的U2_RX和U2_TX并没有直接和FPGA的引脚相连接,而是连接到开发板的P2口,RS232串口和RS485串口共用P2口的UART2_TX和UART2_RX,UART2_TX和UART2_RX是直接和FPGA的引脚相连接的,这样的设计方式实现了有限IO的多种复用功能。因此,在做RS232的通信实验时,需要使用杜邦线或者跳帽将U2_RX和UART2_TX连接在一起,U2_TX和UART2_RX连接在一起。
由于DB9接口类型的RS232串口占用空间较大,很多系统已经选择USB转TTL方案,利用CH340芯片实现USB总线转UART功能,通过MiniUSB接口实现与上位机通信。USB串口原理图如图 16.3.2所示:
图 16.3.2 USB串口
图 16.3.3 USB串口选择口
由上图中的图 16.3.2和图 16.3.3可知,CH340芯片端口的CH340_TXD和CH340_RXD同样没有直接和FPGA的引脚相连接,而是连接到P9口,而P9口的UART1_RX和UART1_TX直接和FPGA的引脚相连接。这样设计的好处就是,在未使用USB串口做通信实验时,P9口的UART1_RX和UART1_TX可以当成普通的扩展口来使用。因此,在使用USB串口做通信实验时,需要使用杜邦线或者跳帽将CH340_TXD和UART1_RX连接在一起,CH340_RXD和UART1_TX连接在一起。
本实验中,系统时钟、按键复位以及USB串口接收发送端口的管脚分配如下表所示:
表 16.3.1 串口通信实验管脚分配
1.4 程序设计
根据实验任务,我们不难想象本系统应该有一个串口接收模块,还要有一个发送模块,然后在顶层把接收模块收到的数据连接到发送模块。由此画出系统总体框架如图 16.4.1所示。在FPGA内部实现串口接收与串口发送模块,串口接收模块接收上位机发送的数据,然后通过串口发送模块将数据发回上位机,实现串口数据环回。
图 16.4.1 系统框图
在编写代码之前,我们首先要确定串口通信的数据格式及波特率。在这里我们选择串口比较常用的一种模式,数据位为8位,停止位为1位,无校验位,波特率为115200bps。则传输一帧数据的时序图如图 16.4.2所示:
图 16.4.2 串口通信时序图
由系统总体框图可知,FPGA部分包括三个模块,顶层模块(uart_top)、接收模块(uart_recv)和发送模块(uart_send)。其中在顶层模块中完成对另外两个模块的例化。
各模块端口及信号连接如图 16.4.3所示:
图 16.4.3 顶层模块原理图
uart_recv为串口接收模块,从串口接收端口uart_rxd接收上位机发送的串行数据,并在一帧数据(8位)接收结束后给出通知信号uart_done;uart_send为串口发送模块,以uart_done为发送使能信号,将接收到的数据uart_data通过串口发送端口uart_txd发送出去。
顶层模块的代码如下:
1moduleuart_top(
2 input sys_clk, //外部50M时钟
3 input sys_rst_n, //外部复位信号,低有效
4 //uart接口
5 input uart_rxd, //UART接收端口
6 output uart_txd //UART发送端口
7 );
8
9//parameter define
10 parameter CLK_FREQ = 50000000; //定义系统时钟频率
11 parameter UART_BPS = 115200; //定义串口波特率
12
13 //wire define
14 wire uart_en_w; //UART发送使能
15 wire uart_data_w; //UART发送数据
16 wire clk_1m_w; //1MHz时钟,用于SignalTap调试
17
18 //*****************************************************
19 //** main code
20 //*****************************************************
21clk_div u_pll( //时钟分频模块,用于调试
22 .inclk0 (sys_clk),
23 .c0 (clk_1m_w)
24 );
25
26uart_recv #( //串口接收模块
27 .CLK_FREQ (CLK_FREQ), //设置系统时钟频率
28 .UART_BPS (UART_BPS)) //设置串口接收波特率
29u_uart_recv(
30 .sys_clk (sys_clk),
31 .sys_rst_n (sys_rst_n),
32
33 .uart_rxd (uart_rxd),
34 .uart_done (uart_en_w),
35 .uart_data (uart_data_w)
36 );
37
38uart_send #( //串口发送模块
39 .CLK_FREQ (CLK_FREQ), //设置系统时钟频率
40 .UART_BPS (UART_BPS)) //设置串口发送波特率
41u_uart_send(
42 .sys_clk (sys_clk),
43 .sys_rst_n (sys_rst_n),
44
45 .uart_en (uart_en_w),
46 .uart_din (uart_data_w),
47 .uart_txd (uart_txd)
48 );
49
50 endmodule
需要注意的是,顶层模块中第10、11行定义了两个变量:系统时钟频率CLK_FREQ与串口波特率UART_BPS,使用时根据不同的系统时钟频率以及所需要的串口波特率设置这两个变量。我们可以尝试将串口波特率UART_BPS设置为其他值(如9600),在模块例化时会将这个变量传递到串口接收与发送模块中,从而实现不同速率的串口通信。
串口接收模块的代码如下所示:
1 moduleuart_recv(
2 input sys_clk, //系统时钟
3 input sys_rst_n, //系统复位,低电平有效
4
5 input uart_rxd, //UART接收端口
6 outputreg uart_done, //接收一帧数据完成标志信号
7 outputreg uart_data //接收的数据
8 );
9
10//parameter define
11parameterCLK_FREQ =50000000; //系统时钟频率
12parameterUART_BPS =9600; //串口波特率
13localparamBPS_CNT=CLK_FREQ/UART_BPS; //对系统时钟计数BPS_CNT次
14
15//reg define
16reg uart_rxd_d0;
17reg uart_rxd_d1;
18reg clk_cnt; //系统时钟计数器
19reg [ 3:0] rx_cnt; //接收数据计数器
20reg rx_flag; //接收过程标志信号
21reg [ 7:0] rxdata; //接收数据寄存器
22
23//wire define
24wire start_flag;
25
26//*****************************************************
27//** main code
28//*****************************************************
29//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
30assignstart_flag =uart_rxd_d1 & (~uart_rxd_d0);
31
32//对UART接收端口的数据延迟两个时钟周期
33always @(posedgesys_clk or negedge sys_rst_n) begin
34 if(!sys_rst_n)begin
35 uart_rxd_d0 <= 1'b0;
36 uart_rxd_d1 <= 1'b0;
37 end
38 else begin
39 uart_rxd_d0<=uart_rxd;
40 uart_rxd_d1<=uart_rxd_d0;
41 end
42end
43
44//当脉冲信号start_flag到达时,进入接收过程
45always@(posedgesys_clk or negedgesys_rst_n) begin
46 if(!sys_rst_n)
47 rx_flag <=1'b0;
48 else begin
49 if(start_flag) //检测到起始位
50 rx_flag <= 1'b1; //进入接收过程,标志位rx_flag拉高
51 elseif((rx_cnt== 4'd9)&&(clk_cnt ==BPS_CNT/2))
52 rx_flag <= 1'b0; //计数到停止位中间时,停止接收过程
53 else
54 rx_flag <= rx_flag;
55 end
56end
57
58//进入接收过程后,启动系统时钟计数器与接收数据计数器
59always@(posedgesys_clk or negedgesys_rst_n) begin
60 if(!sys_rst_n)begin
61 clk_cnt <=16'd0;
62 rx_cnt<= 4'd0;
63 end
64 elseif (rx_flag ) begin //处于接收过程
65 if(clk_cnt <BPS_CNT - 1) begin
66 clk_cnt <= clk_cnt +1'b1;
67 rx_cnt<=rx_cnt;
68 end
69 elsebegin
70 clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
71 rx_cnt<=rx_cnt + 1'b1; //此时接收数据计数器加1
72 end
73 end
74 elsebegin //接收过程结束,计数器清零
75 clk_cnt <= 16'd0;
76 rx_cnt<= 4'd0;
77 end
78end
79
80//根据接收数据计数器来寄存uart接收端口数据
81always @(posedgesys_clk or negedge sys_rst_n) begin
82 if( !sys_rst_n)
83 rxdata <=8'd0;
84 elseif(rx_flag) //系统处于接收过程
85 if(clk_cnt ==BPS_CNT/2) begin //系统时钟计数器计数到数据位中间
86 case( rx_cnt )
87 4'd1: rxdata <= uart_rxd_d1; //寄存数据位最低位
88 4'd2: rxdata <= uart_rxd_d1;
89 4'd3: rxdata <= uart_rxd_d1;
90 4'd4: rxdata <= uart_rxd_d1;
91 4'd5: rxdata <= uart_rxd_d1;
92 4'd6: rxdata <= uart_rxd_d1;
93 4'd7: rxdata <= uart_rxd_d1;
94 4'd8: rxdata <= uart_rxd_d1; //寄存数据位最高位
95 default:;
96 endcase
97 end
98 else
99 rxdata <= rxdata;
100 else
101 rxdata <=8'd0;
102 end
103
104 //数据接收完毕后给出标志信号并寄存输出接收到的数据
105 always @(posedge sys_clk ornegedge sys_rst_n) begin
106 if (!sys_rst_n)begin
107 uart_data <=8'd0;
108 uart_done <=1'b0;
109 end
110 elseif(rx_cnt== 4'd9) begin //接收数据计数器计数到停止位时
111 uart_data <=rxdata; //寄存输出接收到的数据
112 uart_done <=1'b1; //并将接收完成标志位拉高
113 end
114 else begin
115 uart_data <=8'd0;
116 uart_done <=1'b0;
117 end
118 end
119
120 endmodule
串口接收模块程序中29至42行是一个经典的边沿检测电路,通过检测串口接收端uart_rxd的下降沿来捕获起始位。一旦检测到起始位,输出一个时钟周期的脉冲start_flag,并进入串口接收过程。串口接收状态用rx_flag来标志,rx_flag为高标志着串口接收过程正在进行,此时启动系统时钟计数器clk_cnt与接收数据计数器rx_cnt。
由第13行的公式BPS_CNT =CLK_FREQ/UART_BPS可知,BPS_CNT为当前波特率下,串口传输一位所需要的系统时钟周期数。因此clk_cnt从零计数到BPS_CN-1时,串口刚好完成一位数据的传输。由于接收数据计数器rx_cnt在每次clk_cnt计数到BPS_CN-1时加1,因此由rx_cnt的值可以判断串口当前传输的是第几位数据。第80行至第102行就是根据clk_cnt的值将uart接收端口的数据寄存到接收数据寄存器对应的位,从而实现接收数据的串并转换。其中第85行选择clk_cnt计数至BPS_CNT/2时寄存接收端口数据,是因为计数到数据中间时的采样结果最稳定。
程序中需要额外注意的地方是串口接收过程结束条件的判定,由第51行可知,在计数到停止位中间时,标志位rx_flag就已经拉低。这样做是因为虽然此时一帧数据传输还没有完成(停止位只传送到一半),但是数据位已经寄存完毕。而在连续接收数据时,提前半个波特率周期结束接收过程可以为检测下一帧数据的起始位留出充足的时间。
图16.4.4为接收过程中SignalTap抓取的波形图,上位机通过串口发送16进制数5A,从图中可以看到接收模块能够正确接收串口数据并完成串并转换。
图 16.4.4 接收过程SignalTap波形图
串口发送模块与串口接收模块异曲同工,代码中也给出了详尽的注释,此处不再赘述。
串口发送模块代码如下所示:
1 moduleuart_send(
2 input sys_clk, //系统时钟
3 input sys_rst_n, //系统复位,低电平有效
4
5 input uart_en, //发送使能信号
6 inputuart_din, //待发送数据
7 outputreg uart_txd //UART发送端口
8 );
9
10//parameter define
11parameterCLK_FREQ =50000000; //系统时钟频率
12parameterUART_BPS =9600; //串口波特率
13localparamBPS_CNT=CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次
14
15//reg define
16reg uart_en_d0;
17reg uart_en_d1;
18reg clk_cnt; //系统时钟计数器
19reg [ 3:0] tx_cnt; //发送数据计数器
20reg tx_flag; //发送过程标志信号
21reg [ 7:0] tx_data; //寄存发送数据
22
23//wire define
24wire en_flag;
25
26//*****************************************************
27//** main code
28//*****************************************************
29//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
30assignen_flag = (~uart_en_d1) &uart_en_d0;
31
32//对发送使能信号uart_en延迟两个时钟周期
33always@(posedgesys_clk or negedgesys_rst_n) begin
34 if(!sys_rst_n)begin
35 uart_en_d0 <= 1'b0;
36 uart_en_d1 <= 1'b0;
37 end
38 elsebegin
39 uart_en_d0 <= uart_en;
40 uart_en_d1 <= uart_en_d0;
41 end
42end
43
44//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
45always@(posedgesys_clk or negedgesys_rst_n) begin
46 if(!sys_rst_n)begin
47 tx_flag <=1'b0;
48 tx_data <=8'd0;
49 end
50 elseif (en_flag) begin //检测到发送使能上升沿
51 tx_flag <= 1'b1; //进入发送过程,标志位tx_flag拉高
52 tx_data <= uart_din; //寄存待发送的数据
53 end
54 else
55 if((tx_cnt ==4'd9)&&(clk_cnt== BPS_CNT/2))
56 begin //计数到停止位中间时,停止发送过程
57 tx_flag <= 1'b0; //发送过程结束,标志位tx_flag拉低
58 tx_data <= 8'd0;
59 end
60 elsebegin
61 tx_flag <=tx_flag;
62 tx_data <= tx_data;
63 end
64end
65
66//进入发送过程后,启动系统时钟计数器与发送数据计数器
67always@(posedgesys_clk or negedgesys_rst_n) begin
68 if(!sys_rst_n)begin
69 clk_cnt <=16'd0;
70 tx_cnt<= 4'd0;
71 end
72 elseif (tx_flag) begin //处于发送过程
73 if(clk_cnt <BPS_CNT - 1) begin
74 clk_cnt <= clk_cnt +1'b1;
75 tx_cnt<=tx_cnt;
76 end
77 elsebegin
78 clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
79 tx_cnt<=tx_cnt + 1'b1; //此时发送数据计数器加1
80 end
81 end
82 elsebegin //发送过程结束
83 clk_cnt <=16'd0;
84 tx_cnt<= 4'd0;
85 end
86end
87
88//根据发送数据计数器来给uart发送端口赋值
89always@(posedgesys_clk or negedgesys_rst_n) begin
90 if (!sys_rst_n)
91 uart_txd <=1'b1;
92 elseif (tx_flag)
93 case(tx_cnt)
94 4'd0: uart_txd <=1'b0; //起始位
95 4'd1: uart_txd <=tx_data; //数据位最低位
96 4'd2: uart_txd <=tx_data;
97 4'd3: uart_txd <=tx_data;
98 4'd4: uart_txd <=tx_data;
99 4'd5: uart_txd <=tx_data;
100 4'd6: uart_txd <=tx_data;
101 4'd7: uart_txd <=tx_data;
102 4'd8: uart_txd <=tx_data; //数据位最高位
103 4'd9: uart_txd <=1'b1; //停止位
104 default: ;
105 endcase
106 else
107 uart_txd <=1'b1; //空闲时发送端口为高电平
108 end
109
110 endmodule
图16.4.5为发送过程中SignalTap抓取的波形图,发送模块将16进制数5A发送到uart_txd端口,从图中可以看到发送模块能够完成并串转换并正确发送串口数据。
图 16.4.5 发送过程SignalTap波形图
1.5 下载验证
首先我们打开串口工程,在工程所在的路径下打开uart_top/par文件夹,在里面找到“uart_top.qpf”并双击打开。注意工程所在的路径名只能由字母、数字以及下划线组成,不能出现中文、空格以及特殊字符等。串口工程打开后如图 16.5.1所示。
图 16.5.1 UART串口工程
工程打开后通过点击工具栏中的“Programmer”图标打开下载界面,通过“Add File”按钮选择串口工程中uart/par/output_files目录下的“uart_top.sof”文件,下载界面如图 16.5.2所示。
图 16.5.2 程序下载界面
接下来我们下载程序,验证上位机与开发板通过USB串口进行串口数据环回功能。
将USB串口线与下载器一端连电脑,另一端与开发板上对应端口连接,并确保两个跳帽均已经连接,如下图所示。然后连接电源线(由于USB线可以给开发板提供电源,这里电源线也可以不连接)并打开电源开关。
图 16.5.3 开拓者USB UART硬件连接实物图
注意上位机第一次使用USB串口线与FPGA开发板连接时,需要安装USB串口驱动。在开发板随附的资料中找到“6_软件资料/1_软件/CH340驱动(USB串口驱动)”的文件夹,双击打开文件夹中的“SETUP.EXE”进行安装,驱动安装界面如图 16.5.4所示。界面中提示INF文件为CH341SER.INF,我们不需要理会(CH341,CH340驱动是共用的),直接点安装即可。
图 16.5.4 USB串口驱动驱动安装界面
开发板电源打开后,在程序下载界面点击“Hardware Setup”, 在弹出的对话框中选择当前的硬件连接为“USB-Blaster”。然后点击“Start”将工程编译完成后得到的sof文件下载到开发板中,如图 16.5.5所示。
图 16.5.5 程序下载完成界面
串口助手是上位机中用于辅助串口调试的小工具,可以选择安装使用开发板随附资料中“6_软件资料/1_软件/友善串口助手”的文件夹中提供的串口助手,也可从网上下载或选择自己常用的串口调试工具。在上位机中打开串口助手,如图 16.5.6所示。
在串口助手中选择与开发板相连接的串口端口,设置波特率为115200,数据位为8,停止位为1,无校验位。然后在发送设置与接收设置中都选择16进制(HEX)或都选择ASCII,最后通过点击“打开”按钮来打开串口。
串口打开后,在发送文本框中输入数据“5A”并点击发送,可以看到串口助手中接收到数据“5A”,接收到的数据与发送的数据一致,程序所实现的串口数据环回功能验证成功。
图 16.5.6 串口助手操作界面
到这里利用开发板上的USB串口与上位机进行串口通信的实验已经完成了。如果大家想通过RS232串口线(或USB转串口线)验证上位机与开发板上的RS232串口通信,在程序中只需要将串口接收发送端口的管脚分配修改为RS232串口对应的FPGA引脚,其余步骤不变。RS232串口的管脚分配为uart_rxd(A12),uart_txd(B12)。由于DB9接口与VGA接口外形相像,如图 16.5.7所示,使用时请注意区分(DB9为9针接口,VGA接口为15针接口)。下图为RS232硬件连接图。
图 16.5.7 开拓者RS323硬件连接图
串口发送例程第55行 if((tx_cnt ==4'd9)&&(clk_cnt== BPS_CNT/2)),BPS_CNT/2这个有问题,连续发送时会造成数据错误,需要改成(clk_cnt == BPS_CNT-1)才能正确传输。接收例程这样写没有问题,究其原因是发送和接受一个是主动一个是被动,发送时若提前将tx_flag清0,会导致接收端在对应时间对数据线采样出错。下面是连续发送0x11时使用官方例程和修改后的效果:
官方例程:
修改后:
页:
[1]