UART console application for testing SX1272/SX1276

Dependencies:   SX127x

/media/uploads/dudmuck/lora.png

This is a UART console test application for using SX127x library driver for SX1272/SX1276 radio transceivers. Serial console is provided at 57600bps. Refer to Serial Communication with a PC for information about using the serial port with your PC.

Using this command interface, you can exercise the functionality of radio chip without needing specialized software application for your PC.

Commands which can be used include ? to list available commands, or . to query status from radio chip, for example. The serial console allows you to configure the radio chip, such as setting spreading factor, bandwidth, operating frequency, etc.

A simple chat application is provided to try communications between two boards. The SX127x library object is instantiated with pin assignments generic arduino headers, but can be easily reassigned for any mbed board.

The same driver library can operate for both SX1272 and SX1276. Upon starting, the driver auto-detects whether SX1272 or SX1276 transceiver chip is connected by attempting to change the LowFrequencyModeOn bit in RegOpMode register. If this bit can be changed, then the radio device is SX1276. This bit is not implemented in SX1272. A few of the radio driver functions select behavior based on this detection. The differences between these two devices is small, from a software perspective.

Using with SX1276MB1xAS Shield

This component plugs into any board with arduino uno headers.

There are two different version of this shield. European version (MAS), and North American (LAS). The LAS shield uses PA_BOOST transmit pin to permit +20dBm power. The MAS version uses RFO transmit pin in Europe. This software reads RF switch pin (A4 pin) pulling resistor to determine which type of shield is installed.


Using with your own production board

This software is useful for validating RF performance your own LoRa board design, because only two external pins needs to be provided to PC (UART TX/RX). You can select an mbed platform which matches the CPU on your own board. If the memory size doesnt match exactly, you can export the program to an offline toolchain and edit the target type or linker file there.

Transmitter Test Guidelines

FSK mode is used for transmitter testing, because an unmodulated carrier can be sent, permitting easy measurement of TX power and frequency error.

commands used for transmitter testing:

  • frf915.0 change to your desired RF center frequency (in this case 915MHz)
  • L to toggle the radio chip into FSK mode.
  • fdev0 to configure TX frequency deviation to zero, to put the transmitted carrier on the center frequency.
  • pas to select which TX pin is connected to antenna matching (RFO vs PA_BOOST).
  • op<dBm> to configure TX power.
  • If you desire to test higher power PA_BOOST, use ocp<mA>
  • w 01 03 put radio chip into transmit mode (skips writing to FIFO). This will cause radio to transmit preamble, because the FIFO is empty in TX mode. Since Fdev is zero, an unmodulated carrier is sent.
  • Spectrum analyzer can now be used to to observe TX power, harmonics, power consumption, or frequency error.
  • stby to end transmission, or use h to reset radio chip to default condition.
  • Use period . command at any time to review current radio configuration.

LoRa transmitter testing

  • use L command to toggle radio into LoRa, if necessary.
  • Normally the tx command is used to manually send single packets.
  • txc will toggle TxContinuousMode in LoRa modem to send continuous modulated transmission.
  • Useful for checking adjacent channel power.
  • enter txc again to end transmission.

Receiver Test Guidelines

FSK mode is used for receiver sensitivity testing, allowing the use of a BERT signal generator (such as R/S SMIQ03B). Using this method provides real-time indication of receiver sensitivity, useful for tuning and impedance matching. The radio chip outputs DCLK and DATA digital signals which are connected back to BERT signal generator.

commands used for receiver testing:

  • L to toggle the radio chip into FSK mode.
  • datam to toggle FSK modem into continuous mode. This disables packet engine and gives direct access to demodulator.
  • configure DIO1 pin to DCLK function, and DIO2 pin to DATA function:
    • dio command to list current DIO pin asignments
    • d1 to cycle DIO1 function until Dclk is selected
    • d2 for DIO2, only Data function is available in FSK continuous mode
  • frf915.0 change to your desired RF center frequency (in this case 915MHz)
  • rx to start receiver
  • stby to disable receiver

Full command list

Arguments shown in square brackets [] indicate required. <> are optional, where leaving off the argument usually causes a read of the item, and providing the value causes a write operation. You should always have the radio chip datasheet on-hand when using these commands.

Hitting <enter> key by itself will repeat last command.
<Ctrl-C> will cancel an operation in progress.

command list: common commands (both LoRa and FSK)

commanddescription
. (period)print current radio status
?list available commands
Ltoggle active radio modem (LoRa vs FSK)
hhardware reset, put radio into default power-on condition
frf<MHz>get/set RF operating frequency
rxstart radio receiver (any received packets are printed onto your serial terminal)
rssiread instantaneous RSSI (level read at the time command is issued)
tx<%d>transmit test packet. Packet length value can be provided as argument, or uses last value if not provided
payl<%d>get/set payload length
bw<KHz>get/set bandwidth. In LoRa mode, both receive and transmit bandwidth are changed. For FSK, only receive bandwidth is affected. bwa accesses AFC bandwidth in FSK
pastoggle RFO / PA_BOOST transmit pin output selection
op<dBm>get/set TX output power. Value is provided in dBm. Special case is value of 20dBm (on PA_BOOST), which causes increase in TX DAC voltage
ocp<mA>get/set TX current limit, in milliamps. Necessary adjustment when +20dBm is used
dioshow DIO pin assignments
d<0-5>change DIO pin assignment, the pin number is given as arguement. Each pin has up to 4 possible functions
pres<%d>set preamble length. LoRa: number of symbols. FSK: number of bytes
crcontoggle crcOn
lnabcycle LNA-boost setting (receiver performance adjustment)
Rread all radio registers (use only while reading chip datasheet)
r[%x]read single radio register (use only while reading chip datasheet)
w[%x %x]write single radio register (use only while reading chip datasheet)
pllbwchange PLL bandwidth
stbyset chip mode to standby
sleepset chip mode to sleep
fstxset chip mode to fstx
fsrxset chip mode to fsrx
Eiger range test commandsdescription
pid<%d>get set ID number in range test payload
pertx<%d>start Eiger PER transmit. The count of packets to send is provided as arguement
perrxstart Eiger PER receive
txpd<%d>get/set tx delay between PER packets transmitted

command list: LoRa modem commands

LoRa commandLoRa description
iqinvtoggle RX IQ invert
cintoggle TX IQ invert
lhp<%d>(RX) get/set hop period
sync<%x>get/set sync (post-preamble gap, single byte)
cr<1-4>get/set codingRate
lhmtoggle explicit/implicit (explicit mode sends payload length with each packet)
sf<%d>get/set spreadingFactor (SF7 to SF12)
ldrtoggle LowDataRateOptimize (changes payload encoding, for long packets)
txctoggle TxContinuousMode
rxt<%d>get/set SymbTimeout
rxsstart RX_SINGLE (receives only for SymbTimeout symbols)
cad<%d num tries>run channel activity detection

command list: FSK modem commands

FSK commandFSK description
c<%d>get/set test cases. Several FSK bitrates/bandwidths pre-configured to give optimal performance.
fdev<kHz>(TX) get/set frequency deviation
mods(TX) increment modulation shaping
par(TX) increment paRamp
datamtoggle DataMode (packet/continuous)
fifottoggle TxStartCondition (FifoThreshold level vs FifoNotEmpty)
br<%f kbps>get/set bitrate
dcfincrement DcFree (manchester / whitening)
pktftoggle PacketFormat fixed/variable length
syncontoggle SyncOn (frame sync, SFD enable)
bitsynctoggle BitSyncOn (continuous mode only)
syncw<hex bytes>get/set syncword. Sync bytes are provided by hex octects separated by spaces.
fei(RX) read FEI
rxt(RX) increment RxTrigger (RX start on rssi vs. preamble detect)
rssit<-dBm>(RX) get/set rssi threshold (trigger level for RSSI interrupt)
rssis<%d>(RX) get/set rssi smoothing
rssio<%d>(RX) get/set rssi offset
agcauto(RX) toggle AgcAutoOn (true = LNA gain set automatically)
afcauto(RX) toggle AfcAutoOn
ac(RX) AfcClear
ar(RX) increment AutoRestartRxMode
alc(RX) toggle AfcAutoClearOn (only if AfcAutoOn is set)
prep(RX) toggle PreamblePolarity (0xAA vs 0x55)
pde(RX) toggle PreambleDetectorOn
pds<%d>(RX) get/set PreambleDetectorSize
pdt<%d>(RX) get/set PreambleDetectorTol
mp(RX) toggle MapPreambleDetect (DIO function RSSI vs PreambleDetect)
thr<%d>get/set FifoThreshold (triggers FifoLevel interrupt)
polltoggle poll_irq_en. Radio events read from DIO pins vs polling of IrqFlags register
Eempty out FIFO
clkoutincrement ClkOut divider
ookenter OOK mode
ooktincrement OokThreshType
ooksincrement OokPeakTheshStep
sqlch<%d>get/set OokFixedThresh
Revision:
18:9530d682fd9a
Parent:
16:b9d36c60f2d3
Child:
19:be8a8b0e7320
--- a/main.cpp	Thu Oct 22 01:28:07 2015 +0000
+++ b/main.cpp	Thu Jul 28 00:58:55 2016 +0000
@@ -1,12 +1,11 @@
 #include "sx127x_lora.h"
 #include "sx127x_fsk.h"
+//#include "kermit.h"
 
 //#define FSK_PER
 //#define START_EIGER_RX
 //#define START_EIGER_TX
 
-//#define LORA_WAN_ENABLE
-
 DigitalOut led1(LED1);
 Serial pc(USBTX, USBRX);
 
@@ -21,28 +20,27 @@
 
 app_e app = APP_NONE;
 
-#ifdef LORA_WAN_ENABLE
-#define CFG_us915
-#include "oslmic.h"
-#include "lorabase.h"
-char mic_check;
-#endif /* LORA_WAN_ENABLE */
+#define FSK_LARGE_PKT_THRESHOLD  0x3f
 
-
-#define FSK_LARGE_PKT_THRESHOLD  0x3f
+bool ook_test_en;
+bool poll_irq_en;
 
 /***************************** eiger per: *************************************************/
 
+uint32_t num_cads;
+bool cadper_enable;
 bool per_en;
 float per_tx_delay = 0.1;
 int per_id;
-uint32_t PacketTxCnt;
+uint32_t PacketTxCnt, PacketTxCntEnd;
 uint32_t PacketPerOkCnt;
 int PacketRxSequencePrev;
 uint32_t PacketPerKoCnt;
 uint32_t PacketNormalCnt;
 Timeout per_timeout;
 
+
+
 /******************************************************************************/
 #ifdef TARGET_MTS_MDOT_F411RE
 //                mosi,      miso,     sclk,       cs,        rst,      dio0,      dio1
@@ -105,9 +103,7 @@
 
 SX127x_fsk fsk(radio);
 SX127x_lora lora(radio);
-
-
-
+//Kermit kermit(lora);
 
 void printLoraIrqs_(bool clear)
 {
@@ -227,11 +223,14 @@
 void lora_printRxPayloadCrcOn()
 {
     bool on = lora.getRxPayloadCrcOn();
-    //printf("RxPayloadCrcOn:%s ", on ? "on" : "off");
+    printf("RxPayloadCrcOn:%d = ", on);
+    if (lora.getHeaderMode())
+        printf("Rx/");  // implicit mode
+        
     if (on)
-        printf("RxPayloadCrcOn:1 = Tx CRC Enabled\r\n");
+        printf("Tx CRC Enabled\r\n");
     else
-        printf("RxPayloadCrcOn:1 = no Tx CRC\r\n");
+        printf("Tx CRC disabled\r\n");
 }
 
 void lora_printTxContinuousMode()
@@ -443,7 +442,7 @@
         printf("\r\nHopPeriod:0x%02x\r\n", lora.RegHopPeriod);
     }
 
-    printf("SymbTimeout:0x%03x ", radio.read_u16(REG_LR_MODEMCONFIG2) & 0x3ff);
+    printf("SymbTimeout:%d ", radio.read_u16(REG_LR_MODEMCONFIG2) & 0x3ff);
 
     lora.RegPreamble = radio.read_u16(REG_LR_PREAMBLEMSB);
     printf("PreambleLength:%d ", lora.RegPreamble);
@@ -557,6 +556,11 @@
         }
     } else if (radio.RegOpMode.bits.ModulationType == 1) {
         printf("OOK ");
+        switch (radio.RegOpMode.bits.ModulationShaping) {
+            case 1: printf("Fcutoff=bitrate"); break;
+            case 2: printf("Fcutoff=2*bitrate"); break;
+            case 3: printf("?"); break;
+        }        
     }
 
     printf("%dbps fdev:%dHz\r\n", fsk.get_bitrate(), fsk.get_tx_fdev_hz());    
@@ -648,11 +652,14 @@
     } else
         printf("Off ");
 
-    printf(" syncsize:%d ", fsk.RegSyncConfig.bits.SyncSize);
-    printf(" : %02x ", radio.read_reg(REG_FSK_SYNCVALUE1));
-    printf("%02x ", radio.read_reg(REG_FSK_SYNCVALUE2));
-    printf("%02x ", radio.read_reg(REG_FSK_SYNCVALUE3));
-    printf("%02x ", radio.read_reg(REG_FSK_SYNCVALUE4));
+    if (fsk.RegSyncConfig.bits.SyncOn) {
+        printf(" syncsize:%d ", fsk.RegSyncConfig.bits.SyncSize);
+        printf(" : %02x ", radio.read_reg(REG_FSK_SYNCVALUE1));
+        printf("%02x ", radio.read_reg(REG_FSK_SYNCVALUE2));
+        printf("%02x ", radio.read_reg(REG_FSK_SYNCVALUE3));
+        printf("%02x ", radio.read_reg(REG_FSK_SYNCVALUE4));
+    } else
+        printf("Sync Off");
     printf("\r\n");   // end sync config
 
     fsk.RegAfcFei.octet = radio.read_reg(REG_FSK_AFCFEI);
@@ -925,9 +932,15 @@
     printf("\r\n");
     
     if (per_en) {
+        if (cadper_enable) {
+            printf("cadper %d, ", num_cads);
+        }
         printf("per_tx_delay:%f\r\n", per_tx_delay);
         printf("PER device ID:%d\r\n", per_id);
     }    
+    
+    if (poll_irq_en)
+        printf("poll_irq_en\r\n");
 
 }
 
@@ -946,34 +959,6 @@
     printf("\r\n");
 }
 
-#ifdef LORA_WAN_ENABLE
-static const u1_t DEVKEY[16] = {
-        0x3d, 0xfd, 0xf3, 0x80, 0x45, 0x0e, 0x8b, 0x8d, 0x3e, 0xd5, 0x89, 0x25, 0xaa, 0xd4, 0x23, 0x53
-};
-// provide device key (16 bytes)
-void os_getDevKey (u1_t* buf) {
-    memcpy(buf, DEVKEY, 16);
-}
-
-static void aes_encrypt (xref2u1_t pdu, int len) {
-    os_getDevKey(AESkey);
-    os_aes(AES_ENC, pdu, len);
-}
-
-u4_t os_rmsbf4 (xref2cu1_t buf) {
-    return (u4_t)(buf[3] | (buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24));
-}
-u4_t calc_mic;
-u4_t rx_mic;
-static int aes_verifyMic0 (xref2u1_t pdu, int len) {
-    os_getDevKey(AESkey);
-    calc_mic = os_aes(AES_MIC|AES_MICNOAUX, pdu, len);
-    rx_mic = os_rmsbf4(pdu+len);
-    return calc_mic == rx_mic;
-    //return os_aes(AES_MIC|AES_MICNOAUX, pdu, len) == os_rmsbf4(pdu+len);
-}
-#endif /* LORA_WAN_ENABLE */
-
 void print_rx_verbose(uint8_t dlen)
 {
     float dbm;
@@ -991,36 +976,38 @@
         dbm
     );
     print_rx_buf(dlen);
+}
 
-#ifdef LORA_WAN_ENABLE
-    if (mic_check) {    /* LoraWAN MIC check (join accept check) */
-        //int a, d;
-        u1_t hdr;
-        printf("mic_check ");
-        if (dlen != LEN_JA && dlen != LEN_JAEXT) {
-            printf("dlen fail\r\n");
-            return;
+void set_per_en(bool en)
+{
+    if (en) {
+        if (radio.RegOpMode.bits.LongRangeMode) {
+            if (radio.type == SX1272) {
+                lora.RegModemConfig.sx1272bits.LowDataRateOptimize = 1;
+                radio.write_reg(REG_LR_MODEMCONFIG, lora.RegModemConfig.octet);
+            } else if (radio.type == SX1276) {
+                lora.RegModemConfig3.sx1276bits.LowDataRateOptimize = 1;
+                radio.write_reg(REG_LR_MODEMCONFIG3, lora.RegModemConfig3.octet);
+            }
+            lora.RegPayloadLength = 9;
+            radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
+            radio.RegDioMapping1.bits.Dio3Mapping = 1;  // to ValidHeader
+            radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);                                  
+        } else {    // fsk..
+            //fsk_tx_length = 9;
         }
-        hdr = radio.rx_buf[0];
-        if ((hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) {   
-            printf("hdr fail\r\n");
-            return;
-        } 
-        
-        aes_encrypt(radio.rx_buf+1, dlen-1);
-        if (!aes_verifyMic0(radio.rx_buf, dlen-4) ) {       
-            printf("%08x != %08x fail\r\n", calc_mic, rx_mic);
-        } else
-            printf("%08x == %08x\r\n", calc_mic, rx_mic);
-            
-        /*for (a = 0x0d; a < 0x40; a++) {
-            d = radio.read_reg(a);
-            //update_shadow_regs(selected_radio, a, d); 
-            printf("%02x: %02x\r\n", a, d);
-        } */           
-    }
-#endif /* LORA_WAN_ENABLE */
-}
+        PacketRxSequencePrev = -1;
+        //PacketRxSequence = 0;
+        PacketPerKoCnt = 0;
+        PacketPerOkCnt = 0;
+        PacketNormalCnt = 0;
+    } // ..if (per_en)
+    else {
+        per_timeout.detach();
+    }    
+    
+    per_en = en; 
+}   
 
 void per_cb()
 {
@@ -1047,6 +1034,11 @@
     }    
     
     led1 = !led1.read();
+    
+    if (PacketTxCnt == PacketTxCntEnd) {
+        set_per_en(false);
+        return;
+    }    
 }
 
 int per_parse_rx(uint8_t len)
