ExpressLRS开源代码之框架结构从硬件和软件设计角度,抽象整理了一个框架。
本章将结合接收机实际代码实现,进行相应的介绍。
按照框架结构的软件设计角度考虑接收机设计:
setup
loop
注:【???待细看代码逻辑】表示这部分代码的业务逻辑尚不太清楚,后续如果搞清楚了,届时后尽量更新上来。
硬件上电启动后,首先进入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()
整个业务大致切分为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)
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
将报文进行分门别类的报文处理,并做相关校验工作。
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
RF芯片链路层报文发送完成,无需确认相关ack,简单做好后处理即可。
TXdoneISR()
├──>
│ └──> BeginClearChannelAssessment()
├──>
│ └──> Radio.RXnb()
└──>
└──> DBGW('T')
}
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;
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;
本次整理比较快,存在比较多的额问题:
setup
调用的devicesRegister
函数总体上先给出大概的接收机框架代码例程的部分解释,随着深入研读,再后续纠错,补充和完善。
【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