Skip to content

Commit

Permalink
sdk: move buzzer startup sound to buzzer module (#96)
Browse files Browse the repository at this point in the history
sdk: disable I2S ping message
sdk: apply screen rotation early
sdk: add Display::drawTextAligned
sdk: add monospaced fonts
sdk: reduce sprintf buffer size in serial
sdk: cleanup controller code
doom: init audio pins on startup
keira: allow apps to select which core to run on
global: fix clang-format search in case the binary is called clang-format-18
  • Loading branch information
and3rson authored Apr 9, 2024
1 parent 7bffdc4 commit f1acff1
Show file tree
Hide file tree
Showing 14 changed files with 168 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Run clang-format
run: |
apt update -y
apt install -y make clang-format
apt install -y make clang-format-18
make clang-format
cppcheck:
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
IMAGE2CODE = ./sdk/tools/image2code/image2code.py
CPPCHECK = cppcheck
CLANG_FORMAT = clang-format
CPPCHECK ?= cppcheck
CLANG_FORMAT ?= $(shell command -v clang-format-18 2>/dev/null || echo clang-format)

help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-16s\033[0m %s\n", $$1, $$2}'
Expand Down Expand Up @@ -49,7 +49,7 @@ check-docker: ## Run all checks in docker
docker build -t lilka-check -f - . <<EOF
FROM ubuntu:24.04
RUN apt-get update -y && \
apt-get install -y clang-format cppcheck findutils grep make
apt-get install -y clang-format-18 cppcheck findutils grep make
EOF
docker run --rm -it -v $(PWD):/lilka -w /lilka lilka-check make check

Expand Down
1 change: 1 addition & 0 deletions firmware/doom/src/i_i2ssound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ static boolean32 I_I2S_InitSound(boolean32 _use_sfx_prefix) {
xSemaphoreTake(
backBufferMutex, portMAX_DELAY
); // Acquire back buffer mutex to prevent drawing while initializing I2S
lilka::audio.initPins();
esp_i2s::i2s_config_t cfg = {
.mode = (esp_i2s::i2s_mode_t)(esp_i2s::I2S_MODE_MASTER | esp_i2s::I2S_MODE_TX),
.sample_rate = 11025,
Expand Down
12 changes: 9 additions & 3 deletions firmware/keira/src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ App::App(const char* name, uint16_t x, uint16_t y, uint16_t w, uint16_t h) :
backCanvas(new lilka::Canvas(x, y, w, h)),
isDrawQueued(false),
backCanvasMutex(xSemaphoreCreateMutex()),
stackSize(8192) {
stackSize(8192),
appCore(0) {
// Clear buffers
canvas->fillScreen(0);
backCanvas->fillScreen(0);
Serial.println(
"Created app " + String(name) + " at " + String(x) + ", " + String(y) + " with size " + String(w) + "x" +
String(h)
String(h) + " on core " + String(appCore)
);
xSemaphoreGive(backCanvasMutex);
}

void App::start() {
Expand All @@ -27,7 +29,7 @@ void App::start() {
return;
}
Serial.println("Starting app " + String(name));
if (xTaskCreatePinnedToCore(_run, name, stackSize, this, 1, &taskHandle, 0) != pdPASS) {
if (xTaskCreatePinnedToCore(_run, name, stackSize, this, 1, &taskHandle, appCore) != pdPASS) {
Serial.println(
"Failed to create task for app " + String(name) +
" - not enough memory? Try increasing stack size with setStackSize()"
Expand Down Expand Up @@ -92,6 +94,10 @@ void App::queueDraw() {
taskYIELD();
}

void App::setCore(int appCore) {
this->appCore = appCore;
}

void App::setFlags(AppFlags flags) {
this->flags = flags;
}
Expand Down
4 changes: 3 additions & 1 deletion firmware/keira/src/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class App {
void suspend();
void resume();
void stop();
void setCore(int appCore);
/// Встановити прапорці додатку.
///
/// Наприклад, якщо додаток має відображатися на весь екран, то слід викликати setFlags(APP_FLAG_FULLSCREEN).
Expand All @@ -68,7 +69,7 @@ class App {
AppFlags getFlags();
/// Встановити розмір стеку задачі додатку.
///
/// За замовчуванням, розмір стеку задачі дорівнює 16384 байт. Проте деякі додатки можуть вимагати більший розмір стеку.
/// За замовчуванням, розмір стеку задачі дорівнює 8192 байт. Проте деякі додатки можуть вимагати більший розмір стеку.
void setStackSize(uint32_t stackSize);

bool needsRedraw();
Expand Down Expand Up @@ -101,6 +102,7 @@ class App {
const char* name;
SemaphoreHandle_t backCanvasMutex;
bool isDrawQueued;
int appCore;
AppFlags flags;
TaskHandle_t taskHandle;
uint32_t stackSize;
Expand Down
1 change: 1 addition & 0 deletions firmware/keira/src/appmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ AppManager* AppManager::instance = NULL;

AppManager::AppManager() :
mutex(xSemaphoreCreateMutex()), panel(NULL), toastMessage(""), toastStartTime(0), toastEndTime(0) {
xSemaphoreGive(mutex);
}

AppManager::~AppManager() {
Expand Down
34 changes: 18 additions & 16 deletions sdk/lib/lilka/src/lilka/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ void Audio::begin() {
serial_err("Audio not supported in this version of lilka. Try to use Buzzer instead");
#elif LILKA_VERSION == 2

initPins();

I2S.setAllPins(LILKA_I2S_BCLK, LILKA_I2S_LRCK, LILKA_I2S_DOUT, LILKA_I2S_DOUT, -1);

xTaskCreatePinnedToCore(ping_task, "ping_task", 4096, NULL, 1, NULL, 0);
#endif
}

void Audio::initPins() {
// Set up I2S pins globally
constexpr uint8_t pinCount = 3;
uint8_t pins[pinCount] = {LILKA_I2S_BCLK, LILKA_I2S_LRCK, LILKA_I2S_DOUT};
Expand All @@ -24,29 +33,22 @@ void Audio::begin() {
gpio_set_direction((gpio_num_t)pins[i], GPIO_MODE_OUTPUT);
gpio_matrix_out(pins[i], funcs[i], false, false);
}

I2S.setAllPins(LILKA_I2S_BCLK, LILKA_I2S_LRCK, LILKA_I2S_DOUT, LILKA_I2S_DOUT, -1);

I2S.begin(I2S_PHILIPS_MODE, 22050, 16);

xTaskCreatePinnedToCore(ping_task, "ping_task", 4096, NULL, 1, NULL, 0);
#endif
}

void ping_task(void* arg) {
#if LILKA_VERSION == 1
serial_err("This part of code should never be called. Audio not supported for this version of lilka");
#elif LILKA_VERSION == 2
// Signed 16-bit PCM
const int16_t* ping = reinterpret_cast<const int16_t*>(ping_raw);

for (int i = 0; i < ping_raw_size / 2; i++) {
// TODO: Should use i2s_write & DMA
I2S.write(ping[i] >> 2);
I2S.write(ping[i] >> 2);
}

I2S.end();
// const int16_t* ping = reinterpret_cast<const int16_t*>(ping_raw);

// I2S.begin(I2S_PHILIPS_MODE, 22050, 16);
// for (int i = 0; i < ping_raw_size / 2; i++) {
// // TODO: Should use i2s_write & DMA
// I2S.write(ping[i] >> 2);
// I2S.write(ping[i] >> 2);
// }
// I2S.end();

vTaskDelete(NULL);
#endif
Expand Down
3 changes: 3 additions & 0 deletions sdk/lib/lilka/src/lilka/audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class Audio {
/// Налаштоувує піни для I2S і відтворює тестовий звук.
/// \warning Цей метод викликається автоматично при виклику `lilka::begin()`.
void begin();
/// Налаштувує піни для I2S.
/// Цей метод варто викликати перед викликом `i2s_driver_install()`.
void initPins();
};

extern Audio audio;
Expand Down
2 changes: 2 additions & 0 deletions sdk/lib/lilka/src/lilka/buzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ void Buzzer::begin() {
#else
_stop();
pinMode(LILKA_BUZZER, OUTPUT);
const Tone helloTune[] = {{NOTE_C3, 8}, {NOTE_C4, 8}, {NOTE_C5, 8}, {NOTE_C7, 4}, {0, 8}, {NOTE_C6, 4}};
playMelody(helloTune, sizeof(helloTune) / sizeof(Tone), 160);
#endif
}

Expand Down
127 changes: 61 additions & 66 deletions sdk/lib/lilka/src/lilka/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,21 @@

namespace lilka {

SemaphoreHandle_t Controller::semaphore = NULL;
class AcquireController {
public:
explicit AcquireController(SemaphoreHandle_t semaphore) {
this->semaphore = semaphore;
xSemaphoreTakeRecursive(semaphore, portMAX_DELAY);
}
~AcquireController() {
xSemaphoreGiveRecursive(semaphore);
}

private:
SemaphoreHandle_t semaphore;
};

Controller::Controller() : state{} {
Controller::Controller() : state{}, semaphore(xSemaphoreCreateRecursiveMutex()) {
for (int i = 0; i < Button::COUNT; i++) {
_StateButtons& buttons = *reinterpret_cast<_StateButtons*>(&state);

Expand All @@ -20,55 +32,59 @@ Controller::Controller() : state{} {
.time = 0,
};
}
_clearHandlers();
xSemaphoreGive(semaphore);
clearHandlers();
}

void Controller::inputTask(void* arg) {
Controller* self = static_cast<Controller*>(arg);
void Controller::inputTask() {
while (1) {
xSemaphoreTake(self->semaphore, portMAX_DELAY);
for (int i = 0; i < Button::COUNT; i++) {
if (i == Button::ANY) {
// Skip "any" key since its state is computed from other keys
continue;
}
_StateButtons& buttons = *reinterpret_cast<_StateButtons*>(&self->state);
ButtonState* state = &buttons[i];
if (self->pins[i] < 0) {
continue;
}
if (millis() - state->time < LILKA_DEBOUNCE_TIME) {
continue;
}
bool pressed = !digitalRead(self->pins[i]);
if (pressed != state->pressed) {
state->pressed = pressed;
state->justPressed = pressed;
state->justReleased = !pressed;
self->state.any.pressed = pressed;
self->state.any.justPressed = self->state.any.justPressed || pressed;
self->state.any.justReleased = self->state.any.justReleased || !pressed;
if (self->handlers[i] != NULL) {
self->handlers[i](pressed);
{
AcquireController acquire(semaphore);
for (int i = 0; i < Button::COUNT; i++) {
if (i == Button::ANY) {
// Skip "any" key since its state is computed from other keys
continue;
}
_StateButtons& buttons = *reinterpret_cast<_StateButtons*>(&state);
ButtonState* buttonState = &buttons[i];
if (pins[i] < 0) {
continue;
}
if (millis() - buttonState->time < LILKA_DEBOUNCE_TIME) {
continue;
}
if (self->globalHandler != NULL) {
self->globalHandler((Button)i, pressed);
bool pressed = !digitalRead(pins[i]);
if (pressed != buttonState->pressed) {
buttonState->pressed = pressed;
buttonState->justPressed = pressed;
buttonState->justReleased = !pressed;
state.any.pressed = pressed;
state.any.justPressed = state.any.justPressed || pressed;
state.any.justReleased = state.any.justReleased || !pressed;
if (handlers[i] != NULL) {
handlers[i](pressed);
}
if (globalHandler != NULL) {
globalHandler((Button)i, pressed);
}
buttonState->time = millis();
}
state->time = millis();
}
}
// Handle "any" key

xSemaphoreGive(self->semaphore);
// Sleep for 5ms
vTaskDelay(5 / portTICK_PERIOD_MS);
}
}

void Controller::resetState() {
xSemaphoreTake(semaphore, portMAX_DELAY);
_resetState();
xSemaphoreGive(semaphore);
AcquireController acquire(semaphore);
for (int i = 0; i < Button::COUNT; i++) {
_StateButtons& buttons = *reinterpret_cast<_StateButtons*>(&state);
ButtonState* buttonState = &buttons[i];
buttonState->justPressed = false;
buttonState->justReleased = false;
}
}

void Controller::begin() {
Expand All @@ -93,56 +109,35 @@ void Controller::begin() {
}

// Create RTOS task for handling button presses
semaphore = xSemaphoreCreateBinary();
xSemaphoreGive(semaphore);
xTaskCreate(Controller::inputTask, "input", 2048, this, 1, NULL);
xTaskCreate([](void* arg) { static_cast<Controller*>(arg)->inputTask(); }, "input", 2048, this, 1, NULL);

serial_log("controller ready");
}

State Controller::getState() {
xSemaphoreTake(semaphore, portMAX_DELAY);
AcquireController acquire(semaphore);
State _current = state;
_resetState();
xSemaphoreGive(semaphore);
resetState();
return _current;
}

State Controller::peekState() {
xSemaphoreTake(semaphore, portMAX_DELAY);
State _current = state;
xSemaphoreGive(semaphore);
return _current;
}

void Controller::_resetState() {
for (int i = 0; i < Button::COUNT; i++) {
_StateButtons& buttons = *reinterpret_cast<_StateButtons*>(&state);
ButtonState* buttonState = &buttons[i];
buttonState->justPressed = false;
buttonState->justReleased = false;
}
AcquireController acquire(semaphore);
return state;
}

void Controller::setGlobalHandler(void (*handler)(Button, bool)) {
xSemaphoreTake(semaphore, portMAX_DELAY);
AcquireController acquire(semaphore);
globalHandler = handler;
xSemaphoreGive(semaphore);
}

void Controller::setHandler(Button button, void (*handler)(bool)) {
xSemaphoreTake(semaphore, portMAX_DELAY);
AcquireController acquire(semaphore);
handlers[button] = handler;
xSemaphoreGive(semaphore);
}

void Controller::clearHandlers() {
xSemaphoreTake(semaphore, portMAX_DELAY);
_clearHandlers();
xSemaphoreGive(semaphore);
}

void Controller::_clearHandlers() {
AcquireController acquire(semaphore);
for (int i = 0; i < Button::COUNT; i++) {
handlers[i] = NULL;
}
Expand Down
6 changes: 2 additions & 4 deletions sdk/lib/lilka/src/lilka/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,8 @@ class Controller {

private:
// Input task FreeRTOS semaphore
static SemaphoreHandle_t semaphore;
static void inputTask(void* self);
void _resetState();
void _clearHandlers();
SemaphoreHandle_t semaphore;
void inputTask();
State state;
int8_t pins[Button::COUNT] = {
LILKA_GPIO_UP,
Expand Down
Loading

0 comments on commit f1acff1

Please sign in to comment.