V1版本串口框架(以及驱动)总结
发送模式不够完善:
串口驱动中,发送中断模式未发挥出中断应有的特性,造成使用中断时,仍然需要浪费大量CPU时间,影响系统整体性能。
DMA发送使用不当容易丢包:
当写数据为轮询或者中断时,调用rt_device_write正确返回后即代表数据发送完成。而使用DMA时,调用rt_device_write返回后仅仅代表数据准备完毕,并不能代表数据发送完成,如果用户在正确返回后再次调用rt_device_write,将有可能出现上次数据未发送完成,数据又被修改的发送数据错误的问题。
不同模式时,影响应用层执行逻辑:
结合第二点再引申下来这个问题:当使用中断和轮询时,发送模式为阻塞模式,使用DMA时,发送模式为非阻塞模式,且未对数据块进行保护。另外不仅仅是发送端的考虑,接收端也有可能会出现阻塞和非阻塞的模式选择问题。因此框架层应该更多关心阻塞非阻塞的操作模式,使得应用层执行流程能够统一。
V2版本的串口框架(以及驱动)主要改动点:
取消了硬件工作模式的判断,硬件工作模式由驱动层支持,使得框架层与 硬件工作模式 无关;
统一操作接口,应用层不再关心 硬件工作模式,
统一使用 阻塞/非阻塞 操作模式,不会因为模式的变更导致应用层逻辑代码行为不一致。
增加发送缓冲区功能,保证应用层数据的完整性,从而解决丢包率;
每一路的串口都有独立的发送缓冲区和接收缓冲区的宏定义,取代之前的所有串口默认使用RT_SERIAL_RB_BUFSZ这一个宏定义。
完善工作模式,分工明确,轮询、中断、DMA都能按照正确的工作模式执行。
ps: 这里我用了两个词汇用来区分模式:硬件工作模式和应用操作模式,这两个词汇可能不是官方用词,这里旨在用来区别描述上的混淆和困扰
硬件工作模式: 指串口的三种模式,代表的是使用轮询、中断、DMA进行操作串口时的工作模式。
应用操作模式: 指串口的实际操作模式,代表的是应用层使用串口时选择使用阻塞或非阻塞传输的操作模式。
V1和V2版本对比
V2版本的在使用上的改动点,上文已经描述过了,下面主要从代码层面以及用户使用上对比两个版本的差异。
结构体的成员变量的变更
serial_configure
修改V1版本的bufsz为rx_bufsz,指代的含义未变更,均指代接收缓冲区字节长度;增加发送缓冲区字节长度的成员变量tx_bufsz。具体如下代码段所示:
/* V1版本的成员变量 */
struct serial_configure
{
rt_uint32_t baud_rate;
rt_uint32_t data_bits :4;
rt_uint32_t stop_bits :2;
rt_uint32_t parity :2;
rt_uint32_t bit_order :1;
rt_uint32_t invert :1;
rt_uint32_t bufsz :16;
rt_uint32_t reserved :6;
};
/* V2版本的成员变量 */
struct serial_configure
{
rt_uint32_t baud_rate;
rt_uint32_t data_bits :4;
rt_uint32_t stop_bits :2;
rt_uint32_t parity :2;
rt_uint32_t bit_order :1;
rt_uint32_t invert :1;
rt_uint32_t rx_bufsz :16;
rt_uint32_t tx_bufsz :16;
rt_uint32_t reserved :6;
};
与其对应的是宏定义RT_SERIAL_CONFIG_DEFAULT的变更:
/* V1版本的串口的宏定义RT_SERIAL_CONFIG_DEFAULT */
#define RT_SERIAL_CONFIG_DEFAULT \
{ \
BAUD_RATE_115200, /* 115200 bits/s */ \
DATA_BITS_8, /* 8 databits */ \
STOP_BITS_1, /* 1 stopbit */ \
PARITY_NONE, /* No parity */ \
BIT_ORDER_LSB, /* LSB first sent */ \
NRZ_NORMAL, /* Normal mode */ \
RT_SERIAL_RB_BUFSZ, /* Buffer size */ \
0 \
}
/* V2版本的串口的宏定义RT_SERIAL_CONFIG_DEFAULT */
#define RT_SERIAL_CONFIG_DEFAULT \
{ \
BAUD_RATE_115200, /* 115200 bits/s */ \
DATA_BITS_8, /* 8 databits */ \
STOP_BITS_1, /* 1 stopbit */ \
PARITY_NONE, /* No parity */ \
BIT_ORDER_LSB, /* LSB first sent */ \
NRZ_NORMAL, /* Normal mode */ \
RT_SERIAL_RX_MINBUFSZ, /* rxBuf size */ \
RT_SERIAL_TX_MINBUFSZ, /* txBuf size */ \
0 \
}
注意:这里需要注意的是,V2版本完全删除了RT_SERIAL_RB_BUFSZ宏定义。因此如果用户在使用V2版本时候遇到之前的代码或者软件包使用到了RT_SERIAL_RB_BUFSZ,那么需要将其更改为对应的串口设备的接收缓冲区的值。这个下文会再进一步阐述。
rt_uart_ops
更改V1版本的dma_transmit操作接口为transmit:
V1版本的dma_transmit 用于DMA传输,而V2版本的transmit用于阻塞和非阻塞传输(或者认为是中断和DMA模式的传输)
/* V1版本 rt_uart_ops */
struct rt_uart_ops
{
... ...
rt_size_t (*dma_transmit)(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction);
};
/* V2版本 rt_uart_ops */
struct rt_uart_ops
{
... ...
rt_size_t (*transmit)(struct rt_serial_device *serial,
rt_uint8_t *buf,
rt_size_t size,
rt_uint32_t tx_flag);
};
发送接收缓冲区结构体
/* V1版本自主实现ringbuffer */
struct rt_serial_rx_fifo
{
rt_uint8_t *buffer;
rt_uint16_t put_index, get_index;
rt_bool_t is_full;
};
struct rt_serial_tx_fifo
{
struct rt_completion completion;
};
struct rt_serial_rx_dma
{
rt_bool_t activated;
};
struct rt_serial_tx_dma
{
rt_bool_t activated;
struct rt_data_queue data_queue;
};
/* V2版本使用RTT自带的ringbuffer结构体 */
struct rt_serial_rx_fifo
{
struct rt_ringbuffer rb;
struct rt_completion rx_cpt;
rt_uint16_t rx_cpt_index;
rt_uint8_t buffer[];
};
struct rt_serial_tx_fifo
{
struct rt_ringbuffer rb;
rt_size_t put_size;
rt_bool_t activated;
struct rt_completion tx_cpt;
rt_uint8_t buffer[];
};
宏定义的差别
缓冲区宏定义的差别
V1版本统一使用的RT_SERIAL_RB_BUFSZ,每个串口设备统一使用该参数设置接收缓冲区长度。
V2版本删除了该宏定义,取而代之的是让每一个串口都有独立的发送缓冲区和接收缓冲区的宏定义。
举例说明:
/* V1版本 */
#define RT_SERIAL_RB_BUFSZ 64
/* V2版本缓冲区宏定义 */
#define BSP_UART1_RX_BUFSIZE 256
#define BSP_UART1_TX_BUFSIZE 0 /* 不需要发送缓冲区,用于轮询模式 */
#define BSP_UART2_RX_BUFSIZE 256
#define BSP_UART2_TX_BUFSIZE 256
另外需要注意的是,当用户需要使用缓冲区,且缓冲区设置值小于64字节时,将会按照64字节设置。
#define RT_SERIAL_RX_MINBUFSZ 64
#define RT_SERIAL_TX_MINBUFSZ 64
模式选择上的差别
V2版本 基本完全兼容V1版本的串口使用方式,即统一使用rt_device的设备驱动框架。
唯一的区别在于V1版本的串口打开标志是:
/* 接收模式参数 */
#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收模式 */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA 接收模式 */
/* 发送模式参数 */
#define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送模式 */
#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA 发送模式 */
V2版本串口打开标志是:
/* 接收模式参数 */
#define RT_DEVICE_FLAG_RX_BLOCKING 0x1000 /* 接收阻塞模式*/
#define RT_DEVICE_FLAG_RX_NON_BLOCKING 0x2000 /* 接收非阻塞模式*/
/* 发送模式参数 */
#define RT_DEVICE_FLAG_TX_BLOCKING 0x4000 /* 发送阻塞模式*/
#define RT_DEVICE_FLAG_TX_NON_BLOCKING 0x8000 /* 发送非阻塞模式*/
注意:当用户使用V2版本的串口框架时,设置的参数是V1版本的,那么无论设置的何种模式,都会统一按照(发送阻塞模式和接收非阻塞模式)进行配置。
该模式也是串口使用最为广泛的模式组合方式。
其他
V2版本的模式有四种操作模式的配置,然而对应到串口驱动层也有三种硬件工作模式的配置,因此使用起来就是需要将操作模式和工作模式进行组合使用的。
例如 发送阻塞接收非阻塞 ,这个测试有很多种硬件配置,配置情况例如:DMA发送阻塞DMA接收非阻塞、INT发送阻塞DMA接收非阻塞、POLL发送阻塞DMA接收非阻塞等等。
因此通过排列组合后的测试场景有4*9=36种,有意义的组合方式为20种。如下表:
需要解释的是,为什么会存在无意义的组合模式,举个例子,非阻塞模式下,肯定是不会出现POLL(轮询)方式的,因为POLL方式已经表明是阻塞方式了。
这部分的内容详见serial_v2的测试用例的说明。
总结
本章节主要介绍了串口V1和V2对比差异,并引出串口V2的一些设计实现思想。目前串口V2版本已经在ART-PI的serial_lab分支进行了适配。
原作者:123