Public Access
120 lines
3.6 KiB
C++
120 lines
3.6 KiB
C++
#include "EL512Panel.h"
|
|||
|
|
|
||
|
|
#include <esp_heap_caps.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
EL512Panel::EL512Panel(const EL512Pins& pins) : _pins(pins) {}
|
||
|
|
|
||
|
|
EL512Panel::~EL512Panel() { end(); }
|
||
|
|
|
||
|
|
bool EL512Panel::begin() {
|
||
|
|
if (_running) return true;
|
||
|
|
|
||
|
|
pinMode(_pins.enable, OUTPUT);
|
||
|
|
pinMode(_pins.lowpwr_n, OUTPUT);
|
||
|
|
setEnabled(false);
|
||
|
|
setLowPower(false);
|
||
|
|
|
||
|
|
_fb = (uint8_t*)calloc(FRAMEBUFFER_BYTES, 1);
|
||
|
|
// Full-frame stream: 261 * 272 = ~71 KB of DMA-capable internal RAM.
|
||
|
|
// (A line ring buffer refilled from the DMA EOF interrupt would cut this
|
||
|
|
// to a few KB if RAM gets tight.)
|
||
|
|
_stream = (uint8_t*)heap_caps_malloc(STREAM_BYTES, MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
|
||
|
|
if (!_fb || !_stream) {
|
||
|
|
end();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
show();
|
||
|
|
|
||
|
|
if (!startI2S()) {
|
||
|
|
end();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
_running = true;
|
||
|
|
setEnabled(true);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void EL512Panel::end() {
|
||
|
|
setEnabled(false);
|
||
|
|
stopI2S();
|
||
|
|
_running = false;
|
||
|
|
free(_fb);
|
||
|
|
_fb = nullptr;
|
||
|
|
heap_caps_free(_stream);
|
||
|
|
_stream = nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
void EL512Panel::setEnabled(bool on) { digitalWrite(_pins.enable, on ? HIGH : LOW); }
|
||
|
|
|
||
|
|
void EL512Panel::setLowPower(bool on) { digitalWrite(_pins.lowpwr_n, on ? LOW : HIGH); }
|
||
|
|
|
||
|
|
void EL512Panel::setPixel(int16_t x, int16_t y, bool on) {
|
||
|
|
if (!_fb || x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT) return;
|
||
|
|
uint8_t& b = _fb[(y * WIDTH + x) >> 3];
|
||
|
|
const uint8_t mask = 0x80 >> (x & 7);
|
||
|
|
if (on)
|
||
|
|
b |= mask;
|
||
|
|
else
|
||
|
|
b &= ~mask;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool EL512Panel::getPixel(int16_t x, int16_t y) const {
|
||
|
|
if (!_fb || x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT) return false;
|
||
|
|
return _fb[(y * WIDTH + x) >> 3] & (0x80 >> (x & 7));
|
||
|
|
}
|
||
|
|
|
||
|
|
void EL512Panel::clear(bool on) {
|
||
|
|
if (_fb) memset(_fb, on ? 0xFF : 0x00, FRAMEBUFFER_BYTES);
|
||
|
|
}
|
||
|
|
|
||
|
|
void EL512Panel::encodeRow(uint16_t line, uint8_t* out) const {
|
||
|
|
// VS is held high during the last blanking line so that the first HS high
|
||
|
|
// time ending after its rising edge is row 0 (datasheet: video input timing).
|
||
|
|
// TODO: verify VS/HS setup and hold (T4/T5) on hardware.
|
||
|
|
const uint8_t vs = (line == LINES_PER_FRAME - 1) ? BIT_VS : 0;
|
||
|
|
|
||
|
|
if (line >= HEIGHT) {
|
||
|
|
memset(out, vs, CLOCKS_PER_ROW);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const uint8_t* src = _fb + (size_t)line * (WIDTH / 8);
|
||
|
|
for (uint16_t c = 0; c < DATA_CLOCKS_PER_ROW; c++) {
|
||
|
|
// Clock c carries columns 2c (VID, even) and 2c+1 (TVID, odd).
|
||
|
|
const uint8_t bits = (uint8_t)(src[c >> 2] << ((c & 3) * 2));
|
||
|
|
uint8_t v = BIT_HS | vs;
|
||
|
|
if (bits & 0x80) v |= BIT_VID;
|
||
|
|
if (bits & 0x40) v |= BIT_TVID;
|
||
|
|
out[c] = v;
|
||
|
|
}
|
||
|
|
memset(out + DATA_CLOCKS_PER_ROW, vs, BLANK_CLOCKS_PER_ROW);
|
||
|
|
|
||
|
|
// NOTE: in 8-bit I2S LCD mode the ESP32 emits bytes in swapped pairs
|
||
|
|
// (byte 1, byte 0, byte 3, byte 2, ...). Swap here once the DMA path is live.
|
||
|
|
}
|
||
|
|
|
||
|
|
void EL512Panel::show() {
|
||
|
|
if (!_fb || !_stream) return;
|
||
|
|
// TODO: double-buffer or sync to VS to avoid tearing.
|
||
|
|
for (uint16_t line = 0; line < LINES_PER_FRAME; line++) {
|
||
|
|
encodeRow(line, _stream + (size_t)line * CLOCKS_PER_ROW);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool EL512Panel::startI2S() {
|
||
|
|
// TODO: configure I2S0 for 8-bit parallel LCD mode:
|
||
|
|
// - periph_module_enable(PERIPH_I2S0_MODULE), reset TX / FIFO / DMA
|
||
|
|
// - I2S0.conf2.lcd_en = 1, 8-bit TX, clock dividers -> PIXEL_CLOCK_HZ
|
||
|
|
// - route stream bits 0..3 to vid/tvid/hs/vs with gpio_matrix_out()
|
||
|
|
// (I2S0O_DATA_OUT8_IDX.. for 8-bit mode - verify on the core version)
|
||
|
|
// - route the I2S clock (I2S0O_WS_OUT_IDX) to vclk
|
||
|
|
// - build a circular lldesc_t chain over _stream (<= 4092 bytes each)
|
||
|
|
// - start TX via out_link.start
|
||
|
|
log_w("EL512Panel: I2S DMA refresh not implemented yet (stub)");
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void EL512Panel::stopI2S() {
|
||
|
|
// TODO: stop I2S0 TX and release the DMA descriptors.
|
||
|
|
}
|