├── .clang-format ├── .github └── workflows │ ├── stale.yml │ └── test.yml ├── .gitignore ├── .mbedignore ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── devbox.json ├── devbox.lock ├── doc ├── cheat_sheet.jpg ├── fade_from-to.png ├── fadeon_plot.png ├── jled-wasm.png ├── jled.gif ├── morse.jpg ├── multiled.fzz ├── multiled_bb.png ├── multiled_esp32.fzz ├── multiled_esp32_bb.png ├── multiled_mbed.fzz └── multiled_mbed_bb.png ├── examples ├── breathe │ └── breathe.ino ├── candle │ └── candle.ino ├── custom_hal │ └── custom_hal.ino ├── fade_from_to │ └── fade_from_to.ino ├── fade_off │ └── fade_off.ino ├── fade_on │ └── fade_on.ino ├── hello │ └── hello.ino ├── last_brightness │ └── last_brightness.ino ├── morse │ ├── README.md │ ├── bitset.h │ ├── morse.h │ ├── morse.ino │ └── morse_effect.h ├── multiled │ ├── README.md │ └── multiled.ino ├── multiled_mbed │ ├── README.md │ └── multiled_mbed.cpp ├── pulse │ └── pulse.ino ├── raspi_pico │ ├── .gitignore │ ├── CMakeLists.txt │ ├── Dockerfile │ ├── README.md │ ├── build.sh │ ├── pico_demo.cpp │ └── pico_sdk_import.cmake ├── sequence │ └── sequence.ino ├── simple_on │ └── simple_on.ino └── user_func │ └── user_func.ino ├── keywords.txt ├── library.json ├── library.properties ├── platformio.ini ├── src ├── arduino_hal.h ├── esp32_hal.cpp ├── esp32_hal.h ├── esp8266_hal.h ├── jled.h ├── jled_base.cpp ├── jled_base.h ├── mbed_hal.h └── pico_hal.h └── test ├── .lcovrc ├── Arduino.cpp ├── Arduino.h ├── Makefile ├── README.md ├── catch2 ├── catch_amalgamated.cpp └── catch_amalgamated.hpp ├── esp-idf ├── driver │ ├── ledc.cpp │ └── ledc.h ├── esp_timer.cpp └── esp_timer.h ├── hal_mock.h ├── mbed.cpp ├── mbed.h ├── pre-commit ├── test_arduino_hal.cpp ├── test_arduino_mock.cpp ├── test_esp32_hal.cpp ├── test_esp32_mock.cpp ├── test_esp8266_hal.cpp ├── test_example_morse.cpp ├── test_jled.cpp ├── test_jled_sequence.cpp ├── test_main.cpp └── test_mbed_hal.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -3 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: true 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: true 16 | AllowShortLoopsOnASingleLine: true 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Attach 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | BreakAfterJavaFieldAnnotations: false 40 | BreakStringLiterals: true 41 | ColumnLimit: 80 42 | CommentPragmas: '^ IWYU pragma:' 43 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 44 | ConstructorInitializerIndentWidth: 4 45 | ContinuationIndentWidth: 4 46 | Cpp11BracedListStyle: true 47 | DerivePointerAlignment: true 48 | DisableFormat: false 49 | ExperimentalAutoDetectBinPacking: false 50 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 51 | IncludeCategories: 52 | - Regex: '^<.*\.h>' 53 | Priority: 1 54 | - Regex: '^<.*' 55 | Priority: 2 56 | - Regex: '.*' 57 | Priority: 3 58 | IncludeIsMainRegex: '([-_](test|unittest))?$' 59 | IndentCaseLabels: true 60 | IndentWidth: 4 61 | IndentWrappedFunctionNames: false 62 | JavaScriptQuotes: Leave 63 | JavaScriptWrapImports: true 64 | KeepEmptyLinesAtTheStartOfBlocks: false 65 | MacroBlockBegin: '' 66 | MacroBlockEnd: '' 67 | MaxEmptyLinesToKeep: 1 68 | NamespaceIndentation: None 69 | ObjCBlockIndentWidth: 2 70 | ObjCSpaceAfterProperty: false 71 | ObjCSpaceBeforeProtocolList: false 72 | PenaltyBreakBeforeFirstCallParameter: 1 73 | PenaltyBreakComment: 300 74 | PenaltyBreakFirstLessLess: 120 75 | PenaltyBreakString: 1000 76 | PenaltyExcessCharacter: 1000000 77 | PenaltyReturnTypeOnItsOwnLine: 200 78 | PointerAlignment: Left 79 | ReflowComments: true 80 | SortIncludes: true 81 | SpaceAfterCStyleCast: false 82 | SpaceAfterTemplateKeyword: true 83 | SpaceBeforeAssignmentOperators: true 84 | SpaceBeforeParens: ControlStatements 85 | SpaceInEmptyParentheses: false 86 | SpacesBeforeTrailingComments: 2 87 | SpacesInAngles: false 88 | SpacesInContainerLiterals: true 89 | SpacesInCStyleCastParentheses: false 90 | SpacesInParentheses: false 91 | SpacesInSquareBrackets: false 92 | Standard: Auto 93 | TabWidth: 8 94 | UseTab: Never 95 | ... 96 | 97 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Stale issue handler' 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@main 12 | with: 13 | stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 5 days' 14 | days-before-stale: 90 15 | days-before-close: 5 16 | exempt-issue-labels: 'blocked,must,should,keep' 17 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | name: run tests 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: install python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: '3.11' 21 | 22 | - name: linter 23 | run: | 24 | pip install cpplint==2.0.0 25 | make lint 26 | 27 | test: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: checkout code 31 | uses: actions/checkout@v4 32 | 33 | - name: install python 34 | uses: actions/setup-python@v4 35 | with: 36 | python-version: '3.11' 37 | 38 | - name: install tools 39 | run: | 40 | pip install platformio==6.1.10 41 | sudo apt-get update && sudo apt-get install -y lcov 42 | 43 | - name: run tests 44 | run: | 45 | cd test 46 | make clean test OPT=-O2 47 | make clean coverage OPT=-O0 48 | 49 | - name: Upload coverage to coveralls 50 | uses: coverallsapp/github-action@v2.2.3 51 | with: 52 | github-token: ${{ secrets.github_token }} 53 | file: test/coverage.lcov 54 | 55 | - name: build examples 56 | run: make ci 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .envrc 2 | .venv/ 3 | .pio/ 4 | **/.vscode 5 | test/bin 6 | test/.depend 7 | *.o 8 | .pioenvs 9 | .piolibdeps 10 | .clang_complete 11 | .gcc-flags.json 12 | *~ 13 | lib 14 | test/bin 15 | *.gcov 16 | *.gcno 17 | *.gcda 18 | *.lcov 19 | test/coverage.info 20 | test/report 21 | **/tags 22 | .vscode 23 | -------------------------------------------------------------------------------- /.mbedignore: -------------------------------------------------------------------------------- 1 | test/* 2 | examples/* 3 | doc/* 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # JLed changelog (github.com/jandelgado/jled) 2 | 3 | ## [2024-12-01] 4.15.0 4 | 5 | * new: `Update()` methods now optionally return the last brightness value 6 | calculated and written out to the LED. See `examples/last_brightness` 7 | 8 | ## [2024-09-21] 4.14 9 | 10 | * new: make `JLed::Update(unit32_t t)` public, allowing optimizations and 11 | simplified tests 12 | 13 | ## [2023-09-10] 4.13.1 14 | 15 | * fix: `Update()` sometimes returning wrong state (https://github.com/jandelgado/jled/issues/122) 16 | 17 | ## [2023-08-20] 4.13.0 18 | 19 | * new: `Stop()` takes optional parameter allowing to turn LED fully off 20 | 21 | ## [2023-06-29] 4.12.2 22 | 23 | * fix: `JLedSequence` starting again after call to `Stop` (https://github.com/jandelgado/jled/issues/115) 24 | 25 | ## [2023-01-11] 4.12.1 26 | 27 | * fix: add missing keywords to keywords.txt 28 | 29 | ## [2022-11-13] 4.12.0 30 | 31 | * new: add `MinBrightness` method and scale output to interval defined by 32 | `MinBrightness` and `MaxBrightness`. 33 | 34 | ## [2022-11-13] 4.11.1 35 | 36 | * improve: reduce memory consumption of JLed objects by 3 bytes, simplify 37 | state management. 38 | 39 | ## [2022-03-29] 4.11.0 40 | 41 | * change: `JLedSequence` objects are now assignable, making switching 42 | effects easier. See https://github.com/jandelgado/jled-example-switch-sequence for an example. 43 | 44 | ## [2022-03-24] 4.10.0 45 | 46 | * new: `On`, `Off` and `Set` now take an optional `duration` value, making 47 | these effects behave like any other in this regard. This allows to add 48 | an `On` effect to a `JLedSequence` for a specific amount of time. 49 | 50 | ## [2022-02-24] 4.9.1 51 | 52 | * fix: make sure JLedSequence methods like `Repeat` and `Forever` are chainable 53 | like in the `JLed` class 54 | 55 | ## [2022-02-13] 4.9.0 56 | 57 | * new: support ESP-IDF platform for the ESP32 (#87, thanks to @troky for the 58 | initial work). See also repositories 59 | https://github.com/jandelgado/jled-esp-idf-example and 60 | https://github.com/jandelgado/jled-esp-idf-platformio-example 61 | 62 | ## [2021-10-18] 4.8.0 63 | 64 | * new: make `Breathe` method more flexible (#78, thanks to @boraozgen) 65 | 66 | ## [2021-10-13] 4.7.1 67 | 68 | * fix: correct handling of time rollover (#80, thanks to @boraozgen) 69 | 70 | ## [2021-02-02] 4.7.0 71 | 72 | * new: support for Raspberry Pi Pico added 73 | 74 | ## [2021-02-02] 4.6.1 75 | 76 | * fix: `Forever()` on sequence had no effect (#68) 77 | 78 | ## [2021-01-24] 4.6.0 79 | 80 | * new: JLedSequence can be configured to play the sequence multiple times 81 | using the `Repeat()` and `Forever()` methods 82 | * drop travis-ci, use github actions 83 | 84 | ## [2020-10-24] 4.5.2 85 | 86 | * fix: ESP32 led glimming when using low-active connection (#60) 87 | 88 | ## [2020-07-01] 4.5.1 89 | 90 | * fix: support for Nano 33 BLE (#53) 91 | 92 | ## [2020-02-23] 4.5.0 93 | 94 | * new: `JLed::MaxBrightness(uint8_t level)` method to limit output of effects 95 | (implements #43). 96 | 97 | ## [2020-02-21] 4.4.0 98 | 99 | * JLed now supports the mbed framework. See README.md and `examples/multiled_mbed` 100 | for examples. 101 | 102 | ## [2019-09-21] 4.3.0 103 | 104 | * new example: [custom HAL](examples/custom_hal/custom_hal.ino) showing 105 | how to implment a custom HAL. 106 | 107 | ## [2019-08-30] 4.2.1 108 | 109 | * fix: make sure memory alignment is correct (caused hard fault on 110 | SAMD21). Fixes #27. 111 | 112 | ## [2019-06-20] 4.2.0 113 | 114 | * changing an effect resets the Jled object so it starts over with the 115 | new effect (see #25). Prior to this change, calling `Reset()` was 116 | necessary. 117 | 118 | ## [2019-05-11] 4.1.2 119 | 120 | * fix: ESP32 dynamic channel assignment fixed. Sequence demo now working 121 | as expected (see #22). 122 | 123 | ## [2019-05-07] 4.1.1 124 | 125 | * fix: version format in library.properties (removed leading `v`; see #21) 126 | 127 | ## [2019-03-10] v4.1.0 128 | 129 | * change: clean up interface and simplify code: `On()` no longer takes an 130 | optional brightness argument. Call `Set(uint8_t brightness)` instead. 131 | * documentation update 132 | 133 | ## [2019-03-10] v4.0.0 134 | 135 | In addition to the changes introduced with `v4.0.0-rc0` and `v4.0.0-rc1`, the 136 | `v4.0.0` relases adds/changes the following: 137 | 138 | ### Added 139 | 140 | * new `Candle()` effect added for candles and fire like effects 141 | 142 | ### Changed 143 | 144 | * The user provided brightness class no longer needs a `Clone()` method. See 145 | [example](examples/user_func/user_func.ino) for an example 146 | 147 | ### Fixed 148 | 149 | * Makefile (unit testing) dependency checking fixed 150 | 151 | ## [2019-02-17] v4.0.0-rc1 152 | 153 | ### Changed 154 | 155 | * fix: byte buffer alignment for ESP8266 set to DWORD boundary making ESP8266 156 | run again with JLed 4.x 157 | * arduino HAL now does lazy call to pinMode() to prevent STM32 problems 158 | * simplified morse example code 159 | 160 | ## [2019-01-23] v4.0.0-rc0 161 | 162 | ### Added 163 | 164 | * `JLed::Reset()` - resets the led to it's initial state allowing to 165 | to start over 166 | * `JLed::IsRunning()` - return true if effect is active, else false 167 | * new class `JLedSequence` to update JLed objects simultanously or 168 | sequentially. See [README](README.md#controlling-a-group-of-leds) for details. 169 | * added new [morse example](examples/morse) 170 | * clean separation between hardware specific and common code, making 171 | extendability easy 172 | * added STM32 example to [platformio.ini](platformio.ini) 173 | 174 | ### Changed 175 | 176 | * the brightness user function pointer was replaced by an object of type 177 | BrightnessEvaluator. Migration of code should be straight forward, see 178 | below 179 | 180 | #### old brightness function 181 | 182 | In JLed version prio to version 4.0.0, a function pointer was used to specify 183 | a user provided brightness function. 184 | 185 | ```c++ 186 | // this function returns changes between 0 and 255 and vice versa every 250 ms. 187 | uint8_t blinkFunc(uint32_t t, uint16_t, uintptr_t) { 188 | return 255*((t/250)%2); 189 | } 190 | 191 | // Run blinkUserFunc for 5000ms 192 | JLed led = JLed(LED_BUILTIN).UserFunc(blinkFunc, 5000); 193 | ``` 194 | 195 | #### new BrightnessEvaluator class 196 | 197 | The user function is replaced by a class, which provides more flexibility: 198 | 199 | ```c++ 200 | class UserEffect : public jled::BrightnessEvaluator { 201 | uint8_t Eval(uint32_t t) const { 202 | // this function returns changes between 0 and 255 and 203 | // vice versa every 250 ms. 204 | return 255*((t/250)%2); 205 | } 206 | uint16_t Period() const { return 5000; } 207 | }; 208 | 209 | UserEffect userEffect; 210 | JLed led = JLed(LED_BUILTIN).UserFunc(&userEffect); 211 | ``` 212 | 213 | ### Removed 214 | 215 | * `JLed::Invert()` method was removed since became redundant with LowActive() 216 | 217 | 218 | ## [2018-10-03] v3.0.0 219 | 220 | * Major refactoring making support of different platforms easier 221 | * ESP32 support added 222 | * Unit tests refactored 223 | 224 | ## [2018-09-22] v2.4.0 225 | 226 | * `JLed::Update()` now returns a `bool` indicating if the effect is still 227 | active (true), or finished (false). 228 | 229 | ## [2018-09-22] v2.3.0 230 | 231 | * ESP8266 platform: scaling from 8 to 10 bit improved. The scaling makes sure 232 | that 0 is mapped to 0 and 255 is mapped to 1023, preserving min/max 233 | relationships in both ranges. 234 | 235 | ## [2018-06-09] v2.2.3 236 | 237 | ### Fixes 238 | 239 | * ESP8266 platform: analogWrite() resoultion of 10 bit is now honoured. 240 | Previously only a range of 0..255 was used, which resulted in output being 241 | dimmed. 242 | 243 | ### Added 244 | 245 | * It's never to late for a changelog ;) 246 | * ESP8266 environment added to [platform.ini](platform.ini) 247 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(IDF_VER) # ESP-IDF component (ESP32) 2 | 3 | idf_component_register( 4 | SRCS "src/jled_base.cpp" "src/esp32_hal.cpp" 5 | INCLUDE_DIRS "src") 6 | 7 | idf_build_set_property(COMPILE_OPTIONS "-DESP32" APPEND) 8 | set_target_properties(${TARGET} PROPERTIES LINKER_LANGUAGE CXX) 9 | 10 | else() # Raspberry Pi Pico 11 | add_library (JLed src/jled_base.cpp) 12 | target_include_directories (JLed PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) 13 | endif() 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jan Delgado, jdelgado[at]gmx.net 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # use this makefile to build with platformio 2 | # 3 | .PHONY: phony 4 | 5 | # some of the examples use LED_BUILTIN which is not defined for ESP32 6 | CIOPTS=--board=uno --board=esp01 --board=nano33ble --lib="src" 7 | CIOPTS_MBED=--board=nucleo_f401re -Oframework=mbed --lib="src" 8 | CIOPTSALL=--board=esp32dev --board=uno --board=nano33ble --board=esp01 --lib="src" 9 | 10 | all: phony 11 | pio run 12 | 13 | lint: phony 14 | cpplint --filter -readability/check \ 15 | --exclude test/catch2 \ 16 | --extensions=cpp,h,ino $(shell find . -maxdepth 3 \( ! -regex '.*/\..*' \) \ 17 | -type f -a \( -name "*\.cpp" -o -name "*\.h" -o -name "*\.ino" \) ) 18 | 19 | ci: phony 20 | pio ci $(CIOPTS) examples/custom_hal/custom_hal.ino 21 | pio ci $(CIOPTS_MBED) examples/multiled_mbed/multiled_mbed.cpp 22 | pio ci $(CIOPTS) --lib="examples/morse" examples/morse/morse.ino 23 | pio ci $(CIOPTS) examples/candle/candle.ino 24 | pio ci $(CIOPTS) examples/multiled/multiled.ino 25 | pio ci $(CIOPTS) examples/user_func/user_func.ino 26 | pio ci $(CIOPTS) examples/hello/hello.ino 27 | pio ci $(CIOPTSALL) examples/breathe/breathe.ino 28 | pio ci $(CIOPTS) examples/simple_on/simple_on.ino 29 | pio ci $(CIOPTSALL) examples/fade_on/fade_on.ino 30 | pio ci $(CIOPTSALL) examples/sequence/sequence.ino 31 | 32 | envdump: phony 33 | -pio run --target envdump 34 | 35 | clean: phony 36 | -pio run --target clean 37 | cd test && make clean 38 | rm -f src/{*.o,*.gcno,*.gcda} 39 | 40 | upload: phony 41 | pio run --target upload 42 | 43 | monitor: phony 44 | pio device monitor 45 | 46 | test: phony 47 | $(MAKE) -C test coverage OPT=-O0 48 | 49 | tags: phony 50 | ctags -R 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Preferring Python? I just released jled-circuitpython, 3 | a JLed implementation for CircuitPython and MicroPython. 4 |
5 | 6 | # JLed - Advanced LED Library 7 | 8 | ![run tests](https://github.com/jandelgado/jled/workflows/run%20tests/badge.svg) 9 | [![Coverage Status](https://coveralls.io/repos/github/jandelgado/jled/badge.svg?branch=master&dummy=1)](https://coveralls.io/github/jandelgado/jled?branch=master) 10 | 11 | An embedded C++ library to control LEDs. It uses a **non-blocking** approach and can 12 | control LEDs in simple (**on**/**off**) and complex (**blinking**, 13 | **breathing** and more) ways in a **time-driven** manner. 14 | 15 | JLed got some [coverage on Hackaday](https://hackaday.com/2018/06/13/simplifying-basic-led-effects/) 16 | and someone did a [video tutorial for JLed](https://youtu.be/x5V2vdpZq1w) - Thanks! 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 |
JLed in actionInteractive JLed playground
JLed in actionjled running in the browser 26 |
29 | 30 | ## Example 31 | 32 | ```c++ 33 | // breathe LED (on gpio 9) 6 times for 1500ms, waiting for 500ms after each run 34 | #include 35 | 36 | auto led_breathe = JLed(9).Breathe(1500).Repeat(6).DelayAfter(500); 37 | 38 | void setup() { } 39 | 40 | void loop() { 41 | led_breathe.Update(); 42 | } 43 | ``` 44 | 45 | ## Contents 46 | 47 | 48 | 49 | * [Features](#features) 50 | * [Cheat Sheet](#cheat-sheet) 51 | * [Installation](#installation) 52 | * [Arduino IDE](#arduino-ide) 53 | * [PlatformIO](#platformio) 54 | * [Usage](#usage) 55 | * [Output pipeline](#output-pipeline) 56 | * [Effects](#effects) 57 | * [Static on and off](#static-on-and-off) 58 | * [Static on example](#static-on-example) 59 | * [Blinking](#blinking) 60 | * [Blinking example](#blinking-example) 61 | * [Breathing](#breathing) 62 | * [Breathing example](#breathing-example) 63 | * [Candle](#candle) 64 | * [Candle example](#candle-example) 65 | * [FadeOn](#fadeon) 66 | * [FadeOn example](#fadeon-example) 67 | * [FadeOff](#fadeoff) 68 | * [Fade](#fade) 69 | * [Fade example](#fade-example) 70 | * [User provided brightness function](#user-provided-brightness-function) 71 | * [User provided brightness function example](#user-provided-brightness-function-example) 72 | * [Delays and repetitions](#delays-and-repetitions) 73 | * [Initial delay before effect starts](#initial-delay-before-effect-starts) 74 | * [Delay after effect finished](#delay-after-effect-finished) 75 | * [Repetitions](#repetitions) 76 | * [State functions](#state-functions) 77 | * [Update](#update) 78 | * [IsRunning](#isrunning) 79 | * [Reset](#reset) 80 | * [Immediate Stop](#immediate-stop) 81 | * [Misc functions](#misc-functions) 82 | * [Low active for inverted output](#low-active-for-inverted-output) 83 | * [Minimum- and Maximum brightness level](#minimum--and-maximum-brightness-level) 84 | * [Controlling a group of LEDs](#controlling-a-group-of-leds) 85 | * [Framework notes](#framework-notes) 86 | * [Platform notes](#platform-notes) 87 | * [ESP8266](#esp8266) 88 | * [ESP32](#esp32) 89 | * [Using ESP-IDF](#using-esp-idf) 90 | * [STM32](#stm32) 91 | * [Arduino framework](#arduino-framework) 92 | * [Raspberry Pi Pico](#raspberry-pi-pico) 93 | * [Example sketches](#example-sketches) 94 | * [Building examples with PlatformIO](#building-examples-with-platformio) 95 | * [Building examples with the Arduino IDE](#building-examples-with-the-arduino-ide) 96 | * [Extending](#extending) 97 | * [Support new hardware](#support-new-hardware) 98 | * [Unit tests](#unit-tests) 99 | * [Contributing](#contributing) 100 | * [FAQ](#faq) 101 | * [How do I check if a JLed object is still being updated?](#how-do-i-check-if-a-jled-object-is-still-being-updated) 102 | * [How do I restart an effect?](#how-do-i-restart-an-effect) 103 | * [How do I change a running effect?](#how-do-i-change-a-running-effect) 104 | * [Author and Copyright](#author-and-copyright) 105 | * [License](#license) 106 | 107 | 108 | 109 | ## Features 110 | 111 | * non-blocking 112 | * effects: simple on/off, breathe, blink, candle, fade-on, fade-off, [user-defined](examples/morse) (e.g. morse) 113 | * supports inverted polarity of LED 114 | * easy configuration using fluent interface 115 | * can control groups of LEDs sequentially or in parallel 116 | * Portable: Arduino, ESP8266, ESP32, Mbed, Raspberry Pi Pico and more platforms 117 | compatible, runs even in the [browser](https://jandelgado.github.io/jled-wasm) 118 | * supports Arduino, [mbed](https://www.mbed.com), [Raspberry Pi 119 | Pico](https://github.com/raspberrypi/pico-sdk) and ESP32 120 | [ESP-IDF](https://www.espressif.com/en/products/sdks/esp-idf) SDK's 121 | * well [tested](https://coveralls.io/github/jandelgado/jled) 122 | 123 | ## Cheat Sheet 124 | 125 | ![JLed Cheat Sheet](doc/cheat_sheet.jpg) 126 | 127 | ## Installation 128 | 129 | ### Arduino IDE 130 | 131 | In the main menu of the Arduino IDE, select `Sketch` > `Include Library` > 132 | `Manage Libraries...` and search for `jled`, then press `install`. 133 | 134 | ### PlatformIO 135 | 136 | Add `jled` to your library dependencies in your `platformio.ini` project file, 137 | e.g. 138 | 139 | ```ini 140 | ... 141 | [env:nanoatmega328] 142 | platform = atmelavr 143 | board = nanoatmega328 144 | framework = arduino 145 | lib_deps=jled 146 | ... 147 | ``` 148 | 149 | ## Usage 150 | 151 | First, the LED object is constructed and configured, then the state is updated 152 | with subsequent calls to the `Update()` method, typically from the `loop()` 153 | function. While the effect is active, `Update` returns `true`, otherwise 154 | `false`. 155 | 156 | The constructor takes the pin, to which the LED is connected to as 157 | the only argument. Further configuration of the LED object is done using a fluent 158 | interface, e.g. `JLed led = JLed(13).Breathe(2000).DelayAfter(1000).Repeat(5)`. 159 | See the examples section below for further details. 160 | 161 | #### Output pipeline 162 | 163 | First the configured effect (e.g. `Fade`) is evaluated for the current time 164 | `t`. JLed internally uses unsigned bytes to represent brightness values, 165 | ranging from 0 to 255. Next, the value is scaled to the limits set by 166 | `MinBrightness` and `MaxBrightness` (optionally). When the effect is configured 167 | for a low-active LED using `LowActive`, the brightness value will be inverted, 168 | i.e., the value will be subtracted from 255. Finally the value is passed to the 169 | hardware abstraction, which might scale it to the resolution used by the actual 170 | device (e.g. 10 bits for an ESP8266). Finally the brightness value is written 171 | out to the configure GPIO. 172 | 173 | ```text 174 | ┌───────────┐ ┌────────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐ ┌────────┐ 175 | │ Evaluate │ │ Scale to │ │ Low │YES │ Invert │ │Scale for│ │Write to│ 176 | │ effect(t) ├───►│ [min, max] ├───►│ active? ├───►│ signal ├───►│Hardware ├───►│ GPIO │ 177 | └───────────┘ └────────────┘ └────┬────┘ └────────┘ └───▲─────┘ └────────┘ 178 | │ NO │ 179 | └───────────────────────────┘ 180 | ``` 181 | 182 | ### Effects 183 | 184 | #### Static on and off 185 | 186 | Calling `On(uint16_t period=1)` turns the LED on. To immediately turn a LED on, 187 | make a call like `JLed(LED_BUILTIN).On().Update()`. The `period` is optional 188 | and defaults to 1ms. 189 | 190 | `Off()` works like `On()`, except that it turns the LED off, i.e., it sets the 191 | brightness to 0. 192 | 193 | Use the `Set(uint8_t brightness, uint16_t period=1)` method to set the 194 | brightness to the given value, i.e., `Set(255)` is equivalent to calling `On()` 195 | and `Set(0)` is equivalent to calling `Off()`. 196 | 197 | Technically, `Set`, `On` and `Off` are effects with a default period of 1ms, that 198 | set the brightness to a constant value. Specifying a different period has an 199 | effect on when the `Update()` method will be done updating the effect and 200 | return false (like for any other effects). This is important when for example 201 | in a `JLedSequence` the LED should stay on for a given amount of time. 202 | 203 | ##### Static on example 204 | 205 | ```c++ 206 | #include 207 | 208 | // turn builtin LED on after 1 second. 209 | auto led = JLed(LED_BUILTIN).On().DelayBefore(1000); 210 | 211 | void setup() { } 212 | 213 | void loop() { 214 | led.Update(); 215 | } 216 | ``` 217 | 218 | #### Blinking 219 | 220 | In blinking mode, the LED cycles through a given number of on-off cycles, on- 221 | and off-cycle durations are specified independently. The `Blink()` method takes 222 | the duration for the on- and off cycle as arguments. 223 | 224 | ##### Blinking example 225 | 226 | ```c++ 227 | #include 228 | 229 | // blink internal LED every second; 1 second on, 0.5 second off. 230 | auto led = JLed(LED_BUILTIN).Blink(1000, 500).Forever(); 231 | 232 | void setup() { } 233 | 234 | void loop() { 235 | led.Update(); 236 | } 237 | ``` 238 | 239 | #### Breathing 240 | 241 | In breathing mode, the LED smoothly changes the brightness using PWM. The 242 | `Breathe()` method takes the period of the effect as an argument. 243 | 244 | ##### Breathing example 245 | 246 | ```c++ 247 | #include 248 | 249 | // connect LED to pin 13 (PWM capable). LED will breathe with period of 250 | // 2000ms and a delay of 1000ms after each period. 251 | auto led = JLed(13).Breathe(2000).DelayAfter(1000).Forever(); 252 | 253 | void setup() { } 254 | 255 | void loop() { 256 | led.Update(); 257 | } 258 | ``` 259 | 260 | It is also possible to specify fade-on, on- and fade-off durations for the 261 | breathing mode to customize the effect. 262 | 263 | ```c++ 264 | // LED will fade-on in 500ms, stay on for 1000ms, and fade-off in 500ms. 265 | // It will delay for 1000ms afterwards and continue the pattern. 266 | auto led = JLed(13).Breathe(500, 1000, 500).DelayAfter(1000).Forever(); 267 | ``` 268 | 269 | #### Candle 270 | 271 | In candle mode, the random flickering of a candle or fire is simulated. 272 | The builder method has the following signature: 273 | `Candle(uint8_t speed, uint8_t jitter, uin16_t period)` 274 | 275 | * `speed` - controls the speed of the effect. 0 for fastest, increasing speed 276 | divides into halve per increment. The default value is 7. 277 | * `jitter` - the amount of jittering. 0 none (constant on), 255 maximum. Default 278 | value is 15. 279 | * `period` - Period of effect in ms. The default value is 65535 ms. 280 | 281 | The default settings simulate a candle. For a fire effect for example use 282 | call the method with `Candle(5 /*speed*/, 100 /* jitter*/)`. 283 | 284 | ##### Candle example 285 | 286 | ```c++ 287 | #include 288 | 289 | // Candle on LED pin 13 (PWM capable). 290 | auto led = JLed(13).Candle(); 291 | 292 | void setup() { } 293 | 294 | void loop() { 295 | led.Update(); 296 | } 297 | ``` 298 | 299 | #### FadeOn 300 | 301 | In FadeOn mode, the LED is smoothly faded on to 100% brightness using PWM. The 302 | `FadeOn()` method takes the period of the effect as an argument. 303 | 304 | The brightness function uses an approximation of this function (example with 305 | period 1000): 306 | 307 | [![fadeon function](doc/fadeon_plot.png)](https://www.wolframalpha.com/input/?i=plot+(exp(sin((t-1000%2F2.)*PI%2F1000))-0.36787944)*108.0++t%3D0+to+1000) 308 | 309 | ##### FadeOn example 310 | 311 | ```c++ 312 | #include 313 | 314 | // LED is connected to pin 9 (PWM capable) gpio 315 | auto led = JLed(9).FadeOn(1000).DelayBefore(2000); 316 | 317 | void setup() { } 318 | 319 | void loop() { 320 | led.Update(); 321 | } 322 | ``` 323 | 324 | #### FadeOff 325 | 326 | In FadeOff mode, the LED is smoothly faded off using PWM. The fade starts at 327 | 100% brightness. Internally it is implemented as a mirrored version of the 328 | FadeOn function, i.e., FadeOff(t) = FadeOn(period-t). The `FadeOff()` method 329 | takes the period of the effect as argument. 330 | 331 | #### Fade 332 | 333 | The Fade effect allows to fade from any start value `from` to any target value 334 | `to` with the given duration. Internally it sets up a `FadeOn` or `FadeOff` 335 | effect and `MinBrightness` and `MaxBrightness` values properly. The `Fade` 336 | method take three arguments: `from`, `to` and `duration`. 337 | 338 | fade from-to 339 | 340 | ##### Fade example 341 | 342 | ```c++ 343 | #include 344 | 345 | // fade from 100 to 200 with period 1000 346 | auto led = JLed(9).Fade(100, 200, 1000); 347 | 348 | void setup() { } 349 | 350 | void loop() { 351 | led.Update(); 352 | } 353 | ``` 354 | 355 | #### User provided brightness function 356 | 357 | It is also possible to provide a user defined brightness evaluator. The class 358 | must be derived from the `jled::BrightnessEvaluator` class and implement 359 | two methods: 360 | 361 | * `uint8_t Eval(uint32_t t) const` - the brightness evaluation function that 362 | calculates a brightness for the given time `t`. The brightness must be returned 363 | as an unsigned byte, where 0 means LED off and 255 means full brightness. 364 | * `uint16_t Period() const` - period of the effect. 365 | 366 | All time values are specified in milliseconds. 367 | 368 | The [user_func](examples/user_func) example demonstrates a simple user provided 369 | brightness function, while the [morse](examples/morse) example shows how a more 370 | complex application, allowing you to send morse codes (not necessarily with an 371 | LED), can be realized. 372 | 373 | ##### User provided brightness function example 374 | 375 | The example uses a user provided function to calculate the brightness. 376 | 377 | ```c++ 378 | class UserEffect : public jled::BrightnessEvaluator { 379 | public: 380 | uint8_t Eval(uint32_t t) const override { 381 | // this function changes between 0 and 255 and 382 | // vice versa every 250 ms. 383 | return 255*((t/250)%2); 384 | } 385 | // duration of effect: 5 seconds. 386 | uint16_t Period() const override { return 5000; } 387 | }; 388 | ``` 389 | 390 | #### Delays and repetitions 391 | 392 | ##### Initial delay before effect starts 393 | 394 | Use the `DelayBefore()` method to specify a delay before the first effect starts. 395 | The default value is 0 ms. 396 | 397 | ##### Delay after effect finished 398 | 399 | Use the `DelayAfter()` method to specify a delay after each repetition of 400 | an effect. The default value is 0 ms. 401 | 402 | ##### Repetitions 403 | 404 | Use the `Repeat()` method to specify the number of repetitions. The default 405 | value is 1 repetition. The `Forever()` methods sets to repeat the effect 406 | forever. Each repetition includes a full period of the effect and the time 407 | specified by `DelayAfter()` method. 408 | 409 | #### State functions 410 | 411 | ##### Update 412 | 413 | Call `Update(int16_t *pLast=nullptr)` or `Update(uint32_t t, int16_t *pLast=nullptr)` 414 | to periodically update the state of the LED. 415 | 416 | `Update` returns `true`, if the effect is active, or `false` when it finished. 417 | `Update()` is a shortcut to call `Update(uint32_t t)` with the current time in 418 | milliseconds. 419 | 420 | To obtain the value of the last written brightness value (after applying min- 421 | and max-brightness transformations), pass an additional optional pointer 422 | `*pLast` , where this value will be stored, when it was written. Example: 423 | 424 | ```c++ 425 | int16_t lastVal = -1; 426 | led.Update(&lastVal); 427 | if (lastVal != -1) { 428 | // the LED was updated with the brightness value now stored in lastVal 429 | ... 430 | } 431 | ``` 432 | 433 | Most of the time just calling `Update()` without any parameters is what you want. 434 | 435 | See [last_brightness](examples/last_brightness) example for a working example. 436 | 437 | ##### IsRunning 438 | 439 | `IsRunning()` returns `true` if the current effect is running, else `false`. 440 | 441 | ##### Reset 442 | 443 | A call to `Reset()` brings the JLed object to its initial state. Use it when 444 | you want to start-over an effect. 445 | 446 | ##### Immediate Stop 447 | 448 | Call `Stop()` to immediately turn the LED off and stop any running effects. 449 | Further calls to `Update()` will have no effect, unless the Led is reset using 450 | `Reset()` or a new effect is activated. By default, `Stop()` sets the current 451 | brightness level to `MinBrightness`. 452 | 453 | `Stop()` takes an optional argument `mode` of type `JLed::eStopMode`: 454 | 455 | * if set to `JLed::eStopMode::KEEP_CURRENT`, the LEDs current level will be kept 456 | * if set to `JLed::eStopMode::FULL_OFF` the level of the LED is set to `0`, 457 | regardless of what `MinBrightness` is set to, effectively turning the LED off 458 | * if set to `JLed::eStopMode::TO_MIN_BRIGHTNESS` (default behavior), the LED 459 | will set to the value of `MinBrightness` 460 | 461 | ```c++ 462 | // stop the effect and set the brightness level to 0, regardless of min brightness 463 | led.Stop(JLed::eStopMode::FULL_OFF); 464 | ``` 465 | 466 | #### Misc functions 467 | 468 | ##### Low active for inverted output 469 | 470 | Use the `LowActive()` method when the connected LED is low active. All output 471 | will be inverted by JLed (i.e., instead of x, the value of 255-x will be set). 472 | 473 | ##### Minimum- and Maximum brightness level 474 | 475 | The `MaxBrightness(uint8_t level)` method is used to set the maximum brightness 476 | level of the LED. A level of 255 (the default) is full brightness, while 0 477 | effectively turns the LED off. In the same way, the `MinBrightness(uint8_t level)` 478 | method sets the minimum brightness level. The default minimum level is 0. If 479 | minimum or maximum brightness levels are set, the output value is scaled to be 480 | within the interval defined by `[minimum brightness, maximum brightness]`: a 481 | value of 0 will be mapped to the minimum brightness level, a value of 255 will 482 | be mapped to the maximum brightness level. 483 | 484 | The `uint_8 MaxBrightness() const` method returns the current maximum 485 | brightness level. `uint8_t MinBrightness() const` returns the current minimum 486 | brightness level. 487 | 488 | ### Controlling a group of LEDs 489 | 490 | The `JLedSequence` class allows controlling a group of `JLed` objects 491 | simultaneously, either in parallel or sequentially, starting the next `JLed` 492 | effect when the previous finished. The constructor takes the mode (`PARALLEL`, 493 | `SEQUENCE`), an array of `JLed` objects and the size of the array, e.g. 494 | 495 | ```c++ 496 | JLed leds[] = { 497 | JLed(4).Blink(750, 250).Repeat(10), 498 | JLed(3).Breathe(2000).Repeat(5); 499 | }; 500 | 501 | auto sequence = JLedSequence(JLedSequence::eMode::PARALLEL, leds).Repeat(2); 502 | 503 | void setup() { 504 | } 505 | 506 | void loop() { 507 | sequence.Update(); 508 | } 509 | ``` 510 | 511 | Because the size of the array is known at compile time in this example, it is 512 | not necessary to pass the array size to the constructor. A second constructor 513 | is available in case the `JLed` array is created dynamically at runtime: 514 | `JLed(eMode mode, JLed* leds, size_t n)`. 515 | 516 | The `JLedSequence` provides the following methods: 517 | * `Update()` - updates the active `JLed` objects controlled by the sequence. 518 | Like the `JLed::Update()` method, it returns `true` if an effect is running, 519 | else `false`. 520 | * Use the `Repeat(n)` method to specify the number of repetitions. The default 521 | value is 1 repetition. The `Forever()` methods sets to repeat the sequence 522 | forever. 523 | * `Stop()` - turns off all `JLed` objects controlled by the sequence and 524 | stops the sequence. Further calls to `Update()` will have no effect. 525 | * `Reset()` - Resets all `JLed` objects controlled by the sequence and 526 | the sequence, resulting in a start-over. 527 | 528 | ## Framework notes 529 | 530 | JLed supports the Arduino and [mbed](https://www.mbed.org) frameworks. When 531 | using platformio, the framework to be used is configured in the `platform.ini` 532 | file, as shown in the following example, which for example selects the `mbed` 533 | framework: 534 | 535 | ```ini 536 | [env:nucleo_f401re_mbed] 537 | platform=ststm32 538 | board = nucleo_f401re 539 | framework = mbed 540 | build_flags = -Isrc 541 | src_filter = +<../../src/> +<./> 542 | upload_protocol=stlink 543 | ``` 544 | 545 | An [mbed example is provided here](examples/multiled_mbed/multiled_mbed.cpp). 546 | To compile it for the F401RE, make your [plaform.ini](platform.ini) look like: 547 | 548 | ```ini 549 | ... 550 | [platformio] 551 | default_envs = nucleo_f401re_mbed 552 | src_dir = examples/multiled_mbed 553 | ... 554 | ``` 555 | 556 | ## Platform notes 557 | 558 | ### ESP8266 559 | 560 | The DAC of the ESP8266 operates with 10 bits, every value JLed writes out gets 561 | automatically scaled to 10 bits, since JLed internally only uses 8 bits. The 562 | scaling methods make sure that min/max relationships are preserved, i.e., 0 is 563 | mapped to 0 and 255 is mapped to 1023. When using a user-defined brightness 564 | function on the ESP8266, 8-bit values must be returned, all scaling is done by 565 | JLed transparently for the application, yielding platform-independent code. 566 | 567 | ### ESP32 568 | 569 | When compiling for the ESP32, JLed uses `ledc` functions provided by the ESP32 570 | ESP-IDF SDK. (See [esspressif 571 | documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/ledc.html) 572 | for details). 573 | 574 | The `ledc` API connects so-called channels to GPIO pins, enabling them to use 575 | PWM. There are 16 channels available. Unless otherwise specified, JLed 576 | automatically picks the next free channel, starting with channel 0 and wrapping 577 | over after channel 15. To manually specify a channel, the JLed object must be 578 | constructed this way: 579 | 580 | ```c++ 581 | auto esp32Led = JLed(jled::Esp32Hal(2, 7)).Blink(1000, 1000).Forever(); 582 | ``` 583 | 584 | The `jled::Esp32Hal(pin, chan)` constructor takes the pin number as the first 585 | argument and the ESP32 ledc channel number on the second position. Note that 586 | using the above-mentioned constructor results in non-platform independent code, 587 | so it should be avoided and is normally not necessary. 588 | 589 | For completeness, the full signature of the Esp32Hal constructor is 590 | 591 | ``` 592 | Esp32Hal(PinType pin, 593 | int chan = kAutoSelectChan, 594 | uint16_t freq = 5000, 595 | ledc_timer_t timer = LEDC_TIMER_0) 596 | ``` 597 | 598 | which also allows to override the default frequency and timer used, when needed. 599 | 600 | #### Using ESP-IDF 601 | 602 | Since JLed uses the ESP-IDF SDK, JLed can also be directly used in ESP-IDF 603 | projects, without the need of using the Arduino Framework (which is also 604 | possible). See these repositories for example projects: 605 | 606 | * https://github.com/jandelgado/jled-esp-idf-example 607 | * https://github.com/jandelgado/jled-esp-idf-platformio-example 608 | 609 | ### STM32 610 | 611 | #### Arduino framework 612 | 613 | I had success running JLed on a [STM32 Nucleo64 F401RE 614 | board](https://www.st.com/en/evaluation-tools/nucleo-f401re.html) using this 615 | [STM32 Arduino 616 | core](https://github.com/rogerclarkmelbourne/Arduino_STM32/tree/master/STM32F4) 617 | and compiling examples from the Arduino IDE. Note that the `stlink` is 618 | necessary to upload sketches to the microcontroller. 619 | 620 | ### Raspberry Pi Pico 621 | 622 | When using JLed on a Raspberry Pi Pico, the Pico-SDK and tools can be 623 | used. The Pico supports up to 16 PWM channels in parallel. See 624 | the [pico-demo](examples/raspi_pico) for an example and build instructions when 625 | the Pico-SDK is used. 626 | 627 | A probably easier approach is to use the Arduino platform. See 628 | [platformio.ini](platformio.ini) for details (look for 629 | `env:raspberrypi_pico_w`, which targets the Raspberry Pi Pico W. 630 | 631 | ## Example sketches 632 | 633 | Example sketches are provided in the [examples](examples/) directory. 634 | 635 | * [Hello, world](examples/hello) 636 | * [Turn LED on after a delay](examples/simple_on) 637 | * [Breathe effect](examples/breathe) 638 | * [Candle effect](examples/candle) 639 | * [Fade LED on](examples/fade_on) 640 | * [Fade LED off](examples/fade_off) 641 | * [Fade from-to effect](examples/fade_from_to) 642 | * [Pulse effect](examples/pulse) 643 | * [Controlling multiple LEDs in parallel](examples/multiled) 644 | * [Controlling multiple LEDs in parallel (mbed)](examples/multiled_mbed) 645 | * [Controlling multiple LEDs sequentially](examples/sequence) 646 | * [Simple User provided effect](examples/user_func) 647 | * [Morsecode example](examples/morse) 648 | * [Last brightness value example](examples/last_brightness) 649 | * [Custom HAL example](examples/custom_hal) 650 | * [Custom PCA9685 HAL](https://github.com/jandelgado/jled-pca9685-hal) 651 | * [Dynamically switch sequences](https://github.com/jandelgado/jled-example-switch-sequence) 652 | * [JLed compiled to WASM and running in the browser](https://jandelgado.github.io/jled-wasm) 653 | * [Raspberry Pi Pico Demo](examples/raspi_pico) 654 | * [ESP32 ESP-IDF example](https://github.com/jandelgado/jled-esp-idf-example) 655 | * [ESP32 ESP-IDF PlatformIO example](https://github.com/jandelgado/jled-esp-idf-platformio-example) 656 | 657 | ### Building examples with PlatformIO 658 | 659 | To build an example using [the PlatformIO ide](http://platformio.org/), 660 | uncomment the example to be built in the [platformio.ini](platformio.ini) 661 | project file, e.g.: 662 | 663 | ```ini 664 | [platformio] 665 | ; uncomment example to build 666 | src_dir = examples/hello 667 | ;src_dir = examples/breathe 668 | ``` 669 | 670 | ### Building examples with the Arduino IDE 671 | 672 | To build an example sketch in the Arduino IDE, select an example from 673 | the `File` > `Examples` > `JLed` menu. 674 | 675 | ## Extending 676 | 677 | ### Support new hardware 678 | 679 | JLed uses a very thin hardware abstraction layer (hal) to abstract access to 680 | the actual MCU/framework used (e.g. ESP32, ESP8266). The hal object encapsulate 681 | access to the GPIO and time functionality of the MCU under the framework being 682 | used. During the unit test, mocked hal instances are used, enabling tests to 683 | check the generated output. The [Custom HAL project](examples/custom_hal) 684 | provides an example for a user define HAL. 685 | 686 | ## Unit tests 687 | 688 | JLed comes with an exhaustive host-based unit test suite. Info on how to run 689 | the host-based provided unit tests [is provided here](test/README.md). 690 | 691 | ## Contributing 692 | 693 | * fork this repository 694 | * create your feature branch 695 | * add code 696 | * add [unit test(s)](test/) 697 | * add [documentation](README.md) 698 | * make sure the cpp [linter](https://github.com/cpplint/cpplint) does not 699 | report any problems (run `make lint`). Hint: use `clang-format` with the 700 | provided [settings](.clang-format) 701 | * commit changes 702 | * submit a PR 703 | 704 | ## FAQ 705 | 706 | ### How do I check if a JLed object is still being updated? 707 | 708 | * Check the return value of the `JLed::Update` method: the method returns `true` if 709 | the effect is still running, otherwise `false`. 710 | * The `JLed::IsRunning` method returns `true` if an effect is running, else `false`. 711 | 712 | ### How do I restart an effect? 713 | 714 | Call `Reset()` on a `JLed` object to start over. 715 | 716 | ### How do I change a running effect? 717 | 718 | Just 'reconfigure' the `JLed` with any of the effect methods (e.g. `FadeOn`, 719 | `Breathe`, `Blink` etc). Time-wise, the effect will start over. 720 | 721 | ## Author and Copyright 722 | 723 | Copyright 2017-2022 by Jan Delgado, jdelgado[at]gmx.net. 724 | 725 | ## License 726 | 727 | [MIT](LICENSE) 728 | -------------------------------------------------------------------------------- /devbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "python@3.13", 4 | "lcov@1.16", 5 | "pipx", 6 | "cpplint@2.0.0" 7 | ], 8 | "shell": { 9 | "init_hook": [ 10 | ". $VENV_DIR/bin/activate" 11 | ], 12 | "scripts": { 13 | "test": [ 14 | "echo \"Error: no test specified\" && exit 1" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /devbox.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfile_version": "1", 3 | "packages": { 4 | "cpplint@2.0.0": { 5 | "last_modified": "2024-11-03T14:18:04Z", 6 | "resolved": "github:NixOS/nixpkgs/4ae2e647537bcdbb82265469442713d066675275#cpplint", 7 | "source": "devbox-search", 8 | "version": "2.0.0", 9 | "systems": { 10 | "aarch64-darwin": { 11 | "outputs": [ 12 | { 13 | "name": "out", 14 | "path": "/nix/store/70gc21bl3grda63djlks6x1f5g8h89xk-cpplint-2.0.0", 15 | "default": true 16 | }, 17 | { 18 | "name": "dist", 19 | "path": "/nix/store/3sq5396dcbg3r06g9zci1w4rxql1xf0k-cpplint-2.0.0-dist" 20 | } 21 | ], 22 | "store_path": "/nix/store/70gc21bl3grda63djlks6x1f5g8h89xk-cpplint-2.0.0" 23 | }, 24 | "aarch64-linux": { 25 | "outputs": [ 26 | { 27 | "name": "out", 28 | "path": "/nix/store/3fcbszvn75zwim2n3785bflrwww38l42-cpplint-2.0.0", 29 | "default": true 30 | }, 31 | { 32 | "name": "dist", 33 | "path": "/nix/store/fqrmkx6q4q290hildy4l6gi2pzwzwinw-cpplint-2.0.0-dist" 34 | } 35 | ], 36 | "store_path": "/nix/store/3fcbszvn75zwim2n3785bflrwww38l42-cpplint-2.0.0" 37 | }, 38 | "x86_64-darwin": { 39 | "outputs": [ 40 | { 41 | "name": "out", 42 | "path": "/nix/store/m675bxwgh3wmr0gix5g6xnhkidlql0ii-cpplint-2.0.0", 43 | "default": true 44 | }, 45 | { 46 | "name": "dist", 47 | "path": "/nix/store/2bpdwa3r1kfzf4jkd4i24njxy3pr3xnv-cpplint-2.0.0-dist" 48 | } 49 | ], 50 | "store_path": "/nix/store/m675bxwgh3wmr0gix5g6xnhkidlql0ii-cpplint-2.0.0" 51 | }, 52 | "x86_64-linux": { 53 | "outputs": [ 54 | { 55 | "name": "out", 56 | "path": "/nix/store/vrv52827v0b6h4k3nl7k9zg4ld182khg-cpplint-2.0.0", 57 | "default": true 58 | }, 59 | { 60 | "name": "dist", 61 | "path": "/nix/store/132q2dd3wkkgdpybwzhybm7hbad0g097-cpplint-2.0.0-dist" 62 | } 63 | ], 64 | "store_path": "/nix/store/vrv52827v0b6h4k3nl7k9zg4ld182khg-cpplint-2.0.0" 65 | } 66 | } 67 | }, 68 | "lcov@1.16": { 69 | "last_modified": "2024-03-22T11:26:23Z", 70 | "resolved": "github:NixOS/nixpkgs/a3ed7406349a9335cb4c2a71369b697cecd9d351#lcov", 71 | "source": "devbox-search", 72 | "version": "1.16", 73 | "systems": { 74 | "aarch64-darwin": { 75 | "store_path": "/nix/store/cwjgl90nkg79za5gx41yg4663w7iyj0y-lcov-1.16" 76 | }, 77 | "aarch64-linux": { 78 | "store_path": "/nix/store/81axjrgg3cjx09kdb25psiiyz60zacqw-lcov-1.16" 79 | }, 80 | "x86_64-darwin": { 81 | "store_path": "/nix/store/5jir4n0q72z9p2h9sxwb2rcam3j165wp-lcov-1.16" 82 | }, 83 | "x86_64-linux": { 84 | "store_path": "/nix/store/qfdwnjp77xlvg0jqfv1dl8b40xpv230k-lcov-1.16" 85 | } 86 | } 87 | }, 88 | "pipx": { 89 | "resolved": "github:NixOS/nixpkgs/75a52265bda7fd25e06e3a67dee3f0354e73243c#pipx", 90 | "source": "nixpkg" 91 | }, 92 | "python@3.13": { 93 | "last_modified": "2024-11-28T07:51:56Z", 94 | "plugin_version": "0.0.4", 95 | "resolved": "github:NixOS/nixpkgs/226216574ada4c3ecefcbbec41f39ce4655f78ef#python313", 96 | "source": "devbox-search", 97 | "version": "3.13.0", 98 | "systems": { 99 | "aarch64-darwin": { 100 | "outputs": [ 101 | { 102 | "name": "out", 103 | "path": "/nix/store/fbyrkq5n04a9hn5zs26vrmqjzdx73d4g-python3-3.13.0", 104 | "default": true 105 | } 106 | ], 107 | "store_path": "/nix/store/fbyrkq5n04a9hn5zs26vrmqjzdx73d4g-python3-3.13.0" 108 | }, 109 | "aarch64-linux": { 110 | "outputs": [ 111 | { 112 | "name": "out", 113 | "path": "/nix/store/jbz9fj3sp5c8bf0s6d0bkjjj9mslxsrc-python3-3.13.0", 114 | "default": true 115 | }, 116 | { 117 | "name": "debug", 118 | "path": "/nix/store/60jgy93wj50wwimmhm2p53pzaiap8ypm-python3-3.13.0-debug" 119 | } 120 | ], 121 | "store_path": "/nix/store/jbz9fj3sp5c8bf0s6d0bkjjj9mslxsrc-python3-3.13.0" 122 | }, 123 | "x86_64-darwin": { 124 | "outputs": [ 125 | { 126 | "name": "out", 127 | "path": "/nix/store/c7j1vxcdcqswsddm5m1n2n4z5zfhmbq2-python3-3.13.0", 128 | "default": true 129 | } 130 | ], 131 | "store_path": "/nix/store/c7j1vxcdcqswsddm5m1n2n4z5zfhmbq2-python3-3.13.0" 132 | }, 133 | "x86_64-linux": { 134 | "outputs": [ 135 | { 136 | "name": "out", 137 | "path": "/nix/store/0b83hlniyfbpha92k2j0w93mxdalv8kb-python3-3.13.0", 138 | "default": true 139 | }, 140 | { 141 | "name": "debug", 142 | "path": "/nix/store/xzhxhqs8my0yvfi09aj1s9i1s9nrmpvg-python3-3.13.0-debug" 143 | } 144 | ], 145 | "store_path": "/nix/store/0b83hlniyfbpha92k2j0w93mxdalv8kb-python3-3.13.0" 146 | } 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /doc/cheat_sheet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/cheat_sheet.jpg -------------------------------------------------------------------------------- /doc/fade_from-to.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/fade_from-to.png -------------------------------------------------------------------------------- /doc/fadeon_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/fadeon_plot.png -------------------------------------------------------------------------------- /doc/jled-wasm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/jled-wasm.png -------------------------------------------------------------------------------- /doc/jled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/jled.gif -------------------------------------------------------------------------------- /doc/morse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/morse.jpg -------------------------------------------------------------------------------- /doc/multiled.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/multiled.fzz -------------------------------------------------------------------------------- /doc/multiled_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/multiled_bb.png -------------------------------------------------------------------------------- /doc/multiled_esp32.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/multiled_esp32.fzz -------------------------------------------------------------------------------- /doc/multiled_esp32_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/multiled_esp32_bb.png -------------------------------------------------------------------------------- /doc/multiled_mbed.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/multiled_mbed.fzz -------------------------------------------------------------------------------- /doc/multiled_mbed_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jandelgado/jled/f6ccd9dcb45ac2fd42821521b8607aaf909083ab/doc/multiled_mbed_bb.png -------------------------------------------------------------------------------- /examples/breathe/breathe.ino: -------------------------------------------------------------------------------- 1 | // JLed breathe demo. 2 | // Copyright 2017 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | // breathe LED for 5 times, LED is connected to pin 9 (PWM capable) gpio 7 | auto led = JLed(9).Breathe(2000).Repeat(5).DelayAfter(2000); 8 | 9 | void setup() { 10 | } 11 | 12 | void loop() { 13 | led.Update(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/candle/candle.ino: -------------------------------------------------------------------------------- 1 | // JLed candle effect 2 | // Copyright 2019 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | auto led = JLed(5).Candle().Forever(); 7 | 8 | // change speed and jitter to turn the candle into a fire 9 | // auto led = JLed(5).Candle(5 /* speed */, 100 /*jitter*/).Forever(); 10 | 11 | void setup() { 12 | } 13 | 14 | void loop() { 15 | led.Update(); 16 | } 17 | -------------------------------------------------------------------------------- /examples/custom_hal/custom_hal.ino: -------------------------------------------------------------------------------- 1 | // JLed custom HAL example. 2 | // Copyright 2019 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | 5 | // we include jled_base.h instead of "jled.h" since we define our own JLed 6 | // class using our custom HAL. 7 | #include 8 | 9 | // a custom HAL for the Arduino, inverting output and ticking with half 10 | // the speed. In general, a JLed HAL class must satisfy the following 11 | // interface: 12 | // 13 | // class JledHal { 14 | // public: 15 | // JledHal(PinType pin); 16 | // void analogWrite(uint8_t val) const; 17 | // uint32_t millis() const; 18 | // } 19 | // 20 | class CustomHal { 21 | public: 22 | using PinType = uint8_t; 23 | 24 | explicit CustomHal(PinType pin) noexcept : pin_(pin) {} 25 | 26 | void analogWrite(uint8_t val) const { 27 | // some platforms, e.g. STM need lazy initialization 28 | if (!setup_) { 29 | ::pinMode(pin_, OUTPUT); 30 | setup_ = true; 31 | } 32 | ::analogWrite(pin_, 255 - val); 33 | } 34 | 35 | uint32_t millis() const { return ::millis() >> 1; } 36 | 37 | private: 38 | mutable bool setup_ = false; 39 | PinType pin_; 40 | }; 41 | 42 | class JLed : public jled::TJLed { 43 | using jled::TJLed::TJLed; 44 | }; 45 | 46 | // uses above defined CustomHal 47 | auto led = JLed(LED_BUILTIN).Blink(1000, 1000).Repeat(5); 48 | 49 | void setup() {} 50 | 51 | void loop() { led.Update(); } 52 | -------------------------------------------------------------------------------- /examples/fade_from_to/fade_from_to.ino: -------------------------------------------------------------------------------- 1 | // JLed fade from-to example. Example randomly fades to a new level with 2 | // a random duration. 3 | // Copyright 2022 by Jan Delgado. All rights reserved. 4 | // https://github.com/jandelgado/jled 5 | #include 6 | 7 | auto led = JLed(5).On(1); // start with LED turned on 8 | 9 | void setup() {} 10 | 11 | void loop() { 12 | static uint8_t last_to = 255; 13 | 14 | if (!led.Update()) { 15 | // when effect is done (Update() returns false), 16 | // reconfigure fade effect using random values 17 | auto new_from = last_to; 18 | auto new_to = jled::rand8(); 19 | auto duration = 250 + jled::rand8() * 4; 20 | last_to = new_to; 21 | led.Fade(new_from, new_to, duration).Repeat(1); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/fade_off/fade_off.ino: -------------------------------------------------------------------------------- 1 | // JLed delayed turn-on demo. Turns on built-in LED after 2 seconds. 2 | // Copyright 2017 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | // LED is connected to pin 9 (PWM capable) gpio 7 | auto led = JLed(9); 8 | 9 | void setup() { 10 | led.On().Update(); 11 | led.FadeOff(2000).DelayBefore(5000); 12 | } 13 | 14 | void loop() { 15 | led.Update(); 16 | } 17 | -------------------------------------------------------------------------------- /examples/fade_on/fade_on.ino: -------------------------------------------------------------------------------- 1 | // JLed delayed fade-on demo. Fades on LED after 2 seconds. 2 | // Copyright 2017 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | // LED is connected to pin 9 (PWM capable) gpio 7 | auto led = JLed(9).FadeOn(1000).DelayBefore(2000); 8 | 9 | void setup() { 10 | } 11 | 12 | void loop() { 13 | led.Update(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/hello/hello.ino: -------------------------------------------------------------------------------- 1 | // JLed 'hello, world.'. Blinks built in LED 5 times. 2 | // Copyright 2017 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | // blink builtin LED for 5 times 7 | auto led = JLed(LED_BUILTIN).Blink(1000, 1000).Repeat(5); 8 | 9 | void setup() { 10 | } 11 | 12 | void loop() { 13 | led.Update(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/last_brightness/last_brightness.ino: -------------------------------------------------------------------------------- 1 | // Stops an effect when a button is pressed (and hold). When the button is 2 | // released, the LED will fade to off with starting the brightness value it had 3 | // when the effect was stopped. 4 | // 5 | // dependency: arduinogetstarted/ezButton@1.0.6 to control the button 6 | // 7 | // Copyright 2024 by Jan Delgado. All rights reserved. 8 | // https://github.com/jandelgado/jled 9 | // 10 | #include // arduinogetstarted/ezButton@1.0.6 11 | #include 12 | 13 | constexpr auto LED_PIN = 16; 14 | constexpr auto BUTTON_PIN = 18; 15 | 16 | auto button = ezButton(BUTTON_PIN); 17 | 18 | // start with a pulse effect 19 | auto led = 20 | JLed(LED_PIN).DelayBefore(1000).Breathe(2000).Forever().MinBrightness(25); 21 | 22 | void setup() {} 23 | 24 | void loop() { 25 | static int16_t lastBrightness = 0; 26 | 27 | button.loop(); 28 | led.Update(&lastBrightness); 29 | 30 | if (button.isPressed()) { 31 | // when the button is pressed, stop the effect on led, but keep the LED 32 | // on with it's current brightness ... 33 | led.Stop(JLed::KEEP_CURRENT); 34 | } else if (button.isReleased()) { 35 | // when the button is released, fade from the last brightness to 0 36 | led = JLed(LED_PIN).Fade(lastBrightness, 0, 1000); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/morse/README.md: -------------------------------------------------------------------------------- 1 | # JLed morse example 2 | 3 | This examples demonstrates an efficient method to generate morse code on 4 | an micro controller like the Arduino. 5 | 6 | The morse example uses the morse alphabet encoded in a binary tree to 7 | generate morse code using a JLed user defined brightness class. The text 8 | to be morsed is transformed into morse code and then transformed into a 9 | sequence of `1` and `0` which are written out to a GPIO controlling a LED or 10 | a sound generator. 11 | 12 | ![morse example](../../doc/morse.jpg) 13 | 14 | ## Author 15 | 16 | Jan Delgado 17 | 18 | -------------------------------------------------------------------------------- /examples/morse/bitset.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | 4 | #ifndef EXAMPLES_MORSE_BITSET_H_ 5 | #define EXAMPLES_MORSE_BITSET_H_ 6 | 7 | // a simple bit set with capacity of N bits, just enough for the morse demo 8 | class Bitset { 9 | private: 10 | size_t n_; 11 | uint8_t* bits_; 12 | 13 | protected: 14 | // returns num bytes needed to store n bits. 15 | static constexpr size_t num_bytes(size_t n) { 16 | return n > 0 ? ((n - 1) >> 3) + 1 : 0; 17 | } 18 | 19 | public: 20 | Bitset() : Bitset(0) {} 21 | 22 | Bitset(const Bitset& b) : Bitset() { *this = b; } 23 | 24 | explicit Bitset(size_t n) : n_(n), bits_{new uint8_t[num_bytes(n)]} { 25 | memset(bits_, 0, num_bytes(n_)); 26 | } 27 | 28 | Bitset& operator=(const Bitset& b) { 29 | if (&b == this) return *this; 30 | const auto size_new = num_bytes(b.n_); 31 | if (num_bytes(n_) != size_new) { 32 | delete[] bits_; 33 | bits_ = new uint8_t[size_new]; 34 | n_ = b.n_; 35 | } 36 | memcpy(bits_, b.bits_, size_new); 37 | return *this; 38 | } 39 | 40 | virtual ~Bitset() { 41 | delete[] bits_; 42 | bits_ = nullptr; 43 | } 44 | void set(size_t i, bool val) { 45 | if (val) 46 | bits_[i >> 3] |= (1 << (i & 7)); 47 | else 48 | bits_[i >> 3] &= ~(1 << (i & 7)); 49 | } 50 | bool test(size_t i) const { return (bits_[i >> 3] & (1 << (i & 7))) != 0; } 51 | size_t size() const { return n_; } 52 | }; 53 | #endif // EXAMPLES_MORSE_BITSET_H_ 54 | -------------------------------------------------------------------------------- /examples/morse/morse.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | #include 4 | #include 5 | #include 6 | #include "bitset.h" // NOLINT 7 | 8 | #ifndef EXAMPLES_MORSE_MORSE_H_ 9 | #define EXAMPLES_MORSE_MORSE_H_ 10 | 11 | // The Morse class converts a text sequence into a bit sequence representing 12 | // a morse code sequence. 13 | class Morse { 14 | // pre-ordered tree of morse codes. Bit 1 = 'dah', 0 = 'dit'. 15 | // Position in string corresponds to position in binary tree starting w/ 1 16 | // see https://www.pocketmagic.net/morse-encoder/ for info on encoding 17 | static constexpr auto LATIN = 18 | "*ETIANMSURWDKGOHVF*L*PJBXCYZQ**54*3***2*******16*******7***8*90"; 19 | 20 | static constexpr auto DURATION_DIT = 1; 21 | static constexpr auto DURATION_DAH = 3 * DURATION_DIT; 22 | static constexpr auto DURATION_PAUSE_CHAR = DURATION_DAH; 23 | static constexpr auto DURATION_PAUSE_WORD = 7 * DURATION_DIT; 24 | 25 | protected: 26 | char upper(char c) const { return c >= 'a' && c <= 'z' ? c - 32 : c; } 27 | bool isspace(char c) const { return c == ' '; } 28 | 29 | // returns position of char in morse tree. Count starts with 1, i.e. 30 | // E=2, T=3, etc. 31 | size_t treepos(char c) const { 32 | auto i = 1u; 33 | while (LATIN[i++] != c) { 34 | } 35 | return i; 36 | } 37 | 38 | // returns uint16_t with size of morse sequence (dit's and dah's) in MSB 39 | // and the morse sequence in the LSB 40 | uint16_t pos_to_morse_code(int code) const { 41 | uint8_t res = 0; 42 | uint8_t size = 0; 43 | while (code > 1) { 44 | size++; 45 | res <<= 1; 46 | res |= (code & 1); 47 | code >>= 1; 48 | } 49 | return res | (size << 8); 50 | } 51 | 52 | template 53 | uint16_t iterate_sequence(const char* p, F f) const { 54 | // call f(count,val) num times, incrementing count each time 55 | // and returning num afterwards. 56 | auto set = [](int num, int count, bool val, F f) -> int { 57 | for (auto i = 0; i < num; i++) f(count + i, val); 58 | return num; 59 | }; 60 | 61 | auto bitcount = 0; 62 | while (*p) { 63 | const auto c = upper(*p++); 64 | if (isspace(c)) { // space not part of alphabet, treat separately 65 | bitcount += set(DURATION_PAUSE_WORD, bitcount, false, f); 66 | continue; 67 | } 68 | 69 | const auto morse_code = pos_to_morse_code(treepos(upper(c))); 70 | auto code = morse_code & 0xff; // dits (0) and dahs (1) 71 | auto size = morse_code >> 8; // number of dits and dahs in code 72 | while (size--) { 73 | bitcount += set((code & 1) ? DURATION_DAH : DURATION_DIT, 74 | bitcount, true, f); 75 | 76 | // pause between symbols := 1 dit 77 | if (size) { 78 | bitcount += set(DURATION_DIT, bitcount, false, f); 79 | } 80 | code >>= 1; 81 | } 82 | 83 | if (*p && !isspace(*p)) { 84 | bitcount += set(DURATION_PAUSE_CHAR, bitcount, false, f); 85 | } 86 | } 87 | return bitcount; 88 | } 89 | 90 | public: 91 | // returns ith bit of morse sequence 92 | bool test(uint16_t i) const { return bits_->test(i); } 93 | 94 | // length of complete morse sequence in in bits 95 | size_t size() const { return bits_->size(); } 96 | 97 | Morse() : bits_(new Bitset(0)) {} 98 | 99 | explicit Morse(const char* s) { 100 | const auto length = iterate_sequence(s, [](int, bool) -> void {}); 101 | auto bits = new Bitset(length); 102 | iterate_sequence(s, [bits](int i, bool v) -> void { bits->set(i, v); }); 103 | bits_ = bits; 104 | } 105 | 106 | ~Morse() { delete bits_; } 107 | 108 | // make sure that the following, currently not needed, methods are not used 109 | Morse(const Morse&m) {*this = m;} 110 | Morse& operator=(const Morse&m) { 111 | delete bits_; 112 | bits_ = new Bitset(*m.bits_); 113 | return *this; 114 | } 115 | 116 | private: 117 | // stores morse bit sequence 118 | const Bitset* bits_ = nullptr; 119 | }; 120 | 121 | #endif // EXAMPLES_MORSE_MORSE_H_ 122 | -------------------------------------------------------------------------------- /examples/morse/morse.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Jan Delgado 2 | // JLed user defined brightness morse example 3 | // https://github.com/jandelgado/jled 4 | #include "morse_effect.h" // NOLINT 5 | #include 6 | #include 7 | 8 | MorseEffect morseEffect("HELLO JLED"); 9 | auto morseLed = 10 | JLed(LED_BUILTIN).UserFunc(&morseEffect).DelayAfter(2000).Forever(); 11 | 12 | void setup() {} 13 | 14 | void loop() { morseLed.Update(); } 15 | -------------------------------------------------------------------------------- /examples/morse/morse_effect.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | #ifndef EXAMPLES_MORSE_MORSE_EFFECT_H_ 4 | #define EXAMPLES_MORSE_MORSE_EFFECT_H_ 5 | 6 | #include 7 | #include "morse.h" // NOLINT 8 | 9 | class MorseEffect : public jled::BrightnessEvaluator { 10 | Morse morse_; 11 | // duration of a single 'dit' in ms 12 | const uint16_t speed_; 13 | 14 | public: 15 | explicit MorseEffect(const char* message, uint16_t speed = 200) 16 | : morse_(message), speed_(speed) {} 17 | 18 | uint8_t Eval(uint32_t t) const override { 19 | const auto pos = t / speed_; 20 | if (pos >= morse_.size()) return 0; 21 | return morse_.test(pos) ? 255 : 0; 22 | } 23 | 24 | uint16_t Period() const override { return (morse_.size() + 1) * speed_; } 25 | }; 26 | 27 | #endif // EXAMPLES_MORSE_MORSE_EFFECT_H_ 28 | -------------------------------------------------------------------------------- /examples/multiled/README.md: -------------------------------------------------------------------------------- 1 | # Multi LED example 2 | 3 | This example controls 4+1 LEDs, showing different effects, yet all synchronized: 4 | 5 | * blue LED: breathe (period 2s) 6 | * green LED: blink (0.75s on/0.25s off) 7 | * red LED: fade off (period 1s) 8 | * yellow LED: fade on (period 1s) 9 | * built-in LED: blink (0.5s on/0.5s off) 10 | 11 | ## Wiring 12 | 13 | ![mutliled](../../doc/multiled_bb.png) 14 | 15 | ## Result 16 | 17 | ![multiled](../../doc/jled.gif) 18 | -------------------------------------------------------------------------------- /examples/multiled/multiled.ino: -------------------------------------------------------------------------------- 1 | // JLed multi LED demo. control multiple LEDs in-sync. 2 | // Copyright (c) 2017-2021 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | JLed leds[] = { 7 | JLed(4).Blink(750, 250).Repeat(2), 8 | JLed(3).Breathe(2000), 9 | JLed(5).FadeOff(1000).Repeat(2), 10 | JLed(6).FadeOn(1000).Repeat(2), 11 | JLed(LED_BUILTIN).Blink(500, 500).Repeat(2) 12 | }; 13 | 14 | auto sequence = JLedSequence(JLedSequence::eMode::PARALLEL, leds).Repeat(5); 15 | 16 | void setup() { } 17 | 18 | void loop() { 19 | sequence.Update(); 20 | delay(1); 21 | } 22 | -------------------------------------------------------------------------------- /examples/multiled_mbed/README.md: -------------------------------------------------------------------------------- 1 | # Multi LED example for mbed 2 | 3 | This example controls 4+1 LEDs, showing different effects, yet all synchronized: 4 | 5 | * blue LED: breathe (period 2s) 6 | * green LED: blink (0.75s on/0.25s off) 7 | * red LED: fade off (period 1s) 8 | * yellow LED: fade on (period 1s) 9 | * built-in LED: blink (0.5s on/0.5s off) 10 | 11 | ## Wiring 12 | 13 | The example uses a STM32 Nucleo F401RE and is wired like shown: 14 | 15 | ![mutliled](../../doc/multiled_mbed_bb.png) 16 | 17 | -------------------------------------------------------------------------------- /examples/multiled_mbed/multiled_mbed.cpp: -------------------------------------------------------------------------------- 1 | // JLed multi LED demo for mbed. Controls multiple LEDs parallel in-sync. 2 | // 3 | // mbed version tested with ST Nucleo F401RE 4 | // 5 | // See https://os.mbed.com/platforms/ST-Nucleo-F401RE/ for pin names and 6 | // assignments. 7 | // 8 | // Copyright 2020 by Jan Delgado. All rights reserved. 9 | // https://github.com/jandelgado/jled 10 | // 11 | #include 12 | #include 13 | 14 | int main() { 15 | JLed leds[] = {JLed(LED1).Blink(750, 250).Forever(), 16 | JLed(PA_8).Breathe(2000).Forever(), 17 | JLed(PB_10).FadeOff(1000).Forever(), 18 | JLed(PB_4).FadeOn(1000).Forever(), 19 | JLed(PB_3).Blink(500, 500).Forever()}; 20 | 21 | JLedSequence sequence(JLedSequence::eMode::PARALLEL, leds); 22 | 23 | while (1) { 24 | sequence.Update(); 25 | } 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /examples/pulse/pulse.ino: -------------------------------------------------------------------------------- 1 | // JLed pulse demo. 2 | // Copyright 2022 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | // "pulse" LED on GPIO pin 5. The "pulse" is accomplished by setting 7 | // a minimal brightness so the LED will not be dark. 8 | auto led = JLed(5).Breathe(2000).MinBrightness(20).Forever().DelayAfter(500); 9 | 10 | void setup() {} 11 | 12 | void loop() { led.Update(); } 13 | -------------------------------------------------------------------------------- /examples/raspi_pico/.gitignore: -------------------------------------------------------------------------------- 1 | CMakeFiles/ 2 | elf2uf2/ 3 | cmake_install.cmake/ 4 | generated/ 5 | pico-sdk/ 6 | build/ 7 | CMakeCache.txt 8 | Makefile 9 | cmake_install.cmake 10 | *.bin 11 | *.dis 12 | *.elf 13 | *.map 14 | *.hex 15 | *.uf2 16 | -------------------------------------------------------------------------------- /examples/raspi_pico/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | set(CMAKE_C_STANDARD 11) 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | # Pull in Pico SDK (must be before project) - set PICO_SDK_PATH env var 7 | include(pico_sdk_import.cmake) 8 | 9 | project(pico_demo C CXX ASM) 10 | 11 | # Initialise the Pico SDK 12 | pico_sdk_init() 13 | 14 | # Add executable. Default name is the project name, version 0.1 15 | add_executable(pico_demo pico_demo.cpp ) 16 | pico_set_program_name(pico_demo "pico_demo") 17 | pico_set_program_version(pico_demo "0.1") 18 | 19 | pico_enable_stdio_uart(pico_demo 1) 20 | pico_enable_stdio_usb(pico_demo 0) 21 | 22 | # Add the standard library to the build 23 | target_link_libraries(pico_demo pico_stdlib hardware_pwm JLed) 24 | 25 | pico_add_extra_outputs(pico_demo) 26 | 27 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../..src) 28 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../.. build) 29 | 30 | -------------------------------------------------------------------------------- /examples/raspi_pico/Dockerfile: -------------------------------------------------------------------------------- 1 | # A minimal dockerfile to provide a build environment to compile the JLed 2 | # raspberry pi pico JLed in a docker container. 3 | FROM ubuntu:20.04 4 | 5 | LABEL MAINTAINER "Jan Delgado " 6 | 7 | ARG TZ=Europe/Berlin 8 | ENV DEBIAN_FRONTEND=noninteractive 9 | 10 | RUN echo ${TZ} > /etc/timezone && rm -f /etc/localtime \ 11 | && cat /etc/timezone\ 12 | && apt-get update \ 13 | && apt-get install -y git cmake gcc-arm-none-eabi libnewlib-arm-none-eabi \ 14 | build-essential vim python3 python3-pip 15 | 16 | RUN mkdir /pico 17 | WORKDIR /pico 18 | 19 | # install SDK 20 | RUN git clone --depth=1 -b master https://github.com/raspberrypi/pico-sdk.git \ 21 | && cd pico-sdk && git submodule update --init 22 | 23 | ENV PICO_SDK_PATH=/pico/pico-sdk 24 | 25 | -------------------------------------------------------------------------------- /examples/raspi_pico/README.md: -------------------------------------------------------------------------------- 1 | # JLed for the Raspberry Pi Pico 2 | 3 | This examples demonstrates how to use JLed on the Raspberry Pi Pico. The 4 | built-in LED (GPIO 25) and a LED on GPIO 16 will be faded. 5 | 6 | Also a Dockerfile is provided to enable a hassle-free build. 7 | 8 | 9 | 10 | * [Building the demo](#building-the-demo) 11 | * [Docker build](#docker-build) 12 | * [Local build](#local-build) 13 | * [Deploy the sketch to the Raspberry Pi Pico](#deploy-the-sketch-to-the-raspberry-pi-pico) 14 | * [Author](#author) 15 | 16 | 17 | ## Building the demo 18 | 19 | You have two options to build the demo. Either use a docker image (recommended), 20 | or setup everything yourself (consult Pico Quickstart Guide). 21 | 22 | ### Docker build 23 | 24 | A [Dockerfile](Dockerfile) and a [build-script](build.sh) are provided to 25 | enable a hassle-free build of the demo. The script will first build a 26 | docker-image containing the build environment, then build the example. 27 | 28 | * run `./build.sh docker-image` to build the docker image with the build 29 | environment (compilers, pico SDK etc.) - only needed to run once (as long 30 | as the Dockerfile is not changed). 31 | * run `./build.sh compile` to compile the example. The resulting `pico_demo.uf2` 32 | file to be uploaded will be found in this directory afterwards. 33 | * run `./build.sh clean` to clean up all files created during a build. 34 | 35 | ### Local build 36 | 37 | You need the [pico-sdk](https://github.com/raspberrypi/pico-sdk) and 38 | necessary tools to compile everything installed. 39 | 40 | The `PICO_SDK_PATH` environment variable must point to the installation 41 | directory of the SDK. 42 | 43 | To compile the demo sketch, run `cmake . && make`. The resulting 44 | `pico_demo.uf2` file to be uploaded will be found in this directory afterwards. 45 | 46 | ## Deploy the sketch to the Raspberry Pi Pico 47 | 48 | To deploy the demo sketch `pico_demo.uf2`, press and hold `BOOTSEL` on the Pico 49 | and connect the Pico to your PC. The Pico will now be mounted as an external 50 | drive. Copy the file `pico_demo.uf2` to the mount point. The sketch should now 51 | start automatically. 52 | 53 | ## Author 54 | 55 | (c) Copyright 2021 by Jan Delgado, License: MIT 56 | 57 | -------------------------------------------------------------------------------- /examples/raspi_pico/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # build the raspberry pi pico JLed example using a docker container. 3 | # Run "./build.sh docker-image" first to build the docker-image, 4 | # then run "./build.sh compile" to compile the example. 5 | # 6 | # Jan Delgado 02/2021 7 | set -eou pipefail 8 | 9 | usage() { 10 | echo "$0: " 11 | } 12 | 13 | build_image() { 14 | docker build \ 15 | --build-arg="TZ=$(timedatectl show -p Timezone --value)" \ 16 | -t picosdk:latest . 17 | } 18 | 19 | run_cmd() { 20 | docker run -ti --rm \ 21 | --user="$(id -u):$(id -g)" \ 22 | -v "$(pwd)/../..:/src:z" \ 23 | picosdk:latest "$@" 24 | } 25 | 26 | main() { 27 | case $action in 28 | docker-image) build_image ;; 29 | compile) 30 | run_cmd sh -c "cd /src/examples/raspi_pico && cmake . && make" 31 | local -r line=$(printf '=%.0s' {1..75}) 32 | echo "$line" 33 | echo "BUILD SUCCESSFUL." 34 | echo "Now upload the file pico_demo.uf2 to your Pico manually." 35 | echo "$line" 36 | ;; 37 | shell) run_cmd bash ;; 38 | clean) git clean -d -x -f ;; 39 | *) usage ;; 40 | esac 41 | } 42 | 43 | action=${1:-""} 44 | main action 45 | 46 | -------------------------------------------------------------------------------- /examples/raspi_pico/pico_demo.cpp: -------------------------------------------------------------------------------- 1 | // JLed demo for the Raspberry Pi Pico 2 | // Fade the built-in LED (GPIO 25) and a LED on GPIO 16. 3 | // 4 | // Copyright 2021 by Jan Delgado. All rights reserved. 5 | // https://github.com/jandelgado/jled 6 | // 7 | #include "pico/stdlib.h" // NOLINT 8 | #include "jled.h" // NOLINT 9 | 10 | int main() { 11 | auto led1 = JLed(25).FadeOff(2000).DelayAfter(1000).Forever(); 12 | auto led2 = JLed(16).FadeOn(2000).DelayAfter(1000).Forever(); 13 | 14 | while (true) { 15 | led1.Update(); 16 | led2.Update(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/raspi_pico/pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | FetchContent_Declare( 33 | pico_sdk 34 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 35 | GIT_TAG master 36 | ) 37 | if (NOT pico_sdk) 38 | message("Downloading Raspberry Pi Pico SDK") 39 | FetchContent_Populate(pico_sdk) 40 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 41 | endif () 42 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 43 | else () 44 | message(FATAL_ERROR 45 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 46 | ) 47 | endif () 48 | endif () 49 | 50 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 51 | if (NOT EXISTS ${PICO_SDK_PATH}) 52 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 53 | endif () 54 | 55 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 56 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 57 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 58 | endif () 59 | 60 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 61 | 62 | include(${PICO_SDK_INIT_CMAKE_FILE}) 63 | -------------------------------------------------------------------------------- /examples/sequence/sequence.ino: -------------------------------------------------------------------------------- 1 | // JLed multi LED demo. 'Play' multiple LEDs, one after another. 2 | // Copyright 2019 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | constexpr auto LED_PIN = 3; 7 | 8 | JLed leds[] = { 9 | JLed(LED_PIN).Breathe(2000).Repeat(3), 10 | JLed(LED_PIN).Blink(750, 250).Repeat(3), 11 | JLed(LED_PIN).FadeOff(1000).Repeat(3), 12 | JLed(LED_PIN).Blink(500, 500).Repeat(3), 13 | JLed(LED_PIN).FadeOn(1000).Repeat(3), 14 | JLed(LED_PIN).Off() 15 | }; 16 | 17 | JLedSequence sequence(JLedSequence::eMode::SEQUENCE, leds); 18 | 19 | void setup() { } 20 | 21 | void loop() { 22 | sequence.Update(); 23 | delay(1); 24 | } 25 | -------------------------------------------------------------------------------- /examples/simple_on/simple_on.ino: -------------------------------------------------------------------------------- 1 | // JLed delayed turn-on demo. Turns on built-in LED after 2 seconds. 2 | // Copyright 2017 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | JLed led = JLed(LED_BUILTIN).On().DelayBefore(2000); 7 | 8 | void setup() { 9 | } 10 | 11 | void loop() { 12 | led.Update(); 13 | } 14 | -------------------------------------------------------------------------------- /examples/user_func/user_func.ino: -------------------------------------------------------------------------------- 1 | // JLed user provided brightness function demo. 2 | // Copyright 2017 by Jan Delgado. All rights reserved. 3 | // https://github.com/jandelgado/jled 4 | #include 5 | 6 | class UserEffect : public jled::BrightnessEvaluator { 7 | uint8_t Eval(uint32_t t) const override { 8 | // this function returns changes between 0 and 255 and 9 | // vice versa every 250 ms. 10 | return 255*((t/250)%2); 11 | } 12 | uint16_t Period() const override { return 5000; } 13 | }; 14 | 15 | UserEffect userEffect; 16 | auto led = JLed(LED_BUILTIN).UserFunc(&userEffect); 17 | 18 | void setup() {} 19 | 20 | void loop() { 21 | led.Update(); 22 | } 23 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # vim: se noet: 2 | 3 | ####################################### 4 | # Syntax Coloring Map For JLed 5 | ####################################### 6 | 7 | ####################################### 8 | # Datatypes (KEYWORD1) 9 | ####################################### 10 | 11 | JLed KEYWORD1 12 | JLedSequence KEYWORD1 13 | BlinkBrightnessEvaluator KEYWORD1 14 | BrightnessEvaluator KEYWORD1 15 | BreatheBrightnessEvaluator KEYWORD1 16 | CandleBrightnessEvaluator KEYWORD1 17 | ConstantBrightnessEvaluator KEYWORD1 18 | 19 | ####################################### 20 | # Methods and Functions (KEYWORD2) 21 | ####################################### 22 | 23 | On KEYWORD2 24 | Off KEYWORD2 25 | Set KEYWORD2 26 | Blink KEYWORD2 27 | Breathe KEYWORD2 28 | Candle KEYWORD2 29 | FadeOn KEYWORD2 30 | FadeOff KEYWORD2 31 | Repeat KEYWORD2 32 | Forever KEYWORD2 33 | DelayBefore KEYWORD2 34 | DelayAfter KEYWORD2 35 | Forever KEYWORD2 36 | Hal KEYWORD2 37 | IsForever KEYWORD2 38 | IsRunning KEYWORD2 39 | LowActive KEYWORD2 40 | IsLowActive KEYWORD2 41 | Stop KEYWORD2 42 | Update KEYWORD2 43 | UserFunc KEYWORD2 44 | Reset KEYWORD2 45 | MinBrightness KEYWORD2 46 | MaxBrightness KEYWORD2 47 | 48 | ####################################### 49 | # Instances (KEYWORD2) 50 | ####################################### 51 | 52 | ####################################### 53 | # Constants (LITERAL1) 54 | ####################################### 55 | SEQUENCE LITERAL1 56 | PARALLEL LITERAL1 57 | 58 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JLed", 3 | "version": "4.15.0", 4 | "description": "An embedded library to control LEDs", 5 | "license": "MIT", 6 | "frameworks": ["espidf", "arduino", "mbed"], 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/jandelgado/jled" 10 | }, 11 | "authors": { 12 | "name": "Jan Delgado", 13 | "email": "jdelgado[at]gmx.net", 14 | "maintainer": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=JLed 2 | version=4.15.0 3 | author=Jan Delgado 4 | maintainer=Jan Delgado 5 | sentence=An Arduino library to control LEDs 6 | paragraph=JLed uses a non-blocking approach and can control LEDs in simple (on/off) and complex (blinking, breathing) ways in a time-driven manner. 7 | category=Other 8 | url=https://github.com/jandelgado/jled 9 | architectures=* 10 | includes=jled.h 11 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | ; uncomment platform to build for 13 | ;default_envs = nucleo_f401re_mbed 14 | ;default_envs = nucleo_f401re 15 | ;default_envs = nanoatmega328 16 | ;default_envs = nano33ble 17 | ;default_envs = nucleo_f401re 18 | ;default_envs = esp8266 19 | default_envs = esp32 20 | ;default_envs = sparkfun_samd21_dev_usb 21 | ;default_envs = raspberrypi_pico_w 22 | 23 | ; uncomment example to build 24 | src_dir = examples/hello 25 | ;src_dir = examples/morse 26 | ;src_dir = examples/breathe 27 | ;src_dir = examples/candle 28 | ;src_dir = examples/fade_on 29 | ;src_dir = examples/fade_off 30 | ;src_dir = examples/simple_on 31 | ;src_dir = examples/last_brightness 32 | ;src_dir = examples/multiled 33 | ;src_dir = examples/multiled_mbed 34 | ;src_dir = examples/user_func 35 | ;src_dir = examples/sequence 36 | ;src_dir = examples/custom_hal 37 | ;src_dir = examples/pulse 38 | ;src_dir = examples/fade_from_to 39 | 40 | [env] 41 | build_flags = -Isrc 42 | build_src_filter = +<../../src/> +<./> 43 | ;lib_deps = arduinogetstarted/ezButton@1.0.6 44 | 45 | [env:nanoatmega328] 46 | platform = atmelavr 47 | board = nanoatmega328 48 | framework = arduino 49 | 50 | [env:nucleo_f401re_mbed] 51 | platform=ststm32 52 | board = nucleo_f401re 53 | framework = mbed 54 | upload_protocol=stlink 55 | 56 | [env:nucleo_f401re] 57 | platform=ststm32 58 | board = nucleo_f401re 59 | framework = arduino 60 | upload_protocol=stlink 61 | debug_speed=auto 62 | 63 | [env:esp8266] 64 | platform = espressif8266 65 | board = nodemcuv2 66 | framework = arduino 67 | 68 | [env:esp32] 69 | lib_ldf_mode = off 70 | platform = espressif32 71 | board = esp32dev 72 | framework = arduino 73 | 74 | [env:sparkfun_samd21_dev_usb] 75 | platform = atmelsam 76 | framework = arduino 77 | board = sparkfun_samd21_dev_usb 78 | 79 | [env:nano33ble] 80 | platform=https://github.com/platformio/platform-nordicnrf52.git 81 | board = nano33ble 82 | framework = arduino 83 | upload_protocol=stlink 84 | 85 | [env:raspberrypi_pico_w] 86 | build_flags = ${env.build_flags} -D ARDUINO_RASPBERRY_PI_PICO_W 87 | platform = https://github.com/maxgerhardt/platform-raspberrypi.git 88 | board = rpipicow 89 | framework = arduino 90 | board_build.filesystem_size = 0.5m 91 | board_build.core = earlephilhower 92 | upload_protocol = picotool 93 | -------------------------------------------------------------------------------- /src/arduino_hal.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to 6 | // deal in the Software without restriction, including without limitation the 7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | // sell copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | // IN THE SOFTWARE. 21 | // 22 | #ifndef SRC_ARDUINO_HAL_H_ 23 | #define SRC_ARDUINO_HAL_H_ 24 | 25 | #include 26 | 27 | namespace jled { 28 | 29 | class ArduinoHal { 30 | public: 31 | using PinType = uint8_t; 32 | 33 | explicit ArduinoHal(PinType pin) noexcept : pin_(pin) {} 34 | 35 | void analogWrite(uint8_t val) const { 36 | // some platforms, e.g. STM need lazy initialization 37 | if (!setup_) { 38 | ::pinMode(pin_, OUTPUT); 39 | setup_ = true; 40 | } 41 | ::analogWrite(pin_, val); 42 | } 43 | 44 | uint32_t millis() const { return ::millis(); } 45 | 46 | private: 47 | mutable bool setup_ = false; 48 | PinType pin_; 49 | }; 50 | } // namespace jled 51 | #endif // SRC_ARDUINO_HAL_H_ 52 | -------------------------------------------------------------------------------- /src/esp32_hal.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to 6 | // deal in the Software without restriction, including without limitation the 7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | // sell copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | // IN THE SOFTWARE. 21 | // 22 | #ifdef ESP32 23 | #include "esp32_hal.h" // NOLINT 24 | 25 | using jled::Esp32ChanMapper; 26 | using jled::Esp32Hal; 27 | 28 | Esp32ChanMapper Esp32Hal::chanMapper_ = Esp32ChanMapper(); 29 | #endif 30 | -------------------------------------------------------------------------------- /src/esp32_hal.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2022 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | // 4 | // HAL for the ESP32 compatible with Arduino and ESP-IDF framework. Uses 5 | // ESP-IDF SDK under the hood. 6 | // 7 | // Documentation: 8 | // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html 9 | // 10 | // Inspiration from: 11 | // https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-ledc.c 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to 15 | // deal in the Software without restriction, including without limitation the 16 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 17 | // sell copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 29 | // IN THE SOFTWARE. 30 | // 31 | #ifndef SRC_ESP32_HAL_H_ 32 | #define SRC_ESP32_HAL_H_ 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | namespace jled { 39 | 40 | class Esp32ChanMapper { 41 | static constexpr auto kFreeChan = 0xff; 42 | 43 | public: 44 | using PinType = uint8_t; 45 | 46 | static constexpr auto kLedcMaxChan = 16; 47 | 48 | Esp32ChanMapper() { 49 | for (auto i = 0; i < kLedcMaxChan; i++) chanMap_[i] = 0xff; 50 | } 51 | 52 | ledc_channel_t chanForPin(PinType pin) { 53 | // find existing channel for given pin 54 | for (auto i = 0; i < kLedcMaxChan; i++) { 55 | if (chanMap_[i] == pin) return (ledc_channel_t)i; 56 | } 57 | // find and return first free slot 58 | for (auto i = 0; i < kLedcMaxChan; i++) { 59 | if (chanMap_[i] == kFreeChan) { 60 | chanMap_[i] = pin; 61 | return (ledc_channel_t)i; 62 | } 63 | } 64 | // no more free slots, start over 65 | const auto i = nextChan_; 66 | chanMap_[i] = pin; 67 | nextChan_ = (nextChan_ + 1) % kLedcMaxChan; 68 | return (ledc_channel_t)i; 69 | } 70 | 71 | private: 72 | PinType nextChan_ = 0; 73 | PinType chanMap_[kLedcMaxChan]; 74 | }; 75 | 76 | class Esp32Hal { 77 | static constexpr auto kLedcTimerResolution = LEDC_TIMER_8_BIT; 78 | static constexpr auto kLedcSpeedMode = LEDC_LOW_SPEED_MODE; 79 | 80 | public: 81 | using PinType = Esp32ChanMapper::PinType; 82 | 83 | static constexpr auto kAutoSelectChan = -1; 84 | 85 | // construct an ESP32 analog write object connected 86 | // pin gpio pin to connect to 87 | // chan specifies the EPS32 ledc channel to use. If set to 88 | // kAutoSelectChan, 89 | // the next available channel will be used, otherwise the specified 90 | // one. 91 | // freq defines the ledc base frequency to be used (default: 5000 Hz). 92 | // timer is the ledc timer to use (default: LEDC_TIMER_0). When different 93 | // frequencies are used, also different timers must be used. 94 | Esp32Hal(PinType pin, int chan = kAutoSelectChan, uint16_t freq = 5000, 95 | ledc_timer_t timer = LEDC_TIMER_0) noexcept { 96 | chan_ = (chan == kAutoSelectChan) 97 | ? Esp32Hal::chanMapper_.chanForPin(pin) 98 | : (ledc_channel_t)chan; 99 | 100 | ledc_timer_config_t ledc_timer{}; 101 | ledc_timer.speed_mode = kLedcSpeedMode; 102 | ledc_timer.duty_resolution = kLedcTimerResolution; 103 | ledc_timer.timer_num = timer; 104 | ledc_timer.freq_hz = freq; 105 | #if ESP_IDF_VERSION_MAJOR > 3 106 | ledc_timer.clk_cfg = LEDC_AUTO_CLK; 107 | #endif 108 | ledc_timer_config(&ledc_timer); 109 | 110 | ledc_channel_t channel = (ledc_channel_t)(chan_ % LEDC_CHANNEL_MAX); 111 | ledc_channel_config_t ledc_channel{}; 112 | ledc_channel.gpio_num = pin; 113 | ledc_channel.speed_mode = kLedcSpeedMode; 114 | ledc_channel.channel = channel; 115 | ledc_channel.intr_type = LEDC_INTR_DISABLE; 116 | ledc_channel.timer_sel = timer; 117 | ledc_channel.duty = 0; 118 | ledc_channel.hpoint = 0; 119 | #if ESP_IDF_VERSION_MAJOR > 4 120 | ledc_channel.flags.output_invert = 0; 121 | #endif 122 | ledc_channel_config(&ledc_channel); 123 | } 124 | 125 | void analogWrite(uint8_t duty) const { 126 | // Fixing if all bits in resolution is set = LEDC FULL ON 127 | const uint32_t _duty = (duty == (1 << kLedcTimerResolution) - 1) 128 | ? 1 << kLedcTimerResolution 129 | : duty; 130 | 131 | ledc_set_duty(kLedcSpeedMode, chan_, _duty); 132 | ledc_update_duty(kLedcSpeedMode, chan_); 133 | } 134 | 135 | uint32_t millis() const { 136 | return static_cast(esp_timer_get_time() / 1000ULL); 137 | } 138 | 139 | PinType chan() const { return chan_; } 140 | 141 | private: 142 | static Esp32ChanMapper chanMapper_; 143 | ledc_channel_t chan_; 144 | }; 145 | } // namespace jled 146 | #endif // SRC_ESP32_HAL_H_ 147 | -------------------------------------------------------------------------------- /src/esp8266_hal.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to 6 | // deal in the Software without restriction, including without limitation the 7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | // sell copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | // IN THE SOFTWARE. 21 | // 22 | #ifndef SRC_ESP8266_HAL_H_ 23 | #define SRC_ESP8266_HAL_H_ 24 | 25 | #include 26 | 27 | namespace jled { 28 | 29 | class Esp8266Hal { 30 | public: 31 | using PinType = uint8_t; 32 | 33 | explicit Esp8266Hal(PinType pin) noexcept : pin_(pin) { 34 | ::pinMode(pin_, OUTPUT); 35 | } 36 | void analogWrite(uint8_t val) const { 37 | // ESP8266 uses 10bit PWM range per default, scale value up 38 | ::analogWrite(pin_, Esp8266Hal::ScaleTo10Bit(val)); 39 | } 40 | uint32_t millis() const { return ::millis(); } 41 | 42 | protected: 43 | // scale an 8bit value to 10bit: 0 -> 0, ..., 255 -> 1023, 44 | // preserving min/max relationships in both ranges. 45 | static uint16_t ScaleTo10Bit(uint8_t x) { 46 | return (x == 0) ? 0 : (x << 2) + 3; 47 | } 48 | 49 | private: 50 | PinType pin_; 51 | }; 52 | } // namespace jled 53 | #endif // SRC_ESP8266_HAL_H_ 54 | -------------------------------------------------------------------------------- /src/jled.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to 6 | // deal in the Software without restriction, including without limitation the 7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | // sell copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | // IN THE SOFTWARE. 21 | // 22 | #ifndef SRC_JLED_H_ 23 | #define SRC_JLED_H_ 24 | 25 | // JLed - non-blocking LED abstraction library. 26 | // 27 | // Example Arduino sketch: 28 | // JLed led = JLed(LED_BUILTIN).Blink(500, 500).Repeat(10).DelayBefore(1000); 29 | // 30 | // void setup() {} 31 | // 32 | // void loop() { 33 | // led.Update(); 34 | // } 35 | 36 | #include "jled_base.h" // NOLINT 37 | 38 | #ifdef PICO_SDK_VERSION_MAJOR 39 | #include "pico_hal.h" // NOLINT 40 | namespace jled {using JLedHalType = PicoHal;} 41 | #elif defined(__MBED__) && !defined(ARDUINO_API_VERSION) 42 | #include "mbed_hal.h" // NOLINT 43 | namespace jled {using JLedHalType = MbedHal;} 44 | #elif defined(ESP32) 45 | #include "esp32_hal.h" // NOLINT 46 | namespace jled {using JLedHalType = Esp32Hal;} 47 | #elif defined(ESP8266) 48 | #include "esp8266_hal.h" // NOLINT 49 | namespace jled {using JLedHalType = Esp8266Hal;} 50 | #else 51 | #include "arduino_hal.h" // NOLINT 52 | namespace jled {using JLedHalType = ArduinoHal;} 53 | #endif 54 | 55 | namespace jled { 56 | class JLed : public TJLed { 57 | using TJLed::TJLed; 58 | }; 59 | 60 | // a group of JLed objects which can be controlled simultanously 61 | class JLedSequence : public TJLedSequence { 62 | using TJLedSequence::TJLedSequence; 63 | }; 64 | 65 | }; // namespace jled 66 | 67 | using JLed = jled::JLed; 68 | using JLedSequence = jled::JLedSequence; 69 | 70 | #endif // SRC_JLED_H_ 71 | -------------------------------------------------------------------------------- /src/jled_base.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to 6 | // deal in the Software without restriction, including without limitation the 7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | // sell copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | // IN THE SOFTWARE. 21 | // 22 | #include "jled_base.h" // NOLINT 23 | 24 | namespace jled { 25 | 26 | // pre-calculated fade-on function. This table samples the function 27 | // y(x) = exp(sin((t - period / 2.) * PI / period)) - 0.36787944) 28 | // * 108. 29 | // at x={0,32,...,256}. In FadeOnFunc() we us linear interpolation to 30 | // approximate the original function (so we do not need fp-ops). 31 | // fade-off and breath functions are all derived from fade-on, see 32 | // below. 33 | static constexpr uint8_t kFadeOnTable[] = {0, 3, 13, 33, 68, 34 | 118, 179, 232, 255}; // NOLINT 35 | 36 | // https://www.wolframalpha.com/input/?i=plot+(exp(sin((x-100%2F2.)*PI%2F100))-0.36787944)*108.0++x%3D0+to+100 37 | // The fade-on func is an approximation of 38 | // y(x) = exp(sin((t-period/2.) * PI / period)) - 0.36787944) * 108.) 39 | uint8_t fadeon_func(uint32_t t, uint16_t period) { 40 | if (t + 1 >= period) return 255; // kFullBrightness; 41 | 42 | // approximate by linear interpolation. 43 | // scale t according to period to 0..255 44 | t = ((t << 8) / period) & 0xff; 45 | const auto i = (t >> 5); // -> i will be in range 0 .. 7 46 | const auto y0 = kFadeOnTable[i]; 47 | const auto y1 = kFadeOnTable[i + 1]; 48 | const auto x0 = i << 5; // *32 49 | 50 | // y(t) = mt+b, with m = dy/dx = (y1-y0)/32 = (y1-y0) >> 5 51 | return (((t - x0) * (y1 - y0)) >> 5) + y0; 52 | } 53 | 54 | static uint32_t rand_ = 0; 55 | 56 | void rand_seed(uint32_t seed) { rand_ = seed; } 57 | 58 | uint8_t rand8() { 59 | if (rand_ & 1) { 60 | rand_ = (rand_ >> 1); 61 | } else { 62 | rand_ = (rand_ >> 1) ^ 0x7FFFF159; 63 | } 64 | 65 | return (uint8_t)rand_; 66 | } 67 | 68 | // scale a byte (val) by a byte (factor). scale8 has the following properties: 69 | // scale8(0, f) == 0 for all f 70 | // scale8(x, 255) == x for all x 71 | uint8_t scale8(uint8_t val, uint8_t factor) { 72 | return (static_cast(val)*static_cast(factor))/255; 73 | } 74 | 75 | // interpolate a byte (val) to the interval [a,b]. 76 | uint8_t lerp8by8(uint8_t val, uint8_t a, uint8_t b) { 77 | if (a == 0 && b == 255) return val; // optimize for most common case 78 | const uint8_t delta = b - a; 79 | return a + scale8(val, delta); 80 | } 81 | 82 | // the inverse of lerp8by8: invlerp8by8(lerp8by8(x, a, b,), a, b,) = x 83 | uint8_t invlerp8by8(uint8_t val, uint8_t a, uint8_t b) { 84 | const uint16_t delta = b - a; 85 | if (delta == 0) return 0; 86 | return (static_cast(val-a)*255)/(delta); 87 | } 88 | 89 | }; // namespace jled 90 | -------------------------------------------------------------------------------- /src/jled_base.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2021 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to 6 | // deal in the Software without restriction, including without limitation the 7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | // sell copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | // IN THE SOFTWARE. 21 | // 22 | #ifndef SRC_JLED_BASE_H_ 23 | #define SRC_JLED_BASE_H_ 24 | 25 | #include // types, e.g. uint8_t 26 | #include // size_t 27 | 28 | // JLed - non-blocking LED abstraction library. 29 | // 30 | // Example Arduino sketch: 31 | // auto led = JLed(LED_BUILTIN).Blink(500, 500).Repeat(10).DelayBefore(1000); 32 | // 33 | // void setup() {} 34 | // 35 | // void loop() { 36 | // led.Update(); 37 | // } 38 | 39 | namespace jled { 40 | 41 | static constexpr uint8_t kFullBrightness = 255; 42 | static constexpr uint8_t kZeroBrightness = 0; 43 | 44 | uint8_t fadeon_func(uint32_t t, uint16_t period); 45 | uint8_t rand8(); 46 | void rand_seed(uint32_t s); 47 | uint8_t scale8(uint8_t val, uint8_t f); 48 | uint8_t lerp8by8(uint8_t val, uint8_t a, uint8_t b); 49 | uint8_t invlerp8by8(uint8_t val, uint8_t a, uint8_t b); 50 | 51 | template 52 | static constexpr T __max(T a, T b) { 53 | return (a > b) ? a : b; 54 | } 55 | 56 | // a function f(t,period,param) that calculates the LEDs brightness for a given 57 | // point in time and the given period. param is an optionally user provided 58 | // parameter. t will always be in range [0..period-1]. 59 | // f(period-1,period,param) will be called last to calculate the final state of 60 | // the LED. 61 | class BrightnessEvaluator { 62 | public: 63 | virtual uint16_t Period() const = 0; 64 | virtual uint8_t Eval(uint32_t t) const = 0; 65 | }; 66 | 67 | class CloneableBrightnessEvaluator : public BrightnessEvaluator { 68 | public: 69 | virtual BrightnessEvaluator* clone(void* ptr) const = 0; 70 | static void* operator new(size_t, void* ptr) { return ptr; } 71 | static void operator delete(void*) {} 72 | }; 73 | 74 | class ConstantBrightnessEvaluator : public CloneableBrightnessEvaluator { 75 | uint8_t val_; 76 | uint16_t duration_; 77 | 78 | public: 79 | ConstantBrightnessEvaluator() = delete; 80 | explicit ConstantBrightnessEvaluator(uint8_t val, uint16_t duration = 1) 81 | : val_(val), duration_(duration) {} 82 | BrightnessEvaluator* clone(void* ptr) const override { 83 | return new (ptr) ConstantBrightnessEvaluator(*this); 84 | } 85 | uint16_t Period() const override { return duration_; } 86 | uint8_t Eval(uint32_t) const override { return val_; } 87 | }; 88 | 89 | // BlinkBrightnessEvaluator does one on-off cycle in the specified period 90 | class BlinkBrightnessEvaluator : public CloneableBrightnessEvaluator { 91 | uint16_t duration_on_, duration_off_; 92 | 93 | public: 94 | BlinkBrightnessEvaluator() = delete; 95 | BlinkBrightnessEvaluator(uint16_t duration_on, uint16_t duration_off) 96 | : duration_on_(duration_on), duration_off_(duration_off) {} 97 | BrightnessEvaluator* clone(void* ptr) const override { 98 | return new (ptr) BlinkBrightnessEvaluator(*this); 99 | } 100 | uint16_t Period() const override { return duration_on_ + duration_off_; } 101 | uint8_t Eval(uint32_t t) const override { 102 | return (t < duration_on_) ? kFullBrightness : kZeroBrightness; 103 | } 104 | }; 105 | 106 | // The breathe func is composed by fade-on, on and fade-off phases. For fading 107 | // we approximate the following function: 108 | // y(x) = exp(sin((t-period/4.) * 2. * PI / period)) - 0.36787944) * 108.) 109 | // idea see: 110 | // http://sean.voisen.org/blog/2011/10/breathing-led-with-arduino/ 111 | // But we do it with integers only. 112 | class BreatheBrightnessEvaluator : public CloneableBrightnessEvaluator { 113 | uint16_t duration_fade_on_; 114 | uint16_t duration_on_; 115 | uint16_t duration_fade_off_; 116 | uint8_t from_; 117 | uint8_t to_; 118 | 119 | public: 120 | BreatheBrightnessEvaluator() = delete; 121 | explicit BreatheBrightnessEvaluator(uint16_t duration_fade_on, 122 | uint16_t duration_on, 123 | uint16_t duration_fade_off, 124 | uint8_t from = 0, 125 | uint8_t to = kFullBrightness) 126 | : duration_fade_on_(duration_fade_on), 127 | duration_on_(duration_on), 128 | duration_fade_off_(duration_fade_off), 129 | from_(from), 130 | to_(to) {} 131 | BrightnessEvaluator* clone(void* ptr) const override { 132 | return new (ptr) BreatheBrightnessEvaluator(*this); 133 | } 134 | uint16_t Period() const override { 135 | return duration_fade_on_ + duration_on_ + duration_fade_off_; 136 | } 137 | uint8_t Eval(uint32_t t) const override { 138 | uint8_t val = 0; 139 | if (t < duration_fade_on_) 140 | val = fadeon_func(t, duration_fade_on_); 141 | else if (t < duration_fade_on_ + duration_on_) 142 | val = kFullBrightness; 143 | else 144 | val = fadeon_func(Period() - t, duration_fade_off_); 145 | return lerp8by8(val, from_, to_); 146 | } 147 | 148 | uint16_t DurationFadeOn() const { return duration_fade_on_; } 149 | uint16_t DurationFadeOff() const { return duration_fade_off_; } 150 | uint16_t DurationOn() const { return duration_on_; } 151 | uint8_t From() const { return from_; } 152 | uint8_t To() const { return to_; } 153 | }; 154 | 155 | class CandleBrightnessEvaluator : public CloneableBrightnessEvaluator { 156 | uint8_t speed_; 157 | uint8_t jitter_; 158 | uint16_t period_; 159 | mutable uint8_t last_ = 5; 160 | mutable uint32_t last_t_ = 0; 161 | 162 | public: 163 | CandleBrightnessEvaluator() = delete; 164 | 165 | // speed - speed of effect (0..15). 0 fastest. Each increment by 1 166 | // halfes the speed. 167 | // jitter - amount of jittering to apply. 0 - no jitter, 15 - candle, 168 | // 64 - fire, 255 - storm 169 | CandleBrightnessEvaluator(uint8_t speed, uint8_t jitter, uint16_t period) 170 | : speed_(speed), jitter_(jitter), period_(period) {} 171 | 172 | BrightnessEvaluator* clone(void* ptr) const override { 173 | return new (ptr) CandleBrightnessEvaluator(*this); 174 | } 175 | 176 | uint16_t Period() const override { return period_; } 177 | uint8_t Eval(uint32_t t) const override { 178 | // idea from 179 | // https://cpldcpu.wordpress.com/2013/12/08/hacking-a-candleflicker-led/ 180 | // TODO(jd) finetune values 181 | static constexpr uint8_t kCandleTable[] = { 182 | 5, 10, 20, 30, 50, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 255}; 183 | if ((t >> speed_) == last_t_) return last_; 184 | last_t_ = (t >> speed_); 185 | const auto rnd = rand8() & 255; 186 | last_ = (rnd >= jitter_) ? 255 : (50 + kCandleTable[rnd & 0xf]); 187 | return last_; 188 | } 189 | }; 190 | 191 | template 192 | class TJLed { 193 | protected: 194 | // pointer to a (user defined) brightness evaluator. 195 | BrightnessEvaluator* brightness_eval_ = nullptr; 196 | // Hardware abstraction giving access to the MCU 197 | HalType hal_; 198 | 199 | // Evaluate effect(t) and scale to be within [minBrightness, maxBrightness] 200 | // assumes brigthness_eval_ is set as it is not checked here. 201 | uint8_t Eval(uint32_t t) const { return brightness_eval_->Eval(t); } 202 | 203 | // Write val out to the "hardware", inverting signal when active-low is set. 204 | void Write(uint8_t val) { 205 | hal_.analogWrite(IsLowActive() ? kFullBrightness - val : val); 206 | } 207 | 208 | public: 209 | TJLed() = delete; 210 | explicit TJLed(const HalType& hal) 211 | : hal_{hal}, 212 | state_{ST_INIT}, 213 | bLowActive_{false}, 214 | minBrightness_{0}, 215 | maxBrightness_{255} {} 216 | 217 | explicit TJLed(typename HalType::PinType pin) : TJLed{HalType{pin}} {} 218 | 219 | TJLed(const TJLed& rLed) : hal_{rLed.hal_} { *this = rLed; } 220 | 221 | B& operator=(const TJLed& rLed) { 222 | state_ = rLed.state_; 223 | bLowActive_ = rLed.bLowActive_; 224 | minBrightness_ = rLed.minBrightness_; 225 | maxBrightness_ = rLed.maxBrightness_; 226 | num_repetitions_ = rLed.num_repetitions_; 227 | last_update_time_ = rLed.last_update_time_; 228 | delay_before_ = rLed.delay_before_; 229 | delay_after_ = rLed.delay_after_; 230 | time_start_ = rLed.time_start_; 231 | hal_ = rLed.hal_; 232 | 233 | if (rLed.brightness_eval_ != 234 | reinterpret_cast( 235 | rLed.brightness_eval_buf_)) { 236 | // nullptr or points to (external) user provided evaluator 237 | brightness_eval_ = rLed.brightness_eval_; 238 | } else { 239 | brightness_eval_ = 240 | (reinterpret_cast( 241 | rLed.brightness_eval_)) 242 | ->clone(brightness_eval_buf_); 243 | } 244 | return static_cast(*this); 245 | } 246 | 247 | HalType& Hal() { return hal_; } 248 | 249 | // Set physical LED polarity to be low active. This inverts every 250 | // signal physically output to a pin. 251 | B& LowActive() { 252 | bLowActive_ = true; 253 | return static_cast(*this); 254 | } 255 | 256 | bool IsLowActive() const { return bLowActive_; } 257 | 258 | // turn LED on 259 | B& On(uint16_t duration = 1) { return Set(kFullBrightness, duration); } 260 | 261 | // turn LED off 262 | B& Off(uint16_t duration = 1) { return Set(kZeroBrightness, duration); } 263 | 264 | // Sets LED to given brightness. As for every effect, a duration can be 265 | // specified. Update() will return false after the duration elapsed. 266 | B& Set(uint8_t brightness, uint16_t duration = 1) { 267 | // note: we use placement new and therefore not need to keep track of 268 | // mem allocated 269 | return SetBrightnessEval( 270 | new (brightness_eval_buf_) 271 | ConstantBrightnessEvaluator(brightness, duration)); 272 | } 273 | 274 | // Fade LED on 275 | B& FadeOn(uint16_t duration, uint8_t from = 0, 276 | uint8_t to = kFullBrightness) { 277 | return SetBrightnessEval( 278 | new (brightness_eval_buf_) 279 | BreatheBrightnessEvaluator(duration, 0, 0, from, to)); 280 | } 281 | 282 | // Fade LED off - acutally is just inverted version of FadeOn() 283 | B& FadeOff(uint16_t duration, uint8_t from = kFullBrightness, 284 | uint8_t to = 0) { 285 | return SetBrightnessEval( 286 | new (brightness_eval_buf_) 287 | BreatheBrightnessEvaluator(0, 0, duration, to, from)); 288 | } 289 | 290 | // Fade from "from" to "to" with period "duration". Sets up the breathe 291 | // effect with the proper parameters and sets Min/Max brightness to reflect 292 | // levels specified by "from" and "to". 293 | B& Fade(uint8_t from, uint8_t to, uint16_t duration) { 294 | if (from < to) { 295 | return FadeOn(duration, from, to); 296 | } else { 297 | return FadeOff(duration, from, to); 298 | } 299 | } 300 | 301 | // Set effect to Breathe, with the given period time in ms. 302 | B& Breathe(uint16_t period) { return Breathe(period / 2, 0, period / 2); } 303 | 304 | // Set effect to Breathe, with the given fade on-, on- and fade off- 305 | // duration values. 306 | B& Breathe(uint16_t duration_fade_on, uint16_t duration_on, 307 | uint16_t duration_fade_off) { 308 | return SetBrightnessEval( 309 | new (brightness_eval_buf_) BreatheBrightnessEvaluator( 310 | duration_fade_on, duration_on, duration_fade_off)); 311 | } 312 | 313 | // Set effect to Blink, with the given on- and off- duration values. 314 | B& Blink(uint16_t duration_on, uint16_t duration_off) { 315 | return SetBrightnessEval( 316 | new (brightness_eval_buf_) 317 | BlinkBrightnessEvaluator(duration_on, duration_off)); 318 | } 319 | 320 | // Set effect to Candle light simulation 321 | B& Candle(uint8_t speed = 6, uint8_t jitter = 15, 322 | uint16_t period = 0xffff) { 323 | return SetBrightnessEval( 324 | new (brightness_eval_buf_) 325 | CandleBrightnessEvaluator(speed, jitter, period)); 326 | } 327 | 328 | // Use a user provided brightness evaluator. 329 | B& UserFunc(BrightnessEvaluator* user_eval) { 330 | return SetBrightnessEval(user_eval); 331 | } 332 | 333 | // set number of repetitions for effect. 334 | B& Repeat(uint16_t num_repetitions) { 335 | num_repetitions_ = num_repetitions; 336 | return static_cast(*this); 337 | } 338 | 339 | // repeat Forever 340 | B& Forever() { return Repeat(kRepeatForever); } 341 | bool IsForever() const { return num_repetitions_ == kRepeatForever; } 342 | 343 | // Set amount of time to initially wait before effect starts. Time is 344 | // relative to first call of Update() method and specified in ms. 345 | B& DelayBefore(uint16_t delay_before) { 346 | delay_before_ = delay_before; 347 | return static_cast(*this); 348 | } 349 | 350 | // Set amount of time to wait in ms after each iteration. 351 | B& DelayAfter(uint16_t delay_after) { 352 | delay_after_ = delay_after; 353 | return static_cast(*this); 354 | } 355 | 356 | // Stop current effect and turn LED immeadiately off. Further calls to 357 | // Update() will have no effect. 358 | enum eStopMode { TO_MIN_BRIGHTNESS = 0, FULL_OFF, KEEP_CURRENT }; 359 | B& Stop(eStopMode mode = eStopMode::TO_MIN_BRIGHTNESS) { 360 | if (mode != eStopMode::KEEP_CURRENT) { 361 | Write(mode == eStopMode::FULL_OFF ? kZeroBrightness 362 | : minBrightness_); 363 | } 364 | state_ = ST_STOPPED; 365 | return static_cast(*this); 366 | } 367 | 368 | bool IsRunning() const { return state_ != ST_STOPPED; } 369 | 370 | // Reset to inital state 371 | B& Reset() { 372 | time_start_ = 0; 373 | last_update_time_ = 0; 374 | state_ = ST_INIT; 375 | return static_cast(*this); 376 | } 377 | 378 | // Sets the minimum brightness level (ranging from 0 to 255). 379 | B& MinBrightness(uint8_t level) { 380 | minBrightness_ = level; 381 | return static_cast(*this); 382 | } 383 | 384 | // Returns current minimum brightness level. 385 | uint8_t MinBrightness() const { return minBrightness_; } 386 | 387 | // Sets the minimum brightness level (ranging from 0 to 255). 388 | B& MaxBrightness(uint8_t level) { 389 | maxBrightness_ = level; 390 | return static_cast(*this); 391 | } 392 | 393 | // Returns current maximum brightness level. 394 | uint8_t MaxBrightness() const { return maxBrightness_; } 395 | 396 | // update brightness of LED using the given brightness evaluator and the 397 | // current time. If the optional pLast pointer is set, then the actual 398 | // brightness value (if an update happened), will be returned through 399 | // the pointer. The value returned will be the calculated value after 400 | // min- and max-brightness scaling was applied, which is the value that 401 | // is written to the output. 402 | // 403 | // (brightness) ________________ 404 | // on 255 | ¸-' 405 | // | ¸-' 406 | // | ¸-' 407 | // off 0 |________________¸-' 408 | // |<-delay before->|<--period-->|<-delay after-> (time) 409 | // | func(t) | 410 | // |<- num_repetitions times -> 411 | bool Update(int16_t* pLast = nullptr) { 412 | return Update(hal_.millis(), pLast); 413 | } 414 | 415 | bool Update(uint32_t t, int16_t* pLast = nullptr) { 416 | if (state_ == ST_STOPPED || !brightness_eval_) return false; 417 | 418 | if (state_ == ST_INIT) { 419 | time_start_ = t + delay_before_; 420 | state_ = ST_RUNNING; 421 | } else { 422 | // no need to process updates twice during one time tick. 423 | if (!timeChangedSinceLastUpdate(t)) return true; 424 | } 425 | 426 | trackLastUpdateTime(t); 427 | 428 | if (static_cast(t - time_start_) < 0) return true; 429 | 430 | auto writeCur = [this](uint32_t t, int16_t* p) { 431 | const auto val = lerp8by8(Eval(t), minBrightness_, maxBrightness_); 432 | if (p) { 433 | *p = val; 434 | } 435 | Write(val); 436 | }; 437 | 438 | // t cycles in range [0..period+delay_after-1] 439 | const auto period = brightness_eval_->Period(); 440 | 441 | if (!IsForever()) { 442 | const auto time_end = time_start_ + 443 | static_cast(period + delay_after_) * 444 | num_repetitions_ - 445 | 1; 446 | 447 | if (static_cast(t - time_end) >= 0) { 448 | // make sure final value of t = (period-1) is set 449 | state_ = ST_STOPPED; 450 | writeCur(period - 1, pLast); 451 | return false; 452 | } 453 | } 454 | 455 | t = (t - time_start_) % (period + delay_after_); 456 | if (t < period) { 457 | state_ = ST_RUNNING; 458 | writeCur(t, pLast); 459 | } else { 460 | if (state_ == ST_RUNNING) { 461 | // when in delay after phase, just call Write() 462 | // once at the beginning. 463 | state_ = ST_IN_DELAY_AFTER_PHASE; 464 | writeCur(period - 1, pLast); 465 | } 466 | } 467 | return true; 468 | } 469 | 470 | protected: 471 | // test if time stored in last_update_time_ differs from provided timestamp. 472 | bool inline timeChangedSinceLastUpdate(uint32_t now) { 473 | return (now & 255) != last_update_time_; 474 | } 475 | 476 | void trackLastUpdateTime(uint32_t t) { last_update_time_ = (t & 255); } 477 | 478 | B& SetBrightnessEval(BrightnessEvaluator* be) { 479 | brightness_eval_ = be; 480 | // start over after the brightness evaluator changed 481 | return Reset(); 482 | } 483 | 484 | public: 485 | // Number of bits used to control brightness with Min/MaxBrightness(). 486 | static constexpr uint8_t kBitsBrightness = 8; 487 | static constexpr uint8_t kBrightnessStep = 1; 488 | 489 | private: 490 | static constexpr uint8_t ST_STOPPED = 0; 491 | static constexpr uint8_t ST_INIT = 1; 492 | static constexpr uint8_t ST_RUNNING = 2; 493 | static constexpr uint8_t ST_IN_DELAY_AFTER_PHASE = 3; 494 | 495 | uint8_t state_ : 2; 496 | uint8_t bLowActive_ : 1; 497 | uint8_t minBrightness_; 498 | uint8_t maxBrightness_; 499 | 500 | // this is where the BrightnessEvaluator object will be stored using 501 | // placment new. Set MAX_SIZE to class occupying most memory 502 | static constexpr auto MAX_SIZE = 503 | __max(sizeof(CandleBrightnessEvaluator), 504 | __max(sizeof(BreatheBrightnessEvaluator), 505 | __max(sizeof(ConstantBrightnessEvaluator), // NOLINT 506 | sizeof(BlinkBrightnessEvaluator)))); 507 | alignas(alignof( 508 | CloneableBrightnessEvaluator)) char brightness_eval_buf_[MAX_SIZE]; 509 | 510 | static constexpr uint16_t kRepeatForever = 65535; 511 | uint16_t num_repetitions_ = 1; 512 | 513 | // We store the timestamp the effect was last updated to avoid multiple 514 | // updates when called during the same time tick. Only the lower 8 bits of 515 | // the timestamp are used (which saves us 3 bytes of memory per JLed 516 | // instance), resulting in limited accuracy, which may lead to false 517 | // negatives if Update() is not called for a longer time (i.e. > 255ms), 518 | // which should not be a problem at all. 519 | uint8_t last_update_time_ = 0; 520 | uint32_t time_start_ = 0; 521 | 522 | uint16_t delay_before_ = 0; // delay before the first effect starts 523 | uint16_t delay_after_ = 0; // delay after each repetition 524 | }; 525 | 526 | template 527 | T* ptr(T& obj) { // NOLINT 528 | return &obj; 529 | } 530 | template 531 | T* ptr(T* obj) { 532 | return obj; 533 | } 534 | 535 | // a group of JLed objects which can be controlled simultanously, in parallel 536 | // or sequentially. 537 | template 538 | class TJLedSequence { 539 | protected: 540 | // update all leds parallel. Returns true while any of the JLeds is 541 | // active, else false 542 | bool UpdateParallel() { 543 | auto result = false; 544 | uint32_t t = ptr(leds_[0])->Hal().millis(); 545 | for (auto i = 0u; i < n_; i++) { 546 | result |= ptr(leds_[i])->Update(t); 547 | } 548 | return result; 549 | } 550 | 551 | // update all leds sequentially. Returns true while any of the JLeds is 552 | // active, else false 553 | bool UpdateSequentially() { 554 | if (!ptr(leds_[cur_])->Update()) { 555 | return ++cur_ < n_; 556 | } 557 | return true; 558 | } 559 | 560 | void ResetLeds() { 561 | for (auto i = 0u; i < n_; i++) { 562 | ptr(leds_[i])->Reset(); 563 | } 564 | } 565 | 566 | public: 567 | enum eMode { SEQUENCE, PARALLEL }; 568 | TJLedSequence() = delete; 569 | 570 | template 571 | TJLedSequence(eMode mode, L (&leds)[N]) : TJLedSequence(mode, leds, N) {} 572 | 573 | TJLedSequence(eMode mode, L* leds, size_t n) 574 | : mode_{mode}, leds_{leds}, cur_{0}, n_{n} {} 575 | 576 | bool Update() { 577 | if (!is_running_ || n_ < 1) { 578 | return false; 579 | } 580 | 581 | const auto led_running = (mode_ == eMode::PARALLEL) 582 | ? UpdateParallel() 583 | : UpdateSequentially(); 584 | if (led_running) { 585 | return true; 586 | } 587 | 588 | // start next iteration of sequence 589 | cur_ = 0; 590 | ResetLeds(); 591 | 592 | is_running_ = ++iteration_ < num_repetitions_ || 593 | num_repetitions_ == kRepeatForever; 594 | 595 | return is_running_; 596 | } 597 | 598 | void Reset() { 599 | ResetLeds(); 600 | cur_ = 0; 601 | iteration_ = 0; 602 | is_running_ = true; 603 | } 604 | 605 | void Stop() { 606 | is_running_ = false; 607 | for (auto i = 0u; i < n_; i++) { 608 | ptr(leds_[i])->Stop(); 609 | } 610 | } 611 | 612 | // set number of repetitions for the sequence 613 | B& Repeat(uint16_t num_repetitions) { 614 | num_repetitions_ = num_repetitions; 615 | return static_cast(*this); 616 | } 617 | 618 | // repeat Forever 619 | B& Forever() { return Repeat(kRepeatForever); } 620 | bool IsForever() const { return num_repetitions_ == kRepeatForever; } 621 | 622 | private: 623 | eMode mode_; 624 | L* leds_; 625 | size_t cur_; 626 | size_t n_; 627 | static constexpr uint16_t kRepeatForever = 65535; 628 | uint16_t num_repetitions_ = 1; 629 | uint16_t iteration_ = 0; 630 | bool is_running_ = true; 631 | }; 632 | 633 | }; // namespace jled 634 | #endif // SRC_JLED_BASE_H_ 635 | -------------------------------------------------------------------------------- /src/mbed_hal.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2020 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to 6 | // deal in the Software without restriction, including without limitation the 7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | // sell copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | // IN THE SOFTWARE. 21 | // 22 | #ifndef SRC_MBED_HAL_H_ 23 | #define SRC_MBED_HAL_H_ 24 | 25 | #ifdef __MBED__ 26 | 27 | #include 28 | 29 | namespace jled { 30 | 31 | class MbedHal { 32 | public: 33 | using PinType = ::PinName; 34 | 35 | explicit MbedHal(PinType pin) noexcept : pin_(pin) {} 36 | 37 | MbedHal(const MbedHal& rhs) { pin_ = rhs.pin_; } 38 | 39 | ~MbedHal() { 40 | delete pwmout_; 41 | pwmout_ = nullptr; 42 | } 43 | 44 | void analogWrite(uint8_t val) const { 45 | if (!pwmout_) { 46 | pwmout_ = new PwmOut(pin_); 47 | } 48 | pwmout_->write(val / 255.); 49 | } 50 | 51 | MbedHal& operator=(const MbedHal& rhs) { 52 | delete pwmout_; 53 | pwmout_ = nullptr; 54 | pin_ = rhs.pin_; 55 | return *this; 56 | } 57 | 58 | uint32_t millis() const { 59 | return Kernel::Clock::now().time_since_epoch().count(); 60 | } 61 | 62 | private: 63 | PinType pin_; 64 | mutable PwmOut* pwmout_ = nullptr; 65 | }; 66 | } // namespace jled 67 | #endif // __MBED__ 68 | #endif // SRC_MBED_HAL_H_ 69 | -------------------------------------------------------------------------------- /src/pico_hal.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Jan Delgado 2 | // https://github.com/jandelgado/jled 3 | // HAL for the Raspi Pico 4 | // This HAL uses the Raspi Pico SDK: https://github.com/raspberrypi/pico-sdk 5 | // 6 | // Adapted from https://pastebin.com/uVMigyFN (Scott Beasley) and 7 | // https://github.com/raspberrypi/micropython/blob/pico/ports/rp2/machine_pwm.c 8 | // (Copyright (c) 2020 Damien P. George) 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to 12 | // deal in the Software without restriction, including without limitation the 13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 14 | // sell copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in 18 | // all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 26 | // IN THE SOFTWARE. 27 | // 28 | #ifndef SRC_PICO_HAL_H_ 29 | #define SRC_PICO_HAL_H_ 30 | 31 | namespace jled { 32 | 33 | #include "hardware/clocks.h" 34 | #include "hardware/pwm.h" 35 | #include "pico/time.h" 36 | 37 | class PicoHal { 38 | static constexpr auto TOP_MAX = 65534; 39 | static constexpr auto DUTY_100_PCT = 65535; 40 | static constexpr auto DEFAULT_FREQ_HZ = 5000; 41 | 42 | static int set_pwm_freq(uint slice, int freq, uint32_t *div, 43 | uint32_t *top_) { 44 | // Set the frequency, making "top_" as large as possible for maximum 45 | // resolution. 46 | *div = static_cast(16 * clock_get_hz(clk_sys) / 47 | static_cast(freq)); 48 | *top_ = 1; 49 | for (;;) { 50 | // Try a few small prime factors to get close to the desired 51 | // frequency. 52 | if (*div >= 16 * 5 && *div % 5 == 0 && *top_ * 5 <= TOP_MAX) { 53 | *div /= 5; 54 | *top_ *= 5; 55 | } else if (*div >= 16 * 3 && *div % 3 == 0 && 56 | *top_ * 3 <= TOP_MAX) { 57 | *div /= 3; 58 | *top_ *= 3; 59 | } else if (*div >= 16 * 2 && *top_ * 2 <= TOP_MAX) { 60 | *div /= 2; 61 | *top_ *= 2; 62 | } else { 63 | break; 64 | } 65 | } 66 | 67 | if (*div < 16) { 68 | *div = 0; 69 | *top_ = 0; 70 | return -1; // freq too large 71 | } else if (*div >= 256 * 16) { 72 | *div = 0; 73 | *top_ = 0; 74 | return -2; // freq too small 75 | } 76 | 77 | return 0; 78 | } 79 | 80 | static void set_pwm_duty(uint slice, uint channel, uint32_t top, 81 | uint32_t duty) { 82 | const uint32_t cc = duty * (top + 1) / 65535; 83 | pwm_set_chan_level(slice, channel, cc); 84 | pwm_set_enabled(slice, true); 85 | } 86 | 87 | public: 88 | using PinType = uint8_t; 89 | 90 | explicit PicoHal(PinType pin) noexcept { 91 | slice_num_ = pwm_gpio_to_slice_num(pin); 92 | channel_ = pwm_gpio_to_channel(pin); 93 | gpio_set_function(pin, GPIO_FUNC_PWM); 94 | 95 | uint32_t div = 0; 96 | set_pwm_freq(slice_num_, DEFAULT_FREQ_HZ, &div, &top_); 97 | // TODO(jd) check return value? 98 | 99 | pwm_set_wrap(slice_num_, top_); 100 | } 101 | 102 | void analogWrite(uint8_t val) const { 103 | set_pwm_duty(slice_num_, channel_, top_, 104 | static_cast(DUTY_100_PCT / 255) * val); 105 | } 106 | 107 | uint32_t millis() const { return to_ms_since_boot(get_absolute_time()); } 108 | 109 | private: 110 | uint slice_num_, channel_; 111 | uint32_t top_ = 0; 112 | }; 113 | } // namespace jled 114 | #endif // SRC_PICO_HAL_H_ 115 | -------------------------------------------------------------------------------- /test/.lcovrc: -------------------------------------------------------------------------------- 1 | lcov_branch_coverage = 0 2 | -------------------------------------------------------------------------------- /test/Arduino.cpp: -------------------------------------------------------------------------------- 1 | // Minimal Arduino mock for testing JLed hardware accessing functions 2 | // Copyright 2017 Jan Delgado jdelgado@gmx.net 3 | // 4 | #include "Arduino.h" // NOLINT 5 | #include // NOLINT 6 | #include // NOLINT 7 | 8 | 9 | struct ArduinoState { 10 | time_t millis; // current time 11 | 12 | int pin_state[ARDUINO_PINS]; 13 | uint8_t pin_modes[ARDUINO_PINS]; 14 | } ArduinoState_; 15 | 16 | void arduinoMockInit() { 17 | // TODO(jd) introduce UNDEFINED state to mock instead of initalizing with 0 18 | bzero(&ArduinoState_, sizeof(ArduinoState_)); 19 | } 20 | 21 | void pinMode(uint8_t pin, uint8_t mode) { ArduinoState_.pin_modes[pin] = mode; } 22 | 23 | uint8_t arduinoMockGetPinMode(uint8_t pin) { 24 | return ArduinoState_.pin_modes[pin]; 25 | } 26 | 27 | void analogWrite(uint8_t pin, int value) { 28 | ArduinoState_.pin_state[pin] = value; 29 | } 30 | 31 | int arduinoMockGetPinState(uint8_t pin) { return ArduinoState_.pin_state[pin]; } 32 | 33 | uint32_t millis(void) { return ArduinoState_.millis; } 34 | 35 | void arduinoMockSetMillis(uint32_t value) { ArduinoState_.millis = value; } 36 | 37 | -------------------------------------------------------------------------------- /test/Arduino.h: -------------------------------------------------------------------------------- 1 | // Arduino mock for unit testing JLed. 2 | // Copyright 2017 Jan Delgado jdelgado@gmx.net 3 | #ifndef TEST_ARDUINO_H_ 4 | #define TEST_ARDUINO_H_ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | constexpr auto ARDUINO_PINS = 32; 11 | 12 | void arduinoMockInit(); 13 | 14 | void pinMode(uint8_t pin, uint8_t mode); 15 | uint8_t arduinoMockGetPinMode(uint8_t pin); 16 | 17 | void analogWrite(uint8_t pint, int value); 18 | int arduinoMockGetPinState(uint8_t pin); 19 | 20 | uint32_t millis(void); 21 | void arduinoMockSetMillis(uint32_t value); 22 | 23 | #define OUTPUT 0x1 24 | 25 | #endif // TEST_ARDUINO_H_ 26 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | # JLed unit tests Makefile 2 | # run `make coverage` to run all test and calculate coverage 3 | # 4 | .PHONY: phony 5 | 6 | CFLAGS=-std=c++14 -c -Wall -Wextra -I. -I../src -I./esp-idf \ 7 | --coverage -fno-inline \ 8 | -fno-inline-small-functions -fno-default-inline -g -fmax-errors=5 \ 9 | -fno-omit-frame-pointer -fno-optimize-sibling-calls \ 10 | $(OPT) 11 | 12 | LDFLAGS=-fprofile-arcs -ftest-coverage 13 | 14 | CATCH=catch2/catch_amalgamated.cpp 15 | 16 | TEST_ARDUINO_MOCK_SRC=Arduino.cpp test_arduino_mock.cpp test_main.cpp ${CATCH} 17 | TEST_ARDUINO_MOCK_OBJECTS=$(TEST_ARDUINO_MOCK_SRC:.cpp=.o) 18 | 19 | TEST_JLED_SRC=Arduino.cpp test_jled.cpp test_main.cpp ../src/jled_base.cpp ${CATCH} 20 | TEST_JLED_OBJECTS=$(TEST_JLED_SRC:.cpp=.o) 21 | 22 | TEST_JLED_SEQUENCE_SRC=Arduino.cpp test_jled_sequence.cpp test_main.cpp ../src/jled_base.cpp ${CATCH} 23 | TEST_JLED_SEQUENCE_OBJECTS=$(TEST_JLED_SEQUENCE_SRC:.cpp=.o) 24 | 25 | TEST_ESP32_SRC=esp-idf/esp_timer.cpp esp-idf/driver/ledc.cpp \ 26 | test_esp32_hal.cpp ../src/esp32_hal.cpp test_main.cpp ${CATCH} 27 | TEST_ESP32_OBJECTS=$(TEST_ESP32_SRC:.cpp=.o) 28 | 29 | TEST_ESP8266_SRC=Arduino.cpp test_esp8266_hal.cpp test_main.cpp ${CATCH} 30 | TEST_ESP8266_OBJECTS=$(TEST_ESP8266_SRC:.cpp=.o) 31 | 32 | TEST_MBED_SRC=mbed.cpp test_mbed_hal.cpp test_main.cpp ${CATCH} 33 | TEST_MBED_OBJECTS=$(TEST_MBED_SRC:.cpp=.o) 34 | 35 | TEST_ARDUINO_SRC=Arduino.cpp test_arduino_hal.cpp test_main.cpp ${CATCH} 36 | TEST_ARDUINO_OBJECTS=$(TEST_ARDUINO_SRC:.cpp=.o) 37 | 38 | TEST_MORSE_SRC=test_example_morse.cpp test_main.cpp ${CATCH} 39 | TEST_MORSE_OBJECTS=$(TEST_MORSE_SRC:.cpp=.o) 40 | 41 | 42 | all: bin bin/test_arduino_mock \ 43 | bin/test_jled bin/test_jled_sequence \ 44 | bin/test_esp32_hal bin/test_esp8266_hal bin/test_arduino_hal bin/test_mbed_hal \ 45 | bin/test_example_morse 46 | 47 | bin/test_arduino_mock: $(TEST_ARDUINO_MOCK_OBJECTS) 48 | $(CXX) -o $@ $(LDFLAGS) $(TEST_ARDUINO_MOCK_OBJECTS) 49 | 50 | bin/test_jled: $(TEST_JLED_OBJECTS) 51 | $(CXX) -o $@ $(LDFLAGS) $(TEST_JLED_OBJECTS) 52 | 53 | bin/test_jled_sequence: $(TEST_JLED_SEQUENCE_OBJECTS) 54 | $(CXX) -o $@ $(LDFLAGS) $(TEST_JLED_SEQUENCE_OBJECTS) 55 | 56 | bin/test_esp32_hal: CFLAGS += -DESP32 57 | bin/test_esp32_hal: $(TEST_ESP32_OBJECTS) 58 | $(CXX) -o $@ $(LDFLAGS) $(TEST_ESP32_OBJECTS) 59 | 60 | bin/test_esp8266_hal: $(TEST_ESP8266_OBJECTS) 61 | $(CXX) -o $@ $(LDFLAGS) $(TEST_ESP8266_OBJECTS) 62 | 63 | bin/test_mbed_hal: CFLAGS += -D__MBED__ 64 | bin/test_mbed_hal: $(TEST_MBED_OBJECTS) 65 | $(CXX) -o $@ $(LDFLAGS) $(TEST_MBED_OBJECTS) 66 | 67 | bin/test_arduino_hal: $(TEST_ARDUINO_OBJECTS) 68 | $(CXX) -o $@ $(LDFLAGS) $(TEST_ARDUINO_OBJECTS) 69 | 70 | bin/test_example_morse: CFLAGS += -I../examples/morse 71 | bin/test_example_morse: $(TEST_MORSE_OBJECTS) 72 | $(CXX) -o $@ $(LDFLAGS) $(TEST_MORSE_OBJECTS) 73 | 74 | coverage: test 75 | lcov --config-file=.lcovrc --directory ../src --directory .. --capture --output-file coverage.lcov --no-external 76 | lcov --config-file=.lcovrc --list coverage.lcov 77 | mkdir -p report 78 | genhtml coverage.lcov -o report 79 | 80 | test: depend all 81 | ./bin/test_jled 82 | ./bin/test_jled_sequence 83 | ./bin/test_mbed_hal 84 | ./bin/test_esp32_hal 85 | ./bin/test_esp8266_hal 86 | ./bin/test_arduino_hal 87 | ./bin/test_example_morse 88 | 89 | .cpp.o: 90 | $(CXX) $< $(CFLAGS) -o $@ 91 | 92 | .hpp.pch: 93 | $(CXX) $< $(CFLAGS) -o $@ 94 | 95 | bin: 96 | mkdir -p bin 97 | 98 | clean: phony 99 | rm -f {./,esp-idf,esp-idf/driver,catch2}/{*.gcov,*.gcda,*.gcno,*.o} .depend 100 | 101 | clobber: clean 102 | rm -f bin/* 103 | 104 | depend: .depend 105 | 106 | .depend: $(TEST_JLED_SRC) $(TEST_JLED_SEQUENCE_SRC) $(TEST_ESP32_SRC) $(TEST_ESP8266_SRC) $(TEST_ARDUINO_SRC) $(TEST_MBED_SRC) $(TEST_MORSE_SRC) 107 | @echo updating dependencies in .depend 108 | @rm -f ./.depend 109 | @$(CC) -I ../src -I . -MM $^ > .depend 110 | 111 | include .depend 112 | 113 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # host based unit tests for JLed 2 | 3 | * the tests are using the [catch unit testing framework](https://github.com/catchorg/Catch2). 4 | * test results are available on [coveralls](https://coveralls.io/github/jandelgado/jled) 5 | 6 | Pass `OPT` argument to `make` to control optimization, which affects code 7 | coverage, e.g.: 8 | * `make clean coverage OPT=-O0` 9 | * `make clean test OPT=-O2` 10 | -------------------------------------------------------------------------------- /test/esp-idf/driver/ledc.cpp: -------------------------------------------------------------------------------- 1 | // Minimal ESP-IDF ledc mock for testing JLed ESP32 hardware accessing functions 2 | // Copyright 2022 Jan Delgado jdelgado@gmx.net 3 | // 4 | #include "ledc.h" 5 | #include 6 | #include // NOLINT 7 | 8 | struct ESP32State { 9 | ledc_channel_config_t channel_config; 10 | ledc_timer_config_t timer_config; 11 | esp32_mock_ledc_update_duty_args update_duty[LEDC_CHANNEL_MAX]; 12 | esp32_mock_ledc_set_duty_args set_duty[LEDC_CHANNEL_MAX]; 13 | } ESP32State_; 14 | 15 | void esp32_mock_init() { 16 | // TODO(jd) introduce UNDEFINED state to mock instead of initalizing with 0 17 | bzero(&ESP32State_, sizeof(ESP32State_)); 18 | } 19 | 20 | esp_err_t ledc_channel_config(const ledc_channel_config_t* ledc_conf) { 21 | ESP32State_.channel_config = *ledc_conf; 22 | return (esp_err_t)0; 23 | } 24 | 25 | ledc_channel_config_t esp32_mock_get_ledc_channel_config_args() { 26 | return ESP32State_.channel_config; 27 | } 28 | 29 | esp_err_t ledc_timer_config(const ledc_timer_config_t* timer_conf) { 30 | ESP32State_.timer_config = *timer_conf; 31 | return (esp_err_t)0; 32 | } 33 | 34 | ledc_timer_config_t esp32_mock_get_ledc_timer_config_args() { 35 | return ESP32State_.timer_config; 36 | } 37 | 38 | esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel) { 39 | assert(channel >= LEDC_CHANNEL_0 && channel < LEDC_CHANNEL_MAX); 40 | ESP32State_.update_duty[channel] = esp32_mock_ledc_update_duty_args{speed_mode}; 41 | return (esp_err_t)0; 42 | } 43 | 44 | esp32_mock_ledc_update_duty_args esp32_mock_get_ledc_update_duty_args(ledc_channel_t channel) { 45 | assert(channel >= LEDC_CHANNEL_0 && channel < LEDC_CHANNEL_MAX); 46 | return ESP32State_.update_duty[channel]; 47 | } 48 | 49 | esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty) { 50 | assert(channel >= LEDC_CHANNEL_0 && channel < LEDC_CHANNEL_MAX); 51 | ESP32State_.set_duty[channel] = esp32_mock_ledc_set_duty_args{speed_mode, duty}; 52 | return (esp_err_t)0; 53 | } 54 | 55 | esp32_mock_ledc_set_duty_args esp32_mock_get_ledc_set_duty_args(ledc_channel_t channel) { 56 | assert(channel >= LEDC_CHANNEL_0 && channel < LEDC_CHANNEL_MAX); 57 | return ESP32State_.set_duty[channel]; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /test/esp-idf/driver/ledc.h: -------------------------------------------------------------------------------- 1 | // Minimal ESP-IDF ledc mock for testing JLed ESP32 hardware accessing functions 2 | // Copyright 2022 Jan Delgado jdelgado@gmx.net 3 | #pragma once 4 | 5 | #include 6 | 7 | /* 8 | * adapted from https://github.com/espressif/esp-idf include files 9 | * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD 10 | */ 11 | 12 | typedef int esp_err_t; 13 | 14 | typedef enum { 15 | LEDC_HIGH_SPEED_MODE = 0, /*!< LEDC high speed speed_mode */ 16 | LEDC_LOW_SPEED_MODE, /*!< LEDC low speed speed_mode */ 17 | LEDC_SPEED_MODE_MAX, /*!< LEDC speed limit */ 18 | } ledc_mode_t; 19 | 20 | typedef enum { 21 | LEDC_INTR_DISABLE = 0, /*!< Disable LEDC interrupt */ 22 | LEDC_INTR_FADE_END, /*!< Enable LEDC interrupt */ 23 | LEDC_INTR_MAX, 24 | } ledc_intr_type_t; 25 | 26 | typedef enum { 27 | LEDC_AUTO_CLK = 0 28 | } ledc_clk_cfg_t; 29 | 30 | typedef enum { 31 | LEDC_TIMER_0 = 0, /*!< LEDC timer 0 */ 32 | LEDC_TIMER_1, /*!< LEDC timer 1 */ 33 | LEDC_TIMER_2, /*!< LEDC timer 2 */ 34 | LEDC_TIMER_3, /*!< LEDC timer 3 */ 35 | LEDC_TIMER_MAX, 36 | } ledc_timer_t; 37 | 38 | typedef enum { 39 | LEDC_CHANNEL_0 = 0, /*!< LEDC channel 0 */ 40 | LEDC_CHANNEL_1, /*!< LEDC channel 1 */ 41 | LEDC_CHANNEL_2, /*!< LEDC channel 2 */ 42 | LEDC_CHANNEL_3, /*!< LEDC channel 3 */ 43 | LEDC_CHANNEL_4, /*!< LEDC channel 4 */ 44 | LEDC_CHANNEL_5, /*!< LEDC channel 5 */ 45 | LEDC_CHANNEL_MAX, 46 | } ledc_channel_t; 47 | 48 | typedef enum { 49 | LEDC_TIMER_1_BIT = 1, /*!< LEDC PWM duty resolution of 1 bits */ 50 | LEDC_TIMER_2_BIT, /*!< LEDC PWM duty resolution of 2 bits */ 51 | LEDC_TIMER_3_BIT, /*!< LEDC PWM duty resolution of 3 bits */ 52 | LEDC_TIMER_4_BIT, /*!< LEDC PWM duty resolution of 4 bits */ 53 | LEDC_TIMER_5_BIT, /*!< LEDC PWM duty resolution of 5 bits */ 54 | LEDC_TIMER_6_BIT, /*!< LEDC PWM duty resolution of 6 bits */ 55 | LEDC_TIMER_7_BIT, /*!< LEDC PWM duty resolution of 7 bits */ 56 | LEDC_TIMER_8_BIT, /*!< LEDC PWM duty resolution of 8 bits */ 57 | LEDC_TIMER_9_BIT, /*!< LEDC PWM duty resolution of 9 bits */ 58 | LEDC_TIMER_10_BIT, /*!< LEDC PWM duty resolution of 10 bits */ 59 | LEDC_TIMER_11_BIT, /*!< LEDC PWM duty resolution of 11 bits */ 60 | LEDC_TIMER_12_BIT, /*!< LEDC PWM duty resolution of 12 bits */ 61 | LEDC_TIMER_13_BIT, /*!< LEDC PWM duty resolution of 13 bits */ 62 | LEDC_TIMER_14_BIT, /*!< LEDC PWM duty resolution of 14 bits */ 63 | LEDC_TIMER_BIT_MAX, 64 | } ledc_timer_bit_t; 65 | 66 | /** 67 | * @brief Configuration parameters of LEDC channel for ledc_channel_config function 68 | */ 69 | typedef struct { 70 | int gpio_num; /*!< the LEDC output gpio_num, if you want to use gpio16, gpio_num = 16 */ 71 | ledc_mode_t speed_mode; /*!< LEDC speed speed_mode, high-speed mode or low-speed mode */ 72 | ledc_channel_t channel; /*!< LEDC channel (0 - 7) */ 73 | ledc_intr_type_t intr_type; /*!< configure interrupt, Fade interrupt enable or Fade interrupt disable */ 74 | ledc_timer_t timer_sel; /*!< Select the timer source of channel (0 - 3) */ 75 | uint32_t duty; /*!< LEDC channel duty, the range of duty setting is [0, (2**duty_resolution)] */ 76 | int hpoint; /*!< LEDC channel hpoint value, the max value is 0xfffff */ 77 | struct { 78 | unsigned int output_invert: 1;/*!< Enable (1) or disable (0) gpio output invert */ 79 | } flags; /*!< LEDC flags */ 80 | 81 | } ledc_channel_config_t; 82 | 83 | /** 84 | * @brief Configuration parameters of LEDC Timer timer for ledc_timer_config function 85 | */ 86 | typedef struct { 87 | ledc_mode_t speed_mode; /*!< LEDC speed speed_mode, high-speed mode or low-speed mode */ 88 | ledc_timer_bit_t duty_resolution; /*!< LEDC channel duty resolution */ 89 | ledc_timer_t timer_num; /*!< The timer source of channel (0 - 3) */ 90 | uint32_t freq_hz; /*!< LEDC timer frequency (Hz) */ 91 | ledc_clk_cfg_t clk_cfg; /*!< Configure LEDC source clock. 92 | For low speed channels and high speed channels, you can specify the source clock using LEDC_USE_REF_TICK, LEDC_USE_APB_CLK or LEDC_AUTO_CLK. 93 | For low speed channels, you can also specify the source clock using LEDC_USE_RTC8M_CLK, in this case, all low speed channel's source clock must be RTC8M_CLK*/ 94 | } ledc_timer_config_t; 95 | 96 | /* mocked versions of APIs used by JLed */ 97 | 98 | esp_err_t ledc_channel_config(const ledc_channel_config_t* ledc_conf); 99 | // returns last values passed to ledc_timer_config 100 | ledc_channel_config_t esp32_mock_get_ledc_channel_config_args(); 101 | 102 | esp_err_t ledc_timer_config(const ledc_timer_config_t* timer_conf); 103 | // returns last values passed to ledc_timer_config 104 | ledc_timer_config_t esp32_mock_get_ledc_timer_config_args(); 105 | 106 | esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel); 107 | typedef struct { 108 | ledc_mode_t speed_mode; 109 | } esp32_mock_ledc_update_duty_args; 110 | // returns last values passed to ledc_set_duty 111 | esp32_mock_ledc_update_duty_args esp32_mock_get_ledc_update_duty_args(ledc_channel_t channel); 112 | 113 | esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty); 114 | typedef struct { 115 | ledc_mode_t speed_mode; 116 | uint32_t duty; 117 | } esp32_mock_ledc_set_duty_args; 118 | // returns last values passed to ledc_set_duty 119 | esp32_mock_ledc_set_duty_args esp32_mock_get_ledc_set_duty_args(ledc_channel_t channel); 120 | 121 | void esp32_mock_init(); 122 | 123 | -------------------------------------------------------------------------------- /test/esp-idf/esp_timer.cpp: -------------------------------------------------------------------------------- 1 | // ESP32 ESP-IDF mock 2 | // Copyright (c) 2017-2022 Jan Delgado 3 | // 4 | #include "esp_timer.h" // NOLINT 5 | 6 | int64_t esp32_mock_time_; 7 | 8 | int64_t esp_timer_get_time() { 9 | return esp32_mock_time_; 10 | } 11 | 12 | void esp32_mock_set_esp_timer(int64_t t) { 13 | esp32_mock_time_ = t; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /test/esp-idf/esp_timer.h: -------------------------------------------------------------------------------- 1 | // ESP32 ESP-IDF mock 2 | // Copyright (c) 2017-2022 Jan Delgado 3 | // 4 | #pragma once 5 | #include 6 | 7 | int64_t esp_timer_get_time(); 8 | 9 | void esp32_mock_set_esp_timer(int64_t t); 10 | 11 | -------------------------------------------------------------------------------- /test/hal_mock.h: -------------------------------------------------------------------------------- 1 | // a HAL mock for the JLed unit tests. Behaves like a JLed HAL but 2 | // but can be intrumented & queried. 3 | // Copyright 2017-2020 Jan Delgado jdelgado@gmx.net 4 | // 5 | 6 | #ifndef TEST_HAL_MOCK_H_ 7 | #define TEST_HAL_MOCK_H_ 8 | 9 | class HalMock { 10 | public: 11 | using PinType = uint8_t; 12 | 13 | HalMock() {} 14 | explicit HalMock(PinType pin) : pin_(pin) {} 15 | 16 | void analogWrite(uint8_t val) { val_ = val; } 17 | time_t millis() const { return millis_; } 18 | 19 | // mock functions 20 | void SetMillis(time_t millis) { millis_ = millis; } 21 | uint8_t Pin() const { return pin_; } 22 | uint8_t Value() const { return val_; } 23 | 24 | private: 25 | time_t millis_ = 0; 26 | uint8_t val_ = 0; 27 | PinType pin_ = 0; 28 | }; 29 | 30 | #endif // TEST_HAL_MOCK_H_ 31 | -------------------------------------------------------------------------------- /test/mbed.cpp: -------------------------------------------------------------------------------- 1 | // Minimal mbed mock for testing JLed hardware accessing functions 2 | // Copyright 2020 Jan Delgado jdelgado@gmx.net 3 | // 4 | #include "mbed.h" // NOLINT 5 | 6 | constexpr auto MBED_PINS = 32; 7 | 8 | struct MbedState { 9 | uint32_t us_ticks; 10 | float pin_state[MBED_PINS]; 11 | } MbedState_; 12 | 13 | void mbedMockInit() { 14 | for (auto i = 0; i < MBED_PINS; i++) { 15 | MbedState_.pin_state[i] = kUninitialized; 16 | } 17 | MbedState_.us_ticks = 0; 18 | } 19 | 20 | void mbedMockWrite(PinName pin, float value) { 21 | MbedState_.pin_state[pin] = value; 22 | } 23 | 24 | float mbedMockGetPinState(uint8_t pin) { return MbedState_.pin_state[pin]; } 25 | 26 | void mbedMockSetUsTicks(uint32_t ticks) { MbedState_.us_ticks = ticks; } 27 | 28 | uint32_t us_ticker_read() { return MbedState_.us_ticks; } 29 | 30 | void PwmOut::write(float val) { mbedMockWrite(pin_, val); } 31 | 32 | Kernel::Clock::time_point Kernel::Clock::now() { 33 | return Kernel::Clock::time_point( 34 | std::chrono::microseconds(MbedState_.us_ticks)); 35 | } 36 | -------------------------------------------------------------------------------- /test/mbed.h: -------------------------------------------------------------------------------- 1 | // Minimal mbed mock for testing JLed hardware accessing functions 2 | // Copyright 2020 Jan Delgado jdelgado@gmx.net 3 | // 4 | 5 | #ifndef TEST_MBED_H_ 6 | #define TEST_MBED_H_ 7 | 8 | #include 9 | #include // NOLINT 10 | 11 | using PinName = uint8_t; 12 | 13 | constexpr auto kUninitializedPin = 255; 14 | constexpr auto kUninitialized = -1.; 15 | 16 | uint32_t us_ticker_read(); 17 | 18 | class PwmOut { 19 | PinName pin_ = kUninitializedPin; 20 | 21 | public: 22 | explicit PwmOut(PinName pin) : pin_(pin) {} 23 | void write(float val); 24 | }; 25 | 26 | void mbedMockInit(); 27 | void mbedMockSetUsTicks(uint32_t ticks); 28 | float mbedMockGetPinState(uint8_t pin); 29 | 30 | namespace Kernel { 31 | struct Clock { 32 | using time_point = std::chrono::time_point; 33 | static time_point now(); 34 | }; 35 | }; // namespace Kernel 36 | 37 | #endif // TEST_MBED_H_ 38 | -------------------------------------------------------------------------------- /test/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 3 | cpplint --extensions=cpp,h $(find $DIR/.. -name "*\.cpp" -o -name "*\.h" ) 4 | 5 | -------------------------------------------------------------------------------- /test/test_arduino_hal.cpp: -------------------------------------------------------------------------------- 1 | // JLed Unit tests (run on host). 2 | // Copyright 2017 Jan Delgado jdelgado@gmx.net 3 | #include // NOLINT 4 | #include "catch2/catch_amalgamated.hpp" 5 | 6 | using jled::ArduinoHal; 7 | 8 | TEST_CASE("first call to analogWrite() sets pin mode to OUTPUT", 9 | "[araduino_hal]") { 10 | arduinoMockInit(); 11 | constexpr auto kPin = 10; 12 | auto h = ArduinoHal(kPin); 13 | REQUIRE(arduinoMockGetPinMode(kPin) == 0); 14 | h.analogWrite(123); 15 | REQUIRE(arduinoMockGetPinMode(kPin) == OUTPUT); 16 | } 17 | 18 | TEST_CASE("analogWrite() writes correct value", "[araduino_hal]") { 19 | arduinoMockInit(); 20 | constexpr auto kPin = 10; 21 | auto h = ArduinoHal(kPin); 22 | h.analogWrite(123); 23 | REQUIRE(arduinoMockGetPinState(kPin) == 123); 24 | } 25 | 26 | TEST_CASE("millis() returns correct time", "[arduino_hal]") { 27 | arduinoMockInit(); 28 | auto h = ArduinoHal(1); 29 | REQUIRE(h.millis() == 0); 30 | arduinoMockSetMillis(99); 31 | REQUIRE(h.millis() == 99); 32 | } 33 | -------------------------------------------------------------------------------- /test/test_arduino_mock.cpp: -------------------------------------------------------------------------------- 1 | // JLed Unit tests (run on host). 2 | // Copyright 2017 Jan Delgado jdelgado@gmx.net 3 | 4 | #include "catch2/catch_amalgamated.hpp" 5 | #include 6 | 7 | TEST_CASE("arduino mock correctly initialized", "[mock]") { 8 | arduinoMockInit(); 9 | for (auto i = 0; i < ARDUINO_PINS; i++) { 10 | REQUIRE(arduinoMockGetPinMode(i) == 0); 11 | REQUIRE(arduinoMockGetPinState(i) == 0); 12 | } 13 | REQUIRE(millis() == 0); 14 | } 15 | 16 | TEST_CASE("arduino mock set time", "[mock]") { 17 | arduinoMockInit(); 18 | REQUIRE(millis() == 0); 19 | arduinoMockSetMillis(6502); 20 | REQUIRE(millis() == 6502); 21 | } 22 | 23 | TEST_CASE("arduino mock analog write", "[mock]") { 24 | constexpr auto kTestPin = 10; 25 | arduinoMockInit(); 26 | analogWrite(kTestPin, 99); 27 | REQUIRE(arduinoMockGetPinState(kTestPin) == 99); 28 | } 29 | -------------------------------------------------------------------------------- /test/test_esp32_hal.cpp: -------------------------------------------------------------------------------- 1 | // JLed Unit tests for the ESP32 HAL (runs on host). 2 | // Copyright 2017-2022 Jan Delgado jdelgado@gmx.net 3 | #define ESP_IDF_VERSION_MAJOR 5 4 | #include // NOLINT 5 | #include "catch2/catch_amalgamated.hpp" 6 | 7 | using jled::Esp32ChanMapper; 8 | using jled::Esp32Hal; 9 | 10 | TEST_CASE("channel mapper returns new channels for different pins", 11 | "[esp32_hal]") { 12 | auto m = Esp32ChanMapper(); 13 | 14 | // expect a new channel starting with 0 for different pins and the same 15 | // channel if a pin is used again 16 | REQUIRE(m.chanForPin(10) == 0); 17 | REQUIRE(m.chanForPin(15) == 1); 18 | REQUIRE(m.chanForPin(3) == 2); 19 | REQUIRE(m.chanForPin(1) == 3); 20 | 21 | // no change when same pins are requested 22 | REQUIRE(m.chanForPin(10) == 0); 23 | REQUIRE(m.chanForPin(15) == 1); 24 | REQUIRE(m.chanForPin(3) == 2); 25 | REQUIRE(m.chanForPin(1) == 3); 26 | 27 | REQUIRE(m.chanForPin(7) == 4); 28 | } 29 | 30 | TEST_CASE("channel mapper starts over when channels are exhausted", 31 | "[esp32_hal]") { 32 | auto m = Esp32ChanMapper(); 33 | 34 | for (auto i = 0; i < Esp32ChanMapper::kLedcMaxChan; i++) { 35 | REQUIRE(m.chanForPin(i) == i); 36 | } 37 | 38 | // now all channels are used and the mapper starts over at 0 39 | REQUIRE(m.chanForPin(100) == 0); 40 | REQUIRE(m.chanForPin(101) == 1); 41 | } 42 | 43 | TEST_CASE("ledc ctor correctly initializes hardware", "[esp32_hal]") { 44 | esp32_mock_init(); 45 | 46 | constexpr auto kChan = 5; 47 | constexpr auto kPin = 10; 48 | auto hal[[gnu::unused]] = Esp32Hal(kPin, kChan); 49 | 50 | // check that ledc is initialized correctly 51 | auto timer_config = esp32_mock_get_ledc_timer_config_args(); 52 | REQUIRE(timer_config.speed_mode == LEDC_LOW_SPEED_MODE); 53 | REQUIRE(timer_config.duty_resolution == LEDC_TIMER_8_BIT); 54 | REQUIRE(timer_config.timer_num == LEDC_TIMER_0); 55 | REQUIRE(timer_config.freq_hz == 5000); 56 | REQUIRE(timer_config.clk_cfg == LEDC_AUTO_CLK); 57 | 58 | auto chan_config = esp32_mock_get_ledc_channel_config_args(); 59 | REQUIRE(chan_config.gpio_num == kPin); 60 | REQUIRE(chan_config.speed_mode == LEDC_LOW_SPEED_MODE); 61 | REQUIRE(chan_config.channel == kChan); 62 | REQUIRE(chan_config.intr_type == LEDC_INTR_DISABLE); 63 | REQUIRE(chan_config.timer_sel == LEDC_TIMER_0); 64 | REQUIRE(chan_config.hpoint == 0); 65 | REQUIRE(chan_config.duty == 0); 66 | REQUIRE(chan_config.flags.output_invert == 0); 67 | } 68 | 69 | TEST_CASE("ledc selects same channel for same pin", "[esp32_hal]") { 70 | constexpr auto kPin = 10; 71 | 72 | // note: we test a static property here (auto incremented next channel 73 | // number). so test has side effects. TODO avoid/reset 74 | auto hal1 = Esp32Hal(kPin); 75 | auto hal2 = Esp32Hal(kPin); 76 | 77 | // same channel is to be selected, since pin did not change 78 | REQUIRE(hal1.chan() == hal2.chan()); 79 | } 80 | 81 | TEST_CASE("ledc selects different channels for different pins", "[esp32_hal]") { 82 | constexpr auto kPin = 10; 83 | 84 | auto hal1 = Esp32Hal(kPin); 85 | auto hal2 = Esp32Hal(kPin + 1); 86 | 87 | REQUIRE(hal1.chan() != hal2.chan()); 88 | } 89 | 90 | TEST_CASE("analogWrite() writes value", "[esp32_hal]") { 91 | esp32_mock_init(); 92 | 93 | constexpr auto kChan = 5; 94 | constexpr auto kPin = 10; 95 | auto hal = Esp32Hal(kPin, kChan); 96 | 97 | hal.analogWrite(123); 98 | 99 | auto set_duty = esp32_mock_get_ledc_set_duty_args((ledc_channel_t)kChan); 100 | REQUIRE(set_duty.speed_mode == LEDC_LOW_SPEED_MODE); 101 | REQUIRE(set_duty.duty == 123); 102 | 103 | auto update_duty = 104 | esp32_mock_get_ledc_update_duty_args((ledc_channel_t)kChan); 105 | REQUIRE(update_duty.speed_mode == LEDC_LOW_SPEED_MODE); 106 | } 107 | 108 | TEST_CASE("analogWrite() writes 0 as 0", "[esp32_hal]") { 109 | esp32_mock_init(); 110 | 111 | // attach channel 2 to pin 1 112 | constexpr auto kChan = 5; 113 | constexpr auto kPin = 10; 114 | auto hal = Esp32Hal(kPin, kChan); 115 | 116 | hal.analogWrite(0); 117 | 118 | auto set_duty = esp32_mock_get_ledc_set_duty_args((ledc_channel_t)kChan); 119 | REQUIRE(set_duty.duty == 0); 120 | } 121 | 122 | TEST_CASE("analogWrite() writes 255 as 256", "[esp32_hal]") { 123 | esp32_mock_init(); 124 | 125 | constexpr auto kChan = 5; 126 | constexpr auto kPin = 10; 127 | auto hal = Esp32Hal(kPin, kChan); 128 | 129 | hal.analogWrite(255); 130 | 131 | auto set_duty = esp32_mock_get_ledc_set_duty_args((ledc_channel_t)kChan); 132 | REQUIRE(set_duty.duty == 256); 133 | } 134 | 135 | TEST_CASE("millis() returns correct time", "[esp32_hal]") { 136 | auto hal = Esp32Hal(1); 137 | REQUIRE(hal.millis() == 0); 138 | 139 | esp32_mock_set_esp_timer(99 * 1000); 140 | REQUIRE(hal.millis() == 99); 141 | } 142 | -------------------------------------------------------------------------------- /test/test_esp32_mock.cpp: -------------------------------------------------------------------------------- 1 | // JLed Unit tests (run on host). 2 | // Copyright 2017 Jan Delgado jdelgado@gmx.net 3 | 4 | #include "catch2/catch_amalgamated.hpp" 5 | #include "esp32.h" // NOLINT 6 | 7 | TEST_CASE("esp32 mock correctly initialized", "[mock]") { 8 | esp32MockInit(); 9 | for (auto i = 0; i < ESP32_PINS; i++) { 10 | REQUIRE(arduinoMockGetLedcAttachPin(i) == 0); 11 | REQUIRE(arduinoMockGetLedcAttachPin(i) == 0); 12 | } 13 | for (auto i = 0; i < LEDC_CHANNELS; i++) { 14 | REQUIRE(arduinoMockGetLedcState(i) == 0); 15 | REQUIRE(arduinoMockGetLedcSetup(i).freq == 0); 16 | REQUIRE(arduinoMockGetLedcSetup(i).bit_num == 0); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /test/test_esp8266_hal.cpp: -------------------------------------------------------------------------------- 1 | // JLed Unit tests (run on host). 2 | // Copyright 2017 Jan Delgado jdelgado@gmx.net 3 | #include "catch2/catch_amalgamated.hpp" 4 | #include // NOLINT 5 | 6 | using jled::Esp8266Hal; 7 | 8 | TEST_CASE("properly scale 8bit to 10bit for ESP8266 support", 9 | "[esp8266_analog_writer]") { 10 | class TestableWriter : public Esp8266Hal { 11 | public: 12 | static void test() { 13 | REQUIRE(TestableWriter::ScaleTo10Bit(0) == 0); 14 | REQUIRE(TestableWriter::ScaleTo10Bit(127) == (127 << 2) + 3); 15 | REQUIRE(TestableWriter::ScaleTo10Bit(255) == 1023); 16 | } 17 | }; 18 | TestableWriter::test(); 19 | } 20 | 21 | TEST_CASE("analogWrite() writes correct value", "[esp8266_analog_writer]") { 22 | arduinoMockInit(); 23 | 24 | constexpr auto kPin = 10; 25 | auto aw = Esp8266Hal(kPin); 26 | 27 | aw.analogWrite(123); 28 | 29 | // expect the value to be scaled to 10bit written to port 30 | REQUIRE(arduinoMockGetPinState(kPin) == (123<<2)+3); 31 | } 32 | 33 | TEST_CASE("millis() returns correct time", "[esp8266_hal]") { 34 | arduinoMockInit(); 35 | auto h = Esp8266Hal(1); 36 | REQUIRE(h.millis() == 0); 37 | arduinoMockSetMillis(99); 38 | REQUIRE(h.millis() == 99); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /test/test_example_morse.cpp: -------------------------------------------------------------------------------- 1 | // JLed Unit tests (run on host). 2 | // Tests for the morse code example. 3 | // Copyright 2018 Jan Delgado jdelgado@gmx.net 4 | #include "catch2/catch_amalgamated.hpp" 5 | 6 | #include // NOLINT 7 | 8 | TEST_CASE("calc len of bit array", "[morse_example_bitset]") { 9 | class TestBitset : public Bitset { 10 | public: 11 | static void test() { 12 | REQUIRE(0 == num_bytes(0)); 13 | REQUIRE(1 == num_bytes(1)); 14 | REQUIRE(1 == num_bytes(7)); 15 | REQUIRE(1 == num_bytes(8)); 16 | REQUIRE(2 == num_bytes(9)); 17 | REQUIRE(2 == num_bytes(16)); 18 | REQUIRE(3 == num_bytes(17)); 19 | } 20 | }; 21 | TestBitset::test(); 22 | } 23 | 24 | TEST_CASE("set and test bits in the bitset", "[morse_example_bitset]") { 25 | Bitset bf(18); // 3 bytes 26 | REQUIRE(18 == bf.size()); 27 | REQUIRE(!bf.test(0)); 28 | REQUIRE(!bf.test(10)); 29 | REQUIRE(!bf.test(17)); 30 | bf.set(0, true); 31 | bf.set(10, true); 32 | bf.set(17, true); 33 | REQUIRE(bf.test(0)); 34 | REQUIRE(bf.test(10)); 35 | REQUIRE(bf.test(17)); 36 | } 37 | 38 | TEST_CASE("treepos returns correct position in tree", "[morse_example]") { 39 | class TestMorse : public Morse { 40 | public: 41 | void test() { 42 | REQUIRE(2 == treepos('E')); 43 | REQUIRE(3 == treepos('T')); 44 | REQUIRE(4 == treepos('I')); 45 | REQUIRE(7 == treepos('M')); 46 | REQUIRE(8 == treepos('S')); 47 | REQUIRE(16 == treepos('H')); 48 | REQUIRE(32 == treepos('5')); 49 | } 50 | }; 51 | TestMorse().test(); 52 | } 53 | 54 | TEST_CASE("binary code of character is determined correctly", 55 | "[morse_example]") { 56 | class TestMorse : public Morse { 57 | public: 58 | void test() { 59 | uint16_t code = pos_to_morse_code(treepos('F')); // F = ..-. = 1000 60 | REQUIRE(4 == (code >> 8)); // hi: length in bits 61 | REQUIRE(0b0100 == 62 | (code & 0xff)); // lo: morse code as bits, reversed 63 | } 64 | }; 65 | TestMorse().test(); 66 | } 67 | 68 | TEST_CASE("string is encoded correctly into sequence", "[morse_example]") { 69 | // A = . - => 1 + 3 + (1) dits 70 | // E = . => 1 dit 71 | // B = - . . . => 3 + 1 + 1 + 1 + (3*1) dits 72 | Morse m("AE B"); 73 | // clang-format off 74 | constexpr int expected[]{ 75 | 1, 0, 1, 1, 1, // A 76 | 0, 0, 0, // pause between chars 77 | 1, // E 78 | 0, 0, 0, 0, 0, 0, 0, // 7 dits between words 79 | 1, 1, 1, 0, 1, 0, 1, 0, 1}; // B 80 | // clang-format on 81 | REQUIRE(sizeof(expected) / sizeof(expected[0]) == m.size()); 82 | for (auto i = 0u; i < m.size(); i++) { 83 | REQUIRE(expected[i] == m.test(i)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /test/test_jled.cpp: -------------------------------------------------------------------------------- 1 | // JLed Unit tests (runs on host) 2 | // Copyright 2017-2022 Jan Delgado jdelgado@gmx.net 3 | #include // NOLINT 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "catch2/catch_amalgamated.hpp" 10 | #include "hal_mock.h" // NOLINT 11 | 12 | using jled::BlinkBrightnessEvaluator; 13 | using jled::BreatheBrightnessEvaluator; 14 | using jled::BrightnessEvaluator; 15 | using jled::CandleBrightnessEvaluator; 16 | using jled::ConstantBrightnessEvaluator; 17 | using jled::TJLed; 18 | 19 | // TestJLed is a JLed class using the HalMock for tests. This allows to 20 | // test the code abstracted from the actual hardware in use. 21 | class TestJLed : public TJLed { 22 | using TJLed::TJLed; 23 | }; 24 | // instanciate for test coverage measurement 25 | template class TJLed; 26 | 27 | using ByteVec = std::vector; 28 | 29 | class MockBrightnessEvaluator : public BrightnessEvaluator { 30 | ByteVec values_; 31 | mutable uint16_t count_ = 0; 32 | 33 | public: 34 | explicit MockBrightnessEvaluator(ByteVec values) : values_(values) {} 35 | uint16_t Count() const { return count_; } 36 | uint16_t Period() const { return values_.size(); } 37 | uint8_t Eval(uint32_t t) const { 38 | CHECK(t < values_.size()); 39 | count_++; 40 | return values_[t]; 41 | } 42 | }; 43 | 44 | // expected result when a JLed object is updated: return value 45 | // of Update() and the current brightness 46 | using UpdateResult = std::pair; 47 | using UpdateResults = std::vector; 48 | 49 | // helper to check if a led evaluates to given sequence. TODO use a catch 50 | // matcher 51 | template 52 | void check_led(T *led, const UpdateResults &expected) { 53 | uint32_t time = 0; 54 | for (const auto ¤t : expected) { 55 | led->Hal().SetMillis(time); 56 | const auto updated = led->Update(); 57 | const auto val = led->Hal().Value(); 58 | UNSCOPED_INFO("t=" << time << ", actual=(" 59 | << (updated ? "true" : "false") << ", " << (int)val 60 | << "), expected=(" 61 | << (current.first ? "true" : "false") << ", " 62 | << (int)current.second << ")"); 63 | CHECK(current.first == updated); 64 | CHECK(current.second == val); 65 | time++; 66 | } 67 | } 68 | 69 | TEST_CASE("jled without effect does nothing", "[jled]") { 70 | auto led = TestJLed(1); 71 | CHECK(!led.Update()); 72 | } 73 | 74 | TEST_CASE("On/Off function configuration", "[jled]") { 75 | // class used to access proteced fields during test 76 | class TestableJLed : public TestJLed { 77 | public: 78 | using TestJLed::TestJLed; 79 | static void test() { 80 | SECTION( 81 | "using On() effect uses a BrightnessEval that turns the LED " 82 | "on") { 83 | TestableJLed jled(1); 84 | jled.On(); 85 | REQUIRE(dynamic_cast( 86 | jled.brightness_eval_) != nullptr); 87 | CHECK(jled.brightness_eval_->Eval(0) == 255); 88 | } 89 | 90 | SECTION( 91 | "using Off() effect uses a BrightnessEval that turns the LED " 92 | "off") { 93 | TestableJLed jled(1); 94 | jled.Off(); 95 | REQUIRE(dynamic_cast( 96 | jled.brightness_eval_) != nullptr); 97 | CHECK(jled.brightness_eval_->Eval(0) == 0); 98 | } 99 | 100 | SECTION("using Set() allows to set custom brightness level") { 101 | TestableJLed jled(1); 102 | jled.Set(123); 103 | REQUIRE(dynamic_cast( 104 | jled.brightness_eval_) != nullptr); 105 | CHECK(jled.brightness_eval_->Eval(0) == 123); 106 | } 107 | 108 | SECTION("using Set(0) allows to set custom turn LED off") { 109 | TestableJLed jled(1); 110 | jled.Set(0); 111 | REQUIRE(dynamic_cast( 112 | jled.brightness_eval_) != nullptr); 113 | CHECK(jled.brightness_eval_->Eval(0) == 0); 114 | } 115 | } 116 | }; 117 | TestableJLed::test(); 118 | } 119 | 120 | TEST_CASE("using Breathe() configures BreatheBrightnessEvaluator", "[jled]") { 121 | class TestableJLed : public TestJLed { 122 | public: 123 | using TestJLed::TestJLed; 124 | static void test() { 125 | TestableJLed jled(1); 126 | jled.Breathe(100, 200, 300); 127 | REQUIRE(dynamic_cast( 128 | jled.brightness_eval_) != nullptr); 129 | auto eval = dynamic_cast( 130 | jled.brightness_eval_); 131 | CHECK(100 == eval->DurationFadeOn()); 132 | CHECK(200 == eval->DurationOn()); 133 | CHECK(300 == eval->DurationFadeOff()); 134 | } 135 | }; 136 | TestableJLed::test(); 137 | } 138 | 139 | TEST_CASE("using Candle() configures CandleBrightnessEvaluator", "[jled]") { 140 | class TestableJLed : public TestJLed { 141 | public: 142 | using TestJLed::TestJLed; 143 | static void test() { 144 | TestableJLed jled(1); 145 | jled.Candle(1, 2, 3); 146 | REQUIRE(dynamic_cast( 147 | jled.brightness_eval_) != nullptr); 148 | } 149 | }; 150 | TestableJLed::test(); 151 | } 152 | 153 | TEST_CASE("using Fadeon(), FadeOff() configures Fade-BrightnessEvaluators", 154 | "[jled]") { 155 | class TestableJLed : public TestJLed { 156 | public: 157 | using TestJLed::TestJLed; 158 | static void test() { 159 | SECTION("FadeOff() initializes with BreatheBrightnessEvaluator") { 160 | TestableJLed jled(1); 161 | jled.FadeOff(100); 162 | REQUIRE(dynamic_cast( 163 | jled.brightness_eval_) != nullptr); 164 | auto eval = dynamic_cast( 165 | jled.brightness_eval_); 166 | CHECK(0 == eval->DurationFadeOn()); 167 | CHECK(0 == eval->DurationOn()); 168 | CHECK(100 == eval->DurationFadeOff()); 169 | } 170 | SECTION("FadeOn() initializes with BreatheBrightnessEvaluator") { 171 | TestableJLed jled(1); 172 | jled.FadeOn(100); 173 | REQUIRE(dynamic_cast( 174 | jled.brightness_eval_) != nullptr); 175 | auto eval = dynamic_cast( 176 | jled.brightness_eval_); 177 | CHECK(100 == eval->DurationFadeOn()); 178 | CHECK(0 == eval->DurationOn()); 179 | CHECK(0 == eval->DurationFadeOff()); 180 | } 181 | } 182 | }; 183 | TestableJLed::test(); 184 | } 185 | 186 | TEST_CASE("using Fade() configures BreatheBrightnessEvaluator", "[jled]") { 187 | class TestableJLed : public TestJLed { 188 | public: 189 | using TestJLed::TestJLed; 190 | static void test() { 191 | SECTION("fade with from < to") { 192 | TestableJLed jled(1); 193 | jled.Fade(100, 200, 300); // from, to, duration 194 | REQUIRE(dynamic_cast( 195 | jled.brightness_eval_) != nullptr); 196 | auto eval = dynamic_cast( 197 | jled.brightness_eval_); 198 | CHECK(300 == eval->DurationFadeOn()); 199 | CHECK(0 == eval->DurationOn()); 200 | CHECK(0 == eval->DurationFadeOff()); 201 | CHECK(100 == static_cast(eval->From())); 202 | CHECK(200 == static_cast(eval->To())); 203 | } 204 | SECTION("fade with from >= to") { 205 | TestableJLed jled(1); 206 | jled.Fade(200, 100, 300); 207 | REQUIRE(dynamic_cast( 208 | jled.brightness_eval_) != nullptr); 209 | auto eval = dynamic_cast( 210 | jled.brightness_eval_); 211 | CHECK(0 == eval->DurationFadeOn()); 212 | CHECK(0 == eval->DurationOn()); 213 | CHECK(300 == eval->DurationFadeOff()); 214 | CHECK(100 == static_cast(eval->From())); 215 | CHECK(200 == static_cast(eval->To())); 216 | } 217 | } 218 | }; 219 | TestableJLed::test(); 220 | } 221 | TEST_CASE("UserFunc() allows to use a custom brightness evaluator", "[jled]") { 222 | class TestableJLed : public TestJLed { 223 | public: 224 | using TestJLed::TestJLed; 225 | static void test() { 226 | TestableJLed jled(1); 227 | auto cust = MockBrightnessEvaluator(ByteVec{}); 228 | jled.UserFunc(&cust); 229 | REQUIRE(dynamic_cast( 230 | jled.brightness_eval_) != nullptr); 231 | } 232 | }; 233 | TestableJLed::test(); 234 | } 235 | 236 | TEST_CASE("ConstantBrightnessEvaluator returns constant provided value", 237 | "[jled]") { 238 | auto cbZero = ConstantBrightnessEvaluator(0); 239 | CHECK(1 == cbZero.Period()); 240 | CHECK(0 == cbZero.Eval(0)); 241 | CHECK(0 == cbZero.Eval(1000)); 242 | 243 | auto cbFull = ConstantBrightnessEvaluator(255); 244 | CHECK(1 == cbFull.Period()); 245 | CHECK(255 == cbFull.Eval(0)); 246 | CHECK(255 == cbFull.Eval(1000)); 247 | } 248 | 249 | TEST_CASE( 250 | "BlinkBrightnessEvaluator calculates switches between on and off in given " 251 | "time frames", 252 | "[jled]") { 253 | auto eval = BlinkBrightnessEvaluator(10, 5); 254 | CHECK(10 + 5 == eval.Period()); 255 | CHECK(255 == eval.Eval(0)); 256 | CHECK(255 == eval.Eval(9)); 257 | CHECK(0 == eval.Eval(10)); 258 | CHECK(0 == eval.Eval(14)); 259 | } 260 | 261 | TEST_CASE("CandleBrightnessEvaluator simulated candle flickering", "[jled]") { 262 | auto eval = CandleBrightnessEvaluator(7, 15, 1000); 263 | CHECK(1000 == eval.Period()); 264 | CHECK(eval.Eval(0) > 0); 265 | CHECK(eval.Eval(999) > 0); 266 | } 267 | 268 | TEST_CASE( 269 | "BreatheEvaluator evaluates to bell curve distributed brightness curve", 270 | "[jled]") { 271 | auto eval = BreatheBrightnessEvaluator(100, 200, 300); 272 | CHECK(100 + 200 + 300 == eval.Period()); 273 | 274 | const std::map test_values = { 275 | {0, 0}, {50, 68}, {80, 198}, {99, 255}, {100, 255}, 276 | {299, 255}, {300, 255}, {399, 138}, {499, 26}, {599, 0}}; 277 | 278 | for (const auto &x : test_values) { 279 | INFO("t=" << x.first); 280 | CHECK((int)x.second == (int)eval.Eval(x.first)); 281 | } 282 | } 283 | 284 | TEST_CASE("Forever flag is initially set to false", "[jled]") { 285 | TestJLed jled(1); 286 | CHECK_FALSE(jled.IsForever()); 287 | } 288 | 289 | TEST_CASE("Forever flag is set by call to Forever()", "[jled]") { 290 | TestJLed jled(1); 291 | jled.Forever(); 292 | CHECK(jled.IsForever()); 293 | } 294 | 295 | TEST_CASE("dont evaluate twice during one time tick", "[jled]") { 296 | auto eval = MockBrightnessEvaluator(ByteVec{0, 1, 2}); 297 | TestJLed jled = TestJLed(1).UserFunc(&eval); 298 | 299 | jled.Update(0, nullptr); 300 | CHECK(eval.Count() == 1); 301 | jled.Update(0, nullptr); 302 | CHECK(eval.Count() == 1); 303 | jled.Update(1); 304 | 305 | CHECK(eval.Count() == 2); 306 | } 307 | 308 | TEST_CASE("Handles millis overflow during effect", "[jled]") { 309 | TestJLed jled = TestJLed(10); 310 | // Set time close to overflow 311 | auto time = std::numeric_limits::max() - 25; 312 | CHECK_FALSE(jled.Update(time)); 313 | // Start fade off 314 | jled.FadeOff(100); 315 | CHECK(jled.Update(time)); 316 | CHECK(jled.IsRunning()); 317 | CHECK(jled.Hal().Value() > 0); 318 | // Set time after overflow, before effect ends 319 | CHECK(jled.Update(time + 50)); 320 | CHECK(jled.IsRunning()); 321 | CHECK(jled.Hal().Value() > 0); 322 | // Set time after effect ends 323 | CHECK_FALSE(jled.Update(time + 150)); 324 | CHECK_FALSE(jled.IsRunning()); 325 | CHECK(0 == jled.Hal().Value()); 326 | } 327 | 328 | TEST_CASE("Update returns last written value if requested", "[jled]") { 329 | auto eval = MockBrightnessEvaluator(ByteVec{0, 10}); 330 | int16_t lastVal = -1; 331 | TestJLed jled = TestJLed(1).UserFunc(&eval); 332 | 333 | jled.Update(0, &lastVal); 334 | CHECK(lastVal == 0); 335 | 336 | jled.Update(1, &lastVal); 337 | CHECK(lastVal == 10); 338 | } 339 | 340 | TEST_CASE("Update doesn't change last value ptr if not updated", "[jled]") { 341 | auto eval = MockBrightnessEvaluator(ByteVec{0, 10}); 342 | int16_t lastVal = -1; 343 | TestJLed jled = TestJLed(1).UserFunc(&eval).DelayBefore(1); 344 | 345 | jled.Update(0, &lastVal); 346 | CHECK(lastVal == -1); 347 | 348 | jled.Update(5, &lastVal); 349 | CHECK(lastVal == 10); 350 | 351 | lastVal = -1; 352 | jled.Update(5, &lastVal); 353 | CHECK(lastVal == -1); 354 | } 355 | 356 | TEST_CASE("Stop() stops the effect", "[jled]") { 357 | auto eval = MockBrightnessEvaluator(ByteVec{255, 255, 255, 0}); 358 | TestJLed jled = TestJLed(10).UserFunc(&eval); 359 | 360 | REQUIRE(jled.IsRunning()); 361 | jled.Update(); 362 | jled.Stop(); 363 | 364 | CHECK(!jled.IsRunning()); 365 | } 366 | 367 | TEST_CASE("default Stop() sets the brightness to minBrightness", "[jled]") { 368 | auto eval = MockBrightnessEvaluator(ByteVec{100, 0}); 369 | TestJLed jled = TestJLed(10).UserFunc(&eval).MinBrightness(50); 370 | 371 | jled.Update(); 372 | REQUIRE(130 == 373 | static_cast(jled.Hal().Value())); // 100 scaled to [50,255] 374 | 375 | jled.Stop(); 376 | CHECK(50 == static_cast(jled.Hal().Value())); 377 | } 378 | 379 | TEST_CASE("Stop(FULL_OFF) sets the brightness to 0", "[jled]") { 380 | auto eval = MockBrightnessEvaluator(ByteVec{100, 0}); 381 | TestJLed jled = TestJLed(10).UserFunc(&eval).MinBrightness(50); 382 | 383 | jled.Update(); 384 | REQUIRE(130 == 385 | static_cast(jled.Hal().Value())); // 100 scaled to [50,255] 386 | 387 | jled.Stop(TestJLed::eStopMode::FULL_OFF); 388 | CHECK(0 == static_cast(jled.Hal().Value())); 389 | } 390 | 391 | TEST_CASE("Stop(KEEP_CURRENT) keeps the last brightness level", "[jled]") { 392 | auto eval = MockBrightnessEvaluator(ByteVec{100, 101}); 393 | TestJLed jled = TestJLed(10).UserFunc(&eval).MinBrightness(50); 394 | 395 | jled.Update(); 396 | REQUIRE(130 == 397 | static_cast(jled.Hal().Value())); // 100 scaled to [50,255] 398 | 399 | jled.Stop(TestJLed::eStopMode::KEEP_CURRENT); 400 | CHECK(130 == static_cast(jled.Hal().Value())); 401 | } 402 | 403 | TEST_CASE("LowActive() inverts signal", "[jled]") { 404 | auto eval = MockBrightnessEvaluator(ByteVec{0, 255}); 405 | TestJLed jled = TestJLed(1).UserFunc(&eval).LowActive(); 406 | 407 | CHECK(jled.IsLowActive()); 408 | 409 | jled.Update(0, nullptr); 410 | CHECK(255 == jled.Hal().Value()); 411 | 412 | jled.Update(1); 413 | CHECK(0 == jled.Hal().Value()); 414 | } 415 | 416 | TEST_CASE("effect with repeat 2 repeats sequence once", "[jled]") { 417 | auto eval = MockBrightnessEvaluator(ByteVec{10, 20}); 418 | TestJLed jled = TestJLed(10).UserFunc(&eval).Repeat(2); 419 | 420 | typedef UpdateResult u; 421 | const UpdateResults expected = {u{true, 10}, u{true, 20}, u{true, 10}, 422 | u{false, 20}, u{false, 20}, u{false, 20}}; 423 | 424 | check_led(&jled, expected); 425 | } 426 | 427 | TEST_CASE("effect with delay after delays start of next iteration", "[jled]") { 428 | auto eval = MockBrightnessEvaluator(ByteVec{10, 20}); 429 | TestJLed jled = TestJLed(10).UserFunc(&eval).Repeat(2).DelayAfter(2); 430 | 431 | typedef UpdateResult u; 432 | const UpdateResults expected = { 433 | u{true, 10}, u{true, 20}, u{true, 20}, u{true, 20}, u{true, 10}, 434 | u{true, 20}, u{true, 20}, u{false, 20}, u{false, 20}, u{false, 20}}; 435 | 436 | check_led(&jled, expected); 437 | } 438 | 439 | TEST_CASE("effect with delay before has delayed start ", "[jled]") { 440 | auto eval = MockBrightnessEvaluator(ByteVec{10, 20}); 441 | TestJLed jled = TestJLed(10).UserFunc(&eval).DelayBefore(2); 442 | 443 | typedef UpdateResult u; 444 | const UpdateResults expected = {u{true, 0}, u{true, 0}, u{true, 10}, 445 | u{false, 20}, u{false, 20}, u{false, 20}}; 446 | 447 | check_led(&jled, expected); 448 | } 449 | 450 | TEST_CASE("After calling Forever() the effect is repeated over and over again ", 451 | "[jled]") { 452 | auto eval = MockBrightnessEvaluator(ByteVec{10, 20}); 453 | TestJLed jled = TestJLed(10).UserFunc(&eval).Forever(); 454 | 455 | typedef UpdateResult u; 456 | const UpdateResults expected = {u{true, 10}, u{true, 20}, u{true, 10}, 457 | u{true, 20}, u{true, 10}, u{true, 20}}; 458 | 459 | check_led(&jled, expected); 460 | } 461 | 462 | TEST_CASE("The Hal object provided in the ctor is used during update", 463 | "[jled]") { 464 | auto eval = MockBrightnessEvaluator(ByteVec{10, 20}); 465 | TestJLed jled = TestJLed(HalMock(123)).UserFunc(&eval); 466 | 467 | CHECK(jled.Hal().Pin() == 123); 468 | } 469 | 470 | TEST_CASE("Update returns true while updating, else false", "[jled]") { 471 | auto eval = MockBrightnessEvaluator(ByteVec{10, 20}); 472 | TestJLed jled = TestJLed(10).UserFunc(&eval); 473 | 474 | // Update returns FALSE on last step and beyond, else TRUE 475 | CHECK(jled.Update(0, nullptr)); 476 | 477 | // when effect is done, we expect still false to be returned 478 | CHECK_FALSE(jled.Update(1)); 479 | CHECK_FALSE(jled.Update(2)); 480 | } 481 | 482 | TEST_CASE("After Reset() the effect can be restarted", "[jled]") { 483 | auto eval = MockBrightnessEvaluator(ByteVec{10, 20}); 484 | TestJLed jled = TestJLed(10).UserFunc(&eval); 485 | 486 | typedef UpdateResult u; 487 | 488 | const UpdateResults expected = {u{true, 10}, u{false, 20}, u{false, 20}, 489 | u{false, 20}}; 490 | 491 | check_led(&jled, expected); 492 | 493 | // after Reset() effect starts over 494 | jled.Reset(); 495 | 496 | check_led(&jled, expected); 497 | } 498 | 499 | TEST_CASE("Changing the effect resets object and starts over", "[jled]") { 500 | auto eval = MockBrightnessEvaluator(ByteVec{10, 20}); 501 | TestJLed jled = TestJLed(10).UserFunc(&eval); 502 | 503 | typedef UpdateResult u; 504 | const UpdateResults expected = {u{true, 10}, u{false, 20}, u{false, 20}}; 505 | 506 | check_led(&jled, expected); 507 | 508 | // expect to start over after changing effect. 509 | jled.UserFunc(&eval); 510 | check_led(&jled, expected); 511 | } 512 | 513 | TEST_CASE("Max brightness level is initialized to 255", "[jled]") { 514 | TestJLed jled(10); 515 | CHECK(255 == jled.MaxBrightness()); 516 | } 517 | 518 | TEST_CASE("Previously max brightness level can be read back", "[jled]") { 519 | TestJLed jled(10); 520 | jled.MaxBrightness(100); 521 | CHECK(100 == jled.MaxBrightness()); 522 | } 523 | 524 | TEST_CASE("Min brightness level is initialized to 0", "[jled]") { 525 | TestJLed jled(10); 526 | CHECK(0 == jled.MinBrightness()); 527 | } 528 | 529 | TEST_CASE("Previously set min brightness level can be read back", "[jled]") { 530 | TestJLed jled(10); 531 | 532 | jled.MinBrightness(100); 533 | CHECK(100 == jled.MinBrightness()); 534 | } 535 | 536 | TEST_CASE( 537 | "Setting min and max brightness levels scales evaluated effect values", 538 | "[jled]") { 539 | class TestableJLed : public TestJLed { 540 | public: 541 | using TestJLed::TestJLed; 542 | static void test() { 543 | TestableJLed jled(1); 544 | auto eval = MockBrightnessEvaluator(ByteVec{0, 128, 255}); 545 | jled.UserFunc(&eval).MinBrightness(100).MaxBrightness(200); 546 | 547 | jled.Update(0, nullptr); 548 | CHECK(100 == jled.Hal().Value()); 549 | 550 | jled.Update(2, nullptr); 551 | CHECK(200 == jled.Hal().Value()); 552 | } 553 | }; 554 | TestableJLed::test(); 555 | }; 556 | 557 | TEST_CASE("timeChangeSinceLastUpdate detects time changes", "[jled]") { 558 | class TestableJLed : public TestJLed { 559 | public: 560 | using TestJLed::TestJLed; 561 | static void test() { 562 | TestableJLed jled(1); 563 | 564 | jled.trackLastUpdateTime(1000); 565 | CHECK_FALSE(jled.timeChangedSinceLastUpdate(1000)); 566 | CHECK(jled.timeChangedSinceLastUpdate(1001)); 567 | } 568 | }; 569 | TestableJLed::test(); 570 | } 571 | 572 | TEST_CASE("random generator delivers pseudo random numbers", "[rand]") { 573 | jled::rand_seed(0); 574 | CHECK(0x59 == jled::rand8()); 575 | CHECK(0x159 >> 1 == jled::rand8()); 576 | } 577 | 578 | TEST_CASE("scaling a value with factor 0 scales it to 0", "[scale8]") { 579 | CHECK(0 == jled::scale8(0, 0)); 580 | CHECK(0 == jled::scale8(255, 0)); 581 | } 582 | 583 | TEST_CASE("scaling a value with factor 127 halfes the value", "[scale8]") { 584 | CHECK(0 == jled::scale8(0, 128)); 585 | CHECK(50 == jled::scale8(100, 128)); 586 | CHECK(128 == jled::scale8(255, 128)); 587 | } 588 | 589 | TEST_CASE("scaling a value with factor 255 returns original value", 590 | "[scale8]") { 591 | CHECK(0 == jled::scale8(0, 255)); 592 | CHECK(127 == jled::scale8(127, 255)); 593 | CHECK(255 == jled::scale8(255, 255)); 594 | } 595 | 596 | TEST_CASE("lerp8by8 interpolates a byte into the given interval", 597 | "[lerp8by8]") { 598 | CHECK(0 == (int)(jled::lerp8by8(0, 0, 255))); 599 | CHECK(0 == (int)(jled::lerp8by8(255, 0, 0))); 600 | CHECK(255 == (int)(jled::lerp8by8(255, 0, 255))); 601 | 602 | CHECK(100 == (int)(jled::lerp8by8(0, 100, 255))); 603 | CHECK(100 == (int)(jled::lerp8by8(0, 100, 110))); 604 | 605 | CHECK(255 == (int)(jled::lerp8by8(255, 100, 255))); 606 | CHECK(200 == (int)(jled::lerp8by8(255, 100, 200))); 607 | } 608 | 609 | TEST_CASE("invlerp8by8 is the inverse of lerp8by8", "[invlerp8by8]") { 610 | CHECK(0 == (int)(jled::invlerp8by8(0, 0, 255))); 611 | CHECK(255 == (int)(jled::invlerp8by8(255, 0, 255))); 612 | 613 | CHECK(0 == (int)(jled::invlerp8by8(100, 100, 200))); 614 | CHECK(255 == (int)(jled::invlerp8by8(200, 100, 200))); 615 | } 616 | -------------------------------------------------------------------------------- /test/test_jled_sequence.cpp: -------------------------------------------------------------------------------- 1 | // JLed Unit tests (run on host). 2 | // Copyright 2017-2021 Jan Delgado jdelgado@gmx.net 3 | #include "catch2/catch_amalgamated.hpp" 4 | 5 | #include // NOLINT 6 | #include "hal_mock.h" // NOLINT 7 | 8 | using jled::TJLed; 9 | using jled::TJLedSequence; 10 | 11 | // TestJLed is a JLed class using the HalMock for tests. This allows to 12 | // test the code abstracted from the actual hardware in use. 13 | class TestJLed : public TJLed { 14 | using TJLed::TJLed; 15 | }; 16 | 17 | // a group of JLed objects which can be controlled simultanously 18 | class TestJLedSequence : public TJLedSequence { 19 | using TJLedSequence::TJLedSequence; 20 | }; 21 | 22 | // instanciate for test coverage measurement 23 | template class TJLed; 24 | template class TJLedSequence; 25 | 26 | TEST_CASE("parallel sequence performs all updates", "[jled_sequence]") { 27 | constexpr uint8_t expected1[] = {255, 0, 0}; 28 | constexpr uint8_t expected2[] = {0, 255, 255}; 29 | REQUIRE(sizeof(expected1) == sizeof(expected2)); // enter criteria for test 30 | 31 | TestJLed leds[] = {TestJLed(HalMock(1)).Blink(1, 1).Repeat(1), 32 | TestJLed(HalMock(2)).Blink(1, 1).Repeat(1).LowActive()}; 33 | TestJLedSequence seq(TestJLedSequence::eMode::PARALLEL, leds); 34 | uint32_t time = 0; 35 | for (auto i = 0u; i < 3; i++) { 36 | auto res = seq.Update(); 37 | // Update() returns false on last Update, our example does 2 updates. 38 | REQUIRE(res == (i < 1)); 39 | REQUIRE(expected1[i] == leds[0].Hal().Value()); 40 | REQUIRE(expected2[i] == leds[1].Hal().Value()); 41 | time++; 42 | leds[0].Hal().SetMillis(time); 43 | leds[1].Hal().SetMillis(time); 44 | } 45 | } 46 | 47 | TEST_CASE("sequence performs all updates", "[jled_sequence]") { 48 | constexpr uint8_t expected1[] = {255, // first led turns on ... 49 | 0, // and off 50 | 0, 0, 0}; 51 | constexpr uint8_t expected2[] = {0, 0, 52 | 255, // second led turns on ... 53 | 0, 0}; // and off 54 | REQUIRE(sizeof(expected1) == sizeof(expected2)); // enter criteria for test 55 | 56 | TestJLed leds[] = {TestJLed(HalMock(1)).Blink(1, 1).Repeat(1), 57 | TestJLed(HalMock(2)).Blink(1, 1).Repeat(1)}; 58 | TestJLedSequence seq(TestJLedSequence::eMode::SEQUENCE, leds); 59 | 60 | uint32_t time = 0; 61 | auto constexpr kSteps = sizeof(expected1) / sizeof(expected1[0]); 62 | for (auto i = 0u; i < kSteps; i++) { 63 | auto res = seq.Update(); 64 | INFO("time = " << time << ", i = " << i << ",res=" << res); 65 | // Update() returns false on last Update. This example does only 66 | // 4 updates in total, returning false starting from update 3 67 | REQUIRE(res == (i < 3)); 68 | REQUIRE(expected1[i] == leds[0].Hal().Value()); 69 | REQUIRE(expected2[i] == leds[1].Hal().Value()); 70 | time++; 71 | leds[0].Hal().SetMillis(time); 72 | leds[1].Hal().SetMillis(time); 73 | } 74 | } 75 | 76 | TEST_CASE("stop on sequence stops all JLeds and turns them off", 77 | "[jled_sequence]") { 78 | auto mode = GENERATE(TestJLedSequence::eMode::SEQUENCE, 79 | TestJLedSequence::eMode::PARALLEL); 80 | SECTION("all leds are stopped and turned off") { 81 | INFO("mode = " << mode); 82 | TestJLed leds[] = {TestJLed(HalMock(1)).Blink(100, 100)}; 83 | TestJLedSequence seq(mode, leds); 84 | 85 | seq.Update(); 86 | REQUIRE(255 == leds[0].Hal().Value()); 87 | seq.Stop(); 88 | REQUIRE(0 == leds[0].Hal().Value()); 89 | REQUIRE(!leds[0].IsRunning()); 90 | } 91 | } 92 | 93 | TEST_CASE("sequence will stay off after stop when update is called" 94 | " again (https://github.com/jandelgado/jled/issues/115)", 95 | "[jled_sequence]") { 96 | auto mode = GENERATE(TestJLedSequence::eMode::SEQUENCE, 97 | TestJLedSequence::eMode::PARALLEL); 98 | SECTION("sequence stays off") { 99 | INFO("mode = " << mode); 100 | TestJLed leds[] = {TestJLed(HalMock(1)).On()}; 101 | auto seq = TestJLedSequence(mode, leds).Forever(); 102 | 103 | REQUIRE(seq.Update()); 104 | seq.Stop(); 105 | REQUIRE(!leds[0].IsRunning()); 106 | REQUIRE(!seq.Update()); 107 | } 108 | } 109 | 110 | 111 | TEST_CASE("repeat plays the sequence N times", "[jled_sequence]") { 112 | constexpr uint8_t expected[]{255, 0, 255, 0, 0}; 113 | 114 | // TODO(JD): generate also over N 115 | auto mode = GENERATE(TestJLedSequence::eMode::SEQUENCE, 116 | TestJLedSequence::eMode::PARALLEL); 117 | SECTION("repeat plays the sequence N times") { 118 | // 1 ms on, 1 ms off = 2ms off in total per iteration 119 | TestJLed leds[] = {TestJLed(HalMock(1)).Blink(1, 1)}; 120 | auto seq = TestJLedSequence(mode, leds).Repeat(2); 121 | uint32_t time = 0; 122 | for (const auto val : expected) { 123 | INFO("mode = " << mode << ", time = " << time); 124 | seq.Update(); 125 | REQUIRE(val == leds[0].Hal().Value()); 126 | leds[0].Hal().SetMillis(++time); 127 | } 128 | REQUIRE(!seq.Update()); 129 | } 130 | } 131 | 132 | TEST_CASE("Forever seems to play the sequence forever", "[jled_sequence]") { 133 | constexpr uint8_t expected[]{255, 0, 0}; 134 | constexpr auto num = sizeof(expected) / sizeof(expected[0]); 135 | 136 | auto mode = GENERATE(TestJLedSequence::eMode::SEQUENCE, 137 | TestJLedSequence::eMode::PARALLEL); 138 | SECTION("forever plays sequence forever") { 139 | INFO("mode = " << mode); 140 | // 1 ms on, 2 ms off in total per iteration 141 | TestJLed leds[] = {TestJLed(HalMock(1)).Blink(1, 2)}; 142 | auto seq = TestJLedSequence(mode, leds).Forever(); 143 | 144 | for (uint32_t time = 0; time < 1000; time++) { 145 | INFO("mode = " << mode << ", time = " << time << ",num=" << num 146 | << ", mo=" << (time % num)); 147 | leds[0].Hal().SetMillis(time); 148 | REQUIRE(seq.Update()); 149 | CHECK(expected[time % num] == leds[0].Hal().Value()); 150 | } 151 | } 152 | } 153 | 154 | TEST_CASE("Forever flag is initially set to false", "[jled_sequence]") { 155 | auto mode = GENERATE(TestJLedSequence::eMode::SEQUENCE, 156 | TestJLedSequence::eMode::PARALLEL); 157 | SECTION("forever flag is initially off") { 158 | INFO("mode = " << mode); 159 | TestJLed leds[] = {TestJLed(HalMock(1)).Blink(1, 1)}; 160 | auto seq = TestJLedSequence(mode, leds); 161 | REQUIRE_FALSE(seq.IsForever()); 162 | } 163 | } 164 | 165 | TEST_CASE("Forever flag is set by call to Forever()", "[jled_sequence]") { 166 | auto mode = GENERATE(TestJLedSequence::eMode::SEQUENCE, 167 | TestJLedSequence::eMode::PARALLEL); 168 | SECTION("forever flag is set") { 169 | INFO("mode = " << mode); 170 | TestJLed leds[] = {TestJLed(HalMock(1)).Blink(1, 1)}; 171 | auto seq = TestJLedSequence(mode, leds).Forever(); 172 | REQUIRE(seq.IsForever()); 173 | } 174 | } 175 | 176 | TEST_CASE("Forever and Repeat calls can be chained", "[jled_sequence]") { 177 | // this is a compile time check only 178 | TestJLed leds[] = {TestJLed(0)}; 179 | TestJLedSequence hal[[gnu::unused]] = 180 | TestJLedSequence(TestJLedSequence::eMode::PARALLEL, leds) 181 | .Repeat(1) 182 | .Forever(); 183 | } 184 | 185 | TEST_CASE("reset on sequence resets all JLeds", "[jled_sequence]") { 186 | constexpr uint8_t expected[]{/* 1ms on */ 255, 187 | /* 1ms off */ 0, 255, 0, 188 | /* finally off */ 0}; 189 | 190 | auto mode = GENERATE(TestJLedSequence::eMode::SEQUENCE, 191 | TestJLedSequence::eMode::PARALLEL); 192 | SECTION("reset all leds in sequence") { 193 | INFO("mode = " << mode); 194 | TestJLed leds[] = {TestJLed(HalMock(1)).Blink(1, 1)}; 195 | TestJLedSequence seq(mode, leds); 196 | 197 | uint32_t time = 0; 198 | for (const auto val : expected) { 199 | seq.Update(); 200 | 201 | REQUIRE(val == leds[0].Hal().Value()); 202 | 203 | ++time; 204 | leds[0].Hal().SetMillis(time); 205 | 206 | if (time == 2) { 207 | // expect sequence to stop after 2ms 208 | REQUIRE(!seq.Update()); 209 | seq.Reset(); 210 | } 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Jan Delgado jdelgado@gmx.net 2 | // common main() for all unit tests 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch2/catch_amalgamated.hpp" 5 | 6 | -------------------------------------------------------------------------------- /test/test_mbed_hal.cpp: -------------------------------------------------------------------------------- 1 | // JLed Unit tests for the mbed_hal class (run on host). 2 | // Copyright 2020 Jan Delgado jdelgado@gmx.net 3 | #include "catch2/catch_amalgamated.hpp" 4 | 5 | #include // NOLINT 6 | #include "mbed.h" // NOLINT 7 | 8 | using jled::MbedHal; 9 | 10 | TEST_CASE("mbed_hal outputs 0 as 0 to the given pin using PwmOut", 11 | "[mbed_hal]") { 12 | mbedMockInit(); 13 | constexpr auto kPin = 5; 14 | auto hal = MbedHal(kPin); 15 | 16 | hal.analogWrite(0); 17 | 18 | REQUIRE(mbedMockGetPinState(kPin) == 0.); 19 | } 20 | 21 | TEST_CASE("mbed_hal outputs 255 as 1.0 to the given pin using PwmOut", 22 | "[mbed_hal]") { 23 | mbedMockInit(); 24 | constexpr auto kPin = 5; 25 | auto hal = MbedHal(kPin); 26 | 27 | hal.analogWrite(255); 28 | 29 | REQUIRE(mbedMockGetPinState(kPin) == 1.); 30 | } 31 | 32 | TEST_CASE("mbed_hal writes scaled value to the given pin using PwmOut", 33 | "[mbed_hal]") { 34 | mbedMockInit(); 35 | constexpr auto kPin = 5; 36 | auto hal = MbedHal(kPin); 37 | 38 | hal.analogWrite(127); 39 | 40 | REQUIRE_THAT(mbedMockGetPinState(kPin), 41 | Catch::Matchers::WithinAbs(127 / 255., 0.0001)); 42 | } 43 | --------------------------------------------------------------------------------