-
-
Notifications
You must be signed in to change notification settings - Fork 345
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial functioning version of ESP32 HardwarePWM. (#2599)
* initial functioning version of ESP32 HardwarePWM. Tested to run on the ESP32C3 with up to 5kHz pwm frequency. * some documentation updates * more documentation * more documentation * implemented requested changes from initial PR * implemented requested changes from initial PR (now *with* changes. Sigh) * Revert "more documentation" This reverts commit 7279c66. * implemented requested changes from initial PR (now *with* changes. after goofing up with git Sigh) * fixed some things I didn't understand at first. * HardwarePWM.h was not part of the last commit * .cpp was also missing. why? * Apply coding style * Rebase, tidy * Tidy Esp8266 implementation, add new 'getFrequency' method * Add missing include * Tidy driver/pwm.h * Use static initializers * minor updates as suggested by mikee47 * minor updates as suggested by mikee47 * removed unused variables * missed one occurrence and also messed up in a merge - cleaned out now * removed a piece of code that was doubled * fixed typos and coding style * removed ToDos from the comments that are to be implemented in the next iteraton as they are already underway * fixed return values * changed some coding style issues in esp8266 code * removed unnecessary and improved useful debug output * corrected the PWM_CHANNEL_NUM_MAX define in pwm.h to reflect the correct SoC config * Small coding style fixes. --------- Co-authored-by: mikee47 <[email protected]> Co-authored-by: Slavey Karadzhov <[email protected]>
- Loading branch information
1 parent
4195b90
commit 5eb7426
Showing
6 changed files
with
332 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,18 @@ | ||
/* | ||
* ESPRESSIF MIT License | ||
/**** | ||
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. | ||
* Created 2015 by Skurydin Alexey | ||
* http://github.com/SmingHub/Sming | ||
* All files of the Sming Core are provided under the LGPL v3 license. | ||
* | ||
* Copyright (c) 2016 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD> | ||
* pwm.h | ||
* | ||
* Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case, | ||
* it is free of charge, to any person obtaining a copy of this software and associated | ||
* documentation files (the "Software"), to deal in the Software without restriction, including | ||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished | ||
* to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in all copies or | ||
* substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
* | ||
*/ | ||
|
||
#ifndef __PWM_H__ | ||
#define __PWM_H__ | ||
****/ | ||
|
||
#pragma once | ||
|
||
#if defined(__cplusplus) | ||
extern "C" { | ||
#endif | ||
|
||
/*pwm.h: function and macro definition of PWM API , driver level */ | ||
/*user_light.h: user interface for light API, user level*/ | ||
/*user_light_adj: API for color changing and lighting effects, user level*/ | ||
|
||
/*NOTE!! : DO NOT CHANGE THIS FILE*/ | ||
|
||
/*SUPPORT UP TO 8 PWM CHANNEL*/ | ||
#define PWM_CHANNEL_NUM_MAX 8 | ||
|
||
//struct pwm_param { | ||
// uint32_t period; | ||
// uint32_t freq; | ||
// uint32_t duty[PWM_CHANNEL_NUM_MAX]; //PWM_CHANNEL<=8 | ||
//}; | ||
// | ||
///* pwm_init should be called only once, for now */ | ||
//void pwm_init(uint32_t period, uint32_t* duty, uint32_t pwm_channel_num, uint32_t (*pin_info_list)[3]); | ||
//void pwm_start(void); | ||
// | ||
//void pwm_set_duty(uint32_t duty, uint8_t channel); | ||
//uint32_t pwm_get_duty(uint8_t channel); | ||
//void pwm_set_period(uint32_t period); | ||
//uint32_t pwm_get_period(void); | ||
// | ||
//uint32_t get_pwm_version(void); | ||
//void set_pwm_debug_en(uint8_t print_en); | ||
|
||
#if defined(__cplusplus) | ||
} | ||
#endif | ||
|
||
#ifdef SOC_LEDC_CHANNEL_NUM | ||
#define PWM_CHANNEL_NUM_MAX SOC_LEDC_CHANNEL_NUM | ||
#else | ||
// this should not happen if the correct esp32 includes are used, just to be absolutely sure | ||
#define PWM_CHANNEL_NUM_MAX 8 | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,275 @@ | ||
/**** | ||
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. | ||
* Created 2015 by Skurydin Alexey | ||
* http://github.com/SmingHub/Sming | ||
* All files of the Sming Core are provided under the LGPL v3 license. | ||
* | ||
* HardwarePWM.cpp | ||
* | ||
* Original Author: https://github.com/hrsavla | ||
* Esp32 version: https://github.com/pljakobs | ||
* | ||
* This HardwarePWM library enables Sming framework uses to use the ESP32 ledc PWM api | ||
* | ||
* the ESP32 PWM Hardware is much more powerful than the ESP8266, allowing wider PWM timers (up to 20 bit) | ||
* as well as much higher PWM frequencies (up to 40MHz for a 1 Bit wide PWM) | ||
* | ||
* Overview: | ||
* +------------------------------------------------------------------------------------------------+ | ||
* | LED_PWM | | ||
* | +-------------------------------------------+ +-------------------------------------------+ | | ||
* | | High_Speed_Channels¹ | | Low_Speed_Channels | | | ||
* | | +-----+ +--------+ | | +-----+ +--------+ | | | ||
* | | | | --> | h_ch 0 | | | | | --> | l_ch 0 | | | | ||
* | | +-----------+ | | +--------+ | | +-----------+ | | +--------+ | | | ||
* | | | h_timer 0 | --> | | | | | l_timer 0 | --> | | | | | ||
* | | +-----------+ | | +--------+ | | +-----------+ | | +--------+ | | | ||
* | | | | --> | h_ch 1 | | | | | --> | l_ch 1 | | | | ||
* | | | | +--------+ | | | | +--------+ | | | ||
* | | | | | | | | | | | ||
* | | | | +--------+ | | | | +--------+ | | | ||
* | | | | --> | h_ch 2 | | | | | --> | l_ch 2 | | | | ||
* | | +-----------+ | | +--------+ | | +-----------+ | | +--------+ | | | ||
* | | | h_timer 1 | --> | | | | | l_timer 1 | --> | | | | | ||
* | | +-----------+ | | +--------+ | | +-----------+ | | +--------+ | | | ||
* | | | | --> | h_ch 3 | | | | | --> | l_ch 3 | | | | ||
* | | | | +--------+ | | | | +--------+ | | | ||
* | | | MUX | | | | MUX | | | | ||
* | | | | +--------+ | | | | +--------+ | | | ||
* | | | | --> | h_ch 4 | | | | | --> | l_ch 4 | | | | ||
* | | +-----------+ | | +--------+ | | +-----------+ | | +--------+ | | | ||
* | | | h_timer 2 | --> | | | | | l_timer 2 | --> | | | | | ||
* | | +-----------+ | | +--------+ | | +-----------+ | | +--------+ | | | ||
* | | | | --> | h_ch 5 | | | | | --> | l_ch 5 | | | | ||
* | | | | +--------+ | | | | +--------+ | | | ||
* | | | | | | | | | | | ||
* | | | | +--------+ | | | | +--------+ | | | ||
* | | | | --> | h_ch 6 | | | | | --> | l_ch 6²| | | | ||
* | | +-----------+ | | +--------+ | | +-----------+ | | +--------+ | | | ||
* | | | h_timer 3 | --> | | | | | l_timer 3 | --> | | | | | ||
* | | +-----------+ | | +--------+ | | +-----------+ | | +--------+ | | | ||
* | | | | --> | h_ch 7 | | | | | --> | l_ch 7²| | | | ||
* | | | | +--------+ | | | | +--------+ | | | ||
* | | +-----+ | | +-----+ | | | ||
* | +-------------------------------------------+ +-------------------------------------------+ | | ||
* +------------------------------------------------------------------------------------------------+ | ||
* ¹ High speed channels are only available when SOC_LEDC_SUPPORT_HS_MODE is defined as 1 | ||
* ² The ESP32C3 does only support six channels, so 6 and 7 are not available on that SoC | ||
* | ||
* The nomenclature of timers in the high speed / low speed blocks is a bit misleading as the idf api | ||
* speaks of "speed mode", which, to me, implies that this would be a mode configurable in a specific timer | ||
* while in reality, it does select a block of timers. | ||
* | ||
* Maximum Timer width for PWM: | ||
* ============================ | ||
* esp32 SOC_LEDC_TIMER_BIT_WIDE_NUM (20) | ||
* esp32c3 SOC_LEDC_TIMER_BIT_WIDE_NUM (14) | ||
* esp32s2 SOC_LEDC_TIMER_BIT_WIDE_NUM (14) | ||
* esp32s3 SOC_LEDC_TIMER_BIT_WIDE_NUM (14) | ||
* | ||
* Number of Channels: | ||
* =================== | ||
* esp32 SOC_LEDC_CHANNEL_NUM (8) | ||
* esp32c3 SOC_LEDC_CHANNEL_NUM (6) | ||
* esp32s2 SOC_LEDC_CHANNEL_NUM (8) | ||
* esp32s3 SOC_LEDC_CHANNEL_NUM (8) | ||
* | ||
* Some SoSs support a mode called HIGHSPEED_MODE which is essentially another full block of PWM hardware | ||
* that adds SOC_LEDC_CHANNEL_NUM channels. | ||
* Those Architectures have SOC_LEDC_SUPPORT_HS_MODE defined as 1. | ||
* In esp-idf-4.3 that's currently only the esp32 SOC | ||
* | ||
* Supports highspeed mode: | ||
* ======================== | ||
* esp32 SOC_LEDC_SUPPORT_HS_MODE (1) | ||
* | ||
* hardware technical reference: | ||
* ============================= | ||
* https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf#ledpwm | ||
* | ||
* Overview of the whole ledc-system here: | ||
* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html | ||
* | ||
* the ledc interface also exposes some advanced functions such as fades that are then done in hardware. | ||
* ToDo: implement a Sming interface for fades | ||
* | ||
*/ | ||
|
||
#include <HardwarePWM.h> | ||
#include <debug_progmem.h> | ||
#include <driver/periph_ctrl.h> | ||
#include <driver/ledc.h> | ||
#include <esp_err.h> | ||
#include <hal/ledc_types.h> | ||
|
||
#define DEFAULT_RESOLUTION ledc_timer_bit_t(10) | ||
#define DEFAULT_PERIOD 200 | ||
namespace | ||
{ | ||
ledc_channel_t pinToChannel(uint8_t pin) | ||
{ | ||
return ledc_channel_t(pin % 8); | ||
} | ||
|
||
ledc_mode_t pinToGroup(uint8_t pin) | ||
{ | ||
return ledc_mode_t(pin / 8); | ||
} | ||
|
||
ledc_timer_t pinToTimer(uint8_t pin) | ||
{ | ||
return ledc_timer_t((pin / 2) % 4); | ||
} | ||
|
||
uint32_t periodToFrequency(uint32_t period) | ||
{ | ||
return (period == 0) ? 0 : (1000000 / period); | ||
} | ||
|
||
uint32_t frequencyToPeriod(uint32_t freq) | ||
{ | ||
return (freq == 0) ? 0 : (1000000 / freq); | ||
} | ||
|
||
uint32_t maxDuty(ledc_timer_bit_t bits) | ||
{ | ||
return (1U << bits) - 1; | ||
} | ||
|
||
} //namespace | ||
|
||
HardwarePWM::HardwarePWM(uint8_t* pins, uint8_t no_of_pins) : channel_count(no_of_pins) | ||
{ | ||
debug_d("starting HardwarePWM init"); | ||
periph_module_enable(PERIPH_LEDC_MODULE); | ||
if((no_of_pins == 0) || (no_of_pins > SOC_LEDC_CHANNEL_NUM)) { | ||
return; | ||
} | ||
|
||
for(uint8_t i = 0; i < no_of_pins; i++) { | ||
channels[i] = pins[i]; | ||
|
||
/* | ||
* Prepare and then apply the LEDC PWM timer configuration. | ||
* This may configure the same timer more than once (in fact up to 8 times) | ||
* which should not be an issue, though, since the values should be the same for all timers | ||
*/ | ||
// The two groups (if available) are operating in different speed modes, hence speed mode is an alias for group or vice versa | ||
ledc_timer_config_t ledc_timer{ | ||
.speed_mode = pinToGroup(i), | ||
.duty_resolution = LEDC_TIMER_10_BIT, // todo: make configurable later | ||
.timer_num = pinToTimer(i), | ||
.freq_hz = periodToFrequency(DEFAULT_PERIOD), // todo: make configurable later | ||
.clk_cfg = LEDC_AUTO_CLK, | ||
}; | ||
|
||
debug_d("ledc_timer.\r\n" | ||
"\tspeed_mode: %i\r\n" | ||
"\ttimer_num: %i\r\n" | ||
"\tduty_resolution: %i\r\n" | ||
"\tfreq: %i\n\tclk_cfg: %i\r\n\n", | ||
ledc_timer.speed_mode, ledc_timer.timer_num, ledc_timer.duty_resolution, ledc_timer.freq_hz, | ||
ledc_timer.clk_cfg); | ||
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); | ||
|
||
/* | ||
* Prepare and then apply the LEDC PWM channel configuration | ||
*/ | ||
ledc_channel_config_t ledc_channel{ | ||
.gpio_num = pins[i], | ||
.speed_mode = pinToGroup(i), | ||
.channel = pinToChannel(i), | ||
.intr_type = LEDC_INTR_DISABLE, | ||
.timer_sel = pinToTimer(i), | ||
.duty = 0, | ||
.hpoint = 0, | ||
}; | ||
debug_d("ledc_channel\n" | ||
"\tspeed_mode: %i\r\n" | ||
"\tchannel: %i\r\n" | ||
"\ttimer_sel %i\r\n" | ||
"\tintr_type: %i\r\n" | ||
"\tgpio_num: %i\r\n" | ||
"\tduty: %i\r\n" | ||
"\thpoint: %i\r\n\n", | ||
pinToGroup(i), pinToChannel(i), pinToTimer(i), ledc_channel.intr_type, pins[i], 0, 0); | ||
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); | ||
ledc_bind_channel_timer(pinToGroup(i), pinToChannel(i), pinToTimer(i)); | ||
} | ||
maxduty = maxDuty(DEFAULT_RESOLUTION); | ||
} | ||
|
||
HardwarePWM::~HardwarePWM() | ||
{ | ||
// Stop pwm for all pins and set idle level to 0 | ||
for(uint8_t i = 0; i < channel_count; i++) { | ||
ledc_stop(pinToGroup(i), pinToChannel(i), 0); | ||
} | ||
} | ||
|
||
uint8_t HardwarePWM::getChannel(uint8_t pin) | ||
{ | ||
for(uint8_t i = 0; i < channel_count; i++) { | ||
if(channels[i] == pin) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
|
||
uint32_t HardwarePWM::getDutyChan(uint8_t chan) | ||
{ | ||
// esp32 defines the frequency / period per timer | ||
return (chan == PWM_BAD_CHANNEL) ? 0 : ledc_get_duty(pinToGroup(chan), pinToChannel(chan)); | ||
} | ||
|
||
bool HardwarePWM::setDutyChan(uint8_t chan, uint32_t duty, bool update) | ||
{ | ||
if(chan == PWM_BAD_CHANNEL) { | ||
return false; | ||
} | ||
|
||
if(duty <= maxduty) { | ||
ESP_ERROR_CHECK(ledc_set_duty(pinToGroup(chan), pinToChannel(chan), duty)); | ||
/* | ||
* ignoring the update flag in this release, ToDo: implement a synchronized update mechanism | ||
* if(update) { | ||
* ESP_ERROR_CHECK(ledc_update_duty(pinToGroup(chan), pinToChannel(chan))); | ||
* //update(); | ||
* } | ||
*/ | ||
ESP_ERROR_CHECK(ledc_update_duty(pinToGroup(chan), pinToChannel(chan))); | ||
return true; | ||
} | ||
|
||
debug_d("Duty cycle value too high for current period, max duty cycle is %d", maxduty); | ||
return false; | ||
} | ||
|
||
uint32_t HardwarePWM::getPeriod() | ||
{ | ||
// Sming does not know how to handle different frequencies for channels: this will require an extended interface. | ||
// For now, just report the period for group 0 channel 0. | ||
return frequencyToPeriod(ledc_get_freq(ledc_mode_t(0), ledc_timer_t(0))); | ||
} | ||
|
||
void HardwarePWM::setPeriod(uint32_t period) | ||
{ | ||
// Set the frequency globally, will add per timer functions later. | ||
// Also, this can be done smarter. | ||
for(uint8_t i = 0; i < channel_count; i++) { | ||
ESP_ERROR_CHECK(ledc_set_freq(pinToGroup(i), pinToTimer(i), periodToFrequency(period))); | ||
} | ||
// ledc_update_duty(); | ||
update(); | ||
} | ||
|
||
void HardwarePWM::update() | ||
{ | ||
// ledc_update_duty(); | ||
} | ||
|
||
uint32_t HardwarePWM::getFrequency(uint8_t pin) | ||
{ | ||
return ledc_get_freq(pinToGroup(pin), pinToTimer(pin)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.