cervin sx1265 operating with transceiver firmware

Dependents:   lr1110_wifi_geolocation_device lr1110_wifi_geolocation_gateway

To use this driver, your LR1110 must be programmed as transceiver (not modem, which is LoRaWAN only).
Visit https://github.com/Lora-net/lr1110_updater_tool to update your LR1110, or change it from modem to transceiver if necessary.

lr1110.cpp

Committer:
Wayne Roberts
Date:
2021-02-05
Revision:
1:29294cae9f9a
Parent:
0:987d9022c152

File content as of revision 1:29294cae9f9a:

#include "sx12xx.h"

Callback<void()> SX1265::dio9_topHalf;    // low latency ISR context

void SX1265::dio9isr()
{
    if (dio9_topHalf)
        dio9_topHalf.call();
}

SX1265::SX1265(SPI& _spi, PinName _nss, PinName _busy, PinName _dio9, PinName _nrst, uint32_t di, unsigned tto, uint8_t tv)
    : spi(_spi), nss(_nss), busy(_busy), dio9(_dio9), nrst(_nrst), default_irqs(di), tcxoStartDelay(tto), tcxoVolts(tv)
{
    nss = 1;
    dio9.mode(PullDown);    // dio9 floats if no interrupts enabled

    t.start();

    if (busy) {
        hw_reset();
    }

    dio9.rise(dio9isr);
    txTimeout = 0;    // default tx-timeout off
}


void SX1265::hw_reset(void)
{
    nrst.output();
    nrst.write(0);
    ThisThread::sleep_for(2);
    nrst.write(1);
    nrst.input();
    /* measured 211ms to startup (busy-hi during chip startup) */
    ThisThread::sleep_for(200);
    while (busy)
        ThisThread::sleep_for(2);

    enable_default_irqs_();
}

const char *SX1265::cmdStatus_toString(uint8_t s)
{
    switch (s) {
        case CMD_FAIL: return "CMD_FAIL";
        case CMD_PERR: return "CMD_PERR";
        case CMD_OK: return "CMD_OK";
        case CMD_DAT: return "CMD_DAT";
        default: return NULL;
    }
}

uint32_t SX1265::service(void)
{
    uint32_t ret = 0;
    uint8_t buf[4];
    uint8_t irqbuf[4];

    inService = true;
    if (dio9) {
        bool try_rx = false;
        irq_t irq;
        stat_t stat;
        stat.word = xfer(OPCODE_GET_STATUS, 4, 4, irqbuf);
        if (stat.bits.rfu) {
            inService = false;
            return -1;
        }
        irq.dword = from_big_endian32(irqbuf);
        ret = irq.dword;
        if (stat.bits.rfu) {
            ret = -1;
            goto done;
        }
        if (irq.bits.TxDone) {
            chipMode = CHIPMODE_NONE;
            if (chipModeChange)
                chipModeChange.call();  // might change to Rx
            if (txDone)
                txDone.call();
        }
        if (irq.bits.RxDone) {
            uint8_t len, offset;
            float rssi, snr;
            int8_t s;

            stat.word = xfer(OPCODE_GET_RX_BUFFER_STATUS, 0, 0, NULL);
            stat.word = xfer(0x0000, 0, 2, buf);
            rx_buf_offset = buf[1];
            len = buf[0];
            offset = buf[1];
            
            buf[0] = offset;
            buf[1] = len;
            stat.word = xfer(OPCODE_READ_BUFFER8, 2, 0, buf);
            stat.word = xfer(0x0000, 0, len, rx_buf);
            stat.word = xfer(OPCODE_GET_PKT_STATUS, 0, 0, NULL);
            stat.word = xfer(0x0000, 0, 3, buf);
            rssi = buf[0] / -2.0;
            s = buf[1];
            snr = s / 4.0;

            rxDone(len, rssi, snr);
            stat.word = xfer(OPCODE_CLEAR_RX_BUFFER, 0, 0, NULL); // yyy
        }
        if (irq.bits.CadDone) {
            if (cadDone)
                cadDone(irq.bits.CadDetected);
        }
        if (irq.bits.CmdErr) {
            err_opcode = prev_opcode; // culprit opcode
        }
        if (irq.bits.Error) {
            stat.word = xfer(OPCODE_GET_ERRORS, 0, 0, NULL);
            stat.word = xfer(0x0000, 0, 2, buf);
            if (stat.bits.cmdStatus == CMD_DAT) {
                errorStat.word = buf[0];
                errorStat.word <<= 8;
                errorStat.word |= buf[1];
                if (errorStat.bits.hf_xosc_start_) {
                    buf[0] = tcxoVolts;
                    to_big_endian32(tcxoStartDelay, buf+1);
                    xfer(OPCODE_SET_TCXO_MODE, 5, 0, buf);
                    buf[0] = 0;
                    xfer(OPCODE_CALIBRATE, 1, 0, buf);

                    if (chipMode == CHIPMODE_RX) {
                        /* RX start failed due to HF xosc being a tcxo */
                        try_rx = true;
                    }
                }
                /* ? OPCODE_CLEAR_ERRORS ? */
            }
        }
        if (irq.bits.Timeout) {
            if (chipMode != CHIPMODE_NONE) {
                if (timeout)
                    timeout(chipMode == CHIPMODE_TX);
            }
            chipMode = CHIPMODE_NONE;
            if (chipModeChange)
                chipModeChange.call();
        }

        stat.word = xfer(OPCODE_CLEAR_IRQ, 4, 0, irqbuf);

        if (try_rx) {
            /* RX wasnt started because xosc wasnt started */
            xfer(OPCODE_SET_RX, 3, 0, rxArgs);
        }
    } // ..if (dio9)

done:
    inService = false;
    return ret;
}

