BetaFlight模块设计之三十六:SoftSerial

BetaFlight模块设计之三十六:SoftSerial

  • 1. 源由
  • 2. API接口
    • 2.1 openSoftSerial
    • 2.2 onSerialRxPinChange
    • 2.3 onSerialTimerOverflow
    • 2.4 processTxState
    • 2.5 processRxState
  • 3. 辅助函数
    • 3.1 applyChangedBits
    • 3.2 extractAndStoreRxByte
    • 3.3 prepareForNextRxByte
  • 4. 总结

1. 源由

鉴于Betaflight关于STM32F405 SBUS协议兼容硬件电气特性问题,从程序代码上看,软串口应该能够采用定时器、中断的方式进行电平协议的解析。

但是从实测Betaflight4.4.2固件的角度看,又无法使用,怀疑可能存在以下问题:

  1. 配置问题
  2. 代码移植BUG(unified_target ==> config)
  3. 代码不支持

所以尝试整理下SoftSerial代码结构,通过对整体代码的了解,能否找出其中的一些深层次原因。

2. API接口

从对外接口的角度看,主要有以下API:

  • 打开软件串口openSoftSerial
  • 底层串行信号电平变更处理onSerialRxPinChange
  • 底层串行信号超市处理onSerialTimerOverflow
  • 后端Tx状态处理processTxState
  • 后端Rx状态处理processRxState
serialPort_t *openSoftSerial(softSerialPortIndex_e portIndex, serialReceiveCallbackPtr rxCallback, void *rxCallbackData, uint32_t baud, portMode_e mode, portOptions_e options)
void onSerialRxPinChange(timerCCHandlerRec_t *cbRec, captureCompare_t capture)
void onSerialTimerOverflow(timerOvrHandlerRec_t *cbRec, captureCompare_t capture)
void processTxState(softSerial_t *softSerial)
void processRxState(softSerial_t *softSerial)

2.1 openSoftSerial

根据资源进行配置:

  • 【Hardware】GPIO:Tx/Rx/SERIAL_INVERTED
  • 【Hardware】TIMER
  • 【Hardware】Interrupt:ICPOLARITY_RISING/ICPOLARITY_FALLING
  • 【Software】Buffer
  • 【Software】Callback:onSerialRxPinChange(edgeCb)/onSerialTimerOverflow(overCb)/rxCallback
