ExpressLRS开源代码之发射机代码框架结构

news/2024/7/10 20:20:15 标签: 开源, ELRS

ExpressLRS开源代码之发射机代码框架结构

  • 1. 源由
  • 2. 分析
  • 3. 发射机应用
    • 3.1 设备初始化
    • 3.2 业务应用任务
    • 3.3 RF接收任务&驱动
      • 3.3.1 RXdoneISR
      • 3.3.2 ProcessTLMpacket
      • 3.3.3 TXdoneISR
  • 4. 总结
  • 5. 参考资料

1. 源由

ExpressLRS开源代码之框架结构从硬件和软件设计角度,抽象整理了一个框架。

本章将结合发射机实际代码实现,进行相应的介绍。

2. 分析

按照框架结构的软件设计角度考虑发射机设计:

  1. 设备初始化setup
  2. 业务应用任务loop
  3. RF接收任务&驱动

3. 发射机应用

3.1 设备初始化

硬件上电启动后,首先进入setup例程,对硬件做一个初始化。

setup
 │   /*
 │    * Step 1:串口波特率115200;EEPROM失败直接进入wifi模式
 │    * 注:不是TARGET_UNIFIED_TX特殊处理。
 │    */
 ├──> <setupHardwareFromOptions()>
 │   │  /*
 │   │   * Step 2:发射机业务初始化
 │   │   */
 │   ├──> initUID()
 │   ├──> setupTarget()
 │   ├──> devicesRegister(ui_devices, ARRAY_SIZE(ui_devices))  // Register the devices with the framework
 │   ├──> devicesInit() // Initialise the devices
 │   ├──> DBGLN("Initialised devices")
 │   ├──> FHSSrandomiseFHSSsequence(uidMacSeedGet())
 │   │  /*
 │   │   * Step 3:RF通讯中断函数:RXdoneISR/TXdoneISR
 │   │   */
 │   ├──> Radio.RXdoneCallback = &RXdoneISR
 │   ├──> Radio.TXdoneCallback = &TXdoneISR
 │   ├──> crsf.connected = &UARTconnected // it will auto init when it detects UART connection
 │   │  /*
 │   │   * Step 4:Airport功能检查
 │   │   */
 │   ├──> <!firmwareOptions.is_airport>
 │   │   └──> crsf.disconnected = &UARTdisconnected
 │   ├──> crsf.RecvModelUpdate = &ModelUpdateReq
 │   ├──> hwTimer.callbackTock = &timerCallbackNormal
 │   ├──> DBGLN("ExpressLRS TX Module Booted...")
 │   ├──> eeprom.Begin() // Init the eeprom
 │   ├──> config.SetStorageProvider(&eeprom) // Pass pointer to the Config class for access to storage
 │   ├──> config.Load() // Load the stored values from eeprom
 │   ├──> Radio.currFreq = GetInitialFreq() //set frequency first or an error will occur!!!
 │   │  /*
 │   │   * Step 5:链路初始化(BLE or Radio)
 │   │   */
 │   ├──> bool init_success
 │   ├──> <USE_BLE_JOYSTICK>
 │   │   └──> init_success = true // No radio is attached with a joystick only module.  So we are going to fake success so that crsf, hwTimer etc are initiated below.
 │   ├──> <!USE_BLE_JOYSTICK>
 │   │   ├──> <GPIO_PIN_SCK != UNDEF_PIN> init_success = Radio.Begin()
 │   │   └──> <!(GPIO_PIN_SCK != UNDEF_PIN)> init_success = true  // Assume BLE Joystick mode if no radio SCK pin
 │   ├──> <!init_success>
 │   │   └──> connectionState = radioFailed
 │   └──> <init_success>
 │       ├──> TelemetryReceiver.SetDataToReceive(CRSFinBuffer, sizeof(CRSFinBuffer))
 │       ├──> POWERMGNT.init()
 │       ├──> DynamicPower_Init()
 │       ├──> ChangeRadioParams() // Set the pkt rate, TLM ratio, and power from the stored eeprom values
 │       ├──> <Regulatory_Domain_EU_CE_2400>
 │       │   └──> BeginClearChannelAssessment()
 │       ├──> hwTimer.init()
 │       └──> connectionState = noCrossfire
 │   /*
 │    * Step 6:配置按钮
 │    */
 ├──> <HAS_BUTTON>
 │   ├──> registerButtonFunction(ACTION_BIND, EnterBindingMode)
 │   └──> registerButtonFunction(ACTION_INCREASE_POWER, cyclePower)
 │   /*
 │    * Step 7:设备启动
 │    */
 ├──> devicesStart()
 │   /*
 │    * Step 8:Airport配置设置
 │    */
 └──> <firmwareOptions.is_airport>
     ├──> config.SetTlm(TLM_RATIO_1_2) // Force TLM ratio of 1:2 for balanced bi-dir link
     ├──> config.SetMotionMode(0) // Ensure motion detection is off
     └──> UARTconnected()

