repeat message down a chain, adding to the payload at each repeating device

Dependencies:   sx12xx_hal

radio chip selection

Radio chip driver is not included, because options are available.
If you're using SX1272 or SX1276, then import sx127x driver into your program.
if you're using SX1261 or SX1262, then import sx126x driver into your program.
if you're using SX1280, then import sx1280 driver into your program.
If you're using NAmote72 or Murata discovery, then you must import only sx127x driver.

/media/uploads/dudmuck/chain.png

network architecture

  • UNIT 0x00 transmitting only device: mandatory.
  • UNIT 0x01: repeating device
  • Uni-directional network: Each unit can only receive message from UNIT_ID - 1 (previous unit)
  • UINT n receiving only device LAST_UNIT: mandatory; prints payload onto UART.

configuration

Each device in the network is uniquely identified by:

  • UNIT_ID: ID byte designating address of this device.
  • UNIT_LAST: If defined, this device prints payload onto serial port instead of re-transmitting payload.

All devices in network must be configured identically with the following:

  • TX_INTERVAL_US: how often to take measurement and send to UNIT_ID+1 (time of complete cycle).
  • MAX_TX_LENGTH: Maximum size of payload, in bytes. Payload is sent in fragments when exceeds this value; aka size of each fragment.
  • TXRX_PADDING_US : Time allotted for RX-TX turnaround and CPU overhead
  • MAX_TX_ATTEMPTS: Count of transmit retries permitted
  • SPREADING_FACTOR LoRa configuration of datarate
  • CF_MHZ : Operating radio frequency


Duration of retry interval is auto-calculated from LoRa modem configuration (bandwidth/sf) and MAX_TX_LENGTH.
Take care that TX_INTERVAL_US value is appropriate relative to total retry interval (interval * MAX_TX_ATTEMPTS)

main.cpp

Committer:
Wayne Roberts
Date:
2019-09-16
Revision:
6:63981d2f88a7
Parent:
5:62c9ddaa5ea6

File content as of revision 6:63981d2f88a7:

#include "radio.h" 

#define UNIT_ID     0x00            /* 0x00: first unit */
//#define UNIT_LAST

// test large sample, large pkt size  #define N_SMP    10
typedef struct __attribute__((__packed__)) msg {
    uint8_t unit_id;
    uint8_t flags;
    uint16_t sample;

    #ifdef N_SMP
    uint16_t samples[N_SMP];
    #endif
} message_t;

#define TX_INTERVAL_US              5000000
#define MAX_TX_LENGTH               64
#define TXRX_PADDING_US             10000
#define MAX_TX_ATTEMPTS             4
#define SPREADING_FACTOR            9
#ifdef SX128x_H
    #define CF_MHZ                      2487.0
    #define BW_KHZ                      200
    #define TX_DBM                      12
#else
    #define CF_MHZ                      917.6
    #define BW_KHZ                      500
    #define TX_DBM                      17
#endif

#define EXPECTED_LENGTH     (UNIT_ID * sizeof(message_t))
//#define MEASURED_MAX_ERR        (TX_INTERVAL_US / 4000)     // +/-100ppm allowance
#define MEASURED_MAX_ERR        (TX_INTERVAL_US / 300)     // +/-Xppm allowance

#if defined(SX127x_H)
    #define RX_STARTUP_US           1500
#elif defined(SX126x_H)
    #define RX_STARTUP_US           1000
#elif defined(SX128x_H)
    #define RX_STARTUP_US           1000
#endif
unsigned rxStartup_us = RX_STARTUP_US;

RawSerial pc(USBTX, USBRX); 
DigitalIn button(USER_BUTTON);

#ifdef TARGET_DISCO_L072CZ_LRWAN1
        AnalogIn ain(A0);
#elif defined(TARGET_MOTE_L152RC)
        AnalogIn ain(A0);
#else
    #ifdef TARGET_FF_MORPHO
        AnalogIn ain(PC_4); // pin unused by arduino shields
    #endif /* TARGET_FF_MORPHO */
#endif /* !TARGET_DISCO_L072CZ_LRWAN1 */