openSoftSerial││   // get serial port description├──> softSerial_t *softSerial = &(softSerialPorts[portIndex]);││   // get serial port rx/tx ioTag├──> ioTag_t tagRx = softSerialPinConfig()->ioTagRx[portIndex];├──> ioTag_t tagTx = softSerialPinConfig()->ioTagTx[portIndex];││   // one wire(Sbus etc.) or two wire softserial(UART etc.)├──> const timerHardware_t *timerTx = timerAllocate(tagTx, OWNER_SOFTSERIAL_TX, RESOURCE_INDEX(portIndex));├──> const timerHardware_t *timerRx = (tagTx == tagRx) ? timerTx : timerAllocate(tagRx, OWNER_SOFTSERIAL_RX, RESOURCE_INDEX(portIndex));││   // get serial port rx/tx IO_t├──> IO_t rxIO = IOGetByTag(tagRx);├──> IO_t txIO = IOGetByTag(tagTx);││   // timer & io set├──> <options & SERIAL_BIDIR> // bi-direction configuration│   ├──> <!timerTx || (timerTx->output & TIMER_OUTPUT_N_CHANNEL)>│   │   │   // If RX and TX pins are both assigned, we CAN use either with a timer.│   │   │   // However, for consistency with hardware UARTs, we only use TX pin,│   │   │   // and this pin must have a timer, and it should not be N-Channel.│   │   └──> return NULL;│   ├──> softSerial->timerHardware = timerTx;│   ├──> softSerial->txIO = txIO;│   ├──> softSerial->rxIO = txIO;│   └──> IOInit(txIO, OWNER_SOFTSERIAL_TX, RESOURCE_INDEX(portIndex));├──> < else > // unidirection configuration│   ├──> <mode & MODE_RX>│   │   ├──> <!timerRx || (timerRx->output & TIMER_OUTPUT_N_CHANNEL)>│   │   │   │   // Need a pin & a timer on RX. Channel should not be N-Channel.│   │   │   └──> return NULL;│   │   ├──> softSerial->rxIO = rxIO;│   │   ├──> softSerial->timerHardware = timerRx;│   │   └──> <!((mode & MODE_TX) && rxIO == txIO)>│   │       └──> IOInit(rxIO, OWNER_SOFTSERIAL_RX, RESOURCE_INDEX(portIndex));│   └──> <mode & MODE_TX>│       ├──> <!tagTx>│       │   │   // Need a pin on TX│       │   └──> return NULL;│       ├──> softSerial->txIO = txIO;│       ├──> <!(mode & MODE_RX)>│       │   ├──> <!timerTx> return NULL;│       │   │   // TX Simplex, must have a timer│       │   └──> softSerial->timerHardware = timerTx;│       ├──> < else >  // Duplex│       │   └──> softSerial->exTimerHardware = timerTx;│       └──> IOInit(txIO, OWNER_SOFTSERIAL_TX, RESOURCE_INDEX(portIndex));││   // port configuration├──> softSerial->port.vTable = &softSerialVTable;├──> softSerial->port.baudRate = baud;├──> softSerial->port.mode = mode;├──> softSerial->port.options = options;├──> softSerial->port.rxCallback = rxCallback;├──> softSerial->port.rxCallbackData = rxCallbackData;│├──> resetBuffers(softSerial);│├──> softSerial->softSerialPortIndex = portIndex;│├──> softSerial->transmissionErrors = 0;├──> softSerial->receiveErrors = 0;│├──> softSerial->rxActive = false;├──> softSerial->isTransmittingData = false;││   // Configure master timer (on RX); time base and input capture├──> serialTimerConfigureTimebase(softSerial->timerHardware, baud);├──> timerChConfigIC(softSerial->timerHardware, (options & SERIAL_INVERTED) ? ICPOLARITY_RISING : ICPOLARITY_FALLING, 0);││   // Initialize callbacks├──> timerChCCHandlerInit(&softSerial->edgeCb, onSerialRxPinChange);├──> timerChOvrHandlerInit(&softSerial->overCb, onSerialTimerOverflow);││   // Configure bit clock interrupt & handler.│   // If we have an extra timer (on TX), it is initialized and configured│   // for overflow interrupt.│   // Receiver input capture is configured when input is activated.├──> <(mode & MODE_TX) && softSerial->exTimerHardware && softSerial->exTimerHardware->tim != softSerial->timerHardware->tim>│   ├──> softSerial->timerMode = TIMER_MODE_DUAL;│   ├──> serialTimerConfigureTimebase(softSerial->exTimerHardware, baud);│   ├──> timerChConfigCallbacks(softSerial->exTimerHardware, NULL, &softSerial->overCb);│   └──> timerChConfigCallbacks(softSerial->timerHardware, &softSerial->edgeCb, NULL);├──> < else >│   ├──> softSerial->timerMode = TIMER_MODE_SINGLE;│   └──> timerChConfigCallbacks(softSerial->timerHardware, &softSerial->edgeCb, &softSerial->overCb);│├──> <USE_HAL_DRIVER>│   └──> softSerial->timerHandle = timerFindTimerHandle(softSerial->timerHardware->tim);││   // antivate port├──> <!(options & SERIAL_BIDIR)>│   ├──> serialOutputPortActivate(softSerial);│   └──> setTxSignal(softSerial, ENABLE);├──> serialInputPortActivate(softSerial);└──> return &softSerial->port;

2.2 onSerialRxPinChange

通过边沿中断记录bit数据流。

