随笔 - 39  文章 - 0  评论 - 37  阅读 - 10万

zynq串口接收超时加软件FIFO+printf重定向

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 }

 printf重定向

zynq裸机的printf重定向是需要重载write函数,而write函数是封装在BSP中了,在应用程序中重载不成功。所以需要改BSP:

在ps7_cortexa9_0\libsrc\standalone_v6_8\src\write.c中重写write函数:

1 void void_write(u8 *p,int n){}
2 void (*printf_write)(u8 *p,int n)=void_write;
3 __attribute__((weak)) sint32 write (sint32 fd, char8* buf, sint32 nbytes)
4 {
5     printf_write(buf,nbytes);
6     return nbytes;
7 }

然后在应用程序中给printf_write函数指针赋值,将其设置为想要的数据流中,例如设置为uart0:

在main.c中添加:

复制代码
 1 void write (u8 *buf, int n) //重定向printf
 2 {
 3     uart_send((u8*)buf,n,&uart0);
 4 }
 5 extern void (*printf_write)(u8 *p,int n);
 6 
 7 int main()
 8 {
 9     …………
10     printf_write=write;
11     printf("hello");
12 }
复制代码

然后就能用非阻塞的printf了,中断里也能用,打印数据量大的话,可以提高队列容量

posted on   yangzifb  阅读(940)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 百万级群聊的设计实践
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示