diff --git a/boards/common/esp32/include/periph_conf_common.h b/boards/common/esp32/include/periph_conf_common.h
index 437ba58f49..31c4894ca2 100644
--- a/boards/common/esp32/include/periph_conf_common.h
+++ b/boards/common/esp32/include/periph_conf_common.h
@@ -138,33 +138,92 @@ static const i2c_conf_t i2c_config[] = {
*/
/**
- * @brief Static array of GPIOs that can be used as channels of PWM0
+ * @brief GPIOs used as channels for the according PWM device
*/
#ifdef PWM0_GPIOS
-static const gpio_t pwm0_channels[] = PWM0_GPIOS;
+static const gpio_t pwm0_gpios[] = PWM0_GPIOS;
#endif
+
/**
- * @brief Static array of GPIOs that can be used as channels of PWM0
+ * @brief GPIOs used as channels for the according PWM device
*/
#ifdef PWM1_GPIOS
-static const gpio_t pwm1_channels[] = PWM1_GPIOS;
+static const gpio_t pwm1_gpios[] = PWM1_GPIOS;
#endif
+/**
+ * @brief GPIOs used as channels for the according PWM device
+ */
+#ifdef PWM2_GPIOS
+static const gpio_t pwm2_gpios[] = PWM2_GPIOS;
+#endif
+
+/**
+ * @brief GPIOs used as channels for the according PWM device
+ */
+#ifdef PWM3_GPIOS
+static const gpio_t pwm3_gpios[] = PWM3_GPIOS;
+#endif
+
+/**
+ * @brief PWM device configuration based on defined PWM channel GPIOs
+ */
+static const pwm_config_t pwm_config[] =
+{
+#ifdef PWM0_GPIOS
+ {
+ .module = PERIPH_LEDC_MODULE,
+ .group = LEDC_LOW_SPEED_MODE,
+ .timer = LEDC_TIMER_0,
+ .ch_numof = ARRAY_SIZE(pwm0_gpios),
+ .gpios = pwm0_gpios,
+ },
+#endif
+#ifdef PWM1_GPIOS
+ {
+ .module = PERIPH_LEDC_MODULE,
+#ifdef SOC_LEDC_SUPPORT_HS_MODE
+ .group = LEDC_HIGH_SPEED_MODE,
+#else
+ .group = LEDC_LOW_SPEED_MODE,
+#endif
+ .timer = LEDC_TIMER_1,
+ .ch_numof = ARRAY_SIZE(pwm1_gpios),
+ .gpios = pwm1_gpios,
+ },
+#endif
+#ifdef PWM2_GPIOS
+ {
+ .module = PERIPH_LEDC_MODULE,
+ .group = LEDC_LOW_SPEED_MODE,
+ .timer = LEDC_TIMER_2,
+ .ch_numof = ARRAY_SIZE(pwm2_gpios),
+ .gpios = pwm2_gpios,
+ },
+#endif
+#ifdef PWM3_GPIOS
+ {
+ .module = PERIPH_LEDC_MODULE,
+#ifdef SOC_LEDC_SUPPORT_HS_MODE
+ .group = LEDC_HIGH_SPEED_MODE,
+#else
+ .group = LEDC_LOW_SPEED_MODE,
+#endif
+ .timer = LEDC_TIMER_3,
+ .ch_numof = ARRAY_SIZE(pwm3_gpios),
+ .gpios = pwm3_gpios,
+ },
+#endif
+};
+
/**
* @brief Number of PWM devices
*
- * The number of PWM devices is determined from the PWM0_GPIOS and PWM1_GPIOS
- * definitions.
+ * The number of PWM devices is determined from the PWM device configuration.
*
* @note PWM_NUMOF definition must not be changed.
*/
-#if defined(PWM0_GPIOS) && defined(PWM1_GPIOS)
-#define PWM_NUMOF (2)
-#elif defined(PWM0_GPIOS) || defined(PWM1_GPIOS)
-#define PWM_NUMOF (1)
-#else
-#define PWM_NUMOF (0)
-#endif
+#define PWM_NUMOF ARRAY_SIZE(pwm_config)
/** @} */
diff --git a/boards/esp32-ethernet-kit-v1_0/include/periph_conf.h b/boards/esp32-ethernet-kit-v1_0/include/periph_conf.h
index d8f7682a9a..5c5663fef5 100644
--- a/boards/esp32-ethernet-kit-v1_0/include/periph_conf.h
+++ b/boards/esp32-ethernet-kit-v1_0/include/periph_conf.h
@@ -88,10 +88,6 @@
#endif
#endif /* PWM0_GPIOS */
-/** PWM_DEV(1) is not used */
-#ifndef PWM1_GPIOS
-#define PWM1_GPIOS { }
-#endif
/** @} */
/**
diff --git a/boards/esp32-mh-et-live-minikit/include/periph_conf.h b/boards/esp32-mh-et-live-minikit/include/periph_conf.h
index 719eb04904..15fe2760ae 100644
--- a/boards/esp32-mh-et-live-minikit/include/periph_conf.h
+++ b/boards/esp32-mh-et-live-minikit/include/periph_conf.h
@@ -101,10 +101,6 @@
#define PWM0_GPIOS { GPIO2, GPIO0, GPIO4, GPIO15 }
#endif
-/** PWM_DEV(1) is not used */
-#ifndef PWM1_GPIOS
-#define PWM1_GPIOS { }
-#endif
/** @} */
/**
diff --git a/boards/esp32-olimex-evb/include/periph_conf.h b/boards/esp32-olimex-evb/include/periph_conf.h
index 6ab9fd4e6e..3f3cfe610d 100644
--- a/boards/esp32-olimex-evb/include/periph_conf.h
+++ b/boards/esp32-olimex-evb/include/periph_conf.h
@@ -134,14 +134,9 @@ extern "C" {
#else
#error Configuration problem: Flash mode qio or qout is used, \
GPIO9 and GPIO10 cannot be used as PWM channels as configured
-#define PWM0_GPIOS { }
#endif
#endif
-/** PWM_DEV(1) is not used */
-#ifndef PWM1_GPIOS
-#define PWM1_GPIOS { }
-#endif
/** @} */
/**
diff --git a/boards/esp32-wemos-lolin-d32-pro/include/periph_conf.h b/boards/esp32-wemos-lolin-d32-pro/include/periph_conf.h
index 5c79d3ba13..9b6ec7001d 100644
--- a/boards/esp32-wemos-lolin-d32-pro/include/periph_conf.h
+++ b/boards/esp32-wemos-lolin-d32-pro/include/periph_conf.h
@@ -119,10 +119,6 @@
#define PWM0_GPIOS { GPIO0, GPIO2 }
#endif
-/** PWM_DEV(1) is not used */
-#ifndef PWM1_GPIOS
-#define PWM1_GPIOS { }
-#endif
/** @} */
/**
diff --git a/boards/esp32-wrover-kit/include/periph_conf.h b/boards/esp32-wrover-kit/include/periph_conf.h
index acccc5a673..0b1010cdd9 100644
--- a/boards/esp32-wrover-kit/include/periph_conf.h
+++ b/boards/esp32-wrover-kit/include/periph_conf.h
@@ -127,10 +127,6 @@
#endif
#endif
-/** PWM_DEV(1) is not used */
-#ifndef PWM1_GPIOS
-#define PWM1_GPIOS { }
-#endif
/** @} */
/**
diff --git a/cpu/esp32/doc.txt b/cpu/esp32/doc.txt
index 1d589ecf23..4ccc18f10f 100644
--- a/cpu/esp32/doc.txt
+++ b/cpu/esp32/doc.txt
@@ -99,6 +99,8 @@ Parameter | Short Description
[I2C1_SDA](#esp32_i2c_interfaces) | GPIO used as SCL for I2C_DEV(1) | o
[PWM0_GPIOS](#esp32_pwm_channels) | GPIOs that can be used at channels of PWM_DEV(0) | o
[PWM1_GPIOS](#esp32_pwm_channels) | GPIOs that can be used at channels of PWM_DEV(1) | o
+[PWM3_GPIOS](#esp32_pwm_channels) | GPIOs that can be used at channels of PWM_DEV(2) | o
+[PWM4_GPIOS](#esp32_pwm_channels) | GPIOs that can be used at channels of PWM_DEV(3) | o
[SPI0_CTRL](#esp32_spi_interfaces) | SPI Controller used for SPI_DEV(0), can be `VSPI` `HSPI` | o
[SPI0_SCK](#esp32_spi_interfaces) | GPIO used as SCK for SPI_DEV(0) | o
[SPI0_MOSI](#esp32_spi_interfaces) | GPIO used as MOSI for SPI_DEV(0) | o
@@ -184,8 +186,8 @@ The key features of ESP32 are:
| Ethernet | MAC interface with dedicated DMA and IEEE 1588 support | yes |
| CAN | version 2.0 | yes |
| IR | up to 8 channels TX/RX | no |
-| Motor PWM | 2 devices x 6 channels | yes |
-| LED PWM | 16 channels | no |
+| Motor PWM | 2 devices x 6 channels | no |
+| LED PWM | 16 channels | yes |
| Crypto | Hardware acceleration of AES, SHA-2, RSA, ECC, RNG | no |
| Vcc | 2.5 - 3.6 V | |
| Documents | [Datasheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf)
[Technical Reference](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf) | |
@@ -810,6 +812,62 @@ waiting.
ESP supports two types of PWM generators
+The implementation of the PWM peripheral driver uses the LED PWM Controller
+(LEDC) module of the ESP32x SoC. This LEDC module has one or two channel
+groups with 6 or 8 channels each. The channels of each channel group can
+use one of 4 timers as clock source. Thus, it is possible to define at
+4 or 8 virtual PWM devices in RIOT with different frequencies and
+resolutions. Regardless of whether the LEDC module of the ESP32x SoC has
+one or two channel groups, the PWM driver implementation allows to organize
+the available channels into up to 4 virtual PWM devices.
+
+The assignment of the available channels to the virtual PWM devices is
+done in the board-specific peripheral configuration by defining the
+macros `PWM0_GPIOS`, `PWM1_GPIOS`, `PWM2_GPIOS` and `PWM3_GPIOS` These
+macros specify the GPIOs that are used as channels for the available
+virtual PWM devices PWM_DEV(0) ... PWM_DEV(3) in RIOT. The mapping of
+these channels to the available channel groups and channel group timers
+is done by the driver automatically as follows:
+
+
+Macro | 1 Channel Group | 2 Channel Groups | Timer
+-------------|-----------------------|------------------------|---------------
+`PWM0_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_LOW_SPEED_MODE` | `LEDC_TIMER_0`
+`PWM1_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_HIGH_SPEED_MODE` | `LEDC_TIMER_1`
+`PWM2_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_LOW_SPEED_MODE` | `LEDC_TIMER_2`
+`PWM3_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_HIGH_SPEED_MODE` | `LEDC_TIMER_3`
+
+
+For example, if the LEDC module of the ESP32x SoC has two channel groups,
+two virtual PWM devices with 2 x 6/8 channels could be used by defining
+'PWM0_GPIOS' and 'PWM1_GPIOS' with up to 6 or 8 GPIOs each.
+
+Example:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
+#define PWM0_GPIOS { GPIO0, GPIO2, GPIO4, GPIO16, GPIO17 }
+#define PWM1_GPIOS { GPIO27, GPIO32, GPIO33 }
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This configuration can be changed by
+[application specific configurations](#esp32_application_specific_configurations).
+
+@note
+- The total number of channels defined for a channel group must not exceed
+ #PWM_CH_NUMOF_MAX.
+- The definition of `PWM0_GPIOS`, `PWM1_GPIOS`, `PWM2_GPIOS` and
+ `PWM3_GPIOS` can be omitted. In this case the existing macros should
+ be defined in ascending order, as the first defined macro is assigned
+ to PWM_DEV(0), the second defined macro is assigned to PWM_DEV(1)
+ and so on. So the minimal configuration would define all channels by
+ `PWM0_GPIOS` as PWM_DEV(0).
+- #PWM_NUMOF is determined automatically.
+- The order of the GPIOs in these macros determines the mapping between
+ RIOT's PWM channels and the GPIOs.
+- As long as the GPIOs listed in `PWM0_GPIOS`, `PWM1_GPIOS`,
+ `PWM2_GPIOS` and `PWM3_GPIOS` are not initialized as PWM channels with
+ the #pwm_init function, they can be used for other purposes.
+
+
- one LED PWM controller (LEDPWM) with 16 channels, and
- two high-speed Motor Control PWM controllers (MCPWM) with 6 channels each.
diff --git a/cpu/esp32/esp-idf-api/ledc.c b/cpu/esp32/esp-idf-api/ledc.c
new file mode 100644
index 0000000000..74a3dcb140
--- /dev/null
+++ b/cpu/esp32/esp-idf-api/ledc.c
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 Gunar Schorcht
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup cpu_esp32_esp_idf_api
+ * @{
+ *
+ * @file
+ * @brief Interface for the ESP-IDF LEDC HAL API
+ *
+ * @author Gunar Schorcht
+ * @}
+ */
+
+#include
+
+#include "driver/ledc.h"
+
+int esp_ledc_channel_config(const ledc_channel_config_t* ledc_conf)
+{
+ return ledc_channel_config(ledc_conf);
+}
+
+int esp_ledc_timer_config(const ledc_timer_config_t* timer_conf)
+{
+ return ledc_timer_config(timer_conf);
+}
+
+int esp_ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel)
+{
+ return ledc_update_duty(speed_mode, channel);
+}
+
+int esp_ledc_set_duty_with_hpoint(ledc_mode_t speed_mode,
+ ledc_channel_t channel,
+ uint32_t duty, uint32_t hpoint)
+{
+ return ledc_set_duty_with_hpoint(speed_mode, channel, duty, hpoint);
+}
diff --git a/cpu/esp32/include/esp_idf_api/ledc.h b/cpu/esp32/include/esp_idf_api/ledc.h
new file mode 100644
index 0000000000..442360ff55
--- /dev/null
+++ b/cpu/esp32/include/esp_idf_api/ledc.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 Gunar Schorcht
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup cpu_esp32_esp_idf_api
+ * @{
+ *
+ * @file
+ * @brief Interface for the ESP-IDF LEDC HAL API
+ *
+ * @author Gunar Schorcht
+ * @}
+ */
+
+#ifndef ESP_IDF_API_LEDC_H
+#define ESP_IDF_API_LEDC_H
+
+#include "hal/ledc_types.h"
+
+#ifndef DOXYGEN /* Hide implementation details from doxygen */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @name ESP-IDF interface wrapper functions
+ * @{
+ */
+int esp_ledc_channel_config(const ledc_channel_config_t* ledc_conf);
+int esp_ledc_timer_config(const ledc_timer_config_t* timer_conf);
+int esp_ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);
+int esp_ledc_set_duty_with_hpoint(ledc_mode_t speed_mode,
+ ledc_channel_t channel,
+ uint32_t duty, uint32_t hpoint);
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DOXYGEN */
+#endif /* ESP_IDF_API_LEDC_H */
diff --git a/cpu/esp32/include/periph_cpu.h b/cpu/esp32/include/periph_cpu.h
index ddd5d60b8a..a2312b316e 100644
--- a/cpu/esp32/include/periph_cpu.h
+++ b/cpu/esp32/include/periph_cpu.h
@@ -21,6 +21,10 @@
#include
#include "sdkconfig.h"
+#include "hal/ledc_types.h"
+#include "soc/ledc_struct.h"
+#include "soc/periph_defs.h"
+#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
@@ -363,39 +367,86 @@ typedef struct {
/**
* @name PWM configuration
*
- * PWM implementation uses ESP32's high-speed MCPWM modules. ESP32 has 2 such
- * modules, each with up to 6 channels (PWM_CHANNEL_NUM_DEV_MAX). Thus, the
- * maximum number of PWM devices is 2 and the maximum total number of PWM
- * channels is 12.
+ * The implementation of the PWM peripheral driver uses the LED PWM Controller
+ * (LEDC) module of the ESP32x SoC. This LEDC module has one or two channel
+ * groups with 6 or 8 channels each. The channels of each channel group can
+ * use one of 4 timers as clock source. Thus, it is possible to define at
+ * 4 or 8 virtual PWM devices in RIOT with different frequencies and
+ * resolutions. Regardless of whether the LEDC module of the ESP32x SoC has
+ * one or two channel groups, the PWM driver implementation allows to organize
+ * the available channels into up to 4 virtual PWM devices.
*
- * PWM0_GPIOS and PWM1_GPIOS in the board-specific peripheral configuration
- * each define a list of GPIOs that can be used with the respective PWM
- * devices as PWM channels. The order of the listed GPIOs determines the
- * association between the RIOT PWM channels and the GPIOs.
+ * The assignment of the available channels to the virtual PWM devices is
+ * done in the board-specific peripheral configuration by defining the
+ * macros `PWM0_GPIOS`, `PWM1_GPIOS`, `PWM2_GPIOS` and `PWM3_GPIOS` These
+ * macros specify the GPIOs that are used as channels for the available
+ * virtual PWM devices PWM_DEV(0) ... PWM_DEV(3) in RIOT. The mapping of
+ * these channels to the available channel groups and channel group timers
+ * is done by the driver automatically as follows.
*
- * @note The definition of PWM0_GPIOS and PWM1_GPIOS can be omitted or
- * empty. In the latter case, they must at least contain the curly braces.
- * The corresponding PWM device can not be used in this case.
+ *
+ * Macro | 1 Channel Group | 2 Channel Groups | Timer
+ * -------------|-----------------------|------------------------|---------------
+ * `PWM0_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_LOW_SPEED_MODE` | `LEDC_TIMER_0`
+ * `PWM1_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_HIGH_SPEED_MODE` | `LEDC_TIMER_1`
+ * `PWM2_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_LOW_SPEED_MODE` | `LEDC_TIMER_2`
+ * `PWM3_GPIOS` | `LEDC_LOW_SPEED_MODE` | `LEDC_HIGH_SPEED_MODE` | `LEDC_TIMER_3`
*
- * PWM_NUMOF is determined automatically from the PWM0_GPIOS and PWM1_GPIOS
- * definitions.
+ *
+ * For example, if the LEDC module of the ESP32x SoC has two channel groups,
+ * two virtual PWM devices with 2 x 6/8 channels could be used by defining
+ * 'PWM0_GPIOS' and 'PWM1_GPIOS' with 6/8 GPIOs each.
*
- * @note As long as the GPIOs listed in PWM0_GPIOS and PMW1_GPIOS are not
- * initialized as PWM channels with the *pwm_init* function, they can be used
- * other purposes.
+ * @note
+ * - The total number of channels defined for a channel group must not exceed
+ * #PWM_CH_NUMOF_MAX.
+ * - The definition of `PWM0_GPIOS`, `PWM1_GPIOS`, `PWM2_GPIOS` and
+ * `PWM3_GPIOS` can be omitted. In this case the existing macros should
+ * be defined in ascending order, as the first defined macro is assigned
+ * to PWM_DEV(0), the second defined macro is assigned to PWM_DEV(1)
+ * and so on. So the minimal configuration would define all channels by
+ * `PWM0_GPIOS` as PWM_DEV(0).
+ * - #PWM_NUMOF is determined automatically.
+ * - The order of the GPIOs in these macros determines the mapping between
+ * RIOT's PWM channels and the GPIOs.
+ * - As long as the GPIOs listed in `PWM0_GPIOS`, `PWM1_GPIOS`,
+ * `PWM2_GPIOS` and `PWM3_GPIOS` are not initialized as PWM channels with
+ * the #pwm_init function, they can be used for other purposes.
*
* @{
*/
+/**
+ * @brief PWM configuration structure type
+ *
+ * The implementation of the PWM peripheral driver uses the LED PWM Controller
+ * (LEDC) module of the ESP32x SoC. The LEDC module has up to 2 channel groups
+ * with 6 or 8 channels each, which can use one of 4 timers.
+ *
+ * Based on these maximum 2 channel groups with 6 or 8 channels each and 4
+ * timers, up to 4 PWM devices can be configured in RIOT. The configuration
+ * structure defines static parameters for each virtual PWM device, i.e.
+ * the channel group used, the timer used, the number of channels used and
+ * the GPIOs assigned to the channels. The number of channels used by a PWM
+ * device corresponds to the number of GPIOs assigned to this PWM device.
+ */
+typedef struct {
+ uint8_t module; /**< LEDC module identifier */
+ ledc_mode_t group; /**< LEDC channel group used (low/high speed) */
+ ledc_timer_t timer; /**< LEDC timer used by this device */
+ uint8_t ch_numof; /**< Number of channels used by this device */
+ const gpio_t *gpios; /**< GPIOs used as channels of this device */
+} pwm_config_t;
+
/**
* @brief Maximum number of PWM devices
*/
-#define PWM_NUMOF_MAX (2)
+#define PWM_NUMOF_MAX (4)
/**
* @brief Maximum number of channels per PWM device.
*/
-#define PWM_CHANNEL_NUM_DEV_MAX (6)
+#define PWM_CH_NUMOF_MAX (SOC_LEDC_CHANNEL_NUM)
/** @} */
diff --git a/cpu/esp32/periph/pwm.c b/cpu/esp32/periph/pwm.c
index 4e4f6b9af1..e1ad2afbed 100644
--- a/cpu/esp32/periph/pwm.c
+++ b/cpu/esp32/periph/pwm.c
@@ -18,410 +18,352 @@
* @}
*/
-#define ENABLE_DEBUG 0
-#include "debug.h"
-
+#include "bitarithm.h"
#include "board.h"
#include "cpu.h"
+#include "gpio_arch.h"
+#include "kernel_defines.h"
#include "log.h"
#include "irq_arch.h"
#include "periph/pwm.h"
#include "periph/gpio.h"
#include "esp_common.h"
-#include "gpio_arch.h"
+#include "esp_rom_gpio.h"
+#include "hal/ledc_hal.h"
+#include "soc/ledc_struct.h"
+#include "soc/rtc.h"
-#include "driver/periph_ctrl.h"
-#include "soc/gpio_struct.h"
-#include "soc/gpio_sig_map.h"
-#include "soc/mcpwm_reg.h"
-#include "soc/mcpwm_struct.h"
+#include "esp_idf_api/periph_ctrl.h"
-#if defined(PWM0_GPIOS) || defined(PWM1_GPIOS)
+#define ENABLE_DEBUG 0
+#include "debug.h"
-#define PWM_CLK (160000000UL) /* base clock of PWM devices */
-#define PWM_CPS_MAX (10000000UL) /* maximum cycles per second supported */
-#define PWM_CPS_MIN (2500UL) /* minimum cycles per second supported */
+#if defined(PWM0_GPIOS) || defined(PWM1_GPIOS) || defined(PWM2_GPIOS) || defined(PWM3_GPIOS)
-#define PWM_TIMER_MOD_FREEZE 0 /* timer is disabled */
-#define PWM_TIMER_MOD_UP 1 /* timer counts up */
-#define PWM_TIMER_MOD_DOWN 2 /* timer counts down */
-#define PWM_TIMER_MOD_UP_DOWN 3 /* timer counts up and then down */
+#define SOC_LEDC_CLK_DIV_BIT_NUM 18
+#define SOC_LEDC_CLK_DIV_INT_BIT_NUM 10 /* integral part of CLK divider */
+#define SOC_LEDC_CLK_DIV_FRAC_BIT_NUM 8 /* fractional part of CLK divider */
-#define PWM_TIMER_STOPS_AT_TEZ 0 /* PWM starts, then stops at next TEZ */
-#define PWM_TIMER_STOPS_AT_TEP 1 /* PWM starts, then stops at next TEP */
-#define PWM_TIMER_RUNS_ON 2 /* PWM runs on */
-#define PWM_TIMER_STARTS_STOPS_AT_TEZ 3 /* PWM starts and stops at next TEZ */
-#define PWM_TIMER_STARTS_STOPS_AT_TEP 4 /* PWM starts and stops at next TEP */
+#define PWM_HW_RES_MAX ((uint32_t)1 << SOC_LEDC_TIMER_BIT_WIDE_NUM)
+#define PWM_HW_RES_MIN ((uint32_t)1 << 1)
-#define PWM_TIMER_UPDATE_IMMIDIATE 0 /* update period immediately */
-#define PWM_TIMER_UPDATE_AT_TEZ 1 /* update period at TEZ */
-#define PWM_TIMER_UPDATE_AT_SYNC 2 /* update period at sync */
-#define PWM_TIMER_UPDATE_AT_TEZ_SYNC 3 /* update period at TEZ and sync */
+#define _DEV _pwm_dev[pwm] /* shortcut for PWM device descriptor */
+#define _CFG pwm_config[pwm] /* shortcut for PWM device configuration */
-#define PWM_OP_ACTION_NO_CHANGE 0 /* do not change output */
-#define PWM_OP_ACTION_LOW 1 /* set the output to high */
-#define PWM_OP_ACTION_HIGH 2 /* set the output to low */
-#define PWM_OP_ACTION_TOGGLE 3 /* toggle the output */
-
-#define PWM_OP_CHANNEL_A 0 /* operator channel A */
-#define PWM_OP_CHANNEL_B 0 /* operator channel B */
-
-/* forward declaration of internal functions */
-static void _pwm_start(pwm_t pwm);
-static void _pwm_stop(pwm_t pwm);
-static bool _pwm_configuration(void);
-
-/* data structure for static configuration of PWM devices */
-struct _pwm_hw_t {
- mcpwm_dev_t* regs; /* PWM's registers set address */
- uint8_t mod; /* PWM's hardware module */
- uint8_t int_src; /* PWM's peripheral interrupt source */
- uint32_t signal_group; /* PWM's base peripheral signal index */
- uint8_t gpio_num; /* number of GPIOs used as channels outputs */
- const gpio_t* gpios; /* GPIOs used as channel outputs */
-};
-
-#ifdef PWM0_GPIOS
-static const gpio_t _pwm_channel_gpios_0[] = PWM0_GPIOS;
-#endif
-
-#ifdef PWM1_GPIOS
-static const gpio_t _pwm_channel_gpios_1[] = PWM1_GPIOS;
-#endif
-
-/* static configuration of PWM devices */
-static const struct _pwm_hw_t _pwm_hw[] =
-{
-#ifdef PWM0_GPIOS
- {
- .regs = &MCPWM0,
- .mod = PERIPH_PWM0_MODULE,
- .int_src = ETS_PWM0_INTR_SOURCE,
- .signal_group = PWM0_OUT0A_IDX,
- .gpio_num = ARRAY_SIZE(_pwm_channel_gpios_0),
- .gpios = _pwm_channel_gpios_0,
- },
-#endif
-#ifdef PWM1_GPIOS
- {
- .regs = &MCPWM1,
- .mod = PERIPH_PWM1_MODULE,
- .int_src = ETS_PWM1_INTR_SOURCE,
- .signal_group = PWM1_OUT0A_IDX,
- .gpio_num = ARRAY_SIZE(_pwm_channel_gpios_1),
- .gpios = _pwm_channel_gpios_1,
- },
-#endif
-};
-
-/* data structure dynamic channel configuration */
+/* data structure for dynamic channel parameters */
typedef struct {
- bool used;
- uint32_t duty;
-} _pwm_chn_t;
+ uint32_t duty; /* actual duty value */
+ uint32_t hpoint; /* actual hpoing value */
+ bool used; /* true if the channel is set by pwm_set */
+ uint8_t ch; /* actual channel index within used channel group */
+} _pwm_ch_t;
-/* data structure for dynamic configuration of PWM devices */
-struct _pwm_dev_t {
- uint16_t res;
- uint32_t freq;
- pwm_mode_t mode;
- uint8_t chn_num;
- _pwm_chn_t chn[PWM_CHANNEL_NUM_DEV_MAX];
-};
+/* data structure for device handling at runtime */
+typedef struct {
+ uint32_t freq; /* specified frequency parameter */
+ uint32_t res; /* specified resolution parameter */
+ uint32_t hw_freq; /* used hardware frequency */
+ uint32_t hw_res; /* used hardware resolution */
+ uint32_t hw_clk_div; /* used hardware clock divider */
+ _pwm_ch_t ch[PWM_CH_NUMOF_MAX]; /* dynamic channel parameters at runtime */
+ ledc_hal_context_t hw; /* used hardware device context */
+ pwm_mode_t mode; /* specified mode */
+ ledc_timer_bit_t hw_res_bit; /* used hardware resolution in bit */
+ bool enabled; /* true if the device is used (powered on) */
+} _pwm_dev_t;
-/* dynamic configuration of PWM devices */
-static struct _pwm_dev_t _pwm_dev[PWM_NUMOF_MAX] = {};
+static _pwm_dev_t _pwm_dev[PWM_NUMOF] = { };
-/* if pwm_init is called first time, it checks the overall pwm configuration */
-static bool _pwm_init_first_time = true;
+/* if pwm_init is called first time, it checks the pwm configuration */
+static bool _pwm_initialized = false;
+
+/* static configuration checks and initialization on first pwm_init */
+static bool _pwm_initialize(void);
/* Initialize PWM device */
uint32_t pwm_init(pwm_t pwm, pwm_mode_t mode, uint32_t freq, uint16_t res)
{
- DEBUG ("%s pwm=%u mode=%u freq=%u, res=%u\n", __func__, pwm, mode, freq, res);
+ _Static_assert(PWM_NUMOF <= PWM_NUMOF_MAX, "Too many PWM devices defined");
- CHECK_PARAM_RET (pwm < PWM_NUMOF, 0);
- CHECK_PARAM_RET (freq > 0, 0);
-
- if (_pwm_init_first_time) {
- if (!_pwm_configuration())
+ if (!_pwm_initialized) {
+ if (!_pwm_initialize()) {
return 0;
+ }
+ _pwm_initialized = true;
}
- if (_pwm_hw[pwm].gpio_num == 0) {
- LOG_TAG_ERROR("pwm", "PWM device %d has no assigned pins\n", pwm);
+ assert(pwm < PWM_NUMOF);
+ assert(freq > 0);
+
+ _DEV.enabled = true;
+ _DEV.res = res;
+ _DEV.freq = freq;
+ _DEV.mode = mode;
+
+ if ((res < PWM_HW_RES_MIN) || (_DEV.res > PWM_HW_RES_MAX)) {
+ LOG_TAG_ERROR("pwm", "Resolution of PWM device %u to be in "
+ "range [%"PRIu32", %"PRIu32"]\n",
+ pwm, PWM_HW_RES_MIN, PWM_HW_RES_MAX);
return 0;
}
- /* reset by disabling and enable the PWM module */
- periph_module_disable(_pwm_hw[pwm].mod);
- periph_module_enable(_pwm_hw[pwm].mod);
+ /*
+ * The hardware resolution must be a power of two, so we determine the
+ * next power of two, which covers the desired resolution
+ */
+ ledc_timer_bit_t hw_res_bit = bitarithm_msb(res - 1);
+ if (hw_res_bit < SOC_LEDC_TIMER_BIT_WIDE_NUM) {
+ hw_res_bit++;
+ }
- _pwm_dev[pwm].res = res;
- _pwm_dev[pwm].freq = freq;
- _pwm_dev[pwm].mode = mode;
- _pwm_dev[pwm].chn_num = _pwm_hw[pwm].gpio_num;
+ uint32_t hw_res = 1 << hw_res_bit;
- for (int i = 0; i < _pwm_dev[pwm].chn_num; i++) {
+ uint32_t hw_ticks_max = rtc_clk_apb_freq_get();
+ uint32_t hw_ticks_min = hw_ticks_max / (1 << SOC_LEDC_CLK_DIV_INT_BIT_NUM);
+ uint32_t hw_freq_min = hw_ticks_min / (1 << SOC_LEDC_TIMER_BIT_WIDE_NUM) + 1;
+
+ if (freq < hw_freq_min) {
+ LOG_TAG_ERROR("pwm", "Frequency of %"PRIu32" Hz is too less, minimum "
+ "frequency is %"PRIu32" Hz\n", freq, hw_freq_min);
+ return 0;
+ }
+
+ /* number of hardware ticks required, at maximum it can be APB clock */
+ uint32_t hw_ticks = MIN(freq * hw_res, rtc_clk_apb_freq_get());
+
+ /*
+ * if the number of required ticks is less than minimum ticks supported by
+ * the hardware supports, we have to increase the resolution.
+ */
+ while (hw_ticks < hw_ticks_min) {
+ hw_res_bit++;
+ hw_res = 1 << hw_res_bit;
+ hw_ticks = freq * hw_res;
+ }
+
+ /* LEDC_CLK_DIV is given in Q10.8 format */
+ uint32_t hw_clk_div =
+ ((uint64_t)rtc_clk_apb_freq_get() << SOC_LEDC_CLK_DIV_FRAC_BIT_NUM) / hw_ticks;
+
+ _DEV.freq = freq;
+ _DEV.res = res;
+ _DEV.hw_freq = hw_ticks / hw_res;
+ _DEV.hw_res = hw_res;
+ _DEV.hw_res_bit = hw_res_bit;
+ _DEV.hw_clk_div = hw_clk_div;
+
+ DEBUG("%s hw_freq=%"PRIu32" hw_res=%"PRIu32" hw_ticks=%"PRIu32
+ " hw_clk_div=%"PRIu32"\n", __func__,
+ _DEV.hw_freq, _DEV.hw_res, hw_ticks, _DEV.hw_clk_div);
+
+ for (int i = 0; i < _CFG.ch_numof; i++) {
/* initialize channel data */
- _pwm_dev[pwm].chn[i].used = false;
- _pwm_dev[pwm].chn[i].duty = 0;
+ _DEV.ch[i].used = false;
+ _DEV.ch[i].duty = 0;
/* reset GPIO usage type if the pins were used already for PWM before
to make it possible to reinitialize PWM with new parameters */
- if (gpio_get_pin_usage(_pwm_hw[pwm].gpios[i]) == _PWM) {
- gpio_set_pin_usage(_pwm_hw[pwm].gpios[i], _GPIO);
+ if (gpio_get_pin_usage(_CFG.gpios[i]) == _PWM) {
+ gpio_set_pin_usage(_CFG.gpios[i], _GPIO);
}
- if (gpio_get_pin_usage(_pwm_hw[pwm].gpios[i]) != _GPIO) {
- LOG_TAG_ERROR("pwm", "GPIO%d is used for %s and cannot be used as PWM output\n", i,
- gpio_get_pin_usage_str(_pwm_hw[pwm].gpios[i]));
+ if (gpio_get_pin_usage(_CFG.gpios[i]) != _GPIO) {
+ LOG_TAG_ERROR("pwm", "GPIO%d is used for %s and cannot be used as "
+ "PWM output\n",
+ i, gpio_get_pin_usage_str(_CFG.gpios[i]));
return 0;
}
- if (gpio_init(_pwm_hw[pwm].gpios[i], GPIO_OUT) < 0) {
+ /* initialize the GPIOs and route the PWM signal output to the GPIO */
+ if (gpio_init(_CFG.gpios[i], GPIO_OUT) < 0) {
return 0;
}
- /* initialize the GPIO and route the PWM signal output to the GPIO */
- gpio_set_pin_usage(_pwm_hw[pwm].gpios[i], _GPIO);
- gpio_clear (_pwm_hw[pwm].gpios[i]);
- GPIO.func_out_sel_cfg[_pwm_hw[pwm].gpios[i]].func_sel = _pwm_hw[pwm].signal_group + i;
+ gpio_set_pin_usage(_CFG.gpios[i], _PWM);
+ gpio_clear(_CFG.gpios[i]);
+
+ esp_rom_gpio_connect_out_signal(
+ _CFG.gpios[i],
+ ledc_periph_signal[_CFG.group].sig_out0_idx + _DEV.ch[i].ch, 0, 0);
+
}
- /* start the PWM device */
- _pwm_start(pwm);
+ pwm_poweron(pwm);
- return freq;
+ return _DEV.hw_freq;
}
uint8_t pwm_channels(pwm_t pwm)
{
- CHECK_PARAM_RET (pwm < PWM_NUMOF, 0);
-
- return _pwm_hw[pwm].gpio_num;
+ assert(pwm < PWM_NUMOF);
+ return _CFG.ch_numof;
}
void pwm_set(pwm_t pwm, uint8_t channel, uint16_t value)
{
DEBUG("%s pwm=%u channel=%u value=%u\n", __func__, pwm, channel, value);
- CHECK_PARAM (pwm < PWM_NUMOF);
- CHECK_PARAM (channel < _pwm_dev[pwm].chn_num);
- CHECK_PARAM (value <= _pwm_dev[pwm].res);
+ assert(pwm < PWM_NUMOF);
+ assert(channel < _CFG.ch_numof);
- uint32_t state = irq_disable();
+ value = MIN(value, _DEV.res);
- _pwm_dev[pwm].chn[channel].duty = value;
- _pwm_dev[pwm].chn[channel].used = true;
+ _DEV.ch[channel].used = true;
+ _DEV.ch[channel].duty = value * _DEV.hw_res / _DEV.res;
+ _DEV.ch[channel].hpoint = 0;
- /* determine used operator and operator output */
- uint8_t op_idx = channel >> 1;
- uint8_t op_out = channel & 0x01;
-
- /* compute and set shadow register (compare) )value of according channel */
- uint16_t cmp = 0;
- switch (_pwm_dev[pwm].mode) {
- case PWM_LEFT: cmp = value;
- break;
- case PWM_RIGHT: cmp = value - 1;
- break;
- case PWM_CENTER: cmp = _pwm_hw[pwm].regs->timer[0].timer_cfg0.timer_period - value;
- break;
- }
- _pwm_hw[pwm].regs->operators[op_idx].timestamp[op_out].gen = cmp;
-
- /* set actions for timing events (reset all first) */
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].val = 0;
-
- if (op_out == 0)
- {
- /* channel/output A is used -> set actions for channel A */
- switch (_pwm_dev[pwm].mode)
- {
- case PWM_LEFT:
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_utez = PWM_OP_ACTION_HIGH;
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_utea = PWM_OP_ACTION_LOW;
- break;
-
- case PWM_RIGHT:
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dtea = PWM_OP_ACTION_HIGH;
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dtep = PWM_OP_ACTION_LOW;
- break;
-
- case PWM_CENTER:
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_utea = PWM_OP_ACTION_HIGH;
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dtea = PWM_OP_ACTION_LOW;
- break;
- }
- }
- else {
- /* channel/output B is used -> set actions for channel B */
- switch (_pwm_dev[pwm].mode)
- {
- case PWM_LEFT:
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_utez = PWM_OP_ACTION_HIGH;
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_uteb = PWM_OP_ACTION_LOW;
- break;
-
- case PWM_RIGHT:
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dteb = PWM_OP_ACTION_HIGH;
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dtep = PWM_OP_ACTION_LOW;
- break;
-
- case PWM_CENTER:
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_uteb = PWM_OP_ACTION_HIGH;
- _pwm_hw[pwm].regs->operators[op_idx].generator[op_out].gen_dteb = PWM_OP_ACTION_LOW;
- break;
- }
+ switch (_DEV.mode) {
+ case PWM_LEFT:
+ _DEV.ch[channel].hpoint = 0;
+ break;
+ case PWM_RIGHT:
+ _DEV.ch[channel].hpoint = _DEV.hw_res - 1 - _DEV.ch[channel].duty;
+ break;
+ case PWM_CENTER:
+ _DEV.ch[channel].hpoint = (_DEV.hw_res - _DEV.ch[channel].duty) >> 1;
+ break;
}
- irq_restore(state);
+ DEBUG("%s pwm=%u duty=%"PRIu32" hpoint=%"PRIu32"\n",
+ __func__, pwm, _DEV.ch[channel].duty, _DEV.ch[channel].hpoint);
+
+ unsigned ch = _DEV.ch[channel].ch; /* internal channel mapping */
+
+ critical_enter();
+ ledc_hal_set_duty_int_part(&_DEV.hw, ch, _DEV.ch[channel].duty);
+ ledc_hal_set_hpoint(&_DEV.hw, ch, _DEV.ch[channel].hpoint);
+ ledc_hal_set_sig_out_en(&_DEV.hw, ch, true);
+ ledc_hal_ls_channel_update(&_DEV.hw, ch);
+ ledc_hal_set_duty_start(&_DEV.hw, ch, true);
+ critical_exit();
}
void pwm_poweron(pwm_t pwm)
{
- CHECK_PARAM (pwm < PWM_NUMOF);
- periph_module_enable(_pwm_hw[pwm].mod);
- _pwm_start(pwm);
+ DEBUG("%s pwm=%u\n", __func__, pwm);
+
+ /* enable and init the module and select the right clock source */
+ esp_idf_periph_module_enable(_CFG.module);
+ ledc_hal_init(&_DEV.hw, _CFG.group);
+ ledc_hal_set_slow_clk_sel(&_DEV.hw, LEDC_SLOW_CLK_APB);
+ ledc_hal_set_clock_source(&_DEV.hw, _CFG.timer, LEDC_APB_CLK);
+
+ /* update the timer according to determined parameters */
+ ledc_hal_set_clock_divider(&_DEV.hw, _CFG.timer, _DEV.hw_clk_div);
+ ledc_hal_set_duty_resolution(&_DEV.hw, _CFG.timer, _DEV.hw_res_bit);
+ ledc_hal_ls_timer_update(&_DEV.hw, _CFG.timer);
+ ledc_hal_timer_rst(&_DEV.hw, _CFG.timer);
+
+ critical_enter();
+ for (unsigned i = 0; i < _CFG.ch_numof; i++) {
+ /* static configuration of the channel, no fading */
+ ledc_hal_set_duty_direction(&_DEV.hw, _DEV.ch[i].ch, 1);
+ ledc_hal_set_duty_num(&_DEV.hw, _DEV.ch[i].ch, 1);
+ ledc_hal_set_duty_cycle(&_DEV.hw, _DEV.ch[i].ch, 1);
+ ledc_hal_set_duty_scale(&_DEV.hw, _DEV.ch[i].ch, 0);
+ ledc_hal_set_fade_end_intr(&_DEV.hw, _DEV.ch[i].ch, 0);
+
+ /* bind the channel to the timer and disable the output for now */
+ ledc_hal_bind_channel_timer(&_DEV.hw, _DEV.ch[i].ch, _CFG.timer);
+
+ /* restore used parameters */
+ ledc_hal_set_duty_int_part(&_DEV.hw, _DEV.ch[i].ch, _DEV.ch[i].duty);
+ ledc_hal_set_hpoint(&_DEV.hw, _DEV.ch[i].ch, _DEV.ch[i].hpoint);
+ ledc_hal_set_sig_out_en(&_DEV.hw, _DEV.ch[i].ch, _DEV.ch[i].used);
+ ledc_hal_ls_channel_update(&_DEV.hw, _DEV.ch[i].ch);
+ ledc_hal_set_duty_start(&_DEV.hw, _DEV.ch[i].ch, _DEV.ch[i].used);
+ }
+ _DEV.enabled = true;
+ critical_exit();
}
void pwm_poweroff(pwm_t pwm)
{
- CHECK_PARAM (pwm < PWM_NUMOF);
- _pwm_stop (pwm);
- periph_module_disable(_pwm_hw[pwm].mod);
-}
+ DEBUG("%s pwm=%u\n", __func__, pwm);
-static void _pwm_start(pwm_t pwm)
-{
- pwm_mode_t mode = _pwm_dev[pwm].mode;
- uint16_t res = _pwm_dev[pwm].res;
- uint32_t freq = _pwm_dev[pwm].freq;
- uint32_t period = 0;
+ if (!_pwm_dev[pwm].enabled) {
+ return;
+ }
- /* set timer mode */
- switch (mode) {
- case PWM_LEFT:
- period = res;
- _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_UP;
- break;
- case PWM_RIGHT:
- period = res;
- _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_DOWN;
- break;
- case PWM_CENTER:
- period = res * 2;
- _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_UP_DOWN;
+ unsigned i;
+
+ /* disable the signal of all channels */
+ critical_enter();
+ for (i = 0; i < _CFG.ch_numof; i++) {
+ ledc_hal_set_idle_level(&_DEV.hw, _DEV.ch[i].ch, 0);
+ ledc_hal_set_sig_out_en(&_DEV.hw, _DEV.ch[i].ch, false);
+ ledc_hal_set_duty_start(&_DEV.hw, _DEV.ch[i].ch, false);
+ ledc_hal_ls_channel_update(&_DEV.hw, _DEV.ch[i].ch);
+ }
+ _DEV.enabled = false;
+ critical_exit();
+
+ /* check whether all devices of the same hardware module are disabled */
+ for (i = 0; i < PWM_NUMOF; i++) {
+ if ((_CFG.module == pwm_config[i].module) && _pwm_dev[i].enabled) {
break;
+ }
}
- uint32_t cps = period * freq;
- /* maximum number of timer clock cycles per second (freq*period) must not
- be greater than PWM_CPS_MAX, reduce the freq if necessary and keep
- the resolution */
- if (cps > PWM_CPS_MAX) {
- freq = PWM_CPS_MAX / period;
- _pwm_dev[pwm].freq = freq;
- DEBUG("%s freq*res was to high, freq was reduced to %d Hz\n",
- __func__, freq);
- }
- /* minimum number of timer clock cycles per second (freq*period) must not
- be less than PWM_CPS_MIN, increase the freq if necessary and keep
- the resolution */
- else if (cps < PWM_CPS_MIN) {
- freq = PWM_CPS_MIN / period;
- _pwm_dev[pwm].freq = freq;
- DEBUG("%s freq*res was to low, freq was increased to %d Hz\n",
- __func__, freq);
- }
-
- /* determine a suitable pwm clock prescale */
- uint32_t prescale;
- if (cps > 1000000) {
- /* pwm clock is not scaled,
- 8 bit timer prescaler can scale down timer clock to 625 kHz */
- prescale = 1;
- }
- else if (cps > 100000) {
- /* pwm clock is scaled down to 10 MHz,
- 8 bit timer prescaler can scale down timer clock to 39,0625 kHz */
- prescale = 16;
- }
- else if (cps > 10000) {
- /* pwm clock is scaled down to 1 MHz
- 8 bit timer prescaler can scale down timer clock to 3,90625 kHz */
- prescale = 160;
- }
- else {
- /* pwm clock is scaled down to 640 kHz
- 8 bit timer prescaler can scale down timer clock to 2,5 kHz */
- prescale = 250;
- }
- _pwm_hw[pwm].regs->clk_cfg.clk_prescale = prescale - 1;
-
- /* set timing parameters (only timer0 is used) */
- _pwm_hw[pwm].regs->timer[0].timer_cfg0.timer_prescale = (PWM_CLK / prescale / cps) - 1;
- _pwm_hw[pwm].regs->timer[0].timer_cfg0.timer_period = (mode == PWM_CENTER) ? res : res - 1;
- _pwm_hw[pwm].regs->timer[0].timer_cfg0.timer_period_upmethod = PWM_TIMER_UPDATE_IMMIDIATE;
-
- /* start the timer */
- _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_start = PWM_TIMER_RUNS_ON;
-
- /* set timer sync phase and enable timer sync input */
- _pwm_hw[pwm].regs->timer[0].timer_sync.timer_phase = 0;
- _pwm_hw[pwm].regs->timer[0].timer_sync.timer_synci_en = 1;
-
- /* set the duty for all channels to start them */
- for (int i = 0; i < _pwm_dev[pwm].chn_num; i++) {
- if (_pwm_dev[pwm].chn[i].used)
- pwm_set(pwm, i, _pwm_dev[pwm].chn[i].duty);
- }
-
- /* sync all timers */
- for (unsigned i = 0; i < PWM_NUMOF; i++) {
- _pwm_hw[i].regs->timer[0].timer_sync.timer_sync_sw =
- ~_pwm_hw[i].regs->timer[0].timer_sync.timer_sync_sw;
+ /* if all devices of the same hardware module are disable, it is powered off */
+ if (i == PWM_NUMOF) {
+ esp_idf_periph_module_disable(_CFG.module);
}
}
-static void _pwm_stop(pwm_t pwm)
+void pwm_print_config(void)
{
- /* disable the timer */
- _pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_FREEZE;
+ for (unsigned pwm = 0; pwm < PWM_NUMOF; pwm++) {
+ printf("\tPWM_DEV(%d)\tchannels=[ ", pwm);
+ for (int i = 0; i < _CFG.ch_numof; i++) {
+ printf("%d ", _CFG.gpios[i]);
+ }
+ printf("]\n");
+ }
}
-/* do some static initialization and configuration checks */
-static bool _pwm_configuration(void)
+/* do static configuration checks */
+static bool _pwm_initialize(void)
{
- if (PWM_NUMOF > PWM_NUMOF_MAX) {
- LOG_TAG_ERROR("pwm", "%d PWM devices were defined, only %d PWM are "
- "supported\n", PWM_NUMOF, PWM_NUMOF_MAX);
- return false;
- }
+ unsigned ch_numof[2] = {};
- for (unsigned i = 0; i < PWM_NUMOF; i++) {
- if (_pwm_hw[i].gpio_num > PWM_CHANNEL_NUM_DEV_MAX) {
+ for (unsigned pwm = 0; pwm < PWM_NUMOF; pwm++) {
+ /* compute the channel indices */
+ for (unsigned i = 0; i < _CFG.ch_numof; i++) {
+ _pwm_dev[pwm].ch[i].ch = ch_numof[_CFG.group] + i;
+ }
+ ch_numof[_CFG.group] += _CFG.ch_numof;
+ if (_CFG.ch_numof > PWM_CH_NUMOF_MAX) {
LOG_TAG_ERROR("pwm", "Number of PWM channels of device %d is %d, "
- "at maximum only %d channels per PWM device are "
- "supported\n",
- i, _pwm_hw[i].gpio_num, PWM_CHANNEL_NUM_DEV_MAX);
+ "only %d channels per PWM device are supported\n",
+ pwm, _CFG.ch_numof, PWM_CH_NUMOF_MAX);
return false;
}
}
+
+ unsigned total_ch_numof = ch_numof[0] + ch_numof[1];
+
+ if (total_ch_numof > (SOC_LEDC_CHANNEL_NUM * ARRAY_SIZE(ledc_periph_signal))) {
+ LOG_TAG_ERROR("pwm", "Total number of PWM channels is %d, only "
+ "%d channels are supported at maximum\n", total_ch_numof,
+ PWM_CH_NUMOF_MAX * ARRAY_SIZE(ledc_periph_signal));
+ return false;
+ }
+
bool multiple_used = false;
- for (unsigned i = 0; i < PWM_NUMOF; i++) {
- for (unsigned j = 0; j < PWM_NUMOF; j++) {
- if (i != j) {
- for (unsigned k = 0; k < _pwm_hw[i].gpio_num >> 2; k++) {
- for (unsigned l = 0; l < _pwm_hw[j].gpio_num >> 2; l++) {
- if (_pwm_hw[i].gpios[k] == _pwm_hw[j].gpios[l]) {
- LOG_TAG_ERROR("pwm", "GPIO%d is used multiple times in "
- "PWM devices %d and %d\n",
- _pwm_hw[i].gpios[k], i, j);
- multiple_used = true;
- }
+ for (unsigned pi = 0; pi < PWM_NUMOF; pi++) {
+ for (unsigned ci = 0; ci < pwm_config[pi].ch_numof; ci++) {
+ for (unsigned pj = 0; pj < PWM_NUMOF; pj++) {
+ if (pi == pj) {
+ continue;
+ }
+ for (unsigned cj = 0; cj < pwm_config[pj].ch_numof; cj++) {
+ if (pwm_config[pi].gpios[ci] == pwm_config[pj].gpios[cj]) {
+ LOG_TAG_ERROR("pwm", "GPIO%d is used multiple times in "
+ "PWM devices %d and %d\n",
+ pwm_config[pi].gpios[ci], pi, pj);
+ multiple_used = true;
}
}
}
@@ -434,22 +376,11 @@ static bool _pwm_configuration(void)
return true;
}
-void pwm_print_config(void)
-{
- for (unsigned pwm = 0; pwm < PWM_NUMOF; pwm++) {
- printf("\tPWM_DEV(%d)\tchannels=[ ", pwm);
- for (int i = 0; i < _pwm_hw[pwm].gpio_num; i++) {
- printf("%d ", _pwm_hw[pwm].gpios[i]);
- }
- printf("]\n");
- }
-}
-
-#else /* defined(PWM0_GPIOS) || defined(PWM1_GPIOS) */
+#else
void pwm_print_config(void)
{
LOG_TAG_INFO("pwm", "no PWM devices\n");
}
-#endif /* defined(PWM0_GPIOS) || defined(PWM1_GPIOS) */
+#endif