onSerialRxPinChange├──> softSerial_t *self = container_of(cbRec, softSerial_t, edgeCb);├──> bool inverted = self->port.options & SERIAL_INVERTED;│├──> <(self->port.mode & MODE_RX) == 0>│   └──> return;  // 无接收模式,直接返回│├──> <self->isSearchingForStartBit>│   │  // Synchronize the bit timing so that it will interrupt at the center│   │  // of the bit period.│   ├──> <USE_HAL_DRIVER>│   │   └──> __HAL_TIM_SetCounter(self->timerHandle, __HAL_TIM_GetAutoreload(self->timerHandle) / 2);│   ├──> <else>│   │   └──> TIM_SetCounter(self->timerHardware->tim, self->timerHardware->tim->ARR / 2);│   ││   │  // For a mono-timer full duplex configuration, this may clobber the│   │  // transmission because the next callback to the onSerialTimerOverflow│   │  // will happen too early causing transmission errors.│   │  // For a dual-timer configuration, there is no problem.│   ├──> <(self->timerMode != TIMER_MODE_DUAL) && self->isTransmittingData>│   │   └──> self->transmissionErrors++;│   ││   ├──> timerChConfigIC(self->timerHardware, inverted ? ICPOLARITY_FALLING : ICPOLARITY_RISING, 0);│   ├──> <defined(STM32F7) || defined(STM32H7) || defined(STM32G4)>│   │   └──> serialEnableCC(self);│   ││   ├──> self->rxEdge = LEADING;│   ││   ├──> self->rxBitIndex = 0;│   ├──> self->rxLastLeadingEdgeAtBitIndex = 0;│   ├──> self->internalRxBuffer = 0;│   ├──> self->isSearchingForStartBit = false;│   └──> return;││   // handle leveled signal├──> <self->rxEdge == LEADING>│   └──> self->rxLastLeadingEdgeAtBitIndex = self->rxBitIndex;├──>  applyChangedBits(self);│├──> <self->rxEdge == TRAILING>│   ├──> self->rxEdge = LEADING;│   └──> timerChConfigIC(self->timerHardware, inverted ? ICPOLARITY_FALLING : ICPOLARITY_RISING, 0);├──> < else >│   ├──> self->rxEdge = TRAILING;│   └──> timerChConfigIC(self->timerHardware, inverted ? ICPOLARITY_RISING : ICPOLARITY_FALLING, 0);└──> <defined(STM32F7) || defined(STM32H7) || defined(STM32G4)>└──> serialEnableCC(self);

2.3 onSerialTimerOverflow

串行数据从原理上属于字符流协议,从实际应用角度,还是一包一包的数据(通常不会密集到头尾相连)。

因此,超时机制相当于处理:

  • 数据帧
  • 异常中断
onSerialTimerOverflow├──> softSerial_t *self = container_of(cbRec, softSerial_t, overCb);├──> <self->port.mode & MODE_TX> processTxState(self);└──> <self->port.mode & MODE_RX> processRxState(self);

2.4 processTxState

Tx数据处理存在三种情况:

  • 发送数据前处理
  • 发送数据
  • 发送数据后处理
processTxState│   // 发送数据前处理├──> <!softSerial->isTransmittingData>│   ├──> <isSoftSerialTransmitBufferEmpty((serialPort_t *)softSerial)>│   │   │   // Transmit buffer empty.│   │   │   // Start listening if not already in if half-duplex│   │   ├──> <!softSerial->rxActive && softSerial->port.options & SERIAL_BIDIR) {│   │   │   ├──> serialOutputPortDeActivate(softSerial);│   │   │   └──> serialInputPortActivate(softSerial);│   │   └──> return;│   │    │   │   // data to send│   ├──> uint8_t byteToSend = softSerial->port.txBuffer[softSerial->port.txBufferTail++];│   ├──> <softSerial->port.txBufferTail >= softSerial->port.txBufferSize>│   │   └──> softSerial->port.txBufferTail = 0;│   │   │   │   // build internal buffer, MSB = Stop Bit (1) + data bits (MSB to LSB) + start bit(0) LSB│   ├──> softSerial->internalTxBuffer = (1 << (TX_TOTAL_BITS - 1)) | (byteToSend << 1);│   ├──> softSerial->bitsLeftToTransmit = TX_TOTAL_BITS;│   ├──> softSerial->isTransmittingData = true;│   └──> <softSerial->rxActive && (softSerial->port.options & SERIAL_BIDIR)>│       │   // Half-duplex: Deactivate receiver, activate transmitter│       ├──> serialInputPortDeActivate(softSerial);│       ├──> serialOutputPortActivate(softSerial);│       ││       │   // Start sending on next bit timing, as port manipulation takes time,│       │   // and continuing here may cause bit period to decrease causing sampling errors│       │   // at the receiver under high rates.│       │   // Note that there will be (little less than) 1-bit delay; take it as "turn around time".│       │   // XXX We may be able to reload counter and continue. (Future work.)│       └──> return;││   // 发送bit数据:高/低 电平├──> <softSerial->bitsLeftToTransmit>│   ├──> mask = softSerial->internalTxBuffer & 1;│   ├──> softSerial->internalTxBuffer >>= 1;│   ││   ├──> setTxSignal(softSerial, mask);│   ├──> softSerial->bitsLeftToTransmit--;│   └──> return;││   // 发送数据后处理└──> softSerial->isTransmittingData = false;

