wayne roberts / Mbed OS utility_sx12xx

radio_sx126x.cpp

Committer:
dudmuck
Date:
2021-09-16
Revision:
14:14b9e1c08bfc
Parent:
13:8ce61a1897ab
Child:
15:703ca340d0fb

File content as of revision 14:14b9e1c08bfc:

#include "radio.h"
#ifdef SX126x_H 

#define CHIP_TYPE_SX1262        0
#define CHIP_TYPE_SX1261        1

#if defined(TARGET_FF_ARDUINO) || defined(TARGET_FF_ARDUINO_UNO) 
#ifdef TARGET_DISCO_L072CZ_LRWAN1
    /* Type1SJ */
    SPI spi(PB_15, PB_14, PB_13); // mosi, miso, sclk
                   //spi, nss, busy, dio1
    SX126x Radio::radio(spi, PB_12, PC_2, PB_0);

    DigitalOut antswPower(PA_15);
    const uint8_t chipType = CHIP_TYPE_SX1262;
    #define PINNAME_NRST            PB_1
    void Radio::chipModeChange() { }
#else
    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 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_DISCO_L072CZ_LRWAN1 */
#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;
unsigned Radio::tcxoDelayTicks;

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);
    printf("%06x:%u->", d ,d);
    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);
    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_*
    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);
        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);
    }

    pa_config_buf[2] = chipType;    /* auto-detect device from pin pull */
    radio.xfer(OPCODE_SET_PA_CONFIG, 4, 0, pa_config_buf);
}

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.SetDIO2AsRfSwitchCtrl(1);
    antswPower = 1;
    radio.xfer(OPCODE_SET_TX_CARRIER, 0, 0, NULL);
}

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

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

    radio.SetDIO2AsRfSwitchCtrl(1);
    antswPower = 1;
    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) {
        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)
        printf("%d", PwrCtrl.bits.tx_pwr - 17);
    else
        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
    "40  ", // 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()
{
    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;
    //static uint64_t prev_now;
    static Kernel::Clock::time_point prev_now;
    //us_timestamp_t now = lpt.read_us();
    //uint64_t now = Kernel::get_ms_count();
    Kernel::Clock::time_point now = Kernel::Clock::now();
    

    radio.service();

    if (statusRow > 0 && now-prev_now > 50ms) {
        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) {
            printf("\e[%u;1f", statusRow);  // set (force) cursor to row;column

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

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

            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();
        
    antswPower = 0;
}

/*void Radio::cadDone(bool det)
{
    log_printf("cadDone ");
    if (det)
        printf("CadDetected");

    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();
    radio.SetDIO2AsRfSwitchCtrl(1);

    tcxoDelayTicks = 3200;   // some default timeout for tcxo
}

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;*/
    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_PA_CONFIG, 4, 0, pa_config_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_PA_CONFIG, 4, 0, pa_config_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);
    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);
    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);
    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};

void Radio::ldo_push()
{
    uint8_t buf = 0;
    radio.xfer(OPCODE_SET_REGULATOR_MODE, 1, 0, &buf);
    log_printf("-> LDO\r\n");
}

const button_item_t Radio::ldo_item = { _ITEM_BUTTON, "LDO", ldo_push };

void Radio::dcdc_push()
{
    uint8_t buf = 1;
    radio.xfer(OPCODE_SET_REGULATOR_MODE, 1, 0, &buf);
    log_printf("-> DC-DC\r\n");
}

const button_item_t Radio::dcdc_item = { _ITEM_BUTTON, "DCDC", dcdc_push };

static const char* const rxfe_pms[] = {
    "LP  0dB",
    "HP1 2dB",
    "HP2 4dB",
    "HP3 6dB",
    NULL
};

unsigned Radio::rxfe_pm_read(bool)
{
    AgcSensiAdj_t agcs;
    agcs.octet = radio.readReg(REG_ADDR_AGC_SENSI_ADJ, 1);
    return agcs.bits.power_mode;
}

