超级版主
- 积分
- 4659
- 金钱
- 4659
- 注册时间
- 2019-5-8
- 在线时间
- 1223 小时
|
本帖最后由 正点原子运营 于 2021-10-30 10:09 编辑
1)实验平台:正点原子新起点V2FPGA开发板
2) 章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_xinqidian(V2).html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122
第二十一章 UART串口通信实验
串口是“串行接口”的简称,即采用串行通信方式的接口。串行通信将数据字节分成一位一位的形式在一条数据线上逐个传送,其特点是通信线路简单,但传输速度较慢。因此串口广泛应用于嵌入式、工业控制等领域中对数据传输速度要求不高的场合。本章我们将使用新起点开发板上的UART串口完成与上位机的通信。
本章包括以下几个部分:
2121.1简介
21.2实验任务
21.3硬件设计
21.4软件设计
21.5下载验证
21.1简介
串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。UART在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图 21.1.1所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时,对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数。
图 21.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,样式如图 21.1.2所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。
图 21.1.2 DB9接口
图 21.1.3 TYPE_C USB接口
由于传统的DB9接口体积较大,会占用开发板过多空间,在新起点开发板上我们采用的是TYPE_C USB接口,样式如图 21.1.3所示,另一端直接和电脑USB相连。连接示意图如图 21.1.4所示。
图 21.1.4 USB串口通信连接示意图
21.2实验任务
本节实验任务是上位机通过串口调试助手发送数据给FPGA,FPGA通过USB串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。
21.3硬件设计
新起点FPGA开发板上USB串口的原理图如下所示:
图 21.3.1 串口原理图
原理图中的CH340就是图 21.1.4所示的转换芯片,其中UART1_RXD连接到FPGA芯片的的RX管脚,UART1_TXD连接到FPGA芯片的TX管脚(注意这里有一个编号P9的x4排针,在做串口通信实验时需要用跳线帽接上,具体接法看下载验证部分内容),CH340_P/N分别接到开发板的TYPE_C USB插座D+/-上。CH340具体的工作原理是:发送的时候把FPGA的UART1_TXD管脚送出的信号由TTL电平转换为USB差分电平并送到TYPE_C USB插座,接收的时候则把TYPE_C USB插座送来的USB差分电平转换为TTL电平送给FPGA的UART1_RXD管脚,同时我们通过USB数据线将TYPE_C USB插座D+/-直连到PC上相应的USB口的D+/-,当然也包括电源和地,这样就完成了USB串口通信的硬件准备工作。
本实验中,系统时钟、按键复位以及串口的接收、发送端口的管脚分配如下表所示:
表 21.3.1 串口通信实验管脚分配
对应的TCL约束语句如下所示:
- set_location_assignment PIN_M2 -to sys_clk
- set_location_assignment PIN_M1 -to sys_rst_n
- set_location_assignment PIN_N5 -to uart_rxd
- set_location_assignment PIN_M7 -to uart_txd
复制代码
21.4程序设计
根据实验任务,我们不难想象本系统应该有一个串口接收模块,用来接收上位机发送的数据;还要有一个串口发送模块,用于将数据发回上位机;另外还应该有一个对数据进行环回控制的模块,它负责把从串口接收模块接收到的数据送给串口发送模块,以实现串口数据的环回。由此可以画出本次实验的系统框图,如下所示:
图 21.4.1 系统框图
由系统总体框图可知,整个工程包括四个模块,顶层模块、接收模块、发送模块和数据环回模块。其中在顶层模块中完成对另外三个模块的例化,顶层模块原理图如下所示:
图 21.4.2 顶层模块原理图
在图 21.4.2中,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。则传输一帧数据的时序图如下图所示:
图 21.4.3 串口通信时序图
顶层模块的代码如下:
- 1 module 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 parameter CLK_FREQ = 50000000; //定义系统时钟频率
- 11 parameter UART_BPS = 115200; //定义串口波特率
- 12
- 13 //wire define
- 14 wire uart_recv_done; //UART接收完成
- 15 wire [7:0] uart_recv_data; //UART接收数据
- 16 wire uart_send_en; //UART发送使能
- 17 wire [7:0] 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),在模块例化时会将这个变量传递到串口接收与发送模块中,从而实现不同速率的串口通信。
串口接收模块的代码如下所示:
串口接收模块程序中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,在串口接收过程中SignalTap抓取的波形图如下所示:
图 21.4.4 串口接收过程波形图
从图 21.4.4中可以看到在整个接收过程中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 input [ 7:0] uart_din, //待发送数据
- 7 output uart_tx_busy, //发送忙状态标志
- 8 output en_flag ,
- 9 output reg tx_flag, //发送过程标志信号
- 10 output reg [ 7:0] tx_data, //寄存发送数据
- 11 output reg [ 3:0] tx_cnt, //发送数据计数器
- 12 output reg uart_txd //UART发送端口
- 13 );
- 14
- 15 //parameter define
- 16 parameter CLK_FREQ = 50000000; //系统时钟频率
- 17 parameter UART_BPS = 9600; //串口波特率
- 18 localparam BPS_CNT = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次
- 19
- 20 //reg define
- 21 reg uart_en_d0;
- 22 reg uart_en_d1;
- 23 reg [15:0] clk_cnt; //系统时钟计数器
- 24
- 25 //*****************************************************
- 26 //** main code
- 27 //*****************************************************
- 28 //在串口发送过程中给出忙状态标志
- 29 assign uart_tx_busy = tx_flag;
- 30
- 31 //捕获uart_en上升沿,得到一个时钟周期的脉冲信号
- 32 assign en_flag = (~uart_en_d1) & uart_en_d0;
- 33
- 34 //对发送使能信号uart_en延迟两个时钟周期
- 35 always @(posedge sys_clk or negedge sys_rst_n) begin
- 36 if (!sys_rst_n) begin
- 37 uart_en_d0 <= 1'b0;
- 38 uart_en_d1 <= 1'b0;
- 39 end
- 40 else begin
- 41 uart_en_d0 <= uart_en;
- 42 uart_en_d1 <= uart_en_d0;
- 43 end
- 44 end
- 45
- 46 //当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
- 47 always @(posedge sys_clk or negedge sys_rst_n) begin
- 48 if (!sys_rst_n) begin
- 49 tx_flag <= 1'b0;
- 50 tx_data <= 8'd0;
- 51 end
- 52 else if (en_flag) begin //检测到发送使能上升沿
- 53 tx_flag <= 1'b1; //进入发送过程,标志位tx_flag拉高
- 54 tx_data <= uart_din; //寄存待发送的数据
- 55 end
- 56 //计数到停止位结束时,停止发送过程
- 57 else if ((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT - (BPS_CNT/16))) begin
- 58 tx_flag <= 1'b0; //发送过程结束,标志位tx_flag拉低
- 59 tx_data <= 8'd0;
- 60 end
- 61 else begin
- 62 tx_flag <= tx_flag;
- 63 tx_data <= tx_data;
- 64 end
- 65 end
- 66
- 67 //进入发送过程后,启动系统时钟计数器
- 68 always @(posedge sys_clk or negedge sys_rst_n) begin
- 69 if (!sys_rst_n)
- 70 clk_cnt <= 16'd0;
- 71 else if (tx_flag) begin //处于发送过程
- 72 if (clk_cnt < BPS_CNT - 1)
- 73 clk_cnt <= clk_cnt + 1'b1;
- 74 else
- 75 clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
- 76 end
- 77 else
- 78 clk_cnt <= 16'd0; //发送过程结束
- 79 end
- 80
- 81 //进入发送过程后,启动发送数据计数器
- 82 always @(posedge sys_clk or negedge sys_rst_n) begin
- 83 if (!sys_rst_n)
- 84 tx_cnt <= 4'd0;
- 85 else if (tx_flag) begin //处于发送过程
- 86 if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期
- 87 tx_cnt <= tx_cnt + 1'b1; //此时发送数据计数器加1
- 88 else
- 89 tx_cnt <= tx_cnt;
- 90 end
- 91 else
- 92 tx_cnt <= 4'd0; //发送过程结束
- 93 end
- 94
- 95 //根据发送数据计数器来给uart发送端口赋值
- 96 always @(posedge sys_clk or negedge sys_rst_n) begin
- 97 if (!sys_rst_n)
- 98 uart_txd <= 1'b1;
- 99 else if (tx_flag)
- 100 case(tx_cnt)
- 101 4'd0: uart_txd <= 1'b0; //起始位
- 102 4'd1: uart_txd <= tx_data[0]; //数据位最低位
- 103 4'd2: uart_txd <= tx_data[1];
- 104 4'd3: uart_txd <= tx_data[2];
- 105 4'd4: uart_txd <= tx_data[3];
- 106 4'd5: uart_txd <= tx_data[4];
- 107 4'd6: uart_txd <= tx_data[5];
- 108 4'd7: uart_txd <= tx_data[6];
- 109 4'd8: uart_txd <= tx_data[7]; //数据位最高位
- 110 4'd9: uart_txd <= 1'b1; //停止位
- 111 default: ;
- 112 endcase
- 113 else
- 114 uart_txd <= 1'b1; //空闲时发送端口为高电平
- 115 end
- 116
- 117 endmodule
复制代码
串口发送模块与串口接收模块异曲同工,代码中也给出了详尽的注释,此处不再赘述。需要注意的是,在程序的58行,我们将tx_flag提前1/16个停止位拉低,是为了确保发送模块发送数据的时间略小于接收模块接收数据的时间,否则当连续传输大量数据时,发送数据的时间会不断累积,最终导致在做串口环回实验时丢失数据。
尽管串口发送数据只是接收数据的反过程,理论上在传输的时间上是一致的,考虑到我们模块里计算波特率会有较小的偏差,并且串口对端的通信设备(如电脑等)收发数据的波特率同样可能会出现较小的偏差,因此这里为了确保环回实验的成功,这里将发送模块的停止位略微提前结束。
需要说明的是,较小偏差的波特率在串口通信时是允许的,同样可以保证数据可靠稳定的传输。
另外我们在代码的第29行将用于标志串口发送过程的tx_flag信号赋值给uart_tx_busy,并通过模块端口输出。这样其他模块就可以通过检测uart_tx_busy信号是否为低电平,从而判断串口发送模块是否处于空闲状态。若uart_tx_busy为高电平,那么uart_send正处于发送过程,外部模块需要等待当前发送过程结束之后,才能通过uart_en信号的上升沿来启动新的发送过程。
图 21.4.5为串口发送过程中SignalTap抓取的波形图,我们使用开发板通过串口向上位机向发送16进制数55。在检测到uart_en的上升沿后,en_flag会拉高一个时钟周期,此时将uart_din端口上的待发送数据寄存到tx_data中,并进入串口发送过程。
在整个发送过程中tx_flag保持为高电平,tx_cnt对串口数据进行计数,同时tx_data的各个数据位依次通过串口发送端uart_txd发送出去。当tx_cnt计数到9时,串口数据发送完成,开始发送停止位。在一个波特率周期的停止位发送完成后,串口发送过程结束,uart_tx_busy信号拉低,表明串口发送模块进入空闲状态。
图 21.4.5 串口发送过程波形图
从图 21.4.5中可以看出串口发送模块能够完成并串转换并正确发送串口数据。
在介绍完串口的接收和发送模块后,最后我们来看一下串口环回模块的代码:
- 1 module uart_loop(
- 2 input sys_clk, //系统时钟
- 3 input sys_rst_n, //系统复位,低电平有效
- 4
- 5 input recv_done, //接收一帧数据完成标志
- 6 input [7:0] recv_data, //接收的数据
- 7
- 8 input tx_busy, //发送忙状态标志
- 9 output reg send_en, //发送使能信号
- 10 output reg [7:0] 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信号拉低,等待下一个串口接收数据的到来。
21.5下载验证
将USB串口线与下载器一端连电脑,另一端与开发板上对应TypeC端口连接,然后将跳线帽插上,如下图所示:
图 21.5.1 硬件连接图
注意上位机第一次使用USB串口线与FPGA开发板连接时,需要安装USB串口驱动。在开发板随附的资料中找到“6_软件资料/1_软件/CH340驱动(USB串口驱动)”的文件夹,双击打开文件夹中的“SETUP.EXE”进行安装,驱动安装界面如图 21.5.2所示。界面中提示INF文件为CH341SER.INF,我们不需要理会(CH341,CH340驱动是共用的),直接点安装即可。
图 21.5.2 USB串口驱动驱动安装界面
最后打开开发板电源,下载sof文件,并打开串口助手。串口助手是上位机中用于辅助串口调试的小工具,可以选择安装使用开发板随附资料中“6_软件资料/1_软件/串口调试助手”文件夹中提供的XCOM串口助手。在上位机中打开串口助手XCOM V2.0,如图 21.5.3所示:
图 21.5.3 串口助手操作界面
在串口助手中选择与开发板相连接的CH340虚拟串口,具体的端口号(这里是COM4)需要根据实际情况选择,可以在计算机设备器中查看。设置波特率为115200,数据位为8,停止位为1,无校验位,最后确认打开串口。
串口打开后,在发送文本框中输入数据“55”并点击发送,可以看到串口助手中接收到数据“55”,接收到的数据与发送的数据一致(注意如果选择16进制发送那么显示也选择16进制显示),说明数据环回实验验证成功。
到这里利用开发板上的USB串口与上位机进行串口通信的实验已经完成了。 |
|