前言
自笔者提出 serialX 串口驱动到今天近半年了,当初只在 STM32F4 NUC970 两个系列芯片上做过理论验证。一个是 ARM CM4 核心架构,一个是 ARM9。这两款芯片能完美实现笔者的需求。
经过这半年的实践考验,笔者还是相信 serialX 的实力的,最近这几天笔者尝试在 N32 AB32 RA6M4 上适配 serialX,下面就向各位汇报一下适配结果。
芯片 | STM32F4 | NUC970 | N32 | AB32 | RA6M4 | GD32F4 |
架构 | CM4 | ARM9 | CM4 | RISC-V | CM33 | CM4 |
N32G45
因为这个也是 CM4,和 STM32F4 相较而言,可能差别很小。让笔者感到欣慰的是用`DMA_GetFlagStatus(uart->dma_tx.dma_flag, uart->dma_tx.dma_module) == SET` 代替了 `uart->dmaTxing` 。这是一处小改进。
除此之外,没啥可说的了。
已实现的功能有:
轮询收发
中断收发(可阻塞可非阻塞)
DMA 收发(可阻塞可非阻塞)
AB32VG1
这个是 RISC-V 架构的 CPU。
从芯片手册我们可以看到,它的串口外设只有“接收一个字节完成”和“发送一个字节完成”两个中断。
在 serialX 的设计构想里,我们希望有个“发送寄存器空”中断。因为这样很容易启动一次中断,在中断里判断是否有数据需要发送,进而启动一次发送过程。
假如没有这个中断,我们必须通过先写一个字节引起一次“发送完成中断”,然后借助这次中断继续判断是否有数据需要发送。在数据所有数据发送完之前,我们还需要有个 flag 标识一下现在处于发送流程中。
因此,serialX 需要进行一些改动:
`_serial_int_tx` 函数
// TODO: start tx
#if defined (RT_SERIAL_NO_TXEIT)
if (serial->ops->is_int_txing != RT_NULL && serial->ops->is_int_txing(serial) == RT_FALSE) {
ch = _serial_fifo_pop_data(tx_fifo);
serial->ops->start_tx(serial, ch);
}
#else
serial->ops->start_tx(serial);
#endif
```
`struct rt_uart_ops`
```
#if defined (RT_SERIAL_NO_TXEIT)
rt_bool_t (*is_int_txing)(struct rt_serial_device *serial);
void (*start_tx)(struct rt_serial_device *serial, rt_uint8_t ch);
#else
void (*start_tx)(struct rt_serial_device *serial);
#endif
因为这些改动,AB32VG1 的底层驱动写法也就不一样了,多了一个判断是否处于发送流程中的 api。start_tx stop_tx 也不仅仅是开关中断那么简单了,需要改变 intTxing 这个 flag 标识发送流程状态。
rt_bool_t ab32_int_txing(struct rt_serial_device *serial)
{
struct ab32_uart *uart;
RT_ASSERT(serial != RT_NULL);
uart = rt_container_of(serial, struct ab32_uart, serial);
return uart->intTxing;
}
static void ab32_start_tx(struct rt_serial_device *serial, rt_uint8_t ch)
{
struct ab32_uart *uart;
RT_ASSERT(serial != RT_NULL);
uart = rt_container_of(serial, struct ab32_uart, serial);
uart->intTxing = RT_TRUE;
hal_uart_control(uart->handle.instance, UART_TXIT_ENABLE, HAL_ENABLE);
hal_uart_write(uart->handle.instance, ch);
}
static void ab32_stop_tx(struct rt_serial_device *serial)
{
struct ab32_uart *uart;
RT_ASSERT(serial != RT_NULL);
uart = rt_container_of(serial, struct ab32_uart, serial);
hal_uart_control(uart->handle.instance, UART_TXIT_ENABLE, HAL_DISABLE);
uart->intTxing = RT_FALSE;
}
为此,我们需要添加个新配置,components/drivers/Kconfig
config RT_SERIAL_NO_TXEIT
bool "No TX Empty interrupt"
default n
help
Useful only if the chip hasn't Transmit Register Empty interrupt
Such as: AB32 RA6M4
意思是说,当芯片没有“发送寄存器空中断”支持的时候,我们需要用 `intTxing` 代替实现控制发送过程。
另外,发送寄存器也没有空状态,`putc` 函数倒是可以判断发送完成标志,但是这样就不能在中断里调用 `putc` 了;不加发送完成判断,就不能在轮询发送中调用它。总之,轮询发送和中断发送不用用一样的 `putc` 函数了。
已实现的功能有:
中断收发(可阻塞可非阻塞)
RA6M4
RA6M4 是一款 CM33 核 ARM 芯片,本以为它比 CM4 高级可以很容易实现 CM4 上实现的操作。
但是,笔者也没有从手册中找到“发送寄存器空中断”。所以 RA6M4 和 AB32VG1 有一样的补救处理。
但是,笔者还发现另外一个问题,**如果是中断发送,每次写完 TDR 寄存器后,必须重新使能发送中断**。不这样做,就不会出现发送完成中断。
虽然如此,连续发送多个字节仍然会出现发送中断不触发(或丢失)的情况,导致发送功能完全瘫痪(这也是 `intTxing` 引入的隐患)。
已实现的功能有:
中断接收(可阻塞可非阻塞)
中断发送(未完),暂时可以用轮询发送代替
多说两句,RA6M4 的 SCI 好像可以启用 FIFO ,这样一来串口收发寄存器就是带 FIFO 的。遗憾的是笔者不会用啊,有会用的大佬可以尝试移植一下,用 FIFO 了就相当于用 DMA 了。
GD32F4
这个也可以做到和 STM32F4 一样的程度,DMA 没有发送标志,只能继续用 `dmaTxing` 。
已实现的功能有:
轮询收发
中断收发(可阻塞可非阻塞)
DMA收发(可阻塞可非阻塞)
注:只分配了 UART0 的 DMA 通道,如果其它的也需要开启 DMA 请自行修改 `struct gd32_uart uarts` 数组变量分配 DMA 通道。
注:还有一点,rt-studio 里下载的 GD32F4 firmware 库版本是很多年前的,现在已经改动过好几次了。笔者使用的 `gd32f4xx_usart.h` 版本是 “2020-09-30, V2.1.0, firmware for GD32F4xx” 。如有编译错误请升级 firmware 库。
结束语
关于 serialX 理论的部分,之前的文章已经说的够多了。这次是想在多种平台上用实践检验一下 serialX 理论的可行性。经过这几天的投入,最终多多少少有些收获,还是很欣慰的。
汇总一下,目前可以适配的芯片包括如下几类
1. 没有 DMA ,只有串口接收发送中断
2. 没有“发送寄存器空”状态或没有“发送寄存器空”中断
3. 带接收 IDLE 检测,带“发送寄存器空”中断
4. 带 DMA ,并且至少有 DMA 半传输中断和全传输中断
5. 串口外设自带收发 FIFO (可认为是 DMA ,但是比 DMA 使用更简单)
在此,特别感谢[嚜軒公告](https://club.rt-thread.org/u/7c37fff6229d1ccd)支援的开发板,最终完成了 serialX 在这些平台上的实现。
下期预告,我们来扒一扒 serialX 的缺陷,对,它的缺陷。准确的讲是在 RTOS 上引入的坑有哪些以及怎么避免。
附 [serialX](https://gitee.com/thewon/serialX) 仓库地址,感兴趣的可以下载最新版 serialX 源码。本文提到的几种芯片的驱动也都已提交。