1

radio_sx128x.cpp

Committer:
advxolltm
Date:
2022-06-06
Revision:
22:fc88e265f425
Parent:
20:75635d50262e

File content as of revision 22:fc88e265f425:

#include "radio.h"
#ifdef SX128x_H 
#include "SPIu.h"
#include <float.h>

#ifdef DEVICE_LPTICKER
LowPowerTimer Radio::lpt;
#else
Timer Radio::lpt;
#endif

#if (MBED_MAJOR_VERSION < 6)
volatile us_timestamp_t Radio::irqAt;
#else
LowPowerClock::time_point Radio::irqAt;
#endif

#ifdef TARGET_FF_ARDUINO    /* pins of SX126xDVK1xAS board */
    #define NRST_PIN        A0
    SPIu spi(D11, D12, D13); // mosi, miso, sclk
    //           spi, nss, busy, dio1
    SX128x Radio::radio(spi,  D7,   D3,   D5, NRST_PIN);

    #define LED_ON      1
    #define LED_OFF     0
    DigitalOut tx_led(A4);
    DigitalOut rx_led(A5);


    DigitalOut ant_sw(A3);
    DigitalOut cps(D6); // SE2436L

    bool fe_enable; // SE2436L

    void Radio::chipModeChange()
    {
        if (radio.chipMode == CHIPMODE_NONE) {
            cps = 0;
            tx_led = LED_OFF;
            rx_led = LED_OFF;
        } else if (radio.chipMode == CHIPMODE_TX) {
            cps = fe_enable;
            tx_led = LED_ON;
            rx_led = LED_OFF;
        } else if (radio.chipMode == CHIPMODE_RX) {
            cps = fe_enable;
            tx_led = LED_OFF;
            rx_led = LED_ON;
        }
    }
#endif /* TARGET_FF_ARDUINO */

#ifdef TARGET_FF_MORPHO
    DigitalOut pc3(PC_3);   // debug RX indication, for nucleo boards
    #define RX_INDICATION       pc3
#endif /* TARGET_FF_MORPHO */


PacketParams_t Radio::ppGFSK;
PacketParams_t Radio::ppLORA;
PacketParams_t Radio::ppFLRC;

ModulationParams_t Radio::mpBLE_GFSK;
ModulationParams_t Radio::mpFLRC;
ModulationParams_t Radio::mpLORA;

const RadioEvents_t* RadioEvents;

unsigned Radio::symbolPeriodUs;
unsigned Radio::nSymbs;
unsigned Radio::rxTimeoutMs;

