diff --git a/desktop/Makefile.ems b/desktop/Makefile.ems index 68526ab..8c97da8 100644 --- a/desktop/Makefile.ems +++ b/desktop/Makefile.ems @@ -48,7 +48,7 @@ all: $(TARGET) # Create executable from object files $(TARGET): $(OBJS) Makefile - $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) `sdl2-config --libs` $(LDFLAGS) + $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) `sdl2-config --libs` $(LDFLAGS) --embed-file filesystem # Object file rules (object files are placed next to the source files) %.o: %.cpp Makefile diff --git a/desktop/src/main.mm b/desktop/src/main.mm index c5f8529..fcc27ee 100644 --- a/desktop/src/main.mm +++ b/desktop/src/main.mm @@ -380,13 +380,9 @@ int main() return -1; } - audioOutput = new SDLAudioOutput(machine); - audioOutput->start(15600); - // run the spectrum for 4 second so that it boots up int start = SDL_GetTicks(); - #ifndef __EMSCRIPTEN__ for(int i = 0; i < 200; i++) { machine->runForFrame(nullptr, nullptr); } @@ -402,9 +398,14 @@ int main() int end = SDL_GetTicks(); std::cout << "Time to boot spectrum: " << end - start << "ms" << std::endl; // load a tap file + #if __EMSCRIPTEN__ + std::string filename = "filesystem/manic.z80"; + #else std::string filename = OpenFileDialog(); - loadGame(filename); #endif + loadGame(filename); + audioOutput = new SDLAudioOutput(machine); + audioOutput->start(15600); isRunning = true; #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(main_loop, 50, 1); diff --git a/firmware/platformio.ini b/firmware/platformio.ini index 55af26e..2c2d4f7 100644 --- a/firmware/platformio.ini +++ b/firmware/platformio.ini @@ -21,6 +21,7 @@ build_flags = -Wl,-Map,output.map build_unflags = -std=gnu++11 + -fno-rtti lib_deps = SPI diff --git a/firmware/src/Emulator/spectrum.h b/firmware/src/Emulator/spectrum.h index 13b2755..561fa5a 100644 --- a/firmware/src/Emulator/spectrum.h +++ b/firmware/src/Emulator/spectrum.h @@ -138,6 +138,8 @@ class ZXSpectrum uint8_t ulaport_FF = 0xFF; bool micLevel = false; uint8_t borderColors[312] = {0}; + // indicates that the ROM loading routine is active + bool romLoadingRoutineHit = false; ZXSpectrum(); void reset(); diff --git a/firmware/src/Emulator/z80/z80.cpp b/firmware/src/Emulator/z80/z80.cpp index e6419de..982796c 100644 --- a/firmware/src/Emulator/z80/z80.cpp +++ b/firmware/src/Emulator/z80/z80.cpp @@ -176,9 +176,13 @@ uint16_t Z80Run(Z80Regs *regs, int numcycles) micValue += spectrum->hwopt.SoundBits != 0 ? totalCycles : 0; /* patch ROM loading routine */ // address contributed by Ignacio BurgueƱo :) - // if (r_PC >= 0x0556 && r_PC <= 0x056c) { + if (r_PC >= 0x04C2 && r_PC < 0x09F4) { + // set a flag to indicate that the ROM loading routine has been hit + spectrum->romLoadingRoutineHit = true; // printf("ROM loading routine hit\n"); - // } + } else { + spectrum->romLoadingRoutineHit = false; + } } return micValue; } diff --git a/firmware/src/Files/Files.h b/firmware/src/Files/Files.h index 2593f6e..6698f68 100644 --- a/firmware/src/Files/Files.h +++ b/firmware/src/Files/Files.h @@ -189,6 +189,16 @@ class FileInfo : title(title), path(path) {} std::string getTitle() const { return title; } std::string getPath() const { return path; } + std::string getExtension() const + { + size_t pos = title.find_last_of('.'); + if (pos != std::string::npos) + { + std::string extension = title.substr(pos); + return StringUtils::downcase(extension); + } + return ""; + } private: std::string title; @@ -220,14 +230,23 @@ using FileLetterCountPtr = std::shared_ptr; using FileLetterCountVector = std::vector; // Files class to list files in a directory +class IFiles +{ +public: + virtual bool isAvailable() = 0; + virtual void createDirectory(const char *folder) = 0; + virtual FileLetterCountVector getFileLetters(const char *folder, const std::vector &extensions) = 0; + virtual FileInfoVector getFileStartingWithPrefix(const char *folder, const char *prefix, const std::vector &extensions) = 0; +}; + template -class Files +class FilesImplementation: public IFiles { private: FileSystemT *fileSystem; public: - Files(FileSystemT *fileSystem) : fileSystem(fileSystem) + FilesImplementation(FileSystemT *fileSystem) : fileSystem(fileSystem) { } diff --git a/firmware/src/Screens/AlphabetPicker.h b/firmware/src/Screens/AlphabetPicker.h index f833f25..156cb8c 100644 --- a/firmware/src/Screens/AlphabetPicker.h +++ b/firmware/src/Screens/AlphabetPicker.h @@ -4,21 +4,21 @@ #include "NavigationStack.h" #include "PickerScreen.h" -template +template class AlphabetPicker : public PickerScreen { private: - Files_T *m_files; + IFiles *m_files; std::string m_path; std::vector m_extensions; public: - AlphabetPicker(std::string title, Files_T *files, TFTDisplay &tft, AudioOutput *audioOutput, std::string path, std::vector extensions) + AlphabetPicker(std::string title, IFiles *files, TFTDisplay &tft, AudioOutput *audioOutput, std::string path, std::vector extensions) : m_files(files), PickerScreen(title, tft, audioOutput), m_path(path), m_extensions(extensions) { } void onItemSelect(FileLetterCountPtr item, int index) override { - FilePickerScreen_T *filePickerScreen = new FilePickerScreen_T(m_tft, m_audioOutput); + FilePickerScreen_T *filePickerScreen = new FilePickerScreen_T(m_tft, m_audioOutput, m_files); drawBusy(); filePickerScreen->setItems(m_files->getFileStartingWithPrefix(m_path.c_str(), item->getLetter().c_str(), m_extensions)); m_navigationStack->push(filePickerScreen); diff --git a/firmware/src/Screens/EmulatorScreen.cpp b/firmware/src/Screens/EmulatorScreen.cpp index fc65ad9..1cc335b 100644 --- a/firmware/src/Screens/EmulatorScreen.cpp +++ b/firmware/src/Screens/EmulatorScreen.cpp @@ -4,17 +4,60 @@ #include "../Emulator/snaps.h" #include "../AudioOutput/AudioOutput.h" #include "../Files/Files.h" +#include "ErrorScreen.h" #include "EmulatorScreen.h" +#include "AlphabetPicker.h" +#include "GameFilePickerScreen.h" #include "NavigationStack.h" #include "SaveSnapshotScreen.h" #include "EmulatorScreen/Renderer.h" #include "EmulatorScreen/Machine.h" #include "EmulatorScreen/GameLoader.h" -EmulatorScreen::EmulatorScreen(TFTDisplay &tft, AudioOutput *audioOutput) : Screen(tft, audioOutput) +const std::vector tap_extensions = {".tap", ".tzx"}; +const std::vector no_sd_card_error = {"No SD Card", "Insert an SD Card", "to load games"}; +const std::vector no_files_error = {"No games found", "on the SD Card", "add Z80 or SNA files"}; + +void EmulatorScreen::triggerLoadTape() +{ + if (isLoading) { + return; + } + machine->pause(); + renderer->pause(); + if (!m_files->isAvailable()) + { + ErrorScreen *errorScreen = new ErrorScreen( + no_sd_card_error, + m_tft, + m_audioOutput); + m_navigationStack->push(errorScreen); + return; + } + drawBusy(); + FileLetterCountVector fileLetterCounts = m_files->getFileLetters("/", tap_extensions); + if (fileLetterCounts.size() == 0) + { + ErrorScreen *errorScreen = new ErrorScreen( + no_files_error, + m_tft, + m_audioOutput); + m_navigationStack->push(errorScreen); + return; + } + AlphabetPicker *alphabetPicker = new AlphabetPicker("Select Tape File", m_files, m_tft, m_audioOutput, "/", tap_extensions); + alphabetPicker->setItems(fileLetterCounts); + m_navigationStack->push(alphabetPicker); +} + +EmulatorScreen::EmulatorScreen(TFTDisplay &tft, AudioOutput *audioOutput, IFiles *files) + : Screen(tft, audioOutput), m_files(files) { renderer = new Renderer(tft); - machine = new Machine(renderer, audioOutput); + machine = new Machine(renderer, audioOutput, [&]() + { + Serial.println("ROM loading routine hit"); + triggerLoadTape(); }); gameLoader = new GameLoader(machine, renderer, audioOutput); pinMode(0, INPUT_PULLUP); } @@ -34,6 +77,7 @@ void EmulatorScreen::run(std::string filename, models_enum model) { return std::tolower(c); }); if (ext == "tap" || ext == "tzx") { + machine->startLoading(); gameLoader->loadTape(filename.c_str()); } else @@ -79,3 +123,15 @@ void EmulatorScreen::showSaveSnapshotScreen() { m_navigationStack->push(new SaveSnapshotScreen(m_tft, m_audioOutput, machine->getMachine())); } + +void EmulatorScreen::loadTape(std::string filename) +{ + isLoading = true; + renderer->resume(); + renderer->setNeedsRedraw(); + gameLoader->loadTape(filename); + renderer->setIsLoading(false); + renderer->setNeedsRedraw(); + machine->resume(); + isLoading = false; +} diff --git a/firmware/src/Screens/EmulatorScreen.h b/firmware/src/Screens/EmulatorScreen.h index 5e65e8c..0ddf9c2 100644 --- a/firmware/src/Screens/EmulatorScreen.h +++ b/firmware/src/Screens/EmulatorScreen.h @@ -18,8 +18,11 @@ class EmulatorScreen : public Screen Machine *machine = nullptr; GameLoader *gameLoader = nullptr; FILE *audioFile = nullptr; + IFiles *m_files; + void triggerLoadTape(); + bool isLoading = false; public: - EmulatorScreen(TFTDisplay &tft, AudioOutput *audioOutput); + EmulatorScreen(TFTDisplay &tft, AudioOutput *audioOutput, IFiles *files); void updatekey(SpecKeys key, uint8_t state); void run(std::string filename, models_enum model); void pause(); @@ -28,4 +31,5 @@ class EmulatorScreen : public Screen void didAppear() { resume(); } + void loadTape(std::string filename); }; diff --git a/firmware/src/Screens/EmulatorScreen/GameLoader.cpp b/firmware/src/Screens/EmulatorScreen/GameLoader.cpp index fd91335..47d6add 100644 --- a/firmware/src/Screens/EmulatorScreen/GameLoader.cpp +++ b/firmware/src/Screens/EmulatorScreen/GameLoader.cpp @@ -8,7 +8,6 @@ #include "../../AudioOutput/AudioOutput.h" #include "../../utils.h" - GameLoader::GameLoader(Machine *machine, Renderer *renderer, AudioOutput *audioOutput) : machine(machine), renderer(renderer), audioOutput(audioOutput) {} void GameLoader::loadTape(std::string filename) @@ -25,7 +24,6 @@ void GameLoader::loadTape(std::string filename) uint64_t startTime = get_usecs(); renderer->setIsLoading(true); Serial.printf("Loading tape %s\n", filename.c_str()); - machine->startLoading(); Serial.printf("Loading tape file\n"); FILE *fp = fopen(filename.c_str(), "rb"); if (fp == NULL) diff --git a/firmware/src/Screens/EmulatorScreen/Machine.cpp b/firmware/src/Screens/EmulatorScreen/Machine.cpp index ec15383..d55b8cd 100644 --- a/firmware/src/Screens/EmulatorScreen/Machine.cpp +++ b/firmware/src/Screens/EmulatorScreen/Machine.cpp @@ -27,6 +27,10 @@ void Machine::runEmulator() { renderer->resetFrameCount(); cycleCount = 0; } + if (machine->romLoadingRoutineHit) + { + romLoadingRoutineHitCallback(); + } } else { @@ -36,7 +40,9 @@ void Machine::runEmulator() { } -Machine::Machine(Renderer *renderer, AudioOutput *audioOutput): renderer(renderer), audioOutput(audioOutput) { +Machine::Machine(Renderer *renderer, AudioOutput *audioOutput, std::function romLoadingRoutineHitCallback) +: renderer(renderer), audioOutput(audioOutput), romLoadingRoutineHitCallback(romLoadingRoutineHitCallback) { + Serial.println("Creating machine"); machine = new ZXSpectrum(); } @@ -47,12 +53,14 @@ void Machine::updatekey(SpecKeys key, uint8_t state) { } void Machine::setup(models_enum model) { + Serial.println("Setting up machine"); machine->reset(); machine->init_spectrum(model); machine->reset_spectrum(machine->z80Regs); } void Machine::start(FILE *audioFile) { + Serial.println("Starting machine"); this->audioFile = audioFile; isRunning = true; xTaskCreatePinnedToCore(runnerTask, "z80Runner", 8192, this, 5, NULL, 0); @@ -80,13 +88,17 @@ void Machine::startLoading() machine->runForFrame(nullptr, nullptr); } renderer->triggerDraw(machine->mem.currentScreen, machine->borderColors); + // TODO load screenshot... if (machine->hwopt.hw_model == SPECMDL_48K) { tapKey(SPECKEY_J); machine->updatekey(SPECKEY_SYMB, 1); tapKey(SPECKEY_P); tapKey(SPECKEY_P); + tapKey(SPECKEY_SHIFT); + tapKey(SPECKEY_K); machine->updatekey(SPECKEY_SYMB, 0); + machine->updatekey(SPECKEY_SHIFT, 0); tapKey(SPECKEY_ENTER); } else diff --git a/firmware/src/Screens/EmulatorScreen/Machine.h b/firmware/src/Screens/EmulatorScreen/Machine.h index 71cba29..efeac1c 100644 --- a/firmware/src/Screens/EmulatorScreen/Machine.h +++ b/firmware/src/Screens/EmulatorScreen/Machine.h @@ -1,6 +1,7 @@ #pragma once #include "stdio.h" +#include #include "../../Emulator/spectrum.h" void runnerTask(void *pvParameter); @@ -26,8 +27,10 @@ class Machine { FILE *audioFile = nullptr; // keeps track of how many tstates we've run uint32_t cycleCount = 0; + // callback for when rom loading routine is hit + std::function romLoadingRoutineHitCallback; public: - Machine(Renderer *renderer, AudioOutput *audioOutput); + Machine(Renderer *renderer, AudioOutput *audioOutput, std::function romLoadingRoutineHitCallback); void updatekey(SpecKeys key, uint8_t state); void setup(models_enum model); void start(FILE *audioFile); diff --git a/firmware/src/Screens/EmulatorScreen/Renderer.cpp b/firmware/src/Screens/EmulatorScreen/Renderer.cpp index 38aba38..1115152 100644 --- a/firmware/src/Screens/EmulatorScreen/Renderer.cpp +++ b/firmware/src/Screens/EmulatorScreen/Renderer.cpp @@ -29,12 +29,12 @@ void Renderer::drawBorder(int startPos, int endPos, int offset, int length, int for (int borderPos = startPos; borderPos < endPos;) { uint8_t borderColor = currentBorderColors[borderPos + offset]; - if (drawnBorderColors[borderPos + offset] != borderColor) + if (drawnBorderColors[borderPos + offset] != borderColor || firstDraw) { // Find consecutive lines with the same color int rangeStart = borderPos; - while (borderPos < endPos && currentBorderColors[borderPos + offset] == borderColor && - drawnBorderColors[borderPos + offset] != borderColor) + while (borderPos < endPos && ((currentBorderColors[borderPos + offset] == borderColor && + drawnBorderColors[borderPos + offset] != borderColor) || firstDraw)) { drawnBorderColors[borderPos + offset] = borderColor; borderPos++; diff --git a/firmware/src/Screens/EmulatorScreen/Renderer.h b/firmware/src/Screens/EmulatorScreen/Renderer.h index bbb0bec..aee073f 100644 --- a/firmware/src/Screens/EmulatorScreen/Renderer.h +++ b/firmware/src/Screens/EmulatorScreen/Renderer.h @@ -100,4 +100,7 @@ class Renderer { void setLoadProgress(uint16_t progress) { loadProgress = progress; } + void setNeedsRedraw() { + firstDraw = true; + } }; diff --git a/firmware/src/Screens/ErrorScreen.h b/firmware/src/Screens/ErrorScreen.h index e8a106e..032dc2f 100644 --- a/firmware/src/Screens/ErrorScreen.h +++ b/firmware/src/Screens/ErrorScreen.h @@ -3,6 +3,7 @@ #include "Screen.h" #include "../TFT/TFTDisplay.h" #include "fonts/GillSans_30_vlw.h" +#include "NavigationStack.h" class TFTDisplay; diff --git a/firmware/src/Screens/GameFilePickerScreen.h b/firmware/src/Screens/GameFilePickerScreen.h index 3882f86..aaaa4b6 100644 --- a/firmware/src/Screens/GameFilePickerScreen.h +++ b/firmware/src/Screens/GameFilePickerScreen.h @@ -6,15 +6,39 @@ class GameFilePickerScreen : public PickerScreen { + private: + IFiles *m_files; public: - GameFilePickerScreen(TFTDisplay &tft, AudioOutput *audioOutput) - : PickerScreen("Games", tft, audioOutput) {} + GameFilePickerScreen(TFTDisplay &tft, AudioOutput *audioOutput, IFiles *files) + : PickerScreen("Games", tft, audioOutput), m_files(files) {} void onItemSelect(FileInfoPtr item, int index) { + Serial.printf("Selected %s\n", item->getPath().c_str()); + // locate the emaulator screen if it's on the stack already + EmulatorScreen *emulatorScreen = nullptr; + for (int i = 0; i < m_navigationStack->stack.size(); i++) { + emulatorScreen = dynamic_cast(m_navigationStack->stack.at(i)); + if (emulatorScreen != nullptr) { + break; + } + } + // if we found the emulator screen and if we're loading a tape file then we've been triggered due to the user starting the + // load routine from the emulator screen. We just need to tell the emulator screen to load the tape file and pop back to it + if (emulatorScreen != nullptr && item->getExtension() == ".tap" || item->getExtension() == ".tzx") { + Serial.println("Loading tape file into existing emulator screen"); + // pop to the emaulator screen - get a local copy of the stack as it will be set to null when we pop + NavigationStack *navStack = m_navigationStack; + while(navStack->stack.back() != emulatorScreen) { + navStack->pop(); + } + emulatorScreen->loadTape(item->getPath()); + return; + } + Serial.println("Starting new emulator screen"); drawBusy(); - EmulatorScreen *emulatorScreen = new EmulatorScreen(m_tft, m_audioOutput); + emulatorScreen = new EmulatorScreen(m_tft, m_audioOutput, m_files); // TODO - we should pick the machine to run on - 48k or 128k // there's no way to know from the file name or the file contents - emulatorScreen->run(item->getPath(), models_enum::SPECMDL_128K); + emulatorScreen->run(item->getPath(), models_enum::SPECMDL_48K); m_navigationStack->push(emulatorScreen); } void onBack() { diff --git a/firmware/src/Screens/MainMenuScreen.h b/firmware/src/Screens/MainMenuScreen.h index bf58a66..b24a45d 100644 --- a/firmware/src/Screens/MainMenuScreen.h +++ b/firmware/src/Screens/MainMenuScreen.h @@ -26,14 +26,14 @@ class MenuItem using MenuItemPtr = std::shared_ptr; using MenuItemVector = std::vector; -template + class MainMenuScreen : public PickerScreen { private: - Files_T *m_files; + IFiles *m_files; public: - MainMenuScreen(TFTDisplay &tft, AudioOutput *audioOutput, Files_T *files) : m_files(files), PickerScreen("Main Menu", tft, audioOutput) + MainMenuScreen(TFTDisplay &tft, AudioOutput *audioOutput, IFiles *files) : m_files(files), PickerScreen("Main Menu", tft, audioOutput) { // Main menu MenuItemVector menuItems = { @@ -62,14 +62,14 @@ class MainMenuScreen : public PickerScreen void run48K() { - EmulatorScreen *emulatorScreen = new EmulatorScreen(m_tft, m_audioOutput); + EmulatorScreen *emulatorScreen = new EmulatorScreen(m_tft, m_audioOutput, m_files); emulatorScreen->run("", models_enum::SPECMDL_48K); // touchKeyboard->setToggleMode(true); m_navigationStack->push(emulatorScreen); } void run128K() { - EmulatorScreen *emulatorScreen = new EmulatorScreen(m_tft, m_audioOutput); + EmulatorScreen *emulatorScreen = new EmulatorScreen(m_tft, m_audioOutput, m_files); emulatorScreen->run("", models_enum::SPECMDL_128K); // touchKeyboard->setToggleMode(true); m_navigationStack->push(emulatorScreen); @@ -103,7 +103,7 @@ class MainMenuScreen : public PickerScreen m_navigationStack->push(errorScreen); return; } - AlphabetPicker *alphabetPicker = new AlphabetPicker(title, m_files, m_tft, m_audioOutput, path, extensions); + AlphabetPicker *alphabetPicker = new AlphabetPicker(title, m_files, m_tft, m_audioOutput, path, extensions); alphabetPicker->setItems(fileLetterCounts); m_navigationStack->push(alphabetPicker); } diff --git a/firmware/src/Screens/NavigationStack.h b/firmware/src/Screens/NavigationStack.h index 8003c01..b061018 100644 --- a/firmware/src/Screens/NavigationStack.h +++ b/firmware/src/Screens/NavigationStack.h @@ -5,9 +5,8 @@ class NavigationStack { - private: - std::vector stack; public: + std::vector stack; NavigationStack() {} ~NavigationStack() {} Screen *getTop() { diff --git a/firmware/src/Screens/PickerScreen.h b/firmware/src/Screens/PickerScreen.h index 296667d..e8f76e8 100644 --- a/firmware/src/Screens/PickerScreen.h +++ b/firmware/src/Screens/PickerScreen.h @@ -12,10 +12,6 @@ class TFTDisplay; class ScrollingList; -bool starts_with(const std::string& str, const std::string& prefix) { - return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0; -} - template class PickerScreen : public Screen { @@ -26,6 +22,10 @@ class PickerScreen : public Screen int lastSearchPrefix = 0; int m_selectedItem = 0; int m_lastPageDrawn = -1; + + bool starts_with(const std::string& str, const std::string& prefix) { + return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0; + } public: PickerScreen( std::string title, diff --git a/firmware/src/Screens/VideoFilePickerScreen.h b/firmware/src/Screens/VideoFilePickerScreen.h index 5d67b60..ec74676 100644 --- a/firmware/src/Screens/VideoFilePickerScreen.h +++ b/firmware/src/Screens/VideoFilePickerScreen.h @@ -5,9 +5,11 @@ class VideoFilePickerScreen : public PickerScreen { + private: + IFiles *m_files; public: - VideoFilePickerScreen(TFTDisplay &tft, AudioOutput *audioOutput) - : PickerScreen("Videos", tft, audioOutput) {} + VideoFilePickerScreen(TFTDisplay &tft, AudioOutput *audioOutput, IFiles *files) + : PickerScreen("Videos", tft, audioOutput), m_files(files) {} void onItemSelect(FileInfoPtr item, int index) { VideoPlayerScreen *playerScreen = new VideoPlayerScreen(m_tft, m_audioOutput); playerScreen->play(item->getPath().c_str()); diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 61667e3..72232d8 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -47,11 +47,11 @@ void setup(void) #ifdef USE_SDCARD #ifdef USE_SDIO SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_CLK, SD_CARD_CMD, SD_CARD_D0, SD_CARD_D1, SD_CARD_D2, SD_CARD_D3); - Files *files = new Files(fileSystem); + IFiles *files = new FilesImplementation(fileSystem); setupUSB(fileSystem); #else SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_MISO, SD_CARD_MOSI, SD_CARD_CLK, SD_CARD_CS); - Files *files = new Files(fileSystem); + IFiles *files = new FilesImplementation(fileSystem); #endif // Serial.begin(115200); // for(int i = 0; i < 5; i++) { diff --git a/firmware/src/utils.h b/firmware/src/utils.h index 6c3cd7b..8ac399e 100644 --- a/firmware/src/utils.h +++ b/firmware/src/utils.h @@ -26,3 +26,4 @@ class ScopeGuard { std::function func_; bool active_; }; +