@@ -1075,7 +1067,7 @@
         PacketRxSequencePrev = PacketRxSequence;
         // increment 'missed' counter for the RX session
         PacketPerKoCnt += i;
-        per = ( 1.0 - ( float )PacketPerOkCnt / ( float )( PacketPerOkCnt + PacketPerKoCnt ) ) * 100.0;
+        per = ( (float)1.0 - ( float )PacketPerOkCnt / ( float )( PacketPerOkCnt + PacketPerKoCnt ) ) * (float)100.0;
         printf("%d, ok=%d missed=%d normal=%d per:%.3f ", PacketRxSequence, PacketPerOkCnt, PacketPerKoCnt, PacketNormalCnt, per);
         if (radio.RegOpMode.bits.LongRangeMode)
             printf("pkt:%ddBm, snr:%.1fdB, %ddBm\r\n", lora.get_pkt_rssi(), lora.RegPktSnrValue / 4.0, lora.get_current_rssi());
@@ -1090,6 +1082,122 @@
     }    
 }
 
+typedef enum {
+    ON_TXDONE_STATE_NONE = 0,
+    ON_TXDONE_STATE_SYNC_HI_NIBBLE,
+    ON_TXDONE_STATE_SYNC_LO_NIBBLE,
+    ON_TXDONE_STATE_PAYLOAD_LENGTH,
+} on_txdone_state_e;
+
+on_txdone_state_e on_txdone_state;
+
+uint8_t lora_sync_byte;
+float on_txdone_delay;
+Timeout on_txdone_timeout;
+uint8_t on_txdone_repeat_cnt;
+
+void txdone_timeout_cb()
+{
+    uint8_t nib;
+    
+    switch (on_txdone_state) {
+        case ON_TXDONE_STATE_SYNC_HI_NIBBLE:
+            nib = lora_sync_byte >> 4;
+            if (nib >= 15) {
+                on_txdone_state = ON_TXDONE_STATE_SYNC_LO_NIBBLE;
+                lora_sync_byte = 0x00;
+            } else
+                nib++;
+                
+            lora_sync_byte = nib << 4;     
+            
+            radio.write_reg(REG_LR_SYNC_BYTE, lora_sync_byte);
+            printf("upper %02x\r\n", lora_sync_byte);               
+            break;
+        case ON_TXDONE_STATE_SYNC_LO_NIBBLE:
+            nib = lora_sync_byte & 0x0f;
+            if (nib >= 15) {
+                on_txdone_state = ON_TXDONE_STATE_SYNC_LO_NIBBLE;
+                lora_sync_byte = 0x00;
+            } else
+                nib++;
+            
+            lora_sync_byte = nib & 0x0f;   
+            
+            radio.write_reg(REG_LR_SYNC_BYTE, lora_sync_byte);
+            printf("lower %02x\r\n", lora_sync_byte);                 
+            break;
+        case ON_TXDONE_STATE_PAYLOAD_LENGTH:
+            if (++on_txdone_repeat_cnt >= 10) {
+                on_txdone_repeat_cnt = 0;
+                if (lora.RegPayloadLength == 255) {
+                    lora.RegPayloadLength = 1;
+                    printf("done\r\n");
+                    on_txdone_state = ON_TXDONE_STATE_NONE;
+                    return;
+                }
+                lora.RegPayloadLength++;
+                radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
+                printf("pl %d\r\n", lora.RegPayloadLength);
+            }
+            tx_cnt++;
+            radio.tx_buf[0] = tx_cnt;     
+            radio.tx_buf[1] = ~tx_cnt;              
+            break;
+        default:
+            return;
+    } // ..switch (on_txdone_state)
+
+    lora.start_tx(lora.RegPayloadLength);
+}
+
+void
+poll_service_radio()
+{
+    if (radio.RegOpMode.bits.LongRangeMode) {
+    } else { // fsk:
+        RegIrqFlags2_t RegIrqFlags2;
+        if (radio.RegOpMode.bits.Mode == RF_OPMODE_TRANSMITTER) {
+            RegIrqFlags2.octet = radio.read_reg(REG_FSK_IRQFLAGS2);
+            if (RegIrqFlags2.bits.PacketSent) {
+                radio.set_opmode(RF_OPMODE_SLEEP);
+                printf("poll mode fsk tx done\r\n");
+            }
+        }
+    } // ..fsk
+}
+
+void cadper_service()
+{
+    lora.RegIrqFlags.octet = radio.read_reg(REG_LR_IRQFLAGS);
+    
+
+    if (lora.RegIrqFlags.bits.CadDetected) {
+        lora.start_rx(RF_OPMODE_RECEIVER_SINGLE);
+        do {
+            lora.RegIrqFlags.octet = radio.read_reg(REG_LR_IRQFLAGS);
+            if (lora.RegIrqFlags.bits.RxDone) {
+                service_action_e act = lora.service();
+                if (act == SERVICE_READ_FIFO) {
+                    if (!per_parse_rx(lora.RegRxNbBytes)) {
+                        PacketNormalCnt++;
+                        print_rx_verbose(lora.RegRxNbBytes);                            
+                    }                        
+                }
+                break;
+            }
+        } while (!lora.RegIrqFlags.bits.RxTimeout);
+        radio.write_reg(REG_LR_IRQFLAGS, lora.RegIrqFlags.octet);
+    }
+    
+    if (lora.RegIrqFlags.bits.CadDone) {
+        lora.RegIrqFlags.octet = radio.read_reg(REG_LR_IRQFLAGS);
+        num_cads++;
+        radio.set_opmode(RF_OPMODE_CAD);
+    }        
+
+}
+
 void
 service_radio()
 {
@@ -1097,6 +1205,9 @@
     static uint8_t rssi = 0;
     
     if (radio.RegOpMode.bits.LongRangeMode) {
+        if (cadper_enable) {
+            cadper_service();
+        }
 
         act = lora.service();
     
@@ -1132,9 +1243,11 @@
                 break;
             case SERVICE_TX_DONE:
                 if (app == APP_CHAT) {
-                    lora.start_rx();
+                    lora.start_rx(RF_OPMODE_RECEIVER);
                 } else if (per_en) {
-                    per_timeout.attach(&per_cb, per_tx_delay); // start next TX
+                    per_timeout.attach(&per_cb, per_tx_delay); // start next TX              
+                } else if (on_txdone_state != ON_TXDONE_STATE_NONE) {
+                    on_txdone_timeout.attach(&txdone_timeout_cb, on_txdone_delay);
                 }
                 break;
             case SERVICE_ERROR:
@@ -1172,6 +1285,8 @@
                 }
                 break;
             case SERVICE_TX_DONE:
+                if (ook_test_en)
+                    radio.set_opmode(RF_OPMODE_SLEEP);
                 if (app == APP_CHAT) {
                     fsk.start_rx();
                 } else if (per_en) {
@@ -1256,792 +1371,1939 @@
     }
 }
 