3.2 业务应用任务

整个业务大致切分为15段:

loop
 ├──> uint32_t now = millis()
 │   /*
 │    * Step 1:Airport报文处理
 │    */
 ├──> HandleUARTout() // Only used for non-CRSF output
 │   /*
 │    * Step 2:检查BLE_JOYSTICK
 │    */
 ├──> <USE_BLE_JOYSTICK> <connectionState != bleJoystick && connectionState != noCrossfire> // Wait until the correct crsf baud has been found
 │   └──> connectionState = bleJoystick
 │   /*
 │    * Step 3:发射机连接状态检查
 │    */
 ├──> <connectionState < MODE_STATES>
 │   └──> UpdateConnectDisconnectStatus()
 │   /*
 │    * Step 4:状态更新
 │    */
 ├──> devicesUpdate(now)  // Update UI devices
 ├──> checkBackpackUpdate()  // Not a device because it must be run on the loop core
 │   /*
 │    * Step 5:是否有软重启需要
 │    */
 ├──> <PLATFORM_ESP8266 || PLATFORM_ESP32>
 │   └──> <rebootTime != 0 && now > rebootTime> // If the reboot time is set and the current time is past the reboot time then reboot.
 │       └──> ESP.restart()
 │   /*
 │    * Step 6:???待细看代码逻辑
 │    */
 ├──> executeDeferredFunction(now)
 │   /*
 │    * Step 7:接收USB串口报文
 │    */
 ├──> <firmwareOptions.is_airport && connectionState == connected>
 │   ├──> auto size = std::min(AP_MAX_BUF_LEN - apInputBuffer.size(), TxUSB->available())
 │   └──> <size > 0>
 │       ├──> uint8_t buf[size]
 │       ├──> TxUSB->readBytes(buf, size)
 │       └──> apInputBuffer.pushBytes(buf, size)
 │   /*
 │    * Step 8:MSP报文处理
 │    */
 ├──> <TxBackpack->available()> <msp.processReceivedByte(TxBackpack->read())>
 │   ├──> ProcessMSPPacket(now, msp.getReceivedPacket()) // Finished processing a complete packet
 │   └──> msp.markPacketReceived()
 │   /*
 │    * Step 9:状态判断
 │    */
 ├──> <connectionState > MODE_STATES>
 │   └──> return
 │   /*
 │    * Step 10:???待细看代码逻辑
 │    */
 ├──> CheckReadyToSend()
 │   /*
 │    * Step 11:是否配置生效
 │    */
 ├──> CheckConfigChangePending()
 │   /*
 │    * Step 12:功率模式切换
 │    */
 ├──> DynamicPower_Update(now)
 ├──> VtxPitmodeSwitchUpdate()
 │   /*
 │    * Step 13:电传报文处理
 │    */
 ├──> <(connectionState == connected) && (LastTLMpacketRecvMillis != 0) &&
 │    (now >= (uint32_t)(firmwareOptions.tlm_report_interval + TLMpacketReported))>
 │    /* Send TLM updates to handset if connected + reporting period
 │     * is elapsed. This keeps handset happy dispite of the telemetry ratio */
 │   ├──> crsf.sendLinkStatisticsToTX()
 │   └──> TLMpacketReported = now
 ├──> <TelemetryReceiver.HasFinishedData()>
 │   ├──> crsf.sendTelemetryToTX(CRSFinBuffer)
 │   └──> TelemetryReceiver.Unlock()
 ├──> static bool mspTransferActive = false  // only send msp data when binding is not active
 │   /*
 │    * Step 14:绑定模式跳出
 │    */
 ├──> <InBindingMode>
 │   └──> <BindingSendCount > 6>
 │       └──> ExitBindingMode() // exit bind mode if package after some repeats
 │   /*
 │    * Step 15:MSP模式下的相关业务处理
 │    */
 └──> <MspSender.IsActive()>
     ├──> <mspTransferActive> / sending is done and we need to update our flag
     │   ├──> crsf.UnlockMspMessage()  // unlock buffer for msp messages
     │   └──> mspTransferActive = false
     └──> <!mspTransferActive> // we are not sending so look for next msp package
         ├──> uint8_t* mspData
         ├──> uint8_t mspLen
         ├──> crsf.GetMspMessage(&mspData, &mspLen)
         └──> <mspData != nullptr> // if we have a new msp package start sending
             ├──> MspSender.SetDataToTransmit(mspData, mspLen)
             └──> mspTransferActive = true

