diff --git a/platformio.ini b/platformio.ini index 386aadb..8d13cde 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,6 +22,5 @@ build_flags = lib_deps = jgromes/RadioLib - adafruit/Adafruit GFX Library - adafruit/Adafruit SH110X + olikraus/U8g2 lewisxhe/XPowersLib \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f312a87..9aaacde 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,16 +2,21 @@ #include #include #include -#include -#include +#include #define XPOWERS_CHIP_AXP2101 #include -// LilyGO T-Beam Supreme V3.0 / ESP32-S3 / SX1262 / u-blox MAX-M10S -// Pin values follow LilyGO's T_BEAM_S3_SUPREME_SX1262 definitions. +// PMU pins +static const int PMU_SDA = 42; +static const int PMU_SCL = 41; -// LoRa pins - T-Beam Supreme SX1262 +// OLED pins +static const int OLED_SDA = 17; +static const int OLED_SCL = 18; +static const uint8_t OLED_ADDR = 0x3D; + +// LoRa pins static const int PIN_LORA_SCK = 12; static const int PIN_LORA_MISO = 13; static const int PIN_LORA_MOSI = 11; @@ -20,284 +25,522 @@ static const int PIN_LORA_RST = 5; static const int PIN_LORA_DIO1 = 1; static const int PIN_LORA_BUSY = 4; -// OLED pins - T-Beam Supreme SH1106, I2C bus 0 -static const int OLED_SDA = 17; -static const int OLED_SCL = 18; -static const int OLED_ADDR = 0x3C; -static const int SCREEN_WIDTH = 128; -static const int SCREEN_HEIGHT = 64; +// Radio profile +static const float LORA_FREQ_MHZ = 865.000; +static const float LORA_BW_KHZ = 125.0; +static const uint8_t LORA_SF = 12; +static const uint8_t LORA_CR = 5; +static const uint8_t LORA_SYNC_WORD = 0x12; +static const int8_t LORA_TX_POWER = 10; +static const uint16_t LORA_PREAMBLE = 10; +static const float LORA_TCXO_VOLTAGE = 1.6; +static const bool LORA_USE_LDO = false; +static const bool LORA_USE_CRC = false; +static const bool LORA_EXPLICIT_HDR = true; -// PMU pins - AXP2101, I2C bus 1 -static const int PMU_SDA = 42; -static const int PMU_SCL = 41; - -// GPS pins - u-blox MAX-M10S -static const int GPS_RX_PIN = 9; // ESP32 RX, connect to GPS TX -static const int GPS_TX_PIN = 8; // ESP32 TX, connect to GPS RX -static const int GPS_EN_PIN = 7; -static const uint32_t GPS_BAUD = 9600; - -// ACTIVE PROFILE (Shelly / custom LoRa) -static const float LORA_FREQ_MHZ = 865.000; -static const float LORA_BW_KHZ = 125.0; -static const uint8_t LORA_SF = 12; -static const uint8_t LORA_CR = 5; -static const uint8_t LORA_SYNC_WORD = 0x12; -static const int8_t LORA_TX_POWER = 10; -static const uint16_t LORA_PREAMBLE = 10; -static const float LORA_TCXO_VOLTAGE = 1.6; -static const bool LORA_USE_LDO = false; -static const bool LORA_USE_CRC = false; -static const bool LORA_EXPLICIT_HDR = true; - -// MESHCORE SWITZERLAND PROFILE (COMMENTED) +// Changeable variables reference /* -static const float LORA_FREQ_MHZ = 869.618; -static const float LORA_BW_KHZ = 62.5; -static const uint8_t LORA_SF = 8; -static const uint8_t LORA_CR = 8; -static const uint8_t LORA_SYNC_WORD = 0x34; -static const int8_t LORA_TX_POWER = 10; -static const uint16_t LORA_PREAMBLE = 10; -static const float LORA_TCXO_VOLTAGE = 1.6; -static const bool LORA_USE_LDO = false; -static const bool LORA_USE_CRC = true; -static const bool LORA_EXPLICIT_HDR = true; +LORA_FREQ_MHZ + Frequency in MHz + Example: 868.0 / 869.525 / 865.0 + +LORA_BW_KHZ + Bandwidth + Values: 7.8 / 10.4 / 15.6 / 20.8 / 31.25 / 41.7 / 62.5 / 125 / 250 / 500 + +LORA_SF + Spreading factor + Values: 5 to 12 + +LORA_CR + Coding rate + Values: + 5 = 4/5 + 6 = 4/6 + 7 = 4/7 + 8 = 4/8 + +LORA_SYNC_WORD + Network separation + Example: + 0x12 private + 0x34 LoRaWAN/public + +LORA_TX_POWER + TX power in dBm + Typical: 2 to 22 + +LORA_PREAMBLE + Preamble length + Typical: 8 to 16 + +LORA_USE_CRC + Packet CRC check + +LORA_EXPLICIT_HDR + Explicit or implicit header mode */ -// Objects -TwoWire PMUWire = TwoWire(1); -XPowersPMU PMU; -Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); -HardwareSerial GPSSerial(1); -SX1262 radio = new Module(PIN_LORA_CS, PIN_LORA_DIO1, PIN_LORA_RST, PIN_LORA_BUSY); +TwoWire pmuWire = TwoWire(1); + +XPowersPMU pmu; + +SPIClass loraSPI(FSPI); + +SX1262 radio = new Module( + PIN_LORA_CS, + PIN_LORA_DIO1, + PIN_LORA_RST, + PIN_LORA_BUSY, + loraSPI +); + +U8G2_SH1106_128X64_NONAME_F_HW_I2C oled( + U8G2_R0, + U8X8_PIN_NONE +); // Runtime state String lastMsg = "none"; + float lastRssi = 0.0; + float lastSnr = 0.0; + unsigned long rxCount = 0; + unsigned long lastIdleRefresh = 0; + unsigned long showPacketUntil = 0; -bool pmuOk = false; // Helpers String formatCR(uint8_t cr) { - return "4/" + String(cr); + + return "4/" + String(cr); } String formatHexByte(uint8_t value) { - char buf[5]; - snprintf(buf, sizeof(buf), "%02X", value); - return String(buf); + + char buf[5]; + + snprintf( + buf, + sizeof(buf), + "%02X", + value + ); + + return String(buf); } -void drawLine(int x, int y, const String& text) { - display.setCursor(x, y); - display.print(text); +void drawLine( + int x, + int y, + const String& text +) { + + oled.drawStr( + x, + y, + text.c_str() + ); } -void drawWrappedText(const String& text, int startY, int maxLines) { - const int charsPerLine = 21; - int len = text.length(); - int index = 0; - int line = 0; +void drawWrappedText( + const String& text, + int startY, + int maxLines +) { - while (index < len && line < maxLines) { - String chunk = text.substring(index, min(index + charsPerLine, len)); - drawLine(0, startY + line * 8, chunk); - index += charsPerLine; - line++; - } + const int charsPerLine = 21; + + int len = text.length(); + + int index = 0; + + int line = 0; + + while ( + index < len && + line < maxLines + ) { + + String chunk = text.substring( + index, + min( + index + charsPerLine, + len + ) + ); + + drawLine( + 0, + startY + line * 8, + chunk + ); + + index += charsPerLine; + + line++; + } } void drawListeningScreen() { - display.clearDisplay(); - display.setTextSize(1); - display.setTextColor(SH110X_WHITE); - drawLine(0, 0, "T-Beam Supreme RX"); - drawLine(92, 0, String(rxCount)); + oled.clearBuffer(); - drawLine(0, 10, "F:" + String(LORA_FREQ_MHZ, 3)); - drawLine(0, 20, "BW:" + String(LORA_BW_KHZ, 1) + " SF:" + String(LORA_SF)); - drawLine(0, 30, "CR:" + formatCR(LORA_CR) + " SW:0x" + formatHexByte(LORA_SYNC_WORD)); + oled.setFont(u8g2_font_6x10_tf); - drawLine(0, 40, "Last:"); - drawWrappedText(lastMsg, 48, 2); + drawLine( + 0, + 10, + "Listening" + ); - display.display(); + drawLine( + 78, + 10, + String(rxCount) + " pk" + ); + + drawLine( + 0, + 22, + "F:" + String(LORA_FREQ_MHZ, 3) + ); + + drawLine( + 0, + 32, + "BW:" + String(LORA_BW_KHZ, 1) + ); + + drawLine( + 72, + 32, + "SF:" + String(LORA_SF) + ); + + drawLine( + 0, + 42, + "CR:" + formatCR(LORA_CR) + ); + + drawLine( + 60, + 42, + "SW:" + formatHexByte(LORA_SYNC_WORD) + ); + + drawLine( + 0, + 54, + "Last:" + ); + + drawWrappedText( + lastMsg, + 64, + 1 + ); + + oled.sendBuffer(); } void drawPacketScreen() { - display.clearDisplay(); - display.setTextSize(1); - display.setTextColor(SH110X_WHITE); - drawLine(0, 0, "PACKET RECEIVED"); - drawLine(0, 10, "RSSI:" + String(lastRssi, 1) + " SNR:" + String(lastSnr, 1)); - drawLine(0, 20, "Len:" + String(lastMsg.length())); + oled.clearBuffer(); - drawLine(0, 32, "Text:"); - drawWrappedText(lastMsg, 40, 3); + oled.setFont(u8g2_font_6x10_tf); - display.display(); + drawLine( + 0, + 10, + "PACKET RECEIVED" + ); + + drawLine( + 0, + 22, + "RSSI:" + String(lastRssi, 1) + ); + + drawLine( + 72, + 22, + "SNR:" + String(lastSnr, 1) + ); + + drawLine( + 0, + 34, + "Len:" + String(lastMsg.length()) + ); + + drawLine( + 0, + 46, + "Text:" + ); + + drawWrappedText( + lastMsg, + 56, + 1 + ); + + oled.sendBuffer(); } -void drawErrorScreen(const String& title, int code) { - display.clearDisplay(); - display.setTextSize(1); - display.setTextColor(SH110X_WHITE); +void drawErrorScreen( + const String& title, + int code +) { - drawLine(0, 0, title); - drawLine(0, 12, "code: " + String(code)); + oled.clearBuffer(); - display.display(); -} + oled.setFont(u8g2_font_6x10_tf); -void initPower() { - PMUWire.begin(PMU_SDA, PMU_SCL); - pmuOk = PMU.begin(PMUWire, AXP2101_SLAVE_ADDRESS, PMU_SDA, PMU_SCL); + drawLine( + 0, + 10, + title + ); - if (!pmuOk) { - Serial.println("PMU init failed - continuing, but OLED/LoRa/GPS power may be off"); - return; - } + drawLine( + 0, + 24, + "Code: " + String(code) + ); - // T-Beam Supreme PMU channels: - // ALDO1 = BME280 + display + magnetometer - // ALDO3 = radio - // ALDO4 = GPS - PMU.setALDO1Voltage(3300); - PMU.enableALDO1(); - - PMU.setALDO3Voltage(3300); - PMU.enableALDO3(); - - PMU.setALDO4Voltage(3300); - PMU.enableALDO4(); - - PMU.disableTSPinMeasure(); - PMU.enableBattVoltageMeasure(); - PMU.enableVbusVoltageMeasure(); - - Serial.print("PMU AXP2101 OK, chip ID: 0x"); - Serial.println(PMU.getChipID(), HEX); -} - -void initGpsPowerOnly() { - pinMode(GPS_EN_PIN, OUTPUT); - digitalWrite(GPS_EN_PIN, HIGH); - GPSSerial.begin(GPS_BAUD, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); - Serial.println("GPS UART started, GPS data not parsed in this listener sketch"); + oled.sendBuffer(); } void setup() { - Serial.begin(115200); - delay(1500); - initPower(); - delay(200); + Serial.begin(115200); - Wire.begin(OLED_SDA, OLED_SCL); + delay(1500); - if (!display.begin(OLED_ADDR, true)) { - Serial.println("SH1106 OLED init failed"); - while (true) delay(1000); - } + // PMU init + pmuWire.begin( + PMU_SDA, + PMU_SCL + ); - display.clearDisplay(); - display.setTextSize(1); - display.setTextColor(SH110X_WHITE); - drawLine(0, 0, "Booting..."); - display.display(); + if ( + !pmu.begin( + pmuWire, + AXP2101_SLAVE_ADDRESS, + PMU_SDA, + PMU_SCL + ) + ) { - initGpsPowerOnly(); + Serial.println("PMU init failed"); - SPI.begin(PIN_LORA_SCK, PIN_LORA_MISO, PIN_LORA_MOSI, PIN_LORA_CS); + while (true) { + delay(1000); + } + } - Serial.println(); - Serial.println("Starting LoRa receiver on T-Beam Supreme..."); + pmu.setALDO1Voltage(3300); + pmu.enableALDO1(); - int state = radio.begin( - LORA_FREQ_MHZ, - LORA_BW_KHZ, - LORA_SF, - LORA_CR, - LORA_SYNC_WORD, - LORA_TX_POWER, - LORA_PREAMBLE, - LORA_TCXO_VOLTAGE, - LORA_USE_LDO - ); + pmu.setBLDO1Voltage(3300); + pmu.enableBLDO1(); - if (state != RADIOLIB_ERR_NONE) { - Serial.print("radio.begin failed, code "); - Serial.println(state); - drawErrorScreen("LoRa init failed", state); - while (true) delay(1000); - } + delay(500); - if (LORA_EXPLICIT_HDR) { - state = radio.explicitHeader(); - } else { - state = radio.implicitHeader(LORA_PREAMBLE); - } + // OLED init + Wire.begin( + OLED_SDA, + OLED_SCL + ); - if (state != RADIOLIB_ERR_NONE) { - Serial.print("header mode failed, code "); - Serial.println(state); - drawErrorScreen("Header config failed", state); - while (true) delay(1000); - } + Wire.setClock(100000); - state = radio.setCRC(LORA_USE_CRC); - if (state != RADIOLIB_ERR_NONE) { - Serial.print("CRC config failed, code "); - Serial.println(state); - drawErrorScreen("CRC config failed", state); - while (true) delay(1000); - } + oled.setBusClock(100000); - Serial.println("Listening..."); - drawListeningScreen(); + oled.setI2CAddress( + OLED_ADDR * 2 + ); + + oled.begin(); + + oled.setPowerSave(0); + + oled.setContrast(255); + + oled.clearBuffer(); + + oled.setFont(u8g2_font_6x10_tf); + + drawLine( + 0, + 10, + "Booting..." + ); + + oled.sendBuffer(); + + // SPI init + loraSPI.begin( + PIN_LORA_SCK, + PIN_LORA_MISO, + PIN_LORA_MOSI, + PIN_LORA_CS + ); + + Serial.println(); + + Serial.println( + "Starting LoRa receiver..." + ); + + int state = radio.begin( + LORA_FREQ_MHZ, + LORA_BW_KHZ, + LORA_SF, + LORA_CR, + LORA_SYNC_WORD, + LORA_TX_POWER, + LORA_PREAMBLE, + LORA_TCXO_VOLTAGE, + LORA_USE_LDO + ); + + if (state != RADIOLIB_ERR_NONE) { + + Serial.print( + "radio.begin failed, code " + ); + + Serial.println(state); + + drawErrorScreen( + "LoRa init failed", + state + ); + + while (true) { + delay(1000); + } + } + + if (LORA_EXPLICIT_HDR) { + + state = radio.explicitHeader(); + + } else { + + state = radio.implicitHeader( + LORA_PREAMBLE + ); + } + + if (state != RADIOLIB_ERR_NONE) { + + Serial.print( + "header mode failed, code " + ); + + Serial.println(state); + + drawErrorScreen( + "Header config failed", + state + ); + + while (true) { + delay(1000); + } + } + + state = radio.setCRC( + LORA_USE_CRC + ); + + if (state != RADIOLIB_ERR_NONE) { + + Serial.print( + "CRC config failed, code " + ); + + Serial.println(state); + + drawErrorScreen( + "CRC config failed", + state + ); + + while (true) { + delay(1000); + } + } + + Serial.println("Listening..."); + + drawListeningScreen(); } void loop() { - String str; - int state = radio.receive(str); - if (state == RADIOLIB_ERR_NONE) { - lastMsg = str; - lastRssi = radio.getRSSI(); - lastSnr = radio.getSNR(); - rxCount++; + String str; - Serial.print("RX: "); - Serial.println(lastMsg); - Serial.print("RSSI: "); - Serial.println(lastRssi); - Serial.print("SNR: "); - Serial.println(lastSnr); - Serial.println(); + int state = radio.receive(str); - drawPacketScreen(); - showPacketUntil = millis() + 2500; - } - else if (state != RADIOLIB_ERR_RX_TIMEOUT) { - Serial.print("Receive failed, code "); - Serial.println(state); - } + if (state == RADIOLIB_ERR_NONE) { - // Drain GPS UART to avoid buffer filling. This sketch does not parse GPS. - while (GPSSerial.available()) { - Serial.write(GPSSerial.read()); - } + lastMsg = str; - if (millis() > showPacketUntil) { - if (millis() - lastIdleRefresh > 500) { - drawListeningScreen(); - lastIdleRefresh = millis(); + lastRssi = radio.getRSSI(); + + lastSnr = radio.getSNR(); + + rxCount++; + + Serial.print("RX: "); + + Serial.println(lastMsg); + + Serial.print("RSSI: "); + + Serial.println(lastRssi); + + Serial.print("SNR: "); + + Serial.println(lastSnr); + + Serial.println(); + + drawPacketScreen(); + + showPacketUntil = millis() + 2500; } - } -} + else if ( + state != RADIOLIB_ERR_RX_TIMEOUT + ) { + + Serial.print( + "Receive failed, code " + ); + + Serial.println(state); + } + + if ( + millis() > showPacketUntil + ) { + + if ( + millis() - lastIdleRefresh > 500 + ) { + + drawListeningScreen(); + + lastIdleRefresh = millis(); + } + } +} \ No newline at end of file