ExpressLRS开源代码之接收机代码框架结构

ExpressLRS开源代码之接收机代码框架结构

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

1. 源由

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

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

2. 分析

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

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

3. 接收机

注:【???待细看代码逻辑】表示这部分代码的业务逻辑尚不太清楚,后续如果搞清楚了,届时后尽量更新上来。

3.1 设备初始化

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

setup
 │   /*
 │    * Step 1:串口波特率115200;EEPROM失败直接进入wifi模式
 │    * 注:不是TARGET_UNIFIED_RX特殊处理。
 │    */
 ├──> 
 │   ├──> [Setup default logging in case of failure, or no layout]
 │   │   ├──> Serial.begin(115200)
 │   │   └──> SerialLogger = &Serial
 │   ├──> hardwareConfigured = options_init()
 │   └──>  // Register the WiFi with the framework
 │       ├──> devicesRegister(wifi_device, ARRAY_SIZE(wifi_device))
 │       ├──> devicesInit()
 │       └──> connectionState = hardwareUndefined
 ├──> 
 │   └──> hardwareConfigured = options_init()
 │   /*
 │    * Step 2:接收机业务初始化
 │    */
 ├──> 
 │   │   /*
 │   │    * Step 2.1:日志接口定向
 │   │    */
 │   ├──> [pre-initialise serial] //prevent block if the buffer fills
 │   │   ├──> serialBaud = firmwareOptions.uart_baud  // default to CRSF protocol and the compiled baud rate
 │   │   ├──> 
 │   │   │   ├──> Serial.begin(serialBaud)
 │   │   │   └──> SerialLogger = &Serial
 │   │   └──> 
 │   │       └──> SerialLogger = new NullStream()
 │   │   /*
 │   │    * Step 2.2:UID/IO/Serial
 │   │    */
 │   ├──> initUID()
 │   ├──> setupTarget()
 │   ├──> setupConfigAndPocCheck() // Init EEPROM and load config, checking powerup count
 │   ├──>  
 │   │    /* If serial is not already defined, 
 │   │       then see if there is serial pin configured in the PWM configuration
 │   │     */
 │   │   └──> for (int i = 0 ; i < GPIO_PIN_PWM_OUTPUTS_COUNT ; i++)
 │   │       ├──> eServoOutputMode pinMode = (eServoOutputMode)config.GetPwmChannel(i)->val.mode
 │   │       └──> 
 │   │           ├──> pwmSerialDefined = true
 │   │           └──> break
 │   ├──> setupSerial()
 │   │   /*
 │   │    * Step 2.3:ESP32模块任务相关启动
 │   │    * 注:ESP32会建立任务
 │   │    */
 │   ├──> DBGLN("ExpressLRS Module Booting...")
 │   ├──> devicesRegister(ui_devices, ARRAY_SIZE(ui_devices))
 │   ├──> devicesInit()
 │   │   /*
 │   │    * Step 2.4:根据绑定配置信息,建立RF通信热点
 │   │    * 注1:挂上定时轮训钩子:HWtimerCallbackTock/HWtimerCallbackTick
 │   │    * 注2:挂上RF通讯中断函数:RXdoneISR/TXdoneISR
 │   │    */
 │   ├──> setupBindingFromConfig()
 │   ├──> FHSSrandomiseFHSSsequence(uidMacSeedGet())
 │   ├──> setupRadio()
 │   └──> 
 │       ├──> hwTimer.callbackTock = &HWtimerCallbackTock
 │       ├──> hwTimer.callbackTick = &HWtimerCallbackTick
 │       ├──> MspReceiver.SetDataToReceive(MspData, ELRS_MSP_BUFFER)
 │       ├──> Radio.RXnb()
 │       └──> hwTimer.init()
 │   /*
 │    * Step 3:配置按钮
 │    */
 ├──> 
 │   ├──> registerButtonFunction(ACTION_BIND, EnterBindingMode)
 │   └──> registerButtonFunction(ACTION_RESET_REBOOT, resetConfigAndReboot)
 │   /*
 │    * Step 4:设备启动
 │    */
 └──> devicesStart()

3.2 业务应用任务

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

