[X]关闭
0

S02-CH09 UART串口中断实验

摘要: 软件版本:VIVADO2017.4操作系统:WIN10 64bit硬件平台:适用米联客 ZYNQ系列开发板米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!9.1 概述 串口自诞生以来由于其简单可靠的传输方式,被大 ...

软件版本:VIVADO2017.4

操作系统:WIN10 64bit

硬件平台:适用米联客 ZYNQ系列开发板

米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!

9.1 概述

      串口自诞生以来由于其简单可靠的传输方式,被大量使用,对于现在这个充满高速通信设备的时代来说,串口依然不过时。老早以前的台式机和笔记本可都是有串口的,虽然现在很多台式机串口没有了,笔记本也没有串口了,但是串口从来没有被丢弃,只是以另外一种形式存在,这就是USB转串口芯片。串口只需要2根线就可以实现一收一发,使用简单,可靠方便,在低速场合大量使用。比如一些工控的人机界面、我们用的一些单片机的USB下载器都是用USB转串口来实现的。

      本课详细的分析了UART中断的实现过程,在PL_PS中断和定时器中断的基础上进行UART中断开发,因此本课有难度。建议在学习PL_PS中断和定时器中断后,再来学习本课内容。硬件工程可以直接使用定时器中断的硬件工程,因此实验直接讲述SDK软件部分。

      通过PL_PS中断、定时器中断、UART中断内容学习,应对中断的使用得心应手,重点需要掌握分析问题的方法。

9.2串口通信介绍

      在进行具体的串口设计之前,先了解串口通信协议。通常串口的一次发送或接收由四个部分组成:起始位S(“一般为逻辑‘0’)、数据位D0~D7(一般为6位~8位之间可变,数据低位在前)、校验位(奇校验、偶检验或不需要校验位)、停止位(通常为1位、1.5位、2位)。停止位必须为逻辑1。在一次串口通信过程中,数据接收与发送双方没有共享时钟,因此,双方必须协商好数据传输波特率。波特率即数据传输速率。根据双方协议好的传输速率,接收端即可对发送端的数据进行采样。常见的波特率标准为300bps,600bps,800bps,9600bps,19200bps等。当然更块的速度意味着对采样的要求更高,有可能误码率会逐渐提高。

      通常对串口进行数据采样,采用更高频的时钟。这样做的目的是采用高频时钟来锁存低频时钟,减少数据的误码率,增加接收模块的自纠错能力。

具体的工作流程为:

      发送端按照预先设定好的波特率,发送起始位(Start)+数据位(data)+奇偶校验位+结束位。其中,起始位为逻辑0,结束位为逻辑1,发送端在空闲状态为1。发送数据包格式如下图所示。

• 接收端通过检测电平‘1’到‘0’的跳变来确定一个数据包的开始。确定开始位接收完成之后,依次接收数据,使用更高的采样时钟,完成数据采集。接收完数据位后,继续接收奇偶校验位和停止位。

• 串口的接收与发送,其主要时序设计包括两个部分:1、波特率的产生时序;2、数据传输时序,包括接收与发送。

• 波特率产生时序设计:FPGA输入时钟50Mhz,为得到常用的波特率,仍然采用计数分频来得到。BAUD_DIV=50_000000/波特率。其中采样中心点为发送或接收时钟的中心点,即BAUD_DIV_CAP=50_000000/(2*波特率)。该部分在数据接收和发送部分均单独完成。

• 数据接收模块:在设置好传输波特率的情况下,根据串口传输时序,进行解串。空闲状态时,接收 数据为逻辑高电平,等待起始位逻辑低电平的到来。当起始位到达后,由低位到高位,依次采集8位数据,并进行相应的解串,存入临时寄存器。接收有效数据完成后,判断结束位,接收完毕。

• 数据发送模块:设置发送使能信号和待发送的数据。通过计数器,表示10个数据发送的周期。这10个数据,依次为起始位+8位数据位+1位结束位,实现数据位的逐个发送。

• 本设计中,采用PC机的串口调试助手,发送数据位至FPGA,FPGA接收到数据位之后,立即回传至PC机。具体的设计原理和代码思路,在后续章节逐一介绍。

