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_sx126x.cpp

Committer:
Wayne Roberts
Date:
2018-12-06
Revision:
5:1e5cb7139acb
Parent:
4:fa31fdf4ec8d
Child:
6:44a9df0e7855

File content as of revision 5:1e5cb7139acb:

#include "radio.h"
#ifdef SX126x_H 

#ifdef TARGET_FF_ARDUINO
    SPI spi(D11, D12, D13); // mosi, miso, sclk
                   //spi, nss, busy, dio1
    SX126x Radio::radio(spi, D7, D3, D5);

    DigitalOut antswPower(D8);
    AnalogIn xtalSel(A3);

    DigitalIn chipType(A2);
    #define CHIP_TYPE_SX1262        0
    #define CHIP_TYPE_SX1261        1

    #define PINNAME_NRST            A0

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

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

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

ModulationParams_t Radio::mpFSK, Radio::mpLORA;
PacketParams_t Radio::ppFSK, Radio::ppLORA;

const RadioEvents_t* Radio::RadioEvents;
LowPowerTimer Radio::lpt;
uint8_t Radio::pktType;
uint8_t Radio::bw_idx;
uint8_t Radio::cadParams[7];
uint16_t Radio::ppg;

const char* opModes[] = {
    "SLEEP    ", // 0
    "STBY_RC  ", // 1
    "STBY_XOSC", // 2
    "FS       ", // 3
    "RX       ", // 4
    "TX       "  // 5
};

void Radio::readChip()
{
    bwSel_t bwSel;
    shapeCfg_t shapeCfg;
    unsigned d = radio.readReg(REG_ADDR_BITRATE, 3);
    pc.printf("%06x:%u->", d ,d);
    pc.printf("bitrate %ubps\r\n", (32 * XTAL_FREQ_HZ) / d);
    mpFSK.gfsk.bitrateHi = d >> 16;
    mpFSK.gfsk.bitrateMid = d >> 8;
    mpFSK.gfsk.bitrateLo = d;

    d = radio.readReg(REG_ADDR_FREQDEV, 3);
    pc.printf("fdev %fKHz\r\n", d / KHZ_TO_FRF);
    mpFSK.gfsk.fdevHi = d >> 16;
    mpFSK.gfsk.fdevMid = d >> 8;
    mpFSK.gfsk.fdevLo = d;

    shapeCfg.octet = radio.readReg(REG_ADDR_SHAPECFG, 1);
    mpFSK.gfsk.PulseShape = shapeCfg.octet;

    bwSel.octet = radio.readReg(REG_ADDR_BWSEL, 1);
    // GFSK_RX_BW_*
    pc.printf("bwsSel:%02x\r\n", bwSel.octet);
    mpFSK.gfsk.bandwidth = bwSel.octet;

    {
        unsigned n = radio.readReg(REG_ADDR_FSK_PREAMBLE_TXLEN , 2);
        ppFSK.gfsk.PreambleLengthHi = n << 8; // param1
        ppFSK.gfsk.PreambleLengthLo = n;// param2
    }

    {
        pktCtrl1_t pktCtrl1;
        pktCtrl1.octet = radio.readReg(REG_ADDR_FSK_PKTCTRL1, 1);
        ppFSK.gfsk.PreambleDetectorLength = pktCtrl1.octet & 0x07;    // param3
    }


    ppFSK.gfsk.SyncWordLength = radio.readReg(REG_ADDR_FSK_SYNC_LEN, 1);// param4
    ppFSK.gfsk.AddrComp = radio.readReg(REG_ADDR_NODEADDRCOMP, 1);// param5

    {
        pktCtrl0_t pktCtrl0;
        pktCtrl0.octet = radio.readReg(REG_ADDR_FSK_PKTCTRL0, 1);
        ppFSK.gfsk.PacketType = pktCtrl0.bits.pkt_len_format;   // param6
    }

    ppFSK.gfsk.PayloadLength = radio.readReg(REG_ADDR_FSK_PAYLOAD_LEN, 1);// param7

    {
        pktCtrl2_t pktCtrl2;
        pktCtrl2.octet = radio.readReg(REG_ADDR_FSK_PKTCTRL2, 1);
        ppFSK.gfsk.CRCType = pktCtrl2.octet & 0x7; // param8
        ppFSK.gfsk.Whitening = pktCtrl2.bits.whit_enable;   // param9
    }

    /*******************************/

    {
        loraConfig0_t conf0;
        conf0.octet = radio.readReg(REG_ADDR_LORA_CONFIG0, 1);
        pc.printf("LoRa bw%u sf%u ", conf0.bits.modem_bw, conf0.bits.modem_sf);
        mpLORA.lora.spreadingFactor = conf0.bits.modem_sf;
        mpLORA.lora.bandwidth = conf0.bits.modem_bw;
    }

    {
        loraConfig1_t conf1;
        conf1.octet = radio.readReg(REG_ADDR_LORA_CONFIG1, 1);
        mpLORA.lora.LowDatarateOptimize = conf1.bits.ppm_offset;
        ppLORA.lora.HeaderType = conf1.bits.implicit_header;
        ppLORA.lora.InvertIQ = conf1.bits.rx_invert_iq;
        mpLORA.lora.codingRate = conf1.bits.tx_coding_rate;
    }

    {
        loraConfig2_t conf2;
        conf2.octet = radio.readReg(REG_ADDR_LORA_CONFIG2, 1);
        ppLORA.lora.CRCType = conf2.bits.tx_payload_crc16_en;
    }

    {
        uint32_t val = radio.readReg(REG_ADDR_LORA_PREAMBLE_SYMBNB, 2);
        ppLORA.lora.PreambleLengthHi = val >> 8;
        ppLORA.lora.PreambleLengthLo = val;
    }

    {
        AnaCtrl6_t AnaCtrl6;
        AnaCtrl7_t AnaCtrl7;
        PaCtrl1b_t PaCtrl1b;

        AnaCtrl6.octet = radio.readReg(REG_ADDR_ANACTRL6, 1);
        pa_config_buf[0] = AnaCtrl6.bits.pa_dctrim_select_ana;  // paDutyCycle

        AnaCtrl7.octet = radio.readReg(REG_ADDR_ANACTRL7, 1);
        pa_config_buf[1] = AnaCtrl7.bits.pa_hp_sel_ana; // hpMax

        PaCtrl1b.octet = radio.readReg(REG_ADDR_PA_CTRL1B, 1);
        pa_config_buf[2] = PaCtrl1b.bits.tx_mode_bat;   // deviceSel

        pa_config_buf[3] = 1;   // paLut
    }

    {
        cadParams[0] = radio.readReg(REG_ADDR_LORA_CONFIG9, 1);
        cadParams[0] >>= 5;

        cadParams[1] = radio.readReg(REG_ADDR_LORA_CAD_PN_RATIO, 1);
        cadParams[2] = radio.readReg(REG_ADDR_LORA_CAD_MINPEAK, 1);
    }

}

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

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

