mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
156 lines
5.7 KiB
C++
156 lines
5.7 KiB
C++
// Double-buffered display implementation with async memcpy ---------------------------------
|
|
|
|
#include "cardboy/backend/esp/display.hpp"
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <driver/gpio.h>
|
|
#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 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;
|
|
static TaskHandle_t s_clearTaskHandle = nullptr;
|
|
static SemaphoreHandle_t s_clearReqSem = nullptr;
|
|
static SemaphoreHandle_t s_bufferSem[2] = {nullptr, nullptr};
|
|
static bool s_clearPending[2] = {true, true};
|
|
|
|
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 = 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) {
|
|
BaseType_t high_task_wakeup = pdFALSE;
|
|
auto sem = static_cast<SemaphoreHandle_t>(cb_args);
|
|
xSemaphoreGiveFromISR(sem, &high_task_wakeup);
|
|
return high_task_wakeup == pdTRUE;
|
|
}
|
|
|
|
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) {
|
|
spi_transaction_t* r = nullptr;
|
|
ESP_ERROR_CHECK(spi_device_get_trans_result(SMD::_spi, &r, 0));
|
|
int bufIdx = (int) r->user;
|
|
xSemaphoreGive(_txSem);
|
|
const bool shouldClear = s_clearPending[bufIdx];
|
|
s_clearPending[bufIdx] = true;
|
|
if (shouldClear) {
|
|
constexpr unsigned alignedSize = SMD::kLineDataBytes - (SMD::kLineDataBytes % 4);
|
|
static_assert(SMD::kLineDataBytes - alignedSize < 8); // Last byte is zero anyway
|
|
ESP_ERROR_CHECK(esp_async_memcpy(driver, s_dma_buffers[bufIdx], dma_buf_template, alignedSize,
|
|
my_async_memcpy_cb, static_cast<void*>(s_bufferSem[bufIdx])));
|
|
} else {
|
|
if (!xSemaphoreGive(s_bufferSem[bufIdx]))
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SMD::init() {
|
|
spi_bus_add_device(SPI_BUS, &_devcfg, &_spi);
|
|
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 (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;
|
|
}
|
|
|
|
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)
|
|
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::frame_transfer_in_flight() { return uxSemaphoreGetCount(s_bufferSem[s_drawBufIdx]) == 0; }
|
|
|
|
void SMD::send_frame(bool clear_after_send) {
|
|
assert(driver != nullptr);
|
|
if (!xSemaphoreTake(_txSem, portMAX_DELAY))
|
|
assert(false);
|
|
|
|
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;
|
|
s_clearPending[sendIdx] = clear_after_send;
|
|
|
|
_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::frame_ready() {
|
|
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;
|
|
}
|
|
// if (waitedUs)
|
|
// printf("Waited %" PRIu64 " us\n", waitedUs);
|
|
}
|