// Copyright (c) Sandeep Mistry. All rights reserved. // Licensed under the MIT license. // Modifications and additions copyright 2018 by Mark Qvist // Obviously still under the MIT license. #include "LoRa.h" #if LIBRARY_TYPE == LIBRARY_C // We need sleep() to use instead of yield() #include // And we need to use the filesystem and IOCTLs instead of an SPI global #include #include #include // And to have memset #include // And we need to be able to report errors #include #include // And we need IO formatting functions for the C++-stream dumpRegisters() #include #endif #if MCU_VARIANT == MCU_ESP32 #include "soc/rtc_wdt.h" #define ISR_VECT IRAM_ATTR #else #define ISR_VECT #endif // Registers #define REG_FIFO 0x00 #define REG_OP_MODE 0x01 #define REG_FRF_MSB 0x06 #define REG_FRF_MID 0x07 #define REG_FRF_LSB 0x08 #define REG_PA_CONFIG 0x09 #define REG_PA_RAMP 0x0a #define REG_OCP 0x0b #define REG_LNA 0x0c #define REG_FIFO_ADDR_PTR 0x0d #define REG_FIFO_TX_BASE_ADDR 0x0e #define REG_FIFO_RX_BASE_ADDR 0x0f #define REG_FIFO_RX_CURRENT_ADDR 0x10 #define REG_IRQ_FLAGS_MASK 0x11 #define REG_IRQ_FLAGS 0x12 #define REG_RX_NB_BYTES 0x13 #define REG_MODEM_STAT 0x18 #define REG_PKT_SNR_VALUE 0x19 #define REG_PKT_RSSI_VALUE 0x1a #define REG_MODEM_CONFIG_1 0x1d #define REG_MODEM_CONFIG_2 0x1e #define REG_SYMB_TIMEOUT_LSB 0x1f #define REG_PREAMBLE_MSB 0x20 #define REG_PREAMBLE_LSB 0x21 #define REG_PAYLOAD_LENGTH 0x22 #define REG_PAYLOAD_MAX_LENGTH 0x23 #define REG_HOP_PERIOD 0x24 #define REG_MODEM_CONFIG_3 0x26 #define REG_PPM_CORRECTION 0x27 #define REG_FREQ_ERROR_MSB 0x28 #define REG_FREQ_ERROR_MID 0x29 #define REG_FREQ_ERROR_LSB 0x2a #define REG_RSSI_WIDEBAND 0x2c #define REG_IF_FREQ_2 0x2f #define REG_IF_FREQ_1 0x30 #define REG_DETECTION_OPTIMIZE 0x31 #define REG_INVERT_IQ 0x33 #define REG_HIGH_BW_OPTIMIZE_1 0x36 #define REG_DETECTION_THRESHOLD 0x37 #define REG_SYNC_WORD 0x39 #define REG_HIGH_BW_OPTIMIZE_2 0x3a #define REG_INVERT_IQ_2 0x3b #define REG_DIO_MAPPING_1 0x40 #define REG_VERSION 0x42 #define REG_TXCO 0x4B #define REG_PA_DAC 0x4D // These registers have different values in high and low frequency modes (flag 0x08 in mode) // We always stay in high frequency mode (flag is 0) #define REG_AGC_REF 0x61 #define REG_AGC_THRESHOLD_1 0x62 #define REG_AGC_THRESHOLD_2 0x63 #define REG_AGC_THRESHOLD_3 0x64 #define REG_PLL 0x70 // Modes #define MODE_LONG_RANGE_MODE 0x80 #define MODE_SLEEP 0x00 #define MODE_STDBY 0x01 #define MODE_TX 0x03 #define MODE_RX_CONTINUOUS 0x05 #define MODE_RX_SINGLE 0x06 // PA config #define PA_BOOST 0x80 // IRQ masks #define IRQ_TX_DONE_MASK 0x08 #define IRQ_PAYLOAD_CRC_ERROR_MASK 0x20 #define IRQ_RX_DONE_MASK 0x40 #define MAX_PKT_LENGTH 255 LoRaClass::LoRaClass() : _spiSettings(8E6, MSBFIRST, SPI_MODE0), _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN), _frequency(0), _packetIndex(0), _implicitHeaderMode(0), _onReceive(NULL), _spiBegun(false) { #if LIBRARY_TYPE == LIBRARY_ARDUINO // overide Stream timeout value setTimeout(0); #elif LIBRARY_TYPE == LIBRARY_C _fd = 0; #endif } int LoRaClass::begin(long frequency) { #if LIBRARY_TYPE == LIBRARY_ARDUINO // setup pins pinMode(_ss, OUTPUT); // set SS high digitalWrite(_ss, HIGH); #endif #if LIBRARY_TYPE == LIBRARY_ARDUINO // start SPI SPI.begin(); #elif LIBRARY_TYPE == LIBRARY_C const char* spi_filename = "/dev/spidev0.0"; // We need to be re-entrant for restart if (_fd <= 0) { std::cerr << "Opening SPI device " << spi_filename << std::endl; _fd = open(spi_filename, O_RDWR); if (_fd <= 0) { perror("could not open SPI device"); exit(1); } } else { std::cerr << "Skipping LoRa SPI reinitialization" << std::endl; } #endif _spiBegun = true; if (!resetModem()) { return 0; } // set frequency setFrequency(frequency); // set base addresses writeRegister(REG_FIFO_TX_BASE_ADDR, 0); writeRegister(REG_FIFO_RX_BASE_ADDR, 0); // set LNA boost writeRegister(REG_LNA, readRegister(REG_LNA) | 0x03); // set auto AGC writeRegister(REG_MODEM_CONFIG_3, 0x04); // set output power to 2 dBm setTxPower(2); // put in standby mode idle(); return 1; } void LoRaClass::end() { // We need to be safe to call when the main loop is shutting down because // it's in a bad state, even if we ourselves haven't been begun yet. We can't // safely talk to the modem if the SPI link isn't begun, though. if (_spiBegun) { // put in sleep mode this->sleep(); } #if LIBRARY_TYPE == LIBRARY_ARDUINO // stop SPI SPI.end(); #elif LIBRARY_TYPE == LIBRARY_C // Don't do anything. We need to keep things open for restart. #endif _spiBegun = false; } int LoRaClass::beginPacket(int implicitHeader) { // put in standby mode idle(); if (implicitHeader) { implicitHeaderMode(); } else { explicitHeaderMode(); } // reset FIFO address and paload length writeRegister(REG_FIFO_ADDR_PTR, 0); writeRegister(REG_PAYLOAD_LENGTH, 0); return 1; } int LoRaClass::endPacket() { // put in TX mode writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX); // wait for TX done while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0) { #if LIBRARY_TYPE == LIBRARY_ARDUINO yield(); #elif LIBRARY_TYPE == LIBRARY_C ::sleep(0); #endif } // clear IRQ's writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK); return 1; } int LoRaClass::parsePacket(int size) { int packetLength = 0; int irqFlags = readRegister(REG_IRQ_FLAGS); if (size > 0) { implicitHeaderMode(); writeRegister(REG_PAYLOAD_LENGTH, size & 0xff); } else { explicitHeaderMode(); } // clear IRQ's writeRegister(REG_IRQ_FLAGS, irqFlags); if ((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) { // received a packet _packetIndex = 0; // read packet length if (_implicitHeaderMode) { packetLength = readRegister(REG_PAYLOAD_LENGTH); } else { packetLength = readRegister(REG_RX_NB_BYTES); } // set FIFO address to current RX address writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR)); // put in standby mode idle(); } else if (readRegister(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)) { // not currently in RX mode // reset FIFO address writeRegister(REG_FIFO_ADDR_PTR, 0); // put in single RX mode writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE); } return packetLength; } uint8_t LoRaClass::modemStatus() { return readRegister(REG_MODEM_STAT); } uint8_t LoRaClass::packetRssiRaw() { uint8_t pkt_rssi_value = readRegister(REG_PKT_RSSI_VALUE); return pkt_rssi_value; } int ISR_VECT LoRaClass::packetRssi() { int pkt_rssi = (int)readRegister(REG_PKT_RSSI_VALUE) - RSSI_OFFSET; int pkt_snr = packetSnr(); if (_frequency < 820E6) pkt_rssi -= 7; if (pkt_snr < 0) { pkt_rssi += pkt_snr; } else { // Slope correction is (16/15)*pkt_rssi, // this estimation looses one floating point // operation, and should be precise enough. pkt_rssi = (int)(1.066 * pkt_rssi); } return pkt_rssi; } uint8_t ISR_VECT LoRaClass::packetSnrRaw() { return readRegister(REG_PKT_SNR_VALUE); } float ISR_VECT LoRaClass::packetSnr() { return ((int8_t)readRegister(REG_PKT_SNR_VALUE)) * 0.25; } long LoRaClass::packetFrequencyError() { int32_t freqError = 0; freqError = static_cast(readRegister(REG_FREQ_ERROR_MSB) & 0b111); freqError <<= 8L; freqError += static_cast(readRegister(REG_FREQ_ERROR_MID)); freqError <<= 8L; freqError += static_cast(readRegister(REG_FREQ_ERROR_LSB)); if (readRegister(REG_FREQ_ERROR_MSB) & 0b1000) { // Sign bit is on freqError -= 524288; // B1000'0000'0000'0000'0000 } const float fXtal = 32E6; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14) const float fError = ((static_cast(freqError) * (1L << 24)) / fXtal) * (getSignalBandwidth() / 500000.0f); // p. 37 return static_cast(fError); } size_t LoRaClass::write(uint8_t byte) { return write(&byte, sizeof(byte)); } size_t LoRaClass::write(const uint8_t *buffer, size_t size) { int currentLength = readRegister(REG_PAYLOAD_LENGTH); // check size if ((currentLength + size) > MAX_PKT_LENGTH) { size = MAX_PKT_LENGTH - currentLength; } // write data for (size_t i = 0; i < size; i++) { writeRegister(REG_FIFO, buffer[i]); } // update length writeRegister(REG_PAYLOAD_LENGTH, currentLength + size); return size; } int ISR_VECT LoRaClass::available() { return (readRegister(REG_RX_NB_BYTES) - _packetIndex); } int ISR_VECT LoRaClass::read() { if (!available()) { return -1; } _packetIndex++; return readRegister(REG_FIFO); } int LoRaClass::peek() { if (!available()) { return -1; } // store current FIFO address int currentAddress = readRegister(REG_FIFO_ADDR_PTR); // read uint8_t b = readRegister(REG_FIFO); // restore FIFO address writeRegister(REG_FIFO_ADDR_PTR, currentAddress); return b; } void LoRaClass::flush() { } void LoRaClass::pollReceive() { int irqFlags = readRegister(REG_IRQ_FLAGS); // clear IRQ's writeRegister(REG_IRQ_FLAGS, irqFlags); if ((irqFlags & IRQ_RX_DONE_MASK) && !(irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK)) { // received a packet handleRx(); } } void LoRaClass::onReceive(void(*callback)(int)) { _onReceive = callback; if (callback) { #if LIBRARY_TYPE == LIBRARY_ARDUINO pinMode(_dio0, INPUT); #endif writeRegister(REG_DIO_MAPPING_1, 0x00); #if MCU_VARIANT != MCU_LINUX && LIBRARY_TYPE == LIBRARY_ARDUINO #ifdef SPI_HAS_NOTUSINGINTERRUPT SPI.usingInterrupt(digitalPinToInterrupt(_dio0)); #endif attachInterrupt(digitalPinToInterrupt(_dio0), LoRaClass::onDio0Rise, RISING); #endif } else { #if MCU_VARIANT != MCU_LINUX && LIBRARY_TYPE == LIBRARY_ARDUINO detachInterrupt(digitalPinToInterrupt(_dio0)); #ifdef SPI_HAS_NOTUSINGINTERRUPT SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0)); #endif #endif } } void LoRaClass::receive(int size) { if (size > 0) { implicitHeaderMode(); writeRegister(REG_PAYLOAD_LENGTH, size & 0xff); } else { explicitHeaderMode(); } writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS); } void LoRaClass::idle() { writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY); } void LoRaClass::sleep() { writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP); } void LoRaClass::setTxPower(int level, int outputPin) { if (PA_OUTPUT_RFO_PIN == outputPin) { // RFO if (level < 0) { level = 0; } else if (level > 14) { level = 14; } writeRegister(REG_PA_CONFIG, 0x70 | level); } else { // PA BOOST if (level < 2) { level = 2; } else if (level > 17) { level = 17; } writeRegister(REG_PA_CONFIG, PA_BOOST | (level - 2)); } } void LoRaClass::setFrequency(long frequency) { _frequency = frequency; uint32_t frf = ((uint64_t)frequency << 19) / 32000000; writeRegister(REG_FRF_MSB, (uint8_t)(frf >> 16)); writeRegister(REG_FRF_MID, (uint8_t)(frf >> 8)); writeRegister(REG_FRF_LSB, (uint8_t)(frf >> 0)); } uint32_t LoRaClass::getFrequency() { uint8_t msb = readRegister(REG_FRF_MSB); uint8_t mid = readRegister(REG_FRF_MID); uint8_t lsb = readRegister(REG_FRF_LSB); uint32_t frf = ((uint32_t)msb << 16) | ((uint32_t)mid << 8) | (uint32_t)lsb; uint64_t frm = (uint64_t)frf*32000000; uint32_t frequency = (frm >> 19); return frequency; } void LoRaClass::setSpreadingFactor(int sf) { if (sf < 6) { sf = 6; } else if (sf > 12) { sf = 12; } if (sf == 6) { writeRegister(REG_DETECTION_OPTIMIZE, 0xc5); writeRegister(REG_DETECTION_THRESHOLD, 0x0c); } else { writeRegister(REG_DETECTION_OPTIMIZE, 0xc3); writeRegister(REG_DETECTION_THRESHOLD, 0x0a); } writeRegister(REG_MODEM_CONFIG_2, (readRegister(REG_MODEM_CONFIG_2) & 0x0f) | ((sf << 4) & 0xf0)); handleLowDataRate(); } long LoRaClass::getSignalBandwidth() { byte bw = (readRegister(REG_MODEM_CONFIG_1) >> 4); switch (bw) { case 0: return 7.8E3; case 1: return 10.4E3; case 2: return 15.6E3; case 3: return 20.8E3; case 4: return 31.25E3; case 5: return 41.7E3; case 6: return 62.5E3; case 7: return 125E3; case 8: return 250E3; case 9: return 500E3; } return 0; } void LoRaClass::handleLowDataRate(){ int sf = (readRegister(REG_MODEM_CONFIG_2) >> 4); if ( long( (1< 16) { // set auto AGC and LowDataRateOptimize writeRegister(REG_MODEM_CONFIG_3, (1<<3)|(1<<2)); } else { // set auto AGC writeRegister(REG_MODEM_CONFIG_3, (1<<2)); } } void LoRaClass::setSignalBandwidth(long sbw) { int bw; if (sbw <= 7.8E3) { bw = 0; } else if (sbw <= 10.4E3) { bw = 1; } else if (sbw <= 15.6E3) { bw = 2; } else if (sbw <= 20.8E3) { bw = 3; } else if (sbw <= 31.25E3) { bw = 4; } else if (sbw <= 41.7E3) { bw = 5; } else if (sbw <= 62.5E3) { bw = 6; } else if (sbw <= 125E3) { bw = 7; } else if (sbw <= 250E3) { bw = 8; } else /*if (sbw <= 250E3)*/ { bw = 9; } writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0x0f) | (bw << 4)); handleLowDataRate(); } void LoRaClass::setCodingRate4(int denominator) { if (denominator < 5) { denominator = 5; } else if (denominator > 8) { denominator = 8; } int cr = denominator - 4; writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0xf1) | (cr << 1)); } void LoRaClass::setPreambleLength(long length) { writeRegister(REG_PREAMBLE_MSB, (uint8_t)(length >> 8)); writeRegister(REG_PREAMBLE_LSB, (uint8_t)(length >> 0)); } void LoRaClass::setSyncWord(int sw) { writeRegister(REG_SYNC_WORD, sw); } void LoRaClass::enableCrc() { writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) | 0x04); } void LoRaClass::disableCrc() { writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) & 0xfb); } byte LoRaClass::random() { return readRegister(REG_RSSI_WIDEBAND); } void LoRaClass::setPins(int ss, int reset, int dio0) { _ss = ss; _reset = reset; _dio0 = dio0; } void LoRaClass::setSPIFrequency(uint32_t frequency) { _spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0); } #if LIBRARY_TYPE == LIBRARY_ARDUINO void LoRaClass::dumpRegisters(Stream& out) { for (int i = 0; i < 128; i++) { out.print("0x"); out.print(i, HEX); out.print(": 0x"); out.println(readRegister(i), HEX); } } #elif LIBRARY_TYPE == LIBRARY_C void LoRaClass::dumpRegisters(std::ostream& out) { for (int i = 0; i < 128; i++) { out << "0x" << std::hex << i << ": 0x" << std::hex << readRegister(i) << std::endl; } out << std::dec; } #endif bool LoRaClass::resetModem() { // Reset the modem to a known good default state and put it into sleep mode. // Returns false if the modem doesn't appear to be the right version. #if LIBRARY_TYPE == LIBRARY_ARDUINO if (_reset != -1) { pinMode(_reset, OUTPUT); // perform reset digitalWrite(_reset, LOW); delay(10); digitalWrite(_reset, HIGH); delay(10); } #endif // check version uint8_t version = readRegister(REG_VERSION); if (version != 0x12) { return false; } this->sleep(); #if LIBRARY_TYPE == LIBRARY_C byte CLEAN_STATE[] = { REG_PA_RAMP, 0x09, REG_FRF_MSB, 0x6c, REG_FRF_MID, 0x80, REG_FRF_LSB, 0x00, REG_PA_CONFIG, 0x4f, REG_PA_RAMP, 0x09, REG_OCP, 0x2b, REG_LNA, 0x20, REG_FIFO_ADDR_PTR, 0x00, REG_FIFO_TX_BASE_ADDR, 0x80, REG_FIFO_RX_BASE_ADDR, 0x00, REG_FIFO_RX_CURRENT_ADDR, 0x00, REG_IRQ_FLAGS_MASK, 0x00, REG_MODEM_CONFIG_1, 0x72, REG_MODEM_CONFIG_2, 0x70, REG_SYMB_TIMEOUT_LSB, 0x64, REG_PREAMBLE_MSB, 0x00, REG_PREAMBLE_LSB, 0x08, REG_PAYLOAD_LENGTH, 0x01, REG_PAYLOAD_MAX_LENGTH, 0xff, REG_HOP_PERIOD, 0x00, REG_MODEM_CONFIG_3, 0x04, REG_PPM_CORRECTION, 0x00, REG_DETECTION_OPTIMIZE, 0xc3, // Errata says this needs to be set before REG_IF_FREQ_1 and REG_IF_FREQ_2 REG_IF_FREQ_2, 0x45, // Datasheet says this defaults to 0x20, but dumping says 0x45. REG_IF_FREQ_1, 0x55, // Datasheet says this defaults to 0x00, but dumping says 0x55. REG_INVERT_IQ, 0x27, REG_HIGH_BW_OPTIMIZE_1, 0x03, REG_DETECTION_THRESHOLD, 0x0a, REG_SYNC_WORD, 0x12, REG_HIGH_BW_OPTIMIZE_2, 0x52, // Datasheet says this defaults to 0x20, but dumping says 0x52. REG_INVERT_IQ_2, 0x1d, REG_TXCO, 0x09, REG_PA_DAC, 0x84, // These are the high frequency mode (mode flag 0x08 is 0) values REG_AGC_REF, 0x1C, REG_AGC_THRESHOLD_1, 0x0e, REG_AGC_THRESHOLD_2, 0x5b, REG_AGC_THRESHOLD_3, 0xcc, REG_PLL, 0xd0, 0, 0 }; // Manually set important registers to default values because we can't // reset. for (int i = 0; CLEAN_STATE[i] != 0; i += 2) { writeRegister(CLEAN_STATE[i], CLEAN_STATE[i + 1]); } #endif return true; } void LoRaClass::explicitHeaderMode() { _implicitHeaderMode = 0; writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) & 0xfe); } void LoRaClass::implicitHeaderMode() { _implicitHeaderMode = 1; writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) | 0x01); } void ISR_VECT LoRaClass::handleDio0Rise() { int irqFlags = readRegister(REG_IRQ_FLAGS); // clear IRQ's writeRegister(REG_IRQ_FLAGS, irqFlags); if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) { // received a packet handleRx(); } } void ISR_VECT LoRaClass::handleRx() { // received a packet _packetIndex = 0; // read packet length int packetLength = _implicitHeaderMode ? readRegister(REG_PAYLOAD_LENGTH) : readRegister(REG_RX_NB_BYTES); // set FIFO address to current RX address writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR)); if (_onReceive) { _onReceive(packetLength); } // reset FIFO address writeRegister(REG_FIFO_ADDR_PTR, 0); } uint8_t ISR_VECT LoRaClass::readRegister(uint8_t address) { return singleTransfer(address & 0x7f, 0x00); } void LoRaClass::writeRegister(uint8_t address, uint8_t value) { singleTransfer(address | 0x80, value); } uint8_t ISR_VECT LoRaClass::singleTransfer(uint8_t address, uint8_t value) { uint8_t response; #if LIBRARY_TYPE == LIBRARY_ARDUINO // Select chip, send address, and send/read data, the Arduino way digitalWrite(_ss, LOW); SPI.beginTransaction(_spiSettings); SPI.transfer(address); response = SPI.transfer(value); SPI.endTransaction(); digitalWrite(_ss, HIGH); #elif LIBRARY_TYPE == LIBRARY_C // Select chip, send address, and send/read data, the Linux way // In Linux, chip select is automatically turned off outside of transactions. int status; if (_fd <= 0) { throw std::runtime_error("Accessing SPI device without begin()!"); } // Configure SPI speed and mode to match settings status = ioctl(_fd, SPI_IOC_WR_MODE, &_spiSettings.mode); if (status < 0) { perror("ioctl SPI_IOC_WR_MODE failed"); exit(1); } status = ioctl(_fd, SPI_IOC_WR_LSB_FIRST, &_spiSettings.bitness); if (status < 0) { perror("ioctl SPI_IOC_WR_LSB_FIRST failed"); exit(1); } status = ioctl(_fd, SPI_IOC_WR_MAX_SPEED_HZ, &_spiSettings.frequency); if (status < 0) { perror("ioctl SPI_IOC_WR_MAX_SPEED_HZ failed"); exit(1); } // We have two transfers: one send-only to send the address, and one // send/receive, to send the value and get the response. struct spi_ioc_transfer xfer[2]; memset(xfer, 0, sizeof xfer); xfer[0].tx_buf = (unsigned long) &address; xfer[0].len = 1; xfer[1].tx_buf = (unsigned long) &value; xfer[1].rx_buf = (unsigned long) &response; xfer[1].len = 1; // Do the transaction status = ioctl(_fd, SPI_IOC_MESSAGE(2), xfer); if (status < 0) { perror("ioctl SPI_IOC_MESSAGE failed"); exit(1); } #else #error "SPI transfer not implemented for library type" #endif return response; } void ISR_VECT LoRaClass::onDio0Rise() { LoRa.handleDio0Rise(); } LoRaClass LoRa;