loop
 ├──> unsigned long now = millis()
 │   /*
 │    * Step 1:MSP报文处理
 │    */
 ├──> 
 │   └──> MspReceiveComplete()
 ├──> devicesUpdate(now)
 │   /*
 │    * Step 2:是否有软重启需要
 │    */
 ├──>  // If the reboot time is set and the current time is past the reboot time then reboot.
 │   └──>  rebootTime>
 │       └──> ESP.restart()
 │   /*
 │    * Step 3:是否配置生效
 │    */
 ├──> CheckConfigChangePending()
 ├──> executeDeferredFunction(now)
 │   /*
 │    * Step 4:状态判断
 │    */
 ├──>  MODE_STATES>
 │   └──> return
 │   /*
 │    * Step 5:???待细看代码逻辑
 │    */
 ├──> <(connectionState != disconnected) && (ExpressLRS_currAirRate_Modparams->index != ExpressLRS_nextAirRateIndex)> // forced change
 │   ├──> DBGLN("Req air rate change %u->%u", ExpressLRS_currAirRate_Modparams->index, ExpressLRS_nextAirRateIndex)
 │   ├──> LostConnection(true)
 │   ├──> LastSyncPacket = now           // reset this variable to stop rf mode switching and add extra time
 │   ├──> RFmodeLastCycled = now         // reset this variable to stop rf mode switching and add extra time
 │   ├──> SendLinkStatstoFCintervalLastSent = 0
 │   └──> SendLinkStatstoFCForcedSends = 2
 │   /*
 │    * Step 6:???待细看代码逻辑
 │    */
 ├──>  ExpressLRS_currAirRate_RFperfParams->RxLockTimeoutMs)>
 │   ├──> DBGLN("Bad sync, aborting")
 │   ├──> LostConnection(true)
 │   ├──> RFmodeLastCycled = now
 │   └──> LastSyncPacket = now
 │   /*
 │    * Step 7:???待细看代码逻辑
 │    */
 ├──> cycleRfMode(now)
 │   /*
 │    * Step 8:RC链路断链,信号丢失
 │    */
 ├──> uint32_t localLastValidPacket = LastValidPacket // Required to prevent race condition due to LastValidPacket getting updated from ISR
 ├──> <(connectionState == connected) && ((int32_t)ExpressLRS_currAirRate_RFperfParams->DisconnectTimeoutMs < (int32_t)(now - localLastValidPacket))> // check if we lost conn.
 │   └──> LostConnection(true)
 │   /*
 │    * Step 9:RC链路信号恢复
 │    */
 ├──> <(connectionState == tentative) && (abs(LPF_OffsetDx.value()) <= 10) && (LPF_Offset.value() < 100) && (LQCalc.getLQRaw() > minLqForChaos())> //detects when we are connected
 │   └──> GotConnection(now)
 │   /*
 │    * Step 10:RC链路状态信息反馈飞控
 │    */
 ├──> checkSendLinkStatsToFc(now)
 │   /*
 │    * Step 11:???待细看代码逻辑
 │    */
 ├──> <(RXtimerState == tim_tentative) && ((now - GotConnectionMillis) > ConsiderConnGoodMillis) && (abs(LPF_OffsetDx.value()) <= 5)>
 │   ├──> RXtimerState = tim_locked
 │   └──> DBGLN("Timer locked")
 │   /*
 │    * Step 12:电传报文发送
 │    */
 ├──> 
 │   └──> TelemetrySender.SetDataToTransmit(nextPayload, nextPlayloadSize)
 │   /*
 │    * Step 13:参数/状态检查
 │    */
 ├──> updateTelemetryBurst()
 ├──> updateBindingMode(now)
 ├──> updateSwitchMode()
 ├──> checkGeminiMode()
 │   /*
 │    * Step 14:debug函数(貌似这个loop里面的打印量很大啊)
 │    */
 ├──> debugRcvrLinkstats()
 └──> debugRcvrSignalStats(now)

3.3 RF接收任务&驱动

3.3.1 RXdoneISR

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

RXdoneISR(SX12xxDriverCommon::rx_status const status)
 ├──> 
 │   └──> return false // Already received a packet, do not run ProcessRFPacket() again.
 ├──> 
 │   ├──> didFHSS = HandleFHSS()
 │   └──> return true
 └──> return false

3.3.2 ProcessRFPacket

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

