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

Committer:
dudmuck
Date:
2021-09-16
Revision:
14:14b9e1c08bfc
Parent:
13:8ce61a1897ab

File content as of revision 14:14b9e1c08bfc:

#include "radio.h"
#ifdef SX127x_H 

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

const RadioEvents_t* Radio::RadioEvents;
LowPowerTimer Radio::lpt;
uint8_t Radio::bw_idx;

RegPaRamp_t Radio::RegPaRamp;

const char* opModes[] = {
    "SLEEP    ", // 0
    "STANDBY  ", // 1
    "FS_TX    ", // 2
    "TX       ", // 3
    "FS_RX    ", // 4
    "RX       ", // 5
    "RX_SINGLE", // 6
    "CAD      "  // 7
};

const char* Radio::tx_ramp_strs[] = {
    "3400", // 0
    "2000", // 1
    "1000", // 2
    "500 ", // 3
    "250 ", // 4
    "125 ", // 5
    "100 ", // 6
    "62  ", // 7
    "50  ", // 8
    "40  ", // 9
    "31  ", // 10
    "25  ", // 11
    "20  ", // 12
    "15  ", // 13
    "12  ", // 14
    "10  ", // 15
    NULL
};

unsigned Radio::tx_ramp_read(bool fw)
{
    RegPaRamp.octet = radio.read_reg(REG_PARAMP);
    return RegPaRamp.bits.PaRamp;
}

menuMode_e Radio::tx_ramp_write(unsigned val)
{
    RegPaRamp.octet = radio.read_reg(REG_PARAMP);

    RegPaRamp.bits.PaRamp = val;
    radio.write_reg(REG_PARAMP, RegPaRamp.octet);

    return MENUMODE_REDRAW;
}

const char* const Radio::pktType_strs[] = {
    "FSK ", // 0
    "OOK ", // 1
    "LORA", // 2
    NULL
};

unsigned Radio::pktType_read(bool fw)
{
    radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
    if (radio.RegOpMode.bits.LongRangeMode)
        return 2;
    else
        return radio.RegOpMode.bits.ModulationType;
}

menuMode_e Radio::pktType_write(unsigned idx)
{
    if (idx == 2) {
        if (!radio.RegOpMode.bits.LongRangeMode) {
            /* to lora */
            radio.set_opmode(RF_OPMODE_SLEEP);
            radio.RegOpMode.bits.LongRangeMode = 1;
            wait_us(1000);
            radio.write_reg(REG_OPMODE, radio.RegOpMode.octet);
        }
    } else {
        if (radio.RegOpMode.bits.LongRangeMode) {
            /* from lora */
            radio.set_opmode(RF_OPMODE_SLEEP);
            radio.RegOpMode.bits.LongRangeMode = 0;
            wait_us(1000);
            radio.write_reg(REG_OPMODE, radio.RegOpMode.octet);
        }

        if (radio.RegOpMode.bits.ModulationType != idx) {
            radio.RegOpMode.bits.ModulationType = idx;
            radio.write_reg(REG_OPMODE, radio.RegOpMode.octet);
        }
    }

    return MENUMODE_REINIT_MENU;
}

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

void Radio::clearIrqFlags()
{
    if (radio.RegOpMode.bits.LongRangeMode) {
        radio.write_reg(REG_LR_IRQFLAGS, 0xff); // clear flags in radio
    } else {
        radio.write_reg(REG_FSK_IRQFLAGS1, 0x0b);
        radio.write_reg(REG_FSK_IRQFLAGS2, 0x11);
    }
}

void Radio::readChip()
{
}

uint8_t Radio::get_payload_length()
{
    if (radio.RegOpMode.bits.LongRangeMode) {
        lora.RegPayloadLength = radio.read_reg(REG_LR_PAYLOADLENGTH);
        return lora.RegPayloadLength;
    } else {
        fsk.RegPktConfig2.word = radio.read_u16(REG_FSK_PACKETCONFIG2);
        return fsk.RegPktConfig2.bits.PayloadLength;
    }
}

void Radio::set_payload_length(uint8_t len)
{
    if (radio.RegOpMode.bits.LongRangeMode) {
        lora.RegPayloadLength = len;
        radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
    } else {
        fsk.RegPktConfig2.bits.PayloadLength = len;
        radio.write_u16(REG_FSK_PACKETCONFIG2, fsk.RegPktConfig2.word);
    }
}

void Radio::Rx()
{
    if (radio.RegOpMode.bits.LongRangeMode)
        lora.start_rx(RF_OPMODE_RECEIVER);
    else
        fsk.start_rx();
}

void Radio::txPkt()
{
    if (radio.RegOpMode.bits.LongRangeMode) {
        lora.RegPayloadLength = radio.read_reg(REG_LR_PAYLOADLENGTH);
        lora.start_tx(lora.RegPayloadLength);
    } else {
        fsk.RegPktConfig2.word = radio.read_u16(REG_FSK_PACKETCONFIG2);
        //log_printf("fsk payLen %u\r\n", fsk.RegPktConfig2.bits.PayloadLength);
        fsk.start_tx(fsk.RegPktConfig2.bits.PayloadLength);
    }
}

void Radio::tx_carrier()
{
    radio.set_opmode(RF_OPMODE_SLEEP);
    fsk.enable(false);
    radio.write_u16(REG_FSK_FDEVMSB, 0);
    radio.write_u16(REG_FSK_PREAMBLEMSB, 0xffff);
    fsk.start_tx(8);
}

void Radio::tx_preamble()
{
    if (radio.RegOpMode.bits.LongRangeMode) {
        radio.write_u16(REG_LR_PREAMBLEMSB, 0xffff);
        lora.start_tx(8);
    } else {
        radio.write_u16(REG_FSK_PREAMBLEMSB, 0xffff);
        fsk.start_tx(8);
    }
}