uint8_t Radio::get_payload_length()
{
    pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_GFSK) {
        ppFSK.gfsk.PayloadLength = radio.readReg(REG_ADDR_FSK_PAYLOAD_LEN, 1);
        return ppFSK.gfsk.PayloadLength;
    } else if (pktType == PACKET_TYPE_LORA) {
        ppLORA.lora.PayloadLength = radio.readReg(REG_ADDR_LORA_TXPKTLEN, 1);
        return ppLORA.lora.PayloadLength;
    } else
        return 0;
}

const char* const Radio::opmode_status_strs[] = {
    "<0>      ", // 0
    "RFU      ", // 1
    "STBY_RC  ", // 2
    "STBY_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;
    radio.xfer(OPCODE_GET_STATUS, 0, 1, &status.octet);

    if (forWriting) {
        /* translate opmode_status_strs to opmode_select_strs */
        switch (status.bits.chipMode) {
            case 2: return 1; // STBY_RC
            case 3: return 2; // STBY_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:
            antswPower = 0;
            radio.setSleep(true, false);
            break;
        case 1:
            antswPower = 0;
            radio.setStandby(STBY_RC);
            break;
        case 2:
            antswPower = 0;
            radio.setStandby(STBY_XOSC);
            break;
        case 3:
            antswPower = 0;
            radio.setFS();
            break;
        case 4:
            antswPower = 1;
            radio.start_rx(0);
            break;
        case 5:
            antswPower = 1;
            {
                uint8_t buf[3];
                buf[0] = 0;
                buf[0] = 0;
                buf[1] = 0;
                radio.xfer(OPCODE_SET_TX, 3, 0, buf);
            }
            break;
    }

    return MENUMODE_REDRAW;
}

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

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

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

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

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 = get_payload_length();

    radio.setBufferBase(0, 0);

    {
        uint8_t buf[8];
        IrqFlags_t irqEnable;
        irqEnable.word = 0;
        irqEnable.bits.TxDone = 1;
        irqEnable.bits.Timeout = 1;

        buf[0] = irqEnable.word >> 8;    // enable bits
        buf[1] = irqEnable.word; // enable bits
        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);
    }

    radio.start_tx(txlen);
}

uint8_t Radio::tx_param_buf[2];
uint8_t Radio::pa_config_buf[4];

void Radio::tx_dbm_print()
{
    PwrCtrl_t PwrCtrl;
    PaCtrl1b_t PaCtrl1b;
    unsigned v = radio.readReg(REG_ADDR_ANACTRL16, 1);

    if (v & 0x10) {
        pc.printf("%d", PA_OFF_DBM);
        return;
    }

    PwrCtrl.octet = radio.readReg(REG_ADDR_PWR_CTRL, 1);

    PaCtrl1b.octet = radio.readReg(REG_ADDR_PA_CTRL1B, 1);
    pa_config_buf[2] = PaCtrl1b.bits.tx_mode_bat;   // deviceSel

    if (PaCtrl1b.bits.tx_mode_bat)
        pc.printf("%d", PwrCtrl.bits.tx_pwr - 17);
    else
        pc.printf("%d", PwrCtrl.bits.tx_pwr - 9);
}

bool Radio::tx_dbm_write(const char* str)
{
    int dbm;
    unsigned v = radio.readReg(REG_ADDR_ANACTRL16, 1);

    sscanf(str, "%d", &dbm);

    if (dbm == PA_OFF_DBM) {
        /* bench test: prevent overloading receiving station (very low tx power) */
        v |= 0x10;  // pa dac atb tst
        radio.writeReg(REG_ADDR_ANACTRL16, v, 1);
    } else {
        tx_param_buf[0] = dbm;
        radio.xfer(OPCODE_SET_TX_PARAMS, 2, 0, tx_param_buf);

        if (v & 0x10) {
            v &= ~0x10;
            radio.writeReg(REG_ADDR_ANACTRL16, v, 1);
        }
    }

    return false;
}

const char* Radio::tx_ramp_strs[] = {
    "10  ", // 0
    "20  ", // 1
    "80  ", // 2
    "80  ", // 3
    "200 ", // 4
    "800 ", // 5
    "1700", // 6
    "3400", // 7
    NULL
};

unsigned Radio::tx_ramp_read(bool fw)
{
    PwrCtrl_t PwrCtrl;
    PwrCtrl.octet = radio.readReg(REG_ADDR_PWR_CTRL, 1);
    tx_param_buf[1] = PwrCtrl.octet >> 5;
    return PwrCtrl.bits.ramp_time;
}

menuMode_e Radio::tx_ramp_write(unsigned sidx)
{
    tx_param_buf[1] = sidx;
    radio.xfer(OPCODE_SET_TX_PARAMS, 2, 0, tx_param_buf);
    return MENUMODE_REDRAW;
}

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

    if (pktType == PACKET_TYPE_GFSK) {
        ppFSK.gfsk.PayloadLength = len;
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 9, 0, ppFSK.buf);
    } else if (pktType == PACKET_TYPE_LORA) {
        ppLORA.lora.PayloadLength = len;
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 6, 0, ppLORA.buf);
    }
}

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

