搜索
bottom↓
回复: 1

【正点原子FPGA连载】第八章UART串口中断实验--摘自【正点原子】领航者 ZYNQ 之嵌入式开发指南

[复制链接]

出0入234汤圆

发表于 2020-7-24 11:00:56 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-24 10:22 编辑


1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
QQ群头像.png
5)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第八章UART串口中断实验


我们在使用PS的时候,通常会添加UART控制器,用于打印信息和调试代码。除此之外,PS在和外部设备通信时,也会经常使用串口进行通信。在“Hello World实验”章节中,我们在PS中已经添加了UART控制器,本章我们进一步向大家介绍UART控制器以及UART控制器利用中断进行通信的方法。
本章包括以下几个部分:
11.1简介
1.2实验任务
1.3硬件设计
1.4软件设计
1.5下载验证



简介
UART控制器介绍
UART控制器是一个全双工异步收发控制器,ZYNQ内部包含两个UART控制器,UART0和UART1。每一个UART控制器支持可编程的波特率发生器、64字节的接收FIFO和发送FIFO、产生中断、RXD和TXD信号的环回模式设置以及可配置的数据位长度、停止位和校验方式等。
UART控制器系统框图如图 8.1.1所示:
11010406.jpg

图 8.1.1 UART控制器系统框图

由上图可知,UART控制器和IO端口由参考时钟(UART REF_CLK)驱动,同时控制器也需要连接APB总线时钟(CPU_1x clock),UART REF_CLK和CPU_1x clock都是来自于PS时钟子系统。UART控制器的配置以及状态的获取由控制(Control)和状态寄存器(Status Registers)完成。另外,UART控制器不仅可以连接至MIO,也可以映射到EMIO,从而使用PL的端口来实现串口通信的功能。当UART控制器连接到MIO时,只有Tx(发送)和Rx(接收)两个引脚;而当连接EMIO时,除Tx和Rx引脚外,可选的还有CTSN、DSDN、DSRN等引脚,这些引脚用于串口的流控制,即调制解调器的数据通讯中。
UART控制器采用独立的接收和发送数据路径,每个路径包含一个64字节的FIFO,控制器对发送和接收FIFO中的数据进行串并转换操作。FIFO的中断标志支持轮询处理或中断驱动处理两种方式。另外,控制器中还有一个模式开关,支持RXD和TXD信号的各种环回配置。UART控制器内部框图如下图所示:
11010941.jpg

图 8.1.2 UART控制器内部框图

UART控制器的寄存器通过APB 从机接口和PS AXI总线互联,控制器的寄存器用于对UART控制器进行配置和获取状态。波特率发生器(Baud Rate Generator)为UART控制器的接收端和发送端提供位周期时钟;中断控制器(GIC)为串口的收发提供了中断服务的功能。
APB总线接口通过向TxFIFO寄存器写值,将数据加载到TxFIFO存储器中。当数据加载至TxFIFO后,TxFIFO的空标志变成无效的状态,直到最后一个数据从TxFIFO中移出,加载至传输移位寄存器,TxFIFO恢复空的标志位。同时TxFIFO使用TFULL(满中断状态)用于表示当前TxFIFO已经写满,并且会阻止数据继续写入。如果此时继续执行写操作,那么会触发溢出,数据不会加载到TxFIFO中。
RxFIFO存储器接收来自接收移位寄存器的数据,当接收完数据后,RxFIFO空标志信号同样变成无效的状态,直到所有的数据通过APB总线发送出去。RxFIFO的满标志状态用于表示RxFIFO已经写满,并且会阻止更多的数据写入。
图 8.1.3中的模式切换(Mode Switch)控制RxD和TxD的信号连接方式,总共分为四种模式,分别为:正常模式(Normal Mode)、自动回音模式(Automatic Echo Mode)、本地环回模式(Local Loopback Mode)和远程环回模式(Remote Loopback Mode)。
模式切换的功能示意图如所示:
110101659.jpg

图 8.1.3 模式切换功能示意图

