diff --git a/.gitignore b/.gitignore
index dec931d0..4bb3c08b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,7 +16,8 @@ Release/
*.vsarduino.h
__vm/
*.user
-*.vcxproj
-*.vcxproj.filters
+Grbl_Esp32.vcxproj
+Grbl_Esp32.vcxproj.filters
*.suo
Grbl_Esp32.ino.cpp
+packages/
diff --git a/Grbl_Esp32.sln b/Grbl_Esp32.sln
index 0ae0bade..ed5750dd 100644
--- a/Grbl_Esp32.sln
+++ b/Grbl_Esp32.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.29306.81
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Grbl_Esp32", "Grbl_Esp32.vcxproj", "{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests", "UnitTests.vcxproj", "{33ECE513-60D1-4949-A4A9-C95D353C2CF0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -21,6 +23,14 @@ Global
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Release|x64.Build.0 = Release|x64
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Release|x86.ActiveCfg = Release|Win32
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Release|x86.Build.0 = Release|Win32
+ {33ECE513-60D1-4949-A4A9-C95D353C2CF0}.Debug|x64.ActiveCfg = Debug|x64
+ {33ECE513-60D1-4949-A4A9-C95D353C2CF0}.Debug|x64.Build.0 = Debug|x64
+ {33ECE513-60D1-4949-A4A9-C95D353C2CF0}.Debug|x86.ActiveCfg = Debug|Win32
+ {33ECE513-60D1-4949-A4A9-C95D353C2CF0}.Debug|x86.Build.0 = Debug|Win32
+ {33ECE513-60D1-4949-A4A9-C95D353C2CF0}.Release|x64.ActiveCfg = Release|x64
+ {33ECE513-60D1-4949-A4A9-C95D353C2CF0}.Release|x64.Build.0 = Release|x64
+ {33ECE513-60D1-4949-A4A9-C95D353C2CF0}.Release|x86.ActiveCfg = Release|Win32
+ {33ECE513-60D1-4949-A4A9-C95D353C2CF0}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Grbl_Esp32/Grbl_Esp32.ino b/Grbl_Esp32/Grbl_Esp32.ino
index 9c907733..3a4d2ff6 100644
--- a/Grbl_Esp32/Grbl_Esp32.ino
+++ b/Grbl_Esp32/Grbl_Esp32.ino
@@ -18,7 +18,8 @@
along with Grbl. If not, see .
*/
-#include "src/Grbl.h"
+#ifndef UNIT_TEST
+# include "src/Grbl.h"
void setup() {
grbl_init();
@@ -27,3 +28,5 @@ void setup() {
void loop() {
run_once();
}
+
+#endif
diff --git a/Grbl_Esp32/src/Assert.h b/Grbl_Esp32/src/Assert.h
index 468f652d..5cbf55da 100644
--- a/Grbl_Esp32/src/Assert.h
+++ b/Grbl_Esp32/src/Assert.h
@@ -1,5 +1,9 @@
#pragma once
+#include "StackTrace/AssertionFailed.h"
+
+class AssertionFailed;
+
#undef Assert
#define Stringify(x) #x
@@ -7,7 +11,7 @@
#define Assert(condition, ...) \
{ \
if (!(condition)) { \
- const char* ch = #condition " (@line " Stringify2(__LINE__) ")"; \
- throw ch; \
+ const char* ch = #condition " (@line " Stringify2(__LINE__) ")"; \
+ throw AssertionFailed::create(ch, ##__VA_ARGS__); \
} \
}
diff --git a/Grbl_Esp32/src/Grbl.cpp b/Grbl_Esp32/src/Grbl.cpp
index 7a29bc4f..49372bcb 100644
--- a/Grbl_Esp32/src/Grbl.cpp
+++ b/Grbl_Esp32/src/Grbl.cpp
@@ -110,11 +110,16 @@ static void reset_variables() {
}
void run_once() {
- reset_variables();
- // Start Grbl main loop. Processes program inputs and executes them.
- // This can exit on a system abort condition, in which case run_once()
- // is re-executed by an enclosing loop.
- protocol_main_loop();
+ try {
+ reset_variables();
+ // Start Grbl main loop. Processes program inputs and executes them.
+ // This can exit on a system abort condition, in which case run_once()
+ // is re-executed by an enclosing loop.
+ protocol_main_loop();
+ } catch (AssertionFailed ex) {
+ // This means something is terribly broken:
+ grbl_sendf(CLIENT_ALL, "Critical error: %s", ex.stackTrace.c_str());
+ }
}
/*
diff --git a/Grbl_Esp32/src/Pins/PinAttributes.cpp b/Grbl_Esp32/src/Pins/PinAttributes.cpp
index ee56d1df..f67e6295 100644
--- a/Grbl_Esp32/src/Pins/PinAttributes.cpp
+++ b/Grbl_Esp32/src/Pins/PinAttributes.cpp
@@ -35,7 +35,7 @@ namespace Pins {
}
// If it's exclusive, we are not allowed to set it again:
- if (_value != Undefined && this->has(Exclusive) && _value != t._value) {
+ if (_value != Undefined._value && this->has(Exclusive) && _value != t._value) {
return true;
}
diff --git a/Grbl_Esp32/src/StackTrace/AssertionFailed.cpp b/Grbl_Esp32/src/StackTrace/AssertionFailed.cpp
new file mode 100644
index 00000000..a4000acf
--- /dev/null
+++ b/Grbl_Esp32/src/StackTrace/AssertionFailed.cpp
@@ -0,0 +1,83 @@
+#include "AssertionFailed.h"
+
+#include
+#include
+
+#ifdef ESP32
+# ifdef UNIT_TEST
+
+# include "debug_helpers.h"
+# include "WString.h"
+# include "stdio.h"
+
+AssertionFailed AssertionFailed::create(const char* condition, const char* msg, ...) {
+ String st = condition;
+ st += ": ";
+
+ char tmp[255];
+ va_list arg;
+ va_start(arg, msg);
+ size_t len = vsnprintf(tmp, 255, msg, arg);
+ tmp[254] = 0;
+ st += tmp;
+
+ st += " at: ";
+ st += esp_backtrace_print(10);
+
+ return AssertionFailed(st);
+}
+
+# else
+
+# include "stdio.h"
+
+AssertionFailed AssertionFailed::create(const char* condition, const char* msg, ...) {
+ String st = "\r\nError ";
+ st += condition;
+ st += " failed: ";
+
+ char tmp[255];
+ va_list arg;
+ va_start(arg, msg);
+ size_t len = vsnprintf(tmp, 255, msg, arg);
+ tmp[254] = 0;
+ st += tmp;
+
+ return AssertionFailed(st);
+}
+
+# endif
+
+#else
+
+# include
+# include
+# include
+# include "WString.h"
+
+extern void DumpStackTrace(std::ostringstream& builder);
+
+String stackTrace;
+
+std::exception AssertionFailed::create(const char* condition, const char* msg, ...) {
+ std::ostringstream oss;
+ oss << std::endl;
+ oss << "Error: " << std::endl;
+
+ char tmp[255];
+ va_list arg;
+ va_start(arg, msg);
+ size_t len = vsnprintf(tmp, 255, msg, arg);
+ tmp[254] = 0;
+ oss << tmp;
+
+ oss << " at ";
+ DumpStackTrace(oss);
+
+ // Store in a static temp:
+ static std::string info;
+ info = oss.str();
+ throw std::exception(info.c_str());
+}
+
+#endif
diff --git a/Grbl_Esp32/src/StackTrace/AssertionFailed.h b/Grbl_Esp32/src/StackTrace/AssertionFailed.h
new file mode 100644
index 00000000..d8766690
--- /dev/null
+++ b/Grbl_Esp32/src/StackTrace/AssertionFailed.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "WString.h"
+
+#ifdef ESP32
+class AssertionFailed {
+public:
+ String stackTrace;
+
+ AssertionFailed(String st) : stackTrace(st) {}
+
+ static AssertionFailed create(const char* condition) {
+ return create(condition, "Assertion failed");
+ }
+ static AssertionFailed create(const char* condition, const char* msg, ...);
+
+};
+
+#else
+# include
+
+class AssertionFailed {
+public:
+ String stackTrace;
+
+ static std::exception create(const char* condition) {
+ return create(condition, "Assertion failed");
+ }
+ static std::exception create(const char* condition, const char* msg, ...);
+};
+
+#endif
diff --git a/Grbl_Esp32/src/StackTrace/debug_helpers.cpp b/Grbl_Esp32/src/StackTrace/debug_helpers.cpp
new file mode 100644
index 00000000..d19d46c4
--- /dev/null
+++ b/Grbl_Esp32/src/StackTrace/debug_helpers.cpp
@@ -0,0 +1,92 @@
+#ifdef ESP32
+# ifdef UNIT_TEST
+
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+# include
+# include
+
+# include "esp_types.h"
+# include "esp_attr.h"
+# include "esp_err.h"
+# include "debug_helpers.h"
+// #include "esp32/rom/ets_sys.h"
+# include "soc/soc_memory_layout.h"
+# include "soc/cpu.h"
+
+static inline bool esp_stack_ptr_is_sane(uint32_t sp) {
+ return !(sp < 0x3ffae010UL || sp > 0x3ffffff0UL || ((sp & 0xf) != 0));
+}
+
+static inline uint32_t esp_cpu_process_stack_pc(uint32_t pc) {
+ if (pc & 0x80000000) {
+ //Top two bits of a0 (return address) specify window increment. Overwrite to map to address space.
+ pc = (pc & 0x3fffffff) | 0x40000000;
+ }
+ //Minus 3 to get PC of previous instruction (i.e. instruction executed before return address)
+ return pc - 3;
+}
+
+bool IRAM_ATTR esp_backtrace_get_next_frame(esp_backtrace_frame_t* frame) {
+ //Use frame(i-1)'s BS area located below frame(i)'s sp to get frame(i-1)'s sp and frame(i-2)'s pc
+ void* base_save = (void*)frame->sp; //Base save area consists of 4 words under SP
+ frame->pc = frame->next_pc;
+ frame->next_pc = *((uint32_t*)(((char*)base_save) - 16)); //If next_pc = 0, indicates frame(i-1) is the last frame on the stack
+ frame->sp = *((uint32_t*)(((char*)base_save) - 12));
+
+ //Return true if both sp and pc of frame(i-1) are sane, false otherwise
+ return (esp_stack_ptr_is_sane(frame->sp) && esp_ptr_executable((void*)esp_cpu_process_stack_pc(frame->pc)));
+}
+
+String IRAM_ATTR esp_backtrace_print(int depth) {
+ char buf[80];
+
+ //Check arguments
+ if (depth <= 0) {
+ return "";
+ }
+
+ //Initialize stk_frame with first frame of stack
+ esp_backtrace_frame_t stk_frame;
+ esp_backtrace_get_start(&(stk_frame.pc), &(stk_frame.sp), &(stk_frame.next_pc));
+ //esp_cpu_get_backtrace_start(&stk_frame);
+ String s = "backtrace:";
+ snprintf(buf, 80, "0x%08X:0x%08X ", esp_cpu_process_stack_pc(stk_frame.pc), stk_frame.sp);
+ s += buf;
+
+ //Check if first frame is valid
+ bool corrupted = (esp_stack_ptr_is_sane(stk_frame.sp) && esp_ptr_executable((void*)esp_cpu_process_stack_pc(stk_frame.pc))) ? false :
+ true;
+
+ uint32_t i = (depth <= 0) ? INT32_MAX : depth;
+ while (i-- > 0 && stk_frame.next_pc != 0 && !corrupted) {
+ if (!esp_backtrace_get_next_frame(&stk_frame)) { //Get previous stack frame
+ corrupted = true;
+ }
+ snprintf(buf, 80, "0x%08X:0x%08X ", esp_cpu_process_stack_pc(stk_frame.pc), stk_frame.sp);
+ s += buf;
+ }
+
+ //Print backtrace termination marker
+ if (corrupted) {
+ s += " |<-CORRUPTED";
+ } else if (stk_frame.next_pc != 0) { //Backtrace continues
+ s += " |<-CONTINUES";
+ }
+ return s;
+}
+
+# endif
+#endif
diff --git a/Grbl_Esp32/src/StackTrace/debug_helpers.h b/Grbl_Esp32/src/StackTrace/debug_helpers.h
new file mode 100644
index 00000000..a22286d4
--- /dev/null
+++ b/Grbl_Esp32/src/StackTrace/debug_helpers.h
@@ -0,0 +1,106 @@
+#ifdef ESP32
+# ifdef UNIT_TEST
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+# pragma once
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+# ifndef __ASSEMBLER__
+
+# include
+# include "esp_err.h"
+# include "soc/soc.h"
+
+# define ESP_WATCHPOINT_LOAD 0x40000000
+# define ESP_WATCHPOINT_STORE 0x80000000
+# define ESP_WATCHPOINT_ACCESS 0xC0000000
+
+/*
+ * @brief Structure used for backtracing
+ *
+ * This structure stores the backtrace information of a particular stack frame
+ * (i.e. the PC and SP). This structure is used iteratively with the
+ * esp_cpu_get_next_backtrace_frame() function to traverse each frame within a
+ * single stack. The next_pc represents the PC of the current frame's caller, thus
+ * a next_pc of 0 indicates that the current frame is the last frame on the stack.
+ *
+ * @note Call esp_backtrace_get_start() to obtain initialization values for
+ * this structure
+ */
+typedef struct {
+ uint32_t pc; /* PC of the current frame */
+ uint32_t sp; /* SP of the current frame */
+ uint32_t next_pc; /* PC of the current frame's caller */
+} esp_backtrace_frame_t;
+
+/**
+ * Get the first frame of the current stack's backtrace
+ *
+ * Given the following function call flow (B -> A -> X -> esp_backtrace_get_start),
+ * this function will do the following.
+ * - Flush CPU registers and window frames onto the current stack
+ * - Return PC and SP of function A (i.e. start of the stack's backtrace)
+ * - Return PC of function B (i.e. next_pc)
+ *
+ * @note This function is implemented in assembly
+ *
+ * @param[out] pc PC of the first frame in the backtrace
+ * @param[out] sp SP of the first frame in the backtrace
+ * @param[out] next_pc PC of the first frame's caller
+ */
+extern void esp_backtrace_get_start(uint32_t* pc, uint32_t* sp, uint32_t* next_pc);
+
+/**
+ * Get the next frame on a stack for backtracing
+ *
+ * Given a stack frame(i), this function will obtain the next stack frame(i-1)
+ * on the same call stack (i.e. the caller of frame(i)). This function is meant to be
+ * called iteratively when doing a backtrace.
+ *
+ * Entry Conditions: Frame structure containing valid SP and next_pc
+ * Exit Conditions:
+ * - Frame structure updated with SP and PC of frame(i-1). next_pc now points to frame(i-2).
+ * - If a next_pc of 0 is returned, it indicates that frame(i-1) is last frame on the stack
+ *
+ * @param[inout] frame Pointer to frame structure
+ *
+ * @return
+ * - True if the SP and PC of the next frame(i-1) are sane
+ * - False otherwise
+ */
+bool esp_backtrace_get_next_frame(esp_backtrace_frame_t* frame);
+
+/**
+ * @brief Print the backtrace of the current stack
+ *
+ * @param depth The maximum number of stack frames to print (should be > 0)
+ *
+ * @return
+ * - ESP_OK Backtrace successfully printed to completion or to depth limit
+ * - ESP_FAIL Backtrace is corrupted
+ */
+
+String esp_backtrace_print(int depth);
+
+# endif
+# ifdef __cplusplus
+}
+# endif
+
+# endif
+#endif
diff --git a/Grbl_Esp32/src/StackTrace/debug_helpers_asm.S b/Grbl_Esp32/src/StackTrace/debug_helpers_asm.S
new file mode 100644
index 00000000..1232a6de
--- /dev/null
+++ b/Grbl_Esp32/src/StackTrace/debug_helpers_asm.S
@@ -0,0 +1,63 @@
+#ifdef ESP32
+#ifdef UNIT_TEST
+
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+#include
+#include
+#include
+
+/*
+ * esp_backtrace_get_start(uint32_t *pc, uint32_t *sp, uint32_t *next_pc)
+ *
+ * High Addr
+ * ..................
+ * | i-3 BS |
+ * | i-1 locals | Function B
+ * .................. i-1 SP
+ * | i-2 BS |
+ * | i locals | Function A (Start of backtrace)
+ * ------------------ i SP
+ * | i-1 BS |
+ * | i+1 locals | Backtracing function (e.g. esp_backtrace_print())
+ * ------------------ i+1 SP
+ * | i BS |
+ * | i+2 locals | esp_backtrace_get_start() <- This function
+ * ------------------ i+2 SP
+ * | i+1 BS |
+ * | i+3 locals | xthal_window_spill()
+ * ------------------ i+3 SP
+ * .................. Low Addr
+ */
+ .section .iram1, "ax"
+ .align 4
+ .global esp_backtrace_get_start
+ .type esp_backtrace_get_start, @function
+esp_backtrace_get_start:
+ entry a1, 32
+ call8 xthal_window_spill //Spill registers onto stack (excluding this function)
+ //a2, a3, a4 should be out arguments for i SP, i PC, i-1 PC respectively. Use a5 and a6 as scratch
+ l32e a5, sp, -16 //Get i PC, which is ret addres of i+1
+ s32i a5, a2, 0 //Store i PC to arg *pc
+ l32e a6, sp, -12 //Get i+1 SP. Used to access i BS
+ l32e a5, a6, -12 //Get i SP
+ s32i a5, a3, 0 //Store i SP to arg *sp
+ l32e a5, a6, -16 //Get i-1 PC, which is ret address of i
+ s32i a5, a4, 0 //Store i-1 PC to arg *next_pc
+ retw
+
+#endif
+#endif
diff --git a/Grbl_Esp32/test/Pins/BasicGPIO.cpp b/Grbl_Esp32/test/Pins/BasicGPIO.cpp
new file mode 100644
index 00000000..a660368c
--- /dev/null
+++ b/Grbl_Esp32/test/Pins/BasicGPIO.cpp
@@ -0,0 +1,41 @@
+#include "../TestFramework.h"
+
+#include
+
+#ifdef ESP32
+
+extern "C" int __digitalRead(uint8_t pin);
+extern "C" void __pinMode(uint8_t pin, uint8_t mode);
+extern "C" void __digitalWrite(uint8_t pin, uint8_t val);
+
+namespace Pins {
+ Test(BasicGPIO, ReadGPIORaw) {
+ auto pin = 26;
+
+ // Enable driver, write high/low.
+ __pinMode(pin, OUTPUT);
+
+ __digitalWrite(pin, HIGH);
+ auto value = __digitalRead(pin);
+ Assert(value != 0);
+
+ __digitalWrite(pin, LOW);
+ value = __digitalRead(pin);
+ Assert(value == 0);
+
+ __digitalWrite(pin, HIGH);
+ value = __digitalRead(pin);
+ Assert(value != 0);
+
+ __digitalWrite(pin, LOW);
+ value = __digitalRead(pin);
+ Assert(value == 0);
+
+ // Disable driver, should read the last value (low).
+ __pinMode(pin, INPUT);
+ value = __digitalRead(pin);
+ Assert(value == 0);
+ }
+}
+
+#endif
diff --git a/Grbl_Esp32/test/Pins/Error.cpp b/Grbl_Esp32/test/Pins/Error.cpp
new file mode 100644
index 00000000..5435dc1a
--- /dev/null
+++ b/Grbl_Esp32/test/Pins/Error.cpp
@@ -0,0 +1,26 @@
+#include "../TestFramework.h"
+
+#include
+
+namespace Pins {
+ Test(Error, Pins) {
+ // Error pins should throw whenever they are used.
+
+ Pin errorPin = Pin::ERROR;
+
+ AssertThrow(errorPin.write(true));
+ AssertThrow(errorPin.read());
+
+ errorPin.setAttr(Pin::Attr::None);
+
+ AssertThrow(errorPin.write(true));
+ AssertThrow(errorPin.read());
+
+ AssertThrow(errorPin.attachInterrupt([](void* arg) {}, CHANGE));
+ AssertThrow(errorPin.detachInterrupt());
+
+ Assert(errorPin.capabilities() == Pin::Capabilities::None, "Incorrect caps");
+
+ Assert(errorPin.name() == "ERROR_PIN");
+ }
+}
diff --git a/Grbl_Esp32/test/Pins/GPIO.cpp b/Grbl_Esp32/test/Pins/GPIO.cpp
new file mode 100644
index 00000000..15f79355
--- /dev/null
+++ b/Grbl_Esp32/test/Pins/GPIO.cpp
@@ -0,0 +1,250 @@
+#include "../TestFramework.h"
+
+#include
+
+#ifdef ESP32
+extern "C" void __pinMode(uint8_t pin, uint8_t mode);
+extern "C" int __digitalRead(uint8_t pin);
+extern "C" void __digitalWrite(uint8_t pin, uint8_t val);
+
+struct GPIONative {
+ inline static void initialize() {
+ for (int i = 16; i <= 17; ++i) {
+ __pinMode(i, OUTPUT);
+ __digitalWrite(i, LOW);
+ }
+ }
+ inline static void mode(int pin, uint8_t mode) { __pinMode(pin, mode); }
+ inline static void write(int pin, bool val) { __digitalWrite(pin, val ? HIGH : LOW); }
+ inline static bool read(int pin) { return __digitalRead(pin) != LOW; }
+};
+#else
+# include
+
+struct GPIONative {
+ // We test GPIO pin 16 and 17, and GPIO 16 is wired directly to 17:
+ static void WriteVirtualCircuitHystesis(SoftwarePin* pins, int pin, bool value) {
+ switch (pin) {
+ case 16:
+ case 17:
+ pins[16].handlePadChange(value);
+ pins[17].handlePadChange(value);
+ break;
+ }
+ }
+
+ inline static void initialize() { SoftwareGPIO::instance().reset(WriteVirtualCircuitHystesis, false); }
+ inline static void mode(int pin, uint8_t mode) { SoftwareGPIO::instance().setMode(pin, mode); }
+ inline static void write(int pin, bool val) { SoftwareGPIO::instance().writeOutput(pin, val); }
+ inline static bool read(int pin) { return SoftwareGPIO::instance().read(pin); }
+};
+
+void digitalWrite(uint8_t pin, uint8_t val);
+void pinMode(uint8_t pin, uint8_t mode);
+int digitalRead(uint8_t pin);
+
+#endif
+
+namespace Pins {
+ Test(GPIO, BasicInputOutput1) {
+ GPIONative::initialize();
+ PinLookup::ResetAllPins();
+
+ Pin gpio16 = Pin::create("gpio.16");
+ Pin gpio17 = Pin::create("gpio.17");
+
+ gpio16.setAttr(Pin::Attr::Output);
+ gpio17.setAttr(Pin::Attr::Input);
+
+ Assert(false == gpio16.read());
+ Assert(false == gpio17.read());
+ Assert(false == GPIONative::read(16));
+ Assert(false == GPIONative::read(17));
+
+ gpio16.on();
+
+ Assert(true == gpio16.read());
+ Assert(true == gpio17.read());
+ Assert(true == GPIONative::read(16));
+ Assert(true == GPIONative::read(17));
+
+ gpio16.off();
+
+ Assert(false == gpio16.read());
+ Assert(false == gpio17.read());
+ Assert(false == GPIONative::read(16));
+ Assert(false == GPIONative::read(17));
+ }
+
+ Test(GPIO, BasicInputOutput2) {
+ GPIONative::initialize();
+ PinLookup::ResetAllPins();
+
+ Pin gpio16 = Pin::create("gpio.16");
+ Pin gpio17 = Pin::create("gpio.17");
+
+ gpio16.setAttr(Pin::Attr::Input);
+ gpio17.setAttr(Pin::Attr::Output);
+
+ Assert(false == gpio16.read());
+ Assert(false == gpio17.read());
+ Assert(false == GPIONative::read(16));
+ Assert(false == GPIONative::read(17));
+
+ gpio17.on();
+
+ Assert(true == gpio16.read());
+ Assert(true == gpio17.read());
+ Assert(true == GPIONative::read(16));
+ Assert(true == GPIONative::read(17));
+
+ gpio17.off();
+
+ Assert(false == gpio16.read());
+ Assert(false == gpio17.read());
+ Assert(false == GPIONative::read(16));
+ Assert(false == GPIONative::read(17));
+ }
+
+ void TestISR(int deltaRising, int deltaFalling, int mode) {
+ GPIONative::initialize();
+ PinLookup::ResetAllPins();
+
+ Pin gpio16 = Pin::create("gpio.16");
+ Pin gpio17 = Pin::create("gpio.17");
+
+ gpio16.setAttr(Pin::Attr::Input | Pin::Attr::ISR);
+ gpio17.setAttr(Pin::Attr::Output);
+
+ int hitCount = 0;
+ int expected = 0;
+ gpio16.attachInterrupt(
+ [](void* arg) {
+ int* hc = static_cast(arg);
+ ++(*hc);
+ },
+ mode,
+ &hitCount);
+
+ // Two ways to set I/O:
+ // 1. using on/off
+ // 2. external source (e.g. set softwareio pin value)
+ //
+ // We read as well, because that shouldn't modify the state.
+ //
+ // NOTE: Hysteresis tells us that we get changes a lot during a small
+ // window in time. Unfortunately, it's practically impossible to test
+ // because it bounces all over the place... TODO FIXME, some mechanism
+ // to cope with that.
+
+ for (int i = 0; i < 10; ++i) {
+ if (deltaRising) {
+ auto oldCount = hitCount;
+ gpio17.on();
+ delay(1);
+ auto newCount = hitCount;
+
+ Assert(oldCount < newCount, "Expected rise after set state");
+ } else {
+ gpio17.on();
+ }
+
+ if (deltaFalling) {
+ auto oldCount = hitCount;
+ gpio17.off();
+ delay(1);
+ auto newCount = hitCount;
+
+ Assert(oldCount < newCount, "Expected rise after set state");
+ } else {
+ gpio17.off();
+ }
+ }
+
+ // Detach interrupt. Regardless of what we do, it shouldn't change hitcount anymore.
+ gpio16.detachInterrupt();
+
+ auto oldCount = hitCount;
+ gpio17.on();
+ gpio17.off();
+ delay(1);
+ auto newCount = hitCount;
+
+ Assert(oldCount == newCount, "ISR hitcount error");
+ }
+
+ Test(GPIO, ISRRisingPin) { TestISR(1, 0, RISING); }
+
+ Test(GPIO, ISRFallingPin) { TestISR(0, 1, FALLING); }
+
+ Test(GPIO, ISRChangePin) { TestISR(1, 1, CHANGE); }
+
+ Test(GPIO, NativeForwardingInput) {
+ GPIONative::initialize();
+ PinLookup::ResetAllPins();
+
+ Pin gpio16 = Pin::create("gpio.16");
+ Pin gpio17 = Pin::create("gpio.17");
+
+ pinMode(16, INPUT);
+ gpio17.setAttr(Pin::Attr::Output);
+
+ Assert(LOW == digitalRead(16));
+ Assert(false == gpio17.read());
+ Assert(false == GPIONative::read(16));
+ Assert(false == GPIONative::read(17));
+
+ gpio17.on();
+
+ Assert(HIGH == digitalRead(16));
+ Assert(true == gpio17.read());
+ Assert(true == GPIONative::read(16));
+ Assert(true == GPIONative::read(17));
+
+ gpio17.off();
+
+ Assert(LOW == digitalRead(16));
+ Assert(false == gpio17.read());
+ Assert(false == GPIONative::read(16));
+ Assert(false == GPIONative::read(17));
+ }
+
+ Test(GPIO, NativeForwardingOutput) {
+ GPIONative::initialize();
+ PinLookup::ResetAllPins();
+
+ Pin gpio16 = Pin::create("gpio.16");
+ Pin gpio17 = Pin::create("gpio.17");
+
+ pinMode(16, OUTPUT);
+ gpio17.setAttr(Pin::Attr::Input);
+
+ digitalWrite(16, LOW);
+ Assert(LOW == digitalRead(16));
+ Assert(false == gpio17.read());
+ Assert(false == GPIONative::read(16));
+ Assert(false == GPIONative::read(17));
+
+ digitalWrite(16, HIGH);
+
+ Assert(HIGH == digitalRead(16));
+ Assert(true == gpio17.read());
+ Assert(true == GPIONative::read(16));
+ Assert(true == GPIONative::read(17));
+
+ digitalWrite(16, LOW);
+
+ Assert(LOW == digitalRead(16));
+ Assert(false == gpio17.read());
+ Assert(false == GPIONative::read(16));
+ Assert(false == GPIONative::read(17));
+ }
+
+ Test(GPIO, Name) {
+ GPIONative::initialize();
+ PinLookup::ResetAllPins();
+
+ Pin gpio16 = Pin::create("gpio.16");
+ Assert(gpio16.name().equals("GPIO.16"), "Name is %s", gpio16.name().c_str());
+ }
+}
diff --git a/Grbl_Esp32/test/Pins/Undefined.cpp b/Grbl_Esp32/test/Pins/Undefined.cpp
new file mode 100644
index 00000000..41fc6e33
--- /dev/null
+++ b/Grbl_Esp32/test/Pins/Undefined.cpp
@@ -0,0 +1,30 @@
+#include "../TestFramework.h"
+
+#include
+
+namespace Pins {
+ Test(Undefined, Pins) {
+ // Unassigned pins are not doing much...
+
+ Pin unassigned = Pin::UNDEFINED;
+ Assert(Pin::UNDEFINED == unassigned, "Undefined has wrong pin id");
+
+ {
+ unassigned.write(true);
+ auto result = unassigned.read();
+ Assert(0 == result, "Result value incorrect");
+ }
+
+ {
+ unassigned.write(false);
+ auto result = unassigned.read();
+ Assert(0 == result, "Result value incorrect");
+ }
+
+ AssertThrow(unassigned.attachInterrupt([](void* arg) {}, CHANGE));
+ AssertThrow(unassigned.detachInterrupt());
+
+ Assert(unassigned.capabilities() == Pin::Capabilities::None);
+ Assert(unassigned.name().equals("UNDEFINED_PIN"));
+ }
+}
diff --git a/Grbl_Esp32/test/TestFactory.cpp b/Grbl_Esp32/test/TestFactory.cpp
new file mode 100644
index 00000000..de16befb
--- /dev/null
+++ b/Grbl_Esp32/test/TestFactory.cpp
@@ -0,0 +1,101 @@
+#include "TestFactory.h"
+
+#include
+#include
+
+#ifdef ESP32
+
+#include "unity.h"
+#include
+
+void TestFactory::runAll() {
+ int index = 0;
+ auto current = first;
+ const char* prev = nullptr;
+ while (current) {
+ ++index;
+ auto curTestName = current->unitTestName();
+ auto curTestCase = current->unitTestCase();
+
+ char fullName[80];
+ snprintf(fullName, 80, "%s:%s", curTestName, curTestCase);
+
+ auto function = current->getFunction();
+ UnityDefaultTestRun(function, fullName, index);
+
+ current = current->next;
+ }
+}
+
+#else
+
+# include
+# include
+# include
+
+# if defined _WIN32 || defined _WIN64
+# define WIN32_LEAN_AND_MEAN
+# include
+void setColor(int colorIndex) { // 10 = green, 12 = red, 7 = gray, 15 = white
+ HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
+ // you can loop k higher to see more color choices
+ // pick the colorattribute k you want
+ SetConsoleTextAttribute(hConsole, colorIndex);
+}
+# else
+void setColor(int colorIndex) {}
+# endif
+
+void TestFactory::runAll() {
+ const int Indent = 80;
+
+ char spaces[Indent];
+ memset(spaces, ' ', Indent - 1);
+ spaces[Indent - 1] = '\0';
+
+ auto current = first;
+ const char* prev = nullptr;
+ while (current) {
+ auto curTest = current->unitTestName();
+
+ setColor(15);
+
+ if (prev == nullptr || !strcmp(prev, curTest)) {
+ printf("- Test: %s\r\n", curTest);
+ prev = curTest;
+ }
+
+ setColor(7);
+
+ printf(" - Case: %s", current->unitTestCase());
+
+ int len = int(strlen(current->unitTestCase()));
+ if (len >= (Indent - 5)) {
+ len = (Indent - 5);
+ }
+ printf(spaces + len); // pad.
+
+ try {
+ setColor(10);
+
+ current->run();
+ printf("Passed.\r\n");
+ } catch (AssertionFailed& ex) {
+ setColor(12);
+ printf("FAILED!\r\n");
+ printf(ex.stackTrace.c_str());
+ printf("\r\n");
+ } catch (...) {
+ setColor(12);
+ printf("FAILED!\r\n");
+ // We don't know where unfortunately...
+ }
+ current = current->next;
+
+ setColor(7);
+ }
+
+ printf("\r\nDone.\r\n");
+}
+
+#endif
diff --git a/Grbl_Esp32/test/TestFactory.h b/Grbl_Esp32/test/TestFactory.h
new file mode 100644
index 00000000..8a0a28bc
--- /dev/null
+++ b/Grbl_Esp32/test/TestFactory.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include
+
+struct TestBase {
+ TestBase() : next(nullptr) {}
+ TestBase* next;
+
+ virtual const char* unitTestCase() const = 0;
+ virtual const char* unitTestName() const = 0;
+
+#ifdef ESP32
+ typedef void (*TestFunction)();
+ virtual TestFunction getFunction() = 0;
+#else
+#endif
+ virtual void run() = 0;
+};
+
+class TestFactory {
+ TestBase* first;
+ TestBase* last;
+
+ TestFactory() : first(nullptr), last(nullptr) {}
+ TestFactory(const TestFactory& o) = default;
+
+public:
+ static TestFactory& instance() {
+ static TestFactory instance_;
+ return instance_;
+ }
+
+ void registerTest(TestBase* test) {
+ if (last == nullptr) {
+ first = last = test;
+ } else {
+ last->next = test;
+ last = test;
+ }
+ }
+
+ void runAll();
+};
diff --git a/Grbl_Esp32/test/TestFramework.h b/Grbl_Esp32/test/TestFramework.h
new file mode 100644
index 00000000..90f996a0
--- /dev/null
+++ b/Grbl_Esp32/test/TestFramework.h
@@ -0,0 +1,133 @@
+#pragma once
+
+#ifdef ESP32
+
+# include
+# include "unity.h"
+
+# include
+# include "TestFactory.h"
+
+# define TEST_CLASS_NAME(testCase, testName) testCase##_##testName##_Test
+# define TEST_INST_NAME(testCase, testName) testCase##_##testName##_Test_Instance
+
+// Defines a single unit test. Basically creates a small test class.
+
+# define Test(testCase, testName) \
+ struct TEST_CLASS_NAME(testCase, testName) : TestBase { \
+ TEST_CLASS_NAME(testCase, testName)() { TestFactory::instance().registerTest(this); } \
+ \
+ const char* unitTestCase() const override { return #testCase; } \
+ const char* unitTestName() const override { return #testName; } \
+ \
+ static void runDetail(); \
+ static void runWrap() { \
+ try { \
+ runDetail(); \
+ } catch (AssertionFailed ex) { TEST_FAIL_MESSAGE(ex.stackTrace.c_str()); } catch (...) { \
+ TEST_FAIL_MESSAGE("Failed for unknown reason."); \
+ } \
+ } \
+ void run() override { runWrap(); } \
+ \
+ TestFunction getFunction() override { return runWrap; } \
+ }; \
+ \
+ TEST_CLASS_NAME(testCase, testName) TEST_INST_NAME(testCase, testName); \
+ \
+ void TEST_CLASS_NAME(testCase, testName)::runDetail()
+
+# define NativeTest(testCase, testName) TEST_INST_NAME(testCase, testName)
+# define PlatformTest(testCase, testName) Test(testCase, testName)
+
+inline void PrintSerial(const char* format, ...) {
+ va_list arg;
+ va_list copy;
+ va_start(arg, format);
+ va_copy(copy, arg);
+ size_t len = vsnprintf(NULL, 0, format, arg);
+ auto tmp = new char[len + 1];
+ va_end(copy);
+ len = vsnprintf(tmp, len + 1, format, arg);
+ Serial.println(tmp);
+ va_end(arg);
+ delete[] tmp;
+}
+
+# define Debug(fmt, ...) PrintSerial(fmt, __VA_ARGS__);
+
+# define AssertThrow(statement) \
+ try { \
+ statement; \
+ Assert(false, "Expected statement to throw."); \
+ } catch (...) {}
+
+#elif defined _WIN32 || defined _WIN64
+
+# include
+
+// Use 'Assert(...)' please.
+
+# define GTEST_DONT_DEFINE_TEST 1
+# define GTEST_DONT_DEFINE_ASSERT_EQ 1
+# define GTEST_DONT_DEFINE_ASSERT_NE 1
+# define GTEST_DONT_DEFINE_ASSERT_LT 1
+# define GTEST_DONT_DEFINE_ASSERT_LE 1
+# define GTEST_DONT_DEFINE_ASSERT_GE 1
+# define GTEST_DONT_DEFINE_ASSERT_GT 1
+# define GTEST_DONT_DEFINE_FAIL 1
+# define GTEST_DONT_DEFINE_SUCCEED 1
+
+# include "gtest/gtest.h"
+
+# undef EXPECT_THROW
+# undef EXPECT_NO_THROW
+# undef EXPECT_ANY_THROW
+# undef ASSERT_THROW
+# undef ASSERT_NO_THROW
+# undef ASSERT_ANY_THROW
+
+# define Test(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
+
+# define NativeTest(testCase, testName) Test(testCase, testName)
+# define PlatformTest(testCase, testName) TEST_INST_NAME(testCase, testName)
+
+# define Debug(fmt, ...) printf(fmt, __VA_ARGS__); printf("\r\n");
+
+# define AssertThrow(statement) GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_)
+
+#else
+
+# include
+# include "TestFactory.h"
+
+# define TEST_CLASS_NAME(testCase, testName) testCase##_##testName##_Test
+# define TEST_INST_NAME(testCase, testName) testCase##_##testName##_Test_Instance
+
+// Defines a single unit test. Basically creates a small test class.
+
+# define Test(testCase, testName) \
+ struct TEST_CLASS_NAME(testCase, testName) : TestBase { \
+ TEST_CLASS_NAME(testCase, testName)() { TestFactory::instance().registerTest(this); } \
+ \
+ const char* unitTestCase() const override { return #testCase; } \
+ const char* unitTestName() const override { return #testName; } \
+ void run() override; \
+ }; \
+ \
+ TEST_CLASS_NAME(testCase, testName) TEST_INST_NAME(testCase, testName); \
+ \
+ void TEST_CLASS_NAME(testCase, testName)::run()
+
+# define NativeTest(testCase, testName) Test(testCase, testName)
+# define PlatformTest(testCase, testName) TEST_INST_NAME(testCase, testName)
+
+# define Debug(fmt, ...) printf(fmt, __VA_ARGS__);
+
+# define AssertThrow(statement) \
+ try { \
+ statement; \
+ Assert(false, "Expected statement to throw."); \
+ } catch (...) {}
+
+#endif
diff --git a/Grbl_Esp32/test/TestFrameworkTest.cpp b/Grbl_Esp32/test/TestFrameworkTest.cpp
new file mode 100644
index 00000000..c7bffcf9
--- /dev/null
+++ b/Grbl_Esp32/test/TestFrameworkTest.cpp
@@ -0,0 +1,21 @@
+#include "TestFramework.h"
+
+/* Normally you don't want these:
+
+Test(PassingTest, TestFrameworkTest) {
+ Assert(1 == 1);
+}
+
+Test(FailingTest1, TestFrameworkTest) {
+ Assert(1 != 1);
+}
+
+Test(FailingTest2, TestFrameworkTest) {
+ Assert(1 != 1, "Oops");
+}
+
+Test(FailingTest3, TestFrameworkTest) {
+ throw "oops";
+}
+
+*/
diff --git a/Grbl_Esp32/test/UnitTests.md b/Grbl_Esp32/test/UnitTests.md
new file mode 100644
index 00000000..47360adf
--- /dev/null
+++ b/Grbl_Esp32/test/UnitTests.md
@@ -0,0 +1,41 @@
+# Google test code
+
+These unit tests are designed to test a small portion of the GRBL_ESP32
+code, directly from your desktop PC. This is not a complete test of
+GRBL_ESP32, but a starting point from which we can move on. Testing and
+debugging on a desktop machine is obviously much more convenient than
+it is on an ESP32 with a multitude of different configurations, not to
+mention the fact that you can use a large variety of tools such as
+code coverage, profiling, and so forth.
+
+Code here is split into two parts:
+1. A subset of the GRBL code is compiled. Over time, this will become more.
+2. Unit tests are executed on this code.
+
+## Prerequisites
+
+Google test framework.
+
+## Folders and how this works
+
+Support libraries are implemented that sort-of mimick the Arduino API where
+appropriate. This functionality might be extended in the future, and is by
+no means intended to be or a complete or even a "working" version; it's
+designed to be _testable_.
+
+Generally speaking that means that most features are simply not available.
+Things like GPIO just put stuff in a buffer, things like pins can be logged
+for analysis and so forth.
+
+The "Support" folder is the main thing that gives this mimicking ability,
+so that the code in the Grbl_Esp32 folder is able to compile. For example,
+when including ``, in fact `Support/Arduino.h` is included.
+
+The include folders that have to be passed to the x86/x64 compiler are:
+
+- X86TestSupport
+- ..\Grbl_Esp32
+
+## Test code
+
+Google tests can be found in the `Tests` folder.
diff --git a/Grbl_Esp32/test/test_main.cpp b/Grbl_Esp32/test/test_main.cpp
new file mode 100644
index 00000000..8540a30f
--- /dev/null
+++ b/Grbl_Esp32/test/test_main.cpp
@@ -0,0 +1,29 @@
+#ifdef ESP32
+
+# include "TestFactory.h"
+# include
+# include
+# include "unity.h"
+
+void test_blank() {
+ int i = 5;
+ TEST_ASSERT_EQUAL(i, 5);
+}
+
+void setup() {
+ delay(500); // Let's give it some time first, in case it triggers a reboot.
+
+ UNITY_BEGIN();
+
+ // calls to tests will go here
+ // RUN_TEST(test_blank);
+
+ // Run all tests:
+ TestFactory::instance().runAll();
+
+ UNITY_END(); // stop unit testing
+}
+
+void loop() {}
+
+#endif
diff --git a/UnitTests.vcxproj b/UnitTests.vcxproj
new file mode 100644
index 00000000..f321cc27
--- /dev/null
+++ b/UnitTests.vcxproj
@@ -0,0 +1,159 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {33ece513-60d1-4949-a4a9-c95d353c2cf0}
+ Win32Proj
+ 10.0.18362.0
+ Application
+ v142
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Use
+ pch.h
+ Disabled
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ EnableFastChecks
+ MultiThreadedDebugDLL
+ Level3
+
+
+ true
+ Console
+
+
+
+
+ NotUsing
+ pch.h
+ Disabled
+ X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ EnableFastChecks
+ MultiThreadedDebugDLL
+ Level3
+ true
+ $(MSBuildThisFileDirectory)X86TestSupport;$(MSBuildThisFileDirectory)GRBL_Esp32;%(AdditionalIncludeDirectories)
+
+
+ true
+ Console
+
+
+
+
+ Use
+ pch.h
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ MultiThreadedDLL
+ Level3
+ ProgramDatabase
+
+
+ true
+ Console
+ true
+ true
+
+
+
+
+ Use
+ pch.h
+ X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ MultiThreadedDLL
+ Level3
+ ProgramDatabase
+
+
+ true
+ Console
+ true
+ true
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
\ No newline at end of file
diff --git a/UnitTests.vcxproj.filters b/UnitTests.vcxproj.filters
new file mode 100644
index 00000000..0d9c89de
--- /dev/null
+++ b/UnitTests.vcxproj.filters
@@ -0,0 +1,156 @@
+
+
+
+
+ {d251c8e1-710b-4299-bf32-211cbc23e1d2}
+
+
+ {0a063b22-59ea-4f6c-a573-7e4aef718f3b}
+
+
+ {cc13daa8-4fd4-4995-a724-77db03c229af}
+
+
+ {2259029a-8770-45f8-889a-f9c25becf189}
+
+
+ {a068458b-6148-4377-9d86-41337ee3d689}
+
+
+ {a166a529-634f-48dc-b368-6c03f6f0a36f}
+
+
+
+
+ test
+
+
+ test
+
+
+ src
+
+
+ src
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\StackTrace
+
+
+ src\StackTrace
+
+
+ X86TestSupport
+
+
+ X86TestSupport
+
+
+ X86TestSupport
+
+
+
+
+ test
+
+
+ test
+
+
+ test
+
+
+ test\Pins
+
+
+ test\Pins
+
+
+ test\Pins
+
+
+ test\Pins
+
+
+ src
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\Pins
+
+
+ src\StackTrace
+
+
+ src\StackTrace
+
+
+ X86TestSupport
+
+
+ X86TestSupport
+
+
+ X86TestSupport
+
+
+
+
+ test
+
+
+ src\StackTrace
+
+
+
+
\ No newline at end of file
diff --git a/generate_vcxproj.py b/generate_vcxproj.py
index d0662e8c..c2624a6a 100644
--- a/generate_vcxproj.py
+++ b/generate_vcxproj.py
@@ -37,7 +37,7 @@ def FilterFromPath(path):
class Vcxproj:
# configuration, platform
ConfigurationFmt = '\n'.join([
- ' ',
+ ' ',
' {0}',
' {1}',
' '])
@@ -138,7 +138,12 @@ class Generator:
if filters != '':
self.Folders.add(filters)
- def AddFile(self, path):
+ def AddFile(self, path):
+ if path.find('/test/') >= 0:
+ return
+ elif path.find('\\test\\') >= 0:
+ return
+
(root, ext) = os.path.splitext(path)
if ext in HEADER_EXT:
self.Headers.add(path)
@@ -148,7 +153,7 @@ class Generator:
self.Others.add(path)
else:
return
-
+
self.AddFolder(path)
def Walk(self, path):
diff --git a/packages.config b/packages.config
new file mode 100644
index 00000000..6c6422e8
--- /dev/null
+++ b/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index cdd2e8a0..071e8ba9 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -1,6 +1,7 @@
[platformio]
src_dir=Grbl_Esp32
lib_dir=libraries
+test_dir=Grbl_Esp32/test
data_dir=Grbl_Esp32/data
default_envs=release
;extra_configs=debug.ini
@@ -28,8 +29,6 @@ build_flags =
-DCORE_DEBUG_LEVEL=0
-Wno-unused-variable
-Wno-unused-function
- ;-DDEBUG_REPORT_HEAP_SIZE
- ;-DDEBUG_REPORT_STACK_FREE
[env]
lib_deps =
@@ -51,9 +50,13 @@ board_build.flash_mode = qio
build_flags = ${common.build_flags}
src_filter =
+<*.h> +<*.s> +<*.S> +<*.cpp> +<*.c> +<*.ino> +
- -<.git/> - - -
+ -<.git/> - - - -
[env:release]
[env:debug]
build_type = debug
+
+[env:test]
+build_type = debug
+test_build_project_src = true
\ No newline at end of file