uint16_t SX1265::xfer(uint16_t opcode, uint16_t wlen, uint16_t rlen, uint8_t* ptr)
{
    const uint8_t* stopPtr;
    const uint8_t* wstop;
    const uint8_t* rstop;
    const uint8_t nop = 0;
    uint16_t oc;
    stat_t ret;
    unsigned f;

    if (opcode == OPCODE_SET_RX) {
        const uint8_t* _ptr = ptr;
        rxArgs[0] = *_ptr++;
        rxArgs[1] = *_ptr++;
        rxArgs[2] = *_ptr++;
    }

    if (sleeping) {
        nss = 0;
        while (busy)
            ;
        sleeping = false;
    } else {
        if (busy) {
            int startAt = t.read_ms();
            while (busy) {
                if (t.read_ms() - startAt > 50) {
                    ret.bits.rfu = 15;
                    return ret.word;
                }
            }
        }
        nss = 0;
    }

    prev_opcode = this_opcode;
    this_opcode = opcode;

    oc = opcode;
    f = oc >> 8;
    ret.word = spi.write(f);
    ret.word <<= 8;
    if (opcode != 0) {
        /* two byte command sent */
        ret.word |= spi.write(oc & 0xff);
    } /* else: response */
    else
        ret.word |= 0xff;   // put impossible value for stat2, indicating only stat1 returned

    wstop = ptr + wlen;
    rstop = ptr + rlen;
    if (rlen > wlen)
        stopPtr = rstop;
    else
        stopPtr = wstop;

    for (; ptr < stopPtr; ptr++) {
        if (ptr < wstop && ptr < rstop)
            *ptr = spi.write(*ptr);
        else if (ptr < wstop)
            spi.write(*ptr);
        else
            *ptr = spi.write(nop);    // n >= write length: send NOP
    }

    nss = 1;

    if (opcode == OPCODE_SET_SLEEP ||
        opcode == OPCODE_SET_TX ||
        opcode == OPCODE_SET_RX ||
        opcode == OPCODE_SET_STANDBY ||
        opcode == OPCODE_SET_FS)
    {
        if (opcode == OPCODE_SET_TX)
            chipMode = CHIPMODE_TX;
        else if (opcode == OPCODE_SET_RX)
            chipMode = CHIPMODE_RX;
        else if (opcode == OPCODE_SET_SLEEP || opcode == OPCODE_SET_STANDBY || opcode == OPCODE_SET_FS) {
            if (opcode == OPCODE_SET_SLEEP)
                sleeping = true;
            chipMode = CHIPMODE_NONE;
        }

        if (chipModeChange)
            chipModeChange.call();
    }

    if (!inService && ret.bits.intActive)
        service();

    return ret.word;
}

uint32_t SX1265::from_big_endian32(const uint8_t *in)
{
    uint32_t ret;
    ret = *in++;
    ret <<= 8;
    ret |= *in++;
    ret <<= 8;
    ret |= *in++;
    ret <<= 8;
    ret |= *in;
    return ret;
}

