Newer
Older
librfm95 / librfm95.c
/*
 * File:   librfm95.c
 * Author: torsten.roemer@luniks.net
 *
 * Created on 28. Januar 2025, 19:57
 */

#include "librfm95.h"
#include "utils.h"

/* FSK 'Timeout' */
static volatile bool rxTimeout = false;
/* FSK 'PacketSent' */
static volatile bool txDone = false;
/* FSK 'PayloadReady' */
static volatile bool rxDone = false;

/* Current mode: LoRa or FSK */
static bool lora = false;

/**
 * Writes the given value to the given register.
 *
 * @param reg
 * @param value
 */
static void regWrite(uint8_t reg, uint8_t value) {
    _rfmSel();
    _rfmTx(reg | 0x80);
    _rfmTx(value);
    _rfmDes();
}

/**
 * Reads and returns the value of the given register.
 *
 * @param reg
 * @return value
 */
static uint8_t regRead(uint8_t reg) {
    _rfmSel();
    _rfmTx(reg & 0x7f);
    uint8_t value = _rfmTx(0x00);
    _rfmDes();

    return value;
}

/**
 * Sets the module to the given operating mode.
 */
static void setMode(uint8_t mode) {
    regWrite(RFM_OP_MODE, (regRead(RFM_OP_MODE) & ~RFM_MASK_MODE) | (mode & RFM_MASK_MODE));
}

/**
 * Enables or disables timeouts in FSK mode.
 *
 * @param enable
 */
static void timeoutEnableFSK(bool enable) {
    // get "Timeout" on DIO4
    regWrite(RFM_DIO_MAP2, (regRead(RFM_DIO_MAP2) | 0x80) & ~0x40);
    rxTimeout = false;
    if (enable) {
        // TODO calculate - seems to be about 50, 75, 100ms
        regWrite(RFM_FSK_RX_TO_RSSI, 0x1f);
        regWrite(RFM_FSK_RX_TO_PREA, 0x2f);
        regWrite(RFM_FSK_RX_TO_SYNC, 0x3f);
    } else {
        regWrite(RFM_FSK_RX_TO_RSSI, 0x00);
        regWrite(RFM_FSK_RX_TO_PREA, 0x00);
        regWrite(RFM_FSK_RX_TO_SYNC, 0x00);
    }
}

static bool initFSK(uint8_t node, uint8_t cast) {
    // bit rate 9.6 kBit/s
    // regWrite(BITRATE_MSB, 0x0d);
    // regWrite(BITRATE_LSB, 0x05);

    // frequency deviation 10 kHz (default 5 kHz)
    regWrite(RFM_FSK_FDEV_MSB, 0x00);
    regWrite(RFM_FSK_FDEV_LSB, 0xa4);

    // modulation shaping Gaussian filter BT = 0.5, ramp 40 µs (POR)
    regWrite(RFM_FSK_PA_RAMP, 0x49);

    // AgcAutoOn, receiver trigger event PreambleDetect
    regWrite(RFM_FSK_RX_CONFIG, 0x0e);

    // no RSSI offset, 32 samples used
    regWrite(RFM_FSK_RSSI_CONFIG, 0x04);

    // 10 dB threshold for interferer detection
    regWrite(RFM_FSK_RSSI_COLLIS, 0x0a);

    // RSSI threshold
    regWrite(RFM_FSK_RSSI_THRESH, 0xff);

    // channel filter bandwith 20.8 kHz (default 10.4 kHz)
    regWrite(RFM_FSK_RX_BW, 0x14);

    // RX_BW during AFC 41.7 kHz (AFC not used)
    regWrite(RFM_FSK_AFC_BW, 0x13);

    // AFC auto on
    // regWrite(AFC_FEI, 0x00);

    // PreambleDetectorOn, PreambleDetectorSize 2 bytes,
    // PreambleDetectorTol 4 chips per bit
    regWrite(RFM_FSK_PREA_DETECT, 0xaa);

    // Preamble size 5 bytes
    regWrite(RFM_FSK_PREA_MSB, 0x00);
    regWrite(RFM_FSK_PREA_LSB, 0x05);

    // AutoRestartRxMode off, PreamblePolarity 0xaa, SyncOn,
    // SyncSize + 1 = 3 bytes
    regWrite(RFM_FSK_SYNC_CONFIG, 0x12);

    // just set all sync word values to some really creative value
    regWrite(RFM_FSK_SYNC_VAL1, 0x2f);
    regWrite(RFM_FSK_SYNC_VAL2, 0x30);
    regWrite(RFM_FSK_SYNC_VAL3, 0x31);
    regWrite(RFM_FSK_SYNC_VAL4, 0x32);
    regWrite(RFM_FSK_SYNC_VAL5, 0x33);
    regWrite(RFM_FSK_SYNC_VAL6, 0x34);
    regWrite(RFM_FSK_SYNC_VAL7, 0x35);
    regWrite(RFM_FSK_SYNC_VAL8, 0x36);

    // variable payload length, DcFree none, CrcOn, CrcAutoClearOff,
    // match broadcast or node address
    regWrite(RFM_FSK_PCK_CONFIG1, 0x9c);

    // Packet mode, ..., PayloadLength(10:8)
    regWrite(RFM_FSK_PCK_CONFIG2, 0x40);

    // PayloadLength(7:0)
    regWrite(RFM_FSK_PAYLOAD_LEN, 0x40);

    // node and broadcast address
    regWrite(RFM_FSK_NODE_ADDR, node);
    regWrite(RFM_FSK_CAST_ADDR, cast);

    // set TX start condition to "at least one byte in FIFO"
    regWrite(RFM_FSK_FIFO_THRESH, 0x8f);

    // AutoImageCalOn disabled, TempThreshold 10°C, TempMonitorOff 0
    regWrite(RFM_FSK_IMAGE_CAL, 0x02);

    // printString("Radio init done\r\n");

    return true;
}

