├── .github ├── FUNDING.yml └── workflows │ └── push-master.yml ├── .gitignore ├── .vscode └── extensions.json ├── LICENSE ├── MakunaNeoPixelBusLicence.txt ├── README.md ├── extra_script.py ├── include ├── base.h ├── calibration.h ├── framestate.h ├── main.h └── statistics.h ├── lib └── README ├── platformio.ini ├── src └── main.cpp └── test └── test_SingleSegment └── main.cpp /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: awawa-dev 2 | -------------------------------------------------------------------------------- /.github/workflows/push-master.yml: -------------------------------------------------------------------------------- 1 | name: HyperSerialEsp8266 CI Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | 7 | ###################### 8 | #### PlatformIO ###### 9 | ###################### 10 | 11 | PlatformIO: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Cache pip 16 | uses: actions/cache@v3 17 | with: 18 | path: ~/.cache/pip 19 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 20 | restore-keys: | 21 | ${{ runner.os }}-pip- 22 | - name: Cache PlatformIO 23 | uses: actions/cache@v3 24 | with: 25 | path: ~/.platformio 26 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 27 | - name: Set up Python 28 | uses: actions/setup-python@v4 29 | with: 30 | python-version: 3.x 31 | - name: Install PlatformIO 32 | run: | 33 | python -m pip install --upgrade pip 34 | pip install --upgrade platformio 35 | - name: Run PlatformIO 36 | run: pio run 37 | - uses: actions/upload-artifact@v3 38 | name: Upload artifacts (commit) 39 | if: (startsWith(github.event.ref, 'refs/tags') != true) 40 | with: 41 | path: | 42 | .pio/build/*.bin 43 | 44 | - uses: actions/upload-artifact@v3 45 | name: Upload artifacts (release) 46 | if: startsWith(github.ref, 'refs/tags/') 47 | with: 48 | name: firmware-release 49 | path: | 50 | .pio/build/*.bin 51 | 52 | ################################ 53 | ###### Publish Releases ######## 54 | ################################ 55 | 56 | publish: 57 | name: Publish Releases 58 | if: startsWith(github.event.ref, 'refs/tags') 59 | needs: [PlatformIO] 60 | runs-on: ubuntu-latest 61 | steps: 62 | # generate environment variables 63 | - name: Generate environment variables from version and tag 64 | run: | 65 | echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV 66 | echo "preRelease=false" >> $GITHUB_ENV 67 | 68 | # If version contains alpha or beta, mark draft release as pre-release 69 | - name: Mark release as pre-release 70 | if: contains(env.VERSION, 'alpha') || contains(env.VERSION, 'beta') 71 | run: echo "preRelease=true" >> $GITHUB_ENV 72 | 73 | - uses: actions/download-artifact@v3 74 | with: 75 | name: firmware-release 76 | 77 | # create draft release and upload artifacts 78 | - name: Create draft release 79 | uses: softprops/action-gh-release@v1 80 | with: 81 | name: HyperSerialEsp8266 ${{ env.VERSION }} 82 | tag_name: ${{ env.TAG }} 83 | files: | 84 | *.bin 85 | draft: true 86 | prerelease: ${{ env.preRelease }} 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 awawa-dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MakunaNeoPixelBusLicence.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HyperSerialEsp8266 2 | 3 | Exposes USB high speed serial port at 2Mb baud for driving led strip using NeoPixelBus library. It's intended to replace slow Arduino solutions (level shifter 3.3V to 5v may be required). **Data integrity check (Fletcher's checksum) included in new 'Awa' protocol for HyperHDR. So no more random flashing caused by serial transmission errors.** That option must be checked in HyperHDR to make system works as on the screen below. 4 | 5 | Make sure that your serial chip on the ESP8266 can handle 2Mb speed: for example CP2102 can't as its max speed is 1Mb and you can compile a version for that speed but I think that's not the point, cheap CH340G can do it without any problems. CH9102x also should work for you, even at 4Mb speed. 6 | 7 | There is also my new fork named HyperSerialWLED available with the support for the AWA serial protocol at @2Mb speed for both ESP8266 and ESP32: https://github.com/awawa-dev/HyperSerialWLED Can't guarantee it will work as stable as HyperSerialEsp8266 because WLED has a lot of other things to do in the backgrounds (ex. handling Wifi) and timing control for the serial port could be at danger for larger number of LEDs. But you don't need to abandon all the benefits offered by the WLED which can be a big advantage for some users. 8 | 9 | Tested with SK6812 RGBW and cheap 1.75$ noname clone of Wemos ESP8266 d1 mini CH340G. The data output is on the GPIO2 (D4 pin for Wemos mini d1), just like WLED for example. Should work with 3-channel RGB led system like WS8212b also. 10 | 11 | RGB to RGBW conversion is calibrated for the neutral white channel BTF SK6812 but it can be easily changed (for cool and warm temperature) in the code. Search for "color calibration". In HyperHDR use "1.5" gamma for red, blue and green for best effect in the "Image Processing" tab. 12 | 13 | | LED strip / Device | HyperSerialEsp8266 | 14 | |--------------------------------|:------------------:| 15 | | SK6812 cold white | yes | 16 | | SK6812 neutral white | yes | 17 | | WS281x | yes | 18 | | SPI (APA102, SK9812, HD107...) | yes | 19 | 20 | # Example of supported boards 21 | 22 | **Esp8266 Wemos D1 mini (CH340) and Wemos D1 mini pro (CP2104)** 23 |

24 | 25 |