2.5 processRxState

RX_TOTAL_BITS 10 bits format: start bit + 8 bits for one byte + stop bit

在这里插入图片描述

processRxState│   //Start bit处理├──> <softSerial->isSearchingForStartBit>│   └──> return;├──> softSerial->rxBitIndex++;││   //1 Byte数据处理├──> <softSerial->rxBitIndex == RX_TOTAL_BITS - 1>│   ├──> applyChangedBits(softSerial);│   └──> return;│   //Stop bit处理└──> <softSerial->rxBitIndex == RX_TOTAL_BITS>├──> softSerial->rxEdge == TRAILING>│   └──> softSerial->internalRxBuffer |= STOP_BIT_MASK;├──> extractAndStoreRxByte(softSerial);└──> prepareForNextRxByte(softSerial);

注:上述函数过程存在10bit缺损卡死的情况,代码还不够robust。

3. 辅助函数

3.1 applyChangedBits

1~9 bit数据将通过该函数进行存储,最后10bit数据将在processRxState中进行保存。

applyChangedBits└──> <softSerial->rxEdge == TRAILING>└──> for (bitToSet = softSerial->rxLastLeadingEdgeAtBitIndex; bitToSet < softSerial->rxBitIndex; bitToSet++)└──> softSerial->internalRxBuffer |= 1 << bitToSet;

3.2 extractAndStoreRxByte

从10 bit格式中抽取1Byte有效数据。

extractAndStoreRxByte│   //仅TX模式,无需进行任何接收字节的保存工作├──> <(softSerial->port.mode & MODE_RX) == 0>│   └──> return;│   ├──> uint8_t haveStartBit = (softSerial->internalRxBuffer & START_BIT_MASK) == 0;├──> uint8_t haveStopBit = (softSerial->internalRxBuffer & STOP_BIT_MASK) == 1;│  │   //起止bit位,若一项不符合规格,则丢弃数据├──> <!haveStartBit || !haveStopBit>│   ├──> softSerial->receiveErrors++;│   └──> return;│  │   //保存1Byte数据├──> uint8_t rxByte = (softSerial->internalRxBuffer >> 1) & 0xFF;│  ├──> <softSerial->port.rxCallback> //回调接收函数│   └──> softSerial->port.rxCallback(rxByte, softSerial->port.rxCallbackData);└──> < else > //无接收注册函数情况下,将数据存入缓冲buffer中,并采用循环方式覆盖保存├──> softSerial->port.rxBuffer[softSerial->port.rxBufferHead] = rxByte;└──> softSerial->port.rxBufferHead = (softSerial->port.rxBufferHead + 1) % softSerial->port.rxBufferSize;

3.3 prepareForNextRxByte

收录下一字节数据做预处理工作。

prepareForNextRxByte├──> softSerial->rxBitIndex = 0;├──> softSerial->isSearchingForStartBit = true;└──> <softSerial->rxEdge == LEADING>├──> softSerial->rxEdge = TRAILING;├──> timerChConfigIC(softSerial->timerHardware, (softSerial->port.options & SERIAL_INVERTED) ? ICPOLARITY_RISING : ICPOLARITY_FALLING, 0);└──> serialEnableCC(softSerial);