9.2 搭建FPGA BD工程

      Step1:新建一个名为为Miz_sys的工程。

     Step2:创建一个BD文件,并命名为system,添加并且配置好ZYNQ IP。读者需要根据自己的硬件类型配置好输入时钟频率、内存型号、串口,连接时钟等。新手不清楚这些内容个,请参考“CH01 HelloWold/DDR/网口测试及固化”这一节课。

     做这个实验,ZYNQ IP中关键设置至少包括 DDR型号配置、串口配置,以及PS时钟配置,不再啰嗦重复描述。

注意:UART的IO是1.8V如下图

另外可以看到本IP比较简洁,少了GP接口和时钟输出接口如下图设置,可以去掉没有用的GP接口和时钟输出接口。

Step5:单击窗口上的运行按钮,运行程序。

9.5 实验结果

系统运行结果如下图所示:

可以发送并且接收任意长度数据。

9.6 程序分析

9.6.1 Uart控制器


      ZYNQ 的ARM具备2路UART,分辨率为UART0和UART1,我们例子中用到了UART1。每个UART控制器(UART 0和UART 1)具有以下功能:

可编程波特率发生器

64字节接收和发送FIFO

可编程协议:

        6,7或8个数据位

        1,1.5或2个停止位

         奇数,偶数,空格,标记或无奇偶校验

奇偶校验,帧和溢出错误检测

断行生成

中断生成

RxD和TxD模式:通过模式设置,可以支持Normal/echo and diagnostic loopbacks模式

9.6.2 uartps_intr.c

/*

 * uartps_intr.c

 *

 * Created on:

* www.osrc.cn

 * copyright by cz123 msxbo

*/

#include "uartps_intr.h"



int Init_UartPsIntr(XUartPs *UartInstPtr,u16 DeviceId )

{

int Status;

XUartPs_Config *Config;

u32 IntrMask;


if (XGetPlatform_Info() == XPLAT_ZYNQ_ULTRA_MP) {

#ifdef XPAR_XUARTPS_1_DEVICE_ID

DeviceId = XPAR_XUARTPS_1_DEVICE_ID;

#endif

}


Config = XUartPs_LookupConfig(DeviceId);

if (NULL == Config) {

return XST_FAILURE;

}


Status = XUartPs_CfgInitialize(UartInstPtr, Config, Config->BaseAddress);

if (Status != XST_SUCCESS) {

return XST_FAILURE;

}



/*

 * Setup the handlers for the UART that will be called from the

 * interrupt context when data has been sent and received, specify

 * a pointer to the UART driver instance as the callback reference

 * so the handlers are able to access the instance data

 */

XUartPs_SetHandler(UartInstPtr, (XUartPs_Handler)UartPs_Intr_Handler, UartInstPtr);


/*

 * Enable the interrupt of the UART so interrupts will occur, setup

 * a local loopback so data that is sent will be received.

 */

IntrMask =

XUARTPS_IXR_TOUT | XUARTPS_IXR_PARITY | XUARTPS_IXR_FRAMING |

XUARTPS_IXR_OVER | XUARTPS_IXR_TXEMPTY | XUARTPS_IXR_RXFULL |

XUARTPS_IXR_RXOVR;


if (UartInstPtr->Platform == XPLAT_ZYNQ_ULTRA_MP) {

IntrMask |= XUARTPS_IXR_RBRK;

}


XUartPs_SetInterruptMask(UartInstPtr, IntrMask);


XUartPs_SetRecvTimeout(UartInstPtr, 8);


return XST_SUCCESS;

}



void UartPs_Intr_Handler(void *CallBackRef, u32 Event, unsigned int EventData)

