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.hqwc.cn/news/221436.html

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

相关文章

电子学会C/C++编程等级考试2021年03月(三级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:找和为K的两个元素 在一个长度为n(n < 1000)的整数序列中,判断是否存在某两个元素之和为k。 时间限制:1000 内存限制:65536输入 第一行输入序列的长度n和k,用空格分开。 第二行输入序列中的n个整数,用空格分开。输出 如…

居家适老化设计第三十二条---卫生间之扶手

以上产品图片均来源于淘宝 侵权联系删除 居家适老化中的扶手是指在家居环境中&#xff0c;为老年人提供支撑和帮助的装置&#xff0c;通常安装在家中的各个需要扶抓的位置&#xff0c;如楼梯、卫生间、浴室、厨房等处。扶手的设计应考虑老年人的体力、平衡和安全需求&#xf…

Windows安装Python环境(V3.6)

文章目录 一&#xff1a;进入网址&#xff1a;https://www.python.org/downloads/ 二&#xff1a;执行安装包 默认C盘&#xff0c;选择自定义安装目录 记得勾选add python path 下面文件夹最好不要有 . 等特殊符号 可以创建 python36 如果安装失败Option可以选默认的&#x…

一起学docker系列之十一使用 Docker 安装 Redis 并配置持久化存储

目录 前言1 基本安装步骤安装Redis镜像&#xff1a;查看已下载的Redis镜像&#xff1a;运行Redis容器&#xff1a;进入Redis容器&#xff1a;使用Redis CLI进行基本操作&#xff1a; 2 配置文件同步准备配置文件&#xff1a;修改Redis配置文件 /app/redis/redis.conf&#xff1…

Matplotlib不规则子图_Python数据分析与可视化

除了网格子图&#xff0c;matplotlib还支持不规则的多行多列子图网格。 plt.GridSpec()对象本事不能直接创建一个图形&#xff0c;他只是 plt.subplot()命令可以识别的简易接口。 这里创建了一个带行列间距的23网格&#xff1a; grid plt.GridSpec(2, 3, wspace0.4, hspace0…

免费分享一套基于springboot的餐饮美食分享平台系统,挺漂亮的

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的基于springboot的餐饮美食分享平台系统&#xff0c;分享下哈。 项目视频演示 【免费】基于springboot的餐饮美食分享平台 Java毕业设计_哔哩哔哩_bilibili【免费】基于springboot的餐饮美食分享平台 Java毕…

【Kotlin】内联函数

文章目录 内联函数noinline: 避免参数被内联非局部返回使用标签实现Lambda非局部返回为什么要设计noinline crossinline具体化参数类型 Kotlin中的内联函数之所以被设计出来&#xff0c;主要是为了优化Kotlin支持Lambda表达式之后所带来的开销。然而&#xff0c;在Java中我们似…

【腾讯云 HAI域探秘】基于高性能应用服务器HAI部署的 ChatGLM2-6B模型,我开发了AI办公助手,公司行政小姐姐用了都说好!

目录 前言 一、腾讯云HAI介绍&#xff1a; 1、即插即用 轻松上手 2、横向对比 青出于蓝 3、多种高性能应用部署场景 二、腾讯云HAI一键部署并使用ChatGLM2-6B快速实现开发者所需的相关API服务 1、登录 高性能应用服务 HAI 控制台 2、点击 新建 选择 AI模型&#xff0c;…

【开源】基于Vue和SpringBoot的个人健康管理系统

项目编号&#xff1a; S 040 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S040&#xff0c;文末获取源码。} 项目编号&#xff1a;S040&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 健康档案模块2.2 体检档案模块2.3 健…

【社会网络分析第6期】Ucient实操

一、导入数据处理二、核心——边缘分析三、聚类分析四、网络密度 一、导入数据处理 将数据导入Ucinet首先需要对数据进行处理。 承接上一期的数据格式&#xff1a;【社会网络分析第5期】gephi使用指南 原先得到的数据格式如下&#xff1a; 接下来打开ucinet&#xff1a; 之后…

armbian折腾之docker搭建chatgptweb指导(无需魔法)

文章目录 前言面板/docker的安装获取中转Key创建docker容器chatgpt-next-web部署[推荐]chatgpt-Web部署 推荐学习openai-hk官方的部署指导 前言 好久都没有折腾armbian&#xff0c;导致吃了很长时间的灰&#xff0c;今天偶然看到B站UP主JeeJK007的搭建视频&#xff0c;便想着能…

前沿重器[38] | 微软新文query2doc:用大模型做query检索拓展

前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享&#xff0c;从中抽取关键精华的部分和大家分享&#xff0c;和大家一起把握前沿技术。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。&#xff08;算起来&#xff0c;专项启动已经…