void SX1265::to_big_endian16(uint16_t in, uint8_t *out)
{
    out[1] = in & 0xff;
    in >>= 8;
    out[0] = in & 0xff;
}

void SX1265::to_big_endian24(uint32_t in, uint8_t *out)
{
    out[2] = in & 0xff;
    in >>= 8;
    out[1] = in & 0xff;
    in >>= 8;
    out[0] = in & 0xff;
}

void SX1265::to_big_endian32(uint32_t in, uint8_t *out)
{
    out[3] = in & 0xff;
    in >>= 8;
    out[2] = in & 0xff;
    in >>= 8;
    out[1] = in & 0xff;
    in >>= 8;
    out[0] = in & 0xff;
}

int SX1265::memRegRead(uint32_t addr, uint16_t len_dwords, uint32_t *dest)
{
    uint8_t buf[5];
    stat_t stat;
    while (len_dwords > 0) {
        unsigned this_len_dwords = len_dwords;
        if (this_len_dwords > 64)
            this_len_dwords = 64;
        buf[4] = this_len_dwords;
        to_big_endian32(addr, buf);
        stat.word = xfer(OPCODE_READREGMEM32, 5, 5, buf);
        if (stat.bits.rfu) {
            hw_reset();
            return -1;
        }
        unsigned n, len_bytes = this_len_dwords * 4;
        stat.word = xfer(0x0000, 0, len_bytes, (uint8_t*)dest);
        if (stat.bits.rfu) {
            hw_reset();
            return -1;
        } else {
            if (stat.bits.cmdStatus != CMD_DAT) {
                return -1;
            }
            for (n = 0; n < this_len_dwords; n++) {
                *dest = from_big_endian32((uint8_t*)dest);
                dest++;
            }
        }

        addr += len_bytes;
        len_dwords -= this_len_dwords;
    } // ..while (len_dwords > 0)

    return 0;
} // ..memRegRead()

void SX1265::enable_default_irqs_()
{
    /* DIO9 is hi-z when no irqs enabled (floats high) */
    uint8_t buf[4];
    to_big_endian32(default_irqs, buf);
    xfer(OPCODE_SET_DIOIRQPARAMS, 4, 4, buf);
}

float SX1265::getMHz()
{
    uint32_t frf;
    memRegRead(REG_ADDR_RFFREQ, 1, &frf);
    return frf / 1048576.0;
}

uint8_t SX1265::setMHz(float MHz)
{
    uint8_t buf[4];
    to_big_endian32(MHz * 1000000, buf);
    xfer(OPCODE_SET_RF_FREQ_HZ, 4, 0, buf);
    return 0;
}

void SX1265::setPacketType(uint8_t pt)
{
    xfer(OPCODE_SET_PACKET_TYPE, 1, 0, &pt);
}

uint8_t SX1265::getPacketType()
{
    uint8_t buf;
    stat_t stat;
    xfer(OPCODE_GET_PACKET_TYPE, 0, 0, NULL);
    stat.word = xfer(0x0000, 0, 1, &buf);
    if (stat.bits.cmdStatus == CMD_DAT)
        return buf;
    else
        return 0;
}

void SX1265::start_tx(uint8_t pktLen)
{
    uint8_t buf[3];
    xfer(OPCODE_WRITE_BUFFER8, pktLen, 0, tx_buf);
    to_big_endian24(txTimeout, buf);
    xfer(OPCODE_SET_TX, 3, 0, buf);
}

void SX1265::GetPaConfig(uint8_t *out)
{
    txParamsA_t tpa;
    txParamsB_t tpb;
    /* PaSel also in 0x00f30088 */
    memRegRead(REG_ADDR_TX_PARAMS_A, 1, &tpa.dword);
    memRegRead(REG_ADDR_TX_PARAMS_B, 1, &tpb.dword);
    out[0] = tpb.bits.PaSel;
    out[1] = tpa.bits.RegPASupply;
    out[2] = tpb.bits.PaDutyCycle;
    out[3] = tpb.bits.PaHPSel;
}

void SX1265::GetLoRaModulationParameters(uint8_t *out)
{
    loraConfig0_t cfg0;
    memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);

    out[0] = cfg0.bits.modem_sf;
    out[1] = cfg0.bits.modem_bw;
    out[2] = cfg0.bits.coding_rate;
    out[3] = cfg0.bits.ppm_offset;
}

