diff options
| author | jacqueline <me@jacqueline.id.au> | 2022-11-07 12:01:29 +1100 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2022-11-07 12:01:29 +1100 |
| commit | 28d73ad8660e27f9c7b20b6e978d3d0c412dec00 (patch) | |
| tree | c50b739ae4712f5ddb9fb6e44e39e01e4c20356d /main/display.cpp | |
| parent | b13a9793e17e7e0e52ea08fa5fb69ca9b90ad56d (diff) | |
| download | tangara-fw-28d73ad8660e27f9c7b20b6e978d3d0c412dec00.tar.gz | |
Split driver-y things into a separate component
Diffstat (limited to 'main/display.cpp')
| -rw-r--r-- | main/display.cpp | 285 |
1 files changed, 0 insertions, 285 deletions
diff --git a/main/display.cpp b/main/display.cpp deleted file mode 100644 index 8708436f..00000000 --- a/main/display.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include "display.hpp" -#include <atomic> -#include <cstdint> -#include <cstring> -#include <memory> -#include <mutex> -#include "assert.h" -#include "display-init.hpp" -#include "driver/gpio.h" -#include "driver/spi_master.h" -#include "esp_attr.h" -#include "esp_heap_caps.h" -#include "freertos/portable.h" -#include "freertos/portmacro.h" -#include "freertos/projdefs.h" -#include "hal/gpio_types.h" -#include "hal/spi_types.h" -#include "lvgl/lvgl.h" - -static const char* kTag = "DISPLAY"; -static const gpio_num_t kCommandOrDataPin = GPIO_NUM_21; -static const gpio_num_t kLedPin = GPIO_NUM_22; - -static const uint8_t kDisplayWidth = 128; -static const uint8_t kDisplayHeight = 160; -static const uint8_t kTransactionQueueSize = 10; - -/* - * The size of each of our two display buffers. This is fundamentally a balance - * between performance and memory usage. LVGL docs recommend a buffer 1/10th the - * size of the screen is the best tradeoff. - * We use two buffers so that one can be flushed to the screen at the same time - * as the other is being drawn. - */ -static const int kDisplayBufferSize = (kDisplayWidth * kDisplayHeight) / 10; - -// Allocate both buffers in static memory to ensure that they're in DRAM, with -// minimal fragmentation. We most cases we always need these buffers anyway, so -// it's not a memory hit we can avoid anyway. -// Note: 128 * 160 / 10 * 2 bpp * 2 buffers = 8 KiB -DMA_ATTR static lv_color_t sBuffer1[kDisplayBufferSize]; -DMA_ATTR static lv_color_t sBuffer2[kDisplayBufferSize]; - -namespace gay_ipod { - -// Static functions for interrop with the LVGL display driver API, which -// requires a function pointer. -namespace callback { -static std::atomic<Display*> instance = nullptr; - -static void flush_cb(lv_disp_drv_t* disp_drv, - const lv_area_t* area, - lv_color_t* color_map) { - auto instance_unwrapped = instance.load(); - if (instance_unwrapped == nullptr) { - ESP_LOGW(kTag, "uncaught flush callback"); - return; - } - // TODO: what if a transaction comes in right now? - instance_unwrapped->Flush(disp_drv, area, color_map); -} - -static void IRAM_ATTR post_cb(spi_transaction_t* transaction) { - auto instance_unwrapped = instance.load(); - if (instance_unwrapped == nullptr) { - // Can't log in ISR. - return; - } - instance_unwrapped->PostTransaction(*transaction); -} -} // namespace callback - -auto Display::create(GpioExpander* expander, - const displays::InitialisationData& init_data) - -> cpp::result<std::unique_ptr<Display>, Error> { - // First, set up our GPIOs - gpio_config_t gpio_cfg = { - .pin_bit_mask = GPIO_SEL_22 | GPIO_SEL_21, - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - gpio_config(&gpio_cfg); - - gpio_set_level(kLedPin, 1); - gpio_set_level(kCommandOrDataPin, 0); - - // Next, init the SPI device - spi_device_interface_config_t spi_cfg = { - .command_bits = 0, // No command phase - .address_bits = 0, // No address phase - .dummy_bits = 0, - // For ST7789, mode should be 2 - .mode = 0, - .duty_cycle_pos = 0, // Unused - .cs_ena_pretrans = 0, - .cs_ena_posttrans = 0, - .clock_speed_hz = SPI_MASTER_FREQ_40M, - .input_delay_ns = 0, - .spics_io_num = -1, // TODO: change for R2 - .flags = 0, - .queue_size = kTransactionQueueSize, - .pre_cb = NULL, - .post_cb = &callback::post_cb, - }; - spi_device_handle_t handle; - spi_bus_add_device(VSPI_HOST, &spi_cfg, &handle); - - // TODO: ideally create this later? a bit awkward rn. - auto display = std::make_unique<Display>(expander, handle); - - // Now we reset the display into a known state, then configure it - // TODO: set rotatoin - ESP_LOGI(kTag, "Sending init sequences"); - for (int i = 0; i < init_data.num_sequences; i++) { - display->SendInitialisationSequence(init_data.sequences[i]); - } - - // The hardware is now configured correctly. Next, initialise the LVGL display - // driver. - ESP_LOGI(kTag, "Init buffers"); - lv_disp_draw_buf_init(&display->buffers_, sBuffer1, sBuffer2, - kDisplayBufferSize); - lv_disp_drv_init(&display->driver_); - display->driver_.draw_buf = &display->buffers_; - display->driver_.hor_res = kDisplayWidth; - display->driver_.ver_res = kDisplayHeight; - display->driver_.flush_cb = &callback::flush_cb; - - ESP_LOGI(kTag, "Registering driver"); - display->display_ = lv_disp_drv_register(&display->driver_); - - return std::move(display); -} - -Display::Display(GpioExpander* gpio, spi_device_handle_t handle) - : gpio_(gpio), handle_(handle) { - callback::instance = this; -} - -Display::~Display() { - callback::instance = nullptr; - // TODO. -} - -void Display::SendInitialisationSequence(const uint8_t* data) { - uint8_t command, num_args; - uint16_t sleep_duration_ms; - - // First byte of the data is the number of commands. - for (int i = *(data++); i > 0; i--) { - command = *(data++); - num_args = *(data++); - bool has_delay = (num_args & displays::kDelayBit) > 0; - num_args &= ~displays::kDelayBit; - - SendCommandWithData(command, data, num_args); - - data += num_args; - if (has_delay) { - sleep_duration_ms = *(data++); - if (sleep_duration_ms == 0xFF) { - sleep_duration_ms = 500; - } - vTaskDelay(pdMS_TO_TICKS(sleep_duration_ms)); - } - } -} - -void Display::SendCommandWithData(uint8_t command, - const uint8_t* data, - size_t length, - uintptr_t flags) { - SendCmd(&command, 1, flags); - SendData(data, length, flags); -} - -void Display::SendCmd(const uint8_t* data, size_t length, uintptr_t flags) { - SendTransaction(COMMAND, data, length, flags); -} - -void Display::SendData(const uint8_t* data, size_t length, uintptr_t flags) { - SendTransaction(DATA, data, length, flags); -} - -void Display::SendTransaction(TransactionType type, - const uint8_t* data, - size_t length, - uint32_t flags) { - if (length == 0) { - return; - } - - // TODO: Use a memory pool for these. - spi_transaction_t* transaction = (spi_transaction_t*)heap_caps_calloc( - 1, sizeof(spi_transaction_t), MALLOC_CAP_DMA); - - transaction->rx_buffer = NULL; - // Length is in bits, so multiply by 8. - transaction->length = length * 8; - transaction->rxlength = 0; // Match `length` value. - - // If the data to transmit is very short, then we can fit it directly - // inside the transaction struct. - if (length * 8 <= 32) { - transaction->flags = SPI_TRANS_USE_TXDATA; - std::memcpy(&transaction->tx_data, data, length); - } else { - // TODO: copy data to a DMA-capable transaction buffer - transaction->tx_buffer = const_cast<uint8_t*>(data); - } - - transaction->user = reinterpret_cast<void*>(flags); - - // TODO: acquire the bus first? Or in an outer scope? - // TODO: fail gracefully - // ESP_ERROR_CHECK(spi_device_queue_trans(handle_, transaction, - // portMAX_DELAY)); - // - - ServiceTransactions(); - gpio_set_level(kCommandOrDataPin, type); - - gpio_->with([&](auto& gpio_) { - gpio_.set_pin(GpioExpander::DISPLAY_CHIP_SELECT, 0); - }); - { - // auto lock = gpio_->AcquireSpiBus(GpioExpander::DISPLAY); - ESP_ERROR_CHECK(spi_device_polling_transmit(handle_, transaction)); - } - - free(transaction); -} - -void Display::Flush(lv_disp_drv_t* disp_drv, - const lv_area_t* area, - lv_color_t* color_map) { - uint16_t data[2] = {0, 0}; - - data[0] = SPI_SWAP_DATA_TX(area->x1, 16); - data[1] = SPI_SWAP_DATA_TX(area->x2, 16); - SendCommandWithData(displays::ST77XX_CASET, (uint8_t*)data, 4); - - data[0] = SPI_SWAP_DATA_TX(area->y1, 16); - data[1] = SPI_SWAP_DATA_TX(area->y2, 16); - SendCommandWithData(displays::ST77XX_RASET, (uint8_t*)data, 4); - - uint32_t size = lv_area_get_width(area) * lv_area_get_height(area); - SendCommandWithData(displays::ST77XX_RAMWR, (uint8_t*)color_map, size * 2, - LVGL_FLUSH); - - // ESP_LOGI(kTag, "finished flush."); - // lv_disp_flush_ready(&driver_); -} - -void IRAM_ATTR Display::PostTransaction(const spi_transaction_t& transaction) { - if (reinterpret_cast<uintptr_t>(transaction.user) & LVGL_FLUSH) { - lv_disp_flush_ready(&driver_); - } -} - -void Display::ServiceTransactions() { - // todo - if (1) - return; - spi_transaction_t* transaction = nullptr; - // TODO: just wait '1' here, provide mechanism to wait for sure (poll?) - while (spi_device_get_trans_result(handle_, &transaction, pdMS_TO_TICKS(1)) != - ESP_ERR_TIMEOUT) { - ESP_LOGI(kTag, "cleaning up finished transaction"); - - // TODO: a bit dodge lmao - // TODO: also this should happen in the post callback instead i guess? - if (transaction->length > 1000) { - ESP_LOGI(kTag, "finished flush."); - lv_disp_flush_ready(&driver_); - } - - // TODO: place back into pool. - free(transaction); - } -} - -} // namespace gay_ipod |
