wayne roberts / Mbed OS utility_sx12xx

radio_sx128x.cpp

Committer:
Wayne Roberts
Date:
2018-11-01
Revision:
3:56fc764dee0a
Parent:
2:ea9245bb1c53
Child:
4:fa31fdf4ec8d

File content as of revision 3:56fc764dee0a:

#include "radio.h"
#ifdef SX128x_H 

#include <float.h>

#ifdef TARGET_FF_ARDUINO    /* pins of SX126xDVK1xAS board */
    #define NRST_PIN        A0
    SPI 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 */

const char* const Radio::chipNum_str = "SX1280";

uint8_t Radio::tx_param_buf[2];

ModulationParams_t Radio::Radio::mpFLRC, Radio::Radio::mpBLE_GFSK, Radio::mpLORA;
PacketParams_t Radio::ppGFSK, Radio::ppFLRC, Radio::ppLORA, Radio::ppBLE;

const RadioEvents_t* Radio::RadioEvents;
LowPowerTimer Radio::lpt;
uint8_t Radio::pktType;
uint16_t Radio::ppg;

const char* const Radio::pktType_strs[] = {
    "GFSK   ",
    "LORA   ",
    "RANGING",
    "FLRC   ",
    "BLE    ",
    NULL
};

unsigned Radio::pktType_read(bool fw)
{
    return radio.getPacketType();
}

menuMode_e Radio::pktType_write(unsigned idx)
{
    radio.setPacketType(idx);
    return MENUMODE_REINIT_MENU;
}

const char* Radio::tx_ramp_strs[] = {
    "2 ", // 0
    "4 ", // 1
    "6 ", // 2
    "8 ", // 3
    "10", // 4
    "12", // 5
    "16", // 6
    "20",  // 7
    NULL
};

unsigned Radio::tx_ramp_read(bool fw)
{
    PaPwrCtrl_t PaPwrCtrl;
    PaPwrCtrl.octet = radio.readReg(REG_ADDR_PA_PWR_CTRL, 1);

    switch (PaPwrCtrl.bits.ramp_time) {
        case 0: tx_param_buf[1] = RADIO_RAMP_02_US; break;
        case 1: tx_param_buf[1] = RADIO_RAMP_04_US; break;
        case 2: tx_param_buf[1] = RADIO_RAMP_06_US; break;
        case 3: tx_param_buf[1] = RADIO_RAMP_08_US; break;
        case 4: tx_param_buf[1] = RADIO_RAMP_10_US; break;
        case 5: tx_param_buf[1] = RADIO_RAMP_12_US; break;
        case 6: tx_param_buf[1] = RADIO_RAMP_16_US; break;
        case 7: tx_param_buf[1] = RADIO_RAMP_20_US; break;
    }

    return PaPwrCtrl.bits.ramp_time;
}

menuMode_e Radio::tx_ramp_write(unsigned val)
{
    switch (val) {
        case 0: tx_param_buf[1] = RADIO_RAMP_02_US; break;
        case 1: tx_param_buf[1] = RADIO_RAMP_04_US; break;
        case 2: tx_param_buf[1] = RADIO_RAMP_06_US; break;
        case 3: tx_param_buf[1] = RADIO_RAMP_08_US; break;
        case 4: tx_param_buf[1] = RADIO_RAMP_10_US; break;
        case 5: tx_param_buf[1] = RADIO_RAMP_12_US; break;
        case 6: tx_param_buf[1] = RADIO_RAMP_16_US; break;
        case 7: tx_param_buf[1] = RADIO_RAMP_20_US; break;
    }

    radio.xfer(OPCODE_SET_TX_PARAMS, 2, 0, tx_param_buf);

    return MENUMODE_REDRAW;
}

#define TX_PWR_OFFSET           18

void Radio::tx_dbm_print()
{
    PaPwrCtrl_t PaPwrCtrl;

    PaPwrCtrl.octet = radio.readReg(REG_ADDR_PA_PWR_CTRL, 1);
    pc.printf("%d", PaPwrCtrl.bits.tx_pwr - TX_PWR_OFFSET);

    tx_param_buf[0] = PaPwrCtrl.bits.tx_pwr;
}

bool Radio::tx_dbm_write(const char* str)
{
    int dbm;
    sscanf(str, "%d", &dbm);

    tx_param_buf[0] = dbm + TX_PWR_OFFSET;
    radio.xfer(OPCODE_SET_TX_PARAMS, 2, 0, tx_param_buf);
    return false;
}

const uint8_t ramp_us[] = {
    2,  // 0
    4,  // 1
    6,  // 2
    8,  // 3
    10, // 4
    12, // 5
    16, // 6
    20  // 7
};

const char* const Radio::opmode_status_strs[] = {
    "<0>       ", // 0
    "<1>       ", // 1
    "STDBY_RC  ", // 2
    "STDBY_XOSC", // 3
    "FS        ", // 4
    "RX        ", // 5
    "TX        ", // 6
    "<7>       ", // 7
    NULL
};

const char* const Radio::opmode_select_strs[] = {
    "SLEEP     ", // 0
    "STDBY_RC  ", // 1
    "STDBY_XOSC", // 2
    "FS        ", // 3
    "RX        ", // 4
    "TX        ", // 5
    NULL
};

unsigned Radio::opmode_read(bool forWriting)
{
    status_t status;
    status.octet = radio.xfer(OPCODE_GET_STATUS, 0, 0, NULL);

    if (forWriting) {
        /* translate opmode_status_strs to opmode_select_strs */
        switch (status.bits.chipMode) {
            case 2: return 1;   // STDBY_RC
            case 3: return 2; //STDBY_XOSC
            case 4: return 3; //FS
            case 5: return 4; // RX
            case 6: return 5; // TX
            default: return 0;
        }
    } else
        return status.bits.chipMode;
}

menuMode_e Radio::opmode_write(unsigned sel)
{
    switch (sel) {
        case 0: // SLEEP
            radio.setSleep(true);
            break;
        case 1: // STDBY_RC
            radio.setStandby(STDBY_RC);
            break;
        case 2:  // STDBY_XOSC
            radio.setStandby(STDBY_XOSC);
            break; case 3:  // FS
            radio.setFS();
            break;
        case 4:  // RX
            radio.start_rx(0);
            break;
        case 5:  // TX
            {
                uint8_t buf[3];
                buf[0] = radio.periodBase;
                buf[0] = 0;
                buf[1] = 0;
                radio.xfer(OPCODE_SET_TX, 3, 0, buf);
            }
            break;
    } // ..switch (menuState.sel_idx)

    return MENUMODE_REDRAW;
}

uint8_t Radio::get_payload_length()
{
    uint8_t reg8;

    pktType = radio.getPacketType();

    switch (pktType) {
        case PACKET_TYPE_FLRC:
        case PACKET_TYPE_GFSK:
            reg8 = radio.readReg(REG_ADDR_PAYLOAD_LEN, 1);
            ppGFSK.gfskFLRC.PayloadLength = reg8;
            ppFLRC.gfskFLRC.PayloadLength = reg8;
            return ppFLRC.gfskFLRC.PayloadLength;
        case PACKET_TYPE_RANGING:
        case PACKET_TYPE_LORA:
            ppLORA.lora.PayloadLength = radio.readReg(REG_ADDR_LORA_TX_PAYLOAD_LENGTH, 1);
            return ppLORA.lora.PayloadLength;
        case PACKET_TYPE_BLE:
            return 0;   // TODO BLE
    }

    return 0;
}

void Radio::set_payload_length(uint8_t len)
{
    pktType = radio.getPacketType();

    switch (pktType) {
        case PACKET_TYPE_FLRC:
            ppFLRC.gfskFLRC.PayloadLength = len;
            ppGFSK.gfskFLRC.PayloadLength = len;
            radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppFLRC.buf);
            break;
        case PACKET_TYPE_GFSK:
            ppFLRC.gfskFLRC.PayloadLength = len;
            ppGFSK.gfskFLRC.PayloadLength = len;
            radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
            break;
        case PACKET_TYPE_RANGING:
        case PACKET_TYPE_LORA:
            ppLORA.lora.PayloadLength = len;
            radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);
            break;
        case PACKET_TYPE_BLE:
            // TODO BLE
            break;
    }
}

void Radio::tx_payload_length_print()
{
    pc.printf("%u", get_payload_length());
}

