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;
}