{

int i = 0;

/* All of the data has been sent */

if (Event == XUARTPS_EVENT_SENT_DATA) {

TotalSentCount = EventData;

}


/* All of the data has been received */

if (Event == XUARTPS_EVENT_RECV_DATA) {

TotalReceivedCount = EventData;

if(TotalReceivedCount == TEST_BUFFER_SIZE) {

for(i=0;i<TotalReceivedCount;i++)

SendBuffer[i] = RecvBuffer[i];


XUartPs_Send(&UartPs, SendBuffer, TotalReceivedCount);

XUartPs_Recv(&UartPs, RecvBuffer, TEST_BUFFER_SIZE);

TotalReceivedCount=0;

}


}


/*

 * Data was received, but not the expected number of bytes, a

 * timeout just indicates the data stopped for 8 character times

 */

if (Event == XUARTPS_EVENT_RECV_TOUT) {

TotalReceivedCount = EventData;

for(i=0;i<TotalReceivedCount;i++)

SendBuffer[i] = RecvBuffer[i];


XUartPs_Send(&UartPs, SendBuffer, TotalReceivedCount);

XUartPs_Recv(&UartPs, RecvBuffer, TEST_BUFFER_SIZE);

TotalReceivedCount=0;

}


/*

 * Data was received with an error, keep the data but determine

 * what kind of errors occurred

 */

if (Event == XUARTPS_EVENT_RECV_ERROR) {

TotalReceivedCount = EventData;

TotalErrorCount++;

}


/*

 * Data was received with an parity or frame or break error, keep the data

 * but determine what kind of errors occurred. Specific to Zynq Ultrascale+

 * MP.

 */

if (Event == XUARTPS_EVENT_PARE_FRAME_BRKE) {

TotalReceivedCount = EventData;

TotalErrorCount++;

}


/*

 * Data was received with an overrun error, keep the data but determine

 * what kind of errors occurred. Specific to Zynq Ultrascale+ MP.

 */

if (Event == XUARTPS_EVENT_RECV_ORERR) {

TotalReceivedCount = EventData;

TotalErrorCount++;

}

}


int UartPs_Setup_IntrSystem(XScuGic *IntcInstancePtr,XUartPs *UartInstancePtr,u16 UartIntrId)

{

int Status;


XScuGic_Config *IntcConfig; /* Config for interrupt controller */


/* Initialize the interrupt controller driver */

IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);

if (NULL == IntcConfig) {

return XST_FAILURE;

}


Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,

IntcConfig->CpuBaseAddress);

if (Status != XST_SUCCESS) {

return XST_FAILURE;

}

/*

 * Connect a device driver handler that will be called when an

 * interrupt for the device occurs, the device driver handler

 * performs the specific interrupt processing for the device

 */

Status = XScuGic_Connect(IntcInstancePtr, UartIntrId,

  (Xil_ExceptionHandler) XUartPs_InterruptHandler,

  (void *) UartInstancePtr);

if (Status != XST_SUCCESS) {

return XST_FAILURE;

}


/* Enable the interrupt for the device */

XScuGic_Enable(IntcInstancePtr, UartIntrId);


return XST_SUCCESS;

}


      通过前面两节课详细的介绍,大家应该已经掌握如何分析库函数的办法,实际上采用库函数都可以不需要了解寄存器的具体配置过程,本课程不再对寄存器的详细配置做介绍,如果读者想去了解寄存的配置可以参考前面两节课的内容,参考ug585自行分析。

9.6.2.1Init_UartPsIntr函数

此函数负责初始化 PS UART寄存器。

1 XUartPs_LookupConfig(DeviceId)

首先依然是通过查找配置程序来获取串口的硬件配置。我们跟踪这个程序,看看他获取的配置是什么。

这个程序还是从一个配置表数组中查找的配置文件,继续往下剥离,看一看这个数组中的内容。

       可以看到,这个数组里存放的是UART的设备ID,UART的基地址,时钟频率和一个不知道什么作用的对象。后两个参数是我们没用到的,因此就略过了。前两个都是我们在硬件工程中添加了中断后,系统自动生成的。


2、XUartPs_CfgInitialize(UartInstPtr, Config, Config->BaseAddress)

       这个结构体中的内容比较多,第一个对象是我们UART硬件的一些配置,它指向的是一个结构体。那么就来看看这个结构体吧。

      可以看到,这些就是刚才我们查找配置程序获取到的硬件参数。

      回到XUartPs结构体的分析。第二个对象是输入时钟频率,第三个是设备是否初始化并准备好,第四个是波特率,第五个是两个buffer,一个发送的一个接收的。挑选一个参看一下。

      接着第七个是一个Hander,第八个是一个回掉函数,最后一个是platform具体是什么意思不得而知。

回到初始化程序。我们来看看这个函数与之前有什么不同了。

一开始是一长串的初始化,如下图所示:

