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:
21:b84a77dfb43c
Parent:
20:b11592c9ba5f
Child:
22:2005df80c8a8
--- a/main.cpp	Wed Aug 31 23:55:27 2016 +0000
+++ b/main.cpp	Sun Feb 05 23:34:30 2017 +0000
@@ -7,6 +7,7 @@
 //#define FSK_PER
 //#define START_EIGER_RX
 //#define START_EIGER_TX
+//#define START_OOK_TX_TEST
 
 DigitalOut led1(LED1);
 Serial pc(USBTX, USBRX);
@@ -24,6 +25,34 @@
 
 #define FSK_LARGE_PKT_THRESHOLD  0x3f
 
+bool crc32_en;  // ethcrc
+
+/*********** cmd_ulrx()... ************/
+typedef enum {
+    ULRX_STATE_OFF = 0,
+    ULRX_STATE_NEED_LENGTH,
+    ULRX_STATE_PAYLOAD,
+    ULRX_STATE_SYNC1
+} ulrx_state_e;
+ulrx_state_e ulrx_state = ULRX_STATE_OFF;
+bool ulrx_enable;
+/*********** ...cmd_ulrx() ************/
+
+uint8_t rx_payload_idx;
+
+/************** fsk modeReady isr... **********/
+bool rx_payloadReady_int_en;  // cmd_prrx()
+#define N_RX_PKTS         32
+#define RX_PKT_SIZE_LIMIT      32
+uint8_t rx_pkts[N_RX_PKTS][RX_PKT_SIZE_LIMIT];
+uint8_t n_rx_pkts;
+/************** ...fsk modeReady isr **********/
+
+#ifdef TARGET_STM
+CRC_HandleTypeDef   CrcHandle;
+#endif /* TARGET_STM */
+
+int rssi_polling_thresh; // 0 = polling off
 bool ook_test_en;
 bool poll_irq_en;
 volatile RegIrqFlags2_t fsk_RegIrqFlags2_prev;
@@ -87,9 +116,18 @@
 DigitalInOut rfsw(A4);
 #endif
 
+InterruptIn dio0int(D2);
+InterruptIn dio1int(D3);
+InterruptIn dio2int(D4);
+InterruptIn dio4int(D8);
 DigitalIn dio2(D4);
+DigitalIn dio3(D5);
 DigitalIn dio4(D8);
 
+#ifdef TARGET_STM
+DigitalOut pc3(PC_3);   // nucleo corner pin for misc indication
+#endif
+
 void rfsw_callback()
 {
     if (radio.RegOpMode.bits.Mode == RF_OPMODE_TRANSMITTER)
@@ -116,6 +154,38 @@
 
 volatile bool saved_dio4;
 
+uint32_t crcTable[256];
+void make_crc_table()
+{
+    const uint32_t POLYNOMIAL = 0xEDB88320;
+    uint32_t remainder;
+    uint8_t b = 0;
+    do{
+        // Start with the data byte
+        remainder = b;
+        for (unsigned long bit = 8; bit > 0; --bit)
+        {
+            if (remainder & 1)
+                remainder = (remainder >> 1) ^ POLYNOMIAL;
+            else
+                remainder = (remainder >> 1);
+        }
+        crcTable[(size_t)b] = remainder;
+    } while(0 != ++b);
+}
+
+uint32_t gen_crc(const uint8_t *p, size_t n)
+{
+    uint32_t crc = 0xffffffff;
+    size_t i;
+    for(i = 0; i < n; i++) {
+        crc = crcTable[*p++ ^ (crc&0xff)] ^ (crc>>8);
+    }
+        
+    return(~crc);
+}
+
+
 void printLoraIrqs_(bool clear)
 {
     //in radio class -- RegIrqFlags_t RegIrqFlags;
@@ -340,16 +410,23 @@
     radio.RegDioMapping1.octet = radio.read_reg(REG_DIOMAPPING1);
     
     printf(" DIO3:");
-    switch (radio.RegDioMapping1.bits.Dio3Mapping) {
-        case 0: printf("Timeout"); break;
-        case 1:
-            if (radio.RegDioMapping2.bits.MapPreambleDetect)
-                printf("preamble");
-            else
-                printf("rssi");
-            break;
-        case 2: printf("?automode_status?"); break;
-        case 3: printf("TempChange/LowBat"); break;
+    if (fsk.RegPktConfig2.bits.DataModePacket) {
+        if (radio.RegDioMapping1.bits.Dio3Mapping == 1)
+            printf("TxReady");
+        else
+            printf("FifoEmpty");
+    } else {
+        switch (radio.RegDioMapping1.bits.Dio3Mapping) {
+            case 0: printf("Timeout"); break;
+            case 1:
+                if (radio.RegDioMapping2.bits.MapPreambleDetect)
+                    printf("preamble");
+                else
+                    printf("rssi");
+                break;
+            case 2: printf("?automode_status?"); break;
+            case 3: printf("TempChange/LowBat"); break;
+        }
     }
     
     printf(" DIO2:");
@@ -872,12 +949,10 @@
     }
     if (fsk.RegImageCal.bits.ImageCalRunning)
         printf("ImageCalRunning[\r0m\n");
-
-/*    printf("flags.fifo_flow_ctl:%d pktidx:%d rx_pktlen:%d", flags.fifo_flow_ctl, pktidx, rx_pktlen);
-    printf("\r\n");
-
-    //printf("DIO0_PIN:%d\r\n", digitalRead(DIO0_PIN));
-    printf("pkt_buf_len=%d remaining=%d\r\n", pk*/
+    
+    if (rx_payloadReady_int_en) {
+        printf("n_rx_pkts:%u, dio:%u,%u,%u,%u,%u\r\n", n_rx_pkts, dio4.read(), dio3.read(), dio2.read(), radio.dio1.read(), radio.dio0.read());
+    }
 }
 
 void printOpMode()
@@ -978,7 +1053,7 @@
     printf("\r\n");
 }
 
