Skip to content

Commit 4553e90

Browse files
authored
feat(led): Update how ISR is installed; move impl to cpp file (#354)
* Make bool for isr service installed static within class, not just constructor * Add `static void uninstall_isr()` method so that users can uninstall the ISR if they want to * Update example to use logger for better output * Update example to show use of `uninstall_isr()` method * Move LED implementation to cpp file
1 parent 32367dd commit 4553e90

File tree

4 files changed

+190
-141
lines changed

4 files changed

+190
-141
lines changed

components/led/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
idf_component_register(
22
INCLUDE_DIRS "include"
3+
SRC_DIRS "src"
34
REQUIRES base_component driver task)

components/led/example/main/led_example.cpp

+12-7
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
using namespace std::chrono_literals;
1212

1313
extern "C" void app_main(void) {
14-
fmt::print("Starting led example!\n");
14+
espp::Logger logger({.tag = "LED Example", .level = espp::Logger::Verbosity::DEBUG});
1515
{
1616
//! [linear led example]
17-
fmt::print("Starting linear led example!\n");
1817
float num_seconds_to_run = 10.0f;
18+
logger.info("Starting linear led example for {:.1f}s!", num_seconds_to_run);
1919
int led_fade_time_ms = 1000;
2020
std::vector<espp::Led::ChannelConfig> led_channels{{
2121
.gpio = 2,
@@ -54,9 +54,10 @@ extern "C" void app_main(void) {
5454

5555
{
5656
//! [breathing led example]
57-
fmt::print("Starting gaussian led example!\n");
5857
float breathing_period = 3.5f; // seconds
5958
float num_periods_to_run = 2.0f;
59+
float num_seconds_to_run = num_periods_to_run * breathing_period;
60+
logger.info("Starting gaussian led example for {:.1f}s!", num_seconds_to_run);
6061
std::vector<espp::Led::ChannelConfig> led_channels{{
6162
.gpio = 2,
6263
.channel = LEDC_CHANNEL_5,
@@ -86,13 +87,17 @@ extern "C" void app_main(void) {
8687
auto led_task =
8788
espp::Task::make_unique({.callback = led_callback, .task_config = {.name = "breathe"}});
8889
led_task->start();
89-
float wait_time = num_periods_to_run * breathing_period;
90-
fmt::print("Sleeping for {:.1f}s...\n", wait_time);
91-
std::this_thread::sleep_for(wait_time * 1.0s);
90+
logger.debug("Sleeping for {:.1f}s...", num_seconds_to_run);
91+
std::this_thread::sleep_for(num_seconds_to_run * 1.0s);
9292
//! [breathing led example]
9393
}
9494

95-
fmt::print("LED example complete!\n");
95+
// now uninstall the fade service to free up the ISR, since we have no LEDs
96+
// using it anymore
97+
logger.info("Uninstalling LED ISR...");
98+
espp::Led::uninstall_isr();
99+
100+
logger.info("LED example complete!");
96101

97102
while (true) {
98103
std::this_thread::sleep_for(1s);

components/led/include/led.hpp

+20-134
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <algorithm>
44
#include <cmath>
5+
#include <mutex>
56
#include <optional>
67
#include <vector>
78

@@ -65,79 +66,20 @@ class Led : public BaseComponent {
6566
* @brief Initialize the LEDC subsystem according to the configuration.
6667
* @param config The configuration structure for the LEDC subsystem.
6768
*/
68-
explicit Led(const Config &config) noexcept
69-
: BaseComponent("Led", config.log_level)
70-
, duty_resolution_(config.duty_resolution)
71-
, max_raw_duty_((uint32_t)(std::pow(2, (int)duty_resolution_) - 1))
72-
, channels_(config.channels) {
73-
74-
logger_.info("Initializing timer");
75-
ledc_timer_config_t ledc_timer;
76-
memset(&ledc_timer, 0, sizeof(ledc_timer));
77-
ledc_timer.duty_resolution = duty_resolution_;
78-
ledc_timer.freq_hz = config.frequency_hz;
79-
ledc_timer.speed_mode = config.speed_mode;
80-
ledc_timer.timer_num = config.timer;
81-
ledc_timer.clk_cfg = config.clock_config;
82-
ledc_timer_config(&ledc_timer);
83-
84-
logger_.info("Initializing channels");
85-
for (const auto &conf : channels_) {
86-
uint32_t actual_duty = std::clamp(conf.duty, 0.0f, 100.0f) * max_raw_duty_ / 100.0f;
87-
ledc_channel_config_t channel_conf;
88-
memset(&channel_conf, 0, sizeof(channel_conf));
89-
channel_conf.channel = conf.channel;
90-
channel_conf.duty = actual_duty;
91-
channel_conf.gpio_num = conf.gpio;
92-
channel_conf.speed_mode = conf.speed_mode;
93-
channel_conf.hpoint = 0;
94-
channel_conf.timer_sel = conf.timer;
95-
channel_conf.flags.output_invert = conf.output_invert;
96-
ledc_channel_config(&channel_conf);
97-
}
98-
99-
logger_.info("Initializing the fade service");
100-
static bool fade_service_installed = false;
101-
if (!fade_service_installed) {
102-
auto install_fn = []() -> esp_err_t { return ledc_fade_func_install(0); };
103-
auto err = espp::task::run_on_core(install_fn, config.isr_core_id);
104-
if (err != ESP_OK) {
105-
logger_.error("install ledc fade service failed {}", esp_err_to_name(err));
106-
}
107-
fade_service_installed = err == ESP_OK;
108-
}
109-
110-
ledc_cbs_t callbacks = {.fade_cb = &Led::cb_ledc_fade_end_event};
111-
112-
// we associate each channel with its own semaphore so that they can be
113-
// faded / controlled independently.
114-
logger_.info("Creating semaphores");
115-
fade_semaphores_.resize(channels_.size());
116-
for (auto &sem : fade_semaphores_) {
117-
sem = xSemaphoreCreateBinary();
118-
// go ahead and give to the semaphores so the functions will work
119-
xSemaphoreGive(sem);
120-
}
121-
for (int i = 0; i < channels_.size(); i++) {
122-
ledc_cb_register(channels_[i].speed_mode, channels_[i].channel, &callbacks,
123-
(void *)fade_semaphores_[i]);
124-
}
125-
}
69+
explicit Led(const Config &config) noexcept;
12670

12771
/**
12872
* @brief Stop the LEDC subsystem and free memory.
12973
*/
130-
~Led() {
131-
// clean up the semaphores
132-
for (auto &sem : fade_semaphores_) {
133-
// take the semaphore (so that we don't delete it until no one is
134-
// blocked on it)
135-
xSemaphoreTake(sem, portMAX_DELAY);
136-
// and delete it
137-
vSemaphoreDelete(sem);
138-
}
139-
ledc_fade_func_uninstall();
140-
}
74+
~Led();
75+
76+
/**
77+
* @brief Uninstall the fade service. This should be called if you want to
78+
* stop the fade service and free up the ISR.
79+
* @note This function should only be called if you are sure that no other
80+
* Led objects are using the fade service.
81+
*/
82+
static void uninstall_isr();
14183

14284
/**
14385
* @brief Can the LED settings can be changed for the channel? If this
@@ -146,30 +88,15 @@ class Led : public BaseComponent {
14688
* @param channel The channel to check
14789
* @return True if the channel settings can be changed, false otherwise
14890
*/
149-
bool can_change(ledc_channel_t channel) {
150-
int index = get_channel_index(channel);
151-
if (index == -1) {
152-
return false;
153-
}
154-
auto &sem = fade_semaphores_[index];
155-
return uxSemaphoreGetCount(sem) == 1;
156-
}
91+
bool can_change(ledc_channel_t channel);
15792

15893
/**
15994
* @brief Get the current duty cycle this channel has.
16095
* @param channel The channel in question
16196
* @return The duty percentage [0.0f, 100.0f] if the channel is managed,
16297
* std::nullopt otherwise
16398
*/
164-
std::optional<float> get_duty(ledc_channel_t channel) const {
165-
int index = get_channel_index(channel);
166-
if (index == -1) {
167-
return {};
168-
}
169-
const auto &conf = channels_[index];
170-
auto raw_duty = ledc_get_duty(conf.speed_mode, conf.channel);
171-
return (float)raw_duty / (float)max_raw_duty_ * 100.0f;
172-
}
99+
std::optional<float> get_duty(ledc_channel_t channel) const;
173100

174101
/**
175102
* @brief Set the duty cycle for this channel.
@@ -178,21 +105,7 @@ class Led : public BaseComponent {
178105
* @param channel The channel to set the duty cycle for.
179106
* @param duty_percent The new duty percentage, [0.0, 100.0].
180107
*/
181-
void set_duty(ledc_channel_t channel, float duty_percent) {
182-
int index = get_channel_index(channel);
183-
if (index == -1) {
184-
return;
185-
}
186-
auto conf = channels_[index];
187-
auto &sem = fade_semaphores_[index];
188-
// ensure that it's not fading if it is
189-
xSemaphoreTake(sem, portMAX_DELAY);
190-
uint32_t actual_duty = std::clamp(duty_percent, 0.0f, 100.0f) * max_raw_duty_ / 100.0f;
191-
ledc_set_duty(conf.speed_mode, conf.channel, actual_duty);
192-
ledc_update_duty(conf.speed_mode, conf.channel);
193-
// make sure others can set this channel now as well
194-
xSemaphoreGive(sem);
195-
}
108+
void set_duty(ledc_channel_t channel, float duty_percent);
196109

197110
/**
198111
* @brief Set the duty cycle for this channel, fading from the current duty
@@ -203,23 +116,12 @@ class Led : public BaseComponent {
203116
* @param duty_percent The new duty percentage to fade to, [0.0, 100.0].
204117
* @param fade_time_ms The number of milliseconds for which to fade.
205118
*/
206-
void set_fade_with_time(ledc_channel_t channel, float duty_percent, uint32_t fade_time_ms) {
207-
int index = get_channel_index(channel);
208-
if (index == -1) {
209-
return;
210-
}
211-
auto conf = channels_[index];
212-
auto &sem = fade_semaphores_[index];
213-
// ensure that it's not fading if it is
214-
xSemaphoreTake(sem, portMAX_DELAY);
215-
uint32_t actual_duty = std::clamp(duty_percent, 0.0f, 100.0f) * max_raw_duty_ / 100.0f;
216-
ledc_set_fade_with_time(conf.speed_mode, conf.channel, actual_duty, fade_time_ms);
217-
ledc_fade_start(conf.speed_mode, conf.channel, LEDC_FADE_NO_WAIT);
218-
// NOTE: we don't give the semaphore back here because that is the job of
219-
// the ISR
220-
}
119+
void set_fade_with_time(ledc_channel_t channel, float duty_percent, uint32_t fade_time_ms);
221120

222121
protected:
122+
static std::mutex fade_service_mutex;
123+
static bool fade_service_installed;
124+
223125
/**
224126
* @brief Get the index of channel in channels_, -1 if not found.
225127
* @note We implement this instead of using std::find because we cannot use
@@ -228,30 +130,14 @@ class Led : public BaseComponent {
228130
* @param channel Channel to find.
229131
* @return -1 if not found, index of channel if found.
230132
*/
231-
int get_channel_index(ledc_channel_t channel) const {
232-
for (int i = 0; i < channels_.size(); i++) {
233-
if (channels_[i].channel == channel) {
234-
return i;
235-
}
236-
}
237-
return -1;
238-
}
133+
int get_channel_index(ledc_channel_t channel) const;
239134

240135
/**
241136
* This callback function will be called when fade operation has ended
242137
* Use callback only if you are aware it is being called inside an ISR
243138
* Otherwise, you can use a semaphore to unblock tasks
244139
*/
245-
static bool IRAM_ATTR cb_ledc_fade_end_event(const ledc_cb_param_t *param, void *user_arg) {
246-
portBASE_TYPE taskAwoken = pdFALSE;
247-
248-
if (param->event == LEDC_FADE_END_EVT) {
249-
SemaphoreHandle_t sem = (SemaphoreHandle_t)user_arg;
250-
xSemaphoreGiveFromISR(sem, &taskAwoken);
251-
}
252-
253-
return (taskAwoken == pdTRUE);
254-
}
140+
static bool cb_ledc_fade_end_event(const ledc_cb_param_t *param, void *user_arg);
255141

256142
ledc_timer_bit_t duty_resolution_;
257143
uint32_t max_raw_duty_;

0 commit comments

Comments
 (0)