bool Radio::tx_payload_length_write(const char* txt)
{
    unsigned len;

    sscanf(txt, "%u", &len);

    set_payload_length(len);

    return false;
}

void Radio::hw_reset()
{
    radio.hw_reset();

    manualRngDelay = false;
}

void Radio::clearIrqFlags()
{
    uint8_t buf[2];
    buf[0] = 0xff;
    buf[1] = 0xff;
    radio.xfer(OPCODE_CLEAR_IRQ_STATUS, 2, 0, buf);
}

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.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.bits.rxinvert_iq ? LORA_IQ_INVERTED : LORA_IQ_STD;
        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);
        //printf("freqDev %x, %x\r\n", freqDev, freqDev);
        fdev_hz = freqDev * PLL_STEP_HZ;
        //printf("fdev hz:%f\r\n", fdev_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);

        //printf("rx ");
        switch (RegRxBw.bits.bw) {
            case 0:
                //printf("2.4");
                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:
                //printf("1.2");
                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:
                //printf("0.6");
                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:
                //printf("0.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;
        }
        //printf("MHz bw:%u\r\n", RegRxBw.bits.bw);
        mpBLE_GFSK.gfskBle.bitrateBandwidth = reg8;
    }

    {
        FskCfg_t FskCfg;
        FskCfg.octet = radio.readReg(REG_ADDR_FSK_CFG, 1);
        //printf("gf_bt:%u\r\n", FskCfg.bits.gf_bt);
        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::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::txDoneBottom()
{
    if (RadioEvents->TxDone_botHalf)
        RadioEvents->TxDone_botHalf();
}

void Radio::cadDone(bool det)
{
    log_printf("cadDone ");
    if (det)
        pc.printf("CadDetected");

    pc.printf("\r\n");
}

void Radio::boardInit(const RadioEvents_t* e)
{
    hw_reset();

    radio.txDone = txDoneBottom;
    radio.rxDone = rxDone;
    radio.cadDone = cadDone;

    radio.chipModeChange = chipModeChange;

    readChip();

    RadioEvents = e;

    fe_enable = true;

    lpt.start();
}

void Radio::tx_carrier()
{
    radio.xfer(OPCODE_SET_TX_CARRIER, 0, 0, NULL);

    radio.chipMode = CHIPMODE_TX;
    chipModeChange();
}

void Radio::tx_preamble()
{
    radio.xfer(OPCODE_SET_TX_PREAMBLE, 0, 0, NULL);

    radio.chipMode = CHIPMODE_TX;
    chipModeChange();
}

void Radio::rngTx()
{
    IrqFlags_t irqEnable;
    uint8_t buf[8];

    if (!manualRngDelay)
        rngUpdateDelayCal();

    irqEnable.word = 0;
    irqEnable.bits.TxDone = 1;
    irqEnable.bits.RxTxTimeout = 1;
    irqEnable.bits.RangingMasterTimeout = 1;
    irqEnable.bits.RangingMasterResultValid = 1;
    irqEnable.bits.RangingMasterRequestValid = 1;

    buf[0] = irqEnable.word >> 8;    // enable bits
    buf[1] = irqEnable.word; // enable bits

    irqEnable.bits.RangingMasterTimeout = 0;
    irqEnable.bits.RangingMasterResultValid = 0;
    irqEnable.bits.RangingMasterRequestValid = 0;
    buf[2] = irqEnable.word >> 8;     // dio1
    buf[3] = irqEnable.word;  // dio1

    buf[4] = 0; // dio2
    buf[5] = 0; // dio2
    buf[6] = 0; // dio3
    buf[7] = 0; // dio3
    radio.xfer(OPCODE_SET_DIO_IRQ_PARAMS, 8, 0, buf);

    log_printf("rngTx\r\n");

    buf[0] = radio.periodBase;
    /* no timeout */
    buf[1] = 0;
    buf[2] = 0;
    radio.xfer(OPCODE_SET_TX, 3, 0, buf);

    radio.chipMode = CHIPMODE_TX;
    chipModeChange();
}

void Radio::txPkt()
{
    uint8_t txlen = 0;

    pktType = radio.getPacketType();

    switch (pktType) {
        case PACKET_TYPE_FLRC:
        case PACKET_TYPE_GFSK:
            txlen = radio.readReg(REG_ADDR_PAYLOAD_LEN, 1);
            break;
        case PACKET_TYPE_BLE:
            return; // TODO BLE
        case PACKET_TYPE_RANGING:
            rngTx();
            return;
        case PACKET_TYPE_LORA:
            txlen = radio.readReg(REG_ADDR_LORA_TX_PAYLOAD_LENGTH, 1);
            break;
    }
    log_printf("txPkt%u\r\n", txlen);

    radio.setBufferBase(0, 0);

    radio.start_tx(txlen, 0);

}

#define REGBW_2_4MHZ    0
#define REGBW_1_2MHZ    1
#define REGBW_0_6MHZ    2
#define REGBW_0_3MHZ    3

unsigned Radio::gfsk_flrc_bpsbw_read(bool fw)
{
    unsigned n = UINT_MAX;
    RegRxBw_t RegRxBw;
    FloraPreambleHi_t FloraPreambleHi;
    pktType = radio.getPacketType();

    FloraPreambleHi.octet = radio.readReg(REG_ADDR_FLORA_PREAMBLE_HI, 1);

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

    if (pktType == PACKET_TYPE_GFSK) {
        switch (FloraPreambleHi.bits.data_rate) {
            case 0:
                n = 0;  // 2Mbps, 2.4MHz
                break;
            case 1:
                n = 1;  // 1.6Mbps, 2.4MHz
                break;
            case 2:
                if (RegRxBw.bits.bw == REGBW_2_4MHZ)
                    n = 2;
                else if (RegRxBw.bits.bw == REGBW_1_2MHZ)
                    n = 3;
                break;
            case 3:
                if (RegRxBw.bits.bw == REGBW_2_4MHZ) // 0.8Mbps  2.4   2.4MHz
                    n = 4;
                else if (RegRxBw.bits.bw == REGBW_1_2MHZ) // 0.8Mbps  1.2MHz
                    n = 5;
                break;
            case 4:
                if (RegRxBw.bits.bw == REGBW_1_2MHZ)    // 0.5Mbps  1.2MHz
                    n = 6;
                else if (RegRxBw.bits.bw == REGBW_0_6MHZ)   // 0.5Mbps  0.6MHz
                    n = 7;
                break;
            case 5:
                if (RegRxBw.bits.bw == REGBW_1_2MHZ)    // 0.4Mbps  1.2MHz
                    n = 8;
                else if (RegRxBw.bits.bw == REGBW_0_6MHZ)   // 0.4Mbps  0.6MHz
                    n = 9;
                break;
            case 6:
                if (RegRxBw.bits.bw == REGBW_0_6MHZ)    // 0.25Mbps  0.6MHz
                    n = 10;
                else if (RegRxBw.bits.bw == REGBW_0_3MHZ)   // 0.25Mbps  0.3MHz
                    n = 11;
                break;
            case 7:
                n = 12; // 0.125Mbps, assume bw=0.3MHz
                break;
        } // ..switch (FloraPreambleHi.bits.data_rate)
    } else if (pktType == PACKET_TYPE_FLRC) {
        n = FloraPreambleHi.bits.data_rate;
        // datarate, bits 5,6,7..    bw bits 0,1,2
        switch (FloraPreambleHi.bits.data_rate) {
            case 0:
                break;
            case 1:
                break;
            case 2:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_300_BW_1_2;
                break;
            case 3:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_000_BW_1_2;
                break;
            case 4:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_650_BW_0_6;
                break;
            case 5:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_520_BW_0_6;
                break;
            case 6:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_325_BW_0_3;
                break;
            case 7:
                mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_260_BW_0_3;
                break;
        } // ..switch (FloraPreambleHi.bits.data_rate)
    }

    return n;
}

menuMode_e Radio::gfsk_flrc_bpsbw_write(unsigned sidx)
{
    pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_GFSK) {
        switch (sidx) {
            case 0: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_2_000_BW_2_4; break;
            case 1: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_600_BW_2_4; break;
            case 2: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_000_BW_2_4; break;
            case 3: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_1_000_BW_1_2; break;
            case 4: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_800_BW_2_4; break;
            case 5: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_800_BW_1_2; break;
            case 6: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_500_BW_1_2; break;
            case 7: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_500_BW_0_6; break;
            case 8: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_400_BW_1_2; break;
            case 9: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_400_BW_0_6; break;
            case 10: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_250_BW_0_6; break;
            case 11: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_250_BW_0_3; break;
            case 12: mpBLE_GFSK.gfskBle.bitrateBandwidth = GFSK_BLE_BR_0_125_BW_0_3; break;
        }
        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpBLE_GFSK.buf);
    } else if (pktType == PACKET_TYPE_FLRC) {
        switch (sidx) {
            case 2: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_300_BW_1_2; break;
            case 3: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_1_000_BW_1_2; break;
            case 4: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_650_BW_0_6; break;
            case 5: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_520_BW_0_6; break;
            case 6: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_325_BW_0_3; break;
            case 7: mpFLRC.flrc.bitrateBandwidth = FLRC_BR_0_260_BW_0_3; break;
            default:
                return MENUMODE_REDRAW;
        }
        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpFLRC.buf);
    }

    return MENUMODE_REDRAW;
}

