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. 分析
按照框架结构的软件设计角度考虑发射机设计:
- 设备初始化
setup
- 业务应用任务
loop
- 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. 总结
本次整理比较快,存在比较多的额问题:
- 较多详细代码未经细读,会存在较多问题
- ESP32等MCU存在多核,会新增一个业务任务,详见:setup调用的devicesRegister函数
3.【???待细看代码逻辑】明显就是尚未搞明白的地方
总体上先给出大概的接收机框架代码例程的部分解释,随着深入研读,再后续纠错,补充和完善。
5. 参考资料
【1】[ExpressLRS开源之接收机固件编译烧录步骤](https://blog.csdn.net/lida2003/article/details/132518813)