3.3 RF接收任务&驱动

3.3.1 RXdoneISR

RF芯片收到报文后主要通过ProcessTLMpacket函数进行后续解包工作。

RXdoneISR(SX12xxDriverCommon::rx_status const status)
 │   /*
 │    * Step 1:检查是否已经收到过电传报文
 │    */
 ├──> <LQCalc.currentIsSet()>
 │   └──> return false // Already received tlm, do not run ProcessTLMpacket() again.
 │   /*
 │    * Step 2:电传报文处理
 │    */
 ├──> bool packetSuccessful = ProcessTLMpacket(status)
 ├──> busyTransmitting = false
 └──> return packetSuccessful

3.3.2 ProcessTLMpacket

将报文进行分门别类的报文处理,并做相关校验工作。

ProcessTLMpacket(SX12xxDriverCommon::rx_status const status)
 │   /*
 │    * Step 1:HW check
 │    */
 ├──> <status != SX12xxDriverCommon::SX12XX_RX_OK>
 │   ├──> DBGLN("TLM HW CRC error")
 │   └──> return false
 │   /*
 │    * Step 2:SW check
 │    */
 ├──> OTA_Packet_s * const otaPktPtr = (OTA_Packet_s * const)Radio.RXdataBuffer
 ├──> <!OtaValidatePacketCrc(otaPktPtr>)
 │   ├──> DBGLN("TLM crc error")
 │   └──> return false
 │   /*
 │    * Step 3:报文类型检查
 │    */
 ├──> <otaPktPtr->std.type != PACKET_TYPE_TLM>
 │   ├──> DBGLN("TLM type error %d", otaPktPtr->std.type)
 │   └──> return false
 ├──> LastTLMpacketRecvMillis = millis()
 ├──> LQCalc.add()
 │   /*
 │    * Step 4:获取报文属性状态
 │    */
 ├──> Radio.GetLastPacketStats()
 ├──> crsf.LinkStatistics.downlink_SNR = SNR_DESCALE(Radio.LastPacketSNRRaw)
 ├──> crsf.LinkStatistics.downlink_RSSI = Radio.LastPacketRSSI
 │   /*
 │    * Step 5:Full res mode报文
 │    */
 ├──> <OtaIsFullRes> // Full res mode
 │   ├──> OTA_Packet8_s * const ota8 = (OTA_Packet8_s * const)otaPktPtr
 │   ├──> uint8_t *telemPtr
 │   ├──> uint8_t dataLen
 │   ├──> <ota8->tlm_dl.containsLinkStats>
 │   │   ├──> LinkStatsFromOta(&ota8->tlm_dl.ul_link_stats.stats)
 │   │   ├──> telemPtr = ota8->tlm_dl.ul_link_stats.payload
 │   │   └──> dataLen = sizeof(ota8->tlm_dl.ul_link_stats.payload)
 │   ├──> <!ota8->tlm_dl.containsLinkStats>
 │   │   ├──> <firmwareOptions.is_airport>
 │   │   │   ├──> OtaUnpackAirportData(otaPktPtr, &apOutputBuffer)
 │   │   │   └──> return true
 │   │   ├──> telemPtr = ota8->tlm_dl.payload
 │   │   └──> dataLen = sizeof(ota8->tlm_dl.payload)
 │   └──> TelemetryReceiver.ReceiveData(ota8->tlm_dl.packageIndex, telemPtr, dataLen)
 │   /*
 │    * Step 6:Std res mode报文
 │    */
 ├──> <!OtaIsFullRes> // Std res mode
 │   ├──> <case ELRS_TELEMETRY_TYPE_LINK>
 │   │   ├──> LinkStatsFromOta(&otaPktPtr->std.tlm_dl.ul_link_stats.stats)
 │   │   └──> break
 │   └──> <case ELRS_TELEMETRY_TYPE_DATA>
 │       ├──> <firmwareOptions.is_airport>
 │       │   ├──> OtaUnpackAirportData(otaPktPtr, &apOutputBuffer)
 │       │   └──> return true
 │       ├──> TelemetryReceiver.ReceiveData(otaPktPtr->std.tlm_dl.packageIndex,
 │       │        otaPktPtr->std.tlm_dl.payload,
 │       │        sizeof(otaPktPtr->std.tlm_dl.payload))
 │       └──> break
 └──> <eturn true

3.3.3 TXdoneISR

RF芯片链路层报文发送完成,无需确认相关ack,简单做好后处理即可。