static const char* const gfsk_bpsbw[] = {
    "  2.0Mbps 2.4MHz", //0 GFSK_BLE_BR_2_000_BW_2_4    0x04 // Mbps:2      bw:2.4MHz
    "  1.6Mbps 2.4MHz", //1 GFSK_BLE_BR_1_600_BW_2_4    0x28 // Mbps:1.6    bw:2.4MHz
    "  1.0Mbps 2.4MHz", //2 GFSK_BLE_BR_1_000_BW_2_4    0x4C // Mbps:1      bw:2.4MHz
    "  1.0Mbps 1.2MHz", //3 GFSK_BLE_BR_1_000_BW_1_2    0x45 // Mbps:1      bw:1.2MHz
    "  0.8Mbps 2.4MHz", //4 GFSK_BLE_BR_0_800_BW_2_4    0x70 // Mbps:0.8    bw:2.4MHz
    "  0.8Mbps 1.2MHz", //5 GFSK_BLE_BR_0_800_BW_1_2    0x69 // Mbps:0.8    bw:1.2MHz
    "  0.5Mbps 1.2MHz", //6 GFSK_BLE_BR_0_500_BW_1_2    0x8D // Mbps:0.5    bw:1.2MHz
    "  0.5Mbps 0.6MHz", //7 GFSK_BLE_BR_0_500_BW_0_6    0x86 // Mbps:0.5    bw:0.6MHz
    "  0.4Mbps 1.2MHz", //8 GFSK_BLE_BR_0_400_BW_1_2    0xB1 // Mbps:0.4    bw:1.2MHz
    "  0.4Mbps 0.6MHz", //9 GFSK_BLE_BR_0_400_BW_0_6    0xAA // Mbps:0.4    bw:0.6MHz
    " 0.25Mbps 0.6MHz", //10 GFSK_BLE_BR_0_250_BW_0_6    0xCE // Mbps:0.25   bw:0.6MHz
    " 0.25Mbps 0.3MHz", //11 GFSK_BLE_BR_0_250_BW_0_3    0xC7 // Mbps:0.25   bw:0.3MHz
    "0.125Mbps 0.3MHz", //12 GFSK_BLE_BR_0_125_BW_0_3    0xEF // Mbps:0.125  bw:0.3MHz
//   01234567890123456
    NULL
};

const dropdown_item_t Radio::gfsk_bitrate_item = { _ITEM_DROPDOWN, gfsk_bpsbw, gfsk_bpsbw, gfsk_flrc_bpsbw_read, gfsk_flrc_bpsbw_write};

void Radio::modindex_print()
{
    float mi, Mbps, fdev_hz;
    unsigned bps, freqDev;
    FskModDfH_t FskModDfH;
    FloraPreambleHi_t FloraPreambleHi;

    FloraPreambleHi.octet = radio.readReg(REG_ADDR_FLORA_PREAMBLE_HI, 1);
    switch (FloraPreambleHi.bits.data_rate) {
        case 0: Mbps = 2.0; break;
        case 1: Mbps = 1.6; break;
        case 2: Mbps = 1.0; break;
        case 3: Mbps = 0.8; break;
        case 4: Mbps = 0.5; break;
        case 5: Mbps = 0.4; break;
        case 6: Mbps = 0.25; break;
        case 7: Mbps = 0.125; break;
    }

    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;
    bps = Mbps * 1e6;
    mi = (fdev_hz * 2.0) / bps;

    pc.printf("%.2f", mi);
}

bool Radio::modindex_write(const char* valStr)
{
    float f;

    if (sscanf(valStr, "%f", &f) == 1) {
        log_printf("scanned %f from \"%s\"\r\n", f);
        if (f > 0.35) {
            f -= 0.5;
            f /= 0.25;
            log_printf("set modindex:%f\r\n", f);
            mpBLE_GFSK.gfskBle.ModulationIndex = ((uint8_t)f) + 1;
            log_printf("to set %02x\r\n", mpBLE_GFSK.gfskBle.ModulationIndex);
        } else
            mpBLE_GFSK.gfskBle.ModulationIndex = 0;

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

const value_item_t Radio::gfsk_modindex_item = { _ITEM_VALUE, 7, modindex_print, modindex_write };

static const char* const gfsk_flrc_bts[] = {
    "off",
    "1.0",
    "0.5",
    "0.3",
    NULL
};

unsigned Radio::gfsk_flrc_bt_read(bool fw)
{
    FskCfg_t FskCfg;
    FskCfg.octet = radio.readReg(REG_ADDR_FSK_CFG, 1);
    return FskCfg.bits.gf_bt;
}

menuMode_e Radio::gfsk_flrc_bt_write(unsigned sidx)
{
    mpBLE_GFSK.gfskBle.ModulationShaping = sidx << 4;
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpBLE_GFSK.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_flrc_bt_item = { _ITEM_DROPDOWN, gfsk_flrc_bts, gfsk_flrc_bts, gfsk_flrc_bt_read, gfsk_flrc_bt_write};

static const char* const gfsk_flrc_pblLens[] = {
    "4",
    "8",
    "12",
    "16",
    "20",
    "24",
    "28",
    "32",
    NULL,
};

unsigned Radio::gfsk_flrc_pl_read(bool fw)
{
    PktCtrl1_t PktCtrl1;
    PktCtrl1.octet = radio.readReg(REG_ADDR_PKTCTRL1, 1);
    ppFLRC.gfskFLRC.PreambleLength = PktCtrl1.octet & 0x70;
    ppGFSK.gfskFLRC.PreambleLength = ppFLRC.gfskFLRC.PreambleLength;
    return PktCtrl1.gfsk.preamble_len;
}

menuMode_e Radio::gfsk_flrc_pl_write(unsigned sidx)
{
    ppFLRC.gfskFLRC.PreambleLength = sidx << 4;
    ppGFSK.gfskFLRC.PreambleLength = ppFLRC.gfskFLRC.PreambleLength;
    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_flrc_preamble_item = { _ITEM_DROPDOWN, gfsk_flrc_pblLens, gfsk_flrc_pblLens, gfsk_flrc_pl_read, gfsk_flrc_pl_write};

static const char* const gfsk_syncLens[] = {
    "1", // 0
    "2", // 1
    "3", // 2
    "4", // 3
    "5", // 4
    NULL,
};

unsigned Radio::gfsk_synclen_read(bool fw)
{
    PktCtrl1_t PktCtrl1;
    PktCtrl1.octet = radio.readReg(REG_ADDR_PKTCTRL1, 1);

    ppGFSK.gfskFLRC.SyncWordLength = PktCtrl1.octet & 0x0e;
    ppFLRC.gfskFLRC.SyncWordLength = PktCtrl1.octet & 0x06;

    return PktCtrl1.gfsk.sync_adrs_len;
}

menuMode_e Radio::gfsk_synclen_write(unsigned sidx)
{
    ppGFSK.gfskFLRC.SyncWordLength = sidx << 1;
    //log_printf("SWL %u %02x\r\n", sidx, ppGFSK.gfskFLRC.SyncWordLength);
    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
    //return MENUMODE_NONE;
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_synclen_item = { _ITEM_DROPDOWN, gfsk_syncLens, gfsk_syncLens, gfsk_synclen_read, gfsk_synclen_write};

bool Radio::gfsk_flrc_fixvar_read()
{
    PktCtrl0_t PktCtrl0;
    PktCtrl0.octet = radio.readReg(REG_ADDR_PKTCTRL0, 1);

    ppGFSK.gfskFLRC.HeaderType = PktCtrl0.octet & 0x20;
    ppFLRC.gfskFLRC.HeaderType = PktCtrl0.octet & 0x20;

    return PktCtrl0.bits.pkt_len_format;
}

bool Radio::gfsk_flrc_fixvar_push()
{
    PacketParams_t* pp;
    pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_FLRC)
        pp = &ppFLRC;
    else if (pktType == PACKET_TYPE_GFSK)
        pp = &ppGFSK;
    else
        return false;

    if (pp->gfskFLRC.HeaderType == RADIO_PACKET_VARIABLE_LENGTH)
        pp->gfskFLRC.HeaderType = RADIO_PACKET_FIXED_LENGTH;
    else
        pp->gfskFLRC.HeaderType = RADIO_PACKET_VARIABLE_LENGTH;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, pp->buf);

    return pp->gfskFLRC.HeaderType == RADIO_PACKET_VARIABLE_LENGTH;
}

const toggle_item_t Radio::gfsk_flrc_fixvar_item = { _ITEM_TOGGLE,
    "fixed   ",
    "variable",
    gfsk_flrc_fixvar_read, gfsk_flrc_fixvar_push
};

static const char* const gfsk_flrc_crclens[] = {
    "0 off",
    "1 byte",
    "2 bytes",
    NULL,
};

unsigned Radio::gfsk_flrc_crclen_read(bool fw)
{
    PktBitStreamCtrl_t PktBitStreamCtrl;
    PktBitStreamCtrl.octet = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);
    ppGFSK.gfskFLRC.CRCLength = PktBitStreamCtrl.octet & 0x30;
    return PktBitStreamCtrl.bits.crc_mode;
}

menuMode_e Radio::gfsk_flrc_crclen_write(unsigned sidx)
{
    pktType = radio.getPacketType();

    ppGFSK.gfskFLRC.CRCLength = (sidx & 3) << 4;
    ppFLRC.gfskFLRC.CRCLength = (sidx & 3) << 4;

    if (pktType == PACKET_TYPE_GFSK)
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
    else if (pktType == PACKET_TYPE_FLRC)
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppFLRC.buf);

    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_flrc_crclen_item = { _ITEM_DROPDOWN, gfsk_flrc_crclens, gfsk_flrc_crclens, gfsk_flrc_crclen_read, gfsk_flrc_crclen_write};

bool Radio::gfsk_flrc_whit_read()
{
    PktBitStreamCtrl_t PktBitStreamCtrl;
    PktBitStreamCtrl.octet = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);
    ppGFSK.gfskFLRC.Whitening = PktBitStreamCtrl.octet & 0x08;
    ppFLRC.gfskFLRC.Whitening = PktBitStreamCtrl.octet & 0x08;
    return PktBitStreamCtrl.bits.whit_disable;
}

bool Radio::gfsk_flrc_whit_push()
{
    pktType = radio.getPacketType();

    if (ppGFSK.gfskFLRC.Whitening == WHITENING_DISABLE)
        ppGFSK.gfskFLRC.Whitening = WHITENING_ENABLE;
    else
        ppGFSK.gfskFLRC.Whitening = WHITENING_DISABLE;

    ppFLRC.gfskFLRC.Whitening = ppGFSK.gfskFLRC.Whitening;

    if (pktType == PACKET_TYPE_GFSK)
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppGFSK.buf);
    else if (pktType == PACKET_TYPE_FLRC)
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppFLRC.buf);

    return ppGFSK.gfskFLRC.Whitening == WHITENING_DISABLE;
}