bool Radio::service(int8_t statusRow)
{
    bool ret = false;
    static RegIrqFlags1_t prevRegIrqFlags1;
    static RegIrqFlags2_t prevRegIrqFlags2;
    static us_timestamp_t prev_now;
    us_timestamp_t now = lpt.read_us();

    if (radio.RegOpMode.bits.LongRangeMode) {
        const float bws[] = {7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250, 500};
        int32_t est_freq_error;
        int idx, hz;
        service_action_e act = lora.service();
        switch (act) {
            case SERVICE_READ_FIFO:
                est_freq_error = radio.read_reg(REG_LR_TEST28);
                est_freq_error <<= 8;
                est_freq_error += radio.read_reg(REG_LR_TEST29);
                est_freq_error <<= 8;
                est_freq_error += radio.read_reg(REG_LR_TEST2A);

                if (est_freq_error & 0x80000)
                    est_freq_error |= 0xfff00000;   // extend sign from 20bit to 32bit

                //log_printf("est_freq_error:%08x\r\n", est_freq_error);
                if (radio.type == SX1272)
                    idx = bw_idx + 7;
                else
                    idx = bw_idx;

                hz = est_freq_error * -0.524288 * bws[idx] / 500;
                log_printf("hz:%d\r\n", hz);

                RadioEvents->RxDone(lora.RegRxNbBytes, lora.get_pkt_rssi(), lora.RegPktSnrValue / 4.0);
                break;
            case SERVICE_TX_DONE:
                if (RadioEvents->TxDone_botHalf)
                    RadioEvents->TxDone_botHalf();
                break;
            case SERVICE_ERROR:
            case SERVICE_NONE:
                break;
        }

        if (radio.RegOpMode.bits.Mode == RF_OPMODE_CAD) {
            if (radio.dio1 || radio.dio0) {
                RegIrqFlags_t irqFlags;
                irqFlags.octet = 0;
                log_printf("Cad: ");
                if (radio.dio0) {
                    printf("Done ");
                    radio.RegOpMode.bits.Mode = RF_OPMODE_STANDBY;
                    irqFlags.bits.CadDone = 1;
                }
                if (radio.dio1)  {
                    printf("Detected");
                    irqFlags.bits.CadDetected = 1;
                }
                printf("\r\n");
                radio.write_reg(REG_LR_IRQFLAGS, irqFlags.octet);
            }
        }
    } else {
        service_action_e act = fsk.service();
        switch (act) {
            case SERVICE_READ_FIFO:
                /*if (fsk.RegRxConfig.bits.AfcAutoOn) {
                    printf("%dHz ", (int)(FREQ_STEP_HZ * fsk.RegAfcValue));
                    if (rssi != 0) {
                        printf("pkt:-%.1fdBm ", rssi / 2.0);
                        rssi = 0;
                    }
                }*/
                if (fsk.RegRxConfig.bits.AfcAutoOn)
                    log_printf("%dHz\r\n", (int)(FREQ_STEP_HZ * fsk.RegAfcValue));

                RadioEvents->RxDone(fsk.rx_buf_length, /*TODO rssi*/0, 0);
                break;
            case SERVICE_TX_DONE:
                if (RadioEvents->TxDone_botHalf)
                    RadioEvents->TxDone_botHalf();
                break;
            case SERVICE_ERROR:
            case SERVICE_NONE:
                break;
        }

        if (statusRow > 0 && now-prev_now > 50000) {
            RegIrqFlags1_t RegIrqFlags1;
            RegIrqFlags2_t RegIrqFlags2;

            RegIrqFlags1.octet = radio.read_reg(REG_FSK_IRQFLAGS1);
            RegIrqFlags2.octet = radio.read_reg(REG_FSK_IRQFLAGS2);
            prev_now = now;

            if (RegIrqFlags1.octet != prevRegIrqFlags1.octet || RegIrqFlags2.octet != prevRegIrqFlags2.octet) {

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

                if (RegIrqFlags1.bits.ModeReady)
                    printf("ModeReady ");
                if (RegIrqFlags1.bits.RxReady)
                    printf("RxReady ");
                if (RegIrqFlags1.bits.TxReady)
                    printf("TxReady ");
                if (RegIrqFlags1.bits.PllLock)
                    printf("PllLock ");
                if (RegIrqFlags1.bits.Rssi)
                    printf("Rssi ");
                if (RegIrqFlags1.bits.Timeout)
                    printf("Timeout ");
                if (RegIrqFlags1.bits.PreambleDetect)
                    printf("PreambleDetect ");
                if (RegIrqFlags1.bits.SyncAddressMatch)
                    printf("SyncAddressMatch ");

                printf(" | ");
                if (RegIrqFlags2.bits.FifoFull)
                    printf("FifoFull ");
                if (RegIrqFlags2.bits.FifoEmpty)
                    printf("FifoEmpty ");
                if (RegIrqFlags2.bits.FifoLevel)
                    printf("FifoLevel ");
                if (RegIrqFlags2.bits.FifoOverrun)
                    printf("FifoOverrun ");
                if (RegIrqFlags2.bits.PacketSent)
                    printf("PacketSent ");
                if (RegIrqFlags2.bits.PayloadReady)
                    printf("PayloadReady ");
                if (RegIrqFlags2.bits.CrcOk)
                    printf("CrcOk ");
                if (RegIrqFlags2.bits.LowBat)
                    printf("LowBat ");

                prevRegIrqFlags1.octet = RegIrqFlags1.octet;
                prevRegIrqFlags2.octet = RegIrqFlags2.octet;

                printf("\e[K");
                ret = true;
            } // ..if irq flag changed
        } // ..if (++cnt > X)
    } // ..!radio.RegOpMode.bits.LongRangeMode

    return ret;
}

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

const menu_t* Radio::get_modem_sub_menu()
{
    radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
    if (radio.RegOpMode.bits.LongRangeMode) {
        return NULL;
    } else {
        if (radio.RegOpMode.bits.ModulationType == 1) {
            fsk.RegOokPeak.octet = radio.read_reg(REG_FSK_OOKPEAK);
            if (fsk.RegOokPeak.bits.OokThreshType == 0) {
                return ook_fixed_menu;
            } else if (fsk.RegOokPeak.bits.OokThreshType == 1) {
                return ook_peak_menu;
            } else if (fsk.RegOokPeak.bits.OokThreshType == 2) {
                return ook_average_menu;
            }
        } else
            return NULL;
    }

    return NULL;
}

const menu_t* Radio::get_modem_menu()
{
    radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);

    if (radio.RegOpMode.bits.LongRangeMode) {
        return lora_menu;
    } else {
        if (radio.RegOpMode.bits.ModulationType == 0)
            return fsk_menu;
        else {
            return ook_menu;
        }
    }
}

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);
    //log_printf("scanned %u from \"%s\"\r\n", len, txt);

    set_payload_length(len);

    return false;
}

const char* const Radio::opmode_status_strs[] = {
    "SLEEP    ", // 0
    "STANDBY  ", // 1
    "FS_TX    ", // 2
    "TX       ", // 3
    "FS_RX    ", // 4
    "RX       ", // 5
    "RX_SINGLE", // 6
    "CAD      ", // 7
    NULL
};

const char* const Radio::opmode_select_strs[] = {
    "SLEEP    ", // 0
    "STANDBY  ", // 1
    "FS_TX    ", // 2
    "TX       ", // 3
    "FS_RX    ", // 4
    "RX       ", // 5
    "RX_SINGLE", // 6
    "CAD      ", // 7
    NULL
};

unsigned Radio::opmode_read(bool forWriting)
{
    radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
    return radio.RegOpMode.bits.Mode;
}

menuMode_e Radio::opmode_write(unsigned sel)
{
    radio.RegOpMode.bits.Mode = sel;
    radio.write_reg(REG_OPMODE, radio.RegOpMode.octet);

    return MENUMODE_REDRAW;
}

void Radio::ocp_print(void)
{
    unsigned i;

    radio.RegOcp.octet = radio.read_reg(REG_OCP);
    if (radio.RegOcp.bits.OcpTrim < 16)
        i = 45 + (5 * radio.RegOcp.bits.OcpTrim);
    else if (radio.RegOcp.bits.OcpTrim < 28)
        i = (10 * radio.RegOcp.bits.OcpTrim) - 30;
    else
        i = 240;

    printf("%u", i); 
}

