Browse Source

Used Austin Morlan's code to get a good base

master
Macoy Madson 3 years ago
parent
commit
77125e3177
  1. 2
      .gitignore
  2. 3
      .gitmodules
  3. 1
      Dependencies/embedded-game-programming
  4. 48
      ReadMe.org
  5. 9
      app/CMakeLists.txt
  6. 24
      app/export.sh
  7. 13
      app/sdkconfig.defaults
  8. 17
      app/src/CMakeLists.txt
  9. 387
      app/src/font.h
  10. 21
      app/src/macros.h
  11. 274
      app/src/main.c
  12. 85
      app/src/odroid/audio.c
  13. 8
      app/src/odroid/audio.h
  14. 73
      app/src/odroid/battery.c
  15. 10
      app/src/odroid/battery.h
  16. 184
      app/src/odroid/display.c
  17. 13
      app/src/odroid/display.h
  18. 99
      app/src/odroid/input.c
  19. 22
      app/src/odroid/input.h
  20. 33
      app/src/odroid/sdcard.c
  21. 5
      app/src/odroid/sdcard.h
  22. 50
      app/src/text.c
  23. 3
      app/src/text.h

2
.gitignore

@ -32,3 +32,5 @@
*.out
*.app
sdkconfig
app/build

3
.gitmodules

@ -4,3 +4,6 @@
[submodule "Dependencies/crosstool-NG"]
path = Dependencies/crosstool-NG
url = https://github.com/espressif/crosstool-NG.git
[submodule "Dependencies/embedded-game-programming"]
path = Dependencies/embedded-game-programming
url = https://code.austinmorlan.com/austin/embedded-game-programming

1
Dependencies/embedded-game-programming

@ -0,0 +1 @@
Subproject commit 1096c7eac2d69a0fc90ad06b0b454c6091d542c0

48
ReadMe.org

@ -5,7 +5,6 @@ A timing app designed to run on my Odroid-GO.
* Setting up ESP-IDF
[[https://docs.espressif.com/projects/esp-idf/en/latest/][See docs]].
The following is to set up the toolchain by building as much of it from source as the docs have specified. While I could download a binary, I like the idea of compiling from source in case their website goes down. I'm doing this on Ubuntu 18.04.
#+BEGIN_SRC sh
# Dependencies
sudo apt-get install git wget libncurses-dev flex bison gperf python python-pip python-setuptools python-serial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja-build ccache libffi-dev libssl-dev gawk gperf grep gettext python python-dev automake bison flex texinfo help2man libtool libtool-bin make
@ -13,7 +12,35 @@ sudo apt-get install git wget libncurses-dev flex bison gperf python python-pip
# Submodules
cd Dependencies/esp-idf/
git submodule update --init
#+END_SRC
Next, build the toolchain:
#+BEGIN_SRC sh
cd Dependencies/esp-idf/
./install.sh
#+END_SRC
** Possibly necessary step
I'm not sure this is necessary. [[https://code.austinmorlan.com/austin/embedded-game-programming][The readme recommends doing it]].
Comment out line 981 of ~esp-idf/components/driver/sdspi_host.c~ to enable the shared SPI bus:
#+BEGIN_SRC C
// Initialize SPI bus
esp_err_t ret = spi_bus_initialize((spi_host_device_t)slot, &buscfg,
slot_config->dma_channel);
if (ret != ESP_OK) {
ESP_LOGD(TAG, "spi_bus_initialize failed with rc=0x%x", ret);
//return ret;
}
#+END_SRC
** Building compiler from source
The following is to set up the toolchain by building as much of it from source as the docs have specified. While I could download a binary, I like the idea of compiling from source in case their website goes down. I'm doing this on Ubuntu 18.04.
TODO: This doesn't work yet.
#+BEGIN_SRC sh
# Crosstool - this builds GCC for ESP32. This will take a while (27 minutes in my case)
cd ../crosstool-NG/
git submodule update --init
@ -29,3 +56,22 @@ cd Dependencies/esp-idf/
# TODO: This doesn't actually use the GCC I built, nor the tools path...
./install.sh
#+END_SRC
* Building the app
Build and flash the application to the Odroid, which should be connected via USB and powered on:
#+BEGIN_SRC sh
cd Dependencies/esp-idf/
. ./export.sh
cd ../../app
idf.py build && idf.py -p /dev/ttyUSB0 flash
#+END_SRC
Monitor, if it doesn't work:
#+BEGIN_SRC sh
idf.py -p /dev/ttyUSB0 monitor
#+END_SRC
* Source
Thanks to [[https://austinmorlan.com/][Austin Morlan]] for his Odroid Go code (MIT license), some of which I have modified.
I found it much easier to get working than [[https://github.com/OtherCrashOverride/odroid-go-firmware][Odroid-Go-Firmware]] (note: different version of that firmware [[https://github.com/OtherCrashOverride/go-play][here]]).

9
app/CMakeLists.txt

@ -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)

24
app/export.sh

@ -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

13
app/sdkconfig.defaults

@ -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

17
app/src/CMakeLists.txt

@ -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")

387
app/src/font.h

@ -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,
},
};

21
app/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 )

274
app/src/main.c

@ -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();
}

85
app/src/odroid/audio.c

@ -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);
}

8
app/src/odroid/audio.h

@ -0,0 +1,8 @@
#pragma once
#include <stdint.h>
void Odroid_InitializeAudio(void);
void Odroid_PlayAudio(uint16_t* buffer, int length);

73
app/src/odroid/battery.c

@ -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);
}

10
app/src/odroid/battery.h

@ -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);

184
app/src/odroid/display.c

@ -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);
}

13
app/src/odroid/display.h

@ -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);

99
app/src/odroid/input.c

@ -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;
}

22
app/src/odroid/input.h

@ -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);

33
app/src/odroid/sdcard.c

@ -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));
}

5
app/src/odroid/sdcard.h

@ -0,0 +1,5 @@
#pragma once
void Odroid_InitializeSdcard(void);

50
app/src/text.c

@ -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;
}
}
}
}
}

3
app/src/text.h

@ -0,0 +1,3 @@
void DrawText(uint16_t* framebuffer, char* string, int length, int tileX, int tileY, uint16_t color);
Loading…
Cancel
Save