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

kermit.cpp

Committer:
Wayne Roberts
Date:
2018-05-22
Revision:
24:9ba45aa15b53
Parent:
18:9530d682fd9a

File content as of revision 24:9ba45aa15b53:

#if 0

#include "kermit.h"


#ifdef RADIO_FILE_XFER
typedef enum {
    XFER_STATE__NONE = 0,
    XFER_STATE_WAIT_S,  // 1
    XFER_STATE_S_ACKED, // 2
    XFER_STATE_WAIT_F, // 3
    XFER_STATE_F_ACKED, // 4
    XFER_STATE_WAIT_DATA_ACK,   // 5
    XFER_STATE_D_ACKED, // 6
    XFER_STATE_WAIT_Z,  // 7
    XFER_STATE_Z_ACKED,  // 8
    XFER_STATE_WAIT_B,  // 9
    XFER_STATE_B_ACKED  // 10
} xfer_state_e;

typedef struct {
    bool radio_initialized;
    bool do_tx;
    xfer_state_e state;
    int fail_length;
    float tx_sleep;
    char seq_from_rx;
    float data_tx_delay;
    char data_ack_char; // tmp debug
} radio_xfer_t;
radio_xfer_t radio_xfer;

#endif /* RADIO_FILE_XFER */

#ifdef TARGET_NUCLEO_F103RB
/* NUCLEO-F103RB UARTs:
 * #  TX       RX        use
 * 2  PA_2     PA_3     mbed default
 * 1  PA_9     PA_10          PA_9=D8=DIO4a   PA_10=D2=DIO0
 * 3  PB_10    PB_11     PB_10=D6=nothing   PB11=C26=4.7uF
 * 1  PB_6     PB_7     remap      PB_6=D10=SX1276_NSS   PB7=CN7-21
 * 3  PC_10    PC_11    partial remap
 * SX127x radio(D11,   D12, D13,    D10,  A0,   D2,   D3); 
 */
Serial pc_b(PB_10, PB_11);   //PB_10=D6=nothing   PB11=C26=4.7uF
CRC_HandleTypeDef   CrcHandle;
#endif /* TARGET_NUCLEO_F103RB */

#ifdef TARGET_LPC11U6X
/* U1_RXD: PIO0_13=A2, PIO1_2=P2-24
 * U1_TXD: PIO0_14=A1, PIO1_8=P2-50
 * U0_RXD: PIO0_18=mbed, PIO1_26=D5, PIO1_17=J4-4
 * U0_TXD: PIO0_19=mbed, PIO1_18=D2, PIO1_27=D6
 * U2_RXD: PIO0_20=P2-14, PIO1_6=P2-53
 * U2_TXD: PIO1_0=P2-13, PIO1_23=J4-1
 * PIO2_3:  U3_RXD=D9
 * PIO2_4:  U3_TXD=J8-3
 * PIO2_11: U4_RXD
*/
Serial pc_b(P1_8, P1_2);    // TX=PIO1_8=P2-50,    RX=PIO1_2=P2-24
#endif

#define MAX_LEN_FRAME       130 /* */

Kermit::Kermit(SX127x_lora& _lora) : lora(_lora)
{
    uart_rx_enabled = false;
}

Kermit::~Kermit()
{
}

uint8_t Kermit::tochar(uint8_t c) { return c + 32; }
uint8_t Kermit::unchar(uint8_t c) { return c - 32; }
uint8_t Kermit::ctl(uint8_t c) { return c ^ 64; }