bool Radio::ocp_write(const char* txt)
{
    unsigned i;

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

    if (i < 130)
        radio.RegOcp.bits.OcpTrim = (i - 45) / 5;
    else
        radio.RegOcp.bits.OcpTrim = (i + 30) / 10;

    radio.write_reg(REG_OCP, radio.RegOcp.octet);

    return false;
}

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

static const char* const lora_bws_1276[] = {
    "  7.8KHz", // 0
    " 10.4KHz", // 1
    " 15.6KHz", // 2
    " 20.8KHz", // 3
    "31.25KHz", // 4
    " 41.7KHz", // 5
    " 62.5KHz", // 6
    "  125KHz", // 7
    "  250KHz", // 8
    "  500KHz", // 9
    NULL
};
static const char* const lora_bws_1272[] = {
    "125KHz", // 0
    "250KHz", // 1
    "500KHz", // 2
    NULL
};

unsigned Radio::lora_bw_read(bool fw)
{
    bw_idx = lora.getBw();
    return bw_idx;
}

menuMode_e Radio::lora_bw_write(unsigned sidx)
{
    lora.setBw(sidx);
    bw_idx = sidx;

    return MENUMODE_REDRAW;
}

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

void Radio::lora_sf_print()
{
    printf("%u", lora.getSf());
}

bool Radio::lora_sf_write(const char* txt)
{
    unsigned sf;

    sscanf(txt, "%u", &sf);
    lora.setSf(sf);

    return false;
}

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

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

unsigned Radio::lora_cr_read(bool)
{
    return lora.getCodingRate(false);
}

menuMode_e Radio::lora_cr_write(unsigned sidx)
{
    lora.setCodingRate(sidx);

    return MENUMODE_REDRAW;
}

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

void Radio::lora_pblLen_print()
{
    lora.RegPreamble = radio.read_u16(REG_LR_PREAMBLEMSB);
    printf("%u", lora.RegPreamble);
}

bool Radio::lora_pblLen_write(const char* str)
{
    unsigned n;

    sscanf(str, "%u", &n);

    lora.RegPreamble = n;
    radio.write_u16(REG_LR_PREAMBLEMSB, lora.RegPreamble);

    return false;
}

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

static const char* const lora_fixlen[] = {
    "EXPLICIT", // 0
    "IMPLICIT", // 1
    NULL
};

unsigned Radio::lora_fixlen_read(bool f)
{
    if (lora.getHeaderMode())
        return 1;
    else
        return 0;
}

menuMode_e Radio::lora_fixlen_write(unsigned sidx)
{
    lora.setHeaderMode(sidx == 1);   // true = implicit

    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::lora_fixlen_item = { _ITEM_DROPDOWN, lora_fixlen, lora_fixlen, lora_fixlen_read, lora_fixlen_write};

bool Radio::lora_crcon_read()
{
    return lora.getRxPayloadCrcOn();
}

bool Radio::lora_crcon_push()
{
    lora.setRxPayloadCrcOn(!lora.getRxPayloadCrcOn());
    return lora.getRxPayloadCrcOn();
}

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

bool Radio::lora_iqinvTX_read()
{
    lora.RegTest33.octet = radio.read_reg(REG_LR_TEST33);
    return !lora.RegTest33.bits.chirp_invert_tx;
}

bool Radio::lora_iqinvTX_push()
{
    lora.invert_tx(lora.RegTest33.bits.chirp_invert_tx);
    return !lora.RegTest33.bits.chirp_invert_tx;
}

const toggle_item_t Radio::lora_iqinvTX_item = { _ITEM_TOGGLE, "iqInvTX", NULL, lora_iqinvTX_read, lora_iqinvTX_push};

bool Radio::lora_iqinvRX_read()
{
    lora.RegTest33.octet = radio.read_reg(REG_LR_TEST33);
    return lora.RegTest33.bits.invert_i_q;
}

bool Radio::lora_iqinvRX_push()
{
    lora.invert_rx(!lora.RegTest33.bits.invert_i_q);
    lora.RegTest33.octet = radio.read_reg(REG_LR_TEST33);
    return lora.RegTest33.bits.invert_i_q;
}

const toggle_item_t Radio::lora_iqinvRX_item = { _ITEM_TOGGLE, "iqInvRX", NULL, lora_iqinvRX_read, lora_iqinvRX_push};

void Radio::lora_ppg_print()
{
    printf("%02x", radio.read_reg(REG_LR_SYNC_BYTE));
}

bool Radio::lora_ppg_write(const char* str)
{
    unsigned ppg;
    sscanf(str, "%x", &ppg);

    radio.write_reg(REG_LR_SYNC_BYTE, ppg);

    return false;
}

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

void Radio::cadrx_push()
{
    if (radio.RegDioMapping1.bits.Dio0Mapping != 2 || radio.RegDioMapping1.bits.Dio1Mapping != 2) {
        radio.RegDioMapping1.bits.Dio0Mapping = 2;    // DIO0 to CadDone
        radio.RegDioMapping1.bits.Dio1Mapping = 2;    // DIO1 to CadDetected
        radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
    }

    radio.set_opmode(RF_OPMODE_CAD);
}

const button_item_t Radio::lora_cadrx_item = { _ITEM_BUTTON, "CADRX", cadrx_push };

bool Radio::lora_ppm_offset_read()
{
    if (radio.type == SX1276) {
        lora.RegModemConfig3.octet = radio.read_reg(REG_LR_MODEMCONFIG3);
        return lora.RegModemConfig3.sx1276bits.LowDataRateOptimize;
    } else if (radio.type == SX1272) {
        lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
        return lora.RegModemConfig.sx1272bits.LowDataRateOptimize;
    } else
        return false;
}

bool Radio::lora_ppm_offset_push()
{
    if (radio.type == SX1276) {
        lora.RegModemConfig3.octet = radio.read_reg(REG_LR_MODEMCONFIG3);
        lora.RegModemConfig3.sx1276bits.LowDataRateOptimize ^= 1;
        radio.write_reg(REG_LR_MODEMCONFIG3, lora.RegModemConfig3.octet);
        return lora.RegModemConfig3.sx1276bits.LowDataRateOptimize;
    } else if (radio.type == SX1272) {
        lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
        lora.RegModemConfig.sx1272bits.LowDataRateOptimize ^= 1;
        radio.write_reg(REG_LR_MODEMCONFIG, lora.RegModemConfig.octet);
        return lora.RegModemConfig.sx1272bits.LowDataRateOptimize;
    } else
        return false;
}

const toggle_item_t Radio::lora_ppm_offset_item = { _ITEM_TOGGLE, "LowDatarateOptimize", NULL, lora_ppm_offset_read, lora_ppm_offset_push};

const menu_t Radio::lora_menu[] = {
    { {FIRST_CHIP_MENU_ROW, 22},     "bw:", &lora_bw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 35},      "sf:",   &lora_sf_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 42},      "cr:",   &lora_cr_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 50}, "PreambleLength:", &lora_pblLen_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+1, 1},    NULL, &lora_fixlen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 10},   NULL, &lora_crcon_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 20},   NULL, &lora_iqinvTX_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 30},   NULL, &lora_iqinvRX_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 40}, "ppg:", &lora_ppg_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 48},   NULL, &lora_ppm_offset_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2, 1},    NULL, &lora_cadrx_item, FLAG_MSGTYPE_ALL },

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

