Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge v1.3.0 into master #63

Merged
merged 9 commits into from
Mar 28, 2022
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# Changelog

* Unreleased
* 1.3.0 (2022-03-28)
* Add support for `EXTRA_CPPFLAGS`, similar to `EXTRA_CXXFLAGS`.
* Add `digitalReadValue(pin, val)` to control the return value of
`digitalRead(pin)`.
* May be useful for testing purposes.
* The `pin` parameter must satisfy `0 <= pin < 32`, otherwise
`digitalReadValue()` is a no-op.
* See [PR#61](https://github.com/bxparks/EpoxyDuino/pull/61).
* Add an empty `EpoxyDuino.h` at the top level to stop warning messages from
the Arduino IDE.
* Fixes [Issue#62](https://github.com/bxparks/EpoxyDuino/issues/62).
* Add [libraries/EpoxyMockSTM32RTC](libraries/EpoxyMockSTM32RTC)
* A mock of the [STM32RTC](https://github.com/stm32duino/STM32RTC)
library.
* 1.2.3 (2022-02-24)
* Rename `unixhostduino_main()` to `epoxyduino_main()`, and make it
static. No need to expose it publicly.
Expand Down
14 changes: 14 additions & 0 deletions EpoxyDuino.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This header file is empty. Its sole purpose is to disable warnings in the
// Arduino IDE which identifies EpoxyDuino as an invalid Arduino library when it
// is installed inside the Arduino sketchbook libraries directory. The warning
// message looks like this:
//
// "Invalid library found in /home/brian/src/arduino/libraries/EpoxyDuino: no
// headers files (.h) found in /home/brian/src/arduino/libraries/EpoxyDuino"
//
// EpoxyDuino is *not* an Arduino library. But it is very convenient to install
// it along side the other Arduino libraries so that it can search for those
// libraries as siblings to its own location when they are referenced in the
// `ARDUINO_LIBS` Makefile variable. It can be installed somewhere else, but you
// then have to define the `ARDUINO_LIB_DIRS` variable in all your Makefiles to
// tell EpoxyDuino where to look for those libraries.
2 changes: 1 addition & 1 deletion EpoxyDuino.mk
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ endif
CXXFLAGS += $(EXTRA_CXXFLAGS)

# Pre-processor flags (-I, -D, etc), mostly for header files.
CPPFLAGS ?=
CPPFLAGS += $(EXTRA_CPPFLAGS)
# Define a macro to indicate that EpoxyDuino is being used. Defined here
# instead of Arduino.h so that files like 'compat.h' can determine the
# compile-time environment without having to include <Arduino.h>.
Expand Down
168 changes: 117 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,19 @@ also provided:
TimerOne (https://github.com/PaulStoffregen/TimerOne) library
* [EpoxyMockFastLED](libraries/EpoxyMockFastLED/): mock version of the
FastLED (https://github.com/FastLED/FastLED) library
* [EpoxyMockSTM32RTC](libraries/EpoxyMockSTM32RTC/): mock version of the
STM32RTC (https://github.com/stm32duino/STM32RTC) library

These mock libraries may be sufficient for a CI pipeline.

For actual application development, I have started to build a set of
libraries within EpoxyDuino which emulate the versions that run the actual
hardware:

* EpoxyFS: emulation of the ESP8266 LittleFS or ESP32 LittleFS
* EpoxyEepromAvr: emulation of AVR-flavored `EEPROM`
* EpoxyEepromEsp: emulation of ESP-flavored `EEPROM`
* [EpoxyFS](libraries/EpoxyFS): emulation of the ESP8266 LittleFS or
ESP32 LittleFS filesystem
* [EpoxyEepromAvr](libraries/EpoxyEepromAvr): emulation of AVR-flavored `EEPROM`
* [EpoxyEepromEsp](libraries/EpoxyEepromEsp): emulation of ESP-flavored `EEPROM`

If your program has limited hardware dependencies so that it is conceptually
portable to a vanilla Unix environment, EpoxyDuino may work well for you.
Expand All @@ -68,7 +71,7 @@ The disadvantages are:
environments (e.g. 16-bit `int` versus 32-bit `int`, or 32-bit `long` versus
64-bit `long`).

**Version**: 1.2.3 (2022-02-24)
**Version**: 1.3.0 (2022-03.28)

**Changelog**: See [CHANGELOG.md](CHANGELOG.md)

Expand All @@ -85,14 +88,17 @@ The disadvantages are:
* [Continuous Integration](#ContinuousIntegration)
* [Advanced Usage](#AdvancedUsage)
* [Alternate C++ Compiler](#AlternateCompiler)
* [Generated Source Code](#GeneratedSourceCode)
* [Additional Cpp Flags](#AdditionalCppFlags)
* [Additional Compiler Flags](#AdditionalCompilerFlags)
* [Additional Clean Up](#AdditionalCleanUp)
* [Additional Dependencies](#AdditionalDependencies)
* [Generated Source Code](#GeneratedSourceCode)
* [Alternate Arduino Core](#AlternateArduinoCore)
* [PlatformIO](#PlatformIO)
* [Command Line Flags and Arguments](#CommandLineFlagsAndArguments)
* [Debugging](#Debugging)
* [Valgrind](#Valgrind)
* [Controlling digitalRead()](#DigitalReadValue)
* [Supported Arduino Features](#SupportedArduinoFeatures)
* [Arduino Functions](#ArduinoFunctions)
* [Serial Port Emulation](#SerialPortEmulation)
Expand Down Expand Up @@ -449,52 +455,35 @@ Take a look at some of my GitHub Actions YAML config files:
### Alternate C++ Compiler

Normally the C++ compiler on Linux is `g++`. If you have `clang++` installed
you can use that instead by specifying the `CXX` environment variable:
you can use that instead by specifying the `CXX` makefile variable:
```
$ CXX=clang++ make
$ make CXX=clang++
```
(This sets the `CXX` shell environment variable temporarily, for the duration of
the `make` command, which causes `make` to set its internal `CXX` variable,
which causes `EpoxyDuino.mk` to use `clang++` over the default `g++`.)
(This tells `make` to set the `CXX` variable to `clang++` within the context of
`EpoxyDuino.mk` which causes `clang++` to be used over the default `g++`.)

The `clang++` compiler will sometimes catch a different set of programming
errors.
One reason to use `clang++` instead of `g++` is that the `clang++` compiler will
sometimes catch a different set of programming errors.

<a name="GeneratedSourceCode"></a>
### Generated Source Code
<a name="AdditionalCppFlags"></a>
### Additional Cpp Flags

If a source file is generated dynamically through a code generation script,
and the source file is *not* checked into the repository because it is too
dynamic, then you can include the generated files using the `GENERATED`
and the `OBJS` variables.

First add the list of generated files `*.cpp` or `*.c` to the `GENERATED`
variable. Then add the corresponding `*.o` files to the `OBJS` variable, like
this:
You can pass additional flags to the C preprocessor through the `EXTRA_CPPFLAGS`
variable, like this:

```
GENERATED := foo.cpp bar.cpp
OBJS := foo.o bar.o
APP_NAME := {name of project}
ARDUINO_LIBS := {list of dependent Arduino libraries}
include {path/to/EpoxyDuino.mk}
$ make EXTRA_CPPFLAGS='-D DEBUG=2'
```

foo.cpp: foo.h generate_foo.sh
./generate_foo.sh # creates 'foo.cpp'
<a name="AdditionalCompilerFlags"></a>
### Additional Compiler Flags

bar.cpp: bar.h generate_bar.sh
./generate_bar.sh # creates 'bar.cpp'
You can pass additional flags to the C++ compiler through the `EXTRA_CXXFLAGS`
variable, like this:

...
```

The `*.o` files in `OJBS` are passed to the linker when the `app.out` binary
file is created.

The `GENERATED` is not strictly required, since the default rules already know
how to compile the `*.o` files from the `*.cpp` or `*.c` files. The primary
effect of `GENERATED` currently is to cause the generated files to be removed
when `make clean` is called.
$ make EXTRA_CXXFLAGS='-g'
```

<a name="AdditionalCleanUp"></a>
### Additional Clean Up
Expand Down Expand Up @@ -535,6 +524,42 @@ DEPS := header1.h header2.h
include {path/to/EpoxyDuino.mk}
```

<a name="GeneratedSourceCode"></a>
### Generated Source Code

If a source file is generated dynamically through a code generation script,
and the source file is *not* checked into the repository because it is too
dynamic, then you can include the generated files using the `GENERATED`
and the `OBJS` variables.

First add the list of generated files `*.cpp` or `*.c` to the `GENERATED`
variable. Then add the corresponding `*.o` files to the `OBJS` variable, like
this:

```
GENERATED := foo.cpp bar.cpp
OBJS := foo.o bar.o
APP_NAME := {name of project}
ARDUINO_LIBS := {list of dependent Arduino libraries}
include {path/to/EpoxyDuino.mk}

foo.cpp: foo.h generate_foo.sh
./generate_foo.sh # creates 'foo.cpp'

bar.cpp: bar.h generate_bar.sh
./generate_bar.sh # creates 'bar.cpp'

...
```

The `*.o` files in `OJBS` are passed to the linker when the `app.out` binary
file is created.

The `GENERATED` is not strictly required, since the default rules already know
how to compile the `*.o` files from the `*.cpp` or `*.c` files. The primary
effect of `GENERATED` currently is to cause the generated files to be removed
when `make clean` is called.

<a name="AlternateArduinoCore"></a>
### Alternate Arduino Core

Expand Down Expand Up @@ -694,6 +719,45 @@ start:
When the program crashes because of a `nullptr` dereference, Valgrind will show
exactly where that happened in the source code.

<a name="DigitalReadValue"></a>
### Controlling digitalRead()

By default, the `digitalRead(pin)` function simply returns a 0, because
EpoxyDuino does not actually have any hardware pins. For testing purposes, it
can be useful to control the value that will be returned by a `digitalRead()`.

The `digitalReadValue(pin, val)` function sets the value that will be returned
by the corresponding `digitalRead(pin)`. Here is an example of how this can be
used:

```C++
#include <Arduino.h>

...
const uint8_t PIN = 8;

void something() {
uint8_t val = digitalRead(PIN); // val == 0

#if defined(EPOXY_DUINO)
digitalReadValue(PIN, 1);
#endif
val = digitalRead(PIN); // val == 1

#if defined(EPOXY_DUINO)
digitalReadValue(PIN, 0);
#endif
val = digitalRead(PIN); // val == 0
}
```

The `#if defined(EPOXY_DUINO)` is recommended because `digitalReadValue()` is
not a standard Arduino function. It is defined only in EpoxyDuino.

The `pin` parameter should satisfy `0 <= pin < 32`. If `pin >= 32`, then
`digitalReadValue()` is a no-op and the corresponding `digitalRead(pin)` will
always return 0.

<a name="SupportedArduinoFeatures"></a>
## Supported Arduino Features

Expand Down Expand Up @@ -799,8 +863,6 @@ worth the trade-off.
<a name="UnixLineMode"></a>
#### Unix Line Mode

(Added in v1.2.0)

The `Print` class in the Arduino API implements the `Print::println()` function
by printing the DOS line terminator characters `\r\n`. This decision make sense
when the serial port of the microcontroller is connected to a serial terminal,
Expand Down Expand Up @@ -873,8 +935,6 @@ test(myTest) {
<a name="EnableTerminalEcho"></a>
#### Enable Terminal Echno

(Added in v1.2.3)

By default, the `stdin` of the terminal is set to `NOECHO` mode for consistency
with the actual serial port of an Arduino microcontroller. However when running
a command line utility on a Unix terminal emulator using EpoxyDuino, it is often
Expand Down Expand Up @@ -1004,6 +1064,9 @@ intended. This limitation may be sufficient for Continuous Integration purposes.
library.
* [EpoxyMockFastLED](libraries/EpoxyMockFastLED/)
* Mock version of the FastLED (https://github.com/FastLED/FastLED) library.
* [EpoxyMockSTM32RTC](libraries/EpoxyMockSTM32RTC/)
* Mock version of the STM32RTC (https://github.com/stm32duino/STM32RTC)
library.
* EspMock (https://github.com/hsaturn/EspMock)
* This is a separate project that provides various mocks for functions and
libraries included with the ESP8266 and the ESP32 processors.
Expand All @@ -1015,18 +1078,18 @@ intended. This limitation may be sufficient for Continuous Integration purposes.

This library has Tier 1 support on:

* Ubuntu 18.04
* g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
* clang++ 8.0.0-3~ubuntu18.04.2
* clang++ 6.0.0-1ubuntu2
* GNU Make 4.1
* Ubuntu 20.04
* g++ (Ubuntu 9.3.0-10ubuntu2) 9.3.0
* Ubuntu 20.04.4 LTS
* g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
* clang++ version 10.0.0-4ubuntu1
* GNU Make 4.2.1

The following environments are Tier 2 because I do not test them often enough:

* Ubuntu 18.04 LTS
* g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
* clang++ 8.0.0-3~ubuntu18.04.2
* clang++ 6.0.0-1ubuntu2
* GNU Make 4.1
* Raspbian GNU/Linux 10 (buster)
* On Raspberry Pi Model 3B
* g++ (Raspbian 8.3.0-6+rpi1) 8.3.0
Expand Down Expand Up @@ -1136,3 +1199,6 @@ people ask similar questions later.
see [PR#32](https://github.com/bxparks/EpoxyDuino/pull/32).
* Simplify `StdioSerial` by Bernhard (@felias-fogg),
[Issue#43](https://github.com/bxparks/EpoxyDuino/issues/43).
* Add `digitalReadValue(pin, val)` to control the return value of
`digitalRead(pin)` by @CaioPellicani. See
[PR#61](https://github.com/bxparks/EpoxyDuino/pull/61).
18 changes: 17 additions & 1 deletion cores/epoxy/Arduino.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
// Arduino methods emulated in Unix
// -----------------------------------------------------------------------

static uint32_t digitalPinValues = 0;

void yield() {
usleep(1000); // prevents program from consuming 100% CPU
}
Expand All @@ -29,7 +31,21 @@ void pinMode(uint8_t /*pin*/, uint8_t /*mode*/) {}

void digitalWrite(uint8_t /*pin*/, uint8_t /*val*/) {}

int digitalRead(uint8_t /*pin*/) { return 0; }
int digitalRead(uint8_t pin) {
if (pin >= 32) return 0;

return (digitalPinValues & (((uint32_t)0x1) << pin)) != 0;
}

void digitalReadValue(uint8_t pin, uint8_t val) {
if (pin >= 32) return;

if (val == 0) {
digitalPinValues &= ~(((uint32_t)0x1) << pin);
} else {
digitalPinValues |= ((uint32_t)0x1) << pin;
}
}

int analogRead(uint8_t /*pin*/) { return 0; }

Expand Down
16 changes: 14 additions & 2 deletions cores/epoxy/Arduino.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
#define EPOXY_DUINO_EPOXY_ARDUINO_H

// xx.yy.zz => xxyyzz (without leading 0)
#define EPOXY_DUINO_VERSION 10203
#define EPOXY_DUINO_VERSION_STRING "1.2.3"
#define EPOXY_DUINO_VERSION 10300
#define EPOXY_DUINO_VERSION_STRING "1.3.0"

#include <algorithm> // min(), max()
#include <cmath> // abs()
Expand Down Expand Up @@ -223,6 +223,18 @@ void analogWrite(uint8_t pin, int val);
void analogWriteRange(uint32_t range);
#endif

/**
* Control the value that will be returned by `digitalRead(pin)` by setting it
* to `val`, where `val` is either 0 or 1. This may be useful for testing
* purposes. This works only if `pin < 32` because the underlying implementation
* uses a `uint32_t` for storage. If the `pin` is greater than or equal to 32,
* this function does nothing and `digitalRead(pin)` will return 0.
*
* This function is available only on EpoxyDuino. It is not a standard Arduino
* function, so it is not available when compiling on actual hardware.
*/
void digitalReadValue(uint8_t pin, uint8_t val);

unsigned long millis();
unsigned long micros();
void delay(unsigned long ms);
Expand Down
Loading