找回密码
 立即注册
首页 业界区 安全 串口接收的各种方法

串口接收的各种方法

祝安芙 2025-5-31 23:59:32
串口接收的各种方法

0. ISO应用层协议设计

0.1 帧头帧尾标志法


  • 通过帧头帧尾来标志数据包的开头和结尾
  • 注意:标志应避免和数据包中的数据重复
  • 如果数据包出现标志符应该进行转义处理
0.2 长度字段法


  • 在数据包中添加一个字节或多个字节的长度字段,来明确字段长度
  • 长度字段的长度和编码应该提前约定好
  • 考虑字节序的大端小端问题,以保证多字段的正确读取
0.3超时等待法


  • 设置一个超时等待时间,超过时间没有接收到新的数据则认为数据接收完成
  • 要考虑数据包的最大长度和串口的传输速率
  • 数据包在传输时出现错误或丢失数据,可能会导致超时判断错误,所以需要考虑数据的可靠性
1. 串口单字节接收


  • 适用于无中断接收场景: 例如 jetson nano 2GB只有内核虚拟串口,无法做到硬中断
  • 性能分析: 全程由CPU参与,读取速度受限于CPU处理速度,很难接收大数据帧/包
2. 串口中断接收


  • 适用于响应要求高的场景: 例如 机械臂运动,遥控器,可能除去帧头帧尾外只有几个字节,甚至单个字节就能作为有效命令,并且要求有高实时性,能够快速响应
  • 性能分析: 全程由CPU参与,每次中断由CPU搬运数据,数据吞吐量较小,多个串口出现大量数据涌入,中断处理不当,可能会导致MCU卡死,同时大量的中断也会打乱系统时钟,导致IIC软件模拟等软件模拟时序出现延长,导致通信错误
3. 串口+DMA接收

3.0 DMA模式

​        通常DMA有单次传输(normal), 循环传输(circular)
​        同时使用串口结合DMA时一般会有三次中断的产生, 半满中断, 全满中断, 空闲中断(IDLE)

  • 空闲中断是怎么实现的?
    当串口的RX进入空闲状态时, RX线为高电平, 所以当RX线出现上升沿, 并在一定时间内保持高电平, 就会触发空闲中断
  • 为什么需要使用空闲中断?
    一般来说, 一帧数据发送总是一个接着一个发送来的,所以当我们出现空闲中断时,缓冲区很有可能接收到了一帧完整的数据
    fifo, brust等模式不在这一篇博客中讨论
3.1 DMA单缓冲区(缓冲区size == 数据帧size)


  • 适用于数据量大, 但传输频率低(每帧)的场景: 例如: 结合MQTT, TCP协议的数据包, 这种数据包一般比较大, 但是相较于机械臂等场景要求对于实时性的要求相对低
  • 性能分析: 每个数据包来临一般只会触发3次中断,相较于不使用DMA的中断模式, 中断出现更少, 并且数据由DMA搬运, CPU仅做启动DMA等操作
    缺点: 当帧处理数据速度 小于 帧传输速度, 很有可能会出现这次DMA中断结束, DMA还没启动, 又来一帧/包数据, 导致数据丢失,或在帧处理过程中,又来一帧/包数据, 导致数据被覆盖
3.2 DMA双缓冲区+空闲中断(缓冲区size == 数据帧size*2)


  • 适用于数据量大场景, 单缓冲区的改进: 例子: 同DMA单缓冲区
  • 性能分析: 相较于DMA单缓冲区模式, 多了一个缓冲区, 可以将数据解析任务放在中断外执行, 大大减少了中断时间, 中断中可以及时配置DMA启动, 有了更大的数据吞吐量, 同时双缓冲区既可以是一段连续地址(半满,全满中断实现), 也可以是两段连续地址,有些MCU甚至支持硬件双缓冲区(例如STM32F4系列)
    缺点: 相较于DMA单缓冲区模式, 花费了更多的空间(但是也获得了更好的性能和数据安全)
