diff --git a/Config.h b/Config.h index 07927f4..74ba3ca 100644 --- a/Config.h +++ b/Config.h @@ -14,6 +14,7 @@ // along with this program. If not, see . #include "ROM.h" +#include "Modem.h" #ifndef CONFIG_H #define CONFIG_H @@ -23,10 +24,12 @@ #define PLATFORM_AVR 0x90 #define PLATFORM_ESP32 0x80 + #define PLATFORM_NRF52 0x70 #define MCU_1284P 0x91 #define MCU_2560 0x92 #define MCU_ESP32 0x81 + #define MCU_NRF52 0x71 #define BOARD_RNODE 0x31 #define BOARD_HMBRW 0x32 @@ -39,6 +42,8 @@ #define BOARD_HELTEC32_V2 0x38 #define BOARD_RNODE_NG_20 0x40 #define BOARD_RNODE_NG_21 0x41 + #define BOARD_GENERIC_NRF52 0x50 + #define BOARD_RAK4630 0x51 #define MODE_HOST 0x11 #define MODE_TNC 0x12 @@ -61,7 +66,7 @@ #define M_FRQ_S 27388122 #define M_FRQ_R 27388061 bool console_active = false; - bool sx1276_installed = false; + bool modem_installed = false; #if defined(__AVR_ATmega1284P__) #define PLATFORM PLATFORM_AVR @@ -72,6 +77,9 @@ #elif defined(ESP32) #define PLATFORM PLATFORM_ESP32 #define MCU_VARIANT MCU_ESP32 + #elif defined(NRF52840_XXAA) + #define PLATFORM PLATFORM_NRF52 + #define MCU_VARIANT MCU_NRF52 #else #error "The firmware cannot be compiled for the selected MCU variant" #endif @@ -90,6 +98,7 @@ #define HAS_TCXO false #define HAS_PMU false #define HAS_NP false + #define HAS_EEPROM false #if MCU_VARIANT == MCU_1284P const int pin_cs = 4; @@ -100,6 +109,8 @@ #define BOARD_MODEL BOARD_RNODE + #define HAS_EEPROM true + #define CONFIG_UART_BUFFER_SIZE 6144 #define CONFIG_QUEUE_SIZE 6144 #define CONFIG_QUEUE_MAX_LENGTH 200 @@ -116,6 +127,8 @@ #define BOARD_MODEL BOARD_HMBRW + #define HAS_EEPROM true + #define CONFIG_UART_BUFFER_SIZE 768 #define CONFIG_QUEUE_SIZE 5120 #define CONFIG_QUEUE_MAX_LENGTH 24 @@ -131,6 +144,16 @@ // firmware, you can manually define model here. // // #define BOARD_MODEL BOARD_GENERIC_ESP32 + #define CONFIG_UART_BUFFER_SIZE 6144 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + + #define EEPROM_SIZE 1024 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + + #define GPS_BAUD_RATE 9600 + #define PIN_GPS_TX 12 + #define PIN_GPS_RX 34 #if BOARD_MODEL == BOARD_GENERIC_ESP32 const int pin_cs = 4; @@ -140,6 +163,7 @@ const int pin_led_tx = 32; #define HAS_BLUETOOTH true #define HAS_CONSOLE true + #define HAS_EEPROM true #elif BOARD_MODEL == BOARD_TBEAM const int pin_cs = 18; const int pin_reset = 23; @@ -152,6 +176,7 @@ #define HAS_BLUETOOTH true #define HAS_CONSOLE true #define HAS_SD false + #define HAS_EEPROM true #elif BOARD_MODEL == BOARD_HUZZAH32 const int pin_cs = 4; const int pin_reset = 36; @@ -261,24 +286,44 @@ const int pin_led_tx = 25; #endif #endif - #else - #error An unsupported board was selected. Cannot compile RNode firmware. - #endif + #endif + #elif PLATFORM == PLATFORM_NRF52 + #if BOARD_MODEL == BOARD_RAK4630 + #define HAS_EEPROM false + #define HAS_DISPLAY false // set for debugging + #define HAS_BLUETOOTH false + #define HAS_CONSOLE false + #define HAS_PMU false + #define HAS_NP false + #define HAS_SD false + #define HAS_TCXO true + #define HAS_RXEN_BUSY true + #define MODEM SX1262 + + #define CONFIG_UART_BUFFER_SIZE 6144 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + #define EEPROM_SIZE 4096 + #define EEPROM_OFFSET EEPROM_SIZE+0xED000-EEPROM_RESERVED - bool mw_radio_online = false; - - #define CONFIG_UART_BUFFER_SIZE 6144 - #define CONFIG_QUEUE_SIZE 6144 - #define CONFIG_QUEUE_MAX_LENGTH 200 - - #define EEPROM_SIZE 1024 - #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED - - #define GPS_BAUD_RATE 9600 - #define PIN_GPS_TX 12 - #define PIN_GPS_RX 34 + // following pins are for the sx1262 + const int pin_rxen = 37; + const int pin_reset = 38; + const int pin_cs = 42; + const int pin_sclk = 43; + const int pin_mosi = 44; + const int pin_miso = 45; + const int pin_busy = 46; + const int pin_dio = 47; + const int pin_led_rx = LED_BLUE; + const int pin_led_tx = LED_GREEN; + #endif + #else + #error An unsupported board was selected. Cannot compile RNode firmware. #endif + bool mw_radio_online = false; + #if BOARD_MODEL == BOARD_TBEAM #define I2C_SDA 21 #define I2C_SCL 22 @@ -287,6 +332,15 @@ #define eeprom_addr(a) (a+EEPROM_OFFSET) + #ifndef HAS_RXEN_BUSY + const int pin_rxen = -1; + const int pin_busy = -1; + #endif + + #if MODEM == SX1262 + SPIClass spiModem(NRF_SPIM2, pin_miso, pin_sclk, pin_mosi); + #endif + // MCU independent configuration parameters const long serial_baudrate = 115200; @@ -354,7 +408,7 @@ uint32_t stat_tx = 0; #define STATUS_INTERVAL_MS 3 - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 #define DCD_SAMPLES 2500 #define UTIL_UPDATE_INTERVAL_MS 1000 #define UTIL_UPDATE_INTERVAL (UTIL_UPDATE_INTERVAL_MS/STATUS_INTERVAL_MS) diff --git a/Device.h b/Device.h index f70ea55..5f5cbb4 100644 --- a/Device.h +++ b/Device.h @@ -14,13 +14,18 @@ // along with this program. If not, see . #include + +#if MCU_VARIANT == MCU_ESP32 #include "mbedtls/md.h" #include "esp_ota_ops.h" #include "esp_flash_partitions.h" #include "esp_partition.h" +#endif + // Forward declaration from Utilities.h void eeprom_update(int mapped_addr, uint8_t byte); +uint8_t eeprom_read(uint32_t addr); void hard_reset(void); const uint8_t dev_keys [] PROGMEM = { @@ -86,13 +91,21 @@ void device_save_signature() { void device_load_signature() { for (uint8_t i = 0; i < DEV_SIG_LEN; i++) { - dev_sig[i] = EEPROM.read(dev_sig_addr(i)); + #if HAS_EEPROM + dev_sig[i] = EEPROM.read(dev_sig_addr(i)); + #elif MCU_VARIANT == MCU_NRF52 + dev_sig[i] = eeprom_read(dev_sig_addr(i)); + #endif } } void device_load_firmware_hash() { for (uint8_t i = 0; i < DEV_HASH_LEN; i++) { - dev_firmware_hash_target[i] = EEPROM.read(dev_fwhash_addr(i)); + #if HAS_EEPROM + dev_firmware_hash_target[i] = EEPROM.read(dev_fwhash_addr(i)); + #elif MCU_VARIANT == MCU_NRF52 + dev_firmware_hash_target[i] = eeprom_read(dev_fwhash_addr(i)); + #endif } } @@ -103,6 +116,7 @@ void device_save_firmware_hash() { if (!fw_signature_validated) hard_reset(); } +#if MCU_VARIANT == MCU_ESP32 void device_validate_partitions() { device_load_firmware_hash(); esp_partition_t partition; @@ -122,11 +136,13 @@ void device_validate_partitions() { } } } +#endif bool device_firmware_ok() { return fw_signature_validated; } +#if MCU_VARIANT == MCU_ESP32 bool device_init() { if (bt_ready) { for (uint8_t i=0; i= start)) { - version = readRegister(REG_VERSION); - if (version == 0x12) { - break; + #if MODEM == SX1276 || MODEM == SX1278 + uint8_t version; + long start = millis(); + while (((millis() - start) < 2000) && (millis() >= start)) { + version = readRegister(REG_VERSION); + if (version == 0x12) { + break; + } + delay(100); + } + if (version != 0x12) { + return false; + } + + lora_preinit_done = true; + return true; + #elif MODEM == SX1262 + long start = millis(); + uint8_t syncmsb; + uint8_t synclsb; + while (((millis() - start) < 2000) && (millis() >= start)) { + syncmsb = readRegister(REG_SYNC_WORD_MSB); + synclsb = readRegister(REG_SYNC_WORD_LSB); + if ( uint16_t(syncmsb << 8 | synclsb) == 0x1424 || uint16_t(syncmsb << 8 | synclsb) == 0x4434) { + break; + } + delay(100); + } + if ( uint16_t(syncmsb << 8 | synclsb) != 0x1424 && uint16_t(syncmsb << 8 | synclsb) != 0x4434) { + return false; } - delay(100); - } - if (version != 0x12) { - return false; - } - lora_preinit_done = true; - return true; + lora_preinit_done = true; + return true; + #else + return false; + #endif } +#if MODEM == SX1276 || MODEM == SX1278 + 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; + + digitalWrite(_ss, LOW); + + SPI.beginTransaction(_spiSettings); + SPI.transfer(address); + response = SPI.transfer(value); + SPI.endTransaction(); + + digitalWrite(_ss, HIGH); + + return response; + } +#elif MODEM == SX1262 + uint8_t ISR_VECT LoRaClass::readRegister(uint16_t address) + { + return singleTransfer(OP_READ_REGISTER, address, 0x00); + } + + void LoRaClass::writeRegister(uint16_t address, uint8_t value) + { + singleTransfer(OP_WRITE_REGISTER, address, value); + } + + uint8_t ISR_VECT LoRaClass::singleTransfer(uint8_t opcode, uint16_t address, uint8_t value) + { + waitOnBusy(); + + uint8_t response; + + digitalWrite(_ss, LOW); + + SPI.beginTransaction(_spiSettings); + SPI.transfer(opcode); + SPI.transfer((address & 0xFF00) >> 8); + SPI.transfer(address & 0x00FF); + if (opcode == OP_READ_REGISTER) { + SPI.transfer(0x00); + } + response = SPI.transfer(value); + SPI.endTransaction(); + + digitalWrite(_ss, HIGH); + + return response; + } + + void LoRaClass::enableAntenna() + { + uint8_t byte = 0x01; + // enable dio2 rf switch + executeOpcode(OP_DIO2_RF_CTRL, &byte, 1); + digitalWrite(_rxen, HIGH); + } + + void LoRaClass::disableAntenna() + { + digitalWrite(_rxen, LOW); + } + + void LoRaClass::loraMode() { + // enable lora mode on the SX1262 chip + uint8_t mode = MODE_LONG_RANGE_MODE; + executeOpcode(OP_PACKET_TYPE, &mode, 1); + } + + void LoRaClass::waitOnBusy() { + if (_busy != -1) { + while (digitalRead(_busy) == HIGH) + { + // do nothing + } + } + } + + void LoRaClass::executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size) + { + waitOnBusy(); + + digitalWrite(_ss, LOW); + + SPI.beginTransaction(_spiSettings); + SPI.transfer(opcode); + + for (int i = 0; i < size; i++) + { + SPI.transfer(buffer[i]); + } + + SPI.endTransaction(); + + digitalWrite(_ss, HIGH); + } + + void LoRaClass::executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size) + { + waitOnBusy(); + + digitalWrite(_ss, LOW); + + SPI.beginTransaction(_spiSettings); + SPI.transfer(opcode); + SPI.transfer(0x00); + + for (int i = 0; i < size; i++) + { + buffer[i] = SPI.transfer(0x00); + } + + SPI.endTransaction(); + + digitalWrite(_ss, HIGH); + } + + void LoRaClass::writeBuffer(const uint8_t* buffer, size_t size) + { + waitOnBusy(); + + digitalWrite(_ss, LOW); + + SPI.beginTransaction(_spiSettings); + SPI.transfer(OP_FIFO_WRITE); + SPI.transfer(fifo_tx_addr_ptr); + + for (int i = 0; i < size; i++) + { + SPI.transfer(buffer[i]); + fifo_tx_addr_ptr++; + } + + SPI.endTransaction(); + + digitalWrite(_ss, HIGH); + } + + void LoRaClass::readBuffer(uint8_t* buffer, size_t size) + { + waitOnBusy(); + + digitalWrite(_ss, LOW); + + SPI.beginTransaction(_spiSettings); + SPI.transfer(OP_FIFO_READ); + SPI.transfer(fifo_rx_addr_ptr); + SPI.transfer(0x00); + + for (int i = 0; i < size; i++) + { + buffer[i] = SPI.transfer(0x00); + } + + SPI.endTransaction(); + + digitalWrite(_ss, HIGH); + } + + void LoRaClass::setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro) { + // because there is no access to these registers on the sx1262, we have + // to set all these parameters at once or not at all. + uint8_t buf[8]; + + buf[0] = sf; + buf[1] = bw; + buf[2] = cr; + // low data rate toggle + buf[3] = ldro; + // unused params in LoRa mode + buf[4] = 0x00; + buf[5] = 0x00; + buf[6] = 0x00; + buf[7] = 0x00; + + executeOpcode(OP_MODULATION_PARAMS, buf, 8); + } + + void LoRaClass::setPacketParams(long preamble, uint8_t headermode, uint8_t length, uint8_t crc) { + // because there is no access to these registers on the sx1262, we have + // to set all these parameters at once or not at all. + uint8_t buf[9]; + + buf[0] = uint8_t((preamble & 0xFF00) >> 8); + buf[1] = uint8_t((preamble & 0x00FF)); + buf[2] = headermode; + buf[3] = length; + buf[4] = crc; + // standard IQ setting (no inversion) + buf[5] = 0x00; + // unused params + buf[6] = 0x00; + buf[7] = 0x00; + buf[8] = 0x00; + + executeOpcode(OP_PACKET_PARAMS, buf, 9); + } +#endif + + int LoRaClass::begin(long frequency) { if (_reset != -1) { @@ -140,33 +446,69 @@ int LoRaClass::begin(long frequency) delay(10); } + if (_busy != -1) { + pinMode(_busy, INPUT); + } + if (!lora_preinit_done) { if (!preInit()) { return false; } } - // put in sleep mode - sleep(); + #if MODEM == SX1276 || MODEM == SX1278 + // put in sleep mode + sleep(); - // set frequency - setFrequency(frequency); + // set frequency + setFrequency(frequency); - // set base addresses - writeRegister(REG_FIFO_TX_BASE_ADDR, 0); - writeRegister(REG_FIFO_RX_BASE_ADDR, 0); + // 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 LNA boost + writeRegister(REG_LNA, readRegister(REG_LNA) | 0x03); - // set auto AGC - writeRegister(REG_MODEM_CONFIG_3, 0x04); + // set auto AGC + writeRegister(REG_MODEM_CONFIG_3, 0x04); - // set output power to 2 dBm - setTxPower(2); + // set output power to 2 dBm + setTxPower(2); - // put in standby mode - idle(); + // put in standby mode + idle(); + #elif MODEM == SX1262 + //#if HAS_TCXO + // turn TCXO on + enableTCXO(); + //#endif + loraMode(); + idle(); + // cannot access registers in sleep mode on sx1262, set to idle instead + if (_rxen != -1) { + pinMode(_rxen, OUTPUT); + enableAntenna(); + } + // calibrate RC64k, RC13M, PLL, ADC and image + uint8_t calibrate = 0x7F; + executeOpcode(OP_CALIBRATE, &calibrate, 1); + + setFrequency(frequency); + + // set output power to 2 dBm + setTxPower(2); + + // set LNA boost + writeRegister(REG_LNA, 0x96); + + // set base addresses + uint8_t basebuf[2] = {0}; + executeOpcode(OP_BUFFER_BASE_ADDR, basebuf, 2); + + setModulationParams(_sf, _bw, _cr, _ldro); + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + #endif return 1; } @@ -193,138 +535,294 @@ int LoRaClass::beginPacket(int implicitHeader) explicitHeaderMode(); } - // reset FIFO address and paload length - writeRegister(REG_FIFO_ADDR_PTR, 0); - writeRegister(REG_PAYLOAD_LENGTH, 0); + #if MODEM == SX1276 || MODEM == SX1278 + // reset FIFO address and paload length + writeRegister(REG_FIFO_ADDR_PTR, 0); + writeRegister(REG_PAYLOAD_LENGTH, 0); + #elif MODEM == SX1262 + _payloadLength = 0; + fifo_tx_addr_ptr = 0; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + #endif return 1; } int LoRaClass::endPacket() { - // put in TX mode - writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX); + #if MODEM == SX1276 || MODEM == SX1278 + // 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) { - yield(); - } + // wait for TX done + while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0) { + yield(); + } - // clear IRQ's - writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK); + // clear IRQ's + writeRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK); + #elif MODEM == SX1262 + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + // put in single TX mode + uint8_t timeout[3] = {0}; + executeOpcode(OP_TX, timeout, 3); + uint8_t buf[2]; + + buf[0] = 0x00; + buf[1] = 0x00; + + executeOpcodeRead(OP_GET_IRQ_STATUS, buf, 2); + + // wait for TX done + while ((buf[1] & IRQ_TX_DONE_MASK) == 0) { + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS, buf, 2); + yield(); + } + + // clear IRQ's + + uint8_t mask[2]; + mask[0] = 0x00; + mask[1] = IRQ_TX_DONE_MASK; + executeOpcode(OP_CLEAR_IRQ_STATUS, mask, 2); + #endif return 1; } int LoRaClass::parsePacket(int size) { int packetLength = 0; - int irqFlags = readRegister(REG_IRQ_FLAGS); + #if MODEM == SX1276 || MODEM == SX1278 + int irqFlags = readRegister(REG_IRQ_FLAGS); + #elif MODEM == SX1262 + uint8_t buf[2]; + buf[0] = 0x00; + buf[1] = 0x00; + + executeOpcodeRead(OP_GET_IRQ_STATUS, buf, 2); + #endif if (size > 0) { implicitHeaderMode(); - writeRegister(REG_PAYLOAD_LENGTH, size & 0xff); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_PAYLOAD_LENGTH, size & 0xff); + #elif MODEM == SX1262 + // tell radio payload length + _payloadLength = size; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + #endif } else { explicitHeaderMode(); } - // clear IRQ's - writeRegister(REG_IRQ_FLAGS, irqFlags); + #if MODEM == SX1276 || MODEM == SX1278 + // clear IRQ's + writeRegister(REG_IRQ_FLAGS, irqFlags); + if ((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) { + #elif MODEM == SX1262 + uint8_t irqBufFlags[2]; - if ((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) { - // received a packet - _packetIndex = 0; + irqBufFlags[0] = buf[0]; + irqBufFlags[1] = buf[1]; - // read packet length - if (_implicitHeaderMode) { - packetLength = readRegister(REG_PAYLOAD_LENGTH); - } else { - packetLength = readRegister(REG_RX_NB_BYTES); - } + executeOpcode(OP_CLEAR_IRQ_STATUS, irqBufFlags, 2); - // set FIFO address to current RX address - writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR)); + if ((buf[0] & IRQ_RX_DONE_MASK) && (buf[1] & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) { + #endif + + // received a packet + _packetIndex = 0; + + #if MODEM == SX1276 || MODEM == SX1278 + // read packet length + if (_implicitHeaderMode) { + packetLength = readRegister(REG_PAYLOAD_LENGTH); + } else { + packetLength = readRegister(REG_RX_NB_BYTES); + } + #elif MODEM == SX1262 + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_RX_BUFFER_STATUS, buf, 2); + packetLength = buf[0]; + #endif + + #if MODEM == SX1276 || MODEM == SX1278 + // set FIFO address to current RX address + writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR)); + #endif // put in standby mode idle(); - } else if (readRegister(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)) { - // not currently in RX mode + + #if MODEM == SX1276 || MODEM == SX1278 + } 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); + // 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); + // put in single RX mode + writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE); + } + #elif MODEM == SX1262 + } else { + uint8_t status; + status = 0x00; + executeOpcodeRead(OP_STATUS, &status, 1); + if ((status >> 4 & 0x7) != 0x5) { + // not currently in RX mode + + // put in single RX mode + uint8_t buf[3] = {0}; + executeOpcode(OP_RX, buf, 3); + } } - + #endif return packetLength; } uint8_t LoRaClass::modemStatus() { - return readRegister(REG_MODEM_STAT); + #if MODEM == SX1276 || MODEM == SX1278 + return readRegister(REG_MODEM_STAT); + #elif MODEM == SX1262 + // imitate the register status from the sx1276 / 78 + uint8_t buf[2] = {0}; + + + executeOpcodeRead(OP_GET_IRQ_STATUS, buf, 2); + + uint8_t clearbuf[2] = {0}; + + uint8_t byte = 0x00; + + if (buf[1] & IRQ_PREAMBLE_DET_MASK != 0) { + byte = byte | 0x01 | 0x04; + // clear register after reading + clearbuf[1] = IRQ_PREAMBLE_DET_MASK; + } + + if (buf[1] & IRQ_HEADER_DET_MASK != 0) { + byte = byte | 0x02 | 0x04; + // clear register after reading + clearbuf[1] = clearbuf[1] | IRQ_HEADER_DET_MASK; + } + + executeOpcode(OP_CLEAR_IRQ_STATUS, clearbuf, 2); + + return byte; + #endif } uint8_t LoRaClass::currentRssiRaw() { - uint8_t rssi = readRegister(REG_RSSI_VALUE); - return rssi; + #if MODEM == SX1276 || MODEM == SX1278 + uint8_t rssi = readRegister(REG_RSSI_VALUE); + return rssi; + #elif MODEM == SX1262 + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI, &byte, 1); + return byte; + #endif } int ISR_VECT LoRaClass::currentRssi() { - int rssi = (int)readRegister(REG_RSSI_VALUE) - RSSI_OFFSET; - if (_frequency < 820E6) rssi -= 7; - return rssi; + #if MODEM == SX1276 || MODEM == SX1278 + int rssi = (int)readRegister(REG_RSSI_VALUE) - RSSI_OFFSET; + if (_frequency < 820E6) rssi -= 7; + return rssi; + #elif MODEM == SX1262 + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI, &byte, 1); + int rssi = -(int(byte)) / 2; + return rssi; + #endif } uint8_t LoRaClass::packetRssiRaw() { - uint8_t pkt_rssi_value = readRegister(REG_PKT_RSSI_VALUE); - return pkt_rssi_value; + #if MODEM == SX1276 || MODEM == SX1278 + uint8_t pkt_rssi_value = readRegister(REG_PKT_RSSI_VALUE); + return pkt_rssi_value; + #elif MODEM == SX1262 + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS, buf, 3); + return buf[2]; + #endif } int ISR_VECT LoRaClass::packetRssi() { - int pkt_rssi = (int)readRegister(REG_PKT_RSSI_VALUE) - RSSI_OFFSET; - int pkt_snr = packetSnr(); + #if MODEM == SX1276 || MODEM == SX1278 + int pkt_rssi = (int)readRegister(REG_PKT_RSSI_VALUE) - RSSI_OFFSET; + int pkt_snr = packetSnr(); - if (_frequency < 820E6) pkt_rssi -= 7; + 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); - } + 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; - return pkt_rssi; + #elif MODEM == SX1262 + // may need more calculations here + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS, buf, 3); + int pkt_rssi = -(int(buf[2])) / 2; + return pkt_rssi; + #endif } uint8_t ISR_VECT LoRaClass::packetSnrRaw() { - return readRegister(REG_PKT_SNR_VALUE); + #if MODEM == SX1276 || MODEM == SX1278 + return readRegister(REG_PKT_SNR_VALUE); + #elif MODEM == SX1262 + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS, buf, 3); + return buf[1]; + #endif } float ISR_VECT LoRaClass::packetSnr() { - return ((int8_t)readRegister(REG_PKT_SNR_VALUE)) * 0.25; + #if MODEM == SX1276 || MODEM == SX1278 + return ((int8_t)readRegister(REG_PKT_SNR_VALUE)) * 0.25; + #elif MODEM == SX1262 + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS, buf, 3); + return float(buf[1]) / 4.0; + #endif } long LoRaClass::packetFrequencyError() { int32_t freqError = 0; - freqError = static_cast(readRegister(REG_FREQ_ERROR_MSB) & B111); - freqError <<= 8L; - freqError += static_cast(readRegister(REG_FREQ_ERROR_MID)); - freqError <<= 8L; - freqError += static_cast(readRegister(REG_FREQ_ERROR_LSB)); + #if MODEM == SX1276 || MODEM == SX1278 + freqError = static_cast(readRegister(REG_FREQ_ERROR_MSB) & B111); + 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) & B1000) { // Sign bit is on - freqError -= 524288; // B1000'0000'0000'0000'0000 - } + if (readRegister(REG_FREQ_ERROR_MSB) & B1000) { // 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 + 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); + return static_cast(fError); + #elif MODEM == SX1262 + // todo: implement this, no idea how to check it on the sx1262 + const float fError = 0.0; + return static_cast(fError); + #endif } size_t LoRaClass::write(uint8_t byte) @@ -334,27 +832,43 @@ size_t LoRaClass::write(uint8_t byte) size_t LoRaClass::write(const uint8_t *buffer, size_t size) { - int currentLength = readRegister(REG_PAYLOAD_LENGTH); + #if MODEM == SX1276 || MODEM == SX1278 + int currentLength = readRegister(REG_PAYLOAD_LENGTH); - // check size - if ((currentLength + size) > MAX_PKT_LENGTH) { - size = MAX_PKT_LENGTH - currentLength; - } + // check size + if ((currentLength + size) > MAX_PKT_LENGTH) { + size = MAX_PKT_LENGTH - currentLength; + } + #elif MODEM == SX1262 + if ((_payloadLength + size) > MAX_PKT_LENGTH) { + size = MAX_PKT_LENGTH - _payloadLength; + } + #endif // write data - for (size_t i = 0; i < size; i++) { - writeRegister(REG_FIFO, buffer[i]); - } - - // update length - writeRegister(REG_PAYLOAD_LENGTH, currentLength + size); + #if MODEM == SX1276 || MODEM == SX1278 + for (size_t i = 0; i < size; i++) { + writeRegister(REG_FIFO, buffer[i]); + } + // update length + writeRegister(REG_PAYLOAD_LENGTH, currentLength + size); + #elif MODEM == SX1262 + writeBuffer(buffer, size); + _payloadLength = _payloadLength + size; + #endif return size; } int ISR_VECT LoRaClass::available() { - return (readRegister(REG_RX_NB_BYTES) - _packetIndex); + #if MODEM == SX1276 || MODEM == SX1278 + return (readRegister(REG_RX_NB_BYTES) - _packetIndex); + #elif MODEM == SX1262 + uint8_t buf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS, buf, 2); + return buf[0] - _packetIndex; + #endif } int ISR_VECT LoRaClass::read() @@ -363,9 +877,25 @@ int ISR_VECT LoRaClass::read() return -1; } - _packetIndex++; + #if MODEM == SX1276 || MODEM == SX1278 + _packetIndex++; + return readRegister(REG_FIFO); + #elif MODEM == SX1262 + // if received new packet + if (_packetIndex == 0) { + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS, rxbuf, 2); + int size = rxbuf[0]; + fifo_rx_addr_ptr = rxbuf[1]; + + readBuffer(packet, size); + } + + uint8_t byte = packet[_packetIndex]; + _packetIndex++; + return byte; + #endif - return readRegister(REG_FIFO); } int LoRaClass::peek() @@ -374,15 +904,29 @@ int LoRaClass::peek() return -1; } - // store current FIFO address - int currentAddress = readRegister(REG_FIFO_ADDR_PTR); + #if MODEM == SX1276 || MODEM == SX1278 + // store current FIFO address + int currentAddress = readRegister(REG_FIFO_ADDR_PTR); - // read - uint8_t b = readRegister(REG_FIFO); + // read + uint8_t b = readRegister(REG_FIFO); - // restore FIFO address - writeRegister(REG_FIFO_ADDR_PTR, currentAddress); + // restore FIFO address + writeRegister(REG_FIFO_ADDR_PTR, currentAddress); + #elif MODEM == SX1262 + // if received new packet + if (_packetIndex == 0) { + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS, rxbuf, 2); + int size = rxbuf[0]; + fifo_rx_addr_ptr = rxbuf[1]; + + readBuffer(packet, size); + } + + uint8_t b = packet[_packetIndex]; + #endif return b; } @@ -397,7 +941,30 @@ void LoRaClass::onReceive(void(*callback)(int)) if (callback) { pinMode(_dio0, INPUT); - writeRegister(REG_DIO_MAPPING_1, 0x00); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_DIO_MAPPING_1, 0x00); + #elif MODEM == SX1262 + // set preamble and header detection irqs, plus dio0 mask + uint8_t buf[8]; + + // set irq masks, enable all + buf[0] = 0xFF; + buf[1] = 0xFF; + + // set dio0 masks + buf[2] = 0x00; + buf[3] = IRQ_RX_DONE_MASK; + + // set dio1 masks + buf[4] = 0x00; + buf[5] = 0x00; + + // set dio2 masks + buf[6] = 0x00; + buf[7] = 0x00; + + executeOpcode(OP_SET_IRQ_FLAGS, buf, 8); + #endif #ifdef SPI_HAS_NOTUSINGINTERRUPT SPI.usingInterrupt(digitalPinToInterrupt(_dio0)); #endif @@ -415,84 +982,182 @@ void LoRaClass::receive(int size) if (size > 0) { implicitHeaderMode(); - writeRegister(REG_PAYLOAD_LENGTH, size & 0xff); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_PAYLOAD_LENGTH, size & 0xff); + #elif MODEM == SX1262 + // tell radio payload length + _payloadLength = size; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + #endif } else { explicitHeaderMode(); } - writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS); + #elif MODEM == SX1262 + uint8_t mode[3] = {0xFF, 0xFF, 0xFF}; // continuous mode + executeOpcode(OP_RX, mode, 3); + #endif } void LoRaClass::idle() { - writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY); + #elif MODEM == SX1262 + //#if HAS_TCXO + // STDBY_XOSC + uint8_t byte = 0x01; + //#else + // // STDBY_RC + // uint8_t byte = 0x00; + //#endif + executeOpcode(OP_STANDBY, &byte, 1); + #endif } void LoRaClass::sleep() { - writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP); + #elif MODEM == SX1262 + if (_rxen != -1) { + disableAntenna(); + } + uint8_t byte = 0x00; + executeOpcode(OP_SLEEP, &byte, 1); + #endif } void LoRaClass::enableTCXO() { - uint8_t tcxo_reg = readRegister(REG_TCXO); - writeRegister(REG_TCXO, tcxo_reg | 0x10); + #if MODEM == SX1276 || MODEM == SX1278 + uint8_t tcxo_reg = readRegister(REG_TCXO); + writeRegister(REG_TCXO, tcxo_reg | 0x10); + #elif MODEM == SX1262 + // only tested for RAK4630, voltage may be different on other platforms + uint8_t buf[4] = {MODE_TCXO_3_3V, 0x00, 0x00, 0xFF}; + executeOpcode(OP_DIO3_TCXO_CTRL, buf, 4); + #endif } void LoRaClass::disableTCXO() { - uint8_t tcxo_reg = readRegister(REG_TCXO); - writeRegister(REG_TCXO, tcxo_reg & 0xEF); + #if MODEM == SX1276 || MODEM == SX1278 + uint8_t tcxo_reg = readRegister(REG_TCXO); + writeRegister(REG_TCXO, tcxo_reg & 0xEF); + #elif MODEM == SX1262 + // currently cannot disable on SX1262? + #endif } 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; + #if MODEM == SX1276 || MODEM == SX1278 + if (PA_OUTPUT_RFO_PIN == outputPin) { + // RFO + if (level < 0) { + level = 0; + } else if (level > 14) { + level = 14; + } + + writeRegister(REG_PA_DAC, 0x84); + writeRegister(REG_PA_CONFIG, 0x70 | level); + + } else { + // PA BOOST + if (level < 2) { + level = 2; + } else if (level > 17) { + level = 17; + } + + writeRegister(REG_PA_DAC, 0x84); + writeRegister(REG_PA_CONFIG, PA_BOOST | (level - 2)); + } + #elif MODEM == SX1262 + // currently no low power mode for SX1262 implemented, assuming PA boost + + // WORKAROUND - Better Resistance of the SX1262 Tx to Antenna Mismatch, see DS_SX1261-2_V1.2 datasheet chapter 15.2 + // RegTxClampConfig = @address 0x08D8 + writeRegister(0x08D8, readRegister(0x08D8) | (0x0F << 1)); + + uint8_t pa_buf[4]; + + pa_buf[0] = 0x04; + pa_buf[1] = 0x07; + pa_buf[2] = 0x00; + pa_buf[3] = 0x01; + + executeOpcode(OP_PA_CONFIG, pa_buf, 4); // set pa_config for high power + + if (level > 22) { + level = 22; + } + else if (level < -9) { + level = -9; } - writeRegister(REG_PA_DAC, 0x84); - writeRegister(REG_PA_CONFIG, 0x70 | level); + writeRegister(REG_OCP, 0x38); // 160mA limit, overcurrent protection - } else { - // PA BOOST - if (level < 2) { - level = 2; - } else if (level > 17) { - level = 17; - } + uint8_t tx_buf[2]; - writeRegister(REG_PA_DAC, 0x84); - writeRegister(REG_PA_CONFIG, PA_BOOST | (level - 2)); - } + tx_buf[0] = level; + tx_buf[1] = 0x02; // ramping time - 40 microseconds + + executeOpcode(OP_TX_PARAMS, tx_buf, 2); + + _txp = level; + #endif } uint8_t LoRaClass::getTxPower() { - byte txp = readRegister(REG_PA_CONFIG); - return txp; + #if MODEM == SX1276 || MODEM == SX1278 + byte txp = readRegister(REG_PA_CONFIG); + return txp; + #elif MODEM == SX1262 + return _txp; + #endif } void LoRaClass::setFrequency(long frequency) { _frequency = frequency; - uint32_t frf = ((uint64_t)frequency << 19) / 32000000; + #if MODEM == SX1276 || MODEM == SX1278 + 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)); + writeRegister(REG_FRF_MSB, (uint8_t)(frf >> 16)); + writeRegister(REG_FRF_MID, (uint8_t)(frf >> 8)); + writeRegister(REG_FRF_LSB, (uint8_t)(frf >> 0)); - optimizeModemSensitivity(); + optimizeModemSensitivity(); + #elif MODEM == SX1262 + uint8_t buf[4]; + + uint32_t freq = (uint32_t)((double)frequency / (double)FREQ_STEP); + + buf[0] = ((freq >> 24) & 0xFF); + buf[1] = ((freq >> 16) & 0xFF); + buf[2] = ((freq >> 8) & 0xFF); + buf[3] = (freq & 0xFF); + + executeOpcode(OP_RF_FREQ, buf, 4); + #endif } 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); + #if MODEM == SX1276 || MODEM == SX1278 + 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); + 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); + + #elif MODEM == SX1262 + // we can't read the frequency on the sx1262 + uint32_t frequency = _frequency; + #endif return frequency; } @@ -505,93 +1170,149 @@ void LoRaClass::setSpreadingFactor(int sf) 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)); + #if MODEM == SX1276 || MODEM == SX1278 + 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)); + #elif MODEM == SX1262 + setModulationParams(sf, _bw, _cr, _ldro); + #endif 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; - } + #if MODEM == SX1276 || MODEM == SX1278 + 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; + } + #elif MODEM == SX1262 + int bw = _bw; + switch (bw) { + case 0x00: return 7.8E3; + case 0x01: return 15.6E3; + case 0x02: return 31.25E3; + case 0x03: return 62.5E3; + case 0x04: return 125E3; + case 0x05: return 250E3; + case 0x06: return 500E3; + case 0x08: return 10.4E3; + case 0x09: return 20.8E3; + case 0x0A: return 41.7E3; + } + #endif 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)); - } + #if MODEM == SX1276 || MODEM == SX1278 + 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)); + } + #elif MODEM == SX1262 + _ldro = 1; + setModulationParams(_sf, _bw, _cr, _ldro); + #endif } void LoRaClass::optimizeModemSensitivity(){ - byte bw = (readRegister(REG_MODEM_CONFIG_1) >> 4); - uint32_t freq = getFrequency(); + #if MODEM == SX1276 || MODEM == SX1278 + byte bw = (readRegister(REG_MODEM_CONFIG_1) >> 4); + uint32_t freq = getFrequency(); - if (bw == 9 && (410E6 <= freq) && (freq <= 525E6)) { - writeRegister(REG_HIGH_BW_OPTIMIZE_1, 0x02); - writeRegister(REG_HIGH_BW_OPTIMIZE_2, 0x7f); - } else if (bw == 9 && (820E6 <= freq) && (freq <= 1020E6)) { - writeRegister(REG_HIGH_BW_OPTIMIZE_1, 0x02); - writeRegister(REG_HIGH_BW_OPTIMIZE_2, 0x64); - } else { - writeRegister(REG_HIGH_BW_OPTIMIZE_1, 0x03); - } + if (bw == 9 && (410E6 <= freq) && (freq <= 525E6)) { + writeRegister(REG_HIGH_BW_OPTIMIZE_1, 0x02); + writeRegister(REG_HIGH_BW_OPTIMIZE_2, 0x7f); + } else if (bw == 9 && (820E6 <= freq) && (freq <= 1020E6)) { + writeRegister(REG_HIGH_BW_OPTIMIZE_1, 0x02); + writeRegister(REG_HIGH_BW_OPTIMIZE_2, 0x64); + } else { + writeRegister(REG_HIGH_BW_OPTIMIZE_1, 0x03); + } + #elif MODEM == SX1262 + // todo: check if there's anything the sx1262 can do here + #endif } void LoRaClass::setSignalBandwidth(long sbw) { - int bw; + #if MODEM == SX1276 || MODEM == SX1278 + 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; - } + 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)); + + #elif MODEM == SX1262 + uint8_t bw; + + if (sbw <= 7.8E3) { + bw = 0x00; + } else if (sbw <= 10.4E3) { + bw = 0x08; + } else if (sbw <= 15.6E3) { + bw = 0x01; + } else if (sbw <= 20.8E3) { + bw = 0x09; + } else if (sbw <= 31.25E3) { + bw = 0x02; + } else if (sbw <= 41.7E3) { + bw = 0x0A; + } else if (sbw <= 62.5E3) { + bw = 0x03; + } else if (sbw <= 125E3) { + bw = 0x04; + } else if (sbw <= 250E3) { + bw = 0x05; + } else /*if (sbw <= 250E3)*/ { + bw = 0x06; + } + + setModulationParams(_sf, bw, _cr, _ldro); + #endif - writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0x0f) | (bw << 4)); - handleLowDataRate(); optimizeModemSensitivity(); } @@ -606,40 +1327,69 @@ void LoRaClass::setCodingRate4(int denominator) int cr = denominator - 4; - writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0xf1) | (cr << 1)); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_MODEM_CONFIG_1, (readRegister(REG_MODEM_CONFIG_1) & 0xf1) | (cr << 1)); + #elif MODEM == SX1262 + setModulationParams(_sf, _bw, cr, _ldro); + #endif } void LoRaClass::setPreambleLength(long length) { - writeRegister(REG_PREAMBLE_MSB, (uint8_t)(length >> 8)); - writeRegister(REG_PREAMBLE_LSB, (uint8_t)(length >> 0)); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_PREAMBLE_MSB, (uint8_t)(length >> 8)); + writeRegister(REG_PREAMBLE_LSB, (uint8_t)(length >> 0)); + #elif MODEM == SX1262 + setPacketParams(length, _implicitHeaderMode, _payloadLength, _crcMode); + #endif } void LoRaClass::setSyncWord(int sw) { - writeRegister(REG_SYNC_WORD, sw); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_SYNC_WORD, sw); + #elif MODEM == SX1262 + writeRegister(REG_SYNC_WORD_MSB, sw & 0xFF00); + writeRegister(REG_SYNC_WORD_LSB, sw & 0x00FF); + #endif } void LoRaClass::enableCrc() { - writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) | 0x04); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) | 0x04); + #elif MODEM == SX1262 + _crcMode = 1; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + #endif } void LoRaClass::disableCrc() { - writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) & 0xfb); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_MODEM_CONFIG_2, readRegister(REG_MODEM_CONFIG_2) & 0xfb); + #elif MODEM == SX1262 + _crcMode = 0; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + #endif } byte LoRaClass::random() { - return readRegister(REG_RSSI_WIDEBAND); + #if MODEM == SX1276 || MODEM == SX1278 + return readRegister(REG_RSSI_WIDEBAND); + #elif MODEM == SX1262 + return readRegister(REG_RANDOM_GEN); + #endif } -void LoRaClass::setPins(int ss, int reset, int dio0) +void LoRaClass::setPins(int ss, int reset, int dio0, int rxen, int busy) { _ss = ss; _reset = reset; _dio0 = dio0; + _rxen = rxen; + _busy = busy; } void LoRaClass::setSPIFrequency(uint32_t frequency) @@ -661,72 +1411,77 @@ void LoRaClass::explicitHeaderMode() { _implicitHeaderMode = 0; - writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) & 0xfe); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) & 0xfe); + #elif MODEM == SX1262 + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + #endif } void LoRaClass::implicitHeaderMode() { _implicitHeaderMode = 1; - writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) | 0x01); + #if MODEM == SX1276 || MODEM == SX1278 + writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) | 0x01); + #elif MODEM == SX1262 + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + #endif } void ISR_VECT LoRaClass::handleDio0Rise() { - int irqFlags = readRegister(REG_IRQ_FLAGS); + #if MODEM == SX1276 || MODEM == SX1278 + int irqFlags = readRegister(REG_IRQ_FLAGS); - // clear IRQ's - writeRegister(REG_IRQ_FLAGS, irqFlags); + // clear IRQ's + writeRegister(REG_IRQ_FLAGS, irqFlags); + if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) { + #elif MODEM == SX1262 + uint8_t buf[2]; - if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) { - // received a packet - _packetIndex = 0; + buf[0] = 0x00; + buf[1] = 0x00; + + executeOpcodeRead(OP_GET_IRQ_STATUS, buf, 2); + + executeOpcode(OP_CLEAR_IRQ_STATUS, buf, 2); + + + if ((buf[1] & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) { + #endif + + + // received a packet + _packetIndex = 0; // read packet length + #if MODEM == SX1276 || MODEM == SX1278 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)); + #elif MODEM == SX1262 + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS, rxbuf, 2); + int packetLength = rxbuf[0]; + #endif - if (_onReceive) { - _onReceive(packetLength); - } + if (_onReceive) { + _onReceive(packetLength); + } + #if MODEM == SX1276 || MODEM == SX1278 // reset FIFO address writeRegister(REG_FIFO_ADDR_PTR, 0); + #endif } } -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; - - digitalWrite(_ss, LOW); - - SPI.beginTransaction(_spiSettings); - SPI.transfer(address); - response = SPI.transfer(value); - SPI.endTransaction(); - - digitalWrite(_ss, HIGH); - - return response; -} - void ISR_VECT LoRaClass::onDio0Rise() { LoRa.handleDio0Rise(); } -LoRaClass LoRa; \ No newline at end of file +LoRaClass LoRa; diff --git a/LoRa.h b/LoRa.h index c4c290e..102010d 100644 --- a/LoRa.h +++ b/LoRa.h @@ -9,10 +9,13 @@ #include #include +#include "Modem.h" #define LORA_DEFAULT_SS_PIN 10 #define LORA_DEFAULT_RESET_PIN 9 #define LORA_DEFAULT_DIO0_PIN 2 +#define LORA_DEFAULT_RXEN_PIN -1 +#define LORA_DEFAULT_BUSY_PIN -1 #define PA_OUTPUT_RFO_PIN 0 #define PA_OUTPUT_PA_BOOST_PIN 1 @@ -71,13 +74,26 @@ public: void enableTCXO(); void disableTCXO(); + #if MODEM == SX1262 + void enableAntenna(); + void disableAntenna(); + void loraMode(); + void waitOnBusy(); + void executeOpcode(uint8_t opcode, uint8_t *buffer, uint8_t size); + void executeOpcodeRead(uint8_t opcode, uint8_t *buffer, uint8_t size); + void writeBuffer(const uint8_t* buffer, size_t size); + void readBuffer(uint8_t* buffer, size_t size); + void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro); + void setPacketParams(long preamble, uint8_t headermode, uint8_t length, uint8_t crc); + #endif + // deprecated void crc() { enableCrc(); } void noCrc() { disableCrc(); } byte random(); - void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN); + void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int rxen = LORA_DEFAULT_RXEN_PIN, int busy = LORA_DEFAULT_BUSY_PIN); void setSPIFrequency(uint32_t frequency); void dumpRegisters(Stream& out); @@ -88,9 +104,15 @@ private: void handleDio0Rise(); - uint8_t readRegister(uint8_t address); - void writeRegister(uint8_t address, uint8_t value); - uint8_t singleTransfer(uint8_t address, uint8_t value); + #if MODEM == SX1276 || MODEM == SX1278 + uint8_t readRegister(uint8_t address); + void writeRegister(uint8_t address, uint8_t value); + uint8_t singleTransfer(uint8_t address, uint8_t value); + #elif MODEM == SX1262 + uint8_t readRegister(uint16_t address); + void writeRegister(uint16_t address, uint8_t value); + uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value); + #endif static void onDio0Rise(); @@ -102,12 +124,22 @@ private: int _ss; int _reset; int _dio0; + int _rxen; + int _busy; long _frequency; + int _txp; + uint8_t _sf; + uint8_t _bw; + uint8_t _cr; + uint8_t _ldro; int _packetIndex; + int _preambleLength; int _implicitHeaderMode; + int _payloadLength; + int _crcMode; void (*_onReceive)(int); }; extern LoRaClass LoRa; -#endif \ No newline at end of file +#endif diff --git a/Makefile b/Makefile index 91cf74d..c807b0f 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,10 @@ prep-samd: arduino-cli core update-index --config-file arduino-cli.yaml arduino-cli core install adafruit:samd +prep-nrf: + arduino-cli core update-index --config-file arduino-cli.yaml + arduino-cli core install rakwireless:nrf52 + console-site: make -C Console clean site @@ -89,8 +93,10 @@ firmware-featheresp32: arduino-cli compile --fqbn esp32:esp32:featheresp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x34\"" firmware-genericesp32: - arduino-cli compile --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x35\"" + arduino-cli compile --fqbn esp32:esp32:esp32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x35\" \"-DMODEM=0x01\"" +firmware-rak4630: + arduino-cli compile --fqbn rakwireless:nrf52:WisCoreRAK4631Board -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x51\" \"-DMODEM=0x03\"" upload: arduino-cli upload -p /dev/ttyUSB0 --fqbn unsignedio:avr:rnode @@ -154,6 +160,10 @@ upload-featheresp32: @sleep 3 python ./Release/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x210000 ./Release/console_image.bin +upload-rak4630: + arduino-cli upload -p /dev/ttyACM0 --fqbn rakwireless:nrf52:WisCoreRAK4631Board + + release: release-all @@ -287,4 +297,4 @@ release-genericesp32: release-mega2560: arduino-cli compile --fqbn arduino:avr:mega -e cp build/arduino.avr.mega/RNode_Firmware.ino.hex Release/rnode_firmware_m2560.hex - rm -r build \ No newline at end of file + rm -r build diff --git a/Modem.h b/Modem.h new file mode 100644 index 0000000..bb23a57 --- /dev/null +++ b/Modem.h @@ -0,0 +1,3 @@ +#define SX1276 0x01 +#define SX1278 0x02 +#define SX1262 0x03 diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino index 5339564..5367abf 100644 --- a/RNode_Firmware.ino +++ b/RNode_Firmware.ino @@ -43,7 +43,7 @@ volatile bool serial_buffering = false; char sbuf[128]; -#if MCU_VARIANT == MCU_ESP32 +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 bool packet_ready = false; #endif @@ -86,15 +86,15 @@ void setup() { // Set chip select, reset and interrupt // pins for the LoRa module - LoRa.setPins(pin_cs, pin_reset, pin_dio); + LoRa.setPins(pin_cs, pin_reset, pin_dio, pin_rxen, pin_busy); - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 init_channel_stats(); // Check installed transceiver chip and // probe boot parameters. if (LoRa.preInit()) { - sx1276_installed = true; + modem_installed = true; uint32_t lfr = LoRa.getFrequency(); if (lfr == 0) { // Normal boot @@ -110,12 +110,12 @@ void setup() { } LoRa.setFrequency(M_FRQ_S); } else { - sx1276_installed = false; + modem_installed = false; } #else // Older variants only came with SX1276/78 chips, // so assume that to be the case for now. - sx1276_installed = true; + modem_installed = true; #endif #if HAS_DISPLAY @@ -173,7 +173,7 @@ inline void kiss_write_packet() { } serial_write(FEND); read_len = 0; - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 packet_ready = false; #endif } @@ -202,7 +202,7 @@ void ISR_VECT receive_callback(int packet_size) { read_len = 0; seq = sequence; - #if MCU_VARIANT != MCU_ESP32 + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 last_rssi = LoRa.packetRssi(); last_snr_raw = LoRa.packetSnrRaw(); #endif @@ -214,12 +214,14 @@ void ISR_VECT receive_callback(int packet_size) { // packet, so we add it to the buffer // and set the ready flag. - #if MCU_VARIANT != MCU_ESP32 + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 last_rssi = (last_rssi+LoRa.packetRssi())/2; last_snr_raw = (last_snr_raw+LoRa.packetSnrRaw())/2; #endif - + getPacketData(packet_size); + seq = SEQ_UNSET; ready = true; @@ -231,7 +233,7 @@ void ISR_VECT receive_callback(int packet_size) { read_len = 0; seq = sequence; - #if MCU_VARIANT != MCU_ESP32 + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 last_rssi = LoRa.packetRssi(); last_snr_raw = LoRa.packetSnrRaw(); #endif @@ -250,7 +252,7 @@ void ISR_VECT receive_callback(int packet_size) { seq = SEQ_UNSET; } - #if MCU_VARIANT != MCU_ESP32 + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 last_rssi = LoRa.packetRssi(); last_snr_raw = LoRa.packetSnrRaw(); #endif @@ -260,7 +262,7 @@ void ISR_VECT receive_callback(int packet_size) { } if (ready) { - #if MCU_VARIANT != MCU_ESP32 + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 // We first signal the RSSI of the // recieved packet to the host. kiss_indicate_stat_rssi(); @@ -277,7 +279,7 @@ void ISR_VECT receive_callback(int packet_size) { // output directly to the host read_len = 0; - #if MCU_VARIANT != MCU_ESP32 + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 last_rssi = LoRa.packetRssi(); last_snr_raw = LoRa.packetSnrRaw(); getPacketData(packet_size); @@ -375,7 +377,7 @@ void flushQueue(void) { led_tx_on(); uint16_t processed = 0; - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 while (!fifo16_isempty(&packet_starts)) { #else while (!fifo16_isempty_locked(&packet_starts)) { @@ -402,7 +404,7 @@ void flushQueue(void) { queue_height = 0; queued_bytes = 0; - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 update_airtime(); #endif queue_flushing = false; @@ -410,7 +412,7 @@ void flushQueue(void) { #define PHY_HEADER_LORA_SYMBOLS 8 void add_airtime(uint16_t written) { - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 float packet_cost_ms = 0.0; float payload_cost_ms = ((float)written * lora_us_per_byte)/1000.0; packet_cost_ms += payload_cost_ms; @@ -424,7 +426,7 @@ void add_airtime(uint16_t written) { } void update_airtime() { - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 uint16_t cb = current_airtime_bin(); uint16_t pb = cb-1; if (cb-1 < 0) { pb = AIRTIME_BINS-1; } uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } @@ -443,7 +445,7 @@ void update_airtime() { } longterm_channel_util = (float)longterm_channel_util_sum/(float)AIRTIME_BINS; - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 update_csma_p(); #endif kiss_indicate_channel_stats(); @@ -813,13 +815,13 @@ void serialCallback(uint8_t sbyte) { kiss_indicate_fb(); } } else if (command == CMD_DEV_HASH) { - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 if (sbyte != 0x00) { kiss_indicate_device_hash(); } #endif } else if (command == CMD_DEV_SIG) { - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 if (sbyte == FESC) { ESCAPE = true; } else { @@ -843,7 +845,7 @@ void serialCallback(uint8_t sbyte) { firmware_update_mode = false; } } else if (command == CMD_HASHES) { - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 if (sbyte == 0x01) { kiss_indicate_target_fw_hash(); } else if (sbyte == 0x02) { @@ -855,7 +857,7 @@ void serialCallback(uint8_t sbyte) { } #endif } else if (command == CMD_FW_HASH) { - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 if (sbyte == FESC) { ESCAPE = true; } else { @@ -925,6 +927,8 @@ void serialCallback(uint8_t sbyte) { void updateModemStatus() { #if MCU_VARIANT == MCU_ESP32 portENTER_CRITICAL(&update_lock); + #elif MCU_VARIANT == MCU_NRF52 + portENTER_CRITICAL(); #endif uint8_t status = LoRa.modemStatus(); @@ -933,6 +937,8 @@ void updateModemStatus() { #if MCU_VARIANT == MCU_ESP32 portEXIT_CRITICAL(&update_lock); + #elif MCU_VARIANT == MCU_NRF52 + portEXIT_CRITICAL(); #endif if ((status & SIG_DETECT) == SIG_DETECT) { stat_signal_detected = true; } else { stat_signal_detected = false; } @@ -982,7 +988,7 @@ void checkModemStatus() { if (millis()-last_status_update >= status_interval_ms) { updateModemStatus(); - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 util_samples[dcd_sample] = dcd; dcd_sample = (dcd_sample+1)%DCD_SAMPLES; if (dcd_sample % UTIL_UPDATE_INTERVAL == 0) { @@ -1023,6 +1029,12 @@ void validate_status() { uint8_t F_POR = 0x00; uint8_t F_BOR = 0x00; uint8_t F_WDR = 0x01; + #elif MCU_VARIANT == MCU_NRF52 + // TODO: Get NRF52 boot flags + uint8_t boot_flags = 0x02; + uint8_t F_POR = 0x00; + uint8_t F_BOR = 0x00; + uint8_t F_WDR = 0x01; #endif if (hw_ready || device_init_done) { @@ -1059,7 +1071,7 @@ void validate_status() { if (eeprom_product_valid() && eeprom_model_valid() && eeprom_hwrev_valid()) { if (eeprom_checksum_valid()) { eeprom_ok = true; - if (sx1276_installed) { + if (modem_installed) { #if PLATFORM == PLATFORM_ESP32 if (device_init()) { hw_ready = true; @@ -1071,7 +1083,7 @@ void validate_status() { #endif } else { hw_ready = false; - Serial.write("No SX1276/SX1278 radio module found\r\n"); + Serial.write("No valid radio module found\r\n"); #if HAS_DISPLAY if (disp_ready) { device_init_done = true; @@ -1125,7 +1137,7 @@ void validate_status() { } } -#if MCU_VARIANT == MCU_ESP32 +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 #define _e 2.71828183 #define _S 10.0 float csma_slope(float u) { return (pow(_e,_S*u-_S/2.0))/(pow(_e,_S*u-_S/2.0)+1.0); } @@ -1147,6 +1159,21 @@ void loop() { kiss_write_packet(); } + airtime_lock = false; + if (st_airtime_limit != 0.0 && airtime >= st_airtime_limit) airtime_lock = true; + if (lt_airtime_limit != 0.0 && longterm_airtime >= lt_airtime_limit) airtime_lock = true; + + #elif MCU_VARIANT == MCU_NRF52 + if (packet_ready) { + portENTER_CRITICAL(); + last_rssi = LoRa.packetRssi(); + last_snr_raw = LoRa.packetSnrRaw(); + portEXIT_CRITICAL(); + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + kiss_write_packet(); + } + airtime_lock = false; if (st_airtime_limit != 0.0 && airtime >= st_airtime_limit) airtime_lock = true; if (lt_airtime_limit != 0.0 && longterm_airtime >= lt_airtime_limit) airtime_lock = true; @@ -1155,7 +1182,7 @@ void loop() { checkModemStatus(); if (!airtime_lock) { if (queue_height > 0) { - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 long check_time = millis(); if (check_time > post_tx_yield_timeout) { if (dcd_waiting && (check_time >= dcd_wait_until)) { dcd_waiting = false; } @@ -1212,7 +1239,7 @@ void loop() { } } - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 buffer_serial(); if (!fifo_isempty(&serialFIFO)) serial_poll(); #else @@ -1236,7 +1263,7 @@ volatile bool serial_polling = false; void serial_poll() { serial_polling = true; - #if MCU_VARIANT != MCU_ESP32 + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 while (!fifo_isempty_locked(&serialFIFO)) { #else while (!fifo_isempty(&serialFIFO)) { @@ -1270,12 +1297,12 @@ void buffer_serial() { { c++; - #if MCU_VARIANT != MCU_ESP32 + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 if (!fifo_isfull_locked(&serialFIFO)) { fifo_push_locked(&serialFIFO, Serial.read()); } - #else - if (HAS_BLUETOOTH && bt_state == BT_STATE_CONNECTED) { + #elif HAS_BLUETOOTH + if (bt_state == BT_STATE_CONNECTED) { if (!fifo_isfull(&serialFIFO)) { fifo_push(&serialFIFO, SerialBT.read()); } @@ -1284,6 +1311,10 @@ void buffer_serial() { fifo_push(&serialFIFO, Serial.read()); } } + #else + if (!fifo_isfull(&serialFIFO)) { + fifo_push(&serialFIFO, Serial.read()); + } #endif } diff --git a/ROM.h b/ROM.h index 05c89dc..1f96168 100644 --- a/ROM.h +++ b/ROM.h @@ -70,4 +70,4 @@ #define BT_ENABLE_BYTE 0x73 #define EEPROM_RESERVED 200 -#endif \ No newline at end of file +#endif diff --git a/Utilities.h b/Utilities.h index e76f91c..fe61b7c 100644 --- a/Utilities.h +++ b/Utilities.h @@ -13,9 +13,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include -#include #include "Config.h" + +#if HAS_EEPROM + #include +#elif PLATFORM == PLATFORM_NRF52 + #include "flash_nrf5x.h" + int written_bytes = 0; +#endif +#include #include "LoRa.h" #include "ROM.h" #include "Framing.h" @@ -34,8 +40,10 @@ #include "Power.h" #endif -#if MCU_VARIANT == MCU_ESP32 +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 #include "Device.h" +#endif +#if MCU_VARIANT == MCU_ESP32 #include "soc/rtc_wdt.h" #define ISR_VECT IRAM_ATTR #else @@ -57,6 +65,8 @@ uint8_t boot_vector = 0x00; } #elif MCU_VARIANT == MCU_ESP32 // TODO: Get ESP32 boot flags +#elif MCU_VARIANT == MCU_NRF52 + // TODO: Get NRF52 boot flags #endif #if HAS_NP == true @@ -175,6 +185,13 @@ uint8_t boot_vector = 0x00; void led_tx_on() { digitalWrite(pin_led_tx, HIGH); } void led_tx_off() { digitalWrite(pin_led_tx, LOW); } #endif +#elif MCU_VARIANT == MCU_NRF52 + #if BOARD_MODEL == BOARD_RAK4630 + void led_rx_on() { digitalWrite(pin_led_rx, HIGH); } + void led_rx_off() { digitalWrite(pin_led_rx, LOW); } + void led_tx_on() { digitalWrite(pin_led_tx, HIGH); } + void led_tx_off() { digitalWrite(pin_led_tx, LOW); } + #endif #endif void hard_reset(void) { @@ -185,6 +202,8 @@ void hard_reset(void) { } #elif MCU_VARIANT == MCU_ESP32 ESP.restart(); + #elif MCU_VARIANT == MCU_NRF52 + // currently not possible to restart on this platform #endif } @@ -285,7 +304,7 @@ void led_indicate_warning(int cycles) { } led_rx_off(); } -#elif MCU_VARIANT == MCU_ESP32 +#elif MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 #if HAS_NP == true void led_indicate_info(int cycles) { bool forever = (cycles == 0) ? true : false; @@ -375,6 +394,17 @@ unsigned long led_standby_ticks = 0; unsigned long led_standby_wait = 1768; unsigned long led_notready_wait = 150; #endif + +#elif MCU_VARIANT == MCU_NRF52 + uint8_t led_standby_min = 200; + uint8_t led_standby_max = 255; + uint8_t led_notready_min = 0; + uint8_t led_notready_max = 255; + uint8_t led_notready_value = led_notready_min; + int8_t led_notready_direction = 0; + unsigned long led_notready_ticks = 0; + unsigned long led_standby_wait = 1768; + unsigned long led_notready_wait = 150; #endif unsigned long led_standby_value = led_standby_min; @@ -396,7 +426,7 @@ int8_t led_standby_direction = 0; } } -#elif MCU_VARIANT == MCU_ESP32 +#elif MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 #if HAS_NP == true void led_indicate_standby() { led_standby_ticks++; @@ -504,7 +534,7 @@ int8_t led_standby_direction = 0; led_rx_off(); } } -#elif MCU_VARIANT == MCU_ESP32 +#elif MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 #if HAS_NP == true void led_indicate_not_ready() { led_standby_ticks++; @@ -625,7 +655,11 @@ void kiss_indicate_stat_tx() { } void kiss_indicate_stat_rssi() { - uint8_t packet_rssi_val = (uint8_t)(last_rssi+rssi_offset); + #if MODEM == SX1276 || MODEM == SX1278 + uint8_t packet_rssi_val = (uint8_t)(last_rssi+rssi_offset); + #elif MODEM == SX1262 + uint8_t packet_rssi_val = (uint8_t)(last_rssi); + #endif serial_write(FEND); serial_write(CMD_STAT_RSSI); escaped_serial_write(packet_rssi_val); @@ -799,7 +833,7 @@ void kiss_indicate_fbstate() { serial_write(FEND); } -#if MCU_VARIANT == MCU_ESP32 +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 void kiss_indicate_device_hash() { serial_write(FEND); serial_write(CMD_DEV_HASH); @@ -944,7 +978,7 @@ void setPreamble() { } void updateBitrate() { - #if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 if (radio_online) { lora_symbol_rate = (float)lora_bw/(float)(pow(2, lora_sf)); lora_symbol_time_ms = (1.0/lora_symbol_rate)*1000.0; @@ -1058,8 +1092,21 @@ void promisc_disable() { promisc = false; } +#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + uint8_t eeprom_read(uint32_t mapped_addr) { + uint8_t byte; + void* byte_ptr = &byte; + flash_nrf5x_read(byte_ptr, mapped_addr, 1); + return byte; + } +#endif + bool eeprom_info_locked() { - uint8_t lock_byte = EEPROM.read(eeprom_addr(ADDR_INFO_LOCK)); + #if HAS_EEPROM + uint8_t lock_byte = EEPROM.read(eeprom_addr(ADDR_INFO_LOCK)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t lock_byte = eeprom_read(eeprom_addr(ADDR_INFO_LOCK)); + #endif if (lock_byte == INFO_LOCK_BYTE) { return true; } else { @@ -1069,21 +1116,33 @@ bool eeprom_info_locked() { void eeprom_dump_info() { for (int addr = ADDR_PRODUCT; addr <= ADDR_INFO_LOCK; addr++) { - uint8_t byte = EEPROM.read(eeprom_addr(addr)); + #if HAS_EEPROM + uint8_t byte = EEPROM.read(eeprom_addr(addr)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t byte = eeprom_read(eeprom_addr(addr)); + #endif escaped_serial_write(byte); } } void eeprom_dump_config() { for (int addr = ADDR_CONF_SF; addr <= ADDR_CONF_OK; addr++) { - uint8_t byte = EEPROM.read(eeprom_addr(addr)); + #if HAS_EEPROM + uint8_t byte = EEPROM.read(eeprom_addr(addr)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t byte = eeprom_read(eeprom_addr(addr)); + #endif escaped_serial_write(byte); } } void eeprom_dump_all() { for (int addr = 0; addr < EEPROM_RESERVED; addr++) { - uint8_t byte = EEPROM.read(eeprom_addr(addr)); + #if HAS_EEPROM + uint8_t byte = EEPROM.read(eeprom_addr(addr)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t byte = eeprom_read(eeprom_addr(addr)); + #endif escaped_serial_write(byte); } } @@ -1103,6 +1162,22 @@ void eeprom_update(int mapped_addr, uint8_t byte) { EEPROM.write(mapped_addr, byte); EEPROM.commit(); } + #elif !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + uint8_t read_byte; + void* read_byte_ptr = &read_byte; + void const * byte_ptr = &byte; + flash_nrf5x_read(read_byte_ptr, mapped_addr, 1); + if (read_byte != byte) { + flash_nrf5x_write(mapped_addr, byte_ptr, 1); + } + + written_bytes++; + + // flush the cache every 4 bytes to make sure everything is synced + if (written_bytes == 4) { + written_bytes = 0; + flash_nrf5x_flush(); + } #endif } @@ -1123,7 +1198,11 @@ void eeprom_erase() { } bool eeprom_lock_set() { - if (EEPROM.read(eeprom_addr(ADDR_INFO_LOCK)) == INFO_LOCK_BYTE) { + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_INFO_LOCK)) == INFO_LOCK_BYTE) { + #elif MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_INFO_LOCK)) == INFO_LOCK_BYTE) { + #endif return true; } else { return false; @@ -1131,12 +1210,18 @@ bool eeprom_lock_set() { } bool eeprom_product_valid() { - uint8_t rval = EEPROM.read(eeprom_addr(ADDR_PRODUCT)); + #if HAS_EEPROM + uint8_t rval = EEPROM.read(eeprom_addr(ADDR_PRODUCT)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t rval = eeprom_read(eeprom_addr(ADDR_PRODUCT)); + #endif #if PLATFORM == PLATFORM_AVR if (rval == PRODUCT_RNODE || rval == PRODUCT_HMBRW) { #elif PLATFORM == PLATFORM_ESP32 if (rval == PRODUCT_RNODE || rval == BOARD_RNODE_NG_20 || rval == BOARD_RNODE_NG_21 || rval == PRODUCT_HMBRW || rval == PRODUCT_TBEAM || rval == PRODUCT_T32_10 || rval == PRODUCT_T32_20 || rval == PRODUCT_T32_21 || rval == PRODUCT_H32_V2) { + #elif PLATFORM == PLATFORM_NRF52 + if (rval == PRODUCT_HMBRW) { #else if (false) { #endif @@ -1147,7 +1232,11 @@ bool eeprom_product_valid() { } bool eeprom_model_valid() { - model = EEPROM.read(eeprom_addr(ADDR_MODEL)); + #if HAS_EEPROM + model = EEPROM.read(eeprom_addr(ADDR_MODEL)); + #elif MCU_VARIANT == MCU_NRF52 + model = eeprom_read(eeprom_addr(ADDR_MODEL)); + #endif #if BOARD_MODEL == BOARD_RNODE if (model == MODEL_A4 || model == MODEL_A9 || model == MODEL_FF || model == MODEL_FE) { #elif BOARD_MODEL == BOARD_RNODE_NG_20 @@ -1166,6 +1255,8 @@ bool eeprom_model_valid() { if (model == MODEL_B4 || model == MODEL_B9) { #elif BOARD_MODEL == BOARD_HELTEC32_V2 if (model == MODEL_C4 || model == MODEL_C9) { + #elif BOARD_MODEL == BOARD_RAK4630 + if (model == MODEL_FF) { #elif BOARD_MODEL == BOARD_HUZZAH32 if (model == MODEL_FF) { #elif BOARD_MODEL == BOARD_GENERIC_ESP32 @@ -1180,7 +1271,11 @@ bool eeprom_model_valid() { } bool eeprom_hwrev_valid() { - hwrev = EEPROM.read(eeprom_addr(ADDR_HW_REV)); + #if HAS_EEPROM + hwrev = EEPROM.read(eeprom_addr(ADDR_HW_REV)); + #elif MCU_VARIANT == MCU_NRF52 + hwrev = eeprom_read(eeprom_addr(ADDR_HW_REV)); + #endif if (hwrev != 0x00 && hwrev != 0xFF) { return true; } else { @@ -1191,14 +1286,22 @@ bool eeprom_hwrev_valid() { bool eeprom_checksum_valid() { char *data = (char*)malloc(CHECKSUMMED_SIZE); for (uint8_t i = 0; i < CHECKSUMMED_SIZE; i++) { - char byte = EEPROM.read(eeprom_addr(i)); + #if HAS_EEPROM + char byte = EEPROM.read(eeprom_addr(i)); + #elif MCU_VARIANT == MCU_NRF52 + char byte = eeprom_read(eeprom_addr(i)); + #endif data[i] = byte; } unsigned char *hash = MD5::make_hash(data, CHECKSUMMED_SIZE); bool checksum_valid = true; for (uint8_t i = 0; i < 16; i++) { - uint8_t stored_chk_byte = EEPROM.read(eeprom_addr(ADDR_CHKSUM+i)); + #if HAS_EEPROM + uint8_t stored_chk_byte = EEPROM.read(eeprom_addr(ADDR_CHKSUM+i)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t stored_chk_byte = eeprom_read(eeprom_addr(ADDR_CHKSUM+i)); + #endif uint8_t calced_chk_byte = (uint8_t)hash[i]; if (stored_chk_byte != calced_chk_byte) { checksum_valid = false; @@ -1227,7 +1330,11 @@ void da_conf_save(uint8_t dadr) { } bool eeprom_have_conf() { - if (EEPROM.read(eeprom_addr(ADDR_CONF_OK)) == CONF_OK_BYTE) { + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_CONF_OK)) == CONF_OK_BYTE) { + #elif MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_CONF_OK)) == CONF_OK_BYTE) { + #endif return true; } else { return false; @@ -1236,11 +1343,19 @@ bool eeprom_have_conf() { void eeprom_conf_load() { if (eeprom_have_conf()) { - lora_sf = EEPROM.read(eeprom_addr(ADDR_CONF_SF)); - lora_cr = EEPROM.read(eeprom_addr(ADDR_CONF_CR)); - lora_txp = EEPROM.read(eeprom_addr(ADDR_CONF_TXP)); - lora_freq = (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x00) << 24 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x01) << 16 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x02) << 8 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x03); - lora_bw = (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x00) << 24 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x01) << 16 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x02) << 8 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x03); + #if HAS_EEPROM + lora_sf = EEPROM.read(eeprom_addr(ADDR_CONF_SF)); + lora_cr = EEPROM.read(eeprom_addr(ADDR_CONF_CR)); + lora_txp = EEPROM.read(eeprom_addr(ADDR_CONF_TXP)); + lora_freq = (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x00) << 24 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x01) << 16 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x02) << 8 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_FREQ)+0x03); + lora_bw = (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x00) << 24 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x01) << 16 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x02) << 8 | (uint32_t)EEPROM.read(eeprom_addr(ADDR_CONF_BW)+0x03); + #elif MCU_VARIANT == MCU_NRF52 + lora_sf = eeprom_read(eeprom_addr(ADDR_CONF_SF)); + lora_cr = eeprom_read(eeprom_addr(ADDR_CONF_CR)); + lora_txp = eeprom_read(eeprom_addr(ADDR_CONF_TXP)); + lora_freq = (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_FREQ)+0x00) << 24 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_FREQ)+0x01) << 16 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_FREQ)+0x02) << 8 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_FREQ)+0x03); + lora_bw = (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_BW)+0x00) << 24 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_BW)+0x01) << 16 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_BW)+0x02) << 8 | (uint32_t)eeprom_read(eeprom_addr(ADDR_CONF_BW)+0x03); + #endif } } @@ -1327,7 +1442,7 @@ inline void fifo_flush(FIFOBuffer *f) { f->head = f->tail; } -#if MCU_VARIANT != MCU_ESP32 +#if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 static inline bool fifo_isempty_locked(const FIFOBuffer *f) { bool result; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { @@ -1409,7 +1524,7 @@ inline void fifo16_flush(FIFOBuffer16 *f) { f->head = f->tail; } -#if MCU_VARIANT != MCU_ESP32 +#if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 static inline bool fifo16_isempty_locked(const FIFOBuffer16 *f) { bool result; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { diff --git a/arduino-cli.yaml b/arduino-cli.yaml index a461917..b7b1622 100644 --- a/arduino-cli.yaml +++ b/arduino-cli.yaml @@ -1,3 +1,4 @@ board_manager: additional_urls: - - https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json \ No newline at end of file + - https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + - https://raw.githubusercontent.com/RAKwireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless_index.json diff --git a/flash_cache.c b/flash_cache.c new file mode 100644 index 0000000..50d69f7 --- /dev/null +++ b/flash_cache.c @@ -0,0 +1,204 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifdef NRF52840_XXAA +#include +#include "flash_cache.h" +#include "common_func.h" +#include "variant.h" +#include "wiring_digital.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +static inline uint32_t page_addr_of (uint32_t addr) +{ + return addr & ~(FLASH_CACHE_SIZE - 1); +} + +static inline uint32_t page_offset_of (uint32_t addr) +{ + return addr & (FLASH_CACHE_SIZE - 1); +} + +int flash_cache_write (flash_cache_t* fc, uint32_t dst, void const * src, uint32_t len) +{ + uint8_t const * src8 = (uint8_t const *) src; + uint32_t remain = len; + + // Program up to page boundary each loop + while ( remain ) + { + uint32_t const page_addr = page_addr_of(dst); + uint32_t const offset = page_offset_of(dst); + + uint32_t wr_bytes = FLASH_CACHE_SIZE - offset; + wr_bytes = min32(remain, wr_bytes); + + // Page changes, flush old and update new cache + if ( page_addr != fc->cache_addr ) + { + flash_cache_flush(fc); + fc->cache_addr = page_addr; + + // read a whole page from flash + fc->read(fc->cache_buf, page_addr, FLASH_CACHE_SIZE); + } + + memcpy(fc->cache_buf + offset, src8, wr_bytes); + + // adjust for next run + src8 += wr_bytes; + remain -= wr_bytes; + dst += wr_bytes; + } + + return len - remain; +} + +void flash_cache_flush (flash_cache_t* fc) +{ + if ( fc->cache_addr == FLASH_CACHE_INVALID_ADDR ) return; + + // skip erase & program if verify() exists, and memory matches + if ( !(fc->verify && fc->verify(fc->cache_addr, fc->cache_buf, FLASH_CACHE_SIZE)) ) + { + // indicator TODO allow to disable flash indicator + ledOn(LED_BUILTIN); + + fc->erase(fc->cache_addr); + fc->program(fc->cache_addr, fc->cache_buf, FLASH_CACHE_SIZE); + + ledOff(LED_BUILTIN); + } + + fc->cache_addr = FLASH_CACHE_INVALID_ADDR; +} + +int flash_cache_read (flash_cache_t* fc, void* dst, uint32_t addr, uint32_t count) +{ + // there is no check for overflow / wraparound for dst + count, addr + count. + // this might be a useful thing to add for at least debug builds. + + // overwrite with cache value if available + if ( (fc->cache_addr != FLASH_CACHE_INVALID_ADDR) && // cache is not valid + !(addr < fc->cache_addr && addr + count <= fc->cache_addr) && // starts before, ends before cache area + !(addr >= fc->cache_addr + FLASH_CACHE_SIZE) ) // starts after end of cache area + { + // This block is entered only when the read overlaps the cache area by at least one byte. + // If the read starts before the cache area, it's further guaranteed + // that count is large enough to cause the read to enter + // the cache area by at least 1 byte. + uint32_t dst_off = 0; + uint32_t src_off = 0; + if (addr < fc->cache_addr) + { + dst_off = fc->cache_addr - addr; + // Read the bytes prior to the cache address + fc->read(dst, addr, dst_off); + } + else + { + src_off = addr - fc->cache_addr; + } + + // Thus, after the above code block executes: + // *** AT MOST ***, only one of src_off and dst_off are non-zero; + // (Both may be zero when the read starts at the start of the cache area.) + // dst_off corresponds to the number of bytes already read from PRIOR to the cache area. + // src_off corresponds to the byte offset to start reading at, from WITHIN the cache area. + + // How many bytes to memcpy from flash area? + // Remember that, AT MOST, one of src_off and dst_off are non-zero. + // If src_off is non-zero, then dst_off is zero, representing that the + // read starts inside the cache. In this case: + // PARAM1 := FLASH_CACHE_SIZE - src_off == maximum possible bytes to read from cache + // PARAM2 := count + // Thus, taking the minimum of the two gives the number of bytes to read from cache, + // in the range [ 1 .. FLASH_CACHE_SIZE-src_off ]. + // Else if dst_off is non-zero, then src_off is zero, representing that the + // read started prior to the cache area. In this case: + // PARAM1 := FLASH_CACHE_SIZE == full size of the cache + // PARAM2 := count - dst_off == total bytes requested, minus the count of those already read + // Because the original request is guaranteed to overlap the cache, the range for + // PARAM2 is ensured to be [ 1 .. count-1 ]. + // Thus, taking the minimum of the two gives the number of bytes to read from cache, + // in the range [ 1 .. FLASH_CACHE_SIZE ] + // Else both src_off and dst_off are zero, representing that the read is starting + // exactly aligned to the cache. + // PARAM1 := FLASH_CACHE_SIZE + // PARAM2 := count + // Thus, taking the minimum of the two gives the number of bytes to read from cache, + // in the range [ 1 .. FLASH_CACHE_SIZE ] + // + // Therefore, in all cases, there is assurance that cache_bytes + // will be in the final range [1..FLASH_CACHE_SIZE]. + uint32_t cache_bytes = minof(FLASH_CACHE_SIZE-src_off, count - dst_off); + + // Use memcpy to read cached data into the buffer + // If src_off is non-zero, then dst_off is zero, representing that the + // read starts inside the cache. In this case: + // PARAM1 := dst + // PARAM2 := fc->cache_buf + src_off + // PARAM3 := cache_bytes + // Thus, all works as expected when starting in the midst of the cache. + // Else if dst_off is non-zero, then src_off is zero, representing that the + // read started prior to the cache. In this case: + // PARAM1 := dst + dst_off == destination offset by number of bytes already read + // PARAM2 := fc->cache_buf + // PARAM3 := cache_bytes + // Thus, all works as expected when starting prior to the cache. + // Else both src_off and dst_off are zero, representing that the read is starting + // exactly aligned to the cache. + // PARAM1 := dst + // PARAM2 := fc->cache_buf + // PARAM3 := cache_bytes + // Thus, all works as expected when starting exactly at the cache boundary + // + // Therefore, in all cases, there is assurance that cache_bytes + // will be in the final range [1..FLASH_CACHE_SIZE]. + memcpy(dst + dst_off, fc->cache_buf + src_off, cache_bytes); + + // Read any final bytes from flash + // As noted above, dst_off represents the count of bytes read prior to the cache + // while cache_bytes represents the count of bytes read from the cache; + // This code block is guaranteed to overlap the cache area by at least one byte. + // Thus, copied will correspond to the total bytes already copied, + // and is guaranteed to be in the range [ 1 .. count ]. + uint32_t copied = dst_off + cache_bytes; + + // + if ( copied < count ) + { + fc->read(dst + copied, addr + copied, count - copied); + } + } + else + { + // not using the cache, so just forward to read from flash + fc->read(dst, addr, count); + } + + return (int) count; +} +#endif diff --git a/flash_cache.h b/flash_cache.h new file mode 100644 index 0000000..ff585e5 --- /dev/null +++ b/flash_cache.h @@ -0,0 +1,58 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifdef NRF52840_XXAA +#ifndef FLASH_CACHE_H_ +#define FLASH_CACHE_H_ + +#include +#include + +#define FLASH_CACHE_SIZE 4096 // must be a erasable page size +#define FLASH_CACHE_INVALID_ADDR 0xffffffff + +typedef struct +{ + bool (*erase) (uint32_t addr); + uint32_t (*program) (uint32_t dst, void const * src, uint32_t len); + uint32_t (*read) (void* dst, uint32_t src, uint32_t len); + bool (*verify) (uint32_t addr, void const * buf, uint32_t len); + + uint32_t cache_addr; + uint8_t* cache_buf; +} flash_cache_t; + +#ifdef __cplusplus +extern "C" { +#endif + +int flash_cache_write (flash_cache_t* fc, uint32_t dst, void const *src, uint32_t count); +void flash_cache_flush (flash_cache_t* fc); +int flash_cache_read (flash_cache_t* fc, void* dst, uint32_t addr, uint32_t count); + +#ifdef __cplusplus + } +#endif + +#endif /* FLASH_CACHE_H_ */ +#endif diff --git a/flash_nrf5x.c b/flash_nrf5x.c new file mode 100644 index 0000000..0442a39 --- /dev/null +++ b/flash_nrf5x.c @@ -0,0 +1,179 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifdef NRF52840_XXAA +#include "flash_nrf5x.h" +#include "flash_cache.h" +#include "nrf_sdm.h" +#include "nrf_soc.h" +#include "delay.h" +#include "rtos.h" + + +#ifdef NRF52840_XXAA + #define BOOTLOADER_ADDR 0xF4000 +#else + #define BOOTLOADER_ADDR 0x74000 +#endif + +// defined in linker script +extern uint32_t __flash_arduino_start[]; +//extern uint32_t __flash_arduino_end[]; + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +static SemaphoreHandle_t _sem = NULL; + +void flash_nrf5x_event_cb (uint32_t event) +{ +// if (event != NRF_EVT_FLASH_OPERATION_SUCCESS) LOG_LV1("IFLASH", "Flash op Failed"); + if ( _sem ) xSemaphoreGive(_sem); +} + +// Flash Abstraction Layer +static bool fal_erase (uint32_t addr); +static uint32_t fal_program (uint32_t dst, void const * src, uint32_t len); +static uint32_t fal_read (void* dst, uint32_t src, uint32_t len); +static bool fal_verify (uint32_t addr, void const * buf, uint32_t len); + +static uint8_t _cache_buffer[FLASH_CACHE_SIZE] __attribute__((aligned(4))); + +static flash_cache_t _cache = +{ + .erase = fal_erase, + .program = fal_program, + .read = fal_read, + .verify = fal_verify, + + .cache_addr = FLASH_CACHE_INVALID_ADDR, + .cache_buf = _cache_buffer +}; + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ +void flash_nrf5x_flush (void) +{ + flash_cache_flush(&_cache); +} + +int flash_nrf5x_write (uint32_t dst, void const * src, uint32_t len) +{ + // Softdevice region + VERIFY(dst >= ((uint32_t) __flash_arduino_start), -1); + + // Bootloader region + VERIFY(dst < BOOTLOADER_ADDR, -1); + + return flash_cache_write(&_cache, dst, src, len); +} + +int flash_nrf5x_read (void* dst, uint32_t src, uint32_t len) +{ + return flash_cache_read(&_cache, dst, src, len); +} + +bool flash_nrf5x_erase(uint32_t addr) +{ + return fal_erase(addr); +} + +//--------------------------------------------------------------------+ +// HAL for caching +//--------------------------------------------------------------------+ +static bool fal_erase (uint32_t addr) +{ + // Init semaphore for first call + if ( _sem == NULL ) + { + _sem = xSemaphoreCreateCounting(10, 0); + VERIFY(_sem); + } + + // retry if busy + uint32_t err; + while ( NRF_ERROR_BUSY == (err = sd_flash_page_erase(addr / FLASH_NRF52_PAGE_SIZE)) ) + { + delay(1); + } + VERIFY_STATUS(err, false); + + // wait for async event if SD is enabled + uint8_t sd_en = 0; + (void) sd_softdevice_is_enabled(&sd_en); + + if ( sd_en ) xSemaphoreTake(_sem, portMAX_DELAY); + + return true; +} + +static uint32_t fal_program (uint32_t dst, void const * src, uint32_t len) +{ + // wait for async event if SD is enabled + uint8_t sd_en = 0; + (void) sd_softdevice_is_enabled(&sd_en); + + uint32_t err; + + // Somehow S140 v6.1.1 assert an error when writing a whole page + // https://devzone.nordicsemi.com/f/nordic-q-a/40088/sd_flash_write-cause-nrf_fault_id_sd_assert + // Workaround: write half page at a time. +#if NRF52832_XXAA + while ( NRF_ERROR_BUSY == (err = sd_flash_write((uint32_t*) dst, (uint32_t const *) src, len/4)) ) + { + delay(1); + } + VERIFY_STATUS(err, 0); + + if ( sd_en ) xSemaphoreTake(_sem, portMAX_DELAY); +#else + while ( NRF_ERROR_BUSY == (err = sd_flash_write((uint32_t*) dst, (uint32_t const *) src, len/8)) ) + { + delay(1); + } + VERIFY_STATUS(err, 0); + if ( sd_en ) xSemaphoreTake(_sem, portMAX_DELAY); + + while ( NRF_ERROR_BUSY == (err = sd_flash_write((uint32_t*) (dst+ len/2), (uint32_t const *) (src + len/2), len/8)) ) + { + delay(1); + } + VERIFY_STATUS(err, 0); + if ( sd_en ) xSemaphoreTake(_sem, portMAX_DELAY); +#endif + + return len; +} + +static uint32_t fal_read (void* dst, uint32_t src, uint32_t len) +{ + memcpy(dst, (void*) src, len); + return len; +} + +static bool fal_verify (uint32_t addr, void const * buf, uint32_t len) +{ + return 0 == memcmp((void*) addr, buf, len); +} +#endif diff --git a/flash_nrf5x.h b/flash_nrf5x.h new file mode 100644 index 0000000..138cf9c --- /dev/null +++ b/flash_nrf5x.h @@ -0,0 +1,89 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifdef NRF52840_XXAA +#ifndef FLASH_NRF52_H_ +#define FLASH_NRF52_H_ + +#include "common_inc.h" + +#define FLASH_NRF52_PAGE_SIZE 4096 + +#ifdef __cplusplus + extern "C" { +#endif + +void flash_nrf5x_flush (void); +bool flash_nrf5x_erase(uint32_t addr); + +int flash_nrf5x_write (uint32_t dst, void const * src, uint32_t len); +int flash_nrf5x_read (void* dst, uint32_t src, uint32_t len); + +//--------------------------------------------------------------------+ +// Write helper +//--------------------------------------------------------------------+ +static inline int flash_nrf5x_write8 (uint32_t dst, uint8_t num) +{ + return flash_nrf5x_write(dst, &num, sizeof(num)); +} + +static inline int flash_nrf5x_write16 (uint32_t dst, uint8_t num) +{ + return flash_nrf5x_write(dst, &num, sizeof(num)); +} +static inline int flash_nrf5x_write32 (uint32_t dst, uint8_t num) +{ + return flash_nrf5x_write(dst, &num, sizeof(num)); +} + +//--------------------------------------------------------------------+ +// Read helper +//--------------------------------------------------------------------+ +static inline uint8_t flash_nrf5x_read8 (uint32_t src) +{ + uint8_t num; + flash_nrf5x_read(&num, src, sizeof(num)); + return num; +} + +static inline uint16_t flash_nrf5x_read16 (uint32_t src) +{ + uint16_t num; + flash_nrf5x_read(&num, src, sizeof(num)); + return num; +} + +static inline uint16_t flash_nrf5x_read32 (uint32_t src) +{ + uint32_t num; + flash_nrf5x_read(&num, src, sizeof(num)); + return num; +} + + +#ifdef __cplusplus + } +#endif + +#endif /* FLASH_NRF52_H_ */ +#endif