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

Revision:
0:e1e70da93044
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/radio_sx127x.cpp	Wed Apr 24 10:11:06 2019 -0700
@@ -0,0 +1,1752 @@
+#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 */
+