uint8_t forward_buf[255];
uint8_t forwardLen;
uint8_t forwardLenTransmitted;
uint8_t forwardLenAckd;
int prevFrag;
volatile us_timestamp_t forwardedAt;

volatile us_timestamp_t lastRxIrqAt;
volatile us_timestamp_t measuredInterval, measuredIntervalSaved;


enum _state_ {
    /* 0 */ STATE_NONE = 0,
#if (UNIT_ID != 0x00)
    /* 1 */ STATE_GET_REQ,
#endif /* UNIT_ID != 0x00 */
#ifndef UNIT_LAST
    /* 2 */ STATE_ACK_WAITING,
    /* 3 */ STATE_TX_FORWARD,
#endif /* UNIT_LAST */
} state;

void stateToString(enum _state_ s, char* out)
{
    const char* str;

    switch (s) {
        case STATE_NONE: str = "NONE"; break;
#if (UNIT_ID != 0x00)
        case STATE_GET_REQ: str = "GET_REQ"; break;
#endif /* UNIT_ID != 0x00 */
#ifndef UNIT_LAST
        case STATE_ACK_WAITING: str = "ACK_WAITING"; break;
        case STATE_TX_FORWARD: str = "TX_FORWARD"; break;
#endif /* UNIT_LAST */
        default:
            sprintf(out, "??%u??", s);
            return;
    }

    strcpy(out, str);
}

typedef union {
    struct {
        uint8_t attempt : 3;    // 0,1,2
        uint8_t fragNum : 4;    // 3,4,5,6
        uint8_t fragLast : 1;   // 7
    } bits;
    uint8_t octet;
} pkt_flags_t;

volatile struct _f_ {
    uint8_t unused                      : 1;   // 0
    uint8_t mbedTImeout_forwarderStarted: 1;   // 1
    uint8_t run                         : 1;   // 2
    uint8_t svc                         : 1;   // 3
#ifndef UNIT_LAST
    uint8_t sample                      : 1;   // 4
    uint8_t fwd                         : 1;   // 5
#endif /* UNIT_LAST */
} flags;


static uint16_t crc16( uint8_t *buffer, uint16_t length )
{
    uint16_t i;
    // The CRC calculation follows CCITT
    const uint16_t polynom = 0x1021;
    // CRC initial value
    uint16_t crc = 0x0000;

    if( buffer == NULL )
    {
        return 0;
    }

    for( i = 0; i < length; ++i )
    {
        uint16_t j;
        crc ^= ( uint16_t ) buffer[i] << 8;
        for( j = 0; j < 8; ++j )
        {
            crc = ( crc & 0x8000 ) ? ( crc << 1 ) ^ polynom : ( crc << 1 );
        }
    }

    return crc;
}

#ifdef UNIT_LAST
void print_payload()
{
    unsigned n;

    for (n = 0; n < forwardLen; n += sizeof(message_t)) {
        const message_t* m = (message_t*)&forward_buf[n];

        pc.printf("unit %02x: %02x, %u\r\n", m->unit_id, m->flags, m->sample);
    }

}
#endif /* UNIT_LAST */

LowPowerTimeout mbedTimeout_nextRx;
volatile unsigned retryInterval_us;
volatile us_timestamp_t rxStartAt;

#if (UNIT_ID != 0x00)
void setupNext()
{
    state = STATE_GET_REQ;
    forwardLen = 0;
    prevFrag = -1;
    pc.printf("->GET_REQ ");

    if (measuredInterval > 0) {
        Radio::Sleep();
        pc.printf("SLEEP mi:%llu ", measuredInterval);
        measuredInterval = 0; // single use
    } else {
        Radio::Rx(0);

        rxStartAt = 0;  // starting of continuous rx not used

#ifndef UNIT_LAST
        flags.sample = 1;
#endif /* UNIT_LAST */
        pc.printf("RX ");
    }

    memset(forward_buf, 0xff, EXPECTED_LENGTH);
}
#endif /* UNIT_ID != 0x00 */

LowPowerTimeout mbedTImeout_forwarder;


#ifndef UNIT_LAST
volatile uint8_t txCurs;

LowPowerTicker tickerRetry;
volatile us_timestamp_t txStartAt;

