diff options
-rw-r--r-- | os/hal/include/pwm.h | 107 | ||||
-rw-r--r-- | os/hal/platforms/STM32/gpt_lld.c | 1 | ||||
-rw-r--r-- | os/hal/platforms/STM32/pwm_lld.c | 76 | ||||
-rw-r--r-- | os/hal/platforms/STM32/pwm_lld.h | 124 | ||||
-rw-r--r-- | os/hal/src/pwm.c | 43 | ||||
-rw-r--r-- | os/hal/templates/pwm_lld.c | 35 | ||||
-rw-r--r-- | os/hal/templates/pwm_lld.h | 81 | ||||
-rw-r--r-- | readme.txt | 9 | ||||
-rw-r--r-- | testhal/STM32/PWM/main.c | 40 |
9 files changed, 289 insertions, 227 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 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] 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.
- * @details Programs (or reprograms) a PWM channel.
- * @note This function has to be invoked from a lock zone.
+ * @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
+ * @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) {
@@ -481,7 +486,38 @@ 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.
@@ -202,6 +199,18 @@ typedef struct { */
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
* @p NULL then the callback is disabled.
@@ -213,14 +222,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();
@@ -116,8 +119,40 @@ void pwmStop(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 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.
@@ -102,6 +99,18 @@ typedef struct { */
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
* @p NULL then the callback is disabled.
@@ -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);
/*
@@ -100,10 +86,18 @@ int main(void) { 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.
|