-void print_rx_verbose(uint8_t dlen)
+void lora_print_rx_verbose(uint8_t dlen)
 {
     float dbm;
     printLoraIrqs_(false);
@@ -1176,6 +1251,9 @@
 {
     if (radio.RegOpMode.bits.LongRangeMode) {
     } else { // fsk:
+        if (rx_payloadReady_int_en)
+            return;
+
         /*RegIrqFlags2_t RegIrqFlags2;
         if (radio.RegOpMode.bits.Mode == RF_OPMODE_TRANSMITTER) {
             RegIrqFlags2.octet = radio.read_reg(REG_FSK_IRQFLAGS2);
@@ -1372,7 +1450,7 @@
                 if (act == SERVICE_READ_FIFO) {
                     if (!per_parse_rx(lora.RegRxNbBytes)) {
                         PacketNormalCnt++;
-                        print_rx_verbose(lora.RegRxNbBytes);                            
+                        lora_print_rx_verbose(lora.RegRxNbBytes);                            
                     }                        
                 }
                 break;
@@ -1416,6 +1494,16 @@
     static uint8_t rssi = 0;
     
     if (radio.RegOpMode.bits.LongRangeMode) {
+        if (rssi_polling_thresh != 0 && radio.RegOpMode.bits.Mode == RF_OPMODE_RECEIVER) {
+            int rssi = lora.get_current_rssi(); // dBm returned, negative value
+#ifdef TARGET_STM
+            if (rssi < rssi_polling_thresh)
+                pc3 = 0;    // signal weaker than threshold
+            else
+                pc3 = 1;    // signal stronger than threshold            
+#endif
+        }
+        
         if (cadper_enable) {
             cadper_service();
         }
@@ -1428,10 +1516,10 @@
                     if (per_en) {
                         if (!per_parse_rx(lora.RegRxNbBytes)) {
                             PacketNormalCnt++;
-                            print_rx_verbose(lora.RegRxNbBytes);                            
+                            lora_print_rx_verbose(lora.RegRxNbBytes);                            
                         }
                     } else                     
-                        print_rx_verbose(lora.RegRxNbBytes);
+                        lora_print_rx_verbose(lora.RegRxNbBytes);
                     fflush(stdout);
                 } else if (app == APP_CHAT) {
                     if (lora.RegHopChannel.bits.RxPayloadCrcOn) {
@@ -1469,6 +1557,13 @@
         } // ...switch (act)
     } else {
         /* FSK: */
+        
+        if (rx_payloadReady_int_en)
+            return; // radio service by ISR only
+
+        if (ulrx_enable)
+            return;
+
         act = fsk.service();
         
          switch (act) {
@@ -1480,12 +1575,13 @@
                     radio.rx_buf[n] = 0; // null terminate
                     printf((char *)radio.rx_buf);                    
                 } else {
-                    if (fsk.RegRxConfig.bits.AfcAutoOn)
+                    if (fsk.RegRxConfig.bits.AfcAutoOn) {
                         printf("%dHz ", (int)(FREQ_STEP_HZ * fsk.RegAfcValue));   
                         if (rssi != 0) {
                             printf("pkt:-%.1fdBm ", rssi / 2.0);
                             rssi = 0;
                         }    
+                    }
                     if (per_en) { 
                         if (!per_parse_rx(fsk.rx_buf_length)) {
                             PacketNormalCnt++;
@@ -1494,8 +1590,15 @@
                     } else {
                         print_rx_buf(fsk.rx_buf_length);                            
                     }
-                    fflush(stdout);
+                    
                 }
+                if (crc32_en) {
+                    uint32_t c, *u32_ptr = (uint32_t*)&radio.rx_buf[fsk.rx_buf_length-4];
+                    printf("rx crc:%08x, ", *u32_ptr);
+                    c = gen_crc(radio.rx_buf, fsk.rx_buf_length-4);
+                    printf("calc crc:%08x\r\n", c);                    
+                }
+                fflush(stdout);
                 break;
             case SERVICE_TX_DONE:
                 if (ook_test_en)
@@ -1546,6 +1649,16 @@
               = radio.read_reg(REG_FSK_RSSIVALUE);
          }*/
          
+        if (rssi_polling_thresh != 0 && radio.RegOpMode.bits.Mode == RF_OPMODE_RECEIVER) {
+            rssi = radio.read_reg(REG_FSK_RSSIVALUE);
+            rssi = -rssi;
+#ifdef TARGET_STM
+            if (rssi < rssi_polling_thresh)
+                pc3 = 0;    // signal weaker than threshold
+            else
+                pc3 = 1;    // signal stronger than threshold
+        }
+#endif
     } // ...!radio.RegOpMode.bits.LongRangeMode
 }
 
@@ -1614,6 +1727,31 @@
     }
 }
 