TXdoneISR()
 ├──> <!busyTransmitting>
 │   └──> return // Already finished transmission and do not call HandleFHSS() a second time, which may hop the frequency!
 ├──> <connectionState != awaitingModelId>
 │   ├──> HandleFHSS()
 │   ├──> HandlePrepareForTLM()
 │   └──> <Regulatory_Domain_EU_CE_2400> <TelemetryRcvPhase != ttrpPreReceiveGap>
 │       │ // Start RX for Listen Before Talk early because it takes about 100us
 │       │ // from RX enable to valid instant RSSI values are returned.
 │       │ // If rx was already started by TLM prepare above, this call will let RX
 │       │ // continue as normal.
 │       └──> BeginClearChannelAssessment()
 └──> busyTransmitting = false

4. 总结

本次整理比较快,存在比较多的额问题:

  1. 较多详细代码未经细读,会存在较多问题
  2. ESP32等MCU存在多核,会新增一个业务任务,详见:setup调用的devicesRegister函数
    3.【???待细看代码逻辑】明显就是尚未搞明白的地方

总体上先给出大概的接收机框架代码例程的部分解释,随着深入研读,再后续纠错,补充和完善。

5. 参考资料

【1】[ExpressLRS开源之接收机固件编译烧录步骤](https://blog.csdn.net/lida2003/article/details/132518813)
【2】ExpressLRS开源之RC链路性能测试
【3】ExpressLRS开源之基本调试数据含义
【4】ExpressLRS开源代码之框架结构
【5】ExpressLRS开源代码之工程结构


http://www.niftyadmin.cn/n/4995942.html

相关文章

基于单片机的简易智能电动车设计

一、项目介绍 智能交通工具在现代社会中起着越来越重要的作用&#xff0c;电动车作为一种环保、便捷的交通工具&#xff0c;受到了广泛的关注和应用。本设计基于单片机技术&#xff0c;设计一款简易智能电动车&#xff0c;实现基本的控制和功能&#xff0c;并提供良好的用户体…

深度学习之视频分类项目小记

写在前面&#xff0c;最近一阵在做视频分类相关的工作&#xff0c;趁有时间来记录一下。本文更注重项目实战与落地&#xff0c;而非重点探讨多模/视频模型结构的魔改 零、背景 目标&#xff1a;通过多模态内容理解技术&#xff0c;构建视频层级分类体系原技术方案&#xff1a…

【LeetCode题目详解】1281题 整数的各位积和之差 面试题 01.01. 判定字符是否唯一 python题解(作业一二)

本文章以python为例! 一、力扣第1281题&#xff1a;整数的各位积和之差 问题描述&#xff1a; 1281. 整数的各位积和之差 给你一个整数 n&#xff0c;请你帮忙计算并返回该整数「各位数字之积」与「各位数字之和」的差。 示例 1&#xff1a; 输入&#xff1a;n 234 输出…

Opencv图像暗通道调优

基于雾天退化模型的去雾算法&#xff0c;Opencv图像暗通道调优&#xff0c;&#xff08;清华版代码&#xff09;对普通相片也有较好的调优效果&#xff0c;相片更通透。 结合代码实际运行效果、算法理论模型、实际代码。我个人理解&#xff0c;实际效果是对图像的三个颜色通道…

【做题笔记】CSES-Mathematics

CSES2164 - Josephus Queries 题目链接 不难发现&#xff0c;当人数是奇数和偶数时&#xff0c;情况稍有不同。先看简单的偶数情况&#xff1a; 以 n 8 n8 n8为例。对于1 2 3 4 5 6 7 8&#xff0c;如果我们当前要走的步数是 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4&#xff0c;那么…

SQL知识点合集(最新)

SQL执行顺序 left join on and 和 inner join on and的多条件查询区别 left join on后面的and条件判断字段必须是左表 inner join on后面的and条件判断字段可以是左表或者右表 -- 查询一个课程包含那些题 SELECT c.id,t.title,t.id from course c left JOIN topical t ON t.cou…

YOLOv5算法改进(14)— 更换Neck之BiFPN

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。BiFPN &#xff08; Bidirectional Feature Pyramid Network &#xff09;是一种加权双向&#xff08;自顶向下 自底向上&#xff09;特征金字塔网络&#xff0c;是目标检测中神经网络架构设计的选择之一&#xff0c;是为…

2023应届生java面试紧张失误之一:CAS口误说成开心锁-笑坏面试官

源于&#xff1a;XX网&#xff0c;如果冒犯&#xff0c;表示歉意 面试官&#xff1a;什么是CAS 我&#xff1a;这个简单&#xff0c;开心锁 面试官&#xff1a;WTF&#xff1f; 我&#xff1a;一脸自信&#xff0c;对&#xff0c;就是这个 面试官&#xff1a;哈哈大笑&#xff…