一、背景
在先楫HPM6000系列中,DMA控制器分别有HDMA和XDMA,每个控制器有8个通道,一共有16个DMA通道。但是hpm6000系列中的串口有些16个,有些8个等等。如果相对应的每个串口的接收和发送都使用了DMA,那么也不一定够用,而且有些重要的外设也需要DMA。在这种情况下,硬件的串口FIFO就有了实际意义了。
先楫hpm6000系列的串口,发送和接收都有16字节的硬件FIFO。
对于接收使用硬件FIFO,还可以使用接收FIFO timeout标志来接收不定长数据,这个先楫官方公众号对此方案进行了阐述(传送门:再谈 HPM6700/6400/6300 产品系列串口接收不定长数据的方式),对应的例子也在hpm_sdk 1.2版本当中,对应相对路径为 ../hpm_sdk/samples/drivers/uart/uart_rx_timeout
因此,本文只阐述发送端的利用硬件FIFO实现无阻塞发送机制。
二、适用性
相比单字节发送进入中断来说,可以减少16倍的中断次数,比如发送5112字节,不使用FIFO的情况下,发送需要进入512次中断,如果使用FIFO,那么只需要进入32次中断。
所以对于这种方式来说,有一部分应用场景可以适用。
1、使用的串口数量多,但是DMA通道不够用的情况。
2 、发送数据量不大的情况。
3、内部数据交互有ringbuffer,依次出列发送的情况。
4、需要非阻塞发送的情况。
5、实在不想用DMA但是想非阻塞发送的情况。
三、实现流程
对于这个流程,hpm_sdk同样也提供了对应例子,对应相对路径为 ../hpm_sdk/samples/drivers/uart/uart_irq。本文就以此例子作为本文阐述发送的sample。
该例子主要是演示串口中断收发功能,也就是串口的收发都在中断中完成,串口的收发可以通过使能FIFO_MODE来决定是否使用硬件FIFO进行收发。
例子中的CMakelists.txt中,宏定义了CONFIG_UART_FIFO_MODE,默认使能了硬件FIFO模式。这样我们可以根据使能和禁能硬件FIFO来测试发送的中断次数。
(一)配置流程
1、串口引脚和时钟初始化
需要注意的是,在自己的项目当中,需要先初始化好引脚,再开启工作时钟。否则可能会出现串口收发上电乱码的问题。这是因为这时候串口已经开始工作,而后初始化引脚后检测到串口电平的变化导致认为是数据产生。
在hpm_sdk中,每个官方开发板的board.c中对于串口的初始化都是基于上述配置。比如hpm6750evk2的board的串口初始化。内部函数也做了注释说明,为了其可靠性,建议开发者按照官方操作流程进行初始化。
2、串口配置初始化
对于配置初始化,比如设置波特率,parity,传输长度等等,在hpm_sdk中,已经封装为uart_config_t结构体进行描述,在其结构体成员中赋值对应成员,然后调用uart_init这个API接口,即可完成串口配置初始化。
在uart_config_t成员赋值中,驱动提供了一个缺省接口赋值,建议开发者可以进行调用,以便串口可以在缺省安全的参数启动。
(二)硬件FIFO开启
在hpm_sdk的uart_irq这个sample中,根据上述阐述,可以通过fifo_enable进行对硬件FIFO的开启和使能,对串口的数据寄存器,手册中,定义了两个寄存器,一个是接收一个是发送。
分别是RBR和THR寄存器。开发者可以根据FIFO模式进行对应的赋值操作。
在hpm_sdk中,对于这两个寄存器,注释也做了说明。
当为FIFO模式的时候,这两个寄存器相当接收和发送硬件FIFO,而当非FIFO模式,也就是BUFFER模式的时候,这两个寄存器仅仅是个字节缓存,也就是单个读写。
(三)填充FIFO,使能中断
经过上述初始化之后,需要开启相关中断,对于中断使能位,手册和SDK同样提供了对应了寄存器操作API。比如hpm6700系列。
在这里使能了发送FIFO为空时候产生中断,也就是上述的ETHEI位域,以便告知这时候发送的硬件FIFO是为空的,可以填充数据到FIFO。
当为非FIFO模式的时候,THRE标志产生的中断标志依旧可以作为发送单字节完成标志。
在发送利用硬件FIFO流程:
1、在填充硬件FIFO之前,需要关闭掉对应的中断,然后根据发送的长度和FIFO长度(16字节)比较得出最小长度。以便保证FIFO空间不溢出,塞满整个硬件FIFO,之后开启FIFO为空中断
2、进入中断之后,重复1的操作,进而在中断里面继续塞入硬件FIFO
3、在中断自己操作2,直到整个发送数据完成。
这个流程,SDK中的例子也封装好了演示接口
那么当接收收到指定的长度数据后,开始在中断调用以上接口,以此在中断循环上述流程。详细的代码参考可以在hpm_sdk对应的sample中看到。
四、验证
在官方的sample中,可以做个测试,以此来验证FIFO模式和非FIFO模式发送所需要的中断次数。收发数据固定为128字节。
定义一个中断次数变量,每当进入一次FIFO为空中断标志,累加1。发送完成打印中断次数。
可以从打印log看到,除了第一次配置发送不需要在中断,按16个字节FIFO,128字节需要填充8次FIFO,相比单字节发送中断次数需要128次中断,利用硬件FIFO发送只需要7次,缩短16倍以上的次数。
五、实战(ringbuffer+硬件FIFO实现异步非阻塞发送)
在实际项目当中,对于发送来说,有一部分利用ringbuffer 软件FIFO异步发送来实现无阻塞发送方案。
把需要发送的数据入列到软件ringbuffer,在一个线程或者状态机,轮询ringbuffer是否有待出列的数据,如果有就进行发送。实际发送的实现可以DMA发送,或者阻塞发送一定数据量。
在这里可以使用上述的阐述,流程如下:
1、在有待发送的软件FIFO出列时候,先判断下硬件FIFO是否为空,若为空,则关闭发送为空的中断,然后读取ringbuffer数据,塞入到硬件FIFO当中
2、当ringbuffer仍有数据,那开启发送为空的中断,若没有,那就是这一帧发送完成,不需要再开中断。
3 、进入中断后,循环1。
如此以来可以实现异步无阻塞发送,并且最大化减少中断次数。
串口中断判断是否为发送FIFO为空标志,若是,则硬件FIFO可以继续塞入数据,再次调用以上接口
效果如下:
六、总结
1、先楫对于串口这个外设,提供了16字节的发送和接收硬件FIFO,开发者可以根据自己需求对其进行利用。
2、对于DMA不适用的场景,比如上述的背景描述,可以利用16字节的硬件FIFO,相比非FIFO能减少16倍以上的中断次数来最大化实现发送性能,实现非阻塞式发送。