zynq的PS端裸跑时,其串口带有硬件FIFO,可大大降低中断频率。配合接收超时中断,可实现任意长度数据的非阻塞收发。
应用与驱动解耦
为实现驱动层与应用层解耦,不在中断服务函数中执行处理操作,串口的收发均使用软件环形队列解耦。
发送时:应用层将数据流写入发送队列,驱动层不是立刻发送,而是闲时从队列中取得,发出(发送至硬件FIFO)
接收时:驱动层将串口数据从硬件FIFO中取出,送入接收队列。应用层不是立刻处理,而是闲时从队列中取得,进行处理。
这样,驱动层中断服务函数仅操作队列,而应用层完全由主函数调用。
串口设备初始化
本来想使用Xilinx的固件库,可是他这库和STM32的固件库一样臃肿,串口本身非常简单,直接自己写了:
定义寄存器:(参考ug585)
1 #pragma pack(1) 2 typedef struct 3 { 4 vu32 CR; //使能收发 5 vu32 MR; //停止位等 6 vu32 IER; //写1中断使能 7 vu32 IDR; //写1中断失能 8 vu32 IMR; //中断是否使能 9 vu32 ISR; //中断状态 10 vu32 BAUD; 11 vu32 RX_TO; //接收超时,单位4bit 12 vu32 RX_WM; //接收FIFO触发数 13 vu32 MODEMCR; 14 vu32 MODEMSR; 15 vu32 SR; //状态寄存器: 16 //RXFIFO超限,RX空,RX满,TX空,TX满 17 vu32 FIFO; //最多64 18 vu32 BAUD_DIV; 19 vu32 FLOW_DELAY; 20 vu32 TX_TR; //发送FIFO触发数 21 } UART_TypeDef; 22 #pragma pack() 23 24 #define UART0 ((UART_TypeDef*)0xe0000000) 25 #define UART1 ((UART_TypeDef*)0xe0001000)
然后就能用UART0这样的方式来访问寄存器了。然后初始化寄存器:
1 void uart_initial(S_UART *obj,int b) 2 { 3 int irq=XPAR_XUARTPS_0_INTR; 4 if(obj->uart==UART0) 5 { 6 Queue_ini(uart0_tx_buf,sizeof(uart0_tx_buf),&(obj->que_tx)); 7 Queue_ini(uart0_rx_buf,sizeof(uart0_rx_buf),&(obj->que_rx)); 8 } 9 if(obj->uart==UART1) 10 { 11 Queue_ini(uart1_tx_buf,sizeof(uart1_tx_buf),&(obj->que_tx)); 12 Queue_ini(uart1_rx_buf,sizeof(uart1_rx_buf),&(obj->que_rx)); 13 irq=XPAR_XUARTPS_1_INTR; 14 } 15 obj->uart->BAUD_DIV=6; //默认15误差大 16 obj->uart->BAUD=100000000/b/(obj->uart->BAUD_DIV+1); 17 obj->uart->CR = 0x14; //使能收发 18 obj->uart->MR = 0x20; //n81 19 obj->uart->RX_TO = 200; //单位4bit 20 obj->uart->RX_WM = 60; //接收FIFO触发数 21 obj->uart->TX_TR = 60; //发送FIFO触发数 22 // obj->uart->IER = (1<<8) | //RX_TO 23 // (0<<3) | //TX空 24 // (1<<0); //接收 25 obj->uart->IER = 0x101; //接收 26 obj->uart->IER = 0x101; //接收,需要写两遍 27 28 irq_reg(irq,uart_irq,obj); 29 irq_enable(irq,1); //使能GIC中的串口中断 30 }
其中波特率为频率除以分频数再除以BAUD寄存器的值,为了保证精度,分频数需要选的比较小,默认为15(15+1分频),出115200波特率的时候误差比较大,无法正确接收数据。
收发FIFO触发数可以选择比较大,在不到FIFO触发数量时,通过接收超时机制来读数据。
其中IER不知道为啥需要写2遍,才能把接收超时的中断打开。
配置好串口寄存器后,通过GIC开串口中断。
串口发送流程
串口的发送是通过发送FIFO空中断实现的,机制与STM32的串口发送一样:
1、当无数据需要发送时,发送缓冲区空中断是关闭的。
2、当应用层需要发送数据时,所需发送的数据入队列,开发送缓冲区空中断,发送函数返回,不阻塞应用层调用。
3、由于发送缓冲区无数据,所以立刻进入中断,在中断中,从队列中取得数据,填入发送FIFO,直到FIFO接近满
4、中断返回
5、待FIFO中的数据发送完成,继续进发送空中断
6、中断中继续发送,或判断发送完成了,关闭发送空中断。
对比STM32的每个字节一个中断,zynq通过发送FIFO,可以实现60多字节一次中断,降低了中断频率。
发送函数:
1 void uart_send(u8 *p,int n,S_UART *obj) //串口发送 2 { 3 int i; 4 OS_CLOSE_INT; 5 for(i=0;i<n;i++) 6 { 7 Queue_set_1(p[i],&(obj->que_tx)); 8 } 9 obj->uart->IER = (1<<3);//TXE使能 10 OS_OPEN_INT; 11 }
在发送函数中,先关闭中断,进入临界区,才能操作队列。
串口接收流程
串口的接收使用FIFO触发中断,设置触发数量为60,则串口接收满60字节才会触发中断。若串口需接收的字节数少于60个,则不会触发此中断。因此,还需要串口接收超时中断实现短帧接收。
串口接收超时机制需要在串口中断结束时人工复位:CR 的 bit6置位,然后才会产生新的计时
串口接收超时中断使能会被串口发送空中断失能信号清除,所以要注意在中断中使能此中断。
最后不能忘了清除中断标志,否则会一直出不来。中断服务函数:
1 void uart_irq(void *para) 2 { 3 u8 t; 4 S_UART *obj=(S_UART*)para; 5 //u32 irq=obj->uart->ISR; 6 obj->uart->ISR=0xfff; //清中断 7 obj->uart->ISR=0xfff; //清中断,为什么需要清两次? 8 9 while((obj->uart->SR & 0x02)==0)//接收非空 10 { 11 t=(u8)(obj->uart->FIFO); 12 Queue_set_1(t,&(obj->que_rx)); 13 } 14 if(obj->uart->SR & 8)//发送空TXE 15 { 16 while((obj->uart->SR & (1<<14))==0) //TX 没有接近满 17 { 18 if(Queue_get_1(&t,&(obj->que_tx))==0) 19 { 20 obj->uart->FIFO=t; 21 } 22 else 23 { 24 obj->uart->IDR = (1<<3);//TXE失能,会导致接收超时的中断失能 25 obj->uart->IER |= (1<<8); //接收超时 26 break; 27 } 28 } 29 } 30 obj->uart->CR = 0x14 | (1<<6); //使能收发,超时重置 31 }
应用层队列访问
在应用层访问队列时,需要通过临界区实现队列的互斥:
1 int get_que_data(u8 *p,Queue *q) 2 { 3 int rst=1; 4 OS_CLOSE_INT; 5 rst=Queue_get_1(p,q); 6 OS_OPEN_INT; 7 return rst; 8 }
在主循环中查询接收队列:
1 if(uart0.que_rx.dlen>0) //这个只读不用锁 2 { 3 while(get_que_data(&tt,&uart0.que_rx)==0) 4 { 5 rec_head(tt,&apptest_syn_obj); 6 } 7 }
标签:obj,uart,FIFO,中断,zynq,串口,vu32 From: https://www.cnblogs.com/yangzifb/p/17216883.html