void retry_cb()
{
    unsigned c;
    pkt_flags_t f;

    Radio::Standby();

    f.octet = Radio::radio.tx_buf[1];
    pc.printf("attempt%u", f.bits.attempt);
    if (++f.bits.attempt >= MAX_TX_ATTEMPTS) {
        pc.printf(" lastTry");
        tickerRetry.detach();
#if (UNIT_ID != 0x00)
        setupNext();
#endif /* UNIT_ID != 0x00 */
        pc.printf("\r\n");
        return;
    }
    pc.printf("->%u\r\n", f.bits.attempt);
    Radio::radio.tx_buf[1] = f.octet;

    c = crc16(Radio::radio.tx_buf, txCurs-2);
    Radio::radio.tx_buf[txCurs-2] = c >> 8;
    Radio::radio.tx_buf[txCurs-1] = c;

    txStartAt = Radio::lpt.read_us();
    Radio::Send(txCurs, 0, 0, 0);
    state = STATE_ACK_WAITING;
}

volatile uint16_t sample;

#ifdef N_SMP
volatile uint16_t samples[N_SMP];
#endif

#if (UNIT_ID != 0x00)
uint8_t _tx_forward()
{
    unsigned fwdLen;
    unsigned c;
    uint8_t added, avail, toSendLen, stop = MAX_TX_LENGTH-2;
    pkt_flags_t f;

    tickerRetry.attach_us(retry_cb, retryInterval_us);

    if (forwardLen < EXPECTED_LENGTH) {
        pc.printf("\e[31mmissing %u bytes\e[0m ", EXPECTED_LENGTH - forwardLen);
        fwdLen = EXPECTED_LENGTH;
    }
        fwdLen = forwardLen;

    f.octet = Radio::radio.tx_buf[1];

    txCurs = 0;
    Radio::radio.tx_buf[txCurs++] = UNIT_ID;
    txCurs++;   // placeholder for flags to be added at end

    toSendLen = fwdLen - forwardLenAckd;
    forwardLenTransmitted = forwardLenAckd;
    added = 0;
    while ((txCurs + sizeof(message_t)) < stop && added < toSendLen) {
        memcpy(Radio::radio.tx_buf + txCurs, forward_buf + forwardLenTransmitted, sizeof(message_t));
        forwardLenTransmitted += sizeof(message_t);
        txCurs += sizeof(message_t);
        added += sizeof(message_t);
    }

    avail = stop - txCurs;
    if (avail >= sizeof(message_t)) {
        message_t* mptr = (message_t*)(Radio::radio.tx_buf + txCurs);
        mptr->unit_id = UNIT_ID;
        mptr->flags = 0x00;
        mptr->sample = sample;  // taken from main loop
#ifdef N_SMP
        for (c = 0; c < N_SMP; c++)
            mptr->samples[c] = samples[c];  // taken from main loop
#endif
        txCurs += sizeof(message_t);

        f.bits.fragLast = 1;
    }

    Radio::radio.tx_buf[1] = f.octet;

    c = crc16(Radio::radio.tx_buf, txCurs);
    Radio::radio.tx_buf[txCurs++] = c >> 8;
    Radio::radio.tx_buf[txCurs++] = c;

    Radio::Send(txCurs, 0, 0, 0);
    state = STATE_ACK_WAITING;

    return txCurs;
} // .._tx_forward()

volatile us_timestamp_t prevFwdStart;

void tx_forward_cb()
{
    unsigned dur;
    uint8_t txlen;
    us_timestamp_t now;

    now = Radio::lpt.read_us();

    if (measuredIntervalSaved != 0)  // in case nothing received
        mbedTImeout_forwarder.attach_us(tx_forward_cb, measuredIntervalSaved);

    Radio::radio.tx_buf[1] = 0; //initialize flags
    forwardLenAckd = 0;

    txlen = _tx_forward();

    flags.mbedTImeout_forwarderStarted = 0;

    dur = Radio::lora_toa_us(txlen);
    pc.printf("\e[7mtx_forward_cb %lld", now - prevFwdStart - TX_INTERVAL_US);
    pc.printf(" dur%u\e[0m\r\n", dur);
    prevFwdStart = now;
}
#endif /* UNIT_ID != 0x00 */

