From a83684d0cb9e26f8fa1c0b909024170ea89367bd Mon Sep 17 00:00:00 2001 From: John Poole Date: Fri, 13 Feb 2026 15:51:57 -0800 Subject: [PATCH] Exercise 02 working: basic screen print out --- exercises/02_oled_display/README.md | 55 ++++++++ exercises/02_oled_display/platformio.ini | 32 +++++ exercises/02_oled_display/src/main.cpp | 162 +++++++++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 exercises/02_oled_display/README.md create mode 100644 exercises/02_oled_display/platformio.ini create mode 100644 exercises/02_oled_display/src/main.cpp diff --git a/exercises/02_oled_display/README.md b/exercises/02_oled_display/README.md new file mode 100644 index 0000000..e69d14b --- /dev/null +++ b/exercises/02_oled_display/README.md @@ -0,0 +1,55 @@ +## Exercise 02: OLED Display + +This exercise demonstrates multiple OLED rendering patterns on the LilyGO T-Beam Supreme (SH1106). + +The firmware runs 3 demos in sequence, then shows a restart banner and repeats forever. + +## Demo Sequence + +### Demo 1: Scrolling Text +- Shows `Hello #` lines. +- New lines are added below prior lines. +- Up to 5 lines are visible; old lines roll off the top. + +### Demo 2: Fill Then Erase +- Displays `Count 1` through `Count 10`. +- Shows a short `Clearing screen...` message. +- Erases the OLED to demonstrate explicit clear behavior. + +### Demo 3: Multi-Page Rotation +- Rotates through 3 distinct text pages. +- Each page has different content/layout to demonstrate refresh transitions. +- Includes a changing packet counter field on one page. + +### Restart Banner +- Shows `Restarting 3 demos` for 5 seconds. +- Sequence restarts at Demo 1. + +## Expected Serial Output +Serial output (115200) prints phase changes, for example: +- `Demo 1/3: scrolling Hello #` +- `Demo 2/3: clear screen after 10` +- `Demo 3/3: three-page rotation` +- `Restarting 3 demos` + +### Build +From this directory: + +```bash +source /home/jlpoole/rnsenv/bin/activate +pio run -e node_a +``` + +### Upload +Set your USB port: + +```bash +source /home/jlpoole/rnsenv/bin/activate +pio run -e node_a -t upload --upload-port /dev/ttyACM0 +``` + +### Serial Monitor + +```bash +screen /dev/ttyACM0 115200 +``` diff --git a/exercises/02_oled_display/platformio.ini b/exercises/02_oled_display/platformio.ini new file mode 100644 index 0000000..7016042 --- /dev/null +++ b/exercises/02_oled_display/platformio.ini @@ -0,0 +1,32 @@ +; 20260213 ChatGPT +; $Id$ +; $HeadURL$ + +[platformio] +default_envs = node_a + +[env] +platform = espressif32 +framework = arduino +board = esp32-s3-devkitc-1 +monitor_speed = 115200 +lib_deps = + olikraus/U8g2@^2.36.4 + +; Common build flags (pins from Meshtastic tbeam-s3-core variant.h) +build_flags = + -D OLED_SDA=17 + -D OLED_SCL=18 + -D OLED_ADDR=0x3C + -D ARDUINO_USB_MODE=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 + +[env:node_a] +build_flags = + ${env.build_flags} + -D NODE_LABEL=\"A\" + +[env:node_b] +build_flags = + ${env.build_flags} + -D NODE_LABEL=\"B\" diff --git a/exercises/02_oled_display/src/main.cpp b/exercises/02_oled_display/src/main.cpp new file mode 100644 index 0000000..f45cd5f --- /dev/null +++ b/exercises/02_oled_display/src/main.cpp @@ -0,0 +1,162 @@ +// 20260213 ChatGPT +// $Id$ +// $HeadURL$ + +#include +#include +#include + +#ifndef OLED_SDA +#define OLED_SDA 17 +#endif + +#ifndef OLED_SCL +#define OLED_SCL 18 +#endif + +#ifndef OLED_ADDR +#define OLED_ADDR 0x3C +#endif + +// LilyGO T-Beam Supreme uses SH1106 OLED on I2C. +U8G2_SH1106_128X64_NONAME_F_HW_I2C oled(U8G2_R0, /* reset=*/U8X8_PIN_NONE); + +static const uint8_t kMaxLines = 5; +static String g_lines[kMaxLines]; +static uint32_t g_iteration = 1; +static uint32_t g_pageCounter = 1; + +static void addLine(const String& line) { + for (uint8_t i = 0; i < kMaxLines - 1; ++i) { + g_lines[i] = g_lines[i + 1]; + } + g_lines[kMaxLines - 1] = line; +} + +static void drawLines() { + oled.clearBuffer(); + oled.setFont(u8g2_font_6x10_tf); + + const int yStart = 12; + const int yStep = 12; + for (uint8_t i = 0; i < kMaxLines; ++i) { + if (g_lines[i].length() == 0) { + continue; + } + oled.drawUTF8(0, yStart + (i * yStep), g_lines[i].c_str()); + } + oled.sendBuffer(); +} + +static void clearLines() { + for (uint8_t i = 0; i < kMaxLines; ++i) { + g_lines[i] = ""; + } +} + +static void showCentered(const char* line1, const char* line2 = nullptr, const char* line3 = nullptr) { + oled.clearBuffer(); + oled.setFont(u8g2_font_6x10_tf); + + if (line1) oled.drawUTF8(0, 16, line1); + if (line2) oled.drawUTF8(0, 32, line2); + if (line3) oled.drawUTF8(0, 48, line3); + + oled.sendBuffer(); +} + +static void demo1_scrollingHello() { + Serial.println("Demo 1/3: scrolling Hello #"); + clearLines(); + for (uint8_t i = 0; i < 10; ++i) { + String line = "Hello #" + String(g_iteration++); + addLine(line); + drawLines(); + delay(1000); + } +} + +static void demo2_clearAfterTen() { + Serial.println("Demo 2/3: clear screen after 10"); + clearLines(); + for (uint8_t i = 1; i <= 10; ++i) { + String line = "Count " + String(i); + addLine(line); + drawLines(); + delay(500); + } + + showCentered("Demo 2", "Clearing screen..."); + delay(1000); + oled.clearDisplay(); + oled.sendBuffer(); + delay(1000); +} + +static void drawPage(uint8_t page) { + oled.clearBuffer(); + oled.setFont(u8g2_font_6x10_tf); + + if (page == 0) { + oled.drawUTF8(0, 12, "Demo 3 - Page 1/3"); + oled.drawUTF8(0, 28, "GPS: 37.7749 N"); + oled.drawUTF8(0, 40, "LON: 122.4194 W"); + oled.drawUTF8(0, 56, "Fix: 3D"); + } else if (page == 1) { + oled.drawUTF8(0, 12, "Demo 3 - Page 2/3"); + oled.drawUTF8(0, 28, "LoRa RSSI: -92 dBm"); + oled.drawUTF8(0, 40, "LoRa SNR: +8.5 dB"); + oled.drawUTF8(0, 56, "Pkts: " ); + oled.setCursor(38, 56); + oled.print(g_pageCounter++); + } else { + oled.drawUTF8(0, 12, "Demo 3 - Page 3/3"); + oled.drawUTF8(0, 28, "Node: TBM-SUPREME"); + oled.drawUTF8(0, 40, "Mode: Field Test"); + oled.drawUTF8(0, 56, "OLED refresh demo"); + } + + oled.sendBuffer(); +} + +static void demo3_threePages() { + Serial.println("Demo 3/3: three-page rotation"); + for (uint8_t round = 0; round < 3; ++round) { + drawPage(0); + delay(700); + drawPage(1); + delay(700); + drawPage(2); + delay(700); + } +} + +static void showRestartBanner() { + Serial.println("Restarting 3 demos"); + showCentered("Restarting 3 demos", "Cycle begins again", "in 5 seconds..."); + delay(5000); +} + +void setup() { + Serial.begin(115200); + delay(250); + + Wire.begin(OLED_SDA, OLED_SCL); + oled.setI2CAddress(OLED_ADDR << 1); // U8g2 expects 8-bit address. + oled.begin(); + + oled.clearBuffer(); + oled.setFont(u8g2_font_6x10_tf); + oled.drawUTF8(0, 12, "Exercise 02 OLED"); + oled.sendBuffer(); + + Serial.println("Exercise 02: OLED display loop"); + Serial.printf("OLED SDA=%d SCL=%d ADDR=0x%02X\r\n", OLED_SDA, OLED_SCL, OLED_ADDR); +} + +void loop() { + demo1_scrollingHello(); + demo2_clearAfterTen(); + demo3_threePages(); + showRestartBanner(); +}