summaryrefslogtreecommitdiff
path: root/main/display.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2022-11-07 12:01:29 +1100
committerjacqueline <me@jacqueline.id.au>2022-11-07 12:01:29 +1100
commit28d73ad8660e27f9c7b20b6e978d3d0c412dec00 (patch)
treec50b739ae4712f5ddb9fb6e44e39e01e4c20356d /main/display.cpp
parentb13a9793e17e7e0e52ea08fa5fb69ca9b90ad56d (diff)
downloadtangara-fw-28d73ad8660e27f9c7b20b6e978d3d0c412dec00.tar.gz
Split driver-y things into a separate component
Diffstat (limited to 'main/display.cpp')
-rw-r--r--main/display.cpp285
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