Files
cardboy/Firmware/main/src/display.cpp
2025-10-07 14:57:15 +02:00

138 lines
5.0 KiB
C++

// Simplified display implementation (no async memcpy) ---------------------------------
#include "display.hpp"
#include <cstring>
#include <driver/gpio.h>
#include "disp_tools.hpp"
#include "driver/spi_master.h"
#include "esp_async_memcpy.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]{};
spi_device_handle_t SMD::_spi;
static spi_transaction_t _tx{};
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 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 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) {
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
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);
if (hpw)
portYIELD_FROM_ISR();
}
static void clear_task(void*) {
for (;;) {
if (xSemaphoreTake(s_clearReqSem, portMAX_DELAY) == pdTRUE) {
printf("Started zeroing\n");
spi_transaction_t* r = nullptr;
ESP_ERROR_CHECK(spi_device_get_trans_result(SMD::_spi, &r, 0));
zero_framebuffer_payload();
// printf("Zeroing done\n");
}
}
}
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;
}
dma_buf[kLineDataBytes - 1] = 0;
for (int y = 0; y < DISP_HEIGHT; ++y)
for (int x = 0; x < DISP_WIDTH; ++x)
DispTools::set_pixel(x, y, false);
std::memcpy(dma_buf_template, dma_buf, sizeof(dma_buf_template));
ESP_ERROR_CHECK(esp_async_memcpy_install(&config, &driver)); // install driver with default DMA engine
}
bool SMD::async_draw_busy() { return _inFlight; }
void SMD::async_draw_start() {
assert(!_inFlight);
if (!xSemaphoreTake(s_clearSem, 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;
ESP_ERROR_CHECK(spi_device_queue_trans(_spi, &_tx, 0));
}
void SMD::async_draw_wait() {
if (uxSemaphoreGetCount(s_clearSem) || !_inFlight) {
assert((uxSemaphoreGetCount(s_clearSem) == 0) == _inFlight);
return;
}
if (!xSemaphoreTake(s_clearSem, portMAX_DELAY))
assert(false);
if (!xSemaphoreGive(s_clearSem))
assert(false);
assert(!_inFlight);
}
// (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() {}