Part 3: Display
This commit is contained in:
		
							parent
							
								
									2156cb1e1a
								
							
						
					
					
						commit
						801a4e790d
					
				|  | @ -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) | ||||
|  |  | |||
|  | @ -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") | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 ) | ||||
| 
 | ||||
							
								
								
									
										37
									
								
								src/main.c
								
								
								
								
							
							
						
						
									
										37
									
								
								src/main.c
								
								
								
								
							|  | @ -1,30 +1,53 @@ | |||
| #include "odroid/display.h" | ||||
| #include "odroid/input.h" | ||||
| #include "macros.h" | ||||
| #include <esp_log.h> | ||||
| #include <freertos/FreeRTOS.h> | ||||
| #include <freertos/task.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| 
 | ||||
| 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
 | ||||
|  |  | |||
|  | @ -0,0 +1,184 @@ | |||
| #include "display.h" | ||||
| #include "macros.h" | ||||
| #include <driver/gpio.h> | ||||
| #include <driver/spi_master.h> | ||||
| #include <esp_log.h> | ||||
| 
 | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
|  | @ -0,0 +1,13 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| 
 | ||||
| #define LCD_WIDTH (320) | ||||
| #define LCD_HEIGHT (240) | ||||
| #define LCD_DEPTH (2) | ||||
| 
 | ||||
| 
 | ||||
| void Odroid_InitializeDisplay(void); | ||||
| void Odroid_DrawFrame(uint16_t* buffer); | ||||
| 
 | ||||
		Loading…
	
		Reference in New Issue