void sample_ticker_cb()
{
    flags.sample = 1;
}

#else // ..UNIT_LAST:
void uart_forward_cb()
{
    if (measuredIntervalSaved != 0)  // in case nothing received
        mbedTImeout_forwarder.attach_us(uart_forward_cb, measuredIntervalSaved);

    forwardLenAckd = 0;
    print_payload();

    setupNext();

    flags.mbedTImeout_forwarderStarted = 0;
}
#endif /* UNIT_LAST */

void nextRxStartCB()
{
    unsigned us;

    Radio::Rx(0);
    rxStartAt = Radio::lpt.read_us();

    us = (MAX_TX_ATTEMPTS * retryInterval_us) + (MAX_TX_ATTEMPTS * TXRX_PADDING_US);

    pc.printf("nextRxStartCB for %uus\r\n", us);

#ifndef UNIT_LAST
    flags.sample = 1;
#endif /* UNIT_LAST */
}


void txDoneCB()
{
    char str[32];

    Radio::Rx(0);   // receive ack
    stateToString(state, str);
    pc.printf("%s:txDone->Rx\r\n", str);
}

void rxDoneCB(uint8_t size, float rssi, float snr)
{
#if (UNIT_ID != 0x00)
    pkt_flags_t f;
    us_timestamp_t rxIrqAt = Radio::irqAt;
#endif /* UNIT_ID != 0x00 */
    char str[32];

    stateToString(state, str);
    pc.printf("\e[33mrxDoneCB() %u rssi:%.1fdBm snr:%.1fdB %s ID:%02x\e[0m ", size, rssi, snr, str, Radio::radio.rx_buf[0]);
#if (UNIT_ID != 0x00)
    if (state == STATE_GET_REQ) {
        uint8_t len;
        unsigned c, rxc;
        if (Radio::radio.rx_buf[0] != UNIT_ID-1) {
            pc.printf(" (not %02x)\r\n", UNIT_ID-1);
            return;
        }
        if (size < 4) {
            /* minimum: header + crc */
            pc.printf(" (size<4)\r\n");
            return;
        }
        f.octet = Radio::radio.rx_buf[1];

        c = crc16(Radio::radio.rx_buf, size-2);
        rxc = Radio::radio.rx_buf[size-2];
        rxc <<= 8;
        rxc |= Radio::radio.rx_buf[size-1];
        if (c != rxc) {
            pc.printf("\e[31mfrom%02x c:%04x rxc:%04x\e[0m\r\n", Radio::radio.rx_buf[0], c, rxc);
            for (unsigned n = 0; n < size; n++)
                pc.printf("%02x ", Radio::radio.rx_buf[n]);
            pc.printf("\r\n");
            return;
        }
        //noRxTimeout.detach();

        pc.printf(" attempt%u frag%u fragLast%u ", f.bits.attempt, f.bits.fragNum, f.bits.fragLast);
        if (state == STATE_GET_REQ && flags.mbedTImeout_forwarderStarted == 0 && f.bits.fragLast) {
            us_timestamp_t now;
            unsigned sinceRxDone, us;
            mbedTImeout_forwarder.detach();
            now = Radio::lpt.read_us();
            sinceRxDone = now - rxIrqAt;
            us = retryInterval_us * (MAX_TX_ATTEMPTS - f.bits.attempt);
            int target_us = us - sinceRxDone;
            // tx to occur after time given for all potential retries
#ifndef UNIT_LAST
            flags.sample = 1;   // sample from main loop, to be ready for tx_forward
            mbedTImeout_forwarder.attach_us(tx_forward_cb, target_us);
#else
            mbedTImeout_forwarder.attach_us(uart_forward_cb, target_us);
#endif /* UNIT_LAST */
            pc.printf("schedule forward %u, forwarding in %dus. sinceRxDone:%u\r\n",  MAX_TX_ATTEMPTS - f.bits.attempt, target_us, sinceRxDone);
            flags.mbedTImeout_forwarderStarted = 1;
            forwardedAt = now + target_us;
        }

        Radio::radio.tx_buf[0] = UNIT_ID;   // OK, send ACK
        Radio::Send(1, 0, 0, 0);


        if (prevFrag != f.bits.fragNum) {
            len = size - 4; // -4: header ... crc
            memcpy(forward_buf + forwardLen, Radio::radio.rx_buf+2, len);
            forwardLen += len;

            prevFrag = f.bits.fragNum;
        }

        if (f.bits.fragNum == 0) {
            unsigned attemptOffset = retryInterval_us * f.bits.attempt;
            if (rxStartAt == 0) {
                pc.printf("\e[7m");
            }
            pc.printf("lastRxIrqAt:%llu measuredInterval:%llu ", lastRxIrqAt, measuredInterval);
            if (lastRxIrqAt != 0) {
                us_timestamp_t thisMeas;
                int err_;
                unsigned abserr;
                thisMeas = (rxIrqAt - attemptOffset) - lastRxIrqAt;
                err_ = thisMeas - TX_INTERVAL_US;
                if (TX_INTERVAL_US > thisMeas)
                    abserr = TX_INTERVAL_US - thisMeas;
                else
                    abserr = thisMeas - TX_INTERVAL_US;

                pc.printf(" this:%llu AO:%u, err_:%d ", thisMeas, attemptOffset, err_);
                if (abserr < MEASURED_MAX_ERR) {
                    int rxPrecedency = 0;
                    unsigned sinceRxDone, _us_;
                    unsigned pktDur = Radio::lora_toa_us(size);
                    us_timestamp_t firstAttemptStartedAt = (rxIrqAt - attemptOffset) - pktDur;

                    measuredInterval = thisMeas;
                    _us_ = measuredInterval;
                    pc.printf("->%llu ", measuredInterval);

                    if (rxStartAt != 0) {
                        rxPrecedency = firstAttemptStartedAt - rxStartAt;
                        if (rxPrecedency > 0)
                            _us_ += rxPrecedency / 2;
                        else
                            _us_ += rxPrecedency;
                    }

                    _us_ -= rxStartup_us;
                    _us_ -= retryInterval_us; // TODO
                    mbedTimeout_nextRx.detach();
                    sinceRxDone = Radio::lpt.read_us() - rxIrqAt;
                    mbedTimeout_nextRx.attach_us(nextRxStartCB, _us_ - sinceRxDone);
                    pc.printf("nextRx:%u ao%u rxPrecedency:%d pktDur%u ri%u sinceRxDone%u ", _us_ - sinceRxDone, attemptOffset, rxPrecedency, pktDur, retryInterval_us, sinceRxDone);

                    if (measuredIntervalSaved == 0)
                        measuredIntervalSaved = measuredInterval;
                    else {
                        measuredIntervalSaved += measuredInterval;
                        measuredIntervalSaved /= 2;
                    }

                    rxStartAt = 0;
                } else
                    pc.printf("\e[31mtoo-much-err\e[0m\r\n");

                pc.printf("\r\n");
            } // ..if (lastRxIrqAt != 0)

            lastRxIrqAt = rxIrqAt - attemptOffset;

            pc.printf("\e[0m");
        } // ..if (f.bits.fragNum == 0)

    } // ..if (state == STATE_GET_REQ)
    else 
#endif /* UNIT_ID != 0x00 */
#ifndef UNIT_LAST
    if (state == STATE_ACK_WAITING) {

        if (Radio::radio.rx_buf[0] == UNIT_ID+1) {
            pkt_flags_t f;
            f.octet = Radio::radio.tx_buf[1];

            tickerRetry.detach();
            forwardLenAckd = forwardLenTransmitted;
            if (f.bits.fragLast) {
                pc.printf("ackOk-last ");
#if (UNIT_ID == 0x00)
                pc.printf("->SLEEP ");
                Radio::Sleep();
#else
                setupNext();
#endif /* UNIT_ID != 0x00 */
            } else {
                f.bits.fragNum++;
                f.bits.attempt = 0;
                Radio::radio.tx_buf[1] = f.octet;
                flags.sample = 1;
                flags.fwd = 1;  // tx_forward from main loop
                pc.printf("ackOk->%u ", f.bits.fragNum);
            }
        } else 
            pc.printf("ack from different ID %02x\r\n", Radio::radio.rx_buf[0]);
    }
#endif /* UNIT_LAST */

    {
        mbed_stats_cpu_t stats;
        mbed_stats_cpu_get(&stats);
        printf("canDeep:%u ", sleep_manager_can_deep_sleep());
        printf("Uptime: %llu ", stats.uptime / 1000);
        printf("Sleep time: %llu ", stats.sleep_time / 1000);
        printf("Deep Sleep: %llu\r\n", stats.deep_sleep_time / 1000);
    }

    pc.printf("\r\n");
} // ..rxDoneCB()