+const uint8_t ookt_tx_payload[] = {
+    0x55, 0x55, 0x55, 0x55, 0xA9, 0x66, 0x69, 0x65,
+    0x39, 0x53, 0xAA, 0xC3, 0xA6, 0x95, 0xC6, 0x3C,
+    0x6A, 0x33, 0x33, 0xC6, 0xCA, 0xA6, 0x33, 0x33,
+    0x55, 0x6A, 0xA6, 0xAA, 0x53
+};
+volatile uint32_t ook_tx_cnt = 0;
+
+void callback_ook_tx_test()
+{
+    int i;  
+    
+    //radio.write_reg(REG_FSK_SYNCCONFIG, 0);
+      
+    printf("%u ookTx: ", ook_tx_cnt++);
+    for (i = 0; i < sizeof(ookt_tx_payload); i++) {
+        radio.tx_buf[i] = ookt_tx_payload[i];
+        printf("%02x ", radio.tx_buf[i]);
+    }
+    printf("\r\n");
+    
+    //printf("syncConf:%x\r\n", radio.read_reg(REG_FSK_SYNCCONFIG));
+    fsk.start_tx(sizeof(ookt_tx_payload));     
+}
+
 typedef enum {
     TXTICKER_STATE_OFF = 0,
     TXTICKER_STATE_TOGGLE_PAYLOAD_BIT,
@@ -1885,6 +2023,21 @@
     printf("per_tx_delay:%dms\r\n", (int)(per_tx_delay * 1000));      
 }
 
+const uint8_t test_payload_A[7] = {
+    0x80, 0x02, 0x58, 0xF5, 0xDF, 0xB8, 0x9E
+};
+
+const uint8_t test_payload_B[] = {
+    0xca, 0xfe, 0xba, 0xbe
+};
+
+const uint8_t test_payload_C[0x1a] = {
+    0x88, 0x39, 0x1F, 0xC6, 0xD3, 0xEB, 0xA4, 0xAC,
+    0xFB, 0xB9, 0xBA, 0xB9, 0xBE, 0x13, 0x61, 0x4C,
+    0x43, 0x83, 0x00, 0x92, 0x84, 0x00, 0x6F, 0x87,
+    0x7C, 0xB2
+};
+
 void cmd_tx(uint8_t idx)
 {
     int i;
@@ -1896,13 +2049,29 @@
             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;
+        if (pcbuf[idx] == 'A') {
+        } else if (pcbuf[idx] == 'B') {
+        } else if (pcbuf[idx] == 'C') {
+        } else {        
+            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:
+    
+        /* always variable-length format */
+        fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
+        if (!fsk.RegPktConfig1.bits.PacketFormatVariable) {
+            printf("fsk fixed->variable\r\n");
+            fsk.RegPktConfig1.bits.PacketFormatVariable = 1;
+            radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);
+        }
+        
         if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
             sscanf(pcbuf+idx, "%d", &i);
             fsk_tx_length = i;
@@ -1911,11 +2080,23 @@
             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;
+                if (pcbuf[idx] == 'A') {
+                    fsk_tx_length = sizeof(test_payload_A);
+                    memcpy(radio.tx_buf, test_payload_A, fsk_tx_length);
+                } else if (pcbuf[idx] == 'B') {
+                    fsk_tx_length = sizeof(test_payload_B);
+                    memcpy(radio.tx_buf, test_payload_B, fsk_tx_length);                    
+                } else if (pcbuf[idx] == 'C') {
+                    fsk_tx_length = sizeof(test_payload_C);
+                    memcpy(radio.tx_buf, test_payload_C, fsk_tx_length);                    
+                } else {   
+                    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);
             }
         }
@@ -1923,6 +2104,140 @@
 
 }
 