const toggle_item_t Radio::gfsk_flrc_whit_item = { _ITEM_TOGGLE,
    "ENABLE ",
    "DISABLE",
    gfsk_flrc_whit_read, gfsk_flrc_whit_push
};

void Radio::gfsk_flrc_crcinit_print()
{
    unsigned val = radio.readReg(0x9c8, 2);
    pc.printf("0x%04x", val);
}

bool Radio::gfsk_flrc_crcinit_write(const char* txt)
{
    unsigned val;
    sscanf(txt, "%x", &val);
    radio.writeReg(0x9c8, val, 2);
    return false;
}

const value_item_t Radio::gfsk_flrc_crcinit_item = { _ITEM_VALUE, 7, gfsk_flrc_crcinit_print, gfsk_flrc_crcinit_write };

void Radio::gfsk_flrc_crcpoly_print(void)
{
    unsigned val = radio.readReg(0x9c6, 2);
    pc.printf("0x%04x", val);
}

bool Radio::gfsk_flrc_crcpoly_write(const char* txt)
{
    unsigned val;
    sscanf(txt, "%x", &val);
    radio.writeReg(0x9c6, val, 2);
    return false;
}

const value_item_t Radio::gfsk_flrc_crcpoly_item = { _ITEM_VALUE, 7, gfsk_flrc_crcpoly_print, gfsk_flrc_crcpoly_write };

bool Radio::gfsk_flrc_sync1en_read()
{
    PktSyncAdrs_t PktSyncAdrs;
    PktSyncAdrs.octet = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_CTRL, 1);

    ppGFSK.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;
    ppFLRC.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;

    return PktSyncAdrs.gfskflrc.sync_addr_mask & 1;
}

bool Radio::gfsk_flrc_sync2en_read()
{
    PktSyncAdrs_t PktSyncAdrs;
    PktSyncAdrs.octet = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_CTRL, 1);

    ppGFSK.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;
    ppFLRC.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x20;
}

bool Radio::gfsk_flrc_sync3en_read()
{
    PktSyncAdrs_t PktSyncAdrs;
    PktSyncAdrs.octet = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_CTRL, 1);

    ppGFSK.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;
    ppFLRC.gfskFLRC.SyncWordMatch = PktSyncAdrs.octet & 0x70;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x40;
    //return PktSyncAdrs.gfskflrc.sync_addr_mask & 4;
}

bool Radio::gfsk_flrc_sync1en_push()
{
    if (ppGFSK.gfskFLRC.SyncWordMatch & 0x10)
        ppGFSK.gfskFLRC.SyncWordMatch &= ~0x10;
    else
        ppGFSK.gfskFLRC.SyncWordMatch |= 0x10;

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

    ppFLRC.gfskFLRC.SyncWordMatch = ppGFSK.gfskFLRC.SyncWordMatch;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x10;
}

bool Radio::gfsk_flrc_sync2en_push()
{
    if (ppGFSK.gfskFLRC.SyncWordMatch & 0x20)
        ppGFSK.gfskFLRC.SyncWordMatch &= ~0x20;
    else
        ppGFSK.gfskFLRC.SyncWordMatch |= 0x20;

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

    ppFLRC.gfskFLRC.SyncWordMatch = ppGFSK.gfskFLRC.SyncWordMatch;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x20;
}

bool Radio::gfsk_flrc_sync3en_push()
{
    if (ppGFSK.gfskFLRC.SyncWordMatch & 0x40)
        ppGFSK.gfskFLRC.SyncWordMatch &= ~0x40;
    else
        ppGFSK.gfskFLRC.SyncWordMatch |= 0x40;

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

    ppFLRC.gfskFLRC.SyncWordMatch = ppGFSK.gfskFLRC.SyncWordMatch;

    return ppGFSK.gfskFLRC.SyncWordMatch & 0x40;
}

const toggle_item_t Radio::gfsk_flrc_sync1en_item = { _ITEM_TOGGLE, "off:", " ON:", gfsk_flrc_sync1en_read, gfsk_flrc_sync1en_push};
const toggle_item_t Radio::gfsk_flrc_sync2en_item = { _ITEM_TOGGLE, "off:", " ON:", gfsk_flrc_sync2en_read, gfsk_flrc_sync2en_push};
const toggle_item_t Radio::gfsk_flrc_sync3en_item = { _ITEM_TOGGLE, "off:", " ON:", gfsk_flrc_sync3en_read, gfsk_flrc_sync3en_push};

void Radio::gfsk_flrc_sync1_print(void)
{
    uint64_t val;
    uint32_t val32 = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_1+1, 4);
    uint32_t upper = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_1, 1);
    val = upper;
    val <<= 32;
    val |= val32;
    pc.printf("%llx", val);
}