#if (UNIT_ID == 0x00) && !defined(UNIT_LAST)
LowPowerTicker      sampleTicker, txTicker;

void tx_ticker_cb(void) {
    unsigned c;
    pkt_flags_t f;
    message_t* mptr;

    tickerRetry.attach_us(retry_cb, retryInterval_us);

    f.bits.attempt = 0;
    f.bits.fragNum = 0;
    f.bits.fragLast = 1;

    Radio::Standby();

    txCurs = 0;
    Radio::radio.tx_buf[txCurs++] = UNIT_ID;
    Radio::radio.tx_buf[txCurs++] = f.octet;

    mptr = (message_t*)(Radio::radio.tx_buf + txCurs);
    mptr->unit_id = UNIT_ID;
    mptr->flags = 0x00;
    mptr->sample = sample;
#ifdef N_SMP
    for (c = 0; c < N_SMP; c++)
        mptr->samples[c] = samples[c];
#endif
    txCurs += sizeof(message_t);

    c = crc16(Radio::radio.tx_buf, txCurs);
    Radio::radio.tx_buf[txCurs++] = c >> 8;
    Radio::radio.tx_buf[txCurs++] = c;

    Radio::Send(txCurs, 0, 0, 0);
    txStartAt = Radio::lpt.read_us();
    state = STATE_ACK_WAITING;

    {
        mbed_stats_cpu_t stats;
        mbed_stats_cpu_get(&stats);
        pc.printf("canDeep:%u ", sleep_manager_can_deep_sleep());
        pc.printf("Uptime: %llu ", stats.uptime / 1000);
        pc.printf("Sleep time: %llu ", stats.sleep_time / 1000);
        pc.printf("Deep Sleep: %llu ", stats.deep_sleep_time / 1000);
    }

    pc.printf("tx_ticker_cb:%u\r\n", mptr->sample);
}
#endif /* UNIT_ID == 0x00 */

