diff --git a/os/hal/include/pwm.h b/os/hal/include/pwm.h index c8095d2b7..2861cbcea 100644 --- a/os/hal/include/pwm.h +++ b/os/hal/include/pwm.h @@ -57,34 +57,50 @@ * @brief Driver state machine possible states. */ typedef enum { - PWM_UNINIT = 0, /**< @brief Not initialized. */ - PWM_STOP = 1, /**< @brief Stopped. */ - PWM_READY = 2, /**< @brief Ready. */ + PWM_UNINIT = 0, /**< Not initialized. */ + PWM_STOP = 1, /**< Stopped. */ + PWM_READY = 2, /**< Ready. */ } pwmstate_t; /** * @brief PWM logic mode. */ typedef enum { - PWM_OUTPUT_DISABLED = 0, /**< @brief Output not driven, callback - only. */ - PWM_OUTPUT_ACTIVE_HIGH = 1, /**< @brief Idle is logic level 0. */ - PWM_OUTPUT_ACTIVE_LOW = 2 /**< @brief Idle is logic level 1. */ + PWM_OUTPUT_DISABLED = 0, /**< Output not driven, callback only. */ + PWM_OUTPUT_ACTIVE_HIGH = 1, /**< Idle is logic level 0. */ + PWM_OUTPUT_ACTIVE_LOW = 2 /**< Idle is logic level 1. */ } pwmmode_t; -/** - * @brief PWM notification callback type. - * - * @param[in] active current channel output state - */ -typedef void (*pwmcallback_t)(void); - #include "pwm_lld.h" /*===========================================================================*/ /* Driver macros. */ /*===========================================================================*/ +/** + * @brief Enables a PWM channel. + * @details Programs (or reprograms) a PWM channel. + * @note This function has to be invoked from a lock zone. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier + * @param[in] width PWM pulse width as clock pulses number + */ +#define pwmEnableChannelI(pwmp, channel, width) \ + pwm_lld_enable_channel(pwmp, channel, width) + +/** + * @brief Disables a PWM channel. + * @details The channel is disabled and its output line returned to the + * idle state. + * @note This function has to be invoked from a lock zone. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier + */ +#define pwmDisableChannelI(pwmp, channel) \ + pwm_lld_disable_channel(pwmp, channel) + /*===========================================================================*/ /* External declarations. */ /*===========================================================================*/ diff --git a/os/hal/platforms/STM32/pwm_lld.c b/os/hal/platforms/STM32/pwm_lld.c index 472b1c15e..fbe986336 100644 --- a/os/hal/platforms/STM32/pwm_lld.c +++ b/os/hal/platforms/STM32/pwm_lld.c @@ -25,8 +25,9 @@ */ /** - * @file STM32/pwm_lld.c - * @brief STM32 PWM subsystem low level driver header. + * @file STM32/pwm_lld.c + * @brief STM32 PWM subsystem low level driver header. + * * @addtogroup STM32_PWM * @{ */ @@ -41,32 +42,32 @@ /*===========================================================================*/ /** - * @brief PWM1 driver identifier. - * @note The driver PWM1 allocates the complex timer TIM1 when enabled. + * @brief PWM1 driver identifier. + * @note The driver PWM1 allocates the complex timer TIM1 when enabled. */ #if defined(USE_STM32_PWM1) || defined(__DOXYGEN__) PWMDriver PWMD1; #endif /** - * @brief PWM2 driver identifier. - * @note The driver PWM2 allocates the timer TIM2 when enabled. + * @brief PWM2 driver identifier. + * @note The driver PWM2 allocates the timer TIM2 when enabled. */ #if defined(USE_STM32_PWM2) || defined(__DOXYGEN__) PWMDriver PWMD2; #endif /** - * @brief PWM3 driver identifier. - * @note The driver PWM3 allocates the timer TIM3 when enabled. + * @brief PWM3 driver identifier. + * @note The driver PWM3 allocates the timer TIM3 when enabled. */ #if defined(USE_STM32_PWM3) || defined(__DOXYGEN__) PWMDriver PWMD3; #endif /** - * @brief PWM4 driver identifier. - * @note The driver PWM4 allocates the timer TIM4 when enabled. + * @brief PWM4 driver identifier. + * @note The driver PWM4 allocates the timer TIM4 when enabled. */ #if defined(USE_STM32_PWM4) || defined(__DOXYGEN__) PWMDriver PWMD4; @@ -80,29 +81,12 @@ PWMDriver PWMD4; /* Driver local functions. */ /*===========================================================================*/ -/** - * @brief Stops all channels. - * - * @param[in] pwmp pointer to a @p PWMDriver object - */ -static void stop_channels(PWMDriver *pwmp) { - - pwmp->pd_enabled_channels = 0; /* All channels disabled. */ - pwmp->pd_tim->CCER = 0; /* Outputs disabled. */ - pwmp->pd_tim->CCR1 = 0; /* Comparator 1 disabled. */ - pwmp->pd_tim->CCR2 = 0; /* Comparator 2 disabled. */ - pwmp->pd_tim->CCR3 = 0; /* Comparator 3 disabled. */ - pwmp->pd_tim->CCR4 = 0; /* Comparator 4 disabled. */ - pwmp->pd_tim->CCMR1 = 0; /* Channels 1 and 2 frozen. */ - pwmp->pd_tim->CCMR2 = 0; /* Channels 3 and 4 frozen. */ -} - #if USE_STM32_PWM2 || USE_STM32_PWM3 || USE_STM32_PWM4 || defined(__DOXYGEN__) /** - * @brief Common TIM2...TIM4 IRQ handler. - * @note It is assumed that the various sources are only activated if the - * associated callback pointer is not equal to @p NULL in order to not - * perform an extra check in a potentially critical interrupt handler. + * @brief Common TIM2...TIM4 IRQ handler. + * @note It is assumed that the various sources are only activated if the + * associated callback pointer is not equal to @p NULL in order to not + * perform an extra check in a potentially critical interrupt handler. */ static void serve_interrupt(PWMDriver *pwmp) { uint16_t sr; @@ -111,15 +95,15 @@ static void serve_interrupt(PWMDriver *pwmp) { pwmp->pd_tim->SR = ~(TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF | TIM_SR_CC4IF | TIM_SR_UIF); if ((sr & TIM_SR_CC1IF) != 0) - pwmp->pd_config->pc_channels[0].pcc_callback(); + pwmp->pd_config->pc_channels[0].pcc_callback(pwmp); if ((sr & TIM_SR_CC2IF) != 0) - pwmp->pd_config->pc_channels[1].pcc_callback(); + pwmp->pd_config->pc_channels[1].pcc_callback(pwmp); if ((sr & TIM_SR_CC3IF) != 0) - pwmp->pd_config->pc_channels[2].pcc_callback(); + pwmp->pd_config->pc_channels[2].pcc_callback(pwmp); if ((sr & TIM_SR_CC4IF) != 0) - pwmp->pd_config->pc_channels[3].pcc_callback(); + pwmp->pd_config->pc_channels[3].pcc_callback(pwmp); if ((sr & TIM_SR_UIF) != 0) - pwmp->pd_config->pc_callback(); + pwmp->pd_config->pc_callback(pwmp); } #endif /* USE_STM32_PWM2 || USE_STM32_PWM3 || USE_STM32_PWM4 */ @@ -129,26 +113,26 @@ static void serve_interrupt(PWMDriver *pwmp) { #if USE_STM32_PWM1 /** - * @brief TIM1 update interrupt handler. - * @note It is assumed that this interrupt is only activated if the callback - * pointer is not equal to @p NULL in order to not perform an extra - * check in a potentially critical interrupt handler. + * @brief TIM1 update interrupt handler. + * @note It is assumed that this interrupt is only activated if the callback + * pointer is not equal to @p NULL in order to not perform an extra + * check in a potentially critical interrupt handler. */ CH_IRQ_HANDLER(VectorA4) { CH_IRQ_PROLOGUE(); TIM1->SR = ~TIM_SR_UIF; - PWMD1.pd_config->pc_callback(); + PWMD1.pd_config->pc_callback(&PWMD1); CH_IRQ_EPILOGUE(); } /** - * @brief TIM1 compare interrupt handler. - * @note It is assumed that the various sources are only activated if the - * associated callback pointer is not equal to @p NULL in order to not - * perform an extra check in a potentially critical interrupt handler. + * @brief TIM1 compare interrupt handler. + * @note It is assumed that the various sources are only activated if the + * associated callback pointer is not equal to @p NULL in order to not + * perform an extra check in a potentially critical interrupt handler. */ CH_IRQ_HANDLER(VectorAC) { uint16_t sr; @@ -158,13 +142,13 @@ CH_IRQ_HANDLER(VectorAC) { sr = TIM1->SR & TIM1->DIER; TIM1->SR = ~(TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF | TIM_SR_CC4IF); if ((sr & TIM_SR_CC1IF) != 0) - PWMD1.pd_config->pc_channels[0].pcc_callback(); + PWMD1.pd_config->pc_channels[0].pcc_callback(&PWMD1); if ((sr & TIM_SR_CC2IF) != 0) - PWMD1.pd_config->pc_channels[1].pcc_callback(); + PWMD1.pd_config->pc_channels[1].pcc_callback(&PWMD1); if ((sr & TIM_SR_CC3IF) != 0) - PWMD1.pd_config->pc_channels[2].pcc_callback(); + PWMD1.pd_config->pc_channels[2].pcc_callback(&PWMD1); if ((sr & TIM_SR_CC4IF) != 0) - PWMD1.pd_config->pc_channels[3].pcc_callback(); + PWMD1.pd_config->pc_channels[3].pcc_callback(&PWMD1); CH_IRQ_EPILOGUE(); } @@ -172,7 +156,7 @@ CH_IRQ_HANDLER(VectorAC) { #if USE_STM32_PWM2 /** - * @brief TIM2 interrupt handler. + * @brief TIM2 interrupt handler. */ CH_IRQ_HANDLER(VectorB0) { @@ -186,7 +170,7 @@ CH_IRQ_HANDLER(VectorB0) { #if USE_STM32_PWM3 /** - * @brief TIM3 interrupt handler. + * @brief TIM3 interrupt handler. */ CH_IRQ_HANDLER(VectorB4) { @@ -200,7 +184,7 @@ CH_IRQ_HANDLER(VectorB4) { #if USE_STM32_PWM4 /** - * @brief TIM4 interrupt handler. + * @brief TIM4 interrupt handler. */ CH_IRQ_HANDLER(VectorB8) { @@ -217,7 +201,7 @@ CH_IRQ_HANDLER(VectorB8) { /*===========================================================================*/ /** - * @brief Low level PWM driver initialization. + * @brief Low level PWM driver initialization. */ void pwm_lld_init(void) { @@ -267,55 +251,73 @@ void pwm_lld_init(void) { } /** - * @brief Configures and activates the PWM peripheral. + * @brief Configures and activates the PWM peripheral. * - * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] pwmp pointer to a @p PWMDriver object */ void pwm_lld_start(PWMDriver *pwmp) { uint16_t ccer; + /* Reset channels.*/ + pwmp->pd_enabled_channels = 0; /* All channels disabled. */ + if (pwmp->pd_state == PWM_STOP) { - /* Clock activation.*/ + /* Clock activation and timer reset.*/ #if USE_STM32_PWM1 if (&PWMD1 == pwmp) { - NVICEnableVector(TIM1_UP_IRQn, - CORTEX_PRIORITY_MASK(STM32_PWM1_IRQ_PRIORITY)); - NVICEnableVector(TIM1_CC_IRQn, - CORTEX_PRIORITY_MASK(STM32_PWM1_IRQ_PRIORITY)); RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; + RCC->APB2RSTR = RCC_APB2RSTR_TIM1RST; + RCC->APB2RSTR = 0; + NVICEnableVector(TIM1_UP_IRQn, + CORTEX_PRIORITY_MASK(STM32_PWM_TIM1_IRQ_PRIORITY)); + NVICEnableVector(TIM1_CC_IRQn, + CORTEX_PRIORITY_MASK(STM32_PWM_TIM1_IRQ_PRIORITY)); } #endif #if USE_STM32_PWM2 if (&PWMD2 == pwmp) { - NVICEnableVector(TIM2_IRQn, - CORTEX_PRIORITY_MASK(STM32_PWM2_IRQ_PRIORITY)); RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; + RCC->APB1RSTR = RCC_APB1RSTR_TIM2RST; + RCC->APB1RSTR = 0; + NVICEnableVector(TIM2_IRQn, + CORTEX_PRIORITY_MASK(STM32_PWM_TIM2_IRQ_PRIORITY)); } #endif #if USE_STM32_PWM3 if (&PWMD3 == pwmp) { - NVICEnableVector(TIM3_IRQn, - CORTEX_PRIORITY_MASK(STM32_PWM3_IRQ_PRIORITY)); RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; + RCC->APB1RSTR = RCC_APB1RSTR_TIM3RST; + RCC->APB1RSTR = 0; + NVICEnableVector(TIM3_IRQn, + CORTEX_PRIORITY_MASK(STM32_PWM_TIM3_IRQ_PRIORITY)); } #endif #if USE_STM32_PWM4 if (&PWMD4 == pwmp) { - NVICEnableVector(TIM4_IRQn, - CORTEX_PRIORITY_MASK(STM32_PWM4_IRQ_PRIORITY)); RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; + RCC->APB1RSTR = RCC_APB1RSTR_TIM4RST; + RCC->APB1RSTR = 0; + NVICEnableVector(TIM4_IRQn, + CORTEX_PRIORITY_MASK(STM32_PWM_TIM4_IRQ_PRIORITY)); } #endif } - /* Reset channels.*/ - stop_channels(pwmp); + else { + /* Driver re-configuration scenario, it must be stopped first.*/ + /* Really required ?????????? */ + pwmp->pd_tim->CR1 = 0; /* Timer stopped. */ + pwmp->pd_tim->CR2 = 0; /* Timer stopped. */ + pwmp->pd_tim->SMCR = 0; /* Slave mode disabled. */ + pwmp->pd_tim->CCR1 = 0; /* Comparator 1 disabled. */ + pwmp->pd_tim->CCR2 = 0; /* Comparator 2 disabled. */ + pwmp->pd_tim->CCR3 = 0; /* Comparator 3 disabled. */ + pwmp->pd_tim->CCR4 = 0; /* Comparator 4 disabled. */ + pwmp->pd_tim->CNT = 0; + } - /* Configuration or reconfiguration.*/ - pwmp->pd_tim->CR1 = 0; /* Timer stopped. */ - pwmp->pd_tim->SMCR = 0; /* Slave mode disabled. */ + /* Timer configuration.*/ pwmp->pd_tim->CR2 = pwmp->pd_config->pc_cr2; pwmp->pd_tim->PSC = pwmp->pd_config->pc_psc; - pwmp->pd_tim->CNT = 0; pwmp->pd_tim->ARR = pwmp->pd_config->pc_arr; /* Output enables and polarities setup.*/ ccer = 0; @@ -356,22 +358,30 @@ void pwm_lld_start(PWMDriver *pwmp) { pwmp->pd_tim->SR = 0; /* Clear pending IRQs. */ pwmp->pd_tim->DIER = pwmp->pd_config->pc_callback == NULL ? 0 : TIM_DIER_UIE; pwmp->pd_tim->BDTR = TIM_BDTR_MOE; - pwmp->pd_tim->CR1 = TIM_CR1_ARPE | TIM_CR1_URS | - TIM_CR1_CEN; /* Timer configured and started.*/ + /* Timer configured and started.*/ + pwmp->pd_tim->CR1 = TIM_CR1_ARPE | TIM_CR1_URS | TIM_CR1_CEN; } /** - * @brief Deactivates the PWM peripheral. + * @brief Deactivates the PWM peripheral. * - * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] pwmp pointer to a @p PWMDriver object */ void pwm_lld_stop(PWMDriver *pwmp) { /* If in ready state then disables the PWM clock.*/ if (pwmp->pd_state == PWM_READY) { - stop_channels(pwmp); - pwmp->pd_tim->CR1 = 0; - pwmp->pd_tim->BDTR = 0; - pwmp->pd_tim->DIER = 0; + pwmp->pd_enabled_channels = 0; /* All channels disabled. */ + pwmp->pd_tim->CR1 = 0; + pwmp->pd_tim->CR2 = 0; + pwmp->pd_tim->CCER = 0; /* Outputs disabled. */ + pwmp->pd_tim->CCR1 = 0; /* Comparator 1 disabled. */ + pwmp->pd_tim->CCR2 = 0; /* Comparator 2 disabled. */ + pwmp->pd_tim->CCR3 = 0; /* Comparator 3 disabled. */ + pwmp->pd_tim->CCR4 = 0; /* Comparator 4 disabled. */ + pwmp->pd_tim->BDTR = 0; + pwmp->pd_tim->DIER = 0; + pwmp->pd_tim->SR = 0; + pwmp->pd_tim->EGR = TIM_EGR_UG; /* Update event. */ #if USE_STM32_PWM1 if (&PWMD1 == pwmp) { @@ -402,112 +412,42 @@ void pwm_lld_stop(PWMDriver *pwmp) { } /** - * @brief Enables a PWM channel. + * @brief Enables a PWM channel. * * @param[in] pwmp pointer to a @p PWMDriver object - * @param[in] channel PWM channel identifier + * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) * @param[in] width PWM pulse width as clock pulses number */ void pwm_lld_enable_channel(PWMDriver *pwmp, pwmchannel_t channel, pwmcnt_t width) { - /* - * Changes the pulse width. - */ - switch (channel) { - case 0: - pwmp->pd_tim->CCR1 = width; - break; - case 1: - pwmp->pd_tim->CCR2 = width; - break; - case 2: - pwmp->pd_tim->CCR3 = width; - break; - case 3: - pwmp->pd_tim->CCR4 = width; - break; - } + *(&pwmp->pd_tim->CCR1 + (channel * 2)) = width; /* New duty cycle. */ if ((pwmp->pd_enabled_channels & (1 << channel)) == 0) { - /* - * The channel is not enabled yet. - */ + /* The channel is not enabled yet.*/ pwmp->pd_enabled_channels |= (1 << channel); - /* - * Setup the comparator, the channel is configured as PWM mode 1 with - * preload enabled. - */ - switch (channel) { - case 0: - pwmp->pd_tim->CCMR1 = (pwmp->pd_tim->CCMR1 & 0xFF00) | - TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | - TIM_CCMR1_OC1PE; - pwmp->pd_tim->SR = ~TIM_SR_CC1IF; - pwmp->pd_tim->DIER |= pwmp->pd_config->pc_channels[0].pcc_callback == NULL - ? 0 : TIM_DIER_CC1IE; - break; - case 1: - pwmp->pd_tim->CCMR1 = (pwmp->pd_tim->CCMR1 & 0x00FF) | - TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2 | - TIM_CCMR1_OC2PE; - pwmp->pd_tim->SR = ~TIM_SR_CC2IF; - pwmp->pd_tim->DIER |= pwmp->pd_config->pc_channels[1].pcc_callback == NULL - ? 0 : TIM_DIER_CC2IE; - break; - case 2: - pwmp->pd_tim->CCMR2 = (pwmp->pd_tim->CCMR2 & 0xFF00) | - TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2 | - TIM_CCMR2_OC3PE; - pwmp->pd_tim->SR = ~TIM_SR_CC3IF; - pwmp->pd_tim->DIER |= pwmp->pd_config->pc_channels[2].pcc_callback == NULL - ? 0 : TIM_DIER_CC3IE; - break; - case 3: - pwmp->pd_tim->CCMR2 = (pwmp->pd_tim->CCMR2 & 0x00FF) | - TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2 | - TIM_CCMR2_OC4PE; - pwmp->pd_tim->SR = ~TIM_SR_CC4IF; - pwmp->pd_tim->DIER |= pwmp->pd_config->pc_channels[3].pcc_callback == NULL - ? 0 : TIM_DIER_CC4IE; - break; + /* If there is a callback associated to the channel then the proper + interrupt is cleared and enabled.*/ + if (pwmp->pd_config->pc_channels[channel].pcc_callback) { + pwmp->pd_tim->SR = ~(2 << channel); + pwmp->pd_tim->DIER |= (2 << channel); } } } /** - * @brief Disables a PWM channel. + * @brief Disables a PWM channel. * @details The channel is disabled and its output line returned to the * idle state. * * @param[in] pwmp pointer to a @p PWMDriver object - * @param[in] channel PWM channel identifier + * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) */ void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t channel) { + *(&pwmp->pd_tim->CCR1 + (channel * 2)) = 0; + pwmp->pd_tim->DIER = ~(2 << channel); pwmp->pd_enabled_channels &= ~(1 << channel); - switch (channel) { - case 0: - pwmp->pd_tim->CCR1 = 0; - pwmp->pd_tim->CCMR1 = pwmp->pd_tim->CCMR1 & 0xFF00; - pwmp->pd_tim->DIER &= ~TIM_DIER_CC1IE; - break; - case 1: - pwmp->pd_tim->CCR2 = 0; - pwmp->pd_tim->CCMR1 = pwmp->pd_tim->CCMR1 & 0x00FF; - pwmp->pd_tim->DIER &= ~TIM_DIER_CC2IE; - break; - case 2: - pwmp->pd_tim->CCR3 = 0; - pwmp->pd_tim->CCMR2 = pwmp->pd_tim->CCMR2 & 0xFF00; - pwmp->pd_tim->DIER &= ~TIM_DIER_CC3IE; - break; - case 3: - pwmp->pd_tim->CCR4 = 0; - pwmp->pd_tim->CCMR2 = pwmp->pd_tim->CCMR2 & 0x00FF; - pwmp->pd_tim->DIER &= ~TIM_DIER_CC4IE; - break; - } } #endif /* CH_HAL_USE_PWM */ diff --git a/os/hal/platforms/STM32/pwm_lld.h b/os/hal/platforms/STM32/pwm_lld.h index dfbb92204..b716d77c2 100644 --- a/os/hal/platforms/STM32/pwm_lld.h +++ b/os/hal/platforms/STM32/pwm_lld.h @@ -10,115 +10,147 @@ ChibiOS/RT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . - - --- - - A special exception to the GPL can be applied should you wish to distribute - a combined work that includes ChibiOS/RT, without being obliged to provide - the source code for any proprietary components. See the file exception.txt - for full details of how and when the exception can be applied. + along with this program. If not, see . */ /** - * @file STM32/pwm_lld.h - * @brief STM32 PWM subsystem low level driver header. - * @addtogroup STM32_PWM + * @file STM32/pwm_lld.h + * @brief STM32 PWM subsystem low level driver header. + * + * @addtogroup PWM * @{ */ #ifndef _PWM_LLD_H_ #define _PWM_LLD_H_ -#if CH_HAL_USE_PWM || defined(__DOXYGEN__) +#if HAL_USE_PWM || defined(__DOXYGEN__) /*===========================================================================*/ /* Driver constants. */ /*===========================================================================*/ /** - * @brief Number of PWM channels per PWM driver. + * @brief Number of PWM channels per PWM driver. */ -#define PWM_CHANNELS 4 +#define PWM_CHANNELS 4 /*===========================================================================*/ /* Driver pre-compile time settings. */ /*===========================================================================*/ /** - * @brief PWM1 driver enable switch. + * @brief PWM1 driver enable switch. * @details If set to @p TRUE the support for PWM1 is included. - * @note The default is @p TRUE. + * @note The default is @p TRUE. */ -#if !defined(USE_STM32_PWM1) || defined(__DOXYGEN__) -#define USE_STM32_PWM1 TRUE +#if !defined(STM32_PWM_USE_TIM1) || defined(__DOXYGEN__) +#define STM32_PWM_USE_TIM1 TRUE #endif /** - * @brief PWM2 driver enable switch. + * @brief PWM2 driver enable switch. * @details If set to @p TRUE the support for PWM2 is included. - * @note The default is @p TRUE. + * @note The default is @p TRUE. */ -#if !defined(USE_STM32_PWM2) || defined(__DOXYGEN__) -#define USE_STM32_PWM2 TRUE +#if !defined(STM32_PWM_USE_TIM2) || defined(__DOXYGEN__) +#define STM32_PWM_USE_TIM2 TRUE #endif /** - * @brief PWM3 driver enable switch. + * @brief PWM3 driver enable switch. * @details If set to @p TRUE the support for PWM3 is included. - * @note The default is @p TRUE. + * @note The default is @p TRUE. */ -#if !defined(USE_STM32_PWM3) || defined(__DOXYGEN__) -#define USE_STM32_PWM3 TRUE +#if !defined(STM32_PWM_USE_TIM3) || defined(__DOXYGEN__) +#define STM32_PWM_USE_TIM3 TRUE #endif /** - * @brief PWM4 driver enable switch. + * @brief PWM4 driver enable switch. * @details If set to @p TRUE the support for PWM4 is included. - * @note The default is @p TRUE. + * @note The default is @p TRUE. */ -#if !defined(USE_STM32_PWM4) || defined(__DOXYGEN__) -#define USE_STM32_PWM4 TRUE +#if !defined(STM32_PWM_USE_TIM4) || defined(__DOXYGEN__) +#define STM32_PWM_USE_TIM4 TRUE #endif /** - * @brief PWM1 interrupt priority level setting. + * @brief PWM5 driver enable switch. + * @details If set to @p TRUE the support for PWM5 is included. + * @note The default is @p TRUE. */ -#if !defined(STM32_PWM1_IRQ_PRIORITY) || defined(__DOXYGEN__) -#define STM32_PWM1_IRQ_PRIORITY 7 +#if !defined(STM32_PWM_USE_TIM5) || defined(__DOXYGEN__) +#define STM32_PWM_USE_TIM5 TRUE #endif /** - * @brief PWM2 interrupt priority level setting. + * @brief PWM1 interrupt priority level setting. */ -#if !defined(STM32_PWM2_IRQ_PRIORITY) || defined(__DOXYGEN__) -#define STM32_PWM2_IRQ_PRIORITY 7 +#if !defined(STM32_PWM_TIM1_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define STM32_PWM_TIM1_IRQ_PRIORITY 7 #endif /** - * @brief PWM3 interrupt priority level setting. + * @brief PWM2 interrupt priority level setting. */ -#if !defined(STM32_PWM3_IRQ_PRIORITY) || defined(__DOXYGEN__) -#define STM32_PWM3_IRQ_PRIORITY 7 +#if !defined(STM32_PWM_TIM2_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define STM32_PWM_TIM2_IRQ_PRIORITY 7 #endif /** - * @brief PWM4 interrupt priority level setting. + * @brief PWM3 interrupt priority level setting. */ -#if !defined(STM32_PWM4_IRQ_PRIORITY) || defined(__DOXYGEN__) -#define STM32_PWM4_IRQ_PRIORITY 7 +#if !defined(STM32_PWM_TIM3_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define STM32_PWM_TIM3_IRQ_PRIORITY 7 +#endif + +/** + * @brief PWM4 interrupt priority level setting. + */ +#if !defined(STM32_PWM_TIM4_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define STM32_PWM_TIM4_IRQ_PRIORITY 7 +#endif + +/** + * @brief PWM5 interrupt priority level setting. + */ +#if !defined(STM32_PWM_TIM5_IRQ_PRIORITY) || defined(__DOXYGEN__) +#define STM32_PWM_TIM5_IRQ_PRIORITY 7 #endif /*===========================================================================*/ /* Configuration checks. */ /*===========================================================================*/ -#if USE_STM32_PWM4 && defined(STM32F10X_LD) -#error "TIM4 not present in low density STM32 devices" +#if STM32_PWM_USE_TIM1 && !STM32_HAS_TIM1 +#error "TIM1 not present in the selected device" +#endif + +#if STM32_PWM_USE_TIM2 && !STM32_HAS_TIM2 +#error "TIM2 not present in the selected device" +#endif + +#if STM32_PWM_USE_TIM3 && !STM32_HAS_TIM3 +#error "TIM3 not present in the selected device" +#endif + +#if STM32_PWM_USE_TIM4 && !STM32_HAS_TIM4 +#error "TIM4 not present in the selected device" +#endif + +#if STM32_PWM_USE_TIM5 && !STM32_HAS_TIM5 +#error "TIM5 not present in the selected device" +#endif + +#if !STM32_PWM_USE_TIM1 && !STM32_PWM_USE_TIM2 && \ + !STM32_PWM_USE_TIM3 && !STM32_PWM_USE_TIM4 && \ + !STM32_PWM_USE_TIM5 +#error "PWM driver activated but no TIM peripheral assigned" #endif /*===========================================================================*/ @@ -126,18 +158,29 @@ /*===========================================================================*/ /** - * @brief PWM channel type. + * @brief PWM channel type. */ typedef uint8_t pwmchannel_t; /** - * @brief PWM counter type. + * @brief PWM counter type. */ typedef uint16_t pwmcnt_t; /** - * @brief PWM driver channel configuration structure. - * @note It could be empty on some architectures. + * @brief Type of a structure representing an PWM driver. + */ +typedef struct PWMDriver PWMDriver; + +/** + * @brief PWM notification callback type. + * + * @param[in] pwmp pointer to a @p PWMDriver object + */ +typedef void (*pwmcallback_t)(PWMDriver *pwmp); + +/** + * @brief PWM driver channel configuration structure. */ typedef struct { /** @@ -146,22 +189,21 @@ typedef struct { pwmmode_t pcc_mode; /** * @brief Channel callback pointer. - * @details This callback is invoked on the channel compare event. If set to - * @p NULL then the callback is disabled. + * @note This callback is invoked on the channel compare event. If set to + * @p NULL then the callback is disabled. */ pwmcallback_t pcc_callback; /* End of the mandatory fields.*/ } PWMChannelConfig; /** - * @brief PWM driver configuration structure. - * @note It could be empty on some architectures. + * @brief PWM driver configuration structure. */ typedef struct { /** * @brief Periodic callback pointer. - * @details This callback is invoked on PWM counter reset. If set to - * @p NULL then the callback is disabled. + * @note This callback is invoked on PWM counter reset. If set to + * @p NULL then the callback is disabled. */ pwmcallback_t pc_callback; /** @@ -179,15 +221,15 @@ typedef struct { uint16_t pc_arr; /** * @brief TIM CR2 register initialization data. - * @note The value of this field should normally be equal to zero. + * @note The value of this field should normally be equal to zero. */ uint16_t pc_cr2; } PWMConfig; /** - * @brief Structure representing a PWM driver. + * @brief Structure representing a PWM driver. */ -typedef struct { +struct PWMDriver { /** * @brief Driver state. */ @@ -196,6 +238,9 @@ typedef struct { * @brief Current driver configuration data. */ const PWMConfig *pd_config; +#if defined(PWM_DRIVER_EXT_FIELDS) + PWM_DRIVER_EXT_FIELDS +#endif /* End of the mandatory fields.*/ /** * @brief Bit mask of the enabled channels. @@ -205,32 +250,119 @@ typedef struct { * @brief Pointer to the TIMx registers block. */ TIM_TypeDef *pd_tim; -} PWMDriver; +}; /*===========================================================================*/ /* Driver macros. */ /*===========================================================================*/ +/** + * @brief PWM clock prescaler initialization utility. + * @note The real clock value is rounded to the lower valid value, please + * make sure that the source clock frequency is a multiple of the + * requested PWM clock frequency. + * @note The calculated value must fit into an unsigned 16 bits integer. + * + * @param[in] clksrc clock source frequency, depending on the target timer + * cell it can be one of: + * - STM32_TIMCLK1 + * - STM32_TIMCLK2 + * . + * Please refer to the STM32 HAL driver documentation + * and/or the STM32 Reference Manual for the right clock + * source. + * @param[in] pwmclk PWM clock frequency in cycles + * @return The value to be stored in the @p pc_psc field of the + * @p PWMConfig structure. + */ +#define PWM_COMPUTE_PSC(clksrc, pwmclk) \ + ((uint16_t)(((clksrc) / (pwmclk)) - 1)) + +/** + * @brief PWM cycle period initialization utility. + * @note The calculated value must fit into an unsigned 16 bits integer. + * + * @param[in] pwmclk PWM clock frequency in cycles + * @param[in] pwmperiod PWM cycle period in nanoseconds + * @return The value to be stored in the @p pc_arr field of the + * @p PWMConfig structure. + */ +#define PWM_COMPUTE_ARR(pwmclk, pwmperiod) \ + ((uint16_t)(((pwmclk) / (1000000000 / (pwmperiod))) - 1)) + +/** + * @brief Converts from fraction to pulse width. + * @note Be careful with rounding errors, this is integer math not magic. + * You can specify tenths of thousandth but make sure you have the + * proper hardware resolution by carefully choosing the clock source + * and prescaler settings, see @p PWM_COMPUTE_PSC. + * + * @param[in] numerator numerator of the fraction + * @param[in] denominator percentage as an integer between 0 and numerator + * @return The pulse width to be passed to @p pwmEnableChannel(). + * + * @api + */ +#define PWM_FRACTION_TO_WIDTH(pwmp, numerator, denominator) \ + ((uint16_t)((((uint32_t)(pwmp)->pd_config->pc_arr + 1UL) * \ + (uint32_t)(denominator)) / (uint32_t)(numerator))) + +/** + * @brief Converts from degrees to pulse width. + * @note Be careful with rounding errors, this is integer math not magic. + * You can specify hundredths of degrees but make sure you have the + * proper hardware resolution by carefully choosing the clock source + * and prescaler settings, see @p PWM_COMPUTE_PSC. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] degrees degrees as an integer between 0 and 36000 + * @return The pulse width to be passed to @p pwmEnableChannel(). + * + * @api + */ +#define PWM_DEGREES_TO_WIDTH(pwmp, degrees) \ + PWM_FRACTION_TO_WIDTH(pwmp, 36000, degrees) + +/** + * @brief Converts from percentage to pulse width. + * @note Be careful with rounding errors, this is integer math not magic. + * You can specify tenths of thousandth but make sure you have the + * proper hardware resolution by carefully choosing the clock source + * and prescaler settings, see @p PWM_COMPUTE_PSC. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] percentage percentage as an integer between 0 and 10000 + * @return The pulse width to be passed to @p pwmEnableChannel(). + * + * @api + */ +#define PWM_PERCENTAGE_TO_WIDTH(pwmp, percentage) \ + PWM_FRACTION_TO_WIDTH(pwmp, 10000, percentage) + /*===========================================================================*/ /* External declarations. */ /*===========================================================================*/ -#if defined(USE_STM32_PWM1) && !defined(__DOXYGEN__) +#if defined(STM32_PWM_USE_TIM1) && !defined(__DOXYGEN__) extern PWMDriver PWMD1; #endif -#if defined(USE_STM32_PWM2) && !defined(__DOXYGEN__) +#if defined(STM32_PWM_USE_TIM2) && !defined(__DOXYGEN__) extern PWMDriver PWMD2; #endif -#if defined(USE_STM32_PWM3) && !defined(__DOXYGEN__) +#if defined(STM32_PWM_USE_TIM3) && !defined(__DOXYGEN__) extern PWMDriver PWMD3; #endif -#if defined(USE_STM32_PWM4) && !defined(__DOXYGEN__) +#if defined(STM32_PWM_USE_TIM4) && !defined(__DOXYGEN__) extern PWMDriver PWMD4; #endif +#if defined(STM32_PWM_USE_TIM5) && !defined(__DOXYGEN__) +extern PWMDriver PWMD5; +#endif + #ifdef __cplusplus extern "C" { #endif @@ -245,7 +377,7 @@ extern "C" { } #endif -#endif /* CH_HAL_USE_PWM */ +#endif /* HAL_USE_PWM */ #endif /* _PWM_LLD_H_ */ diff --git a/os/hal/src/pwm.c b/os/hal/src/pwm.c index 9211091fb..8973537d9 100644 --- a/os/hal/src/pwm.c +++ b/os/hal/src/pwm.c @@ -27,6 +27,7 @@ /** * @file pwm.c * @brief PWM Driver code. + * * @addtogroup PWM * @{ */ @@ -34,7 +35,7 @@ #include "ch.h" #include "hal.h" -#if CH_HAL_USE_PWM || defined(__DOXYGEN__) +#if HAL_USE_PWM || defined(__DOXYGEN__) /*===========================================================================*/ /* Driver exported variables. */ @@ -69,6 +70,9 @@ void pwmObjectInit(PWMDriver *pwmp) { pwmp->pd_state = PWM_STOP; pwmp->pd_config = NULL; +#if defined(PWM_DRIVER_EXT_INIT_HOOK) + PWM_DRIVER_EXT_INIT_HOOK(pwmp); +#endif } /** @@ -83,8 +87,7 @@ void pwmStart(PWMDriver *pwmp, const PWMConfig *config) { chSysLock(); chDbgAssert((pwmp->pd_state == PWM_STOP) || (pwmp->pd_state == PWM_READY), - "pwmStart(), #1", - "invalid state"); + "pwmStart(), #1", "invalid state"); pwmp->pd_config = config; pwm_lld_start(pwmp); pwmp->pd_state = PWM_READY; @@ -102,8 +105,7 @@ void pwmStop(PWMDriver *pwmp) { chSysLock(); chDbgAssert((pwmp->pd_state == PWM_STOP) || (pwmp->pd_state == PWM_READY), - "pwmStop(), #1", - "invalid state"); + "pwmStop(), #1", "invalid state"); pwm_lld_stop(pwmp); pwmp->pd_state = PWM_STOP; chSysUnlock(); @@ -111,9 +113,10 @@ void pwmStop(PWMDriver *pwmp) { /** * @brief Enables a PWM channel. + * @details Programs (or reprograms) a PWM channel. * * @param[in] pwmp pointer to a @p PWMDriver object - * @param[in] channel PWM channel identifier + * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) * @param[in] width PWM pulse width as clock pulses number */ void pwmEnableChannel(PWMDriver *pwmp, @@ -125,18 +128,18 @@ void pwmEnableChannel(PWMDriver *pwmp, chSysLock(); chDbgAssert(pwmp->pd_state == PWM_READY, - "pwmEnableChannel(), #1", "invalid state"); + "pwmEnableChannel(), #1", "not ready"); pwm_lld_enable_channel(pwmp, channel, width); chSysUnlock(); } /** - * @brief Disables a PWM channel. + * @brief Disables a PWM channel. * @details The channel is disabled and its output line returned to the * idle state. * * @param[in] pwmp pointer to a @p PWMDriver object - * @param[in] channel PWM channel identifier + * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) */ void pwmDisableChannel(PWMDriver *pwmp, pwmchannel_t channel) { @@ -145,7 +148,7 @@ void pwmDisableChannel(PWMDriver *pwmp, pwmchannel_t channel) { chSysLock(); chDbgAssert(pwmp->pd_state == PWM_READY, - "pwmDisableChannel(), #1", "invalid state"); + "pwmDisableChannel(), #1", "not ready"); pwm_lld_disable_channel(pwmp, channel); chSysUnlock(); } diff --git a/os/hal/templates/pwm_lld.c b/os/hal/templates/pwm_lld.c index 18f4110d0..a1c7b50c3 100644 --- a/os/hal/templates/pwm_lld.c +++ b/os/hal/templates/pwm_lld.c @@ -35,6 +35,8 @@ #include "ch.h" #include "hal.h" +#if CH_HAL_USE_PWM || defined(__DOXYGEN__) + /*===========================================================================*/ /* Driver exported variables. */ /*===========================================================================*/ @@ -98,25 +100,11 @@ bool_t pwm_lld_is_enabled(PWMDriver *pwmp, pwmchannel_t channel) { return FALSE; } -/** - * @brief Enables a callback mode for the specified PWM channel. - * @details The callback mode must be set before starting a PWM channel. - * - * @param[in] pwmp pointer to the @p PWMDriver object - * @param[in] channel PWM channel identifier - * @param[in] edge output edge mode - * @param[in] callback callback function - */ -void pwm_lld_set_callback(PWMDriver *pwmp, pwmchannel_t channel, - pwmedge_t edge, pwmcallback_t callback) { - -} - /** * @brief Enables a PWM channel. * - * @param[in] pwmp pointer to the @p PWMDriver object - * @param[in] channel PWM channel identifier + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) * @param[in] width PWM pulse width as clock pulses number */ void pwm_lld_enable_channel(PWMDriver *pwmp, @@ -130,11 +118,13 @@ void pwm_lld_enable_channel(PWMDriver *pwmp, * @details The channel is disabled and its output line returned to the * idle state. * - * @param[in] pwmp pointer to the @p PWMDriver object - * @param[in] channel PWM channel identifier + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) */ void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t channel) { } +#endif /* CH_HAL_USE_PWM */ + /** @} */ diff --git a/os/hal/templates/pwm_lld.h b/os/hal/templates/pwm_lld.h index 8db3681fb..8605c3d8c 100644 --- a/os/hal/templates/pwm_lld.h +++ b/os/hal/templates/pwm_lld.h @@ -35,6 +35,8 @@ #ifndef _PWM_LLD_H_ #define _PWM_LLD_H_ +#if CH_HAL_USE_PWM || defined(__DOXYGEN__) + /*===========================================================================*/ /* Driver constants. */ /*===========================================================================*/ @@ -69,17 +71,61 @@ typedef uint8_t pwmchannel_t; typedef uint16_t pwmcnt_t; /** - * @brief Driver configuration structure. - * @note It could be empty on some architectures. + * @brief Type of a structure representing an PWM driver. + */ +typedef struct PWMDriver PWMDriver; + +/** + * @brief PWM notification callback type. + * + * @param[in] pwmp pointer to a @p PWMDriver object + */ +typedef void (*pwmcallback_t)(PWMDriver *pwmp); + +/** + * @brief PWM driver channel configuration structure. + * @note Some architectures may not be able to support the channel mode + * or the callback, in this case the fields are ignored. */ typedef struct { + /** + * @brief Channel active logic level. + */ + pwmmode_t pcc_mode; + /** + * @brief Channel callback pointer. + * @note This callback is invoked on the channel compare event. If set to + * @p NULL then the callback is disabled. + */ + pwmcallback_t pcc_callback; + /* End of the mandatory fields.*/ +} PWMChannelConfig; +/** + * @brief Driver configuration structure. + * @note Implementations may extend this structure to contain more, + * architecture dependent, fields. + */ +typedef struct { + /** + * @brief Periodic callback pointer. + * @note This callback is invoked on PWM counter reset. If set to + * @p NULL then the callback is disabled. + */ + pwmcallback_t pc_callback; + /** + * @brief Channels configurations. + */ + PWMChannelConfig pc_channels[PWM_CHANNELS]; + /* End of the mandatory fields.*/ } PWMConfig; /** * @brief Structure representing an PWM driver. + * @note Implementations may extend this structure to contain more, + * architecture dependent, fields. */ -typedef struct { +struct PWMDriver { /** * @brief Driver state. */ @@ -88,13 +134,63 @@ typedef struct { * @brief Current configuration data. */ const PWMConfig *pd_config; +#if defined(PWM_DRIVER_EXT_FIELDS) + PWM_DRIVER_EXT_FIELDS +#endif /* End of the mandatory fields.*/ -} PWMDriver; +}; /*===========================================================================*/ /* Driver macros. */ /*===========================================================================*/ +/** + * @brief Converts from fraction to pulse width. + * @note Be careful with rounding errors, this is integer math not magic. + * You can specify tenths of thousandth but make sure you have the + * proper hardware resolution by carefully choosing the clock source + * and prescaler settings, see @p PWM_COMPUTE_PSC. + * + * @param[in] numerator numerator of the fraction + * @param[in] denominator percentage as an integer between 0 and numerator + * @return The pulse width to be passed to @p pwmEnableChannel(). + * + * @api + */ +#define PWM_FRACTION_TO_WIDTH(pwmp, numerator, denominator) 0 + +/** + * @brief Converts from degrees to pulse width. + * @note Be careful with rounding errors, this is integer math not magic. + * You can specify hundredths of degrees but make sure you have the + * proper hardware resolution by carefully choosing the clock source + * and prescaler settings, see @p PWM_COMPUTE_PSC. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] degrees degrees as an integer between 0 and 36000 + * @return The pulse width to be passed to @p pwmEnableChannel(). + * + * @api + */ +#define PWM_DEGREES_TO_WIDTH(pwmp, degrees) \ + PWM_FRACTION_TO_WIDTH(pwmp, 36000, degrees) + +/** + * @brief Converts from percentage to pulse width. + * @note Be careful with rounding errors, this is integer math not magic. + * You can specify tenths of thousandth but make sure you have the + * proper hardware resolution by carefully choosing the clock source + * and prescaler settings, see @p PWM_COMPUTE_PSC. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * @param[in] percentage percentage as an integer between 0 and 10000 + * @return The pulse width to be passed to @p pwmEnableChannel(). + * + * @api + */ +#define PWM_PERCENTAGE_TO_WIDTH(pwmp, percentage) \ + PWM_FRACTION_TO_WIDTH(pwmp, 10000, percentage) + /*===========================================================================*/ /* External declarations. */ /*===========================================================================*/ @@ -106,8 +202,6 @@ extern "C" { void pwm_lld_start(PWMDriver *pwmp); void pwm_lld_stop(PWMDriver *pwmp); bool_t pwm_lld_is_enabled(PWMDriver *pwmp, pwmchannel_t channel); - void pwm_lld_set_callback(PWMDriver *pwmp, pwmchannel_t channel, - pwmedge_t edge, pwmcallback_t callback); void pwm_lld_enable_channel(PWMDriver *pwmp, pwmchannel_t channel, pwmcnt_t width); @@ -116,6 +210,8 @@ extern "C" { } #endif +#endif /* CH_HAL_USE_PWM */ + #endif /* _PWM_LLD_H_ */ /** @} */ diff --git a/readme.txt b/readme.txt index cd87e80c5..c5dbd8eca 100644 --- a/readme.txt +++ b/readme.txt @@ -61,6 +61,12 @@ *** 2.0.8 *** - FIX: Fixed failed memory recovery by registry scan, improved the related test case (bug 3116888). +- FIX: Fixed PWM channels going to ACTIVE state when the pulse width is + set to zero in the STM32 PWM driver (bug 3114481). +- FIX: Fixed PWM channels return to IDLE state in STM32 PWM driver (bug + 3114467). +- CHANGE: Bugs 3114467 and 3114481 have been fixed by backporting the 2.1.x + PWM driver, there is a difference in the PWM callback parameters. *** 2.0.7 *** - FIX: Fixed typo in board name (bug 3113574).