+volatile uint16_t long_byte_count, long_byte_count_at_full;
+
+const uint8_t test_preamble_sync[] = {
+    0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x33, 0xcb, 0x82
+};
+
+void _ulm_write_fifo(uint8_t len)
+{
+    uint8_t i;
+    
+    //dio2 is FifoFull
+    radio.m_cs = 0;
+    radio.m_spi.write(REG_FIFO | 0x80); // bit7 is high for writing to radio
+    
+    for (i = 0; i < len; ) {
+        //printf("_%02x\r\n", radio.tx_buf[i]);
+        radio.m_spi.write(radio.tx_buf[i++]);
+        long_byte_count++;
+        if (dio2) {
+            long_byte_count_at_full = long_byte_count;
+            while (radio.dio1)
+                ;
+        }
+    }
+    radio.m_cs = 1;
+}
+
+int write_buf_to_fifo(const uint8_t* send_buf, uint8_t target_length)
+{
+    /* block until all is written */
+    uint8_t total_sent = 0;
+    
+    //printf("wbtf %u\r\n", target_length);
+    while (target_length > total_sent) {
+        uint8_t this_length = target_length - total_sent;
+        memcpy(radio.tx_buf+total_sent, send_buf+total_sent, this_length);
+        _ulm_write_fifo(this_length);
+        total_sent += this_length;
+    }
+    return total_sent;
+}
+
+#define TEST_PAYLOAD        test_payload_C
+//#define TEST_PAYLOAD        test_payload_A
+void cmd_long_tx(uint8_t idx)
+{
+    unsigned int pkt_cnt = 0;
+    bool first_pkt;
+    /* transmit multipe packets without any time between packets (back to back) */
+    
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        sscanf(pcbuf+idx, "%u", &pkt_cnt);
+    }
+    
+    printf("tx %u pkts\r\n", pkt_cnt);
+    if (pkt_cnt < 1)
+        return;
+        
+    radio.RegDioMapping2.octet = radio.read_reg(REG_DIOMAPPING2);
+    radio.RegDioMapping2.bits.Dio5Mapping = 2;  // data output to observation
+    radio.RegDioMapping2.bits.Dio4Mapping = 3;  // output preamble detect indication
+    radio.RegDioMapping2.bits.MapPreambleDetect = 1;
+    radio.write_reg(REG_DIOMAPPING2, radio.RegDioMapping2.octet);
+        
+    //unlimited packet length mode
+    fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
+    fsk.RegPktConfig1.bits.PacketFormatVariable = 0;    // fixed length format
+    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);
+    
+    fsk.RegPktConfig2.word = radio.read_u16(REG_FSK_PACKETCONFIG2);
+    fsk.RegPktConfig2.bits.PayloadLength = 0;
+    radio.write_u16(REG_FSK_PACKETCONFIG2, fsk.RegPktConfig2.word);
+    //DIO3 to FifoEmpty (for end of tx)
+    radio.RegDioMapping1.octet = radio.read_reg(REG_DIOMAPPING1);
+    radio.RegDioMapping1.bits.Dio3Mapping = 0;  // FifoEmpty
+    //DIO2 to FifoFull
+    radio.RegDioMapping1.bits.Dio2Mapping = 0;  // FIfoFull
+    //DIO1 to FifoLevel
+    radio.RegDioMapping1.bits.Dio1Mapping = 0;  // FifoLevel
+    radio.RegDioMapping1.bits.Dio0Mapping = 0;  // PacketSent
+    radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet); 
+    //FifoThreshold to approx 1/5th full
+    fsk.RegFifoThreshold.octet = radio.read_reg(REG_FSK_FIFOTHRESH);  
+    fsk.RegFifoThreshold.bits.FifoThreshold = sizeof(TEST_PAYLOAD)-1; // allow single packet
+    // tx start condition to FifoLevel
+    fsk.RegFifoThreshold.bits.TxStartCondition = 0; // start on FifoLevel
+    radio.write_reg(REG_FSK_FIFOTHRESH, fsk.RegFifoThreshold.octet);
+    
+    long_byte_count = 0;
+    long_byte_count_at_full = 0xffff;
+    
+    radio.set_opmode(RF_OPMODE_TRANSMITTER);
+    first_pkt = true;   // preamble+sync sent by packet engine only for first packet
+    for (; pkt_cnt > 0; pkt_cnt--) {
+        uint8_t len;
+        if (first_pkt)
+            first_pkt = false;
+        else {
+            if (dio3) {
+                printf("fail-empty\r\n");
+            }
+            write_buf_to_fifo(test_preamble_sync, sizeof(test_preamble_sync));
+        }
+        
+        len = sizeof(TEST_PAYLOAD); //TEST_PAYLOAD doesnt start with length
+        write_buf_to_fifo(&len, 1);
+        write_buf_to_fifo(TEST_PAYLOAD, sizeof(TEST_PAYLOAD));        
+    } // ..
+
+    rx_start_timer.reset();
+    rx_start_timer.start();
+    while (!dio3) {
+        if (rx_start_timer.read() > 1) {
+            printf("fifoEmpty fail\r\n");
+            radio.set_opmode(RF_OPMODE_STANDBY);
+            return;
+        }
+    }
+
+    rx_start_timer.reset();
+    rx_start_timer.start();
+    while (!radio.dio0) {
+        if (rx_start_timer.read() > 3) {
+            printf("PacketSent fail\r\n");
+            radio.set_opmode(RF_OPMODE_STANDBY);
+            return;
+        }        
+    }
+    wait_us(100);
+    radio.set_opmode(RF_OPMODE_STANDBY);
+    printf("done ok %u, %u\r\n", long_byte_count, long_byte_count_at_full);
+
+}
+
 void cmd_hw_reset(uint8_t idx)
 {
     printf("hw_reset()\r\n");
@@ -1954,6 +2269,14 @@
         printf("rssi:-%.1f\r\n", radio.read_reg(REG_FSK_RSSIVALUE) / 2.0);
 }
 
+void cmd_rssi_polling(uint8_t idx)
+{
+    if ((pcbuf[idx] >= '0' && pcbuf[idx] <= '9') || pcbuf[idx] == '-') {
+        sscanf(pcbuf+idx, "%d", &rssi_polling_thresh);  
+    }
+    printf("rssi_polling_thresh:%d\r\n", rssi_polling_thresh);
+}
+
 void cmd_lora_continuous_tx(uint8_t idx)
 {
     /* TxContinuousMode same for sx1272 and sx1276 */
@@ -2048,13 +2371,28 @@
                 fsk.set_tx_fdev_hz(16384);
                 fsk.set_rx_dcc_bw_hz(62500, 0);  // rxbw
                 fsk.set_rx_dcc_bw_hz(100000, 1);  // afcbw
+                
+                fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
+                fsk.RegPktConfig1.bits.CrcOn = 0;
+                radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);
+        
                 radio.write_u16(REG_FSK_PREAMBLEMSB, 5);             
                 fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);    
                 fsk.RegSyncConfig.bits.SyncSize = 2;
+                fsk.RegSyncConfig.bits.SyncOn = 1;
                 radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
-                radio.write_reg(REG_FSK_SYNCVALUE3, 0x33);
+                radio.write_reg(REG_FSK_SYNCVALUE1, 0x33);
                 radio.write_reg(REG_FSK_SYNCVALUE2, 0xcb);
-                radio.write_reg(REG_FSK_SYNCVALUE1, 0x82);           
+                radio.write_reg(REG_FSK_SYNCVALUE3, 0x82);    
+                
+                radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
+                radio.RegOpMode.bits.ModulationType = 0;    // 0 = FSK 
+                radio.RegOpMode.bits.ModulationShaping = 2; // 2=BT0.5
+                radio.write_reg(REG_OPMODE, radio.RegOpMode.octet);
+                
+                fsk.RegAfcFei.octet = radio.read_reg(REG_FSK_AFCFEI);
+                fsk.RegAfcFei.bits.AfcAutoClearOn = 0;
+                radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
                 
                 fsk.RegRxConfig.bits.RxTrigger = 6; // preamble
                 radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet);