#ifdef SX128x_H
void
print_radio_chip()
{
    uint8_t buf[6];
    unsigned khz = 0;
    LoRaPktPar0_t LoRaPktPar0;
    LoRaPktPar1_t LoRaPktPar1;
    status_t st;

    Radio::radio.xfer(OPCODE_GET_IRQ_STATUS, 0, 3, buf);
    st.octet = buf[0];
    printf("mode:%u cmdStatus:%u irq:%02x %02x ", st.bits.chipMode, st.bits.cmdStatus, buf[1], buf[2]);

    LoRaPktPar0.octet = Radio::radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);
    switch (LoRaPktPar0.bits.modem_bw) {
        case 2: khz = 200; break;
        case 3: khz = 400; break;
        case 4: khz = 800; break;
        case 5: khz = 1600; break;
    }
    printf("read:%uKHz sf%u\r\n", khz, LoRaPktPar0.bits.modem_sf);

    printf("paylen%u ", (uint8_t)Radio::radio.readReg(REG_ADDR_LORA_TX_PAYLOAD_LENGTH, 1));
    LoRaPktPar1.octet = Radio::radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
    printf("cr%u ", LoRaPktPar1.bits.coding_rate);
    if (LoRaPktPar1.bits.rxinvert_iq)
        printf("std ");
    else
        printf("inv ");
    if (LoRaPktPar1.bits.implicit_header)
        printf("im");
    else
        printf("ex");
    printf("plicit\r\n");
}
#elif defined(SX127x_H) /* ...SX128x_H */

void
print_radio_chip()
{
}
#elif defined(SX126x_H) /* ...SX127x_H */

void
print_radio_chip()
{
}
#endif

