ExpressLRS开源代码之接收机代码框架结构
1. 源由
ExpressLRS开源代码之框架结构从硬件和软件设计角度,抽象整理了一个框架。
本章将结合接收机实际代码实现,进行相应的介绍。
2. 分析
按照框架结构的软件设计角度考虑接收机设计:
- 设备初始化
setup
- 业务应用任务
loop
- RF接收任务&驱动
3. 接收机
注:【???待细看代码逻辑】表示这部分代码的业务逻辑尚不太清楚,后续如果搞清楚了,届时后尽量更新上来。
3.1 设备初始化
硬件上电启动后,首先进入setup
例程,对硬件做一个初始化。
setup
│ /*
│ * Step 1:串口波特率115200;EEPROM失败直接进入wifi模式
│ * 注:不是TARGET_UNIFIED_RX特殊处理。
│ */
├──> <TARGET_UNIFIED_RX>
│ ├──> [Setup default logging in case of failure, or no layout]
│ │ ├──> Serial.begin(115200)
│ │ └──> SerialLogger = &Serial
│ ├──> hardwareConfigured = options_init()
│ └──> <!hardwareConfigured> // Register the WiFi with the framework
│ ├──> devicesRegister(wifi_device, ARRAY_SIZE(wifi_device))
│ ├──> devicesInit()
│ └──> connectionState = hardwareUndefined
├──> <!TARGET_UNIFIED_RX>
│ └──> hardwareConfigured = options_init()
│ /*
│ * Step 2:接收机业务初始化
│ */
├──> <hardwareConfigured>
│ │ /*
│ │ * 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
│ │ ├──> <DEBUG_LOG>
│ │ │ ├──> Serial.begin(serialBaud)
│ │ │ └──> SerialLogger = &Serial
│ │ └──> <!DEBUG_LOG>
│ │ └──> SerialLogger = new NullStream()
│ │ /*
│ │ * Step 2.2:UID/IO/Serial
│ │ */
│ ├──> initUID()
│ ├──> setupTarget()
│ ├──> setupConfigAndPocCheck() // Init EEPROM and load config, checking powerup count
│ ├──> <OPT_HAS_SERVO_OUTPUT> <GPIO_PIN_RCSIGNAL_RX == UNDEF_PIN && GPIO_PIN_RCSIGNAL_TX == UNDEF_PIN>
│ │ /* 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
│ │ └──> <pinMode == somSerial>
│ │ ├──> 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()
│ └──> <connectionState != radioFailed>
│ ├──> hwTimer.callbackTock = &HWtimerCallbackTock
│ ├──> hwTimer.callbackTick = &HWtimerCallbackTick
│ ├──> MspReceiver.SetDataToReceive(MspData, ELRS_MSP_BUFFER)
│ ├──> Radio.RXnb()
│ └──> hwTimer.init()
│ /*
│ * Step 3:配置按钮
│ */
├──> <HAS_BUTTON>
│ ├──> registerButtonFunction(ACTION_BIND, EnterBindingMode)
│ └──> registerButtonFunction(ACTION_RESET_REBOOT, resetConfigAndReboot)
│ /*
│ * Step 4:设备启动
│ */
└──> devicesStart()
3.2 业务应用任务
整个业务大致切分为14段:
loop
├──> unsigned long now = millis()
│ /*
│ * Step 1:MSP报文处理
│ */
├──> <MspReceiver.HasFinishedData()>
│ └──> MspReceiveComplete()
├──> devicesUpdate(now)
│ /*
│ * Step 2:是否有软重启需要
│ */
├──> <PLATFORM_ESP8266 || PLATFORM_ESP32> // If the reboot time is set and the current time is past the reboot time then reboot.
│ └──> <rebootTime != 0 && now > rebootTime>
│ └──> ESP.restart()
│ /*
│ * Step 3:是否配置生效
│ */
├──> CheckConfigChangePending()
├──> executeDeferredFunction(now)
│ /*
│ * Step 4:状态判断
│ */
├──> <connectionState > 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:???待细看代码逻辑
│ */
├──> <connectionState == tentative && (now - LastSyncPacket > 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.IsActive() && telemetry.GetNextPayload(&nextPlayloadSize, &nextPayload)>
│ └──> 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)
│ /*
│ * Step 1:检查是否已经收到过电传报文
│ */
├──> <LQCalc.currentIsSet() && connectionState == connected>
│ └──> return false // Already received a packet, do not run ProcessRFPacket() again.
│ /*
│ * Step 2:电传报文处理
│ */
├──> <ProcessRFPacket(status)>
│ ├──> didFHSS = HandleFHSS()
│ └──> return true
└──> return false
3.3.2 ProcessRFPacket
将报文进行分门别类的报文处理,并做相关校验工作。
ProcessRFPacket(SX12xxDriverCommon::rx_status const status)
│ /*
│ * Step 1:HW check
│ */
├──> <status != SX12xxDriverCommon::SX12XX_RX_OK>
│ ├──> DBGVLN("HW CRC error")
│ ├──> <DEBUG_RX_SCOREBOARD> lastPacketCrcError = true
│ └──> return false
├──> uint32_t const beginProcessing = micros()
│ /*
│ * Step 2:SW check
│ */
├──> OTA_Packet_s * const otaPktPtr = (OTA_Packet_s * const)Radio.RXdataBuffer
├──> <!OtaValidatePacketCrc(otaPktPtr)>
│ ├──> DBGVLN("CRC error")
│ ├──> <DEBUG_RX_SCOREBOARD> 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数据包
│ */
├──> <case PACKET_TYPE_RCDATA> //Standard RC Data Packet
│ ├──> ProcessRfPacket_RC(otaPktPtr)
│ └──> break
│ /*
│ * Step 5:MSP数据包,支持WiFi TCP连接Betaflight
│ */
├──> <case PACKET_TYPE_MSPDATA>
│ ├──> ProcessRfPacket_MSP(otaPktPtr)
│ └──> break
│ /*
│ * Step 6:同步报文
│ */
├──> <case PACKET_TYPE_SYNC> //sync packet from master
│ ├──> doStartTimer = ProcessRfPacket_SYNC(now,
│ │ OtaIsFullRes ? &otaPktPtr->full.sync.sync : &otaPktPtr->std.sync)
│ │ && !InBindingMode
│ └──> break
│ /*
│ * Step 7:Airport 双向透明串行数据链路
│ */
├──> <case PACKET_TYPE_TLM>
│ ├──> <firmwareOptions.is_airport> OtaUnpackAirportData(otaPktPtr, &apOutputBuffer)
│ └──> break
│ /*
│ * Step 8:RF link status
│ */
├──> Radio.GetLastPacketStats() // Store the LQ/RSSI/Antenna
├──> getRFlinkInfo()
│ /*
│ * Step 9:频率温漂调整
│ */
├──> <Radio.FrequencyErrorAvailable()>
│ ├──> int32_t tempFreqCorrection = HandleFreqCorr(Radio.GetFrequencyErrorbool()) // Adjusts FreqCorrection for RX freq offset
│ └──> <RADIO_SX127X> 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)
│ */
├──> <DEBUG_RX_SCOREBOARD>
│ └──> <otaPktPtr->std.type != PACKET_TYPE_SYNC> DBGW(connectionHasModelMatch ? 'R' : 'r')
├──> <doStartTimer> hwTimer.resume() // will throw an interrupt immediately
└──> return true
3.3.3 TXdoneISR
RF芯片链路层报文发送完成,无需确认相关ack,简单做好后处理即可。
TXdoneISR()
├──> <Regulatory_Domain_EU_CE_2400>
│ └──> BeginClearChannelAssessment()
├──> <!Regulatory_Domain_EU_CE_2400>
│ └──> Radio.RXnb()
└──> <DEBUG_RX_SCOREBOARD>
└──> 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:更新上行链路质量
│ */
├──> <ExpressLRS_currAirRate_Modparams->numOfSends == 1>
│ └──> uplinkLQ = LQCalc.getLQ(); // Save the LQ value before the inc() reduces it by 1
│ /*
│ * Step 3:???待细看代码逻辑
│ */
├──> <!((OtaNonce - 1) % ExpressLRS_currAirRate_Modparams->numOfSends)>
│ ├──> uplinkLQ = LQCalcDVDA.getLQ();
│ └──> LQCalcDVDA.inc();
├──> CRSF::LinkStatistics.uplink_Link_quality = uplinkLQ;
│ /*
│ * Step 4:???待细看代码逻辑
│ */
├──> <!alreadyTLMresp> // 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发送成功检查
│ */
├──> <tlmSent && Radio.GetLastTransmitRadio() == SX12XX_Radio_NONE>
│ └──> Radio.TXdoneCallback(); // 3.3.3 TXdoneISR
│ /*
│ * Step 2:内部定时器事件触发
│ */
├──> PFDloop.intEvent(micros()); // our internal osc just fired
│ /*
│ * Step 3:RC数据报文检查
│ */
├──> <ExpressLRS_currAirRate_Modparams->numOfSends > 1 && !(OtaNonce % ExpressLRS_currAirRate_Modparams->numOfSends) && LQCalcDVDA.currentIsSet()>
│ ├──> crsfRCFrameAvailable();
│ └──> servoNewChannelsAvaliable();
│ /*
│ * Step 4:跳频设置
│ */
├──> <!didFHSS>
│ └──> HandleFHSS();
├──> didFHSS = false;
├──> Radio.isFirstRxIrq = true;
│ /*
│ * Step 5:更换当前活跃的天线
│ */
├──> updateDiversity();
│ /*
│ * Step 6:ELRS电传报文
│ */
├──> tlmSent = HandleSendTelemetryResponse();
│ /*
│ * Step 7:调试(DEBUG_RX_SCOREBOARD)
│ */
└──> <DEBUG_RX_SCOREBOARD>
├──> static bool lastPacketWasTelemetry = false;
├──> <!LQCalc.currentIsSet() && !lastPacketWasTelemetry>
│ └──> DBGW(lastPacketCrcError ? '.' : '_');
├──> lastPacketCrcError = false;
└──> lastPacketWasTelemetry = tlmSent;
4. 总结
本次整理比较快,存在比较多的额问题:
- 较多详细代码未经细读,会存在较多问题
- ESP32等MCU存在多核,会新增一个业务任务,详见:
setup
调用的devicesRegister
函数 - 【???待细看代码逻辑】明显就是尚未搞明白的地方
总体上先给出大概的接收机框架代码例程的部分解释,随着深入研读,再后续纠错,补充和完善。
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