ProcessRFPacket(SX12xxDriverCommon::rx_status const status)
 │   /*
 │    * Step 1:HW check
 │    */
 ├──> 
 │   ├──> DBGVLN("HW CRC error")
 │   ├──>  lastPacketCrcError = true
 │   └──> return false
 ├──> uint32_t const beginProcessing = micros()
 │   /*
 │    * Step 2:SW check
 │    */
 ├──> OTA_Packet_s * const otaPktPtr = (OTA_Packet_s * const)Radio.RXdataBuffer
 ├──> 
 │   ├──> DBGVLN("CRC error")
 │   ├──>  lastPacketCrcError = true
 │   └──> return false
 │   /*
 │    * Step 3:事件处理时间更新
 │    */
 ├──> PFDloop.extEvent(beginProcessing + PACKET_TO_TOCK_SLACK)
 ├──> bool doStartTimer = false
 ├──> unsigned long now = millis()
 ├──> LastValidPacket = now
 │   /*
 │    * Step 4:标准RC数据包
 │    */
 ├──>  //Standard RC Data Packet
 │   ├──> ProcessRfPacket_RC(otaPktPtr)
 │   └──> break
 │   /*
 │    * Step 5:MSP数据包,支持WiFi TCP连接Betaflight
 │    */
 ├──> 
 │   ├──> ProcessRfPacket_MSP(otaPktPtr)
 │   └──> break
 │   /*
 │    * Step 6:同步报文
 │    */
 ├──>  //sync packet from master
 │   ├──> doStartTimer = ProcessRfPacket_SYNC(now,
 │   │      OtaIsFullRes ? &otaPktPtr->full.sync.sync : &otaPktPtr->std.sync)
 │   │      && !InBindingMode
 │   └──> break
 │   /*
 │    * Step 7:Airport 双向透明串行数据链路
 │    */
 ├──> 
 │   ├──>  OtaUnpackAirportData(otaPktPtr, &apOutputBuffer)
 │   └──> break
 │   /*
 │    * Step 8:RF link status
 │    */
 ├──> Radio.GetLastPacketStats() // Store the LQ/RSSI/Antenna
 ├──> getRFlinkInfo()
 │   /*
 │    * Step 9:频率温漂调整
 │    */
 ├──> 
 │   ├──> int32_t tempFreqCorrection = HandleFreqCorr(Radio.GetFrequencyErrorbool())      // Adjusts FreqCorrection for RX freq offset
 │   └──>  Radio.SetPPMoffsetReg(tempFreqCorrection)  // Teamp900 also needs to adjust its demood PPM
 │   /*
 │    * Step 10:Link quality
 │    */
 ├──> LQCalc.add()  // Received a packet, that's the definition of LQ
 │
 │  // Extend sync duration since we've received a packet at this rate
 │  // but do not extend it indefinitely
 ├──> RFmodeCycleMultiplier = RFmodeCycleMultiplierSlow
 │   /*
 │    * Step 11:调试(DEBUG_RX_SCOREBOARD)
 │    */
 ├──> 
 │   └──> std.type != PACKET_TYPE_SYNC> DBGW(connectionHasModelMatch ? 'R' : 'r')
 ├──>  hwTimer.resume() // will throw an interrupt immediately
 └──> return true

3.3.3 TXdoneISR

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

TXdoneISR()
 ├──> 
 │   └──> BeginClearChannelAssessment()
 ├──> 
 │   └──> Radio.RXnb()
 └──> 
     └──> DBGW('T')
}

3.3.4 HWtimerCallbackTick

HWtimerCallbackTick() // this is 180 out of phase with the other callback, occurs mid-packet reception
 │   /*
 │    * Step 1:更新相位锁定
 │    */
 ├──> updatePhaseLock();
 ├──> OtaNonce++;
 │   /*
 │    * Step 2:更新上行链路质量
 │    */
 ├──> numOfSends == 1>  
 │   └──> uplinkLQ = LQCalc.getLQ(); // Save the LQ value before the inc() reduces it by 1
 │   /*
 │    * Step 3:???待细看代码逻辑
 │    */
 ├──> numOfSends)>
 │   ├──> uplinkLQ = LQCalcDVDA.getLQ();
 │   └──> LQCalcDVDA.inc();
 ├──> CRSF::LinkStatistics.uplink_Link_quality = uplinkLQ;
 │   /*
 │    * Step 4:???待细看代码逻辑
 │    */
 ├──>  // Only advance the LQI period counter if we didn't send Telemetry this period
 │   └──> LQCalc.inc();
 ├──> alreadyTLMresp = false;
 └──> alreadyFHSS = false;

3.3.5 HWtimerCallbackTock

HWTIMER_TICKS_PER_US(5)定时器回调

HWtimerCallbackTock()
 │   /*
 │    * Step 1:TelemetryResponse发送成功检查
 │    */
 ├──> 
 │   └──> Radio.TXdoneCallback();  // 3.3.3 TXdoneISR
 │   /*
 │    * Step 2:内部定时器事件触发
 │    */
 ├──> PFDloop.intEvent(micros()); // our internal osc just fired
 │   /*
 │    * Step 3:RC数据报文检查
 │    */
 ├──> numOfSends > 1 && !(OtaNonce % ExpressLRS_currAirRate_Modparams->numOfSends) && LQCalcDVDA.currentIsSet()>
 │   ├──> crsfRCFrameAvailable();
 │   └──> servoNewChannelsAvaliable();
 │   /*
 │    * Step 4:跳频设置
 │    */
 ├──> 
 │   └──> HandleFHSS();
 ├──> didFHSS = false;
 ├──> Radio.isFirstRxIrq = true;
 │   /*
 │    * Step 5:更换当前活跃的天线
 │    */
 ├──> updateDiversity();
 │   /*
 │    * Step 6:ELRS电传报文
 │    */
 ├──> tlmSent = HandleSendTelemetryResponse();
 │   /*
 │    * Step 7:调试(DEBUG_RX_SCOREBOARD)
 │    */
 └──> 
     ├──> static bool lastPacketWasTelemetry = false;
     ├──> 
     │   └──> DBGW(lastPacketCrcError ? '.' : '_');
     ├──> lastPacketCrcError = false;
     └──> lastPacketWasTelemetry = tlmSent;

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开源代码之工程结构
【6】Airport - a bi-directional transparent serial data link over the air

你可能感兴趣的:(DIY,Drones,ELRS,开源)