void Radio::readChip()
{
    uint8_t reg8;

    reg8 = radio.readReg(REG_ADDR_PKTCTRL0, 1);
    ppGFSK.gfskFLRC.HeaderType = reg8 & 0x20;
    ppFLRC.gfskFLRC.HeaderType = reg8 & 0x20;

    reg8 = radio.readReg(REG_ADDR_PKTCTRL1, 1);
    ppGFSK.gfskFLRC.PreambleLength = reg8 & 0x70;
    ppFLRC.gfskFLRC.PreambleLength = reg8 & 0x70;
    ppGFSK.gfskFLRC.SyncWordLength = reg8 & 0x0e;
    ppFLRC.gfskFLRC.SyncWordLength = reg8 & 0x06;
    if (ppFLRC.gfskFLRC.SyncWordLength == 0x06)
        ppFLRC.gfskFLRC.SyncWordLength = FLRC_SYNC_WORD_LEN_P32S;

    reg8 = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_CTRL, 1);
    ppGFSK.gfskFLRC.SyncWordMatch = reg8 & 0x70;
    ppFLRC.gfskFLRC.SyncWordMatch = reg8 & 0x70;

    reg8 = radio.readReg(REG_ADDR_PAYLOAD_LEN, 1);
    ppGFSK.gfskFLRC.PayloadLength = reg8;
    ppFLRC.gfskFLRC.PayloadLength = reg8;

    reg8 = radio.readReg(REG_ADDR_PKT_TX_HEADER, 1);    // TODO hi bit of payload length
    //ppBLE.ble.ConnectionState = reg8 & 0xe0;
    //ppBLE.ble.BleTestPayload = reg8 & 0x1c;

    reg8 = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);
    //ppBLE.ble.CrcLength = reg8 & 0x30;
    //ppBLE.ble.Whitening = reg8 & 0x08;
    ppGFSK.gfskFLRC.CRCLength = reg8 & 0x30;
    ppFLRC.gfskFLRC.CRCLength = reg8 & 0x30;
    ppGFSK.gfskFLRC.Whitening = reg8 & 0x08;
    ppFLRC.gfskFLRC.Whitening = reg8 & 0x08;

    {
        LoRaPktPar0_t LoRaPktPar0;
        LoRaPktPar0.octet = radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);
        switch (LoRaPktPar0.bits.modem_bw) {
            case 2: mpLORA.lora.bandwidth = LORA_BW_200; break;
            case 3: mpLORA.lora.bandwidth = LORA_BW_400; break;
            case 4: mpLORA.lora.bandwidth = LORA_BW_800; break;
            case 5: mpLORA.lora.bandwidth = LORA_BW_1600; break;
        }
        mpLORA.lora.spreadingFactor = LoRaPktPar0.bits.modem_sf << 4;
    }

    {
        LoRaPktPar1_t LoRaPktPar1;
        LoRaPktPar1.octet = radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
        mpLORA.lora.codingRate = LoRaPktPar1.bits.coding_rate;
        ppLORA.lora.InvertIQ = LoRaPktPar1.octet & LORA_IQ_STD; // LoRaPktPar1.bits.rxinvert_iq
        ppLORA.lora.HeaderType = LoRaPktPar1.bits.implicit_header ? IMPLICIT_HEADER : EXPLICIT_HEADER;
        // LoRaPktPar1.bits.ppm_offset
    }

    {
        LoRaPreambleReg_t LoRaPreambleReg;
        LoRaPreambleReg.octet = radio.readReg(REG_ADDR_LORA_PREAMBLE, 1);
        ppLORA.lora.PreambleLength = LoRaPreambleReg.bits.preamble_symb1_nb * (1 << LoRaPreambleReg.bits.preamble_symb_nb_exp);
    }
    ppLORA.lora.PayloadLength = radio.readReg(REG_ADDR_LORA_TX_PAYLOAD_LENGTH, 1);

    {
        LoRaLrCtl_t LoRaLrCtl;
        LoRaLrCtl.octet = radio.readReg(REG_ADDR_LORA_LRCTL, 1);
        ppLORA.lora.crc = LoRaLrCtl.octet & 0x20; // LoRaLrCtl.bits.crc_en
    }

    {
        RegRxBw_t RegRxBw;
        unsigned bps;
        FloraPreambleHi_t FloraPreambleHi;
        float mi, fdev_hz;
        unsigned freqDev;
        FskModDfH_t FskModDfH;
        FskModDfH.octet = radio.readReg(REG_ADDR_FSK_MODDFH, 1);
        freqDev = FskModDfH.bits.freqDev;
        freqDev <<= 8;
        freqDev |= radio.readReg(REG_ADDR_FSK_MODDFL, 1);
        fdev_hz = freqDev * PLL_STEP_HZ;

        FloraPreambleHi.octet = radio.readReg(REG_ADDR_FLORA_PREAMBLE_HI, 1);
        switch (FloraPreambleHi.bits.data_rate) {
            case 0:
                bps = 2.0e6;
                //mpFLRC.flrc.bitrateBandwidth = ??; // 2.6
                break;
            case 1:
                bps = 1.6e6;
                //mpFLRC.flrc.bitrateBandwidth = ??; // 2.08
                break;
            case 2:
                bps = 1.0e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_300_BW_1_2; // 1.3
                break;
            case 3:
                bps = 0.8e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_000_BW_1_2; // 1.04
                break;
            case 4:
                bps = 0.5e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_650_BW_0_6; // 0.65
                break;
            case 5:
                bps = 0.4e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_520_BW_0_6; // 0.52
                break;
            case 6:
                bps = 0.25e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_325_BW_0_3; // 0.325
                break;
            case 7:
                bps = 0.125e6;
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_260_BW_0_3; // 0.26
                break;
        }

        mi = (fdev_hz * 2.0) / bps;
        if (mi > 0.35) {
            mi -= 0.5;
            mi /= 0.25;
            mpBLE_GFSK.gfskBle.ModulationIndex = ((uint8_t)mi) + 1;
        } else
            mpBLE_GFSK.gfskBle.ModulationIndex = 0;

        RegRxBw.octet = radio.readReg(REG_ADDR_RXBW, 1);

        switch (RegRxBw.bits.bw) {
            case 0:
                if (FloraPreambleHi.bits.data_rate == 0)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_2_000_BW_2_4;
                if (FloraPreambleHi.bits.data_rate == 1)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_600_BW_2_4;
                if (FloraPreambleHi.bits.data_rate == 2)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_000_BW_2_4;
                if (FloraPreambleHi.bits.data_rate == 3)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_800_BW_2_4;
                break;
            case 1:
                if (FloraPreambleHi.bits.data_rate == 2)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_000_BW_1_2;
                if (FloraPreambleHi.bits.data_rate == 3)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_800_BW_1_2;
                if (FloraPreambleHi.bits.data_rate == 4)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_500_BW_1_2;
                if (FloraPreambleHi.bits.data_rate == 5)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_400_BW_1_2;
                break;
            case 2:
                if (FloraPreambleHi.bits.data_rate == 4)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_500_BW_0_6;
                if (FloraPreambleHi.bits.data_rate == 5)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_400_BW_0_6;
                if (FloraPreambleHi.bits.data_rate == 6)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_250_BW_0_6;
                break;
            case 3:
                if (FloraPreambleHi.bits.data_rate == 6)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_250_BW_0_3;
                if (FloraPreambleHi.bits.data_rate == 7)
                    mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_125_BW_0_3;
                break;
        }
        mpBLE_GFSK.gfskBle.bitrateBandwidth = reg8;
    }

    {
        FskCfg_t FskCfg;
        FskCfg.octet = radio.readReg(REG_ADDR_FSK_CFG, 1);
        mpBLE_GFSK.gfskBle.ModulationShaping = FskCfg.bits.gf_bt << 4;
        mpFLRC.flrc.ModulationShaping = mpBLE_GFSK.gfskBle.ModulationShaping;
    }

    {
        PktBitStreamCtrl_t PktBitStreamCtrl;
        PktBitStreamCtrl.octet = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);
        mpFLRC.flrc.CodingRate = PktBitStreamCtrl.octet & 0x06; // PktBitStreamCtrl.bits.flora_coding_rate 
    }

}