void Radio::gfsk_flrc_sync2_print(void)
{
    uint64_t val;
    uint32_t val32 = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_2+1, 4);
    uint32_t upper = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_2, 1);
    val = upper;
    val <<= 32;
    val |= val32;
    pc.printf("%llx", val);
}

void Radio::gfsk_flrc_sync3_print(void)
{
    uint64_t val;
    uint32_t val32 = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_3+1, 4);
    uint32_t upper = radio.readReg(REG_ADDR_PKT_SYNC_ADRS_3, 1);
    val = upper;
    val <<= 32;
    val |= val32;
    pc.printf("%llx", val);
}

bool Radio::gfsk_flrc_sync1_write(const char* txt)
{
    uint32_t val32, upper;
    uint64_t val;
    sscanf(txt, "%llx", &val);

    val32 = val;
    val >>= 32;
    upper = val;

    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_1, upper, 1);
    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_1+1, val32, 4);

    return false;
}

bool Radio::gfsk_flrc_sync2_write(const char* txt)
{
    uint32_t val32, upper;
    uint64_t val;
    sscanf(txt, "%llx", &val);

    val32 = val;
    val >>= 32;
    upper = val;

    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_2, upper, 1);
    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_2+1, val32, 4);

    return false;
}

bool Radio::gfsk_flrc_sync3_write(const char* txt)
{
    uint32_t val32, upper;
    uint64_t val;
    sscanf(txt, "%llx", &val);

    val32 = val;
    val >>= 32;
    upper = val;

    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_3, upper, 1);
    radio.writeReg(REG_ADDR_PKT_SYNC_ADRS_3+1, val32, 4);

    return false;
}

const value_item_t Radio::gfsk_flrc_sync1_item = { _ITEM_VALUE, 10, gfsk_flrc_sync1_print, gfsk_flrc_sync1_write };
const value_item_t Radio::gfsk_flrc_sync2_item = { _ITEM_VALUE, 10, gfsk_flrc_sync2_print, gfsk_flrc_sync2_write };
const value_item_t Radio::gfsk_flrc_sync3_item = { _ITEM_VALUE, 10, gfsk_flrc_sync3_print, gfsk_flrc_sync3_write };


const menu_t Radio::gfsk_menu[] = {
    { {FIRST_CHIP_MENU_ROW,  1},              NULL,  &gfsk_bitrate_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 19},      "mod index:", &gfsk_modindex_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 35},             "BT:",  &gfsk_flrc_bt_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 43}, "PreambleLength:", &gfsk_flrc_preamble_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 61},     "SyncLength:",  &gfsk_synclen_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+1, 1},        "Length:", &gfsk_flrc_fixvar_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 18},    "crcLength:",   &gfsk_flrc_crclen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 37},    "whitening:", &gfsk_flrc_whit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 57},      "crcInit:",  &gfsk_flrc_crcinit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 72},         "poly:",  &gfsk_flrc_crcpoly_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2,  1}, "sync1 ", &gfsk_flrc_sync1en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 15},     NULL,   &gfsk_flrc_sync1_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 27}, "sync2 ", &gfsk_flrc_sync2en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 41},     NULL,   &gfsk_flrc_sync2_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 53}, "sync3 ", &gfsk_flrc_sync3en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 65},     NULL,   &gfsk_flrc_sync3_item, FLAG_MSGTYPE_ALL },

    { {0, 0}, NULL, NULL }
};

static const char* const flrc_bpsbw[] = {
    "2.6",
    "2.08",
    "1.3Mb/s   1.2MHz",
    "1.04Mb/s  1.2MHz",
    "0.65Mb/s  0.6MHz",
    "0.52Mb/s  0.6MHz",
    "0.325Mb/s 0.3MHz",
    "0.26Mb/s  0.3MHz",
    NULL
};

const dropdown_item_t Radio::flrc_bitrate_item = { _ITEM_DROPDOWN, flrc_bpsbw, flrc_bpsbw, gfsk_flrc_bpsbw_read, gfsk_flrc_bpsbw_write};

static const char* const flrc_crs[] = {
    "1/2",
    "3/4",
    "1  ",
    NULL
};
unsigned Radio::flrc_cr_read(bool fw)
{
    PktBitStreamCtrl_t PktBitStreamCtrl;
    PktBitStreamCtrl.octet = radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);

    mpFLRC.flrc.CodingRate = PktBitStreamCtrl.octet & 0x06; // PktBitStreamCtrl.bits.flora_coding_rate 

    return PktBitStreamCtrl.bits.flora_coding_rate;
}

menuMode_e Radio::flrc_cr_write(unsigned sidx)
{
    mpFLRC.flrc.CodingRate = sidx << 1;
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpFLRC.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::flrc_cr_item = { _ITEM_DROPDOWN, flrc_crs, flrc_crs, flrc_cr_read, flrc_cr_write};

static const char* const flrc_syncLens[] = {
    "SYNC OFF  ",
    "16BIT SYNC",
    "32BIT SYNC",
    NULL
};

unsigned Radio::flrc_synclen_read(bool fw)
{
    PktCtrl1_t PktCtrl1;
    PktCtrl1.octet = radio.readReg(REG_ADDR_PKTCTRL1, 1);
    ppFLRC.gfskFLRC.SyncWordLength = PktCtrl1.octet & 0x06;
    if (ppFLRC.gfskFLRC.SyncWordLength == 0x06) {
        ppFLRC.gfskFLRC.SyncWordLength = FLRC_SYNC_WORD_LEN_P32S;
        return 2;
    }

    return PktCtrl1.flrc.sync_adrs_len;
}

menuMode_e Radio::flrc_synclen_write(unsigned sidx)
{
    ppFLRC.gfskFLRC.SyncWordLength = sidx << 1;
    radio.xfer(OPCODE_SET_PACKET_PARAMS, 7, 0, ppFLRC.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::flrc_synclen_item = { _ITEM_DROPDOWN, flrc_syncLens, flrc_syncLens, flrc_synclen_read, flrc_synclen_write};


const menu_t Radio::flrc_menu[] = {
    { {FIRST_CHIP_MENU_ROW,  1},              NULL,  &flrc_bitrate_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 20},             "cr:",       &flrc_cr_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 27},             "BT:",      &gfsk_flrc_bt_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 34}, "PreambleLength:", &gfsk_flrc_preamble_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 52},     "SyncLength:",  &flrc_synclen_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+1, 1},        "Length:", &gfsk_flrc_fixvar_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 18},    "crcLength:",   &gfsk_flrc_crclen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 38},    "whitening:", &gfsk_flrc_whit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 58},      "crcInit:",  &gfsk_flrc_crcinit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 73},         "poly:",  &gfsk_flrc_crcpoly_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2,  1}, "sync1 ", &gfsk_flrc_sync1en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 12},     NULL,   &gfsk_flrc_sync1_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 24}, "sync2 ", &gfsk_flrc_sync2en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 35},     NULL,   &gfsk_flrc_sync2_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 47}, "sync3 ", &gfsk_flrc_sync3en_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 59},     NULL,   &gfsk_flrc_sync3_item, FLAG_MSGTYPE_ALL },

    { {0, 0}, NULL, NULL }
};

bool Radio::manualRngDelay;
const uint16_t Radio::rngDelays[3][6] = {
    10299, 10271, 10244, 10242, 10230, 10246,
    11486, 11474, 11453, 11426, 11417, 11401,
    13308, 13493, 13528, 13515, 13430, 13376
};

void Radio::rngUpdateDelayCal()
{
    LoRaPktPar0.octet = radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);

    if (LoRaPktPar0.bits.modem_bw > 2 && LoRaPktPar0.bits.modem_sf < 11) {
        uint32_t delayCal = radio.readReg(REG_ADDR_LORA_DELAY_CAL, 3);
        delayCal &= ~0x3fffff;
        delayCal |= rngDelays[LoRaPktPar0.bits.modem_bw-3][LoRaPktPar0.bits.modem_sf-5];
        /*log_printf("%u = rngDelays[%u][%u]\r\n",
            rngDelays[LoRaPktPar0.bits.modem_bw-3][LoRaPktPar0.bits.modem_sf-5],
            LoRaPktPar0.bits.modem_bw-3, LoRaPktPar0.bits.modem_sf-5
        );*/
        radio.writeReg(REG_ADDR_LORA_DELAY_CAL, delayCal, 3);
    }
}

