pwm.c

00001 /*
00002  * Copyright (c) 2010, Mariano Alvira <mar@devl.org> and other contributors
00003  * to the MC1322x project (http://mc1322x.devl.org)
00004  * All rights reserved.
00005  *
00006  * Redistribution and use in source and binary forms, with or without
00007  * modification, are permitted provided that the following conditions
00008  * are met:
00009  * 1. Redistributions of source code must retain the above copyright
00010  *    notice, this list of conditions and the following disclaimer.
00011  * 2. Redistributions in binary form must reproduce the above copyright
00012  *    notice, this list of conditions and the following disclaimer in the
00013  *    documentation and/or other materials provided with the distribution.
00014  * 3. Neither the name of the Institute nor the names of its contributors
00015  *    may be used to endorse or promote products derived from this software
00016  *    without specific prior written permission.
00017  *
00018  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
00019  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00020  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00021  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
00022  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00023  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
00024  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00025  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00026  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
00027  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00028  * SUCH DAMAGE.
00029  *
00030  * This file is part of libmc1322x: see http://mc1322x.devl.org
00031  * for details.
00032  *
00033  *
00034  */
00035 
00036 #include <mc1322x.h>
00037 #include <stdlib.h>
00038 #include "pwm.h"
00039 
00040 static struct {
00041         uint32_t period;
00042         uint32_t guard;
00043         uint32_t pad_forced;
00044 } pwm_info[4];
00045 
00046 static inline void pad_set_output(int timer_num) { // set to output (when in GPIO mode)
00047         switch (timer_num) {
00048         case 0: GPIO->DATA_SEL.TMR0_PIN = 1; GPIO->PAD_DIR.TMR0_PIN = 1; break;
00049         case 1: GPIO->DATA_SEL.TMR1_PIN = 1; GPIO->PAD_DIR.TMR1_PIN = 1; break;
00050         case 2: GPIO->DATA_SEL.TMR2_PIN = 1; GPIO->PAD_DIR.TMR2_PIN = 1; break;
00051         case 3: GPIO->DATA_SEL.TMR3_PIN = 1; GPIO->PAD_DIR.TMR3_PIN = 1; break;
00052         default: break;
00053         }
00054 }
00055 
00056 static inline void pad_set_zero(int timer_num) { // set to zero in GPIO mode
00057         switch (timer_num) {
00058         case 0: GPIO->DATA_RESET.TMR0_PIN = 1; GPIO->FUNC_SEL.TMR0_PIN = 0; break;
00059         case 1: GPIO->DATA_RESET.TMR1_PIN = 1; GPIO->FUNC_SEL.TMR1_PIN = 0; break;
00060         case 2: GPIO->DATA_RESET.TMR2_PIN = 1; GPIO->FUNC_SEL.TMR2_PIN = 0; break;
00061         case 3: GPIO->DATA_RESET.TMR3_PIN = 1; GPIO->FUNC_SEL.TMR3_PIN = 0; break;
00062         default: break;
00063         }
00064 }
00065 
00066 static inline void pad_set_one(int timer_num) { // set to one in GPIO mode
00067         switch (timer_num) {
00068         case 0: GPIO->DATA_SET.TMR0_PIN = 1; GPIO->FUNC_SEL.TMR0_PIN = 0; break;
00069         case 1: GPIO->DATA_SET.TMR1_PIN = 1; GPIO->FUNC_SEL.TMR1_PIN = 0; break;
00070         case 2: GPIO->DATA_SET.TMR2_PIN = 1; GPIO->FUNC_SEL.TMR2_PIN = 0; break;
00071         case 3: GPIO->DATA_SET.TMR3_PIN = 1; GPIO->FUNC_SEL.TMR3_PIN = 0; break;
00072         default: break;
00073         }
00074 }
00075 
00076 static inline void pad_set_normal(int timer_num) { // set to TMR OFLAG output
00077         switch (timer_num) {
00078         case 0: GPIO->FUNC_SEL.TMR0_PIN = 1; break;
00079         case 1: GPIO->FUNC_SEL.TMR1_PIN = 1; break;
00080         case 2: GPIO->FUNC_SEL.TMR2_PIN = 1; break;
00081         case 3: GPIO->FUNC_SEL.TMR3_PIN = 1; break;
00082         default: break;
00083         }
00084 }
00085 
00086 /* Initialize PWM output.
00087    timer_num = 0, 1, 2, 3
00088    rate = desired rate in Hz,
00089    duty = desired duty cycle.  0=always off, 65536=always on.
00090    enable_timer = whether to actually run the timer, versus just configuring it
00091    Returns actual PWM rate. */
00092 uint32_t pwm_init_ex(int timer_num, uint32_t rate, uint32_t duty, int enable_timer)
00093 {
00094         uint32_t actual_rate;
00095         volatile struct TMR_struct *timer = TMR_ADDR(timer_num);
00096         int log_divisor = 0;
00097         uint32_t period, guard;
00098 
00099         /* Turn timer off */
00100         TMR0->ENBL &= ~(1 << timer_num);
00101 
00102         /* Calculate optimal rate */
00103         for (log_divisor = 0; log_divisor < 8; log_divisor++)
00104         {
00105                 int denom = (rate * (1 << log_divisor));
00106                 period = (REF_OSC + denom/2) / denom;
00107                 if (period <= 65535)
00108                         break;
00109         }
00110         if (log_divisor >= 8)
00111         {
00112                 period = 65535;
00113                 log_divisor = 7;
00114         }
00115 
00116         /* Guard value (for safely changing duty cycle) should be
00117            about 32 CPU clocks.  Calculate how many timer counts that
00118            is, based on prescaler */
00119         guard = 32 >> log_divisor;
00120         if (guard < 2) guard = 2;
00121 
00122         /* Period should be about 50% longer than guard */
00123         if (period < ((guard * 3) / 2))
00124                 period = guard + 4;
00125 
00126         /* Store period, guard, actual rate */
00127         pwm_info[timer_num].period = period;
00128         pwm_info[timer_num].guard = guard;
00129         actual_rate = REF_OSC / (period * (1 << log_divisor));
00130 
00131         /* Set up timer */
00132         pwm_duty_ex(timer_num, duty); // sets CMPLD1, LOAD
00133         timer->SCTRLbits = (struct TMR_SCTRL) {
00134                 .OEN = 1,       // drive OFLAG
00135         };
00136         timer->CSCTRLbits = (struct TMR_CSCTRL) {
00137                 .CL1 = 0x01,    // Reload COMP1 when COMP1 matches
00138         };
00139         timer->COMP1 = timer->CMPLD1;
00140         timer->CNTR = timer->LOAD;
00141         timer->CTRLbits = (struct TMR_CTRL) {
00142                 .COUNT_MODE = 1,                // Count rising edge of primary source
00143                 .PRIMARY_CNT_SOURCE = 8 + log_divisor,  // Peripheral clock divided by (divisor)
00144                 .LENGTH = 1,                    // At compare, reset to LOAD
00145                 .OUTPUT_MODE = 6,               // Set on COMP1, clear on rollover
00146         };
00147 
00148         pad_set_output(timer_num);
00149         pad_set_normal(timer_num);
00150 
00151         if (enable_timer) {
00152                 TMR0->ENBL |= (1 << timer_num);
00153         }
00154 
00155 //      printf("pwm timer %d, addr %p, requested rate %d, actual rate: %d, period %d, guard %d, divisor %d\r\n",
00156 //                timer_num, timer, rate, actual_rate, period, guard, 1 << log_divisor);
00157 
00158         return actual_rate;
00159 }
00160 
00161 /* Change duty cycle.  Safe to call at any time.
00162    timer_num = 0, 1, 2, 3
00163    duty = desired duty cycle.  0=always off, 65536=always on.
00164 */
00165 void pwm_duty_ex(int timer_num, uint32_t duty)
00166 {
00167         uint16_t comp1, load;
00168         volatile struct TMR_struct *timer = TMR_ADDR(timer_num);
00169         uint32_t period = pwm_info[timer_num].period;
00170 
00171         duty = (duty * period + 32767) / 65536;
00172 
00173         /* We don't use the "variable PWM" mode described in the datasheet because
00174            there's no way to reliably change the duty cycle without potentially
00175            changing the period for one cycle, which will cause phase drifts.
00176 
00177            Instead, we use the "Set on compare, clear on rollover" output mode:
00178 
00179            waveform:    |_________|          |----------|
00180            counter:     0       COMP1      LOAD        65535
00181 
00182            The low portion of the wave is COMP1 cycles long.  The
00183            compare changes the counter to LOAD, and so the high
00184            portion is (65536 - LOAD) cycles long.
00185 
00186            Now, we just have to make sure we're not about to hit COMP1
00187            before we change LOAD and COMPLD1.  If (COMP1 - CNTR) is less
00188            than GUARD cycles, we wait for it to reload before changing.
00189         */
00190 
00191         if (duty == 0) {
00192                 pad_set_zero(timer_num);
00193                 pwm_info[timer_num].pad_forced = 1;
00194                 return;
00195         }
00196 
00197         if (duty >= period) {
00198                 pad_set_one(timer_num);
00199                 pwm_info[timer_num].pad_forced = 1;
00200                 return;
00201         }
00202 
00203         if (pwm_info[timer_num].pad_forced) {
00204                 pad_set_normal(timer_num);
00205                 pwm_info[timer_num].pad_forced = 0;
00206         }
00207 
00208         comp1 = (period - duty) - 1;
00209         load = (65536 - duty);
00210 
00211         /* Disable interrupts */
00212         uint32_t old_INTCNTL = ITC->INTCNTL;
00213         ITC->INTCNTL = 0;
00214 
00215         if (TMR0->ENBL & (1 << timer_num))
00216         {
00217                 /* Timer is enabled, so use the careful approach.
00218                    Implemented in ASM so we can be sure of the cycle
00219                    count */
00220                 uint32_t tmp1, tmp2;
00221                 asm volatile (//".arm \n\t"
00222                               "1: \n\t"
00223                               "ldrh %[tmp1], %[comp] \n\t"              // load COMP1
00224                               "ldrh %[tmp2], %[count] \n\t"             // load CNTR
00225                               "sub %[tmp1], %[tmp1], %[tmp2] \n\t"      // subtract
00226                               "lsl %[tmp1], %[tmp1], #16 \n\t"          // clear high bits
00227                               "lsr %[tmp1], %[tmp1], #16 \n\t"
00228                               "cmp %[tmp1], %[guard] \n\t"              // compare to GUARD
00229                               "bls 1b \n\t"                             // if less, goto 1
00230 
00231                               "strh %[ld1], %[cmpld] \n\t"              // store CMPLD1
00232                               "strh %[ld2], %[load] \n\t"               // store LOAD
00233                               : /* out */
00234                                 [tmp1] "=&l" (tmp1),
00235                                 [tmp2] "=&l" (tmp2),
00236                                 [cmpld] "=m" (timer->CMPLD1),
00237                                 [load] "=m" (timer->LOAD)
00238                               : /* in */
00239                                 [comp] "m" (timer->COMP1),
00240                                 [count] "m" (timer->CNTR),
00241                                 [ld1] "l" (comp1),
00242                                 [ld2] "l" (load),
00243                                 [guard] "l" (pwm_info[timer_num].guard)
00244                               : "memory"
00245                 );
00246         } else {
00247                 /* Just set it directly, timer isn't running */
00248                 timer->CMPLD1 = comp1;
00249                 timer->LOAD = load;
00250         }
00251 
00252         /* Re-enable interrupts */
00253         ITC->INTCNTL = old_INTCNTL;
00254 }

Generated on Mon Apr 11 14:23:38 2011 for Contiki 2.5 by  doxygen 1.6.1