void Radio:: diox_top_half()
{
#if (MBED_MAJOR_VERSION < 6)
    irqAt = lpt.read_us();
#else
    irqAt = LowPowerClock::now();
#endif

    if (RadioEvents->DioPin_top_half)
        RadioEvents->DioPin_top_half();

    if (radio.chipMode == CHIPMODE_TX) {
        /* TxDone handling requires low latency */
        if (RadioEvents->TxDone_topHalf) {
            RadioEvents->TxDone_topHalf();
        }
    } else {
#ifdef RX_INDICATION
        RX_INDICATION = 0;
#endif
    }
}

void Radio::rxDone(uint8_t size, const pktStatus_t* pktStatus)
{
    float rssi, snr;

    if (pktStatus->ble_gfsk_flrc.sync.syncAddrsCode == 0) {
        int8_t s = pktStatus->lora.snr;
        rssi = -pktStatus->lora.rssiSync / 2.0;
        snr = s / 4.0;
    } else {
        rssi = -pktStatus->ble_gfsk_flrc.rssiSync / 2.0;
        snr = FLT_MIN;
    }

    RadioEvents->RxDone(size, rssi, snr);
}

void Radio::timeout_callback(bool tx)
{
    if (!tx) {
        if (RadioEvents->RxTimeout)
            RadioEvents->RxTimeout();
#ifdef RX_INDICATION
        RX_INDICATION = 0;
#endif
    } // else TODO tx timeout
}

void Radio::txDoneBottom()
{
    if (RadioEvents->TxDone_botHalf)
        RadioEvents->TxDone_botHalf();
}

void Radio::Init(const RadioEvents_t* e, unsigned spi_hz)
{
    uint64_t sa;

    radio.txDone = txDoneBottom;
    radio.rxDone = rxDone;
    radio.timeout = timeout_callback;
    radio.chipModeChange = chipModeChange;
    radio.diox_topHalf = diox_top_half;

    spi.frequency(spi_hz);
    readChip();

    radio.setRegulator(0);  // default to LDO

    sa = 0xc194c1;
    radio.setSyncAddr(1, sa);

    RadioEvents = e;
    lpt.start();

    fe_enable = true;

    radio.periodBase = 2;   // 1ms resolution
    nSymbs = 8;
}

float Radio::GetRssiInst()
{
    uint8_t buf[2];
    radio.xfer(OPCODE_GET_RSSIINST, 0, 2, buf);
    return buf[1] / -2.0;
}