void Kermit::rx_callback(uint8_t c)
{
    static uint8_t ctrl_c_cnt = 0;    
    
    if (c == 3) {
        if (++ctrl_c_cnt > 3) {
            uart_rx_enabled = false;
            end_cause = 3;
            end = true;
            return;
        }
    } else
        ctrl_c_cnt = 0;  
              
    switch (state) {
        case KERMIT_STATE_WAIT_SOH:
            if (c == SOH) {
                state = KERMIT_STATE_WAIT_LEN;
            }
            break;
        case KERMIT_STATE_WAIT_LEN:
            uart_rx_sum = c;
            uart_rx_length = unchar(c) - 2;
            state = KERMIT_STATE_WAIT_SEQ;
            break;
        case KERMIT_STATE_WAIT_SEQ:
            uart_rx_sum += c;
            uart_rx_seq = unchar(c);
            state = KERMIT_STATE_WAIT_TYPE;
            break;
        case KERMIT_STATE_WAIT_TYPE: 
            uart_rx_sum += c;
            uart_rx_type = c;
            state = KERMIT_STATE_DATA;
            uart_rx_data_idx = 0;
            break;
        case KERMIT_STATE_DATA:
            uart_rx_data[uart_rx_data_idx++] = c;
            if (uart_rx_data_idx == uart_rx_length) {
                char check = tochar((uart_rx_sum + ((uart_rx_sum & 192)/64)) & 63);
                if (check == c) {
                    if (parse_rx()) {
                        //kermit_state = KERMIT_STATE_GET_EOL;
                        end_cause = 4;
                        /////////////////////////
                        uart_tx_data_idx = 0;
                        uart_tx_data[uart_tx_data_idx++] = 'E';
                        uart_tx_data[uart_tx_data_idx++] = uart_rx_type;
                        uart_do_tx = true;  
                        end_after_tx = true;
                        /////////////////////////                           
                        break;
                    }
                }
                if (uart_rx_type == 'E') {
                    state = KERMIT_STATE_GET_EOL;
                    end_cause = 2;
                } else
                    state = KERMIT_STATE_WAIT_SOH;
            } else
                uart_rx_sum += c;
            break;
        case KERMIT_STATE_GET_EOL:   
            uart_rx_enabled = false;
            end = true;
            if (uart_rx_type == 'E')
                show_error = true;
            uart_rx_data[uart_rx_data_idx-1] = 0;  //null terminate, this is ascii text string          
            state = KERMIT_STATE_OFF;
            break;
    } // ..switch (state)    
}

#ifdef RADIO_FILE_XFER
void Kermit::radio_xfer_rx()
{
    radio_xfer.seq_from_rx = lora.m_xcvr.rx_buf[0];
    pc_b.printf("rfrx:%02x,%c\r\n", radio_xfer.seq_from_rx, lora.m_xcvr.rx_buf[1]);
    
    switch (radio_xfer.state) {
        case XFER_STATE_WAIT_S: // 'S' response
            end_cause = 0;
            filename[0] = 0;
            uart_tx_data_idx = 0;
            if (lora.m_xcvr.rx_buf[1] == 'S') {
                uart_tx_data[uart_tx_data_idx++] = 'Y';
                uart_tx_data[uart_tx_data_idx++] = tochar(94);   // MAXL
                //uart_tx_data[uart_tx_data_idx++] = tochar(kermit.time-1);//TIME
                uart_tx_data[uart_tx_data_idx++] = tochar(1);//TIME (when reply from other radio is bad)
                uart_tx_data[uart_tx_data_idx++] = tochar(0);    //NPAD
                uart_tx_data[uart_tx_data_idx++] = tochar(32);   //PADC
                uart_tx_data[uart_tx_data_idx++] = tochar('\r'); //EOL
                uart_tx_data[uart_tx_data_idx++] = '#';  //QCTL
                uart_tx_data[uart_tx_data_idx++] = 'Y'; //QBIN
                uart_tx_data[uart_tx_data_idx++] = '1'; //CHKT
                uart_tx_data[uart_tx_data_idx++] = '~'; //REPT                
            } else
                uart_tx_data[uart_tx_data_idx++] = 'E';
                
            uart_do_tx = true;  
            radio_xfer.state = XFER_STATE_S_ACKED;
            break;
        case XFER_STATE_WAIT_F: // 'F' response
            uart_tx_data_idx = 0;
            if (lora.m_xcvr.rx_buf[1] == 'F') {
                uart_tx_data[uart_tx_data_idx++] = 'Y';
            } else
                uart_tx_data[uart_tx_data_idx++] = 'E'; 
                
            uart_do_tx = true; 
            radio_xfer.state = XFER_STATE_F_ACKED;
            break;
        case XFER_STATE_WAIT_DATA_ACK:
            if (lora.m_xcvr.rx_buf[1] == 'E' || lora.m_xcvr.rx_buf[1] == 'Y' || lora.m_xcvr.rx_buf[1] == 'N') {
                uart_tx_data_idx = 0;
                radio_xfer.data_ack_char = lora.m_xcvr.rx_buf[1];
                uart_tx_data[uart_tx_data_idx++] = lora.m_xcvr.rx_buf[1];
                uart_do_tx = true;
                radio_xfer.state = XFER_STATE_D_ACKED;
            }
            break;
        case XFER_STATE_WAIT_Z:
            uart_tx_data_idx = 0;
            uart_tx_data[uart_tx_data_idx++] = lora.m_xcvr.rx_buf[1];
            uart_do_tx = true; 
            radio_xfer.state = XFER_STATE_Z_ACKED;        
            break;
        case XFER_STATE_WAIT_B:
            uart_tx_data_idx = 0;
            uart_tx_data[uart_tx_data_idx++] = lora.m_xcvr.rx_buf[1];
            uart_do_tx = true; 
            end_after_tx = true;
            radio_xfer.state = XFER_STATE_B_ACKED;        
            break;        
    } // ..switch (radio_xfer.state)
    //radio_xfer.ack_waiting = false;
}
#endif /* RADIO_FILE_XFER */  