void Radio::fsk_ook_bps_print(void)
{
    printf("%lu", fsk.get_bitrate());
}

bool Radio::fsk_ook_bps_write(const char* txt)
{
    unsigned bps;

    sscanf(txt, "%u", &bps);
    fsk.set_bitrate(bps);

    return false;
}

const value_item_t Radio::fsk_ook_bitrate_item = { _ITEM_VALUE, 8, fsk_ook_bps_print, fsk_ook_bps_write };

void Radio::gfsk_fdev_print(void)
{
    printf("%lu", fsk.get_tx_fdev_hz());

}

bool Radio::gfsk_fdev_write(const char* txt)
{
    unsigned hz;

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

    fsk.set_tx_fdev_hz(hz);

    return false;
}

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

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

const char* const ook_bts[] = {
    "off      ", // 0
    "bitRate  ", // 1
    "2*bitRate", // 2
    NULL
};

unsigned Radio::bt_read(bool forWriting)
{
    if (radio.type == SX1276) {
        RegPaRamp.octet = radio.read_reg(REG_PARAMP);
        return RegPaRamp.bits.ModulationShaping;
    } else if (radio.type == SX1272) {
        radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
        return radio.RegOpMode.bits.ModulationShaping;
    }
    return 3;
}

menuMode_e Radio::bt_write(unsigned sel)
{
    if (radio.type == SX1276) {
        RegPaRamp.bits.ModulationShaping = sel;
        radio.write_reg(REG_PARAMP, RegPaRamp.octet);
    } else if (radio.type == SX1272) {
        radio.RegOpMode.bits.ModulationShaping = sel;
        radio.write_reg(REG_OPMODE, radio.RegOpMode.octet);
    }
    return MENUMODE_REDRAW;
} 

const dropdown_item_t Radio::gfsk_bt_item = { _ITEM_DROPDOWN, gfsk_bts, gfsk_bts, bt_read, bt_write};
const dropdown_item_t Radio::ook_bt_item = { _ITEM_DROPDOWN, ook_bts, ook_bts, bt_read, bt_write};

bool Radio::paSelect_read()
{
    radio.RegPaConfig.octet = radio.read_reg(REG_PACONFIG);
    return radio.RegPaConfig.bits.PaSelect;
}

bool Radio::paSelect_push()
{
    radio.RegPaConfig.bits.PaSelect ^= 1; 
    radio.write_reg(REG_PACONFIG, radio.RegPaConfig.octet);
    return radio.RegPaConfig.bits.PaSelect;
}

const toggle_item_t Radio::paSelect_item = { _ITEM_TOGGLE,
    "RFO     ", // 0
    "PA_BOOST", // 1
    paSelect_read, paSelect_push
};

const char* const rxbws[] = {
    "  2.6", // 0
    "  3.1", // 1
    "  3.9", // 2
    "  5.2", // 3
    "  6.3", // 4
    "  7.8", // 5
    " 10.4", // 6
    " 12.5", // 7
    " 15.6", // 8
    " 20.8", // 9
    " 25.0", // 10
    " 31.3", // 11
    " 41.7", // 12
    " 50.0", // 13
    " 62.5", // 14
    " 83.3", // 15
    "100.0", // 16
    "125.0", // 17
    "166.7", // 18
    "200.0", // 19
    "250.0", // 20
    NULL
};

unsigned Radio::bw_read(uint8_t regAddr)
{
    RegRxBw_t reg_bw;

    reg_bw.octet = radio.read_reg(regAddr);

    switch (reg_bw.bits.Exponent) {
        case 7:
            if (reg_bw.bits.Mantissa == 2)
                return 0;
            if (reg_bw.bits.Mantissa == 1)
                return 1;
            if (reg_bw.bits.Mantissa == 0)
                return 2;
            break;
        case 6:
            if (reg_bw.bits.Mantissa == 2)
                return 3;
            if (reg_bw.bits.Mantissa == 1)
                return 4;
            if (reg_bw.bits.Mantissa == 0)
                return 5;
            break;
        case 5:
            if (reg_bw.bits.Mantissa == 2)
                return 6;
            if (reg_bw.bits.Mantissa == 1)
                return 7;
            if (reg_bw.bits.Mantissa == 0)
                return 8;
            break;
        case 4:
            if (reg_bw.bits.Mantissa == 2)
                return 9;
            if (reg_bw.bits.Mantissa == 1)
                return 10;
            if (reg_bw.bits.Mantissa == 0)
                return 11;
            break;
        case 3:
            if (reg_bw.bits.Mantissa == 2)
                return 12;
            if (reg_bw.bits.Mantissa == 1)
                return 13;
            if (reg_bw.bits.Mantissa == 0)
                return 14;
            break;
        case 2:
            if (reg_bw.bits.Mantissa == 2)
                return 15;
            if (reg_bw.bits.Mantissa == 1)
                return 16;
            if (reg_bw.bits.Mantissa == 0)
                return 17;
            break;
        case 1:
            if (reg_bw.bits.Mantissa == 2)
                return 18;
            if (reg_bw.bits.Mantissa == 1)
                return 19;
            if (reg_bw.bits.Mantissa == 0)
                return 20;
            break;
    }

    return 21;
}

unsigned Radio::rxbw_read(bool)
{
    return bw_read(REG_FSK_RXBW);
}

unsigned Radio::afcbw_read(bool)
{
    return bw_read(REG_FSK_AFCBW);
}

void Radio::bw_write(unsigned sidx, uint8_t regAddr)
{
    RegRxBw_t reg_bw;

    reg_bw.octet = radio.read_reg(regAddr);

    switch (sidx) {
        case 0:
            reg_bw.bits.Mantissa = 2;
            reg_bw.bits.Exponent = 7;
            break;
        case 1:
            reg_bw.bits.Mantissa = 1;
            reg_bw.bits.Exponent = 7;
            break;
        case 2:
            reg_bw.bits.Mantissa = 0;
            reg_bw.bits.Exponent = 7;
            break;
        case 3:
            reg_bw.bits.Mantissa = 2;
            reg_bw.bits.Exponent = 6;
            break;
        case 4:
            reg_bw.bits.Mantissa = 1;
            reg_bw.bits.Exponent = 6;
            break;
        case 5:
            reg_bw.bits.Mantissa = 0;
            reg_bw.bits.Exponent = 6;
            break;
        case 6:
            reg_bw.bits.Mantissa = 2;
            reg_bw.bits.Exponent = 5;
            break;
        case 7:
            reg_bw.bits.Mantissa = 1;
            reg_bw.bits.Exponent = 5;
            break;
        case 8:
            reg_bw.bits.Mantissa = 0;
            reg_bw.bits.Exponent = 5;
            break;
        case 9:
            reg_bw.bits.Mantissa = 2;
            reg_bw.bits.Exponent = 4;
            break;
        case 10:
            reg_bw.bits.Mantissa = 1;
            reg_bw.bits.Exponent = 4;
            break;
        case 11:
            reg_bw.bits.Mantissa = 0;
            reg_bw.bits.Exponent = 4;
            break;
        case 12:
            reg_bw.bits.Mantissa = 2;
            reg_bw.bits.Exponent = 3;
            break;
        case 13:
            reg_bw.bits.Mantissa = 1;
            reg_bw.bits.Exponent = 3;
            break;
        case 14:
            reg_bw.bits.Mantissa = 0;
            reg_bw.bits.Exponent = 3;
            break;
        case 15:
            reg_bw.bits.Mantissa = 2;
            reg_bw.bits.Exponent = 2;
            break;
        case 16:
            reg_bw.bits.Mantissa = 1;
            reg_bw.bits.Exponent = 2;
            break;
        case 17:
            reg_bw.bits.Mantissa = 0;
            reg_bw.bits.Exponent = 2;
            break;
        case 18:
            reg_bw.bits.Mantissa = 2;
            reg_bw.bits.Exponent = 1;
            break;
        case 19:
            reg_bw.bits.Mantissa = 1;
            reg_bw.bits.Exponent = 1;
            break;
        case 20:
            reg_bw.bits.Mantissa = 0;
            reg_bw.bits.Exponent = 1;
            break;
    }

    radio.write_reg(regAddr, reg_bw.octet);
}