int Radio::Send(uint8_t size, timestamp_t maxListenTime, timestamp_t channelFreeTime, int rssiThresh)
{
    uint8_t buf[8];
    uint8_t pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_LORA) {
        ppLORA.lora.PayloadLength = size;
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);
    } else if (pktType == PACKET_TYPE_GFSK) {
        ppGFSK.gfskFLRC.PayloadLength = size;
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
    }

    if (maxListenTime > 0) {
        int rssi;
        us_timestamp_t startAt, chFreeAt, now;
        radio.start_rx(-1);
    #if (MBED_MAJOR_VERSION < 6)
        startAt = lpt.read_us();
    #else
        startAt = LowPowerClock::now().time_since_epoch().count();
    #endif
Lstart:
        do {
    #if (MBED_MAJOR_VERSION < 6)
            now = lpt.read_us();
    #else
            now = LowPowerClock::now().time_since_epoch().count();
    #endif
            if ((now - startAt) > maxListenTime) {
                return -1;
            }
            radio.xfer(OPCODE_GET_RSSIINST, 0, 2, buf);
            rssi = buf[1] / -2;
        } while (rssi > rssiThresh);
    #if (MBED_MAJOR_VERSION < 6)
        chFreeAt = lpt.read_us();
    #else
        chFreeAt = LowPowerClock::now().time_since_epoch().count();
    #endif
        do {
    #if (MBED_MAJOR_VERSION < 6)
            now = lpt.read_us();
    #else
            now = LowPowerClock::now().time_since_epoch().count();
    #endif
            radio.xfer(OPCODE_GET_RSSIINST, 0, 2, buf);
            rssi = buf[1] / -2;
            if (rssi > rssiThresh) {
                goto Lstart;
            }
        } while ((now - chFreeAt) < channelFreeTime);
    }

    radio.start_tx(size, 4000);

    return 0;
}

void Radio::service()
{
    radio.service();
}

bool Radio::CheckRfFrequency(unsigned hz)
{
    return true;
}

void Radio::Sleep()
{
    radio.setSleep(true);
}

void Radio::SetPublicNetwork(bool en)
{
/*    uint16_t ppg;

    if (en)
        ppg = 0x3444;
    else
        ppg = 0x1424;

    radio.writeReg(REG_ADDR_LORA_SYNC, ppg, 2);
*/
}

uint32_t Radio::lora_toa_us( uint8_t pktLen )
{
    double bwKHz;
    LoRaPktPar0_t LoRaPktPar0;
    LoRaLrCtl_t LoRaLrCtl;
    LoRaPktPar1_t LoRaPktPar1;
    uint8_t LowDatarateOptimize;

    {
        LoRaPktPar1.octet = radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
        LowDatarateOptimize = LoRaPktPar1.bits.ppm_offset ? 1 : 0;
        ppLORA.lora.HeaderType = LoRaPktPar1.bits.implicit_header ? IMPLICIT_HEADER : EXPLICIT_HEADER;
        ppLORA.lora.InvertIQ = LoRaPktPar1.octet & LORA_IQ_STD; // LoRaPktPar1.bits.rxinvert_iq
        mpLORA.lora.codingRate = LoRaPktPar1.bits.coding_rate;
    }

    {
        LoRaLrCtl.octet = radio.readReg(REG_ADDR_LORA_LRCTL, 1);
        ppLORA.lora.crc = LoRaLrCtl.octet & 0x20; // LoRaLrCtl.bits.crc_en
    }

    {
        LoRaPreambleReg_t LoRaPreambleReg;
        LoRaPreambleReg.octet = radio.readReg(REG_ADDR_LORA_PREAMBLE, 1);
        ppLORA.lora.PreambleLength = LoRaPreambleReg.bits.preamble_symb1_nb * (1 << LoRaPreambleReg.bits.preamble_symb_nb_exp);
    }

    {
        LoRaPktPar0.octet = radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);
        switch (LoRaPktPar0.bits.modem_bw) {
            case 0: bwKHz = 50; break;
            case 1: bwKHz = 100; break;
            case 2: mpLORA.lora.bandwidth = LORA_BW_200; bwKHz = 200; break;
            case 3: mpLORA.lora.bandwidth = LORA_BW_400; bwKHz = 400; break;
            case 4: mpLORA.lora.bandwidth = LORA_BW_800; bwKHz = 800; break;
            case 5: mpLORA.lora.bandwidth = LORA_BW_1600; bwKHz = 1600; break;
            default: bwKHz = 0; break;
        }
        mpLORA.lora.spreadingFactor = LoRaPktPar0.bits.modem_sf << 4;
    }

    // Symbol rate : time for one symbol (secs)
    double rs = bwKHz / (1 << LoRaPktPar0.bits.modem_sf);
    double ts = 1 / rs;
    // time of preamble
    //
    double tPreamble = ( ppLORA.lora.PreambleLength + 4.25 ) * ts;
    // Symbol length of payload and time
    
    double tmp = ceil( ( 8 * pktLen - 4 * LoRaPktPar0.bits.modem_sf +
                         28 + 16 * LoRaLrCtl.bits.crc_en -
                         ( LoRaPktPar1.bits.implicit_header ? 20 : 0 ) ) /
                         ( double )( 4 * ( LoRaPktPar0.bits.modem_sf -
                         ( ( LowDatarateOptimize > 0 ) ? 2 : 0 ) ) ) ) *
                         ( LoRaPktPar1.bits.coding_rate + 4 );

    double nPayload = 8 + ( ( tmp > 0 ) ? tmp : 0 );
    double tPayload = nPayload * ts;
    // Time on air
    double tOnAir = tPreamble + tPayload;
    // return microseconds
    return floor( tOnAir * 1000 + 0.999 );
}

