@ -3,7 +3,10 @@
*
* The MIT License ( MIT )
*
* Copyright ( c ) 2016 Damien P . George
* Copyright ( c ) 2016 - 2021 Damien P . George
* Copyright ( c ) 2018 Alan Dragomirecky
* Copyright ( c ) 2020 Antoine Aubert
* Copyright ( c ) 2021 Ihor Nehrutsa
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ) , to deal
@ -30,54 +33,105 @@
# include "driver/ledc.h"
# include "esp_err.h"
// Which channel has which GPIO pin assigned?
// (-1 if not assigned)
STATIC int chan_gpio [ LEDC_CHANNEL_MAX ] ;
# define PWM_DBG(...)
// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__)
// Total number of channels
# define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX)
typedef struct _chan_t {
// Which channel has which GPIO pin assigned?
// (-1 if not assigned)
gpio_num_t pin ;
// Which channel has which timer assigned?
// (-1 if not assigned)
int timer_idx ;
} chan_t ;
// List of PWM channels
STATIC chan_t chans [ PWM_CHANNEL_MAX ] ;
// channel_idx is an index (end-to-end sequential numbering) for all channels
// available on the chip and described in chans[]
# define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel)
# define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX)
# define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX)
// Total number of timers
# define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX)
// List of timer configs
STATIC ledc_timer_config_t timers [ PWM_TIMER_MAX ] ;
// timer_idx is an index (end-to-end sequential numbering) for all timers
// available on the chip and configured in timers[]
# define TIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer)
# define TIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX)
# define TIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX)
// Params for PW operation
// 5khz
// 5khz is default frequency
# define PWFREQ (5000)
// High speed mode
# if CONFIG_IDF_TARGET_ESP32
# define PWMODE (LEDC_HIGH_SPEED_MODE)
# else
# define PWMODE (LEDC_LOW_SPEED_MODE)
# endif
// 10-bit resolution (compatible with esp8266 PWM)
# define PWRES (LEDC_TIMER_10_BIT)
// Timer 1
# define PWTIMER (LEDC_TIMER_1)
// Config of timer upon which we run all PWM'ed GPIO pins
STATIC bool pwm_inited = false ;
STATIC ledc_timer_config_t timer_cfg = {
. duty_resolution = PWRES ,
. freq_hz = PWFREQ ,
. speed_mode = PWMODE ,
. timer_num = PWTIMER
} ;
STATIC void pwm_init ( void ) {
// MicroPython PWM object struct
typedef struct _machine_pwm_obj_t {
mp_obj_base_t base ;
gpio_num_t pin ;
bool active ;
int mode ;
int channel ;
int timer ;
} machine_pwm_obj_t ;
STATIC void pwm_init ( void ) {
// Initial condition: no channels assigned
for ( int x = 0 ; x < LEDC_CHANNEL_MAX ; + + x ) {
chan_gpio [ x ] = - 1 ;
for ( int i = 0 ; i < PWM_CHANNEL_MAX ; + + i ) {
chans [ i ] . pin = - 1 ;
chans [ i ] . timer_idx = - 1 ;
}
// Init with default timer params
ledc_timer_config ( & timer_cfg ) ;
// Prepare all timers config
// Initial condition: no timers assigned
for ( int i = 0 ; i < PWM_TIMER_MAX ; + + i ) {
timers [ i ] . duty_resolution = PWRES ;
// unset timer is -1
timers [ i ] . freq_hz = - 1 ;
timers [ i ] . speed_mode = TIMER_IDX_TO_MODE ( i ) ;
timers [ i ] . timer_num = TIMER_IDX_TO_TIMER ( i ) ;
timers [ i ] . clk_cfg = LEDC_AUTO_CLK ;
}
}
STATIC int set_freq ( int newval ) {
int ores = timer_cfg . duty_resolution ;
int oval = timer_cfg . freq_hz ;
STATIC void configure_channel ( machine_pwm_obj_t * self ) {
ledc_channel_config_t cfg = {
. channel = self - > channel ,
. duty = ( 1 < < ( timers [ TIMER_IDX ( self - > mode , self - > timer ) ] . duty_resolution ) ) / 2 ,
. gpio_num = self - > pin ,
. intr_type = LEDC_INTR_DISABLE ,
. speed_mode = self - > mode ,
. timer_sel = self - > timer ,
} ;
if ( ledc_channel_config ( & cfg ) ! = ESP_OK ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " PWM not supported on Pin(%d) " ) , self - > pin ) ;
}
}
STATIC void set_freq ( int newval , ledc_timer_config_t * timer ) {
// If already set, do nothing
if ( newval = = timer - > freq_hz ) {
return ;
}
// Find the highest bit resolution for the requested frequency
if ( newval < = 0 ) {
newval = 1 ;
}
unsigned int res = 0 ;
for ( unsigned int i = LEDC_APB_CLK_HZ / newval ; i > 1 ; i > > = 1 , + + res ) {
for ( unsigned int i = LEDC_APB_CLK_HZ / newval ; i > 1 ; i > > = 1 ) {
+ + res ;
}
if ( res = = 0 ) {
res = 1 ;
@ -87,32 +141,113 @@ STATIC int set_freq(int newval) {
}
// Configure the new resolution and frequency
timer_cfg . duty_resolution = res ;
timer_cfg . freq_hz = newval ;
if ( ledc_timer_config ( & timer_cfg ) ! = ESP_OK ) {
timer_cfg . duty_resolution = ores ;
timer_cfg . freq_hz = oval ;
return 0 ;
timer - > duty_resolution = res ;
timer - > freq_hz = newval ;
// set freq
esp_err_t err = ledc_timer_config ( timer ) ;
if ( err ! = ESP_OK ) {
if ( err = = ESP_FAIL ) {
PWM_DBG ( " timer timer->speed_mode %d, timer->timer_num %d, timer->clk_cfg %d, timer->freq_hz %d, timer->duty_resolution %d) " , timer - > speed_mode , timer - > timer_num , timer - > clk_cfg , timer - > freq_hz , timer - > duty_resolution ) ;
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " bad frequency %d " ) , newval ) ;
} else {
check_esp_err ( err ) ;
}
}
return 1 ;
}
STATIC int get_duty ( machine_pwm_obj_t * self ) {
uint32_t duty = ledc_get_duty ( self - > mode , self - > channel ) ;
duty < < = PWRES - timers [ TIMER_IDX ( self - > mode , self - > timer ) ] . duty_resolution ;
return duty ;
}
STATIC void set_duty ( machine_pwm_obj_t * self , int duty ) {
if ( ( duty < 0 ) | | ( duty > ( 1 < < PWRES ) - 1 ) ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " duty must be between 0 and %u " ) , ( 1 < < PWRES ) - 1 ) ;
}
duty & = ( 1 < < PWRES ) - 1 ;
duty > > = PWRES - timers [ TIMER_IDX ( self - > mode , self - > timer ) ] . duty_resolution ;
check_esp_err ( ledc_set_duty ( self - > mode , self - > channel , duty ) ) ;
check_esp_err ( ledc_update_duty ( self - > mode , self - > channel ) ) ;
// check_esp_err(ledc_set_duty_and_update(self->mode, self->channel, duty, (1 << PWRES) - 1)); // thread safe function ???
// Bug: Sometimes duty is not set right now.
// See https://github.com/espressif/esp-idf/issues/7288
/*
if ( duty ! = get_duty ( self ) ) {
PWM_DBG ( " \n duty_set %u %u %d %d \n " , duty , get_duty ( self ) , PWRES , timers [ TIMER_IDX ( self - > mode , self - > timer ) ] . duty_resolution ) ;
}
*/
}
/******************************************************************************/
// MicroPython bindings for PWM
# define SAME_FREQ_ONLY (true)
# define SAME_FREQ_OR_FREE (false)
# define ANY_MODE (-1)
// Return timer_idx. Use TIMER_IDX_TO_MODE(timer_idx) and TIMER_IDX_TO_TIMER(timer_idx) to get mode and timer
STATIC int find_timer ( int freq , bool same_freq_only , int mode ) {
int free_timer_idx_found = - 1 ;
// Find a free PWM Timer using the same freq
for ( int timer_idx = 0 ; timer_idx < PWM_TIMER_MAX ; + + timer_idx ) {
if ( ( mode = = ANY_MODE ) | | ( mode = = TIMER_IDX_TO_MODE ( timer_idx ) ) ) {
if ( timers [ timer_idx ] . freq_hz = = freq ) {
// A timer already uses the same freq. Use it now.
return timer_idx ;
}
if ( ! same_freq_only & & ( free_timer_idx_found = = - 1 ) & & ( timers [ timer_idx ] . freq_hz = = - 1 ) ) {
free_timer_idx_found = timer_idx ;
// Continue to check if a channel with the same freq is in use.
}
}
}
typedef struct _machine_pwm_obj_t {
mp_obj_base_t base ;
gpio_num_t pin ;
uint8_t active ;
uint8_t channel ;
} machine_pwm_obj_t ;
return free_timer_idx_found ;
}
// Return true if the timer is in use in addition to current channel
STATIC bool is_timer_in_use ( int current_channel_idx , int timer_idx ) {
for ( int i = 0 ; i < PWM_CHANNEL_MAX ; + + i ) {
if ( ( i ! = current_channel_idx ) & & ( chans [ i ] . timer_idx = = timer_idx ) ) {
return true ;
}
}
return false ;
}
// Find a free PWM channel, also spot if our pin is already mentioned.
// Return channel_idx. Use CHANNEL_IDX_TO_MODE(channel_idx) and CHANNEL_IDX_TO_CHANNEL(channel_idx) to get mode and channel
STATIC int find_channel ( int pin , int mode ) {
int avail_idx = - 1 ;
int channel_idx ;
for ( channel_idx = 0 ; channel_idx < PWM_CHANNEL_MAX ; + + channel_idx ) {
if ( ( mode = = ANY_MODE ) | | ( mode = = CHANNEL_IDX_TO_MODE ( channel_idx ) ) ) {
if ( chans [ channel_idx ] . pin = = pin ) {
break ;
}
if ( ( avail_idx = = - 1 ) & & ( chans [ channel_idx ] . pin = = - 1 ) ) {
avail_idx = channel_idx ;
}
}
}
if ( channel_idx > = PWM_CHANNEL_MAX ) {
channel_idx = avail_idx ;
}
return channel_idx ;
}
/******************************************************************************/
// MicroPython bindings for PWM
STATIC void mp_machine_pwm_print ( const mp_print_t * print , mp_obj_t self_in , mp_print_kind_t kind ) {
machine_pwm_obj_t * self = MP_OBJ_TO_PTR ( self_in ) ;
mp_printf ( print , " PWM(%u " , self - > pin ) ;
mp_printf ( print , " PWM(pin= %u " , self - > pin ) ;
if ( self - > active ) {
mp_printf ( print , " , freq=%u, duty=%u " , timer_cfg . freq_hz ,
ledc_get_duty ( PWMODE , self - > channel ) ) ;
int duty = get_duty ( self ) ;
mp_printf ( print , " , freq=%u, duty=%u " , ledc_get_freq ( self - > mode , self - > timer ) , duty ) ;
mp_printf ( print , " , resolution=%u " , timers [ TIMER_IDX ( self - > mode , self - > timer ) ] . duty_resolution ) ;
mp_printf ( print , " , mode=%d, channel=%d, timer=%d " , self - > mode , self - > channel , self - > timer ) ;
}
mp_printf ( print , " ) " ) ;
}
@ -128,61 +263,72 @@ STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self,
mp_arg_parse_all ( n_args , pos_args , kw_args ,
MP_ARRAY_SIZE ( allowed_args ) , allowed_args , args ) ;
int channel ;
int avail = - 1 ;
int channel_idx = find_channel ( self - > pin , ANY_MODE ) ;
if ( channel_idx = = - 1 ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " out of PWM channels:%d " ) , PWM_CHANNEL_MAX ) ; // in all modes
}
// Find a free PWM channel, also spot if our pin is
// already mentioned.
for ( channel = 0 ; channel < LEDC_CHANNEL_MAX ; + + channel ) {
if ( chan_gpio [ channel ] = = self - > pin ) {
break ;
int freq = args [ ARG_freq ] . u_int ;
if ( ( freq < - 1 ) | | ( freq > 40000000 ) ) {
mp_raise_ValueError ( MP_ERROR_TEXT ( " freqency must be between 1Hz and 40MHz " ) ) ;
}
// Check if freq wasn't passed as an argument
if ( freq = = - 1 ) {
// Check if already set, otherwise use the default freq.
// Possible case:
// pwm = PWM(pin, freq=1000, duty=256)
// pwm = PWM(pin, duty=128)
if ( chans [ channel_idx ] . timer_idx ! = - 1 ) {
freq = timers [ chans [ channel_idx ] . timer_idx ] . freq_hz ;
}
if ( ( avail = = - 1 ) & & ( chan_gpio [ channel ] = = - 1 ) ) {
avail = channel ;
if ( freq < 0 ) {
freq = PWFREQ ;
}
}
if ( channel > = LEDC_CHANNEL_MAX ) {
if ( avail = = - 1 ) {
mp_raise_ValueError ( MP_ERROR_TEXT ( " out of PWM channels " ) ) ;
}
channel = avail ;
int timer_idx = find_timer ( freq , SAME_FREQ_OR_FREE , CHANNEL_IDX_TO_MODE ( channel_idx ) ) ;
if ( timer_idx = = - 1 ) {
timer_idx = find_timer ( freq , SAME_FREQ_OR_FREE , ANY_MODE ) ;
}
if ( timer_idx = = - 1 ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " out of PWM timers:%d " ) , PWM_TIMER_MAX ) ; // in all modes
}
self - > channel = channel ;
// New PWM assignment
self - > active = 1 ;
if ( chan_gpio [ channel ] = = - 1 ) {
ledc_channel_config_t cfg = {
. channel = channel ,
. duty = ( 1 < < timer_cfg . duty_resolution ) / 2 ,
. gpio_num = self - > pin ,
. intr_type = LEDC_INTR_DISABLE ,
. speed_mode = PWMODE ,
. timer_sel = PWTIMER ,
} ;
if ( ledc_channel_config ( & cfg ) ! = ESP_OK ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " PWM not supported on pin %d " ) , self - > pin ) ;
int mode = TIMER_IDX_TO_MODE ( timer_idx ) ;
if ( CHANNEL_IDX_TO_MODE ( channel_idx ) ! = mode ) {
// unregister old channel
chans [ channel_idx ] . pin = - 1 ;
chans [ channel_idx ] . timer_idx = - 1 ;
// find new channel
channel_idx = find_channel ( self - > pin , mode ) ;
if ( CHANNEL_IDX_TO_MODE ( channel_idx ) ! = mode ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " out of PWM channels:%d " ) , PWM_CHANNEL_MAX ) ; // in current mode
}
chan_gpio [ channel ] = self - > pin ;
}
self - > mode = mode ;
self - > timer = TIMER_IDX_TO_TIMER ( timer_idx ) ;
self - > channel = CHANNEL_IDX_TO_CHANNEL ( channel_idx ) ;
// Maybe change PWM timer
int tval = args [ ARG_freq ] . u_int ;
if ( tval ! = - 1 ) {
if ( tval ! = timer_cfg . freq_hz ) {
if ( ! set_freq ( tval ) ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " bad frequency %d " ) , tval ) ;
}
}
// New PWM assignment
if ( ( chans [ channel_idx ] . pin = = - 1 ) | | ( chans [ channel_idx ] . timer_idx ! = timer_idx ) ) {
configure_channel ( self ) ;
chans [ channel_idx ] . pin = self - > pin ;
}
chans [ channel_idx ] . timer_idx = timer_idx ;
self - > active = true ;
// Set timer frequency
set_freq ( freq , & timers [ timer_idx ] ) ;
// Set duty cycle?
int dval = args [ ARG_duty ] . u_int ;
if ( dval ! = - 1 ) {
dval & = ( ( 1 < < PWRES ) - 1 ) ;
dval > > = PWRES - timer_cfg . duty_resolution ;
ledc_set_duty ( PWMODE , channel , dval ) ;
ledc_update_duty ( PWMODE , channel ) ;
int duty = args [ ARG_duty ] . u_int ;
if ( duty ! = - 1 ) {
set_duty ( self , duty ) ;
}
// Reset the timer if low speed
if ( self - > mode = = LEDC_LOW_SPEED_MODE ) {
check_esp_err ( ledc_timer_rst ( self - > mode , self - > timer ) ) ;
}
}
@ -195,8 +341,10 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type,
machine_pwm_obj_t * self = m_new_obj ( machine_pwm_obj_t ) ;
self - > base . type = & machine_pwm_type ;
self - > pin = pin_id ;
self - > active = 0 ;
self - > active = false ;
self - > mode = - 1 ;
self - > channel = - 1 ;
self - > timer = - 1 ;
// start the PWM subsystem if it's not already running
if ( ! pwm_inited ) {
@ -213,38 +361,99 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type,
}
STATIC void mp_machine_pwm_deinit ( machine_pwm_obj_t * self ) {
int chan = self - > channel ;
int chan = CHANNEL_IDX ( self - > mode , self - > channel ) ;
// Valid channel?
if ( ( chan > = 0 ) & & ( chan < LEDC_CHANNEL_MAX ) ) {
if ( ( chan > = 0 ) & & ( chan < PWM_CHANNEL_MAX ) ) {
// Clean up timer if necessary
if ( ! is_timer_in_use ( chan , chans [ chan ] . timer_idx ) ) {
check_esp_err ( ledc_timer_rst ( self - > mode , self - > timer ) ) ;
// Flag it unused
timers [ chans [ chan ] . timer_idx ] . freq_hz = - 1 ;
}
// Mark it unused, and tell the hardware to stop routing
chan_gpio [ chan ] = - 1 ;
ledc_stop ( PWMODE , chan , 0 ) ;
self - > active = 0 ;
check_esp_err ( ledc_stop ( self - > mode , chan , 0 ) ) ;
// Disable ledc signal for the pin
// gpio_matrix_out(self->pin, SIG_GPIO_OUT_IDX, false, false);
if ( self - > mode = = LEDC_LOW_SPEED_MODE ) {
gpio_matrix_out ( self - > pin , LEDC_LS_SIG_OUT0_IDX + self - > channel , false , true ) ;
} else {
# if LEDC_SPEED_MODE_MAX > 1
# if CONFIG_IDF_TARGET_ESP32
gpio_matrix_out ( self - > pin , LEDC_HS_SIG_OUT0_IDX + self - > channel , false , true ) ;
# else
# error Add supported CONFIG_IDF_TARGET_ESP32_xxx
# endif
# endif
}
chans [ chan ] . pin = - 1 ;
chans [ chan ] . timer_idx = - 1 ;
self - > active = false ;
self - > mode = - 1 ;
self - > channel = - 1 ;
gpio_matrix_out ( self - > pin , SIG_GPIO_OUT_IDX , false , false ) ;
self - > timer = - 1 ;
}
}
STATIC mp_obj_t mp_machine_pwm_freq_get ( machine_pwm_obj_t * self ) {
return MP_OBJ_NEW_SMALL_INT ( timer_cfg . freq_hz ) ;
return MP_OBJ_NEW_SMALL_INT ( ledc_get_freq ( self - > mode , self - > timer ) ) ;
}
STATIC void mp_machine_pwm_freq_set ( machine_pwm_obj_t * self , mp_int_t freq ) {
if ( ! set_freq ( freq ) ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " bad frequency %d " ) , freq ) ;
if ( freq = = timers [ TIMER_IDX ( self - > mode , self - > timer ) ] . freq_hz ) {
return ;
}
int current_timer_idx = chans [ CHANNEL_IDX ( self - > mode , self - > channel ) ] . timer_idx ;
bool current_in_use = is_timer_in_use ( CHANNEL_IDX ( self - > mode , self - > channel ) , current_timer_idx ) ;
// Check if an already running timer with the same freq is running
int new_timer_idx = find_timer ( freq , SAME_FREQ_ONLY , self - > mode ) ;
// If no existing timer was found, and the current one is in use, then find a new one
if ( ( new_timer_idx = = - 1 ) & & current_in_use ) {
// Have to find a new timer
new_timer_idx = find_timer ( freq , SAME_FREQ_OR_FREE , self - > mode ) ;
if ( new_timer_idx = = - 1 ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " out of PWM timers:%d " ) , PWM_TIMER_MAX ) ; // in current mode
}
}
if ( ( new_timer_idx ! = - 1 ) & & ( new_timer_idx ! = current_timer_idx ) ) {
// Bind the channel to the new timer
chans [ self - > channel ] . timer_idx = new_timer_idx ;
if ( ledc_bind_channel_timer ( self - > mode , self - > channel , TIMER_IDX_TO_TIMER ( new_timer_idx ) ) ! = ESP_OK ) {
mp_raise_msg_varg ( & mp_type_ValueError , MP_ERROR_TEXT ( " failed to bind timer to channel " ) ) ;
}
if ( ! current_in_use ) {
// Free the old timer
check_esp_err ( ledc_timer_rst ( self - > mode , self - > timer ) ) ;
// Flag it unused
timers [ current_timer_idx ] . freq_hz = - 1 ;
}
current_timer_idx = new_timer_idx ;
}
self - > mode = TIMER_IDX_TO_MODE ( current_timer_idx ) ;
self - > timer = TIMER_IDX_TO_TIMER ( current_timer_idx ) ;
// Set the freq
set_freq ( freq , & timers [ current_timer_idx ] ) ;
// Reset the timer if low speed
if ( self - > mode = = LEDC_LOW_SPEED_MODE ) {
check_esp_err ( ledc_timer_rst ( self - > mode , self - > timer ) ) ;
}
}
STATIC mp_obj_t mp_machine_pwm_duty_get ( machine_pwm_obj_t * self ) {
int duty = ledc_get_duty ( PWMODE , self - > channel ) ;
duty < < = PWRES - timer_cfg . duty_resolution ;
return MP_OBJ_NEW_SMALL_INT ( duty ) ;
return MP_OBJ_NEW_SMALL_INT ( get_duty ( self ) ) ;
}
STATIC void mp_machine_pwm_duty_set ( machine_pwm_obj_t * self , mp_int_t duty ) {
duty & = ( ( 1 < < PWRES ) - 1 ) ;
duty > > = PWRES - timer_cfg . duty_resolution ;
ledc_set_duty ( PWMODE , self - > channel , duty ) ;
ledc_update_duty ( PWMODE , self - > channel ) ;
set_duty ( self , duty ) ;
}