static bool initLoRa(void) {
    // SPI interface address pointer in FIFO data buffer (POR 0x00)
    regWrite(RFM_LORA_FIFO_ADDR_PTR, 0x00);

    // write base address in FIFO data buffer for TX modulator (POR 0x80)
    regWrite(RFM_LORA_FIFO_TX_ADDR, 0x80);

    // read base address in FIFO data buffer for RX demodulator (POR 0x00)
    regWrite(RFM_LORA_FIFO_RX_ADDR, 0x00);

    // signal bandwidth 41.7 kHz, error coding rate 4/5, explicit header mode
    regWrite(RFM_LORA_MODEM_CONFIG1, 0x52);

    // spreading factor 10, TX single packet mode, CRC enable, RX timeout MSB
    regWrite(RFM_LORA_MODEM_CONFIG2, 0xa4);

    // RX (preamble detection) timeout LSB
    // (symbol time 24.58 ms * 0x08 = 196.64 ms)
    regWrite(RFM_LORA_SYMB_TIMEO_LSB, 0x08);

    // low data rate optimize, static node, AGC auto off
    regWrite(RFM_LORA_MODEM_CONFIG3, 0x08);

    // preamble length MSB
    regWrite(RFM_LORA_PREA_LEN_MSB, 0x00);

    // preamble length LSB
    regWrite(RFM_LORA_PREA_LEN_LSB, 0x08);

    // payload length in bytes (>0 only for implicit header mode)
    // regWrite(RFM_LORA_PAYLD_LEN, 0x01);

    // max payload length (CRC error if exceeded)
    regWrite(RFM_LORA_PAYLD_MAX_LEN, RFM_LORA_MSG_SIZE);

    // frequency hopping disabled
    regWrite(RFM_LORA_HOP_PERIOD, 0x00);

    return true;
}

bool rfmInit(uint64_t freq, uint8_t node, uint8_t cast, bool _lora) {
    lora = _lora;

    // wait a bit after power on
    _rfmDelay5();
    _rfmDelay5();

    // pull reset high to turn on the module
    _rfmOn();
    _rfmDelay5();

    uint8_t version = regRead(RFM_VERSION);
    // printString("Version: ");
    // printHex(version);
    if (version == 0x00) {
        return false;
    }

    // set the carrier frequency
    uint32_t frf = freq * 1000000UL / RFM_F_STEP;
    regWrite(RFM_FRF_MSB, frf >> 16);
    regWrite(RFM_FRF_MID, frf >> 8);
    regWrite(RFM_FRF_LSB, frf >> 0);

    // PA level +17 dBm with PA_BOOST pin (Pmax default/not relevant)
    regWrite(RFM_PA_CONFIG, 0xff);

    // LNA highest gain, boost on, 150% LNA current
    regWrite(RFM_LNA, 0x23);

    if (lora) {
        // go to sleep before switching to LoRa mode
        setMode(RFM_MODE_SLEEP);
        _rfmDelay5();

        // LoRa mode, high frequency mode, sleep mode
        regWrite(RFM_OP_MODE, 0x80);

        return initLoRa();
    } else {
        // FSK mode, FSK modulation, high frequency mode, sleep mode
        regWrite(RFM_OP_MODE, 0x00);

        return initFSK(node, cast);
    }
}

