mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-18 12:52:44 +01:00
Merge pull request #18276 from gschorcht/cpu/esp32/periph_hal_esp32_pwm
cpu/esp32: use ESP-IDF ledc HAL for periph/pwm
This commit is contained in:
commit
94e9116c04
@ -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)
|
||||
|
||||
/** @} */
|
||||
|
||||
|
@ -88,10 +88,6 @@
|
||||
#endif
|
||||
#endif /* PWM0_GPIOS */
|
||||
|
||||
/** PWM_DEV(1) is not used */
|
||||
#ifndef PWM1_GPIOS
|
||||
#define PWM1_GPIOS { }
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
@ -119,10 +119,6 @@
|
||||
#define PWM0_GPIOS { GPIO0, GPIO2 }
|
||||
#endif
|
||||
|
||||
/** PWM_DEV(1) is not used */
|
||||
#ifndef PWM1_GPIOS
|
||||
#define PWM1_GPIOS { }
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
@ -127,10 +127,6 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/** PWM_DEV(1) is not used */
|
||||
#ifndef PWM1_GPIOS
|
||||
#define PWM1_GPIOS { }
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
@ -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) <br> [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:
|
||||
|
||||
<center>
|
||||
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`
|
||||
|
||||
</center>
|
||||
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.
|
||||
|
||||
|
44
cpu/esp32/esp-idf-api/ledc.c
Normal file
44
cpu/esp32/esp-idf-api/ledc.c
Normal file
@ -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 <gunar@schorcht.net>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#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);
|
||||
}
|
48
cpu/esp32/include/esp_idf_api/ledc.h
Normal file
48
cpu/esp32/include/esp_idf_api/ledc.h
Normal file
@ -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 <gunar@schorcht.net>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#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 */
|
@ -21,6 +21,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#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.
|
||||
* <center>
|
||||
* 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.
|
||||
* </center>
|
||||
* 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)
|
||||
|
||||
/** @} */
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user