A timing app designed to run on my Odroid-GO
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

411 lines
11 KiB

#include <esp_log.h>
#include <esp_timer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <string.h>
#include "macros.h"
#include "odroid/audio.h"
#include "odroid/battery.h"
#include "odroid/display.h"
#include "odroid/input.h"
#include "odroid/sdcard.h"
#include "odroid/sleep.h"
#include "text.h"
#define ArraySize(array) sizeof((array)) / sizeof((array)[0])
static bool drawBatteryVoltage = false;
static bool drawBatteryPercent = true;
static bool drawTimeSinceBoot = false;
static bool drawFrameRate = true;
/* static bool printInput = true; */
static bool printInput = false;
static bool enableLowFrameRate = true;
static int64_t autoSleepModeMicroseconds = 10 * 1000000;
static int64_t lightSleepMicroseconds = 30 * 1000000;
static const char* LOG_TAG = "Main";
static uint16_t gFramebuffer[LCD_WIDTH * LCD_HEIGHT];
/* static const uint16_t lightPalette[4] = */
/* { */
/* 0xFFFF, */
/* 0x55AD, */
/* 0xAA52, */
/* 0x0000, */
/* }; */
static const uint16_t darkPalette[4] =
{
0x0000,
0xAA52,
0x55AD,
0xFFFF,
};
static const uint16_t* palette = darkPalette;
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();
// Does not work for me yet
/* 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; */
bool timerRunning = false;
int64_t timerStartMicroSecs = 0;
// Includes timings before pause, not current timing
int64_t timerTotalMicroSecsPrePause = 0;
int64_t lastFrameTimeMicroSecs = esp_timer_get_time();
int64_t timeSinceLastInputMicroSecs = esp_timer_get_time();
Odroid_Input lastFrameInput = {0};
bool sleepMode = false;
bool backlightOn = true;
bool waitForSleepActivateRelease = false;
while (true)
{
memset(gFramebuffer, palette[0], 320 * 240 * 2);
{
float batteryPercentage = Odroid_ReadBatteryLevelPercentage();
if (batteryPercentage < 15.f)
Odroid_EnableBatteryLight();
else
Odroid_DisableBatteryLight();
}
int64_t currentTimeMicroSecs = esp_timer_get_time();
Odroid_Input input = Odroid_PollInput();
bool hasInputsThisFrame = Odroid_HasAnyInput(&input);
if (hasInputsThisFrame)
timeSinceLastInputMicroSecs = esp_timer_get_time();
if (printInput)
Odroid_PrintInputState(&input);
// Sleep mode
if (sleepMode)
{
// Try to save battery while still listening to inputs
while (Odroid_EnterLightSleep(lightSleepMicroseconds))
{
float batteryPercentage = Odroid_ReadBatteryLevelPercentage();
if (batteryPercentage < 15.f)
Odroid_EnableBatteryLight();
else
Odroid_DisableBatteryLight();
}
sleepMode = false;
timeSinceLastInputMicroSecs = esp_timer_get_time();
continue;
}
if (!sleepMode && !backlightOn)
{
Odroid_BacklightInit();
backlightOn = true;
}
if (input.a && !lastFrameInput.a)
{
printf("Pause timer\n");
timerRunning = !timerRunning;
if (timerRunning)
timerStartMicroSecs = currentTimeMicroSecs;
else
timerTotalMicroSecsPrePause += currentTimeMicroSecs - timerStartMicroSecs;
/* esp_err_t err = ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0); */
/* if (err != ESP_OK) */
/* { */
/* printf("%s: ledc_stop failed.\n", __func__); */
/* } */
}
if (input.start && !lastFrameInput.start)
{
printf("Reset timer\n");
timerRunning = false;
timerTotalMicroSecsPrePause = 0;
}
bool sleepModeActivate =
currentTimeMicroSecs - timeSinceLastInputMicroSecs > autoSleepModeMicroseconds ||
(input.b && !lastFrameInput.b);
if (sleepModeActivate)
{
if (backlightOn)
{
// TODO: Tell LCD driver to enter sleep
// TODO: Use esp light sleep mode
Odroid_BacklightDeinit();
}
else
Odroid_BacklightInit();
backlightOn = !backlightOn;
sleepMode = !sleepMode;
}
// 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 (drawBatteryPercent)
{
char string[10];
snprintf(string, ArraySize(string), "B: %.0f", Odroid_ReadBatteryLevelPercentage());
DrawText(gFramebuffer, string, ArraySize(string), 0, 0, palette[3]);
}
if (drawBatteryVoltage)
{
char string[10];
snprintf(string, ArraySize(string), "B: %03d", Odroid_ReadBatteryLevel());
DrawText(gFramebuffer, string, ArraySize(string), 0, 3, 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;
}
// Timer
{
char string[18];
int64_t currentTimeMicroSecs = esp_timer_get_time();
float timerSeconds =
timerRunning ?
((timerTotalMicroSecsPrePause + (currentTimeMicroSecs - timerStartMicroSecs)) /
1000000.f) :
timerTotalMicroSecsPrePause / 1000000.f;
snprintf(string, ArraySize(string), "%d:%.2d", (int)timerSeconds / 60,
(int)timerSeconds % 60);
DrawText(gFramebuffer, string, ArraySize(string), 3, 5, palette[3]);
if (!timerRunning)
{
snprintf(string, ArraySize(string), "Paused");
DrawText(gFramebuffer, string, ArraySize(string), 3, 6, palette[3]);
}
}
// 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);
// Just high enough that button presses feel responsive
if (enableLowFrameRate)
vTaskDelay(5);
lastFrameInput = input;
}
// Should never get here
esp_restart();
}