本周笔者花了好多天的时间,计划从多个方面对串口驱动做个比较。下面就从以下几个角度做个对比测试。
其它未测试项:stream 支持,因为 v1 v2 只有 poll 模式支持, serialX 可以全模式支持,这一项未进行对比。
版本 | poll收发 | 阻塞/非阻塞 | 驱动层缓存 | DMA支持 | STREM 支持 |
---|---|---|---|---|---|
v1 | Y | - | - | Y | 仅poll |
v2 | Y | * | Y | Y | 仅poll |
X | Y | Y | Y | Y | 全模式 |
* v2 对阻塞概念的认识,仅认为是降低 cpu 耗用。
测试过程:
版本 | v1 | v2 | X |
---|---|---|---|
测试结果 | 通过 | 通过 | 通过 |
用 poll 模式打开串口,发送若干数据。
版本 | v1 | v2 | X |
---|---|---|---|
测试结果 | 通过 | 通过 | 通过 |
如果没有 flush ,驱动缓存的数据可能没有完全输出到外设,这个时候 close 设备可能出现丢失部分数据。
使用 flush 的目地就是保证驱动层缓存数据完全输出到外设,之后对设备的任何操作不会影响之前的数据。
版本 | v1 | v2 | X |
---|---|---|---|
测试结果 | 不支持 | 不支持 | 通过 |
因为 v1 不支持非阻塞发送,也没有驱动层缓存,write 总是把最后一个字节写到串口移位寄存器后才返回。所以 v1 不会出现丢失数据的现象。
v2 在这一环节的表现和 v1 是一样的,大家可以猜猜原因是啥。
注:本部分为了测试 flush 特性有效性,因此 X 出现 close 的时候出现丢数现象。使用版在 close 设备的时候应该强制 flush 一下的。
使用中断非阻塞模式打开串口设备,发送 10k 左右数据量,同时测量一下时间。
数据量 | v1 | v2 | X |
---|---|---|---|
102400 | 102400 / 9762ticks | 102400 / 8863ticks | 102400 / 8863ticks |
10240 | 10240 / 976ticks | 10240 / 876ticks | 10240 / 876ticks |
128 | 128 / 12ticks | 128 / 11ticks | 128 / 11ticks |
这部分测试大体上符合预期,因为有缓存,v2 和 X 先把数据放到缓存中就返回了。这样可以减少发送等待时间。
数据量 | v1 | v2 | X |
---|---|---|---|
102400 | 102400 / 9762ticks | 102400 / 8902ticks | 102400 / 8866ticks |
10240 | 10240 / 976ticks | 10240 / 890ticks | 10240 / 884ticks |
128 | 128 / 12ticks | 128 / 11ticks | 128 / 11ticks |
v1 在非阻塞和阻塞两种模式下的表现是一样的,因为它没有阻塞概念。
v2 耗时比 v1 少,这是在预料中的,但是,它还是比 X 多了几个 tick 。这也是上文中工作模式对照部分对它的阻塞/非阻塞特性加 * 的原因。
特别测试,当每次写小于串口驱动层缓存大小的数据时,
数据量 | v2 | X |
---|---|---|
16 | 1ticks | 0ticks |
32 | 5ticks | 0ticks |
128 | 11ticks | 0ticks |
为什么出现了和上面表格不一样的结果,因为这次测试,每次写之前有个 1s 延时,保证串口缓存是空的。当串口缓存大小是 N 前提下,每次 write 小于等于 N 数量的数据应该可以直接写到缓存,并立马返回!所以,对于 X 来说耗时就是 0。
这个很重要,当我们用串口调试程序,需要打印一些信息的时候,又不希望因为串口输出数据影响到其它业务的时序,或者,最大限度地降低因串口输出数据而影响其它程序执行时序。
使用阻塞模式打开串口设备。这次通过串口调试助手以 20ms 的定时间隔,发送 384 字节数据。
版本 | v1 | v2 | X |
---|---|---|---|
丢失率 | 671144 / 556848/17.03% | 1208816/1070464/11.45% | 2390800/2390800/0% |
v2 在这一步表现很差,第一次,笔者应用层缓存是 512 字节,想
rt_device_read(uart, -1, recvbuf, 512);
发现 read 不到任何数据,read 也不阻塞了,而是总能返回,单步进去看到,但接收的数据大于驱动缓存的时候,驱动拒绝处理,直接返回0!!!v2 的缺陷之一。鉴于以上原因,之后改成
rt_device_read(uart, -1, recvbuf, 128);
应用缓存和驱动缓存大小相等。手动单次发送,一次发送 344 字节数据(多于驱动缓冲大小),接收 256 字节,再次发送,接收 384 字节,第三次发送接收还是 256 字节,第四次又变成 384字节。
即便考虑到 v2 的上述缺陷,最多有 127 个字节数据被“滞留”串口驱动缓存里未及时返回。也弥补不了上述丢失率!
很遗憾,v1 只支持 DMA 接收不支持 DMA 发送(估计以后也用不上 v1 了),由以上对比测试我们发现 v2 和 v1 很类似,在测试 v2 DMA 接收发送时也发现总体效果和使用中断没多少差异。
X 的表现如何呢?等待您的发现!
遗憾的是,笔者对 STM32 的 HAL 极其不熟悉,又极其不想用 HAL 。花了很长时间想自己通过寄存器配置实现,最终没成功,还是放弃了。
HAL 有一个好处,那就是几乎可以适配 STM32 所有系列芯片。但是,HAL 不是为 OS 而生的
#error "USE_RTOS should be 0 in the current HAL release"
,在 OS 上用终究有可能遇到失锁的问题。使用 HAL 还有个小小的瑕疵,那就是
is_dma_txing
判断变得不友好,无奈之下,笔者在struct stm32_uart
中添加了个rt_bool_t dmaTxing;
变量 —— ”HAL 中 gState 和 RxState 已经够多了“ 。算是目前的一个小遗憾吧。
最后,依旧公开测试代码,本次测试使用的代码可以在 serialX 仓库找到。近期,笔者也会将 serialX 提交到 rt-thread 主仓库。
提前预告,下次我们来聊聊 serialX 在做控制台串口时遇到的问题已经解决方案(包括使用中断 DMA 收发模式打开的串口设备)。
相关文章:
👍👍👍
可以用rtt的AT框架试一下,AT框架那边报告了多起接收数据丢失的问题,疑似是由于串口v1框架引起的 配置过程可参见 https://github.com/RT-Thread/rt-thread/blob/master/bsp/stm32/stm32l475-atk-pandora/board/Kconfig#L496
楼主的测试确实费心了,给你 32 个赞哈😘🥰😍🤩🤩🤩
关于丢包率,其实也要看测试用例及环境,除了丢包率其实还有个指标蛮重要的,就是 CPU 占用率。
和大家分享我现在 V2 的使用场景及体验哈,目前我主要用在 ART-Pi 平台,用于 H7 与 AP6212 的通讯,波特率是 3M ,开启了硬件流控,DMA 发送和 DMA 接收都有使用,而且还是用的标准的 POSIX API 来配置前面的一堆串口参数。
以前用 V1 时存在的串口参数上层感知严重,导致 POSIX 接口无法对接 DMA ,6212 通信时CPU 占用爆满的问题,还有硬件流控的问题,再 V2 上都解决了,目前基于 V2 框架,经典蓝牙书传输速度甚至能做到 200KB+ ,压测条件稳定工作 8H。
关于 STREAM 模式,我感觉好像是没必要让 DMA 模式也支持,毕竟其实串口设备是 CHAR 字符设备,就是一个个字符处理的通信设备,本身不是设计处理块数据的设备,把 DMA 块拆开,中间增加 \r 效率上会不会也有下降呢。
最后感觉楼主是不是也可以把你觉得好的点,合并到 V2 上呢,大家共同维护一个主线,这样我们精力也会比较聚焦。当然做成一个新框架也不是不行,就是太多会比较分裂,使用者也会比较迷茫😬
给楼主点赞。
@123 “串口调试助手以 20ms 的定时间隔,发送 384 字节数据”
这就是测试条件,场景很复杂?请通篇看全所有说明
文章最后一句预警了兼容性问题,请通篇看全所有说明
为什么一直没支持,看我下一篇文章。没人提 pr 就没人提 issue ?去 github 上翻翻 issue 吧。
您理解的阻塞是什么概念?poll 就是阻塞?用上中断和 ipc 就是非阻塞?驱动缓存干嘛用的?当驱动缓存足够用的时候,缓存不缓存,还要等待缓存所有数据清空再返回应用,这是非阻塞?这是阻塞!!!当使用 9600 波特率的时候,每发送一个字节需要耗时 1.1 ms!!!您觉得调试输出十个八个字符信息的时候,应用程序被耽误10ms的时间,而去掉调试信息就少这10多ms时间,这个是非阻塞?
框架和驱动是绝缘的?驱动是建立在框架基础之上的!驱动严重依赖框架的设计理念!框架没有的概念,驱动能变出花来?
没有支持的不仅仅是 flush
模式和硬件来回配置的操作是最要不得的,不然要什么框架和驱动的区别,一起搞好了。既然分开了还要高强度耦合,这不是掩耳盗铃吗?
这段话说的最有道理,“测试丢包率很高也可能是正常”,测试通不过,怎么经得起实际应用的考验?怎么放心用在产品上?丢包的原因在哪儿没搞清楚,就先认为是正常的?!
“框架层我留了大量API进行对接,方便驱动做符合自己应用场景的适配”,您的意思是每个人使用 v2 的时候,都要根据自己是实际情况修改,改成适用自己应用场景的?那让大家用什么 v2 都自己从头写一套框架和驱动好了。
测试方法我也说的很详细,测试很简单。没有人看不懂我的测试过程吧。单串口,无应用,单单收发测试都有丢包,是什么原因引起的数据丢失?发送方?数据线?芯片串口外设?还是 rt-thread 内核不够实时?还是串口框架设计有缺陷?
@armink 道不同,不相为谋。
宣称的概念没做到,是设计理念问题,改动太多太大。
DMA 支持 STREAM 模式的问题,欢迎测试效率问题。如果影响您使用了,我二话不说直接把这个功能砍掉
@123 多说一句,以后这种什么新生什么大佬的话少说,最好不说。瞧不起新生的感觉。
点赞!!!!我比较喜欢write 0等待的特性,一般使用rtt时,为了效率都是将uart驱动重新按照设备驱动框架写一遍,加入rb
@jiezhi320 苦了你们了,用啥都需要自己再搞一遍
毕竟单片机上的DMA就那么几个,我宁愿留给SPI、SD、USB也不会给uart用;宁愿把uart的速度降低,也不想把代码的复杂度搞高了,毕竟产品稳定高于一切。
@linhuikui 这篇文章好像没有鼓吹 DMA ,说 uart 一定要用 DMA。
速度嘛,依据实际应用而定。也没要求必须最高。
建议做个软件包吧。以后有需要的人可以直接拿来就用。
@出出啊 因为不想去研究设备驱动,虽然我知道能省很多事情,但是我喜欢用rt-thread nano 版本,就是希望做的高效,不丢数据
@ZWH 各取所需,但是高效率和低丢失率的诉求是一致的
厉害了