Contiki-NG
Loading...
Searching...
No Matches
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/*---------------------------------------------------------------------------*/
62static uint8_t
63pwm_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/*---------------------------------------------------------------------------*/
77static bool
78permit_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/*---------------------------------------------------------------------------*/
92int8_t
93pwm_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/*---------------------------------------------------------------------------*/
189int8_t
190pwm_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/*---------------------------------------------------------------------------*/
234int8_t
235pwm_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/*---------------------------------------------------------------------------*/
273int8_t
274pwm_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/*---------------------------------------------------------------------------*/
301int8_t
302pwm_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/*---------------------------------------------------------------------------*/
329int8_t
330pwm_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 with register and macro declarations for the cc2538 GPIO module.
#define GPIO_PIN_MASK(PIN)
Converts a pin number to a pin mask.
Definition gpio.h:320
#define GPIO_D_NUM
GPIO_D: 3.
Definition gpio.h:67
#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 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 GPIO_PORT_TO_BASE(PORT)
Converts a port number to the port base address.
Definition gpio.h:328
#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 GPIO_SET_PIN(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE high.
Definition gpio.h:106
#define GPIO_CLR_PIN(PORT_BASE, PIN_MASK)
Set pins with PIN_MASK of port with PORT_BASE low.
Definition gpio.h:113
#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 GPTIMER_TAPMR
GPTM Timer A prescale match.
Definition gptimer.h:73
#define GPTIMER_TAMATCHR
GPTM Timer A match.
Definition gptimer.h:69
#define GPTIMER_CTL_TBPWML
Timer B PWM output level.
Definition gptimer.h:139
#define GPTIMER_CTL_TAPWML
Timer A PWM output level.
Definition gptimer.h:144
#define GPTIMER_CFG
GPTM configuration.
Definition gptimer.h:58
#define GPTIMER_TAMR
GPTM Timer A mode.
Definition gptimer.h:59
#define GPTIMER_TAMR_TAAMS
Timer A alternate mode.
Definition gptimer.h:115
#define GPTIMER_CTL_TBEN
Timer B enable.
Definition gptimer.h:143
#define GPTIMER_CTL_TAEN
Timer A enable.
Definition gptimer.h:149
#define GPTIMER_TAILR
GPTM Timer A interval load.
Definition gptimer.h:67
#define GPTIMER_TAPR
GPTM Timer A prescale.
Definition gptimer.h:71
#define GPTIMER_CTL
GPTM control.
Definition gptimer.h:61
void ioc_set_sel(uint8_t port, uint8_t pin, uint8_t sel)
Function select for Port:Pin.
Definition ioc.c:66
#define IOC_OVERRIDE_OE
Output Enable.
Definition ioc.h:222
void ioc_set_over(uint8_t port, uint8_t pin, uint8_t over)
Set Port:Pin override function.
Definition ioc.c:54
#define IOC_OVERRIDE_DIS
Override Disabled.
Definition ioc.h:226
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
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
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
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
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
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
#define SYS_CTRL_DCGCGPT
GPT[3:0] clocks - PM0.
Definition sys-ctrl.h:69
#define SYS_CTRL_RCGCGPT_GPT0
GPT0 clock enable, CPU running.
Definition sys-ctrl.h:139
#define SYS_CTRL_DCGCGPT_GPT0
GPT0 clock enable, PM0.
Definition sys-ctrl.h:157
uint32_t sys_ctrl_get_sys_clock(void)
Returns the actual system clock in Hz.
Definition sys-ctrl.c:120
#define SYS_CTRL_SCGCGPT
GPT[3:0] clocks - sleep mode.
Definition sys-ctrl.h:68
#define SYS_CTRL_RCGCGPT
GPT[3:0] clocks - active mode.
Definition sys-ctrl.h:67
#define SYS_CTRL_SCGCGPT_GPT0
GPT0 clock enable, CPU IDLE.
Definition sys-ctrl.h:148
static volatile uint64_t count
Num.
Definition clock.c:50
Header file with declarations for the I/O Control module.
Header file for the CC2538 PWM driver.
A timer.
Definition timer.h:84
Header file for the cc2538 System Control driver.