bool Radio::service(int8_t statusRow)
{
    static uint8_t prevRxStatus;
    static uint8_t prevPktCtrl0;
    uint8_t buf[4];
    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) {
        uint8_t rxStatus, pktCtrl0;
        bool chg = false;
        radio.xfer(OPCODE_GET_IRQ_STATUS, 0, 3, buf);
        irqFlags.word = buf[1] << 8;
        irqFlags.word |= buf[2];

        rxStatus = radio.readReg(0x6c9, 1);
        if (rxStatus != prevRxStatus) {
            chg = true;
            prevRxStatus = rxStatus;
        }

        pktCtrl0 = radio.readReg(0x6b3, 1);
        if (pktCtrl0 != prevPktCtrl0) {
            chg = true;
            prevPktCtrl0 = pktCtrl0;
        }

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

            pc.printf("%02x ", rxStatus);
            pc.printf("%02x ", pktCtrl0);

            if (irqFlags.bits.TxDone)
                pc.printf("TxDone ");
            if (irqFlags.bits.RxDone)
                pc.printf("RxDone ");
            if (irqFlags.bits.PreambleDetected)
                pc.printf("PreambleDetected ");
            if (irqFlags.bits.SyncWordValid)
                pc.printf("SyncWordValid ");
            if (irqFlags.bits.HeaderValid)
                pc.printf("HeaderValid ");
            if (irqFlags.bits.HeaderErr)
                pc.printf("HeaderErr ");
            if (irqFlags.bits.CrCerr)
                pc.printf("CrCerr ");
            if (irqFlags.bits.CadDone)
                pc.printf("CadDone ");
            if (irqFlags.bits.CadDetected)
                pc.printf("CadDetected ");
            if (irqFlags.bits.Timeout)
                pc.printf("Timeout ");

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

            prevIrqFlags.word = irqFlags.word;
        }

        prev_now = now;
    }

    return ret;
}

void Radio::Rx()
{
    antswPower = 1;

    {
        uint8_t buf[8];
        IrqFlags_t irqEnable;
        irqEnable.word = 0;
        irqEnable.bits.RxDone = 1;
        irqEnable.bits.Timeout = 1;

        buf[0] = 3;//irqEnable.word >> 8;    // enable bits
        buf[1] = 0xff;//irqEnable.word; // enable bits
        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);
    }

    radio.start_rx(RX_TIMEOUT_CONTINUOUS);
}

void Radio::rxDone(uint8_t size, float rssi, float snr)
{
    if (pktType == PACKET_TYPE_GFSK) {
        int16_t cfo = radio.readReg(REG_ADDR_FSK_DEMOD_CFO, 2);
        // justify 12bit to 16bit signed
        if (cfo & 0x0800)
            cfo |= 0xf000;
        log_printf("cfo:%d\r\n", cfo);
    } else if (pktType == PACKET_TYPE_LORA) {
        const float bwkhzs[] = {
            7.81, 10.42, 15.63, 20.83, 31.25, 41.67, 62.5, 125, 250, 500
        };
        int hz;
        int32_t fei;
        loraStatus1_t loraStatus1;
        loraStatus1.dword = radio.readReg(REG_ADDR_LORA_STATUS, 3);
        if (loraStatus1.bits.est_freq_error & 0x80000)
            fei = 0xfff00000 | loraStatus1.bits.est_freq_error;
        else
            fei = loraStatus1.bits.est_freq_error;

        //hz = fei * HZ_TO_FRF * bwkhzs[bw_idx]/500;
        hz = fei * -HZ_TO_FRF * bwkhzs[bw_idx]/1000;
        log_printf("hz:%d\r\n", hz);
    }

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

uint8_t ana_regs[128];

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

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

    radio.chipModeChange = chipModeChange;

    readChip();

    RadioEvents = e;

    lpt.start();
}

bool Radio::deviceSel_read()
{
    PaCtrl1b_t PaCtrl1b;
    PaCtrl1b.octet = radio.readReg(REG_ADDR_PA_CTRL1B, 1);
    pa_config_buf[2] = PaCtrl1b.bits.tx_mode_bat;   // deviceSel
    return PaCtrl1b.bits.tx_mode_bat;
}

bool Radio::deviceSel_push()
{
    if (pa_config_buf[2])
        pa_config_buf[2] = 0;
    else
        pa_config_buf[2] = 1;

    radio.xfer(OPCODE_SET_PA_CONFIG, 4, 0, pa_config_buf);

    return pa_config_buf[2];
}

const toggle_item_t Radio::deviceSel_item = { _ITEM_TOGGLE, "SX1262", "SX1261", deviceSel_read, deviceSel_push};

static const char* const paDutyCycles[] = {
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    NULL
};

unsigned Radio::paDutyCycle_read(bool forWriting)
{
    AnaCtrl6_t AnaCtrl6;
    AnaCtrl6.octet = radio.readReg(REG_ADDR_ANACTRL6, 1);
    pa_config_buf[0] = AnaCtrl6.bits.pa_dctrim_select_ana;
    return AnaCtrl6.bits.pa_dctrim_select_ana;
}

