operate LoRa radio over I2C

Dependencies:   TimeoutAbs lib_i2c_slave_block sx12xx_hal

radio chip selection

Radio chip driver is not included, allowing choice of radio device.
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.

This project is used as slave device with i2c_lora_master on raspberry pi. This i2c_lora_slave offloads the real-time requirements onto microcontroller. Also permits multiple slave radio devices connected to master. Radio MAC layer exists on I2C master, along with application layer.

If beacon operation is enabled, I2C functions which access radio chip are blocked while beacon is loaded and transmitted.
See lib_i2c_slave_block for wiring connections.

Files at this revision

API Documentation at this revision

Comitter:
Wayne Roberts
Date:
Fri Feb 08 11:58:09 2019 -0800
Commit message:
first commit

Changed in this revision

TimeoutAbs.lib Show annotated file Show diff for this revision Revisions of this file
cmds.c Show annotated file Show diff for this revision Revisions of this file
cmds.h Show annotated file Show diff for this revision Revisions of this file
device_sx126x.cpp Show annotated file Show diff for this revision Revisions of this file
device_sx127x.cpp Show annotated file Show diff for this revision Revisions of this file
device_sx128x.cpp Show annotated file Show diff for this revision Revisions of this file
lib_i2c_slave_block.lib Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed-os.lib Show annotated file Show diff for this revision Revisions of this file
radio_device.h Show annotated file Show diff for this revision Revisions of this file
sx12xx_hal.lib Show annotated file Show diff for this revision Revisions of this file
diff -r 000000000000 -r 9eb5b8bf9f7b TimeoutAbs.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TimeoutAbs.lib	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/dudmuck/code/TimeoutAbs/#ae6b29794806
diff -r 000000000000 -r 9eb5b8bf9f7b cmds.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmds.c	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,43 @@
+#include <stdint.h>
+#include "cmds.h"
+
+const uint8_t cmd_to_length[] =
+{
+    /* CMD_UNUSED              */ 0,
+    /* CMD_TEST                */ 3,
+    /* CMD_TEST32              */ 32,
+    /* CMD_BEACON_PAYLOAD      */ 4,
+    /* CMD_CFG                 */ sizeof(cfg_t),
+    /* CMD_IRQ                 */ sizeof(irq_t),
+    /* CMD_SKIP_BEACONS        */ 1,
+    /* CMD_CURRENT_SLOT        */ 2,
+
+    /* CMD_FSK_MODEM_CFG_WRITE   */ 10,
+    /* CMD_FSK_MODEM_CFG_REQ     */ 1,
+    /* CMD_FSK_PKT_CFG_WRITE     */ 4,
+    /* CMD_FSK_PKT_CFG_REQ       */ 1,
+    /* CMD_FSK_SYNC_WRITE        */ 6,
+    /* CMD_FSK_SYNC_REQ          */ 1,
+    /* CMD_LORA_SYMBTO_WRITE     */ 1,
+    /* CMD_REQ_RANDOM            */ 4,
+    /* CMD_CFHZ_WRITE            */ 4,
+    /* CMD_CFHZ_REQ              */ 1,
+    /* CMD_TXDBM_WRITE           */ 1,
+    /* CMD_TXDBM_REQ             */ 1,
+    /* CMD_LORA_MODEM_CFG_WRITE  */ 4,
+    /* CMD_LORA_MODEM_CFG_REQ    */ 1,
+    /* CMD_RX_START              */ 4,
+    /* CMD_LORA_PKT_CFG_WRITE    */ 3,
+    /* CMD_LORA_PKT_CFG_REQ      */ 1,
+    /* CMD_BEACON_CFG_WRITE      */ 6,
+    /* CMD_STANDBY               */ 1,
+    /* CMD_OPMODE_REQ            */ 1,
+    /* CMD_PUBLIC_NET            */ 1,
+    /* CMD_MAX_PAYLEN_WRITE      */ 1,
+    /* CMD_RX_PAYLOAD            */ 32,
+    /* CMD_TX_BUF_START          */ 32,
+    /* CMD_TX_BUF                */ 32,
+    /* CMD_SEND                  */ 0,
+    /* CMD_RSSI_REQ              */ 1,
+    /* CMD_RADIO_RESET           */ 1
+};
diff -r 000000000000 -r 9eb5b8bf9f7b cmds.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmds.h	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,116 @@
+#define CMD_UNUSED              0x00
+#define CMD_TEST                0x01
+#define CMD_TEST32              0x02
+#define CMD_BEACON_PAYLOAD      0x03
+#define CMD_CFG                 0x04    // firmware config
+#define CMD_IRQ                 0x05    // read when irq pin is asserted
+#define CMD_SKIP_BEACONS        0x06
+#define CMD_CURRENT_SLOT        0x07
+
+#define CMD_RADIO                 0x08
+#define CMD_FSK_MODEM_CFG_WRITE   (CMD_RADIO+0x00)   // 08
+#define CMD_FSK_MODEM_CFG_REQ     (CMD_RADIO+0x01)   // 09
+#define CMD_FSK_PKT_CFG_WRITE     (CMD_RADIO+0x02)   // 0a 
+#define CMD_FSK_PKT_CFG_REQ       (CMD_RADIO+0x03)   // 0b
+#define CMD_FSK_SYNC_WRITE        (CMD_RADIO+0x04)   // 0c
+#define CMD_FSK_SYNC_REQ          (CMD_RADIO+0x05)   // 0d
+#define CMD_LORA_SYMBTO_WRITE     (CMD_RADIO+0x06)   // 0e
+#define CMD_REQ_RANDOM            (CMD_RADIO+0x07)   // 0f
+#define CMD_CFHZ_WRITE            (CMD_RADIO+0x08)   // 10
+#define CMD_CFHZ_REQ              (CMD_RADIO+0x09)   // 11
+#define CMD_TXDBM_WRITE           (CMD_RADIO+0x0a)   // 12
+#define CMD_TXDBM_REQ             (CMD_RADIO+0x0b)   // 13
+#define CMD_LORA_MODEM_CFG_WRITE  (CMD_RADIO+0x0c)   // 14
+#define CMD_LORA_MODEM_CFG_REQ    (CMD_RADIO+0x0d)   // 15
+#define CMD_RX_START              (CMD_RADIO+0x0e)   // 16
+#define CMD_LORA_PKT_CFG_WRITE    (CMD_RADIO+0x0f)   // 17
+#define CMD_LORA_PKT_CFG_REQ      (CMD_RADIO+0x10)   // 18
+#define CMD_BEACON_CFG_WRITE      (CMD_RADIO+0x11)   // 19
+#define CMD_STANDBY               (CMD_RADIO+0x12)   // 1a
+#define CMD_OPMODE_REQ            (CMD_RADIO+0x13)   // 1b
+#define CMD_PUBLIC_NET            (CMD_RADIO+0x14)   // 1c
+#define CMD_MAX_PAYLEN_WRITE      (CMD_RADIO+0x15)   // 1d
+#define CMD_RX_PAYLOAD            (CMD_RADIO+0x16)   // 1e read this when irq.fields.flags.rx_pkt == 1
+#define CMD_TX_BUF_START          (CMD_RADIO+0x17)   // 1f length and begin of payload
+#define CMD_TX_BUF                (CMD_RADIO+0x18)   // 20 continuing payload
+#define CMD_SEND                  (CMD_RADIO+0x19)   // 21 transmit packet, immediate
+#define CMD_RSSI_REQ              (CMD_RADIO+0x1a)   // 22 returned via IRQ
+#define CMD_RADIO_RESET           (CMD_RADIO+0x1b)   // 23
+
+
+#ifdef DEBUG_SMBUS
+#define CMD_ISR                 0xf8
+#define CMD_STOPF               0xf9
+#define CMD_ADDR_HOST_READ      0xfa
+#define CMD_ADDR_HOST_WRITE     0xfb
+#define CMD_AF                  0xfc    // debug for tx buf on host read
+#endif /* DEBUG_SMBUS */
+#define CMD_TIMEOUT             0xfd    // indication of smbus timeout
+#define CMD_ARLO                0xfe    // indication of arbitration lost
+#define CMD_BUSERR              0xff    // indication of start or stop during transfer
+
+typedef union {
+    struct {
+        uint8_t fixLen : 1;    // 0
+        uint8_t crcOn  : 1;    // 1
+        uint8_t invIQ  : 1;    // 2
+    } bits;
+    uint8_t octet;
+} pktcfg_t;
+
+typedef union {
+    struct __attribute__((packed)) {
+        struct {
+            uint8_t rxOnTxDone : 1;    // 0      enables gateway operation
+            uint8_t res        : 7;    // 1,2,3,4,5,6,7
+        } flags;
+        uint32_t maxListenTime; // LBT
+        uint32_t channelFreeTime;   // LBT
+        int8_t rssiThresh;  // LBT
+        uint8_t n_rssi_samples;     // rssi averaging
+        uint16_t downlinkOffset;    // uplink end to downlink in ms, if rxOnTxDone==1
+    } fields;
+    uint8_t buf[13];
+} cfg_t;
+
+enum {
+    OPMODE_SLEEP = 0,
+    OPMODE_STANDBY,
+    OPMODE_FS,
+    OPMODE_RX,
+    OPMODE_TX,
+    OPMODE_FAIL
+};
+
+typedef enum {
+    /* 0 */ IRQ_TYPE_PKT = 0,
+    /* 1 */ IRQ_TYPE_RSSI,
+    /* 2 */ IRQ_TYPE_CF,
+    /* 3 */ IRQ_TYPE_BEACON_DUR,
+    /* 4 */ IRQ_TYPE_RANDOM,
+    /* 5 */ IRQ_TYPE_FSK_MODEM,
+    /* 6 */ IRQ_TYPE_FSK_PKT,
+    /* 7 */ IRQ_TYPE_FSK_SYNC,
+    /* 8 */ IRQ_TYPE_TXDBM,
+    /* 9 */ IRQ_TYPE_LORA_MODEM,
+    /*10 */ IRQ_TYPE_LORA_PKT,
+    /*11 */ IRQ_TYPE_OPMODE
+} irq_type_e;
+
+typedef union {
+    struct __attribute__((packed)) {
+        struct {
+            uint8_t rx_pkt          : 1;    // 0
+            uint8_t rx_pkt_overrun  : 1;    // 1
+            uint8_t unused          : 2;    // 2,3
+            uint8_t irq_type        : 4;    // 4,5,6,7
+        } flags;
+        uint8_t pkt_len;    // if flags.rx_pkt
+        int8_t pkt_snr;     // if flags.rx_pkt
+        uint16_t rx_slot;
+        int16_t rssi;    // dBm * 10: if flags.rx_pkt or flags.bg_rssi
+    } fields;
+    uint8_t buf[11];
+} irq_t;
+
+extern const uint8_t cmd_to_length[];
diff -r 000000000000 -r 9eb5b8bf9f7b device_sx126x.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/device_sx126x.cpp	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,243 @@
+#include "radio_device.h"
+#ifdef SX126x_H 
+void get_opmode()
+{
+    status_t status;
+    Radio::radio.xfer(OPCODE_GET_STATUS, 0, 1, &status.octet);
+
+    switch (status.bits.chipMode) {
+        case 2: // STBY_RC
+        case 3: // STBY_XOSC
+            irq.buf[1] = OPMODE_STANDBY;
+            break;
+        case 4: // FS
+            irq.buf[1] = OPMODE_FS;
+            break;
+        case 5: // RX
+            irq.buf[1] = OPMODE_RX;
+            break;
+        case 6: // TX
+            irq.buf[1] = OPMODE_TX;
+            break;
+        default:
+            irq.buf[1] = OPMODE_FAIL;
+            break;
+    }
+
+    irq.fields.flags.irq_type = IRQ_TYPE_OPMODE;
+    irqOutPin = 1;
+}
+
+void get_lora_packet()
+{
+    loraConfig1_t conf1;
+    loraConfig2_t conf2;
+    //LoRaPacketConfig(unsigned preambleLen, bool fixLen, bool crcOn, bool invIQ)
+    uint32_t val = Radio::radio.readReg(REG_ADDR_LORA_PREAMBLE_SYMBNB, 2);
+    irq.buf[1] = val & 0xff;
+    val >>= 8;
+    irq.buf[2] = val & 0xff;
+
+    conf1.octet = Radio::radio.readReg(REG_ADDR_LORA_CONFIG1, 1);
+    irq.buf[3] = conf1.bits.implicit_header;
+    irq.buf[4] = conf1.bits.rx_invert_iq;
+
+    conf2.octet = Radio::radio.readReg(REG_ADDR_LORA_CONFIG2, 1);
+    irq.buf[5] = conf2.bits.tx_payload_crc16_en;
+
+    irq.fields.flags.irq_type = IRQ_TYPE_LORA_PKT;
+    irqOutPin = 1;
+}
+
+const uint8_t loraBWs[] = {
+    LORA_BW_7, LORA_BW_10, LORA_BW_15,
+    LORA_BW_20, LORA_BW_31, LORA_BW_41,
+    LORA_BW_62, LORA_BW_125, LORA_BW_250,
+    LORA_BW_500
+};
+
+void get_lora_modem()
+{
+    uint16_t khz = 0;
+    loraConfig1_t conf1;
+    loraConfig0_t conf0;
+    conf0.octet = Radio::radio.readReg(REG_ADDR_LORA_CONFIG0, 1);
+
+    switch (conf0.bits.modem_bw) {
+        case LORA_BW_7: khz = 8; break;         // 7.81 kHz real
+        case LORA_BW_10: khz = 10; break;        // 10.42 kHz real
+        case LORA_BW_15: khz = 16; break;        // 15.63 kHz real
+        case LORA_BW_20: khz = 21; break;        // 20.83 kHz real
+        case LORA_BW_31: khz = 31; break;        // 31.25 kHz real
+        case LORA_BW_41: khz = 42; break;        // 41.67 kHz real
+        case LORA_BW_62: khz = 63; break;        // 62.50 kHz real
+        case LORA_BW_125: khz = 125; break;       // 125 kHz real
+        case LORA_BW_250: khz = 250; break;       // 250 kHz real
+        case LORA_BW_500: khz = 500; break;       // 500 kHz real
+    }
+
+    irq.buf[1] = khz & 0xff;
+    khz >>= 8;
+    irq.buf[2] = khz & 0xff;
+
+    //LoRaModemConfig(unsigned KHz, unsigned sf, unsigned cr)
+    irq.buf[3] = conf0.bits.modem_sf;
+
+    conf1.octet = Radio::radio.readReg(REG_ADDR_LORA_CONFIG1, 1);
+    irq.buf[4] = conf1.bits.tx_coding_rate;
+    
+    irq.fields.flags.irq_type = IRQ_TYPE_LORA_MODEM;
+    irqOutPin = 1;
+}
+
+void get_tx_dbm()
+{
+    int8_t dbm;
+    PwrCtrl_t PwrCtrl;
+    PaCtrl1b_t PaCtrl1b;
+    unsigned v = Radio::radio.readReg(REG_ADDR_ANACTRL16, 1);
+
+    if (v & 0x10) {
+        dbm = PA_OFF_DBM;
+        goto dbmDone;
+    }
+
+    PwrCtrl.octet = Radio::radio.readReg(REG_ADDR_PWR_CTRL, 1);
+
+    PaCtrl1b.octet = Radio::radio.readReg(REG_ADDR_PA_CTRL1B, 1);
+    // PaCtrl1b.bits.tx_mode_bat ---  deviceSel
+
+    if (PaCtrl1b.bits.tx_mode_bat)
+        dbm = PwrCtrl.bits.tx_pwr - 17;
+    else
+        dbm = PwrCtrl.bits.tx_pwr - 9;
+
+dbmDone:
+    irq.buf[1] = dbm;
+    irq.fields.flags.irq_type = IRQ_TYPE_TXDBM;
+    irqOutPin = 1;
+}
+
+void get_fsk_sync()
+{
+    unsigned idx = 1;
+    unsigned addr = REG_ADDR_SYNCADDR;
+    uint8_t swl_bits = Radio::radio.readReg(REG_ADDR_FSK_SYNC_LEN, 1);
+    if (swl_bits & 7) {
+        swl_bits |= 7;
+        swl_bits++;
+    }
+    irq.buf[idx++] = swl_bits >> 3;
+
+    while (swl_bits > 0) {
+        irq.buf[idx++] = Radio::radio.readReg(addr++, 1);
+        swl_bits -= 8;
+    }
+
+    irq.fields.flags.irq_type = IRQ_TYPE_FSK_SYNC;
+    irqOutPin = 1;
+}
+
+void get_fsk_modem()
+{
+    uint32_t u32;
+    bwSel_t bwSel;
+    // GFSKModemConfig(unsigned bps, unsigned bwKHz, unsigned fdev_hz)
+
+    unsigned d = Radio::radio.readReg(REG_ADDR_BITRATE, 3);
+    float f = d / 32.0;
+
+    u32 = XTAL_FREQ_HZ / f;
+    irq.buf[1] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[2] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[3] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[4] = u32 & 0xff;
+
+    bwSel.octet = Radio::radio.readReg(REG_ADDR_BWSEL, 1);
+    switch (bwSel.octet) {
+        case GFSK_RX_BW_4800: u32 = 4800; break;
+        case GFSK_RX_BW_5800: u32 = 5800; break;
+        case GFSK_RX_BW_7300: u32 = 7300; break;
+        case GFSK_RX_BW_9700: u32 = 9700; break;
+        case GFSK_RX_BW_11700: u32 = 11700; break;
+        case GFSK_RX_BW_14600: u32 = 14600; break;
+        case GFSK_RX_BW_19500: u32 = 19500; break;
+        case GFSK_RX_BW_23400: u32 = 23400; break;
+        case GFSK_RX_BW_29300: u32 = 29300; break;
+        case GFSK_RX_BW_39000: u32 = 39000; break;
+        case GFSK_RX_BW_46900: u32 = 46900; break;
+        case GFSK_RX_BW_58600: u32 = 58600; break;
+        case GFSK_RX_BW_78200: u32 = 78200; break;
+        case GFSK_RX_BW_93800: u32 = 93800; break;
+        case GFSK_RX_BW_117300: u32 = 117300; break;
+        case GFSK_RX_BW_156200: u32 = 156200; break;
+        case GFSK_RX_BW_187200: u32 = 187200; break;
+        case GFSK_RX_BW_234300: u32 = 234300; break;
+        case GFSK_RX_BW_312000: u32 = 312000; break;
+        case GFSK_RX_BW_373600: u32 = 373600; break;
+        case GFSK_RX_BW_467000: u32 = 467000; break;
+    }
+
+    irq.buf[5] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[6] = u32 & 0xff;
+
+    d = Radio::radio.readReg(REG_ADDR_FREQDEV, 3);
+    u32 = d * FREQ_STEP;
+    irq.buf[7] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[8] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[9] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[10] = u32 & 0xff;
+
+    irq.fields.flags.irq_type = IRQ_TYPE_FSK_MODEM;
+    irqOutPin = 1;
+}
+
+void get_fsk_packet()
+{
+    pktCtrl0_t pktCtrl0;
+    pktCtrl2_t pktCtrl2;
+    // GFSKPacketConfig(unsigned preambleLen, bool fixLen, bool crcOn)
+    unsigned pl = Radio::radio.readReg(REG_ADDR_FSK_PREAMBLE_TXLEN , 2);
+    irq.buf[1] = pl & 0xff;
+    pl >>= 8;
+    irq.buf[2] = pl & 0xff;
+
+    pktCtrl0.octet = Radio::radio.readReg(REG_ADDR_FSK_PKTCTRL0, 1);
+    irq.buf[3] = pktCtrl0.bits.pkt_len_format; // true = fixed
+
+    pktCtrl2.octet = Radio::radio.readReg(REG_ADDR_FSK_PKTCTRL2, 1);
+    switch (pktCtrl2.octet & 0x7) { // param8
+        case GFSK_CRC_OFF: irq.buf[4] = 0;
+        case GFSK_CRC_1_BYTE: irq.buf[4] = 1;
+        case GFSK_CRC_2_BYTE: irq.buf[4] = 2;
+        case GFSK_CRC_1_BYTE_INV: irq.buf[4] = 3;
+        case GFSK_CRC_2_BYTE_INV: irq.buf[4] = 4;
+        default: irq.buf[4] = 5;
+    }
+    
+    irq.fields.flags.irq_type = IRQ_TYPE_FSK_PKT;
+    irqOutPin = 1;
+}
+
+void radio_reset()
+{
+#ifdef TARGET_FF_ARDUINO
+    #define PINNAME_NRST            A0
+    Radio::radio.hw_reset(PINNAME_NRST);
+#else
+    #error reset_pin
+#endif
+}
+
+void radio_device_init()
+{
+}
+
+#endif /* ..SX126x_H */
diff -r 000000000000 -r 9eb5b8bf9f7b device_sx127x.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/device_sx127x.cpp	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,297 @@
+#include "radio_device.h"
+#ifdef SX127x_H 
+void get_opmode()
+{
+    Radio::radio.RegOpMode.octet = Radio::radio.read_reg(REG_OPMODE);
+
+    switch (Radio::radio.RegOpMode.bits.Mode) {
+        case RF_OPMODE_SLEEP:
+            irq.buf[1] = OPMODE_SLEEP;
+            break;
+        case RF_OPMODE_STANDBY:
+            irq.buf[1] = OPMODE_STANDBY;
+            break;
+        case RF_OPMODE_TRANSMITTER:
+            irq.buf[1] = OPMODE_TX;
+            break;
+        case RF_OPMODE_SYNTHESIZER_TX:
+        case RF_OPMODE_SYNTHESIZER_RX:
+            irq.buf[1] = OPMODE_FS;
+            break;
+        case RF_OPMODE_RECEIVER:
+        case RF_OPMODE_RECEIVER_SINGLE:
+        case RF_OPMODE_CAD:
+            irq.buf[1] = OPMODE_RX;
+            break;
+        default:
+            irq.buf[1] = OPMODE_FAIL;
+            break;
+    }
+
+    irq.fields.flags.irq_type = IRQ_TYPE_OPMODE;
+    irqOutPin = 1;
+}
+
+void get_lora_packet()
+{
+    uint32_t val;
+    //LoRaPacketConfig(unsigned preambleLen, bool fixLen, bool crcOn, bool invIQ)
+    Radio::lora.RegPreamble = Radio::radio.read_u16(REG_LR_PREAMBLEMSB);
+    val = Radio::lora.RegPreamble;
+    irq.buf[1] = val & 0xff;
+    val >>= 8;
+    irq.buf[2] = val & 0xff;
+
+    irq.buf[3] = Radio::lora.getHeaderMode();
+
+    Radio::lora.RegTest33.octet = Radio::radio.read_reg(REG_LR_TEST33);
+    irq.buf[4] = Radio::lora.RegTest33.bits.invert_i_q;
+
+    irq.buf[5] = Radio::lora.getRxPayloadCrcOn();
+
+    irq.fields.flags.irq_type = IRQ_TYPE_LORA_PKT;
+    irqOutPin = 1;
+}
+
+static const unsigned* lora_bw_khz;
+
+void get_lora_modem()
+{
+    uint16_t khz = lora_bw_khz[Radio::lora.getBw()];
+
+    irq.buf[1] = khz & 0xff;
+    khz >>= 8;
+    irq.buf[2] = khz & 0xff;
+
+    //LoRaModemConfig(unsigned KHz, unsigned sf, unsigned cr)
+    irq.buf[3] = Radio::lora.getSf();
+    irq.buf[4] = Radio::lora.getCodingRate(false);
+    
+    irq.fields.flags.irq_type = IRQ_TYPE_LORA_MODEM;
+    irqOutPin = 1;
+}
+
+void get_tx_dbm()
+{
+    int8_t dbm;
+    RegPdsTrim1_t pds_trim;
+    uint8_t adr, pa_test_adr;
+
+    if (Radio::radio.type == SX1276) {
+        adr = REG_PDSTRIM1_SX1276;
+        pa_test_adr = REG_PATEST_SX1276;
+    } else {
+        adr = REG_PDSTRIM1_SX1272;
+        pa_test_adr = REG_PATEST_SX1272;
+    }
+
+    if (Radio::radio.read_reg(pa_test_adr) & 0x20) {
+        pds_trim.octet = Radio::radio.read_reg(adr);
+
+        Radio::radio.RegPaConfig.octet = Radio::radio.read_reg(REG_PACONFIG);
+        if (Radio::radio.RegPaConfig.bits.PaSelect) {
+            dbm = Radio::radio.RegPaConfig.bits.OutputPower + pds_trim.bits.prog_txdac - 2;
+        } else {
+            dbm = Radio::radio.RegPaConfig.bits.OutputPower - 1;
+        }
+    } else {
+        dbm = PA_OFF_DBM;
+    }
+
+    irq.buf[1] = dbm;
+    irq.fields.flags.irq_type = IRQ_TYPE_TXDBM;
+    irqOutPin = 1;
+}
+
+void get_fsk_sync()
+{
+    unsigned n, idx = 1;
+
+    Radio::fsk.RegSyncConfig.octet = Radio::radio.read_reg(REG_FSK_SYNCCONFIG);
+
+    irq.buf[idx++] = Radio::fsk.RegSyncConfig.bits.SyncSize + 1;
+    for (n = 0; n < irq.buf[2]; n++) {
+        irq.buf[idx++] = Radio::radio.read_reg(REG_FSK_SYNCVALUE1+n);
+    }
+
+    irq.fields.flags.irq_type = IRQ_TYPE_FSK_SYNC;
+    irqOutPin = 1;
+}
+
+static unsigned bw_read(uint8_t regAddr)
+{
+    RegRxBw_t reg_bw;
+
+    reg_bw.octet = Radio::radio.read_reg(regAddr);
+
+    switch (reg_bw.bits.Exponent) {
+        case 7:
+            if (reg_bw.bits.Mantissa == 2)
+                return 0;
+            if (reg_bw.bits.Mantissa == 1)
+                return 1;
+            if (reg_bw.bits.Mantissa == 0)
+                return 2;
+            break;
+        case 6:
+            if (reg_bw.bits.Mantissa == 2)
+                return 3;
+            if (reg_bw.bits.Mantissa == 1)
+                return 4;
+            if (reg_bw.bits.Mantissa == 0)
+                return 5;
+            break;
+        case 5:
+            if (reg_bw.bits.Mantissa == 2)
+                return 6;
+            if (reg_bw.bits.Mantissa == 1)
+                return 7;
+            if (reg_bw.bits.Mantissa == 0)
+                return 8;
+            break;
+        case 4:
+            if (reg_bw.bits.Mantissa == 2)
+                return 9;
+            if (reg_bw.bits.Mantissa == 1)
+                return 10;
+            if (reg_bw.bits.Mantissa == 0)
+                return 11;
+            break;
+        case 3:
+            if (reg_bw.bits.Mantissa == 2)
+                return 12;
+            if (reg_bw.bits.Mantissa == 1)
+                return 13;
+            if (reg_bw.bits.Mantissa == 0)
+                return 14;
+            break;
+        case 2:
+            if (reg_bw.bits.Mantissa == 2)
+                return 15;
+            if (reg_bw.bits.Mantissa == 1)
+                return 16;
+            if (reg_bw.bits.Mantissa == 0)
+                return 17;
+            break;
+        case 1:
+            if (reg_bw.bits.Mantissa == 2)
+                return 18;
+            if (reg_bw.bits.Mantissa == 1)
+                return 19;
+            if (reg_bw.bits.Mantissa == 0)
+                return 20;
+            break;
+    }
+
+    return 21;
+}
+
+static const unsigned rxbws[] = {
+    2600, // 0
+    3100, // 1
+    3900, // 2
+    5200, // 3
+    6300, // 4
+    7800, // 5
+    10400, // 6
+    12500, // 7
+    15600, // 8
+    20800, // 9
+    25000, // 10
+    31300, // 11
+    41700, // 12
+    50000, // 13
+    62500, // 14
+    83300, // 15
+    100000, // 16
+    125000, // 17
+    166700, // 18
+    200000, // 19
+    250000 // 20
+};
+
+void get_fsk_modem()
+{
+    uint32_t u32;
+    // GFSKModemConfig(unsigned bps, unsigned bwKHz, unsigned fdev_hz)
+
+    u32 = Radio::fsk.get_bitrate();
+    irq.buf[1] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[2] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[3] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[4] = u32 & 0xff;
+
+    u32 = rxbws[bw_read(REG_FSK_RXBW)];
+    irq.buf[5] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[6] = u32 & 0xff;
+
+    u32 = Radio::fsk.get_tx_fdev_hz();
+    irq.buf[7] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[8] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[9] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[10] = u32 & 0xff;
+
+    irq.fields.flags.irq_type = IRQ_TYPE_FSK_MODEM;
+    irqOutPin = 1;
+}
+
+void get_fsk_packet()
+{
+    // GFSKPacketConfig(unsigned preambleLen, bool fixLen, bool crcOn)
+    unsigned pl = Radio::radio.read_u16(REG_FSK_PREAMBLEMSB);
+    irq.buf[1] = pl & 0xff;
+    pl >>= 8;
+    irq.buf[2] = pl & 0xff;
+
+    Radio::fsk.RegPktConfig1.octet = Radio::radio.read_reg(REG_FSK_PACKETCONFIG1);
+    irq.buf[3] = Radio::fsk.RegPktConfig1.bits.PacketFormatVariable;
+
+    Radio::fsk.RegPktConfig1.octet = Radio::radio.read_reg(REG_FSK_PACKETCONFIG1);
+    irq.buf[4] = Radio::fsk.RegPktConfig1.bits.CrcOn;
+    
+    irq.fields.flags.irq_type = IRQ_TYPE_FSK_PKT;
+    irqOutPin = 1;
+}
+
+void radio_reset()
+{
+    Radio::radio.hw_reset();
+}
+
+
+static const unsigned lora_bw_khz_1276[] = {
+      8, // 0
+     10, // 1
+     16, // 2
+     21, // 3
+     31, // 4
+     42, // 5
+     63, // 6
+    125, // 7
+    250, // 8
+    500  // 9
+};
+static const unsigned lora_bw_khz_1272[] = {
+    125, // 0
+    250, // 1
+    500  // 2
+};
+
+void radio_device_init()
+{
+    if (Radio::radio.type == SX1276) {
+        lora_bw_khz = lora_bw_khz_1276;
+    } else if (Radio::radio.type == SX1272) {
+        lora_bw_khz = lora_bw_khz_1272;
+    }
+}
+
+
+#endif /* ..SX127x_H */
diff -r 000000000000 -r 9eb5b8bf9f7b device_sx128x.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/device_sx128x.cpp	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,170 @@
+#include "radio_device.h"
+#ifdef SX128x_H 
+void get_opmode()
+{
+    status_t status;
+    Radio::radio.xfer(OPCODE_GET_STATUS, 0, 1, &status.octet);
+
+    switch (status.bits.chipMode) {
+        case 2: // STBY_RC
+        case 3: // STBY_XOSC
+            irq.buf[1] = OPMODE_STANDBY;
+            break;
+        case 4: // FS
+            irq.buf[1] = OPMODE_FS;
+            break;
+        case 5: // RX
+            irq.buf[1] = OPMODE_RX;
+            break;
+        case 6: // TX
+            irq.buf[1] = OPMODE_TX;
+            break;
+        default:
+            irq.buf[1] = OPMODE_FAIL;
+            break;
+    }
+
+    irq.fields.flags.irq_type = IRQ_TYPE_OPMODE;
+    irqOutPin = 1;
+}
+
+void get_lora_packet()
+{
+    LoRaLrCtl_t LoRaLrCtl;
+    LoRaPktPar1_t LoRaPktPar1;
+    //LoRaPacketConfig(unsigned preambleLen, bool fixLen, bool crcOn, bool invIQ)
+    LoRaPreambleReg_t LoRaPreambleReg;
+    LoRaPreambleReg.octet = Radio::radio.readReg(REG_ADDR_LORA_PREAMBLE, 1);
+    uint32_t val = (1 << LoRaPreambleReg.bits.preamble_symb_nb_exp) * LoRaPreambleReg.bits.preamble_symb1_nb;
+    irq.buf[1] = val & 0xff;
+    val >>= 8;
+    irq.buf[2] = val & 0xff;
+
+    LoRaPktPar1.octet = Radio::radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
+    irq.buf[3] = LoRaPktPar1.bits.implicit_header;
+    irq.buf[4] = LoRaPktPar1.bits.rxinvert_iq ? 0 : 1; // std is 0
+
+    LoRaLrCtl.octet = Radio::radio.readReg(REG_ADDR_LORA_LRCTL, 1);
+    irq.buf[5] = LoRaLrCtl.bits.crc_en;
+
+    irq.fields.flags.irq_type = IRQ_TYPE_LORA_PKT;
+    irqOutPin = 1;
+}
+
+static const unsigned lora_bws[] = {
+    50, // 0
+    100, // 1
+    200, // 2
+    400, // 3
+    800, // 4
+    1600 // 5
+};
+
+void get_lora_modem()
+{
+    uint16_t khz;
+    LoRaPktPar1_t LoRaPktPar1;
+    LoRaPktPar0_t LoRaPktPar0;
+    LoRaPktPar0.octet = Radio::radio.readReg(REG_ADDR_LORA_PKTPAR0, 1);
+
+    khz = lora_bws[LoRaPktPar0.bits.modem_bw];
+    irq.buf[1] = khz & 0xff;
+    khz >>= 8;
+    irq.buf[2] = khz & 0xff;
+
+    //LoRaModemConfig(unsigned KHz, unsigned sf, unsigned cr)
+    irq.buf[3] = LoRaPktPar0.bits.modem_sf;
+
+    LoRaPktPar1.octet = Radio::radio.readReg(REG_ADDR_LORA_PKTPAR1, 1);
+    irq.buf[4] = LoRaPktPar1.bits.coding_rate;
+    
+    irq.fields.flags.irq_type = IRQ_TYPE_LORA_MODEM;
+    irqOutPin = 1;
+}
+
+#define TX_PWR_OFFSET           18
+void get_tx_dbm()
+{
+    PaPwrCtrl_t PaPwrCtrl;
+
+    PaPwrCtrl.octet = Radio::radio.readReg(REG_ADDR_PA_PWR_CTRL, 1);
+    irq.buf[1] = PaPwrCtrl.bits.tx_pwr - TX_PWR_OFFSET;
+    irq.fields.flags.irq_type = IRQ_TYPE_TXDBM;
+    irqOutPin = 1;
+}
+
+void get_fsk_sync()
+{
+    /* TODO 3 separate sync words */
+
+    irq.fields.flags.irq_type = IRQ_TYPE_FSK_SYNC;
+    irqOutPin = 1;
+}
+
+void get_fsk_modem()
+{
+    uint32_t u32;
+    // GFSKModemConfig(unsigned bps, unsigned bwKHz, unsigned fdev_hz)
+
+    /* given in bitrate-bandwith combination */
+    u32 = 0;    // TODO bitrate
+    irq.buf[1] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[2] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[3] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[4] = u32 & 0xff;
+
+
+    u32 = 0;    // TODO  bandwidth
+    irq.buf[5] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[6] = u32 & 0xff;
+
+    u32 = 0;    // TODO fdev  mod index + bitrate
+    irq.buf[7] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[8] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[9] = u32 & 0xff;
+    u32 >>= 8;
+    irq.buf[10] = u32 & 0xff;
+
+    irq.fields.flags.irq_type = IRQ_TYPE_FSK_MODEM;
+    irqOutPin = 1;
+}
+
+static const uint8_t fsk_pbl_lens[] = { 4, 8, 12, 16, 20, 24, 28, 32 };
+
+void get_fsk_packet()
+{
+    PktBitStreamCtrl_t PktBitStreamCtrl;
+    PktCtrl0_t PktCtrl0;
+    // GFSKPacketConfig(unsigned preambleLen, bool fixLen, bool crcOn)
+    PktCtrl1_t PktCtrl1;
+    PktCtrl1.octet = Radio::radio.readReg(REG_ADDR_PKTCTRL1, 1);
+    irq.buf[1] = fsk_pbl_lens[PktCtrl1.gfsk.preamble_len];
+    irq.buf[2] = 0;
+
+    PktCtrl0.octet = Radio::radio.readReg(REG_ADDR_PKTCTRL0, 1);
+    irq.buf[3] = PktCtrl0.bits.pkt_len_format; // true = fixed
+
+    PktBitStreamCtrl.octet = Radio::radio.readReg(REG_ADDR_PKT_BITSTREAM_CTRL, 1);
+    irq.buf[4] = PktBitStreamCtrl.bits.crc_mode;
+    
+    irq.fields.flags.irq_type = IRQ_TYPE_FSK_PKT;
+    irqOutPin = 1;
+}
+
+void radio_reset()
+{
+    Radio::radio.hw_reset();
+}
+
+void radio_device_init()
+{
+}
+
+#endif /* ..SX128x_H */
+
diff -r 000000000000 -r 9eb5b8bf9f7b lib_i2c_slave_block.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib_i2c_slave_block.lib	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/dudmuck/code/lib_i2c_slave_block/#493e5cc9c052
diff -r 000000000000 -r 9eb5b8bf9f7b main.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,709 @@
+
+#include <mbed.h>
+#include "LowPowerTimeoutAbs.h"
+#include "radio_device.h"
+
+#define IRQ_OUT_PIN     D6
+
+#define BEACON_PRELOAD_us               500000
+
+typedef struct {
+    uint8_t beacon_enabled : 1; // 0
+    uint8_t beacon_guard   : 1; // 1
+    uint8_t beacon_loaded  : 1; // 2
+    uint8_t beacon_test    : 1; // 3
+    uint8_t req_type       : 4; // 4,5,6,7
+} flags_t;
+
+const int SLAVE_ADDRESS = 0xA0;
+
+RawSerial pc(USBTX, USBRX);
+
+EventQueue queue;
+uint8_t skip_beacon_cnt = 0;
+uint16_t beaconPreambleSymbs;
+uint16_t preambleSymbs;
+unsigned beaconInterval_us;
+uint8_t beaconSizeBytes;
+/* low power timer used because hi-speed timer doesnt have crystal, and 32Khz crystal is 20ppm */
+LowPowerTimeoutAbs send_beaconTimer;
+LowPowerTimeoutAbs load_beaconTimer;
+uint8_t beacon_payload[4];
+cfg_t cfg;
+uint8_t get_n_rssi;
+
+volatile flags_t flags;
+volatile uint8_t bs;
+volatile uint16_t rx_slot;
+volatile us_timestamp_t beaconAt;
+volatile uint8_t blocked_cmd;
+
+DigitalOut irqOutPin(IRQ_OUT_PIN);
+irq_t irq;
+
+uint8_t toI2C_rx_pkt[256];
+volatile uint16_t toI2C_rx_pkt_idx;   // incremented by 32: will overflow 8bit
+
+int tx_pkt_len;
+uint8_t tx_pkt_idx;
+
+void console()
+{
+    char c;
+    if (pc.readable() != 1)
+        return;
+
+    c = pc.getc();
+
+    if (c == '.') {
+        pc.printf("beacon_guard%u ", flags.beacon_guard);
+        pc.printf("beacon_loaded%u ", flags.beacon_loaded);
+        pc.printf("bs:%u\r\n", bs);
+    }
+}
+
+void
+send_beacon()
+{
+    bs = 3;
+    if (!flags.beacon_loaded)
+        return;
+
+    bs = 4;
+    Radio::Send(beaconSizeBytes, 0, 0, 0);
+    flags.beacon_loaded = false;
+}
+
+static uint16_t beacon_crc( uint8_t *buffer, uint16_t length )
+{
+    // The CRC calculation follows CCITT
+    const uint16_t polynom = 0x1021;
+    // CRC initial value
+    uint16_t crc = 0x0000;
+
+    if( buffer == NULL )
+    {
+        return 0;
+    }
+
+    for( uint16_t i = 0; i < length; ++i )
+    {
+        crc ^= ( uint16_t ) buffer[i] << 8;
+        for( uint16_t j = 0; j < 8; ++j )
+        {
+            crc = ( crc & 0x8000 ) ? ( crc << 1 ) ^ polynom : ( crc << 1 );
+        }
+    }
+
+    return crc;
+}
+
+void
+_load_beacon()
+{
+    bool inv_iq = false;
+    uint16_t crc;
+
+    bs = 2;
+    Radio::Standby( );
+
+    if (skip_beacon_cnt > 0) {
+        inv_iq = true;
+        skip_beacon_cnt--;
+    }
+                       //    preambleLen, fixLen, crcOn, invIQ
+    Radio::LoRaPacketConfig(beaconPreambleSymbs, true, false, inv_iq);
+
+    Radio::SetFixedPayloadLength(beaconSizeBytes);
+
+    Radio::radio.tx_buf[0] = beacon_payload[0];
+    Radio::radio.tx_buf[1] = beacon_payload[1];
+    Radio::radio.tx_buf[2] = beacon_payload[2];
+    Radio::radio.tx_buf[3] = beacon_payload[3];
+    //beacon_payload[0] = CMD_NONE;
+
+    crc = beacon_crc(Radio::radio.tx_buf, 4);
+    Radio::radio.tx_buf[4] = crc & 0xff;
+    Radio::radio.tx_buf[5] = crc >> 8;
+
+    flags.beacon_loaded = true;
+}
+
+uint16_t tim_get_current_slot()
+{
+    us_timestamp_t us_until = beaconAt - send_beaconTimer.read_us();
+    return us_until / 30000;
+}
+
+void load_beacon()
+{
+    if (flags.beacon_enabled) {
+        flags.beacon_guard = true;
+        bs = 1;
+        queue.call_in(200, _load_beacon);
+    }
+}
+
+void get_random()
+{
+    uint32_t r;
+    Radio::Rx(0);
+    wait(0.01);
+    r = Radio::Random();
+
+    irq.buf[1] = r & 0xff;
+    r >>= 8;
+    irq.buf[2] = r & 0xff; 
+    r >>= 8;
+    irq.buf[3] = r & 0xff;
+    r >>= 8;
+    irq.buf[4] = r & 0xff;
+
+    irq.fields.flags.irq_type = IRQ_TYPE_RANDOM;
+    irqOutPin = 1;
+}
+
+void cf_read()
+{
+    uint32_t hz;
+    float MHz;
+#ifdef SX127x_H
+    MHz = Radio::radio.get_frf_MHz();
+#else
+    MHz = Radio::radio.getMHz();
+#endif
+    
+    hz = MHz * 1000000;
+
+    irq.buf[1] = hz & 0xff;
+    hz >>= 8;
+    irq.buf[2] = hz & 0xff; 
+    hz >>= 8;
+    irq.buf[3] = hz & 0xff;
+    hz >>= 8;
+    irq.buf[4] = hz & 0xff;
+
+    irq.fields.flags.irq_type = IRQ_TYPE_CF;
+    irqOutPin = 1;
+}
+
+void measure_ambient(uint8_t n_samples)
+{
+    int i, acc = 0;
+    float bg_rssi;
+    for (i = 0; i < n_samples; i++) {
+        int rssi = Radio::GetRssiInst();
+        acc += rssi;
+        wait(0.01);
+    }
+    bg_rssi = acc / (float)n_samples;
+    pc.printf("bg_rssi:%.1fdBm ", bg_rssi);
+    
+    irq.fields.rssi = bg_rssi * 10;
+    irq.fields.flags.irq_type = IRQ_TYPE_RSSI;
+    irqOutPin = 1;
+}
+
+void txDoneCB()
+{
+    if (flags.beacon_test) {
+        flags.beacon_test = 0;
+        return;
+    }
+
+    if (cfg.fields.flags.rxOnTxDone) {
+               //    preambleLen, fixLen, crcOn, invIQ
+        Radio::LoRaPacketConfig(preambleSymbs, false, true, false);
+        
+        Radio::Rx(0);
+
+        bs = 5;
+        if (flags.beacon_guard) {   // beacon done transmitting
+            measure_ambient(cfg.fields.n_rssi_samples);
+            flags.beacon_guard = false;
+            bs = 6;
+
+            beaconAt += beaconInterval_us;
+            load_beaconTimer.attach_us(load_beacon, beaconAt - BEACON_PRELOAD_us);
+            send_beaconTimer.attach_us(send_beacon, beaconAt);
+        }
+    }
+
+}
+
+void send_downlink()
+{
+    if (tx_pkt_len > 0 && tx_pkt_idx >= tx_pkt_len) {
+        Radio::Send(tx_pkt_len,
+            cfg.fields.maxListenTime,
+            cfg.fields.channelFreeTime,
+            cfg.fields.rssiThresh
+        );
+
+        tx_pkt_len = 0; // packet only sent once, as requested by host
+    }
+}
+
+void rxDoneCB(uint8_t size, float rssi, float snr)
+{
+    if (cfg.fields.flags.rxOnTxDone) {
+        queue.call_in(cfg.fields.downlinkOffset, send_downlink);
+    }
+
+    if (irq.fields.flags.rx_pkt == 0) {
+        if (flags.beacon_enabled)
+            irq.fields.rx_slot = tim_get_current_slot();
+        else
+            irq.fields.rx_slot = 0;
+
+        irq.fields.rssi = rssi * 10;
+        irq.fields.pkt_snr = snr;
+        irq.fields.pkt_len = size;
+        memcpy(toI2C_rx_pkt, Radio::radio.rx_buf, size);
+        irq.fields.flags.rx_pkt = 1;
+        irqOutPin = 1;
+        pc.printf("rxlen%u snr:%.1f rssi:%.1f slot:%u\r\n", size, snr, rssi, irq.fields.rx_slot);
+    } else {
+        pc.printf("rx_pkt_overrun\r\n");
+        irq.fields.flags.rx_pkt_overrun = 1;
+    }
+
+}
+
+void tim_init()
+{
+    beaconAt = send_beaconTimer.read_us() + beaconInterval_us;
+    load_beaconTimer.attach_us(load_beacon, beaconAt - BEACON_PRELOAD_us);
+    send_beaconTimer.attach_us(send_beacon, beaconAt);
+}
+
+volatile uint8_t cnt = 0;
+
+void fill_tx_buf(uint8_t cmd)     // called from isr
+{
+    unsigned i;
+
+    /* Master is reading from slave
+     * Called from ISR; must return immediately */
+
+    if (flags.beacon_guard && cmd >= CMD_RADIO) {
+        blocked_cmd = cmd;
+        return;
+    }
+
+    switch (cmd) {
+        case CMD_TEST:
+            for (i = 0; i < cmd_to_length[CMD_TEST]; i++)
+                i2c.tx_buf[i] = i + cnt;
+            cnt++;
+            break;
+        case CMD_TEST32:
+            for (i = 0; i < cmd_to_length[CMD_TEST32]; i++)
+                i2c.tx_buf[i] = i + cnt;
+            cnt++;
+            break;
+        case CMD_RX_PAYLOAD:
+            memcpy(i2c.tx_buf, toI2C_rx_pkt+toI2C_rx_pkt_idx, sizeof(i2c.tx_buf));
+
+            toI2C_rx_pkt_idx += cmd_to_length[CMD_RX_PAYLOAD];
+            if (toI2C_rx_pkt_idx >= irq.fields.pkt_len) {
+                irq.fields.pkt_len = 0;
+                irq.fields.flags.rx_pkt = 0;
+                if (irq.buf[0] == 0)
+                    irqOutPin = 0;
+            }
+            break;
+        case CMD_SKIP_BEACONS:
+            i2c.tx_buf[0] = skip_beacon_cnt;
+            break;
+        case CMD_CURRENT_SLOT:
+            {
+                uint16_t *u16ptr = (uint16_t*)i2c.tx_buf;
+                *u16ptr = tim_get_current_slot();
+            }
+            break;
+        case CMD_REQ_RANDOM:
+            flags.req_type = IRQ_TYPE_RANDOM;
+            break;
+        case CMD_IRQ:
+            if (irq.fields.flags.rx_pkt)
+                toI2C_rx_pkt_idx = 0;   // packet reading expected to start
+
+            for (i = 0; i < cmd_to_length[CMD_IRQ]; i++)
+                i2c.tx_buf[i] = irq.buf[i];
+
+            irq.fields.flags.rx_pkt_overrun = 0;
+            irq.fields.flags.irq_type = 0;
+            if (irq.buf[0] == 0)
+                irqOutPin = 0;
+
+            break;
+    }
+
+} // ..fill_tx_buf()
+
+void service_i2c_write(uint8_t cmd, uint8_t len, const uint8_t* req)
+{
+    uint32_t u32;
+    uint8_t s8;
+
+    if (flags.beacon_guard && cmd >= CMD_RADIO) {
+        blocked_cmd = cmd;
+        return;
+    }
+
+    switch (cmd) {
+        case CMD_TEST:
+        case CMD_TEST32:
+            pc.printf("test:");
+            for (s8 = 0; s8 < cmd_to_length[cmd]; s8++)
+                pc.printf("%02x ", req[s8]);
+            pc.printf("\r\n");
+            break;
+        case CMD_CFHZ_REQ:
+            flags.req_type = IRQ_TYPE_CF;
+            break;
+        case CMD_FSK_MODEM_CFG_REQ:
+            flags.req_type = IRQ_TYPE_FSK_MODEM;
+            break;
+        case CMD_FSK_PKT_CFG_REQ:
+            flags.req_type = IRQ_TYPE_FSK_PKT;
+            break;
+        case CMD_FSK_SYNC_REQ:
+            flags.req_type = IRQ_TYPE_FSK_SYNC;
+            break;
+        case CMD_TXDBM_REQ:
+            flags.req_type = IRQ_TYPE_TXDBM;
+            break;
+        case CMD_LORA_MODEM_CFG_REQ:
+            flags.req_type = IRQ_TYPE_LORA_MODEM;
+            break;
+        case CMD_LORA_PKT_CFG_REQ:
+            flags.req_type = IRQ_TYPE_LORA_PKT;
+            break;
+        case CMD_OPMODE_REQ:
+            flags.req_type = IRQ_TYPE_OPMODE;
+            break;
+        case CMD_RADIO_RESET:
+            load_beaconTimer.detach();
+            send_beaconTimer.detach();
+            flags.beacon_enabled = 0;
+            radio_reset();
+            break;
+        case CMD_CFHZ_WRITE:
+            u32 = req[3];
+            u32 <<= 8;
+            u32 |= req[2];
+            u32 <<= 8;
+            u32 |= req[1];
+            u32 <<= 8;
+            u32 |= req[0];
+            pc.printf("setCh %luhz\r\n", u32);
+            Radio::SetChannel(u32);
+            break;
+        case CMD_TXDBM_WRITE:
+            s8 = req[0];
+            Radio::set_tx_dbm(s8);
+            pc.printf("set tx dBm:%d\r\n", s8);
+            break;
+        case CMD_LORA_MODEM_CFG_WRITE:
+            {
+                uint8_t sf, cr;
+
+                u32 = req[1];
+                u32 <<= 8;
+                u32 |= req[0];
+                pc.printf("(%04lx) ", u32);
+                sf = req[2];
+                cr = req[3];
+                pc.printf("modemcfg %luKhz sf%u cr%u\r\n", u32, sf, cr);
+                Radio::LoRaModemConfig(u32, sf, cr);
+            }
+            break;
+        case CMD_RX_START:
+            u32 = req[3];
+            u32 <<= 8;
+            u32 |= req[2];
+            u32 <<= 8;
+            u32 |= req[1];
+            u32 <<= 8;
+            u32 |= req[0];
+            Radio::Rx(u32);
+            pc.printf("rx(%lu)\r\n", u32);
+            break;
+        case CMD_LORA_PKT_CFG_WRITE:
+            {
+                pktcfg_t pktcfg;
+                u32 = req[1];
+                u32 <<= 8;
+                u32 |= req[0];
+                preambleSymbs = u32;
+                pktcfg.octet = req[2];
+                pc.printf("pktcfg pre%lu fixlen%u crc%u inv%u\r\n", u32, pktcfg.bits.fixLen, pktcfg.bits.crcOn, pktcfg.bits.invIQ);
+                Radio::LoRaPacketConfig(u32, pktcfg.bits.fixLen, pktcfg.bits.crcOn, pktcfg.bits.invIQ);
+            }
+            break;
+        case CMD_BEACON_CFG_WRITE:
+            {   
+                us_timestamp_t txStartAt;
+
+                u32 = req[1];
+                u32 <<= 8;
+                u32 |= req[0];
+                pc.printf("bi%lu ", u32);
+                beaconInterval_us = u32 * 1000000;
+                pc.printf("{%u} ", beaconInterval_us);
+
+                u32 = req[3];
+                u32 <<= 8;
+                u32 |= req[2];
+                pc.printf("ps%lu ", u32);
+                beaconPreambleSymbs = u32;
+
+                beaconSizeBytes = req[4];
+                pc.printf("len%u\r\n", beaconSizeBytes);
+
+                if (beaconInterval_us > 0) {
+                    _load_beacon();
+                    send_beacon();
+                    txStartAt = Radio::lpt.read_us();
+                    flags.beacon_test = 1;
+                    while (flags.beacon_test)
+                        Radio::service();
+
+                    u32 = Radio::irqAt - txStartAt;
+                    pc.printf("beaconDur:%lu, 0x%lx\r\n", u32, u32);
+
+                    tim_init();
+                    flags.beacon_enabled = 1;
+
+                    irq.buf[1] = u32 & 0xff;
+                    u32 >>= 8;
+                    irq.buf[2] = u32 & 0xff; 
+                    u32 >>= 8;
+                    irq.buf[3] = u32 & 0xff;
+                    u32 >>= 8;
+                    irq.buf[4] = u32 & 0xff;
+                    irq.fields.flags.irq_type = IRQ_TYPE_BEACON_DUR;
+                    //pc.printf("BDirq.buf[0]:%02x\r\n", irq.buf[0]);
+                    irqOutPin = 1;
+                } else {
+                    load_beaconTimer.detach();
+                    send_beaconTimer.detach();
+                    flags.beacon_enabled = 0;
+                }
+            }
+            break;
+        case CMD_BEACON_PAYLOAD:
+            beacon_payload[0] = req[0];
+            beacon_payload[1] = req[1];
+            beacon_payload[2] = req[2];
+            beacon_payload[3] = req[3];
+            break;
+        case CMD_CFG:
+            memcpy(cfg.buf, req, sizeof(cfg_t));
+            pc.printf("cfg %u\r\n", cfg.fields.n_rssi_samples);
+            break;
+        case CMD_STANDBY:
+            Radio::Standby();
+            break;
+        case CMD_PUBLIC_NET:
+            pc.printf("pubnet%02x\r\n", req[0]);
+            Radio::SetPublicNetwork(req[0] == 0 ? false : true);
+            break;
+        case CMD_MAX_PAYLEN_WRITE:
+            pc.printf("maxpay%02x\r\n", req[0]);
+            Radio::SetRxMaxPayloadLength(req[0]);
+            break;
+        case CMD_TX_BUF_START:
+            if (cfg.fields.flags.rxOnTxDone) {
+                /* turn off receiver in preparation for downlink */
+                Radio::Standby();   
+
+                //                         preambleLen, fixLen, crcOn, invIQ
+                Radio::LoRaPacketConfig(preambleSymbs, false, false, true);
+            }
+
+            tx_pkt_len = req[0];
+            memcpy(Radio::radio.tx_buf, req+1, 31);
+            tx_pkt_idx = 31;
+            break;
+        case CMD_TX_BUF:
+            {
+                uint8_t len = 32;
+                uint8_t end = (tx_pkt_idx + 32) & 0xff;
+                if (end < 32)
+                    len -= end;
+                memcpy(Radio::radio.tx_buf + tx_pkt_idx, req, len);
+                tx_pkt_idx += len;
+            }
+            break;
+        case CMD_RSSI_REQ:
+            get_n_rssi = req[0];
+            break;
+        case CMD_SEND:
+            Radio::Send(tx_pkt_len,
+                cfg.fields.maxListenTime,
+                cfg.fields.channelFreeTime,
+                cfg.fields.rssiThresh
+            );
+            break;
+        case CMD_SKIP_BEACONS:
+            skip_beacon_cnt = req[0];
+            break;
+        case CMD_LORA_SYMBTO_WRITE: // 1  byte
+            Radio::SetLoRaSymbolTimeout(req[0]);
+            break;
+        case CMD_FSK_MODEM_CFG_WRITE: // 10bytes
+            {
+                uint32_t bps, fdev_hz;
+                uint16_t khz_bw;
+                u32 = req[3];
+                u32 <<= 8;
+                u32 |= req[2];
+                u32 <<= 8;
+                u32 |= req[1];
+                u32 <<= 8;
+                u32 |= req[0];
+                bps = u32;
+
+                u32 = req[5];
+                u32 <<= 8;
+                u32 |= req[4];
+                khz_bw = u32;
+
+                u32 = req[9];
+                u32 <<= 8;
+                u32 |= req[8];
+                u32 <<= 8;
+                u32 |= req[7];
+                u32 <<= 8;
+                u32 |= req[6];
+                fdev_hz = u32;
+                Radio::GFSKModemConfig(bps, khz_bw, fdev_hz);
+            }
+            break;
+        case CMD_FSK_PKT_CFG_WRITE: // 4 bytes
+            u32 = req[1];
+            u32 <<= 8;
+            u32 |= req[0];
+            Radio::GFSKPacketConfig(u32, req[2], req[3]);
+            break;
+        case CMD_FSK_SYNC_WRITE:
+            /* TODO */
+            break;
+#ifdef DEBUG_SMBUS
+        case CMD_ISR:
+            pc.printf("i%02x ", req[0]);    // lsbyte of I2C_ISR
+            break;
+        case CMD_STOPF:
+            pc.printf("STOPF\r\n");
+            break;
+        case CMD_AF:
+            pc.printf("AF%u ", req[0]); // req[0] is dma tx cndtr
+            break;
+#endif /* DEBUG_SMBUS */
+        case CMD_BUSERR:
+            pc.printf("BUSERR%u\r\n", req[0]);
+            break;
+        /* failures: */
+        case CMD_ARLO:
+            pc.printf("ARLO%u,%u\r\n", toI2C_rx_pkt_idx, req[0]); // req[0] tx_cndtr
+            break;
+        case CMD_TIMEOUT:
+            pc.printf("TIMEOUT(tx%u,rx%u)\r\n", req[0], req[1]);    // req[0] tx_cndtr
+            break;
+        default:
+            pc.printf("??%02x??\r\n", cmd);
+            break;
+    } // ..switch (cmd)
+
+} // ..service_i2c_write()
+
+const RadioEvents_t rev = {
+    /* Dio0_top_half */     NULL,
+    /* TxDone_topHalf */    NULL,
+    /* TxDone_botHalf */    txDoneCB,
+    /* TxTimeout  */        NULL,
+    /* RxDone  */           rxDoneCB,
+    /* RxTimeout  */        NULL,
+    /* RxError  */          NULL,
+    /* FhssChangeChannel  */NULL,
+    /* CadDone  */          NULL
+};
+
+int main()
+{
+    Thread eventThread(osPriorityNormal, 512);
+    int res;
+
+    pc.baud(115200);
+    pc.printf("\r\nreset\r\n");
+
+    Radio::Init(&rev);
+    radio_device_init();
+
+    res = smbus_init(I2C_SDA, I2C_SCL, SLAVE_ADDRESS);
+    pc.printf("%d = smbus_init()\r\n", res);
+    if (res < 0) {
+        for (;;) asm("nop");
+    }
+
+    eventThread.start(callback(&queue, &EventQueue::dispatch_forever));
+
+    while (1) {
+        if (i2c.c_overrun) {
+            pc.printf("c_overrun\r\n");
+            for (;;) asm("nop");
+        }
+
+        service_i2c();
+        console();
+
+        if (get_n_rssi > 0) {
+            measure_ambient(get_n_rssi);
+            get_n_rssi = 0;
+        }
+
+        if (flags.req_type != IRQ_TYPE_PKT) {
+            switch (flags.req_type) {
+                case IRQ_TYPE_CF:
+                    cf_read();
+                    break;
+                case IRQ_TYPE_RANDOM:
+                    get_random();
+                    break;
+                case IRQ_TYPE_FSK_MODEM:
+                    get_fsk_modem();
+                    break;
+                case IRQ_TYPE_FSK_PKT:
+                    get_fsk_packet();
+                    break;
+                case IRQ_TYPE_FSK_SYNC:
+                    get_fsk_sync();
+                    break;
+                case IRQ_TYPE_TXDBM:
+                    get_tx_dbm();
+                    break;
+                case IRQ_TYPE_LORA_MODEM:
+                    get_lora_modem();
+                    break;
+                case IRQ_TYPE_LORA_PKT:
+                    get_lora_packet();
+                    break;
+                case IRQ_TYPE_OPMODE:
+                    get_opmode();
+                    break;
+            } // ..switch (flags.req_type)
+            flags.req_type = IRQ_TYPE_PKT;
+        }
+
+        if (blocked_cmd) {
+            pc.printf("blocked_cmd:%02x\r\n", blocked_cmd);
+            blocked_cmd = 0;
+        }
+
+        Radio::service();
+    } // ..while (1)
+}
+
diff -r 000000000000 -r 9eb5b8bf9f7b mbed-os.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed-os.lib	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,1 @@
+https://github.com/ARMmbed/mbed-os/#a8d1d26fa76a27263cc9a69f65df45e3458517a5
diff -r 000000000000 -r 9eb5b8bf9f7b radio_device.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/radio_device.h	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,18 @@
+#include "radio.h"
+#include "smbus.h"
+
+/* from specific device: */
+void get_opmode(void);
+void get_lora_packet(void);
+void get_lora_modem(void);
+void get_tx_dbm(void);
+void get_lora_symb_timeout(void);
+void get_fsk_sync(void);
+void get_fsk_packet(void);
+void get_fsk_modem(void);
+void radio_reset(void);
+void radio_device_init(void);
+
+/* from main.cpp: */
+extern DigitalOut irqOutPin;
+extern irq_t irq;
diff -r 000000000000 -r 9eb5b8bf9f7b sx12xx_hal.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sx12xx_hal.lib	Fri Feb 08 11:58:09 2019 -0800
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/dudmuck/code/sx12xx_hal/#3303cf2867d4