diff --git a/Arduino.cpp b/Arduino.cpp new file mode 100644 index 0000000..f243680 --- /dev/null +++ b/Arduino.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2019 Brian T. Park + * + * Parts derived from the Arduino SDK + * Copyright (c) 2005-2013 Arduino Team + * + * Parts inspired by [Entering raw + * mode](https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html). + * + * Parts inspired by [ESP8266 Host + * Emulation](https://github.com/esp8266/Arduino/tree/master/tests/host). + * + * The 'Serial' object sends output to STDOUT, and receives input from STDIN in + * 'raw' mode. The main() loop checks the STDIN and if it finds a character, + * inserts it into the Serial buffer. + */ + +#include +#include // usleep() +#include // SIGINT +#include +#include // clock_gettime() +#include // exit() +#include // perror() +#include "Arduino.h" + +// ----------------------------------------------------------------------- +// Arduino methods emulated in Unix +// ----------------------------------------------------------------------- + +void delay(unsigned long ms) { + usleep(ms * 1000); +} + +void yield() { + usleep(1000); // prevents program from consuming 100% CPU +} + +unsigned long millis() { + struct timespec spec; + clock_gettime(CLOCK_MONOTONIC, &spec); + unsigned long ms = spec.tv_sec * 1000U + spec.tv_nsec / 1000000UL; + return ms; +} + +unsigned long micros() { + struct timespec spec; + clock_gettime(CLOCK_MONOTONIC, &spec); + unsigned long us = spec.tv_sec * 1000000UL + spec.tv_nsec / 1000U; + return us; +} + +void digitalWrite(uint8_t pin, uint8_t val) {} + +int digitalRead(uint8_t pin) { return 0; } + +void pinMode(uint8_t pin, uint8_t mode) {} + +// ----------------------------------------------------------------------- +// Unix compatibility. Put STDIN into raw mode and hook it into the 'Serial' +// object. Trap Ctrl-C and perform appropriate clean up. +// ----------------------------------------------------------------------- + +static struct termios orig_termios; +static bool inRawMode = false; + +static void die(const char* s) { + perror(s); + exit(1); +} + +static void disableRawMode() { + if (!inRawMode) return; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) { + inRawMode = false; // prevent exit(1) from being called twice + die("disableRawMode(): tcsetattr() failure"); + } +} + +static void enableRawMode() { + if (!isatty(STDIN_FILENO)) { + die("enableRawMode(): redirection on STDIN not supported"); + } + if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) { + die("enableRawMode(): tcgetattr() failure"); + } + + struct termios raw = orig_termios; + // The 'Enter' key in raw mode is ^M (\r, CR). But internally, we want this + // to be ^J (\n, NL), so ICRNL and INLCR causes the ^M to become a \n. + raw.c_iflag &= ~(/*ICRNL | INLCR |*/ INPCK | ISTRIP | IXON); + // Set the output into cooked mode, to handle NL and CR properly. + // Print.println() sends CR-NL (\r\n). But some code will send just \n. The + // ONLCR translates \n into \r\n. So '\r\n' will become \r\r\n, which is just + // fine. + raw.c_oflag |= (OPOST | ONLCR); + raw.c_cflag |= (CS8); + // Enable ISIG to allow Ctrl-C to kill the program. + raw.c_lflag &= ~(/*ECHO | ISIG |*/ ICANON | IEXTEN); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { + die("enableRawMode(): tcsetattr() failure"); + } + inRawMode = true; +} + +static void handleControlC(int /*sig*/) { + if (inRawMode) { + // If this returns an error, don't call die() because it will call exit(), + // which may call this again, causing an infinite recursion. + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) { + perror("handleControlC(): tcsetattr() failure"); + } + inRawMode = false; + } + exit(1); +} + +// ----------------------------------------------------------------------- +// Main loop. User code will provide setup() and loop(). +// ----------------------------------------------------------------------- + +int main(int argc, char** argv) { + signal(SIGINT, handleControlC); + atexit(disableRawMode); + enableRawMode(); + + setup(); + while (true) { + char c = '\0'; + read(STDIN_FILENO, &c, 1); + if (c) Serial.insertChar(c); + loop(); + yield(); + } +} diff --git a/Arduino.h b/Arduino.h new file mode 100644 index 0000000..a5b65ec --- /dev/null +++ b/Arduino.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Brian T. Park + * MIT License + * + * Parts derived from the Arduino SDK + * Copyright (c) 2005-2013 Arduino Team + */ + +#ifndef UNIX_HOST_DUINO_ARDUINO_H +#define UNIX_HOST_DUINO_ARDUINO_H + +#include "Print.h" +#include "StdioSerial.h" + +// Macros defined when running under UnixHostDuino +#define UNIX_HOST_DUINO 1 +// xx.yy.zz => xxyyzz (without leading 0) +#define UNIX_HOST_DUINO_VERSION 100 +#define UNIX_HOST_DUINO_VERSION_STRING "0.1" + +// Used by digitalRead() and digitalWrite() +#define HIGH 0x1 +#define LOW 0x0 + +// Used by pinMode() +#define INPUT 0x0 +#define OUTPUT 0x1 +#define INPUT_PULLUP 0x2 + +// Various math constants. +#define PI 3.1415926535897932384626433832795 +#define HALF_PI 1.5707963267948966192313216916398 +#define TWO_PI 6.283185307179586476925286766559 +#define DEG_TO_RAD 0.017453292519943295769236907684886 +#define RAD_TO_DEG 57.295779513082320876798154814105 +#define EULER 2.718281828459045235360287471352 + +#define SERIAL 0x0 +#define DISPLAY 0x1 + +#define LSBFIRST 0 +#define MSBFIRST 1 + +#define CHANGE 1 +#define FALLING 2 +#define RISING 3 + +// Arbitrarily define the pin for the LED_BUILTIN +#define LED_BUILTIN 1 + +extern "C" { + +void delay(unsigned long ms); +void yield(); +unsigned long millis(); +unsigned long micros(); +void digitalWrite(uint8_t pin, uint8_t val); +int digitalRead(uint8_t pin); +void pinMode(uint8_t pin, uint8_t mode); + +/** Provided in the client code's *.ino file. */ +void setup(); + +/** Provided in the client code's *.ino file. */ +void loop(); + +} + +#endif diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d7dd2ec --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +* Unreleased +* 0.1 (2019-07-31) + * Split from `AUnit` and renamed from `unitduino` to `UnixHostDuino`. + * Add `UNIT_HOST_DUINO` macro. + * Add `FPSTR()` macro for compatibilty with ESP8266 and ESP32. + * Add `LED_BUILTIN` macro. diff --git a/EEPROM.h b/EEPROM.h new file mode 100644 index 0000000..83f056f --- /dev/null +++ b/EEPROM.h @@ -0,0 +1,149 @@ +/* + EEPROM.h - EEPROM library + Original Copyright (c) 2006 David A. Mellis. All right reserved. + New version by Christopher Andrews 2015. + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef UNIX_HOST_DUINO_EEPROM_H +#define UNIX_HOST_DUINO_EEPROM_H + +#include +/* +#include +#include +*/ + +/*** + EERef class. + + This object references an EEPROM cell. + Its purpose is to mimic a typical byte of RAM, however its storage is the EEPROM. + This class has an overhead of two bytes, similar to storing a pointer to an EEPROM cell. +***/ + +struct EERef{ + + EERef( const int index ) + : index( index ) {} + + //Access/read members. + uint8_t operator*() const { return eeprom_read_byte( (uint8_t*) index ); } + operator uint8_t() const { return **this; } + + //Assignment/write members. + EERef &operator=( const EERef &ref ) { return *this = *ref; } + EERef &operator=( uint8_t in ) { return eeprom_write_byte( (uint8_t*) index, in ), *this; } + EERef &operator +=( uint8_t in ) { return *this = **this + in; } + EERef &operator -=( uint8_t in ) { return *this = **this - in; } + EERef &operator *=( uint8_t in ) { return *this = **this * in; } + EERef &operator /=( uint8_t in ) { return *this = **this / in; } + EERef &operator ^=( uint8_t in ) { return *this = **this ^ in; } + EERef &operator %=( uint8_t in ) { return *this = **this % in; } + EERef &operator &=( uint8_t in ) { return *this = **this & in; } + EERef &operator |=( uint8_t in ) { return *this = **this | in; } + EERef &operator <<=( uint8_t in ) { return *this = **this << in; } + EERef &operator >>=( uint8_t in ) { return *this = **this >> in; } + + EERef &update( uint8_t in ) { return in != *this ? *this = in : *this; } + + /** Prefix increment/decrement **/ + EERef& operator++() { return *this += 1; } + EERef& operator--() { return *this -= 1; } + + /** Postfix increment/decrement **/ + uint8_t operator++ (int){ + uint8_t ret = **this; + return ++(*this), ret; + } + + uint8_t operator-- (int){ + uint8_t ret = **this; + return --(*this), ret; + } + + int index; //Index of current EEPROM cell. +}; + +/*** + EEPtr class. + + This object is a bidirectional pointer to EEPROM cells represented by EERef objects. + Just like a normal pointer type, this can be dereferenced and repositioned using + increment/decrement operators. +***/ + +struct EEPtr{ + + EEPtr( const int index ) + : index( index ) {} + + operator int() const { return index; } + EEPtr &operator=( int in ) { return index = in, *this; } + + //Iterator functionality. + bool operator!=( const EEPtr &ptr ) { return index != ptr.index; } + EERef operator*() { return index; } + + /** Prefix & Postfix increment/decrement **/ + EEPtr& operator++() { return ++index, *this; } + EEPtr& operator--() { return --index, *this; } + EEPtr operator++ (int) { return index++; } + EEPtr operator-- (int) { return index--; } + + int index; //Index of current EEPROM cell. +}; + +/*** + EEPROMClass class. + + This object represents the entire EEPROM space. + It wraps the functionality of EEPtr and EERef into a basic interface. + This class is also 100% backwards compatible with earlier Arduino core releases. +***/ + +struct EEPROMClass{ + + //Basic user access methods. + EERef operator[]( const int idx ) { return idx; } + uint8_t read( int idx ) { return EERef( idx ); } + void write( int idx, uint8_t val ) { (EERef( idx )) = val; } + void update( int idx, uint8_t val ) { EERef( idx ).update( val ); } + + //STL and C++11 iteration capability. + EEPtr begin() { return 0x00; } + EEPtr end() { return length(); } //Standards requires this to be the item after the last valid entry. The returned pointer is invalid. + uint16_t length() { return E2END + 1; } + + //Functionality to 'get' and 'put' objects to and from EEPROM. + template< typename T > T &get( int idx, T &t ){ + EEPtr e = idx; + uint8_t *ptr = (uint8_t*) &t; + for( int count = sizeof(T) ; count ; --count, ++e ) *ptr++ = *e; + return t; + } + + template< typename T > const T &put( int idx, const T &t ){ + EEPtr e = idx; + const uint8_t *ptr = (const uint8_t*) &t; + for( int count = sizeof(T) ; count ; --count, ++e ) (*e).update( *ptr++ ); + return t; + } +}; + +static EEPROMClass EEPROM; +#endif diff --git a/LICENSE b/LICENSE index 4adb227..796278c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Brian Park +Copyright (c) 2019 Brian T. Park Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Print.cpp b/Print.cpp new file mode 100644 index 0000000..a9164c3 --- /dev/null +++ b/Print.cpp @@ -0,0 +1,266 @@ +/* + Print.cpp - Base class that provides print() and println() + Copyright (c) 2008 David A. Mellis. All right reserved. + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 23 November 2006 by David A. Mellis + Modified 03 August 2015 by Chuck Todd + */ + +#include +#include +#include +#include +#include "pgmspace.h" +#include "Print.h" + +// Public Methods ////////////////////////////////////////////////////////////// + +/* default implementation: may be overridden */ +size_t Print::write(const uint8_t *buffer, size_t size) +{ + size_t n = 0; + while (size--) { + if (write(*buffer++)) n++; + else break; + } + return n; +} + +size_t Print::print(const __FlashStringHelper *ifsh) +{ + PGM_P p = reinterpret_cast(ifsh); + size_t n = 0; + while (1) { + unsigned char c = pgm_read_byte(p++); + if (c == 0) break; + if (write(c)) n++; + else break; + } + return n; +} + +size_t Print::print(const String &s) +{ + return write(s.c_str(), s.length()); +} + +size_t Print::print(const char str[]) +{ + return write(str); +} + +size_t Print::print(char c) +{ + return write(c); +} + +size_t Print::print(unsigned char b, int base) +{ + return print((unsigned long) b, base); +} + +size_t Print::print(int n, int base) +{ + return print((long) n, base); +} + +size_t Print::print(unsigned int n, int base) +{ + return print((unsigned long) n, base); +} + +size_t Print::print(long n, int base) +{ + if (base == 0) { + return write(n); + } else if (base == 10) { + if (n < 0) { + int t = print('-'); + n = -n; + return printNumber(n, 10) + t; + } + return printNumber(n, 10); + } else { + return printNumber(n, base); + } +} + +size_t Print::print(unsigned long n, int base) +{ + if (base == 0) return write(n); + else return printNumber(n, base); +} + +size_t Print::print(double n, int digits) +{ + return printFloat(n, digits); +} + +size_t Print::println(const __FlashStringHelper *ifsh) +{ + size_t n = print(ifsh); + n += println(); + return n; +} + +size_t Print::print(const Printable& x) +{ + return x.printTo(*this); +} + +size_t Print::println(void) +{ + return write("\r\n"); +} + +size_t Print::println(const String &s) +{ + size_t n = print(s); + n += println(); + return n; +} + +size_t Print::println(const char c[]) +{ + size_t n = print(c); + n += println(); + return n; +} + +size_t Print::println(char c) +{ + size_t n = print(c); + n += println(); + return n; +} + +size_t Print::println(unsigned char b, int base) +{ + size_t n = print(b, base); + n += println(); + return n; +} + +size_t Print::println(int num, int base) +{ + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(unsigned int num, int base) +{ + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(long num, int base) +{ + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(unsigned long num, int base) +{ + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(double num, int digits) +{ + size_t n = print(num, digits); + n += println(); + return n; +} + +size_t Print::println(const Printable& x) +{ + size_t n = print(x); + n += println(); + return n; +} + +// Private Methods ///////////////////////////////////////////////////////////// + +size_t Print::printNumber(unsigned long n, uint8_t base) +{ + char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte. + char *str = &buf[sizeof(buf) - 1]; + + *str = '\0'; + + // prevent crash if called with base == 1 + if (base < 2) base = 10; + + do { + char c = n % base; + n /= base; + + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while(n); + + return write(str); +} + +size_t Print::printFloat(double number, uint8_t digits) +{ + size_t n = 0; + + if (isnan(number)) return print("nan"); + if (isinf(number)) return print("inf"); + if (number > 4294967040.0) return print ("ovf"); // constant determined empirically + if (number <-4294967040.0) return print ("ovf"); // constant determined empirically + + // Handle negative numbers + if (number < 0.0) + { + n += print('-'); + number = -number; + } + + // Round correctly so that print(1.999, 2) prints as "2.00" + double rounding = 0.5; + for (uint8_t i=0; i 0) { + n += print('.'); + } + + // Extract digits from the remainder one at a time + while (digits-- > 0) + { + remainder *= 10.0; + unsigned int toPrint = (unsigned int)(remainder); + n += print(toPrint); + remainder -= toPrint; + } + + return n; +} diff --git a/Print.h b/Print.h new file mode 100644 index 0000000..acbb86a --- /dev/null +++ b/Print.h @@ -0,0 +1,95 @@ +/* + Print.h - Base class that provides print() and println() + Copyright (c) 2008 David A. Mellis. All right reserved. + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef UNIX_HOST_DUINO_PRINT_H +#define UNIX_HOST_DUINO_PRINT_H + +#include +#include // strlen() +#include // for size_t + +#include "WString.h" +#include "Printable.h" + +#define DEC 10 +#define HEX 16 +#define OCT 8 +#ifdef BIN // Prevent warnings if BIN is previously defined in "iotnx4.h" or similar +#undef BIN +#endif +#define BIN 2 + +class Print +{ + private: + int write_error; + size_t printNumber(unsigned long, uint8_t); + size_t printFloat(double, uint8_t); + protected: + void setWriteError(int err = 1) { write_error = err; } + public: + Print() : write_error(0) {} + + int getWriteError() { return write_error; } + void clearWriteError() { setWriteError(0); } + + virtual size_t write(uint8_t) = 0; + size_t write(const char *str) { + if (str == NULL) return 0; + return write((const uint8_t *)str, strlen(str)); + } + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + // default to zero, meaning "a single write may block" + // should be overriden by subclasses with buffering + virtual int availableForWrite() { return 0; } + + size_t print(const __FlashStringHelper *); + size_t print(const String &); + size_t print(const char[]); + size_t print(char); + size_t print(unsigned char, int = DEC); + size_t print(int, int = DEC); + size_t print(unsigned int, int = DEC); + size_t print(long, int = DEC); + size_t print(unsigned long, int = DEC); + size_t print(double, int = 2); + size_t print(const Printable&); + + size_t println(const __FlashStringHelper *); + size_t println(const String &s); + size_t println(const char[]); + size_t println(char); + size_t println(unsigned char, int = DEC); + size_t println(int, int = DEC); + size_t println(unsigned int, int = DEC); + size_t println(long, int = DEC); + size_t println(unsigned long, int = DEC); + size_t println(double, int = 2); + size_t println(const Printable&); + size_t println(void); + + virtual void flush() { /* Empty implementation for backward compatibility */ } +}; + +#endif diff --git a/Printable.h b/Printable.h new file mode 100644 index 0000000..fbaac1a --- /dev/null +++ b/Printable.h @@ -0,0 +1,41 @@ +/* + Printable.h - Interface class that allows printing of complex types + Copyright (c) 2011 Adrian McEwen. All right reserved. + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef UNIX_HOST_DUINO_PRINTABLE_H +#define UNIX_HOST_DUINO_PRINTABLE_H + +#include + +class Print; + +/** + * The Printable class provides a way for new classes to allow themselves to be + * printed. By deriving from Printable and implementing the printTo method, it + * will then be possible for users to print out instances of this class by + * passing them into the usual Print::print and Print::println methods. +*/ +class Printable +{ + public: + virtual size_t printTo(Print& p) const = 0; +}; + +#endif + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b85259 --- /dev/null +++ b/README.md @@ -0,0 +1,233 @@ +# UnixHostDuino + +This directory contains a small (but often effective) implementation of the +Arduino programming framework for Linux and MacOS. Originally, it was created to +allow [AUnit](https://github.com/bxparks/AUnit) unit tests to be compiled and +run on a Linux or MacOS machine, instead of running on the embedded +microcontroller. As more Arduino functionality was added, it became useful for +other Arduino programs, particularly ones that relied on just the `Serial` +interface. + +The build process uses [GNU Make](https://www.gnu.org/software/make/manual/). +A `Makefile` needs to be created inside the sketch folder. For example, if the +sketch is `SampleTest/SampleTest.ino`, then the makefile should be +`SampleTest/Makefile`. The sketch is compiled with just a `make` command. It +produces an executable with a `.out` extension, for example, `SampleTest.out`. + +Running an Arduino program natively on Linux or MacOS has some advantages: + +* The development cycle can be lot faster because the compilers on the the + desktop machines are a lot faster, and we also avoid the upload and flash + process to the microcontroller. +* The desktop machine can run unit tests which are too much flash or too + much memory to fit inside an embedded microcontroller. + +The disadvantages are: + +* Only a limited set of Arduino functions are supported (see below). +* There may be compiler differences between the desktop and the embedded + environments (e.g. 8-bit integers versus 64-bit integers). + +Version: 0.1 (2019-07-31) + +## Usage + +The minimal `Makefile` has 3 lines: +``` +APP_NAME := {name of project} +ARDUINO_LIBS := {list of dependent Arduino libraries} +include {path/to/UnixHostDuino.mk} +``` + +For example, the [examples/BlinkSOS](examples/BlinkSOS) project contains this +Makefile: +``` +APP_NAME := BlinkSOS +ARDUINO_LIBS := +include ../../../UnixHostDuino/UnixHostDuino.mk +``` + +To build the program, just run `make`: +``` +$ cd examples/BlinkSOS +$ make clean +$ make +``` + +The executable will be created with a `.out` extension. To run it, just type: +``` +$ ./BlinkSOS.out +``` + +The output that would normally be printed on the `Serial` on an Arduino +board will be sent to the `STDOUT` of the Linux or MacOS terminal. The output +should be identical to what would be shown on the serial port of the Arduino +controller. + +### Difference from Arduino IDE + +There are a number of small differences compared to the programming environment +provided by the Arduino IDE: + +* The `*.ino` file is treated like a normal `*.cpp` file. So it must have + an `#include ` include line at the top of the file. This is + compatible with the Arduino IDE which automatically includes ``. +* The Arduion IDE supports multiple `ino` files in the same directory. (I + believe it simply concontenates them all into a single file.) UnixHostDuino + supports only one `ino` file in a given directory. +* The Arduino IDE automatically generates forward declarations for functions + that appear *after* the global `setup()` and `loop()` methods. In a normal + C++ file, these forward declarations must be created by hand. The other + alternative is to move `loop()` and `setup()` functions to the end of the + `ino` file. + +Fortunately, the changes required to make an `ino` file compatible with +UnixHostDuino are backwards compatible with the Arduino IDE. In other words, a +program that compiles with UnixHostDuino will also compile under Ardunio IDE. + +### Conditional Code + +If you want to add code that takes effect only on UnixHostDuino, you can use +the following macro: +```C++ +#if defined(UNIX_HOST_DUINO) + ... +#endif +``` + +If you need to target Linux or MacOS specifically, I have found that the +following works for Linux: +```C++ +#if defined(__linux__) + ... +#endif +``` +and the following works for MacOS: +```C++ +#if defined(__APPLE__) + ... +#endif +``` + +### Additional Arduino Libraries + +If the Arduino program depends on additional Arduino libraries, they must be +specified in the `Makefile` using the `ARDUINO_LIBS` parameter. For example, +this includes the`[AUnit](https://github.com/bxparks/AUnit) library if it is at +the same level as UnixHostDuino:: + +``` +APP_NAME := SampleTest +ARDUINO_LIBS := AUnit +include .../UnixHostDuino/UnixHostDuino.mk +``` + +The libraries are referred +to by their base directory name (e.g. `AceButton`, or `AceRoutine`) not the full +path. The `UnixHostDuino.mk` file will look for these additional libraries at +the same level as the `UnixHostDuino` directory itself. (We assume that the +additional libraries are siblings to the`UnixHostDuino/` library). This search +location can be changed by the user using the `ARDUINO_LIB_DIR` environment +variable. If this is set, then `make` will use this directory to look for the +additional libraries, instead of sibling directories of `UnixHostDuino`. + +## Supported Arduino Features + +The following functions and features of the Arduino framework are implemented: + +* `Arduino.h` + * `setup()`, `loop()` + * `delay()`, `yield()` + * `millis()`, `micros()` + * `digitalWrite()`, `digitalRead()`, `pinMode()` (empty stubs) + * `HIGH`, `LOW`, `INPUT`, `OUTPUT`, `INPUT_PULLUP` +* `StdioSerial.h` + * `Serial.print()`, `Serial.println()`, `Serial.write()` + * `Serial.read()`, `Serial.peek()`, `Serial.available()` + * `SERIAL_PORT_MONITOR` +* `WString.h` + * `class String` + * `class __FlashStringHelper`, `F()` +* `Print.h` + * `class Print`, `class Printable` +* `pgmspace.h` + * `pgm_read_byte()`, `pgm_read_word()`, `pgm_read_dword()`, + `pgm_read_float()`, `pgm_read_ptr()` + * `strlen_P()`, `strcat_P()`, `strcpy_P()`, `strncpy_P()`, `strcmp_P()`, + `strncmp_P()`, `strcasecmp_P()`, `strchr_P()`, `strrchr_P()` + * `PROGMEM`, `PGM_P`, `PGM_VOID_P`, `PSTR()` +* `EEPROM.h` + * compile only +* `Wire.h` + * compile only + +See [Arduino.h](https://github.com/bxparks/UnixHostDuino/blob/develop/Arduino.h) +for the latest list. + +### Serial Port Emulation + +The `Serial` object is an instance of the `StdioSerial` class which emulates the +Serial port using the `STDIN` and `STDOUT` of the Unix system. `Serial.print()` +sends the output to the `STDOUT` and `Serial.read()` reads from the `STDIN`. + +The interaction with the Unix `tty` device is complicated, and I am not entirely +sure that I have implemented things properly. See [Entering raw +mode](https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html) for +in-depth details. The following is a quick summary of how this is implemented +under `UnixHostDuino`. + +The `STDOUT` remains mostly in normal mode. In particular, `ONLCR` mode is +enabled, which translates `\n` (NL) to `\r\n` (CR-NL). This allows the program +to print a line of string terminating in just `\n` (e.g. in a `printf()` +function) and the Unix `tty` device will automatically add the `\r` (CR) to +start the next line at the left. (Interestingly, the `Print.println()` method +prints `\r\n`, which gets translated into `\r\r\n` by the terminal, which still +does the correct thing. The extra `\r` does not do any harm.) + +The `STDIN` is put into "raw" mode to avoid blocking the `loop()` function while +waiting for input from the keyboard. It also allows `ICRNL` and `INLCR` which +flips the mapping of `\r` and `\n` from the keyboard. That's because normally, +the "Enter" or "Return" key transmits a `\r`, but internally, most string +processing code wants to see a line terminated by `\n` instead. This is +convenient because when the `\n` is printed back to the screen, it becomes +translated into `\r\n`, which is what most people expect is the correct +behavior. + +The `ISIG` option on the `tty` device is *enabled*. This allows the usual Unix +signals to be active, such as Ctrl-C to quit the program, or Ctrl-Z to suspend +the program. But this convenience means that the Arduino program running under +`UnixHostDuino` will never receive a control character through the +`Serial.read()` function. The advantages of having normal Unix signals seemed +worth the trade-off. + +## System Requirements + +This library has been tested on: + +* Ubuntu 18.04 + * g++ 7.4.0 + * GNU Make 4.1 +* MacOS 10.14.5 + * clang++ Apple LLVM version 10.0.1 + * GNU Make 3.81 + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md). + +## License + +[MIT License](https://opensource.org/licenses/MIT) + +## Feedback and Support + +If you have any questions, comments, bug reports, or feature requests, please +file a GitHub ticket instead of emailing me unless the content is sensitive. +(The problem with email is that I cannot reference the email conversation when +other people ask similar questions later.) I'd love to hear about how this +software and its documentation can be improved. I can't promise that I will +incorporate everything, but I will give your ideas serious consideration. + +## Authors + +Created by Brian T. Park (brian@xparks.net). diff --git a/StdioSerial.cpp b/StdioSerial.cpp new file mode 100644 index 0000000..a032732 --- /dev/null +++ b/StdioSerial.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2019 Brian T. Park + * MIT License + */ + +#include +#include "StdioSerial.h" + +size_t StdioSerial::write(uint8_t c) { + int result = putchar(c); + return (result == EOF) ? 0 : 1; +} + +StdioSerial Serial; diff --git a/StdioSerial.h b/StdioSerial.h new file mode 100644 index 0000000..e0f99dd --- /dev/null +++ b/StdioSerial.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Brian T. Park + * MIT License + */ + +#ifndef UNIX_HOST_DUINO_STDIO_SERIAL_H +#define UNIX_HOST_DUINO_STDIO_SERIAL_H + +#include "Print.h" +#include "Stream.h" + +/** + * A version of Serial that reads from STDIN and sends output to STDOUT on + * Linux or MacOS (untested). + */ +class StdioSerial: public Stream { + public: + void begin(unsigned long baud) { } + + size_t write(uint8_t c) override; + + operator bool() { return true; } + + int available() override { return mHead != mTail; } + + int read() override { + if (mHead == mTail) { + return -1; + } else { + char c = mBuffer[mHead]; + mHead = (mHead + 1) % kBufSize; + return c; + } + } + + int peek() override { + return (mHead != mTail) ? mBuffer[mHead] : -1; + } + + /** Insert a character into the ring buffer. */ + void insertChar(char c) { + int newTail = (mTail + 1) % kBufSize; + if (newTail == mHead) { + // Buffer full, drop the character. (Strictly speaking, there's one + // remaining slot in the buffer, but we can't use it because we need to + // distinguish between buffer-empty and buffer-full). + return; + } + mBuffer[mTail] = c; + mTail = newTail; + } + + private: + // Ring buffer size (should be a power of 2 for efficiency). + static const int kBufSize = 128; + + char mBuffer[kBufSize]; + int mHead = 0; + int mTail = 0; +}; + +extern StdioSerial Serial; + +#define SERIAL_PORT_MONITOR Serial + +#endif diff --git a/Stream.cpp b/Stream.cpp new file mode 100644 index 0000000..2ebea5e --- /dev/null +++ b/Stream.cpp @@ -0,0 +1,320 @@ +/* + Stream.cpp - adds parsing methods to Stream class + Copyright (c) 2008 David A. Mellis. All right reserved. + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Created July 2011 + parsing functions based on TextFinder library by Michael Margolis + + findMulti/findUntil routines written by Jim Leonard/Xuth + */ + +#include "Arduino.h" +#include "Stream.h" + +#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait + +// protected method to read stream with timeout +int Stream::timedRead() +{ + int c; + _startMillis = millis(); + do { + c = read(); + if (c >= 0) return c; + } while(millis() - _startMillis < _timeout); + return -1; // -1 indicates timeout +} + +// protected method to peek stream with timeout +int Stream::timedPeek() +{ + int c; + _startMillis = millis(); + do { + c = peek(); + if (c >= 0) return c; + } while(millis() - _startMillis < _timeout); + return -1; // -1 indicates timeout +} + +// returns peek of the next digit in the stream or -1 if timeout +// discards non-numeric characters +int Stream::peekNextDigit(LookaheadMode lookahead, bool detectDecimal) +{ + int c; + while (1) { + c = timedPeek(); + + if( c < 0 || + c == '-' || + (c >= '0' && c <= '9') || + (detectDecimal && c == '.')) return c; + + switch( lookahead ){ + case SKIP_NONE: return -1; // Fail code. + case SKIP_WHITESPACE: + switch( c ){ + case ' ': + case '\t': + case '\r': + case '\n': break; + default: return -1; // Fail code. + } + case SKIP_ALL: + break; + } + read(); // discard non-numeric + } +} + +// Public Methods +////////////////////////////////////////////////////////////// + +void Stream::setTimeout(unsigned long timeout) // sets the maximum number of milliseconds to wait +{ + _timeout = timeout; +} + + // find returns true if the target string is found +bool Stream::find(char *target) +{ + return findUntil(target, strlen(target), NULL, 0); +} + +// reads data from the stream until the target string of given length is found +// returns true if target string is found, false if timed out +bool Stream::find(char *target, size_t length) +{ + return findUntil(target, length, NULL, 0); +} + +// as find but search ends if the terminator string is found +bool Stream::findUntil(char *target, char *terminator) +{ + return findUntil(target, strlen(target), terminator, strlen(terminator)); +} + +// reads data from the stream until the target string of the given length is found +// search terminated if the terminator string is found +// returns true if target string is found, false if terminated or timed out +bool Stream::findUntil(char *target, size_t targetLen, char *terminator, size_t termLen) +{ + if (terminator == NULL) { + MultiTarget t[1] = {{target, targetLen, 0}}; + return findMulti(t, 1) == 0 ? true : false; + } else { + MultiTarget t[2] = {{target, targetLen, 0}, {terminator, termLen, 0}}; + return findMulti(t, 2) == 0 ? true : false; + } +} + +// returns the first valid (long) integer value from the current position. +// lookahead determines how parseInt looks ahead in the stream. +// See LookaheadMode enumeration at the top of the file. +// Lookahead is terminated by the first character that is not a valid part of an integer. +// Once parsing commences, 'ignore' will be skipped in the stream. +long Stream::parseInt(LookaheadMode lookahead, char ignore) +{ + bool isNegative = false; + long value = 0; + int c; + + c = peekNextDigit(lookahead, false); + // ignore non numeric leading characters + if(c < 0) + return 0; // zero returned if timeout + + do{ + if(c == ignore) + ; // ignore this character + else if(c == '-') + isNegative = true; + else if(c >= '0' && c <= '9') // is c a digit? + value = value * 10 + c - '0'; + read(); // consume the character we got with peek + c = timedPeek(); + } + while( (c >= '0' && c <= '9') || c == ignore ); + + if(isNegative) + value = -value; + return value; +} + +// as parseInt but returns a floating point value +float Stream::parseFloat(LookaheadMode lookahead, char ignore) +{ + bool isNegative = false; + bool isFraction = false; + long value = 0; + int c; + float fraction = 1.0; + + c = peekNextDigit(lookahead, true); + // ignore non numeric leading characters + if(c < 0) + return 0; // zero returned if timeout + + do{ + if(c == ignore) + ; // ignore + else if(c == '-') + isNegative = true; + else if (c == '.') + isFraction = true; + else if(c >= '0' && c <= '9') { // is c a digit? + value = value * 10 + c - '0'; + if(isFraction) + fraction *= 0.1; + } + read(); // consume the character we got with peek + c = timedPeek(); + } + while( (c >= '0' && c <= '9') || (c == '.' && !isFraction) || c == ignore ); + + if(isNegative) + value = -value; + if(isFraction) + return value * fraction; + else + return value; +} + +// read characters from stream into buffer +// terminates if length characters have been read, or timeout (see setTimeout) +// returns the number of characters placed in the buffer +// the buffer is NOT null terminated. +// +size_t Stream::readBytes(char *buffer, size_t length) +{ + size_t count = 0; + while (count < length) { + int c = timedRead(); + if (c < 0) break; + *buffer++ = (char)c; + count++; + } + return count; +} + + +// as readBytes with terminator character +// terminates if length characters have been read, timeout, or if the terminator character detected +// returns the number of characters placed in the buffer (0 means no valid data found) + +size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length) +{ + if (length < 1) return 0; + size_t index = 0; + while (index < length) { + int c = timedRead(); + if (c < 0 || c == terminator) break; + *buffer++ = (char)c; + index++; + } + return index; // return number of characters, not including null terminator +} + +String Stream::readString() +{ + String ret; + int c = timedRead(); + while (c >= 0) + { + ret += (char)c; + c = timedRead(); + } + return ret; +} + +String Stream::readStringUntil(char terminator) +{ + String ret; + int c = timedRead(); + while (c >= 0 && c != terminator) + { + ret += (char)c; + c = timedRead(); + } + return ret; +} + +int Stream::findMulti( struct Stream::MultiTarget *targets, int tCount) { + // any zero length target string automatically matches and would make + // a mess of the rest of the algorithm. + for (struct MultiTarget *t = targets; t < targets+tCount; ++t) { + if (t->len <= 0) + return t - targets; + } + + while (1) { + int c = timedRead(); + if (c < 0) + return -1; + + for (struct MultiTarget *t = targets; t < targets+tCount; ++t) { + // the simple case is if we match, deal with that first. + if (c == t->str[t->index]) { + if (++t->index == t->len) + return t - targets; + else + continue; + } + + // if not we need to walk back and see if we could have matched further + // down the stream (ie '1112' doesn't match the first position in '11112' + // but it will match the second position so we can't just reset the current + // index to 0 when we find a mismatch. + if (t->index == 0) + continue; + + int origIndex = t->index; + do { + --t->index; + // first check if current char works against the new current index + if (c != t->str[t->index]) + continue; + + // if it's the only char then we're good, nothing more to check + if (t->index == 0) { + t->index++; + break; + } + + // otherwise we need to check the rest of the found string + int diff = origIndex - t->index; + size_t i; + for (i = 0; i < t->index; ++i) { + if (t->str[i] != t->str[i + diff]) + break; + } + + // if we successfully got through the previous loop then our current + // index is good. + if (i == t->index) { + t->index++; + break; + } + + // otherwise we just try the next index + } while (t->index); + } + } + // unreachable + return -1; +} diff --git a/Stream.h b/Stream.h new file mode 100644 index 0000000..9e0d55a --- /dev/null +++ b/Stream.h @@ -0,0 +1,130 @@ +/* + Stream.h - base class for character-based streams. + Copyright (c) 2010 David A. Mellis. All right reserved. + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + parsing functions based on TextFinder library by Michael Margolis +*/ + +#ifndef UNIX_HOST_DUINO_STREAM_H +#define UNIX_HOST_DUINO_STREAM_H + +#include +#include "Print.h" + +// compatability macros for testing +/* +#define getInt() parseInt() +#define getInt(ignore) parseInt(ignore) +#define getFloat() parseFloat() +#define getFloat(ignore) parseFloat(ignore) +#define getString( pre_string, post_string, buffer, length) +readBytesBetween( pre_string, terminator, buffer, length) +*/ + +// This enumeration provides the lookahead options for parseInt(), parseFloat() +// The rules set out here are used until either the first valid character is found +// or a time out occurs due to lack of input. +enum LookaheadMode{ + SKIP_ALL, // All invalid characters are ignored. + SKIP_NONE, // Nothing is skipped, and the stream is not touched unless the first waiting character is valid. + SKIP_WHITESPACE // Only tabs, spaces, line feeds & carriage returns are skipped. +}; + +#define NO_IGNORE_CHAR '\x01' // a char not found in a valid ASCII numeric field + +class Stream : public Print +{ + protected: + unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read + unsigned long _startMillis; // used for timeout measurement + int timedRead(); // read stream with timeout + int timedPeek(); // peek stream with timeout + int peekNextDigit(LookaheadMode lookahead, bool detectDecimal); // returns the next numeric digit in the stream or -1 if timeout + + public: + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + + Stream() {_timeout=1000;} + +// parsing methods + + void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second + unsigned long getTimeout(void) { return _timeout; } + + bool find(char *target); // reads data from the stream until the target string is found + bool find(uint8_t *target) { return find ((char *)target); } + // returns true if target string is found, false if timed out (see setTimeout) + + bool find(char *target, size_t length); // reads data from the stream until the target string of given length is found + bool find(uint8_t *target, size_t length) { return find ((char *)target, length); } + // returns true if target string is found, false if timed out + + bool find(char target) { return find (&target, 1); } + + bool findUntil(char *target, char *terminator); // as find but search ends if the terminator string is found + bool findUntil(uint8_t *target, char *terminator) { return findUntil((char *)target, terminator); } + + bool findUntil(char *target, size_t targetLen, char *terminate, size_t termLen); // as above but search ends if the terminate string is found + bool findUntil(uint8_t *target, size_t targetLen, char *terminate, size_t termLen) {return findUntil((char *)target, targetLen, terminate, termLen); } + + long parseInt(LookaheadMode lookahead = SKIP_ALL, char ignore = NO_IGNORE_CHAR); + // returns the first valid (long) integer value from the current position. + // lookahead determines how parseInt looks ahead in the stream. + // See LookaheadMode enumeration at the top of the file. + // Lookahead is terminated by the first character that is not a valid part of an integer. + // Once parsing commences, 'ignore' will be skipped in the stream. + + float parseFloat(LookaheadMode lookahead = SKIP_ALL, char ignore = NO_IGNORE_CHAR); + // float version of parseInt + + size_t readBytes( char *buffer, size_t length); // read chars from stream into buffer + size_t readBytes( uint8_t *buffer, size_t length) { return readBytes((char *)buffer, length); } + // terminates if length characters have been read or timeout (see setTimeout) + // returns the number of characters placed in the buffer (0 means no valid data found) + + size_t readBytesUntil( char terminator, char *buffer, size_t length); // as readBytes with terminator character + size_t readBytesUntil( char terminator, uint8_t *buffer, size_t length) { return readBytesUntil(terminator, (char *)buffer, length); } + // terminates if length characters have been read, timeout, or if the terminator character detected + // returns the number of characters placed in the buffer (0 means no valid data found) + + // Arduino String functions to be added here + String readString(); + String readStringUntil(char terminator); + + protected: + long parseInt(char ignore) { return parseInt(SKIP_ALL, ignore); } + float parseFloat(char ignore) { return parseFloat(SKIP_ALL, ignore); } + // These overload exists for compatibility with any class that has derived + // Stream and used parseFloat/Int with a custom ignore character. To keep + // the public API simple, these overload remains protected. + + struct MultiTarget { + const char *str; // string you're searching for + size_t len; // length of string you're searching for + size_t index; // index used by the search routine. + }; + + // This allows you to search for an arbitrary number of strings. + // Returns index of the target that is found first or -1 if timeout occurs. + int findMulti(struct MultiTarget *targets, int tCount); +}; + +#undef NO_IGNORE_CHAR +#endif diff --git a/UnixHostDuino.mk b/UnixHostDuino.mk new file mode 100644 index 0000000..69f32a9 --- /dev/null +++ b/UnixHostDuino.mk @@ -0,0 +1,104 @@ +# Include this Makefile to compile an Arduino *.ino file on Linux or MacOS. +# +# Create a 'Makefile' in the sketch folder. For example, for the +# Blink/Blink.ino program, the makefile will be 'Blink/Makefile'. +# The content will look like this: +# +# APP_NAME := {name of *.ino file} +# ARDUINO_LIBS := AUnit AceTime {... additional Arduino libraries} +# include ../../../UnixHostDuino/UnixHostDuino.mk +# +# The 2 required parameters are: +# +# * APP_NAME: base name of the Arduino sketch file, +# e.g. 'Blink' not 'Blink.ino' +# * ARDUINO_LIBS: list of dependent Arduino libraries in sibling directories +# to UnixHostDuino (e.g. AUnit). The UnixHostDuino directory is +# automatically included. +# +# Optional parameters are: +# +# * OBJS: Additional object (*.o) files needed by the binary +# * GENERATED: A list of files which are generated by a script, and therefore +# can be deleted by 'make clean' +# +# Type 'make -n' to verify. +# +# Type 'make' to create the $(APP_NAME).out program. +# +# Type 'make clean' to remove intermediate files. + + +# Detect Linux or MacOS +UNAME := $(shell uname) + +# UnixHostDuino module directory. +UNIX_HOST_DUINO_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))) + +# If ARDUINO_LIB_DIR is not defined, assume that it's 1 level +# above the UnixHostDuino/ directory. +ARDUINO_LIB_DIR ?= $(realpath $(UNIX_HOST_DUINO_DIR)/..) + +# Default modules which are automatically linked in: UnixHostDuino/ +DEFAULT_MODULES := $(UNIX_HOST_DUINO_DIR) + +# Application Modules as specified by the application's ARDUINO_LIBS variable. +APP_MODULES := $(foreach lib,$(ARDUINO_LIBS),${ARDUINO_LIB_DIR}/${lib}) + +# All dependent modules. +ALL_MODULES := $(DEFAULT_MODULES) $(APP_MODULES) + +# Compiler and settings +ifeq ($(UNAME), Linux) +CXX ?= g++ +CXXFLAGS ?= -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics -flto +else ifeq ($(UNAME), Darwin) +CXX ?= clang++ +CXXFLAGS ?= -std=c++11 -stdlib=libc++ # -Weverything +endif + +# pre-processor (-I, -D, etc) +CPPFLAGS_EXPANSION = -I$(module) -I$(module)/src +CPPFLAGS ?= +CPPFLAGS += $(foreach module,$(ALL_MODULES),$(CPPFLAGS_EXPANSION)) + +# linker settings (e.g. -lm) +LDFLAGS ?= + +# C++ srcs. Old Arduino libraries place the source files at the top level. +# Later Arduino libraries put the source files under the src/ directory. +# Support subdirectory expansions up to 3 levels below 'src/'. +# (There might be a better way to do this using GNU Make but I can't find a +# mechanism that doesn't barf when the 'src/' directory doesn't exist.) +SRCS_EXPANSION = $(wildcard $(module)/*.cpp) \ + $(wildcard $(module)/src/*.cpp) \ + $(wildcard $(module)/src/*/*.cpp) \ + $(wildcard $(module)/src/*/*/*.cpp) \ + $(wildcard $(module)/src/*/*/*/*.cpp) +SRCS := $(foreach module,$(ALL_MODULES),$(SRCS_EXPANSION)) +SRCS := ${SRCS} $(wildcard *.cpp) $(wildcard */*.cpp) + +# Objects including *.o from *.ino +OBJS += $(SRCS:%.cpp=%.o) $(APP_NAME).o + +$(APP_NAME).out: $(OBJS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + +$(APP_NAME).o: $(APP_NAME).ino + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -x c++ -c $< + +%.o: %.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ + +# This simple rule does not capture all header dependencies of a given cpp +# file. Maybe it's better to make each cpp to depend on all headers of a given +# module, and force a recompilation of all cpp files. As far as I understand, +# this is what the Arduino IDE does upon each compile iteration. +%.cpp: %.h + +all: $(APP_NAME).out + +.PHONY: clean + +clean: + rm -f $(OBJS) $(APP_NAME).out $(GENERATED) diff --git a/WString.cpp b/WString.cpp new file mode 100644 index 0000000..f38e5f0 --- /dev/null +++ b/WString.cpp @@ -0,0 +1,751 @@ +/* + WString.cpp - String library for Wiring & Arduino + ...mostly rewritten by Paul Stoffregen... + Copyright (c) 2009-10 Hernando Barragan. All rights reserved. + Copyright 2011, Paul Stoffregen, paul@pjrc.com + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "WString.h" + +/*********************************************/ +/* Constructors */ +/*********************************************/ + +String::String(const char *cstr) +{ + init(); + if (cstr) copy(cstr, strlen(cstr)); +} + +String::String(const String &value) +{ + init(); + *this = value; +} + +String::String(const __FlashStringHelper *pstr) +{ + init(); + *this = pstr; +} + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +String::String(String &&rval) +{ + init(); + move(rval); +} +String::String(StringSumHelper &&rval) +{ + init(); + move(rval); +} +#endif + +String::String(char c) +{ + init(); + char buf[2]; + buf[0] = c; + buf[1] = 0; + *this = buf; +} + +String::String(unsigned char value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned char)]; + utoa(value, buf, base); + *this = buf; +} + +String::String(int value, unsigned char base) +{ + init(); + char buf[2 + 8 * sizeof(int)]; + itoa(value, buf, base); + *this = buf; +} + +String::String(unsigned int value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned int)]; + utoa(value, buf, base); + *this = buf; +} + +String::String(long value, unsigned char base) +{ + init(); + char buf[2 + 8 * sizeof(long)]; + ltoa(value, buf, base); + *this = buf; +} + +String::String(unsigned long value, unsigned char base) +{ + init(); + char buf[1 + 8 * sizeof(unsigned long)]; + ultoa(value, buf, base); + *this = buf; +} + +String::String(float value, unsigned char decimalPlaces) +{ + init(); + char buf[33]; + *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); +} + +String::String(double value, unsigned char decimalPlaces) +{ + init(); + char buf[33]; + *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); +} + +String::~String() +{ + free(buffer); +} + +/*********************************************/ +/* Memory Management */ +/*********************************************/ + +inline void String::init(void) +{ + buffer = NULL; + capacity = 0; + len = 0; +} + +void String::invalidate(void) +{ + if (buffer) free(buffer); + buffer = NULL; + capacity = len = 0; +} + +unsigned char String::reserve(unsigned int size) +{ + if (buffer && capacity >= size) return 1; + if (changeBuffer(size)) { + if (len == 0) buffer[0] = 0; + return 1; + } + return 0; +} + +unsigned char String::changeBuffer(unsigned int maxStrLen) +{ + char *newbuffer = (char *)realloc(buffer, maxStrLen + 1); + if (newbuffer) { + buffer = newbuffer; + capacity = maxStrLen; + return 1; + } + return 0; +} + +/*********************************************/ +/* Copy and Move */ +/*********************************************/ + +String & String::copy(const char *cstr, unsigned int length) +{ + if (!reserve(length)) { + invalidate(); + return *this; + } + len = length; + strcpy(buffer, cstr); + return *this; +} + +String & String::copy(const __FlashStringHelper *pstr, unsigned int length) +{ + if (!reserve(length)) { + invalidate(); + return *this; + } + len = length; + strcpy_P(buffer, (PGM_P)pstr); + return *this; +} + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +void String::move(String &rhs) +{ + if (buffer) { + if (rhs && capacity >= rhs.len) { + strcpy(buffer, rhs.buffer); + len = rhs.len; + rhs.len = 0; + return; + } else { + free(buffer); + } + } + buffer = rhs.buffer; + capacity = rhs.capacity; + len = rhs.len; + rhs.buffer = NULL; + rhs.capacity = 0; + rhs.len = 0; +} +#endif + +String & String::operator = (const String &rhs) +{ + if (this == &rhs) return *this; + + if (rhs.buffer) copy(rhs.buffer, rhs.len); + else invalidate(); + + return *this; +} + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +String & String::operator = (String &&rval) +{ + if (this != &rval) move(rval); + return *this; +} + +String & String::operator = (StringSumHelper &&rval) +{ + if (this != &rval) move(rval); + return *this; +} +#endif + +String & String::operator = (const char *cstr) +{ + if (cstr) copy(cstr, strlen(cstr)); + else invalidate(); + + return *this; +} + +String & String::operator = (const __FlashStringHelper *pstr) +{ + if (pstr) copy(pstr, strlen_P((PGM_P)pstr)); + else invalidate(); + + return *this; +} + +/*********************************************/ +/* concat */ +/*********************************************/ + +unsigned char String::concat(const String &s) +{ + return concat(s.buffer, s.len); +} + +unsigned char String::concat(const char *cstr, unsigned int length) +{ + unsigned int newlen = len + length; + if (!cstr) return 0; + if (length == 0) return 1; + if (!reserve(newlen)) return 0; + strcpy(buffer + len, cstr); + len = newlen; + return 1; +} + +unsigned char String::concat(const char *cstr) +{ + if (!cstr) return 0; + return concat(cstr, strlen(cstr)); +} + +unsigned char String::concat(char c) +{ + char buf[2]; + buf[0] = c; + buf[1] = 0; + return concat(buf, 1); +} + +unsigned char String::concat(unsigned char num) +{ + char buf[1 + 3 * sizeof(unsigned char)]; + itoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(int num) +{ + char buf[2 + 3 * sizeof(int)]; + itoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(unsigned int num) +{ + char buf[1 + 3 * sizeof(unsigned int)]; + utoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(long num) +{ + char buf[2 + 3 * sizeof(long)]; + ltoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(unsigned long num) +{ + char buf[1 + 3 * sizeof(unsigned long)]; + ultoa(num, buf, 10); + return concat(buf, strlen(buf)); +} + +unsigned char String::concat(float num) +{ + char buf[20]; + char* string = dtostrf(num, 4, 2, buf); + return concat(string, strlen(string)); +} + +unsigned char String::concat(double num) +{ + char buf[20]; + char* string = dtostrf(num, 4, 2, buf); + return concat(string, strlen(string)); +} + +unsigned char String::concat(const __FlashStringHelper * str) +{ + if (!str) return 0; + int length = strlen_P((const char *) str); + if (length == 0) return 1; + unsigned int newlen = len + length; + if (!reserve(newlen)) return 0; + strcpy_P(buffer + len, (const char *) str); + len = newlen; + return 1; +} + +/*********************************************/ +/* Concatenate */ +/*********************************************/ + +StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(rhs.buffer, rhs.len)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr) +{ + StringSumHelper &a = const_cast(lhs); + if (!cstr || !a.concat(cstr, strlen(cstr))) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, char c) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(c)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, int num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, long num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, float num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, double num) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(num)) a.invalidate(); + return a; +} + +StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs) +{ + StringSumHelper &a = const_cast(lhs); + if (!a.concat(rhs)) a.invalidate(); + return a; +} + +/*********************************************/ +/* Comparison */ +/*********************************************/ + +int String::compareTo(const String &s) const +{ + if (!buffer || !s.buffer) { + if (s.buffer && s.len > 0) return 0 - *(unsigned char *)s.buffer; + if (buffer && len > 0) return *(unsigned char *)buffer; + return 0; + } + return strcmp(buffer, s.buffer); +} + +unsigned char String::equals(const String &s2) const +{ + return (len == s2.len && compareTo(s2) == 0); +} + +unsigned char String::equals(const char *cstr) const +{ + if (len == 0) return (cstr == NULL || *cstr == 0); + if (cstr == NULL) return buffer[0] == 0; + return strcmp(buffer, cstr) == 0; +} + +unsigned char String::operator<(const String &rhs) const +{ + return compareTo(rhs) < 0; +} + +unsigned char String::operator>(const String &rhs) const +{ + return compareTo(rhs) > 0; +} + +unsigned char String::operator<=(const String &rhs) const +{ + return compareTo(rhs) <= 0; +} + +unsigned char String::operator>=(const String &rhs) const +{ + return compareTo(rhs) >= 0; +} + +unsigned char String::equalsIgnoreCase( const String &s2 ) const +{ + if (this == &s2) return 1; + if (len != s2.len) return 0; + if (len == 0) return 1; + const char *p1 = buffer; + const char *p2 = s2.buffer; + while (*p1) { + if (tolower(*p1++) != tolower(*p2++)) return 0; + } + return 1; +} + +unsigned char String::startsWith( const String &s2 ) const +{ + if (len < s2.len) return 0; + return startsWith(s2, 0); +} + +unsigned char String::startsWith( const String &s2, unsigned int offset ) const +{ + if (offset > len - s2.len || !buffer || !s2.buffer) return 0; + return strncmp( &buffer[offset], s2.buffer, s2.len ) == 0; +} + +unsigned char String::endsWith( const String &s2 ) const +{ + if ( len < s2.len || !buffer || !s2.buffer) return 0; + return strcmp(&buffer[len - s2.len], s2.buffer) == 0; +} + +/*********************************************/ +/* Character Access */ +/*********************************************/ + +char String::charAt(unsigned int loc) const +{ + return operator[](loc); +} + +void String::setCharAt(unsigned int loc, char c) +{ + if (loc < len) buffer[loc] = c; +} + +char & String::operator[](unsigned int index) +{ + static char dummy_writable_char; + if (index >= len || !buffer) { + dummy_writable_char = 0; + return dummy_writable_char; + } + return buffer[index]; +} + +char String::operator[]( unsigned int index ) const +{ + if (index >= len || !buffer) return 0; + return buffer[index]; +} + +void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const +{ + if (!bufsize || !buf) return; + if (index >= len) { + buf[0] = 0; + return; + } + unsigned int n = bufsize - 1; + if (n > len - index) n = len - index; + strncpy((char *)buf, buffer + index, n); + buf[n] = 0; +} + +/*********************************************/ +/* Search */ +/*********************************************/ + +int String::indexOf(char c) const +{ + return indexOf(c, 0); +} + +int String::indexOf( char ch, unsigned int fromIndex ) const +{ + if (fromIndex >= len) return -1; + const char* temp = strchr(buffer + fromIndex, ch); + if (temp == NULL) return -1; + return temp - buffer; +} + +int String::indexOf(const String &s2) const +{ + return indexOf(s2, 0); +} + +int String::indexOf(const String &s2, unsigned int fromIndex) const +{ + if (fromIndex >= len) return -1; + const char *found = strstr(buffer + fromIndex, s2.buffer); + if (found == NULL) return -1; + return found - buffer; +} + +int String::lastIndexOf( char theChar ) const +{ + return lastIndexOf(theChar, len - 1); +} + +int String::lastIndexOf(char ch, unsigned int fromIndex) const +{ + if (fromIndex >= len) return -1; + char tempchar = buffer[fromIndex + 1]; + buffer[fromIndex + 1] = '\0'; + char* temp = strrchr( buffer, ch ); + buffer[fromIndex + 1] = tempchar; + if (temp == NULL) return -1; + return temp - buffer; +} + +int String::lastIndexOf(const String &s2) const +{ + return lastIndexOf(s2, len - s2.len); +} + +int String::lastIndexOf(const String &s2, unsigned int fromIndex) const +{ + if (s2.len == 0 || len == 0 || s2.len > len) return -1; + if (fromIndex >= len) fromIndex = len - 1; + int found = -1; + for (char *p = buffer; p <= buffer + fromIndex; p++) { + p = strstr(p, s2.buffer); + if (!p) break; + if ((unsigned int)(p - buffer) <= fromIndex) found = p - buffer; + } + return found; +} + +String String::substring(unsigned int left, unsigned int right) const +{ + if (left > right) { + unsigned int temp = right; + right = left; + left = temp; + } + String out; + if (left >= len) return out; + if (right > len) right = len; + char temp = buffer[right]; // save the replaced character + buffer[right] = '\0'; + out = buffer + left; // pointer arithmetic + buffer[right] = temp; //restore character + return out; +} + +/*********************************************/ +/* Modification */ +/*********************************************/ + +void String::replace(char find, char replace) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) { + if (*p == find) *p = replace; + } +} + +void String::replace(const String& find, const String& replace) +{ + if (len == 0 || find.len == 0) return; + int diff = replace.len - find.len; + char *readFrom = buffer; + char *foundAt; + if (diff == 0) { + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { + memcpy(foundAt, replace.buffer, replace.len); + readFrom = foundAt + replace.len; + } + } else if (diff < 0) { + char *writeTo = buffer; + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { + unsigned int n = foundAt - readFrom; + memcpy(writeTo, readFrom, n); + writeTo += n; + memcpy(writeTo, replace.buffer, replace.len); + writeTo += replace.len; + readFrom = foundAt + find.len; + len += diff; + } + strcpy(writeTo, readFrom); + } else { + unsigned int size = len; // compute size needed for result + while ((foundAt = strstr(readFrom, find.buffer)) != NULL) { + readFrom = foundAt + find.len; + size += diff; + } + if (size == len) return; + if (size > capacity && !changeBuffer(size)) return; // XXX: tell user! + int index = len - 1; + while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) { + readFrom = buffer + index + find.len; + memmove(readFrom + diff, readFrom, len - (readFrom - buffer)); + len += diff; + buffer[len] = 0; + memcpy(buffer + index, replace.buffer, replace.len); + index--; + } + } +} + +void String::remove(unsigned int index){ + // Pass the biggest integer as the count. The remove method + // below will take care of truncating it at the end of the + // string. + remove(index, (unsigned int)-1); +} + +void String::remove(unsigned int index, unsigned int count){ + if (index >= len) { return; } + if (count <= 0) { return; } + if (count > len - index) { count = len - index; } + char *writeTo = buffer + index; + len = len - count; + strncpy(writeTo, buffer + index + count,len - index); + buffer[len] = 0; +} + +void String::toLowerCase(void) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) { + *p = tolower(*p); + } +} + +void String::toUpperCase(void) +{ + if (!buffer) return; + for (char *p = buffer; *p; p++) { + *p = toupper(*p); + } +} + +void String::trim(void) +{ + if (!buffer || len == 0) return; + char *begin = buffer; + while (isspace(*begin)) begin++; + char *end = buffer + len - 1; + while (isspace(*end) && end >= begin) end--; + len = end + 1 - begin; + if (begin > buffer) memcpy(buffer, begin, len); + buffer[len] = 0; +} + +/*********************************************/ +/* Parsing / Conversion */ +/*********************************************/ + +long String::toInt(void) const +{ + if (buffer) return atol(buffer); + return 0; +} + +float String::toFloat(void) const +{ + return float(toDouble()); +} + +double String::toDouble(void) const +{ + if (buffer) return atof(buffer); + return 0; +} diff --git a/WString.h b/WString.h new file mode 100644 index 0000000..9a36629 --- /dev/null +++ b/WString.h @@ -0,0 +1,228 @@ +/* + WString.h - String library for Wiring & Arduino + ...mostly rewritten by Paul Stoffregen... + Copyright (c) 2009-10 Hernando Barragan. All right reserved. + Copyright 2011, Paul Stoffregen, paul@pjrc.com + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef UNIX_HOST_DUINO_STRING_H +#define UNIX_HOST_DUINO_STRING_H +#ifdef __cplusplus + +#include +#include +#include +#include "pgmspace.h" +#include "avr_stdlib.h" + +// Macros for creating and using c-strings in PROGMEM. +// FPSTR() is defined for ESP8266 and ESP32 Cores, but not AVR or SAMD Cores. +class __FlashStringHelper; +#define FPSTR(p) (reinterpret_cast(p)) +#define F(s) FPSTR(PSTR(s)) + +// An inherited class for holding the result of a concatenation. These +// result objects are assumed to be writable by subsequent concatenations. +class StringSumHelper; + +// The string class +class String +{ + // use a function pointer to allow for "if (s)" without the + // complications of an operator bool(). for more information, see: + // http://www.artima.com/cppsource/safebool.html + typedef void (String::*StringIfHelperType)() const; + void StringIfHelper() const {} + +public: + // constructors + // creates a copy of the initial value. + // if the initial value is null or invalid, or if memory allocation + // fails, the string will be marked as invalid (i.e. "if (s)" will + // be false). + String(const char *cstr = ""); + String(const String &str); + String(const __FlashStringHelper *str); + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + String(String &&rval); + String(StringSumHelper &&rval); + #endif + explicit String(char c); + explicit String(unsigned char, unsigned char base=10); + explicit String(int, unsigned char base=10); + explicit String(unsigned int, unsigned char base=10); + explicit String(long, unsigned char base=10); + explicit String(unsigned long, unsigned char base=10); + explicit String(float, unsigned char decimalPlaces=2); + explicit String(double, unsigned char decimalPlaces=2); + ~String(void); + + // memory management + // return true on success, false on failure (in which case, the string + // is left unchanged). reserve(0), if successful, will validate an + // invalid string (i.e., "if (s)" will be true afterwards) + unsigned char reserve(unsigned int size); + inline unsigned int length(void) const {return len;} + + // creates a copy of the assigned value. if the value is null or + // invalid, or if the memory allocation fails, the string will be + // marked as invalid ("if (s)" will be false). + String & operator = (const String &rhs); + String & operator = (const char *cstr); + String & operator = (const __FlashStringHelper *str); + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + String & operator = (String &&rval); + String & operator = (StringSumHelper &&rval); + #endif + + // concatenate (works w/ built-in types) + + // returns true on success, false on failure (in which case, the string + // is left unchanged). if the argument is null or invalid, the + // concatenation is considered unsucessful. + unsigned char concat(const String &str); + unsigned char concat(const char *cstr); + unsigned char concat(char c); + unsigned char concat(unsigned char c); + unsigned char concat(int num); + unsigned char concat(unsigned int num); + unsigned char concat(long num); + unsigned char concat(unsigned long num); + unsigned char concat(float num); + unsigned char concat(double num); + unsigned char concat(const __FlashStringHelper * str); + + // if there's not enough memory for the concatenated value, the string + // will be left unchanged (but this isn't signalled in any way) + String & operator += (const String &rhs) {concat(rhs); return (*this);} + String & operator += (const char *cstr) {concat(cstr); return (*this);} + String & operator += (char c) {concat(c); return (*this);} + String & operator += (unsigned char num) {concat(num); return (*this);} + String & operator += (int num) {concat(num); return (*this);} + String & operator += (unsigned int num) {concat(num); return (*this);} + String & operator += (long num) {concat(num); return (*this);} + String & operator += (unsigned long num) {concat(num); return (*this);} + String & operator += (float num) {concat(num); return (*this);} + String & operator += (double num) {concat(num); return (*this);} + String & operator += (const __FlashStringHelper *str){concat(str); return (*this);} + + friend StringSumHelper & operator + (const StringSumHelper &lhs, const String &rhs); + friend StringSumHelper & operator + (const StringSumHelper &lhs, const char *cstr); + friend StringSumHelper & operator + (const StringSumHelper &lhs, char c); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned char num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned int num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, unsigned long num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, float num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, double num); + friend StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs); + + // comparison (only works w/ Strings and "strings") + operator StringIfHelperType() const { return buffer ? &String::StringIfHelper : 0; } + int compareTo(const String &s) const; + unsigned char equals(const String &s) const; + unsigned char equals(const char *cstr) const; + unsigned char operator == (const String &rhs) const {return equals(rhs);} + unsigned char operator == (const char *cstr) const {return equals(cstr);} + unsigned char operator != (const String &rhs) const {return !equals(rhs);} + unsigned char operator != (const char *cstr) const {return !equals(cstr);} + unsigned char operator < (const String &rhs) const; + unsigned char operator > (const String &rhs) const; + unsigned char operator <= (const String &rhs) const; + unsigned char operator >= (const String &rhs) const; + unsigned char equalsIgnoreCase(const String &s) const; + unsigned char startsWith( const String &prefix) const; + unsigned char startsWith(const String &prefix, unsigned int offset) const; + unsigned char endsWith(const String &suffix) const; + + // character acccess + char charAt(unsigned int index) const; + void setCharAt(unsigned int index, char c); + char operator [] (unsigned int index) const; + char& operator [] (unsigned int index); + void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index=0) const; + void toCharArray(char *buf, unsigned int bufsize, unsigned int index=0) const + { getBytes((unsigned char *)buf, bufsize, index); } + const char* c_str() const { return buffer; } + char* begin() { return buffer; } + char* end() { return buffer + length(); } + const char* begin() const { return c_str(); } + const char* end() const { return c_str() + length(); } + + // search + int indexOf( char ch ) const; + int indexOf( char ch, unsigned int fromIndex ) const; + int indexOf( const String &str ) const; + int indexOf( const String &str, unsigned int fromIndex ) const; + int lastIndexOf( char ch ) const; + int lastIndexOf( char ch, unsigned int fromIndex ) const; + int lastIndexOf( const String &str ) const; + int lastIndexOf( const String &str, unsigned int fromIndex ) const; + String substring( unsigned int beginIndex ) const { return substring(beginIndex, len); }; + String substring( unsigned int beginIndex, unsigned int endIndex ) const; + + // modification + void replace(char find, char replace); + void replace(const String& find, const String& replace); + void remove(unsigned int index); + void remove(unsigned int index, unsigned int count); + void toLowerCase(void); + void toUpperCase(void); + void trim(void); + + // parsing/conversion + long toInt(void) const; + float toFloat(void) const; + double toDouble(void) const; + +protected: + char *buffer; // the actual char array + unsigned int capacity; // the array length minus one (for the '\0') + unsigned int len; // the String length (not counting the '\0') +protected: + void init(void); + void invalidate(void); + unsigned char changeBuffer(unsigned int maxStrLen); + unsigned char concat(const char *cstr, unsigned int length); + + // copy and move + String & copy(const char *cstr, unsigned int length); + String & copy(const __FlashStringHelper *pstr, unsigned int length); + #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) + void move(String &rhs); + #endif +}; + +class StringSumHelper : public String +{ +public: + StringSumHelper(const String &s) : String(s) {} + StringSumHelper(const char *p) : String(p) {} + StringSumHelper(char c) : String(c) {} + StringSumHelper(unsigned char num) : String(num) {} + StringSumHelper(int num) : String(num) {} + StringSumHelper(unsigned int num) : String(num) {} + StringSumHelper(long num) : String(num) {} + StringSumHelper(unsigned long num) : String(num) {} + StringSumHelper(float num) : String(num) {} + StringSumHelper(double num) : String(num) {} +}; + +#endif // __cplusplus +#endif // UNIX_HOST_DUINO_STRING_H diff --git a/Wire.cpp b/Wire.cpp new file mode 100644 index 0000000..22b08d0 --- /dev/null +++ b/Wire.cpp @@ -0,0 +1,351 @@ +/* + TwoWire.cpp - TWI/I2C library for Wiring & Arduino + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts + Modified 2017 by Chuck Todd (ctodd@cableone.net) to correct Unconfigured Slave Mode reboot +*/ + +extern "C" { + #include + #include + #include + //#include "utility/twi.h" +} + +#include "Wire.h" + +// Initialize Class Variables ////////////////////////////////////////////////// + +uint8_t TwoWire::rxBuffer[BUFFER_LENGTH]; +uint8_t TwoWire::rxBufferIndex = 0; +uint8_t TwoWire::rxBufferLength = 0; + +uint8_t TwoWire::txAddress = 0; +uint8_t TwoWire::txBuffer[BUFFER_LENGTH]; +uint8_t TwoWire::txBufferIndex = 0; +uint8_t TwoWire::txBufferLength = 0; + +uint8_t TwoWire::transmitting = 0; +void (*TwoWire::user_onRequest)(void); +void (*TwoWire::user_onReceive)(int); + +// Constructors //////////////////////////////////////////////////////////////// + +TwoWire::TwoWire() +{ +} + +// Public Methods ////////////////////////////////////////////////////////////// + +void TwoWire::begin(void) +{ + /* + rxBufferIndex = 0; + rxBufferLength = 0; + + txBufferIndex = 0; + txBufferLength = 0; + + twi_init(); + twi_attachSlaveTxEvent(onRequestService); // default callback must exist + twi_attachSlaveRxEvent(onReceiveService); // default callback must exist + */ +} + +void TwoWire::begin(uint8_t address) +{ + /* + begin(); + twi_setAddress(address); + */ +} + +void TwoWire::begin(int address) +{ + begin((uint8_t)address); +} + +void TwoWire::end(void) +{ + /* + twi_disable(); + */ +} + +void TwoWire::setClock(uint32_t clock) +{ + /* + twi_setFrequency(clock); + */ +} + +uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint32_t iaddress, uint8_t isize, uint8_t sendStop) +{ + /* + if (isize > 0) { + // send internal address; this mode allows sending a repeated start to access + // some devices' internal registers. This function is executed by the hardware + // TWI module on other processors (for example Due's TWI_IADR and TWI_MMR registers) + + beginTransmission(address); + + // the maximum size of internal address is 3 bytes + if (isize > 3){ + isize = 3; + } + + // write internal register address - most significant byte first + while (isize-- > 0) + write((uint8_t)(iaddress >> (isize*8))); + endTransmission(false); + } + + // clamp to buffer length + if(quantity > BUFFER_LENGTH){ + quantity = BUFFER_LENGTH; + } + // perform blocking read into buffer + uint8_t read = twi_readFrom(address, rxBuffer, quantity, sendStop); + // set rx buffer iterator vars + rxBufferIndex = 0; + rxBufferLength = read; + + return read; + */ + return 0; +} + +uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop) { + return requestFrom((uint8_t)address, (uint8_t)quantity, (uint32_t)0, (uint8_t)0, (uint8_t)sendStop); +} + +uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity) +{ + return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true); +} + +uint8_t TwoWire::requestFrom(int address, int quantity) +{ + return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true); +} + +uint8_t TwoWire::requestFrom(int address, int quantity, int sendStop) +{ + return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)sendStop); +} + +void TwoWire::beginTransmission(uint8_t address) +{ + // indicate that we are transmitting + transmitting = 1; + // set address of targeted slave + txAddress = address; + // reset tx buffer iterator vars + txBufferIndex = 0; + txBufferLength = 0; +} + +void TwoWire::beginTransmission(int address) +{ + beginTransmission((uint8_t)address); +} + +// +// Originally, 'endTransmission' was an f(void) function. +// It has been modified to take one parameter indicating +// whether or not a STOP should be performed on the bus. +// Calling endTransmission(false) allows a sketch to +// perform a repeated start. +// +// WARNING: Nothing in the library keeps track of whether +// the bus tenure has been properly ended with a STOP. It +// is very possible to leave the bus in a hung state if +// no call to endTransmission(true) is made. Some I2C +// devices will behave oddly if they do not see a STOP. +// +uint8_t TwoWire::endTransmission(uint8_t sendStop) +{ + /* + // transmit buffer (blocking) + uint8_t ret = twi_writeTo(txAddress, txBuffer, txBufferLength, 1, sendStop); + // reset tx buffer iterator vars + txBufferIndex = 0; + txBufferLength = 0; + // indicate that we are done transmitting + transmitting = 0; + return ret; + */ + return 0; +} + +// This provides backwards compatibility with the original +// definition, and expected behaviour, of endTransmission +// +uint8_t TwoWire::endTransmission(void) +{ + return endTransmission(true); +} + +// must be called in: +// slave tx event callback +// or after beginTransmission(address) +size_t TwoWire::write(uint8_t data) +{ + /* + if(transmitting){ + // in master transmitter mode + // don't bother if buffer is full + if(txBufferLength >= BUFFER_LENGTH){ + setWriteError(); + return 0; + } + // put byte in tx buffer + txBuffer[txBufferIndex] = data; + ++txBufferIndex; + // update amount in buffer + txBufferLength = txBufferIndex; + }else{ + // in slave send mode + // reply to master + twi_transmit(&data, 1); + } + */ + return 1; +} + +// must be called in: +// slave tx event callback +// or after beginTransmission(address) +size_t TwoWire::write(const uint8_t *data, size_t quantity) +{ + /* + if(transmitting){ + // in master transmitter mode + for(size_t i = 0; i < quantity; ++i){ + write(data[i]); + } + }else{ + // in slave send mode + // reply to master + twi_transmit(data, quantity); + } + return quantity; + */ + return 0; +} + +// must be called in: +// slave rx event callback +// or after requestFrom(address, numBytes) +int TwoWire::available(void) +{ + return rxBufferLength - rxBufferIndex; +} + +// must be called in: +// slave rx event callback +// or after requestFrom(address, numBytes) +int TwoWire::read(void) +{ + int value = -1; + + // get each successive byte on each call + if(rxBufferIndex < rxBufferLength){ + value = rxBuffer[rxBufferIndex]; + ++rxBufferIndex; + } + + return value; +} + +// must be called in: +// slave rx event callback +// or after requestFrom(address, numBytes) +int TwoWire::peek(void) +{ + int value = -1; + + if(rxBufferIndex < rxBufferLength){ + value = rxBuffer[rxBufferIndex]; + } + + return value; +} + +void TwoWire::flush(void) +{ + // XXX: to be implemented. +} + +// behind the scenes function that is called when data is received +void TwoWire::onReceiveService(uint8_t* inBytes, int numBytes) +{ + // don't bother if user hasn't registered a callback + if(!user_onReceive){ + return; + } + // don't bother if rx buffer is in use by a master requestFrom() op + // i know this drops data, but it allows for slight stupidity + // meaning, they may not have read all the master requestFrom() data yet + if(rxBufferIndex < rxBufferLength){ + return; + } + // copy twi rx buffer into local read buffer + // this enables new reads to happen in parallel + for(uint8_t i = 0; i < numBytes; ++i){ + rxBuffer[i] = inBytes[i]; + } + // set rx iterator vars + rxBufferIndex = 0; + rxBufferLength = numBytes; + // alert user program + user_onReceive(numBytes); +} + +// behind the scenes function that is called when data is requested +void TwoWire::onRequestService(void) +{ + // don't bother if user hasn't registered a callback + if(!user_onRequest){ + return; + } + // reset tx buffer iterator vars + // !!! this will kill any pending pre-master sendTo() activity + txBufferIndex = 0; + txBufferLength = 0; + // alert user program + user_onRequest(); +} + +// sets function called on slave write +void TwoWire::onReceive( void (*function)(int) ) +{ + user_onReceive = function; +} + +// sets function called on slave read +void TwoWire::onRequest( void (*function)(void) ) +{ + user_onRequest = function; +} + +// Preinstantiate Objects ////////////////////////////////////////////////////// + +TwoWire Wire = TwoWire(); + diff --git a/Wire.h b/Wire.h new file mode 100644 index 0000000..4fefcab --- /dev/null +++ b/Wire.h @@ -0,0 +1,90 @@ +/* + TwoWire.h - TWI/I2C library for Arduino & Wiring + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + Modified by Brian T. Park 2019. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts +*/ + +#ifndef UNIX_HOST_DUINO_WIRE_H +#define UNIX_HOST_DUINO_WIRE_H + +#include +#include "Stream.h" + +#define BUFFER_LENGTH 32 + +// WIRE_HAS_END means Wire has end() +#define WIRE_HAS_END 1 + +/** + * This class is just a stub under Linux and MacOS. It does not do anything + * real. + */ +class TwoWire : public Stream +{ + private: + static uint8_t rxBuffer[]; + static uint8_t rxBufferIndex; + static uint8_t rxBufferLength; + + static uint8_t txAddress; + static uint8_t txBuffer[]; + static uint8_t txBufferIndex; + static uint8_t txBufferLength; + + static uint8_t transmitting; + static void (*user_onRequest)(void); + static void (*user_onReceive)(int); + static void onRequestService(void); + static void onReceiveService(uint8_t*, int); + public: + TwoWire(); + void begin(); + void begin(uint8_t); + void begin(int); + void end(); + void setClock(uint32_t); + void beginTransmission(uint8_t); + void beginTransmission(int); + uint8_t endTransmission(void); + uint8_t endTransmission(uint8_t); + uint8_t requestFrom(uint8_t, uint8_t); + uint8_t requestFrom(uint8_t, uint8_t, uint8_t); + uint8_t requestFrom(uint8_t, uint8_t, uint32_t, uint8_t, uint8_t); + uint8_t requestFrom(int, int); + uint8_t requestFrom(int, int, int); + virtual size_t write(uint8_t); + virtual size_t write(const uint8_t *, size_t); + virtual int available(void); + virtual int read(void); + virtual int peek(void); + virtual void flush(void); + void onReceive( void (*)(int) ); + void onRequest( void (*)(void) ); + + inline size_t write(unsigned long n) { return write((uint8_t)n); } + inline size_t write(long n) { return write((uint8_t)n); } + inline size_t write(unsigned int n) { return write((uint8_t)n); } + inline size_t write(int n) { return write((uint8_t)n); } + using Print::write; +}; + +extern TwoWire Wire; + +#endif + diff --git a/avr_stdlib.cpp b/avr_stdlib.cpp new file mode 100644 index 0000000..9f9d8d6 --- /dev/null +++ b/avr_stdlib.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019 Brian T. Park + * MIT License + */ + +#include +#include +#include +#include "avr_stdlib.h" + +#define BUFSIZE (sizeof(int) * 8 + 1) + +// Copied and modified from https://people.cs.umu.se/isak/snippets/ltoa.c +// Copyright 1988-90 by Robert B. Stout dba MicroFirm +// Released to public domain, 1991. +char *itoa(int n, char *str, int base) { + if (36 < base || 2 > base) { + base = 10; /* can only use 0-9, A-Z */ + } + + char buf[BUFSIZE]; + char *tail = &buf[BUFSIZE - 1]; /* last character position */ + *tail-- = '\0'; + + char *head = str; + unsigned uarg; + if (10 == base && n < 0) { + *head++ = '-'; + uarg = -n; + } else { + uarg = n; + } + + unsigned i = 2; + if (uarg) { + for (i = 1; uarg; ++i) { + div_t r = div(uarg, base); + *tail-- = (char)(r.rem + ((9 < r.rem) ? ('A' - 10) : '0')); + uarg = r.quot; + } + } else { + *tail-- = '0'; + } + + memcpy(head, ++tail, i); + return str; +} + +// Copied and modified from https://people.cs.umu.se/isak/snippets/ltoa.c +// Copyright 1988-90 by Robert B. Stout dba MicroFirm +// Released to public domain, 1991. +char *utoa(unsigned n, char *str, int base) { + if (36 < base || 2 > base) { + base = 10; /* can only use 0-9, A-Z */ + } + + char buf[BUFSIZE]; + char *tail = &buf[BUFSIZE - 1]; /* last character position */ + *tail-- = '\0'; + + unsigned i = 2; + if (n) { + for (i = 1; n; ++i) { + div_t r = div(n, base); + *tail-- = (char)(r.rem + ((9 < r.rem) ? ('A' - 10) : '0')); + n = r.quot; + } + } else { + *tail-- = '0'; + } + + memcpy(str, ++tail, i); + return str; +} + +// Copied and modified from https://people.cs.umu.se/isak/snippets/ltoa.c +// Copyright 1988-90 by Robert B. Stout dba MicroFirm +// Released to public domain, 1991. +char *ltoa(long n, char *str, int base) { + if (36 < base || 2 > base) { + base = 10; /* can only use 0-9, A-Z */ + } + + char buf[BUFSIZE]; + char *tail = &buf[BUFSIZE - 1]; /* last character position */ + *tail-- = '\0'; + + char *head = str; + unsigned long uarg; + if (10 == base && n < 0) { + *head++ = '-'; + uarg = -n; + } else { + uarg = n; + } + + unsigned i = 2; + if (uarg) { + for (i = 1; uarg; ++i) { + ldiv_t r = ldiv(uarg, base); + *tail-- = (char)(r.rem + ((9 < r.rem) ? ('A' - 10) : '0')); + uarg = r.quot; + } + } else { + *tail-- = '0'; + } + + memcpy(head, ++tail, i); + return str; +} + +// Copied and modified from https://people.cs.umu.se/isak/snippets/ltoa.c +// Copyright 1988-90 by Robert B. Stout dba MicroFirm +// Released to public domain, 1991. +char *ultoa(unsigned long n, char *str, int base) { + if (36 < base || 2 > base) { + base = 10; /* can only use 0-9, A-Z */ + } + + char buf[BUFSIZE]; + char *tail = &buf[BUFSIZE - 1]; /* last character position */ + *tail-- = '\0'; + + unsigned i = 2; + if (n) { + for (i = 1; n; ++i) { + ldiv_t r = ldiv(n, base); + *tail-- = (char)(r.rem + ((9 < r.rem) ? ('A' - 10) : '0')); + n = r.quot; + } + } else { + *tail-- = '0'; + } + + memcpy(str, ++tail, i); + return str; +} + +// This is a terrible, hacky implementation of dtostrf() but this will never be +// used in production. It is only used to allow Arduino unit tests using AUnit +// to compile under Linux and MacOS. +char *dtostrf(double val, signed char width, unsigned char prec, char *s) { + char format[13]; + char swidth[5]; + itoa(width, swidth, 10); + char sprec[5]; + utoa(prec, sprec, 10); + + strcpy(format, "%"); + strcat(format, swidth); + strcat(format, "."); + strcat(format, sprec); + strcat(format, "f"); + + sprintf(s, format, val); + return s; +} diff --git a/avr_stdlib.h b/avr_stdlib.h new file mode 100644 index 0000000..391f8a7 --- /dev/null +++ b/avr_stdlib.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Brian T. Park + * MIT License + */ + +/** + * @file avr_stdlib.h + * + * Non-standard functions from AVR's library which do not exist on + * Linux or MacOS. This contains only the ones required to compile + * AUnit tests on Linux or MacOS. + */ + +#ifndef UNIX_HOST_DUINO_AVR_STDLIB_H +#define UNIX_HOST_DUINO_AVR_STDLIB_H + +extern "C" { + +char *itoa(int n, char *str, int base); +char *utoa(unsigned n, char *str, int base); +char *ltoa(long n, char *str, int base); +char *ultoa(unsigned long n, char *str, int base); +char *dtostrf(double val, signed char width, unsigned char prec, char *s); + +} + +#endif diff --git a/examples/BlinkSOS/BlinkSOS.ino b/examples/BlinkSOS/BlinkSOS.ino new file mode 100644 index 0000000..630a435 --- /dev/null +++ b/examples/BlinkSOS/BlinkSOS.ino @@ -0,0 +1,70 @@ +/* + * Blink SOS - An Arduino program that runs on Linux and MacOS natively. + * + * On Linux or Mac, type: + * * $ make + * * $ ./BlinkSOS.out + * + * Should print out the following: + * + * LED_BUILTIN: 1 + * LED: 1 + * SOS + * SOS + * SOS + * ... + */ + +#include + +#define LED LED_BUILTIN +#define LED_ON HIGH +#define LED_OFF LOW + +void setup() { +#if ! defined(UNIX_HOST_DUINO) + delay(1000); // some boards reboot twice +#endif + SERIAL_PORT_MONITOR.begin(115200); + while (!SERIAL_PORT_MONITOR); // For Leonardo/Micro + + SERIAL_PORT_MONITOR.print(F("LED_BUILTIN: ")); + SERIAL_PORT_MONITOR.println(LED_BUILTIN); + SERIAL_PORT_MONITOR.print(F("LED: ")); + SERIAL_PORT_MONITOR.println(LED); + + pinMode(LED, OUTPUT); + digitalWrite(LED, LED_OFF); +} + +void longOn() { + digitalWrite(LED, LED_ON); + delay(500); + digitalWrite(LED, LED_OFF); + delay(200); +} + +void shortOn() { + digitalWrite(LED, LED_ON); + delay(200); + digitalWrite(LED, LED_OFF); + delay(200); +} + +void loop() { + SERIAL_PORT_MONITOR.println(F("SOS")); + + shortOn(); + shortOn(); + shortOn(); + + longOn(); + longOn(); + longOn(); + + shortOn(); + shortOn(); + shortOn(); + + delay(1000); +} diff --git a/examples/BlinkSOS/Makefile b/examples/BlinkSOS/Makefile new file mode 100644 index 0000000..8a9c0fe --- /dev/null +++ b/examples/BlinkSOS/Makefile @@ -0,0 +1,6 @@ +# See https://github.com/bxparks/UnixHostDuino for documentation about this +# Makefile to compile and run Arduino programs natively on Linux or MacOS. + +APP_NAME := BlinkSOS +ARDUINO_LIBS := +include ../../../UnixHostDuino/UnixHostDuino.mk diff --git a/pgmspace.h b/pgmspace.h new file mode 100644 index 0000000..9c39eda --- /dev/null +++ b/pgmspace.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Brian T. Park + * MIT License + */ + +/** + * @file pgmspace.h + * + * A version of the file in the Arduino environment with + * enough mappings to allow AUnit tests to compile and run on Linux or MacOS. + */ + +#ifndef UNIX_HOST_DUINO_PGMSPACE_H +#define UNIX_HOST_DUINO_PGMSPACE_H + +#include + +#define PGM_P const char * +#define PGM_VOID_P const void * +#define PSTR(str) (str) + +#define PROGMEM + +#define pgm_read_byte(p) (* (const uint8_t*) (p)) +#define pgm_read_word(p) (* (const uint16_t*) (p)) +#define pgm_read_dword(p) (* (const uint32_t*) (p)) +#define pgm_read_float(p) (* (const float*) (p)) +#define pgm_read_ptr(p) (* (const void* const*) (p)) + +#define strlen_P strlen +#define strcat_P strcat +#define strcpy_P strcpy +#define strncpy_P strncpy +#define strcmp_P strcmp +#define strncmp_P strncmp +#define strcasecmp_P strcasecmp +#define strchr_P strchr +#define strrchr_P strrchr + +#endif