From df01e34d6eed5e0255e161e9675e26a7ac33d023 Mon Sep 17 00:00:00 2001 From: Yan Ke Date: Mon, 31 Aug 2026 14:47:30 +0800 Subject: [PATCH 1/4] Add support for platformio, SD card mediaPresent(0, totalSize(), usedSize(), and monotonic clock --- ArduinoCore-Linux/cores/arduino/Arduino.cpp | 40 +++++++++--------- ArduinoCore-Linux/cores/arduino/wiring.h | 4 ++ ArduinoCore-Linux/libraries/SD.h | 45 ++++++++++++++++++--- ArduinoEmulator.h | 4 ++ library.json | 17 ++++++++ 5 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 ArduinoCore-Linux/cores/arduino/wiring.h create mode 100644 ArduinoEmulator.h create mode 100644 library.json diff --git a/ArduinoCore-Linux/cores/arduino/Arduino.cpp b/ArduinoCore-Linux/cores/arduino/Arduino.cpp index ad09528..2aa17b3 100644 --- a/ArduinoCore-Linux/cores/arduino/Arduino.cpp +++ b/ArduinoCore-Linux/cores/arduino/Arduino.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -59,6 +60,24 @@ PluggableUSB_::PluggableUSB_() {} } // namespace arduino +namespace { + +using MonotonicClock = std::chrono::steady_clock; + +const MonotonicClock::time_point& clockEpoch() { + static const MonotonicClock::time_point epoch = MonotonicClock::now(); + return epoch; +} + +uint64_t elapsedMicroseconds() { + return static_cast( + std::chrono::duration_cast( + MonotonicClock::now() - clockEpoch()) + .count()); +} + +} // namespace + /** * @brief Pause the program for the amount of time (in milliseconds) specified as parameter * @param ms The number of milliseconds to pause (unsigned long) @@ -93,18 +112,7 @@ char* dtostrf(double val, signed char width, unsigned char prec, char* sout) { * @return Number of milliseconds passed since the program started (unsigned long) */ unsigned long millis() { - static uint64_t start = 0; - using namespace std::chrono; - // Get current time with precision of milliseconds - auto now = time_point_cast(system_clock::now()); - // sys_milliseconds is type time_point - using sys_milliseconds = decltype(now); - // Convert time_point to signed integral type - auto result = now.time_since_epoch().count(); - if (start == 0) { - start = result; - } - return result - start; + return static_cast(elapsedMicroseconds() / 1000ULL); } /** @@ -112,13 +120,7 @@ unsigned long millis() { * @return Number of microseconds passed since the program started (unsigned long) */ unsigned long micros(void) { - using namespace std::chrono; - // Get current time with precision of milliseconds - auto now = time_point_cast(system_clock::now()); - // sys_milliseconds is type time_point - using sys_milliseconds = decltype(now); - // Convert time_point to signed integral type - return now.time_since_epoch().count(); + return static_cast(elapsedMicroseconds()); } /** diff --git a/ArduinoCore-Linux/cores/arduino/wiring.h b/ArduinoCore-Linux/cores/arduino/wiring.h new file mode 100644 index 0000000..d4f5a4a --- /dev/null +++ b/ArduinoCore-Linux/cores/arduino/wiring.h @@ -0,0 +1,4 @@ +#pragma once + +// Arduino sketches and libraries often include this legacy header directly. +#include "Arduino.h" diff --git a/ArduinoCore-Linux/libraries/SD.h b/ArduinoCore-Linux/libraries/SD.h index aca8aae..2efafd0 100644 --- a/ArduinoCore-Linux/libraries/SD.h +++ b/ArduinoCore-Linux/libraries/SD.h @@ -60,7 +60,11 @@ #define SS -1 #endif -using namespace std; +#ifndef BUILTIN_SDCARD +#define BUILTIN_SDCARD -1 +#endif + +using std::ios; /** * @brief C++ std based emulatoion of SdSpiConfig @@ -252,15 +256,46 @@ class SdFat { int rc = ::rmdir(path); return rc == 0; } + + /** + * @brief Return whether the host filesystem backing the emulator is usable. + * + * The emulator has no removable media, so a writable current working + * directory is the closest equivalent to an inserted SD card. + */ + bool mediaPresent() { + std::error_code ec; + const std::filesystem::path root = std::filesystem::current_path(ec); + if (ec || !std::filesystem::exists(root, ec) || ec) { + return false; + } + + const auto status = std::filesystem::status(root, ec); + if (ec) { + return false; + } + + return (status.permissions() & std::filesystem::perms::owner_write) != + std::filesystem::perms::none; + } + + /** @brief Total capacity in bytes of the host filesystem backing SD. */ + uint64_t totalSize() { return totalBytes(); } + + /** @brief Used bytes of the host filesystem backing SD. */ + uint64_t usedSize() { return usedBytes(); } + uint64_t totalBytes() { std::error_code ec; - const std::filesystem::space_info si = std::filesystem::space("/home", ec); - return si.capacity; + const std::filesystem::space_info si = + std::filesystem::space(std::filesystem::current_path(ec), ec); + return ec ? 0 : si.capacity; } uint64_t usedBytes() { std::error_code ec; - const std::filesystem::space_info si = std::filesystem::space("/home", ec); - return si.capacity - si.available; + const std::filesystem::space_info si = + std::filesystem::space(std::filesystem::current_path(ec), ec); + return ec ? 0 : si.capacity - si.available; } }; diff --git a/ArduinoEmulator.h b/ArduinoEmulator.h new file mode 100644 index 0000000..2b14c97 --- /dev/null +++ b/ArduinoEmulator.h @@ -0,0 +1,4 @@ +#pragma once + +// PlatformIO discovery entry point for the desktop Arduino runtime. +#include "ArduinoCore-Linux/cores/arduino/Arduino.h" diff --git a/library.json b/library.json new file mode 100644 index 0000000..350d383 --- /dev/null +++ b/library.json @@ -0,0 +1,17 @@ +{ + "name": "Arduino-Emulator", + "version": "0.1.0", + "description": "Desktop Arduino compatibility runtime", + "keywords": ["arduino", "emulator", "native"], + "frameworks": "*", + "platforms": "native", + "build": { + "srcDir": ".", + "includeDir": ".", + "srcFilter": [ + "+", + "+", + "-" + ] + } +} From d4ab3de5d4f7bcb5657375ec3c75ebaf5bf8d1a6 Mon Sep 17 00:00:00 2001 From: Yan Ke Date: Mon, 31 Aug 2026 15:06:58 +0800 Subject: [PATCH 2/4] Add include directories --- library.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library.json b/library.json index 350d383..41d3b83 100644 --- a/library.json +++ b/library.json @@ -8,6 +8,12 @@ "build": { "srcDir": ".", "includeDir": ".", + "flags": [ + "-I ArduinoCore-API", + "-I ArduinoCore-API/api", + "-I ArduinoCore-Linux/cores/arduino", + "-I ArduinoCore-Linux/libraries" + ], "srcFilter": [ "+", "+", From 87b10f35d9b215e5604dfcbc794d9e76bcee1705 Mon Sep 17 00:00:00 2001 From: Yan Ke Date: Mon, 31 Aug 2026 15:35:23 +0800 Subject: [PATCH 3/4] Fix shared nonblocking console Serial input --- ArduinoCore-Linux/cores/arduino/StdioDevice.h | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ArduinoCore-Linux/cores/arduino/StdioDevice.h b/ArduinoCore-Linux/cores/arduino/StdioDevice.h index 2a4d22c..5ffc019 100644 --- a/ArduinoCore-Linux/cores/arduino/StdioDevice.h +++ b/ArduinoCore-Linux/cores/arduino/StdioDevice.h @@ -19,6 +19,10 @@ #pragma once #include +#if defined(__unix__) || defined(__APPLE__) +#include +#include +#endif #include #include "api/Stream.h" #include "api/Printable.h" @@ -127,7 +131,15 @@ class StdioDevice : public Stream { return 1; } - int available() override { return std::cin.rdbuf()->in_avail(); }; + int available() override { +#if defined(__unix__) || defined(__APPLE__) + int byteCount = 0; + if (ioctl(STDIN_FILENO, FIONREAD, &byteCount) == 0) { + return byteCount; + } +#endif + return std::cin.rdbuf()->in_avail(); + } int read() override { return std::cin.get(); } @@ -137,9 +149,9 @@ class StdioDevice : public Stream { bool auto_flush = true; }; -static StdioDevice Serial; +inline StdioDevice Serial; #ifndef USE_RPI -static StdioDevice Serial2; +inline StdioDevice Serial2; #endif } // namespace arduino From 7d2a5cadc5a46d870492e69b89af8138f2dfa625 Mon Sep 17 00:00:00 2001 From: Yan Ke Date: Mon, 31 Aug 2026 15:37:24 +0800 Subject: [PATCH 4/4] Buffer POSIX standard input for Serial reads --- ArduinoCore-Linux/cores/arduino/StdioDevice.h | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/ArduinoCore-Linux/cores/arduino/StdioDevice.h b/ArduinoCore-Linux/cores/arduino/StdioDevice.h index 5ffc019..de43dbd 100644 --- a/ArduinoCore-Linux/cores/arduino/StdioDevice.h +++ b/ArduinoCore-Linux/cores/arduino/StdioDevice.h @@ -19,6 +19,8 @@ #pragma once #include +#include +#include #if defined(__unix__) || defined(__APPLE__) #include #include @@ -133,20 +135,53 @@ class StdioDevice : public Stream { int available() override { #if defined(__unix__) || defined(__APPLE__) - int byteCount = 0; - if (ioctl(STDIN_FILENO, FIONREAD, &byteCount) == 0) { - return byteCount; - } + fillInputBuffer(); + return static_cast(inputBuffer_.size()); #endif return std::cin.rdbuf()->in_avail(); } - int read() override { return std::cin.get(); } + int read() override { +#if defined(__unix__) || defined(__APPLE__) + if (available() == 0) return -1; + const int value = inputBuffer_.front(); + inputBuffer_.pop_front(); + return value; +#else + return std::cin.get(); +#endif + } - int peek() override { return std::cin.peek(); } + int peek() override { +#if defined(__unix__) || defined(__APPLE__) + return available() > 0 ? inputBuffer_.front() : -1; +#else + return std::cin.peek(); +#endif + } protected: bool auto_flush = true; + +#if defined(__unix__) || defined(__APPLE__) + private: + void fillInputBuffer() { + int byteCount = 0; + if (ioctl(STDIN_FILENO, FIONREAD, &byteCount) != 0 || byteCount <= 0) { + return; + } + + char buffer[256]; + const size_t bytesToRead = std::min(static_cast(byteCount), sizeof(buffer)); + const ssize_t bytesRead = ::read(STDIN_FILENO, buffer, bytesToRead); + if (bytesRead <= 0) { + return; + } + inputBuffer_.insert(inputBuffer_.end(), buffer, buffer + bytesRead); + } + + std::deque inputBuffer_; +#endif }; inline StdioDevice Serial;