4. 总结

SoftSerial代码角度,采用定时器、边沿中断的方式,随机使用CPU资源。如果应用在高速、大数据量通信场景,将会影响和打扰CPU正常业务逻辑,尤其是在CPU资源紧张时。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/173096.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

老师组织课外活动的好处有哪些

亲爱的小伙伴们&#xff0c;不知道你们有没有注意到&#xff0c;老师除了在课堂上教学之外&#xff0c;还会在课外组织各种各样的活动呢&#xff1f;这些活动不仅好玩&#xff0c;而且对我们有很多好处哦&#xff01;今天我就来给大家分享一下老师组织课外活动的好处吧&#xf…

geemap学习笔记014:加载本地的tif文件

前言 Colab中似乎没法直接加载云盘中的数据&#xff0c;但是可以先上传到GEE中的assets中&#xff0c;再加载本地的数据。下面是以这个数据为例进行展示。 1 上传数据 首先将本地的tif数据上传到Asset中&#xff0c;得到独一的Image ID。 2 加载数据 使用ee.Image加载数据 …

【腾讯云云上实验室】用向量数据库在金融信用数据库分析中的实战运用

一、前言 这篇文章将带领读者探索数据库的多样化解决方案及其演进历程&#xff0c;特别关注向量数据库的重要性和在实际项目中的应用。 通过深入剖析腾讯云向量数据库及其在金融信用数据库分析中的实战运用&#xff0c;为读者提供全面而实用的指南&#xff0c;帮助他们理解、…

【挑战业余一周拿证】一、亚马逊云科技简介 - 第 3 节 - 云计算

第 3 节 - 云计算 在深入了解亚马逊云科技的各个部分之前&#xff0c;让我们先缩小视野&#xff0c;对云进行一个合理的定义。云计算就是通过互联网按需提供 IT 资源并采用按需付费定价模式&#xff0c;下面&#xff0c;我们将进行详细说明。 按需提供表示的是亚马逊云科技会在…

箱型图 Box Plot 数据分析的法宝

文章目录 一、箱形图的介绍二、六大因数三、Box plot的应用四、箱形图的优劣势五、图形拓展 一、箱形图的介绍 箱形图又称为盒须图、盒式图、盒状图或箱线图&#xff0c;是一种用作显示一组数据分散情况资料的统计图。因型状如箱子而得名。 在各种领域也经常被使用&#xff0…

基于springboot实现医院信管系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现医院信管系统演示 摘要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#x…

C语言做一个恶作剧关机程序

一、项目介绍 C语言实现一个简单的"流氓软件"&#xff0c;一个可以强制关机恶作剧关机程序&#xff0c;输入指定指令可以解除 二、运行截图 然后当你输入“n”才可以解锁关机。 三、完整源码 #include <stdlib.h> #include <stdio.h> #include <s…

Java核心知识点整理大全17-笔记

Java核心知识点整理大全-笔记_希斯奎的博客-CSDN博客 Java核心知识点整理大全2-笔记_希斯奎的博客-CSDN博客 Java核心知识点整理大全3-笔记_希斯奎的博客-CSDN博客 Java核心知识点整理大全4-笔记-CSDN博客 Java核心知识点整理大全5-笔记-CSDN博客 Java核心知识点整理大全6…

debian 12设置静态ip、dns

debian 12设置静态ip、dns 1、设置静态ip2、设置dns 1、设置静态ip 查看网卡名称是ens33 ip address编辑网卡配置文件 vi /etc/network/interfaces默认是这样的 在最后面添加下面内容 其中 ens33是上步中查询到的网卡名称address 192.168.2.157 是ip地址netmask 255.255.…

Kubernetes技术与架构-配置

一般情况下&#xff0c;Kubernetes使用yaml文件格式定义配置文件&#xff0c;配置文件须指定对应的API稳定版本号&#xff0c;将配置文件进行版本控制、在发布新版本的过程中出问题时可以执行版本回滚操作&#xff0c;将相关联的对象定义在同一个配置文件中、从而更容易地管理&…