@@ -2331,6 +2669,318 @@
     lora.start_rx(RF_OPMODE_RECEIVER_SINGLE);
 }
 
+void preamble_without_sync()
+{
+    printf("preamble_without_sync Afc:%dHz\r\n", (int)(FREQ_STEP_HZ * radio.read_s16(REG_FSK_AFCMSB))); 
+    fsk.RegRxConfig.bits.RestartRxWithoutPllLock = 1;
+    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet); 
+}
+
+Timeout pd_timeout;
+Timeout sync_timeout;
+void preamble_detect_isr()
+{
+    // only used between frames, on background noise
+    pd_timeout.attach_us(&preamble_without_sync, 1500); // 122us per byte
+}
+ 
+void prrx_end()
+{
+    uint8_t n, i;
+    
+    for (n = 0; n < n_rx_pkts; n++) {
+        printf("%d) %d: ", n, rx_pkts[n][0]);
+        for (i = 0; i < rx_pkts[n][0]; i++) {
+            if (i < RX_PKT_SIZE_LIMIT)
+                printf("%02x ", rx_pkts[n][i+1]);
+        }
+        printf("\r\n");          
+    }
+    
+    n_rx_pkts = 0;    // done with rx packets, clear for next time
+    
+    /* end of this reception, prepare for next super-packet */
+    fsk.RegRxConfig.bits.AgcAutoOn = 1;
+    fsk.RegRxConfig.bits.AfcAutoOn  = 1;
+    fsk.RegRxConfig.bits.RxTrigger = 6; // preamble
+    fsk.RegAfcFei.bits.AfcAutoClearOn = 1;
+    radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
+    
+    fsk.RegRxConfig.bits.RestartRxWithoutPllLock = 1;
+    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet); 
+    
+    dio4int.rise(&preamble_detect_isr); // detect preamble without sync on background noise
+    dio4int.enable_irq();
+    
+    dio0int.disable_irq();      // disable packet end detection       
+}
+
+volatile bool length_byte;
+void syncadrs_dio2_isr(void);
+
+void payloadReady_isr()
+{
+    uint8_t i;
+    
+    dio0int.disable_irq();  // disable ourselves, enabled at syncadrs detection
+    dio1int.disable_irq();      // disable prrx_fifoLevel_isr() while handling end-of-packet
+    
+    if (!rx_payloadReady_int_en) {
+        radio.set_opmode(RF_OPMODE_STANDBY);
+        dio2int.disable_irq();
+        dio4int.disable_irq();
+        printf("payloadReady_isr() not enabled\r\n");
+        return;
+    }
+    
+    if (radio.RegOpMode.bits.Mode != RF_OPMODE_RECEIVER && radio.RegOpMode.bits.Mode != RF_OPMODE_SYNTHESIZER_RX) {
+        rx_payloadReady_int_en = false;
+        printf("payloadReady_isr() not-rx\r\n");
+        return;
+    }
+    
+    //Simultaneously write AgcAutoOn = 0, AgcAutoOn = 0,  RXTrigger = Rssi, LnaGain value = read above (i.e. AGC is fixed from reception of first packet)
+    if (fsk.RegRxConfig.bits.AgcAutoOn) {
+        fsk.RegRxConfig.bits.AgcAutoOn = 0;
+        fsk.RegRxConfig.bits.AfcAutoOn  = 0;
+        fsk.RegRxConfig.bits.RxTrigger = 1; // rssi
+        fsk.RegAfcFei.bits.AfcAutoClearOn = 0;
+        radio.write_reg(REG_FSK_AFCFEI, fsk.RegAfcFei.octet);
+    }       
+    
+    fsk.RegRxConfig.bits.RestartRxWithoutPllLock = 1;
+    radio.write_reg(REG_FSK_RXCONFIG, fsk.RegRxConfig.octet); 
+    
+    radio.m_cs = 0;
+    radio.m_spi.write(REG_FIFO); // bit7 is low for reading from radio
+        
+    if (length_byte) {
+        /* none of the packet has been retrieved */
+        rx_pkts[n_rx_pkts][0] = radio.m_spi.write(0);   // read length byte
+        for (i = 0; i < rx_pkts[n_rx_pkts][0]; i++) {
+            uint8_t o, idx = i + 1;
+            o = radio.m_spi.write(0);
+            if (idx < RX_PKT_SIZE_LIMIT)
+                rx_pkts[n_rx_pkts][idx] = o;
+            else {
+                printf("L-prrx-overflow %u %u %u\r\n", n_rx_pkts, idx, rx_pkts[n_rx_pkts][0]);
+                break;
+            }
+        }        
+    } else {
+        /* last packet bytes havent been retrieved? */
+        while (rx_payload_idx < rx_pkts[n_rx_pkts][0]) {
+            if (rx_payload_idx < RX_PKT_SIZE_LIMIT)
+                rx_pkts[n_rx_pkts][++rx_payload_idx] = radio.m_spi.write(0);
+            else {
+                printf("R-prrx-overflow %u %u %u\r\n", n_rx_pkts, rx_payload_idx, rx_pkts[n_rx_pkts][0]);
+                break;
+            }
+        }
+    }
+    radio.m_cs = 1; 
+    
+    if (++n_rx_pkts >= N_RX_PKTS) {
+        /* overflow, stop */
+        prrx_end();
+        rx_payloadReady_int_en = false;
+        radio.set_opmode(RF_OPMODE_STANDBY);
+        dio2int.disable_irq();
+        dio4int.disable_irq();
+        printf("n_rx_pkts overflow\r\n");
+        return;
+    }
+    
+    // timeout occurs if next syncadrs doesnt come
+    sync_timeout.attach_us(&prrx_end, 2000); // 122us per byte
+    
+    dio2int.rise(&syncadrs_dio2_isr);  // timeout for detecting last packet
+    dio2int.enable_irq();
+}
+
+void prrx_fifoLevel_isr()
+{
+    bool fail = false;
+    
+    if (dio0int.read()) {
+        printf("fifolevel_isr: dio0 set\r\n");
+        return;
+    }
+    
+    radio.m_cs = 0;
+    radio.m_spi.write(REG_FIFO); // bit7 is low for reading from radio
+        
+    while (!dio3) { // while fifo has something
+        if (length_byte) {
+            rx_pkts[n_rx_pkts][0] = radio.m_spi.write(0);
+            length_byte = false;
+            rx_payload_idx = 0;
+        } else {
+            if (rx_payload_idx < RX_PKT_SIZE_LIMIT)
+                rx_pkts[n_rx_pkts][++rx_payload_idx] = radio.m_spi.write(0);
+            else {
+                printf("isr-prrx-overflow %u %u %u, dio3:%d\r\n", n_rx_pkts, rx_payload_idx, rx_pkts[n_rx_pkts][0], dio3.read());
+                fail = true;       
+                break;
+            }
+        }
+    }
+    radio.m_cs = 1;
+    
+    if (fail)
+        fsk_print_IrqFlags2();
+}
+
+void syncadrs_dio2_isr()
+{
+    pc3 = !pc3;
+    /* syncadrs match, disable end-timeout */
+    if (!dio3.read()) {
+        printf("syncadrs: fifo not empty\r\n");
+        rx_payloadReady_int_en = false;
+        radio.set_opmode(RF_OPMODE_STANDBY);
+        dio0int.disable_irq();
+        dio1int.disable_irq();
+        dio2int.disable_irq();
+        dio4int.disable_irq();        
+        return;
+    }
+        
+    sync_timeout.detach();
+    pd_timeout.detach();
+    dio4int.disable_irq(); // inhibit preamble_detect_isr() until end
+    
+    if (dio0int.read()) {
+        printf("syncadrs_dio2_isr: dio0 set\r\n");
+        return;
+    }
+    if (dio1int.read()) {
+        printf("syncadrs_dio2_isr: dio1 set\r\n");
+        return;
+    }    
+    
+    dio0int.rise(&payloadReady_isr); // end of packet detection
+    dio0int.enable_irq();    
+    
+    dio1int.rise(&prrx_fifoLevel_isr); // flow control during packet
+    dio1int.enable_irq(); 
+
+    length_byte = true; // first byte is length
+}
+
+void cmd_prrx(uint8_t idx)
+{
+    radio.m_spi.frequency(10000000);    // max speed
+    radio.set_opmode(RF_OPMODE_STANDBY);
+    
+    fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
+    fsk.RegPktConfig1.bits.PacketFormatVariable = 1;    // varible length format
+    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);    
+    
+    // DIO0 to payloadReady
+    radio.RegDioMapping1.octet = radio.read_reg(REG_DIOMAPPING1);
+    radio.RegDioMapping1.bits.Dio0Mapping = 0;  // PayloadReady
+    radio.RegDioMapping1.bits.Dio1Mapping = 0;  // FifoLevel
+    radio.RegDioMapping1.bits.Dio2Mapping = 3;  // dio2 to SyncAddress in RX
+    radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet); 
+    
+    radio.RegDioMapping2.octet = radio.read_reg(REG_DIOMAPPING2);
+    radio.RegDioMapping2.bits.Dio4Mapping = 3;
+    radio.RegDioMapping2.bits.MapPreambleDetect = 1;    // dio4 to preambleDetect in RX
+    radio.write_reg(REG_DIOMAPPING2, radio.RegDioMapping2.octet);         
+    
+    fsk.RegRxConfig.octet = radio.read_reg(REG_FSK_RXCONFIG);   // update shadow
+
+    fsk.RegFifoThreshold.octet = radio.read_reg(REG_FSK_FIFOTHRESH);  
+    fsk.RegFifoThreshold.bits.FifoThreshold = 6;  // fifoLevel_isr threshold
+    radio.write_reg(REG_FSK_FIFOTHRESH, fsk.RegFifoThreshold.octet);
+    
+    radio.set_opmode(RF_OPMODE_RECEIVER);
+    n_rx_pkts = 0;
+    
+    rx_payloadReady_int_en = true;  
+    
+    dio2int.rise(&syncadrs_dio2_isr);  // timeout for detecting last packet
+    dio2int.enable_irq();
+    
+    dio4int.rise(&preamble_detect_isr); // detect preamble without sync on background noise
+    dio4int.enable_irq();
+}
+
+void ulrx_fifoLevel_isr()
+{
+    uint8_t o;
+    
+    radio.m_cs = 0;
+    radio.m_spi.write(REG_FIFO); // bit7 is low for reading from radio
+
+    while (!dio3) { // while FifoEmpty is not asserted
+        switch (ulrx_state) {
+            case ULRX_STATE_NEED_LENGTH:
+                rx_pkts[n_rx_pkts][0] = radio.m_spi.write(0);   // read length byte
+                ulrx_state = ULRX_STATE_PAYLOAD;
+                rx_payload_idx = 0;
+                break;
+            case ULRX_STATE_PAYLOAD:
+                o = radio.m_spi.write(0);
+                if (rx_payload_idx < rx_pkts[n_rx_pkts][0]) {
+                    if (rx_payload_idx < RX_PKT_SIZE_LIMIT)
+                        rx_pkts[n_rx_pkts][rx_payload_idx++] = o;
+                } else {
+                    /* first byte after payload */
+                    if (++n_rx_pkts >= N_RX_PKTS) {
+                        radio.set_opmode(RF_OPMODE_STANDBY);
+                        ulrx_state = ULRX_STATE_OFF;
+                        printf("n_rx_pkts overflow\r\n");
+                        return;
+                    }
+                    ulrx_state = ULRX_STATE_SYNC1;
+                }
+                break;
+            case ULRX_STATE_SYNC1:
+                o = radio.m_spi.write(0);
+                /* TODO */
+                break;
+        }
+    } // ..while fifo has something
+    
+    radio.m_cs = 1; 
+}
+
+void cmd_ulrx(uint8_t idx)
+{
+    radio.set_opmode(RF_OPMODE_STANDBY);
+    
+    //unlimited packet length mode
+    fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
+    fsk.RegPktConfig1.bits.PacketFormatVariable = 0;    // fixed length format
+    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);
+    
+    fsk.RegPktConfig2.word = radio.read_u16(REG_FSK_PACKETCONFIG2);
+    fsk.RegPktConfig2.bits.PayloadLength = 0;
+    radio.write_u16(REG_FSK_PACKETCONFIG2, fsk.RegPktConfig2.word);    
+    
+    radio.RegDioMapping1.octet = radio.read_reg(REG_DIOMAPPING1);
+    radio.RegDioMapping1.bits.Dio3Mapping = 0;  // FifoEmpty
+    //DIO2 to FifoFull
+    radio.RegDioMapping1.bits.Dio2Mapping = 0;  // FIfoFull
+    //DIO1 to FifoLevel
+    radio.RegDioMapping1.bits.Dio1Mapping = 0;  // FifoLevel
+    //radio.RegDioMapping1.bits.Dio0Mapping = 0;  // useful?
+    radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet); 
+    //FifoThreshold to approx 1/5th full
+    fsk.RegFifoThreshold.octet = radio.read_reg(REG_FSK_FIFOTHRESH);  
+    fsk.RegFifoThreshold.bits.FifoThreshold = 48; // 75% full
+    radio.write_reg(REG_FSK_FIFOTHRESH, fsk.RegFifoThreshold.octet);
+    
+    ulrx_enable = true;
+    ulrx_state = ULRX_STATE_NEED_LENGTH;
+    dio1int.rise(&ulrx_fifoLevel_isr);
+    dio1int.enable_irq();    
+    n_rx_pkts = 0;
+    radio.set_opmode(RF_OPMODE_RECEIVER);
+}
+
 void cmd_rx(uint8_t idx)
 {    
     set_per_en(false);
@@ -2353,7 +3003,11 @@
             fsk.RegRssiConfig.bits.RssiOffset = FSK_RSSI_OFFSET;
             fsk.RegRssiConfig.bits.RssiSmoothing = FSK_RSSI_SMOOTHING;
             radio.write_reg(REG_FSK_RSSICONFIG, fsk.RegRssiConfig.octet);
-        }                                
+        }               
+        
+        // sync shadow regsiters
+        radio.RegDioMapping1.octet = radio.read_reg(REG_DIOMAPPING1);
+        radio.RegDioMapping2.octet = radio.read_reg(REG_DIOMAPPING2);
     }
 }
 
