From a58a524d4c7e58d03a0fa3698f09fb17985a70bc Mon Sep 17 00:00:00 2001 From: gdisirio Date: Thu, 31 Mar 2011 10:21:52 +0000 Subject: [PATCH] Improvements to the PWM driver. git-svn-id: svn://svn.code.sf.net/p/chibios/svn/trunk@2853 35acf78f-673a-0410-8e92-d51de3d6d3f4 --- os/hal/include/pwm.h | 109 +++++++++++++++++++++++---- os/hal/platforms/STM32/gpt_lld.c | 1 + os/hal/platforms/STM32/pwm_lld.c | 76 ++++++++++++++----- os/hal/platforms/STM32/pwm_lld.h | 124 ++++++------------------------- os/hal/src/pwm.c | 43 ++++++++++- os/hal/templates/pwm_lld.c | 35 ++++++--- os/hal/templates/pwm_lld.h | 81 ++++++-------------- readme.txt | 9 ++- testhal/STM32/PWM/main.c | 40 +++++----- 9 files changed, 290 insertions(+), 228 deletions(-) diff --git a/os/hal/include/pwm.h b/os/hal/include/pwm.h index e63478ca3..7181054e4 100644 --- a/os/hal/include/pwm.h +++ b/os/hal/include/pwm.h @@ -57,13 +57,16 @@ typedef enum { } pwmstate_t; /** - * @brief PWM logic mode. + * @brief Type of a structure representing a PWM driver. */ -typedef enum { - 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; +typedef struct PWMDriver PWMDriver; + +/** + * @brief PWM notification callback type. + * + * @param[in] pwmp pointer to a @p PWMDriver object + */ +typedef void (*pwmcallback_t)(PWMDriver *pwmp); #include "pwm_lld.h" @@ -72,12 +75,86 @@ typedef enum { /*===========================================================================*/ /** - * @brief Enables a PWM channel. - * @details Programs (or reprograms) a PWM channel. - * @note This function has to be invoked from a lock zone. + * @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] pwmp pointer to a @p PWMDriver object - * @param[in] channel PWM channel identifier + * @param[in] denominator denominator of the fraction + * @param[in] numerator numerator of the fraction + * @return The pulse width to be passed to @p pwmEnableChannel(). + * + * @api + */ +#define PWM_FRACTION_TO_WIDTH(pwmp, denominator, numerator) \ + ((uint16_t)((((uint32_t)(pwmp)->period) * \ + (uint32_t)(numerator)) / (uint32_t)(denominator))) + +/** + * @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) + +/** + * @brief Changes the period the PWM peripheral. + * @details This function changes the period of a PWM unit that has already + * been activated using @p pwmStart(). + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The PWM unit period is changed to the new value. + * @post Any active channel is disabled by this function and must be + * activated explicitly using @p pwmEnableChannel(). + * @note Depending on the hardware implementation this function has + * effect starting on the next cycle (recommended implementation) + * or immediately (fallback implementation). + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @iclass + */ +#define pwmChangePeriodI(pwmp, period) { \ + (pwmp)->period = (period); \ + pwm_lld_change_period(pwmp, period); \ +} + +/** + * @brief Enables a PWM channel. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is active using the specified configuration. + * @note Depending on the hardware implementation this function has + * effect starting on the next cycle (recommended implementation) + * or immediately (fallback implementation). + * + * @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 * * @iclass @@ -86,13 +163,16 @@ typedef enum { pwm_lld_enable_channel(pwmp, channel, width) /** - * @brief Disables a PWM channel. - * @details The channel is disabled and its output line returned to the + * @brief Disables a PWM channel. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is disabled and its output line returned to the * idle state. - * @note This function has to be invoked from a lock zone. + * @note Depending on the hardware implementation this function has + * effect starting on the next cycle (recommended implementation) + * or immediately (fallback implementation). * * @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) * * @iclass */ @@ -110,6 +190,7 @@ extern "C" { void pwmObjectInit(PWMDriver *pwmp); void pwmStart(PWMDriver *pwmp, const PWMConfig *config); void pwmStop(PWMDriver *pwmp); + void pwmChangePeriod(PWMDriver *pwmp, pwmcnt_t period); void pwmEnableChannel(PWMDriver *pwmp, pwmchannel_t channel, pwmcnt_t width); diff --git a/os/hal/platforms/STM32/gpt_lld.c b/os/hal/platforms/STM32/gpt_lld.c index ffbfe475c..8419cad68 100644 --- a/os/hal/platforms/STM32/gpt_lld.c +++ b/os/hal/platforms/STM32/gpt_lld.c @@ -325,6 +325,7 @@ void gpt_lld_stop(GPTDriver *gptp) { if (gptp->state == GPT_READY) { gptp->tim->CR1 = 0; /* Timer disabled. */ gptp->tim->DIER = 0; /* All IRQs disabled. */ + gptp->tim->SR = 0; /* Clear eventual pending IRQs. */ #if STM32_GPT_USE_TIM1 if (&GPTD1 == gptp) { diff --git a/os/hal/platforms/STM32/pwm_lld.c b/os/hal/platforms/STM32/pwm_lld.c index eaf83bd90..6b05b66aa 100644 --- a/os/hal/platforms/STM32/pwm_lld.c +++ b/os/hal/platforms/STM32/pwm_lld.c @@ -289,12 +289,15 @@ void pwm_lld_init(void) { /** * @brief Configures and activates the PWM peripheral. + * @note Starting a driver that is already in the @p PWM_READY state + * disables all the active channels. * * @param[in] pwmp pointer to a @p PWMDriver object * * @notapi */ void pwm_lld_start(PWMDriver *pwmp) { + uint32_t clock, psc; uint16_t ccer; /* Reset channels.*/ @@ -311,6 +314,7 @@ void pwm_lld_start(PWMDriver *pwmp) { CORTEX_PRIORITY_MASK(STM32_PWM_TIM1_IRQ_PRIORITY)); NVICEnableVector(TIM1_CC_IRQn, CORTEX_PRIORITY_MASK(STM32_PWM_TIM1_IRQ_PRIORITY)); + clock = STM32_TIMCLK2; } #endif #if STM32_PWM_USE_TIM2 @@ -320,6 +324,7 @@ void pwm_lld_start(PWMDriver *pwmp) { RCC->APB1RSTR = 0; NVICEnableVector(TIM2_IRQn, CORTEX_PRIORITY_MASK(STM32_PWM_TIM2_IRQ_PRIORITY)); + clock = STM32_TIMCLK1; } #endif #if STM32_PWM_USE_TIM3 @@ -329,6 +334,7 @@ void pwm_lld_start(PWMDriver *pwmp) { RCC->APB1RSTR = 0; NVICEnableVector(TIM3_IRQn, CORTEX_PRIORITY_MASK(STM32_PWM_TIM3_IRQ_PRIORITY)); + clock = STM32_TIMCLK1; } #endif #if STM32_PWM_USE_TIM4 @@ -338,6 +344,7 @@ void pwm_lld_start(PWMDriver *pwmp) { RCC->APB1RSTR = 0; NVICEnableVector(TIM4_IRQn, CORTEX_PRIORITY_MASK(STM32_PWM_TIM4_IRQ_PRIORITY)); + clock = STM32_TIMCLK1; } #endif @@ -348,6 +355,7 @@ void pwm_lld_start(PWMDriver *pwmp) { RCC->APB1RSTR = 0; NVICEnableVector(TIM5_IRQn, CORTEX_PRIORITY_MASK(STM32_PWM_TIM5_IRQ_PRIORITY)); + clock = STM32_TIMCLK1; } #endif @@ -364,21 +372,26 @@ void pwm_lld_start(PWMDriver *pwmp) { } else { /* Driver re-configuration scenario, it must be stopped first.*/ - /* Really required ?????????? */ - pwmp->tim->CR1 = 0; /* Timer stopped. */ - pwmp->tim->CR2 = 0; /* Timer stopped. */ - pwmp->tim->SMCR = 0; /* Slave mode disabled. */ + pwmp->enabled_channels = 0; /* All channels disabled. */ + pwmp->tim->CR1 = 0; /* Timer disabled. */ + pwmp->tim->DIER = 0; /* All IRQs disabled. */ + pwmp->tim->SR = 0; /* Clear eventual pending IRQs. */ pwmp->tim->CCR1 = 0; /* Comparator 1 disabled. */ pwmp->tim->CCR2 = 0; /* Comparator 2 disabled. */ pwmp->tim->CCR3 = 0; /* Comparator 3 disabled. */ pwmp->tim->CCR4 = 0; /* Comparator 4 disabled. */ - pwmp->tim->CNT = 0; + pwmp->tim->CNT = 0; /* Counter reset to zero. */ } /* Timer configuration.*/ + psc = (clock / pwmp->config->frequency) - 1; + chDbgAssert((psc <= 0xFFFF) && + ((psc + 1) * pwmp->config->frequency) == clock, + "pwm_lld_start(), #1", "invalid frequency"); + pwmp->tim->PSC = (uint16_t)psc; + pwmp->tim->ARR = (uint16_t)(pwmp->period - 1); pwmp->tim->CR2 = pwmp->config->cr2; - pwmp->tim->PSC = pwmp->config->psc; - pwmp->tim->ARR = pwmp->config->arr; + /* Output enables and polarities setup.*/ ccer = 0; switch (pwmp->config->channels[0].mode) { @@ -434,17 +447,9 @@ void pwm_lld_stop(PWMDriver *pwmp) { /* If in ready state then disables the PWM clock.*/ if (pwmp->state == PWM_READY) { pwmp->enabled_channels = 0; /* All channels disabled. */ - pwmp->tim->CR1 = 0; - pwmp->tim->CR2 = 0; - pwmp->tim->CCER = 0; /* Outputs disabled. */ - pwmp->tim->CCR1 = 0; /* Comparator 1 disabled. */ - pwmp->tim->CCR2 = 0; /* Comparator 2 disabled. */ - pwmp->tim->CCR3 = 0; /* Comparator 3 disabled. */ - pwmp->tim->CCR4 = 0; /* Comparator 4 disabled. */ - pwmp->tim->BDTR = 0; - pwmp->tim->DIER = 0; - pwmp->tim->SR = 0; - pwmp->tim->EGR = TIM_EGR_UG; /* Update event. */ + pwmp->tim->CR1 = 0; /* Timer disabled. */ + pwmp->tim->DIER = 0; /* All IRQs disabled. */ + pwmp->tim->SR = 0; /* Clear eventual pending IRQs. */ #if STM32_PWM_USE_TIM1 if (&PWMD1 == pwmp) { @@ -480,8 +485,39 @@ void pwm_lld_stop(PWMDriver *pwmp) { } } +/** + * @brief Changes the period the PWM peripheral. + * @details This function changes the period of a PWM unit that has already + * been activated using @p pwmStart(). + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The PWM unit period is changed to the new value. + * @post Any active channel is disabled by this function and must be + * activated explicitly using @p pwmEnableChannel(). + * @note The function has effect at the next cycle start. + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @api + */ +void pwm_lld_change_period(PWMDriver *pwmp, pwmcnt_t period) { + + pwmp->enabled_channels = 0; /* All channels disabled. */ + pwmp->tim->DIER &= ~(TIM_DIER_CC1IE | + TIM_DIER_CC2IE | + TIM_DIER_CC3IE | + TIM_DIER_CC4IE); /* Channels sources disabled. */ + pwmp->tim->SR = ~(TIM_SR_CC1IF | + TIM_SR_CC1IF | + TIM_SR_CC1IF | + TIM_SR_CC1IF); /* Clears eventual pending IRQs. */ + pwmp->tim->ARR = (uint16_t)(period - 1); +} + /** * @brief Enables a PWM channel. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is active using the specified configuration. + * @note The function has effect at the next cycle start. * * @param[in] pwmp pointer to a @p PWMDriver object * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) @@ -508,8 +544,10 @@ void pwm_lld_enable_channel(PWMDriver *pwmp, /** * @brief Disables a PWM channel. - * @details The channel is disabled and its output line returned to the + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is disabled and its output line returned to the * idle state. + * @note The function has effect at the next cycle start. * * @param[in] pwmp pointer to a @p PWMDriver object * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) diff --git a/os/hal/platforms/STM32/pwm_lld.h b/os/hal/platforms/STM32/pwm_lld.h index 8c15a3c6f..d8e1754c9 100644 --- a/os/hal/platforms/STM32/pwm_lld.h +++ b/os/hal/platforms/STM32/pwm_lld.h @@ -169,16 +169,13 @@ typedef uint8_t pwmchannel_t; typedef uint16_t pwmcnt_t; /** - * @brief Type of a structure representing an PWM driver. + * @brief PWM logic mode. */ -typedef struct PWMDriver PWMDriver; - -/** - * @brief PWM notification callback type. - * - * @param[in] pwmp pointer to a @p PWMDriver object - */ -typedef void (*pwmcallback_t)(PWMDriver *pwmp); +typedef enum { + 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 driver channel configuration structure. @@ -201,6 +198,18 @@ typedef struct { * @brief PWM driver configuration structure. */ typedef struct { + /** + * @brief Timer clock in Hz. + * @note The low level can use assertions in order to catch invalid + * frequency specifications. + */ + uint32_t frequency; + /** + * @brief PWM period in ticks. + * @note The low level can use assertions in order to catch invalid + * period specifications. + */ + pwmcnt_t period; /** * @brief Periodic callback pointer. * @note This callback is invoked on PWM counter reset. If set to @@ -212,14 +221,6 @@ typedef struct { */ PWMChannelConfig channels[PWM_CHANNELS]; /* End of the mandatory fields.*/ - /** - * @brief TIM PSC (pre-scaler) register initialization data. - */ - uint16_t psc; - /** - * @brief TIM ARR (auto-reload) register initialization data. - */ - uint16_t arr; /** * @brief TIM CR2 register initialization data. * @note The value of this field should normally be equal to zero. @@ -239,6 +240,10 @@ struct PWMDriver { * @brief Current driver configuration data. */ const PWMConfig *config; + /** + * @brief Current PWM period in ticks. + */ + pwmcnt_t period; #if defined(PWM_DRIVER_EXT_FIELDS) PWM_DRIVER_EXT_FIELDS #endif @@ -257,90 +262,6 @@ struct 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 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 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] pwmp pointer to a @p PWMDriver object - * @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)->config->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. */ /*===========================================================================*/ @@ -371,6 +292,7 @@ extern "C" { void pwm_lld_init(void); void pwm_lld_start(PWMDriver *pwmp); void pwm_lld_stop(PWMDriver *pwmp); + void pwm_lld_change_period(PWMDriver *pwmp, pwmcnt_t period); void pwm_lld_enable_channel(PWMDriver *pwmp, pwmchannel_t channel, pwmcnt_t width); diff --git a/os/hal/src/pwm.c b/os/hal/src/pwm.c index e510ba180..eaa49bc56 100644 --- a/os/hal/src/pwm.c +++ b/os/hal/src/pwm.c @@ -77,6 +77,8 @@ void pwmObjectInit(PWMDriver *pwmp) { /** * @brief Configures and activates the PWM peripheral. + * @note Starting a driver that is already in the @p PWM_READY state + * disables all the active channels. * * @param[in] pwmp pointer to a @p PWMDriver object * @param[in] config pointer to a @p PWMConfig object @@ -91,6 +93,7 @@ void pwmStart(PWMDriver *pwmp, const PWMConfig *config) { chDbgAssert((pwmp->state == PWM_STOP) || (pwmp->state == PWM_READY), "pwmStart(), #1", "invalid state"); pwmp->config = config; + pwmp->period = config->period; pwm_lld_start(pwmp); pwmp->state = PWM_READY; chSysUnlock(); @@ -115,9 +118,41 @@ void pwmStop(PWMDriver *pwmp) { chSysUnlock(); } +/** + * @brief Changes the period the PWM peripheral. + * @details This function changes the period of a PWM unit that has already + * been activated using @p pwmStart(). + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The PWM unit period is changed to the new value. + * @post Any active channel is disabled by this function and must be + * activated explicitly using @p pwmEnableChannel(). + * @note Depending on the hardware implementation this function has + * effect starting on the next cycle (recommended implementation) + * or immediately (fallback implementation). + * + * @param[in] pwmp pointer to a @p PWMDriver object + * + * @api + */ +void pwmChangePeriod(PWMDriver *pwmp, pwmcnt_t period) { + + chDbgCheck(pwmp != NULL, "pwmChangePeriod"); + + chSysLock(); + chDbgAssert(pwmp->state == PWM_READY, + "pwmChangePeriod(), #1", "invalid state"); + pwmp->period = period; + pwm_lld_change_period(pwmp, period); + chSysUnlock(); +} + /** * @brief Enables a PWM channel. - * @details Programs (or reprograms) a PWM channel. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is active using the specified configuration. + * @note Depending on the hardware implementation this function has + * effect starting on the next cycle (recommended implementation) + * or immediately (fallback implementation). * * @param[in] pwmp pointer to a @p PWMDriver object * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) @@ -141,8 +176,12 @@ void pwmEnableChannel(PWMDriver *pwmp, /** * @brief Disables a PWM channel. - * @details The channel is disabled and its output line returned to the + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is disabled and its output line returned to the * idle state. + * @note Depending on the hardware implementation this function has + * effect starting on the next cycle (recommended implementation) + * or immediately (fallback implementation). * * @param[in] pwmp pointer to a @p PWMDriver object * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) diff --git a/os/hal/templates/pwm_lld.c b/os/hal/templates/pwm_lld.c index 507ba1294..295b0dcb4 100644 --- a/os/hal/templates/pwm_lld.c +++ b/os/hal/templates/pwm_lld.c @@ -87,23 +87,32 @@ void pwm_lld_stop(PWMDriver *pwmp) { } /** - * @brief Determines whatever the PWM channel is already enabled. + * @brief Changes the period the PWM peripheral. + * @details This function changes the period of a PWM unit that has already + * been activated using @p pwmStart(). + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The PWM unit period is changed to the new value. + * @post Any active channel is disabled by this function and must be + * activated explicitly using @p pwmEnableChannel(). + * @note Depending on the hardware implementation this function has + * effect starting on the next cycle (recommended implementation) + * or immediately (fallback implementation). * - * @param[in] pwmp pointer to the @p PWMDriver object - * @param[in] channel PWM channel identifier - * @return The PWM channel status. - * @retval FALSE the channel is not enabled. - * @retval TRUE the channel is enabled. + * @param[in] pwmp pointer to a @p PWMDriver object * - * @notapi + * @api */ -bool_t pwm_lld_is_enabled(PWMDriver *pwmp, pwmchannel_t channel) { +void pwm_lld_change_period(PWMDriver *pwmp, pwmcnt_t period) { - return FALSE; } /** * @brief Enables a PWM channel. + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is active using the specified configuration. + * @note Depending on the hardware implementation this function has + * effect starting on the next cycle (recommended implementation) + * or immediately (fallback implementation). * * @param[in] pwmp pointer to a @p PWMDriver object * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) @@ -119,11 +128,17 @@ void pwm_lld_enable_channel(PWMDriver *pwmp, /** * @brief Disables a PWM channel. - * @details The channel is disabled and its output line returned to the + * @pre The PWM unit must have been activated using @p pwmStart(). + * @post The channel is disabled and its output line returned to the * idle state. + * @note Depending on the hardware implementation this function has + * effect starting on the next cycle (recommended implementation) + * or immediately (fallback implementation). * * @param[in] pwmp pointer to a @p PWMDriver object * @param[in] channel PWM channel identifier (0...PWM_CHANNELS-1) + * + * @notapi */ void pwm_lld_disable_channel(PWMDriver *pwmp, pwmchannel_t channel) { diff --git a/os/hal/templates/pwm_lld.h b/os/hal/templates/pwm_lld.h index bee00c073..9ef7a9853 100644 --- a/os/hal/templates/pwm_lld.h +++ b/os/hal/templates/pwm_lld.h @@ -65,16 +65,13 @@ typedef uint8_t pwmchannel_t; typedef uint16_t pwmcnt_t; /** - * @brief Type of a structure representing an PWM driver. + * @brief PWM logic mode. */ -typedef struct PWMDriver PWMDriver; - -/** - * @brief PWM notification callback type. - * - * @param[in] pwmp pointer to a @p PWMDriver object - */ -typedef void (*pwmcallback_t)(PWMDriver *pwmp); +typedef enum { + 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 driver channel configuration structure. @@ -101,6 +98,18 @@ typedef struct { * architecture dependent, fields. */ typedef struct { + /** + * @brief Timer clock in Hz. + * @note The low level can use assertions in order to catch invalid + * frequency specifications. + */ + uint32_t frequency; + /** + * @brief PWM period in ticks. + * @note The low level can use assertions in order to catch invalid + * period specifications. + */ + pwmcnt_t period; /** * @brief Periodic callback pointer. * @note This callback is invoked on PWM counter reset. If set to @@ -128,6 +137,10 @@ struct PWMDriver { * @brief Current configuration data. */ const PWMConfig *config; + /** + * @brief Current PWM period in ticks. + */ + pwmcnt_t period; #if defined(PWM_DRIVER_EXT_FIELDS) PWM_DRIVER_EXT_FIELDS #endif @@ -138,54 +151,6 @@ struct 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] pwmp pointer to a @p PWMDriver object - * @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. */ /*===========================================================================*/ @@ -196,7 +161,7 @@ extern "C" { void pwm_lld_init(void); 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_change_period(PWMDriver *pwmp, pwmcnt_t period); void pwm_lld_enable_channel(PWMDriver *pwmp, pwmchannel_t channel, pwmcnt_t width); diff --git a/readme.txt b/readme.txt index ddc7c35f1..b0c085811 100644 --- a/readme.txt +++ b/readme.txt @@ -79,7 +79,14 @@ - FIX: Fixed wrong checks in I/O Queues (bug 3219197)(backported to 2.2.3). - FIX: Fixed invalid assertion in adcConvert() (bug 3205410)(backported to 2.2.3). -- NEW: Added new ICU driver model, Input Capture Unit.. +- NEW: Improvements to the PWM driver model: + - Easier configuration similar to the GPT driver initializations, macros + are no more required. + - Added a new function that allows to change the PWM period on the fly, + even from within callbacks. Formerly it was required to stop and restart + the driver. + - Improved driver documentation. +- NEW: Added new ICU driver model, Input Capture Unit. - NEW: ICU driver implementation for STM32. - NEW: Implemented stack checking in the Cortex-Mx RVCT port (backported to 2.2.3). diff --git a/testhal/STM32/PWM/main.c b/testhal/STM32/PWM/main.c index c42b15939..ef08b792c 100644 --- a/testhal/STM32/PWM/main.c +++ b/testhal/STM32/PWM/main.c @@ -21,33 +21,21 @@ #include "ch.h" #include "hal.h" -/* - * Red LEDs blinker thread, times are in milliseconds. - */ -static WORKING_AREA(waThread1, 128); -static msg_t Thread1(void *arg) { - - (void)arg; - while (TRUE) { - palClearPad(IOPORT3, GPIOC_LED); - chThdSleepMilliseconds(500); - palSetPad(IOPORT3, GPIOC_LED); - chThdSleepMilliseconds(500); - } - return 0; -} - static void pwmpcb(PWMDriver *pwmp) { (void)pwmp; + palSetPad(IOPORT3, GPIOC_LED); } static void pwmc1cb(PWMDriver *pwmp) { (void)pwmp; + palClearPad(IOPORT3, GPIOC_LED); } static PWMConfig pwmcfg = { + 10000, /* 10KHz PWM clock frequency. */ + 10000, /* Initial PWM period 1S. */ pwmpcb, { {PWM_OUTPUT_ACTIVE_HIGH, pwmc1cb}, @@ -55,8 +43,6 @@ static PWMConfig pwmcfg = { {PWM_OUTPUT_DISABLED, NULL}, {PWM_OUTPUT_DISABLED, NULL} }, - PWM_COMPUTE_PSC(STM32_TIMCLK1, 10000), /* 10KHz PWM clock frequency. */ - PWM_COMPUTE_ARR(10000, 1000000), /* PWM period 1S. */ 0 }; @@ -76,9 +62,9 @@ int main(void) { chSysInit(); /* - * Creates the blinker thread. + * LED initially off. */ - chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO, Thread1, NULL); + palSetPad(IOPORT3, GPIOC_LED); /* * Initializes the PWM driver 1. @@ -88,9 +74,9 @@ int main(void) { chThdSleepMilliseconds(2000); /* - * Starts the channel 0 using 50% duty cycle. + * Starts the channel 0 using 25% duty cycle. */ - pwmEnableChannel(&PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, 5000)); + pwmEnableChannel(&PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, 2500)); chThdSleepMilliseconds(5000); /* @@ -99,11 +85,19 @@ int main(void) { pwmEnableChannel(&PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, 7500)); chThdSleepMilliseconds(5000); + /* + * Changes PWM period to half second and duty cycle to 50%. + */ + pwmChangePeriod(&PWMD1, 5000); + pwmEnableChannel(&PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, 5000)); + chThdSleepMilliseconds(5000); + /* * Disables channel 0. */ pwmDisableChannel(&PWMD1, 0); - + pwmStop(&PWMD1); + palSetPad(IOPORT3, GPIOC_LED); /* * Normal main() thread activity, in this demo it does nothing.