#ifdef TARGET_STM
uint32_t Kermit::_HAL_CRC_Calculate(uint32_t pBuffer[], uint32_t BufferLength)
{
  uint32_t index = 0;

  /* Reset CRC Calculation Unit */
  __HAL_CRC_DR_RESET(&CrcHandle);
  __nop();
  __nop();
  __nop();
  __nop();
  __nop();

  /* Enter Data to the CRC calculator */
  for(index = 0; index < BufferLength; index++)
  {
    CrcHandle.Instance->DR = pBuffer[index];
    //printf("Calc %08x\r\n", CrcHandle.Instance->DR);
      __nop();
      __nop();
      __nop();    
  }

  /* Return the CRC computed value */
  return CrcHandle.Instance->DR;
}


 // 20000788 08003101 08007e35 08007e37 08007e39 08007e3b 08007e3d 00000000 00000000 00000000 00000000 08007e3f 08007e41 00000000 08007e43 08007e45 0000311b
 void Kermit::test_crc()
 {
     uint32_t tbuf[17] = { 0x20000788, 0x08003101, 0x08007e35, 0x08007e37, 0x08007e39, 0x08007e3b, 0x08007e3d, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x08007e3f, 0x08007e41, 0x00000000, 0x08007e43, 0x08007e45, 0x0000311b };
     uint32_t crc;
     int i;
     crc = _HAL_CRC_Calculate(tbuf, 17);
     printf("Crc:%08x\r\n", crc);
       __HAL_CRC_DR_RESET(&CrcHandle);
  __nop();
  __nop();
  __nop();
  __nop();
  __nop();
    for (i = 0; i < 17; i++) {
        CrcHandle.Instance->DR = tbuf[i];
        printf("%08x: %08x\r\n", tbuf[i], CrcHandle.Instance->DR);
      __nop();
      __nop();
      __nop();           
        
    }
     printf(": %08x\r\n", CrcHandle.Instance->DR);
     
}
#else // !STM...
const uint32_t CrcTable[16] = { // Nibble lookup table for 0x04C11DB7 polynomial
        0x00000000,0x04C11DB7,0x09823B6E,0x0D4326D9,0x130476DC,0x17C56B6B,0x1A864DB2,0x1E475005,
        0x2608EDB8,0x22C9F00F,0x2F8AD6D6,0x2B4BCB61,0x350C9B64,0x31CD86D3,0x3C8EA00A,0x384FBDBD };