void Radio::GFSKModemConfig(unsigned bps, unsigned bw_hz, unsigned fdev_hz)
{
    uint8_t u8;
    float mi, Mbps = bps / 1000000.0;

    if (Mbps > 1.6) {
        /* 2.0Mbps */
        u8 = GFSK_BLE_BR_2_000_BW_2_4;
    } else if (Mbps > 1.0) {
        /* 1.6Mbps */
        u8 = GFSK_BLE_BR_1_600_BW_2_4;
    } else if (Mbps > 0.8) {
        /* 1.0Mbps */
        /*if (bwMHz > 1.2)
            u8 = GFSK_BLE_BR_1_000_BW_2_4;
        else*/
            u8 = GFSK_BLE_BR_1_000_BW_1_2;
    } else if (Mbps > 0.5) {
        /* 0.8Mbps */
        /*if (bwMHz > 1.2)
            u8 = GFSK_BLE_BR_0_800_BW_2_4;
        else*/
            u8 = GFSK_BLE_BR_0_800_BW_1_2;
    } else if (Mbps > 0.4) {
        /* 0.5Mbps */
        /*if (bwMHz > 0.6)
            u8 = GFSK_BLE_BR_0_500_BW_1_2;
        else*/
            u8 = GFSK_BLE_BR_0_500_BW_0_6;
    } else if (Mbps > 0.25) {
        /* 0.4Mbps */
        /*if (bwMHz > 0.6)
            u8 = GFSK_BLE_BR_0_400_BW_1_2;
        else*/
            u8 = GFSK_BLE_BR_0_400_BW_0_6;
    } else if (Mbps > 0.125) {
        /* 0.25Mbps */
        /*if (bwMHz > 0.3)
            u8 = GFSK_BLE_BR_0_250_BW_0_6;
        else*/
            u8 = GFSK_BLE_BR_0_250_BW_0_3;
    } else {
        /* 0.125Mbps */
        u8 = GFSK_BLE_BR_0_125_BW_0_3;
    }

    mpBLE_GFSK.gfskBle.bitrateBandwidth = u8;

    mpBLE_GFSK.gfskBle.ModulationShaping = BT_OFF;

    mi = (fdev_hz * 2.0) / bps;
    if (mi > 0.35) {
        mi -= 0.5;
        mi /= 0.25;
        mpBLE_GFSK.gfskBle.ModulationIndex = ((uint8_t)mi) + 1;
    } else
        mpBLE_GFSK.gfskBle.ModulationIndex = 0;

    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpBLE_GFSK.buf);
}

void Radio::GFSKPacketConfig(unsigned preambleLen, bool fixLen, bool crcOn)
{
    ppGFSK.gfskFLRC.PreambleLength = (preambleLen - 4) / 4;
    ppGFSK.gfskFLRC.PreambleLength <<= 4;
    ppGFSK.gfskFLRC.SyncWordLength = (3 - 1) << 1;  // 3 byte 0xc194c1
    ppGFSK.gfskFLRC.HeaderType = fixLen ? RADIO_PACKET_FIXED_LENGTH : RADIO_PACKET_VARIABLE_LENGTH;
    ppGFSK.gfskFLRC.CRCLength = crcOn ? RADIO_CRC_2_BYTES : RADIO_CRC_OFF;

    // TODO ppGFSK.gfskFLRC.PayloadLength = ;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
}