menuMode_e Radio::rxbw_write(unsigned sidx)
{
    bw_write(sidx, REG_FSK_RXBW);
    return MENUMODE_REDRAW;
}

menuMode_e Radio::afcbw_write(unsigned sidx)
{
    bw_write(sidx, REG_FSK_AFCBW);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::rxbw_item = { _ITEM_DROPDOWN, rxbws, rxbws, rxbw_read, rxbw_write};
const dropdown_item_t Radio::afcbw_item = { _ITEM_DROPDOWN, rxbws, rxbws, afcbw_read, afcbw_write};

void Radio::pblLen_print()
{
    printf("%u", radio.read_u16(REG_FSK_PREAMBLEMSB));
}

bool Radio::pblLen_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    radio.write_u16(REG_FSK_PREAMBLEMSB, n);
    return false;
}

const value_item_t Radio::pblLen_item = { _ITEM_VALUE, 6, pblLen_print, pblLen_write};


const char* const rxTriggers[] = {
    "off          ", // 0
    "RSSI         ", // 1
    "Preamble     ", // 2
    "RSSI+Preamble", // 3
    NULL
};

unsigned Radio::rxTrigger_read(bool fw)
{
    fsk.RegRxConfig.octet = radio.read_reg(REG_FSK_RXCONFIG);
    return fsk.RegRxConfig.bits.RxTrigger;
}

menuMode_e Radio::rxTrigger_write(unsigned sidx)
{
    fsk.RegRxConfig.bits.RxTrigger = sidx;
    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::rxTrigger_item = { _ITEM_DROPDOWN, rxTriggers, rxTriggers, rxTrigger_read, rxTrigger_write};

bool Radio::AgcAutoOn_read()
{
    fsk.RegRxConfig.octet = radio.read_reg(REG_FSK_RXCONFIG);
    return fsk.RegRxConfig.bits.AgcAutoOn;
}

bool Radio::AgcAutoOn_push()
{
    fsk.RegRxConfig.bits.AgcAutoOn ^= 1;
    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet); 
    return fsk.RegRxConfig.bits.AgcAutoOn;
}

bool Radio::AfcAutoOn_read()
{
    fsk.RegRxConfig.octet = radio.read_reg(REG_FSK_RXCONFIG);
    return fsk.RegRxConfig.bits.AfcAutoOn;
}

bool Radio::AfcAutoOn_push()
{
    fsk.RegRxConfig.bits.AfcAutoOn ^= 1;
    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet); 
    return fsk.RegRxConfig.bits.AfcAutoOn;
}

const toggle_item_t Radio::agcautoon_item = { _ITEM_TOGGLE, "AgcAutoOn", NULL, AgcAutoOn_read, AgcAutoOn_push};

const toggle_item_t Radio::afcautoon_item = { _ITEM_TOGGLE, "AfcAutoOn", NULL, AfcAutoOn_read, AfcAutoOn_push};

bool Radio::RestartRxOnCollision_read()
{
    fsk.RegRxConfig.octet = radio.read_reg(REG_FSK_RXCONFIG);
    return fsk.RegRxConfig.bits.RestartRxOnCollision;
}

bool Radio::RestartRxOnCollision_push()
{
    fsk.RegRxConfig.bits.RestartRxOnCollision ^= 1;
    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet); 
    return fsk.RegRxConfig.bits.RestartRxOnCollision;
}

const toggle_item_t Radio::RestartRxOnCollision_item = { _ITEM_TOGGLE,
    "RestartRxOnCollision", NULL,
    RestartRxOnCollision_read, RestartRxOnCollision_push
};

void Radio::RestartRxWithPllLock_push()
{
    fsk.RegRxConfig.bits.RestartRxWithPllLock = 1;
    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);
    fsk.RegRxConfig.bits.RestartRxWithPllLock = 0;
}

const button_item_t Radio::RestartRxWithPllLock_item = { _ITEM_BUTTON,
    "RestartRxWithPllLock",
    RestartRxWithPllLock_push
};

void Radio::RestartRxWithoutPllLock_push()
{
    fsk.RegRxConfig.bits.RestartRxWithoutPllLock = 1;
    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);
    fsk.RegRxConfig.bits.RestartRxWithoutPllLock = 0;
}

const button_item_t Radio::RestartRxWithoutPllLock_item = { _ITEM_BUTTON,
    "RestartRxWithoutPllLock",
    RestartRxWithoutPllLock_push
};

bool Radio::AfcAutoClearOn_read(void)
{
    fsk.RegAfcFei.octet = radio.read_reg(REG_FSK_AFCFEI);
    return fsk.RegAfcFei.bits.AfcAutoClearOn;
}

bool Radio::AfcAutoClearOn_push(void)
{
    fsk.RegAfcFei.bits.AfcAutoClearOn ^= 1;
    radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
    return fsk.RegAfcFei.bits.AfcAutoClearOn;
}

const toggle_item_t Radio::AfcAutoClearOn_item = { _ITEM_TOGGLE, "AfcAutoClearOn", NULL, AfcAutoClearOn_read, AfcAutoClearOn_push};

void Radio::AgcStart_push()
{
    fsk.RegAfcFei.bits.AgcStart = 1;
    radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
    fsk.RegAfcFei.bits.AgcStart = 1;
}

const button_item_t Radio::AgcStart_item = { _ITEM_BUTTON, "AgcStart", AgcStart_push};

void Radio::AfcClear_push()
{
    fsk.RegAfcFei.bits.AfcClear = 1;
    radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
    fsk.RegAfcFei.bits.AfcClear = 0;
}
const button_item_t Radio::AfcClear_item = { _ITEM_BUTTON, "AfcClear", AfcClear_push};

void Radio::syncWord_print(void)
{
    unsigned n, stop;

    fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);

    stop = fsk.RegSyncConfig.bits.SyncSize + 1;
    for (n = 0; n < stop; n++) {
        printf("%02x", radio.read_reg(REG_FSK_SYNCVALUE1+n));
    }
}

