23 changed files with 1383 additions and 1 deletions
@ -0,0 +1 @@ |
|||
Subproject commit 1096c7eac2d69a0fc90ad06b0b454c6091d542c0 |
@ -0,0 +1,9 @@ |
|||
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) |
|||
|
@ -0,0 +1,24 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
IDF_PATH= |
|||
IDF_TOOLS_PATH= |
|||
|
|||
|
|||
if [ -z "$IDF_PATH" ] |
|||
then |
|||
echo "IDF_PATH not set" |
|||
return |
|||
fi |
|||
|
|||
if [ -z "$IDF_TOOLS_PATH" ] |
|||
then |
|||
echo "IDF_TOOLS_PATH not set" |
|||
return |
|||
fi |
|||
|
|||
|
|||
export IDF_PATH |
|||
export IDF_TOOLS_PATH |
|||
|
|||
source $IDF_PATH/export.sh |
|||
|
@ -0,0 +1,13 @@ |
|||
# Set flash size to 16MB |
|||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y |
|||
|
|||
# Set CPU frequency to 80MHz |
|||
CONFIG_ESP32_DEFAULT_CPU_FREQ_80=y |
|||
|
|||
# Enable SPI RAM and allocate with heap_caps_malloc() |
|||
CONFIG_ESP32_SPIRAM_SUPPORT=y |
|||
CONFIG_SPIRAM_USE_CAPS_ALLOC=y |
|||
|
|||
# Enable optimizations |
|||
CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y |
|||
|
@ -0,0 +1,17 @@ |
|||
idf_component_register( |
|||
SRCS |
|||
"main.c" |
|||
"odroid/audio.c" |
|||
"odroid/battery.c" |
|||
"odroid/display.c" |
|||
"odroid/input.c" |
|||
"odroid/sdcard.c" |
|||
"text.c" |
|||
|
|||
INCLUDE_DIRS |
|||
"." |
|||
|
|||
PRIV_REQUIRES |
|||
"esp_adc_cal" |
|||
"fatfs") |
|||
|
@ -0,0 +1,387 @@ |
|||
// AUTOMATICALLY GENERATED. DO NOT EDIT.
|
|||
|
|||
#pragma once |
|||
|
|||
#include <stdio.h> |
|||
#include <assert.h> |
|||
#include <stdint.h> |
|||
|
|||
|
|||
static const int GLYPH_WIDTH = 16; |
|||
static const int GLYPH_HEIGHT = 16; |
|||
|
|||
|
|||
int GetGlyphIndex(char c) |
|||
{ |
|||
switch (c) |
|||
{ |
|||
case 'a': case 'A': { return 0; break; } |
|||
case 'b': case 'B': { return 1; break; } |
|||
case 'c': case 'C': { return 2; break; } |
|||
case 'd': case 'D': { return 3; break; } |
|||
case 'e': case 'E': { return 4; break; } |
|||
case 'f': case 'F': { return 5; break; } |
|||
case 'g': case 'G': { return 6; break; } |
|||
case 'h': case 'H': { return 7; break; } |
|||
case 'i': case 'I': { return 8; break; } |
|||
case 'j': case 'J': { return 9; break; } |
|||
case 'k': case 'K': { return 10; break; } |
|||
case 'l': case 'L': { return 11; break; } |
|||
case 'm': case 'M': { return 12; break; } |
|||
case 'n': case 'N': { return 13; break; } |
|||
case 'o': case 'O': { return 14; break; } |
|||
case 'p': case 'P': { return 15; break; } |
|||
case 'q': case 'Q': { return 16; break; } |
|||
case 'r': case 'R': { return 17; break; } |
|||
case 's': case 'S': { return 18; break; } |
|||
case 't': case 'T': { return 19; break; } |
|||
case 'u': case 'U': { return 20; break; } |
|||
case 'v': case 'V': { return 21; break; } |
|||
case 'w': case 'W': { return 22; break; } |
|||
case 'x': case 'X': { return 23; break; } |
|||
case 'y': case 'Y': { return 24; break; } |
|||
case 'z': case 'Z': { return 25; break; } |
|||
case '1': { return 26; break; } |
|||
case '2': { return 27; break; } |
|||
case '3': { return 28; break; } |
|||
case '4': { return 29; break; } |
|||
case '5': { return 30; break; } |
|||
case '6': { return 31; break; } |
|||
case '7': { return 32; break; } |
|||
case '8': { return 33; break; } |
|||
case '9': { return 34; break; } |
|||
case '0': { return 35; break; } |
|||
case ':': { return 36; break; } |
|||
case '!': { return 37; break; } |
|||
case '?': { return 38; break; } |
|||
case '.': { return 39; break; } |
|||
default: { |
|||
printf("Missing glyph for '%c'", c); |
|||
assert(NULL); break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
static const uint16_t glyphMap[][16] = |
|||
{ |
|||
// A
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x781E,0x781E,0x781E,0x7FFE, |
|||
0x7FFE,0x7FFE,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x0000, |
|||
}, |
|||
|
|||
// B
|
|||
{ |
|||
0x0000,0x7FFC,0x7FFE,0x7FFE, |
|||
0x780E,0x780E,0x7FFE,0x7FFE, |
|||
0x7FFC,0x780C,0x780E,0x780E, |
|||
0x7FFE,0x7FFE,0x7FFC,0x0000, |
|||
}, |
|||
|
|||
// C
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x7800,0x7800,0x7800,0x7800, |
|||
0x7800,0x7800,0x7800,0x7800, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// D
|
|||
{ |
|||
0x0000,0x7FF8,0x7FFE,0x7FFE, |
|||
0x781E,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x781E, |
|||
0x7FFE,0x7FFE,0x7FF8,0x0000, |
|||
}, |
|||
|
|||
// E
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x7800,0x7800,0x7FFC,0x7FFC, |
|||
0x7FFC,0x7800,0x7800,0x7800, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// F
|
|||
{ |
|||
0x0000,0x7FF8,0x7FF8,0x7FF8, |
|||
0x7800,0x7800,0x7FF0,0x7FF0, |
|||
0x7FF0,0x7800,0x7800,0x7800, |
|||
0x7800,0x7800,0x7800,0x0000, |
|||
}, |
|||
|
|||
// G
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x7800,0x7800,0x7800,0x7800, |
|||
0x787E,0x787E,0x781E,0x781E, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// H
|
|||
{ |
|||
0x0000,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x7FFE,0x7FFE, |
|||
0x7FFE,0x7FFE,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x0000, |
|||
}, |
|||
|
|||
// I
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x03C0,0x03C0,0x03C0,0x03C0, |
|||
0x03C0,0x03C0,0x03C0,0x03C0, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// J
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x00F0,0x00F0,0x00F0,0x00F0, |
|||
0x00F0,0x00F0,0x70F0,0x70F0, |
|||
0x7FF0,0x7FF0,0x7FF0,0x0000, |
|||
}, |
|||
|
|||
// K
|
|||
{ |
|||
0x0000,0x780E,0x780E,0x7838, |
|||
0x7838,0x79E0,0x79E0,0x7F80, |
|||
0x7F80,0x79E0,0x79E0,0x7878, |
|||
0x7878,0x781E,0x781E,0x0000, |
|||
}, |
|||
|
|||
// L
|
|||
{ |
|||
0x0000,0x7800,0x7800,0x7800, |
|||
0x7800,0x7800,0x7800,0x7800, |
|||
0x7800,0x7800,0x7800,0x7800, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// M
|
|||
{ |
|||
0x0000,0x781E,0x781E,0x7E7E, |
|||
0x7E7E,0x7FFE,0x7FFE,0x799E, |
|||
0x799E,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x0000, |
|||
}, |
|||
|
|||
// N
|
|||
{ |
|||
0x0000,0x781E,0x781E,0x7E1E, |
|||
0x7E1E,0x7F9E,0x7F9E,0x7FFE, |
|||
0x79FE,0x79FE,0x787E,0x787E, |
|||
0x781E,0x781E,0x781E,0x0000, |
|||
}, |
|||
|
|||
// O
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x781E,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x781E, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// P
|
|||
{ |
|||
0x0000,0x7FF8,0x7FFE,0x7FFE, |
|||
0x781E,0x781E,0x781E,0x7FFE, |
|||
0x7FF8,0x7FF8,0x7800,0x7800, |
|||
0x7800,0x7800,0x7800,0x0000, |
|||
}, |
|||
|
|||
// Q
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x7006,0x7006,0x7006,0x7006, |
|||
0x7006,0x7006,0x70C6,0x70C6, |
|||
0x7FF8,0x7FF8,0x001E,0x0000, |
|||
}, |
|||
|
|||
// R
|
|||
{ |
|||
0x0000,0x7FF8,0x7FFE,0x7FFE, |
|||
0x781E,0x781E,0x781E,0x7FFE, |
|||
0x7FF8,0x7FF8,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x0000, |
|||
}, |
|||
|
|||
// S
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x7800,0x7800,0x7FFE,0x7FFE, |
|||
0x7FFE,0x001E,0x001E,0x001E, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// T
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x03C0,0x03C0,0x03C0,0x03C0, |
|||
0x03C0,0x03C0,0x03C0,0x03C0, |
|||
0x03C0,0x03C0,0x03C0,0x0000, |
|||
}, |
|||
|
|||
// U
|
|||
{ |
|||
0x0000,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x781E, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// V
|
|||
{ |
|||
0x0000,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x781E, |
|||
0x1E78,0x1E78,0x1E78,0x1E78, |
|||
0x07E0,0x07E0,0x07E0,0x0000, |
|||
}, |
|||
|
|||
// W
|
|||
{ |
|||
0x0000,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x781E,0x799E, |
|||
0x799E,0x7FFE,0x7FFE,0x7E7E, |
|||
0x7E7E,0x781E,0x781E,0x0000, |
|||
}, |
|||
|
|||
// X
|
|||
{ |
|||
0x0000,0x781E,0x781E,0x781E, |
|||
0x1E78,0x1E78,0x07E0,0x07E0, |
|||
0x07E0,0x07E0,0x1E78,0x1E78, |
|||
0x781E,0x781E,0x781E,0x0000, |
|||
}, |
|||
|
|||
// Y
|
|||
{ |
|||
0x0000,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x7E7E,0x7E7E, |
|||
0x1FF8,0x1FF8,0x07E0,0x07E0, |
|||
0x07E0,0x07E0,0x07E0,0x0000, |
|||
}, |
|||
|
|||
// Z
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x0078,0x0078,0x01E0,0x01E0, |
|||
0x0780,0x0780,0x1E00,0x1E00, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// 1
|
|||
{ |
|||
0x0000,0x01E0,0x01E0,0x01E0, |
|||
0x01E0,0x01E0,0x01E0,0x01E0, |
|||
0x01E0,0x01E0,0x01E0,0x01E0, |
|||
0x01E0,0x01E0,0x01E0,0x0000, |
|||
}, |
|||
|
|||
// 2
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x001E,0x001E,0x7FFE,0x7FFE, |
|||
0x7FFE,0x7800,0x7800,0x7800, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// 3
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x001E,0x001E,0x3FFE,0x3FFE, |
|||
0x3FFE,0x001E,0x001E,0x001E, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// 4
|
|||
{ |
|||
0x0000,0x781E,0x781E,0x781E, |
|||
0x781E,0x781E,0x7FFE,0x7FFE, |
|||
0x7FFE,0x7FFE,0x001E,0x001E, |
|||
0x001E,0x001E,0x001E,0x0000, |
|||
}, |
|||
|
|||
// 5
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x7800,0x7800,0x7FFE,0x7FFE, |
|||
0x7FFE,0x001E,0x001E,0x001E, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// 6
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x7800,0x7800,0x7FFE,0x7FFE, |
|||
0x7FFE,0x781E,0x781E,0x781E, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// 7
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x001E,0x001E,0x001E,0x001E, |
|||
0x001E,0x001E,0x001E,0x001E, |
|||
0x001E,0x001E,0x001E,0x0000, |
|||
}, |
|||
|
|||
// 8
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x781E, |
|||
0x781E,0x781E,0x7FFE,0x7FFE, |
|||
0x7FFE,0x781E,0x781E,0x781E, |
|||
0x781E,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// 9
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x781E,0x781E,0x781E,0x7FFE, |
|||
0x7FFE,0x7FFE,0x001E,0x001E, |
|||
0x001E,0x001E,0x001E,0x0000, |
|||
}, |
|||
|
|||
// 0
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x781E,0x781E,0x799E,0x799E, |
|||
0x799E,0x799E,0x781E,0x781E, |
|||
0x7FFE,0x7FFE,0x7FFE,0x0000, |
|||
}, |
|||
|
|||
// :
|
|||
{ |
|||
0x0000,0x0000,0x3C00,0x3C00, |
|||
0x3C00,0x3C00,0x0000,0x0000, |
|||
0x0000,0x0000,0x3C00,0x3C00, |
|||
0x3C00,0x3C00,0x0000,0x0000, |
|||
}, |
|||
|
|||
// !
|
|||
{ |
|||
0x0000,0x3C00,0x3C00,0x3C00, |
|||
0x3C00,0x3C00,0x3C00,0x3C00, |
|||
0x3C00,0x3C00,0x0000,0x0000, |
|||
0x3C00,0x3C00,0x3C00,0x0000, |
|||
}, |
|||
|
|||
// ?
|
|||
{ |
|||
0x0000,0x7FFE,0x7FFE,0x7FFE, |
|||
0x781E,0x781E,0x79FE,0x79FE, |
|||
0x01E0,0x01E0,0x0000,0x0000, |
|||
0x01E0,0x01E0,0x01E0,0x0000, |
|||
}, |
|||
|
|||
// .
|
|||
{ |
|||
0x0000,0x0000,0x0000,0x0000, |
|||
0x0000,0x0000,0x0000,0x0000, |
|||
0x0000,0x0000,0x0000,0x0000, |
|||
0x01E0,0x01E0,0x01E0,0x0000, |
|||
}, |
|||
|
|||
}; |
@ -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 ) |
|||
|
@ -0,0 +1,274 @@ |
|||
#include "odroid/audio.h" |
|||
#include "odroid/battery.h" |
|||
#include "odroid/display.h" |
|||
#include "odroid/input.h" |
|||
#include "odroid/sdcard.h" |
|||
#include "macros.h" |
|||
#include "text.h" |
|||
#include <esp_log.h> |
|||
#include <esp_timer.h> |
|||
#include <freertos/FreeRTOS.h> |
|||
#include <freertos/task.h> |
|||
#include <string.h> |
|||
|
|||
#define ArraySize(array) sizeof((array)) / sizeof((array)[0]) |
|||
|
|||
static const char* LOG_TAG = "Main"; |
|||
static uint16_t gFramebuffer[LCD_WIDTH * LCD_HEIGHT]; |
|||
|
|||
static bool drawBattery = true; |
|||
static bool drawTimeSinceBoot = true; |
|||
static bool drawFrameRate = true; |
|||
|
|||
/* static const uint16_t lightPalette[4] = */ |
|||
/* { */ |
|||
/* 0xFFFF, */ |
|||
/* 0x55AD, */ |
|||
/* 0xAA52, */ |
|||
/* 0x0000, */ |
|||
/* }; */ |
|||
|
|||
static const uint16_t palette[4] = |
|||
{ |
|||
0x0000, |
|||
0xAA52, |
|||
0x55AD, |
|||
0xFFFF, |
|||
}; |
|||
|
|||
static const uint8_t tiles[][16*16] = |
|||
{ |
|||
// White
|
|||
{ |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
}, |
|||
|
|||
// Light Grey
|
|||
{ |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, |
|||
0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
}, |
|||
|
|||
// Dark Grey
|
|||
{ |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, |
|||
0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
}, |
|||
|
|||
// Black
|
|||
{ |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
0,0,3,3,3,3,3,3,3,3,3,3,3,3,0,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, |
|||
0,0,3,3,3,3,3,3,3,3,3,3,3,3,0,0, |
|||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|||
}, |
|||
}; |
|||
|
|||
static int tileBuffer[15][40] = |
|||
{ |
|||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
|||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
|||
{0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
|||
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0}, |
|||
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0}, |
|||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0}, |
|||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0}, |
|||
{0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0}, |
|||
{0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0}, |
|||
{0, 0, 3, 0, 0, 0, 3, 3, 3, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0}, |
|||
{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, |
|||
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, |
|||
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, |
|||
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, |
|||
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, |
|||
}; |
|||
|
|||
|
|||
void DrawTile(int index, int x, int y) |
|||
{ |
|||
int startX = x * 16; |
|||
int startY = y * 16; |
|||
|
|||
for (int row = 0; row < 16; ++row) |
|||
{ |
|||
for (int col = 0; col < 16; ++col) |
|||
{ |
|||
uint8_t paletteIndex = tiles[index][row * 16 + col]; |
|||
|
|||
int screenY = startY + row; |
|||
int screenX = startX + col; |
|||
|
|||
uint16_t color = palette[paletteIndex]; |
|||
|
|||
gFramebuffer[screenY * LCD_WIDTH + screenX] = color; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
void app_main(void) |
|||
{ |
|||
Odroid_InitializeInput(); |
|||
Odroid_InitializeDisplay(); |
|||
/* Odroid_InitializeSdcard(); */ |
|||
Odroid_InitializeBatteryReader(); |
|||
/* Odroid_InitializeAudio(); */ |
|||
|
|||
ESP_LOGI(LOG_TAG, "Odroid initialization complete - entering main loop"); |
|||
|
|||
|
|||
uint8_t frameIndex = 0; |
|||
char snapFilename[20]; |
|||
int xLeft = 0; |
|||
|
|||
int64_t lastFrameTimeMicroSecs = esp_timer_get_time(); |
|||
|
|||
for (;;) |
|||
{ |
|||
memset(gFramebuffer, palette[0], 320*240*2); |
|||
|
|||
Odroid_Input input = Odroid_PollInput(); |
|||
|
|||
// Movement
|
|||
/* if (input.left) */ |
|||
/* { */ |
|||
/* xLeft -= 1; */ |
|||
|
|||
/* if (xLeft < 0) */ |
|||
/* { */ |
|||
/* xLeft = 39; */ |
|||
/* } */ |
|||
/* } */ |
|||
|
|||
/* else if (input.right) */ |
|||
/* { */ |
|||
/* xLeft += 1; */ |
|||
|
|||
/* if (xLeft > 39) */ |
|||
/* { */ |
|||
/* xLeft = 0; */ |
|||
/* } */ |
|||
/* } */ |
|||
|
|||
// Tile drawing
|
|||
/* for (int tileY = 0; tileY < 15; ++tileY) */ |
|||
/* { */ |
|||
/* for (int tileX = xLeft; tileX < xLeft + 20; ++tileX) */ |
|||
/* { */ |
|||
/* int tile = tileX % 40; */ |
|||
|
|||
/* int tileIndex = tileBuffer[tileY][tile]; */ |
|||
|
|||
/* DrawTile(tileIndex, tileX - xLeft, tileY); */ |
|||
/* } */ |
|||
/* } */ |
|||
|
|||
// Battery
|
|||
if (drawBattery) |
|||
{ |
|||
char string[10]; |
|||
snprintf(string, ArraySize(string), "B: %02d", Odroid_ReadBatteryLevel()); |
|||
/* snprintf(string, ArraySize(string), "B: %02d", Odroid_ReadBatteryLevel()); */ |
|||
DrawText(gFramebuffer, string, ArraySize(string), 0, 0, palette[3]); |
|||
} |
|||
|
|||
// Time since boot
|
|||
if (drawTimeSinceBoot) |
|||
{ |
|||
char string[10]; |
|||
int64_t timeSinceBoot = esp_timer_get_time(); |
|||
snprintf(string, ArraySize(string), "%lld", timeSinceBoot); |
|||
DrawText(gFramebuffer, string, ArraySize(string), 0, 1, palette[3]); |
|||
} |
|||
|
|||
// Frame time
|
|||
if (drawFrameRate) |
|||
{ |
|||
char string[10]; |
|||
int64_t currentTimeMicroSecs = esp_timer_get_time(); |
|||
int64_t frameTimeMicroSecs = currentTimeMicroSecs - lastFrameTimeMicroSecs; |
|||
snprintf(string, ArraySize(string), "%.2f HZ", 1000000.f / frameTimeMicroSecs); |
|||
DrawText(gFramebuffer, string, ArraySize(string), 12, 0, palette[3]); |
|||
lastFrameTimeMicroSecs = currentTimeMicroSecs; |
|||
} |
|||
|
|||
// Save screenshots for making gifs
|
|||
/* if (input.menu) */ |
|||
/* { */ |
|||
/* snprintf(snapFilename, 20, "/sdcard/frame%02d", frameIndex); */ |
|||
|
|||
/* ESP_LOGI(LOG_TAG, "Writing snapshot to %s", snapFilename); */ |
|||
|
|||
/* FILE* snapFile = fopen(snapFilename, "wb"); */ |
|||
/* assert(snapFile); */ |
|||
|
|||
/* fwrite(gFramebuffer, 1, LCD_WIDTH * LCD_HEIGHT * sizeof(gFramebuffer[0]), snapFile); */ |
|||
|
|||
/* fclose(snapFile); */ |
|||
|
|||
/* ++frameIndex; */ |
|||
/* } */ |
|||
|
|||
|
|||
Odroid_DrawFrame(gFramebuffer); |
|||
} |
|||
|
|||
// Should never get here
|
|||
esp_restart(); |
|||
} |
|||
|
@ -0,0 +1,85 @@ |
|||
#include "audio.h" |
|||
#include <freertos/FreeRTOS.h> |
|||
#include <freertos/task.h> |
|||
#include <driver/i2s.h> |
|||
|
|||
|
|||
static const gpio_num_t AUDIO_AMP_SD_PIN = GPIO_NUM_25; |
|||
|
|||
static QueueHandle_t gQueue; |
|||
|
|||
|
|||
typedef struct |
|||
{ |
|||
uint16_t* buffer; |
|||
int length; |
|||
} QueueData; |
|||
|
|||
|
|||
static void PlayTask(void *arg) |
|||
{ |
|||
for(;;) |
|||
{ |
|||
QueueData data; |
|||
|
|||
if (xQueueReceive(gQueue, &data, 10)) |
|||
{ |
|||
size_t bytesWritten; |
|||
i2s_write(I2S_NUM_0, data.buffer, data.length, &bytesWritten, portMAX_DELAY); |
|||
i2s_zero_dma_buffer(I2S_NUM_0); |
|||
} |
|||
|
|||
vTaskDelay(1 / portTICK_PERIOD_MS); |
|||
} |
|||
} |
|||
|
|||
void Odroid_InitializeAudio(void) |
|||
{ |
|||
// Configure the amplifier shutdown signal
|
|||
{ |
|||
gpio_config_t gpioConfig = {}; |
|||
|
|||
gpioConfig.mode = GPIO_MODE_OUTPUT; |
|||
gpioConfig.pin_bit_mask = 1ULL << AUDIO_AMP_SD_PIN; |
|||
|
|||
ESP_ERROR_CHECK(gpio_config(&gpioConfig)); |
|||
|
|||
gpio_set_level(AUDIO_AMP_SD_PIN, 1); |
|||
} |
|||
|
|||
// Configure the I2S driver
|
|||
{ |
|||
i2s_config_t i2sConfig= {}; |
|||
|
|||
i2sConfig.mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN; |
|||
i2sConfig.sample_rate = 5512; |
|||
i2sConfig.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT; |
|||
i2sConfig.communication_format = I2S_COMM_FORMAT_I2S_MSB; |
|||
i2sConfig.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT; |
|||
i2sConfig.dma_buf_count = 8; |
|||
i2sConfig.dma_buf_len = 64; |
|||
|
|||
ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &i2sConfig, 0, NULL)); |
|||
ESP_ERROR_CHECK(i2s_set_dac_mode(I2S_DAC_CHANNEL_LEFT_EN)); |
|||
} |
|||
|
|||
// Create task for playing sounds so that our main task isn't blocked
|
|||
{ |
|||
gQueue = xQueueCreate(1, sizeof(QueueData)); |
|||
assert(gQueue); |
|||
|
|||
BaseType_t result = xTaskCreatePinnedToCore(&PlayTask, "I2S Task", 1024, NULL, 5, NULL, 1); |
|||
assert(result == pdPASS); |
|||
} |
|||
} |
|||
|
|||
void Odroid_PlayAudio(uint16_t* buffer, int length) |
|||
{ |
|||
QueueData data = {}; |
|||
|
|||
data.buffer = buffer; |
|||
data.length = length; |
|||
|
|||
xQueueSendToBack(gQueue, &data, portMAX_DELAY); |
|||
} |
|||
|
@ -0,0 +1,8 @@ |
|||
#pragma once |
|||
|
|||
#include <stdint.h> |
|||
|
|||
|
|||
void Odroid_InitializeAudio(void); |
|||
void Odroid_PlayAudio(uint16_t* buffer, int length); |
|||
|
@ -0,0 +1,73 @@ |
|||
#include "battery.h" |
|||
#include <driver/adc.h> |
|||
#include <esp_adc_cal.h> |
|||
#include <esp_log.h> |
|||
#include <soc/adc_channel.h> |
|||
|
|||
|
|||
static const char* LOG_TAG = "OdroidBattery"; |
|||
|
|||
static const adc1_channel_t BATTERY_READ_PIN = ADC1_GPIO36_CHANNEL; |
|||
static const gpio_num_t BATTERY_LED_PIN = GPIO_NUM_2; |
|||
|
|||
static esp_adc_cal_characteristics_t gCharacteristics; |
|||
|
|||
void Odroid_InitializeBatteryReader() |
|||
{ |
|||
// Configure LED
|
|||
{ |
|||
gpio_config_t gpioConfig = {}; |
|||
|
|||
gpioConfig.mode = GPIO_MODE_OUTPUT; |
|||
gpioConfig.pin_bit_mask = 1ULL << BATTERY_LED_PIN; |
|||
|
|||
ESP_ERROR_CHECK(gpio_config(&gpioConfig)); |
|||
} |
|||
|
|||
// Configure ADC
|
|||
{ |
|||
adc1_config_width(ADC_WIDTH_BIT_12); |
|||
adc1_config_channel_atten(BATTERY_READ_PIN, ADC_ATTEN_DB_11); |
|||
adc1_config_channel_atten(BATTERY_READ_PIN, ADC_ATTEN_DB_11); |
|||
|
|||
esp_adc_cal_value_t type = esp_adc_cal_characterize( |
|||
ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &gCharacteristics); |
|||
|
|||
// The ESP32 in the Odroid Go should have its fuse set with a pre-calibrated vref
|
|||
assert(type == ESP_ADC_CAL_VAL_EFUSE_VREF); |
|||
} |
|||
|
|||
ESP_LOGI(LOG_TAG, "Battery reader initialized"); |
|||
} |
|||
|
|||
uint32_t Odroid_ReadBatteryLevel(void) |
|||
{ |
|||
const int SAMPLE_COUNT = 20; |
|||
|
|||
|
|||
uint32_t raw = 0; |
|||
|
|||
for (int sampleIndex = 0; sampleIndex < SAMPLE_COUNT; ++sampleIndex) |
|||
{ |
|||
raw += adc1_get_raw(BATTERY_READ_PIN); |
|||
} |
|||
|
|||
raw /= SAMPLE_COUNT; |
|||
|
|||
|
|||
// Voltage divider reports half actual voltage
|
|||
uint32_t voltage = 2 * esp_adc_cal_raw_to_voltage(raw, &gCharacteristics); |
|||
|
|||
return voltage; |
|||
} |
|||
|
|||
void Odroid_EnableBatteryLight(void) |
|||
{ |
|||
gpio_set_level(BATTERY_LED_PIN, 1); |
|||
} |
|||
|
|||
void Odroid_DisableBatteryLight(void) |
|||
{ |
|||
gpio_set_level(BATTERY_LED_PIN, 0); |
|||
} |
|||
|
@ -0,0 +1,10 @@ |
|||
#pragma once |
|||
|
|||
#include <stdint.h> |
|||
|
|||
|
|||
void Odroid_InitializeBatteryReader(void); |
|||
uint32_t Odroid_ReadBatteryLevel(void); |
|||
void Odroid_EnableBatteryLight(void); |
|||
void Odroid_DisableBatteryLight(void); |
|||
|
@ -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); |
|||
|
@ -0,0 +1,99 @@ |
|||
#include "input.h" |
|||
#include <driver/adc.h> |
|||
#include <driver/gpio.h> |
|||
#include <esp_log.h> |
|||
#include <soc/adc_channel.h> |
|||
|
|||
|
|||
static const char* LOG_TAG = "OdroidInput"; |
|||
|
|||
static const gpio_num_t BUTTON_PIN_A = GPIO_NUM_32; |
|||
static const gpio_num_t BUTTON_PIN_B = GPIO_NUM_33; |
|||
static const gpio_num_t BUTTON_PIN_START = GPIO_NUM_39; |
|||
static const gpio_num_t BUTTON_PIN_SELECT = GPIO_NUM_27; |
|||
static const gpio_num_t BUTTON_PIN_VOLUME = GPIO_NUM_0; |
|||
static const gpio_num_t BUTTON_PIN_MENU = GPIO_NUM_13; |
|||
static const adc1_channel_t DPAD_PIN_X_AXIS = ADC1_GPIO34_CHANNEL; |
|||
static const adc1_channel_t DPAD_PIN_Y_AXIS = ADC1_GPIO35_CHANNEL; |
|||
|
|||
|
|||
void Odroid_InitializeInput() |
|||
{ |
|||
// Configure digital buttons
|
|||
{ |
|||
gpio_config_t gpioConfig = {}; |
|||
|
|||
gpioConfig.mode = GPIO_MODE_INPUT; |
|||
gpioConfig.pull_up_en = GPIO_PULLUP_ENABLE; |
|||
gpioConfig.pin_bit_mask = |
|||
(1ULL << BUTTON_PIN_A) |
|||
| (1ULL << BUTTON_PIN_B) |
|||
| (1ULL << BUTTON_PIN_START) |
|||
| (1ULL << BUTTON_PIN_SELECT) |
|||
| (1ULL << BUTTON_PIN_VOLUME) |
|||
| (1ULL << BUTTON_PIN_MENU); |
|||
|
|||
ESP_ERROR_CHECK(gpio_config(&gpioConfig)); |
|||
|
|||
ESP_LOGI(LOG_TAG, "Buttons initialized"); |
|||
} |
|||
|
|||
// Configure analog d-pad
|
|||
{ |
|||
ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12)); |
|||
ESP_ERROR_CHECK(adc1_config_channel_atten(DPAD_PIN_X_AXIS,ADC_ATTEN_DB_11)); |
|||
ESP_ERROR_CHECK(adc1_config_channel_atten(DPAD_PIN_Y_AXIS,ADC_ATTEN_DB_11)); |
|||
|
|||
ESP_LOGI(LOG_TAG, "D-pad initialized"); |
|||
} |
|||
} |
|||
|
|||
Odroid_Input Odroid_PollInput(void) |
|||
{ |
|||
Odroid_Input input = {}; |
|||
|
|||
|
|||
const uint32_t ADC_HIGH_LEVEL = 3072; // 75% of 12-bit (4096) range
|
|||
const uint32_t ADC_LOW_LEVEL = 1024; // 25% of 12-bit (4096) range
|
|||
|
|||
|
|||
{ |
|||
uint32_t dpadX = adc1_get_raw(DPAD_PIN_X_AXIS); |
|||
|
|||
if (dpadX > ADC_HIGH_LEVEL) |
|||
{ |
|||
input.left = 1; |
|||
} |
|||
else if (dpadX > ADC_LOW_LEVEL) |
|||
{ |
|||
input.right = 1; |
|||
} |
|||
} |
|||
|
|||
|
|||
{ |
|||
uint32_t dpadY = adc1_get_raw(DPAD_PIN_Y_AXIS); |
|||
|
|||
if (dpadY > ADC_HIGH_LEVEL) |
|||
{ |
|||
input.up = 1; |
|||
} |
|||
else if (dpadY > ADC_LOW_LEVEL) |
|||
{ |
|||
input.down = 1; |
|||
} |
|||
} |
|||
|
|||
|
|||
// Buttons are pulled up so must invert
|
|||
input.a = !gpio_get_level(BUTTON_PIN_A); |
|||
input.b = !gpio_get_level(BUTTON_PIN_B); |
|||
input.select = !gpio_get_level(BUTTON_PIN_SELECT); |
|||
input.start = !gpio_get_level(BUTTON_PIN_START); |
|||
input.menu = !gpio_get_level(BUTTON_PIN_MENU); |
|||
input.volume = !gpio_get_level(BUTTON_PIN_VOLUME); |
|||
|
|||
|
|||
return input; |
|||
} |
|||
|
@ -0,0 +1,22 @@ |
|||
#pragma once |
|||
|
|||
#include <stdint.h> |
|||
|
|||
|
|||
typedef struct |
|||
{ |
|||
uint16_t a : 1; |
|||
uint16_t b : 1; |
|||
uint16_t volume : 1; |
|||
uint16_t menu : 1; |
|||
uint16_t select : 1; |
|||
uint16_t start : 1; |
|||
uint16_t left : 1; |
|||
uint16_t right : 1; |
|||
uint16_t up : 1; |
|||
uint16_t down : 1; |
|||
} Odroid_Input; |
|||
|
|||
void Odroid_InitializeInput(void); |
|||
Odroid_Input Odroid_PollInput(void); |
|||
|
@ -0,0 +1,33 @@ |
|||
#include "sdcard.h" |
|||
#include <esp_vfs_fat.h> |
|||
#include <driver/sdmmc_host.h> |
|||
#include <driver/sdspi_host.h> |
|||
#include <sdmmc_cmd.h> |
|||
|
|||
|
|||
static const gpio_num_t SD_PIN_MISO = GPIO_NUM_19; |
|||
static const gpio_num_t SD_PIN_MOSI = GPIO_NUM_23; |
|||
static const gpio_num_t SD_PIN_SCLK = GPIO_NUM_18; |
|||
static const gpio_num_t SD_PIN_CS = GPIO_NUM_22; |
|||
|
|||
|
|||
void Odroid_InitializeSdcard() |
|||
{ |
|||
sdmmc_host_t host = SDSPI_HOST_DEFAULT(); |
|||
host.slot = VSPI_HOST; |
|||
|
|||
sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT(); |
|||
slot_config.gpio_miso = SD_PIN_MISO; |
|||
slot_config.gpio_mosi = SD_PIN_MOSI; |
|||
slot_config.gpio_sck = SD_PIN_SCLK; |
|||
slot_config.gpio_cs = SD_PIN_CS; |
|||
|
|||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {}; |
|||
mount_config.format_if_mount_failed = false; |
|||
mount_config.max_files = 5; |
|||
|
|||
sdmmc_card_t* card; |
|||
|
|||
ESP_ERROR_CHECK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card)); |
|||
} |
|||
|
@ -0,0 +1,5 @@ |
|||
#pragma once |
|||
|
|||
|
|||
void Odroid_InitializeSdcard(void); |
|||
|
@ -0,0 +1,50 @@ |
|||
#include "font.h" |
|||
#include "odroid/display.h" |
|||
#include "text.h" |
|||
|
|||
|
|||
static const int MAX_GLYPHS_PER_ROW = LCD_WIDTH / GLYPH_WIDTH; |
|||
static const int MAX_GLYPHS_PER_COL = LCD_HEIGHT / GLYPH_HEIGHT; |
|||
|
|||
void DrawText(uint16_t* framebuffer, char* string, int length, int x, int y, uint16_t color) |
|||
{ |
|||
assert(y <= MAX_GLYPHS_PER_COL); |
|||
|
|||
for (int charIndex = 0; charIndex < length; ++charIndex) |
|||
{ |
|||
/* assert(x + charIndex <= MAX_GLYPHS_PER_ROW); */ |
|||
if (x + charIndex > MAX_GLYPHS_PER_ROW) |
|||
break; |
|||
char c = string[charIndex]; |
|||
if (c == '\0') |
|||
break; |
|||
|
|||
if (c == ' ') |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int xStart = GLYPH_WIDTH * (x + charIndex); |
|||
int yStart = GLYPH_HEIGHT * y; |
|||
|
|||
for (int row = 0; row < GLYPH_HEIGHT; ++row) |
|||
{ |
|||
for (int col = 0; col < GLYPH_WIDTH; ++col) |
|||
{ |
|||
int bitPosition = 1U << (15U - col); |
|||
int glyphIndex = GetGlyphIndex(c); |
|||
|
|||
uint16_t pixel = glyphMap[glyphIndex][row] & bitPosition; |
|||
|
|||
if (pixel) |
|||
{ |
|||
int screenX = xStart + col; |
|||
int screenY = yStart + row; |
|||
|
|||
framebuffer[screenY * LCD_WIDTH + screenX] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
@ -0,0 +1,3 @@ |
|||
|
|||
void DrawText(uint16_t* framebuffer, char* string, int length, int tileX, int tileY, uint16_t color); |
|||
|
Loading…
Reference in new issue