Text menu driven ANSI/VT100 console test utility for LoRa transceivers

radio chip selection

Radio chip driver is not included, allowing choice of radio device.
If you're using SX1272 or SX1276, then import sx127x driver into your program.
if you're using SX1261 or SX1262, then import sx126x driver into your program.
if you're using SX1280, then import sx1280 driver into your program.
if you're using LR1110, then import LR1110 driver into your program.
If you're using NAmote72 or Murata discovery, then you must import only sx127x driver.
If you're using Type1SJ select target DISCO_L072CZ_LRWAN1 and import sx126x driver into your program.

This is VT100 text-based menu driven test program for SX12xx transceiver devices.
Serial console is divided into horizontally into top half and bottom half.
The bottom half serves as scrolling area to log activity.
The top half serves as menu, to configure the radio.
For all devices, the serial console operates at 115200 8N1, and requires terminal with ANSI-VT100 capability, such as putty/teraterm/minicom etc.
Use program only with keyboard up/down/left/right keys. Enter to change an item, or number for value item. Some items are single bit, requiring only enter key to toggle. Others with fixed choices give a drop-down menu.

radio_sx128x.cpp

Committer:
Wayne Roberts
Date:
2018-08-20
Revision:
1:0817a150122b
Child:
2:ea9245bb1c53

File content as of revision 1:0817a150122b:

#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;

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

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

menuMode_e Radio::pktType_write_cb(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_cb(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_cb(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_cb(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_cb(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();
}

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_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.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::boardInit(const RadioEvents_t* e)
{
    hw_reset();

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

    radio.chipModeChange = chipModeChange;

    readChip();

    RadioEvents = e;

    fe_enable = true;

    lpt.start();
}

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

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

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

    radio.setBufferBase(0, 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:
        case PACKET_TYPE_LORA:
            txlen = radio.readReg(REG_ADDR_LORA_TX_PAYLOAD_LENGTH, 1);
            break;
    }
    log_printf("txPkt%u\r\n", txlen);

    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_cb(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_cb(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* 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_cb, gfsk_flrc_bpsbw_write_cb};

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* gfsk_flrc_bts[] = {
    "off",
    "1.0",
    "0.5",
    "0.3",
    NULL
};

unsigned Radio::gfsk_flrc_bt_read_cb(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_cb(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_cb, gfsk_flrc_bt_write_cb };

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

unsigned Radio::gfsk_flrc_pl_read_cb(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_cb(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_cb, gfsk_flrc_pl_write_cb };

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

unsigned Radio::gfsk_synclen_read_cb(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_cb(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_cb, gfsk_synclen_write_cb };

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* gfsk_flrc_crclens[] = {
    "0 off",
    "1 byte",
    "2 bytes",
    NULL,
};

unsigned Radio::gfsk_flrc_crclen_read_cb(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_cb(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_cb, gfsk_flrc_crclen_write_cb };

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* 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_cb, gfsk_flrc_bpsbw_write_cb};

static const char* flrc_crs[] = {
    "1/2",
    "3/4",
    "1  ",
    NULL
};
unsigned Radio::flrc_cr_read_cb(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_cb(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_cb, flrc_cr_write_cb };

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

unsigned Radio::flrc_synclen_read_cb(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_cb(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_cb, flrc_synclen_write_cb };


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 }
};

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

unsigned Radio::lora_bw_read(bool fw)
{
    LoRaPktPar0_t LoRaPktPar0;
    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);

    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_t LoRaPktPar0;
    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);
    }
    return false;
}

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

static const char* 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_cb(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_cb(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_cb, lora_cr_write_cb};

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
};

const menu_t Radio::lora_menu[] = {
    { {FIRST_CHIP_MENU_ROW  ,  1},              NULL,      &lora_bw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW  , 12},             "sf:",      &lora_sf_item, FLAG_MSGTYPE_ALL },
    { {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 },
    { {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() { return NULL; }

bool Radio::service(int8_t statusRow)
{
    static pktStatus_t prevPktStatus;
    //static uint8_t foo;
    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, sizeof(pktStatus_t));
        }

        if (irqFlags.word != prevIrqFlags.word || cmp) {
            pc.printf("\e[%u;1f", statusRow);  // set (force) cursor to row;column

            //pc.printf("%u ", foo++);
            //
            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 ");
            if (irqFlags.bits.RangingSlaveRequestDiscard)
                pc.printf("RangingSlaveRequestDiscard ");
            if (irqFlags.bits.RangingMasterResultValid)
                pc.printf("RangingMasterResultValid ");
            if (irqFlags.bits.RangingMasterTimeout)
                pc.printf("RangingMasterTimeout ");
            if (irqFlags.bits.RangingMasterRequestValid)
                pc.printf("RangingMasterRequestValid ");
            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;
        }

        prev_now = now;
    }

    return ret;
}

void Radio::Rx()
{
    radio.start_rx(0);
}

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

void Radio::test() { }

#endif /* ..SX126x_H */