menuMode_e Radio::paDutyCycle_write(unsigned sidx)
{
    pa_config_buf[0] = sidx;
    radio.xfer(OPCODE_SET_TX_PARAMS, 2, 0, tx_param_buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::paDutyCycle_item = { _ITEM_DROPDOWN, paDutyCycles, paDutyCycles, paDutyCycle_read, paDutyCycle_write};

static const char* const hpMaxs[] = {
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    NULL
};

unsigned Radio::hpMax_read(bool forWriting)
{
    AnaCtrl7_t AnaCtrl7;
    AnaCtrl7.octet = radio.readReg(REG_ADDR_ANACTRL7, 1);
    pa_config_buf[1] = AnaCtrl7.bits.pa_hp_sel_ana;
    return AnaCtrl7.bits.pa_hp_sel_ana;
}

menuMode_e Radio::hpMax_write(unsigned sidx)
{
    pa_config_buf[1] = sidx;
    radio.xfer(OPCODE_SET_TX_PARAMS, 2, 0, tx_param_buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::hpMax_item = { _ITEM_DROPDOWN, hpMaxs, hpMaxs, hpMax_read, hpMax_write};

void Radio::ocp_print()
{
    uint8_t ocp = radio.readReg(REG_ADDR_OCP, 1);
    pc.printf("%.1f", ocp * 2.5);

}

bool Radio::ocp_write(const char* txt)
{
    float mA;
    if (sscanf(txt, "%f", &mA) == 1)
        radio.writeReg(REG_ADDR_OCP, mA / 2.5, 1);

    return false;
}

const value_item_t Radio::ocp_item = { _ITEM_VALUE, 5, ocp_print, ocp_write};

void Radio::xta_print()
{
    uint8_t trim = radio.readReg(REG_ADDR_XTA_TRIM, 1);
    pc.printf("%02x", trim);
}

bool Radio::xta_write(const char* txt)
{
    unsigned trim;
    if (sscanf(txt, "%x", &trim) == 1)
        radio.writeReg(REG_ADDR_XTA_TRIM, trim, 1);

    return false;
}

const value_item_t Radio::xta_item = { _ITEM_VALUE, 3, xta_print, xta_write};

void Radio::xtb_print()
{
    uint8_t trim = radio.readReg(REG_ADDR_XTB_TRIM, 1);
    pc.printf("%02x", trim);
}

bool Radio::xtb_write(const char* txt)
{
    unsigned trim;
    if (sscanf(txt, "%x", &trim) == 1)
        radio.writeReg(REG_ADDR_XTB_TRIM, trim, 1);

    return false;
}

const value_item_t Radio::xtb_item = { _ITEM_VALUE, 3, xtb_print, xtb_write};

const menu_t Radio::common_menu[] = {
    { {FIRST_CHIP_MENU_ROW,  1},   "deviceSel:",   &deviceSel_item, FLAG_MSGTYPE_ALL, &tx_dbm_item },
    { {FIRST_CHIP_MENU_ROW, 18}, "paDutyCycle:", &paDutyCycle_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 33},       "hpMax:",       &hpMax_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 42},      "ocp mA:",         &ocp_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 55},         "XTA:",         &xta_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 62},         "XTB:",         &xtb_item, FLAG_MSGTYPE_ALL },

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

const uint8_t loraBWs[] = {
    LORA_BW_7, LORA_BW_10, LORA_BW_15,
    LORA_BW_20, LORA_BW_31, LORA_BW_41,
    LORA_BW_62, LORA_BW_125, LORA_BW_250,
    LORA_BW_500
};

static const char* const lora_bwstrs[] = {
    " 7.81KHz", "10.42KHz", "15.63KHz",
    "20.83KHz", "31.25KHz", "41.67KHz",
    " 62.5KHz", "  125KHz", "  250KHz",
    "  500KHz",
    NULL
};

unsigned Radio::lora_bw_read(bool forWriting)
{
    unsigned n;
    loraConfig0_t conf0;
    conf0.octet = radio.readReg(REG_ADDR_LORA_CONFIG0, 1);
    mpLORA.lora.bandwidth = conf0.bits.modem_bw;
    for (n = 0; n < sizeof(loraBWs); n++) {
        if (conf0.bits.modem_bw == loraBWs[n]) {
            bw_idx = n;
            return n;
        }
    }
    return sizeof(loraBWs);
}

menuMode_e Radio::lora_bw_write(unsigned sidx)
{
    mpLORA.lora.bandwidth = loraBWs[sidx];
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 4, 0, mpLORA.buf);
    bw_idx = sidx;
    return MENUMODE_REDRAW;
}

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

void Radio::lora_sf_print()
{
    loraConfig0_t conf0;
    conf0.octet = radio.readReg(REG_ADDR_LORA_CONFIG0, 1);
    mpLORA.lora.spreadingFactor = conf0.bits.modem_sf;
    pc.printf("%u", conf0.bits.modem_sf);
}

bool Radio::lora_sf_write(const char* str)
{
    unsigned sf;
    if (sscanf(str, "%u", &sf) == 1) {
        mpLORA.lora.spreadingFactor = sf;
        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 4, 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* const lora_crs[] = {
    "4/5   ",
    "4/6   ",
    "4/7   ",
    "4/8   ",
    NULL
};

unsigned Radio::lora_cr_read(bool forWriting)
{
    loraConfig1_t conf1;
    conf1.octet = radio.readReg(REG_ADDR_LORA_CONFIG1, 1);
    mpLORA.lora.codingRate = conf1.bits.tx_coding_rate;
    return conf1.bits.tx_coding_rate;
}

menuMode_e Radio::lora_cr_write(unsigned sidx)
{
    mpLORA.lora.codingRate = sidx;
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 4, 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};

bool Radio::ppmOffset_read()
{
    loraConfig1_t conf1;
    conf1.octet = radio.readReg(REG_ADDR_LORA_CONFIG1, 1);
    mpLORA.lora.LowDatarateOptimize = conf1.bits.ppm_offset;
    return conf1.bits.ppm_offset;
}

bool Radio::ppmOffset_push()
{
    if (mpLORA.lora.LowDatarateOptimize)
        mpLORA.lora.LowDatarateOptimize = 0;
    else
        mpLORA.lora.LowDatarateOptimize = 1;

    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 4, 0, mpLORA.buf);
    return mpLORA.lora.LowDatarateOptimize;
}

const toggle_item_t Radio::lora_ppmOffset_item = { _ITEM_TOGGLE, "LowDatarateOptimize", NULL, ppmOffset_read, ppmOffset_push};

void Radio::lora_pblLen_print()
{
    uint32_t val = radio.readReg(REG_ADDR_LORA_PREAMBLE_SYMBNB, 2);
    ppLORA.lora.PreambleLengthHi = val >> 8;
    ppLORA.lora.PreambleLengthLo = val;
    pc.printf("%u", val);
}

bool Radio::lora_pblLen_write(const char* txt)
{
    unsigned n;
    if (sscanf(txt, "%u", &n) == 1) {
        ppLORA.lora.PreambleLengthHi = n >> 8;
        ppLORA.lora.PreambleLengthLo = n;
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 6, 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_headerType_read()
{
    loraConfig1_t conf1;
    conf1.octet = radio.readReg(REG_ADDR_LORA_CONFIG1, 1);
    ppLORA.lora.HeaderType = conf1.bits.implicit_header;
    return conf1.bits.implicit_header;
}

bool Radio::lora_headerType_push()
{
    if (ppLORA.lora.HeaderType)
        ppLORA.lora.HeaderType = 0;
    else
        ppLORA.lora.HeaderType = 1;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 6, 0, ppLORA.buf);
    return ppLORA.lora.HeaderType;
}

const toggle_item_t Radio::lora_headerType_item = { _ITEM_TOGGLE, "EXPLICIT", "IMPLICIT", lora_headerType_read, lora_headerType_push};

bool Radio::lora_crcon_read()
{
    loraConfig2_t conf2;
    conf2.octet = radio.readReg(REG_ADDR_LORA_CONFIG2, 1);
    ppLORA.lora.CRCType = conf2.bits.tx_payload_crc16_en;
    return conf2.bits.tx_payload_crc16_en;
}

bool Radio::lora_crcon_push()
{
    if (ppLORA.lora.CRCType)
        ppLORA.lora.CRCType = 0;
    else
        ppLORA.lora.CRCType = 1;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 6, 0, ppLORA.buf);
    return ppLORA.lora.CRCType;
}

const toggle_item_t Radio::lora_crcon_item = { _ITEM_TOGGLE, "CrcOn", NULL, lora_crcon_read, lora_crcon_push};

bool Radio::lora_inviq_read()
{
    loraConfig1_t conf1;
    conf1.octet = radio.readReg(REG_ADDR_LORA_CONFIG1, 1);
    ppLORA.lora.InvertIQ = conf1.bits.rx_invert_iq;
    return conf1.bits.rx_invert_iq;
}

bool Radio::lora_inviq_push()
{
    if (ppLORA.lora.InvertIQ)
        ppLORA.lora.InvertIQ = 0;
    else
        ppLORA.lora.InvertIQ = 1;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 6, 0, ppLORA.buf);
    return ppLORA.lora.InvertIQ;
}