接下来的这个函数是一个用于判断芯片类型的函数。

接下来,程序将Instance(也就是我们的UART硬件)的标志设置为XIL_COMPONENT_IS_READY,表明此时UART已经可以使用了。

接下来,程序将UART的波特率设置为了115200。

接下来的这一句是读取UART的模式寄存器。

我们可以来看看读取的什么内容,把鼠标停放在这个函数的上方,看到函数显示出了这个函数的原函数。

与定时器实验中讲到的读写寄存器的函数差不多,第一个参数是UART的基地址,这在一开始的分析中就提到过,反回去看看UART的基地址。

可以知道,此处的基地址为0xE000100,直接计算:E0001000+0x0004= E0001004。打开ug585查看下这个寄存器的介绍。

      可以看到,这是一个UART的模式寄存器,通过这个寄存器可以设置串口的数据位宽,有无停止位和奇偶校验位等信息。

      再来看看下一句程序。这句是对刚才读出的寄存器的一个运算。

     首先得到方框中这三个参数的值。这里我们已经查看程序得知这三个值分别为:6,A0,38。然后进行运算:ModeRegister=E0001004 & (~(6|A0|38))=E0001004 & 11 =0。

      接下来的这一句也是一个运算,不多讲,直接运算。ModeRegister=0|(0|0|20)=20。

      这段程序就是一个写寄存器的功能了。看看这个函数的原函数。

       由此得出,这个函数读写的地址为刚才模式寄存器的地址,写入的数据就是运算得出的20h。参照刚才ug585里的模式寄存器说明,显而易见,经过这段程序之后,把UART设置为了8个数据位,1个停止位和无奇偶校验位的模式。

       接下来的还有3个写寄存器的程序,分析方法与刚才的一致。这里就只给出它们实现的功能。分别是:设置UART的RX FIFO在8byte处触发、设置UART的超时为1(4个字符时间)、禁止所有中断轮询模式为默认的样式。

       回到main函数的分析当中,接下来的函数实现的是建立起中断的功能,这个函数在我们上一章也进行过详细的讲解

3、XUartPs_SetHandler(UartInstPtr, (XUartPs_Handler)UartPs_Intr_Handler, UartInstPtr)

     函数负责设置中断产生后的回调函数为UartPs_Intr_Handler。

4、XUartPs_SetInterruptMask(UartInstPtr, IntrMask)

     函数负责配置中断类型

5、XUartPs_SetRecvTimeout(UartInstPtr, 8)

     函数设置了空闲超时等待的时间为8x4 = 32 为32个波特率采样时钟,意思就是当串口上超过32个波特率采样时钟没有数据的时候,就会触发一次超时中断,这样可以通知用户程序去读取已经接收到的数据。

以下是寄存器说明

9.6.2.2 UartPs_Setup_IntrSystem函数

此函数初始化PS Uart回调的函数XUartPs_InterruptHandler,并且在最后启动PS UART中断。

9.6.2.3 UartPs_Intr_Handler

PS Uart中断产生后的回调函数,在此回调函数中会去调用mian函数里面的中断处理函数。

9.6.3 main.c

#include "sys_intr.h"

#include "uartps_intr.h"


void init_intr_sys(void)

{

Init_UartPsIntr(&UartPs,UART_DEVICE_ID);

Init_Intr_System(&Intc);

Setup_Intr_Exception(&Intc);

UartPs_Setup_IntrSystem(&Intc, &UartPs, UART_INT_IRQ_ID);

}


int main(void)

{

init_intr_sys();

XUartPs_Recv(&UartPs, RecvBuffer, TEST_BUFFER_SIZE);

     while(1);

return 0;

}

为了正常启动接收,不管有没有数据到来,需要先启动一次接收函数XUartPs_Recv(&UartPs, RecvBuffer, TEST_BUFFER_SIZE)。


路过

雷人

握手

鲜花

鸡蛋

最新评论

本文作者
2019-9-6 19:19
  • 7
    粉丝
  • 7601
    阅读
  • 0
    回复

关注米联客

扫描关注,了解最新资讯

联系人:汤经理
电话:0519-80699907
EMAIL:270682667@qq.com
地址:常州溧阳市天目云谷3号楼北楼201B
热门评论
排行榜