bool Radio::syncWord_write(const char* txt)
{
    const char* ptr;
    const char* endPtr;
    unsigned o, n = 0;

    endPtr = txt + strlen(txt);
    for (ptr = txt; sscanf(ptr, "%02x", &o) == 1; ) {
        radio.write_reg(REG_FSK_SYNCVALUE1+n, o);
        n++;
        ptr += 2;
        if (ptr >= endPtr)
            break;
    }

    fsk.RegSyncConfig.bits.SyncSize = n - 1;
    radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);

    return false;
}

const value_item_t Radio::syncWord_item = { _ITEM_VALUE, 17, syncWord_print, syncWord_write};

void Radio::syncSize_print(void)
{
    fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);

    printf("%u", fsk.RegSyncConfig.bits.SyncSize + 1);
}

bool Radio::syncSize_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    if (n > 0) {
        fsk.RegSyncConfig.bits.SyncSize = n - 1;
        radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
    }
    return false;
}

const value_item_t Radio::syncSize_item = { _ITEM_VALUE, 2, syncSize_print, syncSize_write};

bool Radio::SyncOn_read()
{
    fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);
    return fsk.RegSyncConfig.bits.SyncOn;
}

bool Radio::SyncOn_push()
{
    fsk.RegSyncConfig.bits.SyncOn ^= 1;
    radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
    return fsk.RegSyncConfig.bits.SyncOn;
}

const toggle_item_t Radio::syncOn_item = { _ITEM_TOGGLE, "SyncOn", NULL, SyncOn_read, SyncOn_push};

bool Radio::fsk_pktfmt_read()
{
    fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
    return fsk.RegPktConfig1.bits.PacketFormatVariable;
}

bool Radio::fsk_pktfmt_push()
{
    fsk.RegPktConfig1.bits.PacketFormatVariable ^= 1;
    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);
    return fsk.RegPktConfig1.bits.PacketFormatVariable;
}

const toggle_item_t Radio::fskook_pktfmt_item = { _ITEM_TOGGLE,
    "fixed   ",
    "variable",
    fsk_pktfmt_read, fsk_pktfmt_push
};


void Radio::rssiOffset_print(void)
{
    int ro;
    fsk.RegRssiConfig.octet = radio.read_reg(REG_FSK_RSSICONFIG);
    ro = fsk.RegRssiConfig.bits.RssiOffset;
    printf("%d", ro);
}

bool Radio::rssiOffset_write(const char* txt)
{
    int ro;
    sscanf(txt, "%d", &ro);
    fsk.RegRssiConfig.bits.RssiOffset = ro;
    radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
    return false;
}

const value_item_t Radio::rssiOffset_item = { _ITEM_VALUE, 2, rssiOffset_print, rssiOffset_write};

const char* const rssiSmoothings[] = {
    "2  ", // 0
    "4  ", // 1
    "8  ", // 2
    "16 ", // 3
    "32 ", // 4
    "64 ", // 5
    "128", // 6
    "256", // 7
    NULL
};

unsigned Radio::rssiSmoothing_read(bool fw)
{
    fsk.RegRssiConfig.octet = radio.read_reg(REG_FSK_RSSICONFIG);
    return fsk.RegRssiConfig.bits.RssiSmoothing;
}

menuMode_e Radio::rssiSmoothing_write(unsigned sidx)
{
    radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
    fsk.RegRssiConfig.bits.RssiSmoothing = sidx;
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::rssiSmoothing_item = { _ITEM_DROPDOWN, rssiSmoothings, rssiSmoothings, rssiSmoothing_read, rssiSmoothing_write};

bool Radio::dataMode_read()
{
    fsk.RegPktConfig2.word = radio.read_u16(REG_FSK_PACKETCONFIG2);
    return fsk.RegPktConfig2.bits.DataModePacket;
}

bool Radio::dataMode_push()
{
    fsk.RegPktConfig2.bits.DataModePacket ^= 1;
    radio.write_u16(REG_FSK_PACKETCONFIG2, fsk.RegPktConfig2.word);
    return fsk.RegPktConfig2.bits.DataModePacket;
}

const toggle_item_t Radio::dataMode_item = { _ITEM_TOGGLE,
    "continuous",
    "packet    ",
    dataMode_read, dataMode_push
};

bool Radio::bitSyncOn_read(void)
{
    fsk.RegOokPeak.octet = radio.read_reg(REG_FSK_OOKPEAK);
    return fsk.RegOokPeak.bits.BitSyncOn;
}

bool Radio::bitSyncOn_push(void)
{
    fsk.RegOokPeak.bits.BitSyncOn ^= 1;
    radio.write_reg(REG_FSK_OOKPEAK, fsk.RegOokPeak.octet);
    return fsk.RegOokPeak.bits.BitSyncOn;
}

const toggle_item_t Radio::bitSyncOn_item = { _ITEM_TOGGLE,
    "BitSyncOn", NULL,
    bitSyncOn_read, bitSyncOn_push
};

const button_item_t Radio::pdLabel_item = { _ITEM_BUTTON, "PreambleDetector", NULL};

bool Radio::pdOn_read(void)
{
    fsk.RegPreambleDetect.octet = radio.read_reg(REG_FSK_PREAMBLEDETECT);
    return fsk.RegPreambleDetect.bits.PreambleDetectorOn;
}

bool Radio::pdOn_push(void)
{
    fsk.RegPreambleDetect.bits.PreambleDetectorOn ^= 1;
    radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);
    return fsk.RegPreambleDetect.bits.PreambleDetectorOn;
}

const toggle_item_t Radio::pdOn_item = { _ITEM_TOGGLE,
    "On", NULL,
    pdOn_read, pdOn_push
};

const char* const pdsizes[] = {
    "1 byte ", // 0
    "2 bytes", // 1
    "3 bytes", // 2
    NULL
};

unsigned Radio::pdSize_read(bool)
{
    fsk.RegPreambleDetect.octet = radio.read_reg(REG_FSK_PREAMBLEDETECT);
    return fsk.RegPreambleDetect.bits.PreambleDetectorSize;
}

menuMode_e Radio::pdSize_write(unsigned sidx)
{
    fsk.RegPreambleDetect.bits.PreambleDetectorSize = sidx;
    radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::pdSize_item = { _ITEM_DROPDOWN, pdsizes, pdsizes, pdSize_read, pdSize_write};

void Radio::pdTol_print(void)
{
    fsk.RegPreambleDetect.octet = radio.read_reg(REG_FSK_PREAMBLEDETECT);
    printf("%u", fsk.RegPreambleDetect.bits.PreambleDetectorTol);
}

bool Radio::pdTol_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    fsk.RegPreambleDetect.bits.PreambleDetectorTol = n;
    radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);
    return false;
}

const value_item_t Radio::pdTol_item = { _ITEM_VALUE, 3, pdTol_print, pdTol_write};

bool Radio::TxStartCondition_read()
{
    fsk.RegFifoThreshold.octet = radio.read_reg(REG_FSK_FIFOTHRESH);
    return fsk.RegFifoThreshold.bits.TxStartCondition;
}

bool Radio::TxStartCondition_push()
{
    fsk.RegFifoThreshold.bits.TxStartCondition ^= 1;
    radio.write_reg(REG_FSK_FIFOTHRESH, fsk.RegFifoThreshold.octet);
    return fsk.RegFifoThreshold.bits.TxStartCondition;
}