uint32_t Kermit::_HAL_CRC_Calculate(uint32_t u32_buf[], uint32_t Size)
{
    int i = 0;
    uint32_t Crc = 0xffffffff;

    while(Size--)
    {
        Crc = Crc ^ u32_buf[i++];

        // Process 32-bits, 4 at a time, or 8 rounds
        Crc = (Crc << 4) ^ CrcTable[Crc >> 28]; // Assumes 32-bit reg, masking index to 4-bits
        Crc = (Crc << 4) ^ CrcTable[Crc >> 28]; //  0x04C11DB7 Polynomial used in STM32
        Crc = (Crc << 4) ^ CrcTable[Crc >> 28];
        Crc = (Crc << 4) ^ CrcTable[Crc >> 28];
        Crc = (Crc << 4) ^ CrcTable[Crc >> 28];
        Crc = (Crc << 4) ^ CrcTable[Crc >> 28];
        Crc = (Crc << 4) ^ CrcTable[Crc >> 28];
        Crc = (Crc << 4) ^ CrcTable[Crc >> 28];
    }

    return(Crc);
}
uint32_t g_tbuf[17] = { 0x20000788, 0x08003101, 0x08007e35, 0x08007e37, 0x08007e39, 0x08007e3b, 0x08007e3d, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x08007e3f, 0x08007e41, 0x00000000, 0x08007e43, 0x08007e45, 0x0000311b };

// 20000788 08003101 08007e35 08007e37 08007e39 08007e3b 08007e3d 00000000 00000000 00000000 00000000 08007e3f 08007e41 00000000 08007e43 08007e45 0000311b
void Kermit::test_crc()
{
    //uint32_t tbuf[17] = { 0x20000788, 0x08003101, 0x08007e35, 0x08007e37, 0x08007e39, 0x08007e3b, 0x08007e3d, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x08007e3f, 0x08007e41, 0x00000000, 0x08007e43, 0x08007e45, 0x0000311b };
    uint32_t crc;
    //uint32_t *u32_ptr;
    //u32_ptr = (uint32_t*)bin_data;
    
    printf("test_crc...%p\r\n", g_tbuf);
    //u32_ptr[0] = 0x20000788;
    //u32_ptr[1] = 0x08003101;
    
    //crc = _HAL_CRC_Calculate((uint32_t*)bin_data, 17);
    crc = _HAL_CRC_Calculate(g_tbuf, 17);
    printf("crc:%08x\r\n", crc);
}
#endif /* !TARGET_STM */


