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
 │   │  /*
 │   │   * Step 5:timerCallbackNormal/timerCallbackIdle
 │   │   */
 │   ├──> 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 6:链路初始化(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 7:配置按钮
 │    */
 ├──> <HAS_BUTTON>
 │   ├──> registerButtonFunction(ACTION_BIND, EnterBindingMode)
 │   └──> registerButtonFunction(ACTION_INCREASE_POWER, cyclePower)
 │   /*
 │    * Step 8:设备启动
 │    */
 ├──> devicesStart()
 │   /*
 │    * Step 9: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开源代码之工程结构

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