Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions Boards.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
144 changes: 143 additions & 1 deletion Display.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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();
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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\""

Expand Down Expand Up @@ -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
Expand Down
Loading