C++学习之路(六)C++ 实现简单的工具箱系统命令行应用 - 示例代码拆分讲解

简单的工具箱系统示例介绍: 这个示例展示了一个简单的工具箱框架&#xff0c;它涉及了几个关键概念和知识点&#xff1a; 面向对象编程 (OOP)&#xff1a;使用了类和继承的概念。Tool 是一个纯虚类&#xff0c;CalculatorTool 和 FileReaderTool 是其派生类。 多态&#xff1…

计算一个Series序列的n阶滞后相关系数Series.autocorr()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算一个时间序列的 n阶滞后自相关系数 Series.autocorr(n) [太阳]选择题 以下代码的说法中正确的是? import pandas as pd s1 pd.Series([1,2,3,4,5,6]) print("【显示】s1:\n",…

7 通用数字量输入输出GPIO

文章目录 7.0 GPIO概念7.1 GPIO工作原理7.2 GPIO寄存器以及编程7.2.5 GPIO寄存器编程设置与应用 7.3 GPIO跑马灯7.3.1 LED 输出初始化7.3.2 跑马灯输出实验7.3.3 按键输入实验 7.0 GPIO概念 GPIO&#xff08;general purpose intput output&#xff09;是通用输入输出端口的简…

最火web大屏可视化编辑器

前言&#xff1a; 乐吾乐Le5le大屏可视化设计器&#xff0c;零代码实现物联网、工业智能制造等领域的可视化大屏、触摸屏端UI以及工控可视化的解决方案。同时也是一个Web组态工具&#xff0c;支持2D、3D等多种形式&#xff0c;用于构建具有实时数据展示、监控预警、丰富交互的组…

2009年iMac装64位windows7及win10

2009年iMac装64位windows7及win10 Boot Camp没有“创建 Windows7 或更高版本的安装磁盘”选项 安装完Mac OS系统后&#xff0c;要制作Windows7安装U盘时才发现&#xff0c;Boot Camp没有“创建 Windows7 或更高版本的安装磁盘”选项&#xff0c;搜索到文章&#xff1a;修改Boo…

blender 3D眼球结构

角膜&#xff08;Cornea&#xff09;&#xff1a;眼球的前部&#xff0c;透明的曲面&#xff0c;负责折射光线。虹膜&#xff08;Iris&#xff09;&#xff1a;眼睛的颜色部分&#xff0c;控制瞳孔大小以调整进入眼睛的光量。瞳孔&#xff08;Pupil&#xff09;&#xff1a;虹膜…

Mycat实现读写分离

Mycat实现读写分离 Mycat支持MySQL主从复制状态绑定的读写分离机制。这里实现的也是基于MySQL主从复制的读写分离。 MySQL主从复制配置 首先要配置MySQL的主从复制&#xff0c;这里配置的是一主一次从。可以参考下面的文章。 https://blog.csdn.net/wsb_2526/article/detail…

Zookeeper分布式锁实现Curator十一问

前面我们通过Redis分布式锁实现Redisson 15问文章剖析了Redisson的源码&#xff0c;理清了Redisson是如何实现的分布式锁和一些其它的特性。这篇文章就来接着剖析Zookeeper分布式锁的实现框架Curator的源码&#xff0c;看看Curator是如何实现Zookeeper分布式锁的&#xff0c;以…

OpenMMlab导出yolox模型并用onnxruntime和tensorrt推理

导出onnx文件 直接使用脚本 import torch from mmdet.apis import init_detector, inference_detectorconfig_file ./configs/yolox/yolox_tiny_8xb8-300e_coco.py checkpoint_file yolox_tiny_8x8_300e_coco_20211124_171234-b4047906.pth model init_detector(config_fi…

MATLAB中corrcoef函数用法

目录 语法 说明 示例 矩阵的随机列 两个随机变量 矩阵的 P 值 相关性边界 NaN 值 corrcoef函数的功能是返回数据的相关系数。 语法 R corrcoef(A) R corrcoef(A,B) [R,P] corrcoef(___) [R,P,RL,RU] corrcoef(___) ___ corrcoef(___,Name,Value) 说明 R corrc…