int Kermit::parse_rx()
{
    static char prev_uart_rx_seq;
    static uint32_t prev_bin_data_idx;
    
     //pc_b.printf("kermit_parse_rx %02x '%c'\r\n", uart_rx_seq, uartrx_type);
#ifdef RADIO_FILE_XFER    
    lora.m_xcvr.tx_buf[0] = uart_rx_seq;
    lora.m_xcvr.tx_buf[1] = uart_rx_type;
#endif /* RADIO_FILE_XFER */
            
    if (uart_rx_type == 'S') {
        got_send_init = true;

        if (uart_rx_data_idx > 0)
            maxl = unchar(uart_rx_data[0]);
        if (uart_rx_data_idx > 1)
            time = unchar(uart_rx_data[1]);    
        if (uart_rx_data_idx > 2)
            npad = unchar(uart_rx_data[2]);
        if (uart_rx_data_idx > 3)
            padc = unchar(uart_rx_data[3]);
        if (uart_rx_data_idx > 4)
            eol  = unchar(uart_rx_data[4]);
        if (uart_rx_data_idx > 5)
            qctl = uart_rx_data[5];   // verbatim
        if (uart_rx_data_idx > 6)
            qbin = uart_rx_data[6];   // verbatim   'Y'==agree-to-8bit, 'N'=no-8bit '&'==I need this char to do 8bit quoting
        if (uart_rx_data_idx > 7)
            chkt = uart_rx_data[7];   // verbatim
        if (uart_rx_data_idx > 8)
            rept = uart_rx_data[8];  

#ifdef RADIO_FILE_XFER
        lora.RegPayloadLength = 2;
        lora.m_xcvr.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
        radio_xfer.do_tx = true;
        radio_xfer.state = XFER_STATE_WAIT_S;
#else
        uart_tx_data_idx = 0;
        uart_tx_data[uart_tx_data_idx++] = 'Y';
        uart_tx_data[uart_tx_data_idx++] = tochar(94);   // MAXL
        uart_tx_data[uart_tx_data_idx++] = tochar(kermit.time-1);//TIME
        uart_tx_data[uart_tx_data_idx++] = tochar(0);    //NPAD
        uart_tx_data[uart_tx_data_idx++] = tochar(32);   //PADC
        uart_tx_data[uart_tx_data_idx++] = tochar('\r'); //EOL
        uart_tx_data[uart_tx_data_idx++] = '#';  //QCTL
        uart_tx_data[uart_tx_data_idx++] = 'Y'; //QBIN
        uart_tx_data[uart_tx_data_idx++] = '1'; //CHKT
        uart_tx_data[uart_tx_data_idx++] = '~'; //REPT
        uart_do_tx = true;
#endif /* !RADIO_FILE_XFER */
        pc_b.printf("S\r\n");
    } else if (uart_rx_type == 'F') {
        /* keep filename if desired */
        memcpy(filename, uart_rx_data, uart_rx_data_idx-1);
        filename[uart_rx_data_idx-1] = 0;
        
        total_file_bytes = 0;
        prev_uart_rx_seq = uart_rx_seq;
        prev_bin_data_idx = 0;
#ifdef XXD_PRINT
        xxd_total_file_bytes_so_far = 0;
        xxd_remainder = 0;
#endif /* XXD_PRINT */        

#ifdef RADIO_FILE_XFER
        lora.RegPayloadLength = 2;
        lora.m_xcvr.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
        radio_xfer.do_tx = true;
        radio_xfer.tx_sleep = radio_xfer.data_tx_delay;
        radio_xfer.state = XFER_STATE_WAIT_F;
#else
        uart_tx_data_idx = 0;
        uart_tx_data[uart_tx_data_idx++] = 'Y';
        /*memcpy(uart_tx_data+1, uart_rx_data, uart_rx_data_idx-1);
        uart_tx_data_idx += uart_rx_data_idx-1;*/
        uart_do_tx = true;
#endif /* !RADIO_FILE_XFER */   
        bin_data = (uint8_t*)bin_data_u32;
        pc_b.printf("F\r\n");
    } else if (uart_rx_type == 'D') {
        int i;
#ifdef RADIO_FILE_XFER
        uint8_t len_for_crc;
        //uint32_t* u32_ptr;
        uint32_t uwCRCValue;
#endif /* RADIO_FILE_XFER */   
        
        bin_data_idx = 0;

#ifdef KERMIT_DATA_PRINT
        pc_b.printf("%04x: ", kermit.total_file_bytes);
#endif /* */            
     
        uart_rx_data_idx--;   // cut off trailing sum byte
        for (i = 0; i < uart_rx_data_idx; i++) {
            if (uart_rx_data[i] == qctl) { // escaped..
                uint8_t in = uart_rx_data[++i];
#ifdef KERMIT_DATA_PRINT
                    pc_b.printf("#");
#endif /* */   
                if ((in & 0x7f) == rept || (in & 0x7f) == qctl) {
#ifdef KERMIT_DATA_PRINT
                    pc_b.printf(":%02x ", in);
#endif /* */                  
                    bin_data[bin_data_idx++] = in;
                } else {
#ifdef KERMIT_DATA_PRINT
                    pc_b.printf("ctl:%02x ", ctl(in));
#endif /* */                      
                    bin_data[bin_data_idx++] = ctl(in);
                }
            } else if (uart_rx_data[i] == rept) {   //repeat..
                uint8_t octet, cnt = unchar(uart_rx_data[++i]);
                i++;    // step past count
                if (uart_rx_data[i] == qctl) {
                    uint8_t raw = uart_rx_data[++i];
                    octet = ctl(raw);
                } else {
                    octet = uart_rx_data[i];
                }
                for (int n = 0; n < cnt; n++) {
#ifdef KERMIT_DATA_PRINT
                    pc_b.printf("rep%02x ", octet);
#endif /*  */            
                    bin_data[bin_data_idx++] = octet;
                }
            } else {
#ifdef KERMIT_DATA_PRINT
                    pc_b.printf("%02x ", uart_rx_data[i]);
#endif /*  */    
                bin_data[bin_data_idx++] = uart_rx_data[i];
            }
        } // ..for()
#ifdef KERMIT_DATA_PRINT
        pc_b.printf("\r\n");
#endif /*  */                   
        
        if (prev_uart_rx_seq == uart_rx_seq) {
            // resend of previous 'D' packet
            total_file_bytes -= prev_bin_data_idx;
        } else {
#ifdef XXD_PRINT
            xxd_print(0);
#endif /* XXD_PRINT */            
        }

        total_file_bytes += bin_data_idx;
        prev_bin_data_idx = bin_data_idx;
        prev_uart_rx_seq = uart_rx_seq;        
        
#ifdef RADIO_FILE_XFER               
        // zero-pad to 4byte size alignment
        len_for_crc = bin_data_idx;
        while (len_for_crc & 3) {
            bin_data[len_for_crc++] = 0;
        }
        uwCRCValue = _HAL_CRC_Calculate((uint32_t *)bin_data, len_for_crc >> 2);
        //kermit.crc32 = uwCRCValue;
        //memcpy(crc_buf, bin_data, len_for_crc);
        //kermit.len_for_crc = len_for_crc;
    
        
        if (bin_data_idx > MAX_LEN_FRAME) {  // oversized
            radio_xfer.fail_length = bin_data_idx;
            return 1;   // fail
        }
        
        //u32_ptr = (uint32_t *)&lora.m_xcvr.tx_buf[2];
        //pc_b.printf("D-hhh %p\r\n", u32_ptr);
        //*u32_ptr = uwCRCValue;
        memcpy(&lora.m_xcvr.tx_buf[2], &uwCRCValue, 4);
/*        
        if (prev_rx_seq == kermit.rx_seq) {
            // resend of previous 'D' packet
            radio_xfer.total_file_bytes -= prev_bin_data_idx;
        }
        radio_xfer.total_file_bytes += bin_data_idx;
        prev_bin_data_idx = bin_data_idx;
        prev_rx_seq = kermit.rx_seq;
*/
        
        memcpy(lora.m_xcvr.tx_buf+6, bin_data, bin_data_idx);
        lora.RegPayloadLength = bin_data_idx+6;
        lora.m_xcvr.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
        radio_xfer.do_tx = true;
        radio_xfer.state = XFER_STATE_WAIT_DATA_ACK;
        radio_xfer.tx_sleep = radio_xfer.data_tx_delay;
#else 
        /* send ACK.. */
        while (uart_do_tx);
        uart_tx_data_idx = 0;
        uart_tx_data[uart_tx_data_idx++] = 'Y';
        uart_do_tx = true;        
#endif /* !RADIO_FILE_XFER */
        return 0;
    } else if (uart_rx_type == 'Z') {
#ifdef RADIO_FILE_XFER  
        lora.RegPayloadLength = 2;
        lora.m_xcvr.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
        radio_xfer.do_tx = true;
        radio_xfer.tx_sleep = radio_xfer.data_tx_delay;
        radio_xfer.state = XFER_STATE_WAIT_Z;
#else
        /* send ACK.. */
        while (uart_do_tx);
        uart_tx_data_idx = 0;
        uart_tx_data[uart_tx_data_idx++] = 'Y';
        uart_do_tx = true;               
        uart_tx_sleep = 0.1;
#endif /* !RADIO_FILE_XFER */
        pc_b.printf("Z\r\n");
        return 0;
    } else if (uart_rx_type == 'B') {
#ifdef RADIO_FILE_XFER  
        lora.RegPayloadLength = 2;
        lora.m_xcvr.write_reg(REG_LR_PAYLOADLENGTH, lora.RegPayloadLength);
        radio_xfer.do_tx = true;
        radio_xfer.tx_sleep = radio_xfer.data_tx_delay;
        radio_xfer.state = XFER_STATE_WAIT_B;
#else
        uart_tx_data_idx = 0;
        uart_tx_data[uart_tx_data_idx++] = 'Y';
        uart_do_tx = true;               
        uart_tx_sleep = 0.1;
#endif /* RADIO_FILE_XFER */
#ifdef XXD_PRINT
        xxd_print(1);
#endif /* XXD_PRINT */        
        pc_b.printf("B\r\n");
        return 0;
    } else {
        // unknown packet, prevent further packets from overwriting
        return 1;
    }
    
    return 0;
}

