diff --git a/Boards.h b/Boards.h index 1345081..49791d9 100644 --- a/Boards.h +++ b/Boards.h @@ -104,6 +104,11 @@ #define MODEL_C6 0xC6 // Heltec Mesh Node T114, 470-510 MHz #define MODEL_C7 0xC7 // Heltec Mesh Node T114, 863-928 MHz + #define PRODUCT_VME213 0xC9 // Heltec VisionMaster E213 + #define BOARD_VME213 0x40 + #define MODEL_CD 0xCD // VisionMaster E213, 470-510 MHz + #define MODEL_CE 0xCE // VisionMaster E213, 863-928 MHz + #define PRODUCT_TECHO 0x15 // LilyGO T-Echo devices #define BOARD_TECHO 0x44 #define MODEL_16 0x16 // T-Echo 433 MHz @@ -432,6 +437,64 @@ const int pin_miso = 11; const int pin_sclk = 9; + #elif BOARD_MODEL == BOARD_VME213 + // Heltec VisionMaster E213 with ESP32-S3R8 + SX1262 + 2.13" E-Ink + #define IS_ESP32S3 true + #define HAS_DISPLAY true // 2.13" E-Ink LCMEN2R13EFC1, 250x122 pixels + #define HAS_EINK true // Enable E-Ink specific display code + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU false + #define HAS_CONSOLE true + #define HAS_EEPROM true + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + + const int pin_btn_usr1 = 0; + + // No physical LEDs on VME213, display only + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 35; + const int pin_led_tx = 35; + #else + const int pin_led_rx = -1; + const int pin_led_tx = -1; + #endif + + // SX1262 LoRa radio configuration + #define MODEM SX1262 + #define HAS_TCXO true + const int pin_tcxo_enable = -1; + #define HAS_BUSY true + #define DIO2_AS_RF_SWITCH true + + // SX1262 pins (from Heltec board-config.h) + const int pin_cs = 8; + const int pin_busy = 13; + const int pin_dio = 14; + const int pin_reset = 12; + const int pin_mosi = 10; + const int pin_miso = 11; + const int pin_sclk = 9; + + // E-Ink display pins (HT_ICMEN2R13EFC1 controller) + const int pin_disp_reset = 3; + const int pin_disp_dc = 2; + const int pin_disp_cs = 5; + const int pin_disp_busy = 1; + const int pin_disp_sck = 4; + const int pin_disp_mosi = 6; + const int pin_disp_miso = -1; + + // Vext power control (HIGH=ON, LOW=OFF) + #define Vext GPIO_NUM_18 + + // I2C for sensors (QuickLink) + #define I2C_SDA 39 + #define I2C_SCL 38 + #elif BOARD_MODEL == BOARD_RNODE_NG_20 #define HAS_DISPLAY true #define HAS_BLUETOOTH true diff --git a/Display.h b/Display.h index 83dd394..bea9000 100644 --- a/Display.h +++ b/Display.h @@ -84,6 +84,14 @@ #define DISP_W 128 #define DISP_H 64 #define DISP_ADDR -1 +#elif BOARD_MODEL == BOARD_VME213 + #include "src/LCMEN2R13EFC1.h" + #include "src/einkDetect_VME213.h" + // VME213 E-Ink display: 250x122 pixels + #define DISP_W 250 + #define DISP_H 122 + #define DISP_ADDR -1 + #define VME213_REFRESH_RATIO 10 // 10 FAST : 1 FULL refresh #elif BOARD_MODEL == BOARD_TBEAM_S_V1 #define DISP_RST -1 #define DISP_ADDR 0x3C @@ -121,6 +129,18 @@ uint32_t last_epd_refresh = 0; uint32_t last_epd_full_refresh = 0; #define REFRESH_PERIOD 300000 +#elif BOARD_MODEL == BOARD_VME213 + LCMEN2R13EFC1 vme213_display; + EInkChipType vme213_chip_type = EINK_LCMEN213EFC1; + uint8_t vme213_displayBuffer[4000]; // 250x122 pixels = 4000 bytes + uint32_t last_epd_refresh = 0; + uint32_t last_epd_full_refresh = 0; + uint8_t vme213_fast_refresh_count = 0; + #define REFRESH_PERIOD 300000 + // Compatibility macro + #define display vme213_display + #define SSD1306_BLACK 0 + #define SSD1306_WHITE 1 #else Adafruit_SSD1306 display(DISP_W, DISP_H, &Wire, DISP_RST); #endif @@ -189,6 +209,19 @@ void update_area_positions() { p_as_x = 64; p_as_y = 0; } + #elif BOARD_MODEL == BOARD_VME213 + // VME213: 250x122 landscape - split into two 125x122 areas + if (disp_mode == DISP_MODE_LANDSCAPE) { + p_ad_x = 0; + p_ad_y = 0; + p_as_x = 125; + p_as_y = 0; + } else { + p_ad_x = 0; + p_ad_y = 0; + p_as_x = 0; + p_as_y = 61; + } #else if (disp_mode == DISP_MODE_PORTRAIT) { p_ad_x = 0 * DISPLAY_SCALE; @@ -247,6 +280,58 @@ uint8_t display_contrast = 0x00; } #endif +#if BOARD_MODEL == BOARD_VME213 +// VME213 E-Ink display rendering helpers +void vme213_drawPixel(uint16_t x, uint16_t y, uint8_t color) { + if (x >= DISP_W || y >= DISP_H) return; + uint16_t byteIndex = y * ((DISP_W + 7) / 8) + (x / 8); + uint8_t bitMask = 0x80 >> (x % 8); + + if (color == SSD1306_WHITE) { + vme213_displayBuffer[byteIndex] |= bitMask; // White pixel + } else { + vme213_displayBuffer[byteIndex] &= ~bitMask; // Black pixel + } +} + +void vme213_fillScreen(uint8_t color) { + memset(vme213_displayBuffer, (color == SSD1306_WHITE) ? 0xFF : 0x00, sizeof(vme213_displayBuffer)); +} + +void vme213_drawText(uint16_t x, uint16_t y, const char* text, uint8_t size) { + // Simple text rendering using 5x7 font + // This is a placeholder - full implementation would use Adafruit_GFX font rendering + // For now, just mark text position + for (int i = 0; i < 10; i++) { + vme213_drawPixel(x + i, y, SSD1306_BLACK); + } +} + +void vme213_renderStatus() { + vme213_fillScreen(SSD1306_WHITE); + + // Header area (top 20 pixels) + for (int y = 0; y < 20; y++) { + for (int x = 0; x < DISP_W; x++) { + vme213_drawPixel(x, y, (y % 2 == 0) ? SSD1306_BLACK : SSD1306_WHITE); + } + } + + // Status text placeholder + vme213_drawText(10, 30, "RNode", 2); + + // Signal indicator (simple bars) + if (radio_online) { + for (int i = 0; i < 5; i++) { + int barHeight = 10 + i * 5; + for (int y = 0; y < barHeight; y++) { + vme213_drawPixel(10 + i * 8, 100 - y, SSD1306_BLACK); + } + } + } +} +#endif + bool display_init() { #if HAS_DISPLAY #if BOARD_MODEL == BOARD_RNODE_NG_20 || BOARD_MODEL == BOARD_LORA32_V2_0 @@ -299,6 +384,27 @@ bool display_init() { pinMode(pin_backlight, OUTPUT); analogWrite(pin_backlight, 0); #endif + #elif BOARD_MODEL == BOARD_VME213 + // Enable Vext power (GPIO 18) for peripherals + pinMode(Vext, OUTPUT); + digitalWrite(Vext, HIGH); + delay(100); + + // Detect E-Ink chip type + vme213_chip_type = detectEInkChip(pin_disp_reset, pin_disp_busy); + + // Initialize SPI for E-Ink display + SPI.begin(pin_disp_sck, pin_disp_miso, pin_disp_mosi, pin_disp_cs); + + // Initialize E-Ink driver + vme213_display.begin(&SPI, pin_disp_dc, pin_disp_cs, pin_disp_busy, pin_disp_reset); + + // Clear buffer + memset(vme213_displayBuffer, 0xFF, sizeof(vme213_displayBuffer)); + + // Perform initial FULL refresh to clear display + vme213_display.update(vme213_displayBuffer, LCMEN2R13EFC1::UPDATE_FULL); + vme213_fast_refresh_count = 0; #elif BOARD_MODEL == BOARD_TBEAM_S_V1 Wire.begin(SDA_OLED, SCL_OLED); #elif BOARD_MODEL == BOARD_XIAO_S3 @@ -348,6 +454,9 @@ bool display_init() { #if BOARD_MODEL == BOARD_TECHO // Don't check if display is actually connected if(false) { + #elif BOARD_MODEL == BOARD_VME213 + // VME213 E-Ink display initialization already done, skip connection check + if(false) { #elif BOARD_MODEL == BOARD_TDECK display.init(240, 320); display.setSPISpeed(80e6); @@ -414,6 +523,10 @@ bool display_init() { #elif BOARD_MODEL == BOARD_TECHO disp_mode = DISP_MODE_PORTRAIT; display.setRotation(3); + #elif BOARD_MODEL == BOARD_VME213 + // VME213 E-Ink: 250x122 landscape by default + disp_mode = DISP_MODE_LANDSCAPE; + // No setRotation for VME213 (driver handles orientation) #else disp_mode = DISP_MODE_PORTRAIT; display.setRotation(3); @@ -429,7 +542,7 @@ bool display_init() { stat_area.cp437(true); disp_area.cp437(true); - #if BOARD_MODEL != BOARD_HELTEC_T114 + #if BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_VME213 display.cp437(true); #endif @@ -990,6 +1103,12 @@ void update_display(bool blank = false) { epd_blank(); epd_blanked = true; } + #elif BOARD_MODEL == BOARD_VME213 + if (!epd_blanked) { + vme213_fillScreen(SSD1306_WHITE); + vme213_display.update(vme213_displayBuffer, LCMEN2R13EFC1::UPDATE_FULL); + epd_blanked = true; + } #endif #if BOARD_MODEL == BOARD_HELTEC_T114 @@ -1015,6 +1134,8 @@ void update_display(bool blank = false) { #if BOARD_MODEL == BOARD_HELTEC_T114 display.clear(); + #elif BOARD_MODEL == BOARD_VME213 + // E-Ink buffer will be redrawn completely #elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO display.clearDisplay(); #endif @@ -1027,6 +1148,9 @@ void update_display(bool blank = false) { #if BOARD_MODEL == BOARD_TECHO display.setFullWindow(); display.fillScreen(SSD1306_WHITE); + #elif BOARD_MODEL == BOARD_VME213 + // Render RNode UI to E-Ink buffer + vme213_renderStatus(); #endif update_stat_area(); @@ -1040,6 +1164,24 @@ void update_display(bool blank = false) { last_epd_refresh = millis(); epd_blanked = false; } + #elif BOARD_MODEL == BOARD_VME213 + if (current-last_epd_refresh >= epd_update_interval) { + // Decide refresh type: 10 FAST : 1 FULL + LCMEN2R13EFC1::UpdateType refreshType; + if (vme213_fast_refresh_count >= VME213_REFRESH_RATIO || current-last_epd_full_refresh >= REFRESH_PERIOD) { + refreshType = LCMEN2R13EFC1::UPDATE_FULL; + vme213_fast_refresh_count = 0; + last_epd_full_refresh = millis(); + } else { + refreshType = LCMEN2R13EFC1::UPDATE_FAST; + vme213_fast_refresh_count++; + } + + // Perform E-Ink update + vme213_display.update(vme213_displayBuffer, refreshType); + last_epd_refresh = millis(); + epd_blanked = false; + } #elif BOARD_MODEL != BOARD_TDECK display.display(); #endif diff --git a/Makefile b/Makefile index 758f664..47ab65f 100644 --- a/Makefile +++ b/Makefile @@ -128,6 +128,9 @@ firmware-heltec32_v3: firmware-heltec32_v4: arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x3F\"" +firmware-vme213: + arduino-cli compile --log --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x40\"" + firmware-rnode_ng_20: check_bt_buffers arduino-cli compile --log --fqbn esp32:esp32:ttgo-lora32 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x40\"" @@ -400,6 +403,15 @@ release-heltec32_v4: check_bt_buffers zip --junk-paths ./Release/rnode_firmware_heltec32v4pa.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_heltec32v4pa.boot_app0 build/rnode_firmware_heltec32v4pa.bin build/rnode_firmware_heltec32v4pa.bootloader build/rnode_firmware_heltec32v4pa.partitions rm -r build +release-vme213: + arduino-cli compile --fqbn "esp32:esp32:esp32s3:CDCOnBoot=cdc" -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x40\"" + cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_vme213.boot_app0 + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bin build/rnode_firmware_vme213.bin + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.bootloader.bin build/rnode_firmware_vme213.bootloader + cp build/esp32.esp32.esp32s3/RNode_Firmware.ino.partitions.bin build/rnode_firmware_vme213.partitions + zip --junk-paths ./Release/rnode_firmware_vme213.zip ./Release/esptool/esptool.py ./Release/console_image.bin build/rnode_firmware_vme213.boot_app0 build/rnode_firmware_vme213.bin build/rnode_firmware_vme213.bootloader build/rnode_firmware_vme213.partitions + rm -r build + release-heltec32_v2_extled: check_bt_buffers arduino-cli compile --fqbn esp32:esp32:heltec_wifi_lora_32_V2 -e --build-property "build.partitions=no_ota" --build-property "upload.maximum_size=2097152" --build-property "compiler.cpp.extra_flags=\"-DBOARD_MODEL=0x38\" \"-DEXTERNAL_LEDS=true\"" cp ~/.arduino15/packages/esp32/hardware/esp32/$(ARDUINO_ESP_CORE_VER)/tools/partitions/boot_app0.bin build/rnode_firmware_heltec32v2.boot_app0 diff --git a/RNode_Firmware/BLESerial.cpp b/RNode_Firmware/BLESerial.cpp new file mode 100644 index 0000000..2957755 --- /dev/null +++ b/RNode_Firmware/BLESerial.cpp @@ -0,0 +1,171 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "Boards.h" + +#if PLATFORM != PLATFORM_NRF52 +#if HAS_BLE + +#include "BLESerial.h" + +uint32_t bt_passkey_callback(); +void bt_passkey_notify_callback(uint32_t passkey); +bool bt_security_request_callback(); +void bt_authentication_complete_callback(esp_ble_auth_cmpl_t auth_result); +bool bt_confirm_pin_callback(uint32_t pin); +void bt_connect_callback(BLEServer *server); +void bt_disconnect_callback(BLEServer *server); +bool bt_client_authenticated(); + +uint32_t BLESerial::onPassKeyRequest() { return bt_passkey_callback(); } +void BLESerial::onPassKeyNotify(uint32_t passkey) { bt_passkey_notify_callback(passkey); } +bool BLESerial::onSecurityRequest() { return bt_security_request_callback(); } +void BLESerial::onAuthenticationComplete(esp_ble_auth_cmpl_t auth_result) { bt_authentication_complete_callback(auth_result); } +void BLESerial::onConnect(BLEServer *server) { bt_connect_callback(server); } +void BLESerial::onDisconnect(BLEServer *server) { bt_disconnect_callback(server); ble_server->startAdvertising(); } +bool BLESerial::onConfirmPIN(uint32_t pin) { return bt_confirm_pin_callback(pin); }; +bool BLESerial::connected() { return ble_server->getConnectedCount() > 0; } + +int BLESerial::read() { + int result = this->rx_buffer.pop(); + if (result == '\n') { this->numAvailableLines--; } + return result; +} + +size_t BLESerial::readBytes(uint8_t *buffer, size_t bufferSize) { + int i = 0; + while (i < bufferSize && available()) { buffer[i] = (uint8_t)this->rx_buffer.pop(); i++; } + return i; +} + +int BLESerial::peek() { + if (this->rx_buffer.getLength() == 0) return -1; + return this->rx_buffer.get(0); +} + +int BLESerial::available() { return this->rx_buffer.getLength(); } + +size_t BLESerial::print(const char *str) { + if (ble_server->getConnectedCount() <= 0) return 0; + size_t written = 0; for (size_t i = 0; str[i] != '\0'; i++) { written += this->write(str[i]); } + flush(); + + return written; +} + +size_t BLESerial::write(const uint8_t *buffer, size_t bufferSize) { + if (ble_server->getConnectedCount() <= 0) { return 0; } else { + size_t written = 0; for (int i = 0; i < bufferSize; i++) { written += this->write(buffer[i]); } + flush(); + + return written; + } +} + +size_t BLESerial::write(uint8_t byte) { + if (bt_client_authenticated()) { + if (ble_server->getConnectedCount() <= 0) { return 0; } else { + this->transmitBuffer[this->transmitBufferLength] = byte; + this->transmitBufferLength++; + if (this->transmitBufferLength == maxTransferSize) { flush(); } + return 1; + } + } else { + return 0; + } +} + +void BLESerial::flush() { + if (this->transmitBufferLength > 0) { + TxCharacteristic->setValue(this->transmitBuffer, this->transmitBufferLength); + this->transmitBufferLength = 0; + this->lastFlushTime = millis(); + TxCharacteristic->notify(true); + } +} + +void BLESerial::disconnect() { + if (ble_server->getConnectedCount() > 0) { + uint16_t conn_id = ble_server->getConnId(); + // Serial.printf("Have connected: %d\n", conn_id); + ble_server->disconnect(conn_id); + // Serial.println("Disconnected"); + } else { + // Serial.println("No connected"); + } +} + +void BLESerial::begin(const char *name) { + ConnectedDeviceCount = 0; + BLEDevice::init(name); + + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9); + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9); + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_SCAN ,ESP_PWR_LVL_P9); + + ble_server = BLEDevice::createServer(); + ble_server->setCallbacks(this); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_MITM); + BLEDevice::setSecurityCallbacks(this); + + SetupSerialService(); + this->startAdvertising(); +} + +void BLESerial::startAdvertising() { + ble_adv = BLEDevice::getAdvertising(); + ble_adv->addServiceUUID(BLE_SERIAL_SERVICE_UUID); + ble_adv->setMinPreferred(0x20); + ble_adv->setMaxPreferred(0x40); + ble_adv->setScanResponse(true); + ble_adv->start(); +} + +void BLESerial::stopAdvertising() { + ble_adv = BLEDevice::getAdvertising(); + ble_adv->stop(); +} + +void BLESerial::end() { BLEDevice::deinit(); } + +void BLESerial::onWrite(BLECharacteristic *characteristic) { + if (characteristic->getUUID().toString() == BLE_RX_UUID) { + auto value = characteristic->getValue(); + for (int i = 0; i < value.length(); i++) { rx_buffer.push(value[i]); } + } +} + +void BLESerial::SetupSerialService() { + SerialService = ble_server->createService(BLE_SERIAL_SERVICE_UUID); + + RxCharacteristic = SerialService->createCharacteristic(BLE_RX_UUID, BLECharacteristic::PROPERTY_WRITE); + RxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM); + RxCharacteristic->addDescriptor(new BLE2902()); + RxCharacteristic->setWriteProperty(true); + RxCharacteristic->setCallbacks(this); + + TxCharacteristic = SerialService->createCharacteristic(BLE_TX_UUID, BLECharacteristic::PROPERTY_NOTIFY); + TxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM); + TxCharacteristic->addDescriptor(new BLE2902()); + TxCharacteristic->setNotifyProperty(true); + TxCharacteristic->setReadProperty(true); + + SerialService->start(); +} + +BLESerial::BLESerial() { } + +#endif +#endif \ No newline at end of file diff --git a/RNode_Firmware/BLESerial.h b/RNode_Firmware/BLESerial.h new file mode 100644 index 0000000..f845b56 --- /dev/null +++ b/RNode_Firmware/BLESerial.h @@ -0,0 +1,136 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "Boards.h" + +#if PLATFORM != PLATFORM_NRF52 +#if HAS_BLE + +#include + +#include +#include +#include +#include + +template +class BLEFIFO { +private: + uint8_t buffer[n]; + int head = 0; + int tail = 0; + +public: + void push(uint8_t value) { + buffer[head] = value; + head = (head + 1) % n; + if (head == tail) { tail = (tail + 1) % n; } + } + + int pop() { + if (head == tail) { + return -1; + } else { + uint8_t value = buffer[tail]; + tail = (tail + 1) % n; + return value; + } + } + + void clear() { head = 0; tail = 0; } + + int get(size_t index) { + if (index >= this->getLength()) { + return -1; + } else { + return buffer[(tail + index) % n]; + } + } + + size_t getLength() { + if (head >= tail) { + return head - tail; + } else { + return n - tail + head; + } + } +}; + +#define RX_BUFFER_SIZE 6144 +#define BLE_BUFFER_SIZE 512 // Must fit in max GATT attribute length +#define MIN_MTU 50 + +class BLESerial : public BLECharacteristicCallbacks, public BLEServerCallbacks, public BLESecurityCallbacks, public Stream { +public: + BLESerial(); + + void begin(const char *name); + void end(); + void disconnect(); + void startAdvertising(); + void stopAdvertising(); + void onWrite(BLECharacteristic *characteristic); + int available(); + int peek(); + int read(); + size_t readBytes(uint8_t *buffer, size_t bufferSize); + size_t write(uint8_t byte); + size_t write(const uint8_t *buffer, size_t bufferSize); + size_t print(const char *value); + void flush(); + void onConnect(BLEServer *server); + void onDisconnect(BLEServer *server); + + uint32_t onPassKeyRequest(); + void onPassKeyNotify(uint32_t passkey); + bool onSecurityRequest(); + void onAuthenticationComplete(esp_ble_auth_cmpl_t); + bool onConfirmPIN(uint32_t pin); + + bool connected(); + + BLEServer *ble_server; + BLEAdvertising *ble_adv; + BLEService *SerialService; + BLECharacteristic *TxCharacteristic; + BLECharacteristic *RxCharacteristic; + size_t transmitBufferLength; + unsigned long long lastFlushTime; + +private: + BLESerial(BLESerial const &other) = delete; + void operator=(BLESerial const &other) = delete; + + BLEFIFO rx_buffer; + size_t numAvailableLines; + uint8_t transmitBuffer[BLE_BUFFER_SIZE]; + + int ConnectedDeviceCount; + void SetupSerialService(); + + uint16_t peerMTU; + uint16_t maxTransferSize = BLE_BUFFER_SIZE; + + bool checkMTU(); + + const char *BLE_SERIAL_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"; + const char *BLE_RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; + const char *BLE_TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; + + bool started = false; +}; + +#endif +#endif \ No newline at end of file diff --git a/RNode_Firmware/Bluetooth.h b/RNode_Firmware/Bluetooth.h new file mode 100644 index 0000000..616c4b6 --- /dev/null +++ b/RNode_Firmware/Bluetooth.h @@ -0,0 +1,615 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#if MCU_VARIANT == MCU_ESP32 + +#elif MCU_VARIANT == MCU_NRF52 +#endif + +#if MCU_VARIANT == MCU_ESP32 + #if HAS_BLUETOOTH == true + #include "BluetoothSerial.h" + #include "esp_bt_main.h" + #include "esp_bt_device.h" + BluetoothSerial SerialBT; + #elif HAS_BLE == true + #include "esp_bt_main.h" + #include "esp_bt_device.h" + #include "BLESerial.h" + BLESerial SerialBT; + #endif + +#elif MCU_VARIANT == MCU_NRF52 + #include + #include + #define BLE_RX_BUF 6144 + BLEUart SerialBT(BLE_RX_BUF); + BLEDis bledis; + BLEBas blebas; + bool SerialBT_init = false; +#endif + +#define BT_PAIRING_TIMEOUT 35000 +#define BLE_FLUSH_TIMEOUT 20 +uint32_t bt_pairing_started = 0; + +#define BT_DEV_ADDR_LEN 6 +#define BT_DEV_HASH_LEN 16 +uint8_t dev_bt_mac[BT_DEV_ADDR_LEN]; +char bt_da[BT_DEV_ADDR_LEN]; +char bt_dh[BT_DEV_HASH_LEN]; +char bt_devname[11]; + +#if MCU_VARIANT == MCU_ESP32 + #if HAS_BLUETOOTH == true + + void bt_confirm_pairing(uint32_t numVal) { + bt_ssp_pin = numVal; + kiss_indicate_btpin(); + if (bt_allow_pairing) { + SerialBT.confirmReply(true); + } else { + SerialBT.confirmReply(false); + } + } + + void bt_stop() { + display_unblank(); + if (bt_state != BT_STATE_OFF) { + SerialBT.end(); + bt_allow_pairing = false; + bt_state = BT_STATE_OFF; + } + } + + void bt_start() { + display_unblank(); + if (bt_state == BT_STATE_OFF) { + SerialBT.begin(bt_devname); + bt_state = BT_STATE_ON; + } + } + + void bt_enable_pairing() { + display_unblank(); + if (bt_state == BT_STATE_OFF) bt_start(); + bt_allow_pairing = true; + bt_pairing_started = millis(); + bt_state = BT_STATE_PAIRING; + } + + void bt_disable_pairing() { + display_unblank(); + bt_allow_pairing = false; + bt_ssp_pin = 0; + bt_state = BT_STATE_ON; + } + + void bt_pairing_complete(boolean success) { + display_unblank(); + if (success) { + bt_disable_pairing(); + } else { + bt_ssp_pin = 0; + } + } + + void bt_connection_callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) { + display_unblank(); + if(event == ESP_SPP_SRV_OPEN_EVT) { + bt_state = BT_STATE_CONNECTED; + cable_state = CABLE_STATE_DISCONNECTED; + } + + if(event == ESP_SPP_CLOSE_EVT ){ + bt_state = BT_STATE_ON; + } + } + + bool bt_setup_hw() { + if (!bt_ready) { + if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { + bt_enabled = true; + } else { + bt_enabled = false; + } + if (btStart()) { + if (esp_bluedroid_init() == ESP_OK) { + if (esp_bluedroid_enable() == ESP_OK) { + const uint8_t* bda_ptr = esp_bt_dev_get_address(); + char *data = (char*)malloc(BT_DEV_ADDR_LEN+1); + for (int i = 0; i < BT_DEV_ADDR_LEN; i++) { + data[i] = bda_ptr[i]; + } + data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE)); + unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN); + memcpy(bt_dh, hash, BT_DEV_HASH_LEN); + sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]); + free(data); + + SerialBT.enableSSP(); + SerialBT.onConfirmRequest(bt_confirm_pairing); + SerialBT.onAuthComplete(bt_pairing_complete); + SerialBT.register_callback(bt_connection_callback); + + bt_ready = true; + return true; + + } else { return false; } + } else { return false; } + } else { return false; } + } else { return false; } + } + + bool bt_init() { + bt_state = BT_STATE_OFF; + if (bt_setup_hw()) { + if (bt_enabled && !console_active) bt_start(); + return true; + } else { + return false; + } + } + + void update_bt() { + if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) { + bt_disable_pairing(); + } + } + + #elif HAS_BLE == true + bool bt_setup_hw(); void bt_security_setup(); + BLESecurity *ble_security = new BLESecurity(); + bool ble_authenticated = false; + uint32_t pairing_pin = 0; + + void bt_flush() { if (bt_state == BT_STATE_CONNECTED) { SerialBT.flush(); } } + + void bt_start() { + // Serial.println("BT start"); + display_unblank(); + if (bt_state == BT_STATE_OFF) { + bt_state = BT_STATE_ON; + SerialBT.begin(bt_devname); + SerialBT.setTimeout(10); + } + } + + void bt_stop() { + // Serial.println("BT stop"); + display_unblank(); + if (bt_state != BT_STATE_OFF) { + bt_allow_pairing = false; + bt_state = BT_STATE_OFF; + SerialBT.end(); + } + } + + bool bt_init() { + // Serial.println("BT init"); + bt_state = BT_STATE_OFF; + if (bt_setup_hw()) { + if (bt_enabled && !console_active) bt_start(); + return true; + } else { + return false; + } + } + + void bt_debond_all() { + // Serial.println("Debonding all"); + int dev_num = esp_ble_get_bond_device_num(); + esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + esp_ble_get_bond_device_list(&dev_num, dev_list); + for (int i = 0; i < dev_num; i++) { esp_ble_remove_bond_device(dev_list[i].bd_addr); } + free(dev_list); + } + + void bt_enable_pairing() { + // Serial.println("BT enable pairing"); + display_unblank(); + if (bt_state == BT_STATE_OFF) bt_start(); + + bt_security_setup(); + + bt_allow_pairing = true; + bt_pairing_started = millis(); + bt_state = BT_STATE_PAIRING; + bt_ssp_pin = pairing_pin; + } + + void bt_disable_pairing() { + // Serial.println("BT disable pairing"); + display_unblank(); + bt_allow_pairing = false; + bt_ssp_pin = 0; + bt_state = BT_STATE_ON; + } + + void bt_passkey_notify_callback(uint32_t passkey) { + // Serial.printf("Got passkey notification: %d\n", passkey); + if (bt_allow_pairing) { + bt_ssp_pin = passkey; + bt_pairing_started = millis(); + kiss_indicate_btpin(); + } else { + // Serial.println("Pairing not allowed, re-init"); + SerialBT.disconnect(); + } + } + + bool bt_confirm_pin_callback(uint32_t pin) { + // Serial.printf("Confirm PIN callback: %d\n", pin); + return true; + } + + void bt_update_passkey() { + // Serial.println("Updating passkey"); + pairing_pin = random(899999)+100000; + bt_ssp_pin = pairing_pin; + } + + uint32_t bt_passkey_callback() { + // Serial.println("API passkey request"); + if (pairing_pin == 0) { bt_update_passkey(); } + return pairing_pin; + } + + bool bt_client_authenticated() { + return ble_authenticated; + } + + bool bt_security_request_callback() { + if (bt_allow_pairing) { + // Serial.println("Accepting security request"); + return true; + } else { + // Serial.println("Rejecting security request"); + return false; + } + } + + void bt_authentication_complete_callback(esp_ble_auth_cmpl_t auth_result) { + if (auth_result.success == true) { + // Serial.println("Authentication success"); + ble_authenticated = true; + if (bt_state == BT_STATE_PAIRING) { + // Serial.println("Pairing complete, disconnecting"); + delay(2000); SerialBT.disconnect(); + } else { bt_state = BT_STATE_CONNECTED; } + } else { + // Serial.println("Authentication fail"); + ble_authenticated = false; + bt_state = BT_STATE_ON; + bt_update_passkey(); + bt_security_setup(); + } + bt_allow_pairing = false; + bt_ssp_pin = 0; + } + + void bt_connect_callback(BLEServer *server) { + uint16_t conn_id = server->getConnId(); + // Serial.printf("Connected: %d\n", conn_id); + display_unblank(); + ble_authenticated = false; + if (bt_state != BT_STATE_PAIRING) { bt_state = BT_STATE_CONNECTED; } + cable_state = CABLE_STATE_DISCONNECTED; + } + + void bt_disconnect_callback(BLEServer *server) { + uint16_t conn_id = server->getConnId(); + // Serial.printf("Disconnected: %d\n", conn_id); + display_unblank(); + ble_authenticated = false; + bt_state = BT_STATE_ON; + } + + bool bt_setup_hw() { + // Serial.println("BT setup hw"); + if (!bt_ready) { + if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { + bt_enabled = true; + } else { + bt_enabled = false; + } + if (btStart()) { + if (esp_bluedroid_init() == ESP_OK) { + if (esp_bluedroid_enable() == ESP_OK) { + const uint8_t* bda_ptr = esp_bt_dev_get_address(); + char *data = (char*)malloc(BT_DEV_ADDR_LEN+1); + for (int i = 0; i < BT_DEV_ADDR_LEN; i++) { + data[i] = bda_ptr[i]; + } + data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE)); + unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN); + memcpy(bt_dh, hash, BT_DEV_HASH_LEN); + sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]); + free(data); + + bt_security_setup(); + + bt_ready = true; + return true; + + } else { return false; } + } else { return false; } + } else { return false; } + } else { return false; } + } + + void bt_security_setup() { + // Serial.println("Executing BT security setup"); + if (pairing_pin == 0) { bt_update_passkey(); } + uint32_t passkey = pairing_pin; + // Serial.printf("Passkey is %d\n", passkey); + + uint8_t key_size = 16; + uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + + esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; + uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_ENABLE; + uint8_t oob_support = ESP_BLE_OOB_DISABLE; + + esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT; + + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t)); + } + + void update_bt() { + if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) { + bt_disable_pairing(); + } + if (bt_state == BT_STATE_CONNECTED && millis()-SerialBT.lastFlushTime >= BLE_FLUSH_TIMEOUT) { + if (SerialBT.transmitBufferLength > 0) { + bt_flush(); + } + } + } + #endif + +#elif MCU_VARIANT == MCU_NRF52 + uint32_t pairing_pin = 0; + + uint8_t eeprom_read(uint32_t mapped_addr); + + void bt_stop() { + // Serial.println("BT Stop"); + if (bt_state != BT_STATE_OFF) { + bt_allow_pairing = false; + bt_state = BT_STATE_OFF; + } + } + + void bt_flush() { if (bt_state == BT_STATE_CONNECTED) { SerialBT.flushTXD(); } } + + void bt_disable_pairing() { + // Serial.println("BT Disable pairing"); + bt_allow_pairing = false; + pairing_pin = 0; + bt_ssp_pin = 0; + bt_state = BT_STATE_ON; + } + + void bt_pairing_complete(uint16_t conn_handle, uint8_t auth_status) { + // Serial.println("BT pairing complete"); + BLEConnection* connection = Bluefruit.Connection(conn_handle); + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) { + ble_gap_conn_sec_mode_t security = connection->getSecureMode(); + // Serial.println("Bonding success"); + + // On the NRF52 it is not possible with the Arduino library to reject + // requests from devices with no IO capabilities, which would allow + // bypassing pin entry through pairing using the "just works" mode. + // Therefore, we must check the security level of the connection after + // pairing to ensure "just works" has not been used. If it has, we need + // to disconnect, unpair and delete any bonding information immediately. + // Settings on the SerialBT service should prevent unauthorised access to + // the serial port anyway, but this is still wise to do regardless. + // + // Note: It may be nice to have this done in the BLESecurity class in the + // future, but as it stands right now I'd have to fork the BSP to do + // that, which I don't fancy doing. Impact on security is likely minimal. + // Requires investigation. + + if (security.sm == 1 && security.lv >= 3) { + // Serial.println("Auth level success"); + bt_state = BT_STATE_CONNECTED; + cable_state = CABLE_STATE_DISCONNECTED; + connection->disconnect(); + bt_disable_pairing(); + } else { + // Serial.println("Auth level failure, debonding"); + if (connection->bonded()) { connection->removeBondKey(); } + connection->disconnect(); + bt_disable_pairing(); + } + } else { + // Serial.println("Bonding failure"); + connection->disconnect(); + bt_disable_pairing(); + } + } + + bool bt_passkey_callback(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { + // Serial.println("Passkey callback"); + if (bt_allow_pairing) { + return true; + } + return false; + } + + void bt_connect_callback(uint16_t conn_handle) { + // Serial.println("Connect callback"); + bt_state = BT_STATE_CONNECTED; + cable_state = CABLE_STATE_DISCONNECTED; + + BLEConnection* conn = Bluefruit.Connection(conn_handle); + conn->requestPHY(BLE_GAP_PHY_2MBPS); + conn->requestMtuExchange(512+3); + conn->requestDataLengthUpdate(); + } + + void bt_disconnect_callback(uint16_t conn_handle, uint8_t reason) { + // Serial.println("Disconnect callback"); + if (reason != BLE_GAP_SEC_STATUS_SUCCESS) { + bt_state = BT_STATE_ON; + } + } + + void bt_update_passkey() { + // Serial.println("Update passkey"); + pairing_pin = random(899999)+100000; + bt_ssp_pin = pairing_pin; + } + + uint32_t bt_get_passkey() { + // Serial.println("API passkey request"); + if (pairing_pin == 0) { bt_update_passkey(); } + return pairing_pin; + } + + bool bt_setup_hw() { + // Serial.println("Setup HW"); + if (!bt_ready) { + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { + #else + if (eeprom_read(eeprom_addr(ADDR_CONF_BT)) == BT_ENABLE_BYTE) { + #endif + bt_enabled = true; + } else { + bt_enabled = false; + } + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.autoConnLed(false); + if (Bluefruit.begin()) { + uint32_t pin = bt_get_passkey(); + char pin_char[6]; + sprintf(pin_char,"%lu", pin); + + Bluefruit.setTxPower(8); // Check bluefruit.h for supported values + Bluefruit.Security.setIOCaps(true, false, false); // display, yes; yes / no, no; keyboard, no + // This device is indeed capable of yes / no through the pairing mode + // being set, but I have chosen to set it thus to force the input of the + // pin on the device initiating the pairing. + + Bluefruit.Security.setMITM(true); + Bluefruit.Security.setPairPasskeyCallback(bt_passkey_callback); + Bluefruit.Security.setSecuredCallback(bt_connect_callback); + Bluefruit.Security.setPIN(pin_char); + Bluefruit.Periph.setDisconnectCallback(bt_disconnect_callback); + Bluefruit.Security.setPairCompleteCallback(bt_pairing_complete); + Bluefruit.Periph.setConnInterval(6, 12); // 7.5 - 15 ms + + const ble_gap_addr_t gap_addr = Bluefruit.getAddr(); + char *data = (char*)malloc(BT_DEV_ADDR_LEN+1); + for (int i = 0; i < BT_DEV_ADDR_LEN; i++) { + data[i] = gap_addr.addr[i]; + } + #if HAS_EEPROM + data[BT_DEV_ADDR_LEN] = EEPROM.read(eeprom_addr(ADDR_SIGNATURE)); + #else + data[BT_DEV_ADDR_LEN] = eeprom_read(eeprom_addr(ADDR_SIGNATURE)); + #endif + unsigned char *hash = MD5::make_hash(data, BT_DEV_ADDR_LEN); + memcpy(bt_dh, hash, BT_DEV_HASH_LEN); + sprintf(bt_devname, "RNode %02X%02X", bt_dh[14], bt_dh[15]); + free(data); + + bt_ready = true; + return true; + + } else { return false; } + } else { return false; } + } + + void bt_start() { + // Serial.println("BT Start"); + if (bt_state == BT_STATE_OFF) { + Bluefruit.setName(bt_devname); + bledis.setManufacturer(BLE_MANUFACTURER); + bledis.setModel(BLE_MODEL); + // start device information service + bledis.begin(); + blebas.begin(); + + // Guard to ensure SerialBT service is not duplicated through BT being power cycled + if (!SerialBT_init) { + SerialBT.bufferTXD(true); // enable buffering + + SerialBT.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // enable encryption for BLE serial + SerialBT.begin(); + SerialBT_init = true; + } + + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + + // Include bleuart 128-bit uuid + Bluefruit.Advertising.addService(SerialBT); + + // There is no room for Name in Advertising packet + // Use Scan response for Name + Bluefruit.ScanResponse.addName(); + + Bluefruit.Advertising.start(0); + + bt_state = BT_STATE_ON; + } + } + + bool bt_init() { + // Serial.println("BT init"); + bt_state = BT_STATE_OFF; + if (bt_setup_hw()) { + if (bt_enabled && !console_active) bt_start(); + return true; + } else { + return false; + } + } + + void bt_enable_pairing() { + // Serial.println("BT enable pairing"); + if (bt_state == BT_STATE_OFF) bt_start(); + + uint32_t pin = bt_get_passkey(); + char pin_char[6]; + sprintf(pin_char,"%lu", pin); + Bluefruit.Security.setPIN(pin_char); + + bt_allow_pairing = true; + bt_pairing_started = millis(); + bt_state = BT_STATE_PAIRING; + kiss_indicate_btpin(); + } + + void bt_debond_all() { } + + void update_bt() { + if (bt_allow_pairing && millis()-bt_pairing_started >= BT_PAIRING_TIMEOUT) { + bt_disable_pairing(); + } + } +#endif diff --git a/RNode_Firmware/Boards.h b/RNode_Firmware/Boards.h new file mode 100644 index 0000000..49791d9 --- /dev/null +++ b/RNode_Firmware/Boards.h @@ -0,0 +1,983 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "Modem.h" + +#ifndef BOARDS_H + #define BOARDS_H + + #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 + + // Products, boards and models //// + #define PRODUCT_RNODE 0x03 // RNode devices + #define BOARD_RNODE 0x31 // Original v1.0 RNode + #define MODEL_A4 0xA4 // RNode v1.0, 433 MHz + #define MODEL_A9 0xA9 // RNode v1.0, 868 MHz + + #define BOARD_RNODE_NG_20 0x40 // RNode hardware revision v2.0 + #define MODEL_A3 0xA3 // RNode v2.0, 433 MHz + #define MODEL_A8 0xA8 // RNode v2.0, 868 MHz + + #define BOARD_RNODE_NG_21 0x41 // RNode hardware revision v2.1 + #define MODEL_A2 0xA2 // RNode v2.1, 433 MHz + #define MODEL_A7 0xA7 // RNode v2.1, 868 MHz + + #define BOARD_T3S3 0x42 // T3S3 devices + #define MODEL_A1 0xA1 // T3S3, 433 MHz with SX1268 + #define MODEL_A5 0xA5 // T3S3, 433 MHz with SX1278 + #define MODEL_A6 0xA6 // T3S3, 868 MHz with SX1262 + #define MODEL_AA 0xAA // T3S3, 868 MHz with SX1276 + #define MODEL_AC 0xAC // T3S3, 2.4 GHz with SX1280 and PA + + #define PRODUCT_TBEAM 0xE0 // T-Beam devices + #define BOARD_TBEAM 0x33 + #define MODEL_E4 0xE4 // T-Beam SX1278, 433 Mhz + #define MODEL_E9 0xE9 // T-Beam SX1276, 868 Mhz + #define MODEL_E3 0xE3 // T-Beam SX1268, 433 Mhz + #define MODEL_E8 0xE8 // T-Beam SX1262, 868 Mhz + + #define PRODUCT_TDECK_V1 0xD0 + #define BOARD_TDECK 0x3B + #define MODEL_D4 0xD4 // LilyGO T-Deck, 433 MHz + #define MODEL_D9 0xD9 // LilyGO T-Deck, 868 MHz + + #define PRODUCT_TBEAM_S_V1 0xEA + #define BOARD_TBEAM_S_V1 0x3D + #define MODEL_DB 0xDB // LilyGO T-Beam Supreme, 433 MHz + #define MODEL_DC 0xDC // LilyGO T-Beam Supreme, 868 MHz + + #define PRODUCT_XIAO_S3 0xEB + #define BOARD_XIAO_S3 0x3E + #define MODEL_DE 0xDE // Xiao ESP32S3 with Wio-SX1262 module, 433 MHz + #define MODEL_DD 0xDD // Xiao ESP32S3 with Wio-SX1262 module, 868 MHz + + #define PRODUCT_T32_10 0xB2 + #define BOARD_LORA32_V1_0 0x39 + #define MODEL_BA 0xBA // LilyGO T3 v1.0, 433 MHz + #define MODEL_BB 0xBB // LilyGO T3 v1.0, 868 MHz + + #define PRODUCT_T32_20 0xB0 + #define BOARD_LORA32_V2_0 0x36 + #define MODEL_B3 0xB3 // LilyGO T3 v2.0, 433 MHz + #define MODEL_B8 0xB8 // LilyGO T3 v2.0, 868 MHz + + #define PRODUCT_T32_21 0xB1 + #define BOARD_LORA32_V2_1 0x37 + #define MODEL_B4 0xB4 // LilyGO T3 v2.1, 433 MHz + #define MODEL_B9 0xB9 // LilyGO T3 v2.1, 868 MHz + + #define PRODUCT_H32_V2 0xC0 // Board code 0x38 + #define BOARD_HELTEC32_V2 0x38 + #define MODEL_C4 0xC4 // Heltec Lora32 v2, 433 MHz + #define MODEL_C9 0xC9 // Heltec Lora32 v2, 868 MHz + + #define PRODUCT_H32_V3 0xC1 + #define BOARD_HELTEC32_V3 0x3A + #define MODEL_C5 0xC5 // Heltec Lora32 v3, 433 MHz + #define MODEL_CA 0xCA // Heltec Lora32 v3, 868 MHz + + #define PRODUCT_H32_V4 0xC3 + #define BOARD_HELTEC32_V4 0x3F + #define MODEL_C8 0xC8 // Heltec Lora32 v3, 850-950 MHz, 28dBm + + #define PRODUCT_HELTEC_T114 0xC2 // Heltec Mesh Node T114 + #define BOARD_HELTEC_T114 0x3C + #define MODEL_C6 0xC6 // Heltec Mesh Node T114, 470-510 MHz + #define MODEL_C7 0xC7 // Heltec Mesh Node T114, 863-928 MHz + + #define PRODUCT_VME213 0xC9 // Heltec VisionMaster E213 + #define BOARD_VME213 0x40 + #define MODEL_CD 0xCD // VisionMaster E213, 470-510 MHz + #define MODEL_CE 0xCE // VisionMaster E213, 863-928 MHz + + #define PRODUCT_TECHO 0x15 // LilyGO T-Echo devices + #define BOARD_TECHO 0x44 + #define MODEL_16 0x16 // T-Echo 433 MHz + #define MODEL_17 0x17 // T-Echo 868/915 MHz + + #define PRODUCT_RAK4631 0x10 + #define BOARD_RAK4631 0x51 + #define MODEL_11 0x11 // RAK4631, 433 Mhz + #define MODEL_12 0x12 // RAK4631, 868 Mhz + + #define PRODUCT_HMBRW 0xF0 + #define BOARD_HMBRW 0x32 + #define BOARD_HUZZAH32 0x34 + #define BOARD_GENERIC_ESP32 0x35 + #define BOARD_GENERIC_NRF52 0x50 + #define MODEL_FE 0xFE // Homebrew board, max 17dBm output power + #define MODEL_FF 0xFF // Homebrew board, max 14dBm output power + + #if defined(__AVR_ATmega1284P__) + #define PLATFORM PLATFORM_AVR + #define MCU_VARIANT MCU_1284P + #elif defined(__AVR_ATmega2560__) + #define PLATFORM PLATFORM_AVR + #define MCU_VARIANT MCU_2560 + #elif defined(ESP32) + #define PLATFORM PLATFORM_ESP32 + #define MCU_VARIANT MCU_ESP32 + #elif defined(NRF52840_XXAA) + #include + #define PLATFORM PLATFORM_NRF52 + #define MCU_VARIANT MCU_NRF52 + #else + #error "The firmware cannot be compiled for the selected MCU variant" + #endif + + #ifndef MODEM + #if BOARD_MODEL == BOARD_RAK4631 + #define MODEM SX1262 + #elif BOARD_MODEL == BOARD_GENERIC_NRF52 + #define MODEM SX1262 + #else + #define MODEM SX1276 + #endif + #endif + + #define HAS_DISPLAY false + #define HAS_BLUETOOTH false + #define HAS_BLE false + #define HAS_TCXO false + #define HAS_PMU false + #define HAS_NP false + #define HAS_EEPROM false + #define HAS_INPUT false + #define HAS_SLEEP false + #define HAS_LORA_PA false + #define HAS_LORA_LNA false + #define PIN_DISP_SLEEP -1 + #define VALIDATE_FIRMWARE true + + #if defined(ENABLE_TCXO) + #define HAS_TCXO true + #endif + + #if MCU_VARIANT == MCU_1284P + const int pin_cs = 4; + const int pin_reset = 3; + const int pin_dio = 2; + const int pin_led_rx = 12; + const int pin_led_tx = 13; + + #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 + #define EEPROM_SIZE 4096 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + + #elif MCU_VARIANT == MCU_2560 + const int pin_cs = 5; + const int pin_reset = 4; + const int pin_dio = 2; + const int pin_led_rx = 12; + const int pin_led_tx = 13; + + #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 + #define EEPROM_SIZE 4096 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + + #elif MCU_VARIANT == MCU_ESP32 + + // Board models for ESP32 based builds are + // defined by the build target in the makefile. + // If you are not using make to compile this + // 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 + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 4; + const int pin_reset = 33; + const int pin_dio = 39; + const int pin_led_rx = 14; + const int pin_led_tx = 32; + + #elif BOARD_MODEL == BOARD_TBEAM + #define HAS_DISPLAY true + #define HAS_PMU true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_SD false + #define HAS_EEPROM true + #define I2C_SDA 21 + #define I2C_SCL 22 + #define PMU_IRQ 35 + + #define HAS_INPUT true + const int pin_btn_usr1 = 38; + + const int pin_cs = 18; + const int pin_reset = 23; + const int pin_led_rx = 2; + const int pin_led_tx = 4; + + #if MODEM == SX1262 + #define HAS_TCXO true + #define HAS_BUSY true + #define DIO2_AS_RF_SWITCH true + #define OCP_TUNED 0x18 + const int pin_busy = 32; + const int pin_dio = 33; + const int pin_tcxo_enable = -1; + #else + const int pin_dio = 26; + #endif + + #elif BOARD_MODEL == BOARD_HUZZAH32 + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 4; + const int pin_reset = 33; + const int pin_dio = 39; + const int pin_led_rx = 14; + const int pin_led_tx = 32; + + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 14; + const int pin_dio = 26; + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 25; + const int pin_led_tx = 2; + #else + const int pin_led_rx = 2; + const int pin_led_tx = 2; + #endif + + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 12; + const int pin_dio = 26; + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 2; + const int pin_led_tx = 0; + #else + const int pin_led_rx = 22; + const int pin_led_tx = 22; + #endif + + #elif BOARD_MODEL == BOARD_LORA32_V2_1 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_PMU true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 23; + const int pin_dio = 26; + #if HAS_TCXO == true + const int pin_tcxo_enable = 33; + #endif + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 15; + const int pin_led_tx = 4; + #else + const int pin_led_rx = 25; + const int pin_led_tx = 25; + #endif + + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_EEPROM true + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + + const int pin_btn_usr1 = 0; + + const int pin_cs = 18; + const int pin_reset = 14; + const int pin_dio = 26; + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 36; + const int pin_led_tx = 37; + #else + const int pin_led_rx = 25; + const int pin_led_tx = 25; + #endif + + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + #define IS_ESP32S3 true + #define HAS_DISPLAY true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_CONSOLE true + #define HAS_EEPROM true + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + #define OCP_TUNED 0x18 + + const int pin_btn_usr1 = 0; + + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 13; + const int pin_led_tx = 14; + #else + const int pin_led_rx = 35; + const int pin_led_tx = 35; + #endif + + #define MODEM SX1262 + #define HAS_TCXO true + const int pin_tcxo_enable = -1; + #define HAS_BUSY true + #define DIO2_AS_RF_SWITCH true + + // Following pins are for the SX1262 + const int pin_cs = 8; + const int pin_busy = 13; + const int pin_dio = 14; + const int pin_reset = 12; + const int pin_mosi = 10; + const int pin_miso = 11; + const int pin_sclk = 9; + + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + #define IS_ESP32S3 true + #define HAS_DISPLAY true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_CONSOLE true + #define HAS_EEPROM true + #define HAS_INPUT true + #define HAS_SLEEP true + #define HAS_LORA_PA true + #define HAS_LORA_LNA true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + #define OCP_TUNED 0x18 + #define Vext GPIO_NUM_36 + + const int pin_btn_usr1 = 0; + + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 13; + const int pin_led_tx = 14; + #else + const int pin_led_rx = 35; + const int pin_led_tx = 35; + #endif + + #define MODEM SX1262 + #define HAS_TCXO true + const int pin_tcxo_enable = -1; + #define HAS_BUSY true + #define DIO2_AS_RF_SWITCH true + + #define LORA_LNA_GAIN 17 + #define LORA_PA_GC1109 true + #define LORA_PA_PWR_EN 7 + #define LORA_PA_CSD 2 + #define LORA_PA_CPS 46 + + #define PA_MAX_OUTPUT 28 + #define PA_GAIN_POINTS 22 + #define PA_GAIN_VALUES 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7 + + const int pin_cs = 8; + const int pin_busy = 13; + const int pin_dio = 14; + const int pin_reset = 12; + const int pin_mosi = 10; + const int pin_miso = 11; + const int pin_sclk = 9; + + #elif BOARD_MODEL == BOARD_VME213 + // Heltec VisionMaster E213 with ESP32-S3R8 + SX1262 + 2.13" E-Ink + #define IS_ESP32S3 true + #define HAS_DISPLAY true // 2.13" E-Ink LCMEN2R13EFC1, 250x122 pixels + #define HAS_EINK true // Enable E-Ink specific display code + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU false + #define HAS_CONSOLE true + #define HAS_EEPROM true + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + + const int pin_btn_usr1 = 0; + + // No physical LEDs on VME213, display only + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 35; + const int pin_led_tx = 35; + #else + const int pin_led_rx = -1; + const int pin_led_tx = -1; + #endif + + // SX1262 LoRa radio configuration + #define MODEM SX1262 + #define HAS_TCXO true + const int pin_tcxo_enable = -1; + #define HAS_BUSY true + #define DIO2_AS_RF_SWITCH true + + // SX1262 pins (from Heltec board-config.h) + const int pin_cs = 8; + const int pin_busy = 13; + const int pin_dio = 14; + const int pin_reset = 12; + const int pin_mosi = 10; + const int pin_miso = 11; + const int pin_sclk = 9; + + // E-Ink display pins (HT_ICMEN2R13EFC1 controller) + const int pin_disp_reset = 3; + const int pin_disp_dc = 2; + const int pin_disp_cs = 5; + const int pin_disp_busy = 1; + const int pin_disp_sck = 4; + const int pin_disp_mosi = 6; + const int pin_disp_miso = -1; + + // Vext power control (HIGH=ON, LOW=OFF) + #define Vext GPIO_NUM_18 + + // I2C for sensors (QuickLink) + #define I2C_SDA 39 + #define I2C_SCL 38 + + #elif BOARD_MODEL == BOARD_RNODE_NG_20 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_NP true + #define HAS_CONSOLE true + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 12; + const int pin_dio = 26; + const int pin_np = 4; + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 2; + const int pin_led_tx = 0; + #else + const int pin_led_rx = 22; + const int pin_led_tx = 22; + #endif + #endif + + #elif BOARD_MODEL == BOARD_RNODE_NG_21 + #define HAS_DISPLAY true + #define HAS_BLUETOOTH true + #define HAS_CONSOLE true + #define HAS_PMU true + #define HAS_NP true + #define HAS_SD false + #define HAS_EEPROM true + const int pin_cs = 18; + const int pin_reset = 23; + const int pin_dio = 26; + const int pin_np = 12; + const int pin_dac = 25; + const int pin_adc = 34; + const int SD_MISO = 2; + const int SD_MOSI = 15; + const int SD_CLK = 14; + const int SD_CS = 13; + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 12; + const int pin_led_tx = 4; + #else + const int pin_led_rx = 25; + const int pin_led_tx = 25; + #endif + #endif + + #elif BOARD_MODEL == BOARD_T3S3 + #define IS_ESP32S3 true + #define HAS_DISPLAY true + #define HAS_CONSOLE true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_NP false + #define HAS_SD false + #define HAS_EEPROM true + + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + const int pin_btn_usr1 = 0; + + const int pin_cs = 7; + const int pin_reset = 8; + const int pin_sclk = 5; + const int pin_mosi = 6; + const int pin_miso = 3; + + #if MODEM == SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_BUSY true + #define HAS_TCXO true + const int pin_busy = 34; + const int pin_dio = 33; + const int pin_tcxo_enable = -1; + #elif MODEM == SX1280 + #define CONFIG_QUEUE_SIZE 6144 + #define DIO2_AS_RF_SWITCH false + #define HAS_BUSY true + #define HAS_TCXO true + #define HAS_PA true + const int pa_max_input = 3; + + #define HAS_RF_SWITCH_RX_TX true + const int pin_rxen = 21; + const int pin_txen = 10; + + const int pin_busy = 36; + const int pin_dio = 9; + const int pin_tcxo_enable = -1; + #else + const int pin_dio = 9; + #endif + + const int pin_np = 38; + const int pin_dac = 25; + const int pin_adc = 1; + + const int SD_MISO = 2; + const int SD_MOSI = 11; + const int SD_CLK = 14; + const int SD_CS = 13; + + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 37; + const int pin_led_tx = 37; + #else + const int pin_led_rx = 37; + const int pin_led_tx = 37; + #endif + #endif + + #elif BOARD_MODEL == BOARD_TDECK + #define IS_ESP32S3 true + #define MODEM SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_BUSY true + #define HAS_TCXO true + + #define HAS_DISPLAY false + #define HAS_CONSOLE false + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_NP false + #define HAS_SD false + #define HAS_EEPROM true + + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_0 + #define WAKEUP_LEVEL 0 + + const int pin_poweron = 10; + const int pin_btn_usr1 = 0; + + const int pin_cs = 9; + const int pin_reset = 17; + const int pin_sclk = 40; + const int pin_mosi = 41; + const int pin_miso = 38; + const int pin_tcxo_enable = -1; + const int pin_dio = 45; + const int pin_busy = 13; + + const int SD_MISO = 38; + const int SD_MOSI = 41; + const int SD_CLK = 40; + const int SD_CS = 39; + + const int DISPLAY_DC = 11; + const int DISPLAY_CS = 12; + const int DISPLAY_MISO = 38; + const int DISPLAY_MOSI = 41; + const int DISPLAY_CLK = 40; + const int DISPLAY_BL_PIN = 42; + + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 43; + const int pin_led_tx = 43; + #else + const int pin_led_rx = 43; + const int pin_led_tx = 43; + #endif + #endif + + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + #define IS_ESP32S3 true + #define MODEM SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_BUSY true + #define HAS_TCXO true + #define OCP_TUNED 0x18 + + #define HAS_DISPLAY true + #define HAS_CONSOLE true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_PMU true + #define HAS_NP false + #define HAS_SD false + #define HAS_EEPROM true + + #define HAS_INPUT true + #define HAS_SLEEP false + + #define PMU_IRQ 40 + #define I2C_SCL 41 + #define I2C_SDA 42 + + const int pin_btn_usr1 = 0; + + const int pin_cs = 10; + const int pin_reset = 5; + const int pin_sclk = 12; + const int pin_mosi = 11; + const int pin_miso = 13; + const int pin_tcxo_enable = -1; + const int pin_dio = 1; + const int pin_busy = 4; + + const int SD_MISO = 37; + const int SD_MOSI = 35; + const int SD_CLK = 36; + const int SD_CS = 47; + + const int IMU_CS = 34; + + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 43; + const int pin_led_tx = 43; + #else + const int pin_led_rx = 43; + const int pin_led_tx = 43; + #endif + #endif + + #elif BOARD_MODEL == BOARD_XIAO_S3 + #define IS_ESP32S3 true + #define MODEM SX1262 + #define DIO2_AS_RF_SWITCH true + #define HAS_BUSY true + #define HAS_TCXO true + + #define HAS_DISPLAY false + #define HAS_CONSOLE true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_NP false + #define HAS_SD false + #define HAS_EEPROM true + + #define HAS_INPUT true + #define HAS_SLEEP true + #define PIN_WAKEUP GPIO_NUM_21 + #define WAKEUP_LEVEL 0 + + const int pin_btn_usr1 = 21; + const int pin_cs = 41; + const int pin_reset = 42; + const int pin_sclk = 7; + const int pin_mosi = 9; + const int pin_miso = 8; + const int pin_tcxo_enable = -1; + const int pin_dio = 39; + const int pin_busy = 40; + + #if HAS_NP == false + #if defined(EXTERNAL_LEDS) + const int pin_led_rx = 48; + const int pin_led_tx = 48; + #else + const int pin_led_rx = 48; + const int pin_led_tx = 48; + #endif + #endif + + #else + #error An unsupported ESP32 board was selected. Cannot compile RNode firmware. + #endif + + #elif MCU_VARIANT == MCU_NRF52 + #if BOARD_MODEL == BOARD_RAK4631 + #define HAS_EEPROM false + #define HAS_DISPLAY true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_CONSOLE false + #define HAS_PMU false + #define HAS_NP false + #define HAS_SD false + #define HAS_TCXO true + #define HAS_RF_SWITCH_RX_TX true + #define HAS_BUSY true + #define HAS_INPUT true + #define DIO2_AS_RF_SWITCH true + #define CONFIG_UART_BUFFER_SIZE 6144 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + #define EEPROM_SIZE 296 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + #define BLE_MANUFACTURER "RAK Wireless" + #define BLE_MODEL "RAK4640" + + const int pin_btn_usr1 = 9; + + // Following pins are for the sx1262 + const int pin_rxen = 37; + const int pin_txen = -1; + 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; + const int pin_tcxo_enable = -1; + + #elif BOARD_MODEL == BOARD_TECHO + #define _PINNUM(port, pin) ((port) * 32 + (pin)) + #define MODEM SX1262 + #define HAS_EEPROM false + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_CONSOLE false + #define HAS_PMU true + #define HAS_NP false + #define HAS_SD false + #define HAS_TCXO true + #define HAS_BUSY true + #define HAS_INPUT true + #define HAS_SLEEP true + #define BLE_MANUFACTURER "LilyGO" + #define BLE_MODEL "T-Echo" + + #define HAS_INPUT true + #define EEPROM_SIZE 296 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + + #define CONFIG_UART_BUFFER_SIZE 32768 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + + #define HAS_DISPLAY true + #define HAS_BACKLIGHT true + #define DISPLAY_SCALE 1 + + #define LED_ON LOW + #define LED_OFF HIGH + #define PIN_LED_GREEN _PINNUM(1, 1) + #define PIN_LED_RED _PINNUM(1, 3) + #define PIN_LED_BLUE _PINNUM(0, 14) + #define PIN_VEXT_EN _PINNUM(0, 12) + + const int pin_disp_cs = 30; + const int pin_disp_dc = 28; + const int pin_disp_reset = 2; + const int pin_disp_busy = 3; + const int pin_disp_en = -1; + const int pin_disp_sck = 31; + const int pin_disp_mosi = 29; + const int pin_disp_miso = -1; + const int pin_backlight = 43; + + const int pin_btn_usr1 = _PINNUM(1, 10); + const int pin_btn_touch = _PINNUM(0, 11); + + const int pin_reset = 25; + const int pin_cs = 24; + const int pin_sclk = 19; + const int pin_mosi = 22; + const int pin_miso = 23; + const int pin_busy = 17; + const int pin_dio = 20; + const int pin_tcxo_enable = 21; + const int pin_led_rx = PIN_LED_BLUE; + const int pin_led_tx = PIN_LED_RED; + + #elif BOARD_MODEL == BOARD_HELTEC_T114 + #define MODEM SX1262 + #define HAS_EEPROM false + #define HAS_DISPLAY true + #define HAS_BLUETOOTH false + #define HAS_BLE true + #define HAS_CONSOLE false + #define HAS_PMU true + #define HAS_NP true + #define HAS_SD false + #define HAS_TCXO true + #define HAS_BUSY true + #define HAS_INPUT true + #define HAS_SLEEP true + #define DIO2_AS_RF_SWITCH true + #define CONFIG_UART_BUFFER_SIZE 6144 + #define CONFIG_QUEUE_SIZE 6144 + #define CONFIG_QUEUE_MAX_LENGTH 200 + #define EEPROM_SIZE 296 + #define EEPROM_OFFSET EEPROM_SIZE-EEPROM_RESERVED + #define BLE_MANUFACTURER "Heltec" + #define BLE_MODEL "T114" + + #define PIN_T114_ADC_EN 6 + #define PIN_VEXT_EN 21 + + // LED + #define LED_T114_GREEN 3 + #define PIN_T114_LED 14 + #define NP_M 1 + const int pin_np = PIN_T114_LED; + + // SPI + #define PIN_T114_MOSI 22 + #define PIN_T114_MISO 23 + #define PIN_T114_SCK 19 + #define PIN_T114_SS 24 + + // SX1262 + #define PIN_T114_RST 25 + #define PIN_T114_DIO1 20 + #define PIN_T114_BUSY 17 + + // TFT + #define DISPLAY_SCALE 2 + #define PIN_T114_TFT_MOSI 9 + #define PIN_T114_TFT_MISO 11 // not connected + #define PIN_T114_TFT_SCK 8 + #define PIN_T114_TFT_SS 11 + #define PIN_T114_TFT_DC 12 + #define PIN_T114_TFT_RST 2 + #define PIN_T114_TFT_EN 3 + #define PIN_T114_TFT_BLGT 15 + + // pins for buttons on Heltec T114 + const int pin_btn_usr1 = 42; + + // pins for sx1262 on Heltec T114 + const int pin_reset = PIN_T114_RST; + const int pin_cs = PIN_T114_SS; + const int pin_sclk = PIN_T114_SCK; + const int pin_mosi = PIN_T114_MOSI; + const int pin_miso = PIN_T114_MISO; + const int pin_busy = PIN_T114_BUSY; + const int pin_dio = PIN_T114_DIO1; + const int pin_led_rx = 35; + const int pin_led_tx = 35; + const int pin_tcxo_enable = -1; + + // pins for ST7789 display on Heltec T114 + const int DISPLAY_DC = PIN_T114_TFT_DC; + const int DISPLAY_CS = PIN_T114_TFT_SS; + const int DISPLAY_MISO = PIN_T114_TFT_MISO; + const int DISPLAY_MOSI = PIN_T114_TFT_MOSI; + const int DISPLAY_CLK = PIN_T114_TFT_SCK; + const int DISPLAY_BL_PIN = PIN_T114_TFT_BLGT; + const int DISPLAY_RST = PIN_T114_TFT_RST; + + #else + #error An unsupported nRF board was selected. Cannot compile RNode firmware. + #endif + + #endif + + #ifndef DISPLAY_SCALE + #define DISPLAY_SCALE 1 + #endif + + #ifndef HAS_RF_SWITCH_RX_TX + const int pin_rxen = -1; + const int pin_txen = -1; + #endif + + #ifndef HAS_BUSY + const int pin_busy = -1; + #endif + + #ifndef LED_ON + #define LED_ON HIGH + #endif + + #ifndef LED_OFF + #define LED_OFF LOW + #endif + + #ifndef DIO2_AS_RF_SWITCH + #define DIO2_AS_RF_SWITCH false + #endif + + // Default OCP value if not specified + // in board configuration + #ifndef OCP_TUNED + #define OCP_TUNED 0x18 + #endif + + #ifndef NP_M + #define NP_M 0.15 + #endif + +#endif diff --git a/RNode_Firmware/Config.h b/RNode_Firmware/Config.h new file mode 100644 index 0000000..ca102d0 --- /dev/null +++ b/RNode_Firmware/Config.h @@ -0,0 +1,224 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ROM.h" +#include "Boards.h" + +#ifndef CONFIG_H + #define CONFIG_H + + #define MAJ_VERS 0x01 + #define MIN_VERS 0x54 + + #define MODE_HOST 0x11 + #define MODE_TNC 0x12 + + #define CABLE_STATE_DISCONNECTED 0x00 + #define CABLE_STATE_CONNECTED 0x01 + uint8_t cable_state = CABLE_STATE_DISCONNECTED; + + #define BT_STATE_NA 0xff + #define BT_STATE_OFF 0x00 + #define BT_STATE_ON 0x01 + #define BT_STATE_PAIRING 0x02 + #define BT_STATE_CONNECTED 0x03 + uint8_t bt_state = BT_STATE_NA; + uint32_t bt_ssp_pin = 0; + bool bt_ready = false; + bool bt_enabled = false; + bool bt_allow_pairing = false; + + #define M_FRQ_S 27388122 + #define M_FRQ_R 27388061 + bool console_active = false; + bool modem_installed = false; + + #define MTU 508 + #define SINGLE_MTU 255 + #define HEADER_L 1 + #define MIN_L 1 + #define CMD_L 64 + + bool mw_radio_online = false; + + #define eeprom_addr(a) (a+EEPROM_OFFSET) + + #if (MODEM == SX1262 || MODEM == SX1280) && defined(NRF52840_XXAA) + SPIClass spiModem(NRF_SPIM2, pin_miso, pin_sclk, pin_mosi); + #endif + + // MCU independent configuration parameters + const long serial_baudrate = 115200; + + // SX1276 RSSI offset to get dBm value from + // packet RSSI register + const int rssi_offset = 157; + + // Default LoRa settings + #define PHY_HEADER_LORA_SYMBOLS 20 + #define PHY_CRC_LORA_BITS 16 + #define LORA_PREAMBLE_SYMBOLS_MIN 18 + #define LORA_PREAMBLE_TARGET_MS 24 + #define LORA_PREAMBLE_FAST_DELTA 18 + #define LORA_FAST_THRESHOLD_BPS 30E3 + #define LORA_LIMIT_THRESHOLD_BPS 60E3 + long lora_preamble_symbols = LORA_PREAMBLE_SYMBOLS_MIN; + long lora_preamble_time_ms = 0; + long lora_header_time_ms = 0; + float lora_symbol_time_ms = 0.0; + float lora_symbol_rate = 0.0; + float lora_us_per_byte = 0.0; + bool lora_low_datarate = false; + bool lora_limit_rate = false; + + // CSMA Parameters + #define CSMA_SIFS_MS 0 + #define CSMA_POST_TX_YIELD_SLOTS 3 + #define CSMA_SLOT_MAX_MS 100 + #define CSMA_SLOT_MIN_MS 24 + #define CSMA_SLOT_MIN_FAST_DELTA 18 + #define CSMA_SLOT_SYMBOLS 12 + #define CSMA_CW_BANDS 4 + #define CSMA_CW_MIN 0 + #define CSMA_CW_PER_BAND_WINDOWS 15 + #define CSMA_BAND_1_MAX_AIRTIME 7 + #define CSMA_BAND_N_MIN_AIRTIME 85 + #define CSMA_INFR_THRESHOLD_DB 12 + bool interference_detected = false; + bool avoid_interference = true; + int csma_slot_ms = CSMA_SLOT_MIN_MS; + unsigned long difs_ms = CSMA_SIFS_MS + 2*csma_slot_ms; + unsigned long difs_wait_start = -1; + unsigned long cw_wait_start = -1; + unsigned long cw_wait_target = -1; + unsigned long cw_wait_passed = 0; + int csma_cw = -1; + uint8_t cw_band = 1; + uint8_t cw_min = 0; + uint8_t cw_max = CSMA_CW_PER_BAND_WINDOWS; + + // LoRa settings + int lora_sf = 0; + int lora_cr = 5; + int lora_txp = 0xFF; + uint32_t lora_bw = 0; + uint32_t lora_freq = 0; + uint32_t lora_bitrate = 0; + + // Operational variables + bool radio_locked = true; + bool radio_online = false; + bool community_fw = true; + bool hw_ready = false; + bool radio_error = false; + bool disp_ready = false; + bool pmu_ready = false; + bool promisc = false; + bool implicit = false; + bool memory_low = false; + uint8_t implicit_l = 0; + + uint8_t op_mode = MODE_HOST; + uint8_t model = 0x00; + uint8_t hwrev = 0x00; + + #define NOISE_FLOOR_SAMPLES 64 + int noise_floor = -292; + int current_rssi = -292; + int last_rssi = -292; + uint8_t last_rssi_raw = 0x00; + uint8_t last_snr_raw = 0x80; + uint8_t seq = 0xFF; + uint16_t read_len = 0; + + // Incoming packet buffer + uint8_t pbuf[MTU]; + + // KISS command buffer + uint8_t cmdbuf[CMD_L]; + + // LoRa transmit buffer + uint8_t tbuf[MTU]; + + uint32_t stat_rx = 0; + uint32_t stat_tx = 0; + + #define STATUS_INTERVAL_MS 3 + #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) + #define AIRTIME_LONGTERM 3600 + #define AIRTIME_LONGTERM_MS (AIRTIME_LONGTERM*1000) + #define AIRTIME_BINLEN_MS (STATUS_INTERVAL_MS*DCD_SAMPLES) + #define AIRTIME_BINS ((AIRTIME_LONGTERM*1000)/AIRTIME_BINLEN_MS) + bool util_samples[DCD_SAMPLES]; + uint16_t airtime_bins[AIRTIME_BINS]; + float longterm_bins[AIRTIME_BINS]; + int dcd_sample = 0; + float local_channel_util = 0.0; + float total_channel_util = 0.0; + float longterm_channel_util = 0.0; + float airtime = 0.0; + float longterm_airtime = 0.0; + #define current_airtime_bin(void) (millis()%AIRTIME_LONGTERM_MS)/AIRTIME_BINLEN_MS + #endif + float st_airtime_limit = 0.0; + float lt_airtime_limit = 0.0; + bool airtime_lock = false; + + bool stat_signal_detected = false; + bool stat_signal_synced = false; + bool stat_rx_ongoing = false; + bool dcd = false; + bool dcd_led = false; + bool dcd_waiting = false; + long dcd_wait_until = 0; + uint16_t dcd_count = 0; + uint16_t dcd_threshold = 2; + + uint32_t status_interval_ms = STATUS_INTERVAL_MS; + uint32_t last_status_update = 0; + uint32_t last_dcd = 0; + + // Power management + #define BATTERY_STATE_UNKNOWN 0x00 + #define BATTERY_STATE_DISCHARGING 0x01 + #define BATTERY_STATE_CHARGING 0x02 + #define BATTERY_STATE_CHARGED 0x03 + bool battery_installed = false; + bool battery_indeterminate = false; + bool external_power = false; + bool battery_ready = false; + float battery_voltage = 0.0; + float battery_percent = 0.0; + uint8_t battery_state = 0x00; + uint8_t display_intensity = 0xFF; + uint8_t display_addr = 0xFF; + volatile bool display_updating = false; + bool display_blanking_enabled = false; + bool display_diagnostics = true; + bool device_init_done = false; + bool eeprom_ok = false; + bool firmware_update_mode = false; + bool serial_in_frame = false; + + // Boot flags + #define START_FROM_BOOTLOADER 0x01 + #define START_FROM_POWERON 0x02 + #define START_FROM_BROWNOUT 0x03 + #define START_FROM_JTAG 0x04 + +#endif diff --git a/RNode_Firmware/Console.h b/RNode_Firmware/Console.h new file mode 100644 index 0000000..c59d348 --- /dev/null +++ b/RNode_Firmware/Console.h @@ -0,0 +1,203 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include +#include + +#include "SD.h" +#include "SPI.h" + +#if HAS_SD + SPIClass *spi = NULL; +#endif + + +#if CONFIG_IDF_TARGET_ESP32 +#include "esp32/rom/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32C3 +#include "esp32c3/rom/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/rtc.h" +#else +#error Target CONFIG_IDF_TARGET is not supported +#endif + +WebServer server(80); + +void console_dbg(String msg) { + Serial.print("[Webserver] "); + Serial.println(msg); +} + +bool exists(String path){ + bool yes = false; + File file = SPIFFS.open(path, "r"); + if(!file.isDirectory()){ + yes = true; + } + file.close(); + return yes; +} + +String console_get_content_type(String filename) { + if (server.hasArg("download")) { + return "application/octet-stream"; + } else if (filename.endsWith(".htm")) { + return "text/html"; + } else if (filename.endsWith(".html")) { + return "text/html"; + } else if (filename.endsWith(".css")) { + return "text/css"; + } else if (filename.endsWith(".js")) { + return "application/javascript"; + } else if (filename.endsWith(".png")) { + return "image/png"; + } else if (filename.endsWith(".gif")) { + return "image/gif"; + } else if (filename.endsWith(".jpg")) { + return "image/jpeg"; + } else if (filename.endsWith(".ico")) { + return "image/x-icon"; + } else if (filename.endsWith(".xml")) { + return "text/xml"; + } else if (filename.endsWith(".pdf")) { + return "application/x-pdf"; + } else if (filename.endsWith(".zip")) { + return "application/x-zip"; + } else if (filename.endsWith(".gz")) { + return "application/x-gzip"; + } else if (filename.endsWith(".whl")) { + return "application/octet-stream"; + } + return "text/plain"; +} + +bool console_serve_file(String path) { + console_dbg("Request for: "+path); + if (path.endsWith("/")) { + path += "index.html"; + } + + if (path == "/r/manual/index.html") { + path = "/m.html"; + } + if (path == "/r/manual/Reticulum Manual.pdf") { + path = "/h.html"; + } + + + String content_type = console_get_content_type(path); + String pathWithGz = path + ".gz"; + if (exists(pathWithGz) || exists(path)) { + if (exists(pathWithGz)) { + path += ".gz"; + } + + File file = SPIFFS.open(path, "r"); + console_dbg("Serving file to client"); + server.streamFile(file, content_type); + file.close(); + + console_dbg("File serving done\n"); + return true; + } else { + int spos = pathWithGz.lastIndexOf('/'); + if (spos > 0) { + String remap_path = "/d"; + remap_path.concat(pathWithGz.substring(spos)); + Serial.println(remap_path); + + if (exists(remap_path)) { + File file = SPIFFS.open(remap_path, "r"); + console_dbg("Serving remapped file to client"); + server.streamFile(file, content_type); + console_dbg("Closing file"); + file.close(); + + console_dbg("File serving done\n"); + return true; + } + } + } + + console_dbg("Error: Could not open file for serving\n"); + return false; +} + +void console_register_pages() { + server.onNotFound([]() { + if (!console_serve_file(server.uri())) { + server.send(404, "text/plain", "Not Found"); + } + }); +} + +void console_start() { + Serial.println(""); + console_dbg("Starting Access Point..."); + WiFi.softAP(bt_devname); + delay(150); + IPAddress ip(10, 0, 0, 1); + IPAddress nm(255, 255, 255, 0); + WiFi.softAPConfig(ip, ip, nm); + + if(!SPIFFS.begin(true)){ + console_dbg("Error: Could not mount SPIFFS"); + return; + } else { + console_dbg("SPIFFS Ready"); + } + + #if HAS_SD + spi = new SPIClass(HSPI); + spi->begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS); + if(!SD.begin(SD_CS, *spi)){ + console_dbg("No SD card inserted"); + } else { + uint8_t cardType = SD.cardType(); + if(cardType == CARD_NONE){ + console_dbg("No SD card type"); + } else { + console_dbg("SD Card Type: "); + if(cardType == CARD_MMC){ + console_dbg("MMC"); + } else if(cardType == CARD_SD){ + console_dbg("SDSC"); + } else if(cardType == CARD_SDHC){ + console_dbg("SDHC"); + } else { + console_dbg("UNKNOWN"); + } + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %lluMB\n", cardSize); + } + } + #endif + + console_register_pages(); + server.begin(); + led_indicate_console(); +} + +void console_loop(){ + server.handleClient(); + // Internally, this yields the thread and allows + // other tasks to run. + delay(2); +} \ No newline at end of file diff --git a/RNode_Firmware/Device.h b/RNode_Firmware/Device.h new file mode 100644 index 0000000..e2a1d06 --- /dev/null +++ b/RNode_Firmware/Device.h @@ -0,0 +1,267 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// 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" + +#elif MCU_VARIANT == MCU_NRF52 +#include "Adafruit_nRFCrypto.h" + +// size of chunk to retrieve from flash sector +#define CHUNK_SIZE 128 + +#define END_SECTION_SIZE 256 + +#if defined(NRF52840_XXAA) +// https://learn.adafruit.com/introducing-the-adafruit-nrf52840-feather/hathach-memory-map +// each section follows along from one another, in this order +// this is always at the start of the memory map +#define APPLICATION_START 0x26000 + +#define USER_DATA_START 0xED000 + +#define IMG_SIZE_START 0xFF008 +#endif + +#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); + +#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + void eeprom_flush(); +#endif + +const uint8_t dev_keys [] PROGMEM = { + 0x0f, 0x15, 0x86, 0x74, 0xa0, 0x7d, 0xf2, 0xde, 0x32, 0x11, 0x29, 0xc1, 0x0d, 0xda, 0xcc, 0xc3, + 0xe1, 0x9b, 0xac, 0xf2, 0x27, 0x06, 0xee, 0x89, 0x1f, 0x7a, 0xfc, 0xc3, 0x6a, 0xf5, 0x38, 0x08 +}; + +#define DEV_SIG_LEN 64 +uint8_t dev_sig[DEV_SIG_LEN]; + +#define DEV_KEY_LEN 32 +uint8_t dev_k_prv[DEV_KEY_LEN]; +uint8_t dev_k_pub[DEV_KEY_LEN]; + +#define DEV_HASH_LEN 32 +uint8_t dev_hash[DEV_HASH_LEN]; +uint8_t dev_partition_table_hash[DEV_HASH_LEN]; +uint8_t dev_bootloader_hash[DEV_HASH_LEN]; +uint8_t dev_firmware_hash[DEV_HASH_LEN]; +uint8_t dev_firmware_hash_target[DEV_HASH_LEN]; + +#define EEPROM_SIG_LEN 128 +uint8_t dev_eeprom_signature[EEPROM_SIG_LEN]; + +bool dev_signature_validated = false; +bool fw_signature_validated = true; + +#define DEV_SIG_OFFSET EEPROM_SIZE-EEPROM_RESERVED-DEV_SIG_LEN +#define dev_sig_addr(a) (a+DEV_SIG_OFFSET) + +#define DEV_FWHASH_OFFSET EEPROM_SIZE-EEPROM_RESERVED-DEV_SIG_LEN-DEV_HASH_LEN +#define dev_fwhash_addr(a) (a+DEV_FWHASH_OFFSET) + +bool device_signatures_ok() { + return dev_signature_validated && fw_signature_validated; +} + +void device_validate_signature() { + int n_keys = sizeof(dev_keys)/DEV_KEY_LEN; + bool valid_signature_found = false; + for (int i = 0; i < n_keys; i++) { + memcpy(dev_k_pub, dev_keys+DEV_KEY_LEN*i, DEV_KEY_LEN); + if (Ed25519::verify(dev_sig, dev_k_pub, dev_hash, DEV_HASH_LEN)) { + valid_signature_found = true; + } + } + + if (valid_signature_found) { + dev_signature_validated = true; + } else { + dev_signature_validated = false; + } +} + +void device_save_signature() { + device_validate_signature(); + if (dev_signature_validated) { + for (uint8_t i = 0; i < DEV_SIG_LEN; i++) { + eeprom_update(dev_sig_addr(i), dev_sig[i]); + } + } +} + +void device_load_signature() { + for (uint8_t i = 0; i < DEV_SIG_LEN; 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++) { + #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 + } +} + +void device_save_firmware_hash() { + for (uint8_t i = 0; i < DEV_HASH_LEN; i++) { + eeprom_update(dev_fwhash_addr(i), dev_firmware_hash_target[i]); + } + #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + eeprom_flush(); + #endif + if (!fw_signature_validated) hard_reset(); +} + +#if MCU_VARIANT == MCU_NRF52 +uint32_t retrieve_application_size() { + uint8_t bytes[4]; + memcpy(bytes, (const void*)IMG_SIZE_START, 4); + uint32_t fw_len = bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24; + return fw_len; +} + +void calculate_region_hash(unsigned long long start, unsigned long long end, uint8_t* return_hash) { + // this function calculates the hash digest of a region of memory, + // currently it is only designed to work for the application region + uint8_t chunk[CHUNK_SIZE] = {0}; + + // to store potential last chunk of program + uint8_t chunk_next[CHUNK_SIZE] = {0}; + nRFCrypto_Hash hash; + + hash.begin(CRYS_HASH_SHA256_mode); + + uint8_t size; + + while (start < end ) { + const void* src = (const void*)start; + if (start + CHUNK_SIZE >= end) { + size = end - start; + } + else { + size = CHUNK_SIZE; + } + + memcpy(chunk, src, CHUNK_SIZE); + + hash.update(chunk, size); + + start += CHUNK_SIZE; + } + hash.end(return_hash); +} +#endif + +void device_validate_partitions() { + device_load_firmware_hash(); + #if MCU_VARIANT == MCU_ESP32 + esp_partition_t partition; + partition.address = ESP_PARTITION_TABLE_OFFSET; + partition.size = ESP_PARTITION_TABLE_MAX_LEN; + partition.type = ESP_PARTITION_TYPE_DATA; + esp_partition_get_sha256(&partition, dev_partition_table_hash); + partition.address = ESP_BOOTLOADER_OFFSET; + partition.size = ESP_PARTITION_TABLE_OFFSET; + partition.type = ESP_PARTITION_TYPE_APP; + esp_partition_get_sha256(&partition, dev_bootloader_hash); + esp_partition_get_sha256(esp_ota_get_running_partition(), dev_firmware_hash); + #elif MCU_VARIANT == MCU_NRF52 + // todo, add bootloader, partition table, or softdevice? + calculate_region_hash(APPLICATION_START, APPLICATION_START+retrieve_application_size(), dev_firmware_hash); + #endif + #if VALIDATE_FIRMWARE + for (uint8_t i = 0; i < DEV_HASH_LEN; i++) { + if (dev_firmware_hash_target[i] != dev_firmware_hash[i]) { + fw_signature_validated = false; + break; + } + } + #endif +} + +bool device_firmware_ok() { + return fw_signature_validated; +} + +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 +bool device_init() { + if (bt_ready) { + #if MCU_VARIANT == MCU_ESP32 + for (uint8_t i=0; i. + +#include "Graphics.h" +#include + +#if BOARD_MODEL != BOARD_TECHO + #if BOARD_MODEL == BOARD_TDECK + #include + #elif BOARD_MODEL == BOARD_HELTEC_T114 + #include "ST7789.h" + #define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)) + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + #include + #else + #include + #include + #endif + +#else + void (*display_callback)(); + void display_add_callback(void (*callback)()) { display_callback = callback; } + void busyCallback(const void* p) { display_callback(); } + #define SSD1306_BLACK GxEPD_BLACK + #define SSD1306_WHITE GxEPD_WHITE + #include + #include +#endif + +#include "Fonts/Org_01.h" +#define DISP_W 128 +#define DISP_H 64 + +#if BOARD_MODEL == BOARD_RNODE_NG_20 || BOARD_MODEL == BOARD_LORA32_V2_0 + #define DISP_RST -1 + #define DISP_ADDR 0x3C +#elif BOARD_MODEL == BOARD_TBEAM + #define DISP_RST 13 + #define DISP_ADDR 0x3C + #define DISP_CUSTOM_ADDR true +#elif BOARD_MODEL == BOARD_HELTEC32_V2 || BOARD_MODEL == BOARD_LORA32_V1_0 + #define DISP_RST 16 + #define DISP_ADDR 0x3C + #define SCL_OLED 15 + #define SDA_OLED 4 +#elif BOARD_MODEL == BOARD_HELTEC32_V3 + #define DISP_RST 21 + #define DISP_ADDR 0x3C + #define SCL_OLED 18 + #define SDA_OLED 17 +#elif BOARD_MODEL == BOARD_HELTEC32_V4 + #define DISP_RST 21 + #define DISP_ADDR 0x3C + #define SCL_OLED 18 + #define SDA_OLED 17 +#elif BOARD_MODEL == BOARD_RAK4631 + // RAK1921/SSD1306 + #define DISP_RST -1 + #define DISP_ADDR 0x3C + #define SCL_OLED 14 + #define SDA_OLED 13 +#elif BOARD_MODEL == BOARD_RNODE_NG_21 + #define DISP_RST -1 + #define DISP_ADDR 0x3C +#elif BOARD_MODEL == BOARD_T3S3 + #define DISP_RST 21 + #define DISP_ADDR 0x3C + #define SCL_OLED 17 + #define SDA_OLED 18 +#elif BOARD_MODEL == BOARD_TECHO + SPIClass displaySPI = SPIClass(NRF_SPIM0, pin_disp_miso, pin_disp_sck, pin_disp_mosi); + #define DISP_W 128 + #define DISP_H 64 + #define DISP_ADDR -1 +#elif BOARD_MODEL == BOARD_VME213 + #include "src/LCMEN2R13EFC1.h" + #include "src/einkDetect_VME213.h" + // VME213 E-Ink display: 250x122 pixels + #define DISP_W 250 + #define DISP_H 122 + #define DISP_ADDR -1 + #define VME213_REFRESH_RATIO 10 // 10 FAST : 1 FULL refresh +#elif BOARD_MODEL == BOARD_TBEAM_S_V1 + #define DISP_RST -1 + #define DISP_ADDR 0x3C + #define SCL_OLED 18 + #define SDA_OLED 17 + #define DISP_CUSTOM_ADDR false +#elif BOARD_MODEL == BOARD_XIAO_S3 + #define DISP_RST -1 + #define DISP_ADDR 0x3C + #define SCL_OLED 6 + #define SDA_OLED 5 + #define DISP_CUSTOM_ADDR true +#else + #define DISP_RST -1 + #define DISP_ADDR 0x3C + #define DISP_CUSTOM_ADDR true +#endif + +#define SMALL_FONT &Org_01 + +#if BOARD_MODEL == BOARD_TDECK + Adafruit_ST7789 display = Adafruit_ST7789(DISPLAY_CS, DISPLAY_DC, -1); + #define SSD1306_WHITE ST77XX_WHITE + #define SSD1306_BLACK ST77XX_BLACK +#elif BOARD_MODEL == BOARD_HELTEC_T114 + ST7789Spi display(&SPI1, DISPLAY_RST, DISPLAY_DC, DISPLAY_CS); + #define SSD1306_WHITE ST77XX_WHITE + #define SSD1306_BLACK ST77XX_BLACK +#elif BOARD_MODEL == BOARD_TBEAM_S_V1 + Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire, -1); + #define SSD1306_WHITE SH110X_WHITE + #define SSD1306_BLACK SH110X_BLACK +#elif BOARD_MODEL == BOARD_TECHO + GxEPD2_BW display(GxEPD2_154_D67(pin_disp_cs, pin_disp_dc, pin_disp_reset, pin_disp_busy)); + uint32_t last_epd_refresh = 0; + uint32_t last_epd_full_refresh = 0; + #define REFRESH_PERIOD 300000 +#elif BOARD_MODEL == BOARD_VME213 + LCMEN2R13EFC1 vme213_display; + EInkChipType vme213_chip_type = EINK_LCMEN213EFC1; + uint8_t vme213_displayBuffer[4000]; // 250x122 pixels = 4000 bytes + uint32_t last_epd_refresh = 0; + uint32_t last_epd_full_refresh = 0; + uint8_t vme213_fast_refresh_count = 0; + #define REFRESH_PERIOD 300000 + // Compatibility macro + #define display vme213_display + #define SSD1306_BLACK 0 + #define SSD1306_WHITE 1 +#else + Adafruit_SSD1306 display(DISP_W, DISP_H, &Wire, DISP_RST); +#endif + +float disp_target_fps = 7; +float epd_update_fps = 0.5; + +#define DISP_MODE_UNKNOWN 0x00 +#define DISP_MODE_LANDSCAPE 0x01 +#define DISP_MODE_PORTRAIT 0x02 +#define DISP_PIN_SIZE 6 +#define DISPLAY_BLANKING_TIMEOUT 15*1000 +uint8_t disp_mode = DISP_MODE_UNKNOWN; +uint8_t disp_ext_fb = false; +unsigned char fb[512]; +uint32_t last_disp_update = 0; +uint32_t last_unblank_event = 0; +uint32_t display_blanking_timeout = DISPLAY_BLANKING_TIMEOUT; +uint8_t display_unblank_intensity = display_intensity; +bool display_blanked = false; +bool display_tx = false; +bool recondition_display = false; +int disp_update_interval = 1000/disp_target_fps; +int epd_update_interval = 1000/disp_target_fps; +uint32_t last_page_flip = 0; +int page_interval = 4000; +bool device_signatures_ok(); +bool device_firmware_ok(); + +#define WATERFALL_SIZE 46 +int waterfall[WATERFALL_SIZE]; +int waterfall_head = 0; + +int p_ad_x = 0; +int p_ad_y = 0; +int p_as_x = 0; +int p_as_y = 0; + +GFXcanvas1 stat_area(64, 64); +GFXcanvas1 disp_area(64, 64); + +void fillRect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t colour); + +void update_area_positions() { + #if BOARD_MODEL == BOARD_HELTEC_T114 + if (disp_mode == DISP_MODE_PORTRAIT) { + p_ad_x = 16; + p_ad_y = 64; + p_as_x = 16; + p_as_y = p_ad_y+126; + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + p_ad_x = 0; + p_ad_y = 96; + p_as_x = 126; + p_as_y = p_ad_y; + } + #elif BOARD_MODEL == BOARD_TECHO + if (disp_mode == DISP_MODE_PORTRAIT) { + p_ad_x = 61; + p_ad_y = 36; + p_as_x = 64; + p_as_y = 64+36; + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + p_ad_x = 0; + p_ad_y = 0; + p_as_x = 64; + p_as_y = 0; + } + #elif BOARD_MODEL == BOARD_VME213 + // VME213: 250x122 landscape - split into two 125x122 areas + if (disp_mode == DISP_MODE_LANDSCAPE) { + p_ad_x = 0; + p_ad_y = 0; + p_as_x = 125; + p_as_y = 0; + } else { + p_ad_x = 0; + p_ad_y = 0; + p_as_x = 0; + p_as_y = 61; + } + #else + if (disp_mode == DISP_MODE_PORTRAIT) { + p_ad_x = 0 * DISPLAY_SCALE; + p_ad_y = 0 * DISPLAY_SCALE; + p_as_x = 0 * DISPLAY_SCALE; + p_as_y = 64 * DISPLAY_SCALE; + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + p_ad_x = 0 * DISPLAY_SCALE; + p_ad_y = 0 * DISPLAY_SCALE; + p_as_x = 64 * DISPLAY_SCALE; + p_as_y = 0 * DISPLAY_SCALE; + } + #endif +} + +uint8_t display_contrast = 0x00; +#if BOARD_MODEL == BOARD_TBEAM_S_V1 + void set_contrast(Adafruit_SH1106G *display, uint8_t value) { + } +#elif BOARD_MODEL == BOARD_HELTEC_T114 + void set_contrast(ST7789Spi *display, uint8_t value) { } +#elif BOARD_MODEL == BOARD_TECHO + void set_contrast(void *display, uint8_t value) { + if (value == 0) { analogWrite(pin_backlight, 0); } + else { analogWrite(pin_backlight, value); } + } +#elif BOARD_MODEL == BOARD_TDECK + void set_contrast(Adafruit_ST7789 *display, uint8_t value) { + static uint8_t level = 0; + static uint8_t steps = 16; + if (value > 15) value = 15; + if (value == 0) { + digitalWrite(DISPLAY_BL_PIN, 0); + delay(3); + level = 0; + return; + } + if (level == 0) { + digitalWrite(DISPLAY_BL_PIN, 1); + level = steps; + delayMicroseconds(30); + } + int from = steps - level; + int to = steps - value; + int num = (steps + to - from) % steps; + for (int i = 0; i < num; i++) { + digitalWrite(DISPLAY_BL_PIN, 0); + digitalWrite(DISPLAY_BL_PIN, 1); + } + level = value; + } +#else + void set_contrast(Adafruit_SSD1306 *display, uint8_t contrast) { + display->ssd1306_command(SSD1306_SETCONTRAST); + display->ssd1306_command(contrast); + } +#endif + +#if BOARD_MODEL == BOARD_VME213 +// VME213 E-Ink display rendering helpers +void vme213_drawPixel(uint16_t x, uint16_t y, uint8_t color) { + if (x >= DISP_W || y >= DISP_H) return; + uint16_t byteIndex = y * ((DISP_W + 7) / 8) + (x / 8); + uint8_t bitMask = 0x80 >> (x % 8); + + if (color == SSD1306_WHITE) { + vme213_displayBuffer[byteIndex] |= bitMask; // White pixel + } else { + vme213_displayBuffer[byteIndex] &= ~bitMask; // Black pixel + } +} + +void vme213_fillScreen(uint8_t color) { + memset(vme213_displayBuffer, (color == SSD1306_WHITE) ? 0xFF : 0x00, sizeof(vme213_displayBuffer)); +} + +void vme213_drawText(uint16_t x, uint16_t y, const char* text, uint8_t size) { + // Simple text rendering using 5x7 font + // This is a placeholder - full implementation would use Adafruit_GFX font rendering + // For now, just mark text position + for (int i = 0; i < 10; i++) { + vme213_drawPixel(x + i, y, SSD1306_BLACK); + } +} + +void vme213_renderStatus() { + vme213_fillScreen(SSD1306_WHITE); + + // Header area (top 20 pixels) + for (int y = 0; y < 20; y++) { + for (int x = 0; x < DISP_W; x++) { + vme213_drawPixel(x, y, (y % 2 == 0) ? SSD1306_BLACK : SSD1306_WHITE); + } + } + + // Status text placeholder + vme213_drawText(10, 30, "RNode", 2); + + // Signal indicator (simple bars) + if (radio_online) { + for (int i = 0; i < 5; i++) { + int barHeight = 10 + i * 5; + for (int y = 0; y < barHeight; y++) { + vme213_drawPixel(10 + i * 8, 100 - y, SSD1306_BLACK); + } + } + } +} +#endif + +bool display_init() { + #if HAS_DISPLAY + #if BOARD_MODEL == BOARD_RNODE_NG_20 || BOARD_MODEL == BOARD_LORA32_V2_0 + int pin_display_en = 16; + digitalWrite(pin_display_en, LOW); + delay(50); + digitalWrite(pin_display_en, HIGH); + #elif BOARD_MODEL == BOARD_T3S3 + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + // enable vext / pin 36 + pinMode(Vext, OUTPUT); + digitalWrite(Vext, LOW); + delay(50); + int pin_display_en = 21; + pinMode(pin_display_en, OUTPUT); + digitalWrite(pin_display_en, LOW); + delay(50); + digitalWrite(pin_display_en, HIGH); + delay(50); + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + // enable vext / pin 36 + pinMode(Vext, OUTPUT); + digitalWrite(Vext, LOW); + delay(50); + int pin_display_en = 21; + pinMode(pin_display_en, OUTPUT); + digitalWrite(pin_display_en, LOW); + delay(50); + digitalWrite(pin_display_en, HIGH); + delay(50); + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + int pin_display_en = 16; + digitalWrite(pin_display_en, LOW); + delay(50); + digitalWrite(pin_display_en, HIGH); + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_HELTEC_T114 + pinMode(PIN_T114_TFT_EN, OUTPUT); + digitalWrite(PIN_T114_TFT_EN, LOW); + #elif BOARD_MODEL == BOARD_TECHO + display.init(0, true, 10, false, displaySPI, SPISettings(4000000, MSBFIRST, SPI_MODE0)); + display.setPartialWindow(0, 0, DISP_W, DISP_H); + display.epd2.setBusyCallback(busyCallback); + #if HAS_BACKLIGHT + pinMode(pin_backlight, OUTPUT); + analogWrite(pin_backlight, 0); + #endif + #elif BOARD_MODEL == BOARD_VME213 + // Enable Vext power (GPIO 18) for peripherals + pinMode(Vext, OUTPUT); + digitalWrite(Vext, HIGH); + delay(100); + + // Detect E-Ink chip type + vme213_chip_type = detectEInkChip(pin_disp_reset, pin_disp_busy); + + // Initialize SPI for E-Ink display + SPI.begin(pin_disp_sck, pin_disp_miso, pin_disp_mosi, pin_disp_cs); + + // Initialize E-Ink driver + vme213_display.begin(&SPI, pin_disp_dc, pin_disp_cs, pin_disp_busy, pin_disp_reset); + + // Clear buffer + memset(vme213_displayBuffer, 0xFF, sizeof(vme213_displayBuffer)); + + // Perform initial FULL refresh to clear display + vme213_display.update(vme213_displayBuffer, LCMEN2R13EFC1::UPDATE_FULL); + vme213_fast_refresh_count = 0; + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + Wire.begin(SDA_OLED, SCL_OLED); + #elif BOARD_MODEL == BOARD_XIAO_S3 + Wire.begin(SDA_OLED, SCL_OLED); + #endif + + #if HAS_EEPROM + uint8_t display_rotation = EEPROM.read(eeprom_addr(ADDR_CONF_DROT)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t display_rotation = eeprom_read(eeprom_addr(ADDR_CONF_DROT)); + #endif + if (display_rotation < 0 or display_rotation > 3) display_rotation = 0xFF; + + #if DISP_CUSTOM_ADDR == true + #if HAS_EEPROM + uint8_t display_address = EEPROM.read(eeprom_addr(ADDR_CONF_DADR)); + #elif MCU_VARIANT == MCU_NRF52 + uint8_t display_address = eeprom_read(eeprom_addr(ADDR_CONF_DADR)); + #endif + if (display_address == 0xFF) display_address = DISP_ADDR; + #else + uint8_t display_address = DISP_ADDR; + #endif + + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_CONF_BSET)) == CONF_OK_BYTE) { + uint8_t db_timeout = EEPROM.read(eeprom_addr(ADDR_CONF_DBLK)); + if (db_timeout == 0x00) { + display_blanking_enabled = false; + } else { + display_blanking_enabled = true; + display_blanking_timeout = db_timeout*1000; + } + } + #elif MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_CONF_BSET)) == CONF_OK_BYTE) { + uint8_t db_timeout = eeprom_read(eeprom_addr(ADDR_CONF_DBLK)); + if (db_timeout == 0x00) { + display_blanking_enabled = false; + } else { + display_blanking_enabled = true; + display_blanking_timeout = db_timeout*1000; + } + } + #endif + + #if BOARD_MODEL == BOARD_TECHO + // Don't check if display is actually connected + if(false) { + #elif BOARD_MODEL == BOARD_VME213 + // VME213 E-Ink display initialization already done, skip connection check + if(false) { + #elif BOARD_MODEL == BOARD_TDECK + display.init(240, 320); + display.setSPISpeed(80e6); + #elif BOARD_MODEL == BOARD_HELTEC_T114 + display.init(); + // set white as default pixel colour for Heltec T114 + display.setRGB(COLOR565(0xFF, 0xFF, 0xFF)); + if (false) { + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + if (!display.begin(display_address, true)) { + #else + if (!display.begin(SSD1306_SWITCHCAPVCC, display_address)) { + #endif + return false; + } else { + set_contrast(&display, display_contrast); + if (display_rotation != 0xFF) { + if (display_rotation == 0 || display_rotation == 2) { + disp_mode = DISP_MODE_LANDSCAPE; + } else { + disp_mode = DISP_MODE_PORTRAIT; + } + display.setRotation(display_rotation); + } else { + #if BOARD_MODEL == BOARD_RNODE_NG_20 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_RNODE_NG_21 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_LORA32_V2_1 + disp_mode = DISP_MODE_LANDSCAPE; + display.setRotation(0); + #elif BOARD_MODEL == BOARD_TBEAM + disp_mode = DISP_MODE_LANDSCAPE; + display.setRotation(0); + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_HELTEC_T114 + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(1); + #elif BOARD_MODEL == BOARD_RAK4631 + disp_mode = DISP_MODE_LANDSCAPE; + display.setRotation(0); + #elif BOARD_MODEL == BOARD_TDECK + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_TECHO + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #elif BOARD_MODEL == BOARD_VME213 + // VME213 E-Ink: 250x122 landscape by default + disp_mode = DISP_MODE_LANDSCAPE; + // No setRotation for VME213 (driver handles orientation) + #else + disp_mode = DISP_MODE_PORTRAIT; + display.setRotation(3); + #endif + } + + update_area_positions(); + + for (int i = 0; i < WATERFALL_SIZE; i++) { waterfall[i] = 0; } + + last_page_flip = millis(); + + stat_area.cp437(true); + disp_area.cp437(true); + + #if BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_VME213 + display.cp437(true); + #endif + + #if HAS_EEPROM + display_intensity = EEPROM.read(eeprom_addr(ADDR_CONF_DINT)); + #elif MCU_VARIANT == MCU_NRF52 + display_intensity = eeprom_read(eeprom_addr(ADDR_CONF_DINT)); + #endif + display_unblank_intensity = display_intensity; + + #if BOARD_MODEL == BOARD_TECHO + #if HAS_BACKLIGHT + if (display_intensity == 0) { analogWrite(pin_backlight, 0); } + else { analogWrite(pin_backlight, display_intensity); } + #endif + #endif + + #if BOARD_MODEL == BOARD_TDECK + display.fillScreen(SSD1306_BLACK); + #endif + + #if BOARD_MODEL == BOARD_HELTEC_T114 + // Enable backlight led (display is always black without this) + fillRect(p_ad_x, p_ad_y, 128, 128, SSD1306_BLACK); + fillRect(p_as_x, p_as_y, 128, 128, SSD1306_BLACK); + pinMode(PIN_T114_TFT_BLGT, OUTPUT); + digitalWrite(PIN_T114_TFT_BLGT, LOW); + #endif + + return true; + } + #else + return false; + #endif +} + +// Draws a line on the screen +void drawLine(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t colour) { + #if BOARD_MODEL == BOARD_HELTEC_T114 + if(colour == SSD1306_WHITE){ + display.setColor(WHITE); + } else if(colour == SSD1306_BLACK) { + display.setColor(BLACK); + } + display.drawLine(x, y, width, height); + #else + display.drawLine(x, y, width, height, colour); + #endif +} + +// Draws a filled rectangle on the screen +void fillRect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t colour) { + #if BOARD_MODEL == BOARD_HELTEC_T114 + if(colour == SSD1306_WHITE){ + display.setColor(WHITE); + } else if(colour == SSD1306_BLACK) { + display.setColor(BLACK); + } + display.fillRect(x, y, width, height); + #else + display.fillRect(x, y, width, height, colour); + #endif +} + +// Draws a bitmap to the display and auto scales it based on the boards configured DISPLAY_SCALE +void drawBitmap(int16_t startX, int16_t startY, const uint8_t* bitmap, int16_t bitmapWidth, int16_t bitmapHeight, uint16_t foregroundColour, uint16_t backgroundColour) { + #if DISPLAY_SCALE == 1 + display.drawBitmap(startX, startY, bitmap, bitmapWidth, bitmapHeight, foregroundColour, backgroundColour); + #else + for(int16_t row = 0; row < bitmapHeight; row++){ + for(int16_t col = 0; col < bitmapWidth; col++){ + + // determine index and bitmask + int16_t index = row * ((bitmapWidth + 7) / 8) + (col / 8); + uint8_t bitmask = 1 << (7 - (col % 8)); + + // check if the current pixel is set in the bitmap + if(bitmap[index] & bitmask){ + // draw a scaled rectangle for the foreground pixel + fillRect(startX + col * DISPLAY_SCALE, startY + row * DISPLAY_SCALE, DISPLAY_SCALE, DISPLAY_SCALE, foregroundColour); + } else { + // draw a scaled rectangle for the background pixel + fillRect(startX + col * DISPLAY_SCALE, startY + row * DISPLAY_SCALE, DISPLAY_SCALE, DISPLAY_SCALE, backgroundColour); + } + + } + } + #endif +} + +void draw_cable_icon(int px, int py) { + if (cable_state == CABLE_STATE_DISCONNECTED) { + stat_area.drawBitmap(px, py, bm_cable+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else if (cable_state == CABLE_STATE_CONNECTED) { + stat_area.drawBitmap(px, py, bm_cable+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } +} + +void draw_bt_icon(int px, int py) { + if (bt_state == BT_STATE_OFF) { + stat_area.drawBitmap(px, py, bm_bt+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else if (bt_state == BT_STATE_ON) { + stat_area.drawBitmap(px, py, bm_bt+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else if (bt_state == BT_STATE_PAIRING) { + stat_area.drawBitmap(px, py, bm_bt+2*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else if (bt_state == BT_STATE_CONNECTED) { + stat_area.drawBitmap(px, py, bm_bt+3*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else { + stat_area.drawBitmap(px, py, bm_bt+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } +} + +void draw_lora_icon(int px, int py) { + if (radio_online) { + stat_area.drawBitmap(px, py, bm_rf+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else { + stat_area.drawBitmap(px, py, bm_rf+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } +} + +void draw_mw_icon(int px, int py) { + if (mw_radio_online) { + stat_area.drawBitmap(px, py, bm_rf+3*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } else { + stat_area.drawBitmap(px, py, bm_rf+2*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); + } +} + +uint8_t charge_tick = 0; +void draw_battery_bars(int px, int py) { + if (pmu_ready) { + if (battery_ready) { + if (battery_installed) { + float battery_value = battery_percent; + + // Disable charging state display for now, since + // boards without dedicated PMU are completely + // unreliable for determining actual charging state. + bool disable_charge_status = false; + if (battery_indeterminate && battery_state == BATTERY_STATE_CHARGING) { + disable_charge_status = true; + } + + if (battery_state == BATTERY_STATE_CHARGING && !disable_charge_status) { + float battery_prog = battery_percent; + if (battery_prog > 85) { battery_prog = 84; } + if (charge_tick < battery_prog ) { charge_tick = battery_prog; } + battery_value = charge_tick; + charge_tick += 3; + if (charge_tick > 100) charge_tick = 0; + } + + if (battery_indeterminate && battery_state == BATTERY_STATE_CHARGING && !disable_charge_status) { + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); + } else { + if (battery_state == BATTERY_STATE_CHARGED) { + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); + } else { + // stat_area.fillRect(px, py, 14, 3, SSD1306_BLACK); + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawRect(px-2, py-2, 17, 7, SSD1306_WHITE); + stat_area.drawLine(px+15, py, px+15, py+3, SSD1306_WHITE); + if (battery_value > 7) stat_area.drawLine(px, py, px, py+2, SSD1306_WHITE); + if (battery_value > 20) stat_area.drawLine(px+1*2, py, px+1*2, py+2, SSD1306_WHITE); + if (battery_value > 33) stat_area.drawLine(px+2*2, py, px+2*2, py+2, SSD1306_WHITE); + if (battery_value > 46) stat_area.drawLine(px+3*2, py, px+3*2, py+2, SSD1306_WHITE); + if (battery_value > 59) stat_area.drawLine(px+4*2, py, px+4*2, py+2, SSD1306_WHITE); + if (battery_value > 72) stat_area.drawLine(px+5*2, py, px+5*2, py+2, SSD1306_WHITE); + if (battery_value > 85) stat_area.drawLine(px+6*2, py, px+6*2, py+2, SSD1306_WHITE); + } + } + } else { + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); + } + } + } else { + stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); + stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); + } +} + +#define Q_SNR_STEP 2.0 +#define Q_SNR_MIN_BASE -9.0 +#define Q_SNR_MAX 6.0 +void draw_quality_bars(int px, int py) { + stat_area.fillRect(px, py, 13, 7, SSD1306_BLACK); + if (radio_online) { + signed char t_snr = (signed int)last_snr_raw; + int snr_int = (int)t_snr; + float snr_min = Q_SNR_MIN_BASE-(int)lora_sf*Q_SNR_STEP; + float snr_span = (Q_SNR_MAX-snr_min); + float snr = ((int)snr_int) * 0.25; + float quality = ((snr-snr_min)/(snr_span))*100; + if (quality > 100.0) quality = 100.0; + if (quality < 0.0) quality = 0.0; + + // Serial.printf("Last SNR: %.2f\n, quality: %.2f\n", snr, quality); + if (quality > 0) stat_area.drawLine(px+0*2, py+7, px+0*2, py+6, SSD1306_WHITE); + if (quality > 15) stat_area.drawLine(px+1*2, py+7, px+1*2, py+5, SSD1306_WHITE); + if (quality > 30) stat_area.drawLine(px+2*2, py+7, px+2*2, py+4, SSD1306_WHITE); + if (quality > 45) stat_area.drawLine(px+3*2, py+7, px+3*2, py+3, SSD1306_WHITE); + if (quality > 60) stat_area.drawLine(px+4*2, py+7, px+4*2, py+2, SSD1306_WHITE); + if (quality > 75) stat_area.drawLine(px+5*2, py+7, px+5*2, py+1, SSD1306_WHITE); + if (quality > 90) stat_area.drawLine(px+6*2, py+7, px+6*2, py+0, SSD1306_WHITE); + } +} + +#if MODEM == SX1280 + #define S_RSSI_MIN -105.0 + #define S_RSSI_MAX -65.0 +#else + #define S_RSSI_MIN -135.0 + #define S_RSSI_MAX -75.0 +#endif +#define S_RSSI_SPAN (S_RSSI_MAX-S_RSSI_MIN) +void draw_signal_bars(int px, int py) { + stat_area.fillRect(px, py, 13, 7, SSD1306_BLACK); + + if (radio_online) { + int rssi_val = last_rssi; + if (rssi_val < S_RSSI_MIN) rssi_val = S_RSSI_MIN; + if (rssi_val > S_RSSI_MAX) rssi_val = S_RSSI_MAX; + int signal = ((rssi_val - S_RSSI_MIN)*(1.0/S_RSSI_SPAN))*100.0; + + if (signal > 100.0) signal = 100.0; + if (signal < 0.0) signal = 0.0; + + // Serial.printf("Last SNR: %.2f\n, quality: %.2f\n", snr, quality); + if (signal > 85) stat_area.drawLine(px+0*2, py+7, px+0*2, py+0, SSD1306_WHITE); + if (signal > 72) stat_area.drawLine(px+1*2, py+7, px+1*2, py+1, SSD1306_WHITE); + if (signal > 59) stat_area.drawLine(px+2*2, py+7, px+2*2, py+2, SSD1306_WHITE); + if (signal > 46) stat_area.drawLine(px+3*2, py+7, px+3*2, py+3, SSD1306_WHITE); + if (signal > 33) stat_area.drawLine(px+4*2, py+7, px+4*2, py+4, SSD1306_WHITE); + if (signal > 20) stat_area.drawLine(px+5*2, py+7, px+5*2, py+5, SSD1306_WHITE); + if (signal > 7) stat_area.drawLine(px+6*2, py+7, px+6*2, py+6, SSD1306_WHITE); + } +} + +#if MODEM == SX1280 + #define WF_TX_SIZE 5 +#else + #define WF_TX_SIZE 5 +#endif +#define WF_RSSI_MAX -60 +#define WF_RSSI_MIN -135 +#define WF_RSSI_SPAN (WF_RSSI_MAX-WF_RSSI_MIN) +#define WF_PIXEL_WIDTH 10 +void draw_waterfall(int px, int py) { + int rssi_val = current_rssi; + if (rssi_val < WF_RSSI_MIN) rssi_val = WF_RSSI_MIN; + if (rssi_val > WF_RSSI_MAX) rssi_val = WF_RSSI_MAX; + int rssi_normalised = ((rssi_val - WF_RSSI_MIN)*(1.0/WF_RSSI_SPAN))*WF_PIXEL_WIDTH; + if (display_tx) { + for (uint8_t i = 0; i < WF_TX_SIZE; i++) { + waterfall[waterfall_head++] = -1; + if (waterfall_head >= WATERFALL_SIZE) waterfall_head = 0; + } + display_tx = false; + } else { + waterfall[waterfall_head++] = rssi_normalised; + if (waterfall_head >= WATERFALL_SIZE) waterfall_head = 0; + } + + stat_area.fillRect(px,py,WF_PIXEL_WIDTH, WATERFALL_SIZE, SSD1306_BLACK); + for (int i = 0; i < WATERFALL_SIZE; i++){ + int wi = (waterfall_head+i)%WATERFALL_SIZE; + int ws = waterfall[wi]; + if (ws > 0) { + stat_area.drawLine(px, py+i, px+ws-1, py+i, SSD1306_WHITE); + } else if (ws == -1) { + uint8_t o = i%2; + for (uint8_t ti = 0; ti < WF_PIXEL_WIDTH/2; ti++) { + stat_area.drawPixel(px+ti*2+o, py+i, SSD1306_WHITE); + } + } + } +} + +bool stat_area_intialised = false; +void draw_stat_area() { + if (device_init_done) { + if (!stat_area_intialised) { + stat_area.drawBitmap(0, 0, bm_frame, 64, 64, SSD1306_WHITE, SSD1306_BLACK); + stat_area_intialised = true; + } + + draw_cable_icon(3, 8); + draw_bt_icon(3, 30); + draw_lora_icon(45, 8); + draw_mw_icon(45, 30); + draw_battery_bars(4, 58); + draw_quality_bars(28, 56); + draw_signal_bars(44, 56); + if (radio_online) { + draw_waterfall(27, 4); + } + } +} + +void update_stat_area() { + if (eeprom_ok && !firmware_update_mode && !console_active) { + + draw_stat_area(); + if (disp_mode == DISP_MODE_PORTRAIT) { + drawBitmap(p_as_x, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + drawBitmap(p_as_x+2, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); + if (device_init_done && !disp_ext_fb) drawLine(p_as_x, 0, p_as_x, 64, SSD1306_WHITE); + } + + } else { + if (firmware_update_mode) { + drawBitmap(p_as_x, p_as_y, bm_updating, stat_area.width(), stat_area.height(), SSD1306_BLACK, SSD1306_WHITE); + } else if (console_active && device_init_done) { + drawBitmap(p_as_x, p_as_y, bm_console, stat_area.width(), stat_area.height(), SSD1306_BLACK, SSD1306_WHITE); + if (disp_mode == DISP_MODE_LANDSCAPE) { + drawLine(p_as_x, 0, p_as_x, 64, SSD1306_WHITE); + } + } + } +} + +#define START_PAGE 0 +const uint8_t pages = 3; +uint8_t disp_page = START_PAGE; +extern char bt_devname[11]; +extern char bt_dh[16]; +void draw_disp_area() { + if (!device_init_done || firmware_update_mode) { + uint8_t p_by = 37; + if (disp_mode == DISP_MODE_LANDSCAPE || firmware_update_mode) { + p_by = 18; + disp_area.fillRect(0, 0, disp_area.width(), disp_area.height(), SSD1306_BLACK); + } + if (!device_init_done) disp_area.drawBitmap(0, p_by, bm_boot, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + if (firmware_update_mode) disp_area.drawBitmap(0, p_by, bm_fw_update, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + if (!disp_ext_fb or bt_ssp_pin != 0) { + if (radio_online && display_diagnostics) { + disp_area.fillRect(0,8,disp_area.width(),37, SSD1306_BLACK); disp_area.fillRect(0,37,disp_area.width(),27, SSD1306_WHITE); + disp_area.setFont(SMALL_FONT); disp_area.setTextWrap(false); disp_area.setTextColor(SSD1306_WHITE); disp_area.setTextSize(1); + + disp_area.setCursor(2, 13); + disp_area.print("On"); + disp_area.setCursor(14, 13); + disp_area.print("@"); + disp_area.setCursor(21, 13); + disp_area.printf("%.1fKbps", (float)lora_bitrate/1000.0); + + //disp_area.setCursor(31, 23-1); + disp_area.setCursor(2, 23-1); + disp_area.print("Airtime:"); + + disp_area.setCursor(11, 33-1); + if (total_channel_util < 0.099) { + //disp_area.printf("%.1f%%", total_channel_util*100.0); + disp_area.printf("%.1f%%", airtime*100.0); + } else { + //disp_area.printf("%.0f%%", total_channel_util*100.0); + disp_area.printf("%.0f%%", airtime*100.0); + } + disp_area.drawBitmap(2, 26-1, bm_hg_low, 5, 9, SSD1306_WHITE, SSD1306_BLACK); + + disp_area.setCursor(32+11, 33-1); + if (longterm_channel_util < 0.099) { + //disp_area.printf("%.1f%%", longterm_channel_util*100.0); + disp_area.printf("%.1f%%", longterm_airtime*100.0); + } else { + //disp_area.printf("%.0f%%", longterm_channel_util*100.0); + disp_area.printf("%.0f%%", longterm_airtime*100.0); + } + disp_area.drawBitmap(32+2, 26-1, bm_hg_high, 5, 9, SSD1306_WHITE, SSD1306_BLACK); + + + disp_area.setTextColor(SSD1306_BLACK); + disp_area.setCursor(2, 46); + disp_area.print("Channel"); + disp_area.setCursor(38, 46); + disp_area.print("Load:"); + + disp_area.setCursor(11, 57); + if (total_channel_util < 0.099) { + //disp_area.printf("%.1f%%", airtime*100.0); + disp_area.printf("%.1f%%", total_channel_util*100.0); + } else { + //disp_area.printf("%.0f%%", airtime*100.0); + disp_area.printf("%.0f%%", total_channel_util*100.0); + } + disp_area.drawBitmap(2, 50, bm_hg_low, 5, 9, SSD1306_BLACK, SSD1306_WHITE); + + disp_area.setCursor(32+11, 57); + if (longterm_channel_util < 0.099) { + //disp_area.printf("%.1f%%", longterm_airtime*100.0); + disp_area.printf("%.1f%%", longterm_channel_util*100.0); + } else { + //disp_area.printf("%.0f%%", longterm_airtime*100.0); + disp_area.printf("%.0f%%", longterm_channel_util*100.0); + } + disp_area.drawBitmap(32+2, 50, bm_hg_high, 5, 9, SSD1306_BLACK, SSD1306_WHITE); + + } else { + if (device_signatures_ok()) { disp_area.drawBitmap(0, 0, bm_def_lc, disp_area.width(), 23, SSD1306_WHITE, SSD1306_BLACK); } + else { disp_area.drawBitmap(0, 0, bm_def, disp_area.width(), 23, SSD1306_WHITE, SSD1306_BLACK); } + + disp_area.setFont(SMALL_FONT); disp_area.setTextWrap(false); disp_area.setTextColor(SSD1306_WHITE); disp_area.setTextSize(2); + disp_area.fillRect(0, 20, disp_area.width(), 17, SSD1306_BLACK); uint8_t ofsc = 0; + if ((bt_dh[14] & 0b00001111) == 0x01) { ofsc += 8; } + if ((bt_dh[14] >> 4) == 0x01) { ofsc += 8; } + if ((bt_dh[15] & 0b00001111) == 0x01) { ofsc += 8; } + if ((bt_dh[15] >> 4) == 0x01) { ofsc += 8; } + disp_area.setCursor(17+ofsc, 32); disp_area.printf("%02X%02X", bt_dh[14], bt_dh[15]); + } + + if (!hw_ready || radio_error || !device_firmware_ok()) { + if (!device_firmware_ok()) { + disp_area.drawBitmap(0, 37, bm_fw_corrupt, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + if (!modem_installed) { + disp_area.drawBitmap(0, 37, bm_no_radio, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + disp_area.drawBitmap(0, 37, bm_conf_missing, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } + } + } else if (bt_state == BT_STATE_PAIRING and bt_ssp_pin != 0) { + char *pin_str = (char*)malloc(DISP_PIN_SIZE+1); + sprintf(pin_str, "%06d", bt_ssp_pin); + + disp_area.drawBitmap(0, 37, bm_pairing, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + for (int i = 0; i < DISP_PIN_SIZE; i++) { + uint8_t numeric = pin_str[i]-48; + uint8_t offset = numeric*5; + disp_area.drawBitmap(7+9*i, 37+16, bm_n_uh+offset, 8, 5, SSD1306_WHITE, SSD1306_BLACK); + } + free(pin_str); + } else { + if (millis()-last_page_flip >= page_interval) { + disp_page = (++disp_page%pages); + last_page_flip = millis(); + if (not community_fw and disp_page == 0) disp_page = 1; + } + + if (radio_online) { + if (!display_diagnostics) { + disp_area.drawBitmap(0, 37, bm_online, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } + } else { + if (disp_page == 0) { + if (true || device_signatures_ok()) { + disp_area.drawBitmap(0, 37, bm_checks, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + disp_area.drawBitmap(0, 37, bm_nfr, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } + } else if (disp_page == 1) { + if (!console_active) { + disp_area.drawBitmap(0, 37, bm_hwok, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } else { + disp_area.drawBitmap(0, 37, bm_console_active, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + } + } else if (disp_page == 2) { + disp_area.drawBitmap(0, 37, bm_version, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); + char *v_str = (char*)malloc(3+1); + sprintf(v_str, "%01d%02d", MAJ_VERS, MIN_VERS); + for (int i = 0; i < 3; i++) { + uint8_t numeric = v_str[i]-48; uint8_t bm_offset = numeric*5; + uint8_t dxp = 20; + if (i == 1) dxp += 9*1+4; + if (i == 2) dxp += 9*2+4; + disp_area.drawBitmap(dxp, 37+16, bm_n_uh+bm_offset, 8, 5, SSD1306_WHITE, SSD1306_BLACK); + } + free(v_str); + disp_area.drawLine(27, 37+19, 28, 37+19, SSD1306_BLACK); + disp_area.drawLine(27, 37+20, 28, 37+20, SSD1306_BLACK); + } + } + } + } else { + disp_area.drawBitmap(0, 0, fb, disp_area.width(), disp_area.height(), SSD1306_WHITE, SSD1306_BLACK); + } + } +} + +void update_disp_area() { + draw_disp_area(); + + drawBitmap(p_ad_x, p_ad_y, disp_area.getBuffer(), disp_area.width(), disp_area.height(), SSD1306_WHITE, SSD1306_BLACK); + if (disp_mode == DISP_MODE_LANDSCAPE) { + if (device_init_done && !firmware_update_mode && !disp_ext_fb) { + drawLine(0, 0, 0, 63, SSD1306_WHITE); + } + } +} + +void display_recondition() { + #if PLATFORM == PLATFORM_ESP32 + for (uint8_t iy = 0; iy < disp_area.height(); iy++) { + unsigned char rand_seg [] = {random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF)}; + stat_area.drawBitmap(0, iy, rand_seg, 64, 1, SSD1306_WHITE, SSD1306_BLACK); + disp_area.drawBitmap(0, iy, rand_seg, 64, 1, SSD1306_WHITE, SSD1306_BLACK); + } + + drawBitmap(p_ad_x, p_ad_y, disp_area.getBuffer(), disp_area.width(), disp_area.height(), SSD1306_WHITE, SSD1306_BLACK); + if (disp_mode == DISP_MODE_PORTRAIT) { + drawBitmap(p_as_x, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); + } else if (disp_mode == DISP_MODE_LANDSCAPE) { + drawBitmap(p_as_x, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); + } + #endif +} + +bool epd_blanked = false; +#if BOARD_MODEL == BOARD_TECHO + void epd_blank(bool full_update = true) { + display.setFullWindow(); + display.fillScreen(SSD1306_WHITE); + display.display(full_update); + } + + void epd_black(bool full_update = true) { + display.setFullWindow(); + display.fillScreen(SSD1306_BLACK); + display.display(full_update); + } +#endif + +void update_display(bool blank = false) { + display_updating = true; + if (blank == true) { + last_disp_update = millis()-disp_update_interval-1; + } else { + if (display_blanking_enabled && millis()-last_unblank_event >= display_blanking_timeout) { + blank = true; + display_blanked = true; + if (display_intensity != 0) { + display_unblank_intensity = display_intensity; + } + display_intensity = 0; + } else { + display_blanked = false; + if (display_unblank_intensity != 0x00) { + display_intensity = display_unblank_intensity; + display_unblank_intensity = 0x00; + } + } + } + + if (blank) { + if (millis()-last_disp_update >= disp_update_interval) { + if (display_contrast != display_intensity) { + display_contrast = display_intensity; + set_contrast(&display, display_contrast); + } + + #if BOARD_MODEL == BOARD_TECHO + if (!epd_blanked) { + epd_blank(); + epd_blanked = true; + } + #elif BOARD_MODEL == BOARD_VME213 + if (!epd_blanked) { + vme213_fillScreen(SSD1306_WHITE); + vme213_display.update(vme213_displayBuffer, LCMEN2R13EFC1::UPDATE_FULL); + epd_blanked = true; + } + #endif + + #if BOARD_MODEL == BOARD_HELTEC_T114 + display.clear(); + display.display(); + #elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO + display.clearDisplay(); + display.display(); + #else + // TODO: Clear screen + #endif + + last_disp_update = millis(); + } + + } else { + if (millis()-last_disp_update >= disp_update_interval) { + uint32_t current = millis(); + if (display_contrast != display_intensity) { + display_contrast = display_intensity; + set_contrast(&display, display_contrast); + } + + #if BOARD_MODEL == BOARD_HELTEC_T114 + display.clear(); + #elif BOARD_MODEL == BOARD_VME213 + // E-Ink buffer will be redrawn completely + #elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO + display.clearDisplay(); + #endif + + if (recondition_display) { + disp_target_fps = 30; + disp_update_interval = 1000/disp_target_fps; + display_recondition(); + } else { + #if BOARD_MODEL == BOARD_TECHO + display.setFullWindow(); + display.fillScreen(SSD1306_WHITE); + #elif BOARD_MODEL == BOARD_VME213 + // Render RNode UI to E-Ink buffer + vme213_renderStatus(); + #endif + + update_stat_area(); + update_disp_area(); + } + + #if BOARD_MODEL == BOARD_TECHO + if (current-last_epd_refresh >= epd_update_interval) { + if (current-last_epd_full_refresh >= REFRESH_PERIOD) { display.display(false); last_epd_full_refresh = millis(); } + else { display.display(true); } + last_epd_refresh = millis(); + epd_blanked = false; + } + #elif BOARD_MODEL == BOARD_VME213 + if (current-last_epd_refresh >= epd_update_interval) { + // Decide refresh type: 10 FAST : 1 FULL + LCMEN2R13EFC1::UpdateType refreshType; + if (vme213_fast_refresh_count >= VME213_REFRESH_RATIO || current-last_epd_full_refresh >= REFRESH_PERIOD) { + refreshType = LCMEN2R13EFC1::UPDATE_FULL; + vme213_fast_refresh_count = 0; + last_epd_full_refresh = millis(); + } else { + refreshType = LCMEN2R13EFC1::UPDATE_FAST; + vme213_fast_refresh_count++; + } + + // Perform E-Ink update + vme213_display.update(vme213_displayBuffer, refreshType); + last_epd_refresh = millis(); + epd_blanked = false; + } + #elif BOARD_MODEL != BOARD_TDECK + display.display(); + #endif + + last_disp_update = millis(); + } + } + display_updating = false; +} + +void display_unblank() { + last_unblank_event = millis(); +} + +void ext_fb_enable() { + disp_ext_fb = true; +} + +void ext_fb_disable() { + disp_ext_fb = false; +} diff --git a/RNode_Firmware/Fonts/Org_01.h b/RNode_Firmware/Fonts/Org_01.h new file mode 100644 index 0000000..9b80258 --- /dev/null +++ b/RNode_Firmware/Fonts/Org_01.h @@ -0,0 +1,131 @@ +#pragma once +#include + +// Org_v01 by Orgdot (www.orgdot.com/aliasfonts). A tiny, +// stylized font with all characters within a 6 pixel height. + +const uint8_t Org_01Bitmaps[] PROGMEM = { + 0xE8, 0xA0, 0x57, 0xD5, 0xF5, 0x00, 0xFD, 0x3E, 0x5F, 0x80, 0x88, 0x88, + 0x88, 0x80, 0xF4, 0xBF, 0x2E, 0x80, 0x80, 0x6A, 0x40, 0x95, 0x80, 0xAA, + 0x80, 0x5D, 0x00, 0xC0, 0xF0, 0x80, 0x08, 0x88, 0x88, 0x00, 0xFC, 0x63, + 0x1F, 0x80, 0xF8, 0xF8, 0x7F, 0x0F, 0x80, 0xF8, 0x7E, 0x1F, 0x80, 0x8C, + 0x7E, 0x10, 0x80, 0xFC, 0x3E, 0x1F, 0x80, 0xFC, 0x3F, 0x1F, 0x80, 0xF8, + 0x42, 0x10, 0x80, 0xFC, 0x7F, 0x1F, 0x80, 0xFC, 0x7E, 0x1F, 0x80, 0x90, + 0xB0, 0x2A, 0x22, 0xF0, 0xF0, 0x88, 0xA8, 0xF8, 0x4E, 0x02, 0x00, 0xFD, + 0x6F, 0x0F, 0x80, 0xFC, 0x7F, 0x18, 0x80, 0xF4, 0x7D, 0x1F, 0x00, 0xFC, + 0x21, 0x0F, 0x80, 0xF4, 0x63, 0x1F, 0x00, 0xFC, 0x3F, 0x0F, 0x80, 0xFC, + 0x3F, 0x08, 0x00, 0xFC, 0x2F, 0x1F, 0x80, 0x8C, 0x7F, 0x18, 0x80, 0xF9, + 0x08, 0x4F, 0x80, 0x78, 0x85, 0x2F, 0x80, 0x8D, 0xB1, 0x68, 0x80, 0x84, + 0x21, 0x0F, 0x80, 0xFD, 0x6B, 0x5A, 0x80, 0xFC, 0x63, 0x18, 0x80, 0xFC, + 0x63, 0x1F, 0x80, 0xFC, 0x7F, 0x08, 0x00, 0xFC, 0x63, 0x3F, 0x80, 0xFC, + 0x7F, 0x29, 0x00, 0xFC, 0x3E, 0x1F, 0x80, 0xF9, 0x08, 0x42, 0x00, 0x8C, + 0x63, 0x1F, 0x80, 0x8C, 0x62, 0xA2, 0x00, 0xAD, 0x6B, 0x5F, 0x80, 0x8A, + 0x88, 0xA8, 0x80, 0x8C, 0x54, 0x42, 0x00, 0xF8, 0x7F, 0x0F, 0x80, 0xEA, + 0xC0, 0x82, 0x08, 0x20, 0x80, 0xD5, 0xC0, 0x54, 0xF8, 0x80, 0xF1, 0xFF, + 0x8F, 0x99, 0xF0, 0xF8, 0x8F, 0x1F, 0x99, 0xF0, 0xFF, 0x8F, 0x6B, 0xA4, + 0xF9, 0x9F, 0x10, 0x8F, 0x99, 0x90, 0xF0, 0x55, 0xC0, 0x8A, 0xF9, 0x90, + 0xF8, 0xFD, 0x63, 0x10, 0xF9, 0x99, 0xF9, 0x9F, 0xF9, 0x9F, 0x80, 0xF9, + 0x9F, 0x20, 0xF8, 0x88, 0x47, 0x1F, 0x27, 0xC8, 0x42, 0x00, 0x99, 0x9F, + 0x99, 0x97, 0x8C, 0x6B, 0xF0, 0x96, 0x69, 0x99, 0x9F, 0x10, 0x2E, 0x8F, + 0x2B, 0x22, 0xF8, 0x89, 0xA8, 0x0F, 0xE0}; + +const GFXglyph Org_01Glyphs[] PROGMEM = {{0, 0, 0, 6, 0, 1}, // 0x20 ' ' + {0, 1, 5, 2, 0, -4}, // 0x21 '!' + {1, 3, 1, 4, 0, -4}, // 0x22 '"' + {2, 5, 5, 6, 0, -4}, // 0x23 '#' + {6, 5, 5, 6, 0, -4}, // 0x24 '$' + {10, 5, 5, 6, 0, -4}, // 0x25 '%' + {14, 5, 5, 6, 0, -4}, // 0x26 '&' + {18, 1, 1, 2, 0, -4}, // 0x27 ''' + {19, 2, 5, 3, 0, -4}, // 0x28 '(' + {21, 2, 5, 3, 0, -4}, // 0x29 ')' + {23, 3, 3, 4, 0, -3}, // 0x2A '*' + {25, 3, 3, 4, 0, -3}, // 0x2B '+' + {27, 1, 2, 2, 0, 0}, // 0x2C ',' + {28, 4, 1, 5, 0, -2}, // 0x2D '-' + {29, 1, 1, 2, 0, 0}, // 0x2E '.' + {30, 5, 5, 6, 0, -4}, // 0x2F '/' + {34, 5, 5, 6, 0, -4}, // 0x30 '0' + {38, 1, 5, 2, 0, -4}, // 0x31 '1' + {39, 5, 5, 6, 0, -4}, // 0x32 '2' + {43, 5, 5, 6, 0, -4}, // 0x33 '3' + {47, 5, 5, 6, 0, -4}, // 0x34 '4' + {51, 5, 5, 6, 0, -4}, // 0x35 '5' + {55, 5, 5, 6, 0, -4}, // 0x36 '6' + {59, 5, 5, 6, 0, -4}, // 0x37 '7' + {63, 5, 5, 6, 0, -4}, // 0x38 '8' + {67, 5, 5, 6, 0, -4}, // 0x39 '9' + {71, 1, 4, 2, 0, -3}, // 0x3A ':' + {72, 1, 4, 2, 0, -3}, // 0x3B ';' + {73, 3, 5, 4, 0, -4}, // 0x3C '<' + {75, 4, 3, 5, 0, -3}, // 0x3D '=' + {77, 3, 5, 4, 0, -4}, // 0x3E '>' + {79, 5, 5, 6, 0, -4}, // 0x3F '?' + {83, 5, 5, 6, 0, -4}, // 0x40 '@' + {87, 5, 5, 6, 0, -4}, // 0x41 'A' + {91, 5, 5, 6, 0, -4}, // 0x42 'B' + {95, 5, 5, 6, 0, -4}, // 0x43 'C' + {99, 5, 5, 6, 0, -4}, // 0x44 'D' + {103, 5, 5, 6, 0, -4}, // 0x45 'E' + {107, 5, 5, 6, 0, -4}, // 0x46 'F' + {111, 5, 5, 6, 0, -4}, // 0x47 'G' + {115, 5, 5, 6, 0, -4}, // 0x48 'H' + {119, 5, 5, 6, 0, -4}, // 0x49 'I' + {123, 5, 5, 6, 0, -4}, // 0x4A 'J' + {127, 5, 5, 6, 0, -4}, // 0x4B 'K' + {131, 5, 5, 6, 0, -4}, // 0x4C 'L' + {135, 5, 5, 6, 0, -4}, // 0x4D 'M' + {139, 5, 5, 6, 0, -4}, // 0x4E 'N' + {143, 5, 5, 6, 0, -4}, // 0x4F 'O' + {147, 5, 5, 6, 0, -4}, // 0x50 'P' + {151, 5, 5, 6, 0, -4}, // 0x51 'Q' + {155, 5, 5, 6, 0, -4}, // 0x52 'R' + {159, 5, 5, 6, 0, -4}, // 0x53 'S' + {163, 5, 5, 6, 0, -4}, // 0x54 'T' + {167, 5, 5, 6, 0, -4}, // 0x55 'U' + {171, 5, 5, 6, 0, -4}, // 0x56 'V' + {175, 5, 5, 6, 0, -4}, // 0x57 'W' + {179, 5, 5, 6, 0, -4}, // 0x58 'X' + {183, 5, 5, 6, 0, -4}, // 0x59 'Y' + {187, 5, 5, 6, 0, -4}, // 0x5A 'Z' + {191, 2, 5, 3, 0, -4}, // 0x5B '[' + {193, 5, 5, 6, 0, -4}, // 0x5C '\' + {197, 2, 5, 3, 0, -4}, // 0x5D ']' + {199, 3, 2, 4, 0, -4}, // 0x5E '^' + {200, 5, 1, 6, 0, 1}, // 0x5F '_' + {201, 1, 1, 2, 0, -4}, // 0x60 '`' + {202, 4, 4, 5, 0, -3}, // 0x61 'a' + {204, 4, 5, 5, 0, -4}, // 0x62 'b' + {207, 4, 4, 5, 0, -3}, // 0x63 'c' + {209, 4, 5, 5, 0, -4}, // 0x64 'd' + {212, 4, 4, 5, 0, -3}, // 0x65 'e' + {214, 3, 5, 4, 0, -4}, // 0x66 'f' + {216, 4, 5, 5, 0, -3}, // 0x67 'g' + {219, 4, 5, 5, 0, -4}, // 0x68 'h' + {222, 1, 4, 2, 0, -3}, // 0x69 'i' + {223, 2, 5, 3, 0, -3}, // 0x6A 'j' + {225, 4, 5, 5, 0, -4}, // 0x6B 'k' + {228, 1, 5, 2, 0, -4}, // 0x6C 'l' + {229, 5, 4, 6, 0, -3}, // 0x6D 'm' + {232, 4, 4, 5, 0, -3}, // 0x6E 'n' + {234, 4, 4, 5, 0, -3}, // 0x6F 'o' + {236, 4, 5, 5, 0, -3}, // 0x70 'p' + {239, 4, 5, 5, 0, -3}, // 0x71 'q' + {242, 4, 4, 5, 0, -3}, // 0x72 'r' + {244, 4, 4, 5, 0, -3}, // 0x73 's' + {246, 5, 5, 6, 0, -4}, // 0x74 't' + {250, 4, 4, 5, 0, -3}, // 0x75 'u' + {252, 4, 4, 5, 0, -3}, // 0x76 'v' + {254, 5, 4, 6, 0, -3}, // 0x77 'w' + {257, 4, 4, 5, 0, -3}, // 0x78 'x' + {259, 4, 5, 5, 0, -3}, // 0x79 'y' + {262, 4, 4, 5, 0, -3}, // 0x7A 'z' + {264, 3, 5, 4, 0, -4}, // 0x7B '{' + {266, 1, 5, 2, 0, -4}, // 0x7C '|' + {267, 3, 5, 4, 0, -4}, // 0x7D '}' + {269, 5, 3, 6, 0, -3}}; // 0x7E '~' + +const GFXfont Org_01 PROGMEM = {(uint8_t *)Org_01Bitmaps, + (GFXglyph *)Org_01Glyphs, 0x20, 0x7E, 7}; + +// Approx. 943 bytes diff --git a/RNode_Firmware/Fonts/PicoPixel.h b/RNode_Firmware/Fonts/PicoPixel.h new file mode 100644 index 0000000..01c79d5 --- /dev/null +++ b/RNode_Firmware/Fonts/PicoPixel.h @@ -0,0 +1,123 @@ +#pragma once +#include + +// Picopixel by Sebastian Weber. A tiny font +// with all characters within a 6 pixel height. + +const uint8_t PicopixelBitmaps[] PROGMEM = { + 0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A, + 0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60, + 0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C, + 0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94, + 0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA, + 0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4, + 0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58, + 0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7, + 0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55, + 0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5, + 0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE, + 0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92, + 0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75, + 0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54, + 0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A}; + +const GFXglyph PicopixelGlyphs[] PROGMEM = {{0, 0, 0, 2, 0, 1}, // 0x20 ' ' + {0, 1, 5, 2, 0, -4}, // 0x21 '!' + {1, 3, 2, 4, 0, -4}, // 0x22 '"' + {2, 5, 5, 6, 0, -4}, // 0x23 '#' + {6, 3, 6, 4, 0, -4}, // 0x24 '$' + {9, 3, 5, 4, 0, -4}, // 0x25 '%' + {11, 4, 5, 5, 0, -4}, // 0x26 '&' + {14, 1, 2, 2, 0, -4}, // 0x27 ''' + {15, 2, 5, 3, 0, -4}, // 0x28 '(' + {17, 2, 5, 3, 0, -4}, // 0x29 ')' + {19, 3, 3, 4, 0, -3}, // 0x2A '*' + {21, 3, 3, 4, 0, -3}, // 0x2B '+' + {23, 2, 2, 3, 0, 0}, // 0x2C ',' + {24, 3, 1, 4, 0, -2}, // 0x2D '-' + {25, 1, 1, 2, 0, 0}, // 0x2E '.' + {26, 3, 5, 4, 0, -4}, // 0x2F '/' + {28, 3, 5, 4, 0, -4}, // 0x30 '0' + {30, 2, 5, 3, 0, -4}, // 0x31 '1' + {32, 3, 5, 4, 0, -4}, // 0x32 '2' + {34, 3, 5, 4, 0, -4}, // 0x33 '3' + {36, 3, 5, 4, 0, -4}, // 0x34 '4' + {38, 3, 5, 4, 0, -4}, // 0x35 '5' + {40, 3, 5, 4, 0, -4}, // 0x36 '6' + {42, 3, 5, 4, 0, -4}, // 0x37 '7' + {44, 3, 5, 4, 0, -4}, // 0x38 '8' + {46, 3, 5, 4, 0, -4}, // 0x39 '9' + {48, 1, 3, 2, 0, -3}, // 0x3A ':' + {49, 2, 4, 3, 0, -3}, // 0x3B ';' + {50, 2, 3, 3, 0, -3}, // 0x3C '<' + {51, 3, 3, 4, 0, -3}, // 0x3D '=' + {53, 2, 3, 3, 0, -3}, // 0x3E '>' + {54, 3, 5, 4, 0, -4}, // 0x3F '?' + {56, 3, 5, 4, 0, -4}, // 0x40 '@' + {58, 3, 5, 4, 0, -4}, // 0x41 'A' + {60, 3, 5, 4, 0, -4}, // 0x42 'B' + {62, 3, 5, 4, 0, -4}, // 0x43 'C' + {64, 3, 5, 4, 0, -4}, // 0x44 'D' + {66, 3, 5, 4, 0, -4}, // 0x45 'E' + {68, 3, 5, 4, 0, -4}, // 0x46 'F' + {70, 3, 5, 4, 0, -4}, // 0x47 'G' + {72, 3, 5, 4, 0, -4}, // 0x48 'H' + {74, 1, 5, 2, 0, -4}, // 0x49 'I' + {75, 3, 5, 4, 0, -4}, // 0x4A 'J' + {77, 3, 5, 4, 0, -4}, // 0x4B 'K' + {79, 3, 5, 4, 0, -4}, // 0x4C 'L' + {81, 5, 5, 6, 0, -4}, // 0x4D 'M' + {85, 4, 5, 5, 0, -4}, // 0x4E 'N' + {88, 3, 5, 4, 0, -4}, // 0x4F 'O' + {90, 3, 5, 4, 0, -4}, // 0x50 'P' + {92, 3, 6, 4, 0, -4}, // 0x51 'Q' + {95, 3, 5, 4, 0, -4}, // 0x52 'R' + {97, 3, 5, 4, 0, -4}, // 0x53 'S' + {99, 3, 5, 4, 0, -4}, // 0x54 'T' + {101, 3, 5, 4, 0, -4}, // 0x55 'U' + {103, 3, 5, 4, 0, -4}, // 0x56 'V' + {105, 5, 5, 6, 0, -4}, // 0x57 'W' + {109, 3, 5, 4, 0, -4}, // 0x58 'X' + {111, 3, 5, 4, 0, -4}, // 0x59 'Y' + {113, 3, 5, 4, 0, -4}, // 0x5A 'Z' + {115, 2, 5, 3, 0, -4}, // 0x5B '[' + {117, 3, 5, 4, 0, -4}, // 0x5C '\' + {119, 2, 5, 3, 0, -4}, // 0x5D ']' + {121, 3, 2, 4, 0, -4}, // 0x5E '^' + {122, 4, 1, 4, 0, 1}, // 0x5F '_' + {123, 2, 2, 3, 0, -4}, // 0x60 '`' + {124, 3, 4, 4, 0, -3}, // 0x61 'a' + {126, 3, 5, 4, 0, -4}, // 0x62 'b' + {128, 3, 3, 4, 0, -2}, // 0x63 'c' + {130, 3, 5, 4, 0, -4}, // 0x64 'd' + {132, 3, 4, 4, 0, -3}, // 0x65 'e' + {134, 2, 5, 3, 0, -4}, // 0x66 'f' + {136, 3, 5, 4, 0, -3}, // 0x67 'g' + {138, 3, 5, 4, 0, -4}, // 0x68 'h' + {140, 1, 5, 2, 0, -4}, // 0x69 'i' + {141, 2, 6, 3, 0, -4}, // 0x6A 'j' + {143, 3, 5, 4, 0, -4}, // 0x6B 'k' + {145, 2, 5, 3, 0, -4}, // 0x6C 'l' + {147, 5, 3, 6, 0, -2}, // 0x6D 'm' + {149, 3, 3, 4, 0, -2}, // 0x6E 'n' + {151, 3, 3, 4, 0, -2}, // 0x6F 'o' + {153, 3, 4, 4, 0, -2}, // 0x70 'p' + {155, 3, 4, 4, 0, -2}, // 0x71 'q' + {157, 2, 3, 3, 0, -2}, // 0x72 'r' + {158, 3, 4, 4, 0, -3}, // 0x73 's' + {160, 2, 5, 3, 0, -4}, // 0x74 't' + {162, 3, 3, 4, 0, -2}, // 0x75 'u' + {164, 3, 3, 4, 0, -2}, // 0x76 'v' + {166, 5, 3, 6, 0, -2}, // 0x77 'w' + {168, 3, 3, 4, 0, -2}, // 0x78 'x' + {170, 3, 4, 4, 0, -2}, // 0x79 'y' + {172, 3, 4, 4, 0, -3}, // 0x7A 'z' + {174, 3, 5, 4, 0, -4}, // 0x7B '{' + {176, 1, 6, 2, 0, -4}, // 0x7C '|' + {177, 3, 5, 4, 0, -4}, // 0x7D '}' + {179, 4, 2, 5, 0, -3}}; // 0x7E '~' + +const GFXfont Picopixel PROGMEM = {(uint8_t *)PicopixelBitmaps, + (GFXglyph *)PicopixelGlyphs, 0x20, 0x7E, 7}; + +// Approx. 852 bytes \ No newline at end of file diff --git a/RNode_Firmware/Framing.h b/RNode_Firmware/Framing.h new file mode 100644 index 0000000..94f4e37 --- /dev/null +++ b/RNode_Firmware/Framing.h @@ -0,0 +1,117 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef FRAMING_H + #define FRAMING_H + + #define FEND 0xC0 + #define FESC 0xDB + #define TFEND 0xDC + #define TFESC 0xDD + + #define CMD_UNKNOWN 0xFE + #define CMD_DATA 0x00 + #define CMD_FREQUENCY 0x01 + #define CMD_BANDWIDTH 0x02 + #define CMD_TXPOWER 0x03 + #define CMD_SF 0x04 + #define CMD_CR 0x05 + #define CMD_RADIO_STATE 0x06 + #define CMD_RADIO_LOCK 0x07 + #define CMD_DETECT 0x08 + #define CMD_IMPLICIT 0x09 + #define CMD_LEAVE 0x0A + #define CMD_ST_ALOCK 0x0B + #define CMD_LT_ALOCK 0x0C + #define CMD_PROMISC 0x0E + #define CMD_READY 0x0F + + #define CMD_STAT_RX 0x21 + #define CMD_STAT_TX 0x22 + #define CMD_STAT_RSSI 0x23 + #define CMD_STAT_SNR 0x24 + #define CMD_STAT_CHTM 0x25 + #define CMD_STAT_PHYPRM 0x26 + #define CMD_STAT_BAT 0x27 + #define CMD_STAT_CSMA 0x28 + #define CMD_STAT_TEMP 0x29 + #define CMD_BLINK 0x30 + #define CMD_RANDOM 0x40 + + #define CMD_FB_EXT 0x41 + #define CMD_FB_READ 0x42 + #define CMD_FB_WRITE 0x43 + #define CMD_FB_READL 0x44 + #define CMD_DISP_READ 0x66 + #define CMD_DISP_INT 0x45 + #define CMD_DISP_ADDR 0x63 + #define CMD_DISP_BLNK 0x64 + #define CMD_DISP_ROT 0x67 + #define CMD_DISP_RCND 0x68 + #define CMD_NP_INT 0x65 + #define CMD_BT_CTRL 0x46 + #define CMD_BT_UNPAIR 0x70 + #define CMD_BT_PIN 0x62 + #define CMD_DIS_IA 0x69 + + #define CMD_BOARD 0x47 + #define CMD_PLATFORM 0x48 + #define CMD_MCU 0x49 + #define CMD_FW_VERSION 0x50 + #define CMD_ROM_READ 0x51 + #define CMD_ROM_WRITE 0x52 + #define CMD_CONF_SAVE 0x53 + #define CMD_CONF_DELETE 0x54 + #define CMD_DEV_HASH 0x56 + #define CMD_DEV_SIG 0x57 + #define CMD_FW_HASH 0x58 + #define CMD_HASHES 0x60 + #define CMD_FW_UPD 0x61 + #define CMD_UNLOCK_ROM 0x59 + #define ROM_UNLOCK_BYTE 0xF8 + #define CMD_RESET 0x55 + #define CMD_RESET_BYTE 0xF8 + + #define CMD_LOG 0x80 + #define CMD_TIME 0x81 + #define CMD_MUX_CHAIN 0x82 + #define CMD_MUX_DSCVR 0x83 + + #define DETECT_REQ 0x73 + #define DETECT_RESP 0x46 + + #define RADIO_STATE_OFF 0x00 + #define RADIO_STATE_ON 0x01 + + #define NIBBLE_SEQ 0xF0 + #define NIBBLE_FLAGS 0x0F + #define FLAG_SPLIT 0x01 + #define SEQ_UNSET 0xFF + + #define CMD_ERROR 0x90 + #define ERROR_INITRADIO 0x01 + #define ERROR_TXFAILED 0x02 + #define ERROR_EEPROM_LOCKED 0x03 + #define ERROR_QUEUE_FULL 0x04 + #define ERROR_MEMORY_LOW 0x05 + #define ERROR_MODEM_TIMEOUT 0x06 + + // Serial framing variables + size_t frame_len; + bool IN_FRAME = false; + bool ESCAPE = false; + uint8_t command = CMD_UNKNOWN; + +#endif \ No newline at end of file diff --git a/RNode_Firmware/Graphics.h b/RNode_Firmware/Graphics.h new file mode 100644 index 0000000..3da21f1 --- /dev/null +++ b/RNode_Firmware/Graphics.h @@ -0,0 +1,434 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +const unsigned char bm_cable [] PROGMEM = { + 0x00, 0x00, 0x00, 0x1c, 0x00, 0x38, 0x07, 0xfc, 0x08, 0x38, 0x10, 0x1c, 0x10, 0x00, 0x08, 0x00, + 0x07, 0xc0, 0x00, 0x20, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x07, 0xc0, 0x08, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x80, 0x04, 0x43, 0x08, 0x46, + 0xf1, 0x8f, 0x02, 0x16, 0x02, 0x23, 0x01, 0x20, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const unsigned char bm_rf [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xc4, + 0x4a, 0xaa, 0x4a, 0xce, 0x6e, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x04, 0x20, 0x01, 0x80, 0x00, 0x00, 0x4e, 0xc4, + 0x4a, 0xaa, 0x4a, 0xce, 0x6e, 0xaa, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x4e, + 0x31, 0x48, 0x61, 0xca, 0x74, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xe0, 0x08, 0x10, 0x13, 0xc8, 0x04, 0x20, 0x01, 0x80, 0x00, 0x00, 0x71, 0x4e, + 0x31, 0x48, 0x61, 0xca, 0x74, 0x4e, 0x00, 0x00, 0x01, 0x80, 0x04, 0x20, 0x03, 0xc0, 0x00, 0x00 +}; + +const unsigned char bm_bt [] PROGMEM = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x40, 0x00, 0x00, 0x05, 0x10, 0x00, 0x00, 0x01, 0x40, + 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x05, 0x10, 0x00, 0x00, 0x11, 0x40, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x40, 0x09, 0x20, 0x05, 0x10, 0x03, 0x20, 0x01, 0x40, + 0x01, 0x80, 0x01, 0x40, 0x03, 0x20, 0x05, 0x10, 0x09, 0x20, 0x01, 0x40, 0x01, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x40, 0x09, 0x20, 0x05, 0x10, 0x03, 0x20, 0x01, 0x40, + 0x29, 0x94, 0x01, 0x40, 0x03, 0x20, 0x05, 0x10, 0x09, 0x20, 0x01, 0x40, 0x01, 0x80, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x01, 0x40, 0x09, 0x20, 0x05, 0x10, 0x03, 0x20, 0x11, 0x48, + 0x29, 0x94, 0x11, 0x48, 0x03, 0x20, 0x05, 0x10, 0x09, 0x20, 0x01, 0x40, 0x01, 0x80, 0x01, 0x00 +}; + +const unsigned char bm_boot [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0x38, 0x66, 0x67, 0x1c, 0x3f, 0xff, 0xff, 0xfc, 0x99, 0xe6, 0x66, 0x4c, 0xff, 0xff, + 0xff, 0xfc, 0x98, 0x70, 0xe6, 0x7c, 0x3f, 0xff, 0xff, 0xfc, 0x99, 0xf0, 0xe6, 0x4c, 0xff, 0xff, + 0xff, 0xfc, 0x38, 0x79, 0xe7, 0x1c, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0c, 0x38, 0xe1, 0xc3, 0x33, 0x38, 0x7f, 0xfe, 0x7e, 0x72, 0x64, 0xe7, 0x31, 0x33, 0xff, + 0xff, 0x1e, 0x70, 0x61, 0xe7, 0x30, 0x32, 0x7f, 0xff, 0xce, 0x72, 0x61, 0xe7, 0x32, 0x32, 0x7f, + 0xfe, 0x1e, 0x72, 0x64, 0xe7, 0x33, 0x38, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_fw_update [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0x98, 0x70, 0xf1, 0xc3, 0x33, 0x38, 0x7f, 0xfc, 0x99, 0x32, 0x64, 0xe7, 0x31, 0x33, 0xff, + 0xfc, 0x98, 0x72, 0x60, 0xe7, 0x30, 0x32, 0x7f, 0xfc, 0x99, 0xf2, 0x64, 0xe7, 0x32, 0x32, 0x7f, + 0xfe, 0x39, 0xf0, 0xe4, 0xe7, 0x33, 0x38, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x66, 0x1c, 0xe6, 0x73, 0x8e, 0x1c, 0x3f, 0xf9, 0xe6, 0x4c, 0x46, 0x53, 0x26, 0x4c, 0xff, + 0xf8, 0x66, 0x1c, 0x06, 0x53, 0x06, 0x1c, 0x3f, 0xf9, 0xe6, 0x1c, 0xa6, 0x03, 0x26, 0x1c, 0xff, + 0xf9, 0xe6, 0x4c, 0xe7, 0x27, 0x26, 0x4c, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_console_active [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0x8e, 0x67, 0x0e, 0x39, 0xe1, 0xff, + 0xff, 0x93, 0x26, 0x26, 0x7c, 0x99, 0xe7, 0xff, 0xff, 0x9f, 0x26, 0x07, 0x1c, 0x99, 0xe1, 0xff, + 0xff, 0x93, 0x26, 0x47, 0xcc, 0x99, 0xe7, 0xff, 0xff, 0xc7, 0x8e, 0x66, 0x1e, 0x38, 0x61, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3c, 0x70, 0xcc, 0xcc, 0x3f, 0xff, + 0xff, 0xfc, 0x99, 0x39, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xfc, 0x19, 0xf9, 0xce, 0x1c, 0x3f, 0xff, + 0xff, 0xfc, 0x99, 0x39, 0xce, 0x1c, 0xff, 0xff, 0xff, 0xfc, 0x9c, 0x79, 0xcf, 0x3c, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_updating [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xf1, 0xff, 0x71, 0x7f, 0xff, + 0xff, 0xff, 0x7f, 0xf5, 0xff, 0x75, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xf1, 0xff, 0x71, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x17, 0xd4, 0x7f, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0x57, 0xd5, 0x7f, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x17, 0xd4, 0x7f, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x47, 0x44, 0x71, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0x57, 0x55, 0x75, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x47, 0x44, 0x71, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x44, 0x51, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x44, 0x51, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x54, 0x45, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x14, 0x54, 0x45, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xff, 0xff, + 0xff, 0xff, 0x60, 0x00, 0x00, 0x03, 0x7f, 0xff, 0xff, 0xff, 0x30, 0x00, 0x00, 0x07, 0x7f, 0xff, + 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x1c, 0x7f, 0xff, + 0xff, 0xff, 0x56, 0x00, 0x00, 0x35, 0x7f, 0xff, 0xff, 0xff, 0x57, 0x00, 0x00, 0x74, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x44, 0xc0, 0x01, 0xd1, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x60, 0x03, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x44, 0x70, 0x07, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x5c, 0x1d, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x56, 0x35, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x14, 0x57, 0xe5, 0x44, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0x45, 0x44, 0x51, 0x51, 0x7f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, + 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_version [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x99, 0x86, 0x1e, 0x19, 0xc7, 0x33, 0xff, 0xff, 0x99, 0x9e, 0x4c, 0xf9, 0x93, 0x13, 0xff, + 0xff, 0xc3, 0x86, 0x1e, 0x39, 0x93, 0x03, 0xff, 0xff, 0xc3, 0x9e, 0x1f, 0x99, 0x93, 0x23, 0xff, + 0xff, 0xe7, 0x86, 0x4c, 0x39, 0xc7, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_fw_corrupt [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x30, 0xe7, 0x33, 0x9c, 0x70, 0xe1, 0xff, + 0xcf, 0x32, 0x62, 0x32, 0x99, 0x32, 0x67, 0xff, 0xc3, 0x30, 0xe0, 0x32, 0x98, 0x30, 0xe1, 0xff, + 0xcf, 0x30, 0xe5, 0x30, 0x19, 0x30, 0xe7, 0xff, 0xcf, 0x32, 0x67, 0x39, 0x39, 0x32, 0x61, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xc7, 0x0e, 0x1c, 0x98, 0x70, 0xfc, 0xff, + 0xc9, 0x93, 0x26, 0x4c, 0x99, 0x39, 0xfb, 0x7f, 0xcf, 0x93, 0x0e, 0x1c, 0x98, 0x79, 0xfb, 0x7f, + 0xc9, 0x93, 0x0e, 0x1c, 0x99, 0xf9, 0xf7, 0xbf, 0xe3, 0xc7, 0x26, 0x4e, 0x39, 0xf9, 0xf4, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xf7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static unsigned char bm_def[] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb4, 0x61, 0x10, 0x8c, 0x23, 0xc4, 0x3f, 0xff, + 0xb5, 0xa7, 0xb7, 0xb5, 0xed, 0xed, 0xbf, 0xff, 0xb5, 0xb9, 0xb4, 0xb4, 0x6d, 0xed, 0xbf, 0xff, + 0x85, 0xa1, 0x10, 0xb4, 0x21, 0x44, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe7, 0x1c, 0xfe, 0x7f, 0x8f, 0xf0, 0x00, + 0x1f, 0xf7, 0x9d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, 0x1c, 0x77, 0xfd, 0xc7, 0x73, 0xdc, 0x00, 0x00, + 0x1f, 0xe7, 0xfd, 0xc7, 0x71, 0xdf, 0x00, 0x00, 0x1f, 0xe7, 0x7d, 0xc7, 0x71, 0xdf, 0x00, 0x00, + 0x1c, 0x77, 0x3d, 0xc7, 0x73, 0xdc, 0x00, 0x00, 0x1c, 0x77, 0x1d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, + 0x1c, 0x77, 0x1c, 0xfe, 0x7f, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, + 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x62, 0x24, 0x49, 0x22, 0x4e, 0x44, + 0x00, 0x24, 0x93, 0x66, 0xc9, 0x32, 0x44, 0x28, 0x00, 0x20, 0x92, 0xa5, 0x49, 0x2a, 0x44, 0x10, + 0x00, 0x24, 0x92, 0x24, 0x49, 0x26, 0x44, 0x10, 0x00, 0x18, 0x62, 0x24, 0x46, 0x22, 0x44, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1c, 0x9c, 0x44, 0x88, 0xc7, 0x1c, 0x00, 0x00, 0x10, 0x92, 0x6c, 0xa9, 0x24, 0x90, + 0x00, 0x00, 0x1c, 0x9c, 0x54, 0xa9, 0xe7, 0x1c, 0x00, 0x00, 0x10, 0x94, 0x44, 0xa9, 0x25, 0x10, + 0x00, 0x00, 0x10, 0x92, 0x44, 0x51, 0x24, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const unsigned char bm_def_lc [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb4, 0x61, 0x10, 0x8c, 0x23, 0xc4, 0x3f, 0xff, + 0xb5, 0xa7, 0xb7, 0xb5, 0xed, 0xed, 0xbf, 0xff, 0xb5, 0xb9, 0xb4, 0xb4, 0x6d, 0xed, 0xbf, 0xff, + 0x85, 0xa1, 0x10, 0xb4, 0x21, 0x44, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe7, 0x1c, 0xfe, 0x7f, 0x8f, 0xf0, 0x00, + 0x1f, 0xf7, 0x9d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, 0x1c, 0x77, 0xfd, 0xc7, 0x73, 0xdc, 0x00, 0x00, + 0x1f, 0xe7, 0xfd, 0xc7, 0x71, 0xdf, 0x00, 0x00, 0x1f, 0xe7, 0x7d, 0xc7, 0x71, 0xdf, 0x00, 0x00, + 0x1c, 0x77, 0x3d, 0xc7, 0x73, 0xdc, 0x00, 0x00, 0x1c, 0x77, 0x1d, 0xff, 0x7f, 0x9f, 0xf0, 0x00, + 0x1c, 0x77, 0x1c, 0xfe, 0x7f, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, + 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x8e, 0x39, 0x10, 0x61, 0x88, 0x91, 0x1c, + 0x02, 0x49, 0x21, 0x90, 0x92, 0x4d, 0x9b, 0x20, 0x02, 0x4e, 0x39, 0x50, 0x82, 0x4a, 0x95, 0x18, + 0x02, 0x48, 0x21, 0x30, 0x92, 0x48, 0x91, 0x04, 0x01, 0x88, 0x39, 0x10, 0x61, 0x88, 0x91, 0x38, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xc8, 0x8e, 0x73, 0x91, 0x1c, 0x00, 0x00, 0x02, 0x05, 0x10, 0x22, 0x1b, 0x20, + 0x00, 0x00, 0x01, 0x82, 0x0c, 0x23, 0x95, 0x18, 0x00, 0x00, 0x00, 0x42, 0x02, 0x22, 0x11, 0x04, + 0x00, 0x00, 0x03, 0x82, 0x1c, 0x23, 0x91, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const unsigned char bm_frame [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x40, 0x02, 0x0f, 0xff, 0xfc, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x1e, 0x40, 0x02, 0x78, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x3f, 0xff, 0xf2, 0x40, 0x02, 0x4f, 0xff, 0xfc, + 0x00, 0x00, 0x02, 0x40, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x03, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x40, 0x02, 0x40, 0x00, 0x00, 0x3f, 0xff, 0xf2, 0x40, 0x02, 0x4f, 0xff, 0xfc, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, 0x20, 0x00, 0x12, 0x40, 0x02, 0x48, 0x00, 0x04, + 0x20, 0x00, 0x1e, 0x40, 0x02, 0x78, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, + 0x20, 0x00, 0x10, 0x40, 0x02, 0x08, 0x00, 0x04, 0x3f, 0xff, 0xf0, 0x40, 0x02, 0x0f, 0xff, 0xfc, + 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xaa, 0x8a, 0xaa, 0x80 +}; + +const unsigned char bm_console [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, + 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x1f, 0xcf, 0xff, 0xff, 0xf3, 0xf8, 0xff, + 0xfe, 0x3f, 0x9f, 0xff, 0xff, 0xf9, 0xfc, 0x7f, 0xfc, 0x7f, 0x99, 0xe6, 0x61, 0x99, 0xfe, 0x3f, + 0xf8, 0xe7, 0x99, 0x26, 0x67, 0x99, 0xe7, 0x1f, 0xf9, 0xc7, 0x99, 0x26, 0x61, 0x99, 0xe3, 0x9f, + 0xf1, 0x8f, 0x98, 0x06, 0x67, 0x99, 0xf1, 0x8f, 0xf3, 0x9f, 0x9c, 0xce, 0x67, 0x99, 0xf9, 0xcf, + 0xf3, 0x99, 0x9f, 0xff, 0xff, 0xf9, 0x99, 0xcf, 0xf3, 0x99, 0x9f, 0xff, 0xff, 0xf9, 0x99, 0xcf, + 0xf3, 0x9f, 0x9f, 0xe3, 0x83, 0xf9, 0xf9, 0xcf, 0xf1, 0x8f, 0x9f, 0xc9, 0x93, 0xf9, 0xf1, 0x8f, + 0xf9, 0xc7, 0x9f, 0xc1, 0x83, 0xf9, 0xe3, 0x9f, 0xf8, 0xe7, 0x9f, 0xc9, 0x9f, 0xf9, 0xe7, 0x1f, + 0xfc, 0x7f, 0x9f, 0xc9, 0x9f, 0xf9, 0xfe, 0x3f, 0xfe, 0x3f, 0x9f, 0xff, 0xff, 0xf9, 0xfc, 0x7f, + 0xff, 0x1f, 0xcf, 0xff, 0xff, 0xf3, 0xf8, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, + 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfd, 0xff, 0x7f, 0xdf, 0xf7, 0xfd, 0xff, 0x7f, + 0xfd, 0xff, 0x7f, 0xdf, 0xf7, 0xfd, 0xff, 0x7f, 0xfd, 0xff, 0x7f, 0xdf, 0xf7, 0xfd, 0xff, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x78, 0x1e, 0x07, 0x81, 0xe0, 0x78, 0x1f, + 0xef, 0xbb, 0xee, 0xfb, 0xbe, 0xef, 0xbb, 0xef, 0xe8, 0xda, 0xb6, 0x9d, 0xb3, 0x6d, 0xda, 0x37, + 0xef, 0xda, 0xf6, 0xb5, 0xad, 0x6c, 0xdb, 0xf7, 0xe8, 0x5a, 0x36, 0x95, 0xad, 0x6c, 0xda, 0x97, + 0xef, 0xdb, 0xf6, 0x85, 0xb3, 0x6c, 0xda, 0x97, 0xea, 0x5a, 0x36, 0xb5, 0xb3, 0x6c, 0xdb, 0xf7, + 0xef, 0xda, 0xf6, 0xa5, 0xad, 0x6f, 0xda, 0x57, 0xe8, 0x5a, 0xb6, 0x85, 0xad, 0x6c, 0xda, 0x57, + 0xef, 0xdb, 0xf6, 0xfd, 0xbf, 0x6f, 0xdb, 0xf7, 0xe0, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x07, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x42, 0x7c, 0x60, 0xf0, 0x78, 0x3c, 0x7f, 0xfe, 0x4a, 0x7c, 0x64, 0xf2, 0x79, 0x3c, 0x7f, + 0xfe, 0x43, 0xfe, 0x64, 0xf2, 0x79, 0x3e, 0x7f, 0xfe, 0x4e, 0x7e, 0x64, 0x92, 0x49, 0x26, 0x7f, + 0xfe, 0x4e, 0x7e, 0x60, 0x90, 0x48, 0x26, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +const unsigned char bm_checks [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x87, 0x0c, 0x99, 0xc7, 0x0f, + 0xe6, 0x00, 0x7f, 0x93, 0x3c, 0x99, 0x93, 0x3f, 0xe6, 0x00, 0x7f, 0x93, 0x0e, 0x39, 0x9f, 0x0f, + 0xff, 0xff, 0xff, 0x93, 0x3e, 0x39, 0x93, 0x3f, 0xff, 0xff, 0xff, 0x87, 0x0f, 0x79, 0xc7, 0x0f, + 0xe6, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x39, 0x30, 0xe3, 0x93, 0x87, + 0xe6, 0x00, 0x7c, 0x99, 0x33, 0xc9, 0x87, 0x3f, 0xe6, 0x00, 0x7c, 0xf8, 0x30, 0xcf, 0x8f, 0x8f, + 0xff, 0xff, 0xfc, 0x99, 0x33, 0xc9, 0x87, 0xe7, 0xff, 0xff, 0xfe, 0x39, 0x30, 0xe3, 0x93, 0x0f, + 0xe6, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x9c, 0x3c, 0x78, 0x70, 0xc3, 0x0f, + 0xe6, 0x0d, 0x3c, 0x99, 0x33, 0xe7, 0xcf, 0x27, 0xe6, 0x04, 0x7c, 0x38, 0x38, 0xf1, 0xc3, 0x27, + 0xff, 0xfe, 0xfc, 0xf9, 0x3e, 0x7c, 0xcf, 0x27, 0xff, 0xff, 0xfc, 0xf9, 0x30, 0xe1, 0xc3, 0x0f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_hwfail [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe4, 0xe3, 0x87, 0x0e, 0x73, 0x8e, 0x1c, 0x3f, + 0xe4, 0xc9, 0x93, 0x26, 0x53, 0x26, 0x4c, 0xff, 0xe0, 0xc1, 0x87, 0x26, 0x53, 0x06, 0x1c, 0x3f, + 0xe4, 0xc9, 0x87, 0x26, 0x03, 0x26, 0x1c, 0xff, 0xe4, 0xc9, 0x93, 0x0f, 0x27, 0x26, 0x4c, 0x3f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe1, 0xc7, 0x33, 0xc9, 0x87, 0x0f, 0xf9, 0xff, 0xe7, 0x93, 0x33, 0xc9, 0x93, 0x3f, 0xf6, 0xff, + 0xe1, 0x83, 0x33, 0xc9, 0x87, 0x0f, 0xf6, 0xff, 0xe7, 0x93, 0x33, 0xc9, 0x87, 0x3f, 0xef, 0x7f, + 0xe7, 0x93, 0x30, 0xe3, 0x93, 0x0f, 0xe9, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x79, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_conf_missing [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x33, 0x87, 0x0c, 0xcc, 0xe1, 0xff, 0xff, + 0xe2, 0x33, 0x3e, 0x7c, 0xc4, 0xcf, 0xff, 0xff, 0xe0, 0x33, 0x8f, 0x1c, 0xc0, 0xc9, 0xff, 0xff, + 0xe5, 0x33, 0xe7, 0xcc, 0xc8, 0xc9, 0xff, 0xff, 0xe7, 0x33, 0x0e, 0x1c, 0xcc, 0xe3, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xf1, 0xe3, 0x99, 0x86, 0x70, 0xff, 0xf6, 0xff, + 0xe4, 0xc9, 0x89, 0x9e, 0x67, 0xff, 0xf6, 0xff, 0xe7, 0xc9, 0x81, 0x86, 0x64, 0xff, 0xef, 0x7f, + 0xe4, 0xc9, 0x91, 0x9e, 0x64, 0xff, 0xe9, 0x7f, 0xf1, 0xe3, 0x99, 0x9e, 0x71, 0xff, 0xd9, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x79, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_no_radio [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xc7, 0x0e, 0x71, 0xfc, 0xce, 0x38, 0x7f, + 0xc9, 0x93, 0x26, 0x64, 0xfc, 0x4c, 0x9c, 0xff, 0xc3, 0x83, 0x26, 0x64, 0xfc, 0x0c, 0x9c, 0xff, + 0xc3, 0x93, 0x26, 0x64, 0xfc, 0x8c, 0x9c, 0xff, 0xc9, 0x93, 0x0e, 0x71, 0xfc, 0xce, 0x3c, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x8e, 0x4c, 0xcc, 0x3f, 0xff, 0xfc, 0xff, + 0xcf, 0x26, 0x4c, 0x4c, 0x9f, 0xff, 0xfb, 0x7f, 0xc3, 0x26, 0x4c, 0x0c, 0x9f, 0xff, 0xfb, 0x7f, + 0xcf, 0x26, 0x4c, 0x8c, 0x9f, 0xff, 0xf7, 0xbf, 0xcf, 0x8f, 0x1c, 0xcc, 0x3f, 0xff, 0xf4, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xec, 0xdf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xf7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_hwok [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf2, 0x71, 0xc3, 0x87, 0x39, 0xc7, 0x0e, 0x1f, 0xf2, 0x64, 0xc9, 0x93, 0x29, 0x93, 0x26, 0x7f, + 0xf0, 0x60, 0xc3, 0x93, 0x29, 0x83, 0x0e, 0x1f, 0xf2, 0x64, 0xc3, 0x93, 0x01, 0x93, 0x0e, 0x7f, + 0xf2, 0x64, 0xc9, 0x87, 0x93, 0x93, 0x26, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x30, 0xff, 0x8e, 0x4f, 0xff, + 0xff, 0xf3, 0x13, 0x39, 0xff, 0x26, 0x1f, 0xff, 0xff, 0xf3, 0x03, 0x39, 0xff, 0x26, 0x3f, 0xff, + 0xff, 0xf3, 0x23, 0x39, 0xff, 0x26, 0x1f, 0xff, 0xff, 0xf3, 0x33, 0x39, 0xff, 0x8e, 0x4f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc3, 0x0e, 0x67, 0xf8, 0x70, 0xe3, 0x87, 0x33, 0xe7, 0x27, 0x0f, 0xf9, 0x33, 0xc9, 0x93, 0x87, + 0xe7, 0x0f, 0x9f, 0xf8, 0x70, 0xc1, 0x93, 0xcf, 0xe7, 0x0f, 0x0f, 0xf8, 0x73, 0xc9, 0x93, 0xcf, + 0xe7, 0x26, 0x67, 0xf9, 0x30, 0xc9, 0x87, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_nfr [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x38, 0x66, 0x67, 0x1c, 0x3f, 0xff, 0xff, 0xfc, 0x99, 0xe6, 0x66, 0x4c, 0xff, + 0xff, 0x9f, 0xfc, 0x98, 0x70, 0xe6, 0x7c, 0x3f, 0xff, 0x6f, 0xfc, 0x99, 0xf0, 0xe6, 0x4c, 0xff, + 0xff, 0x6f, 0xfc, 0x38, 0x79, 0xe7, 0x1c, 0x3f, 0xfe, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x97, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x9b, 0xe6, 0x71, 0xc3, 0xe1, 0xc7, 0x0f, + 0xfd, 0x9b, 0xe2, 0x64, 0xe7, 0xe7, 0x93, 0x27, 0xfb, 0x9d, 0xe0, 0x64, 0xe7, 0xe1, 0x93, 0x0f, + 0xfb, 0x9d, 0xe4, 0x64, 0xe7, 0xe7, 0x93, 0x0f, 0xf7, 0xfe, 0xe6, 0x71, 0xe7, 0xe7, 0xc7, 0x27, + 0xf7, 0x9e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x9f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xef, 0xff, 0x7f, 0xf8, 0x71, 0xcf, 0x0f, 0xff, 0xf0, 0x00, 0xff, 0xf3, 0xe4, 0xcf, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xe0, 0xcf, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x64, 0xcf, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xf0, 0xe4, 0xc3, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_online [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x1c, 0x66, 0x61, 0x8c, 0x24, 0x90, 0x87, + 0xe6, 0x49, 0x22, 0x4f, 0x24, 0xe4, 0x93, 0x93, 0xe6, 0x18, 0x20, 0x63, 0x3c, 0x26, 0x30, 0x87, + 0xe6, 0x19, 0x24, 0x79, 0x24, 0xe6, 0x33, 0x87, 0xe6, 0x49, 0x26, 0x43, 0x8c, 0x27, 0x70, 0x93, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xe6, 0x73, 0xe7, 0x33, 0x87, 0xff, + 0xff, 0xe4, 0xe2, 0x73, 0xe7, 0x13, 0x9f, 0xff, 0xff, 0xe4, 0xe0, 0x73, 0xe7, 0x03, 0x87, 0xff, + 0xff, 0xe4, 0xe4, 0x73, 0xe7, 0x23, 0x9f, 0xff, 0xff, 0xf1, 0xe6, 0x70, 0xe7, 0x33, 0x87, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_pairing [] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf0, 0xe3, 0x98, 0x73, 0x33, 0x87, 0xff, 0xff, 0xf2, 0xc9, 0x99, 0x33, 0x13, 0x3f, 0xff, + 0xff, 0xf0, 0xc1, 0x98, 0x73, 0x03, 0x27, 0xff, 0xff, 0xf3, 0xc9, 0x98, 0x73, 0x23, 0x27, 0xff, + 0xff, 0xf3, 0xc9, 0x99, 0x33, 0x33, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char bm_n_uh [] PROGMEM = { + 0x07, 0x27, 0x27, 0x27, 0x07, 0x8f, 0x8f, 0xcf, 0xcf, 0xcf, 0x07, 0xe7, 0x07, 0x3f, 0x07, 0x07, + 0xe7, 0xc7, 0xe7, 0x07, 0x27, 0x27, 0x07, 0xe7, 0xe7, 0x07, 0x3f, 0x07, 0xe7, 0x07, 0x07, 0x3f, + 0x07, 0x27, 0x07, 0x07, 0xc7, 0xcf, 0x9f, 0x1f, 0x07, 0x27, 0x07, 0x27, 0x07, 0x07, 0x27, 0x07, + 0xe7, 0xe7 +}; + +const unsigned char bm_plug [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x7f, 0x80, 0x55, 0xfc, 0x00, 0xaa, 0xfc, 0x00, 0x00, + 0x7f, 0x80, 0x00, 0x1c, 0x00 +}; + +const unsigned char bm_hg_low [] PROGMEM = { + 0xf8, 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0xf8, 0xf8 +}; + +const unsigned char bm_hg_high [] PROGMEM = { + 0xf8, 0x88, 0xf8, 0x70, 0x20, 0x70, 0xf8, 0xf8, 0xf8 +}; \ No newline at end of file diff --git a/RNode_Firmware/Input.h b/RNode_Firmware/Input.h new file mode 100644 index 0000000..996a09c --- /dev/null +++ b/RNode_Firmware/Input.h @@ -0,0 +1,95 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef INPUT_H + #define INPUT_H + + #define PIN_BUTTON pin_btn_usr1 + + #define PRESSED LOW + #define RELEASED HIGH + + #define EVENT_ALL 0x00 + #define EVENT_CLICKS 0x01 + #define EVENT_BUTTON_DOWN 0x11 + #define EVENT_BUTTON_UP 0x12 + #define EVENT_BUTTON_CLICK 0x13 + #define EVENT_BUTTON_DOUBLE_CLICK 0x14 + #define EVENT_BUTTON_TRIPLE_CLICK 0x15 + + int button_events = EVENT_CLICKS; + int button_state = RELEASED; + int debounce_state = button_state; + unsigned long button_debounce_last = 0; + unsigned long button_debounce_delay = 25; + unsigned long button_down_last = 0; + unsigned long button_up_last = 0; + + // Forward declaration + void button_event(uint8_t event, unsigned long duration); + + void input_init() { + pinMode(PIN_BUTTON, INPUT_PULLUP); + } + + void input_get_all_events() { + button_events = EVENT_ALL; + } + + void input_get_click_events() { + button_events = EVENT_CLICKS; + } + + void input_read() { + int button_reading = digitalRead(PIN_BUTTON); + if (button_reading != debounce_state) { + button_debounce_last = millis(); + debounce_state = button_reading; + } + + if ((millis() - button_debounce_last) > button_debounce_delay) { + if (button_reading != button_state) { + // State changed + int previous_state = button_state; + button_state = button_reading; + + if (button_events == EVENT_ALL) { + if (button_state == PRESSED) { + button_event(EVENT_BUTTON_DOWN, 0); + } else if (button_state == RELEASED) { + button_event(EVENT_BUTTON_UP, 0); + } + } else if (button_events == EVENT_CLICKS) { + if (previous_state == PRESSED && button_state == RELEASED) { + button_up_last = millis(); + button_event(EVENT_BUTTON_CLICK, button_up_last-button_down_last); + } else if (previous_state == RELEASED && button_state == PRESSED) { + button_down_last = millis(); + } + } + } + } + + } + + bool button_pressed() { + if (button_state == PRESSED) { + return true; + } else { + return false; + } + } + +#endif \ No newline at end of file diff --git a/RNode_Firmware/MD5.cpp b/RNode_Firmware/MD5.cpp new file mode 100644 index 0000000..56daa77 --- /dev/null +++ b/RNode_Firmware/MD5.cpp @@ -0,0 +1,301 @@ +#include "MD5.h" + +MD5::MD5() +{ + //nothing + return; +} + +char* MD5::make_digest(const unsigned char *digest, int len) /* {{{ */ +{ + char * md5str = (char*) malloc(sizeof(char)*(len*2+1)); + static const char hexits[17] = "0123456789abcdef"; + int i; + + for (i = 0; i < len; i++) { + md5str[i * 2] = hexits[digest[i] >> 4]; + md5str[(i * 2) + 1] = hexits[digest[i] & 0x0F]; + } + md5str[len * 2] = '\0'; + return md5str; +} + +/* + * The basic MD5 functions. + * + * E and G are optimized compared to their RFC 1321 definitions for + * architectures that lack an AND-NOT instruction, just like in Colin Plumb's + * implementation. + * E() has been used instead of F() because F() is already defined in the Arduino core + */ +#define E(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +/* + * The MD5 transformation for all four rounds. + */ +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +/* + * SET reads 4 input bytes in little-endian byte order and stores them + * in a properly aligned word in host byte order. + * + * The check for little-endian architectures that tolerate unaligned + * memory accesses is just an optimization. Nothing will break if it + * doesn't work. + */ +#if defined(__i386__) || defined(__x86_64__) || defined(__vax__) +# define SET(n) \ + (*(MD5_u32plus *)&ptr[(n) * 4]) +# define GET(n) \ + SET(n) +#else +# define SET(n) \ + (ctx->block[(n)] = \ + (MD5_u32plus)ptr[(n) * 4] | \ + ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ + ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ + ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) +# define GET(n) \ + (ctx->block[(n)]) +#endif + +/* + * This processes one or more 64-byte data blocks, but does NOT update + * the bit counters. There are no alignment requirements. + */ +const void *MD5::body(void *ctxBuf, const void *data, size_t size) +{ + MD5_CTX *ctx = (MD5_CTX*)ctxBuf; + const unsigned char *ptr; + MD5_u32plus a, b, c, d; + MD5_u32plus saved_a, saved_b, saved_c, saved_d; + + ptr = (unsigned char*)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + +/* Round 1 + * E() has been used instead of F() because F() is already defined in the Arduino core + */ + STEP(E, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(E, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(E, c, d, a, b, SET(2), 0x242070db, 17) + STEP(E, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(E, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(E, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(E, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(E, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(E, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(E, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(E, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(E, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(E, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(E, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(E, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(E, b, c, d, a, SET(15), 0x49b40821, 22) + +/* Round 2 */ + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + +/* Round 3 */ + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) + +/* Round 4 */ + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void MD5::MD5Init(void *ctxBuf) +{ + MD5_CTX *ctx = (MD5_CTX*)ctxBuf; + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; + + memset(ctx->block, 0, sizeof(ctx->block)); + memset(ctx->buffer, 0, sizeof(ctx->buffer)); +} + +void MD5::MD5Update(void *ctxBuf, const void *data, size_t size) +{ + MD5_CTX *ctx = (MD5_CTX*)ctxBuf; + MD5_u32plus saved_lo; + MD5_u32plus used, free; + + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) { + ctx->hi++; + } + ctx->hi += size >> 29; + + used = saved_lo & 0x3f; + + if (used) { + free = 64 - used; + + if (size < free) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, free); + data = (unsigned char *)data + free; + size -= free; + body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = body(ctx, data, size & ~(size_t)0x3f); + size &= 0x3f; + } + + memcpy(ctx->buffer, data, size); +} + +void MD5::MD5Final(unsigned char *result, void *ctxBuf) +{ + MD5_CTX *ctx = (MD5_CTX*)ctxBuf; + MD5_u32plus used, free; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + free = 64 - used; + + if (free < 8) { + memset(&ctx->buffer[used], 0, free); + body(ctx, ctx->buffer, 64); + used = 0; + free = 64; + } + + memset(&ctx->buffer[used], 0, free - 8); + + ctx->lo <<= 3; + ctx->buffer[56] = ctx->lo; + ctx->buffer[57] = ctx->lo >> 8; + ctx->buffer[58] = ctx->lo >> 16; + ctx->buffer[59] = ctx->lo >> 24; + ctx->buffer[60] = ctx->hi; + ctx->buffer[61] = ctx->hi >> 8; + ctx->buffer[62] = ctx->hi >> 16; + ctx->buffer[63] = ctx->hi >> 24; + + body(ctx, ctx->buffer, 64); + + result[0] = ctx->a; + result[1] = ctx->a >> 8; + result[2] = ctx->a >> 16; + result[3] = ctx->a >> 24; + result[4] = ctx->b; + result[5] = ctx->b >> 8; + result[6] = ctx->b >> 16; + result[7] = ctx->b >> 24; + result[8] = ctx->c; + result[9] = ctx->c >> 8; + result[10] = ctx->c >> 16; + result[11] = ctx->c >> 24; + result[12] = ctx->d; + result[13] = ctx->d >> 8; + result[14] = ctx->d >> 16; + result[15] = ctx->d >> 24; + + memset(ctx, 0, sizeof(*ctx)); +} +unsigned char* MD5::make_hash(char *arg) +{ + MD5_CTX context; + unsigned char * hash = (unsigned char *) malloc(16); + MD5Init(&context); + MD5Update(&context, arg, strlen(arg)); + MD5Final(hash, &context); + return hash; +} +unsigned char* MD5::make_hash(char *arg,size_t size) +{ + MD5_CTX context; + unsigned char * hash = (unsigned char *) malloc(16); + MD5Init(&context); + MD5Update(&context, arg, size); + MD5Final(hash, &context); + return hash; +} \ No newline at end of file diff --git a/RNode_Firmware/MD5.h b/RNode_Firmware/MD5.h new file mode 100644 index 0000000..3ec8d81 --- /dev/null +++ b/RNode_Firmware/MD5.h @@ -0,0 +1,52 @@ +#ifndef MD5_h +#define MD5_h + +#include "Arduino.h" + +/* + * This is an OpenSSL-compatible implementation of the RSA Data Security, + * Inc. MD5 Message-Digest Algorithm (RFC 1321). + * + * Written by Solar Designer in 2001, and placed + * in the public domain. There's absolutely no warranty. + * + * This differs from Colin Plumb's older public domain implementation in + * that no 32-bit integer data type is required, there's no compile-time + * endianness configuration, and the function prototypes match OpenSSL's. + * The primary goals are portability and ease of use. + * + * This implementation is meant to be fast, but not as fast as possible. + * Some known optimizations are not included to reduce source code size + * and avoid compile-time configuration. + */ + +/* + * Updated by Scott MacVicar for arduino + * + */ + +#include + +typedef unsigned long MD5_u32plus; + +typedef struct { + MD5_u32plus lo, hi; + MD5_u32plus a, b, c, d; + unsigned char buffer[64]; + MD5_u32plus block[16]; +} MD5_CTX; + +class MD5 +{ +public: + MD5(); + static unsigned char* make_hash(char *arg); + static unsigned char* make_hash(char *arg,size_t size); + static char* make_digest(const unsigned char *digest, int len); + static const void *body(void *ctxBuf, const void *data, size_t size); + static void MD5Init(void *ctxBuf); + static void MD5Final(unsigned char *result, void *ctxBuf); + static void MD5Update(void *ctxBuf, const void *data, size_t size); +}; + +#endif \ No newline at end of file diff --git a/RNode_Firmware/Modem.h b/RNode_Firmware/Modem.h new file mode 100644 index 0000000..027e314 --- /dev/null +++ b/RNode_Firmware/Modem.h @@ -0,0 +1,4 @@ +#define SX1276 0x01 +#define SX1278 0x02 +#define SX1262 0x03 +#define SX1280 0x04 diff --git a/RNode_Firmware/Power.h b/RNode_Firmware/Power.h new file mode 100644 index 0000000..7ce6d8a --- /dev/null +++ b/RNode_Firmware/Power.h @@ -0,0 +1,650 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#define PMU_TEMP_MIN -30 +#define PMU_TEMP_MAX 90 +#define PMU_TEMP_OFFSET 120 +bool pmu_temp_sensor_ready = false; +float pmu_temperature = PMU_TEMP_MIN-1; + +#if BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1 + #include + XPowersLibInterface* PMU = NULL; + + #ifndef PMU_WIRE_PORT + #if BOARD_MODEL == BOARD_TBEAM_S_V1 + #define PMU_WIRE_PORT Wire1 + #else + #define PMU_WIRE_PORT Wire + #endif + #endif + + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.14 + + void disablePeripherals() { + if (PMU) { + // GNSS RTC PowerVDD + PMU->enablePowerOutput(XPOWERS_VBACKUP); + + // LoRa VDD + PMU->disablePowerOutput(XPOWERS_ALDO2); + + // GNSS VDD + PMU->disablePowerOutput(XPOWERS_ALDO3); + } + } + + bool pmuInterrupt; + void setPmuFlag() + { + pmuInterrupt = true; + } +#elif BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.3 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 5 + const uint8_t pin_vbat = 35; + float bat_p_samples[BAT_SAMPLES]; + float bat_v_samples[BAT_SAMPLES]; + uint8_t bat_samples_count = 0; + int bat_discharging_samples = 0; + int bat_charging_samples = 0; + int bat_charged_samples = 0; + bool bat_voltage_dropping = false; + float bat_delay_v = 0; + float bat_state_change_v = 0; +#elif BOARD_MODEL == BOARD_T3S3 + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.217 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 5 + const uint8_t pin_vbat = 1; + float bat_p_samples[BAT_SAMPLES]; + float bat_v_samples[BAT_SAMPLES]; + uint8_t bat_samples_count = 0; + int bat_discharging_samples = 0; + int bat_charging_samples = 0; + int bat_charged_samples = 0; + bool bat_voltage_dropping = false; + float bat_delay_v = 0; + float bat_state_change_v = 0; +#elif BOARD_MODEL == BOARD_TDECK + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.3 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 5 + const uint8_t pin_vbat = 4; + float bat_p_samples[BAT_SAMPLES]; + float bat_v_samples[BAT_SAMPLES]; + uint8_t bat_samples_count = 0; + int bat_discharging_samples = 0; + int bat_charging_samples = 0; + int bat_charged_samples = 0; + bool bat_voltage_dropping = false; + float bat_delay_v = 0; + float bat_state_change_v = 0; +#elif BOARD_MODEL == BOARD_HELTEC32_V3 + // Unless we implement some real voodoo + // on these boards, we can't say with + // any certainty whether we are actually + // charging and have reached a charge + // complete state. The *only* data point + // we have to go from is the bus voltage. + // The BAT_V_CHG and BAT_V_FLOAT values + // are set high here to avoid the display + // indication confusingly flapping + // between charge completed, charging and + // discharging states. + // Update: Vodoo implemented. Hopefully + // it will work accross different boards. + #define BAT_V_MIN 3.05 + #define BAT_V_MAX 4.0 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 7 + const uint8_t pin_vbat = 1; + const uint8_t pin_ctrl = 37; + float bat_p_samples[BAT_SAMPLES]; + float bat_v_samples[BAT_SAMPLES]; + uint8_t bat_samples_count = 0; + int bat_discharging_samples = 0; + int bat_charging_samples = 0; + int bat_charged_samples = 0; + bool bat_voltage_dropping = false; + float bat_delay_v = 0; + float bat_state_change_v = 0; +#elif BOARD_MODEL == BOARD_HELTEC32_V4 + #define BAT_V_MIN 3.05 + #define BAT_V_MAX 4.0 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 7 + const uint8_t pin_vbat = 1; + const uint8_t pin_ctrl = 37; + float bat_p_samples[BAT_SAMPLES]; + float bat_v_samples[BAT_SAMPLES]; + uint8_t bat_samples_count = 0; + int bat_discharging_samples = 0; + int bat_charging_samples = 0; + int bat_charged_samples = 0; + bool bat_voltage_dropping = false; + float bat_delay_v = 0; + float bat_state_change_v = 0; +#elif BOARD_MODEL == BOARD_HELTEC_T114 + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.165 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 7 + const uint8_t pin_vbat = 4; + const uint8_t pin_ctrl = 6; + float bat_p_samples[BAT_SAMPLES]; + float bat_v_samples[BAT_SAMPLES]; + uint8_t bat_samples_count = 0; + int bat_discharging_samples = 0; + int bat_charging_samples = 0; + int bat_charged_samples = 0; + bool bat_voltage_dropping = false; + float bat_delay_v = 0; + float bat_state_change_v = 0; +#elif BOARD_MODEL == BOARD_TECHO + #define BAT_V_MIN 3.15 + #define BAT_V_MAX 4.16 + #define BAT_V_CHG 4.48 + #define BAT_V_FLOAT 4.33 + #define BAT_SAMPLES 7 + const uint8_t pin_vbat = 4; + float bat_p_samples[BAT_SAMPLES]; + float bat_v_samples[BAT_SAMPLES]; + uint8_t bat_samples_count = 0; + int bat_discharging_samples = 0; + int bat_charging_samples = 0; + int bat_charged_samples = 0; + bool bat_voltage_dropping = false; + float bat_delay_v = 0; + float bat_state_change_v = 0; +#endif + +uint32_t last_pmu_update = 0; +uint8_t pmu_target_pps = 1; +int pmu_update_interval = 1000/pmu_target_pps; +uint8_t pmu_charged_ascertain = 0; +uint8_t pmu_rc = 0; +uint8_t pmu_sc = 0; +float bat_delay_diff = 0; +bool bat_diff_positive = false; +#define PMU_R_INTERVAL 5 +#define PMU_SCV_RESET_INTERVAL 3 +void kiss_indicate_battery(); +void kiss_indicate_temperature(); + +void measure_temperature() { + #if PLATFORM == PLATFORM_ESP32 + if (pmu_temp_sensor_ready) { pmu_temperature = temperatureRead(); } else { pmu_temperature = PMU_TEMP_MIN-1; } + #endif +} + +void measure_battery() { + #if BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC_T114 || BOARD_MODEL == BOARD_TECHO + battery_installed = true; + #if BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 + battery_indeterminate = false; + #else + battery_indeterminate = true; + #endif + + #if BOARD_MODEL == BOARD_HELTEC32_V3 + float battery_measurement = (float)(analogRead(pin_vbat)) * 0.0041; + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + float battery_measurement = (float)(analogRead(pin_vbat)) * 0.00418; + #elif BOARD_MODEL == BOARD_T3S3 + float battery_measurement = (float)(analogRead(pin_vbat)) / 4095.0*6.7828; + #elif BOARD_MODEL == BOARD_HELTEC_T114 + float battery_measurement = (float)(analogRead(pin_vbat)) * 0.017165; + #elif BOARD_MODEL == BOARD_TECHO + float battery_measurement = (float)(analogRead(pin_vbat)) * 0.007067; + #else + float battery_measurement = (float)(analogRead(pin_vbat)) / 4095.0*7.26; + #endif + + bat_v_samples[bat_samples_count%BAT_SAMPLES] = battery_measurement; + bat_p_samples[bat_samples_count%BAT_SAMPLES] = ((battery_voltage-BAT_V_MIN) / (BAT_V_MAX-BAT_V_MIN))*100.0; + + bat_samples_count++; + if (!battery_ready && bat_samples_count >= BAT_SAMPLES) { + battery_ready = true; + } + + if (battery_ready) { + + battery_percent = 0; + for (uint8_t bi = 0; bi < BAT_SAMPLES; bi++) { + battery_percent += bat_p_samples[bi]; + } + battery_percent = battery_percent/BAT_SAMPLES; + + battery_voltage = 0; + for (uint8_t bi = 0; bi < BAT_SAMPLES; bi++) { + battery_voltage += bat_v_samples[bi]; + } + battery_voltage = battery_voltage/BAT_SAMPLES; + + if (bat_delay_v == 0) bat_delay_v = battery_voltage; + if (bat_state_change_v == 0) bat_state_change_v = battery_voltage; + if (battery_percent > 100.0) battery_percent = 100.0; + if (battery_percent < 0.0) battery_percent = 0.0; + + if (bat_samples_count%BAT_SAMPLES == 0) { + pmu_sc++; + bat_delay_diff = battery_voltage-bat_state_change_v; + + if (battery_voltage < bat_delay_v && battery_voltage < BAT_V_FLOAT) { + if (bat_voltage_dropping == false) { + if (bat_delay_diff < -0.008) { + bat_voltage_dropping = true; + bat_state_change_v = battery_voltage; + } + } else { + if (pmu_sc%PMU_SCV_RESET_INTERVAL == 0) { bat_state_change_v = battery_voltage; } + } + } else { + if (bat_voltage_dropping == true) { + if (bat_delay_diff > 0.01) { + bat_voltage_dropping = false; + bat_state_change_v = battery_voltage; + } + } + } + bat_samples_count = 0; + bat_delay_v = battery_voltage; + } + + if (bat_voltage_dropping && battery_voltage < BAT_V_FLOAT) { + // if (battery_state != BATTERY_STATE_DISCHARGING) { SerialBT.printf("STATE CHANGE to DISCHARGING at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v); } + battery_state = BATTERY_STATE_DISCHARGING; + pmu_charged_ascertain = 0; + } else { + if (pmu_charged_ascertain < 8) { pmu_charged_ascertain++; } + else { + if (battery_percent < 100.0) { + // if (battery_state != BATTERY_STATE_CHARGING) { SerialBT.printf("STATE CHANGE to CHARGING at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v); } + battery_state = BATTERY_STATE_CHARGING; + } else { + // if (battery_state != BATTERY_STATE_CHARGED) { SerialBT.printf("STATE CHANGE to CHARGED at delta=%.3fv. State change v is now %.3fv.\n", bat_delay_diff, bat_state_change_v); } + battery_state = BATTERY_STATE_CHARGED; + } + } + } + + #if MCU_VARIANT == MCU_NRF52 + if (bt_state != BT_STATE_OFF) { blebas.write(battery_percent); } + #endif + + // if (bt_state == BT_STATE_CONNECTED) { + // SerialBT.printf("\nBus voltage %.3fv. Unfiltered %.3fv. Diff %.3f", battery_voltage, bat_v_samples[BAT_SAMPLES-1], bat_delay_diff); + // if (bat_voltage_dropping) { SerialBT.printf("\n Voltage is dropping. Percentage %.1f%%.", battery_percent); } + // else { SerialBT.printf("\n Voltage is not dropping. Percentage %.1f%%.", battery_percent); } + // if (battery_state == BATTERY_STATE_DISCHARGING) { SerialBT.printf("\n Battery discharging. delay_v %.3fv\nState change at %.3fv", bat_delay_v, bat_state_change_v); } + // if (battery_state == BATTERY_STATE_CHARGING) { SerialBT.printf("\n Battery charging. delay_v %.3fv\nState change at %.3fv", bat_delay_v, bat_state_change_v); } + // if (battery_state == BATTERY_STATE_CHARGED) { SerialBT.print("\n Battery is charged."); } + // SerialBT.print("\n"); + // } + } + + #elif BOARD_MODEL == BOARD_TBEAM || BOARD_MODEL == BOARD_TBEAM_S_V1 + if (PMU) { + float discharge_current = 0; + float charge_current = 0; + float ext_voltage = 0; + float ext_current = 0; + if (PMU->getChipModel() == XPOWERS_AXP192) { + discharge_current = ((XPowersAXP192*)PMU)->getBattDischargeCurrent(); + charge_current = ((XPowersAXP192*)PMU)->getBatteryChargeCurrent(); + battery_voltage = PMU->getBattVoltage()/1000.0; + // battery_percent = PMU->getBattPercentage()*1.0; + battery_installed = PMU->isBatteryConnect(); + external_power = PMU->isVbusIn(); + ext_voltage = PMU->getVbusVoltage()/1000.0; + ext_current = ((XPowersAXP192*)PMU)->getVbusCurrent(); + } + else if (PMU->getChipModel() == XPOWERS_AXP2101) { + battery_voltage = PMU->getBattVoltage()/1000.0; + // battery_percent = PMU->getBattPercentage()*1.0; + battery_installed = PMU->isBatteryConnect(); + external_power = PMU->isVbusIn(); + ext_voltage = PMU->getVbusVoltage()/1000.0; + } + + if (battery_installed) { + if (PMU->isCharging()) { + battery_state = BATTERY_STATE_CHARGING; + battery_percent = ((battery_voltage-BAT_V_MIN) / (BAT_V_MAX-BAT_V_MIN))*100.0; + } else { + if (PMU->isDischarge()) { + battery_state = BATTERY_STATE_DISCHARGING; + battery_percent = ((battery_voltage-BAT_V_MIN) / (BAT_V_MAX-BAT_V_MIN))*100.0; + } else { + battery_state = BATTERY_STATE_CHARGED; + battery_percent = 100.0; + } + } + } else { + battery_state = BATTERY_STATE_UNKNOWN; + battery_percent = 0.0; + battery_voltage = 0.0; + } + + if (battery_percent > 100.0) battery_percent = 100.0; + if (battery_percent < 0.0) battery_percent = 0.0; + + float charge_watts = battery_voltage*(charge_current/1000.0); + float discharge_watts = battery_voltage*(discharge_current/1000.0); + float ext_watts = ext_voltage*(ext_current/1000.0); + + battery_ready = true; + + // if (bt_state == BT_STATE_CONNECTED) { + // if (battery_installed) { + // if (external_power) { + // SerialBT.printf("External power connected, drawing %.2fw, %.1fmA at %.1fV\n", ext_watts, ext_current, ext_voltage); + // } else { + // SerialBT.println("Running on battery"); + // } + // SerialBT.printf("Battery percentage %.1f%%\n", battery_percent); + // SerialBT.printf("Battery voltage %.2fv\n", battery_voltage); + // // SerialBT.printf("Temperature %.1f%\n", auxillary_temperature); + + // if (battery_state == BATTERY_STATE_CHARGING) { + // SerialBT.printf("Charging with %.2fw, %.1fmA at %.1fV\n", charge_watts, charge_current, battery_voltage); + // } else if (battery_state == BATTERY_STATE_DISCHARGING) { + // SerialBT.printf("Discharging at %.2fw, %.1fmA at %.1fV\n", discharge_watts, discharge_current, battery_voltage); + // } else if (battery_state == BATTERY_STATE_CHARGED) { + // SerialBT.printf("Battery charged\n"); + // } + // } else { + // SerialBT.println("No battery installed"); + // } + // SerialBT.println(""); + // } + } + else { + battery_ready = false; + } + #endif + + if (battery_ready) { + pmu_rc++; + if (pmu_rc%PMU_R_INTERVAL == 0) { + kiss_indicate_battery(); + if (pmu_temp_sensor_ready) { kiss_indicate_temperature(); } + } + } +} + +void update_pmu() { + if (millis()-last_pmu_update >= pmu_update_interval) { + measure_battery(); + measure_temperature(); + last_pmu_update = millis(); + } +} + +bool init_pmu() { + #if IS_ESP32S3 + pmu_temp_sensor_ready = true; + #endif + + #if BOARD_MODEL == BOARD_RNODE_NG_21 || BOARD_MODEL == BOARD_LORA32_V2_1 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_TECHO + pinMode(pin_vbat, INPUT); + return true; + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + // there are three version of V3: V3, V3.1, and V3.2 + // V3 and V3.1 have a pull up on pin_ctrl and are active low + // V3.2 has a transistor and active high + // put the pin input mode and read it. if it's high, we have V3 or V3.1 + // other wise, it's a V3.2 + uint16_t pin_ctrl_value; + uint8_t pin_ctrl_active = LOW; + pinMode(pin_ctrl, INPUT); + pin_ctrl_value = digitalRead(pin_ctrl); + if(pin_ctrl_value == HIGH) { + // We have either a V3 or V3.1 + pin_ctrl_active = LOW; + } + else { + // We have a V3.2 + pin_ctrl_active = HIGH; + } + pinMode(pin_ctrl,OUTPUT); + digitalWrite(pin_ctrl, pin_ctrl_active); + return true; + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + pinMode(pin_ctrl,OUTPUT); + digitalWrite(pin_ctrl, HIGH); + return true; + #elif BOARD_MODEL == BOARD_HELTEC_T114 + pinMode(pin_ctrl,OUTPUT); + digitalWrite(pin_ctrl, HIGH); + return true; + #elif BOARD_MODEL == BOARD_TBEAM + Wire.begin(I2C_SDA, I2C_SCL); + + if (!PMU) { + PMU = new XPowersAXP2101(PMU_WIRE_PORT); + if (!PMU->init()) { + delete PMU; + PMU = NULL; + } + } + + if (!PMU) { + PMU = new XPowersAXP192(PMU_WIRE_PORT); + if (!PMU->init()) { + delete PMU; + PMU = NULL; + } + } + + if (!PMU) { + return false; + } + + // Configure charging indicator + PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF); + + pinMode(PMU_IRQ, INPUT_PULLUP); + attachInterrupt(PMU_IRQ, setPmuFlag, FALLING); + + if (PMU->getChipModel() == XPOWERS_AXP192) { + + // Turn off unused power sources to save power + PMU->disablePowerOutput(XPOWERS_DCDC1); + PMU->disablePowerOutput(XPOWERS_DCDC2); + PMU->disablePowerOutput(XPOWERS_LDO2); + PMU->disablePowerOutput(XPOWERS_LDO3); + + // Set the power of LoRa and GPS module to 3.3V + // LoRa + PMU->setPowerChannelVoltage(XPOWERS_LDO2, 3300); + // GPS + PMU->setPowerChannelVoltage(XPOWERS_LDO3, 3300); + // OLED + PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); + + // Turn on LoRa + PMU->enablePowerOutput(XPOWERS_LDO2); + + // Turn on GPS + //PMU->enablePowerOutput(XPOWERS_LDO3); + + // protected oled power source + PMU->setProtectedChannel(XPOWERS_DCDC1); + // protected esp32 power source + PMU->setProtectedChannel(XPOWERS_DCDC3); + // enable oled power + PMU->enablePowerOutput(XPOWERS_DCDC1); + + PMU->disableIRQ(XPOWERS_AXP192_ALL_IRQ); + + PMU->enableIRQ(XPOWERS_AXP192_VBUS_REMOVE_IRQ | + XPOWERS_AXP192_VBUS_INSERT_IRQ | + XPOWERS_AXP192_BAT_CHG_DONE_IRQ | + XPOWERS_AXP192_BAT_CHG_START_IRQ | + XPOWERS_AXP192_BAT_REMOVE_IRQ | + XPOWERS_AXP192_BAT_INSERT_IRQ | + XPOWERS_AXP192_PKEY_SHORT_IRQ + ); + + } + else if (PMU->getChipModel() == XPOWERS_AXP2101) { + + // Turn off unused power sources to save power + PMU->disablePowerOutput(XPOWERS_DCDC2); + PMU->disablePowerOutput(XPOWERS_DCDC3); + PMU->disablePowerOutput(XPOWERS_DCDC4); + PMU->disablePowerOutput(XPOWERS_DCDC5); + PMU->disablePowerOutput(XPOWERS_ALDO1); + PMU->disablePowerOutput(XPOWERS_ALDO2); + PMU->disablePowerOutput(XPOWERS_ALDO3); + PMU->disablePowerOutput(XPOWERS_ALDO4); + PMU->disablePowerOutput(XPOWERS_BLDO1); + PMU->disablePowerOutput(XPOWERS_BLDO2); + PMU->disablePowerOutput(XPOWERS_DLDO1); + PMU->disablePowerOutput(XPOWERS_DLDO2); + PMU->disablePowerOutput(XPOWERS_VBACKUP); + + // Set the power of LoRa and GPS module to 3.3V + // LoRa + PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); + // GPS + PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); + PMU->setPowerChannelVoltage(XPOWERS_VBACKUP, 3300); + + // ESP32 VDD + // ! No need to set, automatically open , Don't close it + // PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); + // PMU->setProtectedChannel(XPOWERS_DCDC1); + PMU->setProtectedChannel(XPOWERS_DCDC1); + + // LoRa VDD + PMU->enablePowerOutput(XPOWERS_ALDO2); + + // GNSS VDD + //PMU->enablePowerOutput(XPOWERS_ALDO3); + + // GNSS RTC PowerVDD + //PMU->enablePowerOutput(XPOWERS_VBACKUP); + } + + PMU->enableSystemVoltageMeasure(); + PMU->enableVbusVoltageMeasure(); + PMU->enableBattVoltageMeasure(); + // It is necessary to disable the detection function of the TS pin on the board + // without the battery temperature detection function, otherwise it will cause abnormal charging + PMU->disableTSPinMeasure(); + + // Set the time of pressing the button to turn off + PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S); + + return true; + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + Wire1.begin(I2C_SDA, I2C_SCL); + + if (!PMU) { + PMU = new XPowersAXP2101(PMU_WIRE_PORT); + if (!PMU->init()) { + delete PMU; + PMU = NULL; + } + } + + if (!PMU) { + return false; + } + + /** + * gnss module power channel + * The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during + * initialization + */ + PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO4); + + // lora radio power channel + PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO3); + + // m.2 interface + PMU->setPowerChannelVoltage(XPOWERS_DCDC3, 3300); + PMU->enablePowerOutput(XPOWERS_DCDC3); + + /** + * ALDO2 cannot be turned off. + * It is a necessary condition for sensor communication. + * It must be turned on to properly access the sensor and screen + * It is also responsible for the power supply of PCF8563 + */ + PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO2); + + // 6-axis , magnetometer ,bme280 , oled screen power channel + PMU->setPowerChannelVoltage(XPOWERS_ALDO1, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO1); + + // sdcard power channle + PMU->setPowerChannelVoltage(XPOWERS_BLDO1, 3300); + PMU->enablePowerOutput(XPOWERS_BLDO1); + + // PMU->setPowerChannelVoltage(XPOWERS_DCDC4, 3300); + // PMU->enablePowerOutput(XPOWERS_DCDC4); + + // not use channel + PMU->disablePowerOutput(XPOWERS_DCDC2); // not elicited + PMU->disablePowerOutput(XPOWERS_DCDC5); // not elicited + PMU->disablePowerOutput(XPOWERS_DLDO1); // Invalid power channel, it does not exist + PMU->disablePowerOutput(XPOWERS_DLDO2); // Invalid power channel, it does not exist + PMU->disablePowerOutput(XPOWERS_VBACKUP); + + // Configure charging + PMU->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); + PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); + // TODO: Reset + PMU->setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG); + + // Set the time of pressing the button to turn off + PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S); + PMU->setPowerKeyPressOnTime(XPOWERS_POWERON_128MS); + + // disable all axp chip interrupt + PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); + PMU->clearIrqStatus(); + + // It is necessary to disable the detection function of the TS pin on the board + // without the battery temperature detection function, otherwise it will cause abnormal charging + PMU->disableTSPinMeasure(); + PMU->enableVbusVoltageMeasure(); + PMU->enableBattVoltageMeasure(); + + + return true; + #else + return false; + #endif +} diff --git a/RNode_Firmware/RNode_Firmware.ino b/RNode_Firmware/RNode_Firmware.ino new file mode 100644 index 0000000..42177ba --- /dev/null +++ b/RNode_Firmware/RNode_Firmware.ino @@ -0,0 +1,1822 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include +#include +#include "Utilities.h" + +FIFOBuffer serialFIFO; +uint8_t serialBuffer[CONFIG_UART_BUFFER_SIZE+1]; + +FIFOBuffer16 packet_starts; +uint16_t packet_starts_buf[CONFIG_QUEUE_MAX_LENGTH+1]; + +FIFOBuffer16 packet_lengths; +uint16_t packet_lengths_buf[CONFIG_QUEUE_MAX_LENGTH+1]; + +uint8_t packet_queue[CONFIG_QUEUE_SIZE]; + +volatile uint8_t queue_height = 0; +volatile uint16_t queued_bytes = 0; +volatile uint16_t queue_cursor = 0; +volatile uint16_t current_packet_start = 0; +volatile bool serial_buffering = false; +#if HAS_BLUETOOTH || HAS_BLE == true + bool bt_init_ran = false; +#endif + +#if HAS_CONSOLE + #include "Console.h" +#endif + +#if PLATFORM == PLATFORM_ESP32 || PLATFORM == PLATFORM_NRF52 + #define MODEM_QUEUE_SIZE 4 + typedef struct { + size_t len; + int rssi; + int snr_raw; + uint8_t data[]; + } modem_packet_t; + static xQueueHandle modem_packet_queue = NULL; +#endif + +char sbuf[128]; + +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + bool packet_ready = false; +#endif + +void setup() { + #if MCU_VARIANT == MCU_ESP32 + boot_seq(); + EEPROM.begin(EEPROM_SIZE); + Serial.setRxBufferSize(CONFIG_UART_BUFFER_SIZE); + + #if BOARD_MODEL == BOARD_TDECK + pinMode(pin_poweron, OUTPUT); + digitalWrite(pin_poweron, HIGH); + + pinMode(SD_CS, OUTPUT); + pinMode(DISPLAY_CS, OUTPUT); + digitalWrite(SD_CS, HIGH); + digitalWrite(DISPLAY_CS, HIGH); + + pinMode(DISPLAY_BL_PIN, OUTPUT); + #endif + #endif + + #if MCU_VARIANT == MCU_NRF52 + #if BOARD_MODEL == BOARD_TECHO + delay(200); + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, HIGH); + pinMode(pin_btn_usr1, INPUT_PULLUP); + pinMode(pin_btn_touch, INPUT_PULLUP); + pinMode(PIN_LED_RED, OUTPUT); + pinMode(PIN_LED_GREEN, OUTPUT); + pinMode(PIN_LED_BLUE, OUTPUT); + delay(200); + #endif + + if (!eeprom_begin()) { Serial.write("EEPROM initialisation failed.\r\n"); } + #endif + + // Seed the PRNG for CSMA R-value selection + #if MCU_VARIANT == MCU_ESP32 + // On ESP32, get the seed value from the + // hardware RNG + unsigned long seed_val = (unsigned long)esp_random(); + #elif MCU_VARIANT == MCU_NRF52 + // On nRF, get the seed value from the + // hardware RNG + unsigned long seed_val = get_rng_seed(); + #else + // Otherwise, get a pseudo-random seed + // value from an unconnected analog pin + // + // CAUTION! If you are implementing the + // firmware on a platform that does not + // have a hardware RNG, you MUST take + // care to get a seed value with enough + // entropy at each device reset! + unsigned long seed_val = analogRead(0); + #endif + randomSeed(seed_val); + + // Initialise serial communication + memset(serialBuffer, 0, sizeof(serialBuffer)); + fifo_init(&serialFIFO, serialBuffer, CONFIG_UART_BUFFER_SIZE); + + Serial.begin(serial_baudrate); + + #if HAS_NP + led_init(); + #endif + + #if MCU_VARIANT == MCU_NRF52 && HAS_NP == true + boot_seq(); + #endif + + #if BOARD_MODEL != BOARD_RAK4631 && BOARD_MODEL != BOARD_HELTEC_T114 && BOARD_MODEL != BOARD_TECHO && BOARD_MODEL != BOARD_T3S3 && BOARD_MODEL != BOARD_TBEAM_S_V1 && BOARD_MODEL != BOARD_HELTEC32_V4 + // Some boards need to wait until the hardware UART is set up before booting + // the full firmware. In the case of the RAK4631 and Heltec T114, the line below will wait + // until a serial connection is actually established with a master. Thus, it + // is disabled on this platform. + while (!Serial); + #endif + + serial_interrupt_init(); + + // Configure input and output pins + #if HAS_INPUT + input_init(); + #endif + + #if HAS_NP == false + pinMode(pin_led_rx, OUTPUT); + pinMode(pin_led_tx, OUTPUT); + #endif + + #if HAS_TCXO == true + if (pin_tcxo_enable != -1) { + pinMode(pin_tcxo_enable, OUTPUT); + digitalWrite(pin_tcxo_enable, HIGH); + } + #endif + + // Initialise buffers + memset(pbuf, 0, sizeof(pbuf)); + memset(cmdbuf, 0, sizeof(cmdbuf)); + + memset(packet_queue, 0, sizeof(packet_queue)); + + memset(packet_starts_buf, 0, sizeof(packet_starts_buf)); + fifo16_init(&packet_starts, packet_starts_buf, CONFIG_QUEUE_MAX_LENGTH); + + memset(packet_lengths_buf, 0, sizeof(packet_starts_buf)); + fifo16_init(&packet_lengths, packet_lengths_buf, CONFIG_QUEUE_MAX_LENGTH); + + #if PLATFORM == PLATFORM_ESP32 || PLATFORM == PLATFORM_NRF52 + modem_packet_queue = xQueueCreate(MODEM_QUEUE_SIZE, sizeof(modem_packet_t*)); + #endif + + // Set chip select, reset and interrupt + // pins for the LoRa module + #if MODEM == SX1276 || MODEM == SX1278 + LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy); + #elif MODEM == SX1262 + LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen); + #elif MODEM == SX1280 + LoRa->setPins(pin_cs, pin_reset, pin_dio, pin_busy, pin_rxen, pin_txen); + #endif + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + init_channel_stats(); + + #if BOARD_MODEL == BOARD_T3S3 + #if MODEM == SX1280 + delay(300); + LoRa->reset(); + delay(100); + #endif + #endif + + #if BOARD_MODEL == BOARD_XIAO_S3 + // Improve wakeup from sleep + delay(300); + LoRa->reset(); + delay(100); + #endif + + // Check installed transceiver chip and + // probe boot parameters. + if (LoRa->preInit()) { + modem_installed = true; + + #if HAS_INPUT + // Skip quick-reset console activation + #else + uint32_t lfr = LoRa->getFrequency(); + if (lfr == 0) { + // Normal boot + } else if (lfr == M_FRQ_R) { + // Quick reboot + #if HAS_CONSOLE + if (rtc_get_reset_reason(0) == POWERON_RESET) { + console_active = true; + } + #endif + } else { + // Unknown boot + } + LoRa->setFrequency(M_FRQ_S); + #endif + + } else { + modem_installed = false; + } + #else + // Older variants only came with SX1276/78 chips, + // so assume that to be the case for now. + modem_installed = true; + #endif + + #if HAS_DISPLAY + #if HAS_EEPROM + if (EEPROM.read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) { + #elif MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_CONF_DSET)) != CONF_OK_BYTE) { + #endif + eeprom_update(eeprom_addr(ADDR_CONF_DSET), CONF_OK_BYTE); + #if BOARD_MODEL == BOARD_TECHO + eeprom_update(eeprom_addr(ADDR_CONF_DINT), 0x03); + #else + eeprom_update(eeprom_addr(ADDR_CONF_DINT), 0xFF); + #endif + } + #if BOARD_MODEL == BOARD_TECHO + display_add_callback(work_while_waiting); + #endif + + display_unblank(); + disp_ready = display_init(); + update_display(); + #endif + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if HAS_PMU == true + pmu_ready = init_pmu(); + #endif + + #if HAS_BLUETOOTH || HAS_BLE == true + bt_init(); + bt_init_ran = true; + #endif + + if (console_active) { + #if HAS_CONSOLE + console_start(); + #else + kiss_indicate_reset(); + #endif + } else { + kiss_indicate_reset(); + } + #endif + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if MODEM == SX1280 + avoid_interference = false; + #else + #if HAS_EEPROM + uint8_t ia_conf = EEPROM.read(eeprom_addr(ADDR_CONF_DIA)); + if (ia_conf == 0x00) { avoid_interference = true; } + else { avoid_interference = false; } + #elif MCU_VARIANT == MCU_NRF52 + uint8_t ia_conf = eeprom_read(eeprom_addr(ADDR_CONF_DIA)); + if (ia_conf == 0x00) { avoid_interference = true; } + else { avoid_interference = false; } + #endif + #endif + #endif + + // Validate board health, EEPROM and config + validate_status(); + + if (op_mode != MODE_TNC) LoRa->setFrequency(0); +} + +void lora_receive() { + if (!implicit) { + LoRa->receive(); + } else { + LoRa->receive(implicit_l); + } +} + +inline void kiss_write_packet() { + serial_write(FEND); + serial_write(CMD_DATA); + + for (uint16_t i = 0; i < read_len; i++) { + #if MCU_VARIANT == MCU_NRF52 + portENTER_CRITICAL(); + uint8_t byte = pbuf[i]; + portEXIT_CRITICAL(); + #else + uint8_t byte = pbuf[i]; + #endif + + if (byte == FEND) { serial_write(FESC); byte = TFEND; } + if (byte == FESC) { serial_write(FESC); byte = TFESC; } + serial_write(byte); + } + + serial_write(FEND); + read_len = 0; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + packet_ready = false; + #endif + + #if MCU_VARIANT == MCU_ESP32 + #if HAS_BLE + bt_flush(); + #endif + #endif +} + +inline void getPacketData(uint16_t len) { + #if MCU_VARIANT != MCU_NRF52 + while (len-- && read_len < MTU) { + pbuf[read_len++] = LoRa->read(); + } + #else + BaseType_t int_mask = taskENTER_CRITICAL_FROM_ISR(); + while (len-- && read_len < MTU) { + pbuf[read_len++] = LoRa->read(); + } + taskEXIT_CRITICAL_FROM_ISR(int_mask); + #endif +} + +void ISR_VECT receive_callback(int packet_size) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + BaseType_t int_mask; + #endif + + if (!promisc) { + // The standard operating mode allows large + // packets with a payload up to 500 bytes, + // by combining two raw LoRa packets. + // We read the 1-byte header and extract + // packet sequence number and split flags + uint8_t header = LoRa->read(); packet_size--; + uint8_t sequence = packetSequence(header); + bool ready = false; + + if (isSplitPacket(header) && seq == SEQ_UNSET) { + // This is the first part of a split + // packet, so we set the seq variable + // and add the data to the buffer + #if MCU_VARIANT == MCU_NRF52 + int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask); + #else + read_len = 0; + #endif + + seq = sequence; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + #endif + + getPacketData(packet_size); + + } else if (isSplitPacket(header) && seq == sequence) { + // This is the second part of a split + // packet, so we add it to the buffer + // and set the ready flag. + #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; + + } else if (isSplitPacket(header) && seq != sequence) { + // This split packet does not carry the + // same sequence id, so we must assume + // that we are seeing the first part of + // a new split packet. + #if MCU_VARIANT == MCU_NRF52 + int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask); + #else + read_len = 0; + #endif + seq = sequence; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + #endif + + getPacketData(packet_size); + + } else if (!isSplitPacket(header)) { + // This is not a split packet, so we + // just read it and set the ready + // flag to true. + + if (seq != SEQ_UNSET) { + // If we already had part of a split + // packet in the buffer, we clear it. + #if MCU_VARIANT == MCU_NRF52 + int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask); + #else + read_len = 0; + #endif + seq = SEQ_UNSET; + } + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + #endif + + getPacketData(packet_size); + ready = true; + } + + if (ready) { + #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(); + kiss_indicate_stat_snr(); + + // And then write the entire packet + kiss_write_packet(); + + #else + // Allocate packet struct, but abort if there + // is not enough memory available. + modem_packet_t *modem_packet = (modem_packet_t*)malloc(sizeof(modem_packet_t) + read_len); + if(!modem_packet) { memory_low = true; return; } + + // Get packet RSSI and SNR + #if MCU_VARIANT == MCU_ESP32 + modem_packet->snr_raw = LoRa->packetSnrRaw(); + modem_packet->rssi = LoRa->packetRssi(modem_packet->snr_raw); + #endif + + // Send packet to event queue, but free the + // allocated memory again if the queue is + // unable to receive the packet. + modem_packet->len = read_len; + memcpy(modem_packet->data, pbuf, read_len); + if (!modem_packet_queue || xQueueSendFromISR(modem_packet_queue, &modem_packet, NULL) != pdPASS) { + free(modem_packet); + } + #endif + } + } else { + // In promiscuous mode, raw packets are + // output directly to the host + read_len = 0; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + last_rssi = LoRa->packetRssi(); + last_snr_raw = LoRa->packetSnrRaw(); + getPacketData(packet_size); + + // We first signal the RSSI of the + // recieved packet to the host. + kiss_indicate_stat_rssi(); + kiss_indicate_stat_snr(); + + // And then write the entire packet + kiss_write_packet(); + + #else + getPacketData(packet_size); + packet_ready = true; + #endif + } +} + +bool startRadio() { + update_radio_lock(); + if (!radio_online && !console_active) { + if (!radio_locked && hw_ready) { + if (!LoRa->begin(lora_freq)) { + // The radio could not be started. + // Indicate this failure over both the + // serial port and with the onboard LEDs + radio_error = true; + kiss_indicate_error(ERROR_INITRADIO); + led_indicate_error(0); + return false; + } else { + radio_online = true; + + init_channel_stats(); + + setTXPower(); + setBandwidth(); + setSpreadingFactor(); + setCodingRate(); + getFrequency(); + + LoRa->enableCrc(); + LoRa->onReceive(receive_callback); + lora_receive(); + + // Flash an info pattern to indicate + // that the radio is now on + kiss_indicate_radiostate(); + led_indicate_info(3); + return true; + } + + } else { + // Flash a warning pattern to indicate + // that the radio was locked, and thus + // not started + radio_online = false; + kiss_indicate_radiostate(); + led_indicate_warning(3); + return false; + } + } else { + // If radio is already on, we silently + // ignore the request. + kiss_indicate_radiostate(); + return true; + } +} + +void stopRadio() { + LoRa->end(); + radio_online = false; +} + +void update_radio_lock() { + if (lora_freq != 0 && lora_bw != 0 && lora_txp != 0xFF && lora_sf != 0) { + radio_locked = false; + } else { + radio_locked = true; + } +} + +bool queue_full() { return (queue_height >= CONFIG_QUEUE_MAX_LENGTH || queued_bytes >= CONFIG_QUEUE_SIZE); } + +volatile bool queue_flushing = false; +void flush_queue(void) { + if (!queue_flushing) { + queue_flushing = true; + led_tx_on(); uint16_t processed = 0; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + while (!fifo16_isempty(&packet_starts)) { + #else + while (!fifo16_isempty_locked(&packet_starts)) { + #endif + + uint16_t start = fifo16_pop(&packet_starts); + uint16_t length = fifo16_pop(&packet_lengths); + + if (length >= MIN_L && length <= MTU) { + for (uint16_t i = 0; i < length; i++) { + uint16_t pos = (start+i)%CONFIG_QUEUE_SIZE; + tbuf[i] = packet_queue[pos]; + } + + transmit(length); processed++; + } + } + + lora_receive(); led_tx_off(); + } + + queue_height = 0; + queued_bytes = 0; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + update_airtime(); + #endif + + queue_flushing = false; + + #if HAS_DISPLAY + display_tx = true; + #endif +} + +void pop_queue() { + if (!queue_flushing) { + queue_flushing = true; + led_tx_on(); uint16_t processed = 0; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (!fifo16_isempty(&packet_starts)) { + #else + if (!fifo16_isempty_locked(&packet_starts)) { + #endif + + uint16_t start = fifo16_pop(&packet_starts); + uint16_t length = fifo16_pop(&packet_lengths); + if (length >= MIN_L && length <= MTU) { + for (uint16_t i = 0; i < length; i++) { + uint16_t pos = (start+i)%CONFIG_QUEUE_SIZE; + tbuf[i] = packet_queue[pos]; + } + + transmit(length); processed++; + } + queue_height -= processed; + queued_bytes -= length; + } + + lora_receive(); led_tx_off(); + } + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + update_airtime(); + #endif + + queue_flushing = false; + + #if HAS_DISPLAY + display_tx = true; + #endif +} + +void add_airtime(uint16_t written) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + float lora_symbols = 0; + float packet_cost_ms = 0.0; + int ldr_opt = 0; if (lora_low_datarate) ldr_opt = 1; + + #if MODEM == SX1276 || MODEM == SX1278 + lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*lora_sf + 8 + PHY_HEADER_LORA_SYMBOLS); + lora_symbols /= 4*(lora_sf-2*ldr_opt); + lora_symbols *= lora_cr; + lora_symbols += lora_preamble_symbols + 0.25 + 8; + packet_cost_ms += lora_symbols * lora_symbol_time_ms; + + #elif MODEM == SX1262 || MODEM == SX1280 + if (lora_sf < 7) { + lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*lora_sf + PHY_HEADER_LORA_SYMBOLS); + lora_symbols /= 4*lora_sf; + lora_symbols *= lora_cr; + lora_symbols += lora_preamble_symbols + 2.25 + 8; + packet_cost_ms += lora_symbols * lora_symbol_time_ms; + + } else { + lora_symbols += (8*written + PHY_CRC_LORA_BITS - 4*lora_sf + 8 + PHY_HEADER_LORA_SYMBOLS); + lora_symbols /= 4*(lora_sf-2*ldr_opt); + lora_symbols *= lora_cr; + lora_symbols += lora_preamble_symbols + 0.25 + 8; + packet_cost_ms += lora_symbols * lora_symbol_time_ms; + } + + #endif + + uint16_t cb = current_airtime_bin(); + uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } + airtime_bins[cb] += packet_cost_ms; + airtime_bins[nb] = 0; + + #endif +} + +void update_airtime() { + #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; } + airtime_bins[nb] = 0; airtime = (float)(airtime_bins[cb]+airtime_bins[pb])/(2.0*AIRTIME_BINLEN_MS); + + uint32_t longterm_airtime_sum = 0; + for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) { longterm_airtime_sum += airtime_bins[bin]; } + longterm_airtime = (float)longterm_airtime_sum/(float)AIRTIME_LONGTERM_MS; + + float longterm_channel_util_sum = 0.0; + for (uint16_t bin = 0; bin < AIRTIME_BINS; bin++) { longterm_channel_util_sum += longterm_bins[bin]; } + longterm_channel_util = (float)longterm_channel_util_sum/(float)AIRTIME_BINS; + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + update_csma_parameters(); + #endif + + kiss_indicate_channel_stats(); + #endif +} + +void transmit(uint16_t size) { + if (radio_online) { + if (!promisc) { + uint16_t written = 0; + uint8_t header = random(256) & 0xF0; + if (size > SINGLE_MTU - HEADER_L) { header = header | FLAG_SPLIT; } + + LoRa->beginPacket(); + LoRa->write(header); written++; + + for (uint16_t i=0; i < size; i++) { + LoRa->write(tbuf[i]); written++; + + if (written == 255 && isSplitPacket(header)) { + if (!LoRa->endPacket()) { + kiss_indicate_error(ERROR_MODEM_TIMEOUT); + kiss_indicate_error(ERROR_TXFAILED); + led_indicate_error(5); + hard_reset(); + } + + add_airtime(written); + LoRa->beginPacket(); + LoRa->write(header); + written = 1; + } + } + + if (!LoRa->endPacket()) { + kiss_indicate_error(ERROR_MODEM_TIMEOUT); + kiss_indicate_error(ERROR_TXFAILED); + led_indicate_error(5); + hard_reset(); + } + + add_airtime(written); + + } else { + led_tx_on(); uint16_t written = 0; + if (size > SINGLE_MTU) { size = SINGLE_MTU; } + if (!implicit) { LoRa->beginPacket(); } + else { LoRa->beginPacket(size); } + for (uint16_t i=0; i < size; i++) { LoRa->write(tbuf[i]); written++; } + LoRa->endPacket(); add_airtime(written); + } + + } else { kiss_indicate_error(ERROR_TXFAILED); led_indicate_error(5); } +} + +void serial_callback(uint8_t sbyte) { + if (IN_FRAME && sbyte == FEND && command == CMD_DATA) { + IN_FRAME = false; + + if (!fifo16_isfull(&packet_starts) && queued_bytes < CONFIG_QUEUE_SIZE) { + uint16_t s = current_packet_start; + int16_t e = queue_cursor-1; if (e == -1) e = CONFIG_QUEUE_SIZE-1; + uint16_t l; + + if (s != e) { l = (s < e) ? e - s + 1 : CONFIG_QUEUE_SIZE - s + e + 1; } + else { l = 1; } + + if (l >= MIN_L) { + queue_height++; + fifo16_push(&packet_starts, s); + fifo16_push(&packet_lengths, l); + current_packet_start = queue_cursor; + } + } + + } else if (sbyte == FEND) { + IN_FRAME = true; + command = CMD_UNKNOWN; + frame_len = 0; + } else if (IN_FRAME && frame_len < MTU) { + // Have a look at the command byte first + if (frame_len == 0 && command == CMD_UNKNOWN) { + command = sbyte; + } else if (command == CMD_DATA) { + if (bt_state != BT_STATE_CONNECTED) { + cable_state = CABLE_STATE_CONNECTED; + } + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (queue_height < CONFIG_QUEUE_MAX_LENGTH && queued_bytes < CONFIG_QUEUE_SIZE) { + queued_bytes++; + packet_queue[queue_cursor++] = sbyte; + if (queue_cursor == CONFIG_QUEUE_SIZE) queue_cursor = 0; + } + } + } else if (command == CMD_FREQUENCY) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 4) { + uint32_t freq = (uint32_t)cmdbuf[0] << 24 | (uint32_t)cmdbuf[1] << 16 | (uint32_t)cmdbuf[2] << 8 | (uint32_t)cmdbuf[3]; + + if (freq == 0) { + kiss_indicate_frequency(); + } else { + lora_freq = freq; + if (op_mode == MODE_HOST) setFrequency(); + kiss_indicate_frequency(); + } + } + } else if (command == CMD_BANDWIDTH) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 4) { + uint32_t bw = (uint32_t)cmdbuf[0] << 24 | (uint32_t)cmdbuf[1] << 16 | (uint32_t)cmdbuf[2] << 8 | (uint32_t)cmdbuf[3]; + + if (bw == 0) { + kiss_indicate_bandwidth(); + } else { + lora_bw = bw; + if (op_mode == MODE_HOST) setBandwidth(); + kiss_indicate_bandwidth(); + } + } + } else if (command == CMD_TXPOWER) { + if (sbyte == 0xFF) { + kiss_indicate_txpower(); + } else { + int txp = sbyte; + #if MODEM == SX1262 + #if HAS_LORA_PA + if (txp > PA_MAX_OUTPUT) txp = PA_MAX_OUTPUT; + #else + if (txp > 22) txp = 22; + #endif + #elif MODEM == SX1280 + #if HAS_PA + if (txp > 20) txp = 20; + #else + if (txp > 13) txp = 13; + #endif + #else + if (txp > 17) txp = 17; + #endif + + lora_txp = txp; + if (op_mode == MODE_HOST) setTXPower(); + kiss_indicate_txpower(); + } + } else if (command == CMD_SF) { + if (sbyte == 0xFF) { + kiss_indicate_spreadingfactor(); + } else { + int sf = sbyte; + if (sf < 5) sf = 5; + if (sf > 12) sf = 12; + + lora_sf = sf; + if (op_mode == MODE_HOST) setSpreadingFactor(); + kiss_indicate_spreadingfactor(); + } + } else if (command == CMD_CR) { + if (sbyte == 0xFF) { + kiss_indicate_codingrate(); + } else { + int cr = sbyte; + if (cr < 5) cr = 5; + if (cr > 8) cr = 8; + + lora_cr = cr; + if (op_mode == MODE_HOST) setCodingRate(); + kiss_indicate_codingrate(); + } + } else if (command == CMD_IMPLICIT) { + set_implicit_length(sbyte); + kiss_indicate_implicit_length(); + } else if (command == CMD_LEAVE) { + if (sbyte == 0xFF) { + display_unblank(); + cable_state = CABLE_STATE_DISCONNECTED; + current_rssi = -292; + last_rssi = -292; + last_rssi_raw = 0x00; + last_snr_raw = 0x80; + } + } else if (command == CMD_RADIO_STATE) { + if (bt_state != BT_STATE_CONNECTED) { + cable_state = CABLE_STATE_CONNECTED; + display_unblank(); + } + if (sbyte == 0xFF) { + kiss_indicate_radiostate(); + } else if (sbyte == 0x00) { + stopRadio(); + kiss_indicate_radiostate(); + } else if (sbyte == 0x01) { + startRadio(); + kiss_indicate_radiostate(); + } + } else if (command == CMD_ST_ALOCK) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 2) { + uint16_t at = (uint16_t)cmdbuf[0] << 8 | (uint16_t)cmdbuf[1]; + + if (at == 0) { + st_airtime_limit = 0.0; + } else { + st_airtime_limit = (float)at/(100.0*100.0); + if (st_airtime_limit >= 1.0) { st_airtime_limit = 0.0; } + } + kiss_indicate_st_alock(); + } + } else if (command == CMD_LT_ALOCK) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 2) { + uint16_t at = (uint16_t)cmdbuf[0] << 8 | (uint16_t)cmdbuf[1]; + + if (at == 0) { + lt_airtime_limit = 0.0; + } else { + lt_airtime_limit = (float)at/(100.0*100.0); + if (lt_airtime_limit >= 1.0) { lt_airtime_limit = 0.0; } + } + kiss_indicate_lt_alock(); + } + } else if (command == CMD_STAT_RX) { + kiss_indicate_stat_rx(); + } else if (command == CMD_STAT_TX) { + kiss_indicate_stat_tx(); + } else if (command == CMD_STAT_RSSI) { + kiss_indicate_stat_rssi(); + } else if (command == CMD_RADIO_LOCK) { + update_radio_lock(); + kiss_indicate_radio_lock(); + } else if (command == CMD_BLINK) { + led_indicate_info(sbyte); + } else if (command == CMD_RANDOM) { + kiss_indicate_random(getRandom()); + } else if (command == CMD_DETECT) { + if (sbyte == DETECT_REQ) { + if (bt_state != BT_STATE_CONNECTED) cable_state = CABLE_STATE_CONNECTED; + kiss_indicate_detect(); + } + } else if (command == CMD_PROMISC) { + if (sbyte == 0x01) { + promisc_enable(); + } else if (sbyte == 0x00) { + promisc_disable(); + } + kiss_indicate_promisc(); + } else if (command == CMD_READY) { + if (!queue_full()) { + kiss_indicate_ready(); + } else { + kiss_indicate_not_ready(); + } + } else if (command == CMD_UNLOCK_ROM) { + if (sbyte == ROM_UNLOCK_BYTE) { + unlock_rom(); + } + } else if (command == CMD_RESET) { + if (sbyte == CMD_RESET_BYTE) { + hard_reset(); + } + } else if (command == CMD_ROM_READ) { + kiss_dump_eeprom(); + } else if (command == CMD_ROM_WRITE) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == 2) { + eeprom_write(cmdbuf[0], cmdbuf[1]); + } + } else if (command == CMD_FW_VERSION) { + kiss_indicate_version(); + } else if (command == CMD_PLATFORM) { + kiss_indicate_platform(); + } else if (command == CMD_MCU) { + kiss_indicate_mcu(); + } else if (command == CMD_BOARD) { + kiss_indicate_board(); + } else if (command == CMD_CONF_SAVE) { + eeprom_conf_save(); + } else if (command == CMD_CONF_DELETE) { + eeprom_conf_delete(); + } else if (command == CMD_FB_EXT) { + #if HAS_DISPLAY == true + if (sbyte == 0xFF) { + kiss_indicate_fbstate(); + } else if (sbyte == 0x00) { + ext_fb_disable(); + kiss_indicate_fbstate(); + } else if (sbyte == 0x01) { + ext_fb_enable(); + kiss_indicate_fbstate(); + } + #endif + } else if (command == CMD_FB_WRITE) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + #if HAS_DISPLAY + if (frame_len == 9) { + uint8_t line = cmdbuf[0]; + if (line > 63) line = 63; + int fb_o = line*8; + memcpy(fb+fb_o, cmdbuf+1, 8); + } + #endif + } else if (command == CMD_FB_READ) { + if (sbyte != 0x00) { kiss_indicate_fb(); } + } else if (command == CMD_DISP_READ) { + if (sbyte != 0x00) { kiss_indicate_disp(); } + } else if (command == CMD_DEV_HASH) { + #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 || MCU_VARIANT == MCU_NRF52 + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == DEV_SIG_LEN) { + memcpy(dev_sig, cmdbuf, DEV_SIG_LEN); + device_save_signature(); + } + #endif + } else if (command == CMD_FW_UPD) { + if (sbyte == 0x01) { + firmware_update_mode = true; + } else { + firmware_update_mode = false; + } + } else if (command == CMD_HASHES) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte == 0x01) { + kiss_indicate_target_fw_hash(); + } else if (sbyte == 0x02) { + kiss_indicate_fw_hash(); + } else if (sbyte == 0x03) { + kiss_indicate_bootloader_hash(); + } else if (sbyte == 0x04) { + kiss_indicate_partition_table_hash(); + } + #endif + } else if (command == CMD_FW_HASH) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (frame_len < CMD_L) cmdbuf[frame_len++] = sbyte; + } + + if (frame_len == DEV_HASH_LEN) { + memcpy(dev_firmware_hash_target, cmdbuf, DEV_HASH_LEN); + device_save_firmware_hash(); + } + #endif + } else if (command == CMD_BT_CTRL) { + #if HAS_BLUETOOTH || HAS_BLE + if (sbyte == 0x00) { + bt_stop(); + bt_conf_save(false); + } else if (sbyte == 0x01) { + bt_start(); + bt_conf_save(true); + } else if (sbyte == 0x02) { + if (bt_state == BT_STATE_OFF) { + bt_start(); + bt_conf_save(true); + } + if (bt_state != BT_STATE_CONNECTED) { + bt_enable_pairing(); + } + } + #endif + } else if (command == CMD_BT_UNPAIR) { + #if HAS_BLE + if (sbyte == 0x01) { bt_debond_all(); } + #endif + } else if (command == CMD_DISP_INT) { + #if HAS_DISPLAY + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + display_intensity = sbyte; + di_conf_save(display_intensity); + display_unblank(); + } + #endif + } else if (command == CMD_DISP_ADDR) { + #if HAS_DISPLAY + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + display_addr = sbyte; + da_conf_save(display_addr); + } + + #endif + } else if (command == CMD_DISP_BLNK) { + #if HAS_DISPLAY + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + db_conf_save(sbyte); + display_unblank(); + } + #endif + } else if (command == CMD_DISP_ROT) { + #if HAS_DISPLAY + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + drot_conf_save(sbyte); + display_unblank(); + } + #endif + } else if (command == CMD_DIS_IA) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + dia_conf_save(sbyte); + } + } else if (command == CMD_DISP_RCND) { + #if HAS_DISPLAY + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + if (sbyte > 0x00) recondition_display = true; + } + #endif + } else if (command == CMD_NP_INT) { + #if HAS_NP + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + sbyte; + led_set_intensity(sbyte); + np_int_conf_save(sbyte); + } + + #endif + } + } +} + +#if MCU_VARIANT == MCU_ESP32 + portMUX_TYPE update_lock = portMUX_INITIALIZER_UNLOCKED; +#endif + +bool medium_free() { + update_modem_status(); + if (avoid_interference && interference_detected) { return false; } + return !dcd; +} + +bool noise_floor_sampled = false; +int noise_floor_sample = 0; +int noise_floor_buffer[NOISE_FLOOR_SAMPLES] = {0}; +void update_noise_floor() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (!dcd) { + if (!noise_floor_sampled || current_rssi < noise_floor + CSMA_INFR_THRESHOLD_DB) { + noise_floor_buffer[noise_floor_sample] = current_rssi; + noise_floor_sample = noise_floor_sample+1; + if (noise_floor_sample >= NOISE_FLOOR_SAMPLES) { + noise_floor_sample %= NOISE_FLOOR_SAMPLES; + noise_floor_sampled = true; + } + + if (noise_floor_sampled) { + noise_floor = 0; + for (int ni = 0; ni < NOISE_FLOOR_SAMPLES; ni++) { noise_floor += noise_floor_buffer[ni]; } + noise_floor /= NOISE_FLOOR_SAMPLES; + } + } + } + #endif +} + +#define LED_ID_TRIG 16 +uint8_t led_id_filter = 0; +void update_modem_status() { + #if MCU_VARIANT == MCU_ESP32 + portENTER_CRITICAL(&update_lock); + #elif MCU_VARIANT == MCU_NRF52 + portENTER_CRITICAL(); + #endif + + bool carrier_detected = LoRa->dcd(); + current_rssi = LoRa->currentRssi(); + last_status_update = millis(); + + #if MCU_VARIANT == MCU_ESP32 + portEXIT_CRITICAL(&update_lock); + #elif MCU_VARIANT == MCU_NRF52 + portEXIT_CRITICAL(); + #endif + + interference_detected = !carrier_detected && (current_rssi > (noise_floor+CSMA_INFR_THRESHOLD_DB)); + if (interference_detected) { if (led_id_filter < LED_ID_TRIG) { led_id_filter += 1; } } + else { if (led_id_filter > 0) {led_id_filter -= 1; } } + + if (carrier_detected) { dcd = true; } else { dcd = false; } + + dcd_led = dcd; + if (dcd_led) { led_rx_on(); } + else { + if (interference_detected) { + if (led_id_filter >= LED_ID_TRIG && noise_floor_sampled) { led_id_on(); } + } else { + if (airtime_lock) { led_indicate_airtime_lock(); } + else { led_rx_off(); led_id_off(); } + } + } +} + +void check_modem_status() { + if (millis()-last_status_update >= status_interval_ms) { + update_modem_status(); + update_noise_floor(); + + #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) { + int util_count = 0; + for (int ui = 0; ui < DCD_SAMPLES; ui++) { + if (util_samples[ui]) util_count++; + } + local_channel_util = (float)util_count / (float)DCD_SAMPLES; + total_channel_util = local_channel_util + airtime; + if (total_channel_util > 1.0) total_channel_util = 1.0; + + int16_t cb = current_airtime_bin(); + uint16_t nb = cb+1; if (nb == AIRTIME_BINS) { nb = 0; } + if (total_channel_util > longterm_bins[cb]) longterm_bins[cb] = total_channel_util; + longterm_bins[nb] = 0.0; + + update_airtime(); + } + #endif + } +} + +void validate_status() { + #if MCU_VARIANT == MCU_1284P + uint8_t boot_flags = OPTIBOOT_MCUSR; + uint8_t F_POR = PORF; + uint8_t F_BOR = BORF; + uint8_t F_WDR = WDRF; + #elif MCU_VARIANT == MCU_2560 + uint8_t boot_flags = OPTIBOOT_MCUSR; + if (boot_flags == 0x00) boot_flags = 0x03; + uint8_t F_POR = PORF; + uint8_t F_BOR = BORF; + uint8_t F_WDR = WDRF; + #elif MCU_VARIANT == MCU_ESP32 + // TODO: Get ESP32 boot flags + uint8_t boot_flags = 0x02; + 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) { + hw_ready = false; + Serial.write("Error, invalid hardware check state\r\n"); + #if HAS_DISPLAY + if (disp_ready) { + device_init_done = true; + update_display(); + } + #endif + led_indicate_boot_error(); + } + + if (boot_flags & (1< CSMA_CW_BANDS) { new_cw_band = CSMA_CW_BANDS; } + if (new_cw_band != cw_band) { + cw_band = (uint8_t)(new_cw_band); + cw_min = (cw_band-1) * CSMA_CW_PER_BAND_WINDOWS; + cw_max = (cw_band) * CSMA_CW_PER_BAND_WINDOWS - 1; + kiss_indicate_csma_stats(); + } + } +#endif + +void tx_queue_handler() { + if (!airtime_lock && queue_height > 0) { + if (csma_cw == -1) { + csma_cw = random(cw_min, cw_max); + cw_wait_target = csma_cw * csma_slot_ms; + } + + if (difs_wait_start == -1) { // DIFS wait not yet started + if (medium_free()) { difs_wait_start = millis(); return; } // Set DIFS wait start time + else { return; } } // Medium not yet free, continue waiting + + else { // We are waiting for DIFS or CW to pass + if (!medium_free()) { difs_wait_start = -1; cw_wait_start = -1; return; } // Medium became occupied while in DIFS wait, restart waiting when free again + else { // Medium is free, so continue waiting + if (millis() < difs_wait_start+difs_ms) { return; } // DIFS has not yet passed, continue waiting + else { // DIFS has passed, and we are now in CW wait + if (cw_wait_start == -1) { cw_wait_start = millis(); return; } // If we haven't started counting CW wait time, do it from now + else { // If we are already counting CW wait time, add it to the counter + cw_wait_passed += millis()-cw_wait_start; cw_wait_start = millis(); + if (cw_wait_passed < cw_wait_target) { return; } // Contention window wait time has not yet passed, continue waiting + else { // Wait time has passed, flush the queue + if (!lora_limit_rate) { flush_queue(); } else { pop_queue(); } + cw_wait_passed = 0; csma_cw = -1; difs_wait_start = -1; } + } + } + } + } + } +} + +void work_while_waiting() { loop(); } + +void loop() { + if (radio_online) { + #if MCU_VARIANT == MCU_ESP32 + modem_packet_t *modem_packet = NULL; + if(modem_packet_queue && xQueueReceive(modem_packet_queue, &modem_packet, 0) == pdTRUE && modem_packet) { + read_len = modem_packet->len; + last_rssi = modem_packet->rssi; + last_snr_raw = modem_packet->snr_raw; + memcpy(&pbuf, modem_packet->data, modem_packet->len); + free(modem_packet); + modem_packet = NULL; + + 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; + + #elif MCU_VARIANT == MCU_NRF52 + modem_packet_t *modem_packet = NULL; + if(modem_packet_queue && xQueueReceive(modem_packet_queue, &modem_packet, 0) == pdTRUE && modem_packet) { + memcpy(&pbuf, modem_packet->data, modem_packet->len); + read_len = modem_packet->len; + free(modem_packet); + modem_packet = NULL; + + 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; + + #endif + + tx_queue_handler(); + check_modem_status(); + + } else { + if (hw_ready) { + if (console_active) { + #if HAS_CONSOLE + console_loop(); + #endif + } else { + led_indicate_standby(); + } + } else { + + led_indicate_not_ready(); + stopRadio(); + } + } + + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + buffer_serial(); + if (!fifo_isempty(&serialFIFO)) serial_poll(); + #else + if (!fifo_isempty_locked(&serialFIFO)) serial_poll(); + #endif + + #if HAS_DISPLAY + if (disp_ready && !display_updating) update_display(); + #endif + + #if HAS_PMU + if (pmu_ready) update_pmu(); + #endif + + #if HAS_BLUETOOTH || HAS_BLE == true + if (!console_active && bt_ready) update_bt(); + #endif + + #if HAS_INPUT + input_read(); + #endif + + if (memory_low) { + #if PLATFORM == PLATFORM_ESP32 + if (esp_get_free_heap_size() < 8192) { + kiss_indicate_error(ERROR_MEMORY_LOW); memory_low = false; + } else { + memory_low = false; + } + #else + kiss_indicate_error(ERROR_MEMORY_LOW); memory_low = false; + #endif + } +} + +void sleep_now() { + #if HAS_SLEEP == true + stopRadio(); // TODO: Check this on all platforms + #if PLATFORM == PLATFORM_ESP32 + #if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_XIAO_S3 + #if HAS_DISPLAY + display_intensity = 0; + update_display(true); + #endif + #endif + #if BOARD_MODEL == BOARD_HELTEC32_V4 + digitalWrite(LORA_PA_CPS, LOW); + digitalWrite(LORA_PA_CSD, LOW); + digitalWrite(LORA_PA_PWR_EN, LOW); + digitalWrite(Vext, HIGH); + #endif + #if PIN_DISP_SLEEP >= 0 + pinMode(PIN_DISP_SLEEP, OUTPUT); + digitalWrite(PIN_DISP_SLEEP, DISP_SLEEP_LEVEL); + #endif + #if HAS_BLUETOOTH + if (bt_state == BT_STATE_CONNECTED) { + bt_stop(); + delay(100); + } + #endif + esp_sleep_enable_ext0_wakeup(PIN_WAKEUP, WAKEUP_LEVEL); + esp_deep_sleep_start(); + #elif PLATFORM == PLATFORM_NRF52 + #if BOARD_MODEL == BOARD_HELTEC_T114 + npset(0,0,0); + digitalWrite(PIN_VEXT_EN, LOW); + digitalWrite(PIN_T114_TFT_BLGT, HIGH); + digitalWrite(PIN_T114_TFT_EN, HIGH); + #elif BOARD_MODEL == BOARD_TECHO + for (uint8_t i = display_intensity; i > 0; i--) { analogWrite(pin_backlight, i-1); delay(1); } + epd_black(true); delay(300); epd_black(true); delay(300); epd_black(false); + delay(2000); + analogWrite(PIN_VEXT_EN, 0); + delay(100); + #endif + sd_power_gpregret_set(0, 0x6d); + nrf_gpio_cfg_sense_input(pin_btn_usr1, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); + NRF_POWER->SYSTEMOFF = 1; + #endif + #endif +} + +void button_event(uint8_t event, unsigned long duration) { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (display_blanked) { + display_unblank(); + } else { + if (duration > 10000) { + #if HAS_CONSOLE + #if HAS_BLUETOOTH || HAS_BLE + bt_stop(); + #endif + console_active = true; + console_start(); + #endif + } else if (duration > 5000) { + #if HAS_BLUETOOTH || HAS_BLE + if (bt_state != BT_STATE_CONNECTED) { bt_enable_pairing(); } + #endif + } else if (duration > 700) { + #if HAS_SLEEP + sleep_now(); + #endif + } else { + #if HAS_BLUETOOTH || HAS_BLE + if (bt_state != BT_STATE_CONNECTED) { + if (bt_state == BT_STATE_OFF) { + bt_start(); + bt_conf_save(true); + } else { + bt_stop(); + bt_conf_save(false); + } + } + #endif + } + } + #endif +} + +volatile bool serial_polling = false; +void serial_poll() { + serial_polling = true; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + while (!fifo_isempty_locked(&serialFIFO)) { + #else + while (!fifo_isempty(&serialFIFO)) { + #endif + char sbyte = fifo_pop(&serialFIFO); + serial_callback(sbyte); + } + + serial_polling = false; +} + +#if MCU_VARIANT != MCU_ESP32 + #define MAX_CYCLES 20 +#else + #define MAX_CYCLES 10 +#endif +void buffer_serial() { + if (!serial_buffering) { + serial_buffering = true; + + uint8_t c = 0; + + #if HAS_BLUETOOTH || HAS_BLE == true + while ( + c < MAX_CYCLES && + ( (bt_state != BT_STATE_CONNECTED && Serial.available()) || (bt_state == BT_STATE_CONNECTED && SerialBT.available()) ) + ) + #else + while (c < MAX_CYCLES && Serial.available()) + #endif + { + c++; + + #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + if (!fifo_isfull_locked(&serialFIFO)) { + fifo_push_locked(&serialFIFO, Serial.read()); + } + #elif HAS_BLUETOOTH || HAS_BLE == true + if (bt_state == BT_STATE_CONNECTED) { + if (!fifo_isfull(&serialFIFO)) { + fifo_push(&serialFIFO, SerialBT.read()); + } + } else { + if (!fifo_isfull(&serialFIFO)) { + fifo_push(&serialFIFO, Serial.read()); + } + } + #else + if (!fifo_isfull(&serialFIFO)) { + fifo_push(&serialFIFO, Serial.read()); + } + #endif + } + + serial_buffering = false; + } +} + +void serial_interrupt_init() { + #if MCU_VARIANT == MCU_1284P + TCCR3A = 0; + TCCR3B = _BV(CS10) | + _BV(WGM33)| + _BV(WGM32); + + // Buffer incoming frames every 1ms + ICR3 = 16000; + TIMSK3 = _BV(ICIE3); + + #elif MCU_VARIANT == MCU_2560 + // TODO: This should probably be updated for + // atmega2560 support. Might be source of + // reported issues from snh. + TCCR3A = 0; + TCCR3B = _BV(CS10) | + _BV(WGM33)| + _BV(WGM32); + + // Buffer incoming frames every 1ms + ICR3 = 16000; + TIMSK3 = _BV(ICIE3); + + #elif MCU_VARIANT == MCU_ESP32 + // No interrupt-based polling on ESP32 + #endif + +} + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + ISR(TIMER3_CAPT_vect) { buffer_serial(); } +#endif diff --git a/RNode_Firmware/ROM.h b/RNode_Firmware/ROM.h new file mode 100644 index 0000000..27c38bb --- /dev/null +++ b/RNode_Firmware/ROM.h @@ -0,0 +1,55 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef ROM_H + #define ROM_H + #define CHECKSUMMED_SIZE 0x0B + + // ROM address map /////////////// + #define ADDR_PRODUCT 0x00 + #define ADDR_MODEL 0x01 + #define ADDR_HW_REV 0x02 + #define ADDR_SERIAL 0x03 + #define ADDR_MADE 0x07 + #define ADDR_CHKSUM 0x0B + #define ADDR_SIGNATURE 0x1B + #define ADDR_INFO_LOCK 0x9B + + #define ADDR_CONF_SF 0x9C + #define ADDR_CONF_CR 0x9D + #define ADDR_CONF_TXP 0x9E + #define ADDR_CONF_BW 0x9F + #define ADDR_CONF_FREQ 0xA3 + #define ADDR_CONF_OK 0xA7 + + #define ADDR_CONF_BT 0xB0 + #define ADDR_CONF_DSET 0xB1 + #define ADDR_CONF_DINT 0xB2 + #define ADDR_CONF_DADR 0xB3 + #define ADDR_CONF_DBLK 0xB4 + #define ADDR_CONF_DROT 0xB8 + #define ADDR_CONF_PSET 0xB5 + #define ADDR_CONF_PINT 0xB6 + #define ADDR_CONF_BSET 0xB7 + #define ADDR_CONF_DIA 0xB9 + + #define INFO_LOCK_BYTE 0x73 + #define CONF_OK_BYTE 0x73 + #define BT_ENABLE_BYTE 0x73 + + #define EEPROM_RESERVED 200 + ////////////////////////////////// + +#endif diff --git a/RNode_Firmware/ST7789.h b/RNode_Firmware/ST7789.h new file mode 100644 index 0000000..85012e8 --- /dev/null +++ b/RNode_Firmware/ST7789.h @@ -0,0 +1,440 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2024 by Heltec AutoMation + * 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. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef ST7789Spi_h +#define ST7789Spi_h + +#include "OLEDDisplay.h" +#include + + +#define ST_CMD_DELAY 0x80 // special signifier for command lists + +#define ST77XX_NOP 0x00 +#define ST77XX_SWRESET 0x01 +#define ST77XX_RDDID 0x04 +#define ST77XX_RDDST 0x09 + +#define ST77XX_SLPIN 0x10 +#define ST77XX_SLPOUT 0x11 +#define ST77XX_PTLON 0x12 +#define ST77XX_NORON 0x13 + +#define ST77XX_INVOFF 0x20 +#define ST77XX_INVON 0x21 +#define ST77XX_DISPOFF 0x28 +#define ST77XX_DISPON 0x29 +#define ST77XX_CASET 0x2A +#define ST77XX_RASET 0x2B +#define ST77XX_RAMWR 0x2C +#define ST77XX_RAMRD 0x2E + +#define ST77XX_PTLAR 0x30 +#define ST77XX_TEOFF 0x34 +#define ST77XX_TEON 0x35 +#define ST77XX_MADCTL 0x36 +#define ST77XX_COLMOD 0x3A + +#define ST77XX_MADCTL_MY 0x80 +#define ST77XX_MADCTL_MX 0x40 +#define ST77XX_MADCTL_MV 0x20 +#define ST77XX_MADCTL_ML 0x10 +#define ST77XX_MADCTL_RGB 0x00 + +#define ST77XX_RDID1 0xDA +#define ST77XX_RDID2 0xDB +#define ST77XX_RDID3 0xDC +#define ST77XX_RDID4 0xDD + +// Some ready-made 16-bit ('565') color settings: +#define ST77XX_BLACK 0x0000 +#define ST77XX_WHITE 0xFFFF +#define ST77XX_RED 0xF800 +#define ST77XX_GREEN 0x07E0 +#define ST77XX_BLUE 0x001F +#define ST77XX_CYAN 0x07FF +#define ST77XX_MAGENTA 0xF81F +#define ST77XX_YELLOW 0xFFE0 +#define ST77XX_ORANGE 0xFC00 + +#define LED_A_ON LOW + +#ifdef ESP_PLATFORM +#undef LED_A_ON +#define LED_A_ON HIGH +#define rtos_free free +#define rtos_malloc malloc +//SPIClass SPI1(HSPI); +#endif +class ST7789Spi : public OLEDDisplay { + private: + uint8_t _rst; + uint8_t _dc; + uint8_t _cs; + uint8_t _ledA; + int _miso; + int _mosi; + int _clk; + SPIClass * _spi; + SPISettings _spiSettings; + uint16_t _RGB=0xFFFF; + uint8_t _buffheight; + public: + /* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */ + ST7789Spi(SPIClass *spiClass,uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_RAWMODE,uint16_t width=240,uint16_t height=320,int mosi=-1,int miso=-1,int clk=-1) { + this->_spi = spiClass; + this->_rst = _rst; + this->_dc = _dc; + this->_cs = _cs; + this->_mosi=mosi; + this->_miso=miso; + this->_clk=clk; + //this->_ledA = _ledA; + _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0); + setGeometry(g,width,height); + } + + bool connect(){ + this->_buffheight=displayHeight / 8; + this->_buffheight+=displayHeight % 8 ? 1:0; + pinMode(_cs, OUTPUT); + pinMode(_dc, OUTPUT); + //pinMode(_ledA, OUTPUT); + if (_cs != (uint8_t) -1) { + pinMode(_cs, OUTPUT); + } + pinMode(_rst, OUTPUT); + +#ifdef ESP_PLATFORM + _spi->begin(_clk,_miso,_mosi,-1); +#else + _spi->begin(); +#endif + _spi->setClockDivider (SPI_CLOCK_DIV2); + + // Pulse Reset low for 10ms + digitalWrite(_rst, HIGH); + delay(1); + digitalWrite(_rst, LOW); + delay(10); + digitalWrite(_rst, HIGH); + _spi->begin (); + //digitalWrite(_ledA, LED_A_ON); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + + uint16_t minBoundY = UINT16_MAX; + uint16_t maxBoundY = 0; + + uint16_t minBoundX = UINT16_MAX; + uint16_t maxBoundX = 0; + + uint16_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < _buffheight; y++) { + for (x = 0; x < displayWidth; x++) { + //Serial.printf("x %d y %d\r\n",x,y); + uint16_t pos = x + y * displayWidth; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = min(minBoundY, y); + maxBoundY = max(maxBoundY, y); + minBoundX = min(minBoundX, x); + maxBoundX = max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == UINT16_MAX) return; + + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + + for (y = minBoundY; y <= maxBoundY; y++) + { + for(int temp = 0; temp<8;temp++) + { + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + //setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1); + uint32_t const pixbufcount = maxBoundX-minBoundX+1; + uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); + for (x = minBoundX; x <= maxBoundX; x++) + { + pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; + } +#ifdef ESP_PLATFORM + _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); +#else + _spi->transfer(pixbuf, NULL, 2 * pixbufcount); +#endif + rtos_free(pixbuf); + } + } + _spi->endTransaction(); + set_CS(HIGH); + + #else + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + uint8_t x, y; + for (y = 0; y < _buffheight; y++) + { + for(int temp = 0; temp<8;temp++) + { + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + setAddrWindow(y*8+temp,0,1,displayWidth); + uint32_t const pixbufcount = displayWidth; + uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); + for (x = 0; x < displayWidth; x++) + { + pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; + } +#ifdef ESP_PLATFORM + _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); +#else + _spi->transfer(pixbuf, NULL, 2 * pixbufcount); +#endif + rtos_free(pixbuf); + } + } + _spi->endTransaction(); + set_CS(HIGH); + + #endif + } + + virtual void resetOrientation() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void flipScreenVertically() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void mirrorScreen() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void setRotation(uint8_t r) { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + if (r == 1) { madctl = 0xC0; } + if (r == 2) { madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; } + if (r == 3) { madctl = 0x00; } + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + void setRGB(uint16_t c) + { + + this->_RGB=0x00|c>>8|c<<8&0xFF00; + } + + void displayOn(void) { + //sendCommand(DISPLAYON); + } + + void displayOff(void) { + //sendCommand(DISPLAYOFF); + } + +//#define ST77XX_MADCTL_MY 0x80 +//#define ST77XX_MADCTL_MX 0x40 +//#define ST77XX_MADCTL_MV 0x20 +//#define ST77XX_MADCTL_ML 0x10 + protected: + // Send all the init commands + virtual void sendInitCommands() + { + sendCommand(ST77XX_SWRESET); // 1: Software reset, no args, w/delay + delay(150); + + sendCommand(ST77XX_SLPOUT); // 2: Out of sleep mode, no args, w/delay + delay(10); + + sendCommand(ST77XX_COLMOD); // 3: Set color mode, 16-bit color + WriteData(0x55); + delay(10); + + sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions), Row/col addr, bottom-top refresh + WriteData(0x08); + + sendCommand(ST77XX_CASET); // 5: Column addr set, + WriteData(0x00); + WriteData(0x00); // XSTART = 0 + WriteData(0x00); + WriteData(240); // XEND = 240 + + sendCommand(ST77XX_RASET); // 6: Row addr set, + WriteData(0x00); + WriteData(0x00); // YSTART = 0 + WriteData(320>>8); + WriteData(320&0xFF); // YSTART = 320 + + sendCommand(ST77XX_SLPOUT); // 7: hack + delay(10); + + sendCommand(ST77XX_NORON); // 8: Normal display on, no args, w/delay + delay(10); + + sendCommand(ST77XX_DISPON); // 9: Main screen turn on, no args, delay + delay(10); + + sendCommand(ST77XX_INVON); // 10: invert + delay(10); + + //uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MX; + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + setRGB(ST77XX_GREEN); + } + + + private: + + void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + x += (320-displayWidth)/2; + y += (240-displayHeight)/2; + uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); + uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); + + writeCommand(ST77XX_CASET); // Column addr set + SPI_WRITE32(xa); + + writeCommand(ST77XX_RASET); // Row addr set + SPI_WRITE32(ya); + + writeCommand(ST77XX_RAMWR); // write to RAM + } + int getBufferOffset(void) { + return 0; + } + inline void set_CS(bool level) { + if (_cs != (uint8_t) -1) { + digitalWrite(_cs, level); + } + }; + inline void sendCommand(uint8_t com) __attribute__((always_inline)){ + set_CS(HIGH); + digitalWrite(_dc, LOW); + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(com); + _spi->endTransaction(); + set_CS(HIGH); + digitalWrite(_dc, HIGH); + } + + inline void WriteData(uint8_t data) __attribute__((always_inline)){ + digitalWrite(_cs, LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(data); + _spi->endTransaction(); + digitalWrite(_cs, HIGH); + } + void SPI_WRITE32(uint32_t l) + { + _spi->transfer(l >> 24); + _spi->transfer(l >> 16); + _spi->transfer(l >> 8); + _spi->transfer(l); + } + void writeCommand(uint8_t cmd) { + digitalWrite(_dc, LOW); + _spi->transfer(cmd); + digitalWrite(_dc, HIGH); + } + +// Private functions + void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { + this->geometry = g; + + switch (g) { + case GEOMETRY_128_128: + this->displayWidth = 128; + this->displayHeight = 128; + break; + case GEOMETRY_128_64: + this->displayWidth = 128; + this->displayHeight = 64; + break; + case GEOMETRY_128_32: + this->displayWidth = 128; + this->displayHeight = 32; + break; + case GEOMETRY_64_48: + this->displayWidth = 64; + this->displayHeight = 48; + break; + case GEOMETRY_64_32: + this->displayWidth = 64; + this->displayHeight = 32; + break; + case GEOMETRY_RAWMODE: + this->displayWidth = width > 0 ? width : 128; + this->displayHeight = height > 0 ? height : 64; + break; + } + uint8_t tmp=displayHeight % 8; + uint8_t _buffheight=displayHeight / 8; + + if(tmp!=0) + _buffheight++; + this->displayBufferSize = displayWidth * _buffheight ; + } + + + +}; + +#endif \ No newline at end of file diff --git a/RNode_Firmware/Utilities.h b/RNode_Firmware/Utilities.h new file mode 100644 index 0000000..0f85a80 --- /dev/null +++ b/RNode_Firmware/Utilities.h @@ -0,0 +1,1987 @@ +// Copyright (C) 2024, Mark Qvist + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "Config.h" + +#if HAS_EEPROM + #include +#elif PLATFORM == PLATFORM_NRF52 + #include + #include + #include + using namespace Adafruit_LittleFS_Namespace; + #define EEPROM_FILE "eeprom" + bool file_exists = false; + int written_bytes = 4; + File file(InternalFS); +#endif +#include + +#if MODEM == SX1262 +#include "sx126x.h" +sx126x *LoRa = &sx126x_modem; +#elif MODEM == SX1276 || MODEM == SX1278 +#include "sx127x.h" +sx127x *LoRa = &sx127x_modem; +#elif MODEM == SX1280 +#include "sx128x.h" +sx128x *LoRa = &sx128x_modem; +#endif + +#include "ROM.h" +#include "Framing.h" +#include "MD5.h" + +#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 +uint8_t eeprom_read(uint32_t mapped_addr); +#endif + +#if HAS_DISPLAY == true + #include "Display.h" +#else + void display_unblank() {} + bool display_blanked = false; +#endif + +#if HAS_BLUETOOTH == true || HAS_BLE == true + void kiss_indicate_btpin(); + #include "Bluetooth.h" +#endif + +#if HAS_PMU == true + #include "Power.h" +#endif + +#if HAS_INPUT == true + #include "Input.h" +#endif + +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #include "Device.h" +#endif +#if MCU_VARIANT == MCU_ESP32 + //https://github.com/espressif/esp-idf/issues/8855 + #if BOARD_MODEL == BOARD_HELTEC32_V3 + #include "hal/wdt_hal.h" + #elif BOARD_MODEL == BOARD_T3S3 + #include "hal/wdt_hal.h" + #else + #include "hal/wdt_hal.h" + #endif + #define ISR_VECT IRAM_ATTR +#else + #define ISR_VECT +#endif + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + #include + #include +#endif + +uint8_t boot_vector = 0x00; + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + uint8_t OPTIBOOT_MCUSR __attribute__ ((section(".noinit"))); + void resetFlagsInit(void) __attribute__ ((naked)) __attribute__ ((used)) __attribute__ ((section (".init0"))); + void resetFlagsInit(void) { + __asm__ __volatile__ ("sts %0, r2\n" : "=m" (OPTIBOOT_MCUSR) :); + } +#elif MCU_VARIANT == MCU_ESP32 + // TODO: Get ESP32 boot flags +#elif MCU_VARIANT == MCU_NRF52 + // TODO: Get NRF52 boot flags +#endif + +#if MCU_VARIANT == MCU_NRF52 + unsigned long get_rng_seed() { + nrf_rng_error_correction_enable(NRF_RNG); + nrf_rng_shorts_disable(NRF_RNG, NRF_RNG_SHORT_VALRDY_STOP_MASK); + nrf_rng_task_trigger(NRF_RNG, NRF_RNG_TASK_START); + while (!nrf_rng_event_check(NRF_RNG, NRF_RNG_EVENT_VALRDY)); + uint8_t rb_a = nrf_rng_random_value_get(NRF_RNG); + nrf_rng_event_clear(NRF_RNG, NRF_RNG_EVENT_VALRDY); + while (!nrf_rng_event_check(NRF_RNG, NRF_RNG_EVENT_VALRDY)); + uint8_t rb_b = nrf_rng_random_value_get(NRF_RNG); + nrf_rng_event_clear(NRF_RNG, NRF_RNG_EVENT_VALRDY); + while (!nrf_rng_event_check(NRF_RNG, NRF_RNG_EVENT_VALRDY)); + uint8_t rb_c = nrf_rng_random_value_get(NRF_RNG); + nrf_rng_event_clear(NRF_RNG, NRF_RNG_EVENT_VALRDY); + while (!nrf_rng_event_check(NRF_RNG, NRF_RNG_EVENT_VALRDY)); + uint8_t rb_d = nrf_rng_random_value_get(NRF_RNG); + nrf_rng_event_clear(NRF_RNG, NRF_RNG_EVENT_VALRDY); + nrf_rng_task_trigger(NRF_RNG, NRF_RNG_TASK_STOP); + return rb_a << 24 | rb_b << 16 | rb_c << 8 | rb_d; + } +#endif + +#if HAS_NP == true + #include + #define NUMPIXELS 1 + Adafruit_NeoPixel pixels(NUMPIXELS, pin_np, NEO_GRB + NEO_KHZ800); + + uint8_t npr = 0; + uint8_t npg = 0; + uint8_t npb = 0; + float npi = NP_M; + bool pixels_started = false; + + void led_set_intensity(uint8_t intensity) { + npi = (float)intensity/255.0; + } + + void led_init() { + #if BOARD_MODEL == BOARD_HELTEC_T114 + // Enable vext power supply to neopixel + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, HIGH); + #endif + + #if MCU_VARIANT == MCU_NRF52 + if (eeprom_read(eeprom_addr(ADDR_CONF_PSET)) == CONF_OK_BYTE) { + uint8_t int_val = eeprom_read(eeprom_addr(ADDR_CONF_PINT)); + led_set_intensity(int_val); + } + #else + if (EEPROM.read(eeprom_addr(ADDR_CONF_PSET)) == CONF_OK_BYTE) { + uint8_t int_val = EEPROM.read(eeprom_addr(ADDR_CONF_PINT)); + led_set_intensity(int_val); + } + #endif + } + + void npset(uint8_t r, uint8_t g, uint8_t b) { + if (pixels_started != true) { + pixels.begin(); + pixels_started = true; + } + + if (r != npr || g != npg || b != npb) { + npr = r; npg = g; npb = b; + pixels.setPixelColor(0, pixels.Color(npr*npi, npg*npi, npb*npi)); + pixels.show(); + } + } + + void boot_seq() { + uint8_t rs[] = { 0x00, 0x00, 0x00 }; + uint8_t gs[] = { 0x10, 0x08, 0x00 }; + uint8_t bs[] = { 0x00, 0x08, 0x10 }; + for (int i = 0; i < 1*sizeof(rs); i++) { + npset(rs[i%sizeof(rs)], gs[i%sizeof(gs)], bs[i%sizeof(bs)]); + delay(33); + npset(0x00, 0x00, 0x00); + delay(66); + } + } +#else + void boot_seq() { } +#endif + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + 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); } + void led_id_on() { } + void led_id_off() { } +#elif MCU_VARIANT == MCU_ESP32 + #if HAS_NP == true + void led_rx_on() { npset(0, 0, 0xFF); } + void led_rx_off() { npset(0, 0, 0); } + void led_tx_on() { npset(0xFF, 0x50, 0x00); } + void led_tx_off() { npset(0, 0, 0); } + void led_id_on() { npset(0x90, 0, 0x70); } + void led_id_off() { npset(0, 0, 0); } + #elif BOARD_MODEL == BOARD_RNODE_NG_20 + 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); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_RNODE_NG_21 + 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); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_T3S3 + 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); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_TBEAM + 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, LOW); } + void led_tx_off() { digitalWrite(pin_led_tx, HIGH); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_TDECK + void led_rx_on() { } + void led_rx_off() { } + void led_tx_on() { } + void led_tx_off() { } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + void led_rx_on() { } + void led_rx_off() { } + void led_tx_on() { } + void led_tx_off() { } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + #if defined(EXTERNAL_LEDS) + 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); } + void led_id_on() { } + void led_id_off() { } + #else + 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); } + void led_id_on() { } + void led_id_off() { } + #endif + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + #if defined(EXTERNAL_LEDS) + 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); } + void led_id_on() { } + void led_id_off() { } + #else + void led_rx_on() { digitalWrite(pin_led_rx, LOW); } + void led_rx_off() { digitalWrite(pin_led_rx, HIGH); } + void led_tx_on() { digitalWrite(pin_led_tx, LOW); } + void led_tx_off() { digitalWrite(pin_led_tx, HIGH); } + void led_id_on() { } + void led_id_off() { } + #endif + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + #if defined(EXTERNAL_LEDS) + 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); } + void led_id_on() { } + void led_id_off() { } + #else + 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); } + void led_id_on() { } + void led_id_off() { } + #endif + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + 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); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + 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); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_LORA32_V2_1 + 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); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_XIAO_S3 + void led_rx_on() { digitalWrite(pin_led_rx, LED_ON); } + void led_rx_off() { digitalWrite(pin_led_rx, LED_OFF); } + void led_tx_on() { digitalWrite(pin_led_tx, LED_ON); } + void led_tx_off() { digitalWrite(pin_led_tx, LED_OFF); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_VME213 + // VME213 has no physical LEDs, only E-Ink display + void led_rx_on() { } + void led_rx_off() { } + void led_tx_on() { } + void led_tx_off() { } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_HUZZAH32 + 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); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_GENERIC_ESP32 + 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); } + void led_id_on() { } + void led_id_off() { } + #endif +#elif MCU_VARIANT == MCU_NRF52 + #if HAS_NP == true + void led_rx_on() { npset(0, 0, 0xFF); } + void led_rx_off() { npset(0, 0, 0); } + void led_tx_on() { npset(0xFF, 0x50, 0x00); } + void led_tx_off() { npset(0, 0, 0); } + void led_id_on() { npset(0x90, 0, 0x70); } + void led_id_off() { npset(0, 0, 0); } + #elif BOARD_MODEL == BOARD_RAK4631 + 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); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_HELTEC_T114 + // Heltec T114 pulls pins LOW to turn on + void led_rx_on() { digitalWrite(pin_led_rx, LOW); } + void led_rx_off() { digitalWrite(pin_led_rx, HIGH); } + void led_tx_on() { digitalWrite(pin_led_tx, LOW); } + void led_tx_off() { digitalWrite(pin_led_tx, HIGH); } + void led_id_on() { } + void led_id_off() { } + #elif BOARD_MODEL == BOARD_TECHO + void led_rx_on() { digitalWrite(pin_led_rx, LED_ON); } + void led_rx_off() { digitalWrite(pin_led_rx, LED_OFF); } + void led_tx_on() { digitalWrite(pin_led_tx, LED_ON); } + void led_tx_off() { digitalWrite(pin_led_tx, LED_OFF); } + void led_id_on() { } + void led_id_off() { } + #endif +#endif + +void hard_reset(void) { + #if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + wdt_enable(WDTO_15MS); + while(true) { + led_tx_on(); led_rx_off(); + } + #elif MCU_VARIANT == MCU_ESP32 + ESP.restart(); + #elif MCU_VARIANT == MCU_NRF52 + NVIC_SystemReset(); + #endif +} + +// LED Indication: Error +void led_indicate_error(int cycles) { + #if HAS_NP == true + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + npset(0xFF, 0x00, 0x00); + delay(100); + npset(0xFF, 0x50, 0x00); + delay(100); + if (!forever) cycles--; + } + npset(0,0,0); + #else + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + digitalWrite(pin_led_rx, HIGH); + digitalWrite(pin_led_tx, LOW); + delay(100); + digitalWrite(pin_led_rx, LOW); + digitalWrite(pin_led_tx, HIGH); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + led_tx_off(); + #endif +} + +// LED Indication: Airtime Lock +void led_indicate_airtime_lock() { + #if HAS_NP == true + npset(32,0,2); + #endif +} + +// LED Indication: Boot Error +void led_indicate_boot_error() { + #if HAS_NP == true + while(true) { + npset(0xFF, 0xFF, 0xFF); + } + #else + while (true) { + led_tx_on(); + led_rx_off(); + delay(10); + led_rx_on(); + led_tx_off(); + delay(5); + } + #endif +} + +// LED Indication: Warning +void led_indicate_warning(int cycles) { + #if HAS_NP == true + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + npset(0xFF, 0x50, 0x00); + delay(100); + npset(0x00, 0x00, 0x00); + delay(100); + if (!forever) cycles--; + } + npset(0,0,0); + #else + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + digitalWrite(pin_led_tx, HIGH); + while(cycles > 0) { + led_tx_off(); + delay(100); + led_tx_on(); + delay(100); + if (!forever) cycles--; + } + led_tx_off(); + #endif +} + +// LED Indication: Info +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_rx_off(); + delay(100); + led_rx_on(); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + } +#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; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + npset(0x00, 0x00, 0xFF); + delay(100); + npset(0x00, 0x00, 0x00); + delay(100); + if (!forever) cycles--; + } + npset(0,0,0); + } + #elif BOARD_MODEL == BOARD_LORA32_V2_1 + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_rx_off(); + delay(100); + led_rx_on(); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + } + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_rx_off(); + delay(100); + led_rx_on(); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + } + #elif BOARD_MODEL == BOARD_TECHO + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_rx_off(); + delay(100); + led_rx_on(); + delay(100); + if (!forever) cycles--; + } + led_rx_off(); + } + #else + void led_indicate_info(int cycles) { + bool forever = (cycles == 0) ? true : false; + cycles = forever ? 1 : cycles; + while(cycles > 0) { + led_tx_off(); + delay(100); + led_tx_on(); + delay(100); + if (!forever) cycles--; + } + led_tx_off(); + } + #endif +#endif + + +unsigned long led_standby_ticks = 0; +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + uint8_t led_standby_min = 1; + uint8_t led_standby_max = 40; + unsigned long led_standby_wait = 11000; + +#elif MCU_VARIANT == MCU_ESP32 + + #if HAS_NP == true + int led_standby_lng = 200; + int led_standby_cut = 100; + int led_standby_min = 0; + int led_standby_max = 375+led_standby_lng; + int led_notready_min = 0; + int led_notready_max = led_standby_max; + int led_notready_value = led_notready_min; + int8_t led_notready_direction = 0; + unsigned long led_notready_ticks = 0; + unsigned long led_standby_wait = 350; + unsigned long led_console_wait = 1; + unsigned long led_notready_wait = 200; + + #else + 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 + +#elif MCU_VARIANT == MCU_NRF52 + int led_standby_lng = 200; + int led_standby_cut = 100; + 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; +int8_t led_standby_direction = 0; + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + void led_indicate_standby() { + led_standby_ticks++; + if (led_standby_ticks > led_standby_wait) { + led_standby_ticks = 0; + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + led_standby_value += led_standby_direction; + analogWrite(pin_led_rx, led_standby_value); + led_tx_off(); + } + } + +#elif MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if HAS_NP == true + void led_indicate_standby() { + led_standby_ticks++; + + if (led_standby_ticks > led_standby_wait) { + led_standby_ticks = 0; + + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + + uint8_t led_standby_intensity; + led_standby_value += led_standby_direction; + int led_standby_ti = led_standby_value - led_standby_lng; + + if (led_standby_ti < 0) { + led_standby_intensity = 0; + } else if (led_standby_ti > led_standby_cut) { + led_standby_intensity = led_standby_cut; + } else { + led_standby_intensity = led_standby_ti; + } + npset(led_standby_intensity/3, led_standby_intensity/3, led_standby_intensity/3); + } + } + + void led_indicate_console() { + npset(0x60, 0x00, 0x60); + // led_standby_ticks++; + + // if (led_standby_ticks > led_console_wait) { + // led_standby_ticks = 0; + + // if (led_standby_value <= led_standby_min) { + // led_standby_direction = 1; + // } else if (led_standby_value >= led_standby_max) { + // led_standby_direction = -1; + // } + + // uint8_t led_standby_intensity; + // led_standby_value += led_standby_direction; + // int led_standby_ti = led_standby_value - led_standby_lng; + + // if (led_standby_ti < 0) { + // led_standby_intensity = 0; + // } else if (led_standby_ti > led_standby_cut) { + // led_standby_intensity = led_standby_cut; + // } else { + // led_standby_intensity = led_standby_ti; + // } + // npset(led_standby_intensity, 0x00, led_standby_intensity); + // } + } + + #else + void led_indicate_standby() { + led_standby_ticks++; + if (led_standby_ticks > led_standby_wait) { + led_standby_ticks = 0; + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + led_standby_value += led_standby_direction; + if (led_standby_value > 253) { + #if BOARD_MODEL == BOARD_TECHO + led_rx_on(); + #else + led_tx_on(); + #endif + } else { + #if BOARD_MODEL == BOARD_TECHO + led_rx_off(); + #else + led_tx_off(); + #endif + } + #if BOARD_MODEL == BOARD_LORA32_V2_1 + #if defined(EXTERNAL_LEDS) + led_rx_off(); + #endif + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + #if defined(EXTERNAL_LEDS) + led_rx_off(); + #endif + #else + led_rx_off(); + #endif + } + } + + void led_indicate_console() { + led_indicate_standby(); + } + #endif +#endif + +#if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + void led_indicate_not_ready() { + led_standby_ticks++; + if (led_standby_ticks > led_standby_wait) { + led_standby_ticks = 0; + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + led_standby_value += led_standby_direction; + analogWrite(pin_led_tx, led_standby_value); + led_rx_off(); + } + } +#elif MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + #if HAS_NP == true + void led_indicate_not_ready() { + led_standby_ticks++; + + if (led_standby_ticks > led_notready_wait) { + led_standby_ticks = 0; + + if (led_standby_value <= led_standby_min) { + led_standby_direction = 1; + } else if (led_standby_value >= led_standby_max) { + led_standby_direction = -1; + } + + uint8_t led_standby_intensity; + led_standby_value += led_standby_direction; + int led_standby_ti = led_standby_value - led_standby_lng; + + if (led_standby_ti < 0) { + led_standby_intensity = 0; + } else if (led_standby_ti > led_standby_cut) { + led_standby_intensity = led_standby_cut; + } else { + led_standby_intensity = led_standby_ti; + } + + npset(led_standby_intensity, 0x00, 0x00); + } + } + #else + void led_indicate_not_ready() { + led_notready_ticks++; + if (led_notready_ticks > led_notready_wait) { + led_notready_ticks = 0; + if (led_notready_value <= led_notready_min) { + led_notready_direction = 1; + } else if (led_notready_value >= led_notready_max) { + led_notready_direction = -1; + } + led_notready_value += led_notready_direction; + if (led_notready_value > 128) { + led_tx_on(); + } else { + led_tx_off(); + } + #if BOARD_MODEL == BOARD_LORA32_V2_1 + #if defined(EXTERNAL_LEDS) + led_rx_off(); + #endif + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + #if defined(EXTERNAL_LEDS) + led_rx_off(); + #endif + #else + led_rx_off(); + #endif + } + } + #endif +#endif + +void serial_write(uint8_t byte) { + #if HAS_BLUETOOTH || HAS_BLE == true + if (bt_state != BT_STATE_CONNECTED) { + Serial.write(byte); + } else { + SerialBT.write(byte); + #if MCU_VARIANT == MCU_NRF52 && HAS_BLE + // This ensures that the TX buffer is flushed after a frame is queued in serial. + // serial_in_frame is used to ensure that the flush only happens at the end of the frame + if (serial_in_frame && byte == FEND) { SerialBT.flushTXD(); serial_in_frame = false; } + else if (!serial_in_frame && byte == FEND) { serial_in_frame = true; } + #endif + } + #else + Serial.write(byte); + #endif +} + +void escaped_serial_write(uint8_t byte) { + if (byte == FEND) { serial_write(FESC); byte = TFEND; } + if (byte == FESC) { serial_write(FESC); byte = TFESC; } + serial_write(byte); +} + +void kiss_indicate_reset() { + serial_write(FEND); + serial_write(CMD_RESET); + serial_write(CMD_RESET_BYTE); + serial_write(FEND); +} + +void kiss_indicate_error(uint8_t error_code) { + serial_write(FEND); + serial_write(CMD_ERROR); + serial_write(error_code); + serial_write(FEND); +} + +void kiss_indicate_radiostate() { + serial_write(FEND); + serial_write(CMD_RADIO_STATE); + serial_write(radio_online); + serial_write(FEND); +} + +void kiss_indicate_stat_rx() { + serial_write(FEND); + serial_write(CMD_STAT_RX); + escaped_serial_write(stat_rx>>24); + escaped_serial_write(stat_rx>>16); + escaped_serial_write(stat_rx>>8); + escaped_serial_write(stat_rx); + serial_write(FEND); +} + +void kiss_indicate_stat_tx() { + serial_write(FEND); + serial_write(CMD_STAT_TX); + escaped_serial_write(stat_tx>>24); + escaped_serial_write(stat_tx>>16); + escaped_serial_write(stat_tx>>8); + escaped_serial_write(stat_tx); + serial_write(FEND); +} + +void kiss_indicate_stat_rssi() { + uint8_t packet_rssi_val = (uint8_t)(last_rssi+rssi_offset); + serial_write(FEND); + serial_write(CMD_STAT_RSSI); + escaped_serial_write(packet_rssi_val); + serial_write(FEND); +} + +void kiss_indicate_stat_snr() { + serial_write(FEND); + serial_write(CMD_STAT_SNR); + escaped_serial_write(last_snr_raw); + serial_write(FEND); +} + +void kiss_indicate_radio_lock() { + serial_write(FEND); + serial_write(CMD_RADIO_LOCK); + serial_write(radio_locked); + serial_write(FEND); +} + +void kiss_indicate_spreadingfactor() { + serial_write(FEND); + serial_write(CMD_SF); + serial_write((uint8_t)lora_sf); + serial_write(FEND); +} + +void kiss_indicate_codingrate() { + serial_write(FEND); + serial_write(CMD_CR); + serial_write((uint8_t)lora_cr); + serial_write(FEND); +} + +void kiss_indicate_implicit_length() { + serial_write(FEND); + serial_write(CMD_IMPLICIT); + serial_write(implicit_l); + serial_write(FEND); +} + +void kiss_indicate_txpower() { + serial_write(FEND); + serial_write(CMD_TXPOWER); + serial_write((uint8_t)lora_txp); + serial_write(FEND); +} + +void kiss_indicate_bandwidth() { + serial_write(FEND); + serial_write(CMD_BANDWIDTH); + escaped_serial_write(lora_bw>>24); + escaped_serial_write(lora_bw>>16); + escaped_serial_write(lora_bw>>8); + escaped_serial_write(lora_bw); + serial_write(FEND); +} + +void kiss_indicate_frequency() { + serial_write(FEND); + serial_write(CMD_FREQUENCY); + escaped_serial_write(lora_freq>>24); + escaped_serial_write(lora_freq>>16); + escaped_serial_write(lora_freq>>8); + escaped_serial_write(lora_freq); + serial_write(FEND); +} + +void kiss_indicate_st_alock() { + uint16_t at = (uint16_t)(st_airtime_limit*100*100); + serial_write(FEND); + serial_write(CMD_ST_ALOCK); + escaped_serial_write(at>>8); + escaped_serial_write(at); + serial_write(FEND); +} + +void kiss_indicate_lt_alock() { + uint16_t at = (uint16_t)(lt_airtime_limit*100*100); + serial_write(FEND); + serial_write(CMD_LT_ALOCK); + escaped_serial_write(at>>8); + escaped_serial_write(at); + serial_write(FEND); +} + +void kiss_indicate_channel_stats() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + uint16_t ats = (uint16_t)(airtime*100*100); + uint16_t atl = (uint16_t)(longterm_airtime*100*100); + uint16_t cls = (uint16_t)(total_channel_util*100*100); + uint16_t cll = (uint16_t)(longterm_channel_util*100*100); + uint8_t crs = (uint8_t)(current_rssi+rssi_offset); + uint8_t nfl = (uint8_t)(noise_floor+rssi_offset); + uint8_t ntf = 0xFF; if (interference_detected) { ntf = (uint8_t)(current_rssi+rssi_offset); } + serial_write(FEND); + serial_write(CMD_STAT_CHTM); + escaped_serial_write(ats>>8); + escaped_serial_write(ats); + escaped_serial_write(atl>>8); + escaped_serial_write(atl); + escaped_serial_write(cls>>8); + escaped_serial_write(cls); + escaped_serial_write(cll>>8); + escaped_serial_write(cll); + escaped_serial_write(crs); + escaped_serial_write(nfl); + escaped_serial_write(ntf); + serial_write(FEND); + #endif +} + +void kiss_indicate_csma_stats() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + serial_write(FEND); + serial_write(CMD_STAT_CSMA); + escaped_serial_write(cw_band); + escaped_serial_write(cw_min); + escaped_serial_write(cw_max); + serial_write(FEND); + #endif +} + +void kiss_indicate_phy_stats() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + uint16_t lst = (uint16_t)(lora_symbol_time_ms*1000); + uint16_t lsr = (uint16_t)(lora_symbol_rate); + uint16_t prs = (uint16_t)(lora_preamble_symbols); + uint16_t prt = (uint16_t)(lora_preamble_time_ms); + uint16_t cst = (uint16_t)(csma_slot_ms); + uint16_t dft = (uint16_t)(difs_ms); + serial_write(FEND); + serial_write(CMD_STAT_PHYPRM); + escaped_serial_write(lst>>8); escaped_serial_write(lst); + escaped_serial_write(lsr>>8); escaped_serial_write(lsr); + escaped_serial_write(prs>>8); escaped_serial_write(prs); + escaped_serial_write(prt>>8); escaped_serial_write(prt); + escaped_serial_write(cst>>8); escaped_serial_write(cst); + escaped_serial_write(dft>>8); escaped_serial_write(dft); + serial_write(FEND); + #endif +} + +void kiss_indicate_battery() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + serial_write(FEND); + serial_write(CMD_STAT_BAT); + escaped_serial_write(battery_state); + escaped_serial_write((uint8_t)int(battery_percent)); + serial_write(FEND); + #endif +} + +void kiss_indicate_temperature() { + #if HAS_PMU + #if MCU_VARIANT == MCU_ESP32 + float pmu_temp = pmu_temperature+PMU_TEMP_OFFSET; + uint8_t temp = (uint8_t)pmu_temp; + serial_write(FEND); + serial_write(CMD_STAT_TEMP); + escaped_serial_write(pmu_temp); + serial_write(FEND); + #endif + #endif +} + +void kiss_indicate_btpin() { + #if HAS_BLUETOOTH || HAS_BLE == true + serial_write(FEND); + serial_write(CMD_BT_PIN); + escaped_serial_write(bt_ssp_pin>>24); + escaped_serial_write(bt_ssp_pin>>16); + escaped_serial_write(bt_ssp_pin>>8); + escaped_serial_write(bt_ssp_pin); + serial_write(FEND); + #endif +} + +void kiss_indicate_random(uint8_t byte) { + serial_write(FEND); + serial_write(CMD_RANDOM); + serial_write(byte); + serial_write(FEND); +} + +void kiss_indicate_fbstate() { + serial_write(FEND); + serial_write(CMD_FB_EXT); + #if HAS_DISPLAY + if (disp_ext_fb) { + serial_write(0x01); + } else { + serial_write(0x00); + } + #else + serial_write(0xFF); + #endif + serial_write(FEND); +} + +#if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + void kiss_indicate_device_hash() { + serial_write(FEND); + serial_write(CMD_DEV_HASH); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_hash[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } + + void kiss_indicate_target_fw_hash() { + serial_write(FEND); + serial_write(CMD_HASHES); + serial_write(0x01); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_firmware_hash_target[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } + + void kiss_indicate_fw_hash() { + serial_write(FEND); + serial_write(CMD_HASHES); + serial_write(0x02); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_firmware_hash[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } + + void kiss_indicate_bootloader_hash() { + serial_write(FEND); + serial_write(CMD_HASHES); + serial_write(0x03); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_bootloader_hash[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } + + void kiss_indicate_partition_table_hash() { + serial_write(FEND); + serial_write(CMD_HASHES); + serial_write(0x04); + for (int i = 0; i < DEV_HASH_LEN; i++) { + uint8_t byte = dev_partition_table_hash[i]; + escaped_serial_write(byte); + } + serial_write(FEND); + } +#endif + +void kiss_indicate_fb() { + serial_write(FEND); + serial_write(CMD_FB_READ); + #if HAS_DISPLAY + for (int i = 0; i < 512; i++) { + uint8_t byte = fb[i]; + escaped_serial_write(byte); + } + #else + serial_write(0xFF); + #endif + serial_write(FEND); +} + +void kiss_indicate_disp() { + serial_write(FEND); + serial_write(CMD_DISP_READ); + #if HAS_DISPLAY + uint8_t *da = disp_area.getBuffer(); + uint8_t *sa = stat_area.getBuffer(); + for (int i = 0; i < 512; i++) { escaped_serial_write(da[i]); } + for (int i = 0; i < 512; i++) { escaped_serial_write(sa[i]); } + #else + serial_write(0xFF); + #endif + serial_write(FEND); +} + +void kiss_indicate_ready() { + serial_write(FEND); + serial_write(CMD_READY); + serial_write(0x01); + serial_write(FEND); +} + +void kiss_indicate_not_ready() { + serial_write(FEND); + serial_write(CMD_READY); + serial_write(0x00); + serial_write(FEND); +} + +void kiss_indicate_promisc() { + serial_write(FEND); + serial_write(CMD_PROMISC); + if (promisc) { + serial_write(0x01); + } else { + serial_write(0x00); + } + serial_write(FEND); +} + +void kiss_indicate_detect() { + serial_write(FEND); + serial_write(CMD_DETECT); + serial_write(DETECT_RESP); + serial_write(FEND); +} + +void kiss_indicate_version() { + serial_write(FEND); + serial_write(CMD_FW_VERSION); + serial_write(MAJ_VERS); + serial_write(MIN_VERS); + serial_write(FEND); +} + +void kiss_indicate_platform() { + serial_write(FEND); + serial_write(CMD_PLATFORM); + serial_write(PLATFORM); + serial_write(FEND); +} + +void kiss_indicate_board() { + serial_write(FEND); + serial_write(CMD_BOARD); + serial_write(BOARD_MODEL); + serial_write(FEND); +} + +void kiss_indicate_mcu() { + serial_write(FEND); + serial_write(CMD_MCU); + serial_write(MCU_VARIANT); + serial_write(FEND); +} + +inline bool isSplitPacket(uint8_t header) { + return (header & FLAG_SPLIT); +} + +inline uint8_t packetSequence(uint8_t header) { + return header >> 4; +} + +void setPreamble() { + if (radio_online) LoRa->setPreambleLength(lora_preamble_symbols); + kiss_indicate_phy_stats(); +} + +void updateBitrate() { + #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52 + if (!radio_online) { lora_bitrate = 0; } + else { + lora_symbol_rate = (float)lora_bw/(float)(pow(2, lora_sf)); + lora_symbol_time_ms = (1.0/lora_symbol_rate)*1000.0; + lora_bitrate = (uint32_t)(lora_sf * ( (4.0/(float)lora_cr) / ((float)(pow(2, lora_sf))/((float)lora_bw/1000.0)) ) * 1000.0); + lora_us_per_byte = 1000000.0/((float)lora_bitrate/8.0); + + bool fast_rate = lora_bitrate > LORA_FAST_THRESHOLD_BPS; + lora_limit_rate = lora_bitrate > LORA_LIMIT_THRESHOLD_BPS; + + int csma_slot_min_ms = CSMA_SLOT_MIN_MS; + float lora_preamble_target_ms = LORA_PREAMBLE_TARGET_MS; + if (fast_rate) { csma_slot_min_ms -= CSMA_SLOT_MIN_FAST_DELTA; + lora_preamble_target_ms -= LORA_PREAMBLE_FAST_DELTA; } + + csma_slot_ms = lora_symbol_time_ms*CSMA_SLOT_SYMBOLS; + if (csma_slot_ms > CSMA_SLOT_MAX_MS) { csma_slot_ms = CSMA_SLOT_MAX_MS; } + if (csma_slot_ms < CSMA_SLOT_MIN_MS) { csma_slot_ms = csma_slot_min_ms; } + difs_ms = CSMA_SIFS_MS + 2*csma_slot_ms; + + float target_preamble_symbols = lora_preamble_target_ms/lora_symbol_time_ms; + if (target_preamble_symbols < LORA_PREAMBLE_SYMBOLS_MIN) { target_preamble_symbols = LORA_PREAMBLE_SYMBOLS_MIN; } + else { target_preamble_symbols = (ceil)(target_preamble_symbols); } + + lora_preamble_symbols = (long)target_preamble_symbols; setPreamble(); + lora_preamble_time_ms = (ceil)(lora_preamble_symbols * lora_symbol_time_ms); + lora_header_time_ms = (ceil)(PHY_HEADER_LORA_SYMBOLS * lora_symbol_time_ms); + } + #endif +} + +void setSpreadingFactor() { + if (radio_online) LoRa->setSpreadingFactor(lora_sf); + updateBitrate(); +} + +void setCodingRate() { + if (radio_online) LoRa->setCodingRate4(lora_cr); + updateBitrate(); +} + +void set_implicit_length(uint8_t len) { + implicit_l = len; + if (implicit_l != 0) { + implicit = true; + } else { + implicit = false; + } +} + +int getTxPower() { + uint8_t txp = LoRa->getTxPower(); + return (int)txp; +} + +#if HAS_LORA_PA + const int tx_gain[PA_GAIN_POINTS] = {PA_GAIN_VALUES}; +#endif + +int map_target_power_to_modem_output(int target_tx_power) { + #if HAS_LORA_PA + int modem_output_dbm = -9; + for (int i = 0; i < PA_GAIN_POINTS; i++) { + int gain = tx_gain[i]; + int effective_output_dbm = i + gain; + printf("At %d dBm modem output, gain is %d dBm, effective output is %d\n", i, gain, effective_output_dbm); + if (effective_output_dbm > target_tx_power) { + int diff = effective_output_dbm - target_tx_power; + modem_output_dbm = -1*diff; + break; + } else if (effective_output_dbm == target_tx_power) { + modem_output_dbm = i; break; + } else if (i == PA_GAIN_POINTS-1) { + int diff = target_tx_power - effective_output_dbm; + modem_output_dbm = i+diff; break; + } + } + #else + int modem_output_dbm = target_tx_power; + #endif + + return modem_output_dbm; +} + +int map_modem_output_to_target_power(int modem_output_dbm) { + #if HAS_LORA_PA + if (modem_output_dbm < 0) { modem_output_dbm = 0; } + if (modem_output_dbm >= PA_GAIN_POINTS) { modem_output_dbm = PA_GAIN_POINTS-1; } + int gain = tx_gain[modem_output_dbm]; + int target_tx_power = modem_output_dbm+gain; + #else + int target_tx_power = modem_output_dbm; + #endif + + return target_tx_power; +} + +void setTXPower() { + if (radio_online) { + int mapped_lora_txp = map_target_power_to_modem_output(lora_txp); + + #if HAS_LORA_PA + int real_lora_txp = map_modem_output_to_target_power(mapped_lora_txp); + lora_txp = real_lora_txp; + #endif + + if (model == MODEL_11) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + if (model == MODEL_12) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + + if (model == MODEL_C6) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + if (model == MODEL_C7) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + + if (model == MODEL_A1) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A2) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A3) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + if (model == MODEL_A4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + if (model == MODEL_A5) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A6) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A7) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_A9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_AA) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_AC) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_BA) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_BB) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_B3) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_B4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_B8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_B9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_C4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_C9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_C5) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_CA) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_C8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_D4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_D9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_DB) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_DC) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_DD) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_DE) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_E4) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_E9) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_E3) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_E8) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + + if (model == MODEL_FE) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_PA_BOOST_PIN); + if (model == MODEL_FF) LoRa->setTxPower(mapped_lora_txp, PA_OUTPUT_RFO_PIN); + } +} + + +void getBandwidth() { + if (radio_online) { + lora_bw = LoRa->getSignalBandwidth(); + } + updateBitrate(); +} + +void setBandwidth() { + if (radio_online) { + LoRa->setSignalBandwidth(lora_bw); + getBandwidth(); + } +} + +void getFrequency() { + if (radio_online) { + lora_freq = LoRa->getFrequency(); + } +} + +void setFrequency() { + if (radio_online) { + LoRa->setFrequency(lora_freq); + getFrequency(); + } +} + +uint8_t getRandom() { return random(0xFF); } + +void promisc_enable() { + promisc = true; +} + +void promisc_disable() { + promisc = false; +} + +#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + bool eeprom_begin() { + InternalFS.begin(); + + file.open(EEPROM_FILE, FILE_O_READ); + if (!file) { + if (file.open(EEPROM_FILE, FILE_O_WRITE)) { + for (uint32_t mapped_addr = 0; mapped_addr < EEPROM_SIZE; mapped_addr++) { file.seek(mapped_addr); file.write(0xFF); } + eeprom_flush(); + return true; + } else { + return false; + } + } else { + file.close(); + file.open(EEPROM_FILE, FILE_O_WRITE); + return true; + } + } + + uint8_t eeprom_read(uint32_t mapped_addr) { + uint8_t byte; + void* byte_ptr = &byte; + file.seek(mapped_addr); + file.read(byte_ptr, 1); + return byte; + } +#endif + +bool eeprom_info_locked() { + #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 { + return false; + } +} + +void eeprom_dump_info() { + for (int addr = ADDR_PRODUCT; addr <= ADDR_INFO_LOCK; 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++) { + #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++) { + #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 kiss_dump_eeprom() { + serial_write(FEND); + serial_write(CMD_ROM_READ); + eeprom_dump_all(); + serial_write(FEND); +} + +#if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 +void eeprom_flush() { + file.close(); + file.open(EEPROM_FILE, FILE_O_WRITE); + written_bytes = 0; +} +#endif + +void eeprom_update(int mapped_addr, uint8_t byte) { + #if MCU_VARIANT == MCU_1284P || MCU_VARIANT == MCU_2560 + EEPROM.update(mapped_addr, byte); + #elif MCU_VARIANT == MCU_ESP32 + if (EEPROM.read(mapped_addr) != byte) { + EEPROM.write(mapped_addr, byte); + EEPROM.commit(); + } + #elif !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + // todo: clean up this implementation, writing one byte and syncing + // each time is really slow, but this is also suboptimal + uint8_t read_byte; + void* read_byte_ptr = &read_byte; + file.seek(mapped_addr); + file.read(read_byte_ptr, 1); + file.seek(mapped_addr); + if (read_byte != byte) { + file.write(byte); + } + written_bytes++; + eeprom_flush(); + #endif +} + +void eeprom_write(uint8_t addr, uint8_t byte) { + if (!eeprom_info_locked() && addr >= 0 && addr < EEPROM_RESERVED) { + eeprom_update(eeprom_addr(addr), byte); + } else { + kiss_indicate_error(ERROR_EEPROM_LOCKED); + } +} + +void eeprom_erase() { + #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + InternalFS.format(); + #else + for (int addr = 0; addr < EEPROM_RESERVED; addr++) { + eeprom_update(eeprom_addr(addr), 0xFF); + } + #endif + hard_reset(); +} + +bool eeprom_lock_set() { + #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; + } +} + +bool eeprom_product_valid() { + #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 || rval == PRODUCT_H32_V3 || rval == PRODUCT_H32_V4 || rval == PRODUCT_TDECK_V1 || rval == PRODUCT_TBEAM_S_V1 || rval == PRODUCT_XIAO_S3) { + #elif PLATFORM == PLATFORM_NRF52 + if (rval == PRODUCT_RAK4631 || rval == PRODUCT_HELTEC_T114 || rval == PRODUCT_TECHO || rval == PRODUCT_HMBRW) { + #else + if (false) { + #endif + return true; + } else { + return false; + } +} + +bool eeprom_model_valid() { + #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 + if (model == MODEL_A3 || model == MODEL_A8) { + #elif BOARD_MODEL == BOARD_RNODE_NG_21 + if (model == MODEL_A2 || model == MODEL_A7) { + #elif BOARD_MODEL == BOARD_T3S3 + if (model == MODEL_A1 || model == MODEL_A6 || model == MODEL_A5 || model == MODEL_AA || model == MODEL_AC) { + #elif BOARD_MODEL == BOARD_HMBRW + if (model == MODEL_FF || model == MODEL_FE) { + #elif BOARD_MODEL == BOARD_TBEAM + if (model == MODEL_E4 || model == MODEL_E9 || model == MODEL_E3 || model == MODEL_E8) { + #elif BOARD_MODEL == BOARD_TDECK + if (model == MODEL_D4 || model == MODEL_D9) { + #elif BOARD_MODEL == BOARD_TECHO + if (model == MODEL_16 || model == MODEL_17) { + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + if (model == MODEL_DB || model == MODEL_DC) { + #elif BOARD_MODEL == BOARD_XIAO_S3 + if (model == MODEL_DD || model == MODEL_DE) { + #elif BOARD_MODEL == BOARD_LORA32_V1_0 + if (model == MODEL_BA || model == MODEL_BB) { + #elif BOARD_MODEL == BOARD_LORA32_V2_0 + if (model == MODEL_B3 || model == MODEL_B8) { + #elif BOARD_MODEL == BOARD_LORA32_V2_1 + if (model == MODEL_B4 || model == MODEL_B9) { + #elif BOARD_MODEL == BOARD_HELTEC32_V2 + if (model == MODEL_C4 || model == MODEL_C9) { + #elif BOARD_MODEL == BOARD_HELTEC32_V3 + if (model == MODEL_C5 || model == MODEL_CA) { + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + if (model == MODEL_C8) { + #elif BOARD_MODEL == BOARD_HELTEC_T114 + if (model == MODEL_C6 || model == MODEL_C7) { + #elif BOARD_MODEL == BOARD_RAK4631 + if (model == MODEL_11 || model == MODEL_12) { + #elif BOARD_MODEL == BOARD_HUZZAH32 + if (model == MODEL_FF) { + #elif BOARD_MODEL == BOARD_GENERIC_ESP32 + if (model == MODEL_FF || model == MODEL_FE) { + #else + if (false) { + #endif + return true; + } else { + return false; + } +} + +bool eeprom_hwrev_valid() { + #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 { + return false; + } +} + +bool eeprom_checksum_valid() { + char *data = (char*)malloc(CHECKSUMMED_SIZE); + for (uint8_t i = 0; i < CHECKSUMMED_SIZE; 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++) { + #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; + } + } + + free(hash); + free(data); + return checksum_valid; +} + +void bt_conf_save(bool is_enabled) { + if (is_enabled) { + eeprom_update(eeprom_addr(ADDR_CONF_BT), BT_ENABLE_BYTE); + #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + // have to do a flush because we're only writing 1 byte and it syncs after 8 + eeprom_flush(); + #endif + } else { + eeprom_update(eeprom_addr(ADDR_CONF_BT), 0x00); + #if !HAS_EEPROM && MCU_VARIANT == MCU_NRF52 + // have to do a flush because we're only writing 1 byte and it syncs after 8 + eeprom_flush(); + #endif + } +} + +void di_conf_save(uint8_t dint) { + eeprom_update(eeprom_addr(ADDR_CONF_DINT), dint); +} + +void da_conf_save(uint8_t dadr) { + eeprom_update(eeprom_addr(ADDR_CONF_DADR), dadr); +} + +void db_conf_save(uint8_t val) { + #if HAS_DISPLAY + if (val == 0x00) { + display_blanking_enabled = false; + } else { + display_blanking_enabled = true; + display_blanking_timeout = val*1000; + } + eeprom_update(eeprom_addr(ADDR_CONF_BSET), CONF_OK_BYTE); + eeprom_update(eeprom_addr(ADDR_CONF_DBLK), val); + #endif +} + +void drot_conf_save(uint8_t val) { + #if HAS_DISPLAY + if (val >= 0x00 and val <= 0x03) { + eeprom_update(eeprom_addr(ADDR_CONF_DROT), val); + hard_reset(); + } + #endif +} + +void dia_conf_save(uint8_t val) { + if (val > 0x00) { eeprom_update(eeprom_addr(ADDR_CONF_DIA), 0x01); } + else { eeprom_update(eeprom_addr(ADDR_CONF_DIA), 0x00); } + hard_reset(); +} + +void np_int_conf_save(uint8_t p_int) { + eeprom_update(eeprom_addr(ADDR_CONF_PSET), CONF_OK_BYTE); + eeprom_update(eeprom_addr(ADDR_CONF_PINT), p_int); +} + + +bool eeprom_have_conf() { + #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; + } +} + +void eeprom_conf_load() { + if (eeprom_have_conf()) { + #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 + } +} + +void eeprom_conf_save() { + if (hw_ready && radio_online) { + eeprom_update(eeprom_addr(ADDR_CONF_SF), lora_sf); + eeprom_update(eeprom_addr(ADDR_CONF_CR), lora_cr); + eeprom_update(eeprom_addr(ADDR_CONF_TXP), lora_txp); + + eeprom_update(eeprom_addr(ADDR_CONF_BW)+0x00, lora_bw>>24); + eeprom_update(eeprom_addr(ADDR_CONF_BW)+0x01, lora_bw>>16); + eeprom_update(eeprom_addr(ADDR_CONF_BW)+0x02, lora_bw>>8); + eeprom_update(eeprom_addr(ADDR_CONF_BW)+0x03, lora_bw); + + eeprom_update(eeprom_addr(ADDR_CONF_FREQ)+0x00, lora_freq>>24); + eeprom_update(eeprom_addr(ADDR_CONF_FREQ)+0x01, lora_freq>>16); + eeprom_update(eeprom_addr(ADDR_CONF_FREQ)+0x02, lora_freq>>8); + eeprom_update(eeprom_addr(ADDR_CONF_FREQ)+0x03, lora_freq); + + eeprom_update(eeprom_addr(ADDR_CONF_OK), CONF_OK_BYTE); + led_indicate_info(10); + } else { + led_indicate_warning(10); + } +} + +void eeprom_conf_delete() { + eeprom_update(eeprom_addr(ADDR_CONF_OK), 0x00); +} + +void unlock_rom() { + led_indicate_error(50); + eeprom_erase(); +} + +void init_channel_stats() { + #if MCU_VARIANT == MCU_ESP32 + for (uint16_t ai = 0; ai < DCD_SAMPLES; ai++) { util_samples[ai] = false; } + for (uint16_t ai = 0; ai < AIRTIME_BINS; ai++) { airtime_bins[ai] = 0; } + for (uint16_t ai = 0; ai < AIRTIME_BINS; ai++) { longterm_bins[ai] = 0.0; } + local_channel_util = 0.0; + total_channel_util = 0.0; + airtime = 0.0; + longterm_airtime = 0.0; + #endif +} + +typedef struct FIFOBuffer +{ + unsigned char *begin; + unsigned char *end; + unsigned char * volatile head; + unsigned char * volatile tail; +} FIFOBuffer; + +inline bool fifo_isempty(const FIFOBuffer *f) { + return f->head == f->tail; +} + +inline bool fifo_isfull(const FIFOBuffer *f) { + return ((f->head == f->begin) && (f->tail == f->end)) || (f->tail == f->head - 1); +} + +inline void fifo_push(FIFOBuffer *f, unsigned char c) { + *(f->tail) = c; + + if (f->tail == f->end) { + f->tail = f->begin; + } else { + f->tail++; + } +} + +inline unsigned char fifo_pop(FIFOBuffer *f) { + if(f->head == f->end) { + f->head = f->begin; + return *(f->end); + } else { + return *(f->head++); + } +} + +inline void fifo_flush(FIFOBuffer *f) { + f->head = f->tail; +} + +#if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + static inline bool fifo_isempty_locked(const FIFOBuffer *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo_isempty(f); + } + return result; + } + + static inline bool fifo_isfull_locked(const FIFOBuffer *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo_isfull(f); + } + return result; + } + + static inline void fifo_push_locked(FIFOBuffer *f, unsigned char c) { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + fifo_push(f, c); + } + } +#endif + +/* +static inline unsigned char fifo_pop_locked(FIFOBuffer *f) { + unsigned char c; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + c = fifo_pop(f); + } + return c; +} +*/ + +inline void fifo_init(FIFOBuffer *f, unsigned char *buffer, size_t size) { + f->head = f->tail = f->begin = buffer; + f->end = buffer + size; +} + +inline size_t fifo_len(FIFOBuffer *f) { + return f->end - f->begin; +} + +typedef struct FIFOBuffer16 +{ + uint16_t *begin; + uint16_t *end; + uint16_t * volatile head; + uint16_t * volatile tail; +} FIFOBuffer16; + +inline bool fifo16_isempty(const FIFOBuffer16 *f) { + return f->head == f->tail; +} + +inline bool fifo16_isfull(const FIFOBuffer16 *f) { + return ((f->head == f->begin) && (f->tail == f->end)) || (f->tail == f->head - 1); +} + +inline void fifo16_push(FIFOBuffer16 *f, uint16_t c) { + *(f->tail) = c; + + if (f->tail == f->end) { + f->tail = f->begin; + } else { + f->tail++; + } +} + +inline uint16_t fifo16_pop(FIFOBuffer16 *f) { + if(f->head == f->end) { + f->head = f->begin; + return *(f->end); + } else { + return *(f->head++); + } +} + +inline void fifo16_flush(FIFOBuffer16 *f) { + f->head = f->tail; +} + +#if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52 + static inline bool fifo16_isempty_locked(const FIFOBuffer16 *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo16_isempty(f); + } + + return result; + } +#endif + +/* +static inline bool fifo16_isfull_locked(const FIFOBuffer16 *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo16_isfull(f); + } + return result; +} + + +static inline void fifo16_push_locked(FIFOBuffer16 *f, uint16_t c) { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + fifo16_push(f, c); + } +} + +static inline size_t fifo16_pop_locked(FIFOBuffer16 *f) { + size_t c; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + c = fifo16_pop(f); + } + return c; +} +*/ + +inline void fifo16_init(FIFOBuffer16 *f, uint16_t *buffer, uint16_t size) { + f->head = f->tail = f->begin = buffer; + f->end = buffer + size; +} + +inline uint16_t fifo16_len(FIFOBuffer16 *f) { + return (f->end - f->begin); +} diff --git a/RNode_Firmware/src/LCMEN2R13EFC1.cpp b/RNode_Firmware/src/LCMEN2R13EFC1.cpp new file mode 100644 index 0000000..4b0b1c5 --- /dev/null +++ b/RNode_Firmware/src/LCMEN2R13EFC1.cpp @@ -0,0 +1,242 @@ +// E-Ink display driver for LCMEN2R13EFC1 +// Adapted from Meshtastic NicheGraphics driver + +#include "LCMEN2R13EFC1.h" + +// Look-up tables for FAST refresh +static const uint8_t LUT_FAST_VCOMDC[] = { + 0x01, 0x06, 0x03, 0x02, 0x01, 0x01, 0x01, + 0x01, 0x06, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_FAST_WW[] = { + 0x01, 0x06, 0x03, 0x02, 0x81, 0x01, 0x01, + 0x01, 0x06, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_FAST_BW[] = { + 0x01, 0x86, 0x83, 0x82, 0x81, 0x01, 0x01, + 0x01, 0x86, 0x82, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_FAST_WB[] = { + 0x01, 0x46, 0x43, 0x02, 0x01, 0x01, 0x01, + 0x01, 0x46, 0x42, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_FAST_BB[] = { + 0x01, 0x06, 0x03, 0x42, 0x41, 0x01, 0x01, + 0x01, 0x06, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +LCMEN2R13EFC1::LCMEN2R13EFC1() + : _spiSettings(6000000, MSBFIRST, SPI_MODE0) +{ + // Calculate buffer size (8 pixels per byte) + _bufferRowSize = ((DISPLAY_WIDTH - 1) / 8) + 1; // 122px -> 16 bytes + _bufferSize = _bufferRowSize * DISPLAY_HEIGHT; // 16 * 250 = 4000 bytes +} + +void LCMEN2R13EFC1::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst) { + _spi = spi; + _pin_dc = pin_dc; + _pin_cs = pin_cs; + _pin_busy = pin_busy; + _pin_rst = pin_rst; + + pinMode(_pin_dc, OUTPUT); + pinMode(_pin_cs, OUTPUT); + pinMode(_pin_busy, INPUT); + pinMode(_pin_rst, INPUT_PULLUP); // Active low, hold high + + reset(); +} + +void LCMEN2R13EFC1::reset() { + pinMode(_pin_rst, OUTPUT); + digitalWrite(_pin_rst, LOW); + delay(10); + pinMode(_pin_rst, INPUT_PULLUP); + wait(); + + sendCommand(0x12); // Software reset + wait(); +} + +void LCMEN2R13EFC1::wait() { + // Busy when LOW + while (digitalRead(_pin_busy) == LOW) { + yield(); + } +} + +bool LCMEN2R13EFC1::isBusy() { + return (digitalRead(_pin_busy) == LOW); +} + +void LCMEN2R13EFC1::sendCommand(const uint8_t command) { + _spi->beginTransaction(_spiSettings); + digitalWrite(_pin_dc, LOW); // DC low = command + digitalWrite(_pin_cs, LOW); + _spi->transfer(command); + digitalWrite(_pin_cs, HIGH); + digitalWrite(_pin_dc, HIGH); + _spi->endTransaction(); +} + +void LCMEN2R13EFC1::sendData(uint8_t data) { + sendData(&data, 1); +} + +void LCMEN2R13EFC1::sendData(const uint8_t *data, uint32_t size) { + _spi->beginTransaction(_spiSettings); + digitalWrite(_pin_dc, HIGH); // DC high = data + digitalWrite(_pin_cs, LOW); + + #if defined(ARCH_ESP32) || defined(ESP32) + _spi->transferBytes(data, NULL, size); + #else + for (uint32_t i = 0; i < size; i++) { + _spi->transfer(data[i]); + } + #endif + + digitalWrite(_pin_cs, HIGH); + digitalWrite(_pin_dc, HIGH); + _spi->endTransaction(); +} + +void LCMEN2R13EFC1::configFull() { + sendCommand(0x00); // Panel setting register + sendData(0b11 << 6 // Display resolution + | 1 << 4 // B&W only + | 1 << 3 // Vertical scan direction + | 1 << 2 // Horizontal scan direction + | 1 << 1 // Shutdown: no + | 1 << 0 // Reset: no + ); + + sendCommand(0x50); // VCOM and data interval setting + sendData(0b10 << 6 // Border driven white + | 0b11 << 4 // Invert image colors: no + | 0b0111 // Interval between VCOM on and image data + ); +} + +void LCMEN2R13EFC1::configFast() { + sendCommand(0x00); // Panel setting register + sendData(0b11 << 6 // Display resolution + | 1 << 5 // LUT from registers (set below) + | 1 << 4 // B&W only + | 1 << 3 // Vertical scan direction + | 1 << 2 // Horizontal scan direction + | 1 << 1 // Shutdown: no + | 1 << 0 // Reset: no + ); + + sendCommand(0x50); // VCOM and data interval setting + sendData(0b11 << 6 // Border floating + | 0b01 << 4 // Invert image colors: no + | 0b0111 // Interval between VCOM on and image data + ); + + // Load LUT tables + sendCommand(0x20); // VCOM + sendData(LUT_FAST_VCOMDC, sizeof(LUT_FAST_VCOMDC)); + + sendCommand(0x21); // White -> White + sendData(LUT_FAST_WW, sizeof(LUT_FAST_WW)); + + sendCommand(0x22); // Black -> White + sendData(LUT_FAST_BW, sizeof(LUT_FAST_BW)); + + sendCommand(0x23); // White -> Black + sendData(LUT_FAST_WB, sizeof(LUT_FAST_WB)); + + sendCommand(0x24); // Black -> Black + sendData(LUT_FAST_BB, sizeof(LUT_FAST_BB)); +} + +void LCMEN2R13EFC1::writeNewImage() { + sendCommand(0x13); + sendData(_buffer, _bufferSize); +} + +void LCMEN2R13EFC1::writeOldImage() { + sendCommand(0x10); + sendData(_buffer, _bufferSize); +} + +void LCMEN2R13EFC1::update(uint8_t *imageData, UpdateType type) { + _updateType = type; + _buffer = imageData; + + reset(); + + // Configure display + if (_updateType == UPDATE_FULL) { + configFull(); + } else { + configFast(); + } + + // Transfer image data + if (_updateType == UPDATE_FULL) { + writeNewImage(); + writeOldImage(); + } else { + writeNewImage(); + } + + // Power on and start refresh + sendCommand(0x04); // Power on panel voltage + wait(); + + sendCommand(0x12); // Begin executing update + wait(); + + // Power off + sendCommand(0x02); + wait(); + + // Update "old memory" for next differential refresh + if (_updateType != UPDATE_FULL) { + writeOldImage(); + wait(); + } +} + +void LCMEN2R13EFC1::powerOn() { + sendCommand(0x04); + wait(); +} + +void LCMEN2R13EFC1::powerOff() { + sendCommand(0x02); + wait(); +} diff --git a/RNode_Firmware/src/LCMEN2R13EFC1.h b/RNode_Firmware/src/LCMEN2R13EFC1.h new file mode 100644 index 0000000..8bae3c9 --- /dev/null +++ b/RNode_Firmware/src/LCMEN2R13EFC1.h @@ -0,0 +1,59 @@ +// E-Ink display driver for LCMEN2R13EFC1 (Heltec VisionMaster E213) +// Adapted from Meshtastic firmware for RNode use +// Controller IC: Fitipower JD79656 +// Resolution: 122x250 pixels (width x height) +// Supports FAST (partial) and FULL refresh + +#ifndef LCMEN2R13EFC1_H +#define LCMEN2R13EFC1_H + +#include +#include + +class LCMEN2R13EFC1 { +public: + // Display properties + static constexpr uint16_t DISPLAY_WIDTH = 122; + static constexpr uint16_t DISPLAY_HEIGHT = 250; + + // Update types + enum UpdateType { + UPDATE_FULL = 0, // Full refresh (slower, ~3.6s) + UPDATE_FAST = 1 // Partial refresh (faster, ~720ms) + }; + + LCMEN2R13EFC1(); + + void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst); + void update(uint8_t *imageData, UpdateType type); + bool isBusy(); + void powerOn(); + void powerOff(); + +private: + SPIClass *_spi; + uint8_t _pin_dc; + uint8_t _pin_cs; + uint8_t _pin_busy; + uint8_t _pin_rst; + + SPISettings _spiSettings; + + uint8_t *_buffer; + uint16_t _bufferRowSize; + uint32_t _bufferSize; + UpdateType _updateType; + + void reset(); + void wait(); + void sendCommand(const uint8_t command); + void sendData(uint8_t data); + void sendData(const uint8_t *data, uint32_t size); + + void configFull(); + void configFast(); + void writeNewImage(); + void writeOldImage(); +}; + +#endif // LCMEN2R13EFC1_H diff --git a/RNode_Firmware/src/einkDetect_VME213.h b/RNode_Firmware/src/einkDetect_VME213.h new file mode 100644 index 0000000..cdd5af4 --- /dev/null +++ b/RNode_Firmware/src/einkDetect_VME213.h @@ -0,0 +1,41 @@ +// E-Ink display chip detection for VME213 +// Detects between LCMEN213EFC1 (V1) and E0213A367 (V1.1) +// Based on Meshtastic einkDetect.h + +#ifndef EINK_DETECT_VME213_H +#define EINK_DETECT_VME213_H + +#include + +enum EInkChipType { + EINK_LCMEN213EFC1 = 0, // Initial version (Fitipower JD79656) + EINK_E0213A367 = 1 // V1.1+ (Solomon Systech SSD1682) +}; + +// Detect E-Ink controller IC type by BUSY pin logic +// Fitipower: BUSY=LOW when busy +// Solomon Systech: BUSY=HIGH when busy +inline EInkChipType detectEInkChip(uint8_t pin_reset, uint8_t pin_busy) { + // Force display BUSY by holding reset pin active + pinMode(pin_reset, OUTPUT); + digitalWrite(pin_reset, LOW); + + delay(10); + + // Read BUSY pin logic while display is busy + pinMode(pin_busy, INPUT); + bool busyLogic = digitalRead(pin_busy); + + // Release reset pin + pinMode(pin_reset, INPUT); + + // Fitipower = LOW when busy + // Solomon = HIGH when busy + if (busyLogic == LOW) { + return EINK_LCMEN213EFC1; + } else { + return EINK_E0213A367; + } +} + +#endif // EINK_DETECT_VME213_H diff --git a/RNode_Firmware/sx126x.cpp b/RNode_Firmware/sx126x.cpp new file mode 100644 index 0000000..36879be --- /dev/null +++ b/RNode_Firmware/sx126x.cpp @@ -0,0 +1,803 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#include "Boards.h" + +#if MODEM == SX1262 +#include "sx126x.h" + +#if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 and !defined(CONFIG_IDF_TARGET_ESP32S3) + #include "soc/rtc_wdt.h" + #endif + #define ISR_VECT IRAM_ATTR +#else + #define ISR_VECT +#endif + +#define OP_RF_FREQ_6X 0x86 +#define OP_SLEEP_6X 0x84 +#define OP_STANDBY_6X 0x80 +#define OP_TX_6X 0x83 +#define OP_RX_6X 0x82 +#define OP_PA_CONFIG_6X 0x95 +#define OP_SET_IRQ_FLAGS_6X 0x08 // Also provides info such as + // preamble detection, etc for + // knowing when it's safe to switch + // antenna modes +#define OP_CLEAR_IRQ_STATUS_6X 0x02 +#define OP_GET_IRQ_STATUS_6X 0x12 +#define OP_RX_BUFFER_STATUS_6X 0x13 +#define OP_PACKET_STATUS_6X 0x14 // Get snr & rssi of last packet +#define OP_CURRENT_RSSI_6X 0x15 +#define OP_MODULATION_PARAMS_6X 0x8B // BW, SF, CR, etc. +#define OP_PACKET_PARAMS_6X 0x8C // CRC, preamble, payload length, etc. +#define OP_STATUS_6X 0xC0 +#define OP_TX_PARAMS_6X 0x8E // Set dbm, etc +#define OP_PACKET_TYPE_6X 0x8A +#define OP_BUFFER_BASE_ADDR_6X 0x8F +#define OP_READ_REGISTER_6X 0x1D +#define OP_WRITE_REGISTER_6X 0x0D +#define OP_DIO3_TCXO_CTRL_6X 0x97 +#define OP_DIO2_RF_CTRL_6X 0x9D +#define OP_CAD_PARAMS 0x88 +#define OP_CALIBRATE_6X 0x89 +#define OP_RX_TX_FALLBACK_MODE_6X 0x93 +#define OP_REGULATOR_MODE_6X 0x96 +#define OP_CALIBRATE_IMAGE_6X 0x98 + +#define MASK_CALIBRATE_ALL 0x7f + +#define IRQ_TX_DONE_MASK_6X 0x01 +#define IRQ_RX_DONE_MASK_6X 0x02 +#define IRQ_HEADER_DET_MASK_6X 0x10 +#define IRQ_PREAMBLE_DET_MASK_6X 0x04 +#define IRQ_PAYLOAD_CRC_ERROR_MASK_6X 0x40 +#define IRQ_ALL_MASK_6X 0b0100001111111111 + +#define MODE_LONG_RANGE_MODE_6X 0x01 + +#define OP_FIFO_WRITE_6X 0x0E +#define OP_FIFO_READ_6X 0x1E +#define REG_OCP_6X 0x08E7 +#define REG_LNA_6X 0x08AC // No agc in sx1262 +#define REG_SYNC_WORD_MSB_6X 0x0740 +#define REG_SYNC_WORD_LSB_6X 0x0741 +#define REG_PAYLOAD_LENGTH_6X 0x0702 // https://github.com/beegee-tokyo/SX126x-Arduino/blob/master/src/radio/sx126x/sx126x.h#L98 +#define REG_RANDOM_GEN_6X 0x0819 + +#define MODE_TCXO_3_3V_6X 0x07 +#define MODE_TCXO_3_0V_6X 0x06 +#define MODE_TCXO_2_7V_6X 0x06 +#define MODE_TCXO_2_4V_6X 0x06 +#define MODE_TCXO_2_2V_6X 0x03 +#define MODE_TCXO_1_8V_6X 0x02 +#define MODE_TCXO_1_7V_6X 0x01 +#define MODE_TCXO_1_6V_6X 0x00 + +#define MODE_STDBY_RC_6X 0x00 +#define MODE_STDBY_XOSC_6X 0x01 +#define MODE_FALLBACK_STDBY_RC_6X 0x20 +#define MODE_IMPLICIT_HEADER 0x01 +#define MODE_EXPLICIT_HEADER 0x00 + +#define SYNC_WORD_6X 0x1424 + +#define XTAL_FREQ_6X (double)32000000 +#define FREQ_DIV_6X (double)pow(2.0, 25.0) +#define FREQ_STEP_6X (double)(XTAL_FREQ_6X / FREQ_DIV_6X) + +#if BOARD_MODEL == BOARD_TECHO + SPIClass spim3 = SPIClass(NRF_SPIM3, pin_miso, pin_sclk, pin_mosi) ; + #define SPI spim3 + +#elif defined(NRF52840_XXAA) + extern SPIClass spiModem; + #define SPI spiModem +#endif + +extern SPIClass SPI; + +#define MAX_PKT_LENGTH 255 + +sx126x::sx126x() : + _spiSettings(16E6, MSBFIRST, SPI_MODE0), + _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN), _busy(LORA_DEFAULT_BUSY_PIN), _rxen(LORA_DEFAULT_RXEN_PIN), + _frequency(0), + _txp(0), + _sf(0x07), + _bw(0x04), + _cr(0x01), + _ldro(0x00), + _packetIndex(0), + _preambleLength(18), + _implicitHeaderMode(0), + _payloadLength(255), + _crcMode(1), + _fifo_tx_addr_ptr(0), + _fifo_rx_addr_ptr(0), + _packet({0}), + _preinit_done(false), + _onReceive(NULL) +{ setTimeout(0); } + +bool sx126x::preInit() { + pinMode(_ss, OUTPUT); + digitalWrite(_ss, HIGH); + + #if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_XIAO_S3 || BOARD_MODEL == BOARD_VME213 + SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs); + #elif BOARD_MODEL == BOARD_TECHO + SPI.setPins(pin_miso, pin_sclk, pin_mosi); + SPI.begin(); + #else + SPI.begin(); + #endif + + // Check version (retry for up to 2 seconds) + // TODO: Actually read version registers, not syncwords + long start = millis(); + uint8_t syncmsb; + uint8_t synclsb; + while (((millis() - start) < 2000) && (millis() >= start)) { + syncmsb = readRegister(REG_SYNC_WORD_MSB_6X); + synclsb = readRegister(REG_SYNC_WORD_LSB_6X); + 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; + } + + _preinit_done = true; + return true; +} + +uint8_t ISR_VECT sx126x::readRegister(uint16_t address) { + return singleTransfer(OP_READ_REGISTER_6X, address, 0x00); +} + +void sx126x::writeRegister(uint16_t address, uint8_t value) { + singleTransfer(OP_WRITE_REGISTER_6X, address, value); +} + +uint8_t ISR_VECT sx126x::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_6X) { SPI.transfer(0x00); } + response = SPI.transfer(value); + SPI.endTransaction(); + + digitalWrite(_ss, HIGH); + + return response; +} + +void sx126x::rxAntEnable() { + if (_rxen != -1) { digitalWrite(_rxen, HIGH); } +} + +void sx126x::loraMode() { + // Enable lora mode on the SX1262 chip + uint8_t mode = MODE_LONG_RANGE_MODE_6X; + executeOpcode(OP_PACKET_TYPE_6X, &mode, 1); +} + +void sx126x::waitOnBusy() { + unsigned long time = millis(); + if (_busy != -1) { + while (digitalRead(_busy) == HIGH) { + if (millis() >= (time + 100)) { break; } + } + } +} + +void sx126x::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 sx126x::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 sx126x::writeBuffer(const uint8_t* buffer, size_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + SPI.beginTransaction(_spiSettings); + SPI.transfer(OP_FIFO_WRITE_6X); + 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 sx126x::readBuffer(uint8_t* buffer, size_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + SPI.beginTransaction(_spiSettings); + SPI.transfer(OP_FIFO_READ_6X); + 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 sx126x::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; + buf[3] = ldro; // Low data rate toggle + buf[4] = 0x00; // Unused params in LoRa mode + buf[5] = 0x00; + buf[6] = 0x00; + buf[7] = 0x00; + executeOpcode(OP_MODULATION_PARAMS_6X, buf, 8); +} + +void sx126x::setPacketParams(long preamble_symbols, uint8_t headermode, uint8_t payload_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_symbols & 0xFF00) >> 8); + buf[1] = uint8_t((preamble_symbols & 0x00FF)); + buf[2] = headermode; + buf[3] = payload_length; + buf[4] = crc; + buf[5] = 0x00; // standard IQ setting (no inversion) + buf[6] = 0x00; // unused params + buf[7] = 0x00; + buf[8] = 0x00; + executeOpcode(OP_PACKET_PARAMS_6X, buf, 9); +} + +void sx126x::reset(void) { + if (_reset != -1) { + pinMode(_reset, OUTPUT); + digitalWrite(_reset, LOW); + delay(10); + digitalWrite(_reset, HIGH); + delay(10); + } +} + +void sx126x::calibrate(void) { + // Put in STDBY_RC mode before calibration + uint8_t mode_byte = MODE_STDBY_RC_6X; + executeOpcode(OP_STANDBY_6X, &mode_byte, 1); + + // Calibrate RC64k, RC13M, PLL, ADC and image + uint8_t calibrate = MASK_CALIBRATE_ALL; + executeOpcode(OP_CALIBRATE_6X, &calibrate, 1); + + delay(5); + waitOnBusy(); +} + +void sx126x::calibrate_image(long frequency) { + uint8_t image_freq[2] = {0}; + if (frequency >= 430E6 && frequency <= 440E6) { image_freq[0] = 0x6B; image_freq[1] = 0x6F; } + else if (frequency >= 470E6 && frequency <= 510E6) { image_freq[0] = 0x75; image_freq[1] = 0x81; } + else if (frequency >= 779E6 && frequency <= 787E6) { image_freq[0] = 0xC1; image_freq[1] = 0xC5; } + else if (frequency >= 863E6 && frequency <= 870E6) { image_freq[0] = 0xD7; image_freq[1] = 0xDB; } + else if (frequency >= 902E6 && frequency <= 928E6) { image_freq[0] = 0xE1; image_freq[1] = 0xE9; } // TODO: Allow higher freq calibration + executeOpcode(OP_CALIBRATE_IMAGE_6X, image_freq, 2); + waitOnBusy(); +} + +int sx126x::begin(long frequency) { + reset(); + + if (_busy != -1) { pinMode(_busy, INPUT); } + if (!_preinit_done) { if (!preInit()) { return false; } } + if (_rxen != -1) { pinMode(_rxen, OUTPUT); } + + calibrate(); + calibrate_image(frequency); + enableTCXO(); + loraMode(); + standby(); + + // Set sync word + setSyncWord(SYNC_WORD_6X); + + #if DIO2_AS_RF_SWITCH + // enable dio2 rf switch + uint8_t byte = 0x01; + executeOpcode(OP_DIO2_RF_CTRL_6X, &byte, 1); + #endif + + rxAntEnable(); + setFrequency(frequency); + setTxPower(2); + enableCrc(); + writeRegister(REG_LNA_6X, 0x96); // Set LNA boost + uint8_t basebuf[2] = {0}; // Set base addresses + executeOpcode(OP_BUFFER_BASE_ADDR_6X, basebuf, 2); + + setModulationParams(_sf, _bw, _cr, _ldro); + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + + #if HAS_LORA_PA + #if LORA_PA_GC1109 + // Enable Vfem_ctl for supply to + // PA power net. + pinMode(LORA_PA_PWR_EN, OUTPUT); + digitalWrite(LORA_PA_PWR_EN, HIGH); + + // Enable PA LNA and TX standby + pinMode(LORA_PA_CSD, OUTPUT); + digitalWrite(LORA_PA_CSD, HIGH); + + // Keep PA CPS low until actual + // transmit. Does it save power? + // Who knows? Will have to measure. + pinMode(LORA_PA_CPS, OUTPUT); + digitalWrite(LORA_PA_CPS, LOW); + + // On Heltec V4, the PA CTX pin + // is driven by the SX1262 DIO2 + // pin directly, so we do not + // need to manually raise this. + #endif + #endif + + return 1; +} + +void sx126x::end() { sleep(); SPI.end(); _preinit_done = false; } + +int sx126x::beginPacket(int implicitHeader) { + #if HAS_LORA_PA + #if LORA_PA_GC1109 + // Enable PA CPS for transmit + digitalWrite(LORA_PA_CPS, HIGH); + #endif + #endif + + standby(); + if (implicitHeader) { implicitHeaderMode(); } + else { explicitHeaderMode(); } + + _payloadLength = 0; + _fifo_tx_addr_ptr = 0; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + + return 1; +} + +int sx126x::endPacket() { + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + uint8_t timeout[3] = {0}; // Put in single TX mode + executeOpcode(OP_TX_6X, timeout, 3); + + uint8_t buf[2]; + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_6X, buf, 2); + + // Wait for TX done + bool timed_out = false; + uint32_t w_timeout = millis()+LORA_MODEM_TIMEOUT_MS; + while ((millis() < w_timeout) && ((buf[1] & IRQ_TX_DONE_MASK_6X) == 0)) { + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_6X, buf, 2); + yield(); + } + + if (!(millis() < w_timeout)) { timed_out = true; } + + // Clear IRQs + uint8_t mask[2]; + mask[0] = 0x00; + mask[1] = IRQ_TX_DONE_MASK_6X; + executeOpcode(OP_CLEAR_IRQ_STATUS_6X, mask, 2); + if (timed_out) { return 0; } else { return 1; } +} + +unsigned long preamble_detected_at = 0; +extern long lora_preamble_time_ms; +extern long lora_header_time_ms; +bool false_preamble_detected = false; + +bool sx126x::dcd() { + uint8_t buf[2] = {0}; executeOpcodeRead(OP_GET_IRQ_STATUS_6X, buf, 2); + uint32_t now = millis(); + + bool header_detected = false; + bool carrier_detected = false; + + if ((buf[1] & IRQ_HEADER_DET_MASK_6X) != 0) { header_detected = true; carrier_detected = true; } + else { header_detected = false; } + + if ((buf[1] & IRQ_PREAMBLE_DET_MASK_6X) != 0) { + carrier_detected = true; + if (preamble_detected_at == 0) { preamble_detected_at = now; } + if (now - preamble_detected_at > lora_preamble_time_ms + lora_header_time_ms) { + preamble_detected_at = 0; + if (!header_detected) { false_preamble_detected = true; } + uint8_t clearbuf[2] = {0}; + clearbuf[1] = IRQ_PREAMBLE_DET_MASK_6X; + executeOpcode(OP_CLEAR_IRQ_STATUS_6X, clearbuf, 2); + } + } + + // TODO: Maybe there's a way of unlatching the RSSI + // status without re-activating receive mode? + if (false_preamble_detected) { sx126x_modem.receive(); false_preamble_detected = false; } + return carrier_detected; +} + +uint8_t sx126x::currentRssiRaw() { + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1); + return byte; +} + +int ISR_VECT sx126x::currentRssi() { + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1); + int rssi = -(int(byte)) / 2; + #if HAS_LORA_LNA + rssi -= LORA_LNA_GAIN; + #endif + return rssi; +} + +uint8_t sx126x::packetRssiRaw() { + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + return buf[2]; +} + +int ISR_VECT sx126x::packetRssi() { + // TODO: May need more calculations here + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + int pkt_rssi = -buf[0] / 2; + #if HAS_LORA_LNA + pkt_rssi -= LORA_LNA_GAIN; + #endif + return pkt_rssi; +} + +int ISR_VECT sx126x::packetRssi(uint8_t pkt_snr_raw) { + // TODO: May need more calculations here + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + int pkt_rssi = -buf[0] / 2; + return pkt_rssi; +} + +uint8_t ISR_VECT sx126x::packetSnrRaw() { + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + return buf[1]; +} + +float ISR_VECT sx126x::packetSnr() { + uint8_t buf[3] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); + return float(buf[1]) * 0.25; +} + +long sx126x::packetFrequencyError() { + // TODO: Implement this, no idea how to check it on the sx1262 + const float fError = 0.0; + return static_cast(fError); +} + +size_t sx126x::write(uint8_t byte) { return write(&byte, sizeof(byte)); } +size_t sx126x::write(const uint8_t *buffer, size_t size) { + if ((_payloadLength + size) > MAX_PKT_LENGTH) { size = MAX_PKT_LENGTH - _payloadLength; } + writeBuffer(buffer, size); + _payloadLength = _payloadLength + size; + return size; +} + +int ISR_VECT sx126x::available() { + uint8_t buf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_6X, buf, 2); + return buf[0] - _packetIndex; +} + +int ISR_VECT sx126x::read(){ + if (!available()) { return -1; } + if (_packetIndex == 0) { + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_6X, rxbuf, 2); + int size = rxbuf[0]; + _fifo_rx_addr_ptr = rxbuf[1]; + readBuffer(_packet, size); + } + + uint8_t byte = _packet[_packetIndex]; + _packetIndex++; + return byte; +} + +int sx126x::peek() { + if (!available()) { return -1; } + if (_packetIndex == 0) { + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_6X, rxbuf, 2); + int size = rxbuf[0]; + _fifo_rx_addr_ptr = rxbuf[1]; + readBuffer(_packet, size); + } + + uint8_t b = _packet[_packetIndex]; + return b; +} + +void sx126x::flush() { } + +void sx126x::onReceive(void(*callback)(int)){ + _onReceive = callback; + + if (callback) { + pinMode(_dio0, INPUT); + uint8_t buf[8]; // Set preamble and header detection irqs, plus dio0 mask + buf[0] = 0xFF; // Set irq masks, enable all + buf[1] = 0xFF; + buf[2] = 0x00; // Set dio0 masks + buf[3] = IRQ_RX_DONE_MASK_6X; + buf[4] = 0x00; // Set dio1 masks + buf[5] = 0x00; + buf[6] = 0x00; // Set dio2 masks + buf[7] = 0x00; + executeOpcode(OP_SET_IRQ_FLAGS_6X, buf, 8); + + #ifdef SPI_HAS_NOTUSINGINTERRUPT + SPI.usingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + attachInterrupt(digitalPinToInterrupt(_dio0), sx126x::onDio0Rise, RISING); + + } else { + detachInterrupt(digitalPinToInterrupt(_dio0)); + #ifdef SPI_HAS_NOTUSINGINTERRUPT + SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + } +} + +void sx126x::receive(int size) { + #if HAS_LORA_PA + #if LORA_PA_GC1109 + // Disable PA CPS for receive + digitalWrite(LORA_PA_CPS, LOW); + #endif + #endif + + if (size > 0) { + implicitHeaderMode(); + _payloadLength = size; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + } else { explicitHeaderMode(); } + + if (_rxen != -1) { rxAntEnable(); } + uint8_t mode[3] = {0xFF, 0xFF, 0xFF}; // Continuous mode + executeOpcode(OP_RX_6X, mode, 3); +} + +void sx126x::standby() { + uint8_t byte = MODE_STDBY_XOSC_6X; // STDBY_XOSC + executeOpcode(OP_STANDBY_6X, &byte, 1); +} + +void sx126x::sleep() { uint8_t byte = 0x00; executeOpcode(OP_SLEEP_6X, &byte, 1); } + +void sx126x::enableTCXO() { + #if HAS_TCXO + #if BOARD_MODEL == BOARD_RAK4631 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_XIAO_S3 || BOARD_MODEL == BOARD_VME213 + uint8_t buf[4] = {MODE_TCXO_3_3V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_TBEAM + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_TDECK + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_TBEAM_S_V1 + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_T3S3 + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_HELTEC_T114 + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_TECHO + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #elif BOARD_MODEL == BOARD_HELTEC32_V4 + uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; + #endif + executeOpcode(OP_DIO3_TCXO_CTRL_6X, buf, 4); + #endif +} + +// TODO: Once enabled, SX1262 needs a complete reset to disable TCXO +void sx126x::disableTCXO() { } + +void sx126x::setTxPower(int level, int outputPin) { + // 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; // PADutyCycle needs to be 0x04 to achieve 22dBm output, but can be lowered for better efficiency at lower outputs + pa_buf[1] = 0x07; // HPMax at 0x07 is maximum supported for SX1262 + pa_buf[2] = 0x00; // DeviceSel 0x00 for SX1262 (0x01 for SX1261) + pa_buf[3] = 0x01; // PALut always 0x01 (reserved according to datasheet) + executeOpcode(OP_PA_CONFIG_6X, pa_buf, 4); // set pa_config for high power + + if (level > 22) { level = 22; } + else if (level < -9) { level = -9; } + writeRegister(REG_OCP_6X, OCP_TUNED); // Use board-specific tuned OCP + + uint8_t tx_buf[2]; + tx_buf[0] = level; + tx_buf[1] = 0x02; // PA ramping time - 40 microseconds + executeOpcode(OP_TX_PARAMS_6X, tx_buf, 2); + + _txp = level; +} + +uint8_t sx126x::getTxPower() { return _txp; } + +void sx126x::setFrequency(long frequency) { + _frequency = frequency; + uint8_t buf[4]; + uint32_t freq = (uint32_t)((double)frequency / (double)FREQ_STEP_6X); + buf[0] = ((freq >> 24) & 0xFF); + buf[1] = ((freq >> 16) & 0xFF); + buf[2] = ((freq >> 8) & 0xFF); + buf[3] = (freq & 0xFF); + executeOpcode(OP_RF_FREQ_6X, buf, 4); +} + +uint32_t sx126x::getFrequency() { + // We can't read the frequency on the sx1262 / 80 + uint32_t frequency = _frequency; + return frequency; +} + +void sx126x::setSpreadingFactor(int sf) { + if (sf < 5) { sf = 5; } + else if (sf > 12) { sf = 12; } + _sf = sf; + + handleLowDataRate(); + setModulationParams(sf, _bw, _cr, _ldro); +} + +long sx126x::getSignalBandwidth() { + 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; + } + return 0; +} + +extern bool lora_low_datarate; +void sx126x::handleLowDataRate() { + if ( long( (1<<_sf) / (getSignalBandwidth()/1000)) > 16) + { _ldro = 0x01; lora_low_datarate = true; } + else { _ldro = 0x00; lora_low_datarate = false; } +} + +// TODO: Check if there's anything the sx1262 can do here +void sx126x::optimizeModemSensitivity(){ } + +void sx126x::setSignalBandwidth(long sbw) { + 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 { _bw = 0x06; } + + handleLowDataRate(); + setModulationParams(_sf, _bw, _cr, _ldro); + optimizeModemSensitivity(); +} + +void sx126x::setCodingRate4(int denominator) { + if (denominator < 5) { denominator = 5; } + else if (denominator > 8) { denominator = 8; } + int cr = denominator - 4; + _cr = cr; + setModulationParams(_sf, _bw, cr, _ldro); +} + +void sx126x::setPreambleLength(long preamble_symbols) { + _preambleLength = preamble_symbols; + setPacketParams(preamble_symbols, _implicitHeaderMode, _payloadLength, _crcMode); +} + +void sx126x::setSyncWord(uint16_t sw) { + // TODO: Why was this hardcoded instead of using the config value? + // writeRegister(REG_SYNC_WORD_MSB_6X, (sw & 0xFF00) >> 8); + // writeRegister(REG_SYNC_WORD_LSB_6X, sw & 0x00FF); + writeRegister(REG_SYNC_WORD_MSB_6X, 0x14); + writeRegister(REG_SYNC_WORD_LSB_6X, 0x24); +} + +void sx126x::setPins(int ss, int reset, int dio0, int busy, int rxen) { + _ss = ss; + _reset = reset; + _dio0 = dio0; + _busy = busy; + _rxen = rxen; +} + +void sx126x::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); + } +} + +void ISR_VECT sx126x::handleDio0Rise() { + uint8_t buf[2]; + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_6X, buf, 2); + executeOpcode(OP_CLEAR_IRQ_STATUS_6X, buf, 2); + + if ((buf[1] & IRQ_PAYLOAD_CRC_ERROR_MASK_6X) == 0) { + _packetIndex = 0; + uint8_t rxbuf[2] = {0}; // Read packet length + executeOpcodeRead(OP_RX_BUFFER_STATUS_6X, rxbuf, 2); + int packetLength = rxbuf[0]; + if (_onReceive) { _onReceive(packetLength); } + } +} + +void ISR_VECT sx126x::onDio0Rise() { sx126x_modem.handleDio0Rise(); } +void sx126x::setSPIFrequency(uint32_t frequency) { _spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0); } +void sx126x::enableCrc() { _crcMode = 1; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx126x::disableCrc() { _crcMode = 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx126x::explicitHeaderMode() { _implicitHeaderMode = 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx126x::implicitHeaderMode() { _implicitHeaderMode = 1; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +byte sx126x::random() { return readRegister(REG_RANDOM_GEN_6X); } + +sx126x sx126x_modem; + +#endif \ No newline at end of file diff --git a/RNode_Firmware/sx126x.h b/RNode_Firmware/sx126x.h new file mode 100644 index 0000000..068a1bb --- /dev/null +++ b/RNode_Firmware/sx126x.h @@ -0,0 +1,145 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#ifndef SX126X_H +#define SX126X_H + +#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_TXEN_PIN -1 +#define LORA_DEFAULT_BUSY_PIN -1 +#define LORA_MODEM_TIMEOUT_MS 20E3 + +#define PA_OUTPUT_RFO_PIN 0 +#define PA_OUTPUT_PA_BOOST_PIN 1 + +#define RSSI_OFFSET 157 + +class sx126x : public Stream { +public: + sx126x(); + + int begin(long frequency); + void end(); + + int beginPacket(int implicitHeader = false); + int endPacket(); + + int parsePacket(int size = 0); + int packetRssi(); + int packetRssi(uint8_t pkt_snr_raw); + int currentRssi(); + uint8_t packetRssiRaw(); + uint8_t currentRssiRaw(); + uint8_t packetSnrRaw(); + float packetSnr(); + long packetFrequencyError(); + + // from Print + virtual size_t write(uint8_t byte); + virtual size_t write(const uint8_t *buffer, size_t size); + + // from Stream + virtual int available(); + virtual int read(); + virtual int peek(); + virtual void flush(); + + void onReceive(void(*callback)(int)); + + void receive(int size = 0); + void standby(); + void sleep(); + void reset(void); + + bool preInit(); + uint8_t getTxPower(); + void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN); + uint32_t getFrequency(); + void setFrequency(long frequency); + void setSpreadingFactor(int sf); + long getSignalBandwidth(); + void setSignalBandwidth(long sbw); + void setCodingRate4(int denominator); + void setPreambleLength(long preamble_symbols); + void setSyncWord(uint16_t sw); + bool dcd(); + void enableCrc(); + void disableCrc(); + void enableTCXO(); + void disableTCXO(); + + void rxAntEnable(); + 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 setPacketParams(long preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc); + + void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro); + + // 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, int busy = LORA_DEFAULT_BUSY_PIN, int rxen = LORA_DEFAULT_RXEN_PIN); + void setSPIFrequency(uint32_t frequency); + + void dumpRegisters(Stream& out); + +private: + void explicitHeaderMode(); + void implicitHeaderMode(); + + void handleDio0Rise(); + + 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); + + static void onDio0Rise(); + + void handleLowDataRate(); + void optimizeModemSensitivity(); + + void calibrate(void); + void calibrate_image(long frequency); + +private: + SPISettings _spiSettings; + 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; + int _fifo_tx_addr_ptr; + int _fifo_rx_addr_ptr; + uint8_t _packet[255]; + bool _preinit_done; + void (*_onReceive)(int); +}; + +extern sx126x sx126x_modem; + +#endif diff --git a/RNode_Firmware/sx127x.cpp b/RNode_Firmware/sx127x.cpp new file mode 100644 index 0000000..ef9038d --- /dev/null +++ b/RNode_Firmware/sx127x.cpp @@ -0,0 +1,503 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#include "Boards.h" + +#if MODEM == SX1276 +#include "sx127x.h" + +#if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 and !defined(CONFIG_IDF_TARGET_ESP32S3) + #include "hal/wdt_hal.h" + #endif + #define ISR_VECT IRAM_ATTR +#else + #define ISR_VECT +#endif + +// Registers +#define REG_FIFO_7X 0x00 +#define REG_OP_MODE_7X 0x01 +#define REG_FRF_MSB_7X 0x06 +#define REG_FRF_MID_7X 0x07 +#define REG_FRF_LSB_7X 0x08 +#define REG_PA_CONFIG_7X 0x09 +#define REG_OCP_7X 0x0b +#define REG_LNA_7X 0x0c +#define REG_FIFO_ADDR_PTR_7X 0x0d +#define REG_FIFO_TX_BASE_ADDR_7X 0x0e +#define REG_FIFO_RX_BASE_ADDR_7X 0x0f +#define REG_FIFO_RX_CURRENT_ADDR_7X 0x10 +#define REG_IRQ_FLAGS_7X 0x12 +#define REG_RX_NB_BYTES_7X 0x13 +#define REG_MODEM_STAT_7X 0x18 +#define REG_PKT_SNR_VALUE_7X 0x19 +#define REG_PKT_RSSI_VALUE_7X 0x1a +#define REG_RSSI_VALUE_7X 0x1b +#define REG_MODEM_CONFIG_1_7X 0x1d +#define REG_MODEM_CONFIG_2_7X 0x1e +#define REG_PREAMBLE_MSB_7X 0x20 +#define REG_PREAMBLE_LSB_7X 0x21 +#define REG_PAYLOAD_LENGTH_7X 0x22 +#define REG_MODEM_CONFIG_3_7X 0x26 +#define REG_FREQ_ERROR_MSB_7X 0x28 +#define REG_FREQ_ERROR_MID_7X 0x29 +#define REG_FREQ_ERROR_LSB_7X 0x2a +#define REG_RSSI_WIDEBAND_7X 0x2c +#define REG_DETECTION_OPTIMIZE_7X 0x31 +#define REG_HIGH_BW_OPTIMIZE_1_7X 0x36 +#define REG_DETECTION_THRESHOLD_7X 0x37 +#define REG_SYNC_WORD_7X 0x39 +#define REG_HIGH_BW_OPTIMIZE_2_7X 0x3a +#define REG_DIO_MAPPING_1_7X 0x40 +#define REG_VERSION_7X 0x42 +#define REG_TCXO_7X 0x4b +#define REG_PA_DAC_7X 0x4d + +// Modes +#define MODE_LONG_RANGE_MODE_7X 0x80 +#define MODE_SLEEP_7X 0x00 +#define MODE_STDBY_7X 0x01 +#define MODE_TX_7X 0x03 +#define MODE_RX_CONTINUOUS_7X 0x05 +#define MODE_RX_SINGLE_7X 0x06 + +// PA config +#define PA_BOOST_7X 0x80 + +// IRQ masks +#define IRQ_TX_DONE_MASK_7X 0x08 +#define IRQ_RX_DONE_MASK_7X 0x40 +#define IRQ_PAYLOAD_CRC_ERROR_MASK_7X 0x20 + +#define SYNC_WORD_7X 0x12 +#define MAX_PKT_LENGTH 255 + +extern SPIClass SPI; + +sx127x::sx127x() : + _spiSettings(8E6, MSBFIRST, SPI_MODE0), + _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN), + _frequency(0), _packetIndex(0), _preinit_done(false), _onReceive(NULL) { setTimeout(0); } + +void sx127x::setSPIFrequency(uint32_t frequency) { _spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0); } +void sx127x::setPins(int ss, int reset, int dio0, int busy) { _ss = ss; _reset = reset; _dio0 = dio0; _busy = busy; } +uint8_t ISR_VECT sx127x::readRegister(uint8_t address) { return singleTransfer(address & 0x7f, 0x00); } +void sx127x::writeRegister(uint8_t address, uint8_t value) { singleTransfer(address | 0x80, value); } +void sx127x::standby() { writeRegister(REG_OP_MODE_7X, MODE_LONG_RANGE_MODE_7X | MODE_STDBY_7X); } +void sx127x::sleep() { writeRegister(REG_OP_MODE_7X, MODE_LONG_RANGE_MODE_7X | MODE_SLEEP_7X); } +void sx127x::setSyncWord(uint8_t sw) { writeRegister(REG_SYNC_WORD_7X, sw); } +void sx127x::enableCrc() { writeRegister(REG_MODEM_CONFIG_2_7X, readRegister(REG_MODEM_CONFIG_2_7X) | 0x04); } +void sx127x::disableCrc() { writeRegister(REG_MODEM_CONFIG_2_7X, readRegister(REG_MODEM_CONFIG_2_7X) & 0xfb); } +void sx127x::enableTCXO() { uint8_t tcxo_reg = readRegister(REG_TCXO_7X); writeRegister(REG_TCXO_7X, tcxo_reg | 0x10); } +void sx127x::disableTCXO() { uint8_t tcxo_reg = readRegister(REG_TCXO_7X); writeRegister(REG_TCXO_7X, tcxo_reg & 0xEF); } +void sx127x::explicitHeaderMode() { _implicitHeaderMode = 0; writeRegister(REG_MODEM_CONFIG_1_7X, readRegister(REG_MODEM_CONFIG_1_7X) & 0xfe); } +void sx127x::implicitHeaderMode() { _implicitHeaderMode = 1; writeRegister(REG_MODEM_CONFIG_1_7X, readRegister(REG_MODEM_CONFIG_1_7X) | 0x01); } +byte sx127x::random() { return readRegister(REG_RSSI_WIDEBAND_7X); } +void sx127x::flush() { } + +bool sx127x::preInit() { + pinMode(_ss, OUTPUT); + digitalWrite(_ss, HIGH); + + #if BOARD_MODEL == BOARD_T3S3 + SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs); + #else + SPI.begin(); + #endif + + // Check modem version + uint8_t version; + long start = millis(); + while (((millis() - start) < 500) && (millis() >= start)) { + version = readRegister(REG_VERSION_7X); + if (version == 0x12) { break; } + delay(100); + } + + if (version != 0x12) { return false; } + _preinit_done = true; + return true; +} + +uint8_t ISR_VECT sx127x::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; +} + +int sx127x::begin(long frequency) { + if (_reset != -1) { + pinMode(_reset, OUTPUT); + digitalWrite(_reset, LOW); + delay(10); + digitalWrite(_reset, HIGH); + delay(10); + } + + if (_busy != -1) { pinMode(_busy, INPUT); } + if (!_preinit_done) { if (!preInit()) { return false; } } + + sleep(); + setFrequency(frequency); + + // Set base addresses + writeRegister(REG_FIFO_TX_BASE_ADDR_7X, 0); + writeRegister(REG_FIFO_RX_BASE_ADDR_7X, 0); + + // Set LNA boost and auto AGC + writeRegister(REG_LNA_7X, readRegister(REG_LNA_7X) | 0x03); + writeRegister(REG_MODEM_CONFIG_3_7X, 0x04); + + setSyncWord(SYNC_WORD_7X); + enableCrc(); + setTxPower(2); + + standby(); + + return 1; +} + +void sx127x::end() { sleep(); SPI.end(); _preinit_done = false; } + +int sx127x::beginPacket(int implicitHeader) { + standby(); + + if (implicitHeader) { implicitHeaderMode(); } + else { explicitHeaderMode(); } + + // Reset FIFO address and payload length + writeRegister(REG_FIFO_ADDR_PTR_7X, 0); + writeRegister(REG_PAYLOAD_LENGTH_7X, 0); + + return 1; +} + +int sx127x::endPacket() { + // Enter TX mode + writeRegister(REG_OP_MODE_7X, MODE_LONG_RANGE_MODE_7X | MODE_TX_7X); + + // Wait for TX completion + while ((readRegister(REG_IRQ_FLAGS_7X) & IRQ_TX_DONE_MASK_7X) == 0) { + yield(); + } + + // Clear TX complete IRQ + writeRegister(REG_IRQ_FLAGS_7X, IRQ_TX_DONE_MASK_7X); + return 1; +} + +bool sx127x::dcd() { + bool carrier_detected = false; + uint8_t status = readRegister(REG_MODEM_STAT_7X); + if ((status & SIG_DETECT) == SIG_DETECT) { carrier_detected = true; } + if ((status & SIG_SYNCED) == SIG_SYNCED) { carrier_detected = true; } + return carrier_detected; +} + +uint8_t sx127x::currentRssiRaw() { + uint8_t rssi = readRegister(REG_RSSI_VALUE_7X); + return rssi; +} + +int ISR_VECT sx127x::currentRssi() { + int rssi = (int)readRegister(REG_RSSI_VALUE_7X) - RSSI_OFFSET; + if (_frequency < 820E6) rssi -= 7; + return rssi; +} + +uint8_t sx127x::packetRssiRaw() { + uint8_t pkt_rssi_value = readRegister(REG_PKT_RSSI_VALUE_7X); + return pkt_rssi_value; +} + +int ISR_VECT sx127x::packetRssi(uint8_t pkt_snr_raw) { + int pkt_rssi = (int)readRegister(REG_PKT_RSSI_VALUE_7X) - RSSI_OFFSET; + int pkt_snr = ((int8_t)pkt_snr_raw)*0.25; + + 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; +} + +int ISR_VECT sx127x::packetRssi() { + int pkt_rssi = (int)readRegister(REG_PKT_RSSI_VALUE_7X) - 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 sx127x::packetSnrRaw() { return readRegister(REG_PKT_SNR_VALUE_7X); } + +float ISR_VECT sx127x::packetSnr() { return ((int8_t)readRegister(REG_PKT_SNR_VALUE_7X)) * 0.25; } + +long sx127x::packetFrequencyError() { + int32_t freqError = 0; + freqError = static_cast(readRegister(REG_FREQ_ERROR_MSB_7X) & B111); + freqError <<= 8L; + freqError += static_cast(readRegister(REG_FREQ_ERROR_MID_7X)); + freqError <<= 8L; + freqError += static_cast(readRegister(REG_FREQ_ERROR_LSB_7X)); + + if (readRegister(REG_FREQ_ERROR_MSB_7X) & 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); + + return static_cast(fError); +} + +size_t sx127x::write(uint8_t byte) { return write(&byte, sizeof(byte)); } + +size_t sx127x::write(const uint8_t *buffer, size_t size) { + int currentLength = readRegister(REG_PAYLOAD_LENGTH_7X); + if ((currentLength + size) > MAX_PKT_LENGTH) { size = MAX_PKT_LENGTH - currentLength; } + + for (size_t i = 0; i < size; i++) { writeRegister(REG_FIFO_7X, buffer[i]); } + writeRegister(REG_PAYLOAD_LENGTH_7X, currentLength + size); + + return size; +} + +int ISR_VECT sx127x::available() { return (readRegister(REG_RX_NB_BYTES_7X) - _packetIndex); } + +int ISR_VECT sx127x::read() { + if (!available()) { return -1; } + _packetIndex++; + return readRegister(REG_FIFO_7X); +} + +int sx127x::peek() { + if (!available()) { return -1; } + + // Remember current FIFO address, read, and then reset address + int currentAddress = readRegister(REG_FIFO_ADDR_PTR_7X); + uint8_t b = readRegister(REG_FIFO_7X); + writeRegister(REG_FIFO_ADDR_PTR_7X, currentAddress); + + return b; +} + +void sx127x::onReceive(void(*callback)(int)) { + _onReceive = callback; + + if (callback) { + pinMode(_dio0, INPUT); + writeRegister(REG_DIO_MAPPING_1_7X, 0x00); + + #ifdef SPI_HAS_NOTUSINGINTERRUPT + SPI.usingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + + attachInterrupt(digitalPinToInterrupt(_dio0), sx127x::onDio0Rise, RISING); + + } else { + detachInterrupt(digitalPinToInterrupt(_dio0)); + + #ifdef SPI_HAS_NOTUSINGINTERRUPT + SPI.notUsingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + } +} + +void sx127x::receive(int size) { + if (size > 0) { + implicitHeaderMode(); + writeRegister(REG_PAYLOAD_LENGTH_7X, size & 0xff); + } else { explicitHeaderMode(); } + + writeRegister(REG_OP_MODE_7X, MODE_LONG_RANGE_MODE_7X | MODE_RX_CONTINUOUS_7X); +} + +void sx127x::setTxPower(int level, int outputPin) { + // Setup according to RFO or PA_BOOST output pin + if (PA_OUTPUT_RFO_PIN == outputPin) { + if (level < 0) { level = 0; } + else if (level > 14) { level = 14; } + + writeRegister(REG_PA_DAC_7X, 0x84); + writeRegister(REG_PA_CONFIG_7X, 0x70 | level); + + } else { + if (level < 2) { level = 2; } + else if (level > 17) { level = 17; } + + writeRegister(REG_PA_DAC_7X, 0x84); + writeRegister(REG_PA_CONFIG_7X, PA_BOOST_7X | (level - 2)); + } +} + +uint8_t sx127x::getTxPower() { byte txp = readRegister(REG_PA_CONFIG_7X); return txp; } + +void sx127x::setFrequency(unsigned long frequency) { + _frequency = frequency; + uint32_t frf = ((uint64_t)frequency << 19) / 32000000; + + writeRegister(REG_FRF_MSB_7X, (uint8_t)(frf >> 16)); + writeRegister(REG_FRF_MID_7X, (uint8_t)(frf >> 8)); + writeRegister(REG_FRF_LSB_7X, (uint8_t)(frf >> 0)); + + optimizeModemSensitivity(); +} + +uint32_t sx127x::getFrequency() { + uint8_t msb = readRegister(REG_FRF_MSB_7X); + uint8_t mid = readRegister(REG_FRF_MID_7X); + uint8_t lsb = readRegister(REG_FRF_LSB_7X); + + 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 sx127x::setSpreadingFactor(int sf) { + if (sf < 6) { sf = 6; } + else if (sf > 12) { sf = 12; } + + if (sf == 6) { + writeRegister(REG_DETECTION_OPTIMIZE_7X, 0xc5); + writeRegister(REG_DETECTION_THRESHOLD_7X, 0x0c); + } else { + writeRegister(REG_DETECTION_OPTIMIZE_7X, 0xc3); + writeRegister(REG_DETECTION_THRESHOLD_7X, 0x0a); + } + + writeRegister(REG_MODEM_CONFIG_2_7X, (readRegister(REG_MODEM_CONFIG_2_7X) & 0x0f) | ((sf << 4) & 0xf0)); + handleLowDataRate(); +} + +long sx127x::getSignalBandwidth() { + byte bw = (readRegister(REG_MODEM_CONFIG_1_7X) >> 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 sx127x::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_7X, (readRegister(REG_MODEM_CONFIG_1_7X) & 0x0f) | (bw << 4)); + handleLowDataRate(); + optimizeModemSensitivity(); +} + +void sx127x::setCodingRate4(int denominator) { + if (denominator < 5) { denominator = 5; } + else if (denominator > 8) { denominator = 8; } + int cr = denominator - 4; + writeRegister(REG_MODEM_CONFIG_1_7X, (readRegister(REG_MODEM_CONFIG_1_7X) & 0xf1) | (cr << 1)); +} + +void sx127x::setPreambleLength(long preamble_symbols) { + long length = preamble_symbols - 4; + writeRegister(REG_PREAMBLE_MSB_7X, (uint8_t)(length >> 8)); + writeRegister(REG_PREAMBLE_LSB_7X, (uint8_t)(length >> 0)); +} + +extern bool lora_low_datarate; +void sx127x::handleLowDataRate() { + int sf = (readRegister(REG_MODEM_CONFIG_2_7X) >> 4); + if ( long( (1< 16) { + // Set auto AGC and LowDataRateOptimize + writeRegister(REG_MODEM_CONFIG_3_7X, (1<<3)|(1<<2)); + lora_low_datarate = true; + } else { + // Only set auto AGC + writeRegister(REG_MODEM_CONFIG_3_7X, (1<<2)); + lora_low_datarate = false; + } +} + +void sx127x::optimizeModemSensitivity() { + byte bw = (readRegister(REG_MODEM_CONFIG_1_7X) >> 4); + uint32_t freq = getFrequency(); + + if (bw == 9 && (410E6 <= freq) && (freq <= 525E6)) { + writeRegister(REG_HIGH_BW_OPTIMIZE_1_7X, 0x02); + writeRegister(REG_HIGH_BW_OPTIMIZE_2_7X, 0x7f); + } else if (bw == 9 && (820E6 <= freq) && (freq <= 1020E6)) { + writeRegister(REG_HIGH_BW_OPTIMIZE_1_7X, 0x02); + writeRegister(REG_HIGH_BW_OPTIMIZE_2_7X, 0x64); + } else { + writeRegister(REG_HIGH_BW_OPTIMIZE_1_7X, 0x03); + } +} + +void ISR_VECT sx127x::handleDio0Rise() { + int irqFlags = readRegister(REG_IRQ_FLAGS_7X); + + // Clear IRQs + writeRegister(REG_IRQ_FLAGS_7X, irqFlags); + if ((irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK_7X) == 0) { + _packetIndex = 0; + int packetLength = _implicitHeaderMode ? readRegister(REG_PAYLOAD_LENGTH_7X) : readRegister(REG_RX_NB_BYTES_7X); + writeRegister(REG_FIFO_ADDR_PTR_7X, readRegister(REG_FIFO_RX_CURRENT_ADDR_7X)); + if (_onReceive) { _onReceive(packetLength); } + writeRegister(REG_FIFO_ADDR_PTR_7X, 0); + } +} + +void ISR_VECT sx127x::onDio0Rise() { sx127x_modem.handleDio0Rise(); } + +sx127x sx127x_modem; + +#endif \ No newline at end of file diff --git a/RNode_Firmware/sx127x.h b/RNode_Firmware/sx127x.h new file mode 100644 index 0000000..7639857 --- /dev/null +++ b/RNode_Firmware/sx127x.h @@ -0,0 +1,114 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#ifndef SX1276_H +#define SX1276_H + +#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_BUSY_PIN -1 + +#define PA_OUTPUT_RFO_PIN 0 +#define PA_OUTPUT_PA_BOOST_PIN 1 + +#define RSSI_OFFSET 157 + +// Modem status flags +#define SIG_DETECT 0x01 +#define SIG_SYNCED 0x02 +#define RX_ONGOING 0x04 + +class sx127x : public Stream { +public: + sx127x(); + + int begin(long frequency); + void end(); + + int beginPacket(int implicitHeader = false); + int endPacket(); + + int parsePacket(int size = 0); + int packetRssi(); + int packetRssi(uint8_t pkt_snr_raw); + int currentRssi(); + uint8_t packetRssiRaw(); + uint8_t currentRssiRaw(); + uint8_t packetSnrRaw(); + float packetSnr(); + long packetFrequencyError(); + + // from Print + virtual size_t write(uint8_t byte); + virtual size_t write(const uint8_t *buffer, size_t size); + + // from Stream + virtual int available(); + virtual int read(); + virtual int peek(); + virtual void flush(); + + void onReceive(void(*callback)(int)); + + void receive(int size = 0); + void standby(); + void sleep(); + + bool preInit(); + uint8_t getTxPower(); + void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN); + uint32_t getFrequency(); + void setFrequency(unsigned long frequency); + void setSpreadingFactor(int sf); + long getSignalBandwidth(); + void setSignalBandwidth(long sbw); + void setCodingRate4(int denominator); + void setPreambleLength(long preamble_symbols); + void setSyncWord(uint8_t sw); + bool dcd(); + void enableCrc(); + void disableCrc(); + void enableTCXO(); + void disableTCXO(); + + byte random(); + + void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int busy = LORA_DEFAULT_BUSY_PIN); + void setSPIFrequency(uint32_t frequency); + +private: + void explicitHeaderMode(); + void implicitHeaderMode(); + + 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); + + static void onDio0Rise(); + + void handleLowDataRate(); + void optimizeModemSensitivity(); + +private: + SPISettings _spiSettings; + int _ss; + int _reset; + int _dio0; + int _busy; + long _frequency; + int _packetIndex; + int _implicitHeaderMode; + bool _preinit_done; + void (*_onReceive)(int); +}; + +extern sx127x sx127x_modem; + +#endif diff --git a/RNode_Firmware/sx128x.cpp b/RNode_Firmware/sx128x.cpp new file mode 100644 index 0000000..59c464e --- /dev/null +++ b/RNode_Firmware/sx128x.cpp @@ -0,0 +1,887 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#include "Boards.h" + +#if MODEM == SX1280 +#include "sx128x.h" + +#define MCU_1284P 0x91 +#define MCU_2560 0x92 +#define MCU_ESP32 0x81 +#define MCU_NRF52 0x71 +#if defined(__AVR_ATmega1284P__) + #define PLATFORM PLATFORM_AVR + #define MCU_VARIANT MCU_1284P +#elif defined(__AVR_ATmega2560__) + #define PLATFORM PLATFORM_AVR + #define MCU_VARIANT MCU_2560 +#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 +#endif + +#ifndef MCU_VARIANT + #error No MCU variant defined, cannot compile +#endif + +#if MCU_VARIANT == MCU_ESP32 + #if MCU_VARIANT == MCU_ESP32 and !defined(CONFIG_IDF_TARGET_ESP32S3) + #include "hal/wdt_hal.h" + #endif + #define ISR_VECT IRAM_ATTR +#else + #define ISR_VECT +#endif + +// SX128x registers +#define OP_RF_FREQ_8X 0x86 +#define OP_SLEEP_8X 0x84 +#define OP_STANDBY_8X 0x80 +#define OP_TX_8X 0x83 +#define OP_RX_8X 0x82 +#define OP_SET_IRQ_FLAGS_8X 0x8D +#define OP_CLEAR_IRQ_STATUS_8X 0x97 +#define OP_GET_IRQ_STATUS_8X 0x15 +#define OP_RX_BUFFER_STATUS_8X 0x17 +#define OP_PACKET_STATUS_8X 0x1D +#define OP_CURRENT_RSSI_8X 0x1F +#define OP_MODULATION_PARAMS_8X 0x8B +#define OP_PACKET_PARAMS_8X 0x8C +#define OP_STATUS_8X 0xC0 +#define OP_TX_PARAMS_8X 0x8E +#define OP_PACKET_TYPE_8X 0x8A +#define OP_BUFFER_BASE_ADDR_8X 0x8F +#define OP_READ_REGISTER_8X 0x19 +#define OP_WRITE_REGISTER_8X 0x18 +#define IRQ_TX_DONE_MASK_8X 0x01 +#define IRQ_RX_DONE_MASK_8X 0x02 +#define IRQ_HEADER_DET_MASK_8X 0x10 +#define IRQ_HEADER_ERROR_MASK_8X 0x20 +#define IRQ_PAYLOAD_CRC_ERROR_MASK_8X 0x40 + +#define MODE_LONG_RANGE_MODE_8X 0x01 + +#define OP_FIFO_WRITE_8X 0x1A +#define OP_FIFO_READ_8X 0x1B +#define IRQ_PREAMBLE_DET_MASK_8X 0x80 + +#define REG_PACKET_SIZE 0x901 +#define REG_FIRM_VER_MSB 0x154 +#define REG_FIRM_VER_LSB 0x153 + +#define XTAL_FREQ_8X (double)52000000 +#define FREQ_DIV_8X (double)pow(2.0, 18.0) +#define FREQ_STEP_8X (double)(XTAL_FREQ_8X / FREQ_DIV_8X) + +#if defined(NRF52840_XXAA) + extern SPIClass spiModem; + #define SPI spiModem +#endif + +extern SPIClass SPI; + +#define MAX_PKT_LENGTH 255 + +sx128x::sx128x() : + _spiSettings(8E6, MSBFIRST, SPI_MODE0), + _ss(LORA_DEFAULT_SS_PIN), _reset(LORA_DEFAULT_RESET_PIN), _dio0(LORA_DEFAULT_DIO0_PIN), _rxen(pin_rxen), _busy(LORA_DEFAULT_BUSY_PIN), _txen(pin_txen), + _frequency(0), _txp(0), _sf(0x05), _bw(0x34), _cr(0x01), _packetIndex(0), _implicitHeaderMode(0), _payloadLength(255), _crcMode(0), _fifo_tx_addr_ptr(0), + _fifo_rx_addr_ptr(0), _rxPacketLength(0), _preinit_done(false), _tcxo(false) { setTimeout(0); } + +bool ISR_VECT sx128x::getPacketValidity() { + uint8_t buf[2]; + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_8X, buf, 2); + executeOpcode(OP_CLEAR_IRQ_STATUS_8X, buf, 2); + if ((buf[1] & IRQ_PAYLOAD_CRC_ERROR_MASK_8X) == 0) { return true; } + else { return false; } +} + +void ISR_VECT sx128x::onDio0Rise() { + BaseType_t int_status = taskENTER_CRITICAL_FROM_ISR(); + // On the SX1280, there is a bug which can cause the busy line + // to remain high if a high amount of packets are received when + // in continuous RX mode. This is documented as Errata 16.1 in + // the SX1280 datasheet v3.2 (page 149) + // Therefore, the modem is set into receive mode each time a packet is received. + if (sx128x_modem.getPacketValidity()) { sx128x_modem.receive(); sx128x_modem.handleDio0Rise(); } + else { sx128x_modem.receive(); } + + taskEXIT_CRITICAL_FROM_ISR(int_status); +} + +void sx128x::handleDio0Rise() { + _packetIndex = 0; + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_8X, rxbuf, 2); + + // If implicit header mode is enabled, use pre-set packet length as payload length instead. + // See SX1280 datasheet v3.2, page 92 + if (_implicitHeaderMode == 0x80) { _rxPacketLength = _payloadLength; } + else { _rxPacketLength = rxbuf[0]; } + + if (_receive_callback) { _receive_callback(_rxPacketLength); } +} + +bool sx128x::preInit() { + pinMode(_ss, OUTPUT); + digitalWrite(_ss, HIGH); + + // TODO: Check if this change causes issues on any platforms + #if MCU_VARIANT == MCU_ESP32 + #if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK + SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs); + #else + SPI.begin(); + #endif + #else + SPI.begin(); + #endif + + // Detect modem (retry for up to 500ms) + long start = millis(); + uint8_t version_msb; + uint8_t version_lsb; + while (((millis() - start) < 500) && (millis() >= start)) { + version_msb = readRegister(REG_FIRM_VER_MSB); + version_lsb = readRegister(REG_FIRM_VER_LSB); + if ((version_msb == 0xB7 && version_lsb == 0xA9) || (version_msb == 0xB5 && version_lsb == 0xA9)) { break; } + delay(100); + } + + if ((version_msb != 0xB7 || version_lsb != 0xA9) && (version_msb != 0xB5 || version_lsb != 0xA9)) { return false; } + _preinit_done = true; + return true; +} + +uint8_t ISR_VECT sx128x::readRegister(uint16_t address) { return singleTransfer(OP_READ_REGISTER_8X, address, 0x00); } +void sx128x::writeRegister(uint16_t address, uint8_t value) { singleTransfer(OP_WRITE_REGISTER_8X, address, value); } + +uint8_t ISR_VECT sx128x::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_8X) { SPI.transfer(0x00); } + response = SPI.transfer(value); + SPI.endTransaction(); + digitalWrite(_ss, HIGH); + + return response; +} + +void sx128x::rxAntEnable() { + if (_txen != -1) { digitalWrite(_txen, LOW); } + if (_rxen != -1) { digitalWrite(_rxen, HIGH); } +} + +void sx128x::txAntEnable() { + if (_txen != -1) { digitalWrite(_txen, HIGH); } + if (_rxen != -1) { digitalWrite(_rxen, LOW); } +} + +void sx128x::loraMode() { + uint8_t mode = MODE_LONG_RANGE_MODE_8X; + executeOpcode(OP_PACKET_TYPE_8X, &mode, 1); +} + +void sx128x::waitOnBusy() { + unsigned long time = millis(); + while (digitalRead(_busy) == HIGH) { + if (millis() >= (time + 100)) { break; } + } +} + +void sx128x::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 sx128x::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 sx128x::writeBuffer(const uint8_t* buffer, size_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + SPI.beginTransaction(_spiSettings); + SPI.transfer(OP_FIFO_WRITE_8X); + 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 sx128x::readBuffer(uint8_t* buffer, size_t size) { + waitOnBusy(); + digitalWrite(_ss, LOW); + SPI.beginTransaction(_spiSettings); + SPI.transfer(OP_FIFO_READ_8X); + 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 sx128x::setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr) { + // because there is no access to these registers on the sx1280, we have + // to set all these parameters at once or not at all. + uint8_t buf[3]; + buf[0] = sf << 4; + buf[1] = bw; + buf[2] = cr; + executeOpcode(OP_MODULATION_PARAMS_8X, buf, 3); + + if (sf <= 6) { writeRegister(0x925, 0x1E); } + else if (sf <= 8) { writeRegister(0x925, 0x37); } + else if (sf >= 9) { writeRegister(0x925, 0x32); } + writeRegister(0x093C, 0x1); +} + +uint8_t preamble_e = 0; +uint8_t preamble_m = 0; +uint32_t last_me_result_target = 0; +extern long lora_preamble_symbols; +void sx128x::setPacketParams(uint32_t target_preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc) { + if (last_me_result_target != target_preamble_symbols) { + // Calculate exponent and mantissa values for modem + if (target_preamble_symbols >= 0xF000) target_preamble_symbols = 0xF000; + uint32_t calculated_preamble_symbols; + uint8_t e = 1; + uint8_t m = 1; + while (e <= 15) { + while (m <= 15) { + calculated_preamble_symbols = m * (pow(2,e)); + if (calculated_preamble_symbols >= target_preamble_symbols-4) break; + m++; + } + + if (calculated_preamble_symbols >= target_preamble_symbols-4) break; + m = 1; e++; + } + + last_me_result_target = target_preamble_symbols; + lora_preamble_symbols = calculated_preamble_symbols+4; + _preambleLength = lora_preamble_symbols; + + preamble_e = e; + preamble_m = m; + } + + uint8_t buf[7]; + buf[0] = (preamble_e << 4) | preamble_m; + buf[1] = headermode; + buf[2] = payload_length; + buf[3] = crc; + buf[4] = 0x40; // Standard IQ setting (no inversion) + buf[5] = 0x00; // Unused params + buf[6] = 0x00; + + executeOpcode(OP_PACKET_PARAMS_8X, buf, 7); +} + +void sx128x::reset() { + if (_reset != -1) { + pinMode(_reset, OUTPUT); + digitalWrite(_reset, LOW); + delay(10); + digitalWrite(_reset, HIGH); + delay(10); + } +} + +int sx128x::begin(unsigned long frequency) { + reset(); + + if (_rxen != -1) { pinMode(_rxen, OUTPUT); } + if (_txen != -1) { pinMode(_txen, OUTPUT); } + if (_busy != -1) { pinMode(_busy, INPUT); } + + if (!_preinit_done) { + if (!preInit()) { + return false; + } + } + + standby(); + loraMode(); + rxAntEnable(); + setFrequency(frequency); + + // TODO: Implement LNA boost + //writeRegister(REG_LNA, 0x96); + + setModulationParams(_sf, _bw, _cr); + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + setTxPower(_txp); + + // Set base addresses + uint8_t basebuf[2] = {0}; + executeOpcode(OP_BUFFER_BASE_ADDR_8X, basebuf, 2); + + _radio_online = true; + return 1; +} + +void sx128x::end() { + sleep(); + SPI.end(); + _bitrate = 0; + _radio_online = false; + _preinit_done = false; +} + +int sx128x::beginPacket(int implicitHeader) { + standby(); + + if (implicitHeader) { implicitHeaderMode(); } + else { explicitHeaderMode(); } + + _payloadLength = 0; + _fifo_tx_addr_ptr = 0; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + + return 1; +} + +int sx128x::endPacket() { + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + txAntEnable(); + + // Put in single TX mode + uint8_t timeout[3] = {0}; + executeOpcode(OP_TX_8X, timeout, 3); + + uint8_t buf[2]; + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_8X, buf, 2); + + // Wait for TX done + bool timed_out = false; + uint32_t w_timeout = millis()+LORA_MODEM_TIMEOUT_MS; + while ((millis() < w_timeout) && ((buf[1] & IRQ_TX_DONE_MASK_8X) == 0)) { + buf[0] = 0x00; + buf[1] = 0x00; + executeOpcodeRead(OP_GET_IRQ_STATUS_8X, buf, 2); + yield(); + } + + if (!(millis() < w_timeout)) { timed_out = true; } + + // clear IRQ's + uint8_t mask[2]; + mask[0] = 0x00; + mask[1] = IRQ_TX_DONE_MASK_8X; + executeOpcode(OP_CLEAR_IRQ_STATUS_8X, mask, 2); + + if (timed_out) { return 0; } + else { return 1; } +} + +unsigned long preamble_detected_at = 0; +extern long lora_preamble_time_ms; +extern long lora_header_time_ms; +bool false_preamble_detected = false; +bool sx128x::dcd() { + uint8_t buf[2] = {0}; executeOpcodeRead(OP_GET_IRQ_STATUS_8X, buf, 2); + uint32_t now = millis(); + + bool header_detected = false; + bool carrier_detected = false; + + if ((buf[1] & IRQ_HEADER_DET_MASK_8X) != 0) { header_detected = true; carrier_detected = true; } + else { header_detected = false; } + + if ((buf[0] & IRQ_PREAMBLE_DET_MASK_8X) != 0) { + carrier_detected = true; + if (preamble_detected_at == 0) { preamble_detected_at = now; } + if (now - preamble_detected_at > lora_preamble_time_ms + lora_header_time_ms) { + preamble_detected_at = 0; + if (!header_detected) { false_preamble_detected = true; } + uint8_t clearbuf[2] = {0}; clearbuf[0] = IRQ_PREAMBLE_DET_MASK_8X; + executeOpcode(OP_CLEAR_IRQ_STATUS_8X, clearbuf, 2); + } + } + + // TODO: Maybe there's a way of unlatching the RSSI + // status without re-activating receive mode? + if (false_preamble_detected) { sx128x_modem.receive(); false_preamble_detected = false; } + return carrier_detected; +} + + +uint8_t sx128x::currentRssiRaw() { + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI_8X, &byte, 1); + return byte; +} + +int ISR_VECT sx128x::currentRssi() { + uint8_t byte = 0; + executeOpcodeRead(OP_CURRENT_RSSI_8X, &byte, 1); + int rssi = -byte / 2; + return rssi; +} + +uint8_t sx128x::packetRssiRaw() { + uint8_t buf[5] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_8X, buf, 5); + return buf[0]; +} + +int ISR_VECT sx128x::packetRssi(uint8_t pkt_snr_raw) { + // TODO: May need more calculations here + uint8_t buf[5] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_8X, buf, 5); + int pkt_rssi = -buf[0] / 2; + return pkt_rssi; +} + +uint8_t ISR_VECT sx128x::packetSnrRaw() { + uint8_t buf[5] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_8X, buf, 5); + return buf[1]; +} + +float ISR_VECT sx128x::packetSnr() { + uint8_t buf[5] = {0}; + executeOpcodeRead(OP_PACKET_STATUS_8X, buf, 5); + return float(buf[1]) * 0.25; +} + +long sx128x::packetFrequencyError() { + // TODO: Implement this, page 120 of sx1280 datasheet + int32_t freqError = 0; + const float fError = 0.0; + return static_cast(fError); +} + +void sx128x::flush() { } +int ISR_VECT sx128x::available() { return _rxPacketLength - _packetIndex; } +size_t sx128x::write(uint8_t byte) { return write(&byte, sizeof(byte)); } +size_t sx128x::write(const uint8_t *buffer, size_t size) { + if ((_payloadLength + size) > MAX_PKT_LENGTH) { size = MAX_PKT_LENGTH - _payloadLength; } + writeBuffer(buffer, size); + _payloadLength = _payloadLength + size; + return size; +} + +int ISR_VECT sx128x::read() { + if (!available()) { return -1; } + + // If received new packet + if (_packetIndex == 0) { + uint8_t rxbuf[2] = {0}; + executeOpcodeRead(OP_RX_BUFFER_STATUS_8X, rxbuf, 2); + int size; + + // If implicit header mode is enabled, read packet length as payload length instead. + // See SX1280 datasheet v3.2, page 92 + if (_implicitHeaderMode == 0x80) { + size = _payloadLength; + } else { + size = rxbuf[0]; + } + + _fifo_rx_addr_ptr = rxbuf[1]; + if (size > 255) { size = 255; } + + readBuffer(_packet, size); + } + + uint8_t byte = _packet[_packetIndex]; + _packetIndex++; + return byte; +} + +int sx128x::peek() { + if (!available()) { return -1; } + uint8_t b = _packet[_packetIndex]; + return b; +} + + +void sx128x::onReceive(void(*callback)(int)) { + _receive_callback = callback; + + if (callback) { + pinMode(_dio0, INPUT); + + // Set preamble and header detection irqs, plus dio0 mask + uint8_t buf[8]; + + // Set irq masks, enable all + buf[0] = 0xFF; + buf[1] = 0xFF; + + // On the SX1280, no RxDone IRQ is generated if a packet is received with + // an invalid header, but the modem will be taken out of single RX mode. + // This can cause the modem to not receive packets until it is reset + // again. This is documented as Errata 16.2 in the SX1280 datasheet v3.2 + // (page 150) Below, the header error IRQ is mapped to dio0 so that the + // modem can be set into RX mode again on reception of a corrupted + // header. + // set dio0 masks + buf[2] = 0x00; + buf[3] = IRQ_RX_DONE_MASK_8X | IRQ_HEADER_ERROR_MASK_8X; + + // Set dio1 masks + buf[4] = 0x00; + buf[5] = 0x00; + + // Set dio2 masks + buf[6] = 0x00; + buf[7] = 0x00; + + executeOpcode(OP_SET_IRQ_FLAGS_8X, buf, 8); + + #ifdef SPI_HAS_NOTUSINGINTERRUPT + SPI.usingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + + attachInterrupt(digitalPinToInterrupt(_dio0), onDio0Rise, RISING); + + } else { + detachInterrupt(digitalPinToInterrupt(_dio0)); + #ifdef SPI_HAS_NOTUSINGINTERRUPT + _spiModem->notUsingInterrupt(digitalPinToInterrupt(_dio0)); + #endif + } +} + +void sx128x::receive(int size) { + if (size > 0) { + implicitHeaderMode(); + // Tell radio payload length + //_rxPacketLength = size; + //_payloadLength = size; + //setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + } else { + explicitHeaderMode(); + } + + rxAntEnable(); + + // On the SX1280, there is a bug which can cause the busy line + // to remain high if a high amount of packets are received when + // in continuous RX mode. This is documented as Errata 16.1 in + // the SX1280 datasheet v3.2 (page 149) + // Therefore, the modem is set to single RX mode below instead. + + // uint8_t mode[3] = {0x03, 0xFF, 0xFF}; // Countinuous RX mode + uint8_t mode[3] = {0}; // single RX mode + executeOpcode(OP_RX_8X, mode, 3); +} + +void sx128x::standby() { + uint8_t byte = 0x01; // Always use STDBY_XOSC + executeOpcode(OP_STANDBY_8X, &byte, 1); +} + +void sx128x::setPins(int ss, int reset, int dio0, int busy, int rxen, int txen) { + _ss = ss; + _reset = reset; + _dio0 = dio0; + _busy = busy; + _rxen = rxen; + _txen = txen; +} + +void sx128x::setTxPower(int level, int outputPin) { + uint8_t tx_buf[2]; + + // RAK4631 with WisBlock SX1280 module (LIBSYS002) + #if BOARD_VARIANT == MODEL_13 || BOARD_VARIANT == MODEL_21 + if (level > 27) { level = 27; } + else if (level < 0) { level = 0; } + + _txp = level; + int reg_value; + switch (level) { + case 0: + reg_value = -18; + break; + case 1: + reg_value = -16; + break; + case 2: + reg_value = -15; + break; + case 3: + reg_value = -14; + break; + case 4: + reg_value = -13; + break; + case 5: + reg_value = -12; + break; + case 6: + reg_value = -11; + break; + case 7: + reg_value = -9; + break; + case 8: + reg_value = -8; + break; + case 9: + reg_value = -7; + break; + case 10: + reg_value = -6; + break; + case 11: + reg_value = -5; + break; + case 12: + reg_value = -4; + break; + case 13: + reg_value = -3; + break; + case 14: + reg_value = -2; + break; + case 15: + reg_value = -1; + break; + case 16: + reg_value = 0; + break; + case 17: + reg_value = 1; + break; + case 18: + reg_value = 2; + break; + case 19: + reg_value = 3; + break; + case 20: + reg_value = 4; + break; + case 21: + reg_value = 5; + break; + case 22: + reg_value = 6; + break; + case 23: + reg_value = 7; + break; + case 24: + reg_value = 8; + break; + case 25: + reg_value = 9; + break; + case 26: + reg_value = 12; + break; + case 27: + reg_value = 13; + break; + default: + reg_value = 0; + break; + } + + tx_buf[0] = reg_value + 18; + tx_buf[1] = 0xE0; // Ramping time, 20 microseconds + executeOpcode(OP_TX_PARAMS_8X, tx_buf, 2); + + // T3S3 SX1280 PA + #elif BOARD_VARIANT == MODEL_AC + if (level > 20) { level = 20; } + else if (level < 0) { level = 0; } + + _txp = level; + int reg_value; + switch (level) { + case 0: + reg_value = -18; + break; + case 1: + reg_value = -17; + break; + case 2: + reg_value = -16; + break; + case 3: + reg_value = -15; + break; + case 4: + reg_value = -14; + break; + case 5: + reg_value = -13; + break; + case 6: + reg_value = -12; + break; + case 7: + reg_value = -10; + break; + case 8: + reg_value = -9; + break; + case 9: + reg_value = -8; + break; + case 10: + reg_value = -7; + break; + case 11: + reg_value = -6; + break; + case 12: + reg_value = -5; + break; + case 13: + reg_value = -4; + break; + case 14: + reg_value = -3; + break; + case 15: + reg_value = -2; + break; + case 16: + reg_value = -1; + break; + case 17: + reg_value = 0; + break; + case 18: + reg_value = 1; + break; + case 19: + reg_value = 2; + break; + case 20: + reg_value = 3; + break; + default: + reg_value = 0; + break; + } + tx_buf[0] = reg_value; + tx_buf[1] = 0xE0; // Ramping time, 20 microseconds + + // For SX1280 boards with no specific PA requirements + #else + if (level > 13) { level = 13; } + else if (level < -18) { level = -18; } + _txp = level; + tx_buf[0] = level + 18; + tx_buf[1] = 0xE0; // Ramping time, 20 microseconds + #endif + + executeOpcode(OP_TX_PARAMS_8X, tx_buf, 2); +} + +void sx128x::setFrequency(uint32_t frequency) { + _frequency = frequency; + uint8_t buf[3]; + uint32_t freq = (uint32_t)((double)frequency / (double)FREQ_STEP_8X); + buf[0] = ((freq >> 16) & 0xFF); + buf[1] = ((freq >> 8) & 0xFF); + buf[2] = (freq & 0xFF); + + executeOpcode(OP_RF_FREQ_8X, buf, 3); +} + +uint32_t sx128x::getFrequency() { + // We can't read the frequency on the sx1280 + uint32_t frequency = _frequency; + return frequency; +} + +void sx128x::setSpreadingFactor(int sf) { + if (sf < 5) { sf = 5; } + else if (sf > 12) { sf = 12; } + _sf = sf; + + setModulationParams(sf, _bw, _cr); + handleLowDataRate(); +} + +uint32_t sx128x::getSignalBandwidth() { + int bw = _bw; + switch (bw) { + case 0x34: return 203.125E3; + case 0x26: return 406.25E3; + case 0x18: return 812.5E3; + case 0x0A: return 1625E3; + } + + return 0; +} + +void sx128x::setSignalBandwidth(uint32_t sbw) { + if (sbw <= 203.125E3) { _bw = 0x34; } + else if (sbw <= 406.25E3) { _bw = 0x26; } + else if (sbw <= 812.5E3) { _bw = 0x18; } + else { _bw = 0x0A; } + + setModulationParams(_sf, _bw, _cr); + handleLowDataRate(); + optimizeModemSensitivity(); +} + +// TODO: add support for new interleaving scheme, see page 117 of sx1280 datasheet +void sx128x::setCodingRate4(int denominator) { + if (denominator < 5) { denominator = 5; } + else if (denominator > 8) { denominator = 8; } + _cr = denominator - 4; + setModulationParams(_sf, _bw, _cr); +} + +extern bool lora_low_datarate; +void sx128x::handleLowDataRate() { + if (_sf > 10) { lora_low_datarate = true; } + else { lora_low_datarate = false; } +} + +void sx128x::optimizeModemSensitivity() { } // TODO: Check if there's anything the sx1280 can do here +uint8_t sx128x::getCodingRate4() { return _cr + 4; } +void sx128x::setPreambleLength(long preamble_symbols) { setPacketParams(preamble_symbols, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::setSyncWord(int sw) { } // TODO: Implement +void sx128x::enableTCXO() { } // TODO: Need to check how to implement on sx1280 +void sx128x::disableTCXO() { } // TODO: Need to check how to implement on sx1280 +void sx128x::sleep() { uint8_t byte = 0x00; executeOpcode(OP_SLEEP_8X, &byte, 1); } +uint8_t sx128x::getTxPower() { return _txp; } +uint8_t sx128x::getSpreadingFactor() { return _sf; } +void sx128x::enableCrc() { _crcMode = 0x20; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::disableCrc() { _crcMode = 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::setSPIFrequency(uint32_t frequency) { _spiSettings = SPISettings(frequency, MSBFIRST, SPI_MODE0); } +void sx128x::explicitHeaderMode() { _implicitHeaderMode = 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::implicitHeaderMode() { _implicitHeaderMode = 0x80; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); } +void sx128x::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); } } + +sx128x sx128x_modem; +#endif \ No newline at end of file diff --git a/RNode_Firmware/sx128x.h b/RNode_Firmware/sx128x.h new file mode 100644 index 0000000..ccaeb04 --- /dev/null +++ b/RNode_Firmware/sx128x.h @@ -0,0 +1,146 @@ +// Copyright Sandeep Mistry, Mark Qvist and Jacob Eva. +// Licensed under the MIT license. + +#ifndef SX128X_H +#define SX128X_H + +#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_TXEN_PIN -1 +#define LORA_DEFAULT_BUSY_PIN -1 +#define LORA_MODEM_TIMEOUT_MS 15E3 +#define PA_OUTPUT_RFO_PIN 0 +#define PA_OUTPUT_PA_BOOST_PIN 1 +#define RSSI_OFFSET 157 + +class sx128x : public Stream { +public: + sx128x(); + + int begin(unsigned long frequency); + void end(); + void reset(); + + int beginPacket(int implicitHeader = false); + int endPacket(); + + int parsePacket(int size = 0); + int packetRssi(); + int packetRssi(uint8_t pkt_snr_raw); + int currentRssi(); + uint8_t packetRssiRaw(); + uint8_t currentRssiRaw(); + uint8_t packetSnrRaw(); + float packetSnr(); + long packetFrequencyError(); + + // from Print + virtual size_t write(uint8_t byte); + virtual size_t write(const uint8_t *buffer, size_t size); + + // from Stream + virtual int available(); + virtual int read(); + virtual int peek(); + virtual void flush(); + + void onReceive(void(*callback)(int)); + + void receive(int size = 0); + void standby(); + void sleep(); + + bool preInit(); + uint8_t getTxPower(); + void setTxPower(int level, int outputPin = PA_OUTPUT_PA_BOOST_PIN); + uint32_t getFrequency(); + void setFrequency(uint32_t frequency); + void setSpreadingFactor(int sf); + uint8_t getSpreadingFactor(); + uint32_t getSignalBandwidth(); + void setSignalBandwidth(uint32_t sbw); + void setCodingRate4(int denominator); + uint8_t getCodingRate4(); + void setPreambleLength(long preamble_symbols); + void setSyncWord(int sw); + bool dcd(); + void clearIRQStatus(); + void enableCrc(); + void disableCrc(); + void enableTCXO(); + void disableTCXO(); + + void txAntEnable(); + void rxAntEnable(); + 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 setPacketParams(uint32_t target_preamble_symbols, uint8_t headermode, uint8_t payload_length, uint8_t crc); + void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr); + + void crc() { enableCrc(); } + void noCrc() { disableCrc(); } + + void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN, int busy = LORA_DEFAULT_BUSY_PIN, int rxen = LORA_DEFAULT_RXEN_PIN, int txen = LORA_DEFAULT_TXEN_PIN); + void setSPIFrequency(uint32_t frequency); + + void dumpRegisters(Stream& out); + +private: + void explicitHeaderMode(); + void implicitHeaderMode(); + + bool getPacketValidity(); + void handleDio0Rise(); + + 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); + + static void onDio0Rise(); + + void handleLowDataRate(); + void optimizeModemSensitivity(); + +private: + SPISettings _spiSettings; + int _ss; + int _reset; + int _dio0; + int _rxen; + int _txen; + int _busy; + int _modem; + unsigned long _frequency; + int _txp; + uint8_t _sf; + uint8_t _bw; + uint8_t _cr; + int _packetIndex; + uint32_t _preambleLength; + int _implicitHeaderMode; + int _payloadLength; + int _crcMode; + int _fifo_tx_addr_ptr; + int _fifo_rx_addr_ptr; + uint8_t _packet[256]; + bool _preinit_done; + bool _tcxo; + bool _radio_online; + int _rxPacketLength; + uint32_t _bitrate; + void (*_receive_callback)(int); +}; + +extern sx128x sx128x_modem; + +#endif diff --git a/Utilities.h b/Utilities.h index b31ff90..0f85a80 100644 --- a/Utilities.h +++ b/Utilities.h @@ -322,6 +322,14 @@ uint8_t boot_vector = 0x00; void led_tx_off() { digitalWrite(pin_led_tx, LED_OFF); } void led_id_on() { } void led_id_off() { } + #elif BOARD_MODEL == BOARD_VME213 + // VME213 has no physical LEDs, only E-Ink display + void led_rx_on() { } + void led_rx_off() { } + void led_tx_on() { } + void led_tx_off() { } + void led_id_on() { } + void led_id_off() { } #elif BOARD_MODEL == BOARD_HUZZAH32 void led_rx_on() { digitalWrite(pin_led_rx, HIGH); } void led_rx_off() { digitalWrite(pin_led_rx, LOW); } diff --git a/build-vme213.ps1 b/build-vme213.ps1 new file mode 100644 index 0000000..575a45a --- /dev/null +++ b/build-vme213.ps1 @@ -0,0 +1,74 @@ +# Build script for Heltec VisionMaster E213 +$ErrorActionPreference = "Stop" + +$ARDUINO_CLI = ".\tools\arduino-cli.exe" +$BOARD_MODEL = "0x40" +$ARDUINO_ESP_CORE_VER = "2.0.17" + +Write-Host "=== RNode Firmware Build for VME213 ===" -ForegroundColor Cyan + +# Check if arduino-cli exists +if (-not (Test-Path $ARDUINO_CLI)) { + Write-Host "ERROR: arduino-cli not found at $ARDUINO_CLI" -ForegroundColor Red + exit 1 +} + +# Update core index +Write-Host "`nUpdating Arduino core index..." -ForegroundColor Yellow +& $ARDUINO_CLI core update-index --config-file arduino-cli.yaml + +# Install ESP32 core if not installed +Write-Host "`nChecking ESP32 core..." -ForegroundColor Yellow +$coreInstalled = & $ARDUINO_CLI core list | Select-String "esp32:esp32" +if (-not $coreInstalled) { + Write-Host "Installing ESP32 core $ARDUINO_ESP_CORE_VER..." -ForegroundColor Yellow + & $ARDUINO_CLI core install "esp32:esp32@$ARDUINO_ESP_CORE_VER" --config-file arduino-cli.yaml +} + +# Install required libraries +Write-Host "`nChecking required libraries..." -ForegroundColor Yellow +$requiredLibs = @( + "Adafruit SSD1306", + "Adafruit SH110X", + "Adafruit GFX Library", + "Adafruit BusIO", + "Crypto" +) + +foreach ($lib in $requiredLibs) { + $libInstalled = & $ARDUINO_CLI lib list | Select-String $lib + if (-not $libInstalled) { + Write-Host "Installing $lib..." -ForegroundColor Yellow + & $ARDUINO_CLI lib install $lib + } +} + +# Create build directory +if (-not (Test-Path "build")) { + New-Item -ItemType Directory -Path "build" | Out-Null +} + +# Compile firmware +Write-Host "`nCompiling firmware for VME213..." -ForegroundColor Green +& $ARDUINO_CLI compile ` + --fqbn esp32:esp32:esp32s3:CDCOnBoot=cdc,PartitionScheme=no_ota ` + --build-property "build.extra_flags=-DBOARD_MODEL=$BOARD_MODEL" ` + --output-dir build ` + --verbose ` + RNode_Firmware.ino + +if ($LASTEXITCODE -eq 0) { + Write-Host "`n=== Build SUCCESS ===" -ForegroundColor Green + Write-Host "Firmware binary: build\RNode_Firmware.ino.bin" -ForegroundColor Cyan + + # Show file size + $binFile = "build\RNode_Firmware.ino.bin" + if (Test-Path $binFile) { + $size = (Get-Item $binFile).Length + $sizeKB = [math]::Round($size / 1KB, 2) + Write-Host "Binary size: $sizeKB KB" -ForegroundColor Cyan + } +} else { + Write-Host "`n=== Build FAILED ===" -ForegroundColor Red + exit 1 +} diff --git a/src/LCMEN2R13EFC1.cpp b/src/LCMEN2R13EFC1.cpp new file mode 100644 index 0000000..4b0b1c5 --- /dev/null +++ b/src/LCMEN2R13EFC1.cpp @@ -0,0 +1,242 @@ +// E-Ink display driver for LCMEN2R13EFC1 +// Adapted from Meshtastic NicheGraphics driver + +#include "LCMEN2R13EFC1.h" + +// Look-up tables for FAST refresh +static const uint8_t LUT_FAST_VCOMDC[] = { + 0x01, 0x06, 0x03, 0x02, 0x01, 0x01, 0x01, + 0x01, 0x06, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_FAST_WW[] = { + 0x01, 0x06, 0x03, 0x02, 0x81, 0x01, 0x01, + 0x01, 0x06, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_FAST_BW[] = { + 0x01, 0x86, 0x83, 0x82, 0x81, 0x01, 0x01, + 0x01, 0x86, 0x82, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_FAST_WB[] = { + 0x01, 0x46, 0x43, 0x02, 0x01, 0x01, 0x01, + 0x01, 0x46, 0x42, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_FAST_BB[] = { + 0x01, 0x06, 0x03, 0x42, 0x41, 0x01, 0x01, + 0x01, 0x06, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +LCMEN2R13EFC1::LCMEN2R13EFC1() + : _spiSettings(6000000, MSBFIRST, SPI_MODE0) +{ + // Calculate buffer size (8 pixels per byte) + _bufferRowSize = ((DISPLAY_WIDTH - 1) / 8) + 1; // 122px -> 16 bytes + _bufferSize = _bufferRowSize * DISPLAY_HEIGHT; // 16 * 250 = 4000 bytes +} + +void LCMEN2R13EFC1::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst) { + _spi = spi; + _pin_dc = pin_dc; + _pin_cs = pin_cs; + _pin_busy = pin_busy; + _pin_rst = pin_rst; + + pinMode(_pin_dc, OUTPUT); + pinMode(_pin_cs, OUTPUT); + pinMode(_pin_busy, INPUT); + pinMode(_pin_rst, INPUT_PULLUP); // Active low, hold high + + reset(); +} + +void LCMEN2R13EFC1::reset() { + pinMode(_pin_rst, OUTPUT); + digitalWrite(_pin_rst, LOW); + delay(10); + pinMode(_pin_rst, INPUT_PULLUP); + wait(); + + sendCommand(0x12); // Software reset + wait(); +} + +void LCMEN2R13EFC1::wait() { + // Busy when LOW + while (digitalRead(_pin_busy) == LOW) { + yield(); + } +} + +bool LCMEN2R13EFC1::isBusy() { + return (digitalRead(_pin_busy) == LOW); +} + +void LCMEN2R13EFC1::sendCommand(const uint8_t command) { + _spi->beginTransaction(_spiSettings); + digitalWrite(_pin_dc, LOW); // DC low = command + digitalWrite(_pin_cs, LOW); + _spi->transfer(command); + digitalWrite(_pin_cs, HIGH); + digitalWrite(_pin_dc, HIGH); + _spi->endTransaction(); +} + +void LCMEN2R13EFC1::sendData(uint8_t data) { + sendData(&data, 1); +} + +void LCMEN2R13EFC1::sendData(const uint8_t *data, uint32_t size) { + _spi->beginTransaction(_spiSettings); + digitalWrite(_pin_dc, HIGH); // DC high = data + digitalWrite(_pin_cs, LOW); + + #if defined(ARCH_ESP32) || defined(ESP32) + _spi->transferBytes(data, NULL, size); + #else + for (uint32_t i = 0; i < size; i++) { + _spi->transfer(data[i]); + } + #endif + + digitalWrite(_pin_cs, HIGH); + digitalWrite(_pin_dc, HIGH); + _spi->endTransaction(); +} + +void LCMEN2R13EFC1::configFull() { + sendCommand(0x00); // Panel setting register + sendData(0b11 << 6 // Display resolution + | 1 << 4 // B&W only + | 1 << 3 // Vertical scan direction + | 1 << 2 // Horizontal scan direction + | 1 << 1 // Shutdown: no + | 1 << 0 // Reset: no + ); + + sendCommand(0x50); // VCOM and data interval setting + sendData(0b10 << 6 // Border driven white + | 0b11 << 4 // Invert image colors: no + | 0b0111 // Interval between VCOM on and image data + ); +} + +void LCMEN2R13EFC1::configFast() { + sendCommand(0x00); // Panel setting register + sendData(0b11 << 6 // Display resolution + | 1 << 5 // LUT from registers (set below) + | 1 << 4 // B&W only + | 1 << 3 // Vertical scan direction + | 1 << 2 // Horizontal scan direction + | 1 << 1 // Shutdown: no + | 1 << 0 // Reset: no + ); + + sendCommand(0x50); // VCOM and data interval setting + sendData(0b11 << 6 // Border floating + | 0b01 << 4 // Invert image colors: no + | 0b0111 // Interval between VCOM on and image data + ); + + // Load LUT tables + sendCommand(0x20); // VCOM + sendData(LUT_FAST_VCOMDC, sizeof(LUT_FAST_VCOMDC)); + + sendCommand(0x21); // White -> White + sendData(LUT_FAST_WW, sizeof(LUT_FAST_WW)); + + sendCommand(0x22); // Black -> White + sendData(LUT_FAST_BW, sizeof(LUT_FAST_BW)); + + sendCommand(0x23); // White -> Black + sendData(LUT_FAST_WB, sizeof(LUT_FAST_WB)); + + sendCommand(0x24); // Black -> Black + sendData(LUT_FAST_BB, sizeof(LUT_FAST_BB)); +} + +void LCMEN2R13EFC1::writeNewImage() { + sendCommand(0x13); + sendData(_buffer, _bufferSize); +} + +void LCMEN2R13EFC1::writeOldImage() { + sendCommand(0x10); + sendData(_buffer, _bufferSize); +} + +void LCMEN2R13EFC1::update(uint8_t *imageData, UpdateType type) { + _updateType = type; + _buffer = imageData; + + reset(); + + // Configure display + if (_updateType == UPDATE_FULL) { + configFull(); + } else { + configFast(); + } + + // Transfer image data + if (_updateType == UPDATE_FULL) { + writeNewImage(); + writeOldImage(); + } else { + writeNewImage(); + } + + // Power on and start refresh + sendCommand(0x04); // Power on panel voltage + wait(); + + sendCommand(0x12); // Begin executing update + wait(); + + // Power off + sendCommand(0x02); + wait(); + + // Update "old memory" for next differential refresh + if (_updateType != UPDATE_FULL) { + writeOldImage(); + wait(); + } +} + +void LCMEN2R13EFC1::powerOn() { + sendCommand(0x04); + wait(); +} + +void LCMEN2R13EFC1::powerOff() { + sendCommand(0x02); + wait(); +} diff --git a/src/LCMEN2R13EFC1.h b/src/LCMEN2R13EFC1.h new file mode 100644 index 0000000..8bae3c9 --- /dev/null +++ b/src/LCMEN2R13EFC1.h @@ -0,0 +1,59 @@ +// E-Ink display driver for LCMEN2R13EFC1 (Heltec VisionMaster E213) +// Adapted from Meshtastic firmware for RNode use +// Controller IC: Fitipower JD79656 +// Resolution: 122x250 pixels (width x height) +// Supports FAST (partial) and FULL refresh + +#ifndef LCMEN2R13EFC1_H +#define LCMEN2R13EFC1_H + +#include +#include + +class LCMEN2R13EFC1 { +public: + // Display properties + static constexpr uint16_t DISPLAY_WIDTH = 122; + static constexpr uint16_t DISPLAY_HEIGHT = 250; + + // Update types + enum UpdateType { + UPDATE_FULL = 0, // Full refresh (slower, ~3.6s) + UPDATE_FAST = 1 // Partial refresh (faster, ~720ms) + }; + + LCMEN2R13EFC1(); + + void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst); + void update(uint8_t *imageData, UpdateType type); + bool isBusy(); + void powerOn(); + void powerOff(); + +private: + SPIClass *_spi; + uint8_t _pin_dc; + uint8_t _pin_cs; + uint8_t _pin_busy; + uint8_t _pin_rst; + + SPISettings _spiSettings; + + uint8_t *_buffer; + uint16_t _bufferRowSize; + uint32_t _bufferSize; + UpdateType _updateType; + + void reset(); + void wait(); + void sendCommand(const uint8_t command); + void sendData(uint8_t data); + void sendData(const uint8_t *data, uint32_t size); + + void configFull(); + void configFast(); + void writeNewImage(); + void writeOldImage(); +}; + +#endif // LCMEN2R13EFC1_H diff --git a/src/einkDetect_VME213.h b/src/einkDetect_VME213.h new file mode 100644 index 0000000..cdd5af4 --- /dev/null +++ b/src/einkDetect_VME213.h @@ -0,0 +1,41 @@ +// E-Ink display chip detection for VME213 +// Detects between LCMEN213EFC1 (V1) and E0213A367 (V1.1) +// Based on Meshtastic einkDetect.h + +#ifndef EINK_DETECT_VME213_H +#define EINK_DETECT_VME213_H + +#include + +enum EInkChipType { + EINK_LCMEN213EFC1 = 0, // Initial version (Fitipower JD79656) + EINK_E0213A367 = 1 // V1.1+ (Solomon Systech SSD1682) +}; + +// Detect E-Ink controller IC type by BUSY pin logic +// Fitipower: BUSY=LOW when busy +// Solomon Systech: BUSY=HIGH when busy +inline EInkChipType detectEInkChip(uint8_t pin_reset, uint8_t pin_busy) { + // Force display BUSY by holding reset pin active + pinMode(pin_reset, OUTPUT); + digitalWrite(pin_reset, LOW); + + delay(10); + + // Read BUSY pin logic while display is busy + pinMode(pin_busy, INPUT); + bool busyLogic = digitalRead(pin_busy); + + // Release reset pin + pinMode(pin_reset, INPUT); + + // Fitipower = LOW when busy + // Solomon = HIGH when busy + if (busyLogic == LOW) { + return EINK_LCMEN213EFC1; + } else { + return EINK_E0213A367; + } +} + +#endif // EINK_DETECT_VME213_H diff --git a/sx126x.cpp b/sx126x.cpp index c1e7c20..36879be 100644 --- a/sx126x.cpp +++ b/sx126x.cpp @@ -125,7 +125,7 @@ bool sx126x::preInit() { pinMode(_ss, OUTPUT); digitalWrite(_ss, HIGH); - #if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_XIAO_S3 + #if BOARD_MODEL == BOARD_T3S3 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 || BOARD_MODEL == BOARD_TDECK || BOARD_MODEL == BOARD_XIAO_S3 || BOARD_MODEL == BOARD_VME213 SPI.begin(pin_sclk, pin_miso, pin_mosi, pin_cs); #elif BOARD_MODEL == BOARD_TECHO SPI.setPins(pin_miso, pin_sclk, pin_mosi); @@ -614,7 +614,7 @@ void sx126x::sleep() { uint8_t byte = 0x00; executeOpcode(OP_SLEEP_6X, &byte, 1) void sx126x::enableTCXO() { #if HAS_TCXO - #if BOARD_MODEL == BOARD_RAK4631 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_XIAO_S3 + #if BOARD_MODEL == BOARD_RAK4631 || BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_XIAO_S3 || BOARD_MODEL == BOARD_VME213 uint8_t buf[4] = {MODE_TCXO_3_3V_6X, 0x00, 0x00, 0xFF}; #elif BOARD_MODEL == BOARD_TBEAM uint8_t buf[4] = {MODE_TCXO_1_8V_6X, 0x00, 0x00, 0xFF}; diff --git a/tools/LICENSE.txt b/tools/LICENSE.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/tools/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/tools/arduino-cli.exe b/tools/arduino-cli.exe new file mode 100644 index 0000000..5061829 Binary files /dev/null and b/tools/arduino-cli.exe differ