test sending sensor results over lora radio. Accelerometer and temp/pressure.

Dependencies:   SX127x

Serial terminal operates at 115200.

This project provides a text-based menu over serial port.
Operating the program only requires using the arrow keys, enter key to activate a control, or entering numbers.

Two sensors provided:


LIS12DH12 accelerometer operates in a continuous sampling mode. Enable control for accelerometer enables this continuous sampling, approx every 3 seconds.
LPS22HH temperature / pressure sensor operates as single shot, where pressing the control button on terminal causes single sample to be performed.

poll rate control will enable repeated reading of pressure/temperature-sensor or photo-sensor when poll rate is greater than zero.

target must be: DISCO_L072CZ_LRWAN1

radio_sx127x.cpp

Committer:
Wayne Roberts
Date:
2019-04-24
Revision:
0:e1e70da93044

File content as of revision 0:e1e70da93044:

#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) {
                    pc.printf("Done ");
                    radio.RegOpMode.bits.Mode = RF_OPMODE_STANDBY;
                    irqFlags.bits.CadDone = 1;
                }
                if (radio.dio1)  {
                    pc.printf("Detected");
                    irqFlags.bits.CadDetected = 1;
                }
                pc.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) {

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

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

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

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

                pc.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()
{
    pc.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;

    pc.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 MENUMODE_REDRAW;
}

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()
{
    pc.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);
    pc.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()
{
    pc.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 };

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+2, 1}, NULL, &lora_cadrx_item, FLAG_MSGTYPE_ALL },

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

void Radio::fsk_ook_bps_print(void)
{
    pc.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)
{
    pc.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()
{
    pc.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++) {
        pc.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);

    pc.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;
    pc.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);
    pc.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);
    pc.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);
    pc.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()
{
    pc.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 */