3.3 DMA环形缓冲区+空闲中断(一般缓冲区size >= 数据帧size*2)


  • 适用于数据量大场景, 支持不定长数据: 例子: 同DMA单缓冲区, 但是对于不定长数据接收的设计更加简单
  • 性能分析: 相较于DMA双缓冲区模式, 需要一大段连续的内存空间, 但是对于不定长数据, 我们只需要通过两个指针(读写指针管理), 具体管理环形实现和管理可以参考 环形队列 , 并且和双缓冲模式一样, 可以将数据处理任务放在中断外处理, 有更大的数据吞吐量
    缺点: 相较于DMA单缓冲区模式, 需要一大段连续内存空间
单中断和DMA+空闲中断应该怎么选择?

​        当你的数据小于三个byte时, 使用DMA+空闲中断得不偿失, 因为这样通用会出现三次中断, DMA中断中的固定操作往往比单中断更复杂, 剩余根据自己的系统实时性, 数据帧/包发送频率, 来权衡使用那种方法更好
4. 数据帧设计和解析

4.1 数据帧设计

​        设数据帧结构为 0xaa, data1, data2, crc-8校验码, 0xee
​        即帧头, 数据帧, 校验码, 帧尾
​        当数据内容复杂到一定程度可能还需要考虑碰撞问题,  转义字符
4.2 数据帧解析(状态机模式)

使用状态机法对数据帧进行解析
状态机


  • 设计状态转移图
  • 根据状态转移图实现状态跳转


  • 每个状态都要 有进有出 , 否则会出现在某一状态卡死
  • 主要设计状态机解析函数时, 部分变量的生命周期
4.3 异地解析和原地解析

原地解析: 在接收的缓冲区对数据帧/包进行解析

优点: 无需二次拷贝, 解析数据快
缺点: 在接受缓冲区中, 数据来不及处理可能会被覆盖
异地解析: 需要解析数据帧/包的时候, 再从接收缓冲区拷贝数据到解析缓冲区中

优点: 数据不会被覆盖, 大大提升了解析的稳定性
缺点: 需要二次拷贝(RTOS中三次), 需要额外的时间和空间开销
5.中断处理,生产者任务,消费者任务

有RTOS的情况下, 我们会将串口的接收与解析拆分为三个子程序: 中断处理, 生产者线程(接收), 消费者线程(解析)

  • 为什么要将这一个动作拆分成三个线程来实现?这样不是多此一举增加调度开销么?

  • 中断程序必须快进快出
  • 生产者线程和消费者线程处理消耗的时间差距较大,一般情况下生产者线程消耗少(取值),消费者线程消耗多(取值,解码,执行)
中断处理(最高)

​        中断中将读取到的数据存入缓冲区,唤醒生产者线程
生产者线程(靠近中断, 次高)

​        当生产者线程满时,   通知消费者线程对消费者线程对数据进行消费
​        当生产者线程未满时,使能相关硬件执行生产(一般为adc或串口的DMA中断)
消费者线程(最低, 执行依赖优先级翻转)

​        被唤醒后,检查缓冲区是否满,若满,则挟持互斥锁,将自身优先级提升到生产者线程之上,在消费(解码,执行)结束后,释放互斥锁,并通知生产者线程已消费结束,可继续执行生产
优先级

优先级:高-->低
中断-->生产者线程-->消费者线程

  • 为什么优先级要这样设置
    生产者线程属于靠近中断的线程,为了系统的实时性我们会将优先级设置相对较高
  • 消费者线程优先级低于生产者线程会不会导致饿死?
    消费者线程由生产者线程唤醒,并通过互斥锁的特性,将消费者线程的优先级临时提高到高于消费者线程,这样就避免了饿死,也方便管理线程的优先级
任务功能设计图

1.png

本文链接:

串口接收的各种方法 - 林接接 - 博客园

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册