static const char* const lora_bws[] = {
    "  50KHz", // 0
    " 100KHz", // 1
    " 200KHz", // 2
    " 400KHz", // 3
    " 800KHz", // 4
    "1600KHz", // 5
    NULL
};

const float Radio::bwMHzs[] = { 0.05, 0.1, 0.2, 0.4, 0.8, 1.6 };

LoRaPktPar0_t Radio::LoRaPktPar0;

unsigned Radio::lora_bw_read(bool fw)
{
    LoRaPktPar0.octet = radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);

    return LoRaPktPar0.bits.modem_bw;
}

menuMode_e Radio::lora_bw_write(unsigned sidx)
{
    switch (sidx) {
        case 0: mpLORA.lora.bandwidth = LORA_BW_50; break;
        case 1: mpLORA.lora.bandwidth = LORA_BW_100; break;
        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;
    }
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpLORA.buf);

    if (pktType == PACKET_TYPE_RANGING) {
        manualRngDelay = false;
        rngUpdateDelayCal();
    }

    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::lora_bw_item = { _ITEM_DROPDOWN, lora_bws, lora_bws, lora_bw_read, lora_bw_write};

void Radio::lora_sf_print()
{
    LoRaPktPar0.octet = radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);

    pc.printf("%u", LoRaPktPar0.bits.modem_sf);
}

bool Radio::lora_sf_write(const char* str)
{
    unsigned n;
    if (sscanf(str, "%u", &n) == 1) {
        mpLORA.lora.spreadingFactor = n << 4;
        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpLORA.buf);

        if (pktType == PACKET_TYPE_RANGING) {
            manualRngDelay = false;
            rngUpdateDelayCal();
        }
    }
    return false;
}

const value_item_t Radio::lora_sf_item = { _ITEM_VALUE, 3, lora_sf_print, lora_sf_write };

static const char* const lora_crs[] = {
    "4/5   ",
    "4/6   ",
    "4/7   ",
    "4/8   ",
    "4/5 LI",
    "4/6 LI",
    "4/7 LI",
    NULL
};

unsigned Radio::lora_cr_read(bool fw)
{
    LoRaPktPar1_t LoRaPktPar1;
    LoRaPktPar1.octet = radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
    mpLORA.lora.codingRate = LoRaPktPar1.bits.coding_rate;
    return LoRaPktPar1.bits.coding_rate;
}

menuMode_e Radio::lora_cr_write(unsigned sidx)
{
    mpLORA.lora.codingRate = sidx;
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 3, 0, mpLORA.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::lora_cr_item = { _ITEM_DROPDOWN, lora_crs, lora_crs, lora_cr_read, lora_cr_write};

void Radio::lora_pblLen_print()
{
    LoRaPreambleReg_t LoRaPreambleReg;
    LoRaPreambleReg.octet = radio.readReg(REG_ADDR_LORA_PREAMBLE, 1);
    ppLORA.lora.PreambleLength = (1 << LoRaPreambleReg.bits.preamble_symb_nb_exp) * LoRaPreambleReg.bits.preamble_symb1_nb;
    pc.printf("%u", ppLORA.lora.PreambleLength);
}

bool Radio::lora_pblLen_write(const char* str)
{
    unsigned val, exp, mant;
    sscanf(str, "%u", &val);

    for (exp = 0; exp < 16; exp++) {
        mant = val / (1 << exp);
        if (mant < 16)
            break;
    }

    ppLORA.lora.PreambleLength = (exp << 4) + mant;
    radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);
    
    return false;
}

const value_item_t Radio::lora_pblLen_item = { _ITEM_VALUE, 5, lora_pblLen_print, lora_pblLen_write};

bool Radio::lora_fixlen_read()
{
    LoRaPktPar1_t LoRaPktPar1;
    LoRaPktPar1.octet = radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
    ppLORA.lora.HeaderType = LoRaPktPar1.bits.implicit_header ? IMPLICIT_HEADER : EXPLICIT_HEADER;
    return LoRaPktPar1.bits.implicit_header;
}

bool Radio::lora_fixlen_push()
{
    if (ppLORA.lora.HeaderType == EXPLICIT_HEADER)
        ppLORA.lora.HeaderType = IMPLICIT_HEADER;
    else
        ppLORA.lora.HeaderType = EXPLICIT_HEADER;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 5, 0, ppLORA.buf);
    return ppLORA.lora.HeaderType == EXPLICIT_HEADER;
}

const toggle_item_t Radio::lora_fixlen_item = { _ITEM_TOGGLE,
    "EXPLICIT", // 0
    "IMPLICIT", // 1
    lora_fixlen_read, lora_fixlen_push
};

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

bool Radio::lora_crcon_push()
{
    if (ppLORA.lora.crc == LORA_CRC_ENABLE)
        ppLORA.lora.crc = LORA_CRC_DISABLE;
    else
        ppLORA.lora.crc = LORA_CRC_ENABLE;

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

    return ppLORA.lora.crc == LORA_CRC_ENABLE;
}

const toggle_item_t Radio::lora_crcon_item = { _ITEM_TOGGLE,
    "CRC_DISABLE", // 0
    "CRC_ENABLE ", // 1
    lora_crcon_read, lora_crcon_push
};

bool Radio::lora_iqinv_read()
{
    LoRaPktPar1_t LoRaPktPar1;
    LoRaPktPar1.octet = radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
    ppLORA.lora.InvertIQ = LoRaPktPar1.bits.rxinvert_iq ? LORA_IQ_STD : LORA_IQ_INVERTED;
    return LoRaPktPar1.bits.rxinvert_iq ? 0 : 1;
}

bool Radio::lora_iqinv_push()
{
    if (ppLORA.lora.InvertIQ == LORA_IQ_STD)
        ppLORA.lora.InvertIQ = LORA_IQ_INVERTED;
    else
        ppLORA.lora.InvertIQ = LORA_IQ_STD;

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

    return ppLORA.lora.InvertIQ == LORA_IQ_STD;
}

const toggle_item_t Radio::lora_iqinv_item = { _ITEM_TOGGLE,
    "IQ_STD", // 0
    "IQ_INV", // 1
    lora_iqinv_read, lora_iqinv_push
};

void Radio::lora_ppg_print()
{
    uint8_t val;
    ppg = radio.readReg(REG_ADDR_LORA_SYNC, 2);

    val = (ppg >> 8) & 0xf0;
    val |= (ppg & 0xf0) >> 4;
    pc.printf("%02x", val);
}

bool Radio::lora_ppg_write(const char* txt)
{
    unsigned val;
    if (sscanf(txt, "%x", &val) == 1) {
        ppg &= 0x0707;
        ppg |= (val & 0xf0) << 8;
        ppg |= (val & 0x0f) << 4;
        radio.writeReg(REG_ADDR_LORA_SYNC, ppg, 2);
    }
    return false;
}

const value_item_t Radio::lora_ppg_item = { _ITEM_VALUE, 4, lora_ppg_print, lora_ppg_write};

void Radio::cad_push()
{
    radio.setCAD();
}

const button_item_t Radio::lora_cad_item = { _ITEM_BUTTON, "CAD", cad_push };

static const char* const lora_cadsymbs[] = {
    " 1",
    " 2",
    " 4",
    " 8",
    "16",
    NULL
};

unsigned Radio::lora_cadsymbs_read(bool forWriting)
{
    unsigned n = radio.readReg(REG_ADDR_LORA_FE_GAIN, 1);
    return n >> 5;
}

