Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/helpers/esp32/SerialBLEInterface.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "SerialBLEInterface.h"
#include "esp_mac.h"
#include <esp_mac.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
Expand Down
32 changes: 32 additions & 0 deletions src/helpers/ui/DisplayDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,38 @@
#include <stdint.h>
#include <string.h>

struct MarqueeScroller {
int offset = 0;
unsigned long next_time = 0;
bool paused = true;

void reset() {
offset = 0;
next_time = 0;
paused = true;
}

// Call once per loop iteration to advance the scroll.
// textW/displayW in pixels, now = millis().
void update(int textW, int displayW, unsigned long now,
unsigned long speed_ms = 150, unsigned long pause_ms = 2000) {
int maxScroll = textW - displayW;
if (maxScroll <= 0) { offset = 0; return; }
if (now < next_time) return;
if (paused) {
paused = false;
next_time = now + pause_ms;
} else {
offset++;
if (offset >= maxScroll) {
offset = 0;
paused = true;
}
next_time = now + speed_ms;
}
}
};

class DisplayDriver {
int _w, _h;
protected:
Expand Down
120 changes: 120 additions & 0 deletions src/helpers/ui/SSD1306SPIDisplay.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include "SSD1306SPIDisplay.h"

// Check if SPI is ready (set by radio_init in target.cpp)
#if defined(P_LORA_SCLK)
extern bool spi_initialized;
#else
static bool spi_initialized = true; // Assume ready if no custom SPI
#endif

bool SSD1306SPIDisplay::begin() {
// Defer actual initialization - SPI may not be ready yet
// Real init happens in lazyInit() on first use (after radio_init)
return true;
}

bool SSD1306SPIDisplay::lazyInit() {
if (_initialized) return true;
if (!spi_initialized) {
Serial.println("SSD1306: SPI not initialized yet");
return false;
}

Serial.println("SSD1306: Attempting display init...");
#ifdef DISPLAY_ROTATION
display.setRotation(DISPLAY_ROTATION);
#endif
// SPI is now initialized by radio_init()
// Pass periphBegin=false to skip spi.begin() since radio already did it
if (!display.begin(SSD1306_SWITCHCAPVCC, 0, true, false)) {
Serial.println("SSD1306: display.begin() FAILED");
return false;
}
Serial.println("SSD1306: display.begin() OK");

// Fix for 64x48 displays: Adafruit library lacks this case and defaults
// to comPins=0x02 (sequential). Displays taller than 32px need 0x12
// (alternative COM pin config) or the output is garbled.
#if defined(DISPLAY_WIDTH) && defined(DISPLAY_HEIGHT)
#if (DISPLAY_WIDTH == 64) && (DISPLAY_HEIGHT == 48)
display.ssd1306_command(SSD1306_SETCOMPINS);
display.ssd1306_command(0x12);
#endif
#endif

// Clear any garbage in the display buffer
display.clearDisplay();
display.display();
_initialized = true;
return true;
}

void SSD1306SPIDisplay::turnOn() {
if (!lazyInit()) return;
display.ssd1306_command(SSD1306_DISPLAYON);
_isOn = true;
}

void SSD1306SPIDisplay::turnOff() {
if (!lazyInit()) return;
display.ssd1306_command(SSD1306_DISPLAYOFF);
_isOn = false;
}

void SSD1306SPIDisplay::clear() {
if (!lazyInit()) return;
display.clearDisplay();
display.display();
}

void SSD1306SPIDisplay::startFrame(Color bkg) {
if (!lazyInit()) return;
display.clearDisplay(); // TODO: apply 'bkg'
_color = SSD1306_WHITE;
display.setTextColor(_color);
display.setFont(NULL); // Default 6x8 font
display.setTextSize(1);
display.setTextWrap(false);
display.cp437(true);
}

void SSD1306SPIDisplay::setTextSize(int sz) {
display.setTextSize(sz);
}

void SSD1306SPIDisplay::setColor(Color c) {
_color = (c != 0) ? SSD1306_WHITE : SSD1306_BLACK;
display.setTextColor(_color);
}

void SSD1306SPIDisplay::setCursor(int x, int y) {
display.setCursor(x, y);
}

void SSD1306SPIDisplay::print(const char* str) {
display.print(str);
}

void SSD1306SPIDisplay::fillRect(int x, int y, int w, int h) {
display.fillRect(x, y, w, h, _color);
}

void SSD1306SPIDisplay::drawRect(int x, int y, int w, int h) {
display.drawRect(x, y, w, h, _color);
}

void SSD1306SPIDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
display.drawBitmap(x, y, bits, w, h, SSD1306_WHITE);
}

uint16_t SSD1306SPIDisplay::getTextWidth(const char* str) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w;
}

void SSD1306SPIDisplay::endFrame() {
if (!_initialized) return;
display.display();
}
38 changes: 38 additions & 0 deletions src/helpers/ui/SSD1306SPIDisplay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

#include "DisplayDriver.h"
#include <SPI.h>
#include <Adafruit_GFX.h>
#define SSD1306_NO_SPLASH
#include <Adafruit_SSD1306.h>

class SSD1306SPIDisplay : public DisplayDriver {
Adafruit_SSD1306 display;
bool _isOn;
bool _initialized;
uint8_t _color;

bool lazyInit(); // Deferred init for SPI bus sharing

public:
// Accept pre-initialized SPI - do NOT call spi.begin()
SSD1306SPIDisplay(SPIClass* spi, int16_t w, int16_t h, int8_t dc, int8_t rst, int8_t cs)
: DisplayDriver(w, h), display(w, h, spi, dc, rst, cs) { _isOn = false; _initialized = false; }

bool begin();

bool isOn() override { return _isOn; }
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(Color bkg = DARK) override;
void setTextSize(int sz) override;
void setColor(Color c) override;
void setCursor(int x, int y) override;
void print(const char* str) override;
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
uint16_t getTextWidth(const char* str) override;
void endFrame() override;
};
11 changes: 8 additions & 3 deletions variants/m5stack_unit_c6l/UnitC6LBoard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
UnitC6LBoard board;