-void toggle_per_en()
+typedef enum {
+    TXTICKER_STATE_OFF = 0,
+    TXTICKER_STATE_TOGGLE_PAYLOAD_BIT,
+    TXTICKER_STATE_CYCLE_PAYLOAD_LENGTH,
+    TXTICKER_STATE_CYCLE_CODING_RATE,
+    TXTICKER_STATE_TOG_HEADER_MODE,
+    TXTICKER_STATE_TOG_CRC_ON,
+    TXTICKER_STATE_CYCLE_SYNC_1,
+    TXTICKER_STATE_CYCLE_SYNC_2,
+    TXTICKER_STATE_RAMP_PAYLOAD_DATA_START,
+    TXTICKER_STATE_RAMP_PAYLOAD_DATA,
+    TXTICKER_STATE_SYMBOL_SWEEP,
+    TXTICKER_STATE_TOGGLE_ALL_BITS_START,
+    TXTICKER_STATE_TOGGLE_ALL_BITS,
+} txticker_state_e;
+
+txticker_state_e txticker_state;
+float tx_ticker_rate = 0.5;
+Ticker tx_ticker;
+
+uint8_t txticker_sync_byte;
+uint8_t payload_length_stop;
+uint8_t symbol_num;
+uint32_t symbol_sweep_bit_counter = 0;
+unsigned int symbol_sweep_bit_counter_stop; 
+uint8_t symbol_sweep_nbits;
+uint8_t byte_pad_length;
+
+uint8_t tab_current_byte_num;
+uint8_t tab_current_bit_in_byte;
+
+void fp_cb()
+{   
+    int i;
+    if (!radio.RegOpMode.bits.LongRangeMode)
+        return;
+          
+    switch (txticker_state) {
+    case TXTICKER_STATE_TOGGLE_PAYLOAD_BIT:
+/*
+        {
+            if (fp_tog_bit_ < 32) {
+                uint32_t bp = 1 << fp_tog_bit_;
+                fp_data ^= bp;
+                //printf("bp%02x ", bp);
+            }
+            memcpy(radio.tx_buf, &fp_data, fp_data_length);
+            printf("TX ");
+            for (i = 0; i < fp_data_length; i++)
+                printf("%02x ", radio.tx_buf[i]);
+                
+            printf("\r\n");
+            lora.start_tx(lora.RegPayloadLength);        
+            break;
+        }
+*/
+        tx_ticker.detach();
+        break;
+    case TXTICKER_STATE_CYCLE_PAYLOAD_LENGTH:
+        {
+            if (lora.RegPayloadLength > payload_length_stop)
+                lora.RegPayloadLength = 0;
+            else
+                lora.RegPayloadLength++;
+                
+            radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);        
+            lora.start_tx(lora.RegPayloadLength);
+            printf("RegPayloadLength:%d\r\n", lora.RegPayloadLength);        
+            break;
+        }
+    case TXTICKER_STATE_CYCLE_CODING_RATE:
+        {
+            uint8_t cr = lora.getCodingRate(false); // false: TX coding rate
+            if (cr == 4)
+                cr = 0;
+            else
+                cr++;
+                
+            lora.setCodingRate(cr);
+            lora.start_tx(lora.RegPayloadLength);
+            printf("tx cr:%d\r\n", cr);        
+            break;
+        }
+    case TXTICKER_STATE_TOG_HEADER_MODE:
+        {
+            lora.setHeaderMode(!lora.getHeaderMode());
+            lora.start_tx(lora.RegPayloadLength);
+            lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
+            lora_printHeaderMode();
+            printf("\r\n");          
+            break;
+        }
+    case TXTICKER_STATE_TOG_CRC_ON:
+        {
+            lora.setRxPayloadCrcOn(!lora.getRxPayloadCrcOn());
+            lora.start_tx(lora.RegPayloadLength);
+            printf("crc on:%d\r\n", lora.getRxPayloadCrcOn());        
+            break;
+        }
+    case TXTICKER_STATE_CYCLE_SYNC_1:
+        {
+            /* cycle hi nibble of 0x39 register */
+            if ((txticker_sync_byte & 0xf0) == 0xf0)
+                txticker_sync_byte &= 0x0f;
+            else
+                txticker_sync_byte += 0x10;
+            radio.write_reg(REG_LR_SYNC_BYTE, txticker_sync_byte);
+            lora.start_tx(lora.RegPayloadLength);
+            printf("0x39: %02x\r\n", txticker_sync_byte);        
+            break;
+        }
+    case TXTICKER_STATE_CYCLE_SYNC_2:
+        {
+            /* cycle lo nibble of 0x39 register */
+            if ((txticker_sync_byte & 0x0f) == 0x0f)
+                txticker_sync_byte &= 0xf0;
+            else
+                txticker_sync_byte += 0x01;
+            radio.write_reg(REG_LR_SYNC_BYTE, txticker_sync_byte);
+            lora.start_tx(lora.RegPayloadLength);
+            printf("0x39: %02x\r\n", txticker_sync_byte);         
+            break;
+        }
+    case TXTICKER_STATE_RAMP_PAYLOAD_DATA_START:
+        txticker_state = TXTICKER_STATE_RAMP_PAYLOAD_DATA;
+        for (i = 0; i < lora.RegPayloadLength; i++)
+            radio.tx_buf[i] = 0;
+
+        lora.start_tx(lora.RegPayloadLength);
+        printf("payload start, len:%d\r\n", lora.RegPayloadLength);
+        break;
+    case TXTICKER_STATE_RAMP_PAYLOAD_DATA:
+        for (i = lora.RegPayloadLength-1; i >= 0; i--) {
+            //printf("i:%d ", i);
+            if (radio.tx_buf[i] == 255) {
+                radio.tx_buf[i] = 0;
+            } else {
+                radio.tx_buf[i]++;
+                break;
+            }
+        }
+        //printf("\r\n");
+        printf("send:");
+        for (i = 0; i < lora.RegPayloadLength; i++) {
+            printf("%02x ", radio.tx_buf[i]);
+        }
+        printf("\r\n");
+        lora.start_tx(lora.RegPayloadLength);
+        if (radio.tx_buf[0] == 255) {
+            printf("payload ramp done\r\n");
+            tx_ticker.detach();
+        }
+        break;
+    case TXTICKER_STATE_SYMBOL_SWEEP:       // fpsNL command, where N=symbol num, L=nbytes
+        {
+            uint32_t mask;
+            /*for (i = 0; i < lora.RegPayloadLength; i++)
+                radio.tx_buf[i] = 0;*/
+            i = byte_pad_length;
+            printf("bit_counter 0x%x : ", symbol_sweep_bit_counter);
+            for (int bn = 0; bn < symbol_sweep_nbits; bn += 2) {
+                /* 2 lsbits going into first byte */
+                mask = 1 << bn;
+                if (symbol_sweep_bit_counter & mask)
+                    radio.tx_buf[i] |= 1 << symbol_num;
+                else
+                    radio.tx_buf[i] &= ~(1 << symbol_num);
+                mask = 2 << bn;
+                if (symbol_sweep_bit_counter & mask)
+                    radio.tx_buf[i] |= 0x10 << symbol_num;
+                else
+                    radio.tx_buf[i] &= ~(0x10 << symbol_num);
+                //printf("%02x ", radio.tx_buf[i]);
+                i++;
+            }
+            for (i = 0; i < lora.RegPayloadLength; i++)
+                printf("%02x ", radio.tx_buf[i]);
+            printf("\r\n");
+            lora.start_tx(lora.RegPayloadLength);
+            if (++symbol_sweep_bit_counter == symbol_sweep_bit_counter_stop) {
+                printf("stop\r\n");
+                tx_ticker.detach();
+            }
+        }
+        break;
+    case TXTICKER_STATE_TOGGLE_ALL_BITS_START:
+        tab_current_byte_num = byte_pad_length;
+        tab_current_bit_in_byte = 0;
+        printf("tx ");
+        for (i = 0; i < lora.RegPayloadLength; i++) {
+            radio.tx_buf[i] = 0;
+            printf("%02x ", radio.tx_buf[i]);
+        }
+        printf("\r\n");
+        txticker_state = TXTICKER_STATE_TOGGLE_ALL_BITS;
+        lora.start_tx(lora.RegPayloadLength);
+        break;
+    case TXTICKER_STATE_TOGGLE_ALL_BITS:
+        {
+            uint8_t mask = 1 << tab_current_bit_in_byte;
+            radio.tx_buf[tab_current_byte_num] = mask;
+            printf("bit%d in [%d]: tx ", tab_current_bit_in_byte, tab_current_byte_num);
+            for (i = 0; i < lora.RegPayloadLength; i++) {
+                printf("%02x ", radio.tx_buf[i]);
+            }
+            printf("\r\n");            
+            lora.start_tx(lora.RegPayloadLength);
+            if (++tab_current_bit_in_byte == 8) {
+                radio.tx_buf[tab_current_byte_num] = 0;
+                tab_current_bit_in_byte = 0;
+                if (++tab_current_byte_num == lora.RegPayloadLength) {
+                    tx_ticker.detach();
+                }
+            }
+        }
+        break;
+    default:
+            tx_ticker.detach();
+            break;
+    } // ...switch (txticker_state)
+}
+
+void ook_test_tx(int len)
+{    
+    int i;
+    /*
+    fsk.RegPktConfig2.bits.PayloadLength = i;
+                    radio.write_u16(REG_FSK_PACKETCONFIG2, fsk.RegPktConfig2.word);fsk.RegPktConfig2.bits.PayloadLength = i;
+                    radio.write_u16(REG_FSK_PACKETCONFIG2, fsk.RegPktConfig2.word);
+                    */
+    for (i = 0; i < 4; i++) {
+        radio.tx_buf[i] = 0xaa;
+    }
+    
+    printf("ooktx:");
+    for (i = 0; i < len; i++) {
+        radio.tx_buf[i+4] = rand() & 0xff;
+        printf("%02x ", radio.tx_buf[i+4]);
+    }
+    printf("\r\n");
+    fsk.start_tx(len+4); 
+    
+    while (radio.RegOpMode.bits.Mode == RF_OPMODE_TRANSMITTER) {
+        if (poll_irq_en)
+            poll_service_radio();
+        else
+            service_radio();      
+    }
+}
+
+void cmd_init(uint8_t args_at)
 {
-    per_en ^= 1;
-    printf("per_en:%d\r\n", per_en);
+    printf("init\r\n");
+    radio.init();
+    if (!radio.RegOpMode.bits.LongRangeMode) {
+        fsk.init();   // put FSK modem to some functioning default
+    } else {
+        // lora configuration is more simple
+    }    
+}
+
+void cmd_per_tx_delay(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        per_tx_delay = i / 1000.0;
+    }
+    printf("per_tx_delay:%dms\r\n", (int)(per_tx_delay * 1000));      
+}
+
+void cmd_tx(uint8_t idx)
+{
+    int i;
+    static uint16_t fsk_tx_length;
+
+    if (radio.RegOpMode.bits.LongRangeMode) {          
+        if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+            sscanf(pcbuf+idx, "%d", &i);
+            lora.RegPayloadLength = i;
+            radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
+        }
+        tx_cnt++;
+        printf("payload:%02x\r\n", tx_cnt);
+        
+        for (i = 0; i < lora.RegPayloadLength; i++)
+            radio.tx_buf[i] = tx_cnt;
+        lora.start_tx(lora.RegPayloadLength);
+    } else {    // FSK:
+        if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+            sscanf(pcbuf+idx, "%d", &i);
+            fsk_tx_length = i;
+        }
+        if (ook_test_en) {
+            ook_test_tx(fsk_tx_length);
+        } else {
+            if (radio.RegOpMode.bits.Mode != RF_OPMODE_TRANSMITTER) { // if not already busy transmitting
+                tx_cnt++;
+                printf("payload:%02x\r\n", tx_cnt);
+                for (i = 0; i < fsk_tx_length; i++) {
+                    radio.tx_buf[i] = tx_cnt;
+                }
+                fsk.start_tx(fsk_tx_length);
+            }
+        }
+    } // !LoRa                
+
+}
+
+void cmd_hw_reset(uint8_t idx)
+{
+    printf("hw_reset()\r\n");
+    radio.hw_reset();
+    ook_test_en = false;
+    poll_irq_en = false;
+}
+
+void cmd_read_all_regs(uint8_t idx)
+{
+    uint8_t a, d;
+    
+    // read all registers
+    for (a = 1; a < 0x71; a++) {
+        d = radio.read_reg(a);
+        printf("%02x: %02x\r\n", a, d);
+    }
+}
+
+void cmd_read_current_rssi(uint8_t idx)
+{
+    if (radio.RegOpMode.bits.Mode != RF_OPMODE_RECEIVER) {
+        radio.set_opmode(RF_OPMODE_RECEIVER);
+        wait_us(10000);
+    }
+    if (radio.RegOpMode.bits.LongRangeMode)
+        printf("rssi:%ddBm\r\n", lora.get_current_rssi());
+    else
+        printf("rssi:-%.1f\r\n", radio.read_reg(REG_FSK_RSSIVALUE) / 2.0);
+}
+
+void cmd_lora_continuous_tx(uint8_t idx)
+{
+    /* TxContinuousMode same for sx1272 and sx1276 */
+    lora.RegModemConfig2.octet = radio.read_reg(REG_LR_MODEMCONFIG2);
+    lora.RegModemConfig2.sx1276bits.TxContinuousMode ^= 1;   
+    radio.write_reg(REG_LR_MODEMCONFIG2, lora.RegModemConfig2.octet);
+    lora.RegModemConfig2.octet = radio.read_reg(REG_LR_MODEMCONFIG2);
     
-    if (per_en) {
-        if (radio.RegOpMode.bits.LongRangeMode) {
-            if (radio.type == SX1272) {
-                lora.RegModemConfig.sx1272bits.LowDataRateOptimize = 1;
-                radio.write_reg(REG_LR_MODEMCONFIG, lora.RegModemConfig.octet);
-            } else if (radio.type == SX1276) {
-                lora.RegModemConfig3.sx1276bits.LowDataRateOptimize = 1;
-                radio.write_reg(REG_LR_MODEMCONFIG3, lora.RegModemConfig3.octet);
+    lora_printTxContinuousMode();
+    printf("\r\n");
+}
+
+void cmd_fsk_test_case(uint8_t idx)
+{
+    if (pcbuf[idx] < '0' || pcbuf[idx] > '9') {
+        printf("%dbps fdev:%dhz ", fsk.get_bitrate(), fsk.get_tx_fdev_hz());
+        printf("rxbw:%dHz ", fsk.get_rx_bw_hz(REG_FSK_RXBW));
+        printf("afcbw:%dHz preambleLen:%d\r\n", fsk.get_rx_bw_hz(REG_FSK_AFCBW), radio.read_u16(REG_FSK_PREAMBLEMSB));        
+    } else {
+        radio.set_opmode(RF_OPMODE_STANDBY);
+        per_tx_delay = 0.3;
+        
+        if (radio.read_reg(REG_FSK_SYNCVALUE1) == 0x55 && radio.read_reg(REG_FSK_SYNCVALUE2)) {
+            fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);    
+            fsk.RegSyncConfig.bits.SyncSize = 2;
+            radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
+            radio.write_reg(REG_FSK_SYNCVALUE3, 0x90);
+            radio.write_reg(REG_FSK_SYNCVALUE2, 0x4e);
+            radio.write_reg(REG_FSK_SYNCVALUE1, 0x63);              
+        }
+        
+        fsk.RegPreambleDetect.octet = radio.read_reg(REG_FSK_PREAMBLEDETECT);
+        fsk.RegPreambleDetect.bits.PreambleDetectorOn = 1;
+        radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);                 
+        
+        fsk.RegRxConfig.octet = radio.read_reg(REG_FSK_RXCONFIG);
+        fsk.RegRxConfig.bits.AfcAutoOn = 1;
+        fsk.RegRxConfig.bits.AgcAutoOn = 1;
+        fsk.RegRxConfig.bits.RxTrigger = 7;
+        radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);    
+        
+        fsk.RegPreambleDetect.bits.PreambleDetectorOn = 1;
+        fsk.RegPreambleDetect.bits.PreambleDetectorSize = 1;
+        fsk.RegPreambleDetect.bits.PreambleDetectorTol = 10;
+        radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);            
+        
+        switch (pcbuf[idx]) {
+            case '0':
+                fsk.set_bitrate(4800);
+                fsk.set_tx_fdev_hz(5005);
+                fsk.set_rx_dcc_bw_hz(10417, 0);  // rxbw
+                fsk.set_rx_dcc_bw_hz(50000, 1);  // afcbw
+                radio.write_u16(REG_FSK_PREAMBLEMSB, 8);                    
+                break;
+            case '1':
+                fsk.set_bitrate(50000);
+                fsk.set_tx_fdev_hz(25000);
+                fsk.set_rx_dcc_bw_hz(62500, 0);  // rxbw
+                fsk.set_rx_dcc_bw_hz(100000, 1);  // afcbw
+                radio.write_u16(REG_FSK_PREAMBLEMSB, 9);                    
+                break;           
+            case '2':
+                fsk.set_bitrate(38400);
+                fsk.set_tx_fdev_hz(20020);
+                fsk.set_rx_dcc_bw_hz(50000, 0);  // rxbw
+                fsk.set_rx_dcc_bw_hz(100000, 1);  // afcbw
+                radio.write_u16(REG_FSK_PREAMBLEMSB, 8);                        
+                break;
+            case '3':
+                fsk.set_bitrate(1201);
+                fsk.set_tx_fdev_hz(20020);
+                fsk.set_rx_dcc_bw_hz(25000, 0);  // rxbw
+                fsk.set_rx_dcc_bw_hz(50000, 1);  // afcbw
+                radio.write_u16(REG_FSK_PREAMBLEMSB, 8);                 
+                break;    
+            case '4':
+                fsk.set_bitrate(1201);
+                fsk.set_tx_fdev_hz(4028);
+                fsk.set_rx_dcc_bw_hz(7813, 0);  // rxbw
+                fsk.set_rx_dcc_bw_hz(25000, 1);  // afcbw
+                radio.write_u16(REG_FSK_PREAMBLEMSB, 8);
+                break;
+            case '5':
+                fsk.set_bitrate(1201);
+                fsk.set_tx_fdev_hz(4028);
+                fsk.set_rx_dcc_bw_hz(5208, 0);  // rxbw
+                fsk.set_rx_dcc_bw_hz(10417, 1);  // afcbw
+                radio.write_u16(REG_FSK_PREAMBLEMSB, 8);                    
+                break;                                                                 
+        } // ...switch (pcbuf[idx])
+        printf("%dbps fdev:%dhz ", fsk.get_bitrate(), fsk.get_tx_fdev_hz());
+        printf("rxbw:%dHz ", fsk.get_rx_bw_hz(REG_FSK_RXBW));
+        printf("afcbw:%dHz preambleLen:%d\r\n", fsk.get_rx_bw_hz(REG_FSK_AFCBW), radio.read_u16(REG_FSK_PREAMBLEMSB));        
+    }
+}
+
+void cmd_toggle_modem(uint8_t idx)
+{
+    ook_test_en = false;
+    poll_irq_en = false;
+    if (radio.RegOpMode.bits.LongRangeMode)
+        fsk.enable(false);
+    else
+        lora.enable();
+
+    radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
+    if (radio.RegOpMode.bits.LongRangeMode)
+        printf("LoRa\r\n");
+    else
+        printf("FSK\r\n");
+}
+
+void cmd_empty_fifo(uint8_t idx)
+{
+    RegIrqFlags2_t RegIrqFlags2;
+    RegIrqFlags2.octet = radio.read_reg(REG_FSK_IRQFLAGS2);
+    while (!RegIrqFlags2.bits.FifoEmpty) {
+        if (pc.readable())
+            break;
+        printf("%02x\r\n", radio.read_reg(REG_FIFO));
+        RegIrqFlags2.octet = radio.read_reg(REG_FSK_IRQFLAGS2);
+    }
+}
+
+void cmd_print_status(uint8_t idx)
+{
+    if (radio.type == SX1276) {
+#ifdef TARGET_MTS_MDOT_F411RE
+        printf("\r\nSX1276 ");
+#else
+        if (shield_type == SHIELD_TYPE_LAS)
+            printf("\r\nSX1276LAS ");
+        if (shield_type == SHIELD_TYPE_MAS)
+            printf("\r\nSX1276MAS ");
+#endif /* !TARGET_MTS_MDOT_F411RE */                       
+    } else if (radio.type == SX1272)
+        printf("\r\nSX1272 ");
+        
+    radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
+    if (radio.RegOpMode.bits.LongRangeMode)
+        lora_print_status();
+    else
+        fsk_print_status();
+    common_print_status();
+}
+                
+void cmd_hop_period(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        lora.RegHopPeriod = i;
+        radio.write_reg(REG_LR_HOPPERIOD, lora.RegHopPeriod);
+        if (radio.RegDioMapping1.bits.Dio1Mapping != 1) {
+            radio.RegDioMapping1.bits.Dio1Mapping = 1;
+            radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
+        }
+    }
+    lora.RegHopPeriod = radio.read_reg(REG_LR_HOPPERIOD);
+    printf("HopPeriod:0x%02x\r\n", lora.RegHopPeriod);
+}       
+
+void cmd_lora_ppg(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] != 0) {
+        sscanf(pcbuf+idx, "%x", &i);
+        radio.write_reg(REG_LR_SYNC_BYTE, i);
+    }
+    printf("lora sync:0x%02x\r\n", radio.read_reg(REG_LR_SYNC_BYTE));
+}
+
+void cmd_rssi_offset(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        fsk.RegRssiConfig.bits.RssiOffset = i;
+        radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
+    }            
+    fsk.RegRssiConfig.octet = radio.read_reg(REG_FSK_RSSICONFIG);          
+    printf("RssiOffset:%d\r\n", fsk.RegRssiConfig.bits.RssiOffset); 
+} 
+
+void cmd_rssi_smoothing(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        fsk.RegRssiConfig.bits.RssiSmoothing = i;
+        radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
+    }                        
+    fsk.RegRssiConfig.octet = radio.read_reg(REG_FSK_RSSICONFIG);
+    printf("RssiSmoothing:%d\r\n", fsk.RegRssiConfig.bits.RssiSmoothing); 
+}
+
+void cmd_rssi_threshold(uint8_t idx)
+{
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9' || pcbuf[idx] == '-') {
+        float dbm;
+        sscanf(pcbuf+idx, "%f", &dbm);
+        dbm *= (float)2.0;
+        fsk.RegRssiThresh = (int)fabs(dbm);
+        radio.write_reg(REG_FSK_RSSITHRESH, fsk.RegRssiThresh);
+    }                                    
+    fsk.RegRssiThresh = radio.read_reg(REG_FSK_RSSITHRESH);
+    printf("rssiThreshold:-%.1f\r\n", fsk.RegRssiThresh / 2.0); 
+}
+
+void cmd_rx_trigger(uint8_t idx)
+{
+    printf("RxTrigger:");
+    switch (fsk.RegRxConfig.bits.RxTrigger) {
+        case 0: fsk.RegRxConfig.bits.RxTrigger = 1;
+            printf("rssi\r\n");
+            break;
+        case 1: fsk.RegRxConfig.bits.RxTrigger = 6;
+            printf("preamble\r\n");
+            break;
+        case 6: fsk.RegRxConfig.bits.RxTrigger = 7;
+            printf("both\r\n");
+            break;
+        case 7: fsk.RegRxConfig.bits.RxTrigger = 0;
+            printf("none\r\n");
+            break;
+        default: fsk.RegRxConfig.bits.RxTrigger = 0;
+            printf("none\r\n");
+            break;
+        }
+    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);
+}
+
+void cmd_cadper(uint8_t idx)
+{
+    set_per_en(true);
+
+    PacketNormalCnt = 0;
+    PacketRxSequencePrev = -1;
+    PacketPerKoCnt = 0;
+    PacketPerOkCnt = 0;
+            
+    cadper_enable = true;
+
+    lora.RegIrqFlags.octet = radio.read_reg(REG_LR_IRQFLAGS);
+    /* clear any stale flag */
+    radio.write_reg(REG_LR_IRQFLAGS, lora.RegIrqFlags.octet);
+    
+    /* start first CAD */
+    radio.set_opmode(RF_OPMODE_CAD);
+    num_cads = 0;    
+}
+
+#if 0
+void cmd_cadrx(uint8_t idx)
+{
+    int n_tries = 1;
+    lora.RegIrqFlags.octet = radio.read_reg(REG_LR_IRQFLAGS);
+    /* clear any stale flag */
+    radio.write_reg(REG_LR_IRQFLAGS, lora.RegIrqFlags.octet);
+    
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &n_tries);
+    }
+    
+    while (n_tries > 0) {
+        radio.set_opmode(RF_OPMODE_CAD);
+        
+        do {
+            lora.RegIrqFlags.octet = radio.read_reg(REG_LR_IRQFLAGS);
+        } while (!lora.RegIrqFlags.bits.CadDetected && !lora.RegIrqFlags.bits.CadDone);
+        if (lora.RegIrqFlags.bits.CadDetected) {
+            lora.start_rx(RF_OPMODE_RECEIVER_SINGLE);            
+            n_tries = 1;    // end
+            printf("CadDetected ");
+        }
+        if (lora.RegIrqFlags.bits.CadDone) {
+            printf("CadDone ");
+        }
+        
+        radio.write_reg(REG_LR_IRQFLAGS, lora.RegIrqFlags.octet);
+        printf("\r\n");
+        n_tries--;
+    }    
+}
+#endif /* #if 0 */
+
+void cmd_cad(uint8_t idx)
+{
+    int n_tries = 1;
+    lora.RegIrqFlags.octet = radio.read_reg(REG_LR_IRQFLAGS);
+    /* clear any stale flag */
+    radio.write_reg(REG_LR_IRQFLAGS, lora.RegIrqFlags.octet);
+    
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &n_tries);
+    }
+    
+    while (n_tries > 0) {
+        radio.set_opmode(RF_OPMODE_CAD);
+        
+        do {
+            lora.RegIrqFlags.octet = radio.read_reg(REG_LR_IRQFLAGS);
+        } while (!lora.RegIrqFlags.bits.CadDetected && !lora.RegIrqFlags.bits.CadDone);
+        if (lora.RegIrqFlags.bits.CadDetected) {
+            n_tries = 1;    // end
+            printf("CadDetected ");
+        }
+        if (lora.RegIrqFlags.bits.CadDone) {
+            if (n_tries == 1)  // print on last try
+                printf("CadDone ");
+        }
+        
+        radio.write_reg(REG_LR_IRQFLAGS, lora.RegIrqFlags.octet);
+        n_tries--;
+    }
+    printf("\r\n");
+}
+
+void cmd_rx_timeout(uint8_t idx)
+{
+    int symb_timeout;
+    uint16_t reg_u16 = radio.read_u16(REG_LR_MODEMCONFIG2);
+    
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &symb_timeout);
+        reg_u16 &= 0xfc00;
+        reg_u16 |= symb_timeout;
+    }
+    reg_u16 = radio.read_u16(REG_LR_MODEMCONFIG2);
+    printf("SymbTimeout:%d\r\n", reg_u16 & 0x3ff);
+}
+
+void cmd_rx_single(uint8_t idx)
+{
+    lora.start_rx(RF_OPMODE_RECEIVER_SINGLE);
+}
+
+void cmd_rx(uint8_t idx)
+{    
+    set_per_en(false);
+  
+    if (radio.RegOpMode.bits.LongRangeMode)
+        lora.start_rx(RF_OPMODE_RECEIVER);
+    else {
+        fsk.start_rx();
+        radio.RegDioMapping1.bits.Dio2Mapping = 3;  // dio2 to syncadrs
+        radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet); 
+        if (radio.HF) {
+            fsk.RegRssiConfig.octet = radio.read_reg(REG_FSK_RSSICONFIG);
+            fsk.RegRssiConfig.bits.RssiOffset = FSK_RSSI_OFFSET;
+            fsk.RegRssiConfig.bits.RssiSmoothing = FSK_RSSI_SMOOTHING;
+            radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
+        }                                
+    }
+}
+
+void cmd_radio_reg_read(uint8_t idx)
+{
+    int i;
+    sscanf(pcbuf+idx, "%x", &i);
+    printf("%02x: %02x\r\n", i, radio.read_reg(i));
+}
+
+void cmd_radio_reg_write(uint8_t idx)
+{
+    int i, n;
+    sscanf(pcbuf+idx, "%x %x", &i, &n);
+    radio.write_reg(i, n);
+    printf("%02x: %02x\r\n", i, radio.read_reg(i));
+}
+
+void cmd_mod_shaping(uint8_t idx)
+{
+    uint8_t s = fsk.get_modulation_shaping();
+    
+    if (s == 3)
+        s = 0;
+    else
+        s++;
+        
+    fsk.set_modulation_shaping(s);
+    
+    if (radio.RegOpMode.bits.ModulationType == 0) {
+        printf("FSK ");
+        switch (s) {
+            case 0: printf("off"); break;
+            case 1: printf("BT1.0 "); break;
+            case 2: printf("BT0.5 "); break;
+            case 3: printf("BT0.3 "); break;
+        }
+    } else if (radio.RegOpMode.bits.ModulationType == 1) {
+        printf("OOK ");
+        switch (s) {
+            case 0: printf("off"); break;
+            case 1: printf("Fcutoff=bitrate"); break;
+            case 2: printf("Fcutoff=2*bitrate"); break;
+            case 3: printf("?"); break;
+        }        
+    }    
+    
+    printf("\r\n");      
+}
+
+void cmd_MapPreambleDetect(uint8_t idx)
+{
+    radio.RegDioMapping2.bits.MapPreambleDetect ^= 1;
+    radio.write_reg(REG_DIOMAPPING2, radio.RegDioMapping2.octet);
+    printf("MapPreambleDetect:");
+    if (radio.RegDioMapping2.bits.MapPreambleDetect)
+        printf("preamble\r\n");
+    else
+        printf("rssi\r\n");
+}
+
+void cmd_bgr(uint8_t idx)
+{
+    RegPdsTrim1_t pds_trim;
+    uint8_t adr;
+    if (radio.type == SX1276)
+        adr = REG_PDSTRIM1_SX1276;
+    else
+        adr = REG_PDSTRIM1_SX1272;
+       
+    pds_trim.octet = radio.read_reg(adr);         
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        int i;
+        sscanf(&pcbuf[idx], "%d", &i);
+        pds_trim.bits.prog_txdac = i;
+    }
+    radio.write_reg(adr, pds_trim.octet);
+    printf("prog_txdac:%.1fuA\r\n", 2.5 + (pds_trim.bits.prog_txdac * 0.625));
+    /* increase OCP threshold to allow more power */
+    radio.RegOcp.octet = radio.read_reg(REG_OCP);
+    if (radio.RegOcp.bits.OcpTrim < 16) {
+        radio.RegOcp.bits.OcpTrim = 16;
+        radio.write_reg(REG_OCP, radio.RegOcp.octet);
+    }    
+}   
+
+void cmd_ook(uint8_t idx)
+{
+    fsk.set_bitrate(32768);
+    radio.write_u16(REG_FSK_PREAMBLEMSB, 0);    // zero preamble length
+    radio.RegOpMode.bits.ModulationType = 1; // to ook mode
+    radio.write_reg(REG_OPMODE, radio.RegOpMode.octet);
+    fsk.RegSyncConfig.bits.SyncOn = 0;
+    radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
+    ook_test_en = true;
+    printf("OOK\r\n");
+}
+
+void cmd_ocp(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &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);
+    }            
+    radio.RegOcp.octet = radio.read_reg(REG_OCP);
+    if (radio.RegOcp.bits.OcpTrim < 16)
+        i = 45 + (5 * radio.RegOcp.bits.OcpTrim);
+    else if (radio.RegOcp.bits.OcpTrim < 28)
+        i = (10 * radio.RegOcp.bits.OcpTrim) - 30;
+    else
+        i = 240;
+    printf("Ocp: %dmA\r\n", i); 
+}
+
+void cmd_op(uint8_t idx)
+{
+    int i, dbm;
+    RegPdsTrim1_t pds_trim;
+    uint8_t adr;
+    if (radio.type == SX1276)
+        adr = REG_PDSTRIM1_SX1276;
+    else
+        adr = REG_PDSTRIM1_SX1272;
+       
+    pds_trim.octet = radio.read_reg(adr);   
+                
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9' || pcbuf[idx] == '-') {
+        sscanf(pcbuf+idx, "%d", &i);
+        if (radio.RegPaConfig.bits.PaSelect) {
+            /* PABOOST used: +2dbm to +17, or +20 */
+            if (i == 20) {
+                printf("+20dBm PADAC bias\r\n");
+                i -= 3;
+                pds_trim.bits.prog_txdac = 7;
             }
-            lora.RegPayloadLength = 9;
+            if (i > 1)
+                    radio.RegPaConfig.bits.OutputPower = i - 2;
+        } else {
+            /* RFO used: -1 to +14dbm */
+            if (i < 15)
+                radio.RegPaConfig.bits.OutputPower = i + 1;
+        }
+        radio.write_reg(REG_PACONFIG, radio.RegPaConfig.octet);
+    }
+    radio.RegPaConfig.octet = radio.read_reg(REG_PACONFIG);
+    if (radio.RegPaConfig.bits.PaSelect) {
+        printf("PA_BOOST ");
+        dbm = radio.RegPaConfig.bits.OutputPower + pds_trim.bits.prog_txdac - 2;
+    } else {
+        printf("RFO ");
+        dbm = radio.RegPaConfig.bits.OutputPower - 1;
+    }
+    printf("OutputPower:%ddBm\r\n", dbm);
+}
+
+
+
+void cmd_fsk_agcauto(uint8_t idx)
+{
+    fsk.RegRxConfig.octet = radio.read_reg(REG_FSK_RXCONFIG);
+    fsk.RegRxConfig.bits.AgcAutoOn ^= 1;
+    printf("AgcAuto:");
+    if (fsk.RegRxConfig.bits.AgcAutoOn)
+        printf("On\r\n");
+    else
+        printf("OFF\r\n");          
+    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet); 
+}
+
+void cmd_fsk_afcauto(uint8_t idx)
+{
+    fsk.RegRxConfig.octet = radio.read_reg(REG_FSK_RXCONFIG);            
+    fsk.RegRxConfig.bits.AfcAutoOn ^= 1;
+    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);
+    printf("AfcAuto:");
+    if (fsk.RegRxConfig.bits.AfcAutoOn)
+        printf("On\r\n");
+    else
+        printf("OFF\r\n");
+}
+
+void cmd_crcOn(uint8_t idx)
+{
+    if (radio.RegOpMode.bits.LongRangeMode) {
+        lora.setRxPayloadCrcOn(!lora.getRxPayloadCrcOn());
+        lora_printRxPayloadCrcOn();
+    } else {
+        printf("CrcOn:");
+        fsk.RegPktConfig1.bits.CrcOn ^= 1;
+        radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);
+        if (fsk.RegPktConfig1.bits.CrcOn)
+            printf("On\r\n");
+        else
+            printf("Off\r\n");
+        if (fsk.RegPktConfig2.bits.DataModePacket && radio.RegOpMode.bits.Mode == RF_OPMODE_RECEIVER) {
+            fsk.config_dio0_for_pktmode_rx();
+        }
+    }
+    printf("\r\n");  
+} 
+
+#ifdef LORA_TX_TEST
+void cmd_lora_fixed_payload_symbol(uint8_t idx) // fixed payload, symbol test
+{
+    int n, i;
+    
+    symbol_num = pcbuf[idx] - '0';
+    sscanf(pcbuf+idx+2, "%d", &i);
+    n = i >> 2; // num nibbles
+    printf("%d nibbles: ", n);
+    lora.RegPayloadLength = byte_pad_length;
+    while (n > 0) {
+        lora.RegPayloadLength++;
+        n -= 2; // one byte = two nibbles
+    }
+    printf("%d bytes\r\n", lora.RegPayloadLength);
+    symbol_sweep_nbits = i >> 2;
+    symbol_sweep_bit_counter = 0;
+    symbol_sweep_bit_counter_stop = 1 << symbol_sweep_nbits;    // one bit per nibble used in symbol (2bits per byte)
+    printf("sweep symbol %d, length bytes:%d nbits:%d stop:0x%x\r\n", symbol_num, lora.RegPayloadLength, symbol_sweep_nbits, symbol_sweep_bit_counter_stop);
+    txticker_state = TXTICKER_STATE_SYMBOL_SWEEP;
+    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
+    tx_ticker.attach(&fp_cb, tx_ticker_rate);
+}
+
+void cmd_fixed_payload_offset(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >='0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        byte_pad_length = i;
+    }
+    printf("byte_pad_length:%d\r\n", byte_pad_length);
+}
+
+void cmd_lora_fixed_payload(uint8_t idx)
+{
+    int n, a, i, d = 0;
+    for (i = idx; i < pcbuf_len; ) {
+        //printf("scan:\"%s\"\r\n", pcbuf+i);
+        sscanf(pcbuf+i, "%x", &n);
+        //printf("n:%x\r\n", n);
+        radio.tx_buf[d] = n;
+        printf("%02x ", n);
+        while (pcbuf[i] == ' ')
+            i++;
+        //printf("%d pcbuf[i]:%x\r\n", i, pcbuf[i]);
+        for (a = i; pcbuf[a] != ' '; a++)
+            if (a >= pcbuf_len)
+                break;
+        i = a;
+        while (pcbuf[i] == ' ') {
+            i++;                 
+            if (i >= pcbuf_len)
+                break;
+        }   
+        d++;
+    }
+    lora.RegPayloadLength = d;
+    printf("\r\nlora.RegPayloadLength:%d\r\n", lora.RegPayloadLength);
+    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
+    lora.start_tx(lora.RegPayloadLength);
+}
+
+void cmd_lora_toggle_crcOn(uint8_t idx)
+{
+    /* test lora crc on/off */
+    lora.RegPayloadLength = 1;
+    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);           
+    txticker_state = TXTICKER_STATE_TOG_CRC_ON;
+    tx_ticker.attach(&fp_cb, tx_ticker_rate);
+}
+
+void lora_cycle_payload_length(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        payload_length_stop = i;
+    }
+    txticker_state = TXTICKER_STATE_CYCLE_PAYLOAD_LENGTH;
+    tx_ticker.attach(&fp_cb, tx_ticker_rate);
+}
+
+void cmd_lora_data_ramp(uint8_t idx)
+{
+    // lora payload data ramping
+    lora.RegPayloadLength = pcbuf[idx] - '0';
+    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);           
+    txticker_state = TXTICKER_STATE_RAMP_PAYLOAD_DATA_START;
+    tx_ticker.attach(&fp_cb, tx_ticker_rate);
+}
+
+void cmd_lora_sync_lo_nibble(uint8_t idx)
+{
+    lora_sync_byte = 0x00;
+    on_txdone_state = ON_TXDONE_STATE_SYNC_LO_NIBBLE;
+    on_txdone_delay = 0.100;
+    txdone_timeout_cb();
+    //sync_sweep_timeout.attach(&txdone_timeout_cb, sync_sweep_delay);
+}
+
+void cmd_lora_toggle_header_mode(uint8_t idx)
+{
+    lora.RegPayloadLength = 1;
+    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);           
+    txticker_state = TXTICKER_STATE_TOG_HEADER_MODE;
+    tx_ticker.attach(&fp_cb, tx_ticker_rate);
+}
+
+void cmd_lora_sync_sweep(uint8_t idx)
+{
+    lora.RegPayloadLength = 1;
+    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
+    txticker_sync_byte = 0x12;
+    if (pcbuf[idx] == '1')
+        txticker_state = TXTICKER_STATE_CYCLE_SYNC_1;
+    else if (pcbuf[idx] == '2')
+        txticker_state = TXTICKER_STATE_CYCLE_SYNC_2;
+    tx_ticker.attach(&fp_cb, tx_ticker_rate);
+}
+
+void cmd_lora_all_payload_lengths(uint8_t idx)
+{
+    on_txdone_repeat_cnt = 0;
+    on_txdone_state = ON_TXDONE_STATE_PAYLOAD_LENGTH;
+    on_txdone_delay = 0.200;
+    txdone_timeout_cb();
+    lora.RegPayloadLength = 0;
+    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
+}
+
+void cmd_lora_toggle_all_bits(uint8_t idx)
+{
+    lora.RegPayloadLength = (pcbuf[idx] - '0') + byte_pad_length;
+    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength); 
+    txticker_state = TXTICKER_STATE_TOGGLE_ALL_BITS_START;
+    printf("tab byte length:%d\r\n", lora.RegPayloadLength);
+
+    if (lora.RegPayloadLength > 0)
+        tx_ticker.attach(&fp_cb, tx_ticker_rate);
+}
+
+void cmd_lora_cycle_codingrates(uint8_t idx)
+{
+    lora.RegPayloadLength = 1;
+    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);     
+    txticker_state = TXTICKER_STATE_CYCLE_CODING_RATE;
+    tx_ticker.attach(&fp_cb, tx_ticker_rate);
+}
+#endif /* LORA_TX_TEST */
+
+void cmd_codingRate(uint8_t idx)
+{
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9')
+        lora.setCodingRate(pcbuf[idx] - '0');
+     lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
+     lora_printCodingRate(false);    // false: transmitted
+     printf("\r\n");
+}
+
+void cmd_lora_header_mode(uint8_t idx)
+{
+    lora.setHeaderMode(!lora.getHeaderMode());
+    lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
+    lora_printHeaderMode();
+    printf("\r\n");
+}
+
+void cmd_fsk_AfcAutoClearOn(uint8_t idx)
+{
+    fsk.RegAfcFei.bits.AfcAutoClearOn ^= 1;
+    printf("AfcAutoClearOn: ");
+    radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
+    if (fsk.RegAfcFei.bits.AfcAutoClearOn)
+        printf("ON\r\n");
+    else
+        printf("off\r\n");
+}
+
+void cmd_fsk_AutoRestartRxMode(uint8_t idx)
+{
+    fsk.RegSyncConfig.bits.AutoRestartRxMode++;
+    radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
+    fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);
+    printf("AutoRestartRxMode:");
+    switch (fsk.RegSyncConfig.bits.AutoRestartRxMode) {
+        case 0: printf("off "); break;
+        case 1: printf("no-pll-wait "); break;
+        case 2: printf("pll-wait "); break;
+        case 3: printf("3 "); break;
+    }
+    printf("\r\n");   
+}
+
+void cmd_AfcClear(uint8_t idx)
+{
+    printf("clear afc: ");
+    fsk.RegAfcFei.bits.AfcClear = 1;
+    radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
+    fsk.RegAfcFei.bits.AfcClear = 0; 
+    printf("%dHz\r\n", (int)(FREQ_STEP_HZ * radio.read_s16(REG_FSK_AFCMSB)));
+}
+
+void cmd_fsk_bitrate(uint8_t idx)
+{
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        float kbits;
+        sscanf(&pcbuf[idx], "%f", &kbits);
+        fsk.set_bitrate((int)(kbits*1000));
+    }
+    printf("%fkbps\r\n", fsk.get_bitrate()/(float)1000.0); 
+}
+
+void cmd_bandwidth(uint8_t idx)
+{
+    int i;
+    float f;
+    if (radio.RegOpMode.bits.LongRangeMode) {
+        if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+            radio.set_opmode(RF_OPMODE_STANDBY);
+            sscanf(&pcbuf[idx], "%d", &i);
+            lora.setBw_KHz(i);
+        } else
+            lora_printAllBw();
+        lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
+        printf("current ");
+        lora_printBw();
+        printf("\r\n");
+    } else { // FSK:
+        if (pcbuf[idx] == 'a') {
+            idx++;
+            if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+                radio.set_opmode(RF_OPMODE_STANDBY);
+                sscanf(&pcbuf[idx], "%f", &f);
+                fsk.set_rx_dcc_bw_hz((int)(f*(float)1000.0), 1);
+            }
+            printf("afcbw:%.3fkHz\r\n", fsk.get_rx_bw_hz(REG_FSK_AFCBW)/1000.0);
+        } else {
+            if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+                radio.set_opmode(RF_OPMODE_STANDBY);
+                sscanf(&pcbuf[idx], "%f", &f);
+                fsk.set_rx_dcc_bw_hz((int)(f*(float)1000.0), 0);
+            }
+            printf("rxbw:%.3fkHz\r\n", fsk.get_rx_bw_hz(REG_FSK_RXBW)/1000.0);
+        }
+    }
+}
+
+void cmd_lora_poll_validHeader(uint8_t idx)
+{
+    lora.poll_vh ^= 1;
+    printf("poll_vh:%d\r\n", lora.poll_vh);
+}
+
+void cmd_fsk_syncword(uint8_t idx)
+{
+    int i, d = 0;
+    uint8_t reg_addr = REG_FSK_SYNCVALUE1;
+    
+    fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);
+    
+    if (pcbuf_len != idx) { // something to write?
+        for (i = idx; i < pcbuf_len; ) {
+            int a, n;
+            sscanf(pcbuf+i, "%x", &n);
+            radio.write_reg(reg_addr++, n);
+            //printf("%02x ", n);
+            while (pcbuf[i] == ' ')
+                i++;
+            for (a = i; pcbuf[a] != ' '; a++)
+                if (a >= pcbuf_len)
+                    break;
+            i = a;
+            while (pcbuf[i] == ' ') {
+                i++;                 
+                if (i >= pcbuf_len)
+                    break;
+            }   
+            d++;
+        }    
+     
+        fsk.RegSyncConfig.bits.SyncSize = reg_addr - REG_FSK_SYNCVALUE1 - 1;
+        radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
+    }
+    
+    printf("%d: ", fsk.RegSyncConfig.bits.SyncSize);
+    for (i = 0; i <= fsk.RegSyncConfig.bits.SyncSize; i++)
+        printf("%02x ", radio.read_reg(REG_FSK_SYNCVALUE1+i));
+    printf("\r\n");
+}
+
+void cmd_fsk_syncOn(uint8_t idx)
+{
+    fsk.RegSyncConfig.bits.SyncOn ^= 1;
+    radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
+    printf("SyncOn:%d\r\n", fsk.RegSyncConfig.bits.SyncOn);
+}
+
+void cmd_fsk_bitsync(uint8_t idx)
+{
+    fsk.RegOokPeak.octet = radio.read_reg(REG_FSK_OOKPEAK);
+    fsk.RegOokPeak.bits.BitSyncOn ^= 1;
+    radio.write_reg(REG_FSK_OOKPEAK, fsk.RegOokPeak.octet);
+    if (fsk.RegOokPeak.bits.BitSyncOn)
+        printf("BitSyncOn\r\n");
+    else
+        printf("BitSync Off\r\n");
+}
+
+void cmd_lora_sf(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        lora.setSf(i);
+        if (i == 6 && !lora.getHeaderMode()) {
+            printf("SF6: to implicit header mode\r\n");
+            lora.setHeaderMode(true);
+        }
+    }
+    lora.RegModemConfig2.octet = radio.read_reg(REG_LR_MODEMCONFIG2);
+    lora_printSf();
+    printf("\r\n");
+}
+
+void cmd_fsk_TxStartCondition(uint8_t idx)
+{
+    fsk.RegFifoThreshold.bits.TxStartCondition ^= 1;
+    radio.write_reg(REG_FSK_FIFOTHRESH, fsk.RegFifoThreshold.octet);
+    printf("TxStartCondition:");
+    if (fsk.RegFifoThreshold.bits.TxStartCondition)
+        printf("!FifoEmpty\r\n");
+    else
+        printf("FifoLevel\r\n"); 
+}        
+
+void cmd_fsk_read_fei(uint8_t idx)
+{
+    printf("fei:%dHz\r\n", (int)(FREQ_STEP_HZ * radio.read_s16(REG_FSK_FEIMSB)));
+}
+
+void cmd_fsk_fdev(uint8_t idx)
+{
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        float khz;
+        sscanf(pcbuf+idx, "%f", &khz);
+        fsk.set_tx_fdev_hz((int)(khz*1000));
+    }
+    printf("fdev:%fKHz\r\n", fsk.get_tx_fdev_hz()/(float)1000.0);
+}
+
+void cmd_frf(uint8_t idx)
+{
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        float MHz;
+        sscanf(pcbuf+idx, "%f", &MHz);
+        //printf("MHz:%f\r\n", MHz);
+        radio.set_frf_MHz(MHz);
+    }
+    printf("%fMHz\r\n", radio.get_frf_MHz());
+#ifndef TARGET_MTS_MDOT_F411RE
+    radio.RegPaConfig.octet = radio.read_reg(REG_PACONFIG);
+    if (shield_type == SHIELD_TYPE_LAS) {
+        // LAS HF=PA_BOOST  LF=RFO
+        if (radio.HF)
+            radio.RegPaConfig.bits.PaSelect = 1;
+        else
+            radio.RegPaConfig.bits.PaSelect = 0;
+    }
+    radio.write_reg(REG_PACONFIG, radio.RegPaConfig.octet);          
+#endif /* !TARGET_MTS_MDOT_F411RE */
+}
+
+void cmd_fsk_PacketFormat(uint8_t idx)
+{
+    printf("PacketFormat:");
+    fsk.RegPktConfig1.bits.PacketFormatVariable ^= 1;
+    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);
+    if (fsk.RegPktConfig1.bits.PacketFormatVariable)
+        printf("variable\r\n");
+    else
+        printf("fixed\r\n");
+}
+
+void cmd_payload_length(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        if (radio.RegOpMode.bits.LongRangeMode) {
+            lora.RegPayloadLength = i;
             radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
-            radio.RegDioMapping1.bits.Dio3Mapping = 1;  // to ValidHeader
-            radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);                                  
-        } else {    // fsk..
-            //fsk_tx_length = 9;
+        } else {
+            fsk.RegPktConfig2.bits.PayloadLength = i;
+            radio.write_u16(REG_FSK_PACKETCONFIG2, fsk.RegPktConfig2.word);
         }