void Kermit::kermit_uart_tx()
{
    uint8_t buf[128];
    uint8_t idx = 0;
    int i;
    uint32_t sum;
           
    buf[idx++] = SOH;   // MARK
    buf[idx++] = 0;   // length to be inserted later
#ifdef RADIO_FILE_XFER
    buf[idx++] = tochar(radio_xfer.seq_from_rx); // SEQ
#else
    buf[idx++] = tochar(uart_rx_seq);   // SEQ
#endif
    // TYPE is uart_tx_data[0]
    for (i = 0; i < uart_tx_data_idx; i++) {
        buf[idx++] = uart_tx_data[i];
    }
    
    buf[1] = tochar(idx - 1); // LEN  (-1 because block check hasnt been included in idx yet)
    
    sum = 0;
    for (i = 1; i < idx; i++) {
        sum += buf[i];
    }
    buf[idx++] = tochar((sum + ((sum & 192)/64)) & 63);
    buf[idx++] = eol;
    
    for (i = 0; i < idx; i++)
        putc(buf[i], stdout);
       //pc.putc(buf[i]);

}

void Kermit::service()
{
    if (end) {
        if (show_error) {
            printf("kermit error:\"%s\"\r\n", uart_rx_data);
            show_error = false;
        }
        printf("kermit_end\r\n");
        printf("total_file_bytes:%d\r\n", total_file_bytes);
        end = false;
    }
    
    if (uart_do_tx) {
        if (uart_tx_sleep > 0.001) {
            wait(uart_tx_sleep);
            uart_tx_sleep = 0;
        }
        kermit_uart_tx();
        uart_do_tx = false;
        if (end_after_tx) {
            //know this cause -- end_cause = 1;
            uart_rx_enabled = false;
            end = true;   
            state = KERMIT_STATE_OFF;            
            end_after_tx = false;
        }
    } // ...if (uart_do_tx)
    
#ifdef RADIO_FILE_XFER
    if (!radio_xfer.radio_initialized) {
        lora.m_xcvr.set_opmode(RF_OPMODE_STANDBY);   
        lora.m_xcvr.write_reg(REG_LR_SYNC_BYTE, 0x34);
        lora.setBw_KHz(500);
        lora.setSf(7); 
        lora.m_xcvr.set_frf_MHz(915.0);
        lora.invert_tx(true);
        radio_xfer.fail_length = -1;
        radio_xfer.radio_initialized = true;
    }

    if (radio_xfer.do_tx) {
        pc_b.printf("rfTX:%02x\r\n", lora.m_xcvr.tx_buf[0]);
        if (radio_xfer.tx_sleep > 0.001) {
            wait(radio_xfer.tx_sleep);
            radio_xfer.tx_sleep = 0;
        }
        lora.start_tx(lora.RegPayloadLength);
        radio_xfer.do_tx = false;
    }
#endif /* RADIO_FILE_XFER */    
}

void Kermit::uart_rx_enable()
{
    got_send_init = false;
    uart_rx_enabled = true;
    state = KERMIT_STATE_WAIT_LEN;
#ifdef RADIO_FILE_XFER    
    radio_xfer.radio_initialized = false;   // causes radio initialization for transfer
#endif /* RADIO_FILE_XFER */    
}
#endif /* #if 0 */