void rfmIrq(void) {
    if (lora) {
        uint8_t irqFlags = regRead(RFM_LORA_IRQ_FLAGS);

        if (irqFlags & (1 << 7)) rxTimeout = true;
        if (irqFlags & (1 << 3)) txDone = true;
        if (irqFlags & (1 << 6)) rxDone = true;
    } else {
        uint8_t irqFlags1 = regRead(RFM_FSK_IRQ_FLAGS1);
        uint8_t irqFlags2 = regRead(RFM_FSK_IRQ_FLAGS2);

        if (irqFlags1 & (1 << 2)) rxTimeout = true;
        if (irqFlags2 & (1 << 3)) txDone = true;
        if (irqFlags2 & (1 << 2)) rxDone = true;
    }
}

void rfmTimeout(void) {
    if (!lora) {
        // workaround for timeout interrupt sometimes not occurring in FSK mode
        // https://electronics.stackexchange.com/q/743099/65699
        rxTimeout = true;
    }
}

void rfmSleep(void) {
    _rfmDelay5();
    setMode(RFM_MODE_SLEEP);
}

void rfmWake(void) {
    setMode(RFM_MODE_STDBY);
    // should better wait for ModeReady irq?
    _rfmDelay5();
}

void rfmSetNodeAddress(uint8_t address) {
    if (!lora) {
        regWrite(RFM_FSK_NODE_ADDR, address);
    }
}

void rfmSetOutputPower(int8_t dBm) {
    uint8_t pa = 0xc0; // +2 dBm with PA_BOOST
    // adjust power from 2 to +17 dBm
    pa |= (min(max(dBm - RFM_PA_OFF, RFM_PA_MIN), RFM_PA_MAX)) & 0x0f;
    regWrite(RFM_PA_CONFIG, pa);
}

int8_t rfmGetOutputPower(void) {
    return (regRead(RFM_PA_CONFIG) & 0x0f) + RFM_PA_OFF;
}

void rfmStartReceive(bool timeout) {
    timeoutEnableFSK(timeout);

    // get "PayloadReady" on DIO0
    regWrite(RFM_DIO_MAP1, regRead(RFM_DIO_MAP1) & ~0xc0);
    rxDone = false;

    setMode(RFM_MODE_RX);
}

RxFlags rfmPayloadReady(void) {
    RxFlags flags = {.ready = false, .rssi = 255, .crc = false};
    if (rxDone) {
        flags.ready = true;
        flags.rssi = divRoundNearest(regRead(RFM_FSK_RSSI_VALUE), 2);
        flags.crc = regRead(RFM_FSK_IRQ_FLAGS2) & (1 << 1);
        setMode(RFM_MODE_STDBY);
    }

    return flags;
}

size_t rfmReadPayload(uint8_t *payload, size_t size) {
    size_t len = regRead(RFM_FIFO);
    size = min(len, size);

    // TODO assume and ignore address for now (already filtered anyway)
    regRead(RFM_FIFO);

    _rfmSel();
    _rfmTx(RFM_FIFO);
    for (size_t i = 0; i < size; i++) {
        payload[i] = _rfmTx(RFM_FIFO);
    }
    _rfmDes();

    return len;
}

size_t rfmReceivePayload(uint8_t *payload, size_t size, bool enable) {
    rfmStartReceive(enable);

    // wait until "PayloadReady" or (forced) "Timeout"
    do {} while (!rxDone && !rxTimeout);

    setMode(RFM_MODE_STDBY);

    if (rxTimeout) {
        timeoutEnableFSK(false);

        return 0;
    }

    timeoutEnableFSK(false);

    return rfmReadPayload(payload, size);
}

