学会Zynq(26)UART轮询(poll)模式示例

Zynq中的UART支持轮询和中断驱动两种模式。本文给出两个使用轮询模式的例子,在24篇程序框架的基础上进行改动(贴出主要改动代码,改动很小的地方,如函数接口变化导致函数声明也要改,相信你可以根据我的代码和设计目的自己完成),最后再讨论一下轮询模式的特点。

第一个例子

改造user_uart.c文件中的Uart_Send函数,将模式设置为本地回环。UART发送数据(小于64个字节,即FIFO长度)后阻塞等待,直到所有数据发送完成。然后再次阻塞,等待所有数据都读取完。比较发送和接收的数据,一致则表示通信成功。

//--------------------------------------------------------------
//                     UART数据发送函数
//--------------------------------------------------------------
int Uart_Send(XUartPs* Uart_Ps, u8 *SendBuffer, u8 *RecvBuffer, int length)
{
	u16 SentCount, RecvCount;

	XUartPs_SetOperMode(Uart_Ps, XUARTPS_OPER_MODE_LOCAL_LOOP); //本地回环模式
	// 发送缓冲区中的数据
	SentCount = XUartPs_Send(Uart_Ps, SendBuffer, length);
	if (SentCount != length) {
		return XST_FAILURE;
	}

	// 如果UART正在发送数据则等待
	while(XUartPs_IsSending(Uart_Ps));

	// 接收数据,阻塞直到接收了全部数据
	RecvCount = 0;
	while(RecvCount < length) {
		RecvCount += XUartPs_Recv(Uart_Ps, &RecvBuffer[RecvCount],
				(length - RecvCount));
	}

	XUartPs_SetOperMode(Uart_Ps, XUARTPS_OPER_MODE_NORMAL); //正常模式
	// 比较接收缓冲区与发送缓冲区中的内容
	int result = memcmp(SendBuffer, RecvBuffer, length);
	if (result != 0) {
		xil_printf("UART Polled Mode Failed!\r\n");
		return XST_FAILURE;
	}
	xil_printf("UART Polled Mode succeeded!\r\n");

	return XST_SUCCESS;
}

main.c文件的代码如下,每隔一段时间进行一次串口的轮询模式测试:

#include "user_uart.h"
#define BUFFER_SIZE 32

XUartPs Uart_Ps;		/* The instance of the UART Driver */
static u8 SendBuffer[BUFFER_SIZE];
static u8 RecvBuffer[BUFFER_SIZE];

int main(void)
{
	int Status;

	/* 串口初始化 */
	Status = Uart_Init(&Uart_Ps, UART_DEVICE_ID);
	if (Status == XST_FAILURE) {
		xil_printf("Uartps Failed\r\n");
		return XST_FAILURE;
	}

	while (1)
	{
		sleep(3);

		/* buffer初始化 */
		for (u16 index = 0; index < BUFFER_SIZE; index++) {
			SendBuffer[index] = '0' + index;
			RecvBuffer[index] = 0;
		}
		/* UART轮询模式  */
		Uart_Send(&Uart_Ps, SendBuffer, RecvBuffer, BUFFER_SIZE);
	}

	return Status;
}

SDK Terminal中添加串口,波特率设置为程序制定的9600,运行程序,将看到轮询测试成功通过。


相关API函数

1. 模式配置

本例中我们在轮询时使用本地回环模式,自发自收,验证UART的功能。回顾22篇对UART控制器的介绍。


完成一次轮询模式下的自发自收后,我们希望比较收发数据是否一致,并通过串口打印相关信息。此时如果还是停留在本地回环模式,数据是无法传输到Zynq外部的。因此每次轮询测试后,都将模式重新设置为正常模式。

2. 数据发送

程序中使用XUartPs_Send函数发送数据。这个函数是非阻塞的,轮询模式和中断驱动模式下都可以使用。它会尽可能地想TxFIFO填充数据,并返回发送的字节数;如果无法填充,会返回0表示发送了0字节,便于用户处理。

中断模式下,该函数会发送指定的缓冲区(Buffer)中的内容,中断处理程序负责将所有数据全部发送完。此时会调用绑定的回调函数,标识发送完成。关于中断的用法在后面文章中专门介绍。

	u32 XUartPs_Send(XUartPs *InstancePtr, u8 *BufferPtr, u32 NumBytes)