-        PacketRxSequencePrev = -1;
-        //PacketRxSequence = 0;
-        PacketPerKoCnt = 0;
-        PacketPerOkCnt = 0;
-        PacketNormalCnt = 0;
-    } // ..if (per_en)
+    }
+    if (radio.RegOpMode.bits.LongRangeMode) {
+        lora.RegPayloadLength = radio.read_reg(REG_LR_PAYLOADLENGTH);
+        printf("PayloadLength:%d\r\n", lora.RegPayloadLength);
+    } else {
+        printf("PayloadLength:%d\r\n", fsk_get_PayloadLength());
+    }
+}
+
+void cmd_paRamp(uint8_t idx)
+{
+    int i;
+    uint8_t reg_par = radio.read_reg(REG_PARAMP);
+    uint8_t PaRamp = reg_par & 0x0f;
+    reg_par &= 0xf0;
+    if (PaRamp == 15)
+        PaRamp = 0;
+    else
+        PaRamp++;
+    radio.write_reg(REG_PARAMP, reg_par | PaRamp);
+    printf("PaRamp:");
+    switch (PaRamp) {
+        case 0: i = 3400; break;
+        case 1: i = 2000; break;
+        case 2: i = 1000; break;
+        case 3: i = 500; break;                
+        case 4: i = 250; break;
+        case 5: i = 125; break;
+        case 6: i = 100; break;
+        case 7: i = 62; break;                 
+        case 8: i = 50; break;
+        case 9: i = 40; break;
+        case 10: i = 31; break;
+        case 11: i = 25; break;                
+        case 12: i = 20; break;
+        case 13: i = 15; break;
+        case 14: i = 12; break;
+        case 15: i = 10; break;                
+    }
+    printf("%dus\r\n", i);
+}
+
+void cmd_paSelect(uint8_t idx)
+{
+    radio.RegPaConfig.bits.PaSelect ^= 1;
+    radio.write_reg(REG_PACONFIG, radio.RegPaConfig.octet);
+    printPa();
+    printf("\r\n");
+}
+
+void cmd_poll_irq_en(uint8_t idx)
+{
+    poll_irq_en ^= 1;
+    printf("poll_irq_en:");
+    if (poll_irq_en)
+        printf("irqFlags register\r\n");
+    else
+        printf("DIO pin interrupt\r\n");
+}
+
+void cmd_per_id(uint8_t idx)
+{
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &per_id);
+    }
+    printf("PER device ID:%d\r\n", per_id);
+}
+
+void cmd_pertx(uint8_t idx)
+{
+    int i;
+    
+    if (cadper_enable)
+        cadper_enable = false;
+    
+    set_per_en(true);
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        PacketTxCntEnd = i;
+    }
+    PacketTxCnt = 0;
+    per_timeout.attach(&per_cb, per_tx_delay);  
+}
+
+void cmd_perrx(uint8_t idx)
+{
+    set_per_en(true);
+
+    PacketNormalCnt = 0;
+    PacketRxSequencePrev = -1;
+    PacketPerKoCnt = 0;
+    PacketPerOkCnt = 0;                
+    //dio3.rise(&dio3_cb);
+
+    if (radio.RegOpMode.bits.LongRangeMode)
+        lora.start_rx(RF_OPMODE_RECEIVER);
     else {
-        per_timeout.detach();
-    }            
+        fsk.start_rx();
+        radio.RegDioMapping1.bits.Dio2Mapping = 3;  // dio2 to syncadrs
+        radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet); 
+        if (radio.HF) {
+            fsk.RegRssiConfig.octet = radio.read_reg(REG_FSK_RSSICONFIG);
+            fsk.RegRssiConfig.bits.RssiOffset = FSK_RSSI_OFFSET;
+            fsk.RegRssiConfig.bits.RssiSmoothing = FSK_RSSI_SMOOTHING;
+            radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
+        }                                
+    }    
+}
+
+void cmd_fsk_PreambleDetectorOn(uint8_t idx)
+{
+    fsk.RegPreambleDetect.bits.PreambleDetectorOn ^= 1;
+    radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);
+    printf("PreambleDetector:");
+    if (fsk.RegPreambleDetect.bits.PreambleDetectorOn)
+        printf("On\r\n");
+    else
+        printf("OFF\r\n"); 
+}
+
+void cmd_fsk_PreambleDetectorSize(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        fsk.RegPreambleDetect.bits.PreambleDetectorSize = i;
+    }
+    printf("PreambleDetectorSize:%d\r\n", fsk.RegPreambleDetect.bits.PreambleDetectorSize);
+    radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet); 
+}
+
+void cmd_fsk_PreambleDetectorTol(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        fsk.RegPreambleDetect.bits.PreambleDetectorTol = i;
+    }
+    printf("PreambleDetectorTol:%d\r\n", fsk.RegPreambleDetect.bits.PreambleDetectorTol);                
+    radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);  
+}
+
+void cmd_PreambleSize(uint8_t idx)
+{
+    int i;
+    if (radio.RegOpMode.bits.LongRangeMode) {
+        if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+            sscanf(pcbuf+idx, "%d", &i);
+            radio.write_u16(REG_LR_PREAMBLEMSB, i);
+        }
+        lora.RegPreamble = radio.read_u16(REG_LR_PREAMBLEMSB);
+        printf("lora PreambleLength:%d\r\n", lora.RegPreamble);                
+    } else {    
+        if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+            sscanf(pcbuf+idx, "%d", &i);  
+            radio.write_u16(REG_FSK_PREAMBLEMSB, i);
+        }
+        printf("PreambleSize:%d\r\n", radio.read_u16(REG_FSK_PREAMBLEMSB));
+    }
+}
+
+void cmd_fsk_PreamblePolarity(uint8_t idx)
+{
+    fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);
+    fsk.RegSyncConfig.bits.PreamblePolarity ^= 1;
+    radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
+    if (fsk.RegSyncConfig.bits.PreamblePolarity)
+        printf("0x55\r\n");
+    else
+        printf("0xaa\r\n");
+}
+
+void cmd_pllbw(uint8_t idx)
+{
+    RegPll_t pll;
+    if (radio.type == SX1272) {
+        // 0x5c and 0x5e registers
+        pll.octet = radio.read_reg(REG_PLL_SX1272);
+        if (pll.bits.PllBandwidth == 3)
+            pll.bits.PllBandwidth = 0;
+        else
+            pll.bits.PllBandwidth++;
+        radio.write_reg(REG_PLL_SX1272, pll.octet);
+        pll.octet = radio.read_reg(REG_PLL_LOWPN_SX1272);
+        if (pll.bits.PllBandwidth == 3)
+            pll.bits.PllBandwidth = 0;
+        else
+            pll.bits.PllBandwidth++;
+        radio.write_reg(REG_PLL_LOWPN_SX1272, pll.octet);                
+    } else if (radio.type == SX1276) {
+        // 0x70 register
+        pll.octet = radio.read_reg(REG_PLL_SX1276);
+        if (pll.bits.PllBandwidth == 3)
+            pll.bits.PllBandwidth = 0;
+        else
+            pll.bits.PllBandwidth++;
+        radio.write_reg(REG_PLL_SX1276, pll.octet);                  
+    }
+    switch (pll.bits.PllBandwidth) {
+        case 0: printf("75"); break;
+        case 1: printf("150"); break;
+        case 2: printf("225"); break;
+        case 3: printf("300"); break;
+    }
+    printf("KHz\r\n");
+}
+
+void cmd_lna_boost(uint8_t idx)
+{
+    radio.RegLna.octet = radio.read_reg(REG_LNA);
+    if (radio.RegLna.bits.LnaBoostHF == 3)
+        radio.RegLna.bits.LnaBoostHF = 0; 
+    else 
+        radio.RegLna.bits.LnaBoostHF++;
+    radio.write_reg(REG_LNA, radio.RegLna.octet);            
+    printf("LNA-boost:%d\r\n", radio.RegLna.bits.LnaBoostHF);
+}
+
+void cmd_LowDataRateOptimize(uint8_t idx)
+{
+    if (radio.type == SX1272) {
+        lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
+        lora.RegModemConfig.sx1272bits.LowDataRateOptimize ^= 1;
+        printf("LowDataRateOptimize:%d\r\n", lora.RegModemConfig.sx1272bits.LowDataRateOptimize);
+        radio.write_reg(REG_LR_MODEMCONFIG, lora.RegModemConfig.octet);
+    } else if (radio.type == SX1276) {
+        lora.RegModemConfig3.octet = radio.read_reg(REG_LR_MODEMCONFIG3);
+        lora.RegModemConfig3.sx1276bits.LowDataRateOptimize ^= 1;
+        printf("LowDataRateOptimize:%d\r\n", lora.RegModemConfig3.sx1276bits.LowDataRateOptimize); 
+        radio.write_reg(REG_LR_MODEMCONFIG3, lora.RegModemConfig3.octet);
+    }
+}
+
+void cmd_fsk_FifoThreshold(uint8_t idx)
+{
+    int i;
+    fsk.RegFifoThreshold.octet = radio.read_reg(REG_FSK_FIFOTHRESH);  
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        fsk.RegFifoThreshold.bits.FifoThreshold = i;
+    }
+    radio.write_reg(REG_FSK_FIFOTHRESH, fsk.RegFifoThreshold.octet);
+    printf("FifoThreshold:%d\r\n", fsk.RegFifoThreshold.bits.FifoThreshold);
+    fsk.RegFifoThreshold.octet = radio.read_reg(REG_FSK_FIFOTHRESH); 
+}
+
+void cmd_tx_ticker_rate(uint8_t idx)
+{
+    if (pcbuf[idx] != 0) {
+        sscanf(pcbuf+idx, "%f", &tx_ticker_rate);
+    }
+    printf("tx_ticker_rate:%f\r\n", tx_ticker_rate);
+}
+
+void cmd_lora_tx_invert(uint8_t idx)
+{
+    lora.invert_tx(lora.RegTest33.bits.chirp_invert_tx);
+    printf("chirp_invert_tx :%d\r\n", lora.RegTest33.bits.chirp_invert_tx);    
+}
+    
+void cmd_lora_rx_invert(uint8_t idx)
+{
+    lora.invert_rx(!lora.RegTest33.bits.invert_i_q);
+    printf("rx invert_i_q:%d\r\n", lora.RegTest33.bits.invert_i_q);    
+}
+
+void cmd_fsk_dcfree(uint8_t idx)
+{
+    fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
+    if (fsk.RegPktConfig1.bits.DcFree == 3)
+        fsk.RegPktConfig1.bits.DcFree = 0;
+    else
+        fsk.RegPktConfig1.bits.DcFree++;
+    printf(" dcFree:");
+    switch (fsk.RegPktConfig1.bits.DcFree) {
+        case 0: printf("none "); break;
+        case 1: printf("Manchester "); break;
+        case 2: printf("Whitening "); break;
+        case 3: printf("reserved "); break;
+    }
+    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet); 
+    printf("\r\n"); 
+}
+
+void cmd_fsk_DataMode(uint8_t idx)
+{
+    fsk.RegPktConfig2.word = radio.read_u16(REG_FSK_PACKETCONFIG2);
+    fsk.RegPktConfig2.bits.DataModePacket ^= 1;
+    radio.write_u16(REG_FSK_PACKETCONFIG2, fsk.RegPktConfig2.word);  
+    printf("datamode:");
+    if (fsk.RegPktConfig2.bits.DataModePacket)
+        printf("packet\r\n");
+    else
+        printf("continuous\r\n");
+}
+
+void cmd_show_dio(uint8_t idx)
+{
+    if (radio.RegOpMode.bits.LongRangeMode)
+        lora_print_dio();
+    else
+        fsk_print_dio(); 
+}
+
+void cmd_set_dio(uint8_t idx)
+{
+    switch (pcbuf[idx]) {
+        case '0':
+            radio.RegDioMapping1.bits.Dio0Mapping++;
+            radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
+            break;
+        case '1':
+            radio.RegDioMapping1.bits.Dio1Mapping++;
+            radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
+            break;    
+        case '2':
+            radio.RegDioMapping1.bits.Dio2Mapping++;
+            radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
+            break;
+        case '3':
+            radio.RegDioMapping1.bits.Dio3Mapping++;
+            radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
+            break;
+        case '4':
+            radio.RegDioMapping2.bits.Dio4Mapping++;
+            radio.write_reg(REG_DIOMAPPING2, radio.RegDioMapping2.octet);
+            break; 
+        case '5':
+            radio.RegDioMapping2.bits.Dio5Mapping++;
+            radio.write_reg(REG_DIOMAPPING2, radio.RegDioMapping2.octet);
+            break;                                                                                             
+    } // ...switch (pcbuf[idx])
+    if (radio.RegOpMode.bits.LongRangeMode)
+        lora_print_dio();
+    else
+        fsk_print_dio();
+}
+
+void cmd_mode_standby(uint8_t idx)
+{
+    radio.set_opmode(RF_OPMODE_STANDBY);
+    printf("standby\r\n");
+}
+    
+void cmd_mode_sleep(uint8_t idx)
+{
+    radio.set_opmode(RF_OPMODE_SLEEP);
+    printf("sleep\r\n");
+}
+
+void cmd_mode_fstx(uint8_t idx)
+    {
+    radio.set_opmode(RF_OPMODE_SYNTHESIZER_TX);
+    printf("fstx\r\n");
+}
+
+void cmd_mode_fsrx(uint8_t idx)
+{
+    radio.set_opmode(RF_OPMODE_SYNTHESIZER_RX);
+    printf("fsrx\r\n");
+}
+
+void cmd_chat(uint8_t idx)
+{
+    app = APP_CHAT;
+    lora.start_rx(RF_OPMODE_RECEIVER);
+    printf("chat start\r\n");
+}
+
+void cmd_OokThreshType(uint8_t idx)
+{
+    fsk.RegOokPeak.octet = radio.read_reg(REG_FSK_OOKPEAK);
+    if (fsk.RegOokPeak.bits.OokThreshType == 2)
+        fsk.RegOokPeak.bits.OokThreshType = 0;
+    else
+        fsk.RegOokPeak.bits.OokThreshType++;
+
+    radio.write_reg(REG_FSK_OOKPEAK, fsk.RegOokPeak.octet);
+    printf("OokThreshType:");
+    switch (fsk.RegOokPeak.bits.OokThreshType) {
+        case 0: printf("fixed"); break;
+        case 1: printf("peak"); break;
+        case 2: printf("average"); break;
+        case 3: printf("?"); break;
+    }
+    printf("\r\n");
+}
+
+void cmd_OokPeakTheshStep(uint8_t idx)
+{
+    float f;
+    fsk.RegOokPeak.octet = radio.read_reg(REG_FSK_OOKPEAK);
+    if (fsk.RegOokPeak.bits.OokPeakThreshStep == 7)
+        fsk.RegOokPeak.bits.OokPeakThreshStep = 0;
+    else
+        fsk.RegOokPeak.bits.OokPeakThreshStep++;    
+
+    radio.write_reg(REG_FSK_OOKPEAK, fsk.RegOokPeak.octet);
+    switch (fsk.RegOokPeak.bits.OokPeakThreshStep) {
+        case 0: f = 0.5; break;
+        case 1: f = 1; break;
+        case 2: f = 1.5; break;
+        case 3: f = 2; break;
+        case 4: f = 3; break;
+        case 5: f = 4; break;
+        case 6: f = 5; break;
+        case 7: f = 6; break;                                                        
+    }    
+    printf("OokPeakThreshStep:%.1fdB\r\n", f); 
+}
+
+void cmd_OokFixedThresh(uint8_t idx)
+{
+    int i;
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%d", &i);
+        radio.write_reg(REG_FSK_OOKFIX, i);
+    }
+    i = radio.read_reg(REG_FSK_OOKFIX);
+    printf("OokFixedThreshold:%d\r\n", i);
+} 
+
+void cmd_clkout(uint8_t idx)
+{
+    RegOsc_t reg_osc;
+    reg_osc.octet = radio.read_reg(REG_FSK_OSC);
+    if (reg_osc.bits.ClkOut == 7)
+        reg_osc.bits.ClkOut = 0;
+    else
+        reg_osc.bits.ClkOut++;
+        
+    printf("ClkOut:%d\r\n", reg_osc.bits.ClkOut);
+    radio.write_reg(REG_FSK_OSC, reg_osc.octet);
+}
+     
+void cmd_help(uint8_t args_at);
+
+typedef enum {
+    MODEM_BOTH,
+    MODEM_FSK,
+    MODEM_LORA
+} modem_e;
+
+typedef struct {
+    modem_e modem;
+    const char* const cmd;
+    void (*handler)(uint8_t args_at);
+    const char* const arg_descr;
+    const char* const description;
+} menu_item_t;
+
+const menu_item_t menu_items[] = 
+{   /* after first character, command names must be [A-Za-z] */
+    { MODEM_BOTH, "chat", cmd_chat, "","start keyboard chat"}, 
+    { MODEM_BOTH, "rssi", cmd_read_current_rssi, "","(RX) read instantaneous RSSI"}, 
+    { MODEM_BOTH, "txpd", cmd_per_tx_delay, "<%d>","get/set PER tx delay (in milliseconds)"},
+    { MODEM_BOTH, "pertx", cmd_pertx, "<%d pkt count>","start Eiger PER TX"},
+    { MODEM_BOTH, "perrx", cmd_perrx, "","start Eiger PER RX"},
+    { MODEM_BOTH, "pres", cmd_PreambleSize, "<%d>", "get/set PreambleSize"},
+    { MODEM_BOTH, "pllbw", cmd_pllbw, "", "increment pllbw"},
+    { MODEM_BOTH, "lnab", cmd_lna_boost, "", "(RX) increment LNA boost"},
+    { MODEM_BOTH, "stby", cmd_mode_standby, "", "set chip mode to standby"},
+    { MODEM_BOTH, "sleep", cmd_mode_sleep, "", "set chip mode to sleep"},
+    { MODEM_BOTH, "fstx", cmd_mode_fstx, "", "set chip mode to fstx"},
+    { MODEM_BOTH, "fsrx", cmd_mode_fsrx, "", "set chip mode to fsrx"}, 
+    { MODEM_BOTH, "crcon", cmd_crcOn, "","toggle crcOn"},
+    { MODEM_BOTH, "payl", cmd_payload_length, "<%d>","get/set payload length"},   
+    { MODEM_BOTH, "bgr", cmd_bgr, "<%d>","(TX) get/set reference for TX DAC"},
+    { MODEM_BOTH, "ocp", cmd_ocp, "<%d>","(TX) get/set milliamps current limit"},
+    { MODEM_BOTH, "frf", cmd_frf, "<MHz>","get/set RF center frequency"},
+    { MODEM_BOTH, "pas", cmd_paSelect, "","(TX) toggle RFO/PA_BOOST"},
+    { MODEM_BOTH, "pid", cmd_per_id, "<%d>","get/set ID number in Eiger PER packet"},
+    { MODEM_BOTH, "dio", cmd_show_dio, "","print dio mapping"},         
+    
+    { MODEM_FSK, "clkout", cmd_clkout, "","increment ClkOut divider"}, 
+    { MODEM_FSK, "ookt", cmd_OokThreshType, "","(RX) increment OokThreshType"}, 
+    { MODEM_FSK, "ooks", cmd_OokPeakTheshStep, "","(RX) increment OokPeakTheshStep"}, 
+    { MODEM_FSK, "sqlch", cmd_OokFixedThresh, "<%d>","(RX) get/set OokFixedThresh"}, 
+    { MODEM_FSK, "rssit", cmd_rssi_threshold, "<-dBm>","(RX) get/set rssi threshold"}, 
+    { MODEM_FSK, "rssis", cmd_rssi_smoothing, "<%d>","(RX) get/set rssi smoothing"}, 
+    { MODEM_FSK, "rssio", cmd_rssi_offset, "<%d>","(RX) get/set rssi offset"},
+    { MODEM_FSK, "mods", cmd_mod_shaping, "", "(TX) increment modulation shaping"},  
+    { MODEM_FSK, "agcauto", cmd_fsk_agcauto, "", "(RX) toggle AgcAutoOn"},
+    { MODEM_FSK, "afcauto", cmd_fsk_afcauto, "", "(RX) toggle AfcAutoOn"},
+    { MODEM_FSK, "syncw", cmd_fsk_syncword, "<hex bytes>", "get/set syncword"},
+    { MODEM_FSK, "syncon", cmd_fsk_syncOn, "", "toggle SyncOn (frame sync, SFD enable)"},
+    { MODEM_FSK, "bitsync", cmd_fsk_bitsync, "", "toggle BitSyncOn (continuous mode only)"},
+    { MODEM_FSK, "fifot", cmd_fsk_TxStartCondition, "", "(TX) toggle TxStartCondition"},
+    { MODEM_FSK, "pktf", cmd_fsk_PacketFormat, "", "toggle PacketFormat fixed/variable length"},
+    { MODEM_FSK, "poll", cmd_poll_irq_en, "", "toggle poll_irq_en"},
+    { MODEM_FSK, "prep", cmd_fsk_PreamblePolarity, "", "toggle PreamblePolarity"},
+    { MODEM_FSK, "datam", cmd_fsk_DataMode, "", "toggle DataMode (packet/continuous)"},    
+    { MODEM_FSK, "rxt", cmd_rx_trigger, "","(RX) increment RxTrigger"},
+    { MODEM_FSK, "ook", cmd_ook, "","enter OOK mode"},
+    { MODEM_FSK, "fei", cmd_fsk_read_fei, "","(RX) read FEI"},
+    { MODEM_FSK, "fdev", cmd_fsk_fdev, "<kHz>","(TX) get/set fdev"},
+    { MODEM_FSK, "par", cmd_paRamp, "","(TX) increment paRamp"},
+    { MODEM_FSK, "pde", cmd_fsk_PreambleDetectorOn, "","(RX) toggle PreambleDetectorOn"},
+    { MODEM_FSK, "pds", cmd_fsk_PreambleDetectorSize, "<%d>","(RX) get/set PreambleDetectorSize"},
+    { MODEM_FSK, "pdt", cmd_fsk_PreambleDetectorTol, "<%d>","(RX) get/set PreambleDetectorTol"},
+    { MODEM_FSK, "thr", cmd_fsk_FifoThreshold, "<%d>","get/set FifoThreshold"},
+    { MODEM_FSK, "dcf", cmd_fsk_dcfree, "","(RX) increment DcFree"},
+    { MODEM_FSK, "br", cmd_fsk_bitrate, "<%f kbps>","get/set bitrate"},
+    { MODEM_FSK, "ac", cmd_AfcClear, "","(RX) AfcClear"},
+    { MODEM_FSK, "ar", cmd_fsk_AutoRestartRxMode, "","(RX) increment AutoRestartRxMode"},
+    { MODEM_FSK, "alc", cmd_fsk_AfcAutoClearOn, "","(RX) toggle AfcAutoClearOn"},
+    { MODEM_FSK, "mp", cmd_MapPreambleDetect, "","(RX) toggle MapPreambleDetect"},
+    { MODEM_BOTH, "op", cmd_op, "<dBm>","(TX) get/set TX power"}, 
+    
+#ifdef LORA_TX_TEST
+    { MODEM_LORA, "apl", cmd_lora_all_payload_lengths, "","(TXTEST) sweep payload lengths 0->255"},
+    { MODEM_LORA, "csn", cmd_lora_sync_sweep, "[12]","(TXTEST) sweep ppg symbol"},
+    { MODEM_LORA, "ss", cmd_lora_sync_lo_nibble, "","(TXTEST) ppg low nibble"},
+    { MODEM_LORA, "cpl", lora_cycle_payload_length, "[%d stop]","(TXTEST) sweep payload length"},
+    { MODEM_LORA, "ro", cmd_lora_data_ramp, "[%d bytes]","(TXTEST) sweep payload data"},
+    { MODEM_LORA, "ccr", cmd_lora_cycle_codingrates, "","(TXTEST) cycle coding rates"},
+    { MODEM_LORA, "fps", cmd_lora_fixed_payload_symbol, "[symbol_num n_bits]","(TXTEST) sweep symbol, n_bits=bits per symbol set (sf8=24, sf9=28, etc)"},    
+    { MODEM_LORA, "fpo", cmd_fixed_payload_offset, "<nbytes>","(TXTEST) padding offset for fp tests"},
+    { MODEM_LORA, "fp", cmd_lora_fixed_payload, "[bytes]","(TXTEST) fixed payload"},
+    { MODEM_LORA, "tab", cmd_lora_toggle_all_bits, "[byte length]","(TXTEST) toggle all bits"},
+    { MODEM_LORA, "tcrc", cmd_lora_toggle_crcOn, "","(TXTEST) toggle crcOn"},
+    { MODEM_LORA, "thm", cmd_lora_toggle_header_mode, "","(TXTEST) toggle explicit/implicit"},
+    { MODEM_BOTH, "ttr", cmd_tx_ticker_rate, "<%f seconds>","(TXTEST) get/set tx_ticker rate"}, 
+#endif /* LORA_TX_TEST */ 
+    
+    { MODEM_LORA, "cadper", cmd_cadper, "","Eiger PER RX using CAD" },
+    { MODEM_LORA, "cad", cmd_cad, "<%d num tries>","(RX) run channel activity detection" },
+    { MODEM_LORA, "iqinv", cmd_lora_rx_invert, "","(RX) toggle RX IQ invert" },
+    { MODEM_LORA, "cin", cmd_lora_tx_invert, "","(TX) toggle TX IQ invert" },   
+    { MODEM_LORA, "lhp", cmd_hop_period, "<%d>","(RX) get/set hop period"},
+    { MODEM_LORA, "sync", cmd_lora_ppg, "<%x>","get/set sync (post-preamble gap)"},
+    { MODEM_LORA, "cr", cmd_codingRate, "<1-4>","get/set codingRate"},
+    { MODEM_LORA, "lhm", cmd_lora_header_mode, "","toggle explicit/implicit"},
+    { MODEM_LORA, "vh", cmd_lora_poll_validHeader, "","toggle polling of validHeader"},
+    { MODEM_LORA, "sf", cmd_lora_sf, "<%d>","get/set spreadingFactor"}, 
+    { MODEM_LORA, "ldr", cmd_LowDataRateOptimize, "","toggle LowDataRateOptimize"}, 
+    { MODEM_LORA, "txc", cmd_lora_continuous_tx, "","(TX) toggle TxContinuousMode"},
+    { MODEM_BOTH, "tx", cmd_tx, "<%d>","transmit packet. optional payload length"},    
+    { MODEM_BOTH, "bw", cmd_bandwidth, "<kHz>","get/set bandwith"},
+    { MODEM_LORA, "rxt", cmd_rx_timeout, "<%d>","(RX) get/set SymbTimeout"},
+    { MODEM_LORA, "rxs", cmd_rx_single, "","start RX_SINGLE"},
+    { MODEM_BOTH, "rx", cmd_rx, "","start RX"},   
+        
+    { MODEM_BOTH, "h", cmd_hw_reset, "","hardware reset"},
+    { MODEM_BOTH, "i", cmd_init, "","initialize radio driver"},
+    { MODEM_BOTH, "R", cmd_read_all_regs, "","read all radio registers"},
+    { MODEM_BOTH, "r", cmd_radio_reg_read, "[%x]","read single radio register"},
+    { MODEM_BOTH, "w", cmd_radio_reg_write, "[%x %x]","write single radio register"},
+
+    { MODEM_BOTH, "L", cmd_toggle_modem, "","toggle between LoRa / FSK"},   
+    { MODEM_FSK, "E", cmd_empty_fifo, "","empty out FIFO"}, 
+    { MODEM_FSK, "c", cmd_fsk_test_case, "<%d>","get/set test cases"},
+    { MODEM_BOTH, "d", cmd_set_dio, "<%d pin num>","increment dio mapping"},
+    { MODEM_BOTH, ".", cmd_print_status, "","print status"},
+    { MODEM_BOTH, "?", cmd_help, "","this list of commands"},
+    { MODEM_BOTH, NULL, NULL, NULL }
+};
+
+void cmd_help(uint8_t args_at)
+{
+    int i;
+    
+    for (i = 0; menu_items[i].cmd != NULL ; i++) {
+        if (menu_items[i].modem == MODEM_BOTH)
+            printf("%s%s\t%s\r\n", menu_items[i].cmd, menu_items[i].arg_descr, menu_items[i].description);
+    }
+    
+    if (radio.RegOpMode.bits.LongRangeMode) {
+        for (i = 0; menu_items[i].cmd != NULL ; i++) {
+            if (menu_items[i].modem == MODEM_LORA)
+                printf("%s%s\t(LoRa) %s\r\n", menu_items[i].cmd, menu_items[i].arg_descr, menu_items[i].description);            
+        }
+    } else {
+        for (i = 0; menu_items[i].cmd != NULL ; i++) {
+            if (menu_items[i].modem == MODEM_FSK)
+                printf("%s%s\t(FSK) %s\r\n", menu_items[i].cmd, menu_items[i].arg_descr, menu_items[i].description);
+        }
+    }
 }
 
 void
 console()
 {
-    //int len, i;
-    int i, n;
-    uint32_t ui;
-    uint8_t a, d;
-    static uint16_t fsk_tx_length;
+    int i;
+    uint8_t user_cmd_len;
     
-    service_radio();
+    if (poll_irq_en)
+        poll_service_radio();
+    else
+        service_radio();    
         
-    //len = get_kbd_str(pcbuf, sizeof(pcbuf));
     if (pcbuf_len < 0) {
         printf("abort\r\n");
+        cadper_enable = false;
         per_en = false;
         pcbuf_len = 0;
         if ((radio.RegOpMode.bits.Mode != RF_OPMODE_SLEEP) && (radio.RegOpMode.bits.Mode != RF_OPMODE_STANDBY)) {
             radio.set_opmode(RF_OPMODE_STANDBY);
         }
+        on_txdone_state = ON_TXDONE_STATE_NONE;
+        tx_ticker.detach();
         return;
     }
     if (pcbuf_len == 0)
         return;
-    
+        
     printf("\r\n");
-    if (pcbuf_len == 1) {
-        switch (pcbuf[0]) {
-            case 'i':
-                printf("init\r\n");
-                radio.init();
-                if (!radio.RegOpMode.bits.LongRangeMode) {
-                    fsk.init();   // put FSK modem to some functioning default
-                } else {
-                    // lora configuration is more simple
-                }
-                break;
-            case 'h':
-                printf("hw_reset()\r\n");
-                radio.hw_reset();
-                break;
-            case 'R':
-                // read all registers
-                for (a = 1; a < 0x71; a++) {
-                    d = radio.read_reg(a);
-                    //update_shadow_regs(selected_radio, a, d); 
-                    printf("%02x: %02x\r\n", a, d);
-                }
-                break;
-            case 'r':
-                if (radio.RegOpMode.bits.Mode != RF_OPMODE_RECEIVER) {
-                    radio.set_opmode(RF_OPMODE_RECEIVER);
-                    wait_us(10000);
-                }
-                if (radio.RegOpMode.bits.LongRangeMode)
-                    printf("rssi:%ddBm\r\n", lora.get_current_rssi());
-                else
-                    printf("rssi:-%.1f\r\n", radio.read_reg(REG_FSK_RSSIVALUE) / 2.0);
-                break;
-            case 'T':
-                if (radio.RegOpMode.bits.LongRangeMode) {
-                    lora.RegModemConfig2.octet = radio.read_reg(REG_LR_MODEMCONFIG2);
-                    //printf("a %02x\r\n", radio.RegModemConfig2.octet);
-                    lora.RegModemConfig2.sx1276bits.TxContinuousMode ^= 1;   // same for sx1272 and sx1276
-                    //printf("b %02x\r\n", radio.RegModemConfig2.octet);
-                    radio.write_reg(REG_LR_MODEMCONFIG2, lora.RegModemConfig2.octet);
-                    lora.RegModemConfig2.octet = radio.read_reg(REG_LR_MODEMCONFIG);
-                    //printf("c %02x\r\n", radio.RegModemConfig2.octet);
-                    lora_printTxContinuousMode();
-                    printf("\r\n");
-                }
-                break;
-            case 'c':
-                if (!radio.RegOpMode.bits.LongRangeMode) {
-                    printf("%dbps fdev:%dhz ", fsk.get_bitrate(), fsk.get_tx_fdev_hz());
-                    printf("rxbw:%dHz ", fsk.get_rx_bw_hz(REG_FSK_RXBW));
-                    printf("afcbw:%dHz preambleLen:%d\r\n", fsk.get_rx_bw_hz(REG_FSK_AFCBW), radio.read_u16(REG_FSK_PREAMBLEMSB));
-                }
-                break;            
-            case 'C':
-                if (radio.RegOpMode.bits.LongRangeMode) {
-                    lora.setRxPayloadCrcOn(!lora.getRxPayloadCrcOn());
-                    lora_printRxPayloadCrcOn();
-                } else {
-                    printf("CrcOn:");
-                    fsk.RegPktConfig1.bits.CrcOn ^= 1;
-                    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);
-                    if (fsk.RegPktConfig1.bits.CrcOn)
-                        printf("On\r\n");
-                    else
-                        printf("Off\r\n");
-                    if (fsk.RegPktConfig2.bits.DataModePacket && radio.RegOpMode.bits.Mode == RF_OPMODE_RECEIVER) {
-                        fsk.config_dio0_for_pktmode_rx();
-                    }
-                }
-                printf("\r\n");
-                break;
-            case 'B':
-                radio.RegPaConfig.bits.PaSelect ^= 1;
-                radio.write_reg(REG_PACONFIG, radio.RegPaConfig.octet);
-                printPa();
-                printf("\r\n");
-                break;
-            case 'L':  
-                if (radio.RegOpMode.bits.LongRangeMode)
-                    fsk.enable(false);
-                else
-                    lora.enable();
-
-                radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
-                if (radio.RegOpMode.bits.LongRangeMode)
-                    printf("LoRa\r\n");
-                else
-                    printf("FSK\r\n");                
-                break;
-            case 's':
-                if (!radio.RegOpMode.bits.LongRangeMode) {
-                    fsk.RegFifoThreshold.bits.TxStartCondition ^= 1;
-                    radio.write_reg(REG_FSK_FIFOTHRESH, fsk.RegFifoThreshold.octet);
-                    printf("TxStartCondition:");
-                    if (fsk.RegFifoThreshold.bits.TxStartCondition)
-                        printf("!FifoEmpty\r\n");
-                    else
-                        printf("FifoLevel\r\n");                    
-                }
-                break;
-            case 'f':
-                if (!radio.RegOpMode.bits.LongRangeMode) {
-                    printf("PacketFormat:");
-                    fsk.RegPktConfig1.bits.PacketFormatVariable ^= 1;
-                    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);
-                    if (fsk.RegPktConfig1.bits.PacketFormatVariable)
-                        printf("variable\r\n");
-                    else
-                        printf("fixed\r\n");
-                    /*if (radio.RegOpMode.bits.Mode == RF_OPMODE_RECEIVER)
-                        reset_flow();*/
-                }
-                break;
-            case 'E':
-                if (!radio.RegOpMode.bits.LongRangeMode) {
-                    RegIrqFlags2_t RegIrqFlags2;
-                    RegIrqFlags2.octet = radio.read_reg(REG_FSK_IRQFLAGS2);
-                    while (!RegIrqFlags2.bits.FifoEmpty) {
-                        if (pc.readable())
-                            break;
-                        printf("%02x\r\n", radio.read_reg(REG_FIFO));
-                        RegIrqFlags2.octet = radio.read_reg(REG_FSK_IRQFLAGS2);
-                    }
-                }
-                break;
-            case 'A':
-                if (!radio.RegOpMode.bits.LongRangeMode) {
-                    fsk.RegRxConfig.bits.AfcAutoOn ^= 1;
-                    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);
-                    printf("AfcAuto:");
-                    if (fsk.RegRxConfig.bits.AfcAutoOn)
-                        printf("On\r\n");
-                    else
-                        printf("OFF\r\n");
-                    break;
-                }
-                break;
-            case '?':
-                printf("L           toggle LongRangeMode/FSK\r\n");
-                printf("i           radio_init\r\n");
-                printf("h           hw_reset\r\n");
-                printf("tx[%%d]    transmit (optional packet length)\r\n");
-                printf("rx          receive\r\n");   
-                printf("C           toggle crcOn\r\n");
-                printf("op[%%d]    get/set output power\r\n");
-                printf("bgr[%%d]        get/set prog_txdac BGR bias for TXDAC (7=+20dBm)\r\n"); 
-                printf("ocp[%%d]     get/set over-current protection (mA)\r\n");
-                printf("d[0-5]      change DIO pin assignment\r\n");
-                printf("frf[%%f]   get/set operating frequency (MHz)\r\n");
-                printf("r %%x        read radio register (addr)\r\n");
-                printf("w %%x %%x     write radio register (addr data)\r\n");
-                printf("per         toggle PER enable (\"tx\" to start, ctrl-C to stop)\r\n"); 
-                printf("pin[%%f]         get/set per_tx_delay (seconds)\r\n");                 
-                printf("pid[%%d]        get/set PER device ID\r\n");               
-                if (radio.RegOpMode.bits.LongRangeMode) {
-                    printf("pl[%%d]    LORA get/set RegPayloadLength\r\n");
-                    printf("cr[1234]    LORA set coding rate \r\n");
-                    printf("bw[%%d]    LORA get/set bandwidth\r\n");
-                    printf("sf[%%d]    LORA get/set spreading factor\r\n");
-                    printf("T           LORA toggle TxContinuousMode\r\n");
-                    printf("hp[%%d]    LORA get/set hop period\r\n");
-                    printf("hm          LORA toggle explicit/explicit header mode\r\n");
-                    printf("rin          LORA toggle RX invert_i_q\r\n");
-                    printf("tin          LORA toggle chirp_invert_tx\r\n");
-                    printf("ld          LORA toggle LowDataRateOptimize\r\n");
-                } else {
-                    printf("bw[a][%%d] FSK get-set rxbw (bwa=afcbw)\r\n");
-                    printf("br[%%d]    FSK get-set bitrate\r\n");
-                    printf("c[%%d]       FSK set test case\r\n");
-                    printf("fdev[%%d]    FSK get-set TX frequency deviation (hz)\r\n");
-                    printf("rt          FSK change RxTrigger\r\n");
-                    printf("pd          FSK enable/disable preamble detector\r\n");
-                    printf("pt          FSK get-set PreambleDetectorTol\r\n");
-                    printf("ro[%%d]     FSK get-set RssiOffset\r\n");
-                    printf("rs[%%d]     FSK get-set RssiSmoothing\r\n");
-                    printf("ss[%%d]    FSK get-set SyncSize\r\n");
-                    printf("S[%%x]     FSK get-set sync word\r\n");
-                    printf("s           FSK toggle TxStartCondition\r\n");
-                    printf("f           FSK toggle PacketFormat fixed-variable\r\n");
-                    printf("E           FSK empty out the fifo\r\n");
-                    printf("ac          FSK AfcClear\r\n");
-                    printf("A           FSK toggle AfcAutoOn\r\n");
-                    printf("mp          FSK toggle MapPreambleDetect\r\n");
-                    printf("ar          FSK change AutoRestartRxMode\r\n");
-                    printf("alc          FSK toggle AfcAutoClearOn\r\n");
-                    printf("pre[%%d]    FSK get-set TX preamble length\r\n");
-                }
-                break;
-            case ',':
-                break;
-            case '.':
-                if (radio.type == SX1276) {
-#ifdef TARGET_MTS_MDOT_F411RE
-                    printf("\r\nSX1276 ");
-#else
-                    if (shield_type == SHIELD_TYPE_LAS)
-                        printf("\r\nSX1276LAS ");
-                    if (shield_type == SHIELD_TYPE_MAS)
-                        printf("\r\nSX1276MAS ");
-#endif /* !TARGET_MTS_MDOT_F411RE */                       
-                } else if (radio.type == SX1272)
-                    printf("\r\nSX1272 ");
-                                
-                if (radio.RegOpMode.bits.LongRangeMode)
-                    lora_print_status();
-                else
-                    fsk_print_status();
-                common_print_status();
-                break;
-        } // ...switch (pcbuf[0])
-    } else {
-        if (pcbuf[0] == 't' && pcbuf[1] == 'x') { // TX
-            if (per_en) {
-                printf("timeout attach %f\r\n", per_tx_delay);
-                PacketTxCnt = 0;
-                per_timeout.attach(&per_cb, per_tx_delay);                
-            } else {
-                if (radio.RegOpMode.bits.LongRangeMode) {          
-                    if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                        sscanf(pcbuf+2, "%d", &i);
-                        lora.RegPayloadLength = i;
-                    }
-                    tx_cnt++;
-                    for (i = 0; i < lora.RegPayloadLength; i++)
-                        radio.tx_buf[i] = tx_cnt;
-                    lora.start_tx(lora.RegPayloadLength);
-                } else {    // FSK:
-                    if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                        sscanf(pcbuf+2, "%d", &i);
-                        fsk_tx_length = i;
-                    }
-                    if (radio.RegOpMode.bits.Mode != RF_OPMODE_TRANSMITTER) { // if not already busy transmitting
-                        tx_cnt++;
-                        for (i = 0; i < fsk_tx_length; i++) {
-                            radio.tx_buf[i] = tx_cnt;
-                        }
-                        fsk.start_tx(fsk_tx_length);
-                    }
-                } // !LoRa
-            } // !per_en
-        } else if (pcbuf[0] == 'h' && pcbuf[1] == 'p' && radio.RegOpMode.bits.LongRangeMode) {
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                sscanf(pcbuf+2, "%d", &i);
-                lora.RegHopPeriod = i;
-                radio.write_reg(REG_LR_HOPPERIOD, lora.RegHopPeriod);
-                if (radio.RegDioMapping1.bits.Dio1Mapping != 1) {
-                    radio.RegDioMapping1.bits.Dio1Mapping = 1;
-                    radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
-                }
-            }
-            lora.RegHopPeriod = radio.read_reg(REG_LR_HOPPERIOD);
-            printf("HopPeriod:0x%02x\r\n", lora.RegHopPeriod);
-        } else if (pcbuf[0] == 'r' && pcbuf[1] == 'o' && !radio.RegOpMode.bits.LongRangeMode) {
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                sscanf(pcbuf+2, "%d", &i);
-                fsk.RegRssiConfig.bits.RssiOffset = i;
-                radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
-            }            
-            fsk.RegRssiConfig.octet = radio.read_reg(REG_FSK_RSSICONFIG);          
-            printf("RssiOffset:%d\r\n", fsk.RegRssiConfig.bits.RssiOffset);
-        } else if (pcbuf[0] == 'r' && pcbuf[1] == 's' && !radio.RegOpMode.bits.LongRangeMode) {     
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                sscanf(pcbuf+2, "%d", &i);
-                fsk.RegRssiConfig.bits.RssiSmoothing = i;
-                radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
-            }                        
-            fsk.RegRssiConfig.octet = radio.read_reg(REG_FSK_RSSICONFIG);
-            printf("RssiSmoothing:%d\r\n", fsk.RegRssiConfig.bits.RssiSmoothing);   
-        } else if (pcbuf[0] == 'r' && pcbuf[1] == 't' && !radio.RegOpMode.bits.LongRangeMode) {
-            printf("RxTrigger:");
-            switch (fsk.RegRxConfig.bits.RxTrigger) {
-                case 0: fsk.RegRxConfig.bits.RxTrigger = 1;
-                    printf("rssi\r\n");
-                    break;
-                case 1: fsk.RegRxConfig.bits.RxTrigger = 6;
-                    printf("preamble\r\n");
-                    break;
-                case 6: fsk.RegRxConfig.bits.RxTrigger = 7;
-                    printf("both\r\n");
-                    break;
-                case 7: fsk.RegRxConfig.bits.RxTrigger = 0;
-                    printf("none\r\n");
-                    break;
-                default: fsk.RegRxConfig.bits.RxTrigger = 0;
-                    printf("none\r\n");
-                    break;
-                }
-            radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);            
-        } else if (pcbuf[0] == 'r' && pcbuf[1] == 'x') { // RX
-            if (radio.HF) {
-                radio.RegLna.bits.LnaBoostHF = 3;
-                radio.write_reg(REG_LNA, radio.RegLna.octet);
-            }
-            if (per_en) {
-                PacketNormalCnt = 0;
-                PacketRxSequencePrev = -1;
-                PacketPerKoCnt = 0;
-                PacketPerOkCnt = 0;                
-                //dio3.rise(&dio3_cb);
-            }        
-            if (radio.RegOpMode.bits.LongRangeMode)
-                lora.start_rx();
-            else {
-                fsk.start_rx();
-                radio.RegDioMapping1.bits.Dio2Mapping = 3;  // dio2 to syncadrs
-                radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet); 
-                if (radio.HF) {
-                    fsk.RegRssiConfig.octet = radio.read_reg(REG_FSK_RSSICONFIG);
-                    fsk.RegRssiConfig.bits.RssiOffset = FSK_RSSI_OFFSET;
-                    fsk.RegRssiConfig.bits.RssiSmoothing = FSK_RSSI_SMOOTHING;
-                    radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
-                }                                
-            }
-        } else if (pcbuf[0] == 'r' && pcbuf[1] == ' ') { // read single register
-            sscanf(pcbuf+2, "%x", &i);
-            printf("%02x: %02x\r\n", i, radio.read_reg(i));
-        } else if (pcbuf[0] == 'w' && pcbuf[1] == ' ') { // write single register
-            sscanf(pcbuf+2, "%x %x", &i, &n);
-            radio.write_reg(i, n);
-            printf("%02x: %02x\r\n", i, radio.read_reg(i));
-        }
-#ifdef LORA_WAN_ENABLE
-        else if (pcbuf[0] == 'm' && pcbuf[1] == 'i') {
-            mic_check ^= 1;
-            printf("mic_check:%d\r\n", mic_check);
+        
+    /* get end of user-entered command */
+    user_cmd_len = 1;   // first character can be any character
+    for (i = 1; i <= pcbuf_len; i++) {
+        if (pcbuf[i] < 'A' || (pcbuf[i] > 'Z' && pcbuf[i] < 'a') || pcbuf[i] > 'z') {
+            user_cmd_len = i;
+            break;
         }
-#endif /* LORA_WAN_ENABLE */             
-        else if (pcbuf[0] == 'm' && pcbuf[1] == 'p' && !radio.RegOpMode.bits.LongRangeMode) {
-            radio.RegDioMapping2.bits.MapPreambleDetect ^= 1;
-            radio.write_reg(REG_DIOMAPPING2, radio.RegDioMapping2.octet);
-            printf("MapPreambleDetect:");
-            if (radio.RegDioMapping2.bits.MapPreambleDetect)
-                printf("preamble\r\n");
-            else
-                printf("rssi\r\n");
-        } else if (pcbuf[0] == 'b' && pcbuf[1] == 'g' && pcbuf[2] == 'r') { // bandgap reference for TX dac
-            RegPdsTrim1_t pds_trim;
-            uint8_t adr;
-            if (radio.type == SX1276)
-                adr = REG_PDSTRIM1_SX1276;
-            else
-                adr = REG_PDSTRIM1_SX1272;
-               
-            pds_trim.octet = radio.read_reg(adr);         
-            if (pcbuf[3] >= '0' && pcbuf[3] <= '9') {
-                sscanf(&pcbuf[3], "%d", &i);
-                pds_trim.bits.prog_txdac = i;
-            }
-            radio.write_reg(adr, pds_trim.octet);
-            printf("prog_txdac:%.1fuA\r\n", 2.5 + (pds_trim.bits.prog_txdac * 0.625));
-            /* increase OCP threshold to allow more power */
-            radio.RegOcp.octet = radio.read_reg(REG_OCP);
-            if (radio.RegOcp.bits.OcpTrim < 16) {
-                radio.RegOcp.bits.OcpTrim = 16;
-                radio.write_reg(REG_OCP, radio.RegOcp.octet);
-            }                    
-        } else if (pcbuf[0] == 'o' && pcbuf[1] == 'c' && pcbuf[2] == 'p') {
-            if (pcbuf[3] >= '0' && pcbuf[3] <= '9') {
-                sscanf(pcbuf+3, "%d", &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);
-            }            
-            radio.RegOcp.octet = radio.read_reg(REG_OCP);
-            if (radio.RegOcp.bits.OcpTrim < 16)
-                i = 45 + (5 * radio.RegOcp.bits.OcpTrim);
-            else if (radio.RegOcp.bits.OcpTrim < 28)
-                i = (10 * radio.RegOcp.bits.OcpTrim) - 30;
-            else
-                i = 240;
-            printf("Ocp: %dmA\r\n", i);                  
-        } else if (pcbuf[0] == 'o' && pcbuf[1] == 'p') {
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                sscanf(pcbuf+2, "%d", &i);
-                radio.RegPaConfig.bits.OutputPower = i;
-                radio.write_reg(REG_PACONFIG, radio.RegPaConfig.octet);
-            }
-            radio.RegPaConfig.octet = radio.read_reg(REG_PACONFIG);
-            printf("OutputPower:%d\r\n", radio.RegPaConfig.bits.OutputPower);
-        } else if (pcbuf[0] == 'c' && (pcbuf[1] >= '0' && pcbuf[1] <= '9') && !radio.RegOpMode.bits.LongRangeMode) {
-            radio.set_opmode(RF_OPMODE_STANDBY);
-            per_tx_delay = 0.3;
-            
-            if (radio.read_reg(REG_FSK_SYNCVALUE1) == 0x55 && radio.read_reg(REG_FSK_SYNCVALUE2)) {
-                fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);    
-                fsk.RegSyncConfig.bits.SyncSize = 2;
-                radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
-                radio.write_reg(REG_FSK_SYNCVALUE3, 0x90);
-                radio.write_reg(REG_FSK_SYNCVALUE2, 0x4e);
-                radio.write_reg(REG_FSK_SYNCVALUE1, 0x63);              
-            }
-            
-            fsk.RegPreambleDetect.octet = radio.read_reg(REG_FSK_PREAMBLEDETECT);
-            fsk.RegPreambleDetect.bits.PreambleDetectorOn = 1;
-            radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);                 
-            
-            fsk.RegRxConfig.octet = radio.read_reg(REG_FSK_RXCONFIG);
-            fsk.RegRxConfig.bits.AfcAutoOn = 1;
-            fsk.RegRxConfig.bits.AgcAutoOn = 1;
-            fsk.RegRxConfig.bits.RxTrigger = 7;
-            radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);    
-            
-            fsk.RegPreambleDetect.bits.PreambleDetectorOn = 1;
-            fsk.RegPreambleDetect.bits.PreambleDetectorSize = 1;
-            fsk.RegPreambleDetect.bits.PreambleDetectorTol = 10;
-            radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);            
-            
-            switch (pcbuf[1]) {
-                case '0':
-                    fsk.set_bitrate(4800);
-                    fsk.set_tx_fdev_hz(5005);
-                    fsk.set_rx_dcc_bw_hz(10417, 0);  // rxbw
-                    fsk.set_rx_dcc_bw_hz(50000, 1);  // afcbw
-                    radio.write_u16(REG_FSK_PREAMBLEMSB, 8);                    
-                    break;
-                case '1':
-                    fsk.set_bitrate(50000);
-                    fsk.set_tx_fdev_hz(25000);
-                    fsk.set_rx_dcc_bw_hz(62500, 0);  // rxbw
-                    fsk.set_rx_dcc_bw_hz(100000, 1);  // afcbw
-                    radio.write_u16(REG_FSK_PREAMBLEMSB, 9);                    
-                    break;           
-                case '2':
-                    fsk.set_bitrate(38400);
-                    fsk.set_tx_fdev_hz(20020);
-                    fsk.set_rx_dcc_bw_hz(50000, 0);  // rxbw
-                    fsk.set_rx_dcc_bw_hz(100000, 1);  // afcbw
-                    radio.write_u16(REG_FSK_PREAMBLEMSB, 8);                        
-                    break;
-                case '3':
-                    fsk.set_bitrate(1201);
-                    fsk.set_tx_fdev_hz(20020);
-                    fsk.set_rx_dcc_bw_hz(25000, 0);  // rxbw
-                    fsk.set_rx_dcc_bw_hz(50000, 1);  // afcbw
-                    radio.write_u16(REG_FSK_PREAMBLEMSB, 8);                 
-                    break;    
-                case '4':
-                    fsk.set_bitrate(1201);
-                    fsk.set_tx_fdev_hz(4028);
-                    fsk.set_rx_dcc_bw_hz(7813, 0);  // rxbw
-                    fsk.set_rx_dcc_bw_hz(25000, 1);  // afcbw
-                    radio.write_u16(REG_FSK_PREAMBLEMSB, 8);
-                    break;
-                case '5':
-                    fsk.set_bitrate(1201);
-                    fsk.set_tx_fdev_hz(4028);
-                    fsk.set_rx_dcc_bw_hz(5208, 0);  // rxbw
-                    fsk.set_rx_dcc_bw_hz(10417, 1);  // afcbw
-                    radio.write_u16(REG_FSK_PREAMBLEMSB, 8);                    
-                    break;                                                                 
-            } // ...switch (pcbuf[1])
-            printf("%dbps fdev:%dhz ", fsk.get_bitrate(), fsk.get_tx_fdev_hz());
-            printf("rxbw:%dHz ", fsk.get_rx_bw_hz(REG_FSK_RXBW));
-            printf("afcbw:%dHz preambleLen:%d\r\n", fsk.get_rx_bw_hz(REG_FSK_AFCBW), radio.read_u16(REG_FSK_PREAMBLEMSB));
-        } else if (pcbuf[0] == 'c' && pcbuf[1] == 'r' && radio.RegOpMode.bits.LongRangeMode) {
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9')
-                lora.setCodingRate(pcbuf[2] - '0');
-             lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
-             lora_printCodingRate(false);    // false: transmitted
-             printf("\r\n");
-        } else if (pcbuf[0] == 'h' && pcbuf[1] == 'm' && radio.RegOpMode.bits.LongRangeMode) {    // toggle implicit/explicit
-            lora.setHeaderMode(!lora.getHeaderMode());
-            lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
-            lora_printHeaderMode();
-            printf("\r\n");
-        } else if (pcbuf[0] == 'a' && pcbuf[1] == 'l' && !radio.RegOpMode.bits.LongRangeMode) {
-            fsk.RegAfcFei.bits.AfcAutoClearOn ^= 1;
-            printf("AfcAutoClearOn: ");
-            radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
-            if (fsk.RegAfcFei.bits.AfcAutoClearOn)
-                printf("ON\r\n");
-            else
-                printf("off\r\n");
-        } else if (pcbuf[0] == 'a' && pcbuf[1] == 'r' && !radio.RegOpMode.bits.LongRangeMode) {
-            fsk.RegSyncConfig.bits.AutoRestartRxMode++;
-            radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
-            fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);
-            printf("AutoRestartRxMode:");
-            switch (fsk.RegSyncConfig.bits.AutoRestartRxMode) {
-                case 0: printf("off "); break;
-                case 1: printf("no-pll-wait "); break;
-                case 2: printf("pll-wait "); break;
-                case 3: printf("3 "); break;
-            }
-            printf("\r\n");            
-        } else if (pcbuf[0] == 'a' && pcbuf[1] == 'c' && !radio.RegOpMode.bits.LongRangeMode) {
-            printf("clear afc: ");
-            fsk.RegAfcFei.bits.AfcClear = 1;
-            radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
-            fsk.RegAfcFei.bits.AfcClear = 0; 
-            printf("%dHz\r\n", (int)(FREQ_STEP_HZ * radio.read_s16(REG_FSK_AFCMSB)));
-        } else if (pcbuf[0] == 'b' && pcbuf[1] == 'r' && !radio.RegOpMode.bits.LongRangeMode) {
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                sscanf(&pcbuf[2], "%d", &i);
-                fsk.set_bitrate(i);
-            }
-            printf("%dbps\r\n", fsk.get_bitrate());             
-        } else if (pcbuf[0] == 'b' && pcbuf[1] == 'w') {
-            if (radio.RegOpMode.bits.LongRangeMode) {
-                if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                    radio.set_opmode(RF_OPMODE_STANDBY);
-                    sscanf(&pcbuf[2], "%d", &i);
-                    lora.setBw(i);
-                } else
-                    lora_printAllBw();
-                lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
-                printf("current ");
-                lora_printBw();
-                printf("\r\n");
-            } else { // FSK:
-                if (pcbuf[2] == 'a') {
-                    if (pcbuf[3] >= '0' && pcbuf[3] <= '9') {
-                        radio.set_opmode(RF_OPMODE_STANDBY);
-                        sscanf(&pcbuf[3], "%d", &i);
-                        fsk.set_rx_dcc_bw_hz(i, 1);
-                    }
-                    printf("afcbw:%dHz\r\n", fsk.get_rx_bw_hz(REG_FSK_AFCBW));
-                } else {
-                    if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                        radio.set_opmode(RF_OPMODE_STANDBY);
-                        sscanf(&pcbuf[2], "%d", &i);
-                        fsk.set_rx_dcc_bw_hz(i, 0);
-                    }
-                    printf("rxbw:%dHz\r\n", fsk.get_rx_bw_hz(REG_FSK_RXBW));
-                }
-            }
-        } else if (pcbuf[0] == 'v' && pcbuf[1] == 'h') {
-            lora.poll_vh ^= 1;
-            printf("poll_vh:%d\r\n", lora.poll_vh);
-        } else if (pcbuf[0] == 'S' && !radio.RegOpMode.bits.LongRangeMode) {
-            if (pcbuf[1] == '0') {
-                sscanf(pcbuf+1, "%x", &ui);
-                if (ui < 0x100) {
-                    fsk.RegSyncConfig.bits.SyncSize = 0;
-                    radio.write_reg(REG_FSK_SYNCVALUE1, ui);
-                } else if (ui < 0x10000) {
-                    fsk.RegSyncConfig.bits.SyncSize = 1;
-                    radio.write_reg(REG_FSK_SYNCVALUE2, ui & 0xff);
-                    radio.write_reg(REG_FSK_SYNCVALUE1, ui >> 8);
-                } else if (ui < 0x1000000) {
-                    fsk.RegSyncConfig.bits.SyncSize = 2;
-                    radio.write_reg(REG_FSK_SYNCVALUE3, ui & 0xff);
-                    radio.write_reg(REG_FSK_SYNCVALUE2, (ui >> 8) & 0xff);
-                    radio.write_reg(REG_FSK_SYNCVALUE1, ui >> 16);                              
-                } else {
-                    fsk.RegSyncConfig.bits.SyncSize = 3;
-                    radio.write_reg(REG_FSK_SYNCVALUE4, ui & 0xff);
-                    radio.write_reg(REG_FSK_SYNCVALUE3, (ui >> 8) & 0xff);
-                    radio.write_reg(REG_FSK_SYNCVALUE2, (ui >> 16) & 0xff);
-                    radio.write_reg(REG_FSK_SYNCVALUE1, ui >> 24);                       
-                }
-                radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
-            }
-            fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);
-            printf("%d: ", fsk.RegSyncConfig.bits.SyncSize);
-            for (i = 0; i <= fsk.RegSyncConfig.bits.SyncSize; i++)
-                printf("%02x ", radio.read_reg(REG_FSK_SYNCVALUE1+i));
-            printf("\r\n");
-        } else if (pcbuf[0] == 's' && pcbuf[1] == 's' && !radio.RegOpMode.bits.LongRangeMode) {
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                sscanf(pcbuf+2, "%d", &i);
-                fsk.RegSyncConfig.bits.SyncSize = i;
-                radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
-            }
-            fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);
-            printf("SyncSize:%d\r\n", fsk.RegSyncConfig.bits.SyncSize);
-        } else if (pcbuf[0] == 's' && pcbuf[1] == 'f' && radio.RegOpMode.bits.LongRangeMode) {
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                sscanf(pcbuf+2, "%d", &i);
-                lora.setSf(i);
-                if (i == 6 && !lora.getHeaderMode()) {
-                    printf("SF6: to implicit header mode\r\n");
-                    lora.setHeaderMode(true);
-                }
-            }
-            lora.RegModemConfig2.octet = radio.read_reg(REG_LR_MODEMCONFIG2);
-            lora_printSf();
-            printf("\r\n");
-        } else if (pcbuf[0] == 'f' && pcbuf[1] == 'd' && pcbuf[2] == 'e' && !radio.RegOpMode.bits.LongRangeMode) {
-            if (pcbuf[4] >= '0' && pcbuf[4] <= '9') {
-                sscanf(pcbuf+4, "%d", &i);
-                fsk.set_tx_fdev_hz(i);
-            }
-            printf("fdev:%dHz\r\n", fsk.get_tx_fdev_hz());
-        } else if (pcbuf[0] == 'f' && pcbuf[1] == 'r' && pcbuf[2] == 'f') {
-            if (pcbuf[3] >= '0' && pcbuf[3] <= '9') {
-                float MHz;
-                sscanf(pcbuf+3, "%f", &MHz);
-                //printf("MHz:%f\r\n", MHz);
-                radio.set_frf_MHz(MHz);
-            }
-            printf("%fMHz\r\n", radio.get_frf_MHz());
-#ifndef TARGET_MTS_MDOT_F411RE
-            radio.RegPaConfig.octet = radio.read_reg(REG_PACONFIG);
-            if (shield_type == SHIELD_TYPE_LAS) {
-                // LAS HF=PA_BOOST  LF=RFO
-                if (radio.HF)
-                    radio.RegPaConfig.bits.PaSelect = 1;
-                else
-                    radio.RegPaConfig.bits.PaSelect = 0;
-            }
-            radio.write_reg(REG_PACONFIG, radio.RegPaConfig.octet);          
-#endif /* !TARGET_MTS_MDOT_F411RE */
-        } else if (pcbuf[0] == 'p' && pcbuf[1] == 'i' && pcbuf[2] == 'd') {
-            if (pcbuf[3] >= '0' && pcbuf[3] <= '9') {
-                sscanf(pcbuf+3, "%d", &per_id);
-            }
-            printf("PER device ID:%d\r\n", per_id);
-        } else if (pcbuf[0] == 'p' && pcbuf[1] == 'i' && pcbuf[2] == 'n') {
-            if (pcbuf[3] >= '0' && pcbuf[3] <= '9') {
-                sscanf(pcbuf+3, "%f", &per_tx_delay);
-            }
-            printf("per_tx_delay:%f\r\n", per_tx_delay);
-        } else if (pcbuf[0] == 'p' && pcbuf[1] == 'e' && pcbuf[2] == 'r') {
-            toggle_per_en();      
-        } else if (pcbuf[0] == 'p' && pcbuf[1] == 'r' && pcbuf[2] == 'e') {
-            if (radio.RegOpMode.bits.LongRangeMode) {
-                if (pcbuf[3] >= '0' && pcbuf[3] <= '9') {
-                    sscanf(pcbuf+3, "%d", &i);
-                    radio.write_u16(REG_LR_PREAMBLEMSB, i);
-                }
-                lora.RegPreamble = radio.read_u16(REG_LR_PREAMBLEMSB);
-                printf("lora PreambleLength:%d\r\n", lora.RegPreamble);                
-            } else {
-                if (pcbuf[3] >= '0' && pcbuf[3] <= '9') {
-                    sscanf(pcbuf+3, "%d", &i);
-                    radio.write_u16(REG_FSK_PREAMBLEMSB, i);
-                }
-                printf("FSK TX PreambleSize:%d\r\n", radio.read_u16(REG_FSK_PREAMBLEMSB));
-            }
-        } else if (pcbuf[0] == 'p' && pcbuf[1] == 't' && !radio.RegOpMode.bits.LongRangeMode) {
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                sscanf(pcbuf+2, "%d", &i);
-                fsk.RegPreambleDetect.bits.PreambleDetectorTol = i;
-                radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);
-            }
-            fsk.RegPreambleDetect.octet = radio.read_reg(REG_FSK_PREAMBLEDETECT);
-            printf("PreambleDetectorTol:%d\r\n", fsk.RegPreambleDetect.bits.PreambleDetectorTol);
-        } else if (pcbuf[0] == 'p' && pcbuf[1] == 'd' && !radio.RegOpMode.bits.LongRangeMode) {
-            fsk.RegPreambleDetect.bits.PreambleDetectorOn ^= 1;
-            radio.write_reg(REG_FSK_PREAMBLEDETECT, fsk.RegPreambleDetect.octet);
-            printf("PreambleDetector:");
-            if (fsk.RegPreambleDetect.bits.PreambleDetectorOn)
-                printf("On\r\n");
-            else
-                printf("OFF\r\n");            
-        } else if (pcbuf[0] == 'p' && pcbuf[1] == 'l') {
-            if (pcbuf[2] >= '0' && pcbuf[2] <= '9') {
-                sscanf(pcbuf+2, "%d", &i);
-                if (radio.RegOpMode.bits.LongRangeMode) {
-                    lora.RegPayloadLength = i;
-                    radio.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
-                } else {
-                    fsk.RegPktConfig2.bits.PayloadLength = i;
-                    radio.write_u16(REG_FSK_PACKETCONFIG2, fsk.RegPktConfig2.word);
-                }
-            }
-            if (radio.RegOpMode.bits.LongRangeMode) {
-                lora.RegPayloadLength = radio.read_reg(REG_LR_PAYLOADLENGTH);
-                printf("PayloadLength:%d\r\n", lora.RegPayloadLength);
-            } else {
-                printf("PayloadLength:%d\r\n", fsk_get_PayloadLength());
-            }
-        } else if (pcbuf[0] == 'l' && pcbuf[1] == 'd') {
-            if (radio.type == SX1272) {
-                lora.RegModemConfig.octet = radio.read_reg(REG_LR_MODEMCONFIG);
-                lora.RegModemConfig.sx1272bits.LowDataRateOptimize ^= 1;
-                printf("LowDataRateOptimize:%d\r\n", lora.RegModemConfig.sx1272bits.LowDataRateOptimize);
-                radio.write_reg(REG_LR_MODEMCONFIG, lora.RegModemConfig.octet);
-            } else if (radio.type == SX1276) {
-                lora.RegModemConfig3.octet = radio.read_reg(REG_LR_MODEMCONFIG3);
-                lora.RegModemConfig3.sx1276bits.LowDataRateOptimize ^= 1;
-                printf("LowDataRateOptimize:%d\r\n", lora.RegModemConfig3.sx1276bits.LowDataRateOptimize); 
-                radio.write_reg(REG_LR_MODEMCONFIG3, lora.RegModemConfig3.octet);
-            }
-        } else if (pcbuf[0] == 't' && pcbuf[1] == 'i' && pcbuf[2] == 'n' && radio.RegOpMode.bits.LongRangeMode) {      
-            lora.invert_tx(lora.RegTest33.bits.chirp_invert_tx);
-            printf("chirp_invert_tx :%d\r\n", lora.RegTest33.bits.chirp_invert_tx);              
-        } else if (pcbuf[0] == 'r' && pcbuf[1] == 'i' && pcbuf[2] == 'n' && radio.RegOpMode.bits.LongRangeMode) {
-            lora.invert_rx(!lora.RegTest33.bits.invert_i_q);
-            printf("rx invert_i_q:%d\r\n", lora.RegTest33.bits.invert_i_q);
-        } else if (pcbuf[0] == 'd' && pcbuf[1] >= '0' && pcbuf[1] <= '5') {
-            switch (pcbuf[1]) {
-                case '0':
-                    radio.RegDioMapping1.bits.Dio0Mapping++;
-                    radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
-                    break;
-                case '1':
-                    radio.RegDioMapping1.bits.Dio1Mapping++;
-                    radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
-                    break;    
-                case '2':
-                    radio.RegDioMapping1.bits.Dio2Mapping++;
-                    radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
-                    break;
-                case '3':
-                    radio.RegDioMapping1.bits.Dio3Mapping++;
-                    radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
-                    break;
-                case '4':
-                    radio.RegDioMapping2.bits.Dio4Mapping++;
-                    radio.write_reg(REG_DIOMAPPING2, radio.RegDioMapping2.octet);
-                    break; 
-                case '5':
-                    radio.RegDioMapping2.bits.Dio5Mapping++;
-                    radio.write_reg(REG_DIOMAPPING2, radio.RegDioMapping2.octet);
-                    break;                                                                                             
-            } // ...switch (pcbuf[1])
-            if (radio.RegOpMode.bits.LongRangeMode)
-                lora_print_dio();
-            else
-                fsk_print_dio();
-        } else if (pcbuf[0] == 's' && pcbuf[1] == 't' && pcbuf[2] == 'b') {
-            radio.set_opmode(RF_OPMODE_STANDBY);
-        } else if (pcbuf[0] == 's' && pcbuf[1] == 'l' && pcbuf[2] == 'e') {
-            radio.set_opmode(RF_OPMODE_SLEEP);
-        } else if (pcbuf[0] == 'c' && pcbuf[1] == 'h' && pcbuf[2] == 'a') {
-            app = APP_CHAT;
-            lora.start_rx();
-            printf("chat start\r\n");
-        }           
     }
