diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml
index f3a7483..c59765f 100644
--- a/.github/workflows/push-master.yml
+++ b/.github/workflows/push-master.yml
@@ -1,4 +1,4 @@
-name: HyperSerialEsp8266 CI Build
+name: HyperSerialESP32 CI Build
on: [push]
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..aca9b5c
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "files.associations": {
+ "algorithm": "cpp"
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index b6630e0..c922b94 100644
--- a/README.md
+++ b/README.md
@@ -24,13 +24,17 @@ Why the data integrity check was introduced which causes incompatibility with ot
Recommend to use [esphome-flasher](https://github.com/esphome/esphome-flasher/releases)
-For **RGBW LED strip** like RGBW SK6812 NEUTRAL white choose: *firmware_SK6812_RGBW_NEUTRAL.bin*
+ESP32-S2 lolin mini requires special firmware version (also provided)
+
+Generic ESP32:
+
+For **RGBW LED strip** like RGBW SK6812 NEUTRAL white choose: *firmware_esp32_SK6812_RGBW_NEUTRAL.bin*
-For **RGBW LED strip** like RGBW SK6812 COLD white choose: *firmware_SK6812_RGBW_COLD.bin*
+For **RGBW LED strip** like RGBW SK6812 COLD white choose: *firmware_esp32_SK6812_RGBW_COLD.bin*
-For **RGB LED strip** like WS8212b or RGB SK6812 variant choose: *firmware_WS281x_RGB.bin*
+For **RGB LED strip** like WS8212b or RGB SK6812 variant choose: *firmware_esp32_WS281x_RGB.bin*
-For **SPI driven RGB LED strip** APA102: *firmware_SPI_APA102_SK9822_HD107.bin*, WS8201: *firmware_SPI_WS2801.bin*
+For **SPI driven RGB LED strip** APA102: *firmware_esp32_SPI_APA102_SK9822_HD107.bin*, WS8201: *firmware_esp32_SPI_WS2801.bin*
If you want to disable your first LED because it's used as a sacrificial level shifter, please use [HyperHDR v19](https://github.com/awawa-dev/HyperHDR/pull/379)
@@ -38,23 +42,64 @@ For the RGBW firmware the white channel is automatically calculated and R,G,B ch
# Usage in HyperHDR
-Make sure you set "Refresh time" to zero, "Baudrate" to 2000000 and enabled HyperHDR's AWA protocol.
-Enabling "White channel calibration" is optional, if you want to fine tune the white channel balance of your sk6812 RGBW LED strip.
+Make sure you are using HyperHDR v19beta2 or above.
+Set `Refresh time` to zero, `Baudrate` to 2000000 and you enabled `HyperHDR's AWA protocol`.
+Enabling `White channel calibration` is optional, if you want to fine tune the white channel balance of your sk6812 RGBW LED strip.
+`ESP8266/ESP32 handshake` could help you to properly initialize the ESP device and enables statistics available in the logs (you must stop the LED device first to get them).
+
+![obraz](https://user-images.githubusercontent.com/69086569/207109594-0493fe58-3530-46bb-a0a3-31a110475ed6.png)
-![obraz](https://user-images.githubusercontent.com/69086569/192893595-324cfcf8-e247-438c-88ce-e52a29463121.png)
# Compiling
Currently we use PlatformIO to compile the project. Install [Visual Studio Code](https://code.visualstudio.com/) and add [PlatformIO plugin](https://platformio.org/).
This environment will take care of everything and compile the firmware for you.
-But there is also an alternative and an easier way. Just fork the project and enable its Github Action. Use the online editor to make changes to the ```platformio.ini``` file, for example change default pin-outs, and save it. Github Action will compile new firmware automatically in the Artifacts archive. It has never been so easy!
+But there is also an alternative and an easier way. Just fork the project and enable its Github Action. Use the online editor to make changes to the ```platformio.ini``` file, for example change default pin-outs/speed or enable multi-segments support, and save it. Github Action will compile new firmware automatically in the Artifacts archive. It has never been so easy!
+
+Tutorial: https://github.com/awawa-dev/HyperSerialESP32/wiki
# Pinout
**ESP32:**
**LED output (non-SPI):** GPIO 2
-**LED output (SPI):** GPIO 0 for Clock, GPIO 2 for Data
+**LED output (SPI):** GPIO 4 for Clock, GPIO 2 for Data
+
+# Some benchmark results
+
+ESP32 MH-ET LIVE mini is capable of 4Mb serial port speed and ESP32-S2 lolin mini is capable of 5Mb. But to give equal chances all models were tested using the default speed of 2Mb.
+
+## Multi-segments can double your large sk6812/ws2812b setup refresh rate for free. All you need is to properly project & construct the LED strip and use HyperSerialESP32 v8.
+
+| LED strip / Device | ESP32
MH-ET LIVE mini |
+|----------------------------------------------------------------------------------|--------------------------|
+| 300LEDs
Refresh rate/continues output=100Hz
SECOND_SEGMENT_START_INDEX=150 | 93-97 |
+| 600LEDs
Refresh rate/continues output=100Hz
SECOND_SEGMENT_START_INDEX=300 | 78-79 |
+| 900LEDs
Refresh rate/continues output=100Hz
SECOND_SEGMENT_START_INDEX=450 | 55-56 |
+
+## Comparing v6.1 and v8 version (single segment) refresh rate using MH-ET LIVE mini
+
+| LED strip / Device | ESP32
MH-ET LIVE mini
HyperSerialESP32 v6.1 | ESP32
MH-ET LIVE mini
HyperSerialESP32 v8 |
+|------------------------------------------------|---------------------------------------------------|-------------------------------------------------|
+| 300LEDs
Refresh rate/continues output=100Hz | 81-83 | 80-83 |
+| 600LEDs
Refresh rate/continues output=60Hz | 39-40 | 41-42 |
+| 900LEDs
Refresh rate/continues output=40Hz | 21-26 | 26-28 |
+
+## Comparing v6.1 and v8 version (single segment) refresh rate using generic ESP32 with CH340C
+
+| LED strip / Device | ESP32 (CH340C)
HyperSerialESP32 v6.1 | ESP32 (CH340C)
HyperSerialESP32 v8 |
+|------------------------------------------------|-----------------------------------------|---------------------------------------|
+| 300LEDs
Refresh rate/continues output=100Hz | 72-78 | 81-83 |
+| 600LEDs
Refresh rate/continues output=60Hz | 33-38 | 39-42 |
+| 900LEDs
Refresh rate/continues output=40Hz | 21-25 | 26-28 |
+
+## ESP32-S2 lolin mini performance
+
+| LED strip / Device | ESP32-S2 lolin mini
HyperSerialESP32 v8 |
+|------------------------------------------------|--------------------------------------------|
+| 300LEDs
Refresh rate/continues output=100Hz | 80-84 |
+| 600LEDs
Refresh rate/continues output=60Hz | 42 |
+| 900LEDs
Refresh rate/continues output=40Hz | 27-28 |
# Disclaimer
diff --git a/include/README b/include/README
deleted file mode 100644
index 194dcd4..0000000
--- a/include/README
+++ /dev/null
@@ -1,39 +0,0 @@
-
-This directory is intended for project header files.
-
-A header file is a file containing C declarations and macro definitions
-to be shared between several project source files. You request the use of a
-header file in your project source file (C, C++, etc) located in `src` folder
-by including it, with the C preprocessing directive `#include'.
-
-```src/main.c
-
-#include "header.h"
-
-int main (void)
-{
- ...
-}
-```
-
-Including a header file produces the same results as copying the header file
-into each source file that needs it. Such copying would be time-consuming
-and error-prone. With a header file, the related declarations appear
-in only one place. If they need to be changed, they can be changed in one
-place, and programs that include the header file will automatically use the
-new version when next recompiled. The header file eliminates the labor of
-finding and changing all the copies as well as the risk that a failure to
-find one copy will result in inconsistencies within a program.
-
-In C, the usual convention is to give header files names that end with `.h'.
-It is most portable to use only letters, digits, dashes, and underscores in
-header file names, and at most one dot.
-
-Read more about using header files in official GCC documentation:
-
-* Include Syntax
-* Include Operation
-* Once-Only Headers
-* Computed Includes
-
-https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
diff --git a/include/base.h b/include/base.h
new file mode 100644
index 0000000..22b1caa
--- /dev/null
+++ b/include/base.h
@@ -0,0 +1,174 @@
+/* base.h
+*
+* MIT License
+*
+* Copyright (c) 2022 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialESP32
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifndef BASE_H
+#define BASE_H
+
+#if defined(SECOND_SEGMENT_START_INDEX)
+ #if !defined(SECOND_SEGMENT_DATA_PIN)
+ #error "Please define SECOND_SEGMENT_DATA_PIN for second segment"
+ #elif !defined(SECOND_SEGMENT_CLOCK_PIN) && !defined(NEOPIXEL_RGBW) && !defined(NEOPIXEL_RGB)
+ #error "Please define SECOND_SEGMENT_CLOCK_PIN and SECOND_SEGMENT_DATA_PIN for second segment"
+ #endif
+#endif
+
+class Base
+{
+ // LED strip number
+ int ledsNumber = 0;
+ // NeoPixelBusLibrary primary object
+ LED_DRIVER* ledStrip1 = nullptr;
+ // NeoPixelBusLibrary second object
+ LED_DRIVER2* ledStrip2 = nullptr;
+ // frame is set and ready to render
+ bool readyToRender = false;
+
+ public:
+ // static data buffer for the loop
+ uint8_t buffer[MAX_BUFFER];
+ // handle to tasks
+ TaskHandle_t processTaskHandle;
+ // current queue position
+ uint16_t queueCurrent = 0;
+ // queue end position
+ volatile uint16_t queueEnd = 0;
+
+ inline int getLedsNumber()
+ {
+ return ledsNumber;
+ }
+
+ inline LED_DRIVER* getLedStrip1()
+ {
+ return ledStrip1;
+ }
+
+ inline LED_DRIVER2* getLedStrip2()
+ {
+ return ledStrip2;
+ }
+
+ void initLedStrip(int count)
+ {
+ if (ledStrip1 != nullptr)
+ {
+ delete ledStrip1;
+ ledStrip1 = nullptr;
+ }
+
+ if (ledStrip2 != nullptr)
+ {
+ delete ledStrip2;
+ ledStrip2 = nullptr;
+ }
+
+ ledsNumber = count;
+
+ #if defined(SECOND_SEGMENT_START_INDEX)
+ if (ledsNumber > SECOND_SEGMENT_START_INDEX)
+ {
+ #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB)
+ ledStrip1 = new LED_DRIVER(SECOND_SEGMENT_START_INDEX, DATA_PIN);
+ ledStrip1->Begin();
+ ledStrip2 = new LED_DRIVER2(ledsNumber - SECOND_SEGMENT_START_INDEX, SECOND_SEGMENT_DATA_PIN);
+ ledStrip2->Begin();
+ #else
+ ledStrip1 = new LED_DRIVER(SECOND_SEGMENT_START_INDEX);
+ ledStrip1->Begin(CLOCK_PIN, 12, DATA_PIN, 15);
+ ledStrip2 = new LED_DRIVER2(ledsNumber - SECOND_SEGMENT_START_INDEX);
+ ledStrip2->Begin(SECOND_SEGMENT_CLOCK_PIN, 12, SECOND_SEGMENT_DATA_PIN, 15);
+ #endif
+ }
+ #endif
+
+ if (ledStrip1 == nullptr)
+ {
+ #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB)
+ ledStrip1 = new LED_DRIVER(ledsNumber, DATA_PIN);
+ ledStrip1->Begin();
+ #else
+ ledStrip1 = new LED_DRIVER(ledsNumber);
+ ledStrip1->Begin(CLOCK_PIN, 12, DATA_PIN, 15);
+ #endif
+ }
+ }
+
+ /**
+ * @brief Check if there is already prepared frame to display
+ *
+ * @return true
+ * @return false
+ */
+ inline bool hasLateFrameToRender()
+ {
+ return readyToRender;
+ }
+
+ inline void renderLeds(bool newFrame)
+ {
+ if (newFrame)
+ readyToRender = true;
+
+ if (readyToRender &&
+ (ledStrip1 != nullptr && ledStrip1->CanShow()) &&
+ !(ledStrip2 != nullptr && !ledStrip2->CanShow()))
+ {
+ statistics.increaseShow();
+ readyToRender = false;
+
+ // display segments
+ ledStrip1->Show(false);
+ if (ledStrip2 != nullptr)
+ ledStrip2->Show(false);
+ }
+ }
+
+ inline bool setStripPixel(uint16_t pix, ColorDefinition &inputColor)
+ {
+ if (pix < ledsNumber)
+ {
+ #if defined(SECOND_SEGMENT_START_INDEX)
+ if (pix < SECOND_SEGMENT_START_INDEX)
+ ledStrip1->SetPixelColor(pix, inputColor);
+ else
+ {
+ #if defined(SECOND_SEGMENT_REVERSED)
+ ledStrip2->SetPixelColor(ledsNumber - pix - 1, inputColor);
+ #else
+ ledStrip2->SetPixelColor(pix - SECOND_SEGMENT_START_INDEX, inputColor);
+ #endif
+ }
+ #else
+ ledStrip1->SetPixelColor(pix, inputColor);
+ #endif
+ }
+
+ return (pix + 1 < ledsNumber);
+ }
+} base;
+
+#endif
\ No newline at end of file
diff --git a/include/calibration.h b/include/calibration.h
index 71c742e..d520837 100644
--- a/include/calibration.h
+++ b/include/calibration.h
@@ -25,7 +25,14 @@
* SOFTWARE.
*/
-#ifndef CALIBRATION_H
+#ifdef NEOPIXEL_RGBW
+ typedef RgbwColor ColorDefinition;
+#else
+ typedef RgbColor ColorDefinition;
+#endif
+
+
+#if !defined(CALIBRATION_H) && (defined(NEOPIXEL_RGBW) || defined(HYPERSERIAL_TESTING))
#define CALIBRATION_H
#include
@@ -33,40 +40,33 @@
#define ROUND_DIVIDE(numer, denom) (((numer) + (denom) / 2) / (denom))
-struct {
- uint8_t white[256];
- uint8_t red[256];
- uint8_t green[256];
- uint8_t blue[256];
+struct
+{
+ uint8_t white[256];
+ uint8_t red[256];
+ uint8_t green[256];
+ uint8_t blue[256];
} channelCorrection;
-struct {
- uint8_t gain = 0xFF;
- #ifdef COLD_WHITE
- uint8_t red = 0xA0; // adjust red -> white in 0-0xFF range
- uint8_t green = 0xA0; // adjust green -> white in 0-0xFF range
- uint8_t blue = 0xA0; // adjust blue -> white in 0-0xFF range
- #else
- uint8_t red = 0xB0; // adjust red -> white in 0-0xFF range
- uint8_t green = 0xB0; // adjust green -> white in 0-0xFF range
- uint8_t blue = 0x70; // adjust blue -> white in 0-0xFF range
- #endif
-
- void setParams(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue)
- {
- gain = _gain;
- red = _red;
- green = _green;
- blue = _blue;
- }
+class CalibrationConfig
+{
+ // calibration parameters
+ uint8_t gain = 0xFF;
+ uint8_t red = 0xA0;
+ uint8_t green = 0xA0;
+ uint8_t blue = 0xA0;
+ /**
+ * @brief Build the LUT table using provided parameters
+ *
+ */
void prepareCalibration()
{
// prepare LUT calibration table, cold white is much better than "neutral" white
for (uint32_t i = 0; i < 256; i++)
{
// color calibration
- uint32_t _gain = uint32_t(gain) * i; // adjust gain
+ uint32_t _gain = gain * i; // adjust gain
uint32_t _red = red * i; // adjust red
uint32_t _green = green * i; // adjust green
uint32_t _blue = blue * i; // adjust blue
@@ -77,6 +77,59 @@ struct {
channelCorrection.blue[i] = (uint8_t)std::min(ROUND_DIVIDE(_blue, 0xFF), (uint32_t)0xFF);
}
}
+
+ public:
+ CalibrationConfig()
+ {
+ prepareCalibration();
+ }
+
+ /**
+ * @brief Compare base calibration settings
+ *
+ */
+ bool compareCalibrationSettings(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue)
+ {
+ return _gain == gain && _red == red && _green == green && _blue == blue;
+ }
+
+ /**
+ * @brief Set the parameters that define RGB to RGBW transformation
+ *
+ * @param _gain
+ * @param _red
+ * @param _green
+ * @param _blue
+ */
+ void setParamsAndPrepareCalibration(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue)
+ {
+ if (gain != _gain || red != _red || green != _green || blue != _blue)
+ {
+ gain = _gain;
+ red = _red;
+ green = _green;
+ blue = _blue;
+ prepareCalibration();
+ }
+ }
+
+ /**
+ * @brief print RGBW calibration parameters when no data is received
+ *
+ */
+ void printCalibration()
+ {
+ #ifdef SerialPort
+ SerialPort.write("\r\nRGBW => Gain: ");
+ SerialPort.print(gain);
+ SerialPort.write("/255, red: ");
+ SerialPort.print(red);
+ SerialPort.write(" , green: ");
+ SerialPort.print(green);
+ SerialPort.write(" , blue: ");
+ SerialPort.print(blue);
+ #endif
+ }
} calibrationConfig;
#endif
diff --git a/include/framestate.h b/include/framestate.h
new file mode 100644
index 0000000..b5f51bc
--- /dev/null
+++ b/include/framestate.h
@@ -0,0 +1,260 @@
+/* framestate.h
+*
+* MIT License
+*
+* Copyright (c) 2022 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialESP32
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifndef FRAMESTATE_H
+#define FRAMESTATE_H
+
+/**
+ * @brief my AWA frame protocol definition
+ *
+ */
+enum class AwaProtocol
+{
+ HEADER_A,
+ HEADER_w,
+ HEADER_a,
+ HEADER_HI,
+ HEADER_LO,
+ HEADER_CRC,
+ VERSION2_GAIN,
+ VERSION2_RED,
+ VERSION2_GREEN,
+ VERSION2_BLUE,
+ RED,
+ GREEN,
+ BLUE,
+ FLETCHER1,
+ FLETCHER2,
+ FLETCHER_EXT
+};
+
+/**
+ * @brief Contains current state of the incoming frame
+ *
+ */
+class
+{
+ AwaProtocol state = AwaProtocol::HEADER_A;
+ bool protocolVersion2 = false;
+ uint8_t CRC = 0;
+ uint16_t count = 0;
+ uint16_t currentLed = 0;
+ uint16_t fletcher1 = 0;
+ uint16_t fletcher2 = 0;
+ uint16_t fletcherExt = 0;
+ uint8_t position = 0;
+
+ public:
+ ColorDefinition color;
+
+ /**
+ * @brief Reset statistics for new frame
+ *
+ * @param input
+ */
+ inline void init(byte input)
+ {
+ currentLed = 0;
+ count = input * 0x100;
+ CRC = input;
+ fletcher1 = 0;
+ fletcher2 = 0;
+ fletcherExt = 0;
+ position = 0;
+ }
+
+ /**
+ * @brief get computed CRC
+ *
+ * @return uint8_t
+ */
+ inline uint8_t getCRC()
+ {
+ return CRC;
+ }
+
+ /**
+ * @brief Get the color count reported by the frame
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getCount()
+ {
+ return count;
+ }
+
+ /**
+ * @brief Get the Fletcher1 total sum
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getFletcher1()
+ {
+ return fletcher1;
+ }
+
+ /**
+ * @brief Get the Fletcher2 total sum
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getFletcher2()
+ {
+ return fletcher2;
+ }
+
+ /**
+ * @brief Get the FletcherExt total sum
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getFletcherExt()
+ {
+ return (fletcherExt != 0x41) ? fletcherExt : 0xaa;
+ }
+
+ /**
+ * @brief Get and increase the current Led index
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getCurrentLedIndex()
+ {
+ return currentLed++;
+ }
+
+ /**
+ * @brief Set if frame protocol version 2 (contains calibration data)
+ *
+ * @param newVer
+ */
+ inline void setProtocolVersion2(bool newVer)
+ {
+ protocolVersion2 = newVer;
+ }
+
+ /**
+ * @brief Verify if frame protocol version 2 (contains calibration data)
+ *
+ * @return true
+ * @return false
+ */
+ inline bool isProtocolVersion2()
+ {
+ return protocolVersion2;
+ }
+
+ /**
+ * @brief Set new AWA frame state
+ *
+ * @param newState
+ */
+ inline void setState(AwaProtocol newState)
+ {
+ state = newState;
+ }
+
+ /**
+ * @brief Get current AWA frame state
+ *
+ * @return AwaProtocol
+ */
+ inline AwaProtocol getState()
+ {
+ return state;
+ }
+
+ /**
+ * @brief Update CRC based on current and previuos input
+ *
+ * @param input
+ */
+ inline void computeCRC(byte input)
+ {
+ count += input;
+ CRC = CRC ^ input ^ 0x55;
+ }
+
+ /**
+ * @brief Update Fletcher checksumn for incoming input
+ *
+ * @param input
+ */
+ inline void addFletcher(byte input)
+ {
+ fletcher1 = (fletcher1 + (uint16_t)input) % 255;
+ fletcher2 = (fletcher2 + fletcher1) % 255;
+ fletcherExt = (fletcherExt + (input ^ (position++))) % 255;
+ }
+
+ /**
+ * @brief Check if the calibration data was updated and calculate new one
+ *
+ */
+ inline void updateIncomingCalibration()
+ {
+ #ifdef NEOPIXEL_RGBW
+ if (protocolVersion2)
+ {
+ calibrationConfig.setParamsAndPrepareCalibration(calibration.gain, calibration.red, calibration.green, calibration.blue);
+ }
+ #endif
+ }
+
+
+ #ifdef NEOPIXEL_RGBW
+ /**
+ * @brief Compute && correct the white channel
+ *
+ */
+ inline void rgb2rgbw()
+ {
+ color.W = min(channelCorrection.red[color.R],
+ min(channelCorrection.green[color.G],
+ channelCorrection.blue[color.B]));
+ color.R -= channelCorrection.red[color.W];
+ color.G -= channelCorrection.green[color.W];
+ color.B -= channelCorrection.blue[color.W];
+ color.W = channelCorrection.white[color.W];
+ }
+ #endif
+
+ /**
+ * @brief Incoming calibration data
+ *
+ */
+ struct
+ {
+ uint8_t gain = 0;
+ uint8_t red = 0;
+ uint8_t green = 0;
+ uint8_t blue = 0;
+ } calibration;
+
+} frameState;
+
+#endif
\ No newline at end of file
diff --git a/include/main.h b/include/main.h
new file mode 100644
index 0000000..fa16422
--- /dev/null
+++ b/include/main.h
@@ -0,0 +1,276 @@
+/* main.h
+*
+* MIT License
+*
+* Copyright (c) 2022 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialESP32
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#define MAX_BUFFER 6000
+#define HELLO_MESSAGE "\r\nWelcome!\r\nAwa driver 8."
+
+#include "calibration.h"
+#include "statistics.h"
+#include "base.h"
+#include "framestate.h"
+
+/**
+ * @brief separete thread on core 1 for handling serial communication using cyclic buffer
+ *
+ */
+void serialTaskHandler()
+{
+ uint16_t incomingSize = min(SerialPort.available(), MAX_BUFFER);
+
+ if (incomingSize > 0)
+ {
+ if (base.queueEnd + incomingSize < MAX_BUFFER)
+ {
+ SerialPort.read(&(base.buffer[base.queueEnd]), incomingSize);
+ base.queueEnd += incomingSize;
+ }
+ else
+ {
+ int left = MAX_BUFFER - base.queueEnd;
+ SerialPort.read(&(base.buffer[base.queueEnd]), left);
+ SerialPort.read(&(base.buffer[0]), incomingSize - left);
+ base.queueEnd = incomingSize - left;
+ }
+ }
+}
+
+/**
+ * @brief process received data on core 0
+ *
+ */
+void processData()
+{
+ // update and print statistics
+ unsigned long currentTime = millis();
+ unsigned long deltaTime = currentTime - statistics.getStartTime();
+
+ if (base.queueCurrent != base.queueEnd && deltaTime >= 1000)
+ {
+ statistics.update(currentTime);
+ }
+ else if (deltaTime >= 3000)
+ {
+ frameState.setState(AwaProtocol::HEADER_A);
+ statistics.print(currentTime, base.processTaskHandle);
+ vTaskDelay(50);
+ }
+
+ // render waiting frame if available
+ if (base.hasLateFrameToRender() && frameState.getState() == AwaProtocol::HEADER_A)
+ base.renderLeds(false);
+
+ // process received data
+ while (base.queueCurrent != base.queueEnd)
+ {
+ byte input = base.buffer[base.queueCurrent++];
+
+ if (base.queueCurrent >= MAX_BUFFER)
+ base.queueCurrent = 0;
+
+ switch (frameState.getState())
+ {
+ case AwaProtocol::HEADER_A:
+ // assume it's protocol version 1, verify it later
+ frameState.setProtocolVersion2(false);
+ if (input == 'A')
+ frameState.setState(AwaProtocol::HEADER_w);
+ break;
+
+ case AwaProtocol::HEADER_w:
+ if (input == 'w')
+ frameState.setState(AwaProtocol::HEADER_a);
+ else
+ frameState.setState(AwaProtocol::HEADER_A);
+ break;
+
+ case AwaProtocol::HEADER_a:
+ // detect protocol version
+ if (input == 'a')
+ frameState.setState(AwaProtocol::HEADER_HI);
+ else if (input == 'A')
+ {
+ frameState.setState(AwaProtocol::HEADER_HI);
+ frameState.setProtocolVersion2(true);
+ }
+ else
+ frameState.setState(AwaProtocol::HEADER_A);
+ break;
+
+ case AwaProtocol::HEADER_HI:
+ // initialize new frame properties
+ statistics.increaseTotal();
+ frameState.init(input);
+ frameState.setState(AwaProtocol::HEADER_LO);
+ break;
+
+ case AwaProtocol::HEADER_LO:
+ frameState.computeCRC(input);
+ frameState.setState(AwaProtocol::HEADER_CRC);
+ break;
+
+ case AwaProtocol::HEADER_CRC:
+ // verify CRC and create/update LED driver if neccesery
+ if (frameState.getCRC() == input)
+ {
+ uint16_t ledSize = frameState.getCount() + 1;
+
+ // sanity check
+ if (ledSize > 4096)
+ frameState.setState(AwaProtocol::HEADER_A);
+ else
+ {
+ if (ledSize != base.getLedsNumber())
+ base.initLedStrip(ledSize);
+
+ frameState.setState(AwaProtocol::RED);
+ }
+ }
+ else if (frameState.getCount() == 0x2aa2 && (input == 0x15 || input == 0x35))
+ {
+ if (input == 0x15)
+ {
+ SerialPort.println(HELLO_MESSAGE);
+ statistics.reset(deltaTime);
+ }
+ else
+ statistics.print(currentTime, base.processTaskHandle);
+
+ frameState.setState(AwaProtocol::HEADER_A);
+ }
+ else
+ frameState.setState(AwaProtocol::HEADER_A);
+ break;
+
+ case AwaProtocol::RED:
+ frameState.color.R = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::GREEN);
+ break;
+
+ case AwaProtocol::GREEN:
+ frameState.color.G = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::BLUE);
+ break;
+
+ case AwaProtocol::BLUE:
+ frameState.color.B = input;
+ frameState.addFletcher(input);
+
+ #ifdef NEOPIXEL_RGBW
+ // calculate RGBW from RGB using provided calibration data
+ frameState.rgb2rgbw();
+ #endif
+
+ // set pixel, increase the index and check if it was the last LED color to come
+ if (base.setStripPixel(frameState.getCurrentLedIndex(), frameState.color))
+ {
+ frameState.setState(AwaProtocol::RED);
+ }
+ else
+ {
+ if (frameState.isProtocolVersion2())
+ frameState.setState(AwaProtocol::VERSION2_GAIN);
+ else
+ frameState.setState(AwaProtocol::FLETCHER1);
+ }
+
+ break;
+
+ case AwaProtocol::VERSION2_GAIN:
+ frameState.calibration.gain = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::VERSION2_RED);
+ break;
+
+ case AwaProtocol::VERSION2_RED:
+ frameState.calibration.red = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::VERSION2_GREEN);
+ break;
+
+ case AwaProtocol::VERSION2_GREEN:
+ frameState.calibration.green = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::VERSION2_BLUE);
+ break;
+
+ case AwaProtocol::VERSION2_BLUE:
+ frameState.calibration.blue = input;
+ frameState.addFletcher(input);
+
+ frameState.setState(AwaProtocol::FLETCHER1);
+ break;
+
+ case AwaProtocol::FLETCHER1:
+ // initial frame data integrity check
+ if (input != frameState.getFletcher1())
+ frameState.setState(AwaProtocol::HEADER_A);
+ else
+ frameState.setState(AwaProtocol::FLETCHER2);
+ break;
+
+ case AwaProtocol::FLETCHER2:
+ // initial frame data integrity check
+ if (input != frameState.getFletcher2())
+ frameState.setState(AwaProtocol::HEADER_A);
+ else
+ frameState.setState(AwaProtocol::FLETCHER_EXT);
+ break;
+
+ case AwaProtocol::FLETCHER_EXT:
+ // final frame data integrity check
+ if (input == frameState.getFletcherExt())
+ {
+ statistics.increaseGood();
+
+ base.renderLeds(true);
+
+ #ifdef NEOPIXEL_RGBW
+ // if received the calibration data, update it now
+ if (frameState.isProtocolVersion2())
+ {
+ frameState.updateIncomingCalibration();
+ }
+ #endif
+ }
+
+ frameState.setState(AwaProtocol::HEADER_A);
+ break;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/include/statistics.h b/include/statistics.h
new file mode 100644
index 0000000..ffb1704
--- /dev/null
+++ b/include/statistics.h
@@ -0,0 +1,159 @@
+/* stats.h
+*
+* MIT License
+*
+* Copyright (c) 2022 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialESP32
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#ifndef STATISTICS_H
+#define STATISTICS_H
+
+// statistics (stats sent only when there is no communication)
+class
+{
+ unsigned long startTime = 0;
+ uint16_t goodFrames = 0;
+ uint16_t showFrames = 0;
+ uint16_t totalFrames = 0;
+ uint16_t finalGoodFrames = 0;
+ uint16_t finalShowFrames = 0;
+ uint16_t finalTotalFrames = 0;
+
+ public:
+ /**
+ * @brief Get the start time of the current period
+ *
+ * @return unsigned long
+ */
+ inline unsigned long getStartTime()
+ {
+ return startTime;
+ }
+
+ /**
+ * @brief Detected new frame
+ *
+ */
+ inline void increaseTotal()
+ {
+ totalFrames++;
+ }
+
+ /**
+ * @brief The frame is received and shown
+ *
+ */
+ inline void increaseShow()
+ {
+ showFrames++;
+ }
+
+ /**
+ * @brief The frame is received correctly (not yet displayed)
+ *
+ */
+ inline void increaseGood()
+ {
+ goodFrames++;
+ }
+
+ /**
+ * @brief Get number of correctly received frames
+ *
+ * @return uint16_t
+ */
+ inline uint16_t getGoodFrames()
+ {
+ return goodFrames;
+ }
+
+ /**
+ * @brief Period restart, save current statistics ans send them later if there is no incoming communication
+ *
+ * @param currentTime
+ */
+ void update(unsigned long currentTime)
+ {
+ if (totalFrames > 0)
+ {
+ finalShowFrames = showFrames;
+ finalGoodFrames = std::min(goodFrames, totalFrames);
+ finalTotalFrames = totalFrames;
+ }
+
+ startTime = currentTime;
+ goodFrames = 0;
+ totalFrames = 0;
+ showFrames = 0;
+ }
+
+ /**
+ * @brief Print last saved statistics to the serial port
+ *
+ * @param curTime
+ * @param taskHandle
+ */
+ void print(unsigned long curTime, TaskHandle_t taskHandle)
+ {
+ startTime = curTime;
+ goodFrames = 0;
+ totalFrames = 0;
+ showFrames = 0;
+
+ SerialPort.write("\r\nHyperHDR frames: ");
+ SerialPort.print(finalShowFrames);
+ SerialPort.write(" (FPS), receiv.: ");
+ SerialPort.print(finalTotalFrames);
+ SerialPort.write(", good: ");
+ SerialPort.print(finalGoodFrames);
+ SerialPort.write(", incompl.: ");
+ SerialPort.print(finalTotalFrames - finalGoodFrames);
+ SerialPort.write(", mem: ");
+ SerialPort.print(uxTaskGetStackHighWaterMark(taskHandle));
+ SerialPort.write(", heap: ");
+ SerialPort.print(ESP.getFreeHeap());
+ #if defined(NEOPIXEL_RGBW)
+ calibrationConfig.printCalibration();
+ #endif
+
+ }
+
+ /**
+ * @brief Reset statistics
+ *
+ */
+ void reset(unsigned long currentTime)
+ {
+ startTime = currentTime;
+
+ finalShowFrames = 0;
+ finalGoodFrames = 0;
+ finalTotalFrames = 0;
+
+ goodFrames = 0;
+ totalFrames = 0;
+ showFrames = 0;
+ }
+} statistics;
+
+#endif
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index 8b9dcd1..1b44779 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -1,40 +1,138 @@
-; PlatformIO Project Configuration File
+; Configurable parameters:
+; SERIALCOM_SPEED = speed of the serial port (baud), global [env] section
+; DATA_PIN = pin/GPIO for the LED strip data channel, specific [board] section
+; CLOCK_PIN = pin/GPIO for the LED strip clock channel, specific [board] section
;
-; Build options: build flags, source filter
-; Upload options: custom upload port, speed and extra flags
-; Library options: dependencies, extra library storages
-; Advanced options: extra scripting
-;
-; Please visit documentation for the other options and examples
-; https://docs.platformio.org/page/projectconf.html
+; MULTI-SEGMENT SUPPORT
+; You can define second segment to handle. Add following parameters (with -D prefix to the build_flags sections).
+; SECOND_SEGMENT_START_INDEX = start index of the second segment
+; SECOND_SEGMENT_CLOCK_PIN = pin/GPIO for the second segment LED strip data channel
+; SECOND_SEGMENT_DATA_PIN = pin/GPIO for the second segment LED strip clock channel
+; SECOND_SEGMENT_REVERSED = if defined, the segment is reversed, the first LED of the second segment becomes the last
+; For example: we have first LED strip with 512 leds and the second LED strip with 400 leds.
+; For such configuration you need to set SECOND_SEGMENT_START_INDEX to 512 here, compile and upload the firmware.
+; In HyperHDR you should have a single LED strip that contains 912 leds.
+; SECOND_SEGMENT_REVERSED could be helpful if you start both segments in the middle of the bottom edge and join again at the top (both ends).
+; Example build string for the second segment (append it to the global [env] or to the specific [board] section):
+; build_flags = ... -DSECOND_SEGMENT_START_INDEX=512 -DSECOND_SEGMENT_DATA_PIN=4 -DSECOND_SEGMENT_REVERSED
+
+
[platformio]
-default_envs = SK6812_RGBW_COLD, SK6812_RGBW_NEUTRAL, WS281x_RGB, SPI_APA102_SK9822_HD107, SPI_WS2801
+default_envs = SK6812_RGBW_COLD, SK6812_RGBW_NEUTRAL, WS281x_RGB, SPI_APA102_SK9822_HD107, SPI_WS2801, s2_mini_SK6812_RGBW_COLD, s2_mini_SK6812_RGBW_NEUTRAL, s2_mini_WS281x_RGB, s2_mini_SPI_APA102_SK9822_HD107, s2_mini_SPI_WS2801
[env]
-platform = espressif32@3.5.0
-board = esp32dev
framework = arduino
-lib_deps = makuna/NeoPixelBus@2.6.9
extra_scripts = pre:extra_script.py
+build_flags = -DSERIALCOM_SPEED=2000000
-test_port = COM3
+;==========================================================
+; ESP32 board
+;==========================================================
+[esp32]
+platform = espressif32@3.5.0
+lib_deps = makuna/NeoPixelBus@2.6.9
+test_ignore =
[env:SK6812_RGBW_COLD]
-build_flags = -DNEOPIXEL_RGBW -DCOLD_WHITE -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2
-custom_prog_version = SK6812_RGBW_COLD
+build_flags = -DNEOPIXEL_RGBW -DCOLD_WHITE -DDATA_PIN=2 ${env.build_flags}
+custom_prog_version = esp32_SK6812_RGBW_COLD
+
+board = esp32dev
+platform = ${esp32.platform}
+lib_deps = ${esp32.lib_deps}
+test_ignore = ${esp32.test_ignore}
[env:SK6812_RGBW_NEUTRAL]
-build_flags = -DNEOPIXEL_RGBW -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2
-custom_prog_version = SK6812_RGBW_NEUTRAL
+build_flags = -DNEOPIXEL_RGBW -DDATA_PIN=2 ${env.build_flags}
+custom_prog_version = esp32_SK6812_RGBW_NEUTRAL
+
+board = esp32dev
+platform = ${esp32.platform}
+lib_deps = ${esp32.lib_deps}
+test_ignore = ${esp32.test_ignore}
[env:WS281x_RGB]
-build_flags = -DNEOPIXEL_RGB -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2
-custom_prog_version = WS281x_RGB
+build_flags = -DNEOPIXEL_RGB -DDATA_PIN=2 ${env.build_flags}
+custom_prog_version = esp32_WS281x_RGB
+
+board = esp32dev
+platform = ${esp32.platform}
+lib_deps = ${esp32.lib_deps}
+test_ignore = ${esp32.test_ignore}
[env:SPI_APA102_SK9822_HD107]
-build_flags = -DSPILED_APA102 -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2 -DCLOCK_PIN=0
-custom_prog_version = SPI_APA102_SK9822_HD107
+build_flags = -DSPILED_APA102 -DDATA_PIN=2 -DCLOCK_PIN=4 ${env.build_flags}
+custom_prog_version = esp32_SPI_APA102_SK9822_HD107
+
+board = esp32dev
+platform = ${esp32.platform}
+lib_deps = ${esp32.lib_deps}
+test_ignore = ${esp32.test_ignore}
[env:SPI_WS2801]
-build_flags = -DSPILED_WS2801 -DSERIALCOM_SPEED=2000000 -DDATA_PIN=2 -DCLOCK_PIN=0
-custom_prog_version = SPI_WS2801
+build_flags = -DSPILED_WS2801 -DDATA_PIN=2 -DCLOCK_PIN=4 ${env.build_flags}
+custom_prog_version = esp32_SPI_WS2801
+
+board = esp32dev
+platform = ${esp32.platform}
+lib_deps = ${esp32.lib_deps}
+test_ignore = ${esp32.test_ignore}
+
+;==========================================================
+; ESP32-S2 board
+;==========================================================
+[esp32_lolin_s2_mini]
+platform = espressif32@>=4.3.0
+lib_deps = makuna/NeoPixelBus@>=2.6.6
+test_ignore = *
+
+[env:s2_mini_SK6812_RGBW_COLD]
+build_flags = -DNEOPIXEL_RGBW -DCOLD_WHITE -DDATA_PIN=2 ${env.build_flags}
+custom_prog_version = esp32_s2_mini_SK6812_RGBW_COLD
+
+board = lolin_s2_mini
+board_build.mcu = esp32s2
+platform = ${esp32_lolin_s2_mini.platform}
+lib_deps = ${esp32_lolin_s2_mini.lib_deps}
+test_ignore = ${esp32_lolin_s2_mini.test_ignore}
+
+[env:s2_mini_SK6812_RGBW_NEUTRAL]
+build_flags = -DNEOPIXEL_RGBW -DDATA_PIN=2 ${env.build_flags}
+custom_prog_version = esp32_s2_mini_SK6812_RGBW_NEUTRAL
+
+board = lolin_s2_mini
+board_build.mcu = esp32s2
+platform = ${esp32_lolin_s2_mini.platform}
+lib_deps = ${esp32_lolin_s2_mini.lib_deps}
+test_ignore = ${esp32_lolin_s2_mini.test_ignore}
+
+[env:s2_mini_WS281x_RGB]
+build_flags = -DNEOPIXEL_RGB -DDATA_PIN=2 ${env.build_flags}
+custom_prog_version = esp32_s2_mini_WS281x_RGB
+
+board = lolin_s2_mini
+board_build.mcu = esp32s2
+platform = ${esp32_lolin_s2_mini.platform}
+lib_deps = ${esp32_lolin_s2_mini.lib_deps}
+test_ignore = ${esp32_lolin_s2_mini.test_ignore}
+
+[env:s2_mini_SPI_APA102_SK9822_HD107]
+build_flags = -DSPILED_APA102 -DDATA_PIN=2 -DCLOCK_PIN=4 ${env.build_flags}
+custom_prog_version = esp32_s2_mini_SPI_APA102_SK9822_HD107
+
+board = lolin_s2_mini
+board_build.mcu = esp32s2
+platform = ${esp32_lolin_s2_mini.platform}
+lib_deps = ${esp32_lolin_s2_mini.lib_deps}
+test_ignore = ${esp32_lolin_s2_mini.test_ignore}
+
+[env:s2_mini_SPI_WS2801]
+build_flags = -DSPILED_WS2801 -DDATA_PIN=2 -DCLOCK_PIN=4 ${env.build_flags}
+custom_prog_version = esp32_s2_mini_SPI_WS2801
+
+board = lolin_s2_mini
+board_build.mcu = esp32s2
+platform = ${esp32_lolin_s2_mini.platform}
+lib_deps = ${esp32_lolin_s2_mini.lib_deps}
+test_ignore = ${esp32_lolin_s2_mini.test_ignore}
+
diff --git a/src/main.cpp b/src/main.cpp
index c76275d..cbfb29a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -27,19 +27,20 @@
#include
#include
-#ifdef NEOPIXEL_RGBW
- #include "calibration.h"
- void printCalibration();
-#endif
-#if ESP_ARDUINO_VERSION_MAJOR == 2 && ESP_ARDUINO_VERSION_MINOR ==0 && ESP_ARDUINO_VERSION_PATCH <= 5
+#if !defined(ARDUINO_LOLIN_S2_MINI) && ESP_ARDUINO_VERSION_MAJOR == 2 && ESP_ARDUINO_VERSION_MINOR ==0 && ESP_ARDUINO_VERSION_PATCH <= 5
#error "Arduino ESP32 versions 2.0.0-2.0.5 are unsupported."
#endif
-// DO NOT EDIT THIS FILE. ADJUST THE CONFIGURATION IN THE platformio.ini
+///////////////////////////////////////////////////////////////////////////
+// DO NOT EDIT THIS FILE. ADJUST THE CONFIGURATION IN THE platformio.ini //
+///////////////////////////////////////////////////////////////////////////
+
#define _STR(x) #x
#define _XSTR(x) _STR(x)
#define VAR_NAME_VALUE(var) #var " = " _XSTR(var)
+#define _XSTR2(x,y) _STR(x) _STR(y)
+#define VAR_NAME_VALUE2(var) #var " = " _XSTR2(var)
#ifdef NEOPIXEL_RGBW
#pragma message(VAR_NAME_VALUE(NEOPIXEL_RGBW))
@@ -58,389 +59,111 @@
#endif
#pragma message(VAR_NAME_VALUE(SERIALCOM_SPEED))
-#ifdef NEOPIXEL_RGBW
- #define LED_DRIVER NeoPixelBus
-#elif NEOPIXEL_RGB
- #define LED_DRIVER NeoPixelBus
-#elif SPILED_APA102
+#if defined(ARDUINO_LOLIN_S2_MINI)
+ #ifdef NEOPIXEL_RGBW
+ #define LED_DRIVER NeoPixelBus
+ #elif NEOPIXEL_RGB
+ #define LED_DRIVER NeoPixelBus
+ #endif
+#else
+ #ifdef NEOPIXEL_RGBW
+ #define LED_DRIVER NeoPixelBus
+ #elif NEOPIXEL_RGB
+ #define LED_DRIVER NeoPixelBus
+ #endif
+#endif
+
+#ifdef SPILED_APA102
#define LED_DRIVER NeoPixelBus
#elif SPILED_WS2801
#define LED_DRIVER NeoPixelBus
#endif
-// STATS (sent only when there is no communication)
-struct
-{
- unsigned long start = 0;
- uint16_t goodFrames = 0;
- uint16_t totalFrames = 0;
- uint16_t finalGoodFrames = 0;
- uint16_t finalTotalFrames = 0;
-
- void update(unsigned long curTime)
- {
- if (totalFrames > 0 && totalFrames >= goodFrames)
- {
- finalGoodFrames = goodFrames;
- finalTotalFrames = totalFrames;
- }
-
- start = curTime;
- goodFrames = 0;
- totalFrames = 0;
- }
-
- void print(unsigned long curTime)
- {
- start = curTime;
- goodFrames = 0;
- totalFrames = 0;
-
- Serial.write("\r\nLast HyperHDR stats. Frames: ");
- Serial.print(finalTotalFrames);
- Serial.write(", good: ");
- Serial.print(finalGoodFrames);
- Serial.write("(FPS), incompl.: ");
- Serial.print(finalTotalFrames - finalGoodFrames);
+#pragma message(VAR_NAME_VALUE(DATA_PIN))
+#ifdef CLOCK_PIN
+ #pragma message(VAR_NAME_VALUE(CLOCK_PIN))
+#endif
+#pragma message(VAR_NAME_VALUE2(LED_DRIVER))
- #if defined(NEOPIXEL_RGBW)
- printCalibration();
+#if defined(SECOND_SEGMENT_START_INDEX)
+ #if defined(ARDUINO_LOLIN_S2_MINI)
+ #ifdef NEOPIXEL_RGBW
+ #define LED_DRIVER2 NeoPixelBus
+ #elif NEOPIXEL_RGB
+ #define LED_DRIVER2 NeoPixelBus
+ #elif SPILED_APA102
+ #define LED_DRIVER2 NeoPixelBus
+ #elif SPILED_WS2801
+ #define LED_DRIVER2 NeoPixelBus
#endif
-
- }
-} stats;
-
-#define MAX_BUFFER 4096
-struct
-{
- // LED strip number
- int ledsNumber = 0;
- // Makuna NeoPixelBusLibrary object
- LED_DRIVER *ledStrip = NULL;
- // want to render a frame?
- bool wantShow = false;
- // static data buffer for the loop
- uint8_t buffer[MAX_BUFFER];
-
- void initLedStrip(int count)
- {
- if (ledStrip != NULL)
- delete ledStrip;
-
- ledsNumber = count;
- #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB)
- ledStrip = new LED_DRIVER(ledsNumber, DATA_PIN);
- ledStrip->Begin();
- #else
- ledStrip = new LED_DRIVER(ledsNumber);
- ledStrip->Begin(CLOCK_PIN, 12, DATA_PIN, 15);
+ #else
+ #ifdef NEOPIXEL_RGBW
+ #define LED_DRIVER2 NeoPixelBus
+ #elif NEOPIXEL_RGB
+ #define LED_DRIVER2 NeoPixelBus
+ #elif SPILED_APA102
+ #define LED_DRIVER2 NeoPixelBus
+ #elif SPILED_WS2801
+ #define LED_DRIVER2 NeoPixelBus
#endif
-
- }
-
- inline void renderLeds()
- {
- if (wantShow && ledStrip != NULL && ledStrip->CanShow())
- {
- stats.goodFrames++;
- wantShow = false;
- ledStrip->Show();
- }
- }
-} base;
-
-// CALIBRATION & COLORSPACE RELATED STUFF
-#ifdef NEOPIXEL_RGBW
-
- RgbwColor inputColor;
-
- void printCalibration()
- {
- Serial.write("\r\nRGBW => Gain: ");
- Serial.print(calibrationConfig.gain);
- Serial.write("/255, red: ");
- Serial.print(calibrationConfig.red);
- Serial.write(" , green: ");
- Serial.print(calibrationConfig.green);
- Serial.write(" , blue: ");
- Serial.print(calibrationConfig.blue);
- }
-
- inline void setStripPixel(uint16_t pix, RgbwColor &inputColor)
- {
- if (pix < base.ledsNumber)
- {
- base.ledStrip->SetPixelColor(pix, inputColor);
- }
- }
-
-#else
-
- RgbColor inputColor;
-
- inline void setStripPixel(uint16_t pix, RgbColor &inputColor)
- {
- if (pix < base.ledsNumber)
- {
- base.ledStrip->SetPixelColor(pix, inputColor);
- }
- }
-
+ #endif
+ #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_START_INDEX))
+ #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_DATA_PIN))
+ #ifdef SECOND_SEGMENT_CLOCK_PIN
+ #pragma message(VAR_NAME_VALUE(SECOND_SEGMENT_CLOCK_PIN))
+ #endif
+ #pragma message(VAR_NAME_VALUE2(LED_DRIVER2))
+#else
+ class LED_DRIVER2 {
+ public:
+ bool CanShow() {return true;}
+ void Show(bool safe) {}
+ };
#endif
-// PROTOCOL DEFINITION
-enum class AwaProtocol
-{
- HEADER_A,
- HEADER_w,
- HEADER_a,
- HEADER_HI,
- HEADER_LO,
- HEADER_CRC,
- VERSION2_GAIN,
- VERSION2_RED,
- VERSION2_GREEN,
- VERSION2_BLUE,
- RED,
- GREEN,
- BLUE,
- FLETCHER1,
- FLETCHER2
-};
+#define SerialPort Serial
+#include "main.h"
-// AWA FRAME PROPERTIES
-struct
-{
- AwaProtocol state = AwaProtocol::HEADER_A;
- bool protocolVersion2 = false;
- uint8_t CRC = 0;
- uint16_t count = 0;
- uint16_t currentLed = 0;
- uint16_t fletcher1 = 0;
- uint16_t fletcher2 = 0;
-
- inline void init(byte input)
- {
- currentLed = 0;
- count = input * 0x100;
- CRC = input;
- fletcher1 = 0;
- fletcher2 = 0;
- }
-
- inline void addFletcher(byte input)
- {
- fletcher1 = (fletcher1 + (uint16_t)input) % 255;
- fletcher2 = (fletcher2 + fletcher1) % 255;
- }
-} frameState;
-
-// INCOMING CALIBRATION DATA
-struct
-{
- uint8_t gain = 0;
- uint8_t red = 0;
- uint8_t green = 0;
- uint8_t blue = 0;
-} incoming;
-
-void readSerialData()
+/**
+ * @brief separete thread for handling incoming data using cyclic buffer
+ *
+ * @param parameters
+ */
+void processTask(void * parameters)
{
- unsigned long curTime = millis();
- uint16_t bufferPointer = 0;
- uint16_t internalIndex = min(Serial.available(), MAX_BUFFER);
-
- if (internalIndex > 0)
- internalIndex = Serial.read(base.buffer, internalIndex);
-
- if (internalIndex > 0 && curTime - stats.start > 1000)
+ for(;;)
{
- stats.update(curTime);
- }
- else if (curTime - stats.start > 5000)
- {
- stats.print(curTime);
- }
-
- if (frameState.state == AwaProtocol::HEADER_A)
- base.renderLeds();
-
- while (bufferPointer < internalIndex)
- {
- byte input = base.buffer[bufferPointer++];
- switch (frameState.state)
- {
- case AwaProtocol::HEADER_A:
- frameState.protocolVersion2 = false;
- if (input == 'A')
- frameState.state = AwaProtocol::HEADER_w;
- break;
-
- case AwaProtocol::HEADER_w:
- if (input == 'w')
- frameState.state = AwaProtocol::HEADER_a;
- else
- frameState.state = AwaProtocol::HEADER_A;
- break;
-
- case AwaProtocol::HEADER_a:
- if (input == 'a')
- frameState.state = AwaProtocol::HEADER_HI;
- else if (input == 'A')
- {
- frameState.state = AwaProtocol::HEADER_HI;
- frameState.protocolVersion2 = true;
- }
- else
- frameState.state = AwaProtocol::HEADER_A;
- break;
-
- case AwaProtocol::HEADER_HI:
- stats.totalFrames++;
- frameState.init(input);
- frameState.state = AwaProtocol::HEADER_LO;
- break;
-
- case AwaProtocol::HEADER_LO:
- frameState.count += input;
- frameState.CRC = frameState.CRC ^ input ^ 0x55;
- frameState.state = AwaProtocol::HEADER_CRC;
- break;
-
- case AwaProtocol::HEADER_CRC:
- if (frameState.CRC == input)
- {
- if (frameState.count + 1 != base.ledsNumber)
- base.initLedStrip(frameState.count + 1);
-
- frameState.state = AwaProtocol::RED;
- }
- else
- frameState.state = AwaProtocol::HEADER_A;
- break;
-
- case AwaProtocol::RED:
- inputColor.R = input;
- frameState.addFletcher(input);
-
- frameState.state = AwaProtocol::GREEN;
- break;
-
- case AwaProtocol::GREEN:
- inputColor.G = input;
- frameState.addFletcher(input);
-
- frameState.state = AwaProtocol::BLUE;
- break;
-
- case AwaProtocol::BLUE:
- inputColor.B = input;
- frameState.addFletcher(input);
-
- #ifdef NEOPIXEL_RGBW
- inputColor.W = min(channelCorrection.red[inputColor.R],
- min(channelCorrection.green[inputColor.G],
- channelCorrection.blue[inputColor.B]));
- inputColor.R -= channelCorrection.red[inputColor.W];
- inputColor.G -= channelCorrection.green[inputColor.W];
- inputColor.B -= channelCorrection.blue[inputColor.W];
- inputColor.W = channelCorrection.white[inputColor.W];
- #endif
-
- setStripPixel(frameState.currentLed++, inputColor);
-
- if (frameState.count-- > 0)
- frameState.state = AwaProtocol::RED;
- else
- {
- if (frameState.protocolVersion2)
- frameState.state = AwaProtocol::VERSION2_GAIN;
- else
- frameState.state = AwaProtocol::FLETCHER1;
- }
-
- break;
-
- case AwaProtocol::VERSION2_GAIN:
- incoming.gain = input;
- frameState.addFletcher(input);
-
- frameState.state = AwaProtocol::VERSION2_RED;
- break;
-
- case AwaProtocol::VERSION2_RED:
- incoming.red = input;
- frameState.addFletcher(input);
-
- frameState.state = AwaProtocol::VERSION2_GREEN;
- break;
-
- case AwaProtocol::VERSION2_GREEN:
- incoming.green = input;
- frameState.addFletcher(input);
-
- frameState.state = AwaProtocol::VERSION2_BLUE;
- break;
-
- case AwaProtocol::VERSION2_BLUE:
- incoming.blue = input;
- frameState.addFletcher(input);
-
- frameState.state = AwaProtocol::FLETCHER1;
- break;
-
- case AwaProtocol::FLETCHER1:
- if (input != frameState.fletcher1)
- frameState.state = AwaProtocol::HEADER_A;
- else
- frameState.state = AwaProtocol::FLETCHER2;
- break;
-
- case AwaProtocol::FLETCHER2:
- if (input == frameState.fletcher2)
- {
- base.wantShow = true;
- base.renderLeds();
-
- #ifdef NEOPIXEL_RGBW
- if (frameState.protocolVersion2)
- {
- if (calibrationConfig.red != incoming.red || calibrationConfig.green != incoming.green ||
- calibrationConfig.blue != incoming.blue || calibrationConfig.gain != incoming.gain)
- {
- calibrationConfig.setParams(incoming.gain, incoming.red, incoming.green, incoming.blue);
- calibrationConfig.prepareCalibration();
- }
- }
- #endif
- }
-
- frameState.state = AwaProtocol::HEADER_A;
- break;
- }
+ processData();
+ delay(1);
}
}
void setup()
{
// Init serial port
- Serial.setRxBufferSize(2048);
+ Serial.setRxBufferSize(MAX_BUFFER);
+ Serial.setTimeout(50);
Serial.begin(SERIALCOM_SPEED);
- Serial.setTimeout(50);
+ while (!Serial) continue;
// Display config
- Serial.println("\r\nWelcome!\r\nAwa driver 7.");
+ Serial.println(HELLO_MESSAGE);
+ #if defined(SECOND_SEGMENT_START_INDEX)
+ SerialPort.write("SECOND_SEGMENT_START_INDEX = ");
+ SerialPort.println(SECOND_SEGMENT_START_INDEX);
+ #endif
// Colorspace/Led type info
#if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB)
#ifdef NEOPIXEL_RGBW
#ifdef COLD_WHITE
+ calibrationConfig.setParamsAndPrepareCalibration(0xFF, 0xA0, 0xA0, 0xA0);
Serial.println("NeoPixelBus SK6812 cold GRBW.");
#else
+ calibrationConfig.setParamsAndPrepareCalibration(0xFF, 0xB0, 0xB0, 0x70);
Serial.println("NeoPixelBus SK6812 neutral GRBW.");
#endif
- calibrationConfig.prepareCalibration();
- printCalibration();
+ calibrationConfig.printCalibration();
#else
Serial.println("NeoPixelBus ws281x type (GRB).");
#endif
@@ -449,9 +172,23 @@ void setup()
#elif defined(SPILED_WS2801)
Serial.println("SPI WS2801 (RBG).");
#endif
+
+ Serial.flush();
+ delay(50);
+
+ // create new task for handling received serial data on core 0
+ xTaskCreatePinnedToCore(
+ processTask,
+ "processTask",
+ 4096,
+ NULL,
+ 5,
+ &base.processTaskHandle,
+ 0);
}
void loop()
{
- readSerialData();
+ serialTaskHandler();
}
+
diff --git a/test/README b/test/README
deleted file mode 100644
index 9b1e87b..0000000
--- a/test/README
+++ /dev/null
@@ -1,11 +0,0 @@
-
-This directory is intended for PlatformIO Test Runner and project tests.
-
-Unit Testing is a software testing method by which individual units of
-source code, sets of one or more MCU program modules together with associated
-control data, usage procedures, and operating procedures, are tested to
-determine whether they are fit for use. Unit testing finds problems early
-in the development cycle.
-
-More information about PlatformIO Unit Testing:
-- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
diff --git a/test/test_MultiSegment/main.cpp b/test/test_MultiSegment/main.cpp
new file mode 100644
index 0000000..9e21e54
--- /dev/null
+++ b/test/test_MultiSegment/main.cpp
@@ -0,0 +1,419 @@
+/* test_MultiSegment/main.cpp
+*
+* MIT License
+*
+* Copyright (c) 2022 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialESP32
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#define NO_GLOBAL_SERIAL
+#define HYPERSERIAL_TESTING
+
+#include
+#include
+#include
+#include "calibration.h"
+
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+/////////////////////// AWA PROTOCOL CORRECTNESS TEST /////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+
+#define TEST_LEDS_NUMBER 1025
+uint8_t _ledBuffer[TEST_LEDS_NUMBER * 3 + 6 + 8];
+
+#define LED_DRIVER ProtocolTester
+#define LED_DRIVER2 ProtocolTester
+#define SECOND_SEGMENT_START_INDEX 513
+#define SECOND_SEGMENT_CLOCK_PIN 100
+#define SECOND_SEGMENT_DATA_PIN 101
+
+/**
+ * @brief Mockup Serial class to simulate the real communition
+ *
+ */
+
+class SerialTester
+{
+ int frameSize = 0;
+ int sent = 0;
+
+ public:
+
+ void createTestFrame(bool _white_channel_calibration, uint8_t _white_channel_limit = 0,
+ uint8_t _white_channel_red = 0, uint8_t _white_channel_green = 0,
+ uint8_t _white_channel_blue = 0)
+ {
+ _ledBuffer[0] = 'A';
+ _ledBuffer[1] = 'w';
+ _ledBuffer[2] = (_white_channel_calibration) ? 'A' : 'a';
+ _ledBuffer[4] = (TEST_LEDS_NUMBER-1) & 0xff;
+ _ledBuffer[3] = ((TEST_LEDS_NUMBER-1) >> 8) & 0xff;
+ _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55;
+
+ uint8_t* writer = &(_ledBuffer[6]);
+ uint8_t* hasher = writer;
+
+ for(int i=0; i < TEST_LEDS_NUMBER; i++)
+ {
+ *(writer++)=random(255);
+ *(writer++)=random(255);
+ *(writer++)=random(255);
+ }
+
+
+ if (_white_channel_calibration)
+ {
+ *(writer++) = _white_channel_limit;
+ *(writer++) = _white_channel_red;
+ *(writer++) = _white_channel_green;
+ *(writer++) = _white_channel_blue;
+ }
+
+ uint16_t fletcher1 = 0, fletcher2 = 0, fletcherExt = 0;
+ uint8_t position = 0;
+ while (hasher < writer)
+ {
+ fletcherExt = (fletcherExt + (*(hasher) ^ (position++))) % 255;
+ fletcher1 = (fletcher1 + *(hasher++)) % 255;
+ fletcher2 = (fletcher2 + fletcher1) % 255;
+ }
+ *(writer++) = (uint8_t)fletcher1;
+ *(writer++) = (uint8_t)fletcher2;
+ *(writer++) = (uint8_t)((fletcherExt != 0x41) ? fletcherExt : 0xaa);
+
+ frameSize = (int)(writer - _ledBuffer);
+ sent = 0;
+ }
+
+
+ inline size_t write(const char * s)
+ {
+ return 0;
+ }
+
+ inline size_t print(unsigned char, int = DEC)
+ {
+ return 0;
+ }
+
+ int available(void)
+ {
+ if (sent < frameSize)
+ {
+ return std::min(std::max((int)(random(64)), 1), frameSize - sent);
+ }
+
+ return 0;
+ }
+
+ int toSend(void)
+ {
+ return frameSize - sent;
+ }
+
+ int getFrameSize()
+ {
+ return frameSize;
+ }
+
+ size_t read(uint8_t *buffer, size_t size)
+ {
+ int max = std::min(frameSize - sent, (int)size);
+ if (max > 0)
+ {
+ memcpy(buffer, &(_ledBuffer[sent]), max);
+ sent += max;
+ return max;
+ }
+ return 0;
+ }
+
+ void println(const String &s)
+ {
+
+ }
+} SerialPort;
+
+
+/**
+ * @brief Mockup LED driver to verify correctness of the received LEDs color values
+ *
+ */
+
+class ProtocolTester {
+ int ledCount;
+ int currentIndex;
+ int lastCount;
+ bool first;
+
+ public:
+ ProtocolTester(int _count, int _pin) : ProtocolTester(_count)
+ {
+ if (_pin == SECOND_SEGMENT_DATA_PIN)
+ first = false;
+ }
+
+ ProtocolTester(int _count)
+ {
+ first = true;
+ ledCount = _count;
+ currentIndex = 0;
+ lastCount = 0;
+ }
+
+ bool CanShow()
+ {
+ return true;
+ }
+
+ void Show(bool safe = true)
+ {
+ lastCount = currentIndex;
+ currentIndex = 0;
+ }
+
+ void Begin()
+ {
+
+ }
+
+ void Begin(int _pin1, int _pin2, int _pin3, int _pin4)
+ {
+ if (_pin1 == SECOND_SEGMENT_CLOCK_PIN)
+ first = false;
+ }
+
+ int getLastCount()
+ {
+ return lastCount;
+ }
+
+ /**
+ * @brief Very important: verify LED color, compare it to the origin
+ *
+ * @param indexPixel
+ * @param color
+ */
+ #ifdef NEOPIXEL_RGBW
+ void SetPixelColor(uint16_t indexPixel, RgbwColor color)
+ {
+ TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index");
+ TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope");
+
+ uint8_t *c = (first) ? &(_ledBuffer[6 + indexPixel * 3]) : &(_ledBuffer[6 + (indexPixel + SECOND_SEGMENT_START_INDEX) * 3]);
+ uint8_t r = *(c++);
+ uint8_t g = *(c++);
+ uint8_t b = *(c++);
+
+ uint8_t w = min(channelCorrection.red[r],
+ min(channelCorrection.green[g],
+ channelCorrection.blue[b]));
+ r -= channelCorrection.red[w];
+ g -= channelCorrection.green[w];
+ b -= channelCorrection.blue[w];
+ w = channelCorrection.white[w];
+
+ TEST_ASSERT_EQUAL_UINT8(r, color.R);
+ TEST_ASSERT_EQUAL_UINT8(g, color.G);
+ TEST_ASSERT_EQUAL_UINT8(b, color.B);
+ TEST_ASSERT_EQUAL_UINT8(w, color.W);
+
+ currentIndex = indexPixel + 1;
+ lastCount = 0;
+ }
+ #else
+ void SetPixelColor(uint16_t indexPixel, RgbColor color)
+ {
+ TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index");
+ TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope");
+
+ uint8_t *c = (first) ? &(_ledBuffer[6 + indexPixel * 3]) : &(_ledBuffer[6 + (indexPixel + SECOND_SEGMENT_START_INDEX) * 3]);
+ uint8_t r = *(c++);
+ uint8_t g = *(c++);
+ uint8_t b = *(c++);
+
+ TEST_ASSERT_EQUAL_UINT8(r, color.R);
+ TEST_ASSERT_EQUAL_UINT8(g, color.G);
+ TEST_ASSERT_EQUAL_UINT8(b, color.B);
+
+ currentIndex = indexPixel + 1;
+ lastCount = 0;
+ }
+ #endif
+};
+
+#include "main.h"
+
+
+
+/**
+ * @brief Send RGBW calibration data and verify it all (including proper colors rendering)
+ *
+ */
+void MultiSegmentTest_SendRgbwCalibration()
+{
+ // set all calibration values to test
+ SerialPort.createTestFrame(true, 10, 20, 30, 40);
+ base.queueCurrent = 0;
+ base.queueEnd = 0;
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result");
+
+ // should not change if the frame doesnt contain calibration data
+ SerialPort.createTestFrame(false);
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result");
+
+ // last test
+ SerialPort.createTestFrame(true, 255, 128, 128, 128);
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(255,128,128,128), "Incorrect calibration result");
+}
+
+/**
+ * @brief Send 100 RGB/RGBW frames and verify it all (including proper colors rendering)
+ *
+ */
+void MultiSegmentTest_Send100Frames()
+{
+ base.queueCurrent = 0;
+ base.queueEnd = 0;
+
+ for(int i = 0; i < 100; i++)
+ {
+ SerialPort.createTestFrame(false);
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ }
+}
+
+/**
+ * @brief Send 200 RGB/RGBW valid/invalid frames and verify it all (including proper colors rendering)
+ *
+ */
+void MultiSegmentTest_Send200UncertainFrames()
+{
+ base.queueCurrent = 0;
+ base.queueEnd = 0;
+
+ for(int i = 0; i < 200; i++)
+ {
+ SerialPort.createTestFrame(false);
+ statistics.update(0);
+
+ bool damaged = (random(255) % 2) == 0;
+ int index;
+ uint8_t backup;
+ if (damaged)
+ {
+ index = random(SerialPort.getFrameSize());
+ backup = _ledBuffer[index];
+ _ledBuffer[index] = backup ^ 0xff;
+ }
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ if (damaged)
+ {
+ char buffer[128];
+ snprintf(buffer, sizeof(buffer), "Damaged frame was received: [%d]=>%d", index, backup);
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), buffer);
+ base.getLedStrip1()->Show();
+ base.getLedStrip2()->Show();
+ frameState.setState(AwaProtocol::HEADER_A);
+ }
+ else
+ {
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////// UNIT TEST ROUTINES //////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+
+void setup()
+{
+ delay(1000);
+ randomSeed(analogRead(0));
+ UNITY_BEGIN();
+ #ifdef NEOPIXEL_RGBW
+ RUN_TEST(MultiSegmentTest_SendRgbwCalibration);
+ #endif
+ RUN_TEST(MultiSegmentTest_Send100Frames);
+ RUN_TEST(MultiSegmentTest_Send200UncertainFrames);
+ UNITY_END();
+}
+
+void loop()
+{
+}
diff --git a/test/test_MultiSegmentReversed/main.cpp b/test/test_MultiSegmentReversed/main.cpp
new file mode 100644
index 0000000..71a0ab5
--- /dev/null
+++ b/test/test_MultiSegmentReversed/main.cpp
@@ -0,0 +1,424 @@
+/* test_MultiSegmentReversed/main.cpp
+*
+* MIT License
+*
+* Copyright (c) 2022 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialESP32
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#define NO_GLOBAL_SERIAL
+#define HYPERSERIAL_TESTING
+
+#include
+#include
+#include
+#include "calibration.h"
+
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+/////////////////////// AWA PROTOCOL CORRECTNESS TEST /////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+
+#define TEST_LEDS_NUMBER 1025
+uint8_t _ledBuffer[TEST_LEDS_NUMBER * 3 + 6 + 8];
+
+#define LED_DRIVER ProtocolTester
+#define LED_DRIVER2 ProtocolTester
+#define SECOND_SEGMENT_START_INDEX 513
+#define SECOND_SEGMENT_CLOCK_PIN 100
+#define SECOND_SEGMENT_DATA_PIN 101
+#define SECOND_SEGMENT_REVERSED
+
+/**
+ * @brief Mockup Serial class to simulate the real communition
+ *
+ */
+
+class SerialTester
+{
+ int frameSize = 0;
+ int sent = 0;
+
+ public:
+
+ void createTestFrame(bool _white_channel_calibration, uint8_t _white_channel_limit = 0,
+ uint8_t _white_channel_red = 0, uint8_t _white_channel_green = 0,
+ uint8_t _white_channel_blue = 0)
+ {
+ _ledBuffer[0] = 'A';
+ _ledBuffer[1] = 'w';
+ _ledBuffer[2] = (_white_channel_calibration) ? 'A' : 'a';
+ _ledBuffer[4] = (TEST_LEDS_NUMBER-1) & 0xff;
+ _ledBuffer[3] = ((TEST_LEDS_NUMBER-1) >> 8) & 0xff;
+ _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55;
+
+ uint8_t* writer = &(_ledBuffer[6]);
+ uint8_t* hasher = writer;
+
+ for(int i=0; i < TEST_LEDS_NUMBER; i++)
+ {
+ *(writer++)=random(255);
+ *(writer++)=random(255);
+ *(writer++)=random(255);
+ }
+
+
+ if (_white_channel_calibration)
+ {
+ *(writer++) = _white_channel_limit;
+ *(writer++) = _white_channel_red;
+ *(writer++) = _white_channel_green;
+ *(writer++) = _white_channel_blue;
+ }
+
+ uint16_t fletcher1 = 0, fletcher2 = 0, fletcherExt = 0;
+ uint8_t position = 0;
+ while (hasher < writer)
+ {
+ fletcherExt = (fletcherExt + (*(hasher) ^ (position++))) % 255;
+ fletcher1 = (fletcher1 + *(hasher++)) % 255;
+ fletcher2 = (fletcher2 + fletcher1) % 255;
+ }
+ *(writer++) = (uint8_t)fletcher1;
+ *(writer++) = (uint8_t)fletcher2;
+ *(writer++) = (uint8_t)((fletcherExt != 0x41) ? fletcherExt : 0xaa);
+
+ frameSize = (int)(writer - _ledBuffer);
+ sent = 0;
+ }
+
+
+ inline size_t write(const char * s)
+ {
+ return 0;
+ }
+
+ inline size_t print(unsigned char, int = DEC)
+ {
+ return 0;
+ }
+
+ int available(void)
+ {
+ if (sent < frameSize)
+ {
+ return std::min(std::max((int)(random(64)), 1), frameSize - sent);
+ }
+
+ return 0;
+ }
+
+ int toSend(void)
+ {
+ return frameSize - sent;
+ }
+
+ int getFrameSize()
+ {
+ return frameSize;
+ }
+
+ size_t read(uint8_t *buffer, size_t size)
+ {
+ int max = std::min(frameSize - sent, (int)size);
+ if (max > 0)
+ {
+ memcpy(buffer, &(_ledBuffer[sent]), max);
+ sent += max;
+ return max;
+ }
+ return 0;
+ }
+
+ void println(const String &s)
+ {
+
+ }
+} SerialPort;
+
+
+/**
+ * @brief Mockup LED driver to verify correctness of the received LEDs color values
+ *
+ */
+
+class ProtocolTester {
+ int ledCount;
+ int currentIndex;
+ int lastCount;
+ bool first;
+
+ public:
+ ProtocolTester(int _count, int _pin) : ProtocolTester(_count)
+ {
+ if (_pin == SECOND_SEGMENT_DATA_PIN)
+ first = false;
+ }
+
+ ProtocolTester(int _count)
+ {
+ first = true;
+ ledCount = _count;
+ currentIndex = 0;
+ lastCount = 0;
+ }
+
+ bool CanShow()
+ {
+ return true;
+ }
+
+ void Show(bool safe = true)
+ {
+ lastCount = currentIndex;
+ currentIndex = 0;
+ }
+
+ void Begin()
+ {
+
+ }
+
+ void Begin(int _pin1, int _pin2, int _pin3, int _pin4)
+ {
+ if (_pin1 == SECOND_SEGMENT_CLOCK_PIN)
+ first = false;
+ }
+
+ int getLastCount()
+ {
+ return lastCount;
+ }
+
+ /**
+ * @brief Very important: verify LED color, compare it to the origin
+ *
+ * @param indexPixel
+ * @param color
+ */
+ #ifdef NEOPIXEL_RGBW
+ void SetPixelColor(uint16_t indexPixel, RgbwColor color)
+ {
+ if (!first)
+ TEST_ASSERT_EQUAL_INT_MESSAGE(ledCount - currentIndex - 1, indexPixel, "Unexpected LED index");
+ TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope");
+ if (!first)
+ indexPixel = TEST_LEDS_NUMBER - 1 - indexPixel;
+ uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]);
+ uint8_t r = *(c++);
+ uint8_t g = *(c++);
+ uint8_t b = *(c++);
+
+ uint8_t w = min(channelCorrection.red[r],
+ min(channelCorrection.green[g],
+ channelCorrection.blue[b]));
+ r -= channelCorrection.red[w];
+ g -= channelCorrection.green[w];
+ b -= channelCorrection.blue[w];
+ w = channelCorrection.white[w];
+
+ TEST_ASSERT_EQUAL_UINT8(r, color.R);
+ TEST_ASSERT_EQUAL_UINT8(g, color.G);
+ TEST_ASSERT_EQUAL_UINT8(b, color.B);
+ TEST_ASSERT_EQUAL_UINT8(w, color.W);
+
+ currentIndex++;
+ lastCount = 0;
+ }
+ #else
+ void SetPixelColor(uint16_t indexPixel, RgbColor color)
+ {
+ if (!first)
+ TEST_ASSERT_EQUAL_INT_MESSAGE(ledCount - currentIndex - 1, indexPixel, "Unexpected LED index");
+ TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope");
+ if (!first)
+ indexPixel = TEST_LEDS_NUMBER - 1 - indexPixel;
+ uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]);
+ uint8_t r = *(c++);
+ uint8_t g = *(c++);
+ uint8_t b = *(c++);
+
+ TEST_ASSERT_EQUAL_UINT8(r, color.R);
+ TEST_ASSERT_EQUAL_UINT8(g, color.G);
+ TEST_ASSERT_EQUAL_UINT8(b, color.B);
+
+ currentIndex++;
+ lastCount = 0;
+ }
+ #endif
+};
+
+#include "main.h"
+
+
+
+/**
+ * @brief Send RGBW calibration data and verify it all (including proper colors rendering)
+ *
+ */
+void MultiSegmentReversedTest_SendRgbwCalibration()
+{
+ // set all calibration values to test
+ SerialPort.createTestFrame(true, 10, 20, 30, 40);
+ base.queueCurrent = 0;
+ base.queueEnd = 0;
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result");
+
+ // should not change if the frame doesnt contain calibration data
+ SerialPort.createTestFrame(false);
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result");
+
+ // last test
+ SerialPort.createTestFrame(true, 255, 128, 128, 128);
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(255,128,128,128), "Incorrect calibration result");
+}
+
+/**
+ * @brief Send 100 RGB/RGBW frames and verify it all (including proper colors rendering)
+ *
+ */
+void MultiSegmentReversedTest_Send100Frames()
+{
+ base.queueCurrent = 0;
+ base.queueEnd = 0;
+
+ for(int i = 0; i < 100; i++)
+ {
+ SerialPort.createTestFrame(false);
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ }
+}
+
+/**
+ * @brief Send 200 RGB/RGBW valid/invalid frames and verify it all (including proper colors rendering)
+ *
+ */
+void MultiSegmentReversedTest_Send200UncertainFrames()
+{
+ base.queueCurrent = 0;
+ base.queueEnd = 0;
+
+ for(int i = 0; i < 200; i++)
+ {
+ SerialPort.createTestFrame(false);
+ statistics.update(0);
+
+ bool damaged = (random(255) % 2) == 0;
+ int index;
+ uint8_t backup;
+ if (damaged)
+ {
+ index = random(SerialPort.getFrameSize());
+ backup = _ledBuffer[index];
+ _ledBuffer[index] = backup ^ 0xff;
+ }
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ if (damaged)
+ {
+ char buffer[128];
+ snprintf(buffer, sizeof(buffer), "Damaged frame was received: [%d]=>%d", index, backup);
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), buffer);
+ base.getLedStrip1()->Show();
+ base.getLedStrip2()->Show();
+ frameState.setState(AwaProtocol::HEADER_A);
+ }
+ else
+ {
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(SECOND_SEGMENT_START_INDEX, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up (segment1)");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER - SECOND_SEGMENT_START_INDEX, base.getLedStrip2()->getLastCount(), "Not all LEDs were set up(segment2)");
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////// UNIT TEST ROUTINES //////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+
+void setup()
+{
+ delay(1000);
+ randomSeed(analogRead(0));
+ UNITY_BEGIN();
+ #ifdef NEOPIXEL_RGBW
+ RUN_TEST(MultiSegmentReversedTest_SendRgbwCalibration);
+ #endif
+ RUN_TEST(MultiSegmentReversedTest_Send100Frames);
+ RUN_TEST(MultiSegmentReversedTest_Send200UncertainFrames);
+ UNITY_END();
+}
+
+void loop()
+{
+}
diff --git a/test/test_SingleSegment/main.cpp b/test/test_SingleSegment/main.cpp
new file mode 100644
index 0000000..99c074c
--- /dev/null
+++ b/test/test_SingleSegment/main.cpp
@@ -0,0 +1,506 @@
+/* test_SingleSegment/main.cpp
+*
+* MIT License
+*
+* Copyright (c) 2022 awawa-dev
+*
+* https://github.com/awawa-dev/HyperSerialESP32
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+ */
+
+#define NO_GLOBAL_SERIAL
+#define HYPERSERIAL_TESTING
+
+#include
+#include
+#include
+#include "calibration.h"
+
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+/////////////////////// WHITE CHANNEL CALIBRATION TEST ////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+
+
+// old calibration params
+float whiteLimit;
+uint8_t rCorrection;
+uint8_t gCorrection;
+uint8_t bCorrection;
+
+uint8_t wChannel[256];
+uint8_t rChannel[256];
+uint8_t gChannel[256];
+uint8_t bChannel[256];
+
+/**
+ * @brief Old calibration procedure
+ *
+ * @return void
+ */
+void oldCalibration()
+{
+ for (uint32_t i = 0; i < 256; i++)
+ {
+ // color calibration
+ float red = rCorrection * i; // adjust red
+ float green = gCorrection * i; // adjust green
+ float blue = bCorrection * i; // adjust blue
+
+ wChannel[i] = (uint8_t)round(std::min(whiteLimit * i, 255.0f));
+ rChannel[i] = (uint8_t)round(std::min(red / 0xFF, 255.0f));
+ gChannel[i] = (uint8_t)round(std::min(green / 0xFF, 255.0f));
+ bChannel[i] = (uint8_t)round(std::min(blue / 0xFF, 255.0f));
+ }
+}
+
+/**
+ * @brief Compare old and new calibration result
+ *
+ * @return void
+ */
+void compareLut()
+{
+ for (uint32_t i = 0; i < 256; i++)
+ {
+ int w = std::abs(int(wChannel[i]) - int(channelCorrection.white[i]));
+ int r = std::abs(int(rChannel[i]) - int(channelCorrection.red[i]));
+ int g = std::abs(int(gChannel[i]) - int(channelCorrection.green[i]));
+ int b = std::abs(int(bChannel[i]) - int(channelCorrection.blue[i]));
+ TEST_ASSERT_LESS_THAN(1, (int)(std::max(w, std::max(r, std::max(g, b)))));
+ }
+}
+
+/**
+ * @brief Calculate the calibration table using old and new method and compare the result
+ *
+ * @return void
+ */
+void CommonTest_OldAndNedCalibrationAlgorithm()
+{
+ // cold white old calibration (full range)
+ whiteLimit = 1.0f;
+ rCorrection = 0xA0;
+ gCorrection = 0xA0;
+ bCorrection = 0xA0;
+ oldCalibration();
+ // new procedure
+ calibrationConfig.setParamsAndPrepareCalibration(255, 0xA0, 0xA0, 0xA0);
+ compareLut();
+
+ // neutral white old calibration (full range)
+ whiteLimit = 1.0f;
+ rCorrection = 0xB0;
+ gCorrection = 0xB0;
+ bCorrection = 0x70;
+ oldCalibration();
+ // new procedure
+ calibrationConfig.setParamsAndPrepareCalibration(255, 0xB0, 0xB0, 0x70);
+ compareLut();
+
+ // cold white old calibration (medium range)
+ whiteLimit = 0.5019607843137255f;
+ rCorrection = 0xA0;
+ gCorrection = 0xA0;
+ bCorrection = 0xA0;
+ oldCalibration();
+ // new procedure
+ calibrationConfig.setParamsAndPrepareCalibration(128, 0xA0, 0xA0, 0xA0);
+ compareLut();
+
+ // neutral white old calibration (medium range)
+ whiteLimit = 0.5019607843137255f;
+ rCorrection = 0xB0;
+ gCorrection = 0xB0;
+ bCorrection = 0x70;
+ oldCalibration();
+ // new procedure
+ calibrationConfig.setParamsAndPrepareCalibration(128, 0xB0, 0xB0, 0x70);
+ compareLut();
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+/////////////////////// AWA PROTOCOL CORRECTNESS TEST /////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+
+#define TEST_LEDS_NUMBER 801
+uint8_t _ledBuffer[TEST_LEDS_NUMBER * 3 + 6 + 8];
+
+/**
+ * @brief Mockup Serial class to simulate the real communition
+ *
+ */
+
+class SerialTester
+{
+ int frameSize = 0;
+ int sent = 0;
+
+ public:
+
+ void createTestFrame(bool _white_channel_calibration, uint8_t _white_channel_limit = 0,
+ uint8_t _white_channel_red = 0, uint8_t _white_channel_green = 0,
+ uint8_t _white_channel_blue = 0)
+ {
+ _ledBuffer[0] = 'A';
+ _ledBuffer[1] = 'w';
+ _ledBuffer[2] = (_white_channel_calibration) ? 'A' : 'a';
+ _ledBuffer[4] = (TEST_LEDS_NUMBER-1) & 0xff;
+ _ledBuffer[3] = ((TEST_LEDS_NUMBER-1) >> 8) & 0xff;
+ _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55;
+
+ uint8_t* writer = &(_ledBuffer[6]);
+ uint8_t* hasher = writer;
+
+ for(int i=0; i < TEST_LEDS_NUMBER; i++)
+ {
+ *(writer++)=random(255);
+ *(writer++)=random(255);
+ *(writer++)=random(255);
+ }
+
+ if (_white_channel_calibration)
+ {
+ *(writer++) = _white_channel_limit;
+ *(writer++) = _white_channel_red;
+ *(writer++) = _white_channel_green;
+ *(writer++) = _white_channel_blue;
+ }
+
+ uint16_t fletcher1 = 0, fletcher2 = 0, fletcherExt = 0;
+ uint8_t position = 0;
+ while (hasher < writer)
+ {
+ fletcherExt = (fletcherExt + (*(hasher) ^ (position++))) % 255;
+ fletcher1 = (fletcher1 + *(hasher++)) % 255;
+ fletcher2 = (fletcher2 + fletcher1) % 255;
+ }
+ *(writer++) = (uint8_t)fletcher1;
+ *(writer++) = (uint8_t)fletcher2;
+ *(writer++) = (uint8_t)((fletcherExt != 0x41) ? fletcherExt : 0xaa);
+
+ frameSize = (int)(writer - _ledBuffer);
+ sent = 0;
+ }
+
+
+ inline size_t write(const char * s)
+ {
+ return 0;
+ }
+
+ inline size_t print(unsigned char, int = DEC)
+ {
+ return 0;
+ }
+
+ int available(void)
+ {
+ if (sent < frameSize)
+ {
+ return std::min(std::max((int)(random(64)), 1), frameSize - sent);
+ }
+
+ return 0;
+ }
+
+ int toSend(void)
+ {
+ return frameSize - sent;
+ }
+
+ int getFrameSize()
+ {
+ return frameSize;
+ }
+
+ size_t read(uint8_t *buffer, size_t size)
+ {
+ int max = std::min(frameSize - sent, (int)size);
+ if (max > 0)
+ {
+ memcpy(buffer, &(_ledBuffer[sent]), max);
+ sent += max;
+ return max;
+ }
+ return 0;
+ }
+
+ void println(const String &s)
+ {
+
+ }
+} SerialPort;
+
+
+/**
+ * @brief Mockup LED driver to verify correctness of the received LEDs color values
+ *
+ */
+
+class ProtocolTester {
+ int ledCount;
+ int currentIndex = 0;
+ int lastCount = 0;
+
+ public:
+ ProtocolTester(int count, int b)
+ {
+ ledCount = count;
+
+ }
+
+ ProtocolTester(int count)
+ {
+ ledCount = count;
+ }
+
+ bool CanShow()
+ {
+ return true;
+ }
+
+ void Show(bool safe = true)
+ {
+ lastCount = currentIndex;
+ currentIndex = 0;
+ }
+
+ void Begin()
+ {
+
+ }
+
+ void Begin(int a, int b, int c, int d)
+ {
+
+ }
+
+ int getLastCount()
+ {
+ return lastCount;
+ }
+
+ /**
+ * @brief Very important: verify LED color, compare it to the origin
+ *
+ * @param indexPixel
+ * @param color
+ */
+ #ifdef NEOPIXEL_RGBW
+ void SetPixelColor(uint16_t indexPixel, RgbwColor color)
+ {
+ TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index");
+ TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope");
+ uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]);
+ uint8_t r = *(c++);
+ uint8_t g = *(c++);
+ uint8_t b = *(c++);
+
+ uint8_t w = min(channelCorrection.red[r],
+ min(channelCorrection.green[g],
+ channelCorrection.blue[b]));
+ r -= channelCorrection.red[w];
+ g -= channelCorrection.green[w];
+ b -= channelCorrection.blue[w];
+ w = channelCorrection.white[w];
+
+ TEST_ASSERT_EQUAL_UINT8(r, color.R);
+ TEST_ASSERT_EQUAL_UINT8(g, color.G);
+ TEST_ASSERT_EQUAL_UINT8(b, color.B);
+ TEST_ASSERT_EQUAL_UINT8(w, color.W);
+
+ currentIndex = indexPixel + 1;
+ lastCount = 0;
+ }
+ #else
+ void SetPixelColor(uint16_t indexPixel, RgbColor color)
+ {
+ TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index");
+ TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope");
+ uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]);
+ uint8_t r = *(c++);
+ uint8_t g = *(c++);
+ uint8_t b = *(c++);
+
+ TEST_ASSERT_EQUAL_UINT8(r, color.R);
+ TEST_ASSERT_EQUAL_UINT8(g, color.G);
+ TEST_ASSERT_EQUAL_UINT8(b, color.B);
+
+ currentIndex = indexPixel + 1;
+ lastCount = 0;
+ }
+ #endif
+};
+
+#define LED_DRIVER ProtocolTester
+#define LED_DRIVER2 ProtocolTester
+#include "main.h"
+
+
+
+/**
+ * @brief Send RGBW calibration data and verify it all (including proper colors rendering)
+ *
+ */
+void SingleSegmentTest_SendRgbwCalibration()
+{
+ // set all calibration values to test
+ SerialPort.createTestFrame(true, 10, 20, 30, 40);
+ base.queueCurrent = 0;
+ base.queueEnd = 0;
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up");
+ TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result");
+
+ // should not change if the frame doesnt contain calibration data
+ SerialPort.createTestFrame(false);
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up");
+ TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result");
+
+ // last test
+ SerialPort.createTestFrame(true, 255, 128, 128, 128);
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up");
+ TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(255,128,128,128), "Incorrect calibration result");
+}
+
+/**
+ * @brief Send 100 RGB/RGBW frames and verify it all (including proper colors rendering)
+ *
+ */
+void SingleSegmentTest_Send100Frames()
+{
+ base.queueCurrent = 0;
+ base.queueEnd = 0;
+
+ for(int i = 0; i < 100; i++)
+ {
+ SerialPort.createTestFrame(false);
+ statistics.update(0);
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up");
+ }
+}
+
+/**
+ * @brief Send 200 RGB/RGBW valid/invalid frames and verify it all (including proper colors rendering)
+ *
+ */
+void SingleSegmentTest_Send200UncertainFrames()
+{
+ base.queueCurrent = 0;
+ base.queueEnd = 0;
+
+ for(int i = 0; i < 200; i++)
+ {
+ SerialPort.createTestFrame(false);
+ statistics.update(0);
+
+ bool damaged = (random(255) % 2) == 0;
+ int index;
+ uint8_t backup;
+ if (damaged)
+ {
+ index = random(SerialPort.getFrameSize());
+ backup = _ledBuffer[index];
+ _ledBuffer[index] = backup ^ 0xff;
+ }
+
+ while(SerialPort.toSend() > 0)
+ {
+ serialTaskHandler();
+ }
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value");
+ processData();
+ if (damaged)
+ {
+ char buffer[128];
+ snprintf(buffer, sizeof(buffer), "Damaged frame was received: [%d]=>%d", index, backup);
+ TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), buffer);
+ base.getLedStrip1()->Show();
+ frameState.setState(AwaProtocol::HEADER_A);
+ }
+ else
+ {
+ TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received");
+ TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up");
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////// UNIT TEST ROUTINES //////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////
+
+void setup()
+{
+ delay(1000);
+ randomSeed(analogRead(0));
+ UNITY_BEGIN();
+ #ifdef NEOPIXEL_RGBW
+ RUN_TEST(CommonTest_OldAndNedCalibrationAlgorithm);
+ RUN_TEST(SingleSegmentTest_SendRgbwCalibration);
+ #endif
+ RUN_TEST(SingleSegmentTest_Send100Frames);
+ RUN_TEST(SingleSegmentTest_Send200UncertainFrames);
+ UNITY_END();
+}
+
+void loop()
+{
+}
diff --git a/test/test_main.cpp b/test/test_main.cpp
deleted file mode 100644
index 40c98ca..0000000
--- a/test/test_main.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/* test_main.cpp
-*
-* MIT License
-*
-* Copyright (c) 2022 awawa-dev
-*
-* https://github.com/awawa-dev/HyperSerialESP32
-*
-* Permission is hereby granted, free of charge, to any person obtaining a copy
-* of this software and associated documentation files (the "Software"), to deal
-* in the Software without restriction, including without limitation the rights
-* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-* copies of the Software, and to permit persons to whom the Software is
-* furnished to do so, subject to the following conditions:
-*
-* The above copyright notice and this permission notice shall be included in all
-* copies or substantial portions of the Software.
-
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-* SOFTWARE.
- */
-
-#include
-#include
-#include "calibration.h"
-
-// old calibration params
-float whiteLimit;
-uint8_t rCorrection;
-uint8_t gCorrection;
-uint8_t bCorrection;
-
-uint8_t wChannel[256];
-uint8_t rChannel[256];
-uint8_t gChannel[256];
-uint8_t bChannel[256];
-
-// old calibration procedure
-void oldCalibration()
-{
- for (uint32_t i = 0; i < 256; i++)
- {
- // color calibration
- float red = rCorrection * i; // adjust red
- float green = gCorrection * i; // adjust green
- float blue = bCorrection * i; // adjust blue
-
- wChannel[i] = (uint8_t)round(std::min(whiteLimit * i, 255.0f));
- rChannel[i] = (uint8_t)round(std::min(red / 0xFF, 255.0f));
- gChannel[i] = (uint8_t)round(std::min(green / 0xFF, 255.0f));
- bChannel[i] = (uint8_t)round(std::min(blue / 0xFF, 255.0f));
- }
-}
-
-void compareLut()
-{
- for (uint32_t i = 0; i < 256; i++)
- {
- int w = std::abs(int(wChannel[i]) - int(channelCorrection.white[i]));
- int r = std::abs(int(rChannel[i]) - int(channelCorrection.red[i]));
- int g = std::abs(int(gChannel[i]) - int(channelCorrection.green[i]));
- int b = std::abs(int(bChannel[i]) - int(channelCorrection.blue[i]));
- TEST_ASSERT_LESS_THAN(1, (int)(std::max(w, std::max(r, std::max(g, b)))));
- }
-}
-
-void testCalibration()
-{
- // cold white old calibration (full range)
- whiteLimit = 1.0f;
- rCorrection = 0xA0;
- gCorrection = 0xA0;
- bCorrection = 0xA0;
- oldCalibration();
- // new procedure
- calibrationConfig.setParams(255, 0xA0, 0xA0, 0xA0);
- calibrationConfig.prepareCalibration();
- compareLut();
-
- // neutral white old calibration (full range)
- whiteLimit = 1.0f;
- rCorrection = 0xB0;
- gCorrection = 0xB0;
- bCorrection = 0x70;
- oldCalibration();
- // new procedure
- calibrationConfig.setParams(255, 0xB0, 0xB0, 0x70);
- calibrationConfig.prepareCalibration();
- compareLut();
-
- // cold white old calibration (medium range)
- whiteLimit = 0.5019607843137255f;
- rCorrection = 0xA0;
- gCorrection = 0xA0;
- bCorrection = 0xA0;
- oldCalibration();
- // new procedure
- calibrationConfig.setParams(128, 0xA0, 0xA0, 0xA0);
- calibrationConfig.prepareCalibration();
- compareLut();
-
- // neutral white old calibration (medium range)
- whiteLimit = 0.5019607843137255f;
- rCorrection = 0xB0;
- gCorrection = 0xB0;
- bCorrection = 0x70;
- oldCalibration();
- // new procedure
- calibrationConfig.setParams(128, 0xB0, 0xB0, 0x70);
- calibrationConfig.prepareCalibration();
- compareLut();
-}
-
-void setup()
-{
- delay(1000);
- UNITY_BEGIN();
- RUN_TEST(testCalibration);
- UNITY_END();
-}
-
-void loop()
-{
-}