void Radio::SetLoRaSymbolTimeout(uint16_t symbs)
{
    nSymbs = symbs;
    rxTimeoutMs = nSymbs * (symbolPeriodUs / 1000.0);
}

void Radio::LoRaModemConfig(unsigned bwKHz, uint8_t sf, uint8_t cr)
{
    if (radio.getPacketType() != PACKET_TYPE_LORA)
        radio.setPacketType(PACKET_TYPE_LORA);

    if (bwKHz > 800)
        mpLORA.lora.bandwidth = LORA_BW_1600;
    else if (bwKHz > 400)
        mpLORA.lora.bandwidth = LORA_BW_800;
    else if (bwKHz > 200)
        mpLORA.lora.bandwidth = LORA_BW_400;
    else if (bwKHz > 100)
        mpLORA.lora.bandwidth = LORA_BW_200;
    else if (bwKHz > 50)
        mpLORA.lora.bandwidth = LORA_BW_100;
    else
        mpLORA.lora.bandwidth = LORA_BW_50;

    mpLORA.lora.codingRate = cr;

    mpLORA.lora.spreadingFactor = sf << 4;

    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpLORA.buf);

    symbolPeriodUs = (1 << sf) / (bwKHz / 1000.0);   // bw in MHz gives microseconds
    rxTimeoutMs = nSymbs * (symbolPeriodUs / 1000.0);
}

void Radio::LoRaPacketConfig(unsigned preambleLen, bool fixLen, bool crcOn, bool invIQ)
{
    if (radio.getPacketType() != PACKET_TYPE_LORA)
        radio.setPacketType(PACKET_TYPE_LORA);

    ppLORA.lora.PreambleLength = preambleLen;
    ppLORA.lora.HeaderType = fixLen ? IMPLICIT_HEADER : EXPLICIT_HEADER;
    ppLORA.lora.crc = crcOn ? LORA_CRC_ENABLE : LORA_CRC_DISABLE;
    ppLORA.lora.InvertIQ = invIQ ? LORA_IQ_INVERTED : LORA_IQ_STD;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);
}

void Radio::SetChannel(unsigned hz)
{
    radio.setMHz(hz / 1000000.0);
}

uint32_t Radio::Random(void)
{
    uint8_t buf[2];
    uint32_t ret = 0;
    unsigned n;

    radio.start_rx(-1);

    for (n = 0; n < 8; n++) {
        uint32_t r, s;
        wait_us(5000);
        radio.xfer(OPCODE_GET_RSSIINST, 0, 2, buf);
        r = buf[1];
        s = n * 4;
        r <<= s;
        ret ^= r;
    }

    radio.setStandby(STDBY_RC);

    return ret;
}

void Radio::Rx(unsigned timeout)
{
#ifdef RX_INDICATION
    RX_INDICATION = 1;
#endif
    if (timeout == 0)
        radio.start_rx(0);  // continuous rx
    else {
        radio.start_rx(rxTimeoutMs);
    }
}

void Radio::Standby()
{
    radio.setStandby(STDBY_RC);
}

#define TX_PWR_OFFSET           18
void Radio::set_tx_dbm(int8_t dbm)
{
    if (dbm == PA_OFF_DBM) {
        /* TODO: shut off PA */
        radio.set_tx_dbm(0);
    } else {
        /* power range -18dBm to +13dBm */
        radio.set_tx_dbm(dbm + TX_PWR_OFFSET);
    }
}

void Radio::SetTxContinuousWave(unsigned hz, int8_t dbm, unsigned timeout_us)
{
    SetChannel(hz);
    radio.set_tx_dbm(dbm);
    radio.xfer(OPCODE_SET_TX_CARRIER, 0, 0, NULL);
}

void Radio::SetRxMaxPayloadLength(uint8_t max)
{
    uint8_t pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_GFSK)
        ppGFSK.gfskFLRC.PayloadLength = max;
    else if (pktType == PACKET_TYPE_LORA)
        ppLORA.lora.PayloadLength = max;
}

#endif /* ..SX126x_H */