mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-29 07:37:48 +01:00
display fix
This commit is contained in:
@@ -21,15 +21,15 @@ static constexpr size_t kLineBytes = DISP_WIDTH / 8;
|
||||
static constexpr size_t kLineMultiSingle = (kLineBytes + 2);
|
||||
static constexpr size_t kLineDataBytes = kLineMultiSingle * DISP_HEIGHT + 2;
|
||||
|
||||
extern uint8_t dma_buf[SMD::kLineDataBytes];
|
||||
extern uint8_t* dma_buf;
|
||||
|
||||
void init();
|
||||
// Simplified asynchronous frame pipeline:
|
||||
// Double-buffered asynchronous frame pipeline:
|
||||
// Usage pattern each frame:
|
||||
// SMD::async_draw_wait(); // (start of frame) waits for previous transfer+clear & guarantees pixel area is zeroed
|
||||
// SMD::async_draw_wait(); // (start of frame) waits for previous transfer & ensures draw buffer is ready/synced
|
||||
// ... write pixels into dma_buf via set_pixel / surface ...
|
||||
// SMD::async_draw_start(); // (end of frame) queues SPI DMA of current framebuffer; when DMA completes it triggers
|
||||
// // a background clear of pixel bytes for next frame
|
||||
// SMD::async_draw_start(); // (end of frame) queues SPI DMA of current framebuffer; once SPI finishes the sent buffer
|
||||
// // is asynchronously cleared so the alternate buffer is ready for the next frame
|
||||
void async_draw_start();
|
||||
void async_draw_wait();
|
||||
bool async_draw_busy(); // optional diagnostic: is a frame transfer still in flight?
|
||||
@@ -50,41 +50,15 @@ static void set_pixel(int x, int y, bool value) {
|
||||
extern "C" void s_spi_post_cb(spi_transaction_t* trans);
|
||||
|
||||
static inline spi_device_interface_config_t _devcfg = {
|
||||
.mode = 0, // SPI mode 0
|
||||
.mode = 0, // SPI mode 0
|
||||
.clock_speed_hz = 10 * 1000 * 1000, // Clock out at 10 MHz
|
||||
.spics_io_num = SPI_DISP_CS, // CS pin
|
||||
.spics_io_num = SPI_DISP_CS, // CS pin
|
||||
.flags = SPI_DEVICE_POSITIVE_CS,
|
||||
.queue_size = 1,
|
||||
.pre_cb = nullptr,
|
||||
.post_cb = s_spi_post_cb,
|
||||
};
|
||||
extern spi_device_handle_t _spi;
|
||||
void ensure_clear_task(); // idempotent; called from init
|
||||
}; // namespace SMD
|
||||
|
||||
class SMDSurface : public Surface<SMDSurface, BwPixel>, public StandardEventQueue<SMDSurface> {
|
||||
public:
|
||||
using PixelType = BwPixel;
|
||||
|
||||
SMDSurface(EventLoop* loop);
|
||||
|
||||
~SMDSurface() override;
|
||||
|
||||
void draw_pixel_impl(unsigned x, unsigned y, const BwPixel& pixel);
|
||||
|
||||
void clear_impl();
|
||||
|
||||
int get_width_impl() const;
|
||||
|
||||
int get_height_impl() const;
|
||||
|
||||
template<typename T>
|
||||
EventHandlingResult handle(const T& event) {
|
||||
return _window->handle(event);
|
||||
}
|
||||
|
||||
EventHandlingResult handle(SurfaceResizeEvent event);
|
||||
};
|
||||
|
||||
|
||||
#endif // DISPLAY_HPP
|
||||
|
||||
@@ -22,7 +22,7 @@ private:
|
||||
.scl_io_num = I2C_SCL,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.flags = {.enable_internal_pullup = false, .allow_pd = true},
|
||||
.flags = {.enable_internal_pullup = true, .allow_pd = true},
|
||||
};
|
||||
i2c_master_bus_handle_t _bus_handle;
|
||||
}; // namespace i2c_global
|
||||
|
||||
@@ -11,14 +11,12 @@
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] bool inputsDiffer(const InputState& a, const InputState& b) {
|
||||
return a.up != b.up || a.down != b.down || a.left != b.left || a.right != b.right || a.a != b.a ||
|
||||
a.b != b.b || a.select != b.select || a.start != b.start;
|
||||
return a.up != b.up || a.down != b.down || a.left != b.left || a.right != b.right || a.a != b.a || a.b != b.b ||
|
||||
a.select != b.select || a.start != b.start;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
AppSystem::AppSystem(AppContext ctx) : context(std::move(ctx)) {
|
||||
context.system = this;
|
||||
}
|
||||
AppSystem::AppSystem(AppContext ctx) : context(std::move(ctx)) { context.system = this; }
|
||||
|
||||
void AppSystem::registerApp(std::unique_ptr<IAppFactory> factory) {
|
||||
if (!factory)
|
||||
@@ -40,8 +38,8 @@ bool AppSystem::startAppByIndex(std::size_t index) {
|
||||
return false;
|
||||
|
||||
context.system = this;
|
||||
auto& factory = factories[index];
|
||||
auto app = factory->create(context);
|
||||
auto& factory = factories[index];
|
||||
auto app = factory->create(context);
|
||||
if (!app)
|
||||
return false;
|
||||
|
||||
@@ -80,8 +78,8 @@ void AppSystem::run() {
|
||||
const InputState inputNow = context.input.readState();
|
||||
if (inputsDiffer(inputNow, lastInputState)) {
|
||||
AppEvent evt{};
|
||||
evt.type = AppEventType::Button;
|
||||
evt.timestamp_ms = now;
|
||||
evt.type = AppEventType::Button;
|
||||
evt.timestamp_ms = now;
|
||||
evt.button.current = inputNow;
|
||||
evt.button.previous = lastInputState;
|
||||
events.push_back(evt);
|
||||
@@ -143,7 +141,7 @@ AppTimerHandle AppSystem::scheduleTimer(uint32_t delay_ms, bool repeat) {
|
||||
if (!current)
|
||||
return kInvalidAppTimer;
|
||||
TimerRecord record;
|
||||
record.id = nextTimerId++;
|
||||
record.id = nextTimerId++;
|
||||
if (record.id == kInvalidAppTimer)
|
||||
record.id = nextTimerId++;
|
||||
record.generation = currentGeneration;
|
||||
@@ -160,8 +158,7 @@ void AppSystem::cancelTimer(AppTimerHandle handle) {
|
||||
auto* timer = findTimer(handle);
|
||||
if (timer)
|
||||
timer->active = false;
|
||||
timers.erase(std::remove_if(timers.begin(), timers.end(),
|
||||
[](const TimerRecord& rec) { return !rec.active; }),
|
||||
timers.erase(std::remove_if(timers.begin(), timers.end(), [](const TimerRecord& rec) { return !rec.active; }),
|
||||
timers.end());
|
||||
}
|
||||
|
||||
@@ -170,8 +167,7 @@ void AppSystem::cancelAllTimers() {
|
||||
if (timer.generation == currentGeneration)
|
||||
timer.active = false;
|
||||
}
|
||||
timers.erase(std::remove_if(timers.begin(), timers.end(),
|
||||
[](const TimerRecord& rec) { return !rec.active; }),
|
||||
timers.erase(std::remove_if(timers.begin(), timers.end(), [](const TimerRecord& rec) { return !rec.active; }),
|
||||
timers.end());
|
||||
}
|
||||
|
||||
@@ -186,20 +182,19 @@ void AppSystem::processDueTimers(std::uint32_t now, std::vector<AppEvent>& outEv
|
||||
continue;
|
||||
if (static_cast<std::int32_t>(now - timer.due_ms) >= 0) {
|
||||
AppEvent ev{};
|
||||
ev.type = AppEventType::Timer;
|
||||
ev.timestamp_ms = now;
|
||||
ev.timer.handle = timer.id;
|
||||
ev.type = AppEventType::Timer;
|
||||
ev.timestamp_ms = now;
|
||||
ev.timer.handle = timer.id;
|
||||
outEvents.push_back(ev);
|
||||
if (timer.repeat) {
|
||||
const std::uint32_t interval = timer.interval_ms ? timer.interval_ms : 1;
|
||||
timer.due_ms = now + interval;
|
||||
timer.due_ms = now + interval;
|
||||
} else {
|
||||
timer.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
timers.erase(std::remove_if(timers.begin(), timers.end(),
|
||||
[](const TimerRecord& rec) { return !rec.active; }),
|
||||
timers.erase(std::remove_if(timers.begin(), timers.end(), [](const TimerRecord& rec) { return !rec.active; }),
|
||||
timers.end());
|
||||
}
|
||||
|
||||
@@ -235,9 +230,9 @@ AppSystem::TimerRecord* AppSystem::findTimer(AppTimerHandle handle) {
|
||||
bool AppSystem::handlePendingSwitchRequest() {
|
||||
if (!context.pendingSwitch)
|
||||
return false;
|
||||
const bool byName = context.pendingSwitchByName;
|
||||
const std::size_t reqIndex = context.pendingAppIndex;
|
||||
const std::string reqName = context.pendingAppName;
|
||||
const bool byName = context.pendingSwitchByName;
|
||||
const std::size_t reqIndex = context.pendingAppIndex;
|
||||
const std::string reqName = context.pendingAppName;
|
||||
context.pendingSwitch = false;
|
||||
context.pendingSwitchByName = false;
|
||||
context.pendingAppName.clear();
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
|
||||
#define GAMEBOY_PERF_METRICS 0
|
||||
#define GAMEBOY_PERF_METRICS 0
|
||||
|
||||
#ifndef GAMEBOY_PERF_METRICS
|
||||
#define GAMEBOY_PERF_METRICS 1
|
||||
@@ -196,7 +196,7 @@ public:
|
||||
|
||||
void handleEvent(const AppEvent& event) override {
|
||||
if (event.type == AppEventType::Timer && event.timer.handle == tickTimer) {
|
||||
tickTimer = kInvalidAppTimer;
|
||||
tickTimer = kInvalidAppTimer;
|
||||
const uint64_t frameStartUs = esp_timer_get_time();
|
||||
performStep();
|
||||
const uint64_t frameEndUs = esp_timer_get_time();
|
||||
@@ -215,7 +215,7 @@ public:
|
||||
GB_PERF_ONLY(perf.resetForStep();)
|
||||
|
||||
GB_PERF_ONLY(const uint64_t inputStartUs = esp_timer_get_time();)
|
||||
const InputState input = context.input.readState();
|
||||
const InputState input = context.input.readState();
|
||||
GB_PERF_ONLY(perf.inputUs = esp_timer_get_time() - inputStartUs;)
|
||||
|
||||
const Mode stepMode = mode;
|
||||
@@ -261,8 +261,8 @@ public:
|
||||
|
||||
GB_PERF_ONLY(perf.finishStep();)
|
||||
GB_PERF_ONLY(perf.accumulate();)
|
||||
GB_PERF_ONLY(perf.printStep(stepMode, gbReady, frameDirty, fpsCurrent, activeRomName, roms.size(), selectedIndex,
|
||||
browserDirty);)
|
||||
GB_PERF_ONLY(perf.printStep(stepMode, gbReady, frameDirty, fpsCurrent, activeRomName, roms.size(),
|
||||
selectedIndex, browserDirty);)
|
||||
GB_PERF_ONLY(perf.maybePrintAggregate();)
|
||||
}
|
||||
|
||||
@@ -476,13 +476,13 @@ private:
|
||||
ScopedCallbackTimer(GameboyApp* instance, PerfTracker::CallbackKind kind) {
|
||||
#if GAMEBOY_PERF_METRICS
|
||||
if (instance) {
|
||||
app = instance;
|
||||
cbKind = kind;
|
||||
startUs = esp_timer_get_time();
|
||||
app = instance;
|
||||
cbKind = kind;
|
||||
startUs = esp_timer_get_time();
|
||||
}
|
||||
#else
|
||||
(void)instance;
|
||||
(void)kind;
|
||||
(void) instance;
|
||||
(void) kind;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -497,7 +497,7 @@ private:
|
||||
|
||||
private:
|
||||
#if GAMEBOY_PERF_METRICS
|
||||
GameboyApp* app = nullptr;
|
||||
GameboyApp* app = nullptr;
|
||||
PerfTracker::CallbackKind cbKind{};
|
||||
uint64_t startUs = 0;
|
||||
#endif
|
||||
@@ -518,12 +518,12 @@ private:
|
||||
std::array<int, LCD_WIDTH> colXEnd{};
|
||||
};
|
||||
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
PerfTracker perf{};
|
||||
AppTimerHandle tickTimer = kInvalidAppTimer;
|
||||
int64_t frameDelayCarryUs = 0;
|
||||
static constexpr uint32_t kTargetFrameUs = 1000000 / 60; // ~16.6 ms
|
||||
AppContext& context;
|
||||
Framebuffer& framebuffer;
|
||||
PerfTracker perf{};
|
||||
AppTimerHandle tickTimer = kInvalidAppTimer;
|
||||
int64_t frameDelayCarryUs = 0;
|
||||
static constexpr uint32_t kTargetFrameUs = 1000000 / 60; // ~16.6 ms
|
||||
|
||||
Mode mode = Mode::Browse;
|
||||
ScaleMode scaleMode = ScaleMode::Original;
|
||||
@@ -936,13 +936,14 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
gb.direct.priv = this;
|
||||
gb.direct.joypad = 0xFF;
|
||||
gb.direct.interlace = false;
|
||||
gb.direct.frame_skip = false;
|
||||
gb.direct.priv = this;
|
||||
gb.direct.joypad = 0xFF;
|
||||
|
||||
gb_init_lcd(&gb, &GameboyApp::lcdDrawLine);
|
||||
|
||||
gb.direct.interlace = false;
|
||||
gb.direct.frame_skip = true;
|
||||
|
||||
const uint_fast32_t saveSize = gb_get_save_size(&gb);
|
||||
cartRam.assign(static_cast<std::size_t>(saveSize), 0);
|
||||
std::string savePath;
|
||||
@@ -1262,7 +1263,7 @@ private:
|
||||
|
||||
GB_PERF_ONLY(const uint64_t waitStartUs = esp_timer_get_time();)
|
||||
DispTools::draw_to_display_async_wait();
|
||||
GB_PERF_ONLY(perf.waitUs = esp_timer_get_time() - waitStartUs;)
|
||||
GB_PERF_ONLY(self->perf.waitUs = esp_timer_get_time() - waitStartUs;)
|
||||
|
||||
if (geom.scaledWidth == LCD_WIDTH && geom.scaledHeight == LCD_HEIGHT) {
|
||||
const int dstY = yStart;
|
||||
|
||||
@@ -19,7 +19,7 @@ static i2c_master_dev_handle_t dev_handle;
|
||||
static inline i2c_device_config_t dev_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = 0x20,
|
||||
.scl_speed_hz = 100000,
|
||||
.scl_speed_hz = 50000,
|
||||
};
|
||||
|
||||
Buttons& Buttons::get() {
|
||||
@@ -62,7 +62,7 @@ Buttons::Buttons() {
|
||||
buf2[0] = 7;
|
||||
buf2[1] = 0x80;
|
||||
ESP_ERROR_CHECK(i2c_master_transmit(dev_handle, buf2, sizeof(buf2), -1));
|
||||
xTaskCreate(&start_pooler, "ButtonsPooler", 2048, this, 1, &_pooler_task);
|
||||
xTaskCreate(&start_pooler, "ButtonsPooler", 2048, this, 2, &_pooler_task);
|
||||
|
||||
ESP_ERROR_CHECK(gpio_reset_pin(EXP_INT));
|
||||
ESP_ERROR_CHECK(gpio_set_direction(EXP_INT, GPIO_MODE_INPUT));
|
||||
@@ -81,17 +81,42 @@ static void delay(unsigned long long loop) {
|
||||
void Buttons::pooler() {
|
||||
while (true) {
|
||||
BaseType_t xResult = xTaskNotifyWait(pdFALSE, ULONG_MAX, nullptr, portMAX_DELAY);
|
||||
uint8_t reg = 0;
|
||||
uint8_t buffer;
|
||||
ESP_ERROR_CHECK(
|
||||
i2c_master_transmit_receive(dev_handle, ®, sizeof(reg), reinterpret_cast<uint8_t*>(&buffer), 1, -1));
|
||||
_current = buffer;
|
||||
// read second port too to clear the interrupt
|
||||
reg = 1;
|
||||
ESP_ERROR_CHECK(
|
||||
i2c_master_transmit_receive(dev_handle, ®, sizeof(reg), reinterpret_cast<uint8_t*>(&buffer), 1, -1));
|
||||
if (_listener)
|
||||
xTaskNotifyGive(_listener);
|
||||
while (true) {
|
||||
auto reset = [&]() {
|
||||
i2c_master_bus_rm_device(dev_handle);
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(I2cGlobal::get().get_bus_handle(), &dev_cfg, &dev_handle));
|
||||
uint8_t buf2[2];
|
||||
buf2[0] = 6;
|
||||
buf2[1] = 0xFF;
|
||||
ESP_ERROR_CHECK(i2c_master_transmit(dev_handle, buf2, sizeof(buf2), -1));
|
||||
buf2[0] = 7;
|
||||
buf2[1] = 0x80;
|
||||
ESP_ERROR_CHECK(i2c_master_transmit(dev_handle, buf2, sizeof(buf2), -1));
|
||||
};
|
||||
uint8_t reg = 0;
|
||||
uint8_t buffer;
|
||||
auto err = i2c_master_transmit_receive(dev_handle, ®, sizeof(reg), reinterpret_cast<uint8_t*>(&buffer),
|
||||
1, 100);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error reading buttons: %d\n", err);
|
||||
reset();
|
||||
continue;
|
||||
}
|
||||
_current = buffer;
|
||||
printf("Read buttons: 0x%02X\n", _current);
|
||||
// read second port too to clear the interrupt
|
||||
reg = 1;
|
||||
err = i2c_master_transmit_receive(dev_handle, ®, sizeof(reg), reinterpret_cast<uint8_t*>(&buffer), 1,
|
||||
100);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error reading buttons: %d\n", err);
|
||||
reset();
|
||||
continue;
|
||||
}
|
||||
if (_listener)
|
||||
xTaskNotifyGive(_listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint8_t Buttons::get_pressed() { return _current; }
|
||||
|
||||
@@ -1,46 +1,49 @@
|
||||
// Simplified display implementation (no async memcpy) ---------------------------------
|
||||
// Double-buffered display implementation with async memcpy ---------------------------------
|
||||
|
||||
#include "display.hpp"
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <driver/gpio.h>
|
||||
#include "disp_tools.hpp"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_async_memcpy.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
DMA_ATTR uint8_t SMD::dma_buf[SMD::kLineDataBytes]{};
|
||||
DMA_ATTR uint8_t dma_buf_template[SMD::kLineDataBytes]{};
|
||||
DMA_ATTR static uint8_t s_dma_buffer0[SMD::kLineDataBytes]{};
|
||||
DMA_ATTR static uint8_t s_dma_buffer1[SMD::kLineDataBytes]{};
|
||||
static uint8_t* s_dma_buffers[2] = {s_dma_buffer0, s_dma_buffer1};
|
||||
DMA_ATTR static uint8_t dma_buf_template[SMD::kLineDataBytes]{};
|
||||
|
||||
uint8_t* SMD::dma_buf = s_dma_buffers[0];
|
||||
|
||||
spi_device_handle_t SMD::_spi;
|
||||
|
||||
static spi_transaction_t _tx{};
|
||||
static SemaphoreHandle_t _txSem = nullptr;
|
||||
static bool _vcom = false;
|
||||
volatile bool _inFlight = false;
|
||||
static TaskHandle_t s_clearTaskHandle = nullptr;
|
||||
static SemaphoreHandle_t s_clearReqSem = nullptr;
|
||||
static SemaphoreHandle_t s_clearSem = nullptr;
|
||||
static SemaphoreHandle_t s_bufferSem[2] = {nullptr, nullptr};
|
||||
|
||||
static async_memcpy_config_t config = ASYNC_MEMCPY_DEFAULT_CONFIG();
|
||||
// update the maximum data stream supported by underlying DMA engine
|
||||
static async_memcpy_handle_t driver = NULL;
|
||||
static async_memcpy_handle_t driver = nullptr;
|
||||
|
||||
static volatile int s_drawBufIdx = 0;
|
||||
|
||||
static unsigned char reverse_bits3(unsigned char b) { return (b * 0x0202020202ULL & 0x010884422010ULL) % 0x3ff; }
|
||||
|
||||
|
||||
static bool IRAM_ATTR my_async_memcpy_cb(async_memcpy_handle_t mcp_hdl, async_memcpy_event_t* event, void* cb_args) {
|
||||
static bool IRAM_ATTR my_async_memcpy_cb(async_memcpy_handle_t /*mcp_hdl*/, async_memcpy_event_t* /*event*/,
|
||||
void* cb_args) {
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
_inFlight = false;
|
||||
xSemaphoreGiveFromISR(s_clearSem,
|
||||
&high_task_wakeup); // high_task_wakeup set to pdTRUE if some high priority task unblocked
|
||||
auto sem = static_cast<SemaphoreHandle_t>(cb_args);
|
||||
xSemaphoreGiveFromISR(sem, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
static void zero_framebuffer_payload() {
|
||||
ESP_ERROR_CHECK(esp_async_memcpy(driver, SMD::dma_buf, dma_buf_template, 12480, my_async_memcpy_cb, nullptr));
|
||||
}
|
||||
|
||||
extern "C" void IRAM_ATTR s_spi_post_cb(spi_transaction_t* /*t*/) {
|
||||
BaseType_t hpw = pdFALSE;
|
||||
xSemaphoreGiveFromISR(s_clearReqSem, &hpw);
|
||||
@@ -53,87 +56,90 @@ static void clear_task(void*) {
|
||||
if (xSemaphoreTake(s_clearReqSem, portMAX_DELAY) == pdTRUE) {
|
||||
spi_transaction_t* r = nullptr;
|
||||
ESP_ERROR_CHECK(spi_device_get_trans_result(SMD::_spi, &r, 0));
|
||||
zero_framebuffer_payload();
|
||||
// printf("Zeroing done\n");
|
||||
int bufIdx = (int) r->user;
|
||||
xSemaphoreGive(_txSem);
|
||||
ESP_ERROR_CHECK(esp_async_memcpy(driver, s_dma_buffers[bufIdx], dma_buf_template, 12480U,
|
||||
my_async_memcpy_cb, static_cast<void*>(s_bufferSem[bufIdx])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SMD::ensure_clear_task() {
|
||||
if (!s_clearReqSem)
|
||||
s_clearReqSem = xSemaphoreCreateBinary();
|
||||
if (!s_clearSem)
|
||||
s_clearSem = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(s_clearSem);
|
||||
|
||||
if (!s_clearTaskHandle)
|
||||
xTaskCreatePinnedToCore(clear_task, "fbclr", 1536, nullptr, tskIDLE_PRIORITY + 1, &s_clearTaskHandle, 0);
|
||||
}
|
||||
|
||||
|
||||
void SMD::init() {
|
||||
spi_bus_add_device(SPI_BUS, &_devcfg, &_spi);
|
||||
ensure_clear_task();
|
||||
ESP_ERROR_CHECK(gpio_reset_pin(SPI_DISP_DISP));
|
||||
ESP_ERROR_CHECK(gpio_set_direction(SPI_DISP_DISP, GPIO_MODE_OUTPUT));
|
||||
ESP_ERROR_CHECK(gpio_set_level(SPI_DISP_DISP, 1));
|
||||
ESP_ERROR_CHECK(gpio_hold_en(SPI_DISP_DISP));
|
||||
for (uint8_t i = 0; i < DISP_HEIGHT; i++) {
|
||||
dma_buf[kLineMultiSingle * i + 1] = reverse_bits3(i + 1);
|
||||
dma_buf[2 + kLineMultiSingle * i + kLineBytes] = 0;
|
||||
for (int buf = 0; buf < 2; ++buf) {
|
||||
auto* fb = s_dma_buffers[buf];
|
||||
for (uint8_t i = 0; i < DISP_HEIGHT; ++i) {
|
||||
fb[kLineMultiSingle * i + 1] = reverse_bits3(i + 1);
|
||||
fb[2 + kLineMultiSingle * i + kLineBytes] = 0;
|
||||
}
|
||||
fb[kLineDataBytes - 1] = 0;
|
||||
}
|
||||
dma_buf[kLineDataBytes - 1] = 0;
|
||||
|
||||
s_drawBufIdx = 0;
|
||||
dma_buf = s_dma_buffers[s_drawBufIdx];
|
||||
|
||||
for (int y = 0; y < DISP_HEIGHT; ++y)
|
||||
for (int x = 0; x < DISP_WIDTH; ++x)
|
||||
DispTools::set_pixel(x, y, false);
|
||||
set_pixel(x, y, false);
|
||||
|
||||
std::memcpy(dma_buf_template, dma_buf, sizeof(dma_buf_template));
|
||||
std::memcpy(s_dma_buffers[1], dma_buf_template, sizeof(dma_buf_template));
|
||||
ESP_ERROR_CHECK(esp_async_memcpy_install(&config, &driver)); // install driver with default DMA engine
|
||||
|
||||
s_clearReqSem = xSemaphoreCreateBinary();
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
s_bufferSem[i] = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(s_bufferSem[i]);
|
||||
}
|
||||
|
||||
_txSem = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(_txSem);
|
||||
xTaskCreate(clear_task, "fbclr", 1536, nullptr, tskIDLE_PRIORITY + 1, &s_clearTaskHandle);
|
||||
}
|
||||
|
||||
bool SMD::async_draw_busy() { return _inFlight; }
|
||||
bool SMD::async_draw_busy() { return uxSemaphoreGetCount(s_bufferSem[s_drawBufIdx]) == 0; }
|
||||
|
||||
void SMD::async_draw_start() {
|
||||
assert(!_inFlight);
|
||||
if (!xSemaphoreTake(s_clearSem, portMAX_DELAY))
|
||||
assert(driver != nullptr);
|
||||
if (!xSemaphoreTake(_txSem, portMAX_DELAY))
|
||||
assert(false);
|
||||
_vcom = !_vcom;
|
||||
_tx = {};
|
||||
_tx.tx_buffer = dma_buf;
|
||||
_tx.length = SMD::kLineDataBytes * 8;
|
||||
dma_buf[0] = 0b10000000 | (_vcom << 6);
|
||||
_inFlight = true;
|
||||
|
||||
const int sendIdx = s_drawBufIdx;
|
||||
assert(sendIdx >= 0 && sendIdx < 2);
|
||||
|
||||
SemaphoreHandle_t sem = s_bufferSem[sendIdx];
|
||||
if (!xSemaphoreTake(sem, 0))
|
||||
assert(false);
|
||||
|
||||
const int nextDrawIdx = sendIdx ^ 1;
|
||||
|
||||
_vcom = !_vcom;
|
||||
_tx = {};
|
||||
_tx.tx_buffer = s_dma_buffers[sendIdx];
|
||||
_tx.length = SMD::kLineDataBytes * 8;
|
||||
_tx.user = (void*) (sendIdx);
|
||||
s_dma_buffers[sendIdx][0] = 0b10000000 | (_vcom << 6);
|
||||
ESP_ERROR_CHECK(spi_device_queue_trans(_spi, &_tx, 0));
|
||||
|
||||
s_drawBufIdx = nextDrawIdx;
|
||||
dma_buf = s_dma_buffers[nextDrawIdx];
|
||||
}
|
||||
|
||||
void SMD::async_draw_wait() {
|
||||
if (!_inFlight || uxSemaphoreGetCount(s_clearSem)) {
|
||||
// assert((uxSemaphoreGetCount(s_clearSem) == 0) == _inFlight);
|
||||
return;
|
||||
SemaphoreHandle_t sem = s_bufferSem[s_drawBufIdx];
|
||||
uint64_t waitedUs = 0;
|
||||
if (!uxSemaphoreGetCount(sem)) {
|
||||
uint64_t start = esp_timer_get_time();
|
||||
if (!xSemaphoreTake(sem, portMAX_DELAY))
|
||||
assert(false);
|
||||
if (!xSemaphoreGive(sem))
|
||||
assert(false);
|
||||
waitedUs = esp_timer_get_time() - start;
|
||||
}
|
||||
auto now = esp_timer_get_time();
|
||||
if (!xSemaphoreTake(s_clearSem, portMAX_DELAY))
|
||||
assert(false);
|
||||
if (!xSemaphoreGive(s_clearSem))
|
||||
assert(false);
|
||||
assert(!_inFlight);
|
||||
auto elapsed = esp_timer_get_time() - now;
|
||||
printf("Waited %" PRIu64 " us\n", elapsed);
|
||||
if (waitedUs)
|
||||
printf("Waited %" PRIu64 " us\n", waitedUs);
|
||||
}
|
||||
|
||||
// (clear_in_progress / wait_clear / request_clear removed from public API)
|
||||
|
||||
// Surface implementation ------------------------------------------------------
|
||||
void SMDSurface::draw_pixel_impl(unsigned x, unsigned y, const BwPixel& pixel) {
|
||||
if (pixel.on)
|
||||
DispTools::set_pixel(x, y);
|
||||
else
|
||||
DispTools::reset_pixel(x, y);
|
||||
}
|
||||
void SMDSurface::clear_impl() { DispTools::clear(); }
|
||||
int SMDSurface::get_width_impl() const { return DISP_WIDTH; }
|
||||
int SMDSurface::get_height_impl() const { return DISP_HEIGHT; }
|
||||
EventHandlingResult SMDSurface::handle(SurfaceResizeEvent event) { return _window->handle(event); }
|
||||
SMDSurface::SMDSurface(EventLoop* loop) :
|
||||
Surface<SMDSurface, BwPixel>(),
|
||||
EventQueue<SMDSurface, KeyboardEvent, SurfaceEvent, SurfaceResizeEvent>(loop, this) {}
|
||||
SMDSurface::~SMDSurface() {}
|
||||
|
||||
Reference in New Issue
Block a user