Contiki-NG
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, uint32_t count, uint8_t timer,
94  uint8_t ab)
95 {
96  uint8_t offset = 0;
97  uint32_t interval_load, duty_count, copy;
98  uint32_t gpt_base, gpt_en, gpt_dir;
99 
100  if((freq < PWM_FREQ_MIN) || (freq > PWM_FREQ_MAX) ||
101  (duty < PWM_DUTY_MIN) || (duty > PWM_DUTY_MAX) ||
102  (timer > PWM_TIMER_MAX) || (timer < PWM_TIMER_MIN)) {
103  PRINTF("PWM: Invalid PWM settings\n");
104  return PWM_ERROR;
105  }
106 
107  /* GPT0 timer A is used for clock_delay_usec() in clock.c */
108  if((ab == PWM_TIMER_A) && (timer == PWM_TIMER_0)) {
109  PRINTF("PWM: GPT0 (timer A) is reserved for clock_delay_usec()\n");
110  return PWM_ERROR;
111  }
112 
113  PRINTF("PWM: F%08luHz: %u%%/%lu on GPT%u-%u\n", freq, duty, count, timer, ab);
114 
115  lpm_register_peripheral(permit_pm1);
116 
117  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
118  gpt_en = GPTIMER_CTL_TAEN;
119  gpt_dir = GPTIMER_CTL_TAPWML;
120 
121  if(ab == PWM_TIMER_B) {
122  offset = 4;
123  gpt_en = GPTIMER_CTL_TBEN;
124  gpt_dir = GPTIMER_CTL_TBPWML;
125  }
126 
127  PRINTF("PWM: GPT_x_BASE 0x%08lX (%u)\n", gpt_base, offset);
128 
129  /* Restore later, ensure GPTIMER_CTL_TxEN and GPTIMER_CTL_TxPWML are clear */
130  copy = REG(gpt_base + GPTIMER_CTL);
131  copy &= ~(gpt_en | gpt_dir);
132 
133  /* Enable module clock for the GPTx in Active mode */
135  /* Enable module clock for the GPTx in Sleep mode */
137  /* Enable module clock for the GPTx in PM0, in PM1 and below this doesn't matter */
139 
140  /* Stop the timer */
141  REG(gpt_base + GPTIMER_CTL) = 0;
142  /* Use 16-bit timer */
143  REG(gpt_base + GPTIMER_CFG) = PWM_GPTIMER_CFG_SPLIT_MODE;
144  /* Configure PWM mode */
145  REG(gpt_base + GPTIMER_TAMR + offset) = 0;
146  REG(gpt_base + GPTIMER_TAMR + offset) |= GPTIMER_TAMR_TAAMS;
147  REG(gpt_base + GPTIMER_TAMR + offset) |= GPTIMER_TAMR_TAMR_PERIODIC;
148 
149  /* If the duty cycle is zero, leave the GPTIMER configured as PWM to pass a next
150  * configured check, but do nothing else */
151  if((!duty) && (!count)) {
152  REG(gpt_base + GPTIMER_CTL) |= (copy | gpt_dir);
153  return PWM_SUCCESS;
154  }
155 
156  /* Get the peripheral clock and equivalent deassert count, depending on the
157  * value given by the user, either use the count number of the duty cycle in
158  * percentage
159  */
160  interval_load = sys_ctrl_get_sys_clock() / freq;
161  if(duty) {
162  duty_count = ((interval_load * duty) + 1) / 100;
163  } else {
164  duty_count = count;
165  }
166 
167  PRINTF("PWM: sys %luHz: %lu %lu\n", sys_ctrl_get_sys_clock(),
168  interval_load, duty_count);
169 
170  /* Set the start value (period), count down */
171  REG(gpt_base + GPTIMER_TAILR + offset) = ((uint16_t *)&interval_load)[0] - 1;
172  /* Set the deassert period */
173  REG(gpt_base + GPTIMER_TAMATCHR + offset) = ((uint16_t *)&duty_count)[0] - 1;
174  /* Set the prescaler if required */
175  REG(gpt_base + GPTIMER_TAPR + offset) = ((uint8_t *)&interval_load)[2];
176  /* Set the prescaler match if required */
177  REG(gpt_base + GPTIMER_TAPMR + offset) = ((uint8_t *)&duty_count)[2];
178  /* Restore the register content */
179  REG(gpt_base + GPTIMER_CTL) |= (copy | gpt_dir);
180 
181  PRINTF("PWM: TnILR %lu ", REG(gpt_base + (GPTIMER_TAILR + offset)));
182  PRINTF("TnMATCHR %lu ", REG(gpt_base + (GPTIMER_TAMATCHR + offset)));
183  PRINTF("TnPR %lu ", REG(gpt_base + (GPTIMER_TAPR + offset)));
184  PRINTF("TnPMR %lu\n", REG(gpt_base + (GPTIMER_TAPMR + offset)));
185 
186  return PWM_SUCCESS;
187 }
188 /*---------------------------------------------------------------------------*/
189 int8_t
190 pwm_stop(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin, uint8_t state)
191 {
192  uint32_t gpt_base, gpt_dis;
193 
194  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
195  (timer > PWM_TIMER_MAX)) {
196  PRINTF("PWM: Invalid PWM values\n");
197  return PWM_ERROR;
198  }
199 
200  if(!pwm_configured(timer, ab)) {
201  PRINTF("PWM: GPTn not configured as PWM\n");
202  return PWM_ERROR;
203  }
204 
205  /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */
206  if((port > GPIO_D_NUM) || (pin > 7)) {
207  PRINTF("PWM: Invalid pin/port settings\n");
208  return PWM_ERROR;
209  }
210 
211  /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */
212  if((state != PWM_OFF_WHEN_STOP) && (state != PWM_ON_WHEN_STOP)) {
213  PRINTF("PWM: Invalid pin state when PWM is halt\n");
214  return PWM_ERROR;
215  }
216 
217  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
218  gpt_dis = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBEN : GPTIMER_CTL_TAEN;
219  REG(gpt_base + GPTIMER_CTL) &= ~gpt_dis;
220 
221  /* Configure the port/pin as GPIO, input */
222  ioc_set_over(port, pin, IOC_OVERRIDE_DIS);
225  if(state) {
227  } else {
229  }
230  PRINTF("PWM: OFF -> Timer %u (%u)\n", timer, ab);
231  return PWM_SUCCESS;
232 }
233 /*---------------------------------------------------------------------------*/
234 int8_t
235 pwm_start(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin)
236 {
237  uint32_t gpt_base, gpt_en, gpt_sel;
238 
239  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
240  (timer > PWM_TIMER_MAX)) {
241  PRINTF("PWM: Invalid PWM values\n");
242  return PWM_ERROR;
243  }
244 
245  if(!pwm_configured(timer, ab)) {
246  PRINTF("PWM: GPTn not configured as PWM\n");
247  return PWM_ERROR;
248  }
249 
250  /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */
251  if((port > GPIO_D_NUM) || (pin > 7)) {
252  PRINTF("PWM: Invalid pin/port settings\n");
253  return PWM_ERROR;
254  }
255 
256  /* Map to given port/pin */
257  gpt_sel = IOC_PXX_SEL_GPT0_ICP1 + (timer * 2);
258  if(ab == PWM_TIMER_B) {
259  gpt_sel++;
260  }
261  ioc_set_sel(port, pin, gpt_sel);
262  ioc_set_over(port, pin, IOC_OVERRIDE_OE);
264 
265  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
266  gpt_en = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBEN : GPTIMER_CTL_TAEN;
267  REG(gpt_base + GPTIMER_CTL) |= gpt_en;
268  PRINTF("PWM: ON -> Timer %u (%u) IOC_PXX_SEL_GPTx_IPCx 0x%08lX\n", timer, ab,
269  gpt_sel);
270  return PWM_SUCCESS;
271 }
272 /*---------------------------------------------------------------------------*/
273 int8_t
274 pwm_set_direction(uint8_t timer, uint8_t ab, uint8_t dir)
275 {
276  uint32_t gpt_base, gpt_dir;
277 
278  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
279  (timer > PWM_TIMER_MAX) || (dir > PWM_SIGNAL_INVERTED)) {
280  PRINTF("PWM: Invalid PWM values\n");
281  return PWM_ERROR;
282  }
283 
284  if(!pwm_configured(timer, ab)) {
285  PRINTF("PWM: GPTn not configured as PWM\n");
286  return PWM_ERROR;
287  }
288 
289  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
290  gpt_dir = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBPWML : GPTIMER_CTL_TAPWML;
291  if(dir) {
292  REG(gpt_base + GPTIMER_CTL) |= gpt_dir;
293  } else {
294  REG(gpt_base + GPTIMER_CTL) &= ~gpt_dir;
295  }
296 
297  PRINTF("PWM: Signal direction (%u) -> Timer %u (%u)\n", dir, timer, ab);
298  return PWM_SUCCESS;
299 }
300 /*---------------------------------------------------------------------------*/
301 int8_t
302 pwm_toggle_direction(uint8_t timer, uint8_t ab)
303 {
304  uint32_t gpt_base, gpt_dir;
305 
306  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
307  (timer > PWM_TIMER_MAX)) {
308  PRINTF("PWM: Invalid PWM values\n");
309  return PWM_ERROR;
310  }
311 
312  if(!pwm_configured(timer, ab)) {
313  PRINTF("PWM: GPTn not configured as PWM\n");
314  return PWM_ERROR;
315  }
316 
317  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
318  gpt_dir = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBPWML : GPTIMER_CTL_TAPWML;
319  if(REG(gpt_base + GPTIMER_CTL) & gpt_dir) {
320  REG(gpt_base + GPTIMER_CTL) &= ~gpt_dir;
321  } else {
322  REG(gpt_base + GPTIMER_CTL) |= gpt_dir;
323  }
324 
325  PRINTF("PWM: direction toggled -> Timer %u (%u)\n", timer, ab);
326  return PWM_SUCCESS;
327 }
328 /*---------------------------------------------------------------------------*/
329 int8_t
330 pwm_disable(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin)
331 {
332  uint32_t gpt_base;
333  uint8_t offset = (ab == PWM_TIMER_B) ? 4 : 0;
334  gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer);
335 
336  if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) ||
337  (timer > PWM_TIMER_MAX)) {
338  PRINTF("PWM: Invalid PWM values\n");
339  return PWM_ERROR;
340  }
341 
342  /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */
343  if((port > GPIO_D_NUM) || (pin > 7)) {
344  PRINTF("PWM: Invalid pin/port settings\n");
345  return PWM_ERROR;
346  }
347 
348  if(!pwm_configured(timer, ab)) {
349  PRINTF("PWM: GPTn not configured as PWM\n");
350  return PWM_ERROR;
351  }
352 
353  /* Stop the PWM */
354  pwm_stop(timer, ab, port, pin, PWM_OFF_WHEN_STOP);
355  /* Disable the PWM mode */
356  REG(gpt_base + (GPTIMER_TAMR + offset)) = 0;
357  /* Restart the interval load and deassert values */
358  REG(gpt_base + (GPTIMER_TAILR + offset)) = 0;
359  REG(gpt_base + (GPTIMER_TAMATCHR + offset)) = 0;
360 
361  /* Configure the port/pin as GPIO, input */
362  ioc_set_over(port, pin, IOC_OVERRIDE_DIS);
365 
366  return PWM_SUCCESS;
367 }
368 /*---------------------------------------------------------------------------*/
369 /** @} */
Header file for the CC2538 PWM driver.
Header file for the cc2538 System Control driver.
#define GPIO_SET_PIN(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE high.
Definition: gpio.h:106
#define GPTIMER_TAPR
GPTM Timer A prescale.
Definition: gptimer.h:71
Header file with register and macro declarations for the cc2538 GPIO module.
#define GPTIMER_TAILR
GPTM Timer A interval load.
Definition: gptimer.h:67
#define GPTIMER_CTL_TBPWML
Timer B PWM output level.
Definition: gptimer.h:139
static const nrf_drv_timer_t timer
Timer instance used for rtimer.
Definition: rtimer-arch.c:48
int8_t pwm_start(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin)
Once configured, starts the PWM.
Definition: pwm.c:235
#define GPTIMER_TAPMR
GPTM Timer A prescale match.
Definition: gptimer.h:73
#define GPIO_CLR_PIN(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE low.
Definition: gpio.h:113
Header file with declarations for the I/O Control module.
#define GPTIMER_TAMATCHR
GPTM Timer A match.
Definition: gptimer.h:69
#define SYS_CTRL_SCGCGPT_GPT0
GPT0 clock enable, CPU IDLE.
Definition: sys-ctrl.h:148
A timer.
Definition: timer.h:82
#define IOC_OVERRIDE_DIS
Override Disabled.
Definition: ioc.h:226
#define GPIO_PIN_MASK(PIN)
Converts a pin number to a pin mask.
Definition: gpio.h:320
int8_t pwm_toggle_direction(uint8_t timer, uint8_t ab)
Toggle the PWM signal direction (inverts the current duty cycle)
Definition: pwm.c:302
#define GPTIMER_CTL_TAEN
Timer A enable.
Definition: gptimer.h:149
#define GPTIMER_TAMR
GPTM Timer A mode.
Definition: gptimer.h:59
#define GPTIMER_CTL_TBEN
Timer B enable.
Definition: gptimer.h:143
#define GPIO_D_NUM
GPIO_D: 3.
Definition: gpio.h:67
#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:258
#define SYS_CTRL_RCGCGPT
GPT[3:0] clocks - active mode.
Definition: sys-ctrl.h:67
#define SYS_CTRL_DCGCGPT_GPT0
GPT0 clock enable, PM0.
Definition: sys-ctrl.h:157
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:274
void ioc_set_over(uint8_t port, uint8_t pin, uint8_t over)
Set Port:Pin override function.
Definition: ioc.c:54
#define SYS_CTRL_RCGCGPT_GPT0
GPT0 clock enable, CPU running.
Definition: sys-ctrl.h:139
#define GPIO_SET_INPUT(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE to input.
Definition: gpio.h:78
#define GPTIMER_CTL_TAPWML
Timer A PWM output level.
Definition: gptimer.h:144
#define GPIO_SET_OUTPUT(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE to output.
Definition: gpio.h:85
#define SYS_CTRL_SCGCGPT
GPT[3:0] clocks - sleep mode.
Definition: sys-ctrl.h:68
uint32_t sys_ctrl_get_sys_clock(void)
Returns the actual system clock in Hz.
Definition: sys-ctrl.c:120
#define IOC_OVERRIDE_OE
Output Enable.
Definition: ioc.h:222
#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:250
#define GPTIMER_CTL
GPTM control.
Definition: gptimer.h:61
int8_t pwm_enable(uint32_t freq, uint8_t duty, uint32_t count, uint8_t timer, uint8_t ab)
Configures the general purpose timer in PWM mode.
Definition: pwm.c:93
void ioc_set_sel(uint8_t port, uint8_t pin, uint8_t sel)
Function select for Port:Pin.
Definition: ioc.c:66
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:190
#define SYS_CTRL_DCGCGPT
GPT[3:0] clocks - PM0.
Definition: sys-ctrl.h:69
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:330
#define GPIO_PORT_TO_BASE(PORT)
Converts a port number to the port base address.
Definition: gpio.h:328
#define GPTIMER_CFG
GPTM configuration.
Definition: gptimer.h:58
#define GPTIMER_TAMR_TAAMS
Timer A alternate mode.
Definition: gptimer.h:115