@@ -2493,6 +3147,7 @@
                 printf("+20dBm PADAC bias\r\n");
                 i -= 3;
                 pds_trim.bits.prog_txdac = 7;
+                radio.write_reg(adr, pds_trim.octet);
             }
             if (i > 1)
                     radio.RegPaConfig.bits.OutputPower = i - 2;
@@ -2540,6 +3195,12 @@
         printf("OFF\r\n");
 }
 
+void cmd_crc32(uint8_t idx)
+{
+    crc32_en ^= true;
+    printf("crc32_en:%u\r\n", crc32_en);
+}
+
 void cmd_crcOn(uint8_t idx)
 {
     if (radio.RegOpMode.bits.LongRangeMode) {
@@ -2911,6 +3572,17 @@
     printf("fdev:%fKHz\r\n", fsk.get_tx_fdev_hz()/(float)1000.0);
 }
 
+void cmd_spifreq(uint8_t idx)
+{
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        int hz, MHz;
+        sscanf(pcbuf+idx, "%d", &MHz);
+        hz = MHz * 1000000;
+        printf("spi hz:%u\r\n", hz);
+        radio.m_spi.frequency(hz);
+    }
+}
+
 void cmd_frf(uint8_t idx)
 {
     if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
@@ -3242,6 +3914,36 @@
     printf("\r\n"); 
 }
 
