Skip to content

Commit

Permalink
ESP32: use esp-idf api to control led (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
jandelgado committed Feb 15, 2022
1 parent c709871 commit 075d120
Show file tree
Hide file tree
Showing 19 changed files with 664 additions and 330 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# JLed changelog (github.com/jandelgado/jled)

## [2022-02-13] 4.9.0

* new: support ESP-IDF platform for the ESP32 (#87, thanks to @troky for the
initial work). See also repositories
https://github.com/jandelgado/jled-esp-idf-example and
https://github.com/jandelgado/jled-esp-idf-platformio-example

## [2021-10-18] 4.8.0

* new: make `Breathe` method more flexible (#78, thanks to @boraozgen)
Expand Down
15 changes: 12 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# this CMakeLists.txt file is needed to include JLed in raspi pico projects
add_library (JLed src/jled_base.cpp)
target_include_directories (JLed PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
if(IDF_VER) # ESP-IDF component (ESP32)

idf_component_register(
SRCS "src/jled_base.cpp" "src/esp32_hal.cpp"
INCLUDE_DIRS "src")

idf_build_set_property(COMPILE_OPTIONS "-DESP32" APPEND)
set_target_properties(${TARGET} PROPERTIES LINKER_LANGUAGE CXX)

else() # Raspberry Pi Pico
add_library (JLed src/jled_base.cpp)
target_include_directories (JLed PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
endif()
55 changes: 45 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,18 @@ control LEDs in simple (**on**/**off**) and complex (**blinking**,
JLed got some [coverage on Hackaday](https://hackaday.com/2018/06/13/simplifying-basic-led-effects/)
and someone did a [video tutorial for JLed](https://youtu.be/x5V2vdpZq1w) - Thanks!

JLed in action | Interactive JLed playground
:-------------:|:--------------------------------------------------:
<a href="examples/multiled"><img alt="breathing, blinking, fadeon and -off at the same time" height=200 src="doc/jled.gif"></a>|<a href="https://jandelgado.github.io/jled-wasm"><img alt="jled running in the browser" height=200 src="doc/jled-wasm.png"></a>
<table>
<tr>
<th>JLed in action</th>
<th>Interactive JLed playground</th>
</tr>
<tr>
<td><a href="examples/multiled"><img alt="JLed in action" src="doc/jled.gif" style="width: 280px"></a></td>
<td><a href="https://jandelgado.github.io/jled-wasm"><img alt="jled running in the browser" src="doc/jled-wasm.png" style="width: 300px"></a>
</td>
</tr>
</table>


## Example

Expand Down Expand Up @@ -70,6 +79,7 @@ void loop() {
* [Platform notes](#platform-notes)
* [ESP8266](#esp8266)
* [ESP32](#esp32)
* [Using ESP-IDF](#using-esp-idf)
* [STM32](#stm32)
* [Arduino framework](#arduino-framework)
* [Raspberry Pi Pico](#raspberry-pi-pico)
Expand Down Expand Up @@ -98,7 +108,9 @@ void loop() {
* can control groups of LEDs sequentially or in parallel
* Portable: Arduino, ESP8266, ESP32, Mbed, Raspberry Pi Pico and more platforms
compatible, runs even in the [browser](https://jandelgado.github.io/jled-wasm)
* supports Arduino, [mbed](https://www.mbed.com) and Raspberry Pi Pico SDKs
* supports Arduino, [mbed](https://www.mbed.com), [Raspberry Pi
Pico](https://github.com/raspberrypi/pico-sdk) and ESP32
[ESP-IDF](https://www.espressif.com/en/products/sdks/esp-idf) SDK's
* well [tested](https://coveralls.io/github/jandelgado/jled)

## Cheat Sheet
Expand Down Expand Up @@ -463,9 +475,10 @@ JLed transparently for the application, yielding platform-independent code.

### ESP32

The ESP32 Arduino SDK does not provide an `analogWrite()` function. To
be able to use PWM, we use the `ledc` functions of the ESP32 SDK.
(See [esspressif documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/ledc.html) for details).
When compiling for the ESP32, JLed uses `ledc` functions provided by the ESP32
ESP-IDF SDK. (See [esspressif
documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/ledc.html)
for details).

The `ledc` API connects so-called channels to GPIO pins, enabling them to use
PWM. There are 16 channels available. Unless otherwise specified, JLed
Expand All @@ -479,8 +492,28 @@ auto esp32Led = JLed(jled::Esp32Hal(2, 7)).Blink(1000, 1000).Forever();

The `jled::Esp32Hal(pin, chan)` constructor takes the pin number as the first
argument and the ESP32 ledc channel number on the second position. Note that
using the above-mentioned constructor yields non-platform independent code, so
it should be avoided and is normally not necessary.
using the above-mentioned constructor results in non-platform independent code,
so it should be avoided and is normally not necessary.

For completeness, the full signature of the Esp32Hal constructor is

```
Esp32Hal(PinType pin,
int chan = kAutoSelectChan,
uint16_t freq = 5000,
ledc_timer_t timer = LEDC_TIMER_0)
```

which also allows to override the default frequency and timer used, when needed.

#### Using ESP-IDF

Since JLed uses the ESP-IDF SDK, JLed can also be directly used in ESP-IDF
projects, without the need of using the Arduino Framework (which is also
possible). See these repositories for example projects:

* https://github.com/jandelgado/jled-esp-idf-example
* https://github.com/jandelgado/jled-esp-idf-platformio-example

### STM32

Expand Down Expand Up @@ -515,8 +548,10 @@ Example sketches are provided in the [examples](examples/) directory.
* [Simple User provided effect](examples/user_func)
* [Morsecode example](examples/morse)
* [Custom HAL example](examples/custom_hal)
* [JLed compiled for WASM and running in the browser](https://jandelgado.github.io/jled-wasm)
* [JLed compiled to WASM and running in the browser](https://jandelgado.github.io/jled-wasm)
* [Raspberry Pi Pico Demo](examples/raspi_pico)
* [ESP32 ESP-IDF example](https://github.com/jandelgado/jled-esp-idf-example)
* [ESP32 ESP-IDF PlatformIO example](https://github.com/jandelgado/jled-esp-idf-platformio-example)

### PlatformIO

Expand Down
Binary file modified doc/jled.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions library.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "JLed",
"version": "4.9.0",
"description": "An embedded library to control LEDs",
"license": "MIT",
"frameworks": ["espidf", "arduino", "mbed"],
"repository": {
"type": "git",
"url": "https://github.com/jandelgado/jled"
},
"authors": {
"name": "Jan Delgado",
"email": "jdelgado[at]gmx.net",
"maintainer": true
}
}
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=JLed
version=4.8.0
version=4.9.0
author=Jan Delgado <jdelgado[at]gmx.net>
maintainer=Jan Delgado <jdelgado[at]gmx.net>
sentence=An Arduino library to control LEDs
Expand Down
95 changes: 70 additions & 25 deletions src/esp32_hal.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
// Copyright (c) 2017-2020 Jan Delgado <jdelgado[at]gmx.net>
// Copyright (c) 2017-2022 Jan Delgado <jdelgado[at]gmx.net>
// https://github.com/jandelgado/jled
// HAL for the ESP32
//
// HAL for the ESP32 compatible with Arduino and ESP-IDF framework. Uses
// ESP-IDF SDK under the hood.
//
// Documentation:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html
//
// Inspiration from:
// https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-ledc.c
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
Expand All @@ -24,7 +31,9 @@
#ifndef SRC_ESP32_HAL_H_
#define SRC_ESP32_HAL_H_

#include <Arduino.h>
#include <driver/ledc.h>
#include <esp_timer.h>
#include <stdint.h>

namespace jled {

Expand All @@ -39,23 +48,24 @@ class Esp32ChanMapper {
Esp32ChanMapper() {
for (auto i = 0; i < kLedcMaxChan; i++) chanMap_[i] = 0xff;
}
PinType chanForPin(PinType pin) {

ledc_channel_t chanForPin(PinType pin) {
// find existing channel for given pin
for (auto i = 0; i < kLedcMaxChan; i++) {
if (chanMap_[i] == pin) return i;
if (chanMap_[i] == pin) return (ledc_channel_t)i;
}
// find and return first free slot
for (auto i = 0; i < kLedcMaxChan; i++) {
if (chanMap_[i] == kFreeChan) {
chanMap_[i] = pin;
return i;
return (ledc_channel_t)i;
}
}
// no more free slots, start over
auto i = nextChan_;
const auto i = nextChan_;
chanMap_[i] = pin;
nextChan_ = (nextChan_ + 1) % kLedcMaxChan;
return i;
return (ledc_channel_t)i;
}

private:
Expand All @@ -64,38 +74,73 @@ class Esp32ChanMapper {
};

class Esp32Hal {
static constexpr auto kLedcTimer8Bit = 8;
static constexpr auto kLedcTimerResolution = LEDC_TIMER_8_BIT;
static constexpr auto kLedcSpeedMode = LEDC_LOW_SPEED_MODE;

public:
using PinType = Esp32ChanMapper::PinType;

static constexpr auto kAutoSelectChan = -1;

// construct an ESP32 analog write object connected to the given pin.
// chan specifies the EPS32 ledc channel to use. If set to kAutoSelectChan,
// the next available channel will be used, otherwise the specified one.
// freq defines the ledc base frequency to be used (default: 5000 Hz).
Esp32Hal(PinType pin, int chan = kAutoSelectChan,
uint16_t freq = 5000) noexcept {
// ESP32 framework lacks analogWrite() support, but behaviour can
// be achievedd using LEDC channels.
// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/ledc.html
// construct an ESP32 analog write object connected
// pin gpio pin to connect to
// chan specifies the EPS32 ledc channel to use. If set to
// kAutoSelectChan,
// the next available channel will be used, otherwise the specified
// one.
// freq defines the ledc base frequency to be used (default: 5000 Hz).
// timer is the ledc timer to use (default: LEDC_TIMER_0). When different
// frequencies are used, also different timers must be used.
Esp32Hal(PinType pin, int chan = kAutoSelectChan, uint16_t freq = 5000,
ledc_timer_t timer = LEDC_TIMER_0) noexcept {
chan_ = (chan == kAutoSelectChan)
? Esp32Hal::chanMapper_.chanForPin(pin)
: chan;
::ledcSetup(chan_, freq, kLedcTimer8Bit);
::ledcAttachPin(pin, chan_);
: (ledc_channel_t)chan;

ledc_timer_config_t ledc_timer{};
ledc_timer.speed_mode = kLedcSpeedMode;
ledc_timer.duty_resolution = kLedcTimerResolution;
ledc_timer.timer_num = timer;
ledc_timer.freq_hz = freq;
#if ESP_IDF_VERSION_MAJOR > 3
ledc_timer.clk_cfg = LEDC_AUTO_CLK;
#endif
ledc_timer_config(&ledc_timer);

ledc_channel_t channel = (ledc_channel_t)(chan_ % LEDC_CHANNEL_MAX);
ledc_channel_config_t ledc_channel{};
ledc_channel.gpio_num = pin;
ledc_channel.speed_mode = kLedcSpeedMode;
ledc_channel.channel = channel;
ledc_channel.intr_type = LEDC_INTR_DISABLE;
ledc_channel.timer_sel = timer;
ledc_channel.duty = 0;
ledc_channel.hpoint = 0;
#if ESP_IDF_VERSION_MAJOR > 4
ledc_channel.flags.output_invert = 0;
#endif
ledc_channel_config(&ledc_channel);
}
void analogWrite(uint8_t val) const {
::ledcWrite(chan_, (val == 255)? 256 : val);

void analogWrite(uint8_t duty) const {
// Fixing if all bits in resolution is set = LEDC FULL ON
const uint32_t _duty = (duty == (1 << kLedcTimerResolution) - 1)
? 1 << kLedcTimerResolution
: duty;

ledc_set_duty(kLedcSpeedMode, chan_, _duty);
ledc_update_duty(kLedcSpeedMode, chan_);
}

uint32_t millis() const {
return (uint32_t)(esp_timer_get_time() / 1000ULL);
}
uint32_t millis() const { return ::millis(); }

PinType chan() const { return chan_; }

private:
static Esp32ChanMapper chanMapper_;
PinType chan_;
ledc_channel_t chan_;
};
} // namespace jled
#endif // SRC_ESP32_HAL_H_
4 changes: 2 additions & 2 deletions src/jled.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ namespace jled {using JLedHalType = PicoHal;}
#elif defined(__MBED__) && !defined(ARDUINO_API_VERSION)
#include "mbed_hal.h" // NOLINT
namespace jled {using JLedHalType = MbedHal;}
#elif ESP32
#elif defined(ESP32)
#include "esp32_hal.h" // NOLINT
namespace jled {using JLedHalType = Esp32Hal;}
#elif ESP8266
#elif defined(ESP8266)
#include "esp8266_hal.h" // NOLINT
namespace jled {using JLedHalType = Esp8266Hal;}
#else
Expand Down
3 changes: 0 additions & 3 deletions test/Arduino.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <math.h>
#include <stdint.h>
#include <cstring>
#include "esp32.h" // NOLINT

constexpr auto ARDUINO_PINS = 32;

Expand All @@ -21,8 +20,6 @@ int arduinoMockGetPinState(uint8_t pin);
uint32_t millis(void);
void arduinoMockSetMillis(uint32_t value);

#define PI 3.1415926535897932384626433832795
#define OUTPUT 0x1


#endif // TEST_ARDUINO_H_
19 changes: 8 additions & 11 deletions test/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# JLed unit tests Makefile
# run `make coverage` to run all test and calculate coverage
CFLAGS=-std=c++14 -c -Wall -Wextra -I. -I../src --coverage -fno-inline \
CFLAGS=-std=c++14 -c -Wall -Wextra -I. -I../src -I./esp-idf \
--coverage -fno-inline \
-fno-inline-small-functions -fno-default-inline -g -fmax-errors=5 \
-fno-omit-frame-pointer -fno-optimize-sibling-calls \
$(OPT)
Expand All @@ -10,16 +11,14 @@ LDFLAGS=-fprofile-arcs -ftest-coverage
TEST_ARDUINO_MOCK_SRC=Arduino.cpp test_arduino_mock.cpp test_main.cpp
TEST_ARDUINO_MOCK_OBJECTS=$(TEST_ARDUINO_MOCK_SRC:.cpp=.o)

TEST_ESP32_MOCK_SRC=esp32.cpp test_esp32_mock.cpp test_main.cpp
TEST_ESP32_MOCK_OBJECTS=$(TEST_ESP32_MOCK_SRC:.cpp=.o)

TEST_JLED_SRC=Arduino.cpp test_jled.cpp test_main.cpp ../src/jled_base.cpp
TEST_JLED_OBJECTS=$(TEST_JLED_SRC:.cpp=.o)

TEST_JLED_SEQUENCE_SRC=Arduino.cpp test_jled_sequence.cpp test_main.cpp ../src/jled_base.cpp
TEST_JLED_SEQUENCE_OBJECTS=$(TEST_JLED_SEQUENCE_SRC:.cpp=.o)

TEST_ESP32_SRC=Arduino.cpp esp32.cpp test_esp32_hal.cpp ../src/esp32_hal.cpp test_main.cpp
TEST_ESP32_SRC=esp-idf/esp_timer.cpp esp-idf/driver/ledc.cpp \
test_esp32_hal.cpp ../src/esp32_hal.cpp test_main.cpp
TEST_ESP32_OBJECTS=$(TEST_ESP32_SRC:.cpp=.o)

TEST_ESP8266_SRC=Arduino.cpp test_esp8266_hal.cpp test_main.cpp
Expand All @@ -35,17 +34,14 @@ TEST_MORSE_SRC=test_example_morse.cpp test_main.cpp
TEST_MORSE_OBJECTS=$(TEST_MORSE_SRC:.cpp=.o)


all: bin bin/test_arduino_mock bin/test_esp32_mock \
all: bin bin/test_arduino_mock \
bin/test_jled bin/test_jled_sequence \
bin/test_esp32_hal bin/test_esp8266_hal bin/test_arduino_hal bin/test_mbed_hal \
bin/test_example_morse

bin/test_arduino_mock: $(TEST_ARDUINO_MOCK_OBJECTS)
$(CXX) -o $@ $(LDFLAGS) $(TEST_ARDUINO_MOCK_OBJECTS)

bin/test_esp32_mock: $(TEST_ESP32_MOCK_OBJECTS)
$(CXX) -o $@ $(LDFLAGS) $(TEST_ESP32_MOCK_OBJECTS)

bin/test_jled: $(TEST_JLED_OBJECTS)
$(CXX) -o $@ $(LDFLAGS) $(TEST_JLED_OBJECTS)

Expand Down Expand Up @@ -103,8 +99,9 @@ clobber: clean
depend: .depend

.depend: $(TEST_JLED_SRC) $(TEST_JLED_SEQUENCE_SRC) $(TEST_ESP32_SRC) $(TEST_ESP8266_SRC) $(TEST_ARDUINO_SRC) $(TEST_MBED_SRC) $(TEST_MORSE_SRC)
rm -f ./.depend
$(CC) -I ../src -I . -MM $^ > .depend
@echo updating dependencies in .depend
@rm -f ./.depend
@$(CC) -I ../src -I . -MM $^ > .depend

include .depend

Loading

0 comments on commit 075d120

Please sign in to comment.