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的轮询模式。除非是特别简单的应用,一般不会使用轮询模式。如果要使用轮询模式,一定要有个良好的程序架构或实现机制,避免程序无限挂起。