Skip to content
Snippets Groups Projects
Select Git revision
  • c6ee27341063eef8a377d408d231a9fdfe22f031
  • wip-bootstrap default
  • dualcore
  • ch3/leds
  • ch3/time
  • master
6 results

obj.h

Blame
  • timer.c 56.33 KiB
    /*
     * This file is part of the Micro Python project, http://micropython.org/
     *
     * The MIT License (MIT)
     *
     * Copyright (c) 2013, 2014 Damien P. George
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to deal
     * in the Software without restriction, including without limitation the rights
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in
     * all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     * THE SOFTWARE.
     */
    
    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    
    #include STM32_HAL_H
    #include "usbd_cdc_msc_hid.h"
    #include "usbd_cdc_interface.h"
    
    #include "py/nlr.h"
    #include "py/runtime.h"
    #include "py/gc.h"
    #include "timer.h"
    #include "servo.h"
    #include "pin.h"
    #include "irq.h"
    
    /// \moduleref pyb
    /// \class Timer - periodically call a function
    ///
    /// Timers can be used for a great variety of tasks.  At the moment, only
    /// the simplest case is implemented: that of calling a function periodically.
    ///
    /// Each timer consists of a counter that counts up at a certain rate.  The rate
    /// at which it counts is the peripheral clock frequency (in Hz) divided by the
    /// timer prescaler.  When the counter reaches the timer period it triggers an
    /// event, and the counter resets back to zero.  By using the callback method,
    /// the timer event can call a Python function.
    ///
    /// Example usage to toggle an LED at a fixed frequency:
    ///
    ///     tim = pyb.Timer(4)              # create a timer object using timer 4
    ///     tim.init(freq=2)                # trigger at 2Hz
    ///     tim.callback(lambda t:pyb.LED(1).toggle())
    ///
    /// Further examples:
    ///
    ///     tim = pyb.Timer(4, freq=100)    # freq in Hz
    ///     tim = pyb.Timer(4, prescaler=0, period=99)
    ///     tim.counter()                   # get counter (can also set)
    ///     tim.prescaler(2)                # set prescaler (can also get)
    ///     tim.period(199)                 # set period (can also get)
    ///     tim.callback(lambda t: ...)     # set callback for update interrupt (t=tim instance)
    ///     tim.callback(None)              # clear callback
    ///
    /// *Note:* Timer 3 is reserved for internal use.  Timer 5 controls
    /// the servo driver, and Timer 6 is used for timed ADC/DAC reading/writing.
    /// It is recommended to use the other timers in your programs.
    
    // The timers can be used by multiple drivers, and need a common point for
    // the interrupts to be dispatched, so they are all collected here.
    //
    // TIM3:
    //  - flash storage controller, to flush the cache
    //  - USB CDC interface, interval, to check for new data
    //  - LED 4, PWM to set the LED intensity
    //
    // TIM5:
    //  - servo controller, PWM
    //
    // TIM6:
    //  - ADC, DAC for read_timed and write_timed
    
    typedef enum {
        CHANNEL_MODE_PWM_NORMAL,
        CHANNEL_MODE_PWM_INVERTED,
        CHANNEL_MODE_OC_TIMING,
        CHANNEL_MODE_OC_ACTIVE,
        CHANNEL_MODE_OC_INACTIVE,
        CHANNEL_MODE_OC_TOGGLE,
        CHANNEL_MODE_OC_FORCED_ACTIVE,
        CHANNEL_MODE_OC_FORCED_INACTIVE,
        CHANNEL_MODE_IC,
        CHANNEL_MODE_ENC_A,
        CHANNEL_MODE_ENC_B,
        CHANNEL_MODE_ENC_AB,
    } pyb_channel_mode;
    
    STATIC const struct {
        qstr        name;
        uint32_t    oc_mode;
    } channel_mode_info[] = {
        { MP_QSTR_PWM,                TIM_OCMODE_PWM1 },
        { MP_QSTR_PWM_INVERTED,       TIM_OCMODE_PWM2 },
        { MP_QSTR_OC_TIMING,          TIM_OCMODE_TIMING },
        { MP_QSTR_OC_ACTIVE,          TIM_OCMODE_ACTIVE },
        { MP_QSTR_OC_INACTIVE,        TIM_OCMODE_INACTIVE },
        { MP_QSTR_OC_TOGGLE,          TIM_OCMODE_TOGGLE },
        { MP_QSTR_OC_FORCED_ACTIVE,   TIM_OCMODE_FORCED_ACTIVE },
        { MP_QSTR_OC_FORCED_INACTIVE, TIM_OCMODE_FORCED_INACTIVE },
        { MP_QSTR_IC,                 0 },
        { MP_QSTR_ENC_A,              TIM_ENCODERMODE_TI1 },
        { MP_QSTR_ENC_B,              TIM_ENCODERMODE_TI2 },
        { MP_QSTR_ENC_AB,             TIM_ENCODERMODE_TI12 },
    };
    
    typedef struct _pyb_timer_channel_obj_t {
        mp_obj_base_t base;
        struct _pyb_timer_obj_t *timer;
        uint8_t channel;
        uint8_t mode;
        mp_obj_t callback;
        struct _pyb_timer_channel_obj_t *next;
    } pyb_timer_channel_obj_t;
    
    typedef struct _pyb_timer_obj_t {
        mp_obj_base_t base;
        uint8_t tim_id;
        uint8_t is_32bit;
        mp_obj_t callback;
        TIM_HandleTypeDef tim;
        IRQn_Type irqn;
        pyb_timer_channel_obj_t *channel;
    } pyb_timer_obj_t;
    
    // The following yields TIM_IT_UPDATE when channel is zero and
    // TIM_IT_CC1..TIM_IT_CC4 when channel is 1..4
    #define TIMER_IRQ_MASK(channel) (1 << (channel))
    #define TIMER_CNT_MASK(self)    ((self)->is_32bit ? 0xffffffff : 0xffff)
    #define TIMER_CHANNEL(self)     ((((self)->channel) - 1) << 2)
    
    TIM_HandleTypeDef TIM3_Handle;
    TIM_HandleTypeDef TIM5_Handle;
    TIM_HandleTypeDef TIM6_Handle;
    
    #define PYB_TIMER_OBJ_ALL_NUM MP_ARRAY_SIZE(MP_STATE_PORT(pyb_timer_obj_all))
    
    STATIC uint32_t timer_get_source_freq(uint32_t tim_id);
    STATIC mp_obj_t pyb_timer_deinit(mp_obj_t self_in);
    STATIC mp_obj_t pyb_timer_callback(mp_obj_t self_in, mp_obj_t callback);
    STATIC mp_obj_t pyb_timer_channel_callback(mp_obj_t self_in, mp_obj_t callback);
    
    void timer_init0(void) {
        for (uint i = 0; i < PYB_TIMER_OBJ_ALL_NUM; i++) {
            MP_STATE_PORT(pyb_timer_obj_all)[i] = NULL;
        }
    }
    
    // unregister all interrupt sources
    void timer_deinit(void) {
        for (uint i = 0; i < PYB_TIMER_OBJ_ALL_NUM; i++) {
            pyb_timer_obj_t *tim = MP_STATE_PORT(pyb_timer_obj_all)[i];
            if (tim != NULL) {
                pyb_timer_deinit(tim);
            }
        }
    }
    
    // TIM3 is set-up for the USB CDC interface
    void timer_tim3_init(void) {
        // set up the timer for USBD CDC
        __TIM3_CLK_ENABLE();
    
        TIM3_Handle.Instance = TIM3;
        TIM3_Handle.Init.Period = (USBD_CDC_POLLING_INTERVAL*1000) - 1; // TIM3 fires every USBD_CDC_POLLING_INTERVAL ms
        TIM3_Handle.Init.Prescaler = timer_get_source_freq(3) / 1000000 - 1; // TIM3 runs at 1MHz
        TIM3_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
        TIM3_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
        HAL_TIM_Base_Init(&TIM3_Handle);
    
        HAL_NVIC_SetPriority(TIM3_IRQn, IRQ_PRI_TIM3, IRQ_SUBPRI_TIM3);
        HAL_NVIC_EnableIRQ(TIM3_IRQn);
    
        if (HAL_TIM_Base_Start(&TIM3_Handle) != HAL_OK) {
            /* Starting Error */
        }
    }
    
    /* unused
    void timer_tim3_deinit(void) {
        // reset TIM3 timer
        __TIM3_FORCE_RESET();
        __TIM3_RELEASE_RESET();
    }
    */
    
    // TIM5 is set-up for the servo controller
    // This function inits but does not start the timer
    void timer_tim5_init(void) {
        // TIM5 clock enable
        __TIM5_CLK_ENABLE();
    
        // set up and enable interrupt
        HAL_NVIC_SetPriority(TIM5_IRQn, IRQ_PRI_TIM5, IRQ_SUBPRI_TIM5);
        HAL_NVIC_EnableIRQ(TIM5_IRQn);
    
        // PWM clock configuration
        TIM5_Handle.Instance = TIM5;
        TIM5_Handle.Init.Period = 2000 - 1; // timer cycles at 50Hz
        TIM5_Handle.Init.Prescaler = (timer_get_source_freq(5) / 100000) - 1; // timer runs at 100kHz
        TIM5_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
        TIM5_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    
        HAL_TIM_PWM_Init(&TIM5_Handle);
    }
    
    #if defined(TIM6)
    // Init TIM6 with a counter-overflow at the given frequency (given in Hz)
    // TIM6 is used by the DAC and ADC for auto sampling at a given frequency
    // This function inits but does not start the timer
    TIM_HandleTypeDef *timer_tim6_init(uint freq) {
        // TIM6 clock enable
        __TIM6_CLK_ENABLE();
    
        // Timer runs at SystemCoreClock / 2
        // Compute the prescaler value so TIM6 triggers at freq-Hz
        uint32_t period = MAX(1, timer_get_source_freq(6) / freq);
        uint32_t prescaler = 1;
        while (period > 0xffff) {
            period >>= 1;
            prescaler <<= 1;
        }
    
        // Time base clock configuration
        TIM6_Handle.Instance = TIM6;
        TIM6_Handle.Init.Period = period - 1;
        TIM6_Handle.Init.Prescaler = prescaler - 1;
        TIM6_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // unused for TIM6
        TIM6_Handle.Init.CounterMode = TIM_COUNTERMODE_UP; // unused for TIM6
        HAL_TIM_Base_Init(&TIM6_Handle);
    
        return &TIM6_Handle;
    }
    #endif
    
    // Interrupt dispatch
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
        #if !defined(MICROPY_HW_USE_ALT_IRQ_FOR_CDC)
        if (htim == &TIM3_Handle) {
            USBD_CDC_HAL_TIM_PeriodElapsedCallback();
        } else
        #endif
        if (htim == &TIM5_Handle) {
            servo_timer_irq_callback();
        }
    }
    
    // Get the frequency (in Hz) of the source clock for the given timer.
    // On STM32F405/407/415/417 there are 2 cases for how the clock freq is set.
    // If the APB prescaler is 1, then the timer clock is equal to its respective
    // APB clock.  Otherwise (APB prescaler > 1) the timer clock is twice its
    // respective APB clock.  See DM00031020 Rev 4, page 115.
    STATIC uint32_t timer_get_source_freq(uint32_t tim_id) {
        uint32_t source;
        if (tim_id == 1 || (8 <= tim_id && tim_id <= 11)) {
            // TIM{1,8,9,10,11} are on APB2
            source = HAL_RCC_GetPCLK2Freq();
            if ((uint32_t)((RCC->CFGR & RCC_CFGR_PPRE2) >> 3) != RCC_HCLK_DIV1) {
                source *= 2;
            }
        } else {
            // TIM{2,3,4,5,6,7,12,13,14} are on APB1
            source = HAL_RCC_GetPCLK1Freq();
            if ((uint32_t)(RCC->CFGR & RCC_CFGR_PPRE1) != RCC_HCLK_DIV1) {
                source *= 2;
            }
        }
        return source;
    }
    
    /******************************************************************************/
    /* Micro Python bindings                                                      */
    
    STATIC const mp_obj_type_t pyb_timer_channel_type;
    
    // This is the largest value that we can multiply by 100 and have the result
    // fit in a uint32_t.
    #define MAX_PERIOD_DIV_100  42949672
    
    // computes prescaler and period so TIM triggers at freq-Hz
    STATIC uint32_t compute_prescaler_period_from_freq(pyb_timer_obj_t *self, mp_obj_t freq_in, uint32_t *period_out) {
        uint32_t source_freq = timer_get_source_freq(self->tim_id);
        uint32_t prescaler = 1;
        uint32_t period;
        if (0) {
        #if MICROPY_PY_BUILTINS_FLOAT
        } else if (MP_OBJ_IS_TYPE(freq_in, &mp_type_float)) {
            float freq = mp_obj_get_float(freq_in);
            if (freq <= 0) {
                goto bad_freq;
            }
            while (freq < 1 && prescaler < 6553) {
                prescaler *= 10;
                freq *= 10;
            }
            period = (float)source_freq / freq;
        #endif
        } else {
            mp_int_t freq = mp_obj_get_int(freq_in);
            if (freq <= 0) {
                goto bad_freq;
                bad_freq:
                nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "must have positive freq"));
            }
            period = source_freq / freq;
        }
        period = MAX(1, period);
        while (period > TIMER_CNT_MASK(self)) {
            // if we can divide exactly, do that first
            if (period % 5 == 0) {
                prescaler *= 5;
                period /= 5;
            } else if (period % 3 == 0) {
                prescaler *= 3;
                period /= 3;
            } else {
                // may not divide exactly, but loses minimal precision
                prescaler <<= 1;
                period >>= 1;
            }
        }
        *period_out = (period - 1) & TIMER_CNT_MASK(self);
        return (prescaler - 1) & 0xffff;
    }
    
    // Helper function for determining the period used for calculating percent
    STATIC uint32_t compute_period(pyb_timer_obj_t *self) {
        // In center mode,  compare == period corresponds to 100%
        // In edge mode, compare == (period + 1) corresponds to 100%
        uint32_t period = (__HAL_TIM_GetAutoreload(&self->tim) & TIMER_CNT_MASK(self));
        if (period != 0xffffffff) {
            if (self->tim.Init.CounterMode == TIM_COUNTERMODE_UP ||
                self->tim.Init.CounterMode == TIM_COUNTERMODE_DOWN) {
                // Edge mode
                period++;
            }
        }
        return period;
    }
    
    // Helper function to compute PWM value from timer period and percent value.
    // 'percent_in' can be an int or a float between 0 and 100 (out of range
    // values are clamped).
    STATIC uint32_t compute_pwm_value_from_percent(uint32_t period, mp_obj_t percent_in) {
        uint32_t cmp;
        if (0) {
        #if MICROPY_PY_BUILTINS_FLOAT
        } else if (MP_OBJ_IS_TYPE(percent_in, &mp_type_float)) {
            mp_float_t percent = mp_obj_get_float(percent_in);
            if (percent <= 0.0) {
                cmp = 0;
            } else if (percent >= 100.0) {
                cmp = period;
            } else {
                cmp = percent / 100.0 * ((mp_float_t)period);
            }
        #endif
        } else {
            // For integer arithmetic, if period is large and 100*period will
            // overflow, then divide period before multiplying by cmp.  Otherwise
            // do it the other way round to retain precision.
            mp_int_t percent = mp_obj_get_int(percent_in);
            if (percent <= 0) {
                cmp = 0;
            } else if (percent >= 100) {
                cmp = period;
            } else if (period > MAX_PERIOD_DIV_100) {
                cmp = (uint32_t)percent * (period / 100);
            } else {
                cmp = ((uint32_t)percent * period) / 100;
            }
        }
        return cmp;
    }
    
    // Helper function to compute percentage from timer perion and PWM value.
    STATIC mp_obj_t compute_percent_from_pwm_value(uint32_t period, uint32_t cmp) {
        #if MICROPY_PY_BUILTINS_FLOAT
        mp_float_t percent;
        if (cmp >= period) {
            percent = 100.0;
        } else {
            percent = (mp_float_t)cmp * 100.0 / ((mp_float_t)period);
        }
        return mp_obj_new_float(percent);
        #else
        mp_int_t percent;
        if (cmp >= period) {
            percent = 100;
        } else if (cmp > MAX_PERIOD_DIV_100) {
            percent = cmp / (period / 100);
        } else {
            percent = cmp * 100 / period;
        }
        return mp_obj_new_int(percent);
        #endif
    }
    
    // Computes the 8-bit value for the DTG field in the BDTR register.
    //
    // 1 tick = 1 count of the timer's clock (source_freq) divided by div.
    // 0-128 ticks in inrements of 1
    // 128-256 ticks in increments of 2
    // 256-512 ticks in increments of 8
    // 512-1008 ticks in increments of 16
    STATIC uint32_t compute_dtg_from_ticks(mp_int_t ticks) {
        if (ticks <= 0) {
            return 0;
        }
        if (ticks < 128) {
            return ticks;
        }
        if (ticks < 256) {
            return 0x80 | ((ticks - 128) / 2);
        }
        if (ticks < 512) {
            return 0xC0 | ((ticks - 256) / 8);
        }
        if (ticks < 1008) {
            return 0xE0 | ((ticks - 512) / 16);
        }
        return 0xFF;
    }
    
    // Given the 8-bit value stored in the DTG field of the BDTR register, compute
    // the number of ticks.
    STATIC mp_int_t compute_ticks_from_dtg(uint32_t dtg) {
        if ((dtg & 0x80) == 0) {
            return dtg & 0x7F;
        }
        if ((dtg & 0xC0) == 0x80) {
            return 128 + ((dtg & 0x3F) * 2);
        }
        if ((dtg & 0xE0) == 0xC0) {
            return 256 + ((dtg & 0x1F) * 8);
        }
        return 512 + ((dtg & 0x1F) * 16);
    }
    
    STATIC void config_deadtime(pyb_timer_obj_t *self, mp_int_t ticks) {
        TIM_BreakDeadTimeConfigTypeDef deadTimeConfig;
        deadTimeConfig.OffStateRunMode  = TIM_OSSR_DISABLE;
        deadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
        deadTimeConfig.LockLevel        = TIM_LOCKLEVEL_OFF;
        deadTimeConfig.DeadTime         = compute_dtg_from_ticks(ticks);
        deadTimeConfig.BreakState       = TIM_BREAK_DISABLE;
        deadTimeConfig.BreakPolarity    = TIM_BREAKPOLARITY_LOW;
        deadTimeConfig.AutomaticOutput  = TIM_AUTOMATICOUTPUT_DISABLE;
        HAL_TIMEx_ConfigBreakDeadTime(&self->tim, &deadTimeConfig);
    }
    
    TIM_HandleTypeDef *pyb_timer_get_handle(mp_obj_t timer) {
        if (mp_obj_get_type(timer) != &pyb_timer_type) {
            nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "need a Timer object"));
        }
        pyb_timer_obj_t *self = timer;
        return &self->tim;
    }
    
    STATIC void pyb_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
        pyb_timer_obj_t *self = self_in;
    
        if (self->tim.State == HAL_TIM_STATE_RESET) {
            mp_printf(print, "Timer(%u)", self->tim_id);
        } else {
            uint32_t prescaler = self->tim.Instance->PSC & 0xffff;
            uint32_t period = __HAL_TIM_GetAutoreload(&self->tim) & TIMER_CNT_MASK(self);
            // for efficiency, we compute and print freq as an int (not a float)
            uint32_t freq = timer_get_source_freq(self->tim_id) / ((prescaler + 1) * (period + 1));
            mp_printf(print, "Timer(%u, freq=%u, prescaler=%u, period=%u, mode=%s, div=%u",
                self->tim_id,
                freq,
                prescaler,
                period,
                self->tim.Init.CounterMode == TIM_COUNTERMODE_UP     ? "UP" :
                self->tim.Init.CounterMode == TIM_COUNTERMODE_DOWN   ? "DOWN" : "CENTER",
                self->tim.Init.ClockDivision == TIM_CLOCKDIVISION_DIV4 ? 4 :
                self->tim.Init.ClockDivision == TIM_CLOCKDIVISION_DIV2 ? 2 : 1);
            if (IS_TIM_ADVANCED_INSTANCE(self->tim.Instance)) {
                mp_printf(print, ", deadtime=%u",
                    compute_ticks_from_dtg(self->tim.Instance->BDTR & TIM_BDTR_DTG));
            }
            mp_print_str(print, ")");
        }
    }
    
    /// \method init(*, freq, prescaler, period)
    /// Initialise the timer.  Initialisation must be either by frequency (in Hz)
    /// or by prescaler and period:
    ///
    ///     tim.init(freq=100)                  # set the timer to trigger at 100Hz
    ///     tim.init(prescaler=83, period=999)  # set the prescaler and period directly
    ///
    /// Keyword arguments:
    ///
    ///   - `freq` - specifies the periodic frequency of the timer. You migh also
    ///              view this as the frequency with which the timer goes through
    ///              one complete cycle.
    ///
    ///   - `prescaler` [0-0xffff] - specifies the value to be loaded into the
    ///                 timer's Prescaler Register (PSC). The timer clock source is divided by
    ///     (`prescaler + 1`) to arrive at the timer clock. Timers 2-7 and 12-14
    ///     have a clock source of 84 MHz (pyb.freq()[2] * 2), and Timers 1, and 8-11
    ///     have a clock source of 168 MHz (pyb.freq()[3] * 2).
    ///
    ///   - `period` [0-0xffff] for timers 1, 3, 4, and 6-15. [0-0x3fffffff] for timers 2 & 5.
    ///              Specifies the value to be loaded into the timer's AutoReload
    ///     Register (ARR). This determines the period of the timer (i.e. when the
    ///     counter cycles). The timer counter will roll-over after `period + 1`
    ///     timer clock cycles.
    ///
    ///   - `mode` can be one of:
    ///     - `Timer.UP` - configures the timer to count from 0 to ARR (default)
    ///     - `Timer.DOWN` - configures the timer to count from ARR down to 0.
    ///     - `Timer.CENTER` - confgures the timer to count from 0 to ARR and
    ///       then back down to 0.
    ///
    ///   - `div` can be one of 1, 2, or 4. Divides the timer clock to determine
    ///       the sampling clock used by the digital filters.
    ///
    ///   - `callback` - as per Timer.callback()
    ///
    ///   - `deadtime` - specifies the amount of "dead" or inactive time between
    ///       transitions on complimentary channels (both channels will be inactive)
    ///       for this time). `deadtime` may be an integer between 0 and 1008, with
    ///       the following restrictions: 0-128 in steps of 1. 128-256 in steps of
    ///       2, 256-512 in steps of 8, and 512-1008 in steps of 16. `deadime`
    ///       measures ticks of `source_freq` divided by `div` clock ticks.
    ///       `deadtime` is only available on timers 1 and 8.
    ///
    ///  You must either specify freq or both of period and prescaler.
    STATIC mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
        static const mp_arg_t allowed_args[] = {
            { MP_QSTR_freq,         MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
            { MP_QSTR_prescaler,    MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} },
            { MP_QSTR_period,       MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} },
            { MP_QSTR_mode,         MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = TIM_COUNTERMODE_UP} },
            { MP_QSTR_div,          MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} },
            { MP_QSTR_callback,     MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
            { MP_QSTR_deadtime,     MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
        };
    
        // parse args
        mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
        mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
    
        // set the TIM configuration values
        TIM_Base_InitTypeDef *init = &self->tim.Init;
    
        if (args[0].u_obj != mp_const_none) {
            // set prescaler and period from desired frequency
            init->Prescaler = compute_prescaler_period_from_freq(self, args[0].u_obj, &init->Period);
        } else if (args[1].u_int != 0xffffffff && args[2].u_int != 0xffffffff) {
            // set prescaler and period directly
            init->Prescaler = args[1].u_int;
            init->Period = args[2].u_int;
        } else {
            nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError, "must specify either freq, or prescaler and period"));
        }
    
        init->CounterMode = args[3].u_int;
        if (!IS_TIM_COUNTER_MODE(init->CounterMode)) {
            nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid mode (%d)", init->CounterMode));
        }
    
        init->ClockDivision = args[4].u_int == 2 ? TIM_CLOCKDIVISION_DIV2 :
                              args[4].u_int == 4 ? TIM_CLOCKDIVISION_DIV4 :
                                                   TIM_CLOCKDIVISION_DIV1;
    
        init->RepetitionCounter = 0;
    
        // enable TIM clock
        switch (self->tim_id) {
            case 1: __TIM1_CLK_ENABLE(); break;
            case 2: __TIM2_CLK_ENABLE(); break;
            case 3: __TIM3_CLK_ENABLE(); break;
            case 4: __TIM4_CLK_ENABLE(); break;
            case 5: __TIM5_CLK_ENABLE(); break;
            #if defined(TIM6)
            case 6: __TIM6_CLK_ENABLE(); break;
            #endif
            #if defined(TIM7)
            case 7: __TIM7_CLK_ENABLE(); break;
            #endif
            #if defined(TIM8)
            case 8: __TIM8_CLK_ENABLE(); break;
            #endif
            case 9: __TIM9_CLK_ENABLE(); break;
            case 10: __TIM10_CLK_ENABLE(); break;
            case 11: __TIM11_CLK_ENABLE(); break;
            #if defined(TIM12)
            case 12: __TIM12_CLK_ENABLE(); break;
            #endif
            #if defined(TIM13)
            case 13: __TIM13_CLK_ENABLE(); break;
            #endif
            #if defined(TIM14)
            case 14: __TIM14_CLK_ENABLE(); break;
            #endif
        }
    
        // set IRQ priority (if not a special timer)
        if (self->tim_id != 3 && self->tim_id != 5) {
            HAL_NVIC_SetPriority(self->irqn, IRQ_PRI_TIMX, IRQ_SUBPRI_TIMX);
        }
    
        // init TIM
        HAL_TIM_Base_Init(&self->tim);
        if (IS_TIM_ADVANCED_INSTANCE(self->tim.Instance)) {
            config_deadtime(self, args[6].u_int);
        }
        if (args[5].u_obj == mp_const_none) {
            HAL_TIM_Base_Start(&self->tim);
        } else {
            pyb_timer_callback(self, args[5].u_obj);
        }
    
        return mp_const_none;
    }
    
    /// \classmethod \constructor(id, ...)
    /// Construct a new timer object of the given id.  If additional
    /// arguments are given, then the timer is initialised by `init(...)`.
    /// `id` can be 1 to 14, excluding 3.
    STATIC mp_obj_t pyb_timer_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
        // check arguments
        mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);
    
        // create new Timer object
        pyb_timer_obj_t *tim = m_new_obj(pyb_timer_obj_t);
        memset(tim, 0, sizeof(*tim));
    
        tim->base.type = &pyb_timer_type;
        tim->callback = mp_const_none;
        tim->channel = NULL;
    
        // get TIM number
        tim->tim_id = mp_obj_get_int(args[0]);
        tim->is_32bit = false;
    
        switch (tim->tim_id) {
            case 1: tim->tim.Instance = TIM1; tim->irqn = TIM1_UP_TIM10_IRQn; break;
            case 2: tim->tim.Instance = TIM2; tim->irqn = TIM2_IRQn; tim->is_32bit = true; break;
            #if defined(MICROPY_HW_USE_ALT_IRQ_FOR_CDC)
            case 3: tim->tim.Instance = TIM3; tim->irqn = TIM3_IRQn; break;
            #else
            case 3: nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Timer 3 is for internal use only")); // TIM3 used for low-level stuff; go via regs if necessary
            #endif
            case 4: tim->tim.Instance = TIM4; tim->irqn = TIM4_IRQn; break;
            case 5: tim->tim.Instance = TIM5; tim->irqn = TIM5_IRQn; tim->is_32bit = true; break;
            #if defined(TIM6)
            case 6: tim->tim.Instance = TIM6; tim->irqn = TIM6_DAC_IRQn; break;
            #endif
            #if defined(TIM7)
            case 7: tim->tim.Instance = TIM7; tim->irqn = TIM7_IRQn; break;
            #endif
            #if defined(TIM8)
            case 8: tim->tim.Instance = TIM8; tim->irqn = TIM8_UP_TIM13_IRQn; break;
            #endif
            case 9: tim->tim.Instance = TIM9; tim->irqn = TIM1_BRK_TIM9_IRQn; break;
            case 10: tim->tim.Instance = TIM10; tim->irqn = TIM1_UP_TIM10_IRQn; break;
            case 11: tim->tim.Instance = TIM11; tim->irqn = TIM1_TRG_COM_TIM11_IRQn; break;
            #if defined(TIM12)
            case 12: tim->tim.Instance = TIM12; tim->irqn = TIM8_BRK_TIM12_IRQn; break;
            #endif
            #if defined(TIM13)
            case 13: tim->tim.Instance = TIM13; tim->irqn = TIM8_UP_TIM13_IRQn; break;
            #endif
            #if defined(TIM14)
            case 14: tim->tim.Instance = TIM14; tim->irqn = TIM8_TRG_COM_TIM14_IRQn; break;
            #endif
            default: nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "Timer %d does not exist", tim->tim_id));
        }
    
        // set the global variable for interrupt callbacks
        if (tim->tim_id - 1 < PYB_TIMER_OBJ_ALL_NUM) {
            MP_STATE_PORT(pyb_timer_obj_all)[tim->tim_id - 1] = tim;
        }
    
        if (n_args > 1 || n_kw > 0) {
            // start the peripheral
            mp_map_t kw_args;
            mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
            pyb_timer_init_helper(tim, n_args - 1, args + 1, &kw_args);
        }
    
        return (mp_obj_t)tim;
    }
    
    STATIC mp_obj_t pyb_timer_init(mp_uint_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
        return pyb_timer_init_helper(args[0], n_args - 1, args + 1, kw_args);
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_timer_init_obj, 1, pyb_timer_init);
    
    // timer.deinit()
    STATIC mp_obj_t pyb_timer_deinit(mp_obj_t self_in) {
        pyb_timer_obj_t *self = self_in;
    
        // Disable the base interrupt
        pyb_timer_callback(self_in, mp_const_none);
    
        pyb_timer_channel_obj_t *chan = self->channel;
        self->channel = NULL;
    
        // Disable the channel interrupts
        while (chan != NULL) {
            pyb_timer_channel_callback(chan, mp_const_none);
            pyb_timer_channel_obj_t *prev_chan = chan;
            chan = chan->next;
            prev_chan->next = NULL;
        }
    
        self->tim.State = HAL_TIM_STATE_RESET;
        self->tim.Instance->CCER = 0x0000; // disable all capture/compare outputs
        self->tim.Instance->CR1 = 0x0000; // disable the timer and reset its state
    
        return mp_const_none;
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_timer_deinit_obj, pyb_timer_deinit);
    
    /// \method channel(channel, mode, ...)
    ///
    /// If only a channel number is passed, then a previously initialized channel
    /// object is returned (or `None` if there is no previous channel).
    ///
    /// Othwerwise, a TimerChannel object is initialized and returned.
    ///
    /// Each channel can be configured to perform pwm, output compare, or
    /// input capture. All channels share the same underlying timer, which means
    /// that they share the same timer clock.
    ///
    /// Keyword arguments:
    ///
    ///   - `mode` can be one of:
    ///     - `Timer.PWM` - configure the timer in PWM mode (active high).
    ///     - `Timer.PWM_INVERTED` - configure the timer in PWM mode (active low).
    ///     - `Timer.OC_TIMING` - indicates that no pin is driven.
    ///     - `Timer.OC_ACTIVE` - the pin will be made active when a compare
    ///        match occurs (active is determined by polarity)
    ///     - `Timer.OC_INACTIVE` - the pin will be made inactive when a compare
    ///        match occurs.
    ///     - `Timer.OC_TOGGLE` - the pin will be toggled when an compare match occurs.
    ///     - `Timer.OC_FORCED_ACTIVE` - the pin is forced active (compare match is ignored).
    ///     - `Timer.OC_FORCED_INACTIVE` - the pin is forced inactive (compare match is ignored).
    ///     - `Timer.IC` - configure the timer in Input Capture mode.
    ///     - `Timer.ENC_A` --- configure the timer in Encoder mode. The counter only changes when CH1 changes.
    ///     - `Timer.ENC_B` --- configure the timer in Encoder mode. The counter only changes when CH2 changes.
    ///     - `Timer.ENC_AB` --- configure the timer in Encoder mode. The counter changes when CH1 or CH2 changes.
    ///
    ///   - `callback` - as per TimerChannel.callback()
    ///
    ///   - `pin` None (the default) or a Pin object. If specified (and not None)
    ///           this will cause the alternate function of the the indicated pin
    ///      to be configured for this timer channel. An error will be raised if
    ///      the pin doesn't support any alternate functions for this timer channel.
    ///
    /// Keyword arguments for Timer.PWM modes:
    ///
    ///   - `pulse_width` - determines the initial pulse width value to use.
    ///   - `pulse_width_percent` - determines the initial pulse width percentage to use.
    ///
    /// Keyword arguments for Timer.OC modes:
    ///
    ///   - `compare` - determines the initial value of the compare register.
    ///
    ///   - `polarity` can be one of:
    ///     - `Timer.HIGH` - output is active high
    ///     - `Timer.LOW` - output is acive low
    ///
    /// Optional keyword arguments for Timer.IC modes:
    ///
    ///   - `polarity` can be one of:
    ///     - `Timer.RISING` - captures on rising edge.
    ///     - `Timer.FALLING` - captures on falling edge.
    ///     - `Timer.BOTH` - captures on both edges.
    ///
    ///   Note that capture only works on the primary channel, and not on the
    ///   complimentary channels.
    ///
    /// Notes for Timer.ENC modes:
    ///
    ///   - Requires 2 pins, so one or both pins will need to be configured to use
    ///     the appropriate timer AF using the Pin API.
    ///   - Read the encoder value using the timer.counter() method.
    ///   - Only works on CH1 and CH2 (and not on CH1N or CH2N)
    ///   - The channel number is ignored when setting the encoder mode.
    ///
    /// PWM Example:
    ///
    ///     timer = pyb.Timer(2, freq=1000)
    ///     ch2 = timer.channel(2, pyb.Timer.PWM, pin=pyb.Pin.board.X2, pulse_width=210000)
    ///     ch3 = timer.channel(3, pyb.Timer.PWM, pin=pyb.Pin.board.X3, pulse_width=420000)
    STATIC mp_obj_t pyb_timer_channel(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
        static const mp_arg_t allowed_args[] = {
            { MP_QSTR_mode,                MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} },
            { MP_QSTR_callback,            MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
            { MP_QSTR_pin,                 MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
            { MP_QSTR_pulse_width,         MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
            { MP_QSTR_pulse_width_percent, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
            { MP_QSTR_compare,             MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
            { MP_QSTR_polarity,            MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} },
        };
    
        pyb_timer_obj_t *self = pos_args[0];
        mp_int_t channel = mp_obj_get_int(pos_args[1]);
    
        if (channel < 1 || channel > 4) {
            nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid channel (%d)", channel));
        }
    
        pyb_timer_channel_obj_t *chan = self->channel;
        pyb_timer_channel_obj_t *prev_chan = NULL;
    
        while (chan != NULL) {
            if (chan->channel == channel) {
                break;
            }
            prev_chan = chan;
            chan = chan->next;
        }
    
        // If only the channel number is given return the previously allocated
        // channel (or None if no previous channel).
        if (n_args == 2 && kw_args->used == 0) {
            if (chan) {
                return chan;
            }
            return mp_const_none;
        }
    
        // If there was already a channel, then remove it from the list. Note that
        // the order we do things here is important so as to appear atomic to
        // the IRQ handler.
        if (chan) {
            // Turn off any IRQ associated with the channel.
            pyb_timer_channel_callback(chan, mp_const_none);
    
            // Unlink the channel from the list.
            if (prev_chan) {
                prev_chan->next = chan->next;
            }
            self->channel = chan->next;
            chan->next = NULL;
        }
    
        // Allocate and initialize a new channel
        mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
        mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
    
        chan = m_new_obj(pyb_timer_channel_obj_t);
        memset(chan, 0, sizeof(*chan));
        chan->base.type = &pyb_timer_channel_type;
        chan->timer = self;
        chan->channel = channel;
        chan->mode = args[0].u_int;
        chan->callback = args[1].u_obj;
    
        mp_obj_t pin_obj = args[2].u_obj;
        if (pin_obj != mp_const_none) {
            if (!MP_OBJ_IS_TYPE(pin_obj, &pin_type)) {
                nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "pin argument needs to be be a Pin type"));
            }
            const pin_obj_t *pin = pin_obj;
            const pin_af_obj_t *af = pin_find_af(pin, AF_FN_TIM, self->tim_id);
            if (af == NULL) {
                nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "pin %q doesn't have an af for TIM%d", pin->name, self->tim_id));
            }
            // pin.init(mode=AF_PP, af=idx)
            const mp_obj_t args2[6] = {
                (mp_obj_t)&pin_init_obj,
                pin_obj,
                MP_OBJ_NEW_QSTR(MP_QSTR_mode),  MP_OBJ_NEW_SMALL_INT(GPIO_MODE_AF_PP),
                MP_OBJ_NEW_QSTR(MP_QSTR_af),    MP_OBJ_NEW_SMALL_INT(af->idx)
            };
            mp_call_method_n_kw(0, 2, args2);
        }
    
        // Link the channel to the timer before we turn the channel on.
        // Note that this needs to appear atomic to the IRQ handler (the write
        // to self->channel is atomic, so we're good, but I thought I'd mention
        // in case this was ever changed in the future).
        chan->next = self->channel;
        self->channel = chan;
    
        switch (chan->mode) {
    
            case CHANNEL_MODE_PWM_NORMAL:
            case CHANNEL_MODE_PWM_INVERTED: {
                TIM_OC_InitTypeDef oc_config;
                oc_config.OCMode = channel_mode_info[chan->mode].oc_mode;
                if (args[4].u_obj != mp_const_none) {
                    // pulse width percent given
                    uint32_t period = compute_period(self);
                    oc_config.Pulse = compute_pwm_value_from_percent(period, args[4].u_obj);
                } else {
                    // use absolute pulse width value (defaults to 0 if nothing given)
                    oc_config.Pulse = args[3].u_int;
                }
                oc_config.OCPolarity   = TIM_OCPOLARITY_HIGH;
                oc_config.OCNPolarity  = TIM_OCNPOLARITY_HIGH;
                oc_config.OCFastMode   = TIM_OCFAST_DISABLE;
                oc_config.OCIdleState  = TIM_OCIDLESTATE_SET;
                oc_config.OCNIdleState = TIM_OCNIDLESTATE_SET;
    
                HAL_TIM_PWM_ConfigChannel(&self->tim, &oc_config, TIMER_CHANNEL(chan));
                if (chan->callback == mp_const_none) {
                    HAL_TIM_PWM_Start(&self->tim, TIMER_CHANNEL(chan));
                } else {
                    HAL_TIM_PWM_Start_IT(&self->tim, TIMER_CHANNEL(chan));
                }
                // Start the complimentary channel too (if its supported)
                if (IS_TIM_CCXN_INSTANCE(self->tim.Instance, TIMER_CHANNEL(chan))) {
                    HAL_TIMEx_PWMN_Start(&self->tim, TIMER_CHANNEL(chan));
                }
                break;
            }
    
            case CHANNEL_MODE_OC_TIMING:
            case CHANNEL_MODE_OC_ACTIVE:
            case CHANNEL_MODE_OC_INACTIVE:
            case CHANNEL_MODE_OC_TOGGLE:
            case CHANNEL_MODE_OC_FORCED_ACTIVE:
            case CHANNEL_MODE_OC_FORCED_INACTIVE: {
                TIM_OC_InitTypeDef oc_config;
                oc_config.OCMode       = channel_mode_info[chan->mode].oc_mode;
                oc_config.Pulse        = args[5].u_int;
                oc_config.OCPolarity   = args[6].u_int;
                if (oc_config.OCPolarity == 0xffffffff) {
                    oc_config.OCPolarity = TIM_OCPOLARITY_HIGH;
                }
                if (oc_config.OCPolarity == TIM_OCPOLARITY_HIGH) {
                    oc_config.OCNPolarity  = TIM_OCNPOLARITY_HIGH;
                } else {
                    oc_config.OCNPolarity  = TIM_OCNPOLARITY_LOW;
                }
                oc_config.OCFastMode   = TIM_OCFAST_DISABLE;
                oc_config.OCIdleState  = TIM_OCIDLESTATE_SET;
                oc_config.OCNIdleState = TIM_OCNIDLESTATE_SET;
    
                if (!IS_TIM_OC_POLARITY(oc_config.OCPolarity)) {
                    nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid polarity (%d)", oc_config.OCPolarity));
                }
                HAL_TIM_OC_ConfigChannel(&self->tim, &oc_config, TIMER_CHANNEL(chan));
                if (chan->callback == mp_const_none) {
                    HAL_TIM_OC_Start(&self->tim, TIMER_CHANNEL(chan));
                } else {
                    HAL_TIM_OC_Start_IT(&self->tim, TIMER_CHANNEL(chan));
                }
                // Start the complimentary channel too (if its supported)
                if (IS_TIM_CCXN_INSTANCE(self->tim.Instance, TIMER_CHANNEL(chan))) {
                    HAL_TIMEx_OCN_Start(&self->tim, TIMER_CHANNEL(chan));
                }
                break;
            }
    
            case CHANNEL_MODE_IC: {
                TIM_IC_InitTypeDef ic_config;
    
                ic_config.ICPolarity  = args[6].u_int;
                if (ic_config.ICPolarity == 0xffffffff) {
                    ic_config.ICPolarity = TIM_ICPOLARITY_RISING;
                }
                ic_config.ICSelection = TIM_ICSELECTION_DIRECTTI;
                ic_config.ICPrescaler = TIM_ICPSC_DIV1;
                ic_config.ICFilter    = 0;
    
                if (!IS_TIM_IC_POLARITY(ic_config.ICPolarity)) {
                    nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid polarity (%d)", ic_config.ICPolarity));
                }
                HAL_TIM_IC_ConfigChannel(&self->tim, &ic_config, TIMER_CHANNEL(chan));
                if (chan->callback == mp_const_none) {
                    HAL_TIM_IC_Start(&self->tim, TIMER_CHANNEL(chan));
                } else {
                    HAL_TIM_IC_Start_IT(&self->tim, TIMER_CHANNEL(chan));
                }
                break;
            }
    
            case CHANNEL_MODE_ENC_A:
            case CHANNEL_MODE_ENC_B:
            case CHANNEL_MODE_ENC_AB: {
                TIM_Encoder_InitTypeDef enc_config;
    
                enc_config.EncoderMode = channel_mode_info[chan->mode].oc_mode;
                enc_config.IC1Polarity  = args[6].u_int;
                if (enc_config.IC1Polarity == 0xffffffff) {
                    enc_config.IC1Polarity = TIM_ICPOLARITY_RISING;
                }
                enc_config.IC2Polarity  = enc_config.IC1Polarity;
                enc_config.IC1Selection = TIM_ICSELECTION_DIRECTTI;
                enc_config.IC2Selection = TIM_ICSELECTION_DIRECTTI;
                enc_config.IC1Prescaler = TIM_ICPSC_DIV1;
                enc_config.IC2Prescaler = TIM_ICPSC_DIV1;
                enc_config.IC1Filter    = 0;
                enc_config.IC2Filter    = 0;
    
                if (!IS_TIM_IC_POLARITY(enc_config.IC1Polarity)) {
                    nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid polarity (%d)", enc_config.IC1Polarity));
                }
                // Only Timers 1, 2, 3, 4, 5, and 8 support encoder mode
                if (self->tim.Instance != TIM1
                &&  self->tim.Instance != TIM2
                &&  self->tim.Instance != TIM3
                &&  self->tim.Instance != TIM4
                &&  self->tim.Instance != TIM5
                #if defined(TIM8)
                &&  self->tim.Instance != TIM8
                #endif
                ) {
                    nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "encoder not supported on timer %d", self->tim_id));
                }
    
                // Disable & clear the timer interrupt so that we don't trigger
                // an interrupt by initializing the timer.
                __HAL_TIM_DISABLE_IT(&self->tim, TIM_IT_UPDATE);
                HAL_TIM_Encoder_Init(&self->tim, &enc_config);
                __HAL_TIM_SetCounter(&self->tim, 0);
                if (self->callback != mp_const_none) {
                    __HAL_TIM_CLEAR_FLAG(&self->tim, TIM_IT_UPDATE);
                    __HAL_TIM_ENABLE_IT(&self->tim, TIM_IT_UPDATE);
                }
                HAL_TIM_Encoder_Start(&self->tim, TIM_CHANNEL_ALL);
                break;
            }
    
            default:
                nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid mode (%d)", chan->mode));
        }
    
        return chan;
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_timer_channel_obj, 2, pyb_timer_channel);
    
    /// \method counter([value])
    /// Get or set the timer counter.
    STATIC mp_obj_t pyb_timer_counter(mp_uint_t n_args, const mp_obj_t *args) {
        pyb_timer_obj_t *self = args[0];
        if (n_args == 1) {
            // get
            return mp_obj_new_int(self->tim.Instance->CNT);
        } else {
            // set
            __HAL_TIM_SetCounter(&self->tim, mp_obj_get_int(args[1]));
            return mp_const_none;
        }
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_counter_obj, 1, 2, pyb_timer_counter);
    
    /// \method source_freq()
    /// Get the frequency of the source of the timer.
    STATIC mp_obj_t pyb_timer_source_freq(mp_obj_t self_in) {
        pyb_timer_obj_t *self = self_in;
        uint32_t source_freq = timer_get_source_freq(self->tim_id);
        return mp_obj_new_int(source_freq);
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_timer_source_freq_obj, pyb_timer_source_freq);
    
    /// \method freq([value])
    /// Get or set the frequency for the timer (changes prescaler and period if set).
    STATIC mp_obj_t pyb_timer_freq(mp_uint_t n_args, const mp_obj_t *args) {
        pyb_timer_obj_t *self = args[0];
        if (n_args == 1) {
            // get
            uint32_t prescaler = self->tim.Instance->PSC & 0xffff;
            uint32_t period = __HAL_TIM_GetAutoreload(&self->tim) & TIMER_CNT_MASK(self);
            uint32_t source_freq = timer_get_source_freq(self->tim_id);
            uint32_t divide = ((prescaler + 1) * (period + 1));
            #if MICROPY_PY_BUILTINS_FLOAT
            if (source_freq % divide != 0) {
                return mp_obj_new_float((float)source_freq / (float)divide);
            } else
            #endif
            {
                return mp_obj_new_int(source_freq / divide);
            }
        } else {
            // set
            uint32_t period;
            uint32_t prescaler = compute_prescaler_period_from_freq(self, args[1], &period);
            self->tim.Instance->PSC = prescaler;
            __HAL_TIM_SetAutoreload(&self->tim, period);
            // Reset the counter to zero. Otherwise, if counter >= period it will
            // continue counting until it wraps (at either 16 or 32 bits depending
            // on the timer).
            __HAL_TIM_SetCounter(&self->tim, 0);
            return mp_const_none;
        }
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_freq_obj, 1, 2, pyb_timer_freq);
    
    /// \method prescaler([value])
    /// Get or set the prescaler for the timer.
    STATIC mp_obj_t pyb_timer_prescaler(mp_uint_t n_args, const mp_obj_t *args) {
        pyb_timer_obj_t *self = args[0];
        if (n_args == 1) {
            // get
            return mp_obj_new_int(self->tim.Instance->PSC & 0xffff);
        } else {
            // set
            self->tim.Instance->PSC = mp_obj_get_int(args[1]) & 0xffff;
            return mp_const_none;
        }
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_prescaler_obj, 1, 2, pyb_timer_prescaler);
    
    /// \method period([value])
    /// Get or set the period of the timer.
    STATIC mp_obj_t pyb_timer_period(mp_uint_t n_args, const mp_obj_t *args) {
        pyb_timer_obj_t *self = args[0];
        if (n_args == 1) {
            // get
            return mp_obj_new_int(__HAL_TIM_GetAutoreload(&self->tim) & TIMER_CNT_MASK(self));
        } else {
            // set
            __HAL_TIM_SetAutoreload(&self->tim, mp_obj_get_int(args[1]) & TIMER_CNT_MASK(self));
            // Reset the counter to zero. Otherwise, if counter >= period it will
            // continue counting until it wraps (at either 16 or 32 bits depending
            // on the timer).
            __HAL_TIM_SetCounter(&self->tim, 0); 
            return mp_const_none;
        }
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_period_obj, 1, 2, pyb_timer_period);
    
    /// \method callback(fun)
    /// Set the function to be called when the timer triggers.
    /// `fun` is passed 1 argument, the timer object.
    /// If `fun` is `None` then the callback will be disabled.
    STATIC mp_obj_t pyb_timer_callback(mp_obj_t self_in, mp_obj_t callback) {
        pyb_timer_obj_t *self = self_in;
        if (callback == mp_const_none) {
            // stop interrupt (but not timer)
            __HAL_TIM_DISABLE_IT(&self->tim, TIM_IT_UPDATE);
            self->callback = mp_const_none;
        } else if (mp_obj_is_callable(callback)) {
            __HAL_TIM_DISABLE_IT(&self->tim, TIM_IT_UPDATE);
            self->callback = callback;
            // start timer, so that it interrupts on overflow, but clear any
            // pending interrupts which may have been set by initializing it.
            __HAL_TIM_CLEAR_FLAG(&self->tim, TIM_IT_UPDATE);
            HAL_TIM_Base_Start_IT(&self->tim); // This will re-enable the IRQ
            HAL_NVIC_EnableIRQ(self->irqn);
        } else {
            nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "callback must be None or a callable object"));
        }
        return mp_const_none;
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_2(pyb_timer_callback_obj, pyb_timer_callback);
    
    STATIC const mp_map_elem_t pyb_timer_locals_dict_table[] = {
        // instance methods
        { MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)&pyb_timer_init_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_deinit), (mp_obj_t)&pyb_timer_deinit_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_channel), (mp_obj_t)&pyb_timer_channel_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_counter), (mp_obj_t)&pyb_timer_counter_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_source_freq), (mp_obj_t)&pyb_timer_source_freq_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_freq), (mp_obj_t)&pyb_timer_freq_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_prescaler), (mp_obj_t)&pyb_timer_prescaler_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_period), (mp_obj_t)&pyb_timer_period_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_callback), (mp_obj_t)&pyb_timer_callback_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_UP),                  MP_OBJ_NEW_SMALL_INT(TIM_COUNTERMODE_UP) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_DOWN),                MP_OBJ_NEW_SMALL_INT(TIM_COUNTERMODE_DOWN) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_CENTER),              MP_OBJ_NEW_SMALL_INT(TIM_COUNTERMODE_CENTERALIGNED1) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_PWM),                 MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_PWM_NORMAL) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_PWM_INVERTED),        MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_PWM_INVERTED) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_OC_TIMING),           MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_OC_TIMING) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_OC_ACTIVE),           MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_OC_ACTIVE) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_OC_INACTIVE),         MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_OC_INACTIVE) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_OC_TOGGLE),           MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_OC_TOGGLE) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_OC_FORCED_ACTIVE),    MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_OC_FORCED_ACTIVE) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_OC_FORCED_INACTIVE),  MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_OC_FORCED_INACTIVE) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_IC),                  MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_IC) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_ENC_A),               MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_ENC_A) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_ENC_B),               MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_ENC_B) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_ENC_AB),              MP_OBJ_NEW_SMALL_INT(CHANNEL_MODE_ENC_AB) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_HIGH),                MP_OBJ_NEW_SMALL_INT(TIM_OCPOLARITY_HIGH) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_LOW),                 MP_OBJ_NEW_SMALL_INT(TIM_OCPOLARITY_LOW) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_RISING),              MP_OBJ_NEW_SMALL_INT(TIM_ICPOLARITY_RISING) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_FALLING),             MP_OBJ_NEW_SMALL_INT(TIM_ICPOLARITY_FALLING) },
        { MP_OBJ_NEW_QSTR(MP_QSTR_BOTH),                MP_OBJ_NEW_SMALL_INT(TIM_ICPOLARITY_BOTHEDGE) },
    };
    STATIC MP_DEFINE_CONST_DICT(pyb_timer_locals_dict, pyb_timer_locals_dict_table);
    
    const mp_obj_type_t pyb_timer_type = {
        { &mp_type_type },
        .name = MP_QSTR_Timer,
        .print = pyb_timer_print,
        .make_new = pyb_timer_make_new,
        .locals_dict = (mp_obj_t)&pyb_timer_locals_dict,
    };
    
    /// \moduleref pyb
    /// \class TimerChannel - setup a channel for a timer.
    ///
    /// Timer channels are used to generate/capture a signal using a timer.
    ///
    /// TimerChannel objects are created using the Timer.channel() method.
    STATIC void pyb_timer_channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
        pyb_timer_channel_obj_t *self = self_in;
    
        mp_printf(print, "TimerChannel(timer=%u, channel=%u, mode=%s)",
              self->timer->tim_id,
              self->channel,
              qstr_str(channel_mode_info[self->mode].name));
    }
    
    /// \method capture([value])
    /// Get or set the capture value associated with a channel.
    /// capture, compare, and pulse_width are all aliases for the same function.
    /// capture is the logical name to use when the channel is in input capture mode.
    
    /// \method compare([value])
    /// Get or set the compare value associated with a channel.
    /// capture, compare, and pulse_width are all aliases for the same function.
    /// compare is the logical name to use when the channel is in output compare mode.
    
    /// \method pulse_width([value])
    /// Get or set the pulse width value associated with a channel.
    /// capture, compare, and pulse_width are all aliases for the same function.
    /// pulse_width is the logical name to use when the channel is in PWM mode.
    ///
    /// In edge aligned mode, a pulse_width of `period + 1` corresponds to a duty cycle of 100%
    /// In center aligned mode, a pulse width of `period` corresponds to a duty cycle of 100%
    STATIC mp_obj_t pyb_timer_channel_capture_compare(mp_uint_t n_args, const mp_obj_t *args) {
        pyb_timer_channel_obj_t *self = args[0];
        if (n_args == 1) {
            // get
            return mp_obj_new_int(__HAL_TIM_GetCompare(&self->timer->tim, TIMER_CHANNEL(self)) & TIMER_CNT_MASK(self->timer));
        } else {
            // set
            __HAL_TIM_SetCompare(&self->timer->tim, TIMER_CHANNEL(self), mp_obj_get_int(args[1]) & TIMER_CNT_MASK(self->timer));
            return mp_const_none;
        }
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_channel_capture_compare_obj, 1, 2, pyb_timer_channel_capture_compare);
    
    /// \method pulse_width_percent([value])
    /// Get or set the pulse width percentage associated with a channel.  The value
    /// is a number between 0 and 100 and sets the percentage of the timer period
    /// for which the pulse is active.  The value can be an integer or
    /// floating-point number for more accuracy.  For example, a value of 25 gives
    /// a duty cycle of 25%.
    STATIC mp_obj_t pyb_timer_channel_pulse_width_percent(mp_uint_t n_args, const mp_obj_t *args) {
        pyb_timer_channel_obj_t *self = args[0];
        uint32_t period = compute_period(self->timer);
        if (n_args == 1) {
            // get
            uint32_t cmp = __HAL_TIM_GetCompare(&self->timer->tim, TIMER_CHANNEL(self)) & TIMER_CNT_MASK(self->timer);
            return compute_percent_from_pwm_value(period, cmp);
        } else {
            // set
            uint32_t cmp = compute_pwm_value_from_percent(period, args[1]);
            __HAL_TIM_SetCompare(&self->timer->tim, TIMER_CHANNEL(self), cmp & TIMER_CNT_MASK(self->timer));
            return mp_const_none;
        }
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_channel_pulse_width_percent_obj, 1, 2, pyb_timer_channel_pulse_width_percent);
    
    /// \method callback(fun)
    /// Set the function to be called when the timer channel triggers.
    /// `fun` is passed 1 argument, the timer object.
    /// If `fun` is `None` then the callback will be disabled.
    STATIC mp_obj_t pyb_timer_channel_callback(mp_obj_t self_in, mp_obj_t callback) {
        pyb_timer_channel_obj_t *self = self_in;
        if (callback == mp_const_none) {
            // stop interrupt (but not timer)
            __HAL_TIM_DISABLE_IT(&self->timer->tim, TIMER_IRQ_MASK(self->channel));
            self->callback = mp_const_none;
        } else if (mp_obj_is_callable(callback)) {
            self->callback = callback;
            HAL_NVIC_EnableIRQ(self->timer->irqn);
            // start timer, so that it interrupts on overflow
            switch (self->mode) {
                case CHANNEL_MODE_PWM_NORMAL:
                case CHANNEL_MODE_PWM_INVERTED:
                    HAL_TIM_PWM_Start_IT(&self->timer->tim, TIMER_CHANNEL(self));
                    break;
                case CHANNEL_MODE_OC_TIMING:
                case CHANNEL_MODE_OC_ACTIVE:
                case CHANNEL_MODE_OC_INACTIVE:
                case CHANNEL_MODE_OC_TOGGLE:
                case CHANNEL_MODE_OC_FORCED_ACTIVE:
                case CHANNEL_MODE_OC_FORCED_INACTIVE:
                    HAL_TIM_OC_Start_IT(&self->timer->tim, TIMER_CHANNEL(self));
                    break;
                case CHANNEL_MODE_IC:
                    HAL_TIM_IC_Start_IT(&self->timer->tim, TIMER_CHANNEL(self));
                    break;
            }
        } else {
            nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "callback must be None or a callable object"));
        }
        return mp_const_none;
    }
    STATIC MP_DEFINE_CONST_FUN_OBJ_2(pyb_timer_channel_callback_obj, pyb_timer_channel_callback);
    
    STATIC const mp_map_elem_t pyb_timer_channel_locals_dict_table[] = {
        // instance methods
        { MP_OBJ_NEW_QSTR(MP_QSTR_callback), (mp_obj_t)&pyb_timer_channel_callback_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_pulse_width), (mp_obj_t)&pyb_timer_channel_capture_compare_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_pulse_width_percent), (mp_obj_t)&pyb_timer_channel_pulse_width_percent_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_capture), (mp_obj_t)&pyb_timer_channel_capture_compare_obj },
        { MP_OBJ_NEW_QSTR(MP_QSTR_compare), (mp_obj_t)&pyb_timer_channel_capture_compare_obj },
    };
    STATIC MP_DEFINE_CONST_DICT(pyb_timer_channel_locals_dict, pyb_timer_channel_locals_dict_table);
    
    STATIC const mp_obj_type_t pyb_timer_channel_type = {
        { &mp_type_type },
        .name = MP_QSTR_TimerChannel,
        .print = pyb_timer_channel_print,
        .locals_dict = (mp_obj_t)&pyb_timer_channel_locals_dict,
    };
    
    STATIC void timer_handle_irq_channel(pyb_timer_obj_t *tim, uint8_t channel, mp_obj_t callback) {
        uint32_t irq_mask = TIMER_IRQ_MASK(channel);
    
        if (__HAL_TIM_GET_FLAG(&tim->tim, irq_mask) != RESET) {
            if (__HAL_TIM_GET_ITSTATUS(&tim->tim, irq_mask) != RESET) {
                // clear the interrupt
                __HAL_TIM_CLEAR_IT(&tim->tim, irq_mask);
    
                // execute callback if it's set
                if (callback != mp_const_none) {
                    // When executing code within a handler we must lock the GC to prevent
                    // any memory allocations.  We must also catch any exceptions.
                    gc_lock();
                    nlr_buf_t nlr;
                    if (nlr_push(&nlr) == 0) {
                        mp_call_function_1(callback, tim);
                        nlr_pop();
                    } else {
                        // Uncaught exception; disable the callback so it doesn't run again.
                        tim->callback = mp_const_none;
                        __HAL_TIM_DISABLE_IT(&tim->tim, irq_mask);
                        if (channel == 0) {
                            printf("uncaught exception in Timer(%u) interrupt handler\n", tim->tim_id);
                        } else {
                            printf("uncaught exception in Timer(%u) channel %u interrupt handler\n", tim->tim_id, channel);
                        }
                        mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
                    }
                    gc_unlock();
                }
            }
        }
    }
    
    void timer_irq_handler(uint tim_id) {
        if (tim_id - 1 < PYB_TIMER_OBJ_ALL_NUM) {
            // get the timer object
            pyb_timer_obj_t *tim = MP_STATE_PORT(pyb_timer_obj_all)[tim_id - 1];
    
            if (tim == NULL) {
                // Timer object has not been set, so we can't do anything.
                // This can happen under normal circumstances for timers like
                // 1 & 10 which use the same IRQ.
                return;
            }
    
            // Check for timer (versus timer channel) interrupt.
            timer_handle_irq_channel(tim, 0, tim->callback);
            uint32_t handled = TIMER_IRQ_MASK(0);
    
            // Check to see if a timer channel interrupt was pending
            pyb_timer_channel_obj_t *chan = tim->channel;
            while (chan != NULL) {
                timer_handle_irq_channel(tim, chan->channel, chan->callback);
                handled |= TIMER_IRQ_MASK(chan->channel);
                chan = chan->next;
            }
    
            // Finally, clear any remaining interrupt sources. Otherwise we'll
            // just get called continuously.
            uint32_t unhandled = tim->tim.Instance->DIER & 0xff & ~handled;
            if (unhandled != 0) {
                __HAL_TIM_DISABLE_IT(&tim->tim, unhandled);
                __HAL_TIM_CLEAR_IT(&tim->tim, unhandled);
                printf("Unhandled interrupt SR=0x%02lx (now disabled)\n", unhandled);
            }
        }
    }