软件环境:vivado 2017.4 硬件平台:XC7Z020
之前的【JokerのZYNQ7020】UART这篇文章,只是解决了zynq7020在sdk下串口的简单收发功能,当时就发现了一些问题,还专门用红字标出来了,只是当时写完这个以后,立马有别的事情要忙,所以到现在才回来填这个坑,如下图。
对于zynq下,串口的收发方式和带来的问题再稍微描述一下,可用的串口收发主要有两种方式,一种是之前文章写过的,基于FIFO的中断方式,在这种方式下,中断的产生取决于FIFO的深度,当深度值过大时,发送小于深度值得数据时,是不引起中断的,自然也不会进一步处理,当大于或等于深度值时,中断确实会触发,也能进行数据处理,但是但是但是!只会触发满FIFO的数据,多出来的数据会等待下次FIFO满触发中断时才能处理,所以如果串口通信使用定长的帧的话倒也无所谓,如果不定长帧通讯,或者还要有串口的交互过程,那简直无法想象,不知道是不是我配置的有问题,还是大家都有这个情况。 xuartps.c文件下,s32 XUartPs_CfgInitialize(XUartPs *InstancePtr,XUartPs_Config * Config, u32 EffectiveAddr)函数中可以设置这个FIFO的大小。
这里再确切的举个例子,假如这个FIFO的值设为8,当串口接收小于8 byte的数据时是不触发FIFO满的中断,当FIFO接收到12 byte数据的时候,会触发一次串口的中断,此时只能处理12 byte中的前8个 byte,后3个 byte+1个 '\n'就要等待下一次凑够8个byte产生中断时候才能处理,3个byte到也好办,难办的是那个'\n',多次不定长交互后,你都无法确定FIFO里到底有几个字节是发送串口时候自带的'\n'。
所以也就有今天的这个填坑的过程了,这里用到的是串口中断的另外一种方法,超时中断,设定一个超时时间,如果串口在不断接收数据的时候没有超过这个超时时间,就认为数据没有接收完毕,还会一直往FIFO里面送数据,如果超过了这个超时时间没有接收到新的数据,则认为本次串口接收完毕,可以进入中断函数中进行数据处理。
话有点多了,接下来看代码,具体说怎么写。
int main()
{
XUartPs_Config *UartConfigPtr;
UartConfigPtr = XUartPs_LookupConfig(UART_DEVICE_ID);
XUartPs_CfgInitialize(&Uart,UartConfigPtr,UartConfigPtr->BaseAddress);
SetupInterruptSystem(&Intc,&Uart,UART_IRPT_INTR);
recv_total_byte = 0;
while(1);
return 0;
}
main还是那个main,只是添加了个recv_total_byte来计算当前总共接收了多少个字节的数据。
void SetupInterruptSystem(XScuGic *GicInstancePtr,
XUartPs *UartInstancePtr, u16 UartIntrId)
{
XScuGic_Config *IntcConfig; //GIC config
Xil_ExceptionInit();
//initialise the GIC
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,//connect to the hardware
GicInstancePtr);
Xil_ExceptionEnable();
XScuGic_Connect(GicInstancePtr, UartIntrId,
(Xil_InterruptHandler)UartIntrHandler,//set up the timer interrupt
(void *)UartInstancePtr);
XScuGic_Enable(GicInstancePtr, UartIntrId);//enable the interrupt for the Timer at GIC
XUartPs_SetInterruptMask(UartInstancePtr, XUARTPS_IXR_RXOVR|XUARTPS_IXR_TOUT /*| XUARTPS_IXR_TXEMPTY | XUARTPS_IXR_TNFUL*/ );
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ); //Enable interrupts in the Processor.
}
这里要注意了,XUartPs_SetInterruptMask这个函数中,需要添加XUARTPS_IXR_TOUT监测超时中断。
接下来去xuartps.c, s32 XUartPs_CfgInitialize(XUartPs *InstancePtr, XUartPs_Config * Config, u32 EffectiveAddr)函数中,修改串口配置,将FIFO从8改到32,超时时间那里说的也比较清楚,写1是4个波特率周期,这里写4就是16个,够用了。
static void UartIntrHandler(void *CallBackRef)
{
XUartPs *InstancePtr = (XUartPs *) CallBackRef;
u32 IsrStatus;
u32 ReceivedCount=0;
u32 CsrRegister;
/*
* Read the interrupt ID register to determine which
* interrupt is active
*/
IsrStatus = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_IMR_OFFSET);//e0001000+10=regaddr=e0001010
IsrStatus &= XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_ISR_OFFSET);//e0001000+14=regaddr=e0001014
/*if((IsrStatus & ((u32)XUARTPS_IXR_RXOVR | (u32)XUARTPS_IXR_RXEMPTY |
(u32)XUARTPS_IXR_RXFULL)) != (u32)0) {
CsrRegister = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,//判断FIFO触发标准位
XUARTPS_SR_OFFSET);//e0001000+2c=regaddr=e000102c
while((CsrRegister & XUARTPS_SR_RXEMPTY)== (u32)0){//读取FIFO中所有数据
XUartPs_WriteReg(InstancePtr->Config.BaseAddress,//每次循环发送读取到的数据
XUARTPS_FIFO_OFFSET,
XUartPs_ReadReg(InstancePtr->Config.
BaseAddress,
XUARTPS_FIFO_OFFSET));
ReceivedCount++;//计数
CsrRegister = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_SR_OFFSET);
}
}
printf("this time ReceivedCount=%d\r\n",ReceivedCount);
XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET,
IsrStatus);*/
if((IsrStatus & ((u32)XUARTPS_IXR_RXOVR)) != (u32)0) //FIFO溢出中断
{
ReceivedCount = XUartPs_Recv(InstancePtr,&uart_send[recv_total_byte],512);
recv_total_byte += ReceivedCount;
XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET,
XUARTPS_IXR_RXOVR); //清中断标志
}
else if((IsrStatus & ((u32)XUARTPS_IXR_TOUT)) != (u32)0) //接收超时中断
{
ReceivedCount = XUartPs_Recv(InstancePtr,&uart_send[recv_total_byte],512);
recv_total_byte += ReceivedCount;
XUartPs_Send(InstancePtr,uart_send,recv_total_byte);
XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET,
XUARTPS_IXR_TOUT); //清中断标志
recv_total_byte = 0;
}
}
接下来当然是重中之重的重写的串口中断函数,之前文章里的中断处理过程在这里屏蔽了,没有删除,可以做下对比。这里对溢出中断和超时中断都进行了处理,不论触发的是哪种中断,都会接收数据放在同一个unsigned char 数组uart_send里面,只在超时中断中,将整个接收到的数据发送出去,计数器recv_total_byte清零,可以更好的保证数据接收的完整性和正确性。
你说不这样搞,简单点,只用溢出中断行不行,行,当然也行,我已经试过了。
XUartPs_SetInterruptMask(UartInstancePtr, XUARTPS_IXR_TOUT);只留下XUARTPS_IXR_TOUT这一块。
中断函数只留下if((IsrStatus & ((u32)XUARTPS_IXR_TOUT)) != (u32)0) 这一个处理,也是能正常收发的,但是上面那种处理肯定更严谨没错。
中断函数中使用到的XUartPs_Recv();和XUartPs_Send();这两个函数,在xuartps.c中可以找到。
2020-03-31更新 完整代码贴上来,费那老鼻子劲代码库里找了半天,还是在这备份下吧
#include
#include "xadcps.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "xuartps.h"
//timer info
#define UART_DEVICE_ID XPAR_PS7_UART_1_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define UART_IRPT_INTR XPAR_XUARTPS_1_INTR
static XScuGic Intc; //GIC
static XUartPs Uart;//uart
unsigned char uart_send[512];
u8 recv_total_byte;
static void UartIntrHandler(void *CallBackRef)
{
XUartPs *InstancePtr = (XUartPs *) CallBackRef;
u32 IsrStatus;
u32 ReceivedCount=0;
u32 CsrRegister;
IsrStatus = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_IMR_OFFSET);//e0001000+10=regaddr=e0001010
IsrStatus &= XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
XUARTPS_ISR_OFFSET);//e0001000+14=regaddr=e0001014
if((IsrStatus & ((u32)XUARTPS_IXR_RXOVR)) != (u32)0) //FIFO溢出中断
{
ReceivedCount = XUartPs_Recv(InstancePtr,&uart_send[recv_total_byte],512);
recv_total_byte += ReceivedCount;
XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET,
XUARTPS_IXR_RXOVR); //清中断标志
}
else if((IsrStatus & ((u32)XUARTPS_IXR_TOUT)) != (u32)0) //接收超时中断
{
ReceivedCount = XUartPs_Recv(InstancePtr,&uart_send[recv_total_byte],512);
recv_total_byte += ReceivedCount;
XUartPs_Send(InstancePtr,uart_send,recv_total_byte);
XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_ISR_OFFSET,
XUARTPS_IXR_TOUT); //清中断标志
recv_total_byte = 0;
}
}
void SetupInterruptSystem(XScuGic *GicInstancePtr,
XUartPs *UartInstancePtr, u16 UartIntrId)
{
XScuGic_Config *IntcConfig; //GIC config
Xil_ExceptionInit();
//initialise the GIC
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,//connect to the hardware
GicInstancePtr);
Xil_ExceptionEnable();
XScuGic_Connect(GicInstancePtr, UartIntrId,
(Xil_InterruptHandler)UartIntrHandler,//set up the timer interrupt
(void *)UartInstancePtr);
XScuGic_Enable(GicInstancePtr, UartIntrId);//enable the interrupt for the Timer at GIC
XUartPs_SetInterruptMask(UartInstancePtr, XUARTPS_IXR_RXOVR|XUARTPS_IXR_TOUT);
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ); //Enable interrupts in the Processor.
}
int main()
{
XUartPs_Config *UartConfigPtr;
UartConfigPtr = XUartPs_LookupConfig(UART_DEVICE_ID);
XUartPs_CfgInitialize(&Uart,UartConfigPtr,UartConfigPtr->BaseAddress);
SetupInterruptSystem(&Intc,&Uart,UART_IRPT_INTR);
recv_total_byte = 0;
while(1);
return 0;
}