+void cmd_bt(uint8_t idx)
+{
+    radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
+    if (radio.RegOpMode.bits.ModulationType != 0) {
+        printf("!fsk\r\n");
+        return;
+    }
+        
+    if (pcbuf[idx] >= '0' && pcbuf[idx] <= '9') {
+        float bt;
+        sscanf(pcbuf+idx, "%f", &bt);
+        if (bt > 1.0)
+            radio.RegOpMode.bits.ModulationShaping = 0;    // 0 = no shaping
+        else if (bt > 0.6)
+            radio.RegOpMode.bits.ModulationShaping = 1;    // 1 = BT1.0
+        else if (bt > 0.4)
+            radio.RegOpMode.bits.ModulationShaping = 2;    // 2 = BT0.5
+        else
+            radio.RegOpMode.bits.ModulationShaping = 3;    // 3 = BT0.3
+    }
+    radio.write_reg(REG_OPMODE, radio.RegOpMode.octet);
+    switch (radio.RegOpMode.bits.ModulationShaping) {
+        case 0: printf("no-shaping "); break;
+        case 1: printf("BT1.0 "); break;
+        case 2: printf("BT0.5 "); break;
+        case 3: printf("BT0.3 "); break;
+    }    
+    printf("\r\n");
+}
+
 void cmd_fsk_DataMode(uint8_t idx)
 {
     fsk.RegPktConfig2.word = radio.read_u16(REG_FSK_PACKETCONFIG2);
@@ -3392,6 +4094,41 @@
     printf("ClkOut:%d\r\n", reg_osc.bits.ClkOut);
     radio.write_reg(REG_FSK_OSC, reg_osc.octet);
 }