const toggle_item_t Radio::TxStartCondition_item = { _ITEM_TOGGLE,
    "FifoLevel   ", // 0
    "FifoNotEmpty", // 1
    TxStartCondition_read, TxStartCondition_push
};

void Radio::FifoThreshold_print(void)
{
    fsk.RegFifoThreshold.octet = radio.read_reg(REG_FSK_FIFOTHRESH);
    printf("%u", fsk.RegFifoThreshold.bits.FifoThreshold);
}

bool Radio::FifoThreshold_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    fsk.RegFifoThreshold.bits.FifoThreshold = n;
    radio.write_reg(REG_FSK_FIFOTHRESH, fsk.RegFifoThreshold.octet);
    return false;
}

const value_item_t Radio::FifoThreshold_item = { _ITEM_VALUE, 3, FifoThreshold_print, FifoThreshold_write};

const char* const dcFrees[] = {
    "none      ", // 0
    "manchester", // 1
    "whitening ", // 2
    NULL
};

unsigned Radio::dcFree_read(bool fw)
{
    fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
    return fsk.RegPktConfig1.bits.DcFree;
}

menuMode_e Radio::dcFree_write(unsigned sidx)
{
    fsk.RegPktConfig1.bits.DcFree = sidx;
    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet); 
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::dcFree_item = { _ITEM_DROPDOWN, dcFrees, dcFrees, dcFree_read, dcFree_write};

bool Radio::fskook_crcon_read()
{
    fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
    return fsk.RegPktConfig1.bits.CrcOn;
}

bool Radio::fskook_crcon_push()
{
    fsk.RegPktConfig1.bits.CrcOn ^= 1;
    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);

    if (radio.RegOpMode.bits.Mode == RF_OPMODE_RECEIVER)
        fsk.config_dio0_for_pktmode_rx();

    return fsk.RegPktConfig1.bits.CrcOn;
}

const toggle_item_t Radio::fskook_crcon_item = { _ITEM_TOGGLE,
    "CrcOn", NULL,
    fskook_crcon_read, fskook_crcon_push
};

void Radio::rssiThresh_print()
{
    fsk.RegRssiThresh = radio.read_reg(REG_FSK_RSSITHRESH);
    printf("%.1f", fsk.RegRssiThresh / -2.0);
}

bool Radio::rssiThresh_write(const char* txt)
{
    float dbm;
    sscanf(txt, "%f", &dbm);
    fsk.RegRssiThresh = dbm * -2.0;
    radio.write_reg(REG_FSK_RSSITHRESH, fsk.RegRssiThresh);
    return false;
}

const value_item_t Radio::rssiThresh_item = { _ITEM_VALUE, 3, rssiThresh_print, rssiThresh_write};

bool Radio::pblPol_read()
{
    fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);
    return fsk.RegSyncConfig.bits.PreamblePolarity;
}

bool Radio::pblPol_push()
{
    fsk.RegSyncConfig.bits.PreamblePolarity ^= 1;
    radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
    return fsk.RegSyncConfig.bits.PreamblePolarity;
}

const toggle_item_t Radio::pblPol_item = { _ITEM_TOGGLE,
    "0xaa",
    "0x55",
    pblPol_read, pblPol_push
};

const menu_t Radio::fsk_menu[] = {
    { {FIRST_CHIP_MENU_ROW, 22},    "bps:", &fsk_ook_bitrate_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 34},   "fdev:",       &gfsk_fdev_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 47},     "BT:",         &gfsk_bt_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 57}, "length:",     &fskook_pktfmt_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+1, 1},         "rxbw:",      &rxbw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 14},       "afcbw:",     &afcbw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 27}, "preambleLen:",    &pblLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 47},   "RxTrigger:", &rxTrigger_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 72},           NULL, &pblPol_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2,  1},   "syncWord:", &syncWord_item, FLAG_MSGTYPE_ALL, &syncSize_item},
    { {FIRST_CHIP_MENU_ROW+2, 27},   "syncSize:", &syncSize_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 39},          NULL,   &syncOn_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 47},   "DataMode:",   &dataMode_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 69},   NULL,   &bitSyncOn_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+3,  1},    NULL,   &pdLabel_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 18},    NULL,   &pdOn_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 23}, "size:",   &pdSize_item , FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 36},  "tol:",   &pdTol_item , FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 48},  "rssiThresh:",   &rssiThresh_item , FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+4,  1},           NULL, &agcautoon_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 11},           NULL, &afcautoon_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 22},           NULL, &RestartRxOnCollision_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 43},           NULL, &RestartRxWithPllLock_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 64},           NULL, &RestartRxWithoutPllLock_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+5,  1},           NULL, &AfcAutoClearOn_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5, 17},           NULL,       &AgcStart_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5, 27},           NULL,       &AfcClear_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5, 37},   "rssiOffset:",    &rssiOffset_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5, 52}, "rssiSmoothing:", &rssiSmoothing_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+6,  1}, "TxStartCondition:", &TxStartCondition_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+6, 32},    "FifoThreshold:", &FifoThreshold_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+6, 50},           "dcFree:", &dcFree_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+6, 68},                NULL, &fskook_crcon_item, FLAG_MSGTYPE_ALL },

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

const button_item_t Radio::ookLabel_item = { _ITEM_BUTTON, "Ook", NULL};

const menu_t Radio::common_menu[] = {
    { {FIRST_CHIP_MENU_ROW,  1},      NULL,        &paSelect_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 10}, "ocp mA:",             &ocp_item, FLAG_MSGTYPE_ALL },
    { {0, 0}, NULL, NULL }
};

const char* const ook_peak_steps[] = {
    "0.5", // 0
    "1.0", // 1
    "1.5", // 2
    "2.0", // 3
    "3.0", // 4
    "4.0", // 5
    "5.0", // 6
    "6.0", // 7
    NULL
};

unsigned Radio::ook_peak_step_read(bool fw)
{
    fsk.RegOokPeak.octet = radio.read_reg(REG_FSK_OOKPEAK);
    return fsk.RegOokPeak.bits.OokPeakThreshStep;
}