从上图中可以清晰的看出UART不同模式下所实现的功能。正常模式是标准的UART操作模式;自动回音模式下,RxD连接至TxD,控制器可以接收数据,但是不能发送数据;本地环回模式没有连接RxD和TxD的引脚,用于本地程序的环回测试;远程环回模式下,RxD连接至TxD,但是并没有和控制器连接,因此控制器在此模式下无法发送数据和接收数据。当然在实际应用中,最常用的就是UART的正常模式。
在讲解完UART控制器之后,接下来我们向大家介绍程序中UART控制器的设计方法。如果我们只是用串口来打印信息的话,那么可以直接使用print()或者xil_printf()函数就可以了,无需在程序中对串口做配置。但是如果我们需要使用UART来完成某些特定功能的话,如串口接收中断,那么就要了解UART控制器初始化、UART中断初始化以及UART常用的API函数等相关内容了。
UART的启动顺序
UART的启动顺序如下:
1、复位控制器(在PS系统复位时);
2、配置IO引脚信号。RxD和TxD可以连接至MIO或者EMIO,只有EMIO可以使用串口的流控制。
3、配置UART参考时钟;
4、配置控制器功能(UART控制器初始化);
5、配置中断,通过中断来管理RxFIFO和TxFIFO;
6、配置串口流控制(可选);
7、管理发送和接收的数据,可以采用轮询或中断驱动处理两种方式。
配置控制器功能
控制器功能主要配置字符帧、波特率、FIFO触发器等级、Rx超时机制,并启用控制器。重置控制器后必须要配置所有这些参数。步骤如下:
1、配置UART数据帧格式。如:数据位长度、停止位、校验方式、IO模式等;
2、设置波特率;
3、设置RxFIFO触发器等级,可以选择启用或禁用该功能;
4、使能UART控制器;
5、配置接收器的超时机制,可以选择启用或禁用该功能。
发送数据
我们可以使用轮询或者中断两种方式控制TxFIFO和RxFIFO的数据流。这两个FIFO大小均为64个字节,因此当TxFIFO的空标志有效时,我们可以直接向其写入64个字节,无需检查TxFIFO的状态。实际上当发送器处于活跃状态时,可写入的字节数要超过64个字节,因为控制器同时也在移出数据,将其串行化转移到TxD信号上。
采用轮询方法发送数据的顺序如下:
1、检查TxFIFO是否为空;
2、向TxFIFO写入数据,可以写入64个字节;
3、向TxFIFO中写入更多数据。我们可以等待TxFIFO为空之后再写入64个字节,即执行第2步;也可以检测TxFIFO是否写满,即不停的读取TFUL标志和写单个字节的数据。
采用中断方法发送数据的顺序如下:
1、禁用TxFIFO空中断;
2、向TxFIFO写数据,可以写入64个字节的数据;
3、检测TxFIFO是否为满状态,不停的读取TFUL标志和写单个字节的数据;
4、重复步骤2和3,直到TxFIFO已满;
5、使能TxFIFO空中断;
6、等待,直到TxFIFO为空,然后从步骤1重新开始;
接收数据
采用轮询方法接收数据的顺序如下:
1、等待,直到RxFIFO中的数据数量达到触发等级;
2、从RxFIFO中读取数据;
3、重复步骤2直到FIFO空;
4、发生Rx超时中断时将其重置。
采用中断方法接收数据的顺序如下:
1、使能中断;
2、等待,直到RxFIFO中的数据数量达到触发等级或者发生超时;
3、从RxFIFO中读取数据;
4、重复步骤2和3,直到RxFIFO为空;
5、清除中断标志。
实验任务
本章的实验任务是使用UART控制器,完成串口中断数据环回的功能。
硬件设计
从实验任务我们可以画出如下的系统框图,DDR3中存放和运行程序、UART实现串口通信。

110103230.jpg

图 8.3.1 系统框图




由系统框图可知,本次实验的框图和《Hello World实验》的框图一样,同样只需要搭建ZYNQ嵌入式最小系统,最小系统只包括PS部分。这里添加的UART控制器不仅仅只是打印信息,同时为了实现串口数据环回的功能。
由于本次实验嵌入式系统的搭建和《Hello World实验》完全相同,这里不再详细讲解搭建的步骤,大家可以按照《Hello World实验》章节的步骤来创建一个新的嵌入式系统,或者将《Hello World实验》章节的工程另存为本次实验的工程,工程名为uart_intr_loop。
这里简单介绍下ZYNQ PS的配置界面,如图 8.3.2所示。领航者ZYNQ开发板上的USB UART连接的引脚是MIO14和MIO15,因此在配置界面选择的是UART0 MIO14..MIO15。图中的Modem signals表示是否添加串口的流控制功能,即调制解调器,如果选中的话,会额外增加一些引脚,一般不勾选。需要注意的是,串口的流控制功能只能用于EMIO接口,MIO接口不支持此功能。
110103776.jpg

