Contiki 3.x
pwm.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015, Zolertia - http://www.zolertia.com
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  *
14  * 3. Neither the name of the copyright holder nor the names of its
15  * contributors may be used to endorse or promote products derived
16  * from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29  * OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 /*---------------------------------------------------------------------------*/
32 /**
33  * \addtogroup cc2538-pwm-driver
34  * @{
35  *
36  * \file
37  * Driver for the CC2538 PWM
38  *
39  * \author
40  * Javier Sanchez <jsanchez@zolertia.com>
41  * Antonio Lignan <alinan@zolertia.com>
42  */
43 /*---------------------------------------------------------------------------*/
44 #include "contiki.h"
45 #include "dev/ioc.h"
46 #include "dev/gpio.h"
47 #include "dev/sys-ctrl.h"
48 #include "dev/pwm.h"
49 #include "lpm.h"
50 #include <stdio.h>
51 #include <stdlib.h>
52 /*---------------------------------------------------------------------------*/
53 #define DEBUG 0
54 #if DEBUG
55 #define PRINTF(...) printf(__VA_ARGS__)
56 #else
57 #define PRINTF(...)
58 #endif
59 /*---------------------------------------------------------------------------*/
60 #define PWM_GPTIMER_NUM_TO_BASE(x) ((GPT_0_BASE) + ((x) << 12))
61 /*---------------------------------------------------------------------------*/
62 static uint8_t
63 pwm_configured(uint8_t timer, uint8_t ab)
64 {
65  uint8_t offset;
66  uint32_t gpt_base;
67  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
68  offset = (ab) ? 4 : 0;
69 
70  if((REG(gpt_base + GPTIMER_TAMR + offset) & GPTIMER_TAMR_TAAMS) &&
71  (REG(gpt_base + GPTIMER_TAMR + offset) & GPTIMER_TAMR_TAMR_PERIODIC)) {
72  return 1;
73  }
74  return 0;
75 }
76 /*---------------------------------------------------------------------------*/
77 static bool
78 permit_pm1(void)
79 {
80  uint8_t timer, ab;
81 
82  for(timer = PWM_TIMER_0; timer <= PWM_TIMER_3; timer++)
83  for(ab = PWM_TIMER_A; ab <= PWM_TIMER_B; ab++)
84  if(pwm_configured(timer, ab) &&
85  REG(PWM_GPTIMER_NUM_TO_BASE(timer) + GPTIMER_CTL) &
86  (ab == PWM_TIMER_A ? GPTIMER_CTL_TAEN : GPTIMER_CTL_TBEN))
87  return false;
88 
89  return true;
90 }
91 /*---------------------------------------------------------------------------*/
92 int8_t
93 pwm_enable(uint32_t freq, uint8_t duty, uint8_t timer, uint8_t ab)
94 {
95  uint8_t offset = 0;
96  uint32_t interval_load, duty_count, copy;
97  uint32_t gpt_base, gpt_en, gpt_dir;
98 
99  if((freq < PWM_FREQ_MIN) || (freq > PWM_FREQ_MAX) ||
100  (duty < PWM_DUTY_MIN) || (duty > PWM_DUTY_MAX) ||
101  (timer > PWM_TIMER_MAX) || (timer < PWM_TIMER_MIN)) {
102  PRINTF("PWM: Invalid PWM settings\n");
103  return PWM_ERROR;
104  }
105 
106  /* GPT0 timer A is used for clock_delay_usec() in clock.c */
107  if((ab == PWM_TIMER_A) && (timer == PWM_TIMER_0)) {
108  PRINTF("PWM: GPT0 (timer A) is reserved for clock_delay_usec()\n");
109  return PWM_ERROR;
110  }
111 
112  PRINTF("PWM: F%08luHz: %u%% on GPT%u-%u\n", freq, duty, timer, ab);
113 
114  lpm_register_peripheral(permit_pm1);
115 
116  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
117  gpt_en = GPTIMER_CTL_TAEN;
118  gpt_dir = GPTIMER_CTL_TAPWML;
119 
120  if(ab == PWM_TIMER_B) {
121  offset = 4;
122  gpt_en = GPTIMER_CTL_TBEN;
123  gpt_dir = GPTIMER_CTL_TBPWML;
124  }
125 
126  PRINTF("PWM: GPT_x_BASE 0x%08lX (%u)\n", gpt_base, offset);
127 
128  /* Restore later, ensure GPTIMER_CTL_TxEN and GPTIMER_CTL_TxPWML are clear */
129  copy = REG(gpt_base + GPTIMER_CTL);
130  copy &= ~(gpt_en | gpt_dir);
131 
132  /* Enable module clock for the GPTx in Active mode */
134  /* Enable module clock for the GPTx in Sleep mode */
136  /* Enable module clock for the GPTx in PM0, in PM1 and below this doesn't matter */
138 
139  /* Stop the timer */
140  REG(gpt_base + GPTIMER_CTL) = 0;
141  /* Use 16-bit timer */
142  REG(gpt_base + GPTIMER_CFG) = PWM_GPTIMER_CFG_SPLIT_MODE;
143  /* Configure PWM mode */
144  REG(gpt_base + GPTIMER_TAMR + offset) = 0;
145  REG(gpt_base + GPTIMER_TAMR + offset) |= GPTIMER_TAMR_TAAMS;
146  REG(gpt_base + GPTIMER_TAMR + offset) |= GPTIMER_TAMR_TAMR_PERIODIC;
147 
148  /* If the duty cycle is zero, leave the GPTIMER configured as PWM to pass a next
149  * configured check, but do nothing else */
150  if(!duty) {
151  REG(gpt_base + GPTIMER_CTL) |= (copy | gpt_dir);
152  return PWM_SUCCESS;
153  }
154 
155  /* Get the peripheral clock and equivalent deassert count */
156  interval_load = sys_ctrl_get_sys_clock() / freq;
157  duty_count = ((interval_load * duty) + 1) / 100;
158 
159  PRINTF("PWM: sys %luHz: %lu %lu\n", sys_ctrl_get_sys_clock(),
160  interval_load, duty_count);
161 
162  /* Set the start value (period), count down */
163  REG(gpt_base + GPTIMER_TAILR + offset) = ((uint16_t *)&interval_load)[0] - 1;
164  /* Set the deassert period */
165  REG(gpt_base + GPTIMER_TAMATCHR + offset) = ((uint16_t *)&duty_count)[0] - 1;
166  /* Set the prescaler if required */
167  REG(gpt_base + GPTIMER_TAPR + offset) = ((uint8_t *)&interval_load)[2];
168  /* Set the prescaler match if required */
169  REG(gpt_base + GPTIMER_TAPMR + offset) = ((uint8_t *)&duty_count)[2];
170  /* Restore the register content */
171  REG(gpt_base + GPTIMER_CTL) |= (copy | gpt_dir);
172 
173  PRINTF("PWM: TnILR %lu ", REG(gpt_base + (GPTIMER_TAILR + offset)));
174  PRINTF("TnMATCHR %lu ", REG(gpt_base + (GPTIMER_TAMATCHR + offset)));
175  PRINTF("TnPR %lu ", REG(gpt_base + (GPTIMER_TAPR + offset)));
176  PRINTF("TnPMR %lu\n", REG(gpt_base + (GPTIMER_TAPMR + offset)));
177 
178  return PWM_SUCCESS;
179 }
180 /*---------------------------------------------------------------------------*/
181 int8_t
182 pwm_stop(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin, uint8_t state)
183 {
184  uint32_t gpt_base, gpt_dis;
185 
186  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
187  (timer > PWM_TIMER_MAX)) {
188  PRINTF("PWM: Invalid PWM values\n");
189  return PWM_ERROR;
190  }
191 
192  if(!pwm_configured(timer, ab)) {
193  PRINTF("PWM: GPTn not configured as PWM\n");
194  return PWM_ERROR;
195  }
196 
197  /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */
198  if((port > GPIO_D_NUM) || (pin > 7)) {
199  PRINTF("PWM: Invalid pin/port settings\n");
200  return PWM_ERROR;
201  }
202 
203  /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */
204  if((state != PWM_OFF_WHEN_STOP) && (state != PWM_ON_WHEN_STOP)) {
205  PRINTF("PWM: Invalid pin state when PWM is halt\n");
206  return PWM_ERROR;
207  }
208 
209  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
210  gpt_dis = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBEN : GPTIMER_CTL_TAEN;
211  REG(gpt_base + GPTIMER_CTL) &= ~gpt_dis;
212 
213  /* Configure the port/pin as GPIO, input */
214  ioc_set_over(port, pin, IOC_OVERRIDE_DIS);
217  if(state) {
219  } else {
221  }
222  PRINTF("PWM: OFF -> Timer %u (%u)\n", timer, ab);
223  return PWM_SUCCESS;
224 }
225 /*---------------------------------------------------------------------------*/
226 int8_t
227 pwm_start(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin)
228 {
229  uint32_t gpt_base, gpt_en, gpt_sel;
230 
231  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
232  (timer > PWM_TIMER_MAX)) {
233  PRINTF("PWM: Invalid PWM values\n");
234  return PWM_ERROR;
235  }
236 
237  if(!pwm_configured(timer, ab)) {
238  PRINTF("PWM: GPTn not configured as PWM\n");
239  return PWM_ERROR;
240  }
241 
242  /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */
243  if((port > GPIO_D_NUM) || (pin > 7)) {
244  PRINTF("PWM: Invalid pin/port settings\n");
245  return PWM_ERROR;
246  }
247 
248  /* Map to given port/pin */
249  gpt_sel = IOC_PXX_SEL_GPT0_ICP1 + (timer * 2);
250  if(ab == PWM_TIMER_B) {
251  gpt_sel++;
252  }
253  ioc_set_sel(port, pin, gpt_sel);
254  ioc_set_over(port, pin, IOC_OVERRIDE_OE);
256 
257  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
258  gpt_en = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBEN : GPTIMER_CTL_TAEN;
259  REG(gpt_base + GPTIMER_CTL) |= gpt_en;
260  PRINTF("PWM: ON -> Timer %u (%u) IOC_PXX_SEL_GPTx_IPCx 0x%08lX\n", timer, ab,
261  gpt_sel);
262  return PWM_SUCCESS;
263 }
264 /*---------------------------------------------------------------------------*/
265 int8_t
266 pwm_set_direction(uint8_t timer, uint8_t ab, uint8_t dir)
267 {
268  uint32_t gpt_base, gpt_dir;
269 
270  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
271  (timer > PWM_TIMER_MAX) || (dir > PWM_SIGNAL_INVERTED)) {
272  PRINTF("PWM: Invalid PWM values\n");
273  return PWM_ERROR;
274  }
275 
276  if(!pwm_configured(timer, ab)) {
277  PRINTF("PWM: GPTn not configured as PWM\n");
278  return PWM_ERROR;
279  }
280 
281  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
282  gpt_dir = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBPWML : GPTIMER_CTL_TAPWML;
283  if(dir) {
284  REG(gpt_base + GPTIMER_CTL) |= gpt_dir;
285  } else {
286  REG(gpt_base + GPTIMER_CTL) &= ~gpt_dir;
287  }
288 
289  PRINTF("PWM: Signal direction (%u) -> Timer %u (%u)\n", dir, timer, ab);
290  return PWM_SUCCESS;
291 }
292 /*---------------------------------------------------------------------------*/
293 int8_t
294 pwm_toggle_direction(uint8_t timer, uint8_t ab)
295 {
296  uint32_t gpt_base, gpt_dir;
297 
298  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
299  (timer > PWM_TIMER_MAX)) {
300  PRINTF("PWM: Invalid PWM values\n");
301  return PWM_ERROR;
302  }
303 
304  if(!pwm_configured(timer, ab)) {
305  PRINTF("PWM: GPTn not configured as PWM\n");
306  return PWM_ERROR;
307  }
308 
309  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
310  gpt_dir = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBPWML : GPTIMER_CTL_TAPWML;
311  if(REG(gpt_base + GPTIMER_CTL) & gpt_dir) {
312  REG(gpt_base + GPTIMER_CTL) &= ~gpt_dir;
313  } else {
314  REG(gpt_base + GPTIMER_CTL) |= gpt_dir;
315  }
316 
317  PRINTF("PWM: direction toggled -> Timer %u (%u)\n", timer, ab);
318  return PWM_SUCCESS;
319 }
320 /*---------------------------------------------------------------------------*/
321 int8_t
322 pwm_disable(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin)
323 {
324  uint32_t gpt_base;
325  uint8_t offset = (ab == PWM_TIMER_B) ? 4 : 0;
326  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
327 
328  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
329  (timer > PWM_TIMER_MAX)) {
330  PRINTF("PWM: Invalid PWM values\n");
331  return PWM_ERROR;
332  }
333 
334  /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */
335  if((port > GPIO_D_NUM) || (pin > 7)) {
336  PRINTF("PWM: Invalid pin/port settings\n");
337  return PWM_ERROR;
338  }
339 
340  if(!pwm_configured(timer, ab)) {
341  PRINTF("PWM: GPTn not configured as PWM\n");
342  return PWM_ERROR;
343  }
344 
345  /* Stop the PWM */
346  pwm_stop(timer, ab, port, pin, PWM_OFF_WHEN_STOP);
347  /* Disable the PWM mode */
348  REG(gpt_base + (GPTIMER_TAMR + offset)) = 0;
349  /* Restart the interval load and deassert values */
350  REG(gpt_base + (GPTIMER_TAILR + offset)) = 0;
351  REG(gpt_base + (GPTIMER_TAMATCHR + offset)) = 0;
352 
353  /* Configure the port/pin as GPIO, input */
354  ioc_set_over(port, pin, IOC_OVERRIDE_DIS);
357 
358  return PWM_SUCCESS;
359 }
360 /*---------------------------------------------------------------------------*/
361 /** @} */
#define GPIO_SET_INPUT(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE to input.
Definition: gpio.h:93
#define GPTIMER_CTL_TBEN
Timer B enable.
Definition: gptimer.h:143
#define SYS_CTRL_RCGCGPT_GPT0
GPT0 clock enable, CPU running.
Definition: sys-ctrl.h:139
#define GPIO_PIN_MASK(PIN)
Converts a pin number to a pin mask.
Definition: gpio.h:321
#define IOC_OVERRIDE_DIS
Override Disabled.
Definition: ioc.h:226
#define GPTIMER_TAMR_TAAMS
Timer A alternate mode.
Definition: gptimer.h:115
static const nrf_drv_timer_t timer
Timer instance used for rtimer.
Definition: rtimer-arch.c:49
int8_t pwm_start(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin)
Once configured, starts the PWM.
Definition: pwm.c:227
#define SYS_CTRL_SCGCGPT
GPT[3:0] clocks - sleep mode.
Definition: sys-ctrl.h:68
#define SYS_CTRL_SCGCGPT_GPT0
GPT0 clock enable, CPU IDLE.
Definition: sys-ctrl.h:148
#define IOC_OVERRIDE_OE
Output Enable.
Definition: ioc.h:222
A timer.
Definition: timer.h:86
#define GPTIMER_TAMR
GPTM Timer A mode.
Definition: gptimer.h:59
#define GPTIMER_CTL_TAEN
Timer A enable.
Definition: gptimer.h:149
#define GPIO_D_NUM
GPIO_D: 3.
Definition: gpio.h:82
void ioc_set_sel(uint8_t port, uint8_t pin, uint8_t sel)
Function select for Port:Pin.
Definition: ioc.c:60
#define GPIO_CLR_PIN(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE low.
Definition: gpio.h:114
#define SYS_CTRL_DCGCGPT_GPT0
GPT0 clock enable, PM0.
Definition: sys-ctrl.h:157
#define SYS_CTRL_DCGCGPT
GPT[3:0] clocks - PM0.
Definition: sys-ctrl.h:69
int8_t pwm_stop(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin, uint8_t state)
Halts the PWM in a given GPT/timer.
Definition: pwm.c:182
#define GPIO_SET_PIN(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE high.
Definition: gpio.h:107
int8_t pwm_set_direction(uint8_t timer, uint8_t ab, uint8_t dir)
Sets the PWM duty cycle signal direction (high/low)
Definition: pwm.c:266
#define GPIO_SET_OUTPUT(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE to output.
Definition: gpio.h:100
#define GPTIMER_TAPMR
GPTM Timer A prescale match.
Definition: gptimer.h:73
Header file with declarations for the I/O Control module.
#define GPIO_SOFTWARE_CONTROL(PORT_BASE, PIN_MASK)
Configure the pin to be software controlled with PIN_MASK of port with PORT_BASE. ...
Definition: gpio.h:259
#define GPIO_PORT_TO_BASE(PORT)
Converts a port number to the port base address.
Definition: gpio.h:329
Header file for the cc2538 System Control driver.
#define GPTIMER_CTL_TAPWML
Timer A PWM output level.
Definition: gptimer.h:144
#define SYS_CTRL_RCGCGPT
GPT[3:0] clocks - active mode.
Definition: sys-ctrl.h:67
#define GPTIMER_TAMATCHR
GPTM Timer A match.
Definition: gptimer.h:69
int8_t pwm_enable(uint32_t freq, uint8_t duty, uint8_t timer, uint8_t ab)
Configures the general purpose timer in PWM mode.
Definition: pwm.c:93
void lpm_register_peripheral(lpm_periph_permit_pm1_func_t permit_pm1_func)
Register a peripheral function which will get called by the LPM module to get 'permission' to drop to...
int8_t pwm_disable(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin)
Disables a previously PWM configured GPTn.
Definition: pwm.c:322
#define GPTIMER_TAILR
GPTM Timer A interval load.
Definition: gptimer.h:67
int8_t pwm_toggle_direction(uint8_t timer, uint8_t ab)
Toggle the PWM signal direction (inverts the current duty cycle)
Definition: pwm.c:294
#define GPTIMER_CFG
GPTM configuration.
Definition: gptimer.h:58
#define GPIO_PERIPHERAL_CONTROL(PORT_BASE, PIN_MASK)
Configure the pin to be under peripheral control with PIN_MASK of port with PORT_BASE.
Definition: gpio.h:251
#define GPTIMER_TAPR
GPTM Timer A prescale.
Definition: gptimer.h:71
Header file for the CC2538 PWM driver.
void ioc_set_over(uint8_t port, uint8_t pin, uint8_t over)
Set Port:Pin override function.
Definition: ioc.c:54
uint32_t sys_ctrl_get_sys_clock(void)
Returns the actual system clock in Hz.
Definition: sys-ctrl.c:120
#define GPTIMER_CTL_TBPWML
Timer B PWM output level.
Definition: gptimer.h:139
#define GPTIMER_CTL
GPTM control.
Definition: gptimer.h:61