menuMode_e Radio::ook_peak_step_write(unsigned sidx)
{
    fsk.RegOokPeak.bits.OokPeakThreshStep = sidx;
    radio.write_reg(REG_FSK_OOKPEAK, fsk.RegOokPeak.octet);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::ook_peak_step_item = { _ITEM_DROPDOWN, ook_peak_steps, ook_peak_steps, ook_peak_step_read, ook_peak_step_write};

const char* const ook_peak_decs[] = {
    "once per chip        ", // 0
    "once every 2 chips   ", // 1
    "once every 4 chips   ", // 2
    "once every 8 chips   ", // 3
    "twice in each chip   ", // 4
    "4 times in each chip ", // 5
    "8 times in each chip ", // 6
    "16 times in each chip", // 7
    NULL
};

unsigned Radio::ook_peak_dec_read(bool fw)
{
    RegOokAvg_t RegOokAvg;
    RegOokAvg.octet = radio.read_reg(REG_FSK_OOKAVG); 
    return RegOokAvg.bits.OokPeakThreshDec;
}

menuMode_e Radio::ook_peak_dec_write(unsigned sidx)
{
    RegOokAvg_t RegOokAvg;
    RegOokAvg.octet = radio.read_reg(REG_FSK_OOKAVG); 
    RegOokAvg.bits.OokPeakThreshDec = sidx;
    radio.write_reg(REG_FSK_OOKAVG, RegOokAvg.octet);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::ook_peak_dec_item = { _ITEM_DROPDOWN, ook_peak_decs, ook_peak_decs, ook_peak_dec_read, ook_peak_dec_write};

void Radio::ookFixedThresh_print()
{
    printf("%u", radio.read_reg(REG_FSK_OOKFIX));
}

bool Radio::ookFixedThresh_write(const char* txt)
{
    unsigned n;
    sscanf(txt, "%u", &n);
    radio.write_reg(REG_FSK_OOKFIX, n);
    return false;
}

const value_item_t Radio::ookFixedThresh_item = { _ITEM_VALUE, 3, ookFixedThresh_print, ookFixedThresh_write};

const char* const ook_avgOffsets[] = {
    "0.0dB", // 0
    "2.0dB", // 1
    "4.0dB", // 2
    "6.0dB", // 3
    NULL
};

unsigned Radio::ook_avgOffset_read(bool fw)
{
    RegOokAvg_t RegOokAvg;
    RegOokAvg.octet = radio.read_reg(REG_FSK_OOKAVG); 
    return RegOokAvg.bits.OokAverageOffset;
}

menuMode_e Radio::ook_avgOffset_write(unsigned sidx)
{
    RegOokAvg_t RegOokAvg;
    RegOokAvg.octet = radio.read_reg(REG_FSK_OOKAVG); 
    RegOokAvg.bits.OokAverageOffset = sidx;
    radio.write_reg(REG_FSK_OOKAVG, RegOokAvg.octet);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::ook_avgOffset_item = { _ITEM_DROPDOWN, ook_avgOffsets, ook_avgOffsets, ook_avgOffset_read, ook_avgOffset_write};

const char* const ook_avgFilters[] = {
    "chip rate / 32.π", // 0
    "chip rate / 8.π ", // 1
    "chip rate / 4.π ", // 2
    "chip rate / 2.π ", // 3
    NULL
};

unsigned Radio::ook_avgFilter_read(bool fw)
{
    RegOokAvg_t RegOokAvg;
    RegOokAvg.octet = radio.read_reg(REG_FSK_OOKAVG); 
    return RegOokAvg.bits.OokAverageThreshFilt;
}

menuMode_e Radio::ook_avgFilter_write(unsigned sidx)
{
    RegOokAvg_t RegOokAvg;
    RegOokAvg.octet = radio.read_reg(REG_FSK_OOKAVG); 
    RegOokAvg.bits.OokAverageThreshFilt = sidx;
    radio.write_reg(REG_FSK_OOKAVG, RegOokAvg.octet);
    return MENUMODE_REDRAW;
}

const dropdown_item_t Radio::ook_avgFilter_item = { _ITEM_DROPDOWN, ook_avgFilters, ook_avgFilters, ook_avgFilter_read, ook_avgFilter_write};

const char* const ookthreshs[] = {
    "fixed  ", // 0
    "peak   ", // 1
    "average", // 2
    NULL
};


unsigned Radio::ookthreshtype_read(bool)
{
    fsk.RegOokPeak.octet = radio.read_reg(REG_FSK_OOKPEAK);

    return fsk.RegOokPeak.bits.OokThreshType;
}

menuMode_e Radio::ookthreshtype_write(unsigned sidx)
{
    fsk.RegOokPeak.bits.OokThreshType = sidx;
    radio.write_reg(REG_FSK_OOKPEAK, fsk.RegOokPeak.octet);

    return MENUMODE_REINIT_MENU;
}

const dropdown_item_t Radio::ookthreshtype_item = { _ITEM_DROPDOWN, ookthreshs, ookthreshs, ookthreshtype_read, ookthreshtype_write};

const menu_t Radio::ook_menu[] = {
    { {FIRST_CHIP_MENU_ROW, 22},   "bps:", &fsk_ook_bitrate_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 34},   "Fcutoff=",      &ook_bt_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW, 56}, "length:",     &fskook_pktfmt_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+1,  1},  "rxbw:",      &rxbw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 12}, "afcbw:",      &afcbw_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 25}, "preambleLen:",      &pblLen_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+1, 47},   "RxTrigger:", &rxTrigger_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+2,  1},   "syncWord:", &syncWord_item, FLAG_MSGTYPE_ALL, &syncSize_item},
    { {FIRST_CHIP_MENU_ROW+2, 27},   "syncSize:", &syncSize_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 39},          NULL,   &syncOn_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 47},   "DataMode:",   &dataMode_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+2, 69},   NULL,   &bitSyncOn_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+3,  1},    NULL,   &pdLabel_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 18},    NULL,   &pdOn_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 23}, "size:",   &pdSize_item , FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 36},  "tol:",   &pdTol_item , FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+3, 48},  "rssiThresh:",   &rssiThresh_item , FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+4,  1}, "TxStartCondition:", &TxStartCondition_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 32},    "FifoThreshold:", &FifoThreshold_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 50},           "dcFree:", &dcFree_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+4, 68},                NULL, &fskook_crcon_item, FLAG_MSGTYPE_ALL },

    { {FIRST_CHIP_MENU_ROW+5,  1},    NULL,   &ookLabel_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5,  5},    "threshType:",   &ookthreshtype_item, FLAG_MSGTYPE_ALL },

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

const menu_t Radio::ook_fixed_menu[] = {
    { {FIRST_CHIP_MENU_ROW+5, 25},    "threshold:",   &ookFixedThresh_item, FLAG_MSGTYPE_ALL },
    { {0, 0}, NULL, NULL }
};

const menu_t Radio::ook_peak_menu[] = {
    { {FIRST_CHIP_MENU_ROW+5, 25},    "step:",   &ook_peak_step_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5, 40},    "dec:",   &ook_peak_dec_item, FLAG_MSGTYPE_ALL },
    { {0, 0}, NULL, NULL }
};

const menu_t Radio::ook_average_menu[] = {
    { {FIRST_CHIP_MENU_ROW+5, 25},    "offset:",   &ook_avgOffset_item, FLAG_MSGTYPE_ALL },
    { {FIRST_CHIP_MENU_ROW+5, 45},    "filter:",   &ook_avgFilter_item, FLAG_MSGTYPE_ALL },
    { {0, 0}, NULL, NULL }
};

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

    RadioEvents = e;

    RegPaRamp.octet = radio.read_reg(REG_PARAMP);

    if (radio.type == SX1276) {
        lora_bw_item.printed_strs = lora_bws_1276;
        lora_bw_item.selectable_strs = lora_bws_1276;
    } else if (radio.type == SX1272) {
        lora_bw_item.printed_strs = lora_bws_1272;
        lora_bw_item.selectable_strs = lora_bws_1272;
    }

    lpt.start();
}

unsigned Radio::read_register(unsigned addr)
{
    return radio.read_reg(addr);
}

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

void Radio::test() { }

#endif /* ..SX127x_H */