menuMode_e Radio::rxfe_pm_write(unsigned sidx)
{
    AgcSensiAdj_t agcs;
    agcs.octet = radio.readReg(REG_ADDR_AGC_SENSI_ADJ, 1);
    agcs.bits.power_mode = sidx;
    radio.writeReg(REG_ADDR_AGC_SENSI_ADJ, agcs.octet, 1);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::rxfe_pm_item = { _ITEM_DROPDOWN, rxfe_pms, rxfe_pms, rxfe_pm_read, rxfe_pm_write};

#ifdef MEMSCAN
#define SHADOW_SIZE     0x9ff
uint8_t Radio::shadow_read[SHADOW_SIZE];

void Radio::memread_push()
{
    unsigned addr;
    for (addr = 0; addr < SHADOW_SIZE; addr++) {
        shadow_read[addr] = radio.readReg(addr, 1);
    }
    log_printf("memread\r\n");
}

const button_item_t Radio::memread_item = { _ITEM_BUTTON, "MRead", memread_push };

void Radio::memcmp_push()
{
    unsigned addr;
    uint8_t r;
    for (addr = 0; addr < SHADOW_SIZE; addr++) {
        r = radio.readReg(addr, 1);
        if (shadow_read[addr] != r) {
            log_printf("%03x: %02x -> %02x\r\n", addr, shadow_read[addr], r);
        }
    }
    log_printf("memcmpDone\r\n");
}

const button_item_t Radio::memcmp_item = { _ITEM_BUTTON, "MCmp", memcmp_push };
#endif /* MEMSCAN */

void to_big_endian24(uint32_t in, uint8_t *out)
{
    out[2] = in & 0xff;
    in >>= 8;
    out[1] = in & 0xff;
    in >>= 8;
    out[0] = in & 0xff;
}

void Radio::tcxo_volts_print(void)
{
    // yyy;
}

bool Radio::tcxo_volts_write(const char *txt)
{
    uint8_t buf[4];
    float volts;
    sscanf(txt, "%f", &volts);
    if (volts > 3.15)
        buf[0] = 7;  // 3.3v
    else if (volts > 2.85)
        buf[0] = 6; // 3.0v
    else if (volts > 2.55)
        buf[0] = 5; // 2.7v
    else if (volts > 2.3)
        buf[0] = 4; // 2.4v
    else if (volts > 2.3)
        buf[0] = 3; // 2.2v
    else if (volts > 2.0)
        buf[0] = 2; // 1.8v
    else if (volts > 1.65)
        buf[0] = 1; // 1.7v
    else
        buf[0] = 0; // 1.6v

    to_big_endian24(tcxoDelayTicks, buf+1);

    radio.xfer(OPCODE_SET_DIO3_AS_TCXO_CTRL, 4, 0, buf);
    log_printf("set txco %u, %u\r\n", buf[0], tcxoDelayTicks);
    return false;
}

const value_item_t Radio::tcxo_volts_item = { _ITEM_VALUE, 3, tcxo_volts_print, tcxo_volts_write};

void Radio::tcxo_delay_print(void)
{
    printf("%u", tcxoDelayTicks / 64);
}


bool Radio::tcxo_delay_write(const char *txt)
{
    unsigned ms;
    sscanf(txt, "%u", &ms);
    tcxoDelayTicks = ms * 64;
    return false;
}

const value_item_t Radio::tcxo_delay_item = { _ITEM_VALUE, 5, tcxo_delay_print, tcxo_delay_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 },

    { {FIRST_CHIP_MENU_ROW+1,  1},  NULL,   &ldo_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1,  5},  NULL,   &dcdc_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 12},  "rxfe power:",   &rxfe_pm_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 35},  "tcxoVolts:",   &tcxo_volts_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 50},  "tcxoDelay(ms):",   &tcxo_delay_item, FLAG_MSGTYPE_ALL },
#ifdef MEMSCAN
    { {LAST_CHIP_MENU_ROW, 1},  NULL,   &memread_item, FLAG_MSGTYPE_ALL },
    { {LAST_CHIP_MENU_ROW, 10},  NULL,   &memcmp_item, FLAG_MSGTYPE_ALL },
#endif /* MEMSCAN */

    { {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;
    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()
{
    unsigned val = radio.readReg(REG_ADDR_LORA_PREAMBLE_SYMBNB, 2);
    ppLORA.lora.PreambleLengthHi = val >> 8;
    ppLORA.lora.PreambleLengthLo = val;
    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;
    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};

bool Radio::lora_sdmode_read()
{
    sdCfg0_t sdcfg;
    sdcfg.octet = radio.readReg(REG_ADDR_SDCFG0, 1);
    return sdcfg.bits.sd_mode;
}

bool Radio::lora_sdmode_push()
{
    sdCfg0_t sdcfg;
    sdcfg.octet = radio.readReg(REG_ADDR_SDCFG0, 1);
    sdcfg.bits.sd_mode ^= 1;
    radio.writeReg(REG_ADDR_SDCFG0, sdcfg.octet, 1);
    return sdcfg.bits.sd_mode;
}

const toggle_item_t Radio::lora_sdmode_item = { _ITEM_TOGGLE, "sd_mode", NULL, lora_sdmode_read, lora_sdmode_push};

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);
    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);
    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];
    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+2,  1},   NULL,      &lora_bw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 12},  "sf:",      &lora_sf_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 20},  "cr:",      &lora_cr_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 30},   NULL, &lora_ppmOffset_item, FLAG_MSGTYPE_ALL },

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

    { {FIRST_CHIP_MENU_ROW+4,  1},          NULL,        &lora_cad_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4,  5},    "symbols:",   &lora_cadsymbs_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 20}, "peak/noise:", &lora_cadpnratio_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 35},        "min:",     &lora_cadmin_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 45},       "exit:",    &lora_cadexit_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 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;

    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);
    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
    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
    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) {
        printf("%02x", (unsigned)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()
{
    printf("%02x", (unsigned)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()
{
    printf("%02x", (unsigned)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()
{
    printf("%04x", (unsigned)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()
{
    printf("%04x", (unsigned)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);
    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+2,  1},    "bps:", &gfsk_bitrate_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 15},    "bt:",       &gfsk_bt_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 23},   "rxbw:",    &gfsk_rxbw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 39},   "fdev:",    &gfsk_fdev_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 53},      NULL,  &gfsk_fixLen_item, FLAG_MSGTYPE_ALL },

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

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

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

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

    { {FIRST_CHIP_MENU_ROW+7,  1},          NULL, &gfsk_white_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+7,  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 */