menuMode_e Radio::lora_cadsymbs_write(unsigned sidx)
{
    uint8_t buf = sidx << 5;
    radio.xfer(OPCODE_SET_CAD_PARAM, 1, 0, &buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::lora_cadsymbs_item = { _ITEM_DROPDOWN, lora_cadsymbs, lora_cadsymbs, lora_cadsymbs_read, lora_cadsymbs_write};

const menu_t Radio::lora_menu[] = {
    { {FIRST_CHIP_MENU_ROW  ,  1},              NULL,     &lora_bw_item, FLAG_MSGTYPE_ALL, &rng_delay_item},
    { {FIRST_CHIP_MENU_ROW  , 12},             "sf:",     &lora_sf_item, FLAG_MSGTYPE_ALL, &rng_delay_item},
    { {FIRST_CHIP_MENU_ROW  , 20},             "cr:",     &lora_cr_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW  , 30}, "PreambleLength:", &lora_pblLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1,  1},              NULL, &lora_fixlen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 12},              NULL,  &lora_crcon_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 25},              NULL,  &lora_iqinv_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 35},            "ppg:",    &lora_ppg_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2,  1},          NULL,        &lora_cad_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2,  5},    "symbols:",   &lora_cadsymbs_item, FLAG_MSGTYPE_ALL },

    { {0, 0}, NULL, NULL }
};

bool Radio::tmp;

bool Radio::rng_role_read()
{
    //RngCfg0_t RngCfg0;
    //RngCfg0.octet = radio.readReg(REG_ADDR_RNGCFG0, 1);

    //return !RngCfg0.bits.ranging_resp_en;
    //log_printf("%02x ranging_resp_en: %u\r\n", RngCfg0.octet, RngCfg0.bits.ranging_resp_en);
    return tmp;
}

bool Radio::rng_role_push()
{
    uint8_t buf;
/*
    RngCfg0_t RngCfg0;
    RngCfg0.octet = radio.readReg(REG_ADDR_RNGCFG0, 1);

    buf = RngCfg0.bits.ranging_resp_en ? 0 : 1;
    log_printf("role %02x  %u\r\n", RngCfg0.octet, buf);
    radio.xfer(OPCODE_SET_RANGING_ROLE, 1, 0, &buf);
    return buf;
*/
    tmp ^= true;
    buf = tmp;
    radio.xfer(OPCODE_SET_RANGING_ROLE, 1, 0, &buf);
    return tmp;
}

const toggle_item_t Radio::rng_role_item = { _ITEM_TOGGLE,
    " SLAVE", // 0
    "MASTER", // 1
    rng_role_read, rng_role_push
};

void Radio::rng_id_send_print()
{
    pc.printf("%08x", radio.readReg(REG_ADDR_LORA_MASTER_REQ_ID, 4));
}

bool Radio::rng_id_send_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%x", &n);
    radio.writeReg(REG_ADDR_LORA_MASTER_REQ_ID, n, 4);
    return false;
}

const value_item_t Radio::rng_id_send_item = { _ITEM_VALUE, 9, rng_id_send_print, rng_id_send_write};

void Radio::rng_slave_id_print()
{
    pc.printf("%08x", radio.readReg(REG_ADDR_LORA_SLAVE_ID, 4));
}

bool Radio::rng_slave_id_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%x", &n);
    radio.writeReg(REG_ADDR_LORA_SLAVE_ID, n, 4);
    return false;
}

const value_item_t Radio::rng_slave_id_item = { _ITEM_VALUE, 9, rng_slave_id_print, rng_slave_id_write};

unsigned Radio::rng_idLength_read(bool forWriting)
{
    RngDebTh2_t RngDebTh2;
    RngDebTh2.octet = radio.readReg(REG_ADDR_LORA_RNGDEBTH2, 1);
    return RngDebTh2.bits.ranging_id_check_length;
}

menuMode_e Radio::rng_idLength_write(unsigned sidx)
{
    RngDebTh2_t RngDebTh2;
    RngDebTh2.octet = radio.readReg(REG_ADDR_LORA_RNGDEBTH2, 1);
    RngDebTh2.bits.ranging_id_check_length = sidx;
    radio.writeReg(REG_ADDR_LORA_RNGDEBTH2, RngDebTh2.octet, 1);
    return MENUMODE_REDRAW;
}

static const char* const rngLens[] = {
    " 8 bits",
    "16 bits",
    "24 bits",
    "32 bits",
    NULL
};

const dropdown_item_t Radio::rng_idLength_item = { _ITEM_DROPDOWN, rngLens, rngLens, rng_idLength_read, rng_idLength_write};


void Radio::rng_delay_print(void)
{
    if (pktType == PACKET_TYPE_RANGING) {
        pc.printf("%u", radio.readReg(REG_ADDR_LORA_DELAY_CAL, 3) & 0x3fffff);
    }
}

bool Radio::rng_delay_write(const char* txt)
{
    unsigned n;
    uint32_t delayCal = radio.readReg(REG_ADDR_LORA_DELAY_CAL, 3);
    sscanf(txt, "%u", &n);
    delayCal &= ~0x3fffff;
    delayCal |= n;
    radio.writeReg(REG_ADDR_LORA_DELAY_CAL, delayCal, 3);

    manualRngDelay = true;
    return false;
}

const value_item_t Radio::rng_delay_item = { _ITEM_VALUE, 7, rng_delay_print, rng_delay_write };

unsigned Radio::rng_resultMux_read(bool)
{
    RngCfg1_t RngCfg1;
    RngCfg1.octet = radio.readReg(REG_ADDR_RNGCFG1, 1);
    return RngCfg1.bits.ranging_result_mux_sel;
}

menuMode_e Radio::rng_resultMux_write(unsigned sidx)
{
    RngCfg1_t RngCfg1;
    RngCfg1.octet = radio.readReg(REG_ADDR_RNGCFG1, 1);
    RngCfg1.bits.ranging_result_mux_sel = sidx;
    radio.writeReg(REG_ADDR_RNGCFG1, RngCfg1.octet, 1);
    return MENUMODE_REDRAW;
}

static const char* const rngResults[] = {
    "     raw   ",
    "  rssiAvg  ",
    " debiased  ",
    "finalFilter",
    NULL
};

const dropdown_item_t Radio::rng_resultMux_item = { _ITEM_DROPDOWN, rngResults, rngResults, rng_resultMux_read, rng_resultMux_write};


void Radio::rng_wndFltSize_print()
{
    pc.printf("%u", radio.readReg(REG_ADDR_RNGFLTWNDSIZE, 1));
}

bool Radio::rng_wndFltSize_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    radio.writeReg(REG_ADDR_RNGFLTWNDSIZE, n, 1);
    return false;
}

const value_item_t Radio::rng_wndFltSize_item = { _ITEM_VALUE, 4, rng_wndFltSize_print, rng_wndFltSize_write};




void Radio::rng_rngRssiThresh_print()
{
    RngDebTh4H_t RngDebTh4H;
    RngDebTh4H.octet = radio.readReg(REG_ADDR_RNGDEBTH4H, 1);
    pc.printf("%u", RngDebTh4H.bits.rng_rssi_threshold);
}

bool Radio::rng_rngRssiThresh_write(const char* txt)
{
    unsigned n;
    RngDebTh4H_t RngDebTh4H;
    RngDebTh4H.octet = radio.readReg(REG_ADDR_RNGDEBTH4H, 1);
    sscanf(txt, "%u", &n);
    RngDebTh4H.bits.rng_rssi_threshold = n;
    radio.writeReg(REG_ADDR_RNGDEBTH4H, RngDebTh4H.octet, 1);
    return false;
}

const value_item_t Radio::rng_rngRssiThresh_item = { _ITEM_VALUE, 4, rng_rngRssiThresh_print, rng_rngRssiThresh_write};

const menu_t Radio::rng_menu[] = {
    { {FIRST_CHIP_MENU_ROW+2, 19},          NULL,      &rng_role_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 28}, "ID to send:",      &rng_id_send_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 49},  "slave ID:",    &rng_slave_id_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 67},      NULL,    &rng_idLength_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3,  1},  "delay:",    &rng_delay_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 14}, "resultMux:", &rng_resultMux_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 37}, "windowFilterSize:", &rng_wndFltSize_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 59}, "rngRssiThresh:", &rng_rngRssiThresh_item, FLAG_MSGTYPE_ALL },

    { {0, 0}, NULL, NULL }
};

const menu_t Radio::common_menu[] = {
    { {0, 0}, NULL, NULL }
};

const menu_t* Radio::get_modem_menu()
{
    pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_RANGING || pktType == PACKET_TYPE_LORA) {
        return lora_menu;
    } else if (pktType == PACKET_TYPE_FLRC) {
        return flrc_menu;
    } else if (pktType == PACKET_TYPE_GFSK) {
        return gfsk_menu;
    }

    return NULL;
}

const menu_t* Radio::get_modem_sub_menu()
{
    if (pktType == PACKET_TYPE_RANGING) {
        return rng_menu;
    }
    return NULL;
}