-    
+
+    for (i = 0; menu_items[i].cmd != NULL ; i++) {
+        int mi_len = strlen(menu_items[i].cmd);
+        if (radio.RegOpMode.bits.LongRangeMode) {
+            if (menu_items[i].modem == MODEM_FSK)
+                continue;  // FSK commands not used in LoRa
+        } else {
+            if (menu_items[i].modem == MODEM_LORA)
+                continue;  // LoRa commands not used in FSK            
+        }
+
+        if (menu_items[i].handler && user_cmd_len == mi_len && (strncmp(pcbuf, menu_items[i].cmd, mi_len) == 0)) {
+            while (pcbuf[mi_len] == ' ')   // skip past spaces
+                mi_len++;
+            menu_items[i].handler(mi_len);
+            break;
+        }
+    }
+   
     pcbuf_len = 0;
     printf("> ");
-    fflush(stdout);
-        
+    fflush(stdout); 
 }
 
 void rx_callback()
@@ -2049,27 +3311,33 @@
     static uint8_t pcbuf_idx = 0;
     static uint8_t prev_len = 0;;
     char c = pc.getc();
-    if (c == 8) {
-        if (pcbuf_idx > 0) {
-            pc.putc(8);
-            pc.putc(' ');
-            pc.putc(8);
-            pcbuf_idx--;
+    /*if (kermit.uart_rx_enabled) {
+        kermit.rx_callback(c);
+    } else*/ {
+        if (c == 8) {
+            if (pcbuf_idx > 0) {
+                pc.putc(8);
+                pc.putc(' ');
+                pc.putc(8);
+                pcbuf_idx--;
+            }
+        } else if (c == 3) {    // ctrl-C
+            pcbuf_len = -1;
+        } else if (c == '\r') {
+            if (pcbuf_idx == 0) {
+                pcbuf_len = prev_len;
+            } else {
+                pcbuf[pcbuf_idx] = 0;   // null terminate
+                prev_len = pcbuf_idx;
+                pcbuf_idx = 0;
+                pcbuf_len = prev_len;
+            }
+        }/* else if (c == SOH) {
+            kermit.uart_rx_enable();
+        }*/ else if (pcbuf_idx < sizeof(pcbuf)) {
+            pcbuf[pcbuf_idx++] = c;
+            pc.putc(c);
         }
-    } else if (c == 3) {    // ctrl-C
-        pcbuf_len = -1;
-    } else if (c == '\r') {
-        if (pcbuf_idx == 0) {
-            pcbuf_len = prev_len;
-        } else {
-            pcbuf[pcbuf_idx] = 0;   // null terminate
-            prev_len = pcbuf_idx;
-            pcbuf_idx = 0;
-            pcbuf_len = prev_len;
-        }
-    } else if (pcbuf_idx < sizeof(pcbuf)) {
-        pcbuf[pcbuf_idx++] = c;
-        pc.putc(c);
     }
 }