Jump to content
Sign in to follow this  
pabigot

Over-engineered PWM solution

Recommended Posts

I'm a little reluctant to post this since I'm not sure how useful it will be, but I'm done working on it for now so what the heck.

 

Inspired by opossum's PWM code, I decided to design a similar system based on my own needs, which would involve indicating subsystem states by the duration of LED flashes.

 

I was also motivated to try this because I report most of the bugs against mspgcc, and projects like this tend to find them. This one revealed a bug I'd fixed in November wasn't quite as dead as I'd thought.

 

Below is the header from the module. The complete source is in the test430 git repository under the demos/launchpad/pwm directory. It includes a unit test to verify manipulation of the data structures, and a demonstration application using an MSP430G2553-loaded launchpad that configures several PWMs (including the LEDs) and allows the user to validate them by cross-connecting the outputs to TA1.0 configured in capture mode. The demonstration allows the overall period and the source clock to be configured at build-time.

 

I do not intend to make any real comparison of this against other PWM implementations; it was simply an exercise. However, as people are likely to be interested:

 

* This probably takes more code space than opossum's version, because I wanted extra features including the ability to use a period other than 65536, and a callback at the end of the cycle (for potential synchronous reconfiguration of the widths). This one takes 882 bytes for the ISR and the support routines.

 

* Although the technique could reduce RAM size too as no pointers are needed, I don't know if this demonstration does, since I'm using a full 16 bits for the widths. RAM size could be reduced by changing pwm_period_t to an 8-bit value and adding a scaling shift.

 

* The code not only requires the development version of mspgcc to run, but the unit test evokes a bug for which the fix has not yet been pushed. (The test runs correctly, but after exiting improperly restarts itself at which point it appears to fail.)

 

* Take this as an example: there are too many design decisions in something like this for it to be generally usable.

 

Students of software development might find it interesting to read through the git logs to see how the project evolved.

 

An example of output from the demonstration program:

Connect to P2.0 to measure pulses based on ACLK:
       P1.0 PWM 2500 of 10000 cycles
       P1.3 PWM 100 of 10000 cycles
       P1.4 PWM 200 of 10000 cycles
       P1.5 PWM 1000 of 10000 cycles
       P1.6 PWM 7500 of 10000 cycles
PWM at 3 of 9, cb 0xc3fe
       0: mask=0079 dwell=100 ; total 100
       1: mask=0008 dwell=100 ; total 200
       2: mask=0010 dwell=800 ; total 1000
       3: mask=0020 dwell=1500 ; total 2500
       4: mask=0001 dwell=5000 ; total 7500
       5: mask=0040 dwell=2500 ; total 10000
       6: mask=0000 dwell=0 ; total 10000
       7: mask=0000 dwell=0 ; total 10000
       8: mask=0000 dwell=0 ; total 10000
Callback 4424 with pwm 0x218, last cycle duration 10000
3 captures: CCTL 0000 CCR 2382 TA1R 2ac9
       high for 201 cycles
       low for 9799 cycles

 

The header, including the vision document/rationale:

#ifndef _PWM_H_
#define _PWM_H_

/*
Copyright (c) 2012, Peter A. Bigot 

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice,
 this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
 this list of conditions and the following disclaimer in the documentation
 and/or other materials provided with the distribution.

* Neither the name of the software nor the names of its contributors may be
 used to endorse or promote products derived from this software without
 specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
 */

/** Inspired by http://www.43oh.com/forum/viewtopic.php?f=10&t=2260
*
* This module provides pulse-width-modulation capability using a
* single capture-compare-register (CCR) of a pre-configured timer.
* The following assumptions are made:
*
* + A timer is externally configured in continuous mode with some
*   source.  The PWM infrastructure must not interfere with other
*   uses of the timer.  To simplify satisfying this requirement, it
*   is assumed that the PWM owns the CCR0 register of the timer along
*   with its dedicated interrupt vector.  This assumption could be
*   removed by converting the hard interrupt handler to a function
*   invoked from an external source.
*
* + The cycle of the PWM is the complete sequence stages, where the
*   first stage enables all active pins and subsequent stages disable
*   them.  The duration of a PWM cycle is the PWM period, which is
*   the number of underlying timer ticks within which individual
*   pulse widths vary.  The period is configured when the PWM is
*   reset.
*
* + The pulse widths may be reconfigured frequently during execution
*   of the application so that a static machine transition array is
*   not appropriate, but not so frequently that changes impact the
*   execution of the transitions.
*
* + Changes in pulse width take effect at the start of the following
*   period.
*
* + The clock source and pulse width timing must not be so tight that
*   the CCR handler cannot complete its work before the next state
*   change.  The caller is responsible for ensuring this.
*
* + The application is responsible for configuring PxDIR and PxSEL
*   for the pins to be used.
*
* + The PWM should be friendly to low-power-mode applications.
*
* + To support the potential for multiple PWMs driven by different
*   interrupts, the PWM state is encapsulated in a structure, but the
*   current implementation retains an internal reference to that
*   structure while the PWM is active so it can be located from the
*   CCR handler interrupt.
*/

#include 

/* Forward declaration */
struct pwm_t;

/** Number of pins supported by the PWM.  This must be consistent with
* the pwm_pinbit_t typedef. */
#define PWM_PIN_COUNT 8