void SX1265::GetGfskModulationParameters(uint8_t *out)
{
    uint32_t u32;
    gfskBW_t bw_reg;
    gfskConfig0_t cfg0;
    uint8_t bwf;
    unsigned hz;

    memRegRead(REG_ADDR_GFSK_BITRATE, 1, &u32);
    hz = GFSK_BITRATE_NUMERATOR / u32;
    to_big_endian32(hz, out);

    memRegRead(REG_ADDR_GFSK_CFG0, 1, &cfg0.dword);
    switch (cfg0.bits.bt) {
        case 0: /* off */ out[4] = GFSK_BT_OFF; break;
        case 1: /* 0.3 */ out[4] = GFSK_BT_0_3; break;
        case 2: /* 0.5 */ out[4] = GFSK_BT_0_5; break;
        case 3: /* 0.7 */ out[4] = GFSK_BT_0_7; break;
        case 4: /* 1.0 */ out[4] = GFSK_BT_1_0; break;
    }

    memRegRead(REG_ADDR_GFSK_BWF, 1, &bw_reg.dword);
    bwf = bw_reg.bits.bwf_hi;
    bwf <<= 3;
    bwf |= bw_reg.bits.bwf_lo;
    out[5] = bwf;

    memRegRead(REG_ADDR_GFSK_FDEV, 1, &u32);
    hz = (unsigned) ((u32 * FREQ_STEP) - 0.5);
    to_big_endian32(hz, out+6);
}

void SX1265::GetLoRaPacketParameters(uint8_t *out)
{
    loraConfig0_t cfg0;
    loraConfigA_t cfgA;
    loraConfigC_t cfgc;
    unsigned pl;

    memRegRead(REG_ADDR_LORA_CONFIGC, 1, &cfgc.dword);
    pl = cfgc.bits.preamble_length;
    out[1] = pl & 0xff;
    pl >>= 8;
    out[0] = pl;

    memRegRead(REG_ADDR_LORA_CONFIG0, 1, &cfg0.dword);
    out[2] = cfg0.bits.implicit_header;
    out[3] = cfg0.bits.payload_length;
    out[4] = cfg0.bits.crc_on;

    memRegRead(REG_ADDR_LORA_CONFIGA, 1, &cfgA.dword);
    out[5] = cfgA.bits.invertIQ;
}

void SX1265::GetGfskPacketParameters(uint8_t *out)
{
    gfskConfig1_t cfg1;
    gfskConfig2_t cfg2;
    gfskConfig3_t cfg3;
    gfskConfig4_t cfg4;
    gfskConfig5_t cfg5;
    uint32_t u32;

    memRegRead(REG_ADDR_GFSK_CFG1, 1, &cfg1.dword);

    u32 = cfg1.bits.preamble_length;
    out[1] = u32;
    u32 >>= 8;
    out[0] = u32;

    memRegRead(REG_ADDR_GFSK_CFG1, 1, &cfg1.dword);
    if (cfg1.bits.preamble_det_enable)
        out[2] = cfg1.bits.preamble_det_len;
    else
        out[2] = 0;

    memRegRead(REG_ADDR_GFSK_CFG2, 1, &cfg2.dword);
    out[3] = cfg2.bits.sync_word_length;

    memRegRead(REG_ADDR_GFSK_PAYLOAD_LENGTH_B, 1, &cfg4.dword);
    out[4] = cfg4.bits.addr_comp;

    memRegRead(REG_ADDR_GFSK_CFG3, 1, &cfg3.dword);
    out[5] = cfg3.bits.variable_length;

    memRegRead(REG_ADDR_GFSK_PAYLOAD_LENGTH_A, 1, &u32);
    out[6] = u32 & 0xff;

    memRegRead(REG_ADDR_GFSK_CFG5, 1, &cfg5.dword);

    if (cfg5.bits.crc_off)
        out[7] |= 1;
    else
        out[7] &= ~1;

    if (cfg5.bits.crc_size)
        out[7] |= 2;
    else
        out[7] &= ~2;

    if (cfg5.bits.crc_invert)
        out[7] |= 4;
    else
        out[7] &= ~4;

    out[8] = cfg5.bits.whitening_enable;
}