图 8.3.2 MIO配置界面

如果想要把UART控制器的引脚映射到EMIO接口,只需要将UART的引脚改为EMIO,如图 8.3.3所示。然后在Vivado工程中添加对应的管脚约束,生成Bitstream文件并导出到SDK即可。
110103977.jpg

图 8.3.3 UART控制器EMIO接口配置

本次实验使用的是板载的USB UART接口,连接的是MIO14和MIO15引脚,因此这里不做修改。
软件设计
在硬件设计的最后,我们打开了SDK开发环境,如下图所示。
110104130.jpg

图 8.4.1 SDK开发环境界面

本次实验的ZYNQ嵌入式环境是重新搭建的,因此导出到SDK后是上图中的界面,如果大家是在前面工程的基础上另存为的话,左侧窗口会多出原工程和板级支持包等项目,这个不影响,直接关闭并删除即可。
下面我们在SDK中创建应用工程,选择菜单File->New->Application Project, 新建一个SDK软件工程。
110104353.jpg

图 8.4.2 新建一个SDK软件工程

在弹出的图 8.4.3所示界面中,输入工程名“uart_intr_loop”,注意图中第二个方框Hardware Platform硬件平台的选择,如果工程是在原有基础上另存为的话,此处可能是system_wrapper_hw_platform_1,所以要注意选择正确。其它选项保持默认即可,点击“Next >”按钮。
110104601.jpg

图 8.4.3 配置工程

接下来选择工程模版Empty Application,然后点击“Finish”按钮。
可以看到SDK创建了一个uart_intr_loop目录和uart_intr_loop_bsp目录。我们打开uart_intr_loop_bsp目录下的system.mss文件,找到ps7_uart_0,可以看到UART文档和导入示例,如图 8.4.4所示:
110104856.jpg

图 8.4.4 system.mss文件

如果我们点击Import Examples,会弹出下图所示的导入示例界面,关于UART有5个示例,如下图所示:
110104978.jpg

图 8.4.5 导入示例