第二个参数是指向要发送的数据缓冲区的指针;第三个参数是发送的字节数;返回值标识实际发送的字节数。本例程序中就是利用返回值确保所有数据都依次发送(虽然本例的数量不大,但要学习这个用法)。

这个函数还有个特殊用法,如果将第三个参数设为0,则会停止正在进行的发送操作,并将已经在TxFIFO中的所有数据都发送出去。可以用这个用法实现某些特殊功能。

3. 发送状态判断

发送数据后使用XUartPs_IsSending函数检测UART是否正在发送数据。因为XUartPs_Send函数只是将数据传入TxFIFO中,UART控制器还要将其串行化转移到发送的移位寄存器中。

	u32 XUartPs_IsSending(XUartPs *InstancePtr)

这样做是为了确保接收数据时可以“确确实实的”收到所有数据。

4. 数据接收

程序中使用XUartPs_Recv函数接收数据。这个函数本身是非阻塞的。轮询模式中,该函数只会接收已经在RxFIFO中的数据,因此用while来反复调用该函数,阻塞接收指定数目的数据。中断模式下该函数的使用在第25篇中介绍。

	u32 XUartPs_Recv(XUartPs *InstancePtr, u8 *BufferPtr, u32 NumBytes)

第二参数指针指向接收到数据要存储的缓冲区;第三个参赛NumBytes是“要”接收的字节数;返回的是实际接收到的字节数。重新节选出接收数据的代码:

	RecvCount = 0;
	while(RecvCount < length) {
		RecvCount += XUartPs_Recv(Uart_Ps, &RecvBuffer[RecvCount],
				(length - RecvCount));
	}

正是利用了第三个参赛和返回值,实现了接收特定数目数据的功能,我们要学习这个写法。我们本例设计的是轮询模式,因此使用while进行阻塞。

和发送一样,这个函数将第三个参数设为0,也会停止正在进行的接收操作。

第二个例子

我们思考一下:轮询模式和中断驱动模式的最主要区别是什么?从上个例子我们还看不太出,因为我们自发自收,数据到的都很及时。考虑下面这个例子。

改造Uart_Send函数,模式设置为正常模式不变,数据源这次由外部输入。为了方便将缓冲区大小改为8。接收时阻塞等待,直到所有数据都读取完,再将收到的数据echo返回。函数代码如下(其它部分只是小改动,相信你可以自己完成):

int Uart_Send(XUartPs* Uart_Ps, u8 *RecvBuffer, int length)
{
	u16 SentCount, RecvCount;

	// 如果UART正在发送数据则等待
	while(XUartPs_IsSending(Uart_Ps));

	// 接收数据,阻塞直到接收了全部数据
	RecvCount = 0;
	while(RecvCount < length) {
		RecvCount += XUartPs_Recv(Uart_Ps, &RecvBuffer[RecvCount],
				(length - RecvCount));
	}

	// 发送接收到的数据
	SentCount = XUartPs_Send(Uart_Ps, RecvBuffer, length);
	if (SentCount != length) {
		return XST_FAILURE;
	}

	xil_printf("\r\nUART Polled Mode succeeded!\r\n");

	return XST_SUCCESS;
}

这种情况就比较尴尬了,我们会发现如果UART没有收到指定数目的数据,则会一致停留在while中挂起等待,然而这也是实际情况。这也是轮询模式的缺点。无操作系统的嵌入式开发本来就是单线程的,如果我们一直在这里挂起,便无法执行其它程序。

SDK自带的串口终端发送数据时会自动加个换行符,为了观察方便,用串口调试助手进行测试,结果如下。


只有我们发送够了8个数据后,UART的挂起状态才会结束。如果我们一次性发送的数据超过了8个(如红框所示),则多余的数据会留在RxFIFO中,直到下一次轮询才被读取。

总结

本文介绍了UART的轮询模式。除非是特别简单的应用,一般不会使用轮询模式。如果要使用轮询模式,一定要有个良好的程序架构或实现机制,避免程序无限挂起。


文章来源:FPGADesigner的博客
*本文由作者授权转发,如需转载请联系作者本人

推荐阅读