+
+void cmd_ook_tx_test(uint8_t idx)
+{
+    radio.set_frf_MHz(915.0);
+
+    radio.RegOpMode.octet = radio.read_reg(REG_OPMODE);
+    if (radio.RegOpMode.bits.LongRangeMode)
+        cmd_toggle_modem(0);
+        
+    fsk.RegPktConfig1.octet = radio.read_reg(REG_FSK_PACKETCONFIG1);
+    fsk.RegPktConfig1.bits.CrcOn = 0;
+    radio.write_reg(REG_FSK_PACKETCONFIG1, fsk.RegPktConfig1.octet);    
+    
+    radio.RegDioMapping2.octet = radio.read_reg(REG_DIOMAPPING2);
+    radio.RegDioMapping2.bits.Dio5Mapping = 2;  // Data
+    radio.write_reg(REG_DIOMAPPING2, radio.RegDioMapping2.octet);
+    
+    radio.RegDioMapping1.octet = radio.read_reg(REG_DIOMAPPING1);
+    radio.RegDioMapping1.bits.Dio3Mapping = 1;  // TxReady
+    radio.write_reg(REG_DIOMAPPING1, radio.RegDioMapping1.octet);
+    
+    //radio.write_reg(REG_FSK_SYNCCONFIG, 0);
+    cmd_ook(0);
+    radio.write_reg(REG_FSK_SYNCCONFIG, 0);
+/*    radio.write_u16(REG_FSK_PREAMBLEMSB, 4);    // preamble length
+    
+    fsk.RegSyncConfig.octet = radio.read_reg(REG_FSK_SYNCCONFIG);
+    fsk.RegSyncConfig.bits.SyncOn = 1;
+    radio.write_reg(REG_FSK_SYNCCONFIG, fsk.RegSyncConfig.octet);
+
+    //              0123456
+    sprintf(pcbuf, "syncw a9 66 69 65");
+    cmd_fsk_syncword(6);*/
+    tx_ticker.attach(&callback_ook_tx_test, tx_ticker_rate);
+}
      
 void cmd_help(uint8_t args_at);
 
@@ -3413,6 +4150,7 @@
 {   /* 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, "prssi", cmd_rssi_polling, "<%d>","dbm of rssi polling, 0 = off"}, 
     { 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"},
@@ -3424,6 +4162,8 @@
     { 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, "ethcrc", cmd_crc32, "","toggle enable software crc32"},
+    { MODEM_BOTH, "spif", cmd_spifreq, "<MHz>","change SPI clock frequency"},
     { 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"},
@@ -3452,6 +4192,7 @@
     { 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, "otx", cmd_ook_tx_test, "","start ook tx repeat"},
     { 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"},
@@ -3467,6 +4208,10 @@
     { MODEM_FSK, "mp", cmd_MapPreambleDetect, "","(RX) toggle MapPreambleDetect"},
     { MODEM_FSK, "rrx", cmd_restart_rx, "","restart RX"},  
     { MODEM_BOTH, "op", cmd_op, "<dBm>","(TX) get/set TX power"}, 
+    { MODEM_FSK, "bt", cmd_bt, "","get/set BT"},  
+    { MODEM_FSK, "ltx", cmd_long_tx, "<%d>","long tx"}, 
+    { MODEM_FSK, "prrx", cmd_prrx, "","start RX with payloadReady isr"},
+    { MODEM_FSK, "ulrx", cmd_ulrx, "","start RX with unlimited length"},
     
 #ifdef LORA_TX_TEST
     { MODEM_LORA, "apl", cmd_lora_all_payload_lengths, "","(TXTEST) sweep payload lengths 0->255"},
@@ -3481,9 +4226,9 @@
     { 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_BOTH, "ttr", cmd_tx_ticker_rate, "<%f seconds>","(TXTEST) get/set tx_ticker rate"},
     { 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" },
@@ -3552,6 +4297,7 @@
         
     if (pcbuf_len < 0) {
         printf("abort\r\n");
+        rx_payloadReady_int_en = false;
         cadper_enable = false;
         per_en = false;
         pcbuf_len = 0;
@@ -3647,6 +4393,8 @@
     
     pc.attach(rx_callback);
     
+    make_crc_table();
+    
 #ifndef TARGET_MTS_MDOT_F411RE
     rfsw.input();
     if (rfsw.read()) {
@@ -3792,6 +4540,9 @@
     radio.RegPaConfig.bits.OutputPower = 15;
     radio.write_reg(REG_PACONFIG, radio.RegPaConfig.octet);
       
+#ifdef START_OOK_TX_TEST
+    cmd_ook_tx_test(0);
+#endif /* START_OOK_TX_TEST */
 
     while(1) {
         switch (app) {