1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #15106 from chrysn-pull-requests/saul-rgbleds

saul_pwm: auto-init'able RGB LEDs and dimmers
This commit is contained in:
benpicco 2020-11-13 18:22:30 +01:00 committed by GitHub
commit be9eb22bb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 550 additions and 43 deletions

View File

@ -1,5 +1,6 @@
ifneq (,$(filter saul_default,$(USEMODULE)))
USEMODULE += saul_gpio
USEMODULE += saul_pwm
endif
ifeq (1,$(PARTICLE_MONOFIRMWARE))

View File

@ -31,24 +31,6 @@ extern "C" {
*/
static const saul_gpio_params_t saul_gpio_params[] =
{
{
.name = "Led Red",
.pin = LED0_PIN,
.mode = GPIO_OUT,
.flags = (SAUL_GPIO_INVERTED | SAUL_GPIO_INIT_CLEAR),
},
{
.name = "Led Green",
.pin = LED1_PIN,
.mode = GPIO_OUT,
.flags = (SAUL_GPIO_INVERTED | SAUL_GPIO_INIT_CLEAR),
},
{
.name = "Led Blue",
.pin = LED2_PIN,
.mode = GPIO_OUT,
.flags = (SAUL_GPIO_INVERTED | SAUL_GPIO_INIT_CLEAR),
},
{
.name = "MODE Button",
.pin = BTN0_PIN,

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 Christian Amsüss <chrysn@fsfe.org>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup boards_common_particle-mesh
* @{
*
* @file
* @brief Configuration of SAUL mapped PWM channels
*
* @author Christian Amsüss <chrysn@fsfe.org>
*/
#ifndef PWM_PARAMS_H
#define PWM_PARAMS_H
#include "board.h"
#include "saul/periph.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SAUL_PWM_NO_DIMMER
static const saul_pwm_rgb_params_t saul_pwm_rgb_params[] =
{
{
.name = "LED",
.channels = {
{ PWM_DEV(0), 0, SAUL_PWM_INVERTED },
{ PWM_DEV(0), 1, SAUL_PWM_INVERTED },
{ PWM_DEV(0), 2, SAUL_PWM_INVERTED }
}
}
};
#ifdef __cplusplus
}
#endif
#endif /* PWM_PARAMS_H */
/** @} */

View File

@ -1,5 +1,6 @@
ifneq (,$(filter saul_default,$(USEMODULE)))
USEMODULE += saul_gpio
USEMODULE += saul_pwm
endif
include $(RIOTBOARD)/common/nrf52/bootloader_nrfutil.dep.mk

View File

@ -31,30 +31,6 @@ extern "C" {
*/
static const saul_gpio_params_t saul_gpio_params[] =
{
{
.name = "LD 1",
.pin = LED0_PIN,
.mode = GPIO_OUT,
.flags = (SAUL_GPIO_INVERTED | SAUL_GPIO_INIT_CLEAR),
},
{
.name = "LD 2 Red",
.pin = LED1_PIN,
.mode = GPIO_OUT,
.flags = (SAUL_GPIO_INVERTED | SAUL_GPIO_INIT_CLEAR),
},
{
.name = "LD 2 Green",
.pin = LED2_PIN,
.mode = GPIO_OUT,
.flags = (SAUL_GPIO_INVERTED | SAUL_GPIO_INIT_CLEAR),
},
{
.name = "LD 2 Blue",
.pin = LED3_PIN,
.mode = GPIO_OUT,
.flags = (SAUL_GPIO_INVERTED | SAUL_GPIO_INIT_CLEAR),
},
{
.name = "SW 1",
.pin = BTN0_PIN,

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2020 Christian Amsüss <chrysn@fsfe.org>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup boards_nrf52840dongle
* @{
*
* @file
* @brief Configuration of SAUL mapped PWM channels
*
* @author Christian Amsüss <chrysn@fsfe.org>
*/
#ifndef PWM_PARAMS_H
#define PWM_PARAMS_H
#include "board.h"
#include "saul/periph.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief The single LED exposed via SAUL
*/
static const saul_pwm_dimmer_params_t saul_pwm_dimmer_params[] =
{
{
.name = "LD 1",
.channel = { PWM_DEV(0), 0, SAUL_PWM_INVERTED },
}
};
static const saul_pwm_rgb_params_t saul_pwm_rgb_params[] =
{
{
.name = "LD 2",
.channels = {
{ PWM_DEV(0), 1, SAUL_PWM_INVERTED },
{ PWM_DEV(0), 2, SAUL_PWM_INVERTED },
{ PWM_DEV(0), 3, SAUL_PWM_INVERTED }
}
}
};
#ifdef __cplusplus
}
#endif
#endif /* PWM_PARAMS_H */
/** @} */

View File

@ -49,7 +49,9 @@ configurations. e.g:
specific pin connections to a LCD screen, radio, etc.). Some boards might also
define optimized `XTIMER_%` values (e.g. @ref XTIMER_BACKOFF).
- `gpio_params.h`: if the board supports @ref drivers_saul "SAUL" then its
@ref saul_gpio_params_t is defined here.
@ref saul_gpio_params_t is defined here. (Analogously, a `adc_params.h` can
contain a @ref saul_adc_params_t, and `pwm_params.h` a @ref
saul_pwm_rgb_params_t and a @ref saul_pwm_dimmer_params_t).
- other: other specific headers needed by one `BOARD`
@note Header files do not need to be defined in `include/`, but if defined

View File

@ -154,6 +154,10 @@ ifneq (,$(filter saul_gpio,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio
endif
ifneq (,$(filter saul_pwm,$(USEMODULE)))
FEATURES_REQUIRED += periph_pwm
endif
ifneq (,$(filter saul,$(USEMODULE)))
USEMODULE += phydat
endif

View File

@ -27,6 +27,10 @@
#include "periph/adc.h"
#endif /* MODULE_SAUL_ADC */
#if MODULE_SAUL_PWM || DOXYGEN
#include "periph/pwm.h"
#endif /* MODULE_SAUL_PWM */
#ifdef __cplusplus
extern "C" {
#endif
@ -63,6 +67,115 @@ typedef struct {
} saul_adc_params_t;
#endif /* MODULE_SAUL_ADC */
#if MODULE_SAUL_PWM || DOXYGEN
/**
* @brief Resolution of SAUL mapped PWMs
*/
static const uint16_t saul_pwm_resolution = 255;
/**
* @brief SAUL PWM parameters
*/
typedef enum {
SAUL_PWM_REGULAR = (0 << 0), /**< Physical values are proportional to
average voltage levels (ie. LEDs are in
active-high, anode driven) */
SAUL_PWM_INVERTED = (1 << 0), /**< Physical values are inverted from
average voltage levels (ie. LEDs are in
active-low, cathode driven) */
} saul_pwm_flags_t;
/**
* @brief Single PWM channel exposed via SAUL
*
* This does never need to be declared on its own, but is used inisde @ref
* saul_pwm_dimmer_params_t and @ref saul_pwm_rgb_params_t structs.
*/
typedef struct {
pwm_t dev; /**< PWM device backing this entry */
uint8_t channel; /**< Channel on the PWM device */
saul_pwm_flags_t flags; /**< Configuration flags */
} saul_pwm_channel_t;
/** @brief Default value for @ref SAUL_PWM_FREQ */
#define SAUL_PWM_FREQ_DEFAULT 1000
/**
* @brief Define the PWM frequency for LEDs
*
* This frequency is requested from the PWM driver. As the per @ref pwm_init,
* the actual frequency may be lower, and the SAUL wrapper does not place a
* limit there.
*
* Frequencies of above 200Hz usually give a smooth visual experience. The
* higher 1kHz is picked as a default as some devices can't go that low with
* their timer.
*
* This is typically set in the board's `pwm_params.h`.
*/
/* This is not applied here as it would later need to be undef'd; actual
* application of the default happens in auto_init_saul_pwm.c */
#if DOXYGEN
#define SAUL_PWM_FREQ SAUL_PWM_FREQ_DEFAULT
#endif
/**
* @brief Suppress saul_pwm's dimmer generation
*
* This can be defined in `pwm_params.h` if the saul_pwm module is used, but no
* dimmers (and only RGB LEDs) are in use. Then, no @ref saul_pwm_dimmer_params
* needs to be set.
*/
#if DOXYGEN
#define SAUL_PWM_NO_DIMMER
#endif
/**
* @brief PWM channels mapped to dimmer-style registration entries
*
* This is used to define a `static const saul_pwm_dimmer_params_t
* saul_pwm_dimer_params[]` in a board's `pwm_params.h` for use by the saul_pwm
* module. If the module is used but only RGB LEDs are present, a @ref
* SAUL_PWM_NO_DIMMER define can be set instead.
*/
typedef struct {
const char *name; /**< Name of the device connected to this
channel */
saul_pwm_channel_t channel; /**< Full channel description (device, channel)
along with flags that indicate whether high
PWM values are dark or bright*/
} saul_pwm_dimmer_params_t;
/**
* @brief Suppress saul_pwm's RGB LED generation
*
* This can be defined in `pwm_params.h` if the saul_pwm module is used, but no
* RGB LEDs (and only dimmers) are in use. Then, no @ref saul_pwm_rgb_params_t
* needs to be set.
*/
#if DOXYGEN
#define SAUL_PWM_NO_RGB
#endif
/**
* @brief PWM channels mapped to RGB LED registration entries
*
* This is used to define a `static const saul_pwm_rgb_params_t
* saul_pwm_rgb_params[]` in a board's `pwm_params.h` for use by the saul_pwm
* module. If the module is used but only dimmers are present, a @ref
* SAUL_PWM_NO_RGB define can be set instead.
*/
typedef struct {
const char *name; /**< Name of the device connected to these
channels */
saul_pwm_channel_t channels[3]; /**< Full channel description (device, channel)
along with flags that indicate whether high
PWM values are dark or bright*/
} saul_pwm_rgb_params_t;
#endif /* MODULE_SAUL_PWM */
#ifdef __cplusplus
}
#endif

View File

@ -6,5 +6,8 @@ endif
ifneq (,$(filter saul_adc,$(USEMODULE)))
SRC += adc_saul.c
endif
ifneq (,$(filter saul_pwm,$(USEMODULE)))
SRC += pwm_saul.c
endif
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,186 @@
/*
* Copyright (C) 2015 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*
*/
/*
* @ingroup sys_auto_init_saul
* @{
*
* @file
* @brief Auto initialization of PWM pins directly mapped to SAUL reg
*
* @author Christian Amsüss <chrysn@fsfe.org>
*
* When this module is used, any PWM device assigned inside the configuration
* structs inside `pwm_params.h` in the @ref saul_pwm_dimmer_params_t and @ref
* saul_pwm_rgb_params_t is initialized at startup for 8-bit dimming at about
* 1kHz, and the indicated channels are exposed via SAUL.
*
* @}
*/
#include "log.h"
#include "saul_reg.h"
#include "saul/periph.h"
#include "pwm_params.h"
#include "periph/pwm.h"
#if !defined(SAUL_PWM_FREQ)
#define SAUL_PWM_FREQ SAUL_PWM_FREQ_DEFAULT
#endif
/**
* @brief Define the number of configured dimmers
*/
#ifndef SAUL_PWM_NO_DIMMER
#define SAUL_PWM_DIMMER_NUMOF ARRAY_SIZE(saul_pwm_dimmer_params)
#else
#define SAUL_PWM_DIMMER_NUMOF 0
static const saul_pwm_dimmer_params_t saul_pwm_dimmer_params[0];
#endif
/**
* @brief Define the number of configured RGB LEDs
*/
#ifndef SAUL_PWM_NO_RGB
#define SAUL_PWM_RGB_NUMOF ARRAY_SIZE(saul_pwm_rgb_params)
#else
#define SAUL_PWM_RGB_NUMOF 0
static const saul_pwm_rgb_params_t saul_pwm_rgb_params[0];
#endif
/**
* @brief Memory for the registry RGB LED entries
*/
/* The static variable will be unused in the 0 case and thus not emitted. */
static saul_reg_t saul_reg_entries_rgb[SAUL_PWM_RGB_NUMOF];
/**
* @brief Memory for the registry dimmer entries
*/
/* The static variable will be unused in the 0 case and thus not emitted. */
static saul_reg_t saul_reg_entries_dimmer[SAUL_PWM_DIMMER_NUMOF];
/**
* @brief Reference to the driver for single-channel dimmers
*/
extern saul_driver_t dimmer_saul_driver;
/**
* @brief Reference to the driver for RGB LEDs
*/
extern saul_driver_t rgb_saul_driver;
/**
* Configure a PWM driver for LED output (1kHz, 8bit)
*/
static int configure(pwm_t dev)
{
LOG_DEBUG("[auto_init_saul] initializing PWM %u for LED operation,", dev);
uint32_t freq = pwm_init(dev, PWM_LEFT, SAUL_PWM_FREQ, saul_pwm_resolution);
LOG_DEBUG(" actual frequency %lu,\n", freq);
return freq != 0 ? 0 : -ENOTSUP;
}
/**
* Configure the PWM driver at the given index (inside saul_pwm_dimmer_params,
* overflowing into saul_pwm_rgb_params) unless that device came up previously,
* in which case the function returns without any action.
* */
static int configure_on_first_use(pwm_t dev, unsigned index)
{
/* Work around -Werror=type-limits that would otherwise trigger */
unsigned dimmer_numof = SAUL_PWM_DIMMER_NUMOF;
for (unsigned i = 0; i < dimmer_numof; ++i) {
pwm_t currentdev = saul_pwm_dimmer_params[i].channel.dev;
if (currentdev == dev) {
if (i == index) {
return configure(dev);
}
return 0;
}
}
/* Work around -Werror=type-limits that would otherwise trigger */
unsigned rgb_numof = SAUL_PWM_RGB_NUMOF;
for (unsigned i = 0; i < rgb_numof; ++i) {
for (int j = 0; j < 3; ++j) {
unsigned index = SAUL_PWM_DIMMER_NUMOF + i * 3 + j;
pwm_t currentdev = saul_pwm_rgb_params[i].channels[j].dev;
if (currentdev == dev) {
if (i == index) {
return configure(dev);
}
return 0;
}
}
}
return -ENOENT;
}
void auto_init_saul_pwm(void)
{
/* Work around -Werror=type-limits that would otherwise trigger */
unsigned dimmer_numof = SAUL_PWM_DIMMER_NUMOF;
for (unsigned i = 0; i < dimmer_numof; i++) {
const saul_pwm_dimmer_params_t *p = &saul_pwm_dimmer_params[i];
LOG_DEBUG("[auto_init_saul] initializing dimmer #%u\n", i);
saul_reg_entries_dimmer[i].dev = (void*)p;
saul_reg_entries_dimmer[i].name = p->name;
saul_reg_entries_dimmer[i].driver = &dimmer_saul_driver;
int err = configure_on_first_use(p->channel.dev, i);
if (err != 0) {
LOG_ERROR(
"[auto_init_saul] Error initializing device for dimmer #%u\n",
i);
/* not `continue`ing: we could run into this on a non-first use and
* then we couldn't break either */
}
/* set initial dark state */
phydat_t s;
s.val[0] = 0;
saul_reg_entries_dimmer[i].driver->write(p, &s);
/* add to registry */
saul_reg_add(&(saul_reg_entries_dimmer[i]));
}
/* Work around -Werror=type-limits that would otherwise trigger */
unsigned rgb_numof = SAUL_PWM_RGB_NUMOF;
for (unsigned i = 0; i < rgb_numof; i++) {
const saul_pwm_rgb_params_t *p = &saul_pwm_rgb_params[i];
LOG_DEBUG("[auto_init_saul] initializing RGB #%u\n", i);
saul_reg_entries_rgb[i].dev = (void*)p;
saul_reg_entries_rgb[i].name = p->name;
saul_reg_entries_rgb[i].driver = &rgb_saul_driver;
for (int j = 0; j < 3; ++j) {
unsigned index = SAUL_PWM_DIMMER_NUMOF + i * 3 + j;
int err = configure_on_first_use(p->channels[j].dev, index);
if (err != 0) {
LOG_ERROR(
"[auto_init_saul] Error initializing device for RGB #%u/%u\n",
i, j);
/* not `continue`ing: we could run into this on a non-first use and
* then we couldn't break either */
}
}
/* set initial dark state */
phydat_t s;
s.val[0] = 0;
s.val[1] = 0;
s.val[2] = 0;
saul_reg_entries_rgb[i].driver->write(p, &s);
/* add to registry */
saul_reg_add(&(saul_reg_entries_rgb[i]));
}
}

View File

@ -35,6 +35,10 @@ void saul_init_devs(void)
extern void auto_init_gpio(void);
auto_init_gpio();
}
if (IS_USED(MODULE_SAUL_PWM)) {
extern void auto_init_saul_pwm(void);
auto_init_saul_pwm();
}
if (IS_USED(MODULE_SAUL_NRF_TEMPERATURE)) {
extern void auto_init_nrf_temperature(void);
auto_init_nrf_temperature();

129
drivers/saul/pwm_saul.c Normal file
View File

@ -0,0 +1,129 @@
/*
* Copyright (C) 2015 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_saul
* @{
*
* @file
* @brief SAUL wrapper for PWM pins
*
* @author Christian Amsüss <chrysn@fsfe.org>
*
* @}
*/
#include "saul.h"
#include "phydat.h"
#include "periph/pwm.h"
#include "saul/periph.h"
#include "bitarithm.h"
/**
* Find factor and shiftback such that for each value entry in the phydat, the
* resulting PWM duty cycle would be (value * factor) >> shiftback.
*
* This also makes the maximum legal input value for that input (which allows
* clipping the input to a value that doesn't wrap during that multiplication,
* or just to err out). If future versions of this take the scale into account,
* they will adjust the maximum accordingly.
*/
static int extract_scaling(const phydat_t *state, int *factor, int *shiftback, int *max)
{
if (state->scale != 0) {
return -ECANCELED;
}
/** Number of bits i by which we can shift the calculation (value * (255 << i)/100) >> i
* to get a better result than value * 2 (which would otherwise happen in integers) */
int shift100 = bitarithm_msb(INT_MAX) - bitarithm_msb(saul_pwm_resolution);
switch (state->unit) {
case UNIT_UNDEF:
case UNIT_NONE:
*factor = 1;
*shiftback = 0;
*max = saul_pwm_resolution;
break;
case UNIT_BOOL:
*factor = saul_pwm_resolution;
*shiftback = 0;
*max = 1;
break;
case UNIT_PERCENT:
*factor = ((int)saul_pwm_resolution << shift100) / 100;
*shiftback = shift100;
*max = 100;
break;
default:
return -ECANCELED;
}
return 0;
}
static inline void setchan(const saul_pwm_channel_t *chan, uint16_t value)
{
pwm_set(chan->dev,
chan->channel,
(chan->flags & SAUL_PWM_INVERTED) ? saul_pwm_resolution - value : value);
}
static int write_dimmer(const void *dev, phydat_t *state)
{
const saul_pwm_dimmer_params_t *p = dev;
int factor, shiftback, max;
int err = extract_scaling(state, &factor, &shiftback, &max);
if (err < 0) {
return err;
}
if (state->val[0] < 0 || state->val[0] > max) {
return -ECANCELED;
}
setchan(&p->channel, (state->val[0] * factor) >> shiftback);
return 3;
}
const saul_driver_t dimmer_saul_driver = {
.read = saul_notsup,
.write = write_dimmer,
.type = SAUL_ACT_DIMMER
};
static int write_rgb(const void *dev, phydat_t *state)
{
const saul_pwm_rgb_params_t *p = dev;
int factor, shiftback, max;
int err = extract_scaling(state, &factor, &shiftback, &max);
if (err < 0) {
return err;
}
for (int i = 0; i < 3; ++i) {
if (state->val[i] < 0 || state->val[i] > max) {
return -ECANCELED;
}
}
for (int i = 0; i < 3; ++i) {
setchan(&p->channels[i], (state->val[i] * factor) >> shiftback);
}
return 3;
}
const saul_driver_t rgb_saul_driver = {
.read = saul_notsup,
.write = write_rgb,
.type = SAUL_ACT_LED_RGB
};

View File

@ -106,6 +106,7 @@ PSEUDOMODULES += saul_adc
PSEUDOMODULES += saul_default
PSEUDOMODULES += saul_gpio
PSEUDOMODULES += saul_nrf_temperature
PSEUDOMODULES += saul_pwm
PSEUDOMODULES += scanf_float
PSEUDOMODULES += sched_cb
PSEUDOMODULES += semtech_loramac_rx