【正点原子FPGA连载】第十九章UART串口通信实验
1)实验平台:正点原子超越者FPGA开发板2)章节摘自【正点原子】超越者之FPGA开发指南
3)购买链接:https://item.taobao.com/item.htm?&id=631660290421
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-chaoyuezhe.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流群:905624739
第十九章UART串口通信实验
串口是“串行接口”的简称,即采用串行通信方式的接口。串行通信将数据字节分成一位一位的形式在一条数据线上逐个传送,其特点是通信线路简单,但传输速度较慢。因此串口广泛应用于嵌入式、工业控制等领域中对数据传输速度要求不高的场合。本章我们将使用超越者开发板上的UART串口完成与上位机的通信。
本章包括以下几个部分:
1919.1简介
19.2实验任务
19.3硬件设计
19.4程序设计
19.5下载验证
19.1简介
串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。UART在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图 19.1.1所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时,对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数。
图 19.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标准的串口最常见的接口类型为DB9,样式如图 19.1.2所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。
图 19.1.2 DB9接口
图 19.1.3 mini USB接口
由于传统的DB9接口体积较大,会占用开发板过多空间,在超越者开发板上我们采用的是mini USB接口,样式如图 19.1.3所示,另一端直接和电脑USB相连。连接示意图如图 19.1.4所示。
图 19.1.4 USB串口通信连接示意图
19.2实验任务
本节实验任务是上位机通过串口调试助手发送数据给FPGA,FPGA通过USB串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。
19.3硬件设计
超越者开发板上USB串口部分的原理图如下所示:
图 19.3.1 串口原理图
原理图中的CH340就是图 19.1.4所示的转换芯片,其中UART1_RXD连接到FPGA芯片的的RX管脚,UART1_TXD连接到FPGA芯片的TX管脚,CH340_P/N分别接到开发板的mini USB插座D+/-上。CH340具体的工作原理是:发送的时候把FPGA的UART1_TXD管脚送出的信号由TTL电平转换为USB差分电平并送到mini USB插座,接收的时候则把mini USB插座送来的USB差分电平转换为TTL电平送给FPGA的UART1_RXD管脚,同时我们通过USB数据线将mini USB插座D+/-直连到PC上相应的USB口的D+/-,当然也包括电源和地,这样就完成了USB串口通信的硬件准备工作。
本实验中,系统时钟、按键复位以及串口的接收、发送端口的管脚分配如下表所示:
表 19.3.1 串口通信实验管脚分配
对应的UCF约束语句如下所示:
# PlanAhead Generated physical constraints
NET "sys_clk" LOC = N8 | IOSTANDARD = "LVCMOS33";
NET "sys_rst_n" LOC = G16 | IOSTANDARD = "LVCMOS33";
NET "uart_rxd" LOC = M12 | IOSTANDARD = "LVCMOS33";
NET "uart_txd" LOC = N11 | IOSTANDARD = "LVCMOS33";
19.4程序设计
根据实验任务,我们不难想象本系统应该有一个串口接收模块,用来接收上位机发送的数据;还要有一个串口发送模块,用于将数据发回上位机;另外还应该有一个对数据进行环回控制的模块,它负责把从串口接收模块接收到的数据送给串口发送模块,以实现串口数据的环回。由此可以画出本次实验的系统框图,如下所示:
图 19.4.1 系统框图
由系统总体框图可知,整个工程包括四个模块,顶层模块、接收模块、发送模块和数据环回模块。其中在顶层模块中完成对另外三个模块的例化:
uart_recv为串口接收模块,从串口接收端口uart_rxd来接收上位机发送的串行数据,并在一帧数据接收结束后给出通知信号uart_done。
uart_send为串口发送模块,以uart_en为发送使能信号。uart_en的上升沿将启动一次串口发送过程,将uart_din接口上的数据通过串口发送端口uart_txd发送出去。
uart_loop模块负责完成串口数据的环回功能。它在uart_recv模块接收完成后,将接收到的串口数据发送到uart_send模块,并通过send_en接口给出一个上升沿,以启动发送过程。
在编写代码之前,我们首先要确定串口通信的数据格式及波特率。在这里我们选择串口比较常用的一种模式,数据位为8位,停止位为1位,无校验位,波特率为115200bps。则传输一帧数据的时序图如下图所示:
图 19.4.2 串口通信时序图
顶层模块的代码如下:
1module uart_loopback_top(
2 input sys_clk, //外部50M时钟
3 input sys_rst_n, //外部复位信号,低有效
4
5 input uart_rxd, //UART接收端口
6 output uart_txd //UART发送端口
7 );
8
9//parameter define
10 parameterCLK_FREQ = 50000000; //定义系统时钟频率
11 parameterUART_BPS = 115200; //定义串口波特率
12
13 //wire define
14 wire uart_recv_done; //UART接收完成
15 wire uart_recv_data; //UART接收数据
16 wire uart_send_en; //UART发送使能
17 wire uart_send_data; //UART发送数据
18 wire uart_tx_busy; //UART发送忙状态标志
19
20 //*****************************************************
21 //** main code
22 //*****************************************************
23
24 //串口接收模块
25 uart_recv #(
26 .CLK_FREQ (CLK_FREQ), //设置系统时钟频率
27 .UART_BPS (UART_BPS)) //设置串口接收波特率
28 u_uart_recv(
29 .sys_clk (sys_clk),
30 .sys_rst_n (sys_rst_n),
31
32 .uart_rxd (uart_rxd),
33 .uart_done (uart_recv_done),
34 .uart_data (uart_recv_data)
35 );
36
37 //串口发送模块
38 uart_send #(
39 .CLK_FREQ (CLK_FREQ), //设置系统时钟频率
40 .UART_BPS (UART_BPS)) //设置串口发送波特率
41 u_uart_send(
42 .sys_clk (sys_clk),
43 .sys_rst_n (sys_rst_n),
44
45 .uart_en (uart_send_en),
46 .uart_din (uart_send_data),
47 .uart_tx_busy (uart_tx_busy),
48 .uart_txd (uart_txd)
49 );
50
51 //串口环回模块
52 uart_loop u_uart_loop(
53 .sys_clk (sys_clk),
54 .sys_rst_n (sys_rst_n),
55
56 .recv_done (uart_recv_done), //接收一帧数据完成标志信号
57 .recv_data (uart_recv_data), //接收的数据
58
59 .tx_busy (uart_tx_busy), //发送忙状态标志
60 .send_en (uart_send_en), //发送使能信号
61 .send_data (uart_send_data) //待发送数据
62 );
63
64 endmodule
在顶层模块中完成了对其余各个子模块的例化。需要注意的是,顶层模块中第10、11行定义了两个变量:系统时钟频率CLK_FREQ与串口波特率UART_BPS,使用时可以根据不同的系统时钟频率以及所需要的串口波特率设置这两个变量。我们可以尝试将串口波特率UART_BPS设置为其他值(如9600),在模块例化时会将这个变量传递到串口接收与发送模块中,从而实现不同速率的串口通信。
串口接收模块的代码如下所示:
1 module uart_recv(
2 input sys_clk, //系统时钟
3 input sys_rst_n, //系统复位,低电平有效
4
5 input uart_rxd, //UART接收端口
6 outputreg uart_done, //接收一帧数据完成标志
7 outputreg rx_flag, //接收过程标志信号
8 outputreg rx_cnt, //接收数据计数器
9 outputreg rxdata,
10 outputreg uart_data //接收的数据
11 );
12
13//parameter define
14parameter CLK_FREQ = 50000000; //系统时钟频率
15parameter UART_BPS = 9600; //串口波特率
16localparamBPS_CNT= CLK_FREQ/UART_BPS; //为得到指定波特率,
17 //需要对系统时钟计数BPS_CNT次
18//reg define
19reg uart_rxd_d0;
20reg uart_rxd_d1;
21reg clk_cnt; //系统时钟计数器
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 @(posedge sys_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 @(posedge sys_clk or negedge sys_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 //计数到停止位中间时,停止接收过程
52 else if((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))
53 rx_flag <= 1'b0; //接收过程结束,标志位rx_flag拉低
54 else
55 rx_flag <= rx_flag;
56 end
57end
58
59//进入接收过程后,启动系统时钟计数器
60always @(posedge sys_clk or negedge sys_rst_n) begin
61 if (!sys_rst_n)
62 clk_cnt <= 16'd0;
63 else if ( rx_flag ) begin //处于接收过程
64 if (clk_cnt < BPS_CNT - 1)
65 clk_cnt <= clk_cnt + 1'b1;
66 else
67 clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
68 end
69 else
70 clk_cnt <= 16'd0; //接收过程结束,计数器清零
71end
72
73//进入接收过程后,启动接收数据计数器
74always @(posedge sys_clk or negedge sys_rst_n) begin
75 if (!sys_rst_n)
76 rx_cnt<= 4'd0;
77 else if ( rx_flag ) begin //处于接收过程
78 if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期
79 rx_cnt <= rx_cnt + 1'b1; //此时接收数据计数器加1
80 else
81 rx_cnt <= rx_cnt;
82 end
83 else
84 rx_cnt<= 4'd0; //接收过程结束,计数器清零
85end
86
87//根据接收数据计数器来寄存uart接收端口数据
88always @(posedge sys_clk or negedge sys_rst_n) begin
89 if ( !sys_rst_n)
90 rxdata <= 8'd0;
91 else if(rx_flag) //系统处于接收过程
92 if (clk_cnt == BPS_CNT/2) begin //判断系统时钟计数器计数到数据位中间
93 case ( rx_cnt )
94 4'd1 : rxdata <= uart_rxd_d1; //寄存数据位最低位
95 4'd2 : rxdata <= uart_rxd_d1;
96 4'd3 : rxdata <= uart_rxd_d1;
97 4'd4 : rxdata <= uart_rxd_d1;
98 4'd5 : rxdata <= uart_rxd_d1;
99 4'd6 : rxdata <= uart_rxd_d1;
100 4'd7 : rxdata <= uart_rxd_d1;
101 4'd8 : rxdata <= uart_rxd_d1; //寄存数据位最高位
102 default:;
103 endcase
104 end
105 else
106 rxdata <= rxdata;
107 else
108 rxdata <= 8'd0;
109 end
110
111 //数据接收完毕后给出标志信号并寄存输出接收到的数据
112 always @(posedge sys_clk or negedge sys_rst_n) begin
113 if (!sys_rst_n) begin
114 uart_data <= 8'd0;
115 uart_done <= 1'b0;
116 end
117 else if(rx_cnt == 4'd9) begin //接收数据计数器计数到停止位时
118 uart_data <= rxdata; //寄存输出接收到的数据
119 uart_done <= 1'b1; //并将接收完成标志位拉高
120 end
121 else begin
122 uart_data <= 8'd0;
123 uart_done <= 1'b0;
124 end
125 end
126
127 endmodule
串口接收模块程序中29至42行是一个经典的边沿检测电路,通过检测串口接收端uart_rxd的下降沿来捕获起始位。一旦检测到起始位,输出一个时钟周期的脉冲start_flag,并进入串口接收过程。串口接收状态用rx_flag来标志,rx_flag为高标志着串口接收过程正在进行,此时启动系统时钟计数器clk_cnt与接收数据计数器rx_cnt。这里我们要明白,uart_rxd信号对于介绍模块来说它何时出现并没有规律,也就是说这是个异步信号,异步信号会带来亚稳态,因此对于异步信号我们需要进行异步处理,常用的异步处理方式是打拍处理,uart_rxd_d0,是打第一拍,uart_rxd_d1打第二拍,通常打两拍就基本上就能避免亚稳态问题。
由第16行的公式BPS_CNT = CLK_FREQ/UART_BPS可知,BPS_CNT为当前波特率下,串口传输一位所需要的系统时钟周期数。因此clk_cnt从零计数到BPS_CN-1时,串口刚好完成一位数据的传输。由于接收数据计数器rx_cnt在每次clk_cnt计数到BPS_CN-1时加1,因此由rx_cnt的值可以判断串口当前传输的是第几位数据。第87行至第109行就是根据clk_cnt的值将uart接收端口的数据寄存到接收数据寄存器对应的位,从而实现接收数据的串并转换。其中第92行选择clk_cnt计数至BPS_CNT/2时寄存接收端口数据,是因为计数到数据中间时的采样结果最稳定。
程序中需要额外注意的地方是串口接收过程结束条件的判定,由第52行可知,在计数到停止位中间时,标志位rx_flag就已经拉低。这样做是因为虽然此时一帧数据传输还没有完成(停止位只传送到一半),但是数据位已经寄存完毕。而在连续接收数据时,提前半个波特率周期结束接收过程可以为检测下一帧数据的起始位留出充足的时间。
我们使用上位机通过串口向开发板发送16进制数55,在串口接收过程中Chipscope抓取的波形图如下所示:
图 19.4.3 串口接收过程波形图
图 19.4.3中红色的触发线标识出了串口接收端uart_rxd的起始位,在整个接收过程中rx_flag保持为高电平,同时rx_cnt对串口数据进行计数。当rx_cnt计数到9时,串口数据接收完成,uart_done拉高,同时uart_data给出接收到的数据。从图中可以看到,接收模块能够正确接收串口数据并完成串并转换。
串口发送模块代码如下所示:
1 module uart_send(
2 input sys_clk, //系统时钟
3 input sys_rst_n, //系统复位,低电平有效
4
5 input uart_en, //发送使能信号
6 inputuart_din, //待发送数据
7 output uart_tx_busy, //发送忙状态标志
8 output en_flag ,
9 outputreg tx_flag, //发送过程标志信号
10 outputreg [ 7:0] tx_data, //寄存发送数据
11 outputreg [ 3:0] tx_cnt, //发送数据计数器
12 outputreg uart_txd //UART发送端口
13 );
14
15//parameter define
16parameterCLK_FREQ = 50000000; //系统时钟频率
17parameterUART_BPS = 9600; //串口波特率
18localparamBPS_CNT= CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次
19
20//reg define
21reg uart_en_d0;
22reg uart_en_d1;
23reg clk_cnt; //系统时钟计数器
24
25//wire define
26wire en_flag;
27
28//*****************************************************
29//** main code
30//*****************************************************
31//在串口发送过程中给出忙状态标志
32assign uart_tx_busy = tx_flag;
33
34//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
35assign en_flag = (~uart_en_d1) & uart_en_d0;
36
37//对发送使能信号uart_en延迟两个时钟周期
38always @(posedge sys_clk or negedge sys_rst_n) begin
39 if (!sys_rst_n) begin
40 uart_en_d0 <= 1'b0;
41 uart_en_d1 <= 1'b0;
42 end
43 else begin
44 uart_en_d0 <= uart_en;
45 uart_en_d1 <= uart_en_d0;
46 end
47end
48
49//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
50always @(posedge sys_clk or negedge sys_rst_n) begin
51 if (!sys_rst_n) begin
52 tx_flag <= 1'b0;
53 tx_data <= 8'd0;
54 end
55 else if (en_flag) begin //检测到发送使能上升沿
56 tx_flag <= 1'b1; //进入发送过程,标志位tx_flag拉高
57 tx_data <= uart_din; //寄存待发送的数据
58 end
59 //计数到停止位结束时,停止发送过程
60 else if ((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT - (BPS_CNT/16))) begin
61 tx_flag <= 1'b0; //发送过程结束,标志位tx_flag拉低
62 tx_data <= 8'd0;
63 end
64 else begin
65 tx_flag <= tx_flag;
66 tx_data <= tx_data;
67 end
68end
69
70//进入发送过程后,启动系统时钟计数器
71always @(posedge sys_clk or negedge sys_rst_n) begin
72 if (!sys_rst_n)
73 clk_cnt <= 16'd0;
74 else if (tx_flag) begin //处于发送过程
75 if (clk_cnt < BPS_CNT - 1)
76 clk_cnt <= clk_cnt + 1'b1;
77 else
78 clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
79 end
80 else
81 clk_cnt <= 16'd0; //发送过程结束
82end
83
84//进入发送过程后,启动发送数据计数器
85always @(posedge sys_clk or negedge sys_rst_n) begin
86 if (!sys_rst_n)
87 tx_cnt <= 4'd0;
88 else if (tx_flag) begin //处于发送过程
89 if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期
90 tx_cnt <= tx_cnt + 1'b1; //此时发送数据计数器加1
91 else
92 tx_cnt <= tx_cnt;
93 end
94 else
95 tx_cnt<= 4'd0; //发送过程结束
96end
97
98//根据发送数据计数器来给uart发送端口赋值
99always @(posedge sys_clk or negedge sys_rst_n) begin
100 if (!sys_rst_n)
101 uart_txd <= 1'b1;
102 else if (tx_flag)
103 case(tx_cnt)
104 4'd0: uart_txd <= 1'b0; //起始位
105 4'd1: uart_txd <= tx_data; //数据位最低位
106 4'd2: uart_txd <= tx_data;
107 4'd3: uart_txd <= tx_data;
108 4'd4: uart_txd <= tx_data;
109 4'd5: uart_txd <= tx_data;
110 4'd6: uart_txd <= tx_data;
111 4'd7: uart_txd <= tx_data;
112 4'd8: uart_txd <= tx_data; //数据位最高位
113 4'd9: uart_txd <= 1'b1; //停止位
114 default: ;
115 endcase
116 else
117 uart_txd <= 1'b1; //空闲时发送端口为高电平
118 end
119
120 endmodule
串口发送模块与串口接收模块异曲同工,代码中也给出了详尽的注释,此处不再赘述。需要注意的是,在程序的60行,我们将tx_flag提前1/16个停止位拉低,是为了确保发送模块发送数据的时间略小于接收模块接收数据的时间,否则当连续传输大量数据时,发送数据的时间会不断累积,最终导致在做串口环回实验时丢失数据。
尽管串口发送数据只是接收数据的反过程,理论上在传输的时间上是一致的,考虑到我们模块里计算波特率会有较小的偏差,并且串口对端的通信设备(如电脑等)收发数据的波特率同样可能会出现较小的偏差,因此这里为了确保环回实验的成功,这里将发送模块的停止位略微提前结束。
需要说明的是,较小偏差的波特率在串口通信时是允许的,同样可以保证数据可靠稳定的传输。
另外我们在代码的第32行将用于标志串口发送过程的tx_flag信号赋值给uart_tx_busy,并通过模块端口输出。这样其他模块就可以通过检测uart_tx_busy信号是否为低电平,从而判断串口发送模块是否处于空闲状态。若uart_tx_busy为高电平,那么uart_send正处于发送过程,外部模块需要等待当前发送过程结束之后,才能通过uart_en信号的上升沿来启动新的发送过程。
图 19.4.4为串口发送过程中Chipscope抓取的波形图,我们使用开发板通过串口向上位机向发送16进制数55。图中红色的触发线标识出了串口发送使能信号uart_en的上升沿。在检测到uart_en的上升沿后,en_flag会拉高一个时钟周期,此时将uart_din端口上的待发送数据寄存到tx_data中,并进入串口发送过程。
在整个发送过程中tx_flag保持为高电平,tx_cnt对串口数据进行计数,同时tx_data的各个数据位依次通过串口发送端uart_txd发送出去。当tx_cnt计数到9时,串口数据发送完成,开始发送停止位。在一个波特率周期的停止位发送完成后,串口发送过程结束,uart_tx_busy信号拉低,表明串口发送模块进入空闲状态。
图 19.4.4 串口发送过程波形图
从图 19.4.4中可以看出串口发送模块能够完成并串转换并正确发送串口数据。在介绍完串口的接收和发送模块后,最后我们来看一下串口环回模块的代码:
1module uart_loop(
2 input sys_clk, //系统时钟
3 input sys_rst_n, //系统复位,低电平有效
4
5 input recv_done, //接收一帧数据完成标志
6 input recv_data, //接收的数据
7
8 input tx_busy, //发送忙状态标志
9 output reg send_en, //发送使能信号
10 output reg send_data //待发送数据
11 );
12
13 //reg define
14 reg recv_done_d0;
15 reg recv_done_d1;
16 reg tx_ready;
17
18 //wire define
19 wire recv_done_flag;
20
21 //*****************************************************
22 //** main code
23 //*****************************************************
24
25 //捕获recv_done上升沿,得到一个时钟周期的脉冲信号
26 assign recv_done_flag = (~recv_done_d1) & recv_done_d0;
27
28 //对发送使能信号recv_done延迟两个时钟周期
29 always @(posedge sys_clk or negedge sys_rst_n) begin
30 if (!sys_rst_n) begin
31 recv_done_d0 <= 1'b0;
32 recv_done_d1 <= 1'b0;
33 end
34 else begin
35 recv_done_d0 <= recv_done;
36 recv_done_d1 <= recv_done_d0;
37 end
38 end
39
40 //判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
41 always @(posedge sys_clk or negedge sys_rst_n) begin
42 if (!sys_rst_n) begin
43 tx_ready<= 1'b0;
44 send_en <= 1'b0;
45 send_data <= 8'd0;
46 end
47 else begin
48 if(recv_done_flag)begin //检测串口接收到数据
49 tx_ready<= 1'b1; //准备启动发送过程
50 send_en <= 1'b0;
51 send_data <= recv_data; //寄存串口接收的数据
52 end
53 else if(tx_ready && (~tx_busy)) begin //检测串口发送模块空闲
54 tx_ready <= 1'b0; //准备过程结束
55 send_en<= 1'b1; //拉高发送使能信号
56 end
57 end
58 end
59
60 endmodule
uart_loop模块的代码比较简单,首先代码的25至38行实现了上升沿检测功能。当检测到recv_done信号的上升沿时,recv_done_flag输出一个时钟周期的高电平,标志着串口接收模块接收到了一帧数据。在代码的48至52行,在判断到recv_done_flag为高电平时,寄存接收到的数据recv_data到send_data中;同时将tx_ready信号拉高,表示已经准备好了待发送的数据。另外还要将send_en信号拉低,为接下来产生一个上升沿作准备。
uart_loop模块还有一个输入信号tx_busy,它是由串口发送模块所输出,该信号为高电平表示串口发送模块正处于发送过程中。在代码的第53行,当tx_ready信号为高电平时,如果检测到tx_busy为低电平,则说明串口发送模块处于空闲状态。此时将send_en信号拉高,由此产生一个上升沿,以启动串口发送模块的发送过程,将寄存到send_data中的数据发送出去。于此同时,将tx_ready信号拉低,等待下一个串口接收数据的到来。
19.5下载验证
编译完工程之后就可以开始下载程序了。首先我们需要准备一根USB数据线,将USB接口一端插入电脑上的USB口,另一端与开发板上的mini USB接口相连接,如图 19.5.1所示:
图 19.5.1串口连接实物图
接下来分别连接JTAG接口和电源线,并打开电源开关。
注意上位机第一次使用USB转串口线与FPGA开发板连接时,需要安装USB串口驱动。在开发板随附的资料中找到“6_软件资料/1_软件/CH340驱动(USB串口驱动)”的文件夹,双击打开文件夹中的“SETUP.EXE”进行安装,驱动安装界面如图 19.5.2所示。界面中提示INF文件为CH341SER.INF,我们不需要理会(CH341,CH340驱动是共用的),直接点安装即可。
图 19.5.2 USB串口驱动安装界面
开发板电源打开后,将本次实验的bit文件下载到开发板中。
接下来打开串口助手。串口助手是上位机中用于辅助串口调试的小工具,可以选择安装使用开发板随附资料中“6_软件资料/1_软件/串口调试助手”文件夹中提供的XCOM串口助手。在上位机中打开串口助手XCOM,如下图所示:
图 19.5.3 串口助手操作界面
在串口助手中选择与开发板相连接的CH340虚拟串口,具体的端口号(这里是COM4)需要根据实际情况选择,可以在计算机设备器中查看,如下图所示:
图 19.5.4 电脑的设备管理器窗口
在串口助手中设置波特率为115200,数据位为8,停止位为1,无校验位,最后确认打开串口。
串口打开后,在发送文本框中输入数据“5A”并点击发送,可以看到串口助手中接收到数据“5A”,如图 19.5.3所示。串口助手接收到的数据与发送的数据一致,说明程序所实现的串口数据环回功能验证成功。
到这里利用开发板上的USB串口与上位机进行串口通信的实验已经完成了。如果大家想通过RS232串口线(或USB转串口线)验证上位机与开发板上的RS232串口通信,在程序中只需要将串口接收发送端口的管脚分配修改为RS232串口对应的FPGA引脚, RS232串口的管脚分配为uart_rxd(M11),uart_txd(P12)。这里有个地方需要注意下,超越者开发板的RS485和RS232共用FPGA管脚,所以在开发板上要用跳帽来区分RS485和RS232,连接图如下图所示。
图 19.5.5 RS232跳帽连接
由于DB9接口与VGA接口外形相像,如图 19.5.6所示,使用时请注意区分(DB9为9针接口,VGA接口为15针接口)。
图 19.5.6 超越者RS232接口
页:
[1]