const toggle_item_t Radio::lora_inviq_item = { _ITEM_TOGGLE, "InvertIQ", NULL, lora_inviq_read, lora_inviq_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()
{
    {
        uint8_t buf[8];
        IrqFlags_t irqEnable;
        irqEnable.word = 0;
        irqEnable.bits.RxDone = 1;
        irqEnable.bits.Timeout = 1;
        irqEnable.bits.CadDetected = 1;
        irqEnable.bits.CadDone = 1;

        buf[0] = 3;//irqEnable.word >> 8;    // enable bits
        buf[1] = 0xff;//irqEnable.word; // enable bits
        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);
    }

    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)
{
    cadParams[0] = radio.readReg(REG_ADDR_LORA_CONFIG9, 1);
    cadParams[0] >>= 5;
    return cadParams[0];
}

menuMode_e Radio::lora_cadsymbs_write(unsigned sidx)
{
    cadParams[0] = sidx;
    radio.xfer(OPCODE_SET_CAD_PARAM, 7, 0, cadParams);
    return MENUMODE_REDRAW;
}

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

void Radio::lora_cadpnratio_print()
{
    cadParams[1] = radio.readReg(REG_ADDR_LORA_CAD_PN_RATIO, 1);
    pc.printf("%u", cadParams[1]);
}

bool Radio::lora_cadpnratio_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    cadParams[1] = n;
    radio.xfer(OPCODE_SET_CAD_PARAM, 7, 0, cadParams);
    return false;
}

const value_item_t Radio::lora_cadpnratio_item = { _ITEM_VALUE, 4, lora_cadpnratio_print, lora_cadpnratio_write};

void Radio::lora_cadmin_print()
{
    cadParams[2] = radio.readReg(REG_ADDR_LORA_CAD_MINPEAK, 1);
    pc.printf("%u", cadParams[2]);
}

bool Radio::lora_cadmin_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    cadParams[2] = n;
    radio.xfer(OPCODE_SET_CAD_PARAM, 7, 0, cadParams);
    return false;
}

const value_item_t Radio::lora_cadmin_item = { _ITEM_VALUE, 4, lora_cadmin_print, lora_cadmin_write};

bool Radio::lora_cadexit_read()
{
    return cadParams[3];
}

bool Radio::lora_cadexit_push()
{
    if (cadParams[3])
        cadParams[3] = 0;
    else
        cadParams[3] = 1;

    radio.xfer(OPCODE_SET_CAD_PARAM, 7, 0, cadParams);

    return cadParams[3];
}

const toggle_item_t Radio::lora_cadexit_item = { _ITEM_TOGGLE, "CAD_ONLY", "CAD_RX  ", lora_cadexit_read, lora_cadexit_push};

void Radio::lora_cadtimeout_print(void)
{
    unsigned n;

    n = cadParams[4];
    n <<= 8;
    n += cadParams[5];
    n <<= 8;
    n += cadParams[6];
    pc.printf("%u", n);
}

bool Radio::lora_cadtimeout_write(const char* txt)
{
    unsigned n;
    float ticks;

    sscanf(txt, "%u", &n);
    ticks = n / 15.625;
    n = ticks;

    cadParams[4] = n >> 16;
    cadParams[5] = n >> 8;
    cadParams[6] = n;

    return false;
}

const value_item_t Radio::lora_cadtimeout_item = { _ITEM_VALUE, 4, lora_cadtimeout_print, lora_cadtimeout_write};

const menu_t Radio::lora_menu[] = {
    { {FIRST_CHIP_MENU_ROW+1,  1},   NULL,      &lora_bw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 12},  "sf:",      &lora_sf_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 20},  "cr:",      &lora_cr_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 30},   NULL, &lora_ppmOffset_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2,  1}, "PreambleLength:", &lora_pblLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 22},              NULL, &lora_headerType_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 32},              NULL, &lora_crcon_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 39},              NULL, &lora_inviq_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 49},            "ppg:", &lora_ppg_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+3,  1},          NULL,        &lora_cad_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3,  5},    "symbols:",   &lora_cadsymbs_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 20}, "peak/noise:", &lora_cadpnratio_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 35},        "min:",     &lora_cadmin_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 45},       "exit:",    &lora_cadexit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 62}, "timeout us:", &lora_cadtimeout_item, FLAG_MSGTYPE_ALL },

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

void Radio::test()
{
    pktType = radio.getPacketType();

    if (pktType == PACKET_TYPE_GFSK) {
    } else if (pktType == PACKET_TYPE_LORA) {
    }
}