/** Type used to represent zero or more PWM pins as a bit mask.  This
* must be consistent with PWM_PIN_COUNT. */
typedef unsigned char pwm_pinbit_t;

/** Type used to represent the period of the PWM, or the width of a
* pin controlled by the PWM. */
typedef unsigned int pwm_period_t;

/** Type used to define a callback executed from the timer interrupt
* at period rollovers.
*
* @note The function is invoked from the CCR interrupt handler.  To
* reduce glitches, active pins are set before the callback is
* enabled.  Be aware of the implications of this in terms of glitches
* that might occur if the pins are reconfigured by the callback.
*
* @param pwm the PWM configuration associated with the CCR handler.
*
* @return The callback should return a nonzero value if the timer
* interrupt should exit low-power mode on return
*/
typedef int (* pwm_callback_fn) (struct pwm_t* pwm);

/** Configuration and state used by the PWM infrastructure.
*
* The PWM begins a cycle by turning on all active pins.  It then sets
* the CCR to be woken at the appropriate time to disable the pin(s)
* with the shortest width. */
typedef struct pwm_t {
 /** An optional callback to be invoked prior to starting the next
  * cycle.  The value is not affected by pwm_reset().  */
 pwm_callback_fn post_cycle_callback;

 /** The index into the ccr_delta and pin_mask array for the next
  * stage to execute.  Initialized by pwm_release(), value ranges
  * from 0 to PWM_PIN_COUNT+1.  A value of zero is transient,
  * occurring only during the CCR handler; the value of
  * PWM_PIN_COUNT+1 indicates the last stage of the cycle is in
  * progress. */
 uint8_t stage_index;

 /** Delta between wake-ups to change the pulse width.
  *
  * A value of zero denotes a cell that is not in use.  All unused
  * cells are placed at the end of the array.  Note that if all pins
  * are active there are no unused cells.
  *
  * Used cells begin with the first element.  The sum of the values
  * of used cells must equal the period of the PWM. */
 pwm_period_t ccr_delta[PWM_PIN_COUNT+1];

 /** Bits denoting pins to be changed at the corresponding stage.
  *
  * For the first stage (executed at the start of the cycle), the
  * corresponding pins will be set.  For subsequent stages, the
  * corresponding pins will be cleared. */
 pwm_pinbit_t pin_mask[PWM_PIN_COUNT+1];
} pwm_t;

/** Return the duration of a PWM cycle in ticks of the underlying clock. */
pwm_period_t pwm_period (const pwm_t *pwm);

/** Reset a PWM
*
* If the PWM is currently associated with the CCR handler, the
* previously active pins are cleared and the pwm is dissociated from
* the handler.
*
* The PWM internal state is cleared: no pins are enabled, and the PWM
* itself remains disabled until pwm_release() is invoked to start it
* running.
*
* @param pwm the PWM configuration to be cleared.
*
* @param period the period for PWM activities.  A zero value may be
* used when the PWM is being disabled permanently.
*
* @param post_cycle_callback an optional callback to be invoked from
* the CCR handler at the end of each cycle.  Pass a null pointer if
* no callback is required.
*
* @return
* @li -EINVAL if the pwm is invalid ;
* @li 0 in the normal case
*/
int pwm_reset (pwm_t *pwm,
              pwm_period_t period,
              pwm_callback_fn post_cycle_callback);

/** Start a configure PWM running.
*
* @param pwm the PWM to start.  A reference to this structure will be
* retained by the PWM internal state, so it must remain valid as long
* as the PWM is active.
*
* @note Start of a PWM is effected by simulating the completion of a
* cycle, causing the CCR handler to execute the corresponding restart
* code including an invocation of pwm->post_cycle_callback.  If
* pwm_release() is invoked while interrupts are disabled, the start
* will not occur until they are enabled, which may result in bad
* output widths for the first period.
*
* @return
* @li -EINVAL if the pwm is not invalid ;
* @li -EBUSY if the timer interrupt is already associated with
* another PWM instance ;
* @li -EALREADY if the pwm is already running
* @li 0 if the pwm is successfully released
*/
int pwm_release (pwm_t *pwm);

/** Read the set of active pins managed by the PWM. */
pwm_pinbit_t pwm_active_pins (const pwm_t *pwm);

/** Change the configuration for a pin within the PWM.
*
* @note This routine does not update PxDIR and PxSEL as pins are
* enabled and disabled.  Only PxOUT is controlled by the PWM.
*
* @note It is safe to invoke this while the PWM is running.  Changes
* will take effect at the start of the following cycle.
*
* @param pwm the PWM state
*
* @param pin the pin to be changed, as an index from 0 to PWM_PIN_COUNT-1.
*
* @param the new width for the pin.  A positive value causes the pin
* to be on for width ticks after the start of the period; if the
* value meets or exceeds the PWM period, the pin is never turned off
* by the PWM, but is turned on at the start of each cycle.  A zero
* value removes the pin from PWM management.
*
* @return
* @li -EINVAL if the pwm or pin parameters are invalid.
* @li 0 if the pin is no longer managed by the PWM.
* @li 1 if the pin remains managed by the PWM.
*/
int pwm_configure_pin (pwm_t *pwm,
                      unsigned int pin,
                      pwm_period_t width);

#endif /* _PWM_H_ */

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...