diff --git a/Sming/Arch/Esp32/Core/HardwarePWM.cpp b/Sming/Arch/Esp32/Core/HardwarePWM.cpp new file mode 100644 index 0000000000..fe8575e526 --- /dev/null +++ b/Sming/Arch/Esp32/Core/HardwarePWM.cpp @@ -0,0 +1,328 @@ +/**** + * 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. I am considering re-naming that to "speed mode block" + * in my interface impmenentation. + * + * As an example, I would use + * setTimerFrequency(speedModeBlock, timer, frequency); + * + * ToDo: see, how this can be implemented to provide maximum overlap with the RP2040 pwm hardware so code does + * not become overly SoC specific. + * + * 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 16 (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) + * + * ToDo: implement awareness of hs mode availablility + * ================================================== + * currently, the code just uses a % 8 operation on the pin index to calculate whether to assign a pin to either + * high speed or low speed pwm blocks. This doesn't make a whole lot of sense since it makes it impossible + * for Sming devs to actually use the functionality behind it. + * Also, it currently does not reflect the fact that different SOCs have a different number of channels per block + * (specifically, the esp32c3 only has six channels and no highspeed mode). + * I will continue in two ways: + * - implement the "vanilla" Sming HardwarePWM interface that will hide the underlying architecture but allow up to 16 + * channels on an ESP32 + * - implement overloads for the relevant functions that allow selecting hs mode where applicable. + * + * ToDo: implement PWM bit width control + * ===================================== + * the current HardwarePWM implementation does not care about the PWM timer bit width. To leverage the functionality + * of the ESP32 hardware, it's necessary to make this configurable. As the width is per timer and all the Sming defined + * functions are basically per pin, this needs to be an extension of the overal model, exposing at least timers. + * This, too, will require a compatible "basic" interface and an advanced interface that allows assiging pins (channels) + * to timers and the configuration of the timers themselves. + * The esp_idf does not provide a way to read the bit width configured for a channel, but I think it'll be useful to be able + * to read back this value, not least to find the value for getMaxDuty() for a channel. It will either have to be stored in the + * module or maybe read from the hardware directly (LEDC_[HL]STIMERx_CONF_REG & 0x1f) + * + * ToDo: implement an abstraction layer + * ==================================== + * as it stands now, this impelmentation does not provide a function to synchronize all the PWM channels (HardwarePWM::update()) + * It might be a good idea to provide an intermediary abstraction that handles all the low level PWM functions (such as flexible + * timer to channel assignments, hs/ls mode awareness, pwm bit width etc) and implements the update() function on that level. + * + * hardware technical reference: + * ============================= + * ESP32: https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf#ledpwm + * ESP32c3: https://www.espressif.com/sites/default/files/documentation/esp32-c3_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 + * + + + + * You can use function setPeriod() to change frequency/period. + * Calculate the max duty as per the formulae give in ESP8266 SDK + * Max Duty = (Period * 1000) / 45 + * + * PWM can be generated on up to 8 pins (ie All pins except pin 16) + * Created on August 17, 2015, 2:27 PM + * + * See also ESP8266 Technical Reference, Chapter 12: + * http://espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf + * + */ + + +#include +//#include +#include "driver/ledc.h" +#include "esp_err.h" +#include +#include +#include "singleton.h" +#include "ledc_channel.h" +#include "ledc_timer.h" + +namespace{ + uint32_t periodToFrequency(uint32_t period); + uint32_t frequencyToPeriod(uint32_t freq); + + //ledc_channel_t getChannel(ledc_mode_t); + //ledc_timer_t getTimer(ledc_mode_t); +} //namespace + +#define DEFAULT_RESOLUTION static_cast(10) +#define DEFAULT_FREQ 400000 +#define DEFAULT_CLOCK_SOURCE LEDC_AUTO_CLK + +HardwarePWM::HardwarePWM(uint8_t* pins, uint8_t no_of_pins) : channel_count(no_of_pins) +{ + //ledc_timer_config_t ledc_timer; + //ledc_channel_config_t ledc_channel; + ledc_mode_t mode; + #ifdef LEDC_HIGH_SPEED_MODE + mode=LEDC_HIGH_SPEED_MODE; + #else + mode=LEDC_LOW_SPEED_MODE; + #endif + + debug_d("starting HardwarePWM init"); + periph_module_enable(PERIPH_LEDC_MODULE); + if((no_of_pins == 0) || (no_of_pins > SOC_LEDC_CHANNEL_NUM)) + { + return; + } + + /* ToDo: this logic seems broken. What needs to happen: + * if LEDC_HIGH_SPEED_MODE, we'll need to check both, HS and LS mode, + * else only LS mode + */ + #ifdef LEDC_HIGH_SPEED_MODE + if(Channel::instance()->getFreeChannels(mode)getFreeChannels(mode)getFreeChannels(mode)getTimerNumber(), 0); + } + +} + +HardwarePWM::~HardwarePWM() +{ + for(uint8_t i = 0; i < channel_count; i++) { + //stop pwm for all pins and set idle level to 0. + channel[i]->stop(); + } +} + +/* Function Name: getChannel + * Description: This function is used to get channel number for given pin + * Parameters: pin - Esp8266 pin number + */ +uint8_t HardwarePWM::getChannel(uint8_t pin) +{ + for(uint8_t i = 0; i < channel_count; i++) { + if(channel[i]->getPin()==pin) { + //debug_d("getChannel %d is %d", pin, i); + return i; + } + } + return PWM_BAD_PIN; +} + +/* Function Name: getDutyChan + * Description: This function is used to get the duty cycle number for a given channel + * Parameters: chan -Esp8266 channel number + */ +uint32_t HardwarePWM::getDutyChan(uint8_t chan) +{ + if(chan == PWM_BAD_CHANNEL) { + return 0; + } else { + return channel[chan]->getDuty(); + } +} + +/* Function Name: setDutyChan + * Description: This function is used to set the pwm duty cycle for a given channel. If parameter 'update' is false + * then you have to call update() later to update duties. + * Parameters: chan - channel number + * duty - duty cycle value + * update - update PWM output + */ +bool HardwarePWM::setDutyChan(uint8_t chan, uint32_t duty, bool update) +{ + if(chan == PWM_BAD_CHANNEL) { + return false; + } else if(duty <= maxduty) { + channel[chan]->setDuty(duty); + if(update) { + channel[chan]->updateDuty(); + } + return true; + } else { + debug_d("Duty cycle value too high for current period."); + return false; + } +} + +/* Function Name: getPeriod + * Description: This function is used to get Period of PWM. + * Period / frequency will remain same for all pins. + * + */ +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(timer->getTimerFrequency()); +} + +/* Function Name: setPeriod + * Description: This function is used to set Period of PWM. + * Period / frequency will remain same for all pins. + */ +void HardwarePWM::setPeriod(uint32_t period) +{ + timer->setTimerFrequency(periodToFrequency(period)); + update(); +} + +/* Function Name: update + * Description: This function is used to actually update the PWM. + */ +void HardwarePWM::update() +{ + for(uint8_t i=0;iupdateDuty(); + } +} + +uint32_t HardwarePWM::getFrequency(uint8_t pin) +{ + return timer->getTimerFrequency(); +} + +namespace{ + + uint32_t periodToFrequency(uint32_t period){ + if(period == 0){ + return 0; + }else{ + return (1000000 / period); + } + } + + uint32_t frequencyToPeriod(uint32_t freq){ + if(freq == 0) { + return 0; + } else { + return (1000000 / freq); + } + } + +} \ No newline at end of file diff --git a/Sming/Arch/Esp32/Core/ledc_channel.cpp b/Sming/Arch/Esp32/Core/ledc_channel.cpp new file mode 100644 index 0000000000..f96ccfb8bb --- /dev/null +++ b/Sming/Arch/Esp32/Core/ledc_channel.cpp @@ -0,0 +1,81 @@ +#ifndef LEDC_CHANNEL_H +#include "ledc_channel.h" + +ledc_channel::ledc_channel(ledc_mode_t mode, int gpio, ledc_timer_t timer, uint32_t duty, int hpoint){ + periph_module_enable(PERIPH_LEDC_MODULE); + channel_config.speed_mode = mode; + channel_config.gpio_num = gpio; + channel_config.channel = ledc_singleton::Channel::instance()->getChannel(mode); + channel_config.intr_type = LEDC_INTR_DISABLE; + channel_config.timer_sel = timer; + channel_config.duty = duty; + channel_config.hpoint = hpoint; + + esp_err_t err=ledc_channel_config(&channel_config); + if(err==ESP_OK){ + bindChannelTimer(timer); + debug_d("configured channel %d pin %d to user timer %d", channel_config.channel, channel_config.gpio_num, timer->getTimerNum()); + } +} + +ledc_channel::ledc_channel(ledc_mode_t mode, int gpio, ledc_timer_t timer, uint32_t duty){ + ledc_channel(mode, gpio, timer, duty, (int) 0); +} + +ledc_channel::ledc_channel(ledc_mode_t mode, int gpio, ledc_timer_t timer){ + ledc_channel(mode, gpio, timer, (uint32_t) 0, (int) 0); +} + +ledc_channel::ledc_channel(ledc_mode_t mode, int gpio){ + ledc_channel(mode, gpio, ledc_singleton::Timer::instance()->getTimer(mode), (uint32_t) 0, (int) 0); +} + +ledc_channel::~ledc_channel(){ + stop((uint32_t)0); +} +/* +ledc_channel::channelConfig(const ledc_channel_config_t *ledc_conf){ + +} +*/ +esp_err_t ledc_channel::updateDuty(void){ + return ledc_update_duty(channel_config.speed_mode, channel_config.channel); +} + +esp_err_t ledc_channel::setPin(int gpio_num){ + esp_err_t err = ledc_set_pin(gpio_num, channel_config.speed_mode, channel_config.channel); + if(err==ESP_OK){ + channel_config.gpio_num=gpio_num; + } + return err; +} + +esp_err_t ledc_channel::stop(uint32_t idle_level){ + return ledc_stop(channel_config.speed_mode, channel_config.channel, idle_level); +} + +esp_err_t ledc_channel::setDutyWithHpoint(uint32_t duty, uint32_t hpoint){ + esp_err_t err = ledc_set_duty_with_hpoint(channel_config.speed_mode, channel_config.channel, duty, hpoint); + if(err==ESP_OK){ + channel_config.duty=duty; + channel_config.hpoint=hpoint; + } + return err; +} + +esp_err_t ledc_channel::setDuty(uint32_t duty){ + esp_err_t err=ledc_set_duty(channel_config.speed_mode, channel_config.channel, duty); + if(err==ESP_OK){ + channel_config.duty=duty; + } + return err; +} + +esp_err_t ledc_channel::bindChannelTimer(ledc_timer_t timer){ + esp_err_t err = ledc_bind_channel_timer(channel_config.speed_mode, channel_config.channel, timer); + if(err==ESP_OK){ + channel_config.timer_sel=timer; + } + return err; +} +#endif \ No newline at end of file diff --git a/Sming/Arch/Esp32/Core/ledc_channel.h b/Sming/Arch/Esp32/Core/ledc_channel.h new file mode 100644 index 0000000000..a265573219 --- /dev/null +++ b/Sming/Arch/Esp32/Core/ledc_channel.h @@ -0,0 +1,175 @@ +#ifndef LEDC_CHANNEL_H +#define LEDC_CHANNEL_H +#include +#include "driver/ledc.h" +#include "esp_err.h" +#include "singleton.h" +#include + +#ifndef LEDC_TIMERS_H +#include "ledc_timer.h" +#endif +/** + * @brief ledc_channel class + * this class is primarily intended to be used by the Sming HardwarePWM implementation for ESP32 class MCUs. + * while it should be possible to use it directly in user code, it is not generally advised as the plan is to + * expose the full esp32 ledc_ interface through the HardwarePWM interface. + * + * If you chose to use this directly, though, the singleton class at the heart of this *should* at least make sure + * that hardware is correctly shared between HardwarePWM use and direct use as both channels and timers are + * managed by the Timer and Channel classes, respectively. + */ +class ledc_channel{ + public: + /** + * @brief Construct a new ledc channel object + * + * @param mode speed mode (see ledc_mode_t typedef for options) + * @param gpio the pin to attach to the channel + * @param timer the timer to use for this channel. Get the timer by instanciating a ledc_timer object and calling the ledc_timer.getTimer() member function + * @param duty the intial duty cycle + * @param hpoint the hpoint as defined by the esp-idf (max: 0xfff) + */ + ledc_channel(ledc_mode_t mode, int gpio, ledc_timer_t timer, uint32_t duty, int hpoint); + + /** + * @brief Construct a new ledc channel object - reduced interface using default hpoint + * + * @param mode speed mode (see ledc_mode_t typedef for options) + * @param gpio the pin to attach to the channel + * @param timer the timer to use for this channel. Get the timer by instanciating a ledc_timer object and calling the ledc_timer.getTimer() member function + * @param duty the intial duty cycle + */ + ledc_channel(ledc_mode_t mode, int gpio, ledc_timer_t timer, uint32_t duty); + + /** + * @brief Construct a new ledc channel object - reduced interface using default hpoint and duty + * + * @param mode speed mode (see ledc_mode_t typedef for options) + * @param gpio the pin to attach to the channel + * @param timer the timer to use for this channel. Get the timer by instanciating a ledc_timer object and calling the ledc_timer.getTimer() member function + */ + ledc_channel(ledc_mode_t mode, int gpio, ledc_timer_t timer); + + /** + * @brief Construct a new ledc channel object - reduced interface using default hpoint, duty and timer. + * The timer will be automatically assigned using the ledc_timer.getTimer() function. + * Caution: if you are using this to extend the sming HardwarePWM function, this constructor will use a new timer + * for every channel generated which will break the functionality of HardwarePWM to, for example, setting the PWM + * frequency for all channels. + * + * @param mode speed mode (see ledc_mode_t typedef for options) + * @param gpio the pin to attach to the channel + */ + ledc_channel(ledc_mode_t mode, int gpio); + + /** + * @brief destructor for the channel. Frees the channel to be then re-used by other instances + * + */ + ~ledc_channel(); + + //esp_err_t channelConfig(const ledc_channel_config_t *ledc_conf); + + /** + * @brief update the duty cycle of the channel + * call this function after you have set the duty cycle either by calling setDutyWithHpoint or setDuty as those two functions + * only set the duty cycle value but not enact it yet. This allows multiple channels to be synchronized. + * + * @return esp_err_t + */ + + esp_err_t updateDuty(void); + + /** + * @brief Set the Pin + * if the channel has been created with one of the reduced interface constructors without setting the actual pin, the pin can be configured here + * + * @param gpio_num + * @return esp_err_t + */ + esp_err_t setPin(int gpio_num); + + /** + * @brief stop the PWM on this channel and set the output to 0 + * + * @return esp_err_t + */ + esp_err_t stop(){ + return stop(0); + }; + + /** + * @brief stop the PWM on this channel and set the output to idle_level + * + * @param idle_level + * @return esp_err_t + */ + esp_err_t stop(uint32_t idle_level); + + /** + * @brief Set the Duty cycle With Hpoint + * + * @param duty + * @param hpoint + * @return esp_err_t + */ + esp_err_t setDutyWithHpoint(uint32_t duty, uint32_t hpoint); + + /** + * @brief Set the Duty cycle + * + * @param duty + * @return esp_err_t + */ + esp_err_t setDuty(uint32_t duty); + + /** + * @brief Get the Hpoint configured for this channel + * + * @return int + */ + int getHpoint(){ + return channel_config.hpoint; + }; + + /** + * @brief Get the current Duty cycle + * + * @return uint32_t + */ + uint32_t getDuty(){ + return channel_config.duty; + }; + + /** + * @brief Get the assigned gpio Pin + * + * @return int the Pin + */ + int getPin(){ + return channel_config.gpio_num; + } + /** + * @brief bind the channel object to an existing timer + * + * @param timer + * @return esp_err_t + */ + esp_err_t bindChannelTimer(ledc_timer_t timer); + + /* omit fades for now... + esp_err_t setFade(uint32_t duty, ledc_duty_direction_t fade_direction, uint32_t step_num, uint32_t duty_cycle_num, uint32_t duty_scale); + esp_err_t setFadeWithStep(uint32_t target_duty, uint32_t scale, uint32_t cycle_num); + esp_err_t setFadeWithTime(uint32_t target_duty, int max_fade_time_ms); + esp_err_t fadeStart(ledc_fade_mode_t fade_mode) + esp_err_t fadeFuncInstall(int intr_alloc_flags); + esp_err_t fadeFuncUninstall(); + esp_err_t ledc_isr_register(void (*fn)(void *), void *arg, int intr_alloc_flags, ledc_isr_handle_t *handle ); + */ + private: + ledc_channel_config_t channel_config; + +}; + +#endif \ No newline at end of file diff --git a/Sming/Arch/Esp32/Core/ledc_timer.cpp b/Sming/Arch/Esp32/Core/ledc_timer.cpp new file mode 100644 index 0000000000..6878368bc7 --- /dev/null +++ b/Sming/Arch/Esp32/Core/ledc_timer.cpp @@ -0,0 +1,89 @@ + +#ifndef LEDC_TIMER_H +#include "ledc_timer.h" +#define LEDC_TIMER_H +#endif + +//namespace ledc_singleton{ + +ledc_timer::ledc_timer(ledc_mode_t mode, ledc_timer_bit_t duty_resolution, uint32_t freq, ledc_clk_cfg_t clk_cfg){ + timer_conf.speed_mode = mode; + timer_conf.timer_num = ledc_singleton::Timer::instance()->getTimer(mode); + timer_conf.duty_resolution = duty_resolution; + timer_conf.freq_hz = freq; + timer_conf.clk_cfg = clk_cfg; + + ledc_timer_config(&timer_conf); +} + +ledc_timer::~ledc_timer(void){ + ledc_timer_pause(timer_conf.speed_mode, timer_conf.timer_num); + ledc_singleton::Timer::instance()->freeTimer(timer_conf.speed_mode, timer_conf.timer_num); +} + +esp_err_t ledc_timer::setTimer(uint32_t clock_divider, ledc_timer_bit_t duty_resolution, ledc_clk_src_t clk_src){ + esp_err_t err=ledc_timer_set( + timer_conf.speed_mode, + timer_conf.timer_num, + clock_divider, + duty_resolution, + clk_src + ); + if(err==ESP_OK){ + timer_conf.duty_resolution = duty_resolution; + //timer_conf.clk_cfg = clk_cfg; + clock_divider=clock_divider; + } + debug_d("configured timer %i",timer_conf.timer_num); + return err; +} + +esp_err_t ledc_timer::setTimerFrequency(uint32_t freq){ + esp_err_t err=ledc_set_freq( + timer_conf.speed_mode, + timer_conf.timer_num, + freq + ); + if(err==ESP_OK){ + timer_conf.freq_hz = freq; + } + return err; +} + +esp_err_t ledc_timer::timerReset(void){ + return ledc_timer_rst(timer_conf.speed_mode, timer_conf.timer_num); +} + +esp_err_t ledc_timer::timerPause(void){ + return ledc_timer_pause(timer_conf.speed_mode, timer_conf.timer_num); +} + +esp_err_t ledc_timer::timerResume(void){ + return ledc_timer_resume(timer_conf.speed_mode, timer_conf.timer_num); +} +/* +ledc_timer::timerBindChannel(ledc_channel_t channel){ + esp_err_t err=ledc_bind_channel_timer(timer_conf.speed_mode, channel, timer_conf.timer_num); + if(err==ESP_OK) +} +*/ +ledc_timer_t ledc_timer::getTimerNumber(void){ + return timer_conf.timer_num; +} + +ledc_mode_t ledc_timer::getTimerMode(void){ + return timer_conf.speed_mode; +} + +ledc_timer_bit_t ledc_timer::getDutyResolution(void){ + return timer_conf.duty_resolution; +} + +uint32_t ledc_timer::getTimerFrequency(void){ + return timer_conf.freq_hz; +} + +uint32_t ledc_timer::getClockDivider(void){ + return clock_divider; +} +// } // end namespace ledc_singleton \ No newline at end of file diff --git a/Sming/Arch/Esp32/Core/ledc_timer.h b/Sming/Arch/Esp32/Core/ledc_timer.h new file mode 100644 index 0000000000..c08b2f198d --- /dev/null +++ b/Sming/Arch/Esp32/Core/ledc_timer.h @@ -0,0 +1,117 @@ +#ifndef LEDC_TIMER_H +#define LEDC_TIMER_H +#include +#include "driver/ledc.h" +#include "esp_err.h" +#include "singleton.h" +#include + +#ifndef LEDC_CHANNEL_H +#include "ledc_channel.h" +#endif + +class ledc_timer { + public: + /** + * @brief Construct a new ledc timer object + * + * @param mode + * @param duty_resolution + * @param freq + * @param clk_cfg + */ + ledc_timer(ledc_mode_t mode, ledc_timer_bit_t duty_resolution, uint32_t freq, ledc_clk_cfg_t clk_cfg); + /** + * @brief Destroy the ledc timer object + * + */ + ~ledc_timer(void); + + /** + * @brief Set the Timer object + * + * @param clock_divider + * @param duty_resolution + * @param clk_src + * + * @return esp_err_t + */ + esp_err_t setTimer(uint32_t clock_divider, ledc_timer_bit_t duty_resolution, ledc_clk_src_t clk_src); + + /** + * @brief set the Frequency for this timer object + * + * @param frequency + * @return esp_err_t + */ + esp_err_t setTimerFrequency(uint32_t freq); + + /** + * @brief reset this timer + * + * @return esp_err_t + */ + esp_err_t timerReset(void); + + /** + * @brief pause this timer + * + * @return esp_err_t + */ + esp_err_t timerPause(void); + + /** + * @brief resume this timer + * + * @return esp_err_t + */ + esp_err_t timerResume(void); + + /* + * esp_err_t timerBindChannel(ledc_channel_t channel); + */ + + /** + * @brief Get the Timer Number + * use this to get the timer number to use in the ledc_channel constructor or ledc_channel.bindTimer() + * + * @return ledc_timer_t Timer Number + */ + ledc_timer_t getTimerNumber(void); + + /** + * @brief Get the Timer Mode + * return the timer mode + * + * @return ledc_mode_t Timer Mode + */ + ledc_mode_t getTimerMode(void); + + /** + * @brief Get the Duty Resolution + * get the resolution in bits configured for this timer + * + * @return ledc_timer_bit_t Timer Resolution + */ + ledc_timer_bit_t getDutyResolution(void); + + /** + * @brief Get the Timer Frequency + * + * @return uint32_t Frequency + */ + uint32_t getTimerFrequency(void); + + /** + * @brief Get the Clock Divider object + * + * @return uint32_t clock divider + */ + uint32_t getClockDivider(void); + private: + ledc_timer_config_t timer_conf; + ledc_timer_t timer; + uint32_t clock_divider; +}; + +#endif \ No newline at end of file diff --git a/Sming/Arch/Esp32/Core/singelton.cpp b/Sming/Arch/Esp32/Core/singelton.cpp new file mode 100644 index 0000000000..4acbe5ff68 --- /dev/null +++ b/Sming/Arch/Esp32/Core/singelton.cpp @@ -0,0 +1,71 @@ +#include "./singleton.h" + +/** + * @brief get a pwm timer + * + * @return timer number + * + */ + +namespace ledc_singleton{ +ledc_timer_t Timer::getTimer(ledc_mode_t mode) +{ + for(uint8_t i = 0; i < (uint8_t)LEDC_TIMER_MAX; i++) { + if(!isUsed[(uint8_t)mode][i]) { + isUsed[(uint8_t)mode][i] = true; + return (ledc_timer_t)i; + } + } + return (ledc_timer_t)LEDC_TIMER_MAX; +} + +void Timer::freeTimer(ledc_mode_t mode, ledc_timer_t timer) +{ + isUsed[(uint8_t)mode][(uint8_t)timer] = false; +} + +Timer::Timer() +{ + for(uint8_t i = 0; i < (uint8_t)LEDC_SPEED_MODE_MAX; i++) { + for(uint8_t j = 0; j < (uint8_t)LEDC_TIMER_MAX; j++) { + isUsed[i][j] = false; + } + } +} + +ledc_channel_t Channel::getChannel(ledc_mode_t mode) +{ + for(uint8_t i = 0; i < (uint8_t)LEDC_CHANNEL_MAX; i++) { + if(!isUsed[(uint8_t)mode][i]) { + isUsed[(uint8_t)mode][i] = true; + return (ledc_channel_t)i; + } + } + return (ledc_channel_t)LEDC_CHANNEL_MAX; +} + +void Channel::freeChannel(ledc_mode_t mode, ledc_channel_t channel) +{ + isUsed[(uint8_t)mode][(uint8_t)channel] = false; +} + +uint8_t Channel::getFreeChannels(ledc_mode_t mode){ + uint8_t count=0; + for(uint8_t i=0; i<(uint8_t)LEDC_CHANNEL_MAX;i++){ + if(!isUsed[(uint8_t)mode][i]){ + count++; + } + } + return count; +} + +Channel::Channel() +{ + for(int i = 0; i < LEDC_CHANNEL_MAX; i++) { + isUsed[LEDC_LOW_SPEED_MODE][i] = false; +#ifdef SOC_LEDC_SUPPORT_HS_MODE + isUsed[LEDC_HIGH_SPEED_MODE][i] = false; +#endif + } +} +} //end namespace ledc_singleton \ No newline at end of file diff --git a/Sming/Arch/Esp32/Core/singleton.h b/Sming/Arch/Esp32/Core/singleton.h new file mode 100644 index 0000000000..656f197603 --- /dev/null +++ b/Sming/Arch/Esp32/Core/singleton.h @@ -0,0 +1,422 @@ +#pragma once +#include +#include +#define PWM_MAX_TIMER 8 + +namespace ledc_singleton{ +template class Singleton +{ +public: + static C* instance() + { + if(!_instance) + _instance = new C(); + return _instance; + } + virtual ~Singleton() + { + _instance = 0; + } + +private: + static C* _instance; + +protected: + Singleton() + { + } +}; + +template C* Singleton::_instance = 0; + +class Timer : public Singleton +{ + friend class Singleton; + +public: + ~Timer(){}; + + /** + * @brief get an unspecified timer + * @param none + * @note as the only timer available eveywhere are low speed, this is low speed + * @retval ledc_timer_t , + * LEDC_TIMER_MAX if no more timers are available + */ + ledc_timer_t getTimer() + { + return getLSTimer(); + }; + + /** + * @brief get a low speed timer + * @param none + * @retval ledc_timer_t + * LEDC_TIMER_MAX if no more timers are available + */ + ledc_timer_t getLSTimer() + { + return getTimer(LEDC_LOW_SPEED_MODE); + }; + #ifdef LEDC_HIGH_SPEED_MODE + /** + * @brief get a high speed timer + * @param none + * @note you are not guaranteed that the SoC has high speed timers + * @retval ledc_timer_t + * LEDC_TIMER_MAX if no more timers are available + */ + ledc_timer_t getHSTimer() + { + return getTimer(LEDC_HIGH_SPEED_MODE); + }; + + + /** + * @brief free a high speed timer + * @param ledc_timer_t + * @note using this to free a low speed mode timer will lead to unexpected effects + * because the seemingly freed timer might be re-used and thus re-configured later on + * @retval none + */ + void freeHSTimer(ledc_timer_t timer) + { + freeTimer(LEDC_HIGH_SPEED_MODE, timer); + }; + + /** + * @brief free a high speed timer + * @param const int + * @note using this to free a low speed mode timer will lead to unexpected effects + * because the seemingly freed timer might be re-used and thus re-configured later on + * @retval none + */ + void freeHSTimer(const int timer) + { + freeTimer(LEDC_HIGH_SPEED_MODE, timer); + }; + #endif + + /** + * @brief get a timer of specified speed mode + * @param ledc_mode_t (LEDC_LOW_SPEED_MODE or LEDC_HIGH_SPEED_MODE) + * @note you are not guaranteed that the SoC has high speed timers + * also, you are required to keep track of the speed mode of the timers yourself. + * @retval ledc_timer_t + * LEDC_TIMER_MAX if no more timers with the specified mode are available + */ + ledc_timer_t getTimer(ledc_mode_t mode); + + /** + * @brief get a timer of specified speed mode + * @param const int (LEDC_LOW_SPEED_MODE or LEDC_HIGH_SPEED_MODE) + * @note you are not guaranteed that the SoC has high speed timers + * also, you are required to keep track of the speed mode of the timers yourself. + * @retval ledc_timer_t + * LEDC_TIMER_MAX if no more timers with the specified mode are available + */ + ledc_timer_t getTimer(const int mode) + { + return getTimer((ledc_mode_t)mode); + }; + + /** + * @brief free an unspecified timer + * @param ledc_timer_t + * @note as the only timer available eveywhere are low speed, this is low speed timers + * using this to free a high speed mode timer will lead to unexpected effects + * because the seemingly freed timer might be re-used and thus re-configured later on + * @retval none + */ + void freeTimer(ledc_timer_t timer) + { + freeLSTimer(timer); + }; + + /** + * @brief free an unspecified timer + * @param const int + * @note as the only timer available eveywhere are low speed, this is low speed timers + * using this to free a high speed mode timer will lead to unexpected effects + * because the seemingly freed timer might be re-used and thus re-configured later on + * @retval none + */ + void freeTimer(const int timer) + { + freeLSTimer((ledc_timer_t)timer); + }; + + /** + * @brief free a low speed timer + * @param ledc_timer_t + * @note using this to free a high speed mode timer will lead to unexpected effects + * because the seemingly freed timer might be re-used and thus re-configured later on + * @retval none + */ + void freeLSTimer(ledc_timer_t timer) + { + freeTimer(LEDC_LOW_SPEED_MODE, timer); + }; + + /** + * @brief free a low speed timer + * @param const int + * @note using this to free a high speed mode timer will lead to unexpected effects + * because the seemingly freed timer might be re-used and thus re-configured later on + * @retval none + */ + void freeLSTimer(const int timer) + { + freeTimer(LEDC_LOW_SPEED_MODE, (ledc_timer_t)timer); + }; + + /** + * @brief free a timer + * @param ledc_timer_t + * @param ledc_mode_t + * @note if you are mixing low speed and high speed timers, this is the safest call to use + * @retval none + */ + void freeTimer(ledc_mode_t mode, ledc_timer_t timer); + + /** + * @brief free a timer + * @param ledc_timer_t + * @param const int + * @note if you are mixing low speed and high speed timers, this is the safest call to use + * @retval none + */ + void freeTimer(const int mode, ledc_timer_t timer) + { + freeTimer((ledc_mode_t)mode, timer); + }; + + /** + * @brief free a timer + * @param const int + * @param ledc_mode_t + * @note if you are mixing low speed and high speed timers, this is the safest call to use + * @retval none + */ + void freeTimer(ledc_mode_t mode, const int timer) + { + freeTimer(mode, (ledc_timer_t)timer); + }; + + /** + * @brief free a timer + * @param const int + * @param const int + * @note if you are mixing low speed and high speed timers, this is the safest call to use + * @retval none + */ + void freeTimer(const int mode, const int timer) + { + freeTimer((ledc_mode_t)mode, (ledc_timer_t)timer); + }; + +protected: + Timer(); + bool isUsed[LEDC_SPEED_MODE_MAX][LEDC_TIMER_MAX]; +}; + +class Channel : public Singleton +{ + friend class Singleton; + +public: + ~Channel(){}; + + /** + * @brief get an unspecified channel + * @param none + * @note as the only channels available eveywhere are low speed, this is low speed + * @retval ledc_channel_t , + * LEDC_CHANNEL_MAX if no more CHANNELS are available + */ + ledc_channel_t getChannel() + { + return getLSChannel(); + }; + + /** + * @brief get a low speed channel + * @param none + * @retval ledc_channel_t , + * LEDC_CHANNEL_MAX if no more low speed CHANNELS are available + */ + ledc_channel_t getLSChannel() + { + return getChannel(LEDC_LOW_SPEED_MODE); + }; + + #ifdef LEDC_HIGH_SPEED_MODE + /** + * @brief get a high speed channel + * @param none + * @note you are not guaranteed that the SoC has high speed channels + * @retval ledc_channel_t , + * LEDC_CHANNEL_MAX if no more high speed CHANNELS are available + */ + ledc_channel_t getHSChannel() + { + return getChannel(LEDC_HIGH_SPEED_MODE); + }; + + /** + * @brief free a high speed channel + * @param ledc_channel_t + * @note using this to free a low speed channel will lead to unexpected effects + * because the seemingly freed channel might be re-used and thus re-configured later on + * @retval none + */ + void freeHSChannel(ledc_channel_t channel) + { + freeChannel(LEDC_HIGH_SPEED_MODE, channel); + }; + + /** + * @brief free a high speed channel + * @param const int + * @note using this to free a low speed channel will lead to unexpected effects + * because the seemingly freed channel might be re-used and thus re-configured later on + * @retval none + */ + void freeHSChannel(const int channel) + { + freeChannel(LEDC_HIGH_SPEED_MODE, (ledc_channel_t)channel); + }; + + #endif + + /** + * @brief get a channel with a defined speed mode + * @param ledc_mode_t mode + * @note you are not guaranteed that the SoC has high speed channels + * also, you are required to keep track of the speed mode of the channels yourself. + * @retval ledc_channel_t , + * LEDC_CHANNEL_MAX if no more channels with the specified mode are available + */ + ledc_channel_t getChannel(ledc_mode_t mode); + + /** + * @brief get a channel with a defined speed mode + * @param const int mode + * @note you are not guaranteed that the SoC has high speed channels + * also, you are required to keep track of the speed mode of the channels yourself. + * @retval ledc_channel_t , + * LEDC_CHANNEL_MAX if no more channels with the specified mode are available + */ + ledc_channel_t getChannel(const int mode) + { + return getChannel((ledc_mode_t)mode); + }; + + /** + * @brief free an unspecified channel + * @param ledc_channel_t + * @note as the only channels available eveywhere are low speed, this is low speed channels + * using this to free a high speed channel will lead to unexpected effects + * because the seemingly freed channel might be re-used and thus re-configured later on + * @retval none + */ + void freeChannel(ledc_channel_t channel) + { + freeLSChannel(channel); + }; + + /** + * @brief free an unspecified channel + * @param const int + * @note as the only channels available eveywhere are low speed, this is low speed channels + * using this to free a high speed channel will lead to unexpected effects + * because the seemingly freed channel might be re-used and thus re-configured later on + * @retval none + */ + void freeChannel(const int channel) + { + freeLSChannel((ledc_channel_t)channel); + }; + + /** + * @brief free a low speed channel + * @param ledc_channel_t + * @note using this to free a high speed channel will lead to unexpected effects + * because the seemingly freed channel might be re-used and thus re-configured later on + * @retval none + */ + void freeLSChannel(ledc_channel_t channel) + { + freeChannel(LEDC_LOW_SPEED_MODE, channel); + }; + + /** + * @brief free a low speed channel + * @param const int + * @note using this to free a high speed channel will lead to unexpected effects + * because the seemingly freed channel might be re-used and thus re-configured later on + * @retval none + */ + void freeLSChannel(const int channel) + { + freeChannel(LEDC_LOW_SPEED_MODE, (ledc_channel_t)channel); + }; + + /** + * @brief free a channel + * @param ledc_mode_t + * @param ledc_channel_t + * @note if you are mixing low speed and high speed channels, this is the safest call to use + * @retval none + */ + void freeChannel(ledc_mode_t mode, ledc_channel_t channel); + + /** + * @brief free a channel + * @param const int + * @param ledc_channel_t + * @note if you are mixing low speed and high speed channels, this is the safest call to use + * @retval none + */ + void freeChannel(const int mode, ledc_channel_t channel) + { + freeChannel((ledc_mode_t)mode, channel); + }; + + /** + * @brief free a channel + * @param ledc_mode_t int + * @param const int + * @note if you are mixing low speed and high speed channels, this is the safest call to use + * @retval none + */ + void freeChannel(ledc_mode_t mode, const int channel) + { + freeChannel(mode, (ledc_channel_t)channel); + }; + + /** + * @brief free a channel + * @param const int + * @param const int + * @note if you are mixing low speed and high speed channels, this is the safest call to use + * @retval none + */ + void freeChannel(const int mode, const int channel) + { + freeChannel((ledc_mode_t)mode, (ledc_channel_t)channel); + }; + + /** + * @brief return number of free channels in mode + * @param const int + * @note this is in no way thread safe. + * @retval number of unassigned channels + */ + uint8_t getFreeChannels(ledc_mode_t mode); + +protected: + Channel(); + bool isUsed[LEDC_SPEED_MODE_MAX][LEDC_CHANNEL_MAX]; +}; +} // end namespace ledc_singleton \ No newline at end of file diff --git a/Sming/Core/HardwarePWM.h b/Sming/Core/HardwarePWM.h index 78b27476fb..8342142d40 100644 --- a/Sming/Core/HardwarePWM.h +++ b/Sming/Core/HardwarePWM.h @@ -31,6 +31,12 @@ #include #define PWM_BAD_CHANNEL 0xff ///< Invalid PWM channel +#define PWM_BAD_PIN 0xff ///< Invalid Pin / Pin is not attached to any channel + +#if SMING_ARCH==Esp32 +#include "../Arch/Esp32/Core/ledc_timer.h" +#include "../Arch/Esp32/Core/ledc_channel.h" +#endif /// Hardware pulse width modulation class HardwarePWM @@ -121,11 +127,21 @@ class HardwarePWM /** @brief This function is used to actually update the PWM. */ void update(); + + /** @brief Get PWM Frequency + * @retval uint32_t Value of Frequency + */ + uint32_t getFrequency(uint8_t pin); private: uint8_t channel_count; uint8_t channels[PWM_CHANNEL_NUM_MAX]; uint32_t maxduty; + + #if SMING_ARCH==Esp32 + ledc_channel* channel[PWM_CHANNEL_NUM_MAX]; + ledc_timer* timer; + #endif }; /** @} */ diff --git a/samples/Singleton_test/Makefile b/samples/Singleton_test/Makefile new file mode 100644 index 0000000000..ff51b6c3a7 --- /dev/null +++ b/samples/Singleton_test/Makefile @@ -0,0 +1,9 @@ +##################################################################### +#### Please don't change this file. Use component.mk instead #### +##################################################################### + +ifndef SMING_HOME +$(error SMING_HOME is not set: please configure it as an environment variable) +endif + +include $(SMING_HOME)/project.mk diff --git a/samples/Singleton_test/README.rst b/samples/Singleton_test/README.rst new file mode 100644 index 0000000000..053e6d5ebb --- /dev/null +++ b/samples/Singleton_test/README.rst @@ -0,0 +1,4 @@ +Basic Hardware PWM +================== + +Demonstrates how to generate PWM signals on multiple pins using Sming's *HardwarePWM* class. diff --git a/samples/Singleton_test/app/application.cpp b/samples/Singleton_test/app/application.cpp new file mode 100644 index 0000000000..6efc8e92c6 --- /dev/null +++ b/samples/Singleton_test/app/application.cpp @@ -0,0 +1,50 @@ +/* + * File: Esp SDK Hardware PWM demo + * Original Author: https://github.com/hrsavla + * + * This HardwarePWM library enables Sming framework user to use ESP SDK PWM API + * Period of PWM is fixed to 1000us / Frequency = 1khz + * Duty at 100% = 22222. Duty at 0% = 0 + * You can use function setPeriod() to change frequency/period. + * Calculate the Duty as per the formulae give in ESP8266 SDK + * Duty = (Period *1000)/45 + * + * PWM can be generated on up to 8 pins (ie All pins except pin 16) + * Created on August 17, 2015, 2:27 PM + * + * See also ESP8266 Technical Reference, Chapter 12: + * http://espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf + */ +#include +//#include +#include "/opt/sming/Sming/Core/HardwarePWM.h" +//#include "singleton.h" +using namespace HardwarePWM; +void setup() { + // put your setup code here, to run once: + + Serial.begin(115200); + delay(2000); + Serial.println("starting"); + Serial.println("========"); + for(int i=1;i<=4;i++){ + Serial.printf("gettig timer %i\n", Timer::instance()->getTimer(0)); + } + Timer::instance()->freeTimer(0,3); + Serial.printf("freed timer 3\n"); + Serial.printf("gettig timer %i\n", Timer::instance()->getTimer(0)); + Timer::instance()->freeTimer(0,2); + Timer::instance()->freeTimer(0,4); + Serial.printf("freed timer 2 and 4\n"); + + for(int i=1;i<=4;i++){ + Serial.printf("gettig timer %i\n", Timer::instance()->getTimer(0)); + } +} + +void loop() { + // put your main code here, to run repeatedly: + +} + + diff --git a/samples/Singleton_test/component.mk b/samples/Singleton_test/component.mk new file mode 100644 index 0000000000..ec78f78162 --- /dev/null +++ b/samples/Singleton_test/component.mk @@ -0,0 +1,5 @@ +COMPONENT_SOC := esp8266 + +# Uncomment the line below if you want to use Espressif's PWM library. +#ENABLE_CUSTOM_PWM=0 +