void Radio::gfsk_bitrate_print()
{
    unsigned d = radio.readReg(REG_ADDR_BITRATE, 3);
    float f = d / 32.0;

    pc.printf("%u", (unsigned)(XTAL_FREQ_HZ / f));

    mpFSK.gfsk.bitrateHi = d >> 16;
    mpFSK.gfsk.bitrateMid = d >> 8;
    mpFSK.gfsk.bitrateLo = d;
}

bool Radio::gfsk_bitrate_write(const char* txt)
{
    unsigned bps, br;

    if (sscanf(txt, "%u", &bps) == 1) {
        br = 32 * (XTAL_FREQ_HZ / (float)bps);
        mpFSK.gfsk.bitrateHi = br >> 16;
        mpFSK.gfsk.bitrateMid = br >> 8;
        mpFSK.gfsk.bitrateLo = br;
        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 8, 0, mpFSK.buf);
    }
    return false;
}

const value_item_t Radio::gfsk_bitrate_item = { _ITEM_VALUE, 8, gfsk_bitrate_print, gfsk_bitrate_write};

static const char* const gfsk_bts[] = {
    "off", // 0
    "0.3", // 1
    "0.5", // 2
    "0.7", // 3
    "1.0", // 4
    NULL
};

unsigned Radio::gfsk_bt_read(bool forWriting)
{
    shapeCfg_t shapeCfg;
    shapeCfg.octet = radio.readReg(REG_ADDR_SHAPECFG, 1);
    mpFSK.gfsk.PulseShape = shapeCfg.octet;
    if (shapeCfg.bits.pulse_shape)
        return shapeCfg.bits.bt + 1;
    else
        return 0;
}