感兴趣的朋友可以参考下官方提供的UART例程,其中xuartps_intr_example是串口中断的示例。
这里我们不导入官方的例程,而是新建一个源文件。在uart_intr_loop/src目录上右键,选择New->Source File一栏。在弹出的对话框中Source file一栏我们输入文件名“main.c”,然后点击“Finish”。
新建源文件之后,在左侧uart/src目录下可以看到main.c文件,同时在主页面已经打开了该文件的文本编辑框。我们在新建的main.c文件中输入以下代码:
  1. 1   #include "xparameters.h"
  2. 2   #include "xuartps.h"
  3. 3   #include "xil_printf.h"
  4. 4   #include "xscugic.h"
  5. 5   #include "stdio.h"
  6. 6   
  7. 7   #define UART_DEVICE_ID     XPAR_PS7_UART_0_DEVICE_ID    //串口设备ID
  8. 8   #define INTC_DEVICE_ID     XPAR_SCUGIC_SINGLE_DEVICE_ID //中断ID
  9. 9   #define UART_INT_IRQ_ID    XPAR_XUARTPS_0_INTR          //串口中断ID
  10. 10  
  11. 11  XScuGic Intc;              //中断控制器驱动程序实例
  12. 12  XUartPs Uart_Ps;           //串口驱动程序实例
  13. 13  
  14. 14  //UART初始化函数
  15. 15  int uart_init(XUartPs* uart_ps)
  16. 16  {
  17. 17      int status;
  18. 18      XUartPs_Config *uart_cfg;
  19. 19  
  20. 20      uart_cfg = XUartPs_LookupConfig(UART_DEVICE_ID);
  21. 21      if (NULL == uart_cfg)
  22. 22          return XST_FAILURE;
  23. 23      status = XUartPs_CfgInitialize(uart_ps, uart_cfg, uart_cfg->BaseAddress);
  24. 24      if (status != XST_SUCCESS)
  25. 25          return XST_FAILURE;
  26. 26  
  27. 27      //UART设备自检
  28. 28      status = XUartPs_SelfTest(uart_ps);
  29. 29      if (status != XST_SUCCESS)
  30. 30          return XST_FAILURE;
  31. 31      
  32. 32      //设置工作模式:正常模式
  33. 33      XUartPs_SetOperMode(uart_ps, XUARTPS_OPER_MODE_NORMAL);
  34. 34      //设置波特率:115200
  35. 35      XUartPs_SetBaudRate(uart_ps,115200);   
  36. 36      //设置RxFIFO的中断触发等级   
  37. 37      XUartPs_SetFifoThreshold(uart_ps, 1);                  
  38. 38  
  39. 39      return XST_SUCCESS;
  40. 40  }
  41. 41  
  42. 42  //UART中断处理函数
  43. 43  void uart_intr_handler(void *call_back_ref)
  44. 44  {
  45. 45      XUartPs *uart_instance_ptr = (XUartPs *) call_back_ref;
  46. 46      u32 rec_data = 0 ;
  47. 47      u32 isr_status ;                           //中断状态标志
  48. 48  
  49. 49      //读取中断ID寄存器,判断触发的是哪种中断
  50. 50      isr_status = XUartPs_ReadReg(uart_instance_ptr->Config.BaseAddress,
  51. 51                     XUARTPS_IMR_OFFSET);
  52. 52      isr_status &= XUartPs_ReadReg(uart_instance_ptr->Config.BaseAddress,
  53. 53                     XUARTPS_ISR_OFFSET);
  54. 54  
  55. 55      //判断中断标志位RxFIFO是否触发
  56. 56      if (isr_status & (u32)XUARTPS_IXR_RXOVR){
  57. 57          rec_data = XUartPs_RecvByte(XPAR_PS7_UART_0_BASEADDR);
  58. 58          //清除中断标志
  59. 59          XUartPs_WriteReg(uart_instance_ptr->Config.BaseAddress,
  60. 60                  XUARTPS_ISR_OFFSET, XUARTPS_IXR_RXOVR) ;
  61. 61      }
  62. 62      XUartPs_SendByte(XPAR_PS7_UART_0_BASEADDR,rec_data);
  63. 63  }
  64. 64  
  65. 65  //串口中断初始化
  66. 66  int uart_intr_init(XScuGic *intc, XUartPs *uart_ps)
  67. 67  {
  68. 68      int status;
  69. 69      //初始化中断控制器
  70. 70      XScuGic_Config *intc_cfg;
  71. 71      intc_cfg = XScuGic_LookupConfig(INTC_DEVICE_ID);
  72. 72      if (NULL == intc_cfg)
  73. 73          return XST_FAILURE;
  74. 74      status = XScuGic_CfgInitialize(intc, intc_cfg,
  75. 75              intc_cfg->CpuBaseAddress);
  76. 76      if (status != XST_SUCCESS)
  77. 77          return XST_FAILURE;
  78. 78  
  79. 79      //设置并打开中断异常处理功能
  80. 80      Xil_ExceptionInit();
  81. 81      Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
  82. 82              (Xil_ExceptionHandler)XScuGic_InterruptHandler,
  83. 83              (void *)intc);
  84. 84      Xil_ExceptionEnable();
  85. 85  
  86. 86      //为中断设置中断处理函数
  87. 87      XScuGic_Connect(intc, UART_INT_IRQ_ID,
  88. 88              (Xil_ExceptionHandler) uart_intr_handler,(void *) uart_ps);
  89. 89      //设置UART的中断触发方式
  90. 90      XUartPs_SetInterruptMask(uart_ps, XUARTPS_IXR_RXOVR);
  91. 91      //使能GIC中的串口中断
  92. 92      XScuGic_Enable(intc, UART_INT_IRQ_ID);
  93. 93      return XST_SUCCESS;
  94. 94  }
  95. 95  
  96. 96  //main函数
  97. 97  int main(void)
  98. 98  {
  99. 99      int status;
  100. 100
  101. 101     status = uart_init(&Uart_Ps);    //串口初始化
  102. 102     if (status == XST_FAILURE) {
  103. 103         xil_printf("Uart Initial Failed\r\n");
  104. 104         return XST_FAILURE;
  105. 105     }
  106. 106     
  107. 107     uart_intr_init(&Intc, &Uart_Ps); //串口中断初始化
  108. 108     while (1);
  109. 109     return status;
  110. 110 }