void uart_rx()
{
    char str[32];

    char ch = pc.getc();
    switch (ch) {
        case '+':
            rxStartup_us += 500;
            pc.printf("rxStartup_us:%u\r\n", rxStartup_us);
            break;
        case '-':
            if (rxStartup_us > 500)
                rxStartup_us -= 500;
            pc.printf("rxStartup_us:%u\r\n", rxStartup_us);
            break;
        case '.':
            //Radio::PrintStatus();
            printf("UNIT_ID:%02x ", UNIT_ID);
            printf(" measuredInterval:%llu\r\n", measuredInterval);
            stateToString(state, str);
            printf(" %s\r\n", str);
            break;
        case 'r':
            flags.run ^= 1;
            printf("\r\nrun %u\r\n", flags.run);
            if (flags.run == 0) {
#ifndef UNIT_LAST
                tickerRetry.detach();
#endif /* !UNIT_LAST */
                mbedTImeout_forwarder.detach();
                mbedTimeout_nextRx.detach();

                Radio::Sleep();
            }
            break;
    } // ..switch (ch)
}

void radio_irq_topHalf()
{
    flags.svc = 1;
}

const RadioEvents_t rev = {
    /* DioPin_top_half */     radio_irq_topHalf,
    /* TxDone_topHalf */    NULL,//txDoneCBth,
    /* TxDone_botHalf */    txDoneCB,
    /* TxTimeout  */        NULL,
    /* RxDone  */           rxDoneCB,
    /* RxTimeout  */        NULL,
    /* RxError  */          NULL,
    /* FhssChangeChannel  */NULL,
    /* CadDone  */          NULL
};

int main()
{
    flags.run = 1;
    pc.baud(115200);
    pc.printf("\r\nreset\r\n");

    Radio::Init(&rev);

    Radio::Standby();
    Radio::LoRaModemConfig(BW_KHZ, SPREADING_FACTOR, 1);
    Radio::LoRaPacketConfig(8, false, true, false);  // preambleLen, fixLen, crcOn, invIQ
    Radio::SetChannel(CF_MHZ * 1000000);

    printf("user_button:%u\r\n", button.read());
    if (button.read()) {
        Radio::set_tx_dbm(TX_DBM);
        printf("PA to %ddBm\r\n", TX_DBM);
    } else {
        Radio::set_tx_dbm(PA_OFF_DBM);
        printf("PA off\r\n");
    }

    /* max TX length + turnaround + ACK length */
    retryInterval_us = Radio::lora_toa_us(MAX_TX_LENGTH) + TXRX_PADDING_US + Radio::lora_toa_us(1);
#ifdef UNIT_LAST
    pc.printf("LAST ");
#endif
    pc.printf("UNIT_ID:%02x retryInterval_us:%u\r\n", UNIT_ID, retryInterval_us);

    state = STATE_NONE;

#if (UNIT_ID == 0x00) && !defined(UNIT_LAST)
    sampleTicker.attach_us(sample_ticker_cb, TX_INTERVAL_US);
    wait_us(50000);
    txTicker.attach_us(tx_ticker_cb, TX_INTERVAL_US);
#else
    //Radio::PrintStatus();

    setupNext();

    measuredInterval = 0;
    lastRxIrqAt = 0;
    measuredIntervalSaved = 0;
#endif /* UNIT_ID != 0x00 */

    /*
    if (sleep_manager_can_deep_sleep())
        sleep_manager_lock_deep_sleep();    // prevent deep sleep
        */

    for (;;) {
        if (pc.readable()) {
            uart_rx();
        }

#ifndef UNIT_LAST
        if (flags.sample) {
            sample = ain.read_u16();
#ifdef N_SMP
            for (c = 0; c < N_SMP; c++)
                samples[c] = ain.read_u16();
#endif
            print_radio_chip();
            if (flags.fwd) {
#if (UNIT_ID == 0x00)
                pc.printf("\e[31mID00-fwd\e[0m\r\n");
#else
                _tx_forward();
#endif /* UNIT_ID != 0x00 */
                flags.fwd = 0;
            }

            flags.sample = 0;
        }
#endif /* UNIT_LAST */

        sleep_manager_sleep_auto();;

        if (flags.svc) {
            Radio::service();
            flags.svc = 0;
        }

    } // ..for (;;)
}