Added PWM driver for the Zolertia Zoul module and CC2538 platforms
This commit is contained in:
parent
ddb6e4b3d7
commit
37fcb927be
|
@ -60,7 +60,7 @@ CONTIKI_CPU_SOURCEFILES += ecc-curve.c
|
||||||
CONTIKI_CPU_SOURCEFILES += dbg.c ieee-addr.c
|
CONTIKI_CPU_SOURCEFILES += dbg.c ieee-addr.c
|
||||||
CONTIKI_CPU_SOURCEFILES += slip-arch.c slip.c
|
CONTIKI_CPU_SOURCEFILES += slip-arch.c slip.c
|
||||||
CONTIKI_CPU_SOURCEFILES += i2c.c cc2538-temp-sensor.c vdd3-sensor.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
|
DEBUG_IO_SOURCEFILES += dbg-printf.c dbg-snprintf.c dbg-sprintf.c strformat.c
|
||||||
|
|
||||||
|
|
343
cpu/cc2538/dev/pwm.c
Normal file
343
cpu/cc2538/dev/pwm.c
Normal file
|
@ -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 <jsanchez@zolertia.com>
|
||||||
|
* Antonio Lignan <alinan@zolertia.com>
|
||||||
|
*/
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
#include "contiki.h"
|
||||||
|
#include "dev/ioc.h"
|
||||||
|
#include "dev/gpio.h"
|
||||||
|
#include "dev/sys-ctrl.h"
|
||||||
|
#include "dev/pwm.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/** @} */
|
177
cpu/cc2538/dev/pwm.h
Normal file
177
cpu/cc2538/dev/pwm.h
Normal file
|
@ -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 <jsanchez@zolertia.com>
|
||||||
|
* Antonio Lignan <alinan@zolertia.com>
|
||||||
|
*/
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
#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_ */
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
* @}
|
||||||
|
*/
|
|
@ -1,5 +1,5 @@
|
||||||
DEFINES+=PROJECT_CONF_H=\"project-conf.h\"
|
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
|
CONTIKI_TARGET_SOURCEFILES += tsl2563.c sht25.c
|
||||||
|
|
||||||
all: $(CONTIKI_PROJECT)
|
all: $(CONTIKI_PROJECT)
|
||||||
|
|
202
examples/zolertia/zoul/test-pwm.c
Normal file
202
examples/zolertia/zoul/test-pwm.c
Normal file
|
@ -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 <jsanchez@zolertia.com>
|
||||||
|
* Antonio Lignan <alinan@zolertia.com>
|
||||||
|
*/
|
||||||
|
#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 <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
* @}
|
||||||
|
*/
|
|
@ -29,6 +29,7 @@ In terms of hardware support, the following drivers have been implemented:
|
||||||
* Low Power Modes
|
* Low Power Modes
|
||||||
* General-Purpose Timers. NB: GPT0 is in use by the platform code, the remaining GPTs are available for application development.
|
* General-Purpose Timers. NB: GPT0 is in use by the platform code, the remaining GPTs are available for application development.
|
||||||
* ADC
|
* ADC
|
||||||
|
* PWM
|
||||||
* Cryptoprocessor (AES-CCM-256, SHA-256)
|
* Cryptoprocessor (AES-CCM-256, SHA-256)
|
||||||
* Public Key Accelerator (ECDH, ECDSA)
|
* Public Key Accelerator (ECDH, ECDSA)
|
||||||
* Flash-based port of Coffee
|
* Flash-based port of Coffee
|
||||||
|
|
|
@ -54,6 +54,7 @@ In terms of hardware support, the following drivers have been implemented for th
|
||||||
* Cryptoprocessor (AES-CCM-256, SHA-256)
|
* Cryptoprocessor (AES-CCM-256, SHA-256)
|
||||||
* Public Key Accelerator (ECDH, ECDSA)
|
* Public Key Accelerator (ECDH, ECDSA)
|
||||||
* Flash-based port of Coffee
|
* Flash-based port of Coffee
|
||||||
|
* PWM
|
||||||
* LEDs
|
* LEDs
|
||||||
* Buttons
|
* Buttons
|
||||||
* Built-in core temperature and battery sensor.
|
* Built-in core temperature and battery sensor.
|
||||||
|
|
Loading…
Reference in a new issue