复制代码

在代码的第11行和第12行,XScuGic和XUartPs为程序中定义的两个结构体。如果在SDK软件中,按住Ctrl键不放,将鼠标移动到XScuGic或者XUartPs上,当鼠标变成手指状时,单击鼠标左键,会自动跳转到定义这两个结构体的地方。其中XScuGic包含了中断控制器相关的参数和数据,而XUartPs则包含了串口相关的参数和数据。
在程序的main函数中,首先对串口进行初始化(uart_init)。初始化完成后,函数返回初始化的结果,如果初始化失败,打印错误信息并返回;如果初始化成功,则开始执行串口中断初始化函数(uart_intr_init)。最后主程序会一直停留在while无限循环,如代码中第96行至第110行代码所示。
在代码的第15行至第40行完成了对UART的初始化。其中代码的第28行XUartPs_SelfTest函数实现了UART设备自检的功能,即使用UART本地环回的模式,并验证数据是否可以正确发送和接收。XUartPs_SetOperMode函数设置串口的工作模式,这里输入的参数XUARTPS_OPER_MODE_NORMAL为正常的工作模式。XUartPs_SetBaudRate函数用于设置串口的通信波特率,这里设置的波特率为115200,如果需要修改成其它波特率,可直接在此修改输入的参数即可。XUartPs_SetFifoThreshold函数用于设置RxFIFO的中断触发等级,即触发RxFIFO中断的数据个数,这里设置的值为1,即每收到一个值就触发中断。注意,中断触发等级最大值不超过63。
在代码的第65行至第94行完成了串口中断的初始化。程序首先对中断控制器进行初始化,随后设置并打开中断异常处理的功能。接下来为串口中断设置中断处理函数,通过XScuGic_Connect函数进行设置,这里设置的串口中断处理函数为uart_intr_handler。XUartPs_SetInterruptMask函数用于设置UART的中断触发方式,函数输入的参数为XUARTPS_IXR_RXOVR,表示达到RxFIFO的触发等级时,开始触发中断,当然也可以设置成RxFIFO为满时触发中断或者为空时触发中断等。最后,通过XScuGic_Enable函数来使能GIC中的串口中断。
在代码的第42行至第63行为UART中断处理函数,由于RxFIFO的触发等级设置为1,因此每次接收到数据都会进入此中断函数。程序中首先读取中断ID寄存器,判断触发的是哪种中断,再读取中断的状态。当判断中断标志位为RxFIFO触发中断时,通过XUartPs_RecvByte函数来读取接收到的数据,并清除对应的中断标志位。最后通过XUartPs_SendByte函数发送接收到的数据,实现串口环回的功能。
下载验证
首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用Mini USB连接线将USB_UART接口与电脑连接,用于串口通信。最后连接开发板的电源,并打开电源开关。
在SDK软件下方的SDK Terminal窗口中点击右上角的加号设置并连接串口。然后在应用工程uart_intr_loop上右击,选择“Run As”,然后选择第一项“1 Launch on Hardware (System Debugger)”。
软件程序下载完成后,在SDK Terminal窗口发送数据一栏,输入待发送的数据,这里输入“Hello Zynq”,然后点击“Send”按钮,即可在接收数据窗口中接收到数据,如下图所示:
1101010288.jpg

图 8.5.1 发送数据操作界面

1101010350.jpg

图 8.5.2 接收数据界面

程序成功打印出了“Hello Zynq”字符串,说明本次实验在领航者ZYNQ开发板上面下载验证成功。





阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出16170入6148汤圆

发表于 2020-8-1 03:25:37 来自手机 | 显示全部楼层
打赏!

庆祝论坛“打赏”功能实施, 现在开始发技术主题,可以获得打赏
https://www.amobbs.com/thread-5735948-1-1.html
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 16:28

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表