From 37fcb927be63bbbdcc3671a472d9ea87f7c61793 Mon Sep 17 00:00:00 2001 From: Antonio Lignan Date: Wed, 25 Nov 2015 23:44:54 +0100 Subject: [PATCH] Added PWM driver for the Zolertia Zoul module and CC2538 platforms --- cpu/cc2538/Makefile.cc2538 | 2 +- cpu/cc2538/dev/pwm.c | 343 ++++++++++++++++++++++++++++++ cpu/cc2538/dev/pwm.h | 177 +++++++++++++++ examples/zolertia/zoul/Makefile | 2 +- examples/zolertia/zoul/test-pwm.c | 202 ++++++++++++++++++ platform/cc2538dk/README.md | 1 + platform/zoul/README.md | 1 + 7 files changed, 726 insertions(+), 2 deletions(-) create mode 100644 cpu/cc2538/dev/pwm.c create mode 100644 cpu/cc2538/dev/pwm.h create mode 100644 examples/zolertia/zoul/test-pwm.c diff --git a/cpu/cc2538/Makefile.cc2538 b/cpu/cc2538/Makefile.cc2538 index 873bf9779..b8230448b 100644 --- a/cpu/cc2538/Makefile.cc2538 +++ b/cpu/cc2538/Makefile.cc2538 @@ -60,7 +60,7 @@ CONTIKI_CPU_SOURCEFILES += ecc-curve.c CONTIKI_CPU_SOURCEFILES += dbg.c ieee-addr.c CONTIKI_CPU_SOURCEFILES += slip-arch.c slip.c CONTIKI_CPU_SOURCEFILES += i2c.c cc2538-temp-sensor.c vdd3-sensor.c -CONTIKI_CPU_SOURCEFILES += cfs-coffee.c cfs-coffee-arch.c +CONTIKI_CPU_SOURCEFILES += cfs-coffee.c cfs-coffee-arch.c pwm.c DEBUG_IO_SOURCEFILES += dbg-printf.c dbg-snprintf.c dbg-sprintf.c strformat.c diff --git a/cpu/cc2538/dev/pwm.c b/cpu/cc2538/dev/pwm.c new file mode 100644 index 000000000..7fb6fc61f --- /dev/null +++ b/cpu/cc2538/dev/pwm.c @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2015, Zolertia - http://www.zolertia.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/*---------------------------------------------------------------------------*/ +/** + * \addtogroup cc2538-pwm-driver + * @{ + * + * \file + * Driver for the CC2538 PWM + * + * \author + * Javier Sanchez + * Antonio Lignan + */ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "dev/ioc.h" +#include "dev/gpio.h" +#include "dev/sys-ctrl.h" +#include "dev/pwm.h" +#include +#include +/*---------------------------------------------------------------------------*/ +#define DEBUG 0 +#if DEBUG +#define PRINTF(...) printf(__VA_ARGS__) +#else +#define PRINTF(...) +#endif +/*---------------------------------------------------------------------------*/ +#define PWM_GPTIMER_NUM_TO_BASE(x) ((GPT_0_BASE) + ((x) << 12)) +/*---------------------------------------------------------------------------*/ +static uint8_t +pwm_configured(uint8_t timer, uint8_t ab) +{ + uint8_t offset; + uint32_t gpt_base; + gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); + offset = (ab) ? 4 : 0; + + if((REG(gpt_base + GPTIMER_TAMR + offset) & GPTIMER_TAMR_TAAMS) && + (REG(gpt_base + GPTIMER_TAMR + offset) & GPTIMER_TAMR_TAMR_PERIODIC)) { + return 1; + } + return 0; +} +/*---------------------------------------------------------------------------*/ +int8_t +pwm_enable(uint32_t freq, uint8_t duty, uint8_t timer, uint8_t ab) +{ + uint8_t offset = 0; + uint32_t interval_load, duty_count, copy; + uint32_t gpt_base, gpt_en, gpt_dir; + + if((freq < PWM_FREQ_MIN) || (freq > PWM_FREQ_MAX) || + (duty < PWM_DUTY_MIN) || (duty > PWM_DUTY_MAX) || + (timer > PWM_TIMER_MAX) || (timer < PWM_TIMER_MIN)) { + PRINTF("PWM: Invalid PWM settings\n"); + return PWM_ERROR; + } + + /* GPT0 timer A is used for clock_delay_usec() in clock.c */ + if((ab == PWM_TIMER_A) && (timer == PWM_TIMER_0)) { + PRINTF("PWM: GPT0 (timer A) is reserved for clock_delay_usec()\n"); + return PWM_ERROR; + } + + PRINTF("PWM: F%08luHz: %u%% on GPT%u-%u\n", freq, duty, timer, ab); + + gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); + gpt_en = GPTIMER_CTL_TAEN; + gpt_dir = GPTIMER_CTL_TAPWML; + + if(ab == PWM_TIMER_B) { + offset = 4; + gpt_en = GPTIMER_CTL_TBEN; + gpt_dir = GPTIMER_CTL_TBPWML; + } + + PRINTF("PWM: GPT_x_BASE 0x%08lX (%u)\n", gpt_base, offset); + + /* Restore later, ensure GPTIMER_CTL_TxEN and GPTIMER_CTL_TxPWML are clear */ + copy = REG(gpt_base + GPTIMER_CTL); + copy &= ~(gpt_en | gpt_dir); + + /* Enable module clock for the GPTx in Active mode */ + REG(SYS_CTRL_RCGCGPT) |= (SYS_CTRL_RCGCGPT_GPT0 << timer); + /* Enable module clock for the GPTx in Sleep mode */ + REG(SYS_CTRL_SCGCGPT) |= (SYS_CTRL_SCGCGPT_GPT0 << timer); + /* Enable module clock for the GPTx in PM0, in PM1 and below this doesn't matter */ + REG(SYS_CTRL_DCGCGPT) |= (SYS_CTRL_DCGCGPT_GPT0 << timer); + + /* Stop the timer */ + REG(gpt_base + GPTIMER_CTL) = 0; + /* Use 16-bit timer */ + REG(gpt_base + GPTIMER_CFG) = PWM_GPTIMER_CFG_SPLIT_MODE; + /* Configure PWM mode */ + REG(gpt_base + GPTIMER_TAMR + offset) = 0; + REG(gpt_base + GPTIMER_TAMR + offset) |= GPTIMER_TAMR_TAAMS; + REG(gpt_base + GPTIMER_TAMR + offset) |= GPTIMER_TAMR_TAMR_PERIODIC; + + /* If the duty cycle is zero, leave the GPTIMER configured as PWM to pass a next + * configured check, but do nothing else */ + if(!duty) { + REG(gpt_base + GPTIMER_CTL) |= (copy | gpt_dir); + return PWM_SUCCESS; + } + + /* Get the peripheral clock and equivalent deassert count */ + interval_load = sys_ctrl_get_io_clock() / freq; + duty_count = ((interval_load * duty) + 1) / 100; + + PRINTF("PWM: IO %luHz: %lu %lu\n", sys_ctrl_get_io_clock(), + interval_load, duty_count); + + /* Set the start value (period), count down */ + REG(gpt_base + GPTIMER_TAILR + offset) = ((uint16_t *)&interval_load)[0] - 1; + /* Set the deassert period */ + REG(gpt_base + GPTIMER_TAMATCHR + offset) = ((uint16_t *)&duty_count)[0] - 1; + /* Set the prescaler if required */ + REG(gpt_base + GPTIMER_TAPR + offset) = ((uint8_t *)&interval_load)[2]; + /* Set the prescaler match if required */ + REG(gpt_base + GPTIMER_TAPMR + offset) = ((uint8_t *)&duty_count)[2]; + /* Restore the register content */ + REG(gpt_base + GPTIMER_CTL) |= (copy | gpt_dir); + + PRINTF("PWM: TnILR %lu ", REG(gpt_base + (GPTIMER_TAILR + offset))); + PRINTF("TnMATCHR %lu ", REG(gpt_base + (GPTIMER_TAMATCHR + offset))); + PRINTF("TnPR %lu ", REG(gpt_base + (GPTIMER_TAPR + offset))); + PRINTF("TnPMR %lu\n", REG(gpt_base + (GPTIMER_TAPMR + offset))); + + return PWM_SUCCESS; +} +/*---------------------------------------------------------------------------*/ +int8_t +pwm_stop(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin, uint8_t state) +{ + uint32_t gpt_base, gpt_dis; + + if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || + (timer > PWM_TIMER_MAX)) { + PRINTF("PWM: Invalid PWM values\n"); + return PWM_ERROR; + } + + if(!pwm_configured(timer, ab)) { + PRINTF("PWM: GPTn not configured as PWM\n"); + return PWM_ERROR; + } + + /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */ + if((port > GPIO_D_NUM) || (pin > 7)) { + PRINTF("PWM: Invalid pin/port settings\n"); + return PWM_ERROR; + } + + /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */ + if((state != PWM_OFF_WHEN_STOP) && (state != PWM_ON_WHEN_STOP)) { + PRINTF("PWM: Invalid pin state when PWM is halt\n"); + return PWM_ERROR; + } + + gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); + gpt_dis = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBEN : GPTIMER_CTL_TAEN; + REG(gpt_base + GPTIMER_CTL) &= ~gpt_dis; + + /* Configure the port/pin as GPIO, input */ + ioc_set_over(port, pin, IOC_OVERRIDE_DIS); + GPIO_SOFTWARE_CONTROL(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); + GPIO_SET_OUTPUT(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); + if(state) { + GPIO_SET_PIN(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); + } else { + GPIO_CLR_PIN(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); + } + PRINTF("PWM: OFF -> Timer %u (%u)\n", timer, ab); + return PWM_SUCCESS; +} +/*---------------------------------------------------------------------------*/ +int8_t +pwm_start(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin) +{ + uint32_t gpt_base, gpt_en, gpt_sel; + + if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || + (timer > PWM_TIMER_MAX)) { + PRINTF("PWM: Invalid PWM values\n"); + return PWM_ERROR; + } + + if(!pwm_configured(timer, ab)) { + PRINTF("PWM: GPTn not configured as PWM\n"); + return PWM_ERROR; + } + + /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */ + if((port > GPIO_D_NUM) || (pin > 7)) { + PRINTF("PWM: Invalid pin/port settings\n"); + return PWM_ERROR; + } + + /* Map to given port/pin */ + gpt_sel = IOC_PXX_SEL_GPT0_ICP1 + (timer * 2); + if(ab == PWM_TIMER_B) { + gpt_sel++; + } + ioc_set_sel(port, pin, gpt_sel); + ioc_set_over(port, pin, IOC_OVERRIDE_OE); + GPIO_PERIPHERAL_CONTROL(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); + + gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); + gpt_en = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBEN : GPTIMER_CTL_TAEN; + REG(gpt_base + GPTIMER_CTL) |= gpt_en; + PRINTF("PWM: ON -> Timer %u (%u) IOC_PXX_SEL_GPTx_IPCx 0x%08lX\n", timer, ab, + gpt_sel); + return PWM_SUCCESS; +} +/*---------------------------------------------------------------------------*/ +int8_t +pwm_set_direction(uint8_t timer, uint8_t ab, uint8_t dir) +{ + uint32_t gpt_base, gpt_dir; + + if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || + (timer > PWM_TIMER_MAX) || (dir > PWM_SIGNAL_INVERTED)) { + PRINTF("PWM: Invalid PWM values\n"); + return PWM_ERROR; + } + + if(!pwm_configured(timer, ab)) { + PRINTF("PWM: GPTn not configured as PWM\n"); + return PWM_ERROR; + } + + gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); + gpt_dir = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBPWML : GPTIMER_CTL_TAPWML; + if(dir) { + REG(gpt_base + GPTIMER_CTL) |= gpt_dir; + } else { + REG(gpt_base + GPTIMER_CTL) &= ~gpt_dir; + } + + PRINTF("PWM: Signal direction (%u) -> Timer %u (%u)\n", dir, timer, ab); + return PWM_SUCCESS; +} +/*---------------------------------------------------------------------------*/ +int8_t +pwm_toggle_direction(uint8_t timer, uint8_t ab) +{ + uint32_t gpt_base, gpt_dir; + + if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || + (timer > PWM_TIMER_MAX)) { + PRINTF("PWM: Invalid PWM values\n"); + return PWM_ERROR; + } + + if(!pwm_configured(timer, ab)) { + PRINTF("PWM: GPTn not configured as PWM\n"); + return PWM_ERROR; + } + + gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); + gpt_dir = (ab == PWM_TIMER_B) ? GPTIMER_CTL_TBPWML : GPTIMER_CTL_TAPWML; + if(REG(gpt_base + GPTIMER_CTL) & gpt_dir) { + REG(gpt_base + GPTIMER_CTL) &= ~gpt_dir; + } else { + REG(gpt_base + GPTIMER_CTL) |= gpt_dir; + } + + PRINTF("PWM: direction toggled -> Timer %u (%u)\n", timer, ab); + return PWM_SUCCESS; +} +/*---------------------------------------------------------------------------*/ +int8_t +pwm_disable(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin) +{ + uint32_t gpt_base; + uint8_t offset = (ab == PWM_TIMER_B) ? 4 : 0; + gpt_base = PWM_GPTIMER_NUM_TO_BASE(timer); + + if((ab > PWM_TIMER_B) || (timer < PWM_TIMER_MIN) || + (timer > PWM_TIMER_MAX)) { + PRINTF("PWM: Invalid PWM values\n"); + return PWM_ERROR; + } + + /* CC2538 has 4 ports (A-D) and up to 8 pins (0-7) */ + if((port > GPIO_D_NUM) || (pin > 7)) { + PRINTF("PWM: Invalid pin/port settings\n"); + return PWM_ERROR; + } + + if(!pwm_configured(timer, ab)) { + PRINTF("PWM: GPTn not configured as PWM\n"); + return PWM_ERROR; + } + + /* Stop the PWM */ + pwm_stop(timer, ab, port, pin, PWM_OFF_WHEN_STOP); + /* Disable the PWM mode */ + REG(gpt_base + (GPTIMER_TAMR + offset)) = 0; + /* Restart the interval load and deassert values */ + REG(gpt_base + (GPTIMER_TAILR + offset)) = 0; + REG(gpt_base + (GPTIMER_TAMATCHR + offset)) = 0; + + /* Configure the port/pin as GPIO, input */ + ioc_set_over(port, pin, IOC_OVERRIDE_DIS); + GPIO_SOFTWARE_CONTROL(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); + GPIO_SET_INPUT(GPIO_PORT_TO_BASE(port), GPIO_PIN_MASK(pin)); + + return PWM_SUCCESS; +} +/*---------------------------------------------------------------------------*/ +/** @} */ diff --git a/cpu/cc2538/dev/pwm.h b/cpu/cc2538/dev/pwm.h new file mode 100644 index 000000000..2d7ea0dcd --- /dev/null +++ b/cpu/cc2538/dev/pwm.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2015, Zolertia - http://www.zolertia.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/*---------------------------------------------------------------------------*/ +/** + * \addtogroup cc2538 + * @{ + * + * \defgroup cc2538-pwm-driver CC2538 PWM driver + * + * Driver for the CC2538 PWM on GPTIMER + * + * The driver uses the timers A and B of the general purpose timers to create + * a PWM signal, allowing to set a duty cycle value from 1-100%. This + * implementation relies on having a peripheral clock of 16MHz, but it can be + * easily changed (see PWM_FREQ_MIN and PWM_FREQ_MAX values). The reason it is + * fixed to these frequencies is to have a consistent duty cycle + * implementation. + * Depending on the specific needs these limits can be changed to meet a given + * duty cycle and lower frequencies by using the prescaler (GPTIMER_TnPR). + * + * @{ + * + * \file + * Header file for the CC2538 PWM driver + * + * \author + * Javier Sanchez + * Antonio Lignan + */ +/*---------------------------------------------------------------------------*/ +#ifndef PWM_H_ +#define PWM_H_ +/*---------------------------------------------------------------------------*/ +#include "contiki.h" +#include "dev/ioc.h" +#include "dev/gpio.h" +#include "dev/sys-ctrl.h" +/*---------------------------------------------------------------------------*/ +/** \name PWM return values + * @{ + */ +#define PWM_SUCCESS 0 +#define PWM_ERROR (-1) +/** @} */ +/*---------------------------------------------------------------------------*/ +/** \name PWM recommended values respect to peripheral clock frequency + * @{ + */ +/* Roughly 244 Hz with a 16 MHz IO clock, no prescaler */ +#define PWM_SYS_IO_16MHZ_NO_PRES_MIN 0xFFFF +#define PWM_SYS_IO_16MHZ_NO_PRES_MIN_FREQ 244 +/* Roughly 1 Hz with a 16 MHz IO clock, to keep frequency parameter in Hz */ +#define PWM_SYS_IO_16MHZ_PRES_MIN 0x00F42400 +#define PWM_SYS_IO_16MHZ_PRES_MIN_FREQ 1 +/* Yields 160 KHz at 16 MHz and allows down to 1% (integer) duty cycles */ +#define PWM_SYS_IO_16MHZ_NO_PRES_MAX 100 +#define PWM_SYS_IO_16MHZ_NO_PRES_MAX_FREQ 160000 +/** @} */ +/*---------------------------------------------------------------------------*/ +/** \name PWM driver definitions and configuration values + * @{ + */ +#define PWM_TIMER_A 0 +#define PWM_TIMER_B 1 +#define PWM_TIMER_0 0 +#define PWM_TIMER_1 1 +#define PWM_TIMER_2 2 +#define PWM_TIMER_3 3 +#define PWM_TIMER_MIN PWM_TIMER_0 +#define PWM_TIMER_MAX PWM_TIMER_3 +#define PWM_SIGNAL_STRAIGHT 1 +#define PWM_SIGNAL_INVERTED 0 +#define PWM_OFF_WHEN_STOP 0 +#define PWM_ON_WHEN_STOP 1 +#define PWM_GPTIMER_CFG_SPLIT_MODE 0x04 +#define PWM_DUTY_MAX 100 +#define PWM_DUTY_MIN 0 +#define PWM_FREQ_MIN PWM_SYS_IO_16MHZ_PRES_MIN_FREQ +#define PWM_FREQ_MAX PWM_SYS_IO_16MHZ_NO_PRES_MAX_FREQ +/** @} */ +/*---------------------------------------------------------------------------*/ +/** \name PWM functions + * @{ + */ +/** \brief Configures the general purpose timer in PWM mode + * \param freq PWM frequency (in Hz) + * \param duty PWM duty cycle (percentage in integers) + * \param timer General purpose timer to use [0-3] + * \param ab Select which timer to use (Timer A or B) + * \return \c PWM_SUCCESS if successful, else \c PWM_ERROR + */ +int8_t pwm_enable(uint32_t freq, uint8_t duty, uint8_t timer, uint8_t ab); +/*---------------------------------------------------------------------------*/ +/** \brief Disables a previously PWM configured GPTn + * \param timer General purpose timer to disable [0-3] + * \param ab Select which timer to disable (Timer A or B) + * \param port Port number used as PWM to disable (set as input GPIO) + * \param pin Pin number used as PWM to disable (set as input GPIO) + * \return \c PWM_SUCCESS if successful, else \c PWM_ERROR + * + * This function disables a specific timer (A or B) and reset related registers + * to default values. The user must explicitely pass the port/pin number of + * the pin to disable as PWM and to be configured as input GPIO. + * The module clock is not disabled with this function + */ +int8_t pwm_disable(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin); +/*---------------------------------------------------------------------------*/ +/** \brief Once configured, starts the PWM + * \param timer General purpose timer to start [0-3] + * \param ab Select which timer to start (Timer A or B) + * \param port Port number to use as PWM + * \param pin Pin number to use as PWM + * \return \c PWM_SUCCESS if successful, else \c PWM_ERROR + */ +int8_t pwm_start(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin); +/*---------------------------------------------------------------------------*/ +/** \brief Halts the PWM in a given GPT/timer + * \param timer General purpose timer to stop [0-3] + * \param ab Select which timer to stop (Timer A or B) + * \param port Port of the gpio port mapped to the PWM to stop + * \param pin Pin of the gpio port mapped to the PWM to stop + * \param state State to leave the pin once stopped, on (1) or off (0) + * \return \c PWM_SUCCESS if successful, else \c PWM_ERROR + */ +int8_t pwm_stop(uint8_t timer, uint8_t ab, uint8_t port, uint8_t pin, uint8_t state); +/*---------------------------------------------------------------------------*/ +/** \brief Sets the PWM duty cycle signal direction (high/low) + * \param timer General purpose timer [0-3] + * \param ab Select which timer to use (Timer A or B) + * \param dir Direction of the PWM signal, \c PWM_SIGNAL_INVERTED or + * \c PWM_SIGNAL_STRAIGHT + * \return \c PWM_SUCCESS if successful, else \c PWM_ERROR + */ +int8_t pwm_set_direction(uint8_t timer, uint8_t ab, uint8_t dir); +/*---------------------------------------------------------------------------*/ +/** \brief Toggle the PWM signal direction (inverts the current duty cycle) + * \param timer General purpose timer to use [0-3] + * \param ab Select which timer to use (Timer A or B) + * \return \c PWM_SUCCESS if successful, else \c PWM_ERROR + */ +int8_t pwm_toggle_direction(uint8_t timer, uint8_t ab); +/*---------------------------------------------------------------------------*/ +/** @} */ +#endif /* PWM_H_ */ +/*---------------------------------------------------------------------------*/ +/** + * @} + * @} + */ \ No newline at end of file diff --git a/examples/zolertia/zoul/Makefile b/examples/zolertia/zoul/Makefile index d5e469498..aa371757b 100644 --- a/examples/zolertia/zoul/Makefile +++ b/examples/zolertia/zoul/Makefile @@ -1,5 +1,5 @@ DEFINES+=PROJECT_CONF_H=\"project-conf.h\" -CONTIKI_PROJECT = zoul-demo test-tsl2563 test-sht25 +CONTIKI_PROJECT = zoul-demo test-tsl2563 test-sht25 test-pwm CONTIKI_TARGET_SOURCEFILES += tsl2563.c sht25.c all: $(CONTIKI_PROJECT) diff --git a/examples/zolertia/zoul/test-pwm.c b/examples/zolertia/zoul/test-pwm.c new file mode 100644 index 000000000..6d378861f --- /dev/null +++ b/examples/zolertia/zoul/test-pwm.c @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2015, Zolertia - http://www.zolertia.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \addtogroup remote-examples + * @{ + * + * \defgroup remote-test-pwm Test the CC2538 PWM driver + * + * Demonstrates the use of the CC2538 PWM driver for the Zolertia's Zoul boards + * @{ + * + * \file + * A quick program for testing the CC2538 PWM driver + * \author + * Javier Sanchez + * Antonio Lignan + */ +#include "contiki.h" +#include "cpu.h" +#include "dev/leds.h" +#include "dev/watchdog.h" +#include "dev/sys-ctrl.h" +#include "pwm.h" +#include "systick.h" +#include "lpm.h" +#include "dev/ioc.h" +#include +#include +/*---------------------------------------------------------------------------*/ +#define DEBUG 0 +#if DEBUG +#define PRINTF(...) printf(__VA_ARGS__) +#else +#define PRINTF(...) +#endif +/*---------------------------------------------------------------------------*/ +typedef struct { + uint8_t timer; + uint8_t ab; + uint8_t port; + uint8_t pin; + uint8_t duty; + uint8_t off_state; + uint32_t freq; +} pwm_config_t; +/*---------------------------------------------------------------------------*/ +#define MAX_PWM 4 +static const pwm_config_t pwm_num[MAX_PWM] = { + { + .timer = PWM_TIMER_1, + .ab = PWM_TIMER_A, + .port = GPIO_D_NUM, + .pin = 5, + .duty = 15, + .freq = 1, + .off_state = PWM_OFF_WHEN_STOP, + }, { + .timer = PWM_TIMER_1, + .ab = PWM_TIMER_B, + .port = GPIO_D_NUM, + .pin = 4, + .duty = 35, + .freq = 100, + .off_state = PWM_ON_WHEN_STOP, + }, { + .timer = PWM_TIMER_2, + .ab = PWM_TIMER_A, + .port = GPIO_D_NUM, + .pin = 3, + .duty = 50, + .freq = 1000, + .off_state = PWM_OFF_WHEN_STOP, + }, { + .timer = PWM_TIMER_2, + .ab = PWM_TIMER_B, + .port = GPIO_D_NUM, + .pin = 2, + .duty = 85, + .freq = 160000, + .off_state = PWM_ON_WHEN_STOP, + } +}; +static uint8_t pwm_en[MAX_PWM]; +/*---------------------------------------------------------------------------*/ +#if DEBUG +static const char * +gpt_name(uint8_t timer) +{ + switch(timer) { + case PWM_TIMER_0: + return "PWM TIMER 0"; + case PWM_TIMER_1: + return "PWM TIMER 1"; + case PWM_TIMER_2: + return "PWM TIMER 2"; + case PWM_TIMER_3: + return "PWM TIMER 3"; + default: + return "Unknown"; + } +} +#endif +/*---------------------------------------------------------------------------*/ +static struct etimer et; +/*---------------------------------------------------------------------------*/ +PROCESS(cc2538_pwm_test, "cc2538 pwm test"); +AUTOSTART_PROCESSES(&cc2538_pwm_test); +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(cc2538_pwm_test, ev, data) +{ + PROCESS_BEGIN(); + + uint8_t i; + memset(pwm_en, 0, MAX_PWM); + + PRINTF("\nStarting the test\n"); + + for(i = 0; i < MAX_PWM; i++) { + if(pwm_enable(pwm_num[i].freq, pwm_num[i].duty, + pwm_num[i].timer, pwm_num[i].ab) == PWM_SUCCESS) { + pwm_en[i] = 1; + PRINTF("%s (%u) configuration OK\n", gpt_name(pwm_num[i].timer), + pwm_num[i].ab); + } + } + + while(1) { + for(i = 0; i < MAX_PWM; i++) { + if((pwm_en[i]) && + (pwm_start(pwm_num[i].timer, pwm_num[i].ab, + pwm_num[i].port, pwm_num[i].pin) != PWM_SUCCESS)) { + pwm_en[i] = 0; + PRINTF("%s (%u) failed to start \n", gpt_name(pwm_num[i].timer), + pwm_num[i].ab); + } + } + + etimer_set(&et, CLOCK_SECOND * 2); + PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et)); + + for(i = 0; i < MAX_PWM; i++) { + if((pwm_en[i]) && + (pwm_toggle_direction(pwm_num[i].timer, + pwm_num[i].ab) != PWM_SUCCESS)) { + PRINTF("%s (%u) invert failed \n", gpt_name(pwm_num[i].timer), + pwm_num[i].ab); + } + } + + etimer_set(&et, CLOCK_SECOND * 2); + PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et)); + + for(i = 0; i < MAX_PWM; i++) { + if((pwm_en[i]) && + (pwm_stop(pwm_num[i].timer, pwm_num[i].ab, + pwm_num[i].port, pwm_num[i].pin, + pwm_num[i].off_state) != PWM_SUCCESS)) { + pwm_en[i] = 0; + PRINTF("%s (%u) failed to stop\n", gpt_name(pwm_num[i].timer), + pwm_num[i].ab); + } + } + + etimer_set(&et, CLOCK_SECOND * 2); + PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et)); + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ +/** + * @} + * @} + */ diff --git a/platform/cc2538dk/README.md b/platform/cc2538dk/README.md index 904ba7d7f..3001886b4 100644 --- a/platform/cc2538dk/README.md +++ b/platform/cc2538dk/README.md @@ -29,6 +29,7 @@ In terms of hardware support, the following drivers have been implemented: * Low Power Modes * General-Purpose Timers. NB: GPT0 is in use by the platform code, the remaining GPTs are available for application development. * ADC + * PWM * Cryptoprocessor (AES-CCM-256, SHA-256) * Public Key Accelerator (ECDH, ECDSA) * Flash-based port of Coffee diff --git a/platform/zoul/README.md b/platform/zoul/README.md index 3565cf1fa..385fe06f1 100644 --- a/platform/zoul/README.md +++ b/platform/zoul/README.md @@ -54,6 +54,7 @@ In terms of hardware support, the following drivers have been implemented for th * Cryptoprocessor (AES-CCM-256, SHA-256) * Public Key Accelerator (ECDH, ECDSA) * Flash-based port of Coffee + * PWM * LEDs * Buttons * Built-in core temperature and battery sensor.