| /* |
| * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #ifndef _HARDWARE_PWM_H |
| #define _HARDWARE_PWM_H |
| |
| #include "pico.h" |
| #include "hardware/structs/pwm.h" |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| // PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PWM, Enable/disable assertions in the PWM module, type=bool, default=0, group=hadrware_pwm |
| #ifndef PARAM_ASSERTIONS_ENABLED_PWM |
| #define PARAM_ASSERTIONS_ENABLED_PWM 0 |
| #endif |
| |
| /** \file hardware/pwm.h |
| * \defgroup hardware_pwm hardware_pwm |
| * |
| * Hardware Pulse Width Modulation (PWM) API |
| * |
| * The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or |
| * measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable |
| * PWM outputs. All 30 GPIOs can be driven by the PWM block |
| * |
| * The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a |
| * toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of |
| * time spent at the high signal level is known as the duty cycle of the signal. |
| * |
| * The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then |
| * immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after |
| * reaching TOP, until it reaches 0 again. |
| * |
| * \subsection pwm_example Example |
| * \addtogroup hardware_pwm |
| * \include hello_pwm.c |
| */ |
| |
| /** \brief PWM Divider mode settings |
| * \ingroup hardware_pwm |
| * |
| */ |
| enum pwm_clkdiv_mode |
| { |
| PWM_DIV_FREE_RUNNING, ///< Free-running counting at rate dictated by fractional divider |
| PWM_DIV_B_HIGH, ///< Fractional divider is gated by the PWM B pin |
| PWM_DIV_B_RISING, ///< Fractional divider advances with each rising edge of the PWM B pin |
| PWM_DIV_B_FALLING ///< Fractional divider advances with each falling edge of the PWM B pin |
| }; |
| |
| enum pwm_chan |
| { |
| PWM_CHAN_A = 0, |
| PWM_CHAN_B = 1 |
| }; |
| |
| typedef struct { |
| uint32_t csr; |
| uint32_t div; |
| uint32_t top; |
| } pwm_config; |
| |
| /** \brief Determine the PWM slice that is attached to the specified GPIO |
| * \ingroup hardware_pwm |
| * |
| * \return The PWM slice number that controls the specified GPIO. |
| */ |
| static inline uint pwm_gpio_to_slice_num(uint gpio) { |
| valid_params_if(PWM, gpio < N_GPIOS); |
| return (gpio >> 1u) & 7u; |
| } |
| |
| /** \brief Determine the PWM channel that is attached to the specified GPIO. |
| * \ingroup hardware_pwm |
| * |
| * Each slice 0 to 7 has two channels, A and B. |
| * |
| * \return The PWM channel that controls the specified GPIO. |
| */ |
| static inline uint pwm_gpio_to_channel(uint gpio) { |
| valid_params_if(PWM, gpio < N_GPIOS); |
| return gpio & 1u; |
| } |
| |
| /** \brief Set phase correction in a PWM configuration |
| * \ingroup hardware_pwm |
| * |
| * \param c PWM configuration struct to modify |
| * \param phase_correct true to set phase correct modulation, false to set trailing edge |
| * |
| * Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached, |
| * the PWM starts counting back down. The output frequency is halved when phase-correct mode is enabled. |
| */ |
| static inline void pwm_config_set_phase_correct(pwm_config *c, bool phase_correct) { |
| c->csr = (c->csr & ~PWM_CH0_CSR_PH_CORRECT_BITS) |
| | (!!phase_correct << PWM_CH0_CSR_PH_CORRECT_LSB); |
| } |
| |
| /** \brief Set clock divider in a PWM configuration |
| * \ingroup hardware_pwm |
| * |
| * \param c PWM configuration struct to modify |
| * \param div Value to divide counting rate by. Must be greater than or equal to 1. |
| * |
| * If the divide mode is free-running, the PWM counter runs at clk_sys / div. |
| * Otherwise, the divider reduces the rate of events seen on the B pin input (level or edge) |
| * before passing them on to the PWM counter. |
| */ |
| static inline void pwm_config_set_clkdiv(pwm_config *c, float div) { |
| c->div = (uint32_t)(div * (float)(1u << PWM_CH1_DIV_INT_LSB)); |
| } |
| |
| /** \brief Set PWM clock divider in a PWM configuration |
| * \ingroup hardware_pwm |
| * |
| * \param c PWM configuration struct to modify |
| * \param div integer value to reduce counting rate by. Must be greater than or equal to 1. |
| * |
| * If the divide mode is free-running, the PWM counter runs at clk_sys / div. |
| * Otherwise, the divider reduces the rate of events seen on the B pin input (level or edge) |
| * before passing them on to the PWM counter. |
| */ |
| static inline void pwm_config_set_clkdiv_int(pwm_config *c, uint div) { |
| c->div = div << PWM_CH1_DIV_INT_LSB; |
| } |
| |
| /** \brief Set PWM counting mode in a PWM configuration |
| * \ingroup hardware_pwm |
| * |
| * \param c PWM configuration struct to modify |
| * \param mode PWM divide/count mode |
| * |
| * Configure which event gates the operation of the fractional divider. |
| * The default is always-on (free-running PWM). Can also be configured to count on |
| * high level, rising edge or falling edge of the B pin input. |
| */ |
| static inline void pwm_config_set_clkdiv_mode(pwm_config *c, enum pwm_clkdiv_mode mode) { |
| valid_params_if(PWM, mode >= PWM_DIV_FREE_RUNNING && mode <= PWM_DIV_B_FALLING); |
| c->csr = (c->csr & ~PWM_CH0_CSR_DIVMODE_BITS) |
| | (mode << PWM_CH0_CSR_DIVMODE_LSB); |
| } |
| |
| /** \brief Set output polarity in a PWM configuration |
| * \ingroup hardware_pwm |
| * |
| * \param c PWM configuration struct to modify |
| * \param a true to invert output A |
| * \param b true to invert output B |
| */ |
| static inline void pwm_config_set_output_polarity(pwm_config *c, bool a, bool b) { |
| c->csr = (c->csr & ~(PWM_CH0_CSR_A_INV_BITS | PWM_CH0_CSR_B_INV_BITS)) |
| | ((!!a << PWM_CH0_CSR_A_INV_LSB) | (!!b << PWM_CH0_CSR_B_INV_LSB)); |
| } |
| |
| /** \brief Set PWM counter wrap value in a PWM configuration |
| * \ingroup hardware_pwm |
| * |
| * Set the highest value the counter will reach before returning to 0. Also known as TOP. |
| * |
| * \param c PWM configuration struct to modify |
| * \param wrap Value to set wrap to |
| */ |
| static inline void pwm_config_set_wrap(pwm_config *c, uint16_t wrap) { |
| c->top = wrap; |
| } |
| |
| /** \brief Initialise a PWM with settings from a configuration object |
| * \ingroup hardware_pwm |
| * |
| * Use the \ref pwm_get_default_config() function to initialise a config structure, make changes as |
| * needed using the pwm_config_* functions, then call this function to set up the PWM. |
| * |
| * \param slice_num PWM slice number |
| * \param c The configuration to use |
| * \param start If true the PWM will be started running once configured. If false you will need to start |
| * manually using \ref pwm_set_enabled() or \ref pwm_set_mask_enabled() |
| */ |
| static inline void pwm_init(uint slice_num, pwm_config *c, bool start) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| pwm_hw->slice[slice_num].csr = 0; |
| |
| pwm_hw->slice[slice_num].ctr = PWM_CH0_CTR_RESET; |
| pwm_hw->slice[slice_num].cc = PWM_CH0_CC_RESET; |
| pwm_hw->slice[slice_num].top = c->top; |
| pwm_hw->slice[slice_num].div = c->div; |
| pwm_hw->slice[slice_num].csr = c->csr | (!!start << PWM_CH0_CSR_EN_LSB); |
| } |
| |
| /** \brief Get a set of default values for PWM configuration |
| * \ingroup hardware_pwm |
| * |
| * PWM config is free running at system clock speed, no phase correction, wrapping at 0xffff, |
| * with standard polarities for channels A and B. |
| * |
| * \return Set of default values. |
| */ |
| static inline pwm_config pwm_get_default_config() { |
| pwm_config c = {0, 0, 0}; |
| pwm_config_set_phase_correct(&c, false); |
| pwm_config_set_clkdiv_int(&c, 1); |
| pwm_config_set_clkdiv_mode(&c, PWM_DIV_FREE_RUNNING); |
| pwm_config_set_output_polarity(&c, false, false); |
| pwm_config_set_wrap(&c, 0xffffu); |
| return c; |
| } |
| |
| /** \brief Set the current PWM counter wrap value |
| * \ingroup hardware_pwm |
| * |
| * Set the highest value the counter will reach before returning to 0. Also known as TOP. |
| * |
| * \param slice_num PWM slice number |
| * \param wrap Value to set wrap to |
| */ |
| static inline void pwm_set_wrap(uint slice_num, uint16_t wrap) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| pwm_hw->slice[slice_num].top = wrap; |
| } |
| |
| /** \brief Set the current PWM counter compare value for one channel |
| * \ingroup hardware_pwm |
| * |
| * Set the value of the PWM counter compare value, for either channel A or channel B |
| * |
| * \param slice_num PWM slice number |
| * \param chan Which channel to update. 0 for A, 1 for B. |
| * \param level new level for the selected output |
| */ |
| static inline void pwm_set_chan_level(uint slice_num, uint chan, uint16_t level) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| hw_write_masked( |
| &pwm_hw->slice[slice_num].cc, |
| level << (chan ? PWM_CH0_CC_B_LSB : PWM_CH0_CC_A_LSB), |
| chan ? PWM_CH0_CC_B_BITS : PWM_CH0_CC_A_BITS |
| ); |
| } |
| |
| /** \brief Set PWM counter compare values |
| * \ingroup hardware_pwm |
| * |
| * Set the value of the PWM counter compare values, A and B |
| * |
| * \param slice_num PWM slice number |
| * \param level_a Value to set compare A to. When the counter reaches this value the A output is deasserted |
| * \param level_b Value to set compare B to. When the counter reaches this value the B output is deasserted |
| */ |
| static inline void pwm_set_both_levels(uint slice_num, uint16_t level_a, uint16_t level_b) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| pwm_hw->slice[slice_num].cc = (level_b << PWM_CH0_CC_B_LSB) | (level_a << PWM_CH0_CC_A_LSB); |
| } |
| |
| /** \brief Helper function to set the PWM level for the slice and channel associated with a GPIO. |
| * \ingroup hardware_pwm |
| * |
| * Look up the correct slice (0 to 7) and channel (A or B) for a given GPIO, and update the corresponding |
| * counter-compare field. |
| * |
| * This PWM slice should already have been configured and set running. Also be careful of multiple GPIOs |
| * mapping to the same slice and channel (if GPIOs have a difference of 16). |
| * |
| * \param gpio GPIO to set level of |
| * \param level PWM level for this GPIO |
| */ |
| static inline void pwm_set_gpio_level(uint gpio, uint16_t level) { |
| valid_params_if(PWM, gpio < N_GPIOS); |
| pwm_set_chan_level(pwm_gpio_to_slice_num(gpio), pwm_gpio_to_channel(gpio), level); |
| } |
| |
| /** \brief Get PWM counter |
| * \ingroup hardware_pwm |
| * |
| * Get current value of PWM counter |
| * |
| * \param slice_num PWM slice number |
| * \return Current value of PWM counter |
| */ |
| static inline int16_t pwm_get_counter(uint slice_num) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| return (pwm_hw->slice[slice_num].ctr); |
| } |
| |
| /** \brief Set PWM counter |
| * \ingroup hardware_pwm |
| * |
| * Set the value of the PWM counter |
| * |
| * \param slice_num PWM slice number |
| * \param c Value to set the PWM counter to |
| * |
| */ |
| static inline void pwm_set_counter(uint slice_num, uint16_t c) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| pwm_hw->slice[slice_num].ctr = c; |
| } |
| |
| /** \brief Advance PWM count |
| * \ingroup hardware_pwm |
| * |
| * Advance the phase of a running the counter by 1 count. |
| * |
| * This function will return once the increment is complete. |
| * |
| * \param slice_num PWM slice number |
| */ |
| static inline void pwm_advance_count(uint slice_num) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| hw_set_bits(&pwm_hw->slice[slice_num].csr, PWM_CH0_CSR_PH_ADV_BITS); |
| while (pwm_hw->slice[slice_num].csr & PWM_CH0_CSR_PH_ADV_BITS) { |
| tight_loop_contents(); |
| } |
| } |
| |
| /** \brief Retard PWM count |
| * \ingroup hardware_pwm |
| * |
| * Retard the phase of a running counter by 1 count |
| * |
| * This function will return once the retardation is complete. |
| * |
| * \param slice_num PWM slice number |
| */ |
| static inline void pwm_retard_count(uint slice_num) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| hw_set_bits(&pwm_hw->slice[slice_num].csr, PWM_CH0_CSR_PH_RET_BITS); |
| while (pwm_hw->slice[slice_num].csr & PWM_CH0_CSR_PH_RET_BITS) { |
| tight_loop_contents(); |
| } |
| } |
| |
| /** \brief Set PWM clock divider using an 8:4 fractional value |
| * \ingroup hardware_pwm |
| * |
| * Set the clock divider. Counter increment will be on sysclock divided by this value, taking in to account the gating. |
| * |
| * \param slice_num PWM slice number |
| * \param integer 8 bit integer part of the clock divider |
| * \param fract 4 bit fractional part of the clock divider |
| */ |
| static inline void pwm_set_clkdiv_int_frac(uint slice_num, uint8_t integer, uint8_t fract) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| valid_params_if(PWM, fract >= 0 && slice_num <= 16); |
| pwm_hw->slice[slice_num].div = (integer << PWM_CH0_DIV_INT_LSB) | (fract << PWM_CH0_DIV_FRAC_LSB); |
| } |
| |
| /** \brief Set PWM clock divider |
| * \ingroup hardware_pwm |
| * |
| * Set the clock divider. Counter increment will be on sysclock divided by this value, taking in to account the gating. |
| * |
| * \param slice_num PWM slice number |
| * \param divider Floating point clock divider, 1.f <= value < 256.f |
| */ |
| static inline void pwm_set_clkdiv(uint slice_num, float divider) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| valid_params_if(PWM, divider >= 1.f && divider < 256.f); |
| uint8_t i = (uint8_t)divider; |
| uint8_t f = (uint8_t)((divider - i) * (0x01 << 4)); |
| pwm_set_clkdiv_int_frac(slice_num, i, f); |
| } |
| |
| /** \brief Set PWM output polarity |
| * \ingroup hardware_pwm |
| * |
| * \param slice_num PWM slice number |
| * \param a true to invert output A |
| * \param b true to invert output B |
| */ |
| static inline void pwm_set_output_polarity(uint slice_num, bool a, bool b) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| hw_write_masked(&pwm_hw->slice[slice_num].csr, !!a << PWM_CH0_CSR_A_INV_LSB | !!b << PWM_CH0_CSR_B_INV_LSB, |
| PWM_CH0_CSR_A_INV_BITS | PWM_CH0_CSR_B_INV_BITS); |
| } |
| |
| |
| /** \brief Set PWM divider mode |
| * \ingroup hardware_pwm |
| * |
| * \param slice_num PWM slice number |
| * \param mode Required divider mode |
| */ |
| static inline void pwm_set_clkdiv_mode(uint slice_num, enum pwm_clkdiv_mode mode) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| valid_params_if(PWM, mode >= PWM_DIV_FREE_RUNNING && mode <= PWM_DIV_B_FALLING); |
| hw_write_masked(&pwm_hw->slice[slice_num].csr, mode << PWM_CH0_CSR_DIVMODE_LSB, PWM_CH0_CSR_DIVMODE_BITS); |
| } |
| |
| /** \brief Set PWM phase correct on/off |
| * \ingroup hardware_pwm |
| * |
| * \param slice_num PWM slice number |
| * \param phase_correct true to set phase correct modulation, false to set trailing edge |
| * |
| * Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached, |
| * the PWM starts counting back down. The output frequency is halved when phase-correct mode is enabled. |
| */ |
| static inline void pwm_set_phase_correct(uint slice_num, bool phase_correct) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| hw_write_masked(&pwm_hw->slice[slice_num].csr, phase_correct << PWM_CH0_CSR_PH_CORRECT_LSB, PWM_CH0_CSR_PH_CORRECT_BITS); |
| } |
| |
| /** \brief Enable/Disable PWM |
| * \ingroup hardware_pwm |
| * |
| * \param slice_num PWM slice number |
| * \param enabled true to enable the specified PWM, false to disable |
| */ |
| static inline void pwm_set_enabled(uint slice_num, bool enabled) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| hw_write_masked(&pwm_hw->slice[slice_num].csr, !!enabled << PWM_CH0_CSR_EN_LSB, PWM_CH0_CSR_EN_BITS); |
| } |
| |
| /** \brief Enable/Disable multiple PWM slices simultaneously |
| * \ingroup hardware_pwm |
| * |
| * \param mask Bitmap of PWMs to enable/disable. Bits 0 to 7 enable slices 0-7 respectively |
| */ |
| static inline void pwm_set_mask_enabled(uint32_t mask) { |
| pwm_hw->en = mask; |
| } |
| |
| /*! \brief Enable PWM instance interrupt |
| * \ingroup hardware_pwm |
| * |
| * Used to enable a single PWM instance interrupt |
| * |
| * \param slice_num PWM block to enable/disable |
| * \param enabled true to enable, false to disable |
| */ |
| static inline void pwm_set_irq_enabled(uint slice_num, bool enabled) { |
| valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES); |
| if (enabled) { |
| hw_set_bits(&pwm_hw->inte, 1u << slice_num); |
| } else { |
| hw_clear_bits(&pwm_hw->inte, 1u << slice_num); |
| } |
| } |
| |
| /*! \brief Enable multiple PWM instance interrupts |
| * \ingroup hardware_pwm |
| * |
| * Use this to enable multiple PWM interrupts at once. |
| * |
| * \param slice_mask Bitmask of all the blocks to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc. |
| * \param enabled true to enable, false to disable |
| */ |
| static inline void pwm_set_irq_mask_enabled(uint32_t slice_mask, bool enabled) { |
| valid_params_if(PWM, slice_mask < 256); |
| if (enabled) { |
| hw_set_bits(&pwm_hw->inte, slice_mask); |
| } else { |
| hw_clear_bits(&pwm_hw->inte, slice_mask); |
| } |
| } |
| |
| /*! \brief Clear single PWM channel interrupt |
| * \ingroup hardware_pwm |
| * |
| * \param slice_num PWM slice number |
| */ |
| static inline void pwm_clear_irq(uint slice_num) { |
| pwm_hw->intr = 1u << slice_num; |
| } |
| |
| /*! \brief Get PWM interrupt status, raw |
| * \ingroup hardware_pwm |
| * |
| * \return Bitmask of all PWM interrupts currently set |
| */ |
| static inline int32_t pwm_get_irq_status_mask() { |
| return pwm_hw->ints; |
| } |
| |
| /*! \brief Force PWM interrupt |
| * \ingroup hardware_pwm |
| * |
| * \param slice_num PWM slice number |
| */ |
| static inline void pwm_force_irq(uint slice_num) { |
| pwm_hw->intf = 1u << slice_num; |
| } |
| |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| #endif |