Contiki 2.5
pwm.c
1 /*
2  * Copyright (c) 2010, Mariano Alvira <mar@devl.org> and other contributors
3  * to the MC1322x project (http://mc1322x.devl.org)
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in the
13  * documentation and/or other materials provided with the distribution.
14  * 3. Neither the name of the Institute nor the names of its contributors
15  * may be used to endorse or promote products derived from this software
16  * without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  * This file is part of libmc1322x: see http://mc1322x.devl.org
31  * for details.
32  *
33  *
34  */
35 
36 #include <mc1322x.h>
37 #include <stdlib.h>
38 #include "pwm.h"
39 
40 static struct {
41  uint32_t period;
42  uint32_t guard;
43  uint32_t pad_forced;
44 } pwm_info[4];
45 
46 static inline void pad_set_output(int timer_num) { // set to output (when in GPIO mode)
47  switch (timer_num) {
48  case 0: GPIO->DATA_SEL.TMR0_PIN = 1; GPIO->PAD_DIR.TMR0_PIN = 1; break;
49  case 1: GPIO->DATA_SEL.TMR1_PIN = 1; GPIO->PAD_DIR.TMR1_PIN = 1; break;
50  case 2: GPIO->DATA_SEL.TMR2_PIN = 1; GPIO->PAD_DIR.TMR2_PIN = 1; break;
51  case 3: GPIO->DATA_SEL.TMR3_PIN = 1; GPIO->PAD_DIR.TMR3_PIN = 1; break;
52  default: break;
53  }
54 }
55 
56 static inline void pad_set_zero(int timer_num) { // set to zero in GPIO mode
57  switch (timer_num) {
58  case 0: GPIO->DATA_RESET.TMR0_PIN = 1; GPIO->FUNC_SEL.TMR0_PIN = 0; break;
59  case 1: GPIO->DATA_RESET.TMR1_PIN = 1; GPIO->FUNC_SEL.TMR1_PIN = 0; break;
60  case 2: GPIO->DATA_RESET.TMR2_PIN = 1; GPIO->FUNC_SEL.TMR2_PIN = 0; break;
61  case 3: GPIO->DATA_RESET.TMR3_PIN = 1; GPIO->FUNC_SEL.TMR3_PIN = 0; break;
62  default: break;
63  }
64 }
65 
66 static inline void pad_set_one(int timer_num) { // set to one in GPIO mode
67  switch (timer_num) {
68  case 0: GPIO->DATA_SET.TMR0_PIN = 1; GPIO->FUNC_SEL.TMR0_PIN = 0; break;
69  case 1: GPIO->DATA_SET.TMR1_PIN = 1; GPIO->FUNC_SEL.TMR1_PIN = 0; break;
70  case 2: GPIO->DATA_SET.TMR2_PIN = 1; GPIO->FUNC_SEL.TMR2_PIN = 0; break;
71  case 3: GPIO->DATA_SET.TMR3_PIN = 1; GPIO->FUNC_SEL.TMR3_PIN = 0; break;
72  default: break;
73  }
74 }
75 
76 static inline void pad_set_normal(int timer_num) { // set to TMR OFLAG output
77  switch (timer_num) {
78  case 0: GPIO->FUNC_SEL.TMR0_PIN = 1; break;
79  case 1: GPIO->FUNC_SEL.TMR1_PIN = 1; break;
80  case 2: GPIO->FUNC_SEL.TMR2_PIN = 1; break;
81  case 3: GPIO->FUNC_SEL.TMR3_PIN = 1; break;
82  default: break;
83  }
84 }
85 
86 /* Initialize PWM output.
87  timer_num = 0, 1, 2, 3
88  rate = desired rate in Hz,
89  duty = desired duty cycle. 0=always off, 65536=always on.
90  enable_timer = whether to actually run the timer, versus just configuring it
91  Returns actual PWM rate. */
92 uint32_t pwm_init_ex(int timer_num, uint32_t rate, uint32_t duty, int enable_timer)
93 {
94  uint32_t actual_rate;
95  volatile struct TMR_struct *timer = TMR_ADDR(timer_num);
96  int log_divisor = 0;
97  uint32_t period, guard;
98 
99  /* Turn timer off */
100  TMR0->ENBL &= ~(1 << timer_num);
101 
102  /* Calculate optimal rate */
103  for (log_divisor = 0; log_divisor < 8; log_divisor++)
104  {
105  int denom = (rate * (1 << log_divisor));
106  period = (REF_OSC + denom/2) / denom;
107  if (period <= 65535)
108  break;
109  }
110  if (log_divisor >= 8)
111  {
112  period = 65535;
113  log_divisor = 7;
114  }
115 
116  /* Guard value (for safely changing duty cycle) should be
117  about 32 CPU clocks. Calculate how many timer counts that
118  is, based on prescaler */
119  guard = 32 >> log_divisor;
120  if (guard < 2) guard = 2;
121 
122  /* Period should be about 50% longer than guard */
123  if (period < ((guard * 3) / 2))
124  period = guard + 4;
125 
126  /* Store period, guard, actual rate */
127  pwm_info[timer_num].period = period;
128  pwm_info[timer_num].guard = guard;
129  actual_rate = REF_OSC / (period * (1 << log_divisor));
130 
131  /* Set up timer */
132  pwm_duty_ex(timer_num, duty); // sets CMPLD1, LOAD
133  timer->SCTRLbits = (struct TMR_SCTRL) {
134  .OEN = 1, // drive OFLAG
135  };
136  timer->CSCTRLbits = (struct TMR_CSCTRL) {
137  .CL1 = 0x01, // Reload COMP1 when COMP1 matches
138  };
139  timer->COMP1 = timer->CMPLD1;
140  timer->CNTR = timer->LOAD;
141  timer->CTRLbits = (struct TMR_CTRL) {
142  .COUNT_MODE = 1, // Count rising edge of primary source
143  .PRIMARY_CNT_SOURCE = 8 + log_divisor, // Peripheral clock divided by (divisor)
144  .LENGTH = 1, // At compare, reset to LOAD
145  .OUTPUT_MODE = 6, // Set on COMP1, clear on rollover
146  };
147 
148  pad_set_output(timer_num);
149  pad_set_normal(timer_num);
150 
151  if (enable_timer) {
152  TMR0->ENBL |= (1 << timer_num);
153  }
154 
155 // printf("pwm timer %d, addr %p, requested rate %d, actual rate: %d, period %d, guard %d, divisor %d\r\n",
156 // timer_num, timer, rate, actual_rate, period, guard, 1 << log_divisor);
157 
158  return actual_rate;
159 }
160 
161 /* Change duty cycle. Safe to call at any time.
162  timer_num = 0, 1, 2, 3
163  duty = desired duty cycle. 0=always off, 65536=always on.
164 */
165 void pwm_duty_ex(int timer_num, uint32_t duty)
166 {
167  uint16_t comp1, load;
168  volatile struct TMR_struct *timer = TMR_ADDR(timer_num);
169  uint32_t period = pwm_info[timer_num].period;
170 
171  duty = (duty * period + 32767) / 65536;
172 
173  /* We don't use the "variable PWM" mode described in the datasheet because
174  there's no way to reliably change the duty cycle without potentially
175  changing the period for one cycle, which will cause phase drifts.
176 
177  Instead, we use the "Set on compare, clear on rollover" output mode:
178 
179  waveform: |_________| |----------|
180  counter: 0 COMP1 LOAD 65535
181 
182  The low portion of the wave is COMP1 cycles long. The
183  compare changes the counter to LOAD, and so the high
184  portion is (65536 - LOAD) cycles long.
185 
186  Now, we just have to make sure we're not about to hit COMP1
187  before we change LOAD and COMPLD1. If (COMP1 - CNTR) is less
188  than GUARD cycles, we wait for it to reload before changing.
189  */
190 
191  if (duty == 0) {
192  pad_set_zero(timer_num);
193  pwm_info[timer_num].pad_forced = 1;
194  return;
195  }
196 
197  if (duty >= period) {
198  pad_set_one(timer_num);
199  pwm_info[timer_num].pad_forced = 1;
200  return;
201  }
202 
203  if (pwm_info[timer_num].pad_forced) {
204  pad_set_normal(timer_num);
205  pwm_info[timer_num].pad_forced = 0;
206  }
207 
208  comp1 = (period - duty) - 1;
209  load = (65536 - duty);
210 
211  /* Disable interrupts */
212  uint32_t old_INTCNTL = ITC->INTCNTL;
213  ITC->INTCNTL = 0;
214 
215  if (TMR0->ENBL & (1 << timer_num))
216  {
217  /* Timer is enabled, so use the careful approach.
218  Implemented in ASM so we can be sure of the cycle
219  count */
220  uint32_t tmp1, tmp2;
221  asm volatile (//".arm \n\t"
222  "1: \n\t"
223  "ldrh %[tmp1], %[comp] \n\t" // load COMP1
224  "ldrh %[tmp2], %[count] \n\t" // load CNTR
225  "sub %[tmp1], %[tmp1], %[tmp2] \n\t" // subtract
226  "lsl %[tmp1], %[tmp1], #16 \n\t" // clear high bits
227  "lsr %[tmp1], %[tmp1], #16 \n\t"
228  "cmp %[tmp1], %[guard] \n\t" // compare to GUARD
229  "bls 1b \n\t" // if less, goto 1
230 
231  "strh %[ld1], %[cmpld] \n\t" // store CMPLD1
232  "strh %[ld2], %[load] \n\t" // store LOAD
233  : /* out */
234  [tmp1] "=&l" (tmp1),
235  [tmp2] "=&l" (tmp2),
236  [cmpld] "=m" (timer->CMPLD1),
237  [load] "=m" (timer->LOAD)
238  : /* in */
239  [comp] "m" (timer->COMP1),
240  [count] "m" (timer->CNTR),
241  [ld1] "l" (comp1),
242  [ld2] "l" (load),
243  [guard] "l" (pwm_info[timer_num].guard)
244  : "memory"
245  );
246  } else {
247  /* Just set it directly, timer isn't running */
248  timer->CMPLD1 = comp1;
249  timer->LOAD = load;
250  }
251 
252  /* Re-enable interrupts */
253  ITC->INTCNTL = old_INTCNTL;
254 }