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.
Diff: lr1110.cpp
- Revision:
- 0:987d9022c152
- Child:
- 1:29294cae9f9a
diff -r 000000000000 -r 987d9022c152 lr1110.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lr1110.cpp Tue May 19 15:30:29 2020 -0700 @@ -0,0 +1,508 @@ +#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 (!busy && 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 (!busy && 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; +} +