menuMode_e Radio::gfsk_bt_write(unsigned sidx)
{
    switch (sidx) {
        case 0: mpFSK.gfsk.PulseShape = GFSK_SHAPE_NONE; break;
        case 1: mpFSK.gfsk.PulseShape = GFSK_SHAPE_BT0_3; break;
        case 2: mpFSK.gfsk.PulseShape = GFSK_SHAPE_BT0_5; break;
        case 3: mpFSK.gfsk.PulseShape = GFSK_SHAPE_BT0_7; break;
        case 4: mpFSK.gfsk.PulseShape = GFSK_SHAPE_BT1_0; break;
    }
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 8, 0, mpFSK.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_bt_item = { _ITEM_DROPDOWN, gfsk_bts, gfsk_bts, gfsk_bt_read, gfsk_bt_write};

static const uint8_t rx_bws[] = {
      GFSK_RX_BW_4800,   GFSK_RX_BW_5800,   GFSK_RX_BW_7300,   GFSK_RX_BW_9700,
     GFSK_RX_BW_11700,  GFSK_RX_BW_14600,  GFSK_RX_BW_19500,  GFSK_RX_BW_23400,
     GFSK_RX_BW_29300,  GFSK_RX_BW_39000,  GFSK_RX_BW_46900,  GFSK_RX_BW_58600,
     GFSK_RX_BW_78200,  GFSK_RX_BW_93800, GFSK_RX_BW_117300, GFSK_RX_BW_156200,
    GFSK_RX_BW_187200, GFSK_RX_BW_234300, GFSK_RX_BW_312000, GFSK_RX_BW_373600,
    GFSK_RX_BW_467000
};

static const char* const rxbw_str[] = {
    "  4.8KHz", "  5.8KHz", "  7.3KHz", "  9.7KHz",
    " 11.7KHz", " 14.6KHz", " 19.5KHz", " 23.4KHz",
    " 29.3KHz", " 39.0KHz", " 46.9KHz", " 58.6KHz",
    " 78.2KHz", " 93.8KHz", "117.3KHz", "156.2KHz",
    "187.2KHz", "234.3KHz", "312.0KHz", "373.6KHz",
    "467.0KHz",
    NULL
};

unsigned Radio::gfsk_rxbw_read(bool forWriting)
{
    unsigned n;
    bwSel_t bwSel;
    bwSel.octet = radio.readReg(REG_ADDR_BWSEL, 1);
    mpFSK.gfsk.bandwidth = bwSel.octet;

    for (n = 0; n < sizeof(rx_bws); n++) {
        if (bwSel.octet == rx_bws[n])
            return n;
    }
    return sizeof(rx_bws);
}

menuMode_e Radio::gfsk_rxbw_write(unsigned sidx)
{
    mpFSK.gfsk.bandwidth = rx_bws[sidx];
    radio.xfer(OPCODE_SET_MODULATION_PARAMS, 8, 0, mpFSK.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_rxbw_item = { _ITEM_DROPDOWN, rxbw_str, rxbw_str, gfsk_rxbw_read, gfsk_rxbw_write};

void Radio::gfsk_fdev_print()
{
    unsigned d = radio.readReg(REG_ADDR_FREQDEV, 3);
    pc.printf("%u", (unsigned)(d * FREQ_STEP));
}

bool Radio::gfsk_fdev_write(const char* txt)
{
    unsigned hz, fdev;
    if (sscanf(txt, "%u", &hz) == 1) {
        fdev = hz / FREQ_STEP;
        mpFSK.gfsk.fdevHi = fdev >> 16;
        mpFSK.gfsk.fdevMid = fdev >> 8;
        mpFSK.gfsk.fdevLo = fdev;
        radio.xfer(OPCODE_SET_MODULATION_PARAMS, 8, 0, mpFSK.buf);
    }
    return false;
}

const value_item_t Radio::gfsk_fdev_item = { _ITEM_VALUE, 8, gfsk_fdev_print, gfsk_fdev_write};

void Radio::gfsk_pblLen_print()
{
    unsigned n = radio.readReg(REG_ADDR_FSK_PREAMBLE_TXLEN , 2);
    ppFSK.gfsk.PreambleLengthHi = n << 8; // param1
    ppFSK.gfsk.PreambleLengthLo = n;// param2
    pc.printf("%u", n);
}

bool Radio::gfsk_pblLen_write(const char* txt)
{
    unsigned n;
    if (sscanf(txt, "%u", &n) == 1) {
        ppFSK.gfsk.PreambleLengthHi = n << 8; // param1
        ppFSK.gfsk.PreambleLengthLo = n;// param2
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 9, 0, ppFSK.buf);
    }
    return false;
}

const value_item_t Radio::gfsk_pblLen_item = { _ITEM_VALUE, 5, gfsk_pblLen_print, gfsk_pblLen_write};

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

unsigned Radio::gfsk_pblDetLen_read(bool forWriting)
{
    pktCtrl1_t pktCtrl1;
    pktCtrl1.octet = radio.readReg(REG_ADDR_FSK_PKTCTRL1, 1);
    ppFSK.gfsk.PreambleDetectorLength = pktCtrl1.octet & 0x07;    // param3
    if (pktCtrl1.bits.preamble_det_on)
        return pktCtrl1.bits.preamble_len_rx + 1;
    else
        return 0;
}

menuMode_e Radio::gfsk_pblDetLen_write(unsigned sidx)
{
    if (sidx == 0)
        ppFSK.gfsk.PreambleDetectorLength = 0;
    else
        ppFSK.gfsk.PreambleDetectorLength = sidx + 3;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 9, 0, ppFSK.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_pblDetLen_item = { _ITEM_DROPDOWN, fsk_detlens, fsk_detlens, gfsk_pblDetLen_read, gfsk_pblDetLen_write};

void Radio::gfsk_swl_print()
{
    ppFSK.gfsk.SyncWordLength = radio.readReg(REG_ADDR_FSK_SYNC_LEN, 1);// param4
    pc.printf("%u", ppFSK.gfsk.SyncWordLength);
}

bool Radio::gfsk_swl_write(const char* txt)
{
    unsigned n;
    unsigned r;
    r = sscanf(txt, "%u", &n);
    if (r == 1) {
        ppFSK.gfsk.SyncWordLength = n;
        radio.xfer(OPCODE_SET_PACKET_PARAMS, 9, 0, ppFSK.buf);
    }
    return false;
}

const value_item_t Radio::gfsk_swl_item = { _ITEM_VALUE, 3, gfsk_swl_print, gfsk_swl_write};

void Radio::gfsk_syncword_print()
{
    unsigned addr = REG_ADDR_SYNCADDR;
    uint8_t swl_bits = radio.readReg(REG_ADDR_FSK_SYNC_LEN, 1);
    if (swl_bits & 7) {
        swl_bits |= 7;
        swl_bits++;
    }
    while (swl_bits > 0) {
        pc.printf("%02x", radio.readReg(addr++, 1));
        swl_bits -= 8;
    }
}

bool Radio::gfsk_syncword_write(const char* txt)
{
    const char* ptr = txt;
    unsigned addr = REG_ADDR_SYNCADDR;
    int8_t swl_bits = radio.readReg(REG_ADDR_FSK_SYNC_LEN, 1);
    if (swl_bits & 7) {
        swl_bits |= 7;
        swl_bits++;
    }
    while (swl_bits > 0) {
        char buf[3];
        unsigned n;
        buf[0] = ptr[0];
        buf[1] = ptr[1];
        buf[2] = 0;
        sscanf(buf, "%x", &n);
        radio.writeReg(addr++, n, 1);
        ptr += 2;
        swl_bits -= 8;
    }
    return false;
}

const value_item_t Radio::gfsk_syncword_item = { _ITEM_VALUE, 17, gfsk_syncword_print, gfsk_syncword_write};

bool Radio::gfsk_fixLen_read()
{
    pktCtrl0_t pktCtrl0;
    pktCtrl0.octet = radio.readReg(REG_ADDR_FSK_PKTCTRL0, 1);
    ppFSK.gfsk.PacketType = pktCtrl0.bits.pkt_len_format;   // param6
    return pktCtrl0.bits.pkt_len_format;
}

bool Radio::gfsk_fixLen_push()
{
    if (ppFSK.gfsk.PacketType)
        ppFSK.gfsk.PacketType = 0;
    else
        ppFSK.gfsk.PacketType = 1;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 9, 0, ppFSK.buf);
    return ppFSK.gfsk.PacketType;
}

const toggle_item_t Radio::gfsk_fixLen_item = { _ITEM_TOGGLE,
    "fixed   ",
    "variable",
    gfsk_fixLen_read, gfsk_fixLen_push
};


static const char* const addrcomps[] = {
    "          off        ",
    "NodeAddress          ",
    "NodeAddress+broadcast",
    NULL
};

unsigned Radio::gfsk_addrcomp_read(bool forWriting)
{
    ppFSK.gfsk.AddrComp = radio.readReg(REG_ADDR_NODEADDRCOMP, 1);// param5
    return ppFSK.gfsk.AddrComp;
}

menuMode_e Radio::gfsk_addrcomp_write(unsigned sidx)
{
    ppFSK.gfsk.AddrComp = sidx;
    radio.xfer(OPCODE_SET_PACKET_PARAMS, 9, 0, ppFSK.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_addrcomp_item = { _ITEM_DROPDOWN, addrcomps, addrcomps, gfsk_addrcomp_read, gfsk_addrcomp_write};

void Radio::gfsk_nodeadrs_print()
{
    pc.printf("%02x", radio.readReg(REG_ADDR_NODEADDR, 1));
}

bool Radio::gfsk_nodeadrs_write(const char* txt)
{
    unsigned v;
    if (sscanf(txt, "%x", &v) == 1)
        radio.writeReg(REG_ADDR_NODEADDR, v, 1);

    return false;
}

const value_item_t Radio::gfsk_nodeadrs_item = { _ITEM_VALUE, 3, gfsk_nodeadrs_print, gfsk_nodeadrs_write};

void Radio::gfsk_broadcast_print()
{
    pc.printf("%02x", radio.readReg(REG_ADDR_BROADCAST, 1));
}

bool Radio::gfsk_broadcast_write(const char* txt)
{
    unsigned v;
    if (sscanf(txt, "%x", &v) == 1)
        radio.writeReg(REG_ADDR_BROADCAST, v, 1);

    return false;
}

const value_item_t Radio::gfsk_broadcast_item = { _ITEM_VALUE, 3, gfsk_broadcast_print, gfsk_broadcast_write};

static const char* crctypes[] = {
    "    off   ", // 0
    "1 Byte    ", // 1
    "2 Byte    ", // 2
    "1 Byte inv", // 3
    "2 Byte inv", // 4
    NULL
};

unsigned Radio::gfsk_crctype_read(bool forWriting)
{
    pktCtrl2_t pktCtrl2;
    pktCtrl2.octet = radio.readReg(REG_ADDR_FSK_PKTCTRL2, 1);
    ppFSK.gfsk.CRCType = pktCtrl2.octet & 0x7; // param8
    switch (ppFSK.gfsk.CRCType) {
        case GFSK_CRC_OFF: return 0;
        case GFSK_CRC_1_BYTE: return 1;
        case GFSK_CRC_2_BYTE: return 2;
        case GFSK_CRC_1_BYTE_INV: return 3;
        case GFSK_CRC_2_BYTE_INV: return 4;
        default: return 5;
    }
}

menuMode_e Radio::gfsk_crctype_write(unsigned sidx)
{
    switch (sidx) {
        case 0: ppFSK.gfsk.CRCType = GFSK_CRC_OFF; break;
        case 1: ppFSK.gfsk.CRCType = GFSK_CRC_1_BYTE; break;
        case 2: ppFSK.gfsk.CRCType = GFSK_CRC_2_BYTE; break;
        case 3: ppFSK.gfsk.CRCType = GFSK_CRC_1_BYTE_INV; break;
        case 4: ppFSK.gfsk.CRCType = GFSK_CRC_2_BYTE_INV; break;
    }

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 9, 0, ppFSK.buf);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::gfsk_crctype_item = { _ITEM_DROPDOWN, crctypes, crctypes, gfsk_crctype_read, gfsk_crctype_write};

bool Radio::gfsk_white_read()
{
    pktCtrl2_t pktCtrl2;
    pktCtrl2.octet = radio.readReg(REG_ADDR_FSK_PKTCTRL2, 1);
    ppFSK.gfsk.Whitening = pktCtrl2.bits.whit_enable;   // param9
    return pktCtrl2.bits.whit_enable;
}

bool Radio::gfsk_white_push()
{
    if (ppFSK.gfsk.Whitening)
        ppFSK.gfsk.Whitening = 0;
    else
        ppFSK.gfsk.Whitening = 1;

    radio.xfer(OPCODE_SET_PACKET_PARAMS, 9, 0, ppFSK.buf);
    return ppFSK.gfsk.Whitening;
}

const toggle_item_t Radio::gfsk_white_item = { _ITEM_TOGGLE, "Whitening", NULL, gfsk_white_read, gfsk_white_push};

void Radio::gfsk_crcinit_print()
{
    pc.printf("%04x", radio.readReg(REG_ADDR_FSK_CRCINIT, 2));
}

bool Radio::gfsk_crcinit_write(const char* txt)
{
    unsigned v;
    if (sscanf(txt, "%x", &v) == 1)
        radio.writeReg(REG_ADDR_FSK_CRCINIT, v, 2);

    return false;
}

const value_item_t Radio::gfsk_crcinit_item = { _ITEM_VALUE, 5, gfsk_crcinit_print, gfsk_crcinit_write};

void Radio::gfsk_crcpoly_print()
{
    pc.printf("%04x", radio.readReg(REG_ADDR_FSK_CRCPOLY, 2));
}

bool Radio::gfsk_crcpoly_write(const char* txt)
{
    unsigned v;
    if (sscanf(txt, "%x", &v) == 1)
        radio.writeReg(REG_ADDR_FSK_CRCPOLY, v, 2);

    return false;
}

const value_item_t Radio::gfsk_crcpoly_item = { _ITEM_VALUE, 5, gfsk_crcpoly_print, gfsk_crcpoly_write};

void Radio::gfsk_whiteInit_print()
{
    PktCtrl1a_t PktCtrl1a;
    PktCtrl1a.word = radio.readReg(REG_ADDR_FSK_PKTCTRL1A, 2);
    pc.printf("%x", PktCtrl1a.bits.whit_init_val);
}

bool Radio::gfsk_whiteInit_write(const char* txt)
{
    unsigned n;
    PktCtrl1a_t PktCtrl1a;
    PktCtrl1a.word = radio.readReg(REG_ADDR_FSK_PKTCTRL1A, 2);
    if (sscanf(txt, "%x", &n) == 1) {
        PktCtrl1a.bits.whit_init_val = n;
        radio.writeReg(REG_ADDR_FSK_PKTCTRL1A, PktCtrl1a.word, 2);
    }
    return false;
}

const value_item_t Radio::gfsk_whiteInit_item = { _ITEM_VALUE, 5, gfsk_whiteInit_print, gfsk_whiteInit_write};

const menu_t Radio::gfsk_menu[] = {
    { {FIRST_CHIP_MENU_ROW+1,  1},    "bps:", &gfsk_bitrate_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 15},    "bt:",       &gfsk_bt_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 23},   "rxbw:",    &gfsk_rxbw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 39},   "fdev:",    &gfsk_fdev_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 53},      NULL,  &gfsk_fixLen_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2,  1}, "PreambleLength:",  &gfsk_pblLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 21}, "PreambleDetectorLength:",  &gfsk_pblDetLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 51}, "SyncWordLength bits:",  &gfsk_swl_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+3, 1}, "SyncWord:",  &gfsk_syncword_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+4,  1}, "AddrComp:",   &gfsk_addrcomp_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 33}, "NodeAdrs:",   &gfsk_nodeadrs_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 47}, "broadcast:", &gfsk_broadcast_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+5,   1}, "crcType:", &gfsk_crctype_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5,  21}, "crcInit:", &gfsk_crcinit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5,  34}, "crcPoly:", &gfsk_crcpoly_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+6,  1},          NULL, &gfsk_white_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+6,  12}, "lfsr init:", &gfsk_whiteInit_item, FLAG_MSGTYPE_ALL },
//12345678901234567890123456789012

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

const menu_t* Radio::get_modem_sub_menu() { return NULL; }

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

    if (pktType == PACKET_TYPE_LORA) {
        return lora_menu;
    } else if (pktType == PACKET_TYPE_GFSK) {
        return gfsk_menu;
    }

    return NULL;
}

unsigned Radio::read_register(unsigned addr)
{
    return radio.readReg(addr, 1);
}

void Radio::write_register(unsigned addr, unsigned val)
{
    radio.writeReg(addr, val, 1);
}

#endif /* ..SX126x_H */