#include "SX126xInterface.h" #include "configuration.h" #include "error.h" #include "mesh/NodeDB.h" #ifdef ARCH_PORTDUINO #include "PortduinoGlue.h" #endif // Particular boards might define a different max power based on what their hardware can do, default to max power output if not // specified (may be dangerous if using external PA and SX126x power config forgotten) #ifndef SX126X_MAX_POWER #define SX126X_MAX_POWER 22 #endif template SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) : RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module) { LOG_DEBUG("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy); } /// Initialise the Driver transport hardware and software. /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. template bool SX126xInterface::init() { // Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO paths). The negation is usually performed in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to this style of RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and DIO2's switching is done by the SX126X itself, so the MCU can't control ¬CTRL at exactly the same time. One solution would be to set ¬CTRL as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=1 has the opposite and stable RF path effect as CTRL=0 and ¬CTRL=1, this depends on the RF switch, but it seems this usually works. Better hardware design, which is done most the time, means this workaround is not necessary. #ifdef SX126X_ANT_SW // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly used and not part of the 'default' set of pin definitions. digitalWrite(SX126X_ANT_SW, HIGH); pinMode(SX126X_ANT_SW, OUTPUT); #endif #ifdef SX126X_POWER_EN // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly used and not part of the 'default' set of pin definitions. digitalWrite(SX126X_POWER_EN, HIGH); pinMode(SX126X_POWER_EN, OUTPUT); #endif #if ARCH_PORTDUINO float tcxoVoltage = 0; if (settingsMap[dio3_tcxo_voltage]) tcxoVoltage = 1.8; // FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE #elif !defined(SX126X_DIO3_TCXO_VOLTAGE) float tcxoVoltage = 0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per // https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.h#L471C26-L471C104 // (DIO3 is free to be used as an IRQ) #elif !defined(TCXO_OPTIONAL) float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // (DIO3 is not free to be used as an IRQ) #endif if (tcxoVoltage == 0) LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage\n"); else LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V\n", tcxoVoltage); // FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC? RadioLibInterface::init(); if (power > SX126X_MAX_POWER) // Clamp power to maximum defined level power = SX126X_MAX_POWER; limitPower(); int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO); // \todo Display actual typename of the adapter, not just `SX126x` LOG_INFO("SX126x init result %d\n", res); if (res == RADIOLIB_ERR_CHIP_NOT_FOUND) return false; LOG_INFO("Frequency set to %f\n", getFreq()); LOG_INFO("Bandwidth set to %f\n", bw); LOG_INFO("Power output set to %d\n", power); // Overriding current limit // (https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.cpp#L85) using // value in SX126xInterface.h (currently 140 mA) It may or may not be neccessary, depending on how RadioLib functions, from // SX1261/2 datasheet: OCP after setting DeviceSel with SetPaConfig(): SX1261 - 60 mA, SX1262 - 140 mA For the SX1268 the IC // defaults to 140mA no matter the set power level, but RadioLib set it lower, this would need further checking Default values // are: SX1262, SX1268: 0x38 (140 mA), SX1261: 0x18 (60 mA) // FIXME: Not ideal to increase SX1261 current limit above 60mA as it can only transmit max 15dBm, should probably only do it // if using SX1262 or SX1268 res = lora.setCurrentLimit(currentLimit); LOG_DEBUG("Current limit set to %f\n", currentLimit); LOG_DEBUG("Current limit set result %d\n", res); #ifdef SX126X_DIO2_AS_RF_SWITCH LOG_DEBUG("Setting DIO2 as RF switch\n"); bool dio2AsRfSwitch = true; #elif defined(ARCH_PORTDUINO) bool dio2AsRfSwitch = false; if (settingsMap[dio2_as_rf_switch]) { LOG_DEBUG("Setting DIO2 as RF switch\n"); dio2AsRfSwitch = true; } #else LOG_DEBUG("Setting DIO2 as not RF switch\n"); bool dio2AsRfSwitch = false; #endif if (res == RADIOLIB_ERR_NONE) { res = lora.setDio2AsRfSwitch(dio2AsRfSwitch); } // If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has // no effect #if ARCH_PORTDUINO if (res == RADIOLIB_ERR_NONE) { LOG_DEBUG("Using MCU pin %i as RXEN and pin %i as TXEN to control RF switching\n", settingsMap[rxen], settingsMap[txen]); lora.setRfSwitchPins(settingsMap[rxen], settingsMap[txen]); } #else #ifndef SX126X_RXEN #define SX126X_RXEN RADIOLIB_NC LOG_DEBUG("SX126X_RXEN not defined, defaulting to RADIOLIB_NC\n"); #endif #ifndef SX126X_TXEN #define SX126X_TXEN RADIOLIB_NC LOG_DEBUG("SX126X_TXEN not defined, defaulting to RADIOLIB_NC\n"); #endif if (res == RADIOLIB_ERR_NONE) { LOG_DEBUG("Using MCU pin %i as RXEN and pin %i as TXEN to control RF switching\n", SX126X_RXEN, SX126X_TXEN); lora.setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); } #endif if (config.lora.sx126x_rx_boosted_gain) { uint16_t result = lora.setRxBoostedGainMode(true); LOG_INFO("Set RX gain to boosted mode; result: %d\n", result); } else { uint16_t result = lora.setRxBoostedGainMode(false); LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d\n", result); } #if 0 // Read/write a register we are not using (only used for FSK mode) to test SPI comms uint8_t crcLSB = 0; int err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1); if(err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure); //if(crcLSB != 0x0f) // RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure); crcLSB = 0x5a; err = lora.writeRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1); if(err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure); err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1); if(err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure); if(crcLSB != 0x5a) RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure); // If we got this far register accesses (and therefore SPI comms) are good #endif if (res == RADIOLIB_ERR_NONE) res = lora.setCRC(RADIOLIB_SX126X_LORA_CRC_ON); if (res == RADIOLIB_ERR_NONE) startReceive(); // start receiving return res == RADIOLIB_ERR_NONE; } template bool SX126xInterface::reconfigure() { RadioLibInterface::reconfigure(); // set mode to standby setStandby(); // configure publicly accessible settings int err = lora.setSpreadingFactor(sf); if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); err = lora.setBandwidth(bw); if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); err = lora.setCodingRate(cr); if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); err = lora.setSyncWord(syncWord); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("Radiolib error %d when attempting SX126X setSyncWord!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setCurrentLimit(currentLimit); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("Radiolib error %d when attempting SX126X setCurrentLimit!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setPreambleLength(preambleLength); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("Radiolib error %d when attempting SX126X setPreambleLength!\n", err); assert(err == RADIOLIB_ERR_NONE); err = lora.setFrequency(getFreq()); if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); if (power > SX126X_MAX_POWER) // This chip has lower power limits than some power = SX126X_MAX_POWER; err = lora.setOutputPower(power); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("Radiolib error %d when attempting SX126X setOutputPower!\n", err); assert(err == RADIOLIB_ERR_NONE); startReceive(); // restart receiving return RADIOLIB_ERR_NONE; } template void INTERRUPT_ATTR SX126xInterface::disableInterrupt() { lora.clearDio1Action(); } template void SX126xInterface::setStandby() { checkNotification(); // handle any pending interrupts before we force standby int err = lora.standby(); if (err != RADIOLIB_ERR_NONE) LOG_DEBUG("SX126x standby failed with error %d\n", err); assert(err == RADIOLIB_ERR_NONE); isReceiving = false; // If we were receiving, not any more activeReceiveStart = 0; disableInterrupt(); completeSending(); // If we were sending, not anymore RadioLibInterface::setStandby(); } /** * Add SNR data to received messages */ template void SX126xInterface::addReceiveMetadata(meshtastic_MeshPacket *mp) { // LOG_DEBUG("PacketStatus %x\n", lora.getPacketStatus()); mp->rx_snr = lora.getSNR(); mp->rx_rssi = lround(lora.getRSSI()); } /** We override to turn on transmitter power as needed. */ template void SX126xInterface::configHardwareForSend() { RadioLibInterface::configHardwareForSend(); } // For power draw measurements, helpful to force radio to stay sleeping // #define SLEEP_ONLY template void SX126xInterface::startReceive() { #ifdef SLEEP_ONLY sleep(); #else setStandby(); // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. // Furthermore, we need the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | RADIOLIB_IRQ_PREAMBLE_DETECTED); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err); assert(err == RADIOLIB_ERR_NONE); RadioLibInterface::startReceive(); // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); #endif } /** Is the channel currently active? */ template bool SX126xInterface::isChannelActive() { // check if we can detect a LoRa preamble on the current channel int16_t result; setStandby(); result = lora.scanChannel(); if (result == RADIOLIB_LORA_DETECTED) return true; if (result != RADIOLIB_CHANNEL_FREE) LOG_ERROR("Radiolib error %d when attempting SX126X scanChannel!\n", result); assert(result != RADIOLIB_ERR_WRONG_MODEM); return false; } /** Could we send right now (i.e. either not actively receiving or transmitting)? */ template bool SX126xInterface::isActivelyReceiving() { // The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet // received and handled the interrupt for reading the packet/handling errors. uint16_t irq = lora.getIrqFlags(); bool detected = (irq & (RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED)); // Handle false detections if (detected) { uint32_t now = millis(); if (!activeReceiveStart) { activeReceiveStart = now; } else if ((now - activeReceiveStart > 2 * preambleTimeMsec) && !(irq & RADIOLIB_SX126X_IRQ_HEADER_VALID)) { // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag activeReceiveStart = 0; LOG_DEBUG("Ignore false preamble detection.\n"); return false; } else if (now - activeReceiveStart > maxPacketTimeMsec) { // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag activeReceiveStart = 0; LOG_DEBUG("Ignore false header detection.\n"); return false; } } // if (detected) LOG_DEBUG("rx detected\n"); return detected; } template bool SX126xInterface::sleep() { // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet // \todo Display actual typename of the adapter, not just `SX126x` LOG_DEBUG("SX126x entering sleep mode (FIXME, don't keep config)\n"); setStandby(); // Stop any pending operations // turn off TCXO if it was powered // FIXME - this isn't correct // lora.setTCXO(0); // put chipset into sleep mode (we've already disabled interrupts by now) bool keepConfig = true; lora.sleep(keepConfig); // Note: we do not keep the config, full reinit will be needed #ifdef SX126X_POWER_EN digitalWrite(SX126X_POWER_EN, LOW); #endif return true; }