size_t rfmTransmitPayload(uint8_t *payload, size_t size, uint8_t node) {
    size_t len = min(size, RFM_FSK_MSG_SIZE);

    _rfmSel();
    _rfmTx(RFM_FIFO | 0x80);
    _rfmTx(len + 1); // +1 for node address
    _rfmTx(node);
    for (size_t i = 0; i < len; i++) {
        _rfmTx(payload[i]);
    }
    _rfmDes();

    // get "PacketSent" on DIO0 (default)
    regWrite(RFM_DIO_MAP1, regRead(RFM_DIO_MAP1) & ~0xc0);
    txDone = false;

    setMode(RFM_MODE_TX);

    // wait until "PacketSent"
    do {} while (!txDone);

    setMode(RFM_MODE_STDBY);

    return len;
}

void rfmLoRaStartRx(void) {
    // clear "RxDone" and "PayloadCrcError" interrupt
    regWrite(RFM_LORA_IRQ_FLAGS, 0x40 | 0x20);
    rxDone = false;

    // get "RxDone" on DIO0
    regWrite(RFM_DIO_MAP1, regRead(RFM_DIO_MAP1) & ~0xc0);

    // set FIFO address pointer to configured RX base address
    regWrite(RFM_LORA_FIFO_ADDR_PTR, regRead(RFM_LORA_FIFO_RX_ADDR));

    // TODO already is in continuous RX mode most of the time
    setMode(RFM_MODE_RX);
}

RxFlags rfmLoRaRxDone(void) {
    RxFlags flags = {.ready = false, .rssi = 255, .crc = false};
    if (rxDone) {
        flags.ready = true;
        flags.rssi = 157 - regRead(RFM_LORA_PCK_RSSI);
        flags.crc = !(regRead(RFM_LORA_IRQ_FLAGS) & (1 << 5));
    }

    return flags;
}

size_t rfmLoRaRxRead(uint8_t *payload, size_t size) {
    // set FIFO address pointer to current RX address
    regWrite(RFM_LORA_FIFO_ADDR_PTR, regRead(RFM_LORA_FIFO_CURR_ADDR));

    size_t len = regRead(RFM_LORA_RX_BYTES_NB);
    size = min(len, size);

    _rfmSel();
    _rfmTx(RFM_FIFO);
    for (size_t i = 0; i < size; i++) {
        payload[i] = _rfmTx(RFM_FIFO);
    }
    _rfmDes();

    return len;
}

size_t rfmLoRaRx(uint8_t *payload, size_t size) {
    // clear "RxTimeout" and "RxDone" interrupt
    // regWrite(RFM_LORA_IRQ_FLAGS, 0xc0);

    // get "RxTimeout" on DIO1 and "RxDone" on DIO0
    regWrite(RFM_DIO_MAP1, regRead(RFM_DIO_MAP1) & ~0xf0);
    rxTimeout = false;
    rxDone = false;

    // set FIFO address pointer to configured TX base address
    regWrite(RFM_LORA_FIFO_ADDR_PTR, regRead(RFM_LORA_FIFO_RX_ADDR));

    setMode(RFM_MODE_RXSINGLE);

    // wait until "RxDone" or "RxTimeout"
    do {} while (!rxDone && !rxTimeout);

    if (rxTimeout) {
        return 0;
    }

    return rfmLoRaRxRead(payload, size);
}

size_t rfmLoRaTx(uint8_t *payload, size_t size) {
    size_t len = min(size, RFM_LORA_MSG_SIZE);

    // set FIFO address pointer to configured TX base address
    regWrite(RFM_LORA_FIFO_ADDR_PTR, regRead(RFM_LORA_FIFO_TX_ADDR));

    regWrite(RFM_LORA_PAYLD_LEN, len);

    _rfmSel();
    _rfmTx(RFM_FIFO | 0x80);
    for (size_t i = 0; i < len; i++) {
        _rfmTx(payload[i]);
    }
    _rfmDes();

    // clear "TxDone" interrupt
    regWrite(RFM_LORA_IRQ_FLAGS, 0x08);

    // get "TxDone" on DIO0
    regWrite(RFM_DIO_MAP1, (regRead(RFM_DIO_MAP1) & ~0x80) | 0x40);
    txDone = false;

    setMode(RFM_MODE_TX);

    // wait until "TxDone"
    do {} while (!txDone);

    return len;
}