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

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

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

1. 源由

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

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

2. 分析

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

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

3. 发射机应用

3.1 设备初始化

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

C 复制代码
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段:

C 复制代码
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函数进行后续解包工作。

C 复制代码
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

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

C 复制代码
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,简单做好后处理即可。

C 复制代码
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开源代码之工程结构

相关推荐
yumgpkpm13 分钟前
(简略)AI 大模型 手机的“简单替换陷阱”与Hadoop、Cloudera CDP 7大数据底座的关系探析
人工智能·hive·zookeeper·flink·spark·kafka·开源
yumgpkpm14 分钟前
Cloudera CDP 7.3下载地址、方式,开源适配 CMP 7.3(或类 CDP 的 CMP 7.13 平台,如华为鲲鹏 ARM 版)值得推荐
大数据·hive·hadoop·分布式·华为·开源·cloudera
周杰伦_Jay12 小时前
【大模型数据标注】核心技术与优秀开源框架
人工智能·机器学习·eureka·开源·github
玄魂12 小时前
如何查看、生成 github 开源项目star 图表
前端·开源·echarts
隐语SecretFlow13 小时前
【隐语Secreflow】如何配置 Kuscia 对请求进行 Path Rewrit
架构·开源
hh.h.13 小时前
开源鸿蒙生态下Flutter的发展前景分析
flutter·开源·harmonyos
一RTOS一18 小时前
光亚鸿道携手AGIROS开源社区,共筑中国具身智能机器人操作系统新生态
机器人·开源·鸿道实时操作系统·国产嵌入式操作系统选型·具身智能操作系统选型
刘发财18 小时前
前端一行代码生成数千页PDF,dompdf.js新增分页功能
前端·typescript·开源
CoderJia程序员甲18 小时前
GitHub 热榜项目 - 日榜(2025-12-15)
git·ai·开源·llm·github