26 | 27 | # Data integrity check 28 | 29 | Why the data integrity check was introduced which causes incompatibility with other software? Because at 2Mb speed many chip-makers allow few percent error in the transmission. And we do not want to have any distracting flashes. Broken frames are abandon without showing them. At 100Hz for 250 leds approximately 1-5% of the frames are broken. 30 | 31 | # Why not Arduino, Raspberry Pi or WLED 32 | 33 | Because Arduino is slow, really slow even at @500000 baud. And no data integrity checking so random flashing may occure... Increasing serial port speed leads to problems with the LED library as most ATMega are at 16MHz only (for comparision Esp8266 is at 80/160MHz). For over 200leds and RGB channel we have merely ~20 frames. For RGBW is even worse. The only advantage of Arduinos is logic at 5V. For Esp8266 level shifter 3.3V to 5V may be required. You can find example of simply and efficient one here: Level shifter for 3.3V logic level 34 | 35 | Rpi is very powerful device but for the SK6812 and WS2812b protocol timing of transmission is crucial. It's hard to maintain it in the multitasking environment. 36 | 37 | WLED is a brilliant app and it's preffered solution in most cases. But sometimes Wifi is not an option: there is a problem with the signal strenght/stability caused by EM interference or obstacles which results in disturbing effects from the LED strip.You can also try https://github.com/awawa-dev/HyperSerialWLED if you don't want to abandon WLED benefits or you are looking for a version for ESP32. 38 | 39 | **Arduino: 250 RGB leds, 500000 baud, 100 Hz output from HyperHDR, real output for the LED strip is around 20Hz.** 40 | **With such slow hardware driver you don't even need 30FPS from the grabber really:** 41 | 42 | p100 43 | 44 | 45 | With HyperSerialEsp8266 you can have over 100Hz refresh rate for 250 RGB and over 80HZ for 250 RGBW LED strips. 46 |   47 | 48 | 49 | Statistics are sent to serial port monitor when there is no data incoming. You can read it with any serial port client. This driver is not hiding any information from you. 50 | 51 | # Flashing 52 | 53 | Recommend to use [esphome-flasher](https://github.com/esphome/esphome-flasher/releases) 54 | 55 | For **RGBW LED strip** like RGBW SK6812 NEUTRAL white choose: *firmware_SK6812_RGBW_NEUTRAL.bin* 56 | 57 | For **RGBW LED strip** like RGBW SK6812 COLD white choose: *firmware_SK6812_RGBW_COLD.bin* 58 | 59 | For **RGB LED strip** like WS8212b or RGB SK6812 variant choose: *firmware_WS281x_RGB.bin* 60 | 61 | For **SPI driven RGB LED strip** APA102: *firmware_SPI_APA102_SK9822_HD107.bin*, WS8201: *firmware_SPI_WS2801.bin* 62 | 63 | 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) 64 | 65 | For the RGBW firmware the white channel is automatically calculated and R,G,B channels are corrected. 66 | 67 | Using esphome-flasher: 68 | 69 | 70 | # Usage in HyperHDR 71 | 72 | Set `Refresh time` to zero, `Baudrate` to 2000000 and you enabled `HyperHDR's AWA protocol`. 73 | Enabling `White channel calibration` is optional, if you want to fine tune the white channel balance of your sk6812 RGBW LED strip. 74 | `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). 75 | 76 | ![obraz](https://user-images.githubusercontent.com/69086569/207109594-0493fe58-3530-46bb-a0a3-31a110475ed6.png) 77 | 78 | # Benchmarks 79 | 80 | 81 | **Refresh rate depending on requested refresh rate/LED strip length**: 82 | | RGBW LED strip / Device | WeMos D1 Mini Pro (CP2104)
HyperSerialEsp8266 v8 | 83 | |------------------------------------------------|-----------------------------------------| 84 | | 300LEDs
Refresh rate/continues output=100Hz | 71-75 | 85 | | 600LEDs
Refresh rate/continues output=60Hz | 34-35 | 86 | | 900LEDs
Refresh rate/continues output=40Hz | 23 | 87 | 88 | 89 | **Logic level analyzer, RGB (250 leds, 100Hz)** 90 | 91 | 92 | 93 | **Logic level analyzer, RGBW (250 leds, 100Hz)** 94 | 95 | 96 | # Compiling 97 | 98 | Currently we use PlatformIO to compile the project. Install [Visual Studio Code](https://code.visualstudio.com/) and add [PlatformIO plugin](https://platformio.org/). 99 | This environment will take care of everything and compile the firmware for you. Low-level LED strip support is provided by the https://github.com/Makuna/NeoPixelBus library. 100 | 101 | 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 and save it. Github Action will compile new firmware automatically in the Artifacts archive. It has never been so easy! 102 | 103 | Tutorial: https://github.com/awawa-dev/HyperSerialEsp8266/wiki 104 | 105 | # Pinout 106 | 107 | ESP8266: 108 | For non-SPI LED strip data pin is GPIO2 (usually D4). 109 | For SPI LED strip is: GPIO13 (MOSI, usually D7) and GPIO14 (SCLK/CLOCK, usually D5). 110 | 111 | # Disclaimer 112 | 113 | You use it on your own risk. 114 | Don't touch these firmwares if you don't know how to put the device in the programming mode if something goes wrong. 115 | As per the MIT license, I assume no liability for any damage to you or any other person or equipment. 116 | -------------------------------------------------------------------------------- /extra_script.py: -------------------------------------------------------------------------------- 1 | Import("env") 2 | from pathlib import Path 3 | import shutil 4 | 5 | 6 | def post_program_action(source, target, env): 7 | program_path = target[0].get_abspath() 8 | path = Path(program_path) 9 | shutil.copy(program_path, path.parent.parent.absolute()) 10 | env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", post_program_action) 11 | 12 | env.Replace(PROGNAME="firmware_%s" % env.GetProjectOption("custom_prog_version")) 13 | 14 | -------------------------------------------------------------------------------- /include/base.h: -------------------------------------------------------------------------------- 1 | /* base.h 2 | * 3 | * MIT License 4 | * 5 | * Copyright (c) 2025 awawa-dev 6 | * 7 | * https://github.com/awawa-dev/HyperSerialEsp8266 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | 28 | #ifndef BASE_H 29 | #define BASE_H 30 | 31 | class Base 32 | { 33 | // LED strip number 34 | int ledsNumber = 0; 35 | // NeoPixelBusLibrary primary object 36 | LED_DRIVER* ledStrip1 = nullptr; 37 | // frame is set and ready to render 38 | bool readyToRender = false; 39 | 40 | public: 41 | // static data buffer for the loop 42 | uint8_t buffer[MAX_BUFFER]; 43 | // current queue position 44 | uint16_t queueCurrent = 0; 45 | // queue end position 46 | volatile uint16_t queueEnd = 0; 47 | 48 | inline int getLedsNumber() 49 | { 50 | return ledsNumber; 51 | } 52 | 53 | inline LED_DRIVER* getLedStrip1() 54 | { 55 | return ledStrip1; 56 | } 57 | 58 | void initLedStrip(int count) 59 | { 60 | if (ledStrip1 != nullptr) 61 | { 62 | delete ledStrip1; 63 | ledStrip1 = nullptr; 64 | } 65 | 66 | ledsNumber = count; 67 | 68 | if (ledStrip1 == nullptr) 69 | { 70 | ledStrip1 = new LED_DRIVER(ledsNumber); 71 | ledStrip1->Begin(); 72 | } 73 | } 74 | 75 | /** 76 | * @brief Check if there is already prepared frame to display 77 | * 78 | * @return true 79 | * @return false 80 | */ 81 | inline bool hasLateFrameToRender() 82 | { 83 | return readyToRender; 84 | } 85 | 86 | inline void renderLeds(bool newFrame) 87 | { 88 | if (newFrame) 89 | readyToRender = true; 90 | 91 | if (readyToRender && 92 | (ledStrip1 != nullptr && ledStrip1->CanShow())) 93 | { 94 | statistics.increaseShow(); 95 | readyToRender = false; 96 | 97 | // display segments 98 | ledStrip1->Show(false); 99 | } 100 | } 101 | 102 | inline bool setStripPixel(uint16_t pix, ColorDefinition &inputColor) 103 | { 104 | if (pix < ledsNumber) 105 | { 106 | ledStrip1->SetPixelColor(pix, inputColor); 107 | } 108 | 109 | return (pix + 1 < ledsNumber); 110 | } 111 | } base; 112 | 113 | #endif -------------------------------------------------------------------------------- /include/calibration.h: -------------------------------------------------------------------------------- 1 | /* calibration.h 2 | * 3 | * MIT License 4 | * 5 | * Copyright (c) 2025 awawa-dev 6 | * 7 | * https://github.com/awawa-dev/HyperSerialEsp8266 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | 28 | #ifdef NEOPIXEL_RGBW 29 | typedef RgbwColor ColorDefinition; 30 | #else 31 | typedef RgbColor ColorDefinition; 32 | #endif 33 | 34 | 35 | #if !defined(CALIBRATION_H) && (defined(NEOPIXEL_RGBW) || defined(HYPERSERIAL_TESTING)) 36 | #define CALIBRATION_H 37 | 38 | #include 39 | #include 40 | 41 | #define ROUND_DIVIDE(numer, denom) (((numer) + (denom) / 2) / (denom)) 42 | 43 | struct 44 | { 45 | uint8_t white[256]; 46 | uint8_t red[256]; 47 | uint8_t green[256]; 48 | uint8_t blue[256]; 49 | } channelCorrection; 50 | 51 | class CalibrationConfig 52 | { 53 | // calibration parameters 54 | uint8_t gain = 0xFF; 55 | uint8_t red = 0xA0; 56 | uint8_t green = 0xA0; 57 | uint8_t blue = 0xA0; 58 | 59 | /** 60 | * @brief Build the LUT table using provided parameters 61 | * 62 | */ 63 | void prepareCalibration() 64 | { 65 | // prepare LUT calibration table, cold white is much better than "neutral" white 66 | for (uint32_t i = 0; i < 256; i++) 67 | { 68 | // color calibration 69 | uint32_t _gain = gain * i; // adjust gain 70 | uint32_t _red = red * i; // adjust red 71 | uint32_t _green = green * i; // adjust green 72 | uint32_t _blue = blue * i; // adjust blue 73 | 74 | channelCorrection.white[i] = (uint8_t)std::min(ROUND_DIVIDE(_gain, 0xFF), (uint32_t)0xFF); 75 | channelCorrection.red[i] = (uint8_t)std::min(ROUND_DIVIDE(_red, 0xFF), (uint32_t)0xFF); 76 | channelCorrection.green[i] = (uint8_t)std::min(ROUND_DIVIDE(_green,0xFF), (uint32_t)0xFF); 77 | channelCorrection.blue[i] = (uint8_t)std::min(ROUND_DIVIDE(_blue, 0xFF), (uint32_t)0xFF); 78 | } 79 | } 80 | 81 | public: 82 | CalibrationConfig() 83 | { 84 | prepareCalibration(); 85 | } 86 | 87 | /** 88 | * @brief Compare base calibration settings 89 | * 90 | */ 91 | bool compareCalibrationSettings(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue) 92 | { 93 | return _gain == gain && _red == red && _green == green && _blue == blue; 94 | } 95 | 96 | /** 97 | * @brief Set the parameters that define RGB to RGBW transformation 98 | * 99 | * @param _gain 100 | * @param _red 101 | * @param _green 102 | * @param _blue 103 | */ 104 | void setParamsAndPrepareCalibration(uint8_t _gain, uint8_t _red, uint8_t _green, uint8_t _blue) 105 | { 106 | if (gain != _gain || red != _red || green != _green || blue != _blue) 107 | { 108 | gain = _gain; 109 | red = _red; 110 | green = _green; 111 | blue = _blue; 112 | prepareCalibration(); 113 | } 114 | } 115 | 116 | /** 117 | * @brief print RGBW calibration parameters when no data is received 118 | * 119 | */ 120 | void printCalibration() 121 | { 122 | #ifdef SerialPort 123 | SerialPort.write("\r\nRGBW => Gain: "); 124 | SerialPort.print(gain); 125 | SerialPort.write("/255, red: "); 126 | SerialPort.print(red); 127 | SerialPort.write(" , green: "); 128 | SerialPort.print(green); 129 | SerialPort.write(" , blue: "); 130 | SerialPort.print(blue); 131 | #endif 132 | } 133 | } calibrationConfig; 134 | #endif 135 | 136 | -------------------------------------------------------------------------------- /include/framestate.h: -------------------------------------------------------------------------------- 1 | /* framestate.h 2 | * 3 | * MIT License 4 | * 5 | * Copyright (c) 2025 awawa-dev 6 | * 7 | * https://github.com/awawa-dev/HyperSerialEsp8266 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | 28 | #ifndef FRAMESTATE_H 29 | #define FRAMESTATE_H 30 | 31 | /** 32 | * @brief my AWA frame protocol definition 33 | * 34 | */ 35 | enum class AwaProtocol 36 | { 37 | HEADER_A, 38 | HEADER_w, 39 | HEADER_a, 40 | HEADER_HI, 41 | HEADER_LO, 42 | HEADER_CRC, 43 | VERSION2_GAIN, 44 | VERSION2_RED, 45 | VERSION2_GREEN, 46 | VERSION2_BLUE, 47 | RED, 48 | GREEN, 49 | BLUE, 50 | FLETCHER1, 51 | FLETCHER2 52 | }; 53 | 54 | /** 55 | * @brief Contains current state of the incoming frame 56 | * 57 | */ 58 | class 59 | { 60 | AwaProtocol state = AwaProtocol::HEADER_A; 61 | bool protocolVersion2 = false; 62 | uint8_t CRC = 0; 63 | uint16_t count = 0; 64 | uint16_t currentLed = 0; 65 | uint16_t fletcher1 = 0; 66 | uint16_t fletcher2 = 0; 67 | 68 | public: 69 | ColorDefinition color; 70 | 71 | /** 72 | * @brief Reset statistics for new frame 73 | * 74 | * @param input 75 | */ 76 | inline void init(byte input) 77 | { 78 | currentLed = 0; 79 | count = input * 0x100; 80 | CRC = input; 81 | fletcher1 = 0; 82 | fletcher2 = 0; 83 | } 84 | 85 | /** 86 | * @brief get computed CRC 87 | * 88 | * @return uint8_t 89 | */ 90 | inline uint8_t getCRC() 91 | { 92 | return CRC; 93 | } 94 | 95 | /** 96 | * @brief Get the color count reported by the frame 97 | * 98 | * @return uint16_t 99 | */ 100 | inline uint16_t getCount() 101 | { 102 | return count; 103 | } 104 | 105 | /** 106 | * @brief Get the Fletcher1 total sum 107 | * 108 | * @return uint16_t 109 | */ 110 | inline uint16_t getFletcher1() 111 | { 112 | return fletcher1; 113 | } 114 | 115 | /** 116 | * @brief Get the Fletcher2 total sum 117 | * 118 | * @return uint16_t 119 | */ 120 | inline uint16_t getFletcher2() 121 | { 122 | return fletcher2; 123 | } 124 | 125 | /** 126 | * @brief Get and increase the current Led index 127 | * 128 | * @return uint16_t 129 | */ 130 | inline uint16_t getCurrentLedIndex() 131 | { 132 | return currentLed++; 133 | } 134 | 135 | /** 136 | * @brief Set if frame protocol version 2 (contains calibration data) 137 | * 138 | * @param newVer 139 | */ 140 | inline void setProtocolVersion2(bool newVer) 141 | { 142 | protocolVersion2 = newVer; 143 | } 144 | 145 | /** 146 | * @brief Verify if frame protocol version 2 (contains calibration data) 147 | * 148 | * @return true 149 | * @return false 150 | */ 151 | inline bool isProtocolVersion2() 152 | { 153 | return protocolVersion2; 154 | } 155 | 156 | /** 157 | * @brief Set new AWA frame state 158 | * 159 | * @param newState 160 | */ 161 | inline void setState(AwaProtocol newState) 162 | { 163 | state = newState; 164 | } 165 | 166 | /** 167 | * @brief Get current AWA frame state 168 | * 169 | * @return AwaProtocol 170 | */ 171 | inline AwaProtocol getState() 172 | { 173 | return state; 174 | } 175 | 176 | /** 177 | * @brief Update CRC based on current and previuos input 178 | * 179 | * @param input 180 | */ 181 | inline void computeCRC(byte input) 182 | { 183 | count += input; 184 | CRC = CRC ^ input ^ 0x55; 185 | } 186 | 187 | /** 188 | * @brief Update Fletcher checksumn for incoming input 189 | * 190 | * @param input 191 | */ 192 | inline void addFletcher(byte input) 193 | { 194 | fletcher1 = (fletcher1 + (uint16_t)input) % 255; 195 | fletcher2 = (fletcher2 + fletcher1) % 255; 196 | } 197 | 198 | /** 199 | * @brief Check if the calibration data was updated and calculate new one 200 | * 201 | */ 202 | inline void updateIncomingCalibration() 203 | { 204 | #ifdef NEOPIXEL_RGBW 205 | if (protocolVersion2) 206 | { 207 | calibrationConfig.setParamsAndPrepareCalibration(calibration.gain, calibration.red, calibration.green, calibration.blue); 208 | } 209 | #endif 210 | } 211 | 212 | 213 | #ifdef NEOPIXEL_RGBW 214 | /** 215 | * @brief Compute && correct the white channel 216 | * 217 | */ 218 | inline void rgb2rgbw() 219 | { 220 | color.W = min(channelCorrection.red[color.R], 221 | min(channelCorrection.green[color.G], 222 | channelCorrection.blue[color.B])); 223 | color.R -= channelCorrection.red[color.W]; 224 | color.G -= channelCorrection.green[color.W]; 225 | color.B -= channelCorrection.blue[color.W]; 226 | color.W = channelCorrection.white[color.W]; 227 | } 228 | #endif 229 | 230 | /** 231 | * @brief Incoming calibration data 232 | * 233 | */ 234 | struct 235 | { 236 | uint8_t gain = 0; 237 | uint8_t red = 0; 238 | uint8_t green = 0; 239 | uint8_t blue = 0; 240 | } calibration; 241 | 242 | } frameState; 243 | 244 | #endif -------------------------------------------------------------------------------- /include/main.h: -------------------------------------------------------------------------------- 1 | /* main.h 2 | * 3 | * MIT License 4 | * 5 | * Copyright (c) 2025 awawa-dev 6 | * 7 | * https://github.com/awawa-dev/HyperSerialEsp8266 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | 28 | #ifndef MAIN_H 29 | #define MAIN_H 30 | 31 | #define MAX_BUFFER 5120 32 | #define HELLO_MESSAGE "\r\nWelcome!\r\nAwa driver 8." 33 | 34 | #include "calibration.h" 35 | #include "statistics.h" 36 | #include "base.h" 37 | #include "framestate.h" 38 | 39 | /** 40 | * @brief shandling serial communication 41 | * 42 | */ 43 | void serialTaskHandler() 44 | { 45 | uint16_t incomingSize = min(SerialPort.available(), MAX_BUFFER); 46 | 47 | if (incomingSize > 0) 48 | { 49 | SerialPort.read(&(base.buffer[0]), incomingSize); 50 | base.queueEnd = incomingSize; 51 | base.queueCurrent = 0; 52 | } 53 | } 54 | 55 | /** 56 | * @brief process received data on core 0 57 | * 58 | */ 59 | void processData() 60 | { 61 | // update and print statistics 62 | unsigned long currentTime = millis(); 63 | unsigned long deltaTime = currentTime - statistics.getStartTime(); 64 | 65 | if (base.queueCurrent != base.queueEnd && deltaTime >= 1000) 66 | { 67 | statistics.update(currentTime); 68 | } 69 | else if (deltaTime >= 3000) 70 | { 71 | frameState.setState(AwaProtocol::HEADER_A); 72 | statistics.print(currentTime); 73 | } 74 | 75 | // render waiting frame if available 76 | if (base.hasLateFrameToRender() && frameState.getState() == AwaProtocol::HEADER_A) 77 | base.renderLeds(false); 78 | 79 | // process received data 80 | while (base.queueCurrent != base.queueEnd) 81 | { 82 | byte input = base.buffer[base.queueCurrent++]; 83 | 84 | switch (frameState.getState()) 85 | { 86 | case AwaProtocol::HEADER_A: 87 | // assume it's protocol version 1, verify it later 88 | frameState.setProtocolVersion2(false); 89 | if (input == 'A') 90 | frameState.setState(AwaProtocol::HEADER_w); 91 | break; 92 | 93 | case AwaProtocol::HEADER_w: 94 | if (input == 'w') 95 | frameState.setState(AwaProtocol::HEADER_a); 96 | else 97 | frameState.setState(AwaProtocol::HEADER_A); 98 | break; 99 | 100 | case AwaProtocol::HEADER_a: 101 | // detect protocol version 102 | if (input == 'a') 103 | frameState.setState(AwaProtocol::HEADER_HI); 104 | else if (input == 'A') 105 | { 106 | frameState.setState(AwaProtocol::HEADER_HI); 107 | frameState.setProtocolVersion2(true); 108 | } 109 | else 110 | frameState.setState(AwaProtocol::HEADER_A); 111 | break; 112 | 113 | case AwaProtocol::HEADER_HI: 114 | // initialize new frame properties 115 | statistics.increaseTotal(); 116 | frameState.init(input); 117 | frameState.setState(AwaProtocol::HEADER_LO); 118 | break; 119 | 120 | case AwaProtocol::HEADER_LO: 121 | frameState.computeCRC(input); 122 | frameState.setState(AwaProtocol::HEADER_CRC); 123 | break; 124 | 125 | case AwaProtocol::HEADER_CRC: 126 | // verify CRC and create/update LED driver if neccesery 127 | if (frameState.getCRC() == input) 128 | { 129 | uint16_t ledSize = frameState.getCount() + 1; 130 | 131 | // sanity check 132 | if (ledSize > 4096) 133 | frameState.setState(AwaProtocol::HEADER_A); 134 | else 135 | { 136 | if (ledSize != base.getLedsNumber()) 137 | base.initLedStrip(ledSize); 138 | 139 | frameState.setState(AwaProtocol::RED); 140 | } 141 | } 142 | else if (frameState.getCount() == 0x2aa2 && (input == 0x15 || input == 0x35)) 143 | { 144 | if (input == 0x15) 145 | { 146 | SerialPort.println(HELLO_MESSAGE); 147 | statistics.reset(deltaTime); 148 | } 149 | else 150 | statistics.print(currentTime); 151 | 152 | frameState.setState(AwaProtocol::HEADER_A); 153 | } 154 | else 155 | frameState.setState(AwaProtocol::HEADER_A); 156 | break; 157 | 158 | case AwaProtocol::RED: 159 | frameState.color.R = input; 160 | frameState.addFletcher(input); 161 | 162 | frameState.setState(AwaProtocol::GREEN); 163 | break; 164 | 165 | case AwaProtocol::GREEN: 166 | frameState.color.G = input; 167 | frameState.addFletcher(input); 168 | 169 | frameState.setState(AwaProtocol::BLUE); 170 | break; 171 | 172 | case AwaProtocol::BLUE: 173 | frameState.color.B = input; 174 | frameState.addFletcher(input); 175 | 176 | #ifdef NEOPIXEL_RGBW 177 | // calculate RGBW from RGB using provided calibration data 178 | frameState.rgb2rgbw(); 179 | #endif 180 | 181 | // set pixel, increase the index and check if it was the last LED color to come 182 | if (base.setStripPixel(frameState.getCurrentLedIndex(), frameState.color)) 183 | { 184 | frameState.setState(AwaProtocol::RED); 185 | } 186 | else 187 | { 188 | if (frameState.isProtocolVersion2()) 189 | frameState.setState(AwaProtocol::VERSION2_GAIN); 190 | else 191 | frameState.setState(AwaProtocol::FLETCHER1); 192 | } 193 | 194 | break; 195 | 196 | case AwaProtocol::VERSION2_GAIN: 197 | frameState.calibration.gain = input; 198 | frameState.addFletcher(input); 199 | 200 | frameState.setState(AwaProtocol::VERSION2_RED); 201 | break; 202 | 203 | case AwaProtocol::VERSION2_RED: 204 | frameState.calibration.red = input; 205 | frameState.addFletcher(input); 206 | 207 | frameState.setState(AwaProtocol::VERSION2_GREEN); 208 | break; 209 | 210 | case AwaProtocol::VERSION2_GREEN: 211 | frameState.calibration.green = input; 212 | frameState.addFletcher(input); 213 | 214 | frameState.setState(AwaProtocol::VERSION2_BLUE); 215 | break; 216 | 217 | case AwaProtocol::VERSION2_BLUE: 218 | frameState.calibration.blue = input; 219 | frameState.addFletcher(input); 220 | 221 | frameState.setState(AwaProtocol::FLETCHER1); 222 | break; 223 | 224 | case AwaProtocol::FLETCHER1: 225 | // initial frame data integrity check 226 | if (input != frameState.getFletcher1()) 227 | frameState.setState(AwaProtocol::HEADER_A); 228 | else 229 | frameState.setState(AwaProtocol::FLETCHER2); 230 | break; 231 | 232 | case AwaProtocol::FLETCHER2: 233 | // final frame data integrity check 234 | if (input == frameState.getFletcher2()) 235 | { 236 | statistics.increaseGood(); 237 | 238 | base.renderLeds(true); 239 | 240 | #ifdef NEOPIXEL_RGBW 241 | // if received the calibration data, update it now 242 | if (frameState.isProtocolVersion2()) 243 | { 244 | frameState.updateIncomingCalibration(); 245 | } 246 | #endif 247 | } 248 | 249 | frameState.setState(AwaProtocol::HEADER_A); 250 | break; 251 | } 252 | } 253 | } 254 | 255 | #endif -------------------------------------------------------------------------------- /include/statistics.h: -------------------------------------------------------------------------------- 1 | /* stats.h 2 | * 3 | * MIT License 4 | * 5 | * Copyright (c) 2025 awawa-dev 6 | * 7 | * https://github.com/awawa-dev/HyperSerialEsp8266 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | 28 | #ifndef STATISTICS_H 29 | #define STATISTICS_H 30 | 31 | // statistics (stats sent only when there is no communication) 32 | class 33 | { 34 | unsigned long startTime = 0; 35 | uint16_t goodFrames = 0; 36 | uint16_t showFrames = 0; 37 | uint16_t totalFrames = 0; 38 | uint16_t finalGoodFrames = 0; 39 | uint16_t finalShowFrames = 0; 40 | uint16_t finalTotalFrames = 0; 41 | 42 | public: 43 | /** 44 | * @brief Get the start time of the current period 45 | * 46 | * @return unsigned long 47 | */ 48 | inline unsigned long getStartTime() 49 | { 50 | return startTime; 51 | } 52 | 53 | /** 54 | * @brief Detected new frame 55 | * 56 | */ 57 | inline void increaseTotal() 58 | { 59 | totalFrames++; 60 | } 61 | 62 | /** 63 | * @brief The frame is received and shown 64 | * 65 | */ 66 | inline void increaseShow() 67 | { 68 | showFrames++; 69 | } 70 | 71 | /** 72 | * @brief The frame is received correctly (not yet displayed) 73 | * 74 | */ 75 | inline void increaseGood() 76 | { 77 | goodFrames++; 78 | } 79 | 80 | /** 81 | * @brief Get number of correctly received frames 82 | * 83 | * @return uint16_t 84 | */ 85 | inline uint16_t getGoodFrames() 86 | { 87 | return goodFrames; 88 | } 89 | 90 | /** 91 | * @brief Period restart, save current statistics ans send them later if there is no incoming communication 92 | * 93 | * @param currentTime 94 | */ 95 | void update(unsigned long currentTime) 96 | { 97 | if (totalFrames > 0) 98 | { 99 | finalShowFrames = showFrames; 100 | finalGoodFrames = std::min(goodFrames, totalFrames); 101 | finalTotalFrames = totalFrames; 102 | } 103 | 104 | startTime = currentTime; 105 | goodFrames = 0; 106 | totalFrames = 0; 107 | showFrames = 0; 108 | } 109 | 110 | /** 111 | * @brief Print last saved statistics to the serial port 112 | * 113 | * @param curTime 114 | */ 115 | void print(unsigned long curTime) 116 | { 117 | startTime = curTime; 118 | goodFrames = 0; 119 | totalFrames = 0; 120 | showFrames = 0; 121 | 122 | SerialPort.write("\r\nHyperHDR frames: "); 123 | SerialPort.print(finalShowFrames); 124 | SerialPort.write(" (FPS), receiv.: "); 125 | SerialPort.print(finalTotalFrames); 126 | SerialPort.write(", good: "); 127 | SerialPort.print(finalGoodFrames); 128 | SerialPort.write(", incompl.: "); 129 | SerialPort.print(finalTotalFrames - finalGoodFrames); 130 | SerialPort.write(", heap: "); 131 | SerialPort.print(ESP.getFreeHeap()); 132 | #if defined(NEOPIXEL_RGBW) 133 | calibrationConfig.printCalibration(); 134 | #endif 135 | 136 | } 137 | 138 | /** 139 | * @brief Reset statistics 140 | * 141 | */ 142 | void reset(unsigned long currentTime) 143 | { 144 | startTime = currentTime; 145 | 146 | finalShowFrames = 0; 147 | finalGoodFrames = 0; 148 | finalTotalFrames = 0; 149 | 150 | goodFrames = 0; 151 | totalFrames = 0; 152 | showFrames = 0; 153 | } 154 | 155 | } statistics; 156 | 157 | #endif -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; You can adjust speed of the serial port communication by adjusting 'SERIALCOM_SPEED' variable in [env] section 2 | ; Also you can force to clear LEDs on the esp8266 start up (-DCLEAR_LEDS=number_of_leds_to_clear) 3 | ; The pinout can not be changed for esp8266 board 4 | 5 | [platformio] 6 | default_envs = SK6812_RGBW_COLD, SK6812_RGBW_NEUTRAL, WS281x_RGB, SPI_APA102_SK9822_HD107, SPI_WS2801 7 | 8 | [env] 9 | build_flags = -DSERIALCOM_SPEED=2000000 ;-DCLEAR_LEDS=24 ;remove leading semi-colon to uncomment 10 | extra_scripts = pre:extra_script.py 11 | platform = espressif8266 12 | board = d1_mini 13 | framework = arduino 14 | lib_deps = makuna/NeoPixelBus@2.7.0 15 | 16 | [env:SK6812_RGBW_COLD] 17 | build_flags = -DNEOPIXEL_RGBW -DCOLD_WHITE ${env.build_flags} 18 | custom_prog_version = SK6812_RGBW_COLD 19 | 20 | [env:SK6812_RGBW_NEUTRAL] 21 | build_flags = -DNEOPIXEL_RGBW ${env.build_flags} 22 | custom_prog_version = SK6812_RGBW_NEUTRAL 23 | 24 | [env:WS281x_RGB] 25 | build_flags = -DNEOPIXEL_RGB ${env.build_flags} 26 | custom_prog_version = WS281x_RGB 27 | 28 | [env:SPI_APA102_SK9822_HD107] 29 | build_flags = -DSPILED_APA102 ${env.build_flags} 30 | custom_prog_version = SPI_APA102_SK9822_HD107 31 | 32 | [env:SPI_WS2801] 33 | build_flags = -DSPILED_WS2801 ${env.build_flags} 34 | custom_prog_version = SPI_WS2801 35 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* main.cpp 2 | * 3 | * MIT License 4 | * 5 | * Copyright (c) 2025 awawa-dev 6 | * 7 | * https://github.com/awawa-dev/HyperSerialEsp8266 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | 31 | /////////////////////////////////////////////////////////////////////////// 32 | // DO NOT EDIT THIS FILE. ADJUST THE CONFIGURATION IN THE platformio.ini // 33 | /////////////////////////////////////////////////////////////////////////// 34 | 35 | #define _STR(x) #x 36 | #define _XSTR(x) _STR(x) 37 | #define VAR_NAME_VALUE(var) #var " = " _XSTR(var) 38 | #define _XSTR2(x,y) _STR(x) _STR(y) 39 | #define VAR_NAME_VALUE2(var) #var " = " _XSTR2(var) 40 | 41 | #ifdef CLEAR_LEDS 42 | #pragma message(VAR_NAME_VALUE(CLEAR_LEDS)) 43 | #endif 44 | #ifdef NEOPIXEL_RGBW 45 | #pragma message(VAR_NAME_VALUE(NEOPIXEL_RGBW)) 46 | #endif 47 | #ifdef NEOPIXEL_RGB 48 | #pragma message(VAR_NAME_VALUE(NEOPIXEL_RGB)) 49 | #endif 50 | #ifdef COLD_WHITE 51 | #pragma message(VAR_NAME_VALUE(COLD_WHITE)) 52 | #endif 53 | #ifdef SPILED_APA102 54 | #pragma message(VAR_NAME_VALUE(SPILED_APA102)) 55 | #endif 56 | #ifdef SPILED_WS2801 57 | #pragma message(VAR_NAME_VALUE(SPILED_WS2801)) 58 | #endif 59 | #pragma message(VAR_NAME_VALUE(SERIALCOM_SPEED)) 60 | 61 | #ifdef NEOPIXEL_RGBW 62 | #define LED_DRIVER NeoPixelBus 63 | #elif NEOPIXEL_RGB 64 | #define LED_DRIVER NeoPixelBus 65 | #elif SPILED_APA102 66 | #define LED_DRIVER NeoPixelBus 67 | #elif SPILED_WS2801 68 | #define LED_DRIVER NeoPixelBus 69 | #endif 70 | 71 | #pragma message(VAR_NAME_VALUE2(LED_DRIVER)) 72 | 73 | #define SerialPort Serial 74 | #include "main.h" 75 | 76 | void setup() 77 | { 78 | // Init serial port 79 | Serial.setRxBufferSize(MAX_BUFFER); 80 | Serial.begin(SERIALCOM_SPEED); 81 | while (!Serial) continue; 82 | 83 | #ifdef CLEAR_LEDS 84 | LED_DRIVER* _ledStrip = new LED_DRIVER(CLEAR_LEDS); 85 | ColorDefinition _black(0); 86 | _ledStrip->Begin(); 87 | for(int i = 0; i < CLEAR_LEDS; i++) 88 | _ledStrip->SetPixelColor(i, _black); 89 | _ledStrip->Dirty(); 90 | _ledStrip->Show(false); 91 | delay(10); 92 | delete _ledStrip; 93 | #endif 94 | 95 | // Display config 96 | Serial.println(HELLO_MESSAGE); 97 | 98 | // Colorspace/Led type info 99 | #if defined(NEOPIXEL_RGBW) || defined(NEOPIXEL_RGB) 100 | #ifdef NEOPIXEL_RGBW 101 | #ifdef COLD_WHITE 102 | calibrationConfig.setParamsAndPrepareCalibration(0xFF, 0xA0, 0xA0, 0xA0); 103 | Serial.println("NeoPixelBus SK6812 cold GRBW."); 104 | #else 105 | calibrationConfig.setParamsAndPrepareCalibration(0xFF, 0xB0, 0xB0, 0x70); 106 | Serial.println("NeoPixelBus SK6812 neutral GRBW."); 107 | #endif 108 | calibrationConfig.printCalibration(); 109 | #else 110 | Serial.println("NeoPixelBus ws281x type (GRB)."); 111 | #endif 112 | #elif defined(SPILED_APA102) 113 | Serial.println("SPI APA102 compatible type (BGR)."); 114 | #elif defined(SPILED_WS2801) 115 | Serial.println("SPI WS2801 (RBG)."); 116 | #endif 117 | 118 | Serial.flush(); 119 | delay(50); 120 | } 121 | 122 | void loop() 123 | { 124 | serialTaskHandler(); 125 | processData(); 126 | } 127 | 128 | -------------------------------------------------------------------------------- /test/test_SingleSegment/main.cpp: -------------------------------------------------------------------------------- 1 | /* test_SingleSegment/main.cpp 2 | * 3 | * MIT License 4 | * 5 | * Copyright (c) 2025 awawa-dev 6 | * 7 | * https://github.com/awawa-dev/HyperSerialEsp8266 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | 28 | #define NO_GLOBAL_SERIAL 29 | #define HYPERSERIAL_TESTING 30 | 31 | #include 32 | 33 | #ifdef NEOPIXEL_RGBW 34 | struct RgbwColor 35 | { 36 | uint8_t R; 37 | uint8_t G; 38 | uint8_t B; 39 | uint8_t W; 40 | }; 41 | #else 42 | struct RgbColor 43 | { 44 | uint8_t R; 45 | uint8_t G; 46 | uint8_t B; 47 | }; 48 | #endif 49 | 50 | #include 51 | #include "calibration.h" 52 | 53 | /////////////////////////////////////////////////////////////////////////////////// 54 | /////////////////////////////////////////////////////////////////////////////////// 55 | /////////////////////// WHITE CHANNEL CALIBRATION TEST //////////////////////////// 56 | /////////////////////////////////////////////////////////////////////////////////// 57 | /////////////////////////////////////////////////////////////////////////////////// 58 | 59 | 60 | // old calibration params 61 | float whiteLimit; 62 | uint8_t rCorrection; 63 | uint8_t gCorrection; 64 | uint8_t bCorrection; 65 | 66 | uint8_t wChannel[256]; 67 | uint8_t rChannel[256]; 68 | uint8_t gChannel[256]; 69 | uint8_t bChannel[256]; 70 | 71 | /** 72 | * @brief Old calibration procedure 73 | * 74 | * @return void 75 | */ 76 | void oldCalibration() 77 | { 78 | for (uint32_t i = 0; i < 256; i++) 79 | { 80 | // color calibration 81 | float red = rCorrection * i; // adjust red 82 | float green = gCorrection * i; // adjust green 83 | float blue = bCorrection * i; // adjust blue 84 | 85 | wChannel[i] = (uint8_t)round(std::min(whiteLimit * i, 255.0f)); 86 | rChannel[i] = (uint8_t)round(std::min(red / 0xFF, 255.0f)); 87 | gChannel[i] = (uint8_t)round(std::min(green / 0xFF, 255.0f)); 88 | bChannel[i] = (uint8_t)round(std::min(blue / 0xFF, 255.0f)); 89 | } 90 | } 91 | 92 | /** 93 | * @brief Compare old and new calibration result 94 | * 95 | * @return void 96 | */ 97 | void compareLut() 98 | { 99 | for (uint32_t i = 0; i < 256; i++) 100 | { 101 | int w = std::abs(int(wChannel[i]) - int(channelCorrection.white[i])); 102 | int r = std::abs(int(rChannel[i]) - int(channelCorrection.red[i])); 103 | int g = std::abs(int(gChannel[i]) - int(channelCorrection.green[i])); 104 | int b = std::abs(int(bChannel[i]) - int(channelCorrection.blue[i])); 105 | TEST_ASSERT_LESS_THAN(1, (int)(std::max(w, std::max(r, std::max(g, b))))); 106 | } 107 | } 108 | 109 | /** 110 | * @brief Calculate the calibration table using old and new method and compare the result 111 | * 112 | * @return void 113 | */ 114 | void CommonTest_OldAndNedCalibrationAlgorithm() 115 | { 116 | // cold white old calibration (full range) 117 | whiteLimit = 1.0f; 118 | rCorrection = 0xA0; 119 | gCorrection = 0xA0; 120 | bCorrection = 0xA0; 121 | oldCalibration(); 122 | // new procedure 123 | calibrationConfig.setParamsAndPrepareCalibration(255, 0xA0, 0xA0, 0xA0); 124 | compareLut(); 125 | 126 | // neutral white old calibration (full range) 127 | whiteLimit = 1.0f; 128 | rCorrection = 0xB0; 129 | gCorrection = 0xB0; 130 | bCorrection = 0x70; 131 | oldCalibration(); 132 | // new procedure 133 | calibrationConfig.setParamsAndPrepareCalibration(255, 0xB0, 0xB0, 0x70); 134 | compareLut(); 135 | 136 | // cold white old calibration (medium range) 137 | whiteLimit = 0.5019607843137255f; 138 | rCorrection = 0xA0; 139 | gCorrection = 0xA0; 140 | bCorrection = 0xA0; 141 | oldCalibration(); 142 | // new procedure 143 | calibrationConfig.setParamsAndPrepareCalibration(128, 0xA0, 0xA0, 0xA0); 144 | compareLut(); 145 | 146 | // neutral white old calibration (medium range) 147 | whiteLimit = 0.5019607843137255f; 148 | rCorrection = 0xB0; 149 | gCorrection = 0xB0; 150 | bCorrection = 0x70; 151 | oldCalibration(); 152 | // new procedure 153 | calibrationConfig.setParamsAndPrepareCalibration(128, 0xB0, 0xB0, 0x70); 154 | compareLut(); 155 | } 156 | 157 | /////////////////////////////////////////////////////////////////////////////////// 158 | /////////////////////////////////////////////////////////////////////////////////// 159 | /////////////////////// AWA PROTOCOL CORRECTNESS TEST ///////////////////////////// 160 | /////////////////////////////////////////////////////////////////////////////////// 161 | /////////////////////////////////////////////////////////////////////////////////// 162 | 163 | #define TEST_LEDS_NUMBER 401 164 | uint8_t _ledBuffer[TEST_LEDS_NUMBER * 3 + 6 + 7]; 165 | 166 | /** 167 | * @brief Mockup Serial class to simulate the real communition 168 | * 169 | */ 170 | 171 | class SerialTester 172 | { 173 | int frameSize = 0; 174 | int sent = 0; 175 | 176 | public: 177 | 178 | void createTestFrame(bool _white_channel_calibration, uint8_t _white_channel_limit = 0, 179 | uint8_t _white_channel_red = 0, uint8_t _white_channel_green = 0, 180 | uint8_t _white_channel_blue = 0) 181 | { 182 | _ledBuffer[0] = 'A'; 183 | _ledBuffer[1] = 'w'; 184 | _ledBuffer[2] = (_white_channel_calibration) ? 'A' : 'a'; 185 | _ledBuffer[4] = (TEST_LEDS_NUMBER-1) & 0xff; 186 | _ledBuffer[3] = ((TEST_LEDS_NUMBER-1) >> 8) & 0xff; 187 | _ledBuffer[5] = _ledBuffer[3] ^ _ledBuffer[4] ^ 0x55; 188 | 189 | uint8_t* writer = &(_ledBuffer[6]); 190 | uint8_t* hasher = writer; 191 | 192 | for(int i=0; i < TEST_LEDS_NUMBER; i++) 193 | { 194 | *(writer++)=random(255); 195 | *(writer++)=random(255); 196 | *(writer++)=random(255); 197 | } 198 | 199 | if (_white_channel_calibration) 200 | { 201 | *(writer++) = _white_channel_limit; 202 | *(writer++) = _white_channel_red; 203 | *(writer++) = _white_channel_green; 204 | *(writer++) = _white_channel_blue; 205 | } 206 | 207 | uint16_t fletcher1 = 0, fletcher2 = 0; 208 | while (hasher < writer) 209 | { 210 | fletcher1 = (fletcher1 + *(hasher++)) % 255; 211 | fletcher2 = (fletcher2 + fletcher1) % 255; 212 | } 213 | *(writer++) = (uint8_t)fletcher1; 214 | *(writer++) = (uint8_t)fletcher2; 215 | 216 | frameSize = (int)(writer - _ledBuffer); 217 | sent = 0; 218 | } 219 | 220 | 221 | inline size_t write(const char * s) 222 | { 223 | return 0; 224 | } 225 | 226 | inline size_t print(unsigned char, int = DEC) 227 | { 228 | return 0; 229 | } 230 | 231 | int available(void) 232 | { 233 | if (sent < frameSize) 234 | { 235 | return std::min(std::max((int)(random(64)), 1), frameSize - sent); 236 | } 237 | 238 | return 0; 239 | } 240 | 241 | int toSend(void) 242 | { 243 | return frameSize - sent; 244 | } 245 | 246 | int getFrameSize() 247 | { 248 | return frameSize; 249 | } 250 | 251 | size_t read(uint8_t *buffer, size_t size) 252 | { 253 | int max = std::min(frameSize - sent, (int)size); 254 | if (max > 0) 255 | { 256 | memcpy(buffer, &(_ledBuffer[sent]), max); 257 | sent += max; 258 | return max; 259 | } 260 | return 0; 261 | } 262 | 263 | void println(const String &s) 264 | { 265 | 266 | } 267 | } SerialPort; 268 | 269 | 270 | /** 271 | * @brief Mockup LED driver to verify correctness of the received LEDs color values 272 | * 273 | */ 274 | 275 | class ProtocolTester { 276 | int ledCount; 277 | int currentIndex = 0; 278 | int lastCount = 0; 279 | 280 | public: 281 | ProtocolTester(int count, int b) 282 | { 283 | ledCount = count; 284 | 285 | } 286 | 287 | ProtocolTester(int count) 288 | { 289 | ledCount = count; 290 | } 291 | 292 | bool CanShow() 293 | { 294 | return true; 295 | } 296 | 297 | void Show(bool safe = true) 298 | { 299 | lastCount = currentIndex; 300 | currentIndex = 0; 301 | } 302 | 303 | void Begin() 304 | { 305 | 306 | } 307 | 308 | void Begin(int a, int b, int c, int d) 309 | { 310 | 311 | } 312 | 313 | int getLastCount() 314 | { 315 | return lastCount; 316 | } 317 | 318 | /** 319 | * @brief Very important: verify LED color, compare it to the origin 320 | * 321 | * @param indexPixel 322 | * @param color 323 | */ 324 | #ifdef NEOPIXEL_RGBW 325 | void SetPixelColor(uint16_t indexPixel, RgbwColor color) 326 | { 327 | TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index"); 328 | TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope"); 329 | uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]); 330 | uint8_t r = *(c++); 331 | uint8_t g = *(c++); 332 | uint8_t b = *(c++); 333 | 334 | uint8_t w = min(channelCorrection.red[r], 335 | min(channelCorrection.green[g], 336 | channelCorrection.blue[b])); 337 | r -= channelCorrection.red[w]; 338 | g -= channelCorrection.green[w]; 339 | b -= channelCorrection.blue[w]; 340 | w = channelCorrection.white[w]; 341 | 342 | TEST_ASSERT_EQUAL_UINT8(r, color.R); 343 | TEST_ASSERT_EQUAL_UINT8(g, color.G); 344 | TEST_ASSERT_EQUAL_UINT8(b, color.B); 345 | TEST_ASSERT_EQUAL_UINT8(w, color.W); 346 | 347 | currentIndex = indexPixel + 1; 348 | lastCount = 0; 349 | } 350 | #else 351 | void SetPixelColor(uint16_t indexPixel, RgbColor color) 352 | { 353 | TEST_ASSERT_EQUAL_INT_MESSAGE(currentIndex, indexPixel, "Unexpected LED index"); 354 | TEST_ASSERT_LESS_THAN_MESSAGE(TEST_LEDS_NUMBER, indexPixel, "LED index out of scope"); 355 | uint8_t *c = &(_ledBuffer[6 + indexPixel * 3]); 356 | uint8_t r = *(c++); 357 | uint8_t g = *(c++); 358 | uint8_t b = *(c++); 359 | 360 | TEST_ASSERT_EQUAL_UINT8(r, color.R); 361 | TEST_ASSERT_EQUAL_UINT8(g, color.G); 362 | TEST_ASSERT_EQUAL_UINT8(b, color.B); 363 | 364 | currentIndex = indexPixel + 1; 365 | lastCount = 0; 366 | } 367 | #endif 368 | }; 369 | 370 | #define LED_DRIVER ProtocolTester 371 | #include "main.h" 372 | 373 | 374 | 375 | /** 376 | * @brief Send RGBW calibration data and verify it all (including proper colors rendering) 377 | * 378 | */ 379 | void SingleSegmentTest_SendRgbwCalibration() 380 | { 381 | // set all calibration values to test 382 | SerialPort.createTestFrame(true, 10, 20, 30, 40); 383 | base.queueCurrent = 0; 384 | base.queueEnd = 0; 385 | statistics.update(0); 386 | 387 | TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); 388 | 389 | while(SerialPort.toSend() > 0) 390 | { 391 | serialTaskHandler(); 392 | processData(); 393 | } 394 | 395 | TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); 396 | TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); 397 | TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result"); 398 | 399 | // should not change if the frame doesnt contain calibration data 400 | SerialPort.createTestFrame(false); 401 | statistics.update(0); 402 | 403 | TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); 404 | 405 | while(SerialPort.toSend() > 0) 406 | { 407 | serialTaskHandler(); 408 | processData(); 409 | } 410 | 411 | TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); 412 | TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); 413 | TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(10,20,30,40), "Incorrect calibration result"); 414 | 415 | // last test 416 | SerialPort.createTestFrame(true, 255, 128, 128, 128); 417 | statistics.update(0); 418 | 419 | TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); 420 | 421 | while(SerialPort.toSend() > 0) 422 | { 423 | serialTaskHandler(); 424 | processData(); 425 | } 426 | 427 | TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); 428 | TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); 429 | TEST_ASSERT_EQUAL_MESSAGE(true, calibrationConfig.compareCalibrationSettings(255,128,128,128), "Incorrect calibration result"); 430 | } 431 | 432 | /** 433 | * @brief Send 100 RGB/RGBW frames and verify it all (including proper colors rendering) 434 | * 435 | */ 436 | void SingleSegmentTest_Send100Frames() 437 | { 438 | base.queueCurrent = 0; 439 | base.queueEnd = 0; 440 | 441 | for(int i = 0; i < 100; i++) 442 | { 443 | SerialPort.createTestFrame(false); 444 | statistics.update(0); 445 | 446 | TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); 447 | 448 | while(SerialPort.toSend() > 0) 449 | { 450 | serialTaskHandler(); 451 | processData(); 452 | } 453 | 454 | TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); 455 | TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); 456 | } 457 | } 458 | 459 | /** 460 | * @brief Send 200 RGB/RGBW valid/invalid frames and verify it all (including proper colors rendering) 461 | * 462 | */ 463 | void SingleSegmentTest_Send200UncertainFrames() 464 | { 465 | base.queueCurrent = 0; 466 | base.queueEnd = 0; 467 | 468 | for(int i = 0; i < 200; i++) 469 | { 470 | SerialPort.createTestFrame(false); 471 | statistics.update(0); 472 | 473 | bool damaged = (random(255) % 2) == 0; 474 | int index; 475 | uint8_t backup; 476 | if (damaged) 477 | { 478 | index = random(SerialPort.getFrameSize()); 479 | backup = _ledBuffer[index]; 480 | _ledBuffer[index] = backup ^ 0xfe; 481 | } 482 | 483 | TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), "Unexpected initial stats value"); 484 | 485 | while(SerialPort.toSend() > 0) 486 | { 487 | serialTaskHandler(); 488 | processData(); 489 | } 490 | 491 | if (damaged) 492 | { 493 | char buffer[128]; 494 | snprintf(buffer, sizeof(buffer), "Damaged frame was received: [%d]=>%d", index, backup); 495 | TEST_ASSERT_EQUAL_INT_MESSAGE(0, statistics.getGoodFrames(), buffer); 496 | base.getLedStrip1()->Show(); 497 | frameState.setState(AwaProtocol::HEADER_A); 498 | } 499 | else 500 | { 501 | TEST_ASSERT_EQUAL_INT_MESSAGE(1, statistics.getGoodFrames(), "Frame is not received"); 502 | TEST_ASSERT_EQUAL_INT_MESSAGE(TEST_LEDS_NUMBER, base.getLedStrip1()->getLastCount(), "Not all LEDs were set up"); 503 | } 504 | } 505 | } 506 | 507 | /////////////////////////////////////////////////////////////////////////////////// 508 | /////////////////////////////////////////////////////////////////////////////////// 509 | ///////////////////////////// UNIT TEST ROUTINES ////////////////////////////////// 510 | /////////////////////////////////////////////////////////////////////////////////// 511 | /////////////////////////////////////////////////////////////////////////////////// 512 | 513 | void setup() 514 | { 515 | delay(1000); 516 | randomSeed(analogRead(0)); 517 | UNITY_BEGIN(); 518 | #ifdef NEOPIXEL_RGBW 519 | RUN_TEST(CommonTest_OldAndNedCalibrationAlgorithm); 520 | RUN_TEST(SingleSegmentTest_SendRgbwCalibration); 521 | #endif 522 | RUN_TEST(SingleSegmentTest_Send100Frames); 523 | RUN_TEST(SingleSegmentTest_Send200UncertainFrames); 524 | UNITY_END(); 525 | } 526 | 527 | void loop() 528 | { 529 | } 530 | --------------------------------------------------------------------------------