#if defined(P_LORA_SCLK)
static SPIClass spi(0);
SPIClass spi(0);
bool spi_initialized = false;
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
#else
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
Expand All @@ -16,12 +17,17 @@ ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;

#ifdef DISPLAY_CLASS
DISPLAY_CLASS display(&spi, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_DC, DISPLAY_RST, DISPLAY_CS);
#endif

bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);

#if defined(P_LORA_SCLK)
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
spi_initialized = true;
return radio.std_init(&spi);
#else
return radio.std_init();
Expand All @@ -30,6 +36,5 @@ bool radio_init() {

mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity
return mesh::LocalIdentity(&rng);
}

117 changes: 116 additions & 1 deletion variants/m5stack_unit_c6l/UnitC6LBoard.h
Original file line number Diff line number Diff line change
@@ -1,15 +1,130 @@
#pragma once

#include <Arduino.h>
#include <Wire.h>
#include <helpers/ESP32Board.h>

// PI4IO I/O Expander (I2C address 0x43)
// Pin mapping:
// P0 = Button (active low)
// P1 = (unused input)
// P5 = LNA_EN (LNA Enable)
// P6 = ANT_SW (RF Switch)
// P7 = NRST (LoRa Reset)
#define PI4IO_ADDR 0x43

// PI4IO registers
#define PI4IO_REG_CHIP_RESET 0x01
#define PI4IO_REG_IO_DIR 0x03
#define PI4IO_REG_OUT_SET 0x05
#define PI4IO_REG_OUT_H_IM 0x07
#define PI4IO_REG_IN_DEF_STA 0x09
#define PI4IO_REG_PULL_EN 0x0B
#define PI4IO_REG_PULL_SEL 0x0D
#define PI4IO_REG_IN_STA 0x0F
#define PI4IO_REG_INT_MASK 0x11
#define PI4IO_REG_IRQ_STA 0x13

class UnitC6LBoard : public ESP32Board {
private:
bool i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value) {
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.write(value);
return Wire.endTransmission() == 0;
}

bool i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value) {
Wire.beginTransmission(addr);
Wire.write(reg);
if (Wire.endTransmission() != 0) return false;
if (Wire.requestFrom(addr, (uint8_t)1) != 1) return false;
if (!Wire.available()) return false;
*value = Wire.read();
return true;
}

void initIOExpander() {
uint8_t in_data;

// Reset the I/O expander
i2c_write_byte(PI4IO_ADDR, PI4IO_REG_CHIP_RESET, 0xFF);
delay(10);

i2c_read_byte(PI4IO_ADDR, PI4IO_REG_CHIP_RESET, &in_data);
delay(10);

// Set P5, P6, P7 as outputs (0: input, 1: output)
i2c_write_byte(PI4IO_ADDR, PI4IO_REG_IO_DIR, 0b11100000);
delay(10);

// Disable High-Impedance for used pins
i2c_write_byte(PI4IO_ADDR, PI4IO_REG_OUT_H_IM, 0b00011100);
delay(10);

// Pull up/down select (0: down, 1: up)
i2c_write_byte(PI4IO_ADDR, PI4IO_REG_PULL_SEL, 0b11100011);
delay(10);

// Pull up/down enable (0: disable, 1: enable)
i2c_write_byte(PI4IO_ADDR, PI4IO_REG_PULL_EN, 0b11100011);
delay(10);

// Default input state for P0, P1 (buttons)
i2c_write_byte(PI4IO_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011);
delay(10);

// Enable interrupts for P0, P1 (0: enable, 1: disable)
i2c_write_byte(PI4IO_ADDR, PI4IO_REG_INT_MASK, 0b11111100);
delay(10);

// Set P7 (Reset) high, P5 and P6 will be set after
i2c_write_byte(PI4IO_ADDR, PI4IO_REG_OUT_SET, 0b10000000);
delay(10);

// Clear IRQ status
i2c_read_byte(PI4IO_ADDR, PI4IO_REG_IRQ_STA, &in_data);

// Enable RF switch (P6 high) and LNA (P5 high)
i2c_read_byte(PI4IO_ADDR, PI4IO_REG_OUT_SET, &in_data);
in_data |= (1 << 6); // P6 = RF Switch = HIGH
in_data |= (1 << 5); // P5 = LNA Enable = HIGH
i2c_write_byte(PI4IO_ADDR, PI4IO_REG_OUT_SET, in_data);
}

public:
void begin() {
ESP32Board::begin();

// Initialize I/O expander for LoRa RF control
initIOExpander();

#ifdef PIN_BUZZER
pinMode(PIN_BUZZER, OUTPUT);
digitalWrite(PIN_BUZZER, LOW);
#endif
}

const char* getManufacturerName() const override {
return "Unit C6L";
return "M5Stack Unit C6L";
}

// Read button state from I/O expander P0 (active low)
bool isButtonPressed() {
uint8_t in_data = 0xFF;
if (!i2c_read_byte(PI4IO_ADDR, PI4IO_REG_IN_STA, &in_data)) {
return false;
}
return !(in_data & 0x01);
}

#ifdef PIN_BUZZER
void playTone(uint16_t frequency, uint16_t duration_ms) {
tone(PIN_BUZZER, frequency, duration_ms);
}

void stopTone() {
noTone(PIN_BUZZER);
}
#endif
};
Loading