1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/esp32/periph/pwm.c

456 lines
15 KiB
C
Raw Normal View History

2018-10-08 12:20:49 +02:00
/*
* Copyright (C) 2018 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
* @ingroup drivers_periph_pwm
* @{
*
* @file
* @brief Low-level PWM driver implementation
*
* @author Gunar Schorcht <gunar@schorcht.net>
* @}
*/
#define ENABLE_DEBUG 0
#include "debug.h"
#include "board.h"
#include "cpu.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 "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"
#if defined(PWM0_GPIOS) || defined(PWM1_GPIOS)
#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 */
2018-10-08 12:20:49 +02:00
#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 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_TIMER_UPDATE_IMMIDIATE 0 /* update period immediately */
2018-10-08 12:20:49 +02:00
#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 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 */
};
2022-02-01 22:00:16 +01:00
#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
2018-10-08 12:20:49 +02:00
/* static configuration of PWM devices */
static const struct _pwm_hw_t _pwm_hw[] =
{
2022-02-01 22:00:16 +01:00
#ifdef PWM0_GPIOS
2018-10-08 12:20:49 +02:00
{
.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,
2018-10-08 12:20:49 +02:00
},
2022-02-01 22:00:16 +01:00
#endif
#ifdef PWM1_GPIOS
2018-10-08 12:20:49 +02:00
{
.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,
2018-10-08 12:20:49 +02:00
},
2022-02-01 22:00:16 +01:00
#endif
2018-10-08 12:20:49 +02:00
};
/* data structure dynamic channel configuration */
typedef struct {
bool used;
uint32_t duty;
} _pwm_chn_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];
};
/* dynamic configuration of PWM devices */
static struct _pwm_dev_t _pwm_dev[PWM_NUMOF_MAX] = {};
/* if pwm_init is called first time, it checks the overall pwm configuration */
static bool _pwm_init_first_time = true;
/* 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);
CHECK_PARAM_RET (pwm < PWM_NUMOF, 0);
2018-10-08 12:20:49 +02:00
CHECK_PARAM_RET (freq > 0, 0);
if (_pwm_init_first_time) {
if (!_pwm_configuration())
return 0;
}
if (_pwm_hw[pwm].gpio_num == 0) {
LOG_TAG_ERROR("pwm", "PWM device %d has no assigned pins\n", pwm);
return 0;
}
/* reset by disabling and enable the PWM module */
periph_module_disable(_pwm_hw[pwm].mod);
periph_module_enable(_pwm_hw[pwm].mod);
_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;
for (int i = 0; i < _pwm_dev[pwm].chn_num; i++) {
/* initialize channel data */
_pwm_dev[pwm].chn[i].used = false;
_pwm_dev[pwm].chn[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(_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]));
return 0;
}
if (gpio_init(_pwm_hw[pwm].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;
}
/* start the PWM device */
_pwm_start(pwm);
return freq;
}
uint8_t pwm_channels(pwm_t pwm)
{
CHECK_PARAM_RET (pwm < PWM_NUMOF, 0);
2018-10-08 12:20:49 +02:00
return _pwm_hw[pwm].gpio_num;
}
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);
2018-10-08 12:20:49 +02:00
CHECK_PARAM (channel < _pwm_dev[pwm].chn_num);
CHECK_PARAM (value <= _pwm_dev[pwm].res);
uint32_t state = irq_disable();
_pwm_dev[pwm].chn[channel].duty = value;
_pwm_dev[pwm].chn[channel].used = true;
/* 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;
2018-10-08 12:20:49 +02:00
break;
}
_pwm_hw[pwm].regs->operators[op_idx].timestamp[op_out].gen = cmp;
2018-10-08 12:20:49 +02:00
/* set actions for timing events (reset all first) */
_pwm_hw[pwm].regs->operators[op_idx].generator[op_out].val = 0;
2018-10-08 12:20:49 +02:00
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;
2018-10-08 12:20:49 +02:00
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;
2018-10-08 12:20:49 +02:00
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;
2018-10-08 12:20:49 +02:00
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;
2018-10-08 12:20:49 +02:00
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;
2018-10-08 12:20:49 +02:00
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;
2018-10-08 12:20:49 +02:00
break;
}
}
irq_restore(state);
}
void pwm_poweron(pwm_t pwm)
{
CHECK_PARAM (pwm < PWM_NUMOF);
2018-10-08 12:20:49 +02:00
periph_module_enable(_pwm_hw[pwm].mod);
_pwm_start(pwm);
}
void pwm_poweroff(pwm_t pwm)
{
CHECK_PARAM (pwm < PWM_NUMOF);
2018-10-08 12:20:49 +02:00
_pwm_stop (pwm);
periph_module_disable(_pwm_hw[pwm].mod);
}
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;
/* set timer mode */
switch (mode) {
case PWM_LEFT:
period = res;
_pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_UP;
2018-10-08 12:20:49 +02:00
break;
case PWM_RIGHT:
period = res;
_pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_DOWN;
2018-10-08 12:20:49 +02:00
break;
case PWM_CENTER:
period = res * 2;
_pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_UP_DOWN;
2018-10-08 12:20:49 +02:00
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
2018-10-08 12:20:49 +02:00
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
2018-10-08 12:20:49 +02:00
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;
2018-10-08 12:20:49 +02:00
/* 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;
2018-10-08 12:20:49 +02:00
/* start the timer */
_pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_start = PWM_TIMER_RUNS_ON;
2018-10-08 12:20:49 +02:00
/* 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;
2018-10-08 12:20:49 +02:00
/* 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;
2018-10-08 12:20:49 +02:00
}
}
static void _pwm_stop(pwm_t pwm)
{
/* disable the timer */
_pwm_hw[pwm].regs->timer[0].timer_cfg1.timer_mod = PWM_TIMER_MOD_FREEZE;
2018-10-08 12:20:49 +02:00
}
/* do some static initialization and configuration checks */
static bool _pwm_configuration(void)
{
if (PWM_NUMOF > PWM_NUMOF_MAX) {
2018-10-08 12:20:49 +02:00
LOG_TAG_ERROR("pwm", "%d PWM devices were defined, only %d PWM are "
"supported\n", PWM_NUMOF, PWM_NUMOF_MAX);
2018-10-08 12:20:49 +02:00
return false;
}
for (unsigned i = 0; i < PWM_NUMOF; i++) {
2018-10-08 12:20:49 +02:00
if (_pwm_hw[i].gpio_num > PWM_CHANNEL_NUM_DEV_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);
return false;
}
}
bool multiple_used = false;
for (unsigned i = 0; i < PWM_NUMOF; i++) {
for (unsigned j = 0; j < PWM_NUMOF; j++) {
2018-10-08 12:20:49 +02:00
if (i != j) {
for (unsigned k = 0; k < _pwm_hw[i].gpio_num >> 2; k++) {
2018-10-09 11:27:54 +02:00
for (unsigned l = 0; l < _pwm_hw[j].gpio_num >> 2; l++) {
2018-10-08 12:20:49 +02:00
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;
}
}
}
}
}
}
if (multiple_used) {
return false;
}
return true;
}
void pwm_print_config(void)
{
for (unsigned pwm = 0; pwm < PWM_NUMOF; pwm++) {
printf("\tPWM_DEV(%d)\tchannels=[ ", pwm);
2018-10-08 12:20:49 +02:00
for (int i = 0; i < _pwm_hw[pwm].gpio_num; i++) {
printf("%d ", _pwm_hw[pwm].gpios[i]);
2018-10-08 12:20:49 +02:00
}
printf("]\n");
2018-10-08 12:20:49 +02:00
}
}
#else /* defined(PWM0_GPIOS) || defined(PWM1_GPIOS) */
void pwm_print_config(void)
{
LOG_TAG_INFO("pwm", "no PWM devices\n");
}
#endif /* defined(PWM0_GPIOS) || defined(PWM1_GPIOS) */