bool Radio::service(int8_t statusRow)
{
    static pktStatus_t prevPktStatus;
    static IrqFlags_t prevIrqFlags;
    IrqFlags_t irqFlags;
    bool ret = false;
    static us_timestamp_t prev_now;
    us_timestamp_t now = lpt.read_us();

    radio.service();

    if (statusRow > 0 && now-prev_now > 50000) {
        int cmp = 0;
        pktStatus_t pktStatus;
        uint8_t buf[6];
        radio.xfer(OPCODE_GET_IRQ_STATUS, 0, 3, buf);
        irqFlags.word = buf[1] << 8;
        irqFlags.word |= buf[2];

        if (rx_led == LED_ON) {
            uint8_t slen;
            if (pktType == PACKET_TYPE_LORA || pktType == PACKET_TYPE_RANGING)
                slen = 3;
            else
                slen = 6;

            radio.xfer(OPCODE_GET_PACKET_STATUS, 0, slen, pktStatus.buf);
            cmp = memcmp(prevPktStatus.buf, pktStatus.buf, slen);
        }

        if (irqFlags.word != prevIrqFlags.word || cmp) {
            IrqFlags_t clearIrqFlags;
            clearIrqFlags.word = 0;

            pc.printf("\e[%u;1f", statusRow);  // set (force) cursor to row;column

            if (cmp) {
                if (pktType == PACKET_TYPE_FLRC || pktType == PACKET_TYPE_BLE || pktType == PACKET_TYPE_GFSK) {
                    if (pktStatus.ble_gfsk_flrc.errors.SyncError)
                        pc.printf("SyncError ");
                    if (pktStatus.ble_gfsk_flrc.errors.LengthError)
                        pc.printf("LengthError ");
                    if (pktStatus.ble_gfsk_flrc.errors.CrcError)
                        pc.printf("CrcError ");
                    if (pktStatus.ble_gfsk_flrc.errors.AbortErr)
                        pc.printf("AbortErr ");
                    if (pktStatus.ble_gfsk_flrc.errors.headerReceived)
                        pc.printf("headerReceived ");
                    if (pktStatus.ble_gfsk_flrc.errors.packetReceived)
                        pc.printf("packetReceived ");
                    if (pktStatus.ble_gfsk_flrc.errors.pktCtrlBusy)
                        pc.printf("pktCtrlBusy ");
                }
                memcpy(prevPktStatus.buf, pktStatus.buf, sizeof(pktStatus_t));
                pc.printf(" | ");
            }

            if (irqFlags.bits.TxDone)
                pc.printf("TxDone ");
            if (irqFlags.bits.RxDone)
                pc.printf("RxDone ");
            if (irqFlags.bits.SyncWordValid)
                pc.printf("SyncWordValid ");
            if (irqFlags.bits.SyncWordError)
                pc.printf("SyncWordError ");
            if (irqFlags.bits.HeaderValid)
                pc.printf("HeaderValid ");
            if (irqFlags.bits.HeaderError)
                pc.printf("HeaderError ");
            if (irqFlags.bits.CrcError)
                pc.printf("CrcError ");
            if (irqFlags.bits.RangingSlaveResponseDone) {
                pc.printf("RangingSlaveResponseDone ");
                clearIrqFlags.bits.RangingSlaveResponseDone = 1;
            } if (irqFlags.bits.RangingSlaveRequestDiscard) {
                pc.printf("RangingSlaveRequestDiscard ");
                clearIrqFlags.bits.RangingSlaveRequestDiscard = 1;
            } if (irqFlags.bits.RangingMasterResultValid) {
                pc.printf("RangingMasterResultValid ");
            } if (irqFlags.bits.RangingMasterTimeout) {
                radio.chipMode = CHIPMODE_NONE;
                chipModeChange();
                pc.printf("RangingMasterTimeout ");
                clearIrqFlags.bits.RangingMasterTimeout = 1;
            } if (irqFlags.bits.RangingMasterRequestValid) {
                pc.printf("RangingMasterRequestValid ");
                clearIrqFlags.bits.RangingMasterRequestValid = 1;
            } if (irqFlags.bits.CadDone)
                pc.printf("CadDone ");
            if (irqFlags.bits.CadDetected)
                pc.printf("CadDetected ");
            if (irqFlags.bits.RxTxTimeout)
                pc.printf("RxTxTimeout ");
            if (irqFlags.bits.PreambleDetected)
                pc.printf("PreambleDetected ");

            pc.printf("\e[K");
            ret = true;

            prevIrqFlags.word = irqFlags.word;

            if (irqFlags.bits.RangingMasterResultValid) {
                float m;
                unsigned rngResult, rngRssi;
                radio.chipMode = CHIPMODE_NONE;
                chipModeChange();
                rngResult = radio.readReg(REG_ADDR_RNGRESULT, 3);
                // Distance [m] = RangingResult*150/(2^12*BwMHz)
                m = rngResult * 150 / (4096*bwMHzs[LoRaPktPar0.bits.modem_bw]);
                rngRssi = radio.readReg(REG_ADDR_RNGRSSI, 1);
                log_printf("%u rngResult %.2fm, RngRssi:%u bw%.1f\r\n", rngResult, m, rngRssi, bwMHzs[LoRaPktPar0.bits.modem_bw]);
                clearIrqFlags.bits.RangingMasterResultValid = 1;
            }

            if (irqFlags.bits.RangingSlaveResponseDone)
                log_printf("RangingSlaveResponseDone\r\n");
            if (irqFlags.bits.RangingSlaveRequestDiscard)
                log_printf("RangingSlaveRequestDiscard\r\n");
            if (irqFlags.bits.RangingMasterRequestValid)
                log_printf("RangingMasterRequestValid\r\n");

            if (clearIrqFlags.word != 0) {
                buf[0] = clearIrqFlags.word >> 8;
                buf[1] = (uint8_t)clearIrqFlags.word;
                radio.xfer(OPCODE_CLEAR_IRQ_STATUS, 2, 0, buf);
            }
        } // ..if change

        prev_now = now;
    }

    return ret;
}

void Radio::Rx()
{
    if (pktType == PACKET_TYPE_RANGING) {
        IrqFlags_t irqEnable;
        uint8_t buf[8];

        if (!manualRngDelay)
            rngUpdateDelayCal();

        irqEnable.word = 0;
        irqEnable.bits.RxDone = 1;
        irqEnable.bits.RxTxTimeout = 1;
        irqEnable.bits.RangingSlaveResponseDone = 1;
        irqEnable.bits.RangingSlaveRequestDiscard = 1;
        irqEnable.bits.RangingMasterRequestValid = 1;

        buf[0] = irqEnable.word >> 8;    // enable bits
        buf[1] = irqEnable.word; // enable bits

        irqEnable.bits.RangingSlaveResponseDone = 0;
        irqEnable.bits.RangingSlaveRequestDiscard = 0;
        irqEnable.bits.RangingMasterRequestValid = 0;
        buf[2] = irqEnable.word >> 8;     // dio1
        buf[3] = irqEnable.word;  // dio1

        buf[4] = 0; // dio2
        buf[5] = 0; // dio2
        buf[6] = 0; // dio3
        buf[7] = 0; // dio3
        radio.xfer(OPCODE_SET_DIO_IRQ_PARAMS, 8, 0, buf);

        buf[0] = radio.periodBase;
        /* receive packets forever */
        buf[1] = 0xff;
        buf[2] = 0xff;
        radio.xfer(OPCODE_SET_RX, 3, 0, buf);

        radio.chipMode = CHIPMODE_RX;
        chipModeChange();
    } else
        radio.start_rx(0);
}

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

void Radio::test()
{
/*
    RngCfg0_t RngCfg0;
    RngCfg0.octet = radio.readReg(REG_ADDR_RNGCFG0, 1);

    log_printf("Rngcfg0 %02x\r\n", RngCfg0.octet);*/
    unsigned a;
    log_printf("%02x ", radio.readReg(0x910, 1));
    for (a = 0x911; a < 0x980; a++) {
        pc.printf("%02x ", radio.readReg(a, 1));
        if ((a & 0x1f) == 0x1f)
            pc.printf("\r\n%03x ", a+1);
    }
    pc.printf("\r\n");
}

#endif /* ..SX126x_H */