diff --git a/CMakeLists.txt b/CMakeLists.txt index 9eb2f37..c54e1d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.5) set(EXTRA_COMPONENT_DIRS "src") set(COMPONENTS "esptool_py src") + include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(game) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6c19f3f..6bfbdb8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,12 @@ idf_component_register( - SRCS "main.c" - INCLUDE_DIRS "" - PRIV_REQUIRES "") + SRCS + "main.c" + "odroid/display.c" + "odroid/input.c" + + INCLUDE_DIRS + "." + + PRIV_REQUIRES + "esp_adc_cal") diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 0000000..f734bf1 --- /dev/null +++ b/src/macros.h @@ -0,0 +1,21 @@ +#pragma once + + +// Counts the number of elements in an array +#define ARRAY_COUNT(value) ( sizeof(value) / sizeof(value[0]) ) + +// Swaps the endianness of a 16-bit value +#define SWAP_ENDIAN_16(value) ( (((value) & 0xFFu) << 8u) | ((value) >> 8u) ) + +// Constructs a 16-bit color value of the form RGB565 with proper ESP32 endianness +#define RGB565(red, green, blue) ( (((red) >> 3u) << 11u) | (((green) >> 2u) << 5u) | ((blue) >> 3u) ) + +// Converts bytes to bits +#define BYTES_TO_BITS(value) ( (value) * 8 ) + +// Extracts the upper byte of a 16-bit value +#define UPPER_BYTE_16(value) ( (value) >> 8u ) + +// Extracts the lower byte of a 16-bit value +#define LOWER_BYTE_16(value) ( (value) & 0xFFu ) + diff --git a/src/main.c b/src/main.c index 4f9bfd7..b282892 100644 --- a/src/main.c +++ b/src/main.c @@ -1,30 +1,53 @@ +#include "odroid/display.h" #include "odroid/input.h" +#include "macros.h" #include #include #include +#include static const char* LOG_TAG = "Main"; - +static uint16_t gFramebuffer[LCD_WIDTH * LCD_HEIGHT]; void app_main(void) { Odroid_InitializeInput(); + Odroid_InitializeDisplay(); ESP_LOGI(LOG_TAG, "Odroid initialization complete - entering main loop"); + int x = 0; + int y = 0; + + uint16_t color = 0xffff; + for (;;) { + memset(gFramebuffer, 0, 320 * 240 * 2); + Odroid_Input input = Odroid_PollInput(); - printf( - "\ra: %d b: %d start: %d select: %d vol: %d menu: %d up: %d down: %d left: %d right: %d ", - input.a, input.b, input.start, input.select, input.volume, input.menu, - input.up, input.down, input.left, input.right); + if (input.left) { x -= 20; } + else if (input.right) { x += 20; } - fflush(stdout); + if (input.up) { y -= 20; } + else if (input.down) { y += 20; } - vTaskDelay(250 / portTICK_PERIOD_MS); + if (input.a) { color = SWAP_ENDIAN_16(RGB565(0xff, 0, 0)); } + else if (input.b) { color = SWAP_ENDIAN_16(RGB565(0, 0xff, 0)); } + else if (input.start) { color = SWAP_ENDIAN_16(RGB565(0, 0, 0xff)); } + else if (input.select) { color = SWAP_ENDIAN_16(RGB565(0xff, 0xff, 0xff)); } + + for (int row = y; row < y + 50; ++row) + { + for (int col = x; col < x + 50; ++col) + { + gFramebuffer[LCD_WIDTH * row + col] = color; + } + } + + Odroid_DrawFrame(gFramebuffer); } // Should never get here diff --git a/src/odroid/display.c b/src/odroid/display.c new file mode 100644 index 0000000..97b4275 --- /dev/null +++ b/src/odroid/display.c @@ -0,0 +1,184 @@ +#include "display.h" +#include "macros.h" +#include +#include +#include + + +static const char* LOG_TAG = "OdroidDisplay"; + +static const gpio_num_t LCD_PIN_MISO = GPIO_NUM_19; +static const gpio_num_t LCD_PIN_MOSI = GPIO_NUM_23; +static const gpio_num_t LCD_PIN_SCLK = GPIO_NUM_18; +static const gpio_num_t LCD_PIN_CS = GPIO_NUM_5; +static const gpio_num_t LCD_PIN_DC = GPIO_NUM_21; +static const gpio_num_t LCD_PIN_BACKLIGHT = GPIO_NUM_14; + + +typedef enum +{ + SOFTWARE_RESET = 0x01u, + SLEEP_OUT = 0x11u, + DISPLAY_ON = 0x29u, + COLUMN_ADDRESS_SET = 0x2Au, + PAGE_ADDRESS_SET = 0x2Bu, + MEMORY_WRITE = 0x2Cu, + MEMORY_ACCESS_CONTROL = 0x36u, + PIXEL_FORMAT_SET = 0x3Au, +} CommandCode; + + +typedef struct +{ + CommandCode code; + uint8_t parameters[15]; + uint8_t length; +} StartupCommand; + + +static spi_device_handle_t gSpiHandle; + +static StartupCommand gStartupCommands[] = +{ + // Reset to defaults + { + SOFTWARE_RESET, + {}, + 0 + }, + + // Landscape Mode + // Top-Left Origin + // BGR Panel + { + MEMORY_ACCESS_CONTROL, + {0x20 | 0xC0 | 0x08}, + 1 + }, + + // 16 bits per pixel + { + PIXEL_FORMAT_SET, + {0x55}, + 1 + }, + + // Exit sleep mode + { + SLEEP_OUT, + {}, + 0 + }, + + // Turn on the display + { + DISPLAY_ON, + {}, + 0 + }, +}; + + +static +void SendCommandCode(CommandCode code) +{ + spi_transaction_t transaction = {}; + + transaction.length = BYTES_TO_BITS(1); + transaction.tx_data[0] = (uint8_t)code; + transaction.flags = SPI_TRANS_USE_TXDATA; + + gpio_set_level(LCD_PIN_DC, 0); + spi_device_transmit(gSpiHandle, &transaction); +} + +static +void SendCommandParameters(uint8_t* data, int length) +{ + spi_transaction_t transaction = {}; + + transaction.length = BYTES_TO_BITS(length); + transaction.tx_buffer = data; + transaction.flags = 0; + + gpio_set_level(LCD_PIN_DC, 1); + spi_device_transmit(gSpiHandle, &transaction); +} + + +void Odroid_InitializeDisplay(void) +{ + // Initialize the SPI bus + { + spi_bus_config_t spiBusConfig = {}; + spiBusConfig.miso_io_num = LCD_PIN_MISO; + spiBusConfig.mosi_io_num = LCD_PIN_MOSI; + spiBusConfig.sclk_io_num = LCD_PIN_SCLK; + spiBusConfig.quadwp_io_num = -1; + spiBusConfig.quadhd_io_num = -1; + spiBusConfig.max_transfer_sz = LCD_WIDTH * LCD_HEIGHT * LCD_DEPTH; + + ESP_ERROR_CHECK(spi_bus_initialize(VSPI_HOST, &spiBusConfig, 1)); + + ESP_LOGI(LOG_TAG, "Initialized SPI Bus"); + } + + + // Add the display device to the SPI bus + { + spi_device_interface_config_t spiDeviceConfig = {}; + spiDeviceConfig.clock_speed_hz = SPI_MASTER_FREQ_40M; + spiDeviceConfig.spics_io_num = LCD_PIN_CS; + spiDeviceConfig.queue_size = 1; + spiDeviceConfig.flags = SPI_DEVICE_NO_DUMMY; + + ESP_ERROR_CHECK(spi_bus_add_device(VSPI_HOST, &spiDeviceConfig, &gSpiHandle)); + + ESP_LOGI(LOG_TAG, "Added display to SPI bus"); + } + + + // Set the DC and backlight pins as outputs + { + gpio_set_direction(LCD_PIN_DC, GPIO_MODE_OUTPUT); + gpio_set_direction(LCD_PIN_BACKLIGHT, GPIO_MODE_OUTPUT); + } + + + // Send the initialization commands to the display + { + int commandCount = ARRAY_COUNT(gStartupCommands); + + for (int commandIndex = 0; commandIndex < commandCount; ++commandIndex) + { + StartupCommand* command = &gStartupCommands[commandIndex]; + + SendCommandCode(command->code); + + if (command->length > 0) + { + SendCommandParameters(command->parameters, command->length); + } + } + + ESP_LOGI(LOG_TAG, "Initialized display"); + } +} + +void Odroid_DrawFrame(uint16_t* buffer) +{ + // Set drawing window width to (0, LCD_WIDTH) + uint8_t drawWidth[] = { 0, 0, UPPER_BYTE_16(LCD_WIDTH), LOWER_BYTE_16(LCD_WIDTH) }; + SendCommandCode(COLUMN_ADDRESS_SET); + SendCommandParameters(drawWidth, ARRAY_COUNT(drawWidth)); + + // Set drawing window height to (0, LCD_HEIGHT) + uint8_t drawHeight[] = { 0, 0, UPPER_BYTE_16(LCD_HEIGHT), LOWER_BYTE_16(LCD_HEIGHT) }; + SendCommandCode(PAGE_ADDRESS_SET); + SendCommandParameters(drawHeight, ARRAY_COUNT(drawHeight)); + + // Send the buffer to the display + SendCommandCode(MEMORY_WRITE); + SendCommandParameters((uint8_t*)buffer, LCD_WIDTH * LCD_HEIGHT * LCD_DEPTH); +} + diff --git a/src/odroid/display.h b/src/odroid/display.h new file mode 100644 index 0000000..a727be1 --- /dev/null +++ b/src/odroid/display.h @@ -0,0 +1,13 @@ +#pragma once + +#include + + +#define LCD_WIDTH (320) +#define LCD_HEIGHT (240) +#define LCD_DEPTH (2) + + +void Odroid_InitializeDisplay(void); +void Odroid_DrawFrame(uint16_t* buffer); +