Merge pull request #10556 from gschorcht/drivers_pca9685

drivers: support for NXP PCA9685 I2C 16-channel, 12-bit PWM controller
This commit is contained in:
benpicco 2019-09-15 19:20:29 +02:00 committed by GitHub
commit 6c95081e42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1406 additions and 0 deletions

View File

@ -388,6 +388,12 @@ ifneq (,$(filter nvram_spi,$(USEMODULE)))
USEMODULE += xtimer USEMODULE += xtimer
endif endif
ifneq (,$(filter pca9685,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_i2c
USEMODULE += xtimer
endif
ifneq (,$(filter pcd8544,$(USEMODULE))) ifneq (,$(filter pcd8544,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_spi FEATURES_REQUIRED += periph_spi

View File

@ -210,6 +210,10 @@ ifneq (,$(filter nrf24l01p,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/nrf24l01p/include USEMODULE_INCLUDES += $(RIOTBASE)/drivers/nrf24l01p/include
endif endif
ifneq (,$(filter pca9685,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/pca9685/include
endif
ifneq (,$(filter pcd8544,$(USEMODULE))) ifneq (,$(filter pcd8544,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/pcd8544/include USEMODULE_INCLUDES += $(RIOTBASE)/drivers/pcd8544/include
endif endif

322
drivers/include/pca9685.h Normal file
View File

@ -0,0 +1,322 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* 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.
*/
/**
* @defgroup drivers_pca9685 PCA9685 I2C PWM controller
* @ingroup drivers_actuators
* @ingroup drivers_saul
* @brief Device driver for the NXP PCA9685
*
* ## Overview
*
* The driver supports the NXP PCA9685 16-channel, 12-bit PWM LED controller
* connected to I2C. Although the controller is optimized for LED control,
* the 12-bit resolution also allows the control of servos with a resolution
* of 4 us at 60 Hz refresh signal.
*
* The following features of the PCA9685 are supported by the driver:
*
* - 16 channels with 12-bit resolution
* - Refresh rates from 24 Hz to 1526 Hz with internal 25 MHz oscillator
* - Totem pole outputs with 25 mA as sink and 10 mA as source at 5V
* - Software programmable open-drain output selection
* - Inverted outputs
* - Active LOW Output Enable (OE) input pin
* - External clock input with max. 50 MHz
*
* ## Usage
*
* The driver interface is kept as compatible as possible with the peripheral
* PWM interface. The only differences are that
*
* - functions have the prefix `pca9685_` and
* - functions require an additional parameter, the pointer to the PWM
* device of type #pca9685_t.
*
* Please refer the test application in `tests/driver_pca9685` for an example
* on how to use the driver.
*
* ## SAUL Capabilities
*
* The driver provides SAUL capabilities that are compatible to the SAUL
* actuators of type #SAUL_ACT_SERVO.
*
* Each PCA9685 channel can be mapped directly to SAUL by defining an
* according entry in \c PCA9685_SAUL_PWM_PARAMS. Please refer file
* `$RIOTBASE/drivers/pca9685/include/pca9685_params.h` for an example.
*
* pca9685_saul_pwm_params_t pca9685_saul_pwm_params[] = {
* {
* .name = "PCA9685-0:0",
* .dev = 0,
* .channel = 0,
* .initial = (PCA9685_PARAM_RES >> 1),
* },
* {
* .name = "PCA9685-0:1",
* .dev = 0,
* .channel = 1,
* .initial = (PCA9685_PARAM_RES >> 2),
* },
* {
* .name = "PCA9685-0:2",
* .dev = 0,
* .channel = 2,
* .initial = (PCA9685_PARAM_RES >> 3),
* },
* };
*
* For each PWM channel that should be used with SAUL, an entry with a name,
* the device, the channel, and the initial value has to be defined as shown
* above.
*
* @{
*
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*/
#ifndef PCA9685_H
#define PCA9685_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdbool.h>
#include <stdint.h>
#include "periph/gpio.h"
#include "periph/i2c.h"
#include "periph/pwm.h"
/**
* @name PCA9685 I2C slave addresses
*
* PCA9685 offers 64 possible hardware-programmable I2C slave addresses.
* Therefore the I2C slave address is defined as an offset in the range
* from 0 to 63 to a base address #PCA9685_I2C_BASE_ADDR. PCA9685 I2C
* slave addresses are then in the range from #PCA9685_I2C_BASE_ADDR + 0 to
* #PCA9685_I2C_BASE_ADDR + 63
*
* Four I2C slave addresses have special meaning when they are enabled, the
* All Call address (enabled by default) and three Sub Call addresses
* disabled by default). These addresses can be used to address either all or
* sub groups of PCF9695 controllers at the same time.
*
* @{
*/
#define PCA9685_I2C_BASE_ADDR (0x40) /**< I2C slave base address */
#define PCA9685_I2C_ALLCALLADDR (0x70) /**< Default All Call adress */
#define PCA9685_I2C_SUBADR1 (0x71) /**< Default Sub Call adress 1 */
#define PCA9685_I2C_SUBADR2 (0x72) /**< Default Sub Call adress 2 */
#define PCA9685_I2C_SUBADR3 (0x73) /**< Default Sub Call adress 3 */
/** @} */
/**
* @brief Number of PWM channels provided by PCA9685
*/
#define PCA9685_CHANNEL_NUM (16U)
/**
* @brief Internal PCA9685 channel resolution is 12-bit
*/
#define PCA9685_RESOLUTION (1 << 12)
/**
* @brief Internal PCA9685 oscilator frequency is 25 MHz
*/
#define PCA9685_OSC_FREQ (25000000)
/**
* @brief Maximum external clock frequency is 50 MHz
*/
#define PCA9685_EXT_FERQ_MAX (50000000)
/**
* @brief PCA9685 driver error codes
*/
typedef enum {
PCA9685_OK = 0, /**< Success */
PCA9685_ERROR_I2C = 1, /**< I2C communication error */
} pca9685_error_t;
/**
* @brief PCA9685 output driver mode
*
* The output driver mode defines how the outputs are configured.
*/
typedef enum {
PCA9685_OPEN_DRAIN = 0, /**< Open-drain structure ouptut */
PCA9685_TOTEM_POLE = 1, /**< Totem pole structure output */
} pca9685_out_drv_t;
/**
* @brief PCA9685 output-not-enabled mode
*
* The output-not-enabled mode defines how the outputs behave when the
* active LOW output enable pin /OE is HIGH.
*/
typedef enum {
PCA9685_OFF = 0, /**< If /OE pin is HIGH, outputs are LOW */
PCA9685_OUT_DRV = 1, /**< Outputs depend on the output driver mode
pca9685_params_t::out_drv. If /OE pin is HIGH,
it is high-impedance for PCA9685_OPEN_DRAIN,
and HIGH for PCA9685_TOTEM_POLE */
PCA9685_HIHGH_Z = 2, /**< If /OE pin is HIGH, outputs are high-impedance */
} pca9685_out_ne_t;
/**
* @brief PCA9685 device initialization parameters
*/
typedef struct {
i2c_t i2c_dev; /**< I2C device, default I2C_DEV(0) */
uint8_t i2c_addr; /**< I2C slave address */
pwm_mode_t mode; /**< PWM mode for all channels: #PWM_LEFT, #PWM_CENTER,
#PWM_RIGHT supported, (default PWM_CENTER) */
uint32_t freq; /**< PWM frequency in Hz (default 100) */
uint16_t res; /**< PWM resolution (default 4096) */
bool inv; /**< Invert outputs, e.g., for LEDs (default yes) */
uint32_t ext_freq; /**< If not 0, EXTCLK pin is used with this frequency */
gpio_t oe_pin; /**< Active LOW output enable pin /OE. If #GPIO_UNDEF,
the pin is not used. (default #GPIO_UNDEF). */
pca9685_out_drv_t out_drv; /**< Output driver mode */
pca9685_out_ne_t out_ne; /**< Output-not-enabled mode */
} pca9685_params_t;
/**
* @brief PCA9685 PWM device data structure type
*/
typedef struct {
pca9685_params_t params; /**< Device initialization parameters */
bool powered_on; /**< Devices is powered on if true */
} pca9685_t;
#if MODULE_SAUL || DOXYGEN
/**
* @brief PCA9685 configuration structure for mapping PWM channels to SAUL
*/
typedef struct {
const char *name; /**< name of the PCA9685 device */
unsigned int dev; /**< index of the PCA9685 device */
uint8_t channel; /**< channel of the PCA9685 device */
uint16_t initial; /**< initial duty-cycle value*/
} pca9685_saul_pwm_params_t;
#endif
/**
* @brief Initialize the PCA9685 PWM device driver
*
* The function initializes the driver. After calling this function, the PWM
* device is in low-power sleep mode (powered off), all outputs off. Before
* the PWM device can be used, it has to be initialized with #pca9685_pwm_init.
*
* @param[in] dev Device descriptor of the PCA9685 to be initialized
* @param[in] params Configuration parameters, see #pca9685_params_t
*
* @retval PCA9685_OK on success
* @retval PCA9685_ERROR_* a negative error code on error, see
* #pca9685_error_t
*/
int pca9685_init(pca9685_t *dev, const pca9685_params_t *params);
/**
* @brief Initialize the PCA9685 PWM device
*
* The function initializes the PWM device with the given parameters that are
* used for all channels. After calling this funcion, the PWM device is
* operational (powered on). That is, all outputs are active with the given
* parameters and the same duty cycle value as before the call.
*
* @note
* - PCA9685 works with internally with a resolution of 12 bit = 4096.
* Using a resolution that is not a power of two, will cause inaccuracy due
* to aligment errors when scaling down the internal resolution to the
* configured resolution.
* - Frequencies from 24 Hz to 1526 Hz can be used with PCF9865.
*
* @param[in] dev Device descriptor of the PCA9685
* @param[in] mode PWM mode, left, right or center aligned
* @param[in] freq PWM frequency in Hz [24...1526]
* @param[in] res PWM resolution [2...4096], should be a power of two
*
* @retval >0 actual frequency on success
* @retval 0 on error
*/
uint32_t pca9685_pwm_init(pca9685_t *dev, pwm_mode_t mode, uint32_t freq,
uint16_t res);
/**
* @brief Set the duty-cycle for a given channel or all channels of the
* given PCA9685 PWM device
*
* The duty-cycle is set in relation to the chosen resolution of the given
* device. If value > resolution, value is set to resolution.
*
* If the given channel is #PCA9685_CHANNEL_NUM, all channels are set to the
* same duty cycle at the same time with only one I2C bus access.
*
* @param[in] dev Device descriptor of the PCA9685
* @param[in] channel Channel of PCA9685 to set, if #PCA9685_CHANNEL_NUM
* all channels are set to the desired duty-cycle
* @param[in] value Desired duty-cycle to set
*/
void pca9685_pwm_set(pca9685_t *dev, uint8_t channel, uint16_t value);
/**
* @brief Resume PWM generation on the given PCA9685 device
*
* When this function is called, the given PWM device is powered on and
* continues its previously configured operation. The duty cycle of each channel
* will be the value that was last set.
*
* This function must not be called before the PWM device was initialized.
*
* @param[in] dev Device descriptor of the PCA9685
*/
void pca9685_pwm_poweron(pca9685_t *dev);
/**
* @brief Stop the PWM generation on the given PCA9685 device
*
* This function switches the PCA9685 into sleep mode which turns off the
* internal oscillator. This disables the PWM generation on all configured.
* If the active LOW output enable pin /OE is used, the signal is set to HIGH.
* Dependent on the pca9685_params_t::out_drv and pca9685_params_t::out_ne
* parameters, the outputs are set 0, 1 or high-impedance. All channel
* duty-cycle values are preserved.
*
* @param[in] dev Device descriptor of the PCA9685
*/
void pca9685_pwm_poweroff(pca9685_t *dev);
/**
* @brief Get the number of available channels of the given PCA9685 device
* @param[in] dev Device descriptor of the PCA9685
* @return Number of channels available
*/
static inline uint8_t pca9685_pwm_channels(pca9685_t *dev)
{
(void)dev;
return PCA9685_CHANNEL_NUM;
}
#ifdef __cplusplus
}
#endif
#endif /* PCA9685_H */
/** @} */

1
drivers/pca9685/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,143 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* 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_pca9685
* @brief Default configuration for the PCA9685 I2C PWM controller
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
* @{
*/
#ifndef PCA9685_PARAMS_H
#define PCA9685_PARAMS_H
#include "board.h"
#include "saul_reg.h"
#include "pca9685.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name Set default configuration parameters
* @{
*/
#ifndef PCA9685_PARAM_DEV
/** device is I2C_DEV(0) */
#define PCA9685_PARAM_DEV I2C_DEV(0)
#endif
#ifndef PCA9685_PARAM_ADDR
/** device address is PCA9685_I2C_ADDR */
#define PCA9685_PARAM_ADDR (PCA9685_I2C_BASE_ADDR + 0)
#endif
#ifndef PCA9685_PARAM_INV
/** Invert outputs: yes */
#define PCA9685_PARAM_INV (false)
#endif
#ifndef PCA9685_PARAM_MODE
/** PWM mode for all channels: PWM_LEFT */
#define PCA9685_PARAM_MODE (PWM_LEFT)
#endif
#ifndef PCA9685_PARAM_FREQ
/** PWM frequency in Hz: 100 */
#define PCA9685_PARAM_FREQ (100)
#endif
#ifndef PCA9685_PARAM_RES
/** PWM resolution: 4096 */
#define PCA9685_PARAM_RES (4096)
#endif
#ifndef PCA9685_PARAM_OE_PIN
/** Output enable pin: not used */
#define PCA9685_PARAM_OE_PIN (GPIO_UNDEF)
#endif
#ifndef PCA9685_PARAM_EXT_FREQ
/** EXTCLK frequency and pin: not used */
#define PCA9685_PARAM_EXT_FREQ (0)
#endif
#ifndef PCA9685_PARAM_OUT_DRV
/** Output driver mode: totem pole */
#define PCA9685_PARAM_OUT_DRV (PCA9685_TOTEM_POLE)
#endif
#ifndef PCA9685_PARAM_OUT_NE
/** Output driver mode: totem pole */
#define PCA9685_PARAM_OUT_NE (PCA9685_OFF)
#endif
#ifndef PCA9685_PARAMS
#define PCA9685_PARAMS { \
.i2c_dev = PCA9685_PARAM_DEV, \
.i2c_addr = PCA9685_PARAM_ADDR, \
.inv = PCA9685_PARAM_INV, \
.mode = PCA9685_PARAM_MODE, \
.freq = PCA9685_PARAM_FREQ, \
.res = PCA9685_PARAM_RES, \
.ext_freq = PCA9685_PARAM_EXT_FREQ, \
.oe_pin = PCA9685_PARAM_OE_PIN, \
.out_drv = PCA9685_PARAM_OUT_DRV, \
.out_ne = PCA9685_PARAM_OUT_NE, \
}
#endif /* PCA9685_PARAMS */
#ifndef PCA9685_SAUL_PWM_PARAMS
/** Example for mapping PWM channels to SAUL */
#define PCA9685_SAUL_PWM_PARAMS { \
.name = "PCA9685-0:0", \
.dev = 0, \
.channel = 0, \
.initial = (PCA9685_PARAM_RES >> 1), \
}, \
{ \
.name = "PCA9685-0:1", \
.dev = 0, \
.channel = 1, \
.initial = (PCA9685_PARAM_RES >> 2), \
}, \
{ \
.name = "PCA9685-0:1", \
.dev = 0, \
.channel = 2, \
.initial = (PCA9685_PARAM_RES >> 3), \
},
#endif /* PCA9685_PARAMS */
/**@}*/
/**
* @brief Allocate some memory to store the actual configuration
*/
static const pca9685_params_t pca9685_params[] =
{
PCA9685_PARAMS
};
#if MODULE_SAUL || DOXYGEN
/**
* @brief Additional meta information to keep in the SAUL registry
*/
static const pca9685_saul_pwm_params_t pca9685_saul_pwm_params[] =
{
PCA9685_SAUL_PWM_PARAMS
};
#endif /* MODULE_SAUL || DOXYGEN */
#ifdef __cplusplus
}
#endif
#endif /* PCA9685_PARAMS_H */
/** @} */

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* 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_pca9685
* @brief Register definitions for the PCA9685 I2C PWM controller
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
* @{
*/
#ifndef PCA9685_REGS_H
#define PCA9685_REGS_H
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @name Register addresses
* @{
*/
#define PCA9685_REG_MODE1 (0x00) /**< Mode1 register */
#define PCA9685_REG_MODE2 (0x01) /**< Mode2 register */
#define PCA9685_REG_SUBADR1 (0x02) /**< I2C bus subaddress 1 register */
#define PCA9685_REG_SUBADR2 (0x03) /**< I2C bus subaddress 2 register */
#define PCA9685_REG_SUBADR3 (0x04) /**< I2C bus subaddress 3 register */
#define PCA9685_REG_ALLCALLADDR (0x05) /**< LED All Call I 2 C-bus address */
#define PCA9685_REG_LED0_ON_L (0x06) /**< LED0 ON control low byte */
#define PCA9685_REG_ALL_LED_ON_L (0xfa) /**< Load all LEDn_OFF register low byte */
#define PCA9685_REG_ALL_LED_ON_H (0xfb) /**< Load all LEDn_OFF register high byte */
#define PCA9685_REG_ALL_LED_OFF_L (0xfc) /**< Load all LEDn_OFF register low byte */
#define PCA9685_REG_ALL_LED_OFF_H (0xfd) /**< Load all LEDn_OFF register high byte */
#define PCA9685_REG_ALL_LED_ON (0xfa) /**< Load all LEDn_OFF register word */
#define PCA9685_REG_ALL_LED_OFF (0xfc) /**< Load all LEDn_OFF register word */
#define PCA9685_REG_PRE_SCALE (0xfe) /**< Prescaler for PWM output frequency */
#define PCA9685_REG_TEST_MODE (0xff) /**< Enter test mode register */
#define PCA9685_REG_LED_ON(n) (0x06 + (n << 2)) /**< LEDn ON control word */
#define PCA9685_REG_LED_OFF(n) (0x08 + (n << 2)) /**< LEDn OFF control word */
#define PCA9685_REG_LED_ON_L(n) (0x06 + (n << 2)) /**< LEDn ON control low byte */
#define PCA9685_REG_LED_ON_H(n) (0x07 + (n << 2)) /**< LEDn ON control high byte */
#define PCA9685_REG_LED_OFF_L(n) (0x08 + (n << 2)) /**< LEDn OFF control low byte */
#define PCA9685_REG_LED_OFF_H(n) (0x09 + (n << 2)) /**< LEDn OFF control high byte */
/** @} */
/**
* @name Register structures
* @{
*/
/* PCA9685_REG_MODE1 */
#define PCA9685_MODE1_RESTART (0x80) /**< State of restart logic, write 1 to clear */
#define PCA9685_MODE1_EXTCLK (0x40) /**< Use EXTCLK pin */
#define PCA9685_MODE1_AI (0x20) /**< Enable register auto-increment*/
#define PCA9685_MODE1_SLEEP (0x10) /**< Enter low power mode, PWM is off */
#define PCA9685_MODE1_SUB1 (0x08) /**< Enable I2C subaddress 1 */
#define PCA9685_MODE1_SUB2 (0x04) /**< Enable I2C subaddress 2 */
#define PCA9685_MODE1_SUB3 (0x02) /**< Enable I2C subaddress 3 */
#define PCA9685_MODE1_ALLCALL (0x01) /**< Enable I2C all call address */
/* PCA9685_REG_MODE2 */
#define PCA9685_MODE2_INVERT (0x10) /**< Invert outputs */
#define PCA9685_MODE2_OCH (0x08) /**< Output change change configuration */
#define PCA9685_MODE2_OUTDRV (0x04) /**< Output driver configuration */
#define PCA9685_MODE2_OUTNE (0x03) /**< Output enabled configuration */
/** @} */
/**
* @name Register value definitions
* @{
*/
#define PCA9685_LED_ON (0x1000) /* LEDs on word */
#define PCA9685_LED_OFF (0x1000) /* LEDs off word */
#define PCA9685_LED_ON_H (0x10) /* LEDs on high byte */
#define PCA9685_LED_OFF_H (0x10) /* LEDs off high byte */
#define PCA9685_ALL_LED_ON (0x1000) /* All LEDs on word */
#define PCA9685_ALL_LED_OFF (0x1000) /* All LEDs off word */
#define PCA9685_ALL_LED_ON_H (0x10) /* All LEDs on high byte */
#define PCA9685_ALL_LED_OFF_H (0x10) /* All LEDs off word */
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* PCA9685_REGS_H */
/** @} */

369
drivers/pca9685/pca9685.c Normal file
View File

@ -0,0 +1,369 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* 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_pca9685
* @brief Device driver for the PCA9685 I2C PWM controller
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
* @{
*/
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "pca9685_regs.h"
#include "pca9685.h"
#include "irq.h"
#include "log.h"
#include "xtimer.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
#if ENABLE_DEBUG
#define ASSERT_PARAM(cond) \
do { \
if (!(cond)) { \
DEBUG("[pca9685] %s: %s\n", \
__func__, "parameter condition (" #cond ") not fulfilled"); \
assert(cond); \
} \
} while(0)
#define DEBUG_DEV(f, d, ...) \
DEBUG("[pca9685] %s i2c dev=%d addr=%02x: " f "\n", \
__func__, d->params.i2c_dev, dev->params.i2c_addr, ## __VA_ARGS__)
#else /* ENABLE_DEBUG */
#define ASSERT_PARAM(cond) assert(cond)
#define DEBUG_DEV(f, d, ...)
#endif /* ENABLE_DEBUG */
#define ERROR_DEV(f, d, ...) \
LOG_ERROR("[pca9685] %s i2c dev=%d addr=%02x: " f "\n", \
__func__, d->params.i2c_dev, dev->params.i2c_addr, ## __VA_ARGS__)
#define EXEC_RET(f) \
do { \
int _r; \
if ((_r = f) != PCA9685_OK) { \
DEBUG("[pca9685] %s: error code %d\n", __func__, _r); \
return _r; \
} \
} while(0)
#define EXEC_RET_CODE(f, c) \
do { \
int _r; \
if ((_r = f) != PCA9685_OK) { \
DEBUG("[pca9685] %s: error code %d\n", __func__, _r); \
return c; \
} \
} while(0)
#define EXEC(f) \
do { \
int _r; \
if ((_r = f) != PCA9685_OK) { \
DEBUG("[pca9685] %s: error code %d\n", __func__, _r); \
return; \
} \
} while(0)
/** Forward declaration of functions for internal use */
static int _is_available(const pca9685_t *dev);
static int _init(pca9685_t *dev);
static void _set_reg_bit(uint8_t *byte, uint8_t mask, uint8_t bit);
static int _read(const pca9685_t *dev, uint8_t reg, uint8_t *data, uint32_t len);
static int _write(const pca9685_t *dev, uint8_t reg, const uint8_t *data, uint32_t len);
static int _update(const pca9685_t *dev, uint8_t reg, uint8_t mask, uint8_t data);
inline static int _write_word(const pca9685_t *dev, uint8_t reg, uint16_t word);
int pca9685_init(pca9685_t *dev, const pca9685_params_t *params)
{
/* some parameter sanity checks */
ASSERT_PARAM(dev != NULL);
ASSERT_PARAM(params != NULL);
ASSERT_PARAM(params->ext_freq <= 50000000);
dev->powered_on = false;
dev->params = *params;
DEBUG_DEV("params=%p", dev, params);
if (dev->params.oe_pin != GPIO_UNDEF) {
/* init the pin an disable outputs first */
gpio_init(dev->params.oe_pin, GPIO_OUT);
gpio_set(dev->params.oe_pin);
}
/* test whether PWM device is available */
EXEC_RET(_is_available(dev));
/* init the PWM device */
EXEC_RET(_init(dev));
return PCA9685_OK;
}
uint32_t pca9685_pwm_init(pca9685_t *dev, pwm_mode_t mode, uint32_t freq,
uint16_t res)
{
/* some parameter sanity checks */
ASSERT_PARAM(dev != NULL);
ASSERT_PARAM(freq >= 24 && freq <= 1526);
ASSERT_PARAM(res >= 2 && res <= 4096);
ASSERT_PARAM(mode == PWM_LEFT || mode == PWM_CENTER || mode == PWM_RIGHT);
DEBUG_DEV("mode=%u freq=%"PRIu32" res=%u", dev, mode, freq, res);
dev->params.mode = mode;
dev->params.freq = freq;
dev->params.res = res;
/* prescale can only be set while in sleep mode (powered off) */
if (dev->powered_on) {
pca9685_pwm_poweroff(dev);
}
/* prescale = round(clk / (PCA9685_RESOLUTION * freq)) - 1; */
uint32_t div = PCA9685_RESOLUTION * freq;
uint8_t byte = ((dev->params.ext_freq ? dev->params.ext_freq
: PCA9685_OSC_FREQ) + div/2) / div - 1;
EXEC_RET(_write(dev, PCA9685_REG_PRE_SCALE, &byte, 1));
if (!dev->powered_on) {
pca9685_pwm_poweron(dev);
}
return freq;
}
void pca9685_pwm_set(pca9685_t *dev, uint8_t chn, uint16_t val)
{
ASSERT_PARAM(dev != NULL);
ASSERT_PARAM(chn <= PCA9685_CHANNEL_NUM);
DEBUG_DEV("chn=%u val=%u", dev, chn, val);
/* limit val to resolution */
val = (val >= dev->params.res) ? dev->params.res : val;
uint16_t on;
uint16_t off;
if (val == 0) {
/* full off */
on = 0;
off = PCA9685_LED_OFF;
}
else if (val == dev->params.res) {
/* full on */
on = PCA9685_LED_ON;
off = 0;
}
else {
/* duty = scale(2^12) / resolution * value */
uint32_t duty = PCA9685_RESOLUTION * val / dev->params.res;
switch (dev->params.mode) {
case PWM_LEFT: on = 0;
off = on + duty;
break;
case PWM_CENTER: on = (PCA9685_RESOLUTION - duty) >> 1;
off = on + duty;
break;
case PWM_RIGHT: off = PCA9685_RESOLUTION - 1;
on = off - duty;
break;
default: return;
}
}
DEBUG_DEV("on=%u off=%u", dev, on, off);
if (chn == PCA9685_CHANNEL_NUM) {
EXEC(_write_word(dev, PCA9685_REG_ALL_LED_ON, on));
EXEC(_write_word(dev, PCA9685_REG_ALL_LED_OFF, off));
}
else {
EXEC(_write_word(dev, PCA9685_REG_LED_ON(chn), on));
EXEC(_write_word(dev, PCA9685_REG_LED_OFF(chn), off));
}
}
void pca9685_pwm_poweron(pca9685_t *dev)
{
ASSERT_PARAM(dev != NULL);
DEBUG_DEV("", dev);
uint8_t byte;
/* read MODE1 register */
EXEC(_read(dev, PCA9685_REG_MODE1, &byte, 1));
/* check if RESTART bit is 1 */
if (byte & PCA9685_MODE1_RESTART) {
/* clear the SLEEP bit */
byte &= ~PCA9685_MODE1_SLEEP;
EXEC(_write(dev, PCA9685_REG_MODE1, &byte, 1));
/* allow 500 us for oscilator to stabilize */
xtimer_usleep(500);
/* clear the RESTART bit to start all PWM channels*/
EXEC(_update(dev, PCA9685_REG_MODE1, PCA9685_MODE1_RESTART, 1));
}
else {
EXEC(_update(dev, PCA9685_REG_MODE1, PCA9685_MODE1_SLEEP, 0));
/* allow 500 us for oscilator to stabilize */
xtimer_usleep(500);
/* clear the RESTART bit to start all PWM channels*/
EXEC(_update(dev, PCA9685_REG_MODE1, PCA9685_MODE1_RESTART, 1));
}
if (dev->params.oe_pin != GPIO_UNDEF) {
gpio_clear(dev->params.oe_pin);
}
dev->powered_on = true;
}
void pca9685_pwm_poweroff(pca9685_t *dev)
{
ASSERT_PARAM(dev != NULL);
DEBUG_DEV("", dev);
if (dev->params.oe_pin != GPIO_UNDEF) {
gpio_set(dev->params.oe_pin);
}
/* set sleep mode */
EXEC(_update(dev, PCA9685_REG_MODE1, PCA9685_MODE1_SLEEP, 1));
dev->powered_on = false;
}
/** Functions for internal use only */
static int _is_available(const pca9685_t *dev)
{
uint8_t byte;
/* simply tests to read */
return _read(dev, PCA9685_REG_MODE1, &byte, 1);
}
static int _init(pca9685_t *dev)
{
/* set Auto-Increment flag */
EXEC_RET(_update(dev, PCA9685_REG_MODE1, PCA9685_MODE1_AI, 1));
/* switch off all channels */
EXEC_RET(_write_word(dev, PCA9685_REG_ALL_LED_OFF, PCA9685_ALL_LED_OFF));
/* set Auto-Increment flag */
uint8_t byte = 0;
_set_reg_bit(&byte, PCA9685_MODE2_INVERT, dev->params.inv);
_set_reg_bit(&byte, PCA9685_MODE2_OUTDRV, dev->params.out_drv);
_set_reg_bit(&byte, PCA9685_MODE2_OUTNE, dev->params.out_ne);
EXEC_RET(_write(dev, PCA9685_REG_MODE2, &byte, 1));
/* set Sleep mode, Auto-Increment, Restart, All call and External Clock */
byte = 0;
_set_reg_bit(&byte, PCA9685_MODE1_AI, 1);
_set_reg_bit(&byte, PCA9685_MODE1_SLEEP, 1);
_set_reg_bit(&byte, PCA9685_MODE1_RESTART, 1);
_set_reg_bit(&byte, PCA9685_MODE1_ALLCALL, 1);
EXEC_RET(_write(dev, PCA9685_REG_MODE1, &byte, 1));
/* must be done only in sleep mode */
_set_reg_bit(&byte, PCA9685_MODE1_EXTCLK, dev->params.ext_freq ? 1 : 0);
EXEC_RET(_write(dev, PCA9685_REG_MODE1, &byte, 1));
return PCA9685_OK;
}
static int _read(const pca9685_t *dev, uint8_t reg, uint8_t *data, uint32_t len)
{
DEBUG_DEV("reg=%02x data=%p len=%"PRIu32"", dev, reg, data, len);
/* acquire the I2C device */
if (i2c_acquire(dev->params.i2c_dev)) {
DEBUG_DEV("could not aquire I2C bus", dev);
return -PCA9685_ERROR_I2C;
}
if (i2c_read_regs(dev->params.i2c_dev,
dev->params.i2c_addr, reg, data, len, 0) != 0) {
i2c_release(dev->params.i2c_dev);
return -PCA9685_ERROR_I2C;
}
/* release the I2C device */
i2c_release(dev->params.i2c_dev);
return PCA9685_OK;
}
static int _write(const pca9685_t *dev, uint8_t reg, const uint8_t *data, uint32_t len)
{
DEBUG_DEV("reg=%02x data=%p len=%"PRIu32"", dev, reg, data, len);
if (i2c_acquire(dev->params.i2c_dev)) {
DEBUG_DEV("could not aquire I2C bus", dev);
return -PCA9685_ERROR_I2C;
}
if (i2c_write_regs(dev->params.i2c_dev,
dev->params.i2c_addr, reg, data, len, 0) != 0) {
i2c_release(dev->params.i2c_dev);
return -PCA9685_ERROR_I2C;
}
/* release the I2C device */
i2c_release(dev->params.i2c_dev);
return PCA9685_OK;
}
inline static int _write_word(const pca9685_t *dev, uint8_t reg, uint16_t data)
{
uint8_t bytes[2] = { data & 0xff, (data >> 8) & 0xff };
return _write (dev, reg, bytes, 2);
}
static int _update(const pca9685_t *dev, uint8_t reg, uint8_t mask, uint8_t data)
{
uint8_t byte;
/* read current register value */
EXEC_RET(_read(dev, reg, &byte, 1));
/* set masked bits to the given value */
_set_reg_bit(&byte, mask, data);
/* write back new register value */
EXEC_RET(_write(dev, reg, &byte, 1));
return PCA9685_OK;
}
static void _set_reg_bit(uint8_t *byte, uint8_t mask, uint8_t bit)
{
uint8_t shift = 0;
while (!((mask >> shift) & 0x01)) {
shift++;
}
*byte = ((*byte & ~mask) | ((bit << shift) & mask));
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* 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_pca9685
* @brief PCA9685 adaption to the RIOT actuator/sensor interface
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*/
#if MODULE_SAUL
#include <string.h>
#include "saul.h"
#include "pca9685.h"
extern pca9685_t pca9685_devs[];
static int set(const void *dev, phydat_t *data)
{
const pca9685_saul_pwm_params_t *p = (const pca9685_saul_pwm_params_t *)dev;
pca9685_pwm_set(&pca9685_devs[p->dev], p->channel, (uint16_t)data->val[0]);
return 1;
}
const saul_driver_t pca9685_pwm_saul_driver = {
.read = saul_notsup,
.write = set,
.type = SAUL_ACT_SERVO
};
#endif /* MODULE_SAUL */

View File

@ -486,6 +486,10 @@ void auto_init(void)
extern void auto_init_mpu9150(void); extern void auto_init_mpu9150(void);
auto_init_mpu9150(); auto_init_mpu9150();
#endif #endif
#ifdef MODULE_PCA9685
extern void auto_init_pca9685(void);
auto_init_pca9685();
#endif
#ifdef MODULE_PH_OEM #ifdef MODULE_PH_OEM
extern void auto_init_ph_oem(void); extern void auto_init_ph_oem(void);
auto_init_ph_oem(); auto_init_ph_oem();

View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2017 Mesotic SAS
*
* 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_pca9685
* @ingroup sys_auto_init_saul
* @brief Auto initialization of PCA9685 I2C PWM controller
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*/
#if MODULE_PCA9685 && MODULE_SAUL
#include "assert.h"
#include "log.h"
#include "saul_reg.h"
#include "saul/periph.h"
#include "pca9685.h"
#include "pca9685_params.h"
/**
* @brief Number of configured PCA9685 DAC devices
*/
#define PCA9685_NUM (ARRAY_SIZE(pca9685_params))
/**
* @brief Number of configured SAUL PCA9685 DAC channels
*/
#define PCA9685_SAUL_DAC_NUMOF (ARRAY_SIZE(pca9685_saul_pwm_params))
/**
* @brief Number of saul info
*/
#define PCA9685_INFO_NUM (ARRAY_SIZE(pca9685_saul_info))
/**
* @brief Allocate the memory for the PCA9685 DAC device descriptors
*/
pca9685_t pca9685_devs[PCA9685_NUM];
/**
* @brief Allocate the memory for PCA9685 DAC SAUL registry entries
*/
static saul_reg_t pca9685_saul_reg_entries[PCA9685_SAUL_DAC_NUMOF];
/**
* @brief Reference the PCA9685 DAC input mode driver struct
*/
extern saul_driver_t pca9685_pwm_in_saul_driver;
/**
* @brief Reference to the PCA9685 DAC output mode driver struct
*/
extern saul_driver_t pca9685_pwm_saul_driver;
void auto_init_pca9685(void)
{
for (unsigned int i = 0; i < PCA9685_NUM; i++) {
LOG_DEBUG("[auto_init_saul] initializing PCA9685 PWM dev #%u\n", i);
pca9685_init(&pca9685_devs[i], &pca9685_params[i]);
pca9685_pwm_init(&pca9685_devs[i], pca9685_params[i].mode,
pca9685_params[i].freq,
pca9685_params[i].res);
}
for (unsigned int i = 0; i < PCA9685_SAUL_DAC_NUMOF; i++) {
const pca9685_saul_pwm_params_t *p = &pca9685_saul_pwm_params[i];
LOG_DEBUG("[auto_init_saul] initializing PCA9685 channel #%u\n", i);
/* check the PCA9685 device index */
assert(p->dev < PCA9685_NUM);
pca9685_saul_reg_entries[i].dev = (void *)p;
pca9685_saul_reg_entries[i].name = p->name;
pca9685_saul_reg_entries[i].driver = &pca9685_pwm_saul_driver;
/* initialize the PCA9685 pin */
pca9685_pwm_set(&pca9685_devs[p->dev], p->channel, p->initial);
/* add to registry */
saul_reg_add(&(pca9685_saul_reg_entries[i]));
}
}
#else
typedef int dont_be_pedantic;
#endif /* MODULE_PCA9685 && MODULE_SAUL */

View File

@ -0,0 +1,12 @@
include ../Makefile.tests_common
# These boards are blacklisted since efm32 CPU dosn't support PWM_RIGHT
BOARD_BLACKLIST := slstk3401a slstk3402a sltb001a slwstk6000b stk3600 stk3700
# Boards with insufficient memory
BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-nano arduino-uno
USEMODULE += pca9685
USEMODULE += shell
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,30 @@
# PCA9685 I2C PWM controller
## Overview
This test appliation demonstrates the usage of the PCA9685 driver interface
and can be used to test each PCA9685 PWM device with shell commands.
The application bases on the test application for PWM peripheral drivers
which is under following copyright:
Copyright (C) 2014-2015 Freie Universität Berlin
@author Hauke Petersen <hauke.petersen@fu-berlin.de>
@author Semjon Kerner <semjon.kerner@fu-berlin.de>
## Usage
To use the test application, compile it with
```
make -C tests/driver_pca9685 BOARD=...
```
Please check the default configuration parameters in
`$(RIOTBASE)/drivers/pca9685/include/pca9685_params.h` and adopt them
if necessary.
If the active LOW output enable pin /OE is used, the GPIO has to be defined
as parameter, e.g.
```
CFLAGS="-DPCA9685_PARAM_INT_PIN=\(GPIO\(0,6\)\)" \
make -C tests/driver_pca9685 BOARD=...
```

286
tests/driver_pca9685/main.c Normal file
View File

@ -0,0 +1,286 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* 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 tests
* @brief Test application for the PCA9685 I2C PWM controller
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*
* ## Overview
*
* This test appliation demonstrates the usage of the PCA9685 driver interface
* and can be used to test each PCA9685 PWM device with shell commands.
*
* The application bases on the test application for PWM peripheral drivers
* which is under following copyright:
*
* Copyright (C) 2014-2015 Freie Universität Berlin
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Semjon Kerner <semjon.kerner@fu-berlin.de>
*
* ## Usage
*
* To use the test application, compile it with
*
* make -C tests/driver_pca9685 BOARD=...
*
* Please check the default configuration parameters in
* `$(RIOTBASE)/drivers/pca9685/include/pca9685_params.h` and adopt them
* if necessary.
*
* If the active LOW output enable pin /OE is used, the GPIO has to be defined
* as parameter, e.g.
*
* CFLAGS="-DPCA9685_PARAM_INT_PIN=\(GPIO\(0,6\)\)" \
* make -C tests/driver_pca9685 BOARD=...
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "xtimer.h"
#include "shell.h"
#include "timex.h"
#include "pca9685.h"
#include "pca9685_params.h"
#define OSC_INTERVAL (10LU * US_PER_MS) /* 10 ms */
#define OSC_STEP (10)
#define OSC_MODE PWM_LEFT
#define OSC_FREQU (1000U)
#define OSC_STEPS (1000U)
#define PWR_SLEEP (1U)
/* Number of configured PCA9685 I/O expander devices */
#define PCA9685_NUMOF ARRAY_SIZE(pca9685_params)
/* PCA9685 devices allocation */
pca9685_t pca9685_dev[PCA9685_NUMOF];
static uint32_t initiated;
static unsigned _get_dev(const char *dev_str)
{
unsigned dev = atoi(dev_str);
if (dev >= PCA9685_NUMOF) {
printf("Error: PWM device %u is unknown\n", dev);
return UINT_MAX;
}
return dev;
}
static int _init(int argc, char** argv)
{
if (argc != 5) {
printf("usage: %s <dev> <mode> <frequency> <resolution>\n", argv[0]);
printf("\tdev: device by number between 0 and %u\n", PCA9685_NUMOF - 1);
puts("\tmode:\n");
puts("\t\t0: left aligned\n");
puts("\t\t1: right aligned\n");
puts("\t\t2: center aligned\n");
puts("\tfrequency: desired frequency in Hz\n");
puts("\tresolution: number between 2 and 65535");
return 1;
}
unsigned dev = _get_dev(argv[1]);
if (dev == UINT_MAX) {
return 1;
}
pwm_mode_t pwm_mode;
switch(atoi(argv[2])) {
case(0):
pwm_mode = PWM_LEFT;
break;
case(1):
pwm_mode = PWM_RIGHT;
break;
case(2):
pwm_mode = PWM_CENTER;
break;
default:
printf("Error: mode %d is not supported.\n", atoi(argv[2]));
return 1;
}
uint32_t pwm_freq = pca9685_pwm_init(&pca9685_dev[dev], pwm_mode,
(uint32_t)atoi(argv[3]),
(uint16_t)atoi(argv[4]));
if (pwm_freq != 0) {
printf("The pwm frequency is set to %" PRIu32 "\n", pwm_freq);
initiated |= (1 << dev);
return 0;
}
puts("Error: device is not initiated");
return 1;
}
static int _set(int argc, char**argv)
{
if (argc != 4) {
printf("usage: %s <dev> <ch> <val>\n", argv[0]);
printf("\tdev: device by number between 0 and %d\n", PCA9685_NUMOF - 1);
puts("\tch: channel of device (if 16, all channels are set)\n");
puts("\tval: duty cycle\n");
return 1;
}
unsigned dev = _get_dev(argv[1]);
if (dev == UINT_MAX) {
return 1;
}
if ((initiated & (1 << dev)) == 0) {
puts("Error: pwm is not initiated.\n");
puts("Execute init function first.\n");
return 1;
}
uint8_t chan = atoi(argv[2]);
if (chan > pca9685_pwm_channels(&pca9685_dev[dev])) {
printf("Error: channel %d is unknown.\n", chan);
return 1;
}
pca9685_pwm_set(&pca9685_dev[dev], chan, (uint16_t)atoi(argv[3]));
return 0;
}
static int _oscillate(int argc, char** argv)
{
(void)argc;
(void)argv;
int state = 0;
int step = OSC_STEP;
xtimer_ticks32_t last_wakeup = xtimer_now();
puts("\nRIOT PWM test");
puts("Connect an LED or scope to PWM pins to see something.\n");
printf("Available PWM device between 0 and %d\n", PCA9685_NUMOF - 1);
for (unsigned i = 0; i < PCA9685_NUMOF; i++) {
uint32_t real_f = pca9685_pwm_init(&pca9685_dev[i], OSC_MODE, OSC_FREQU, OSC_STEPS);
if (real_f == 0) {
printf("Error: initializing PWM device %u.\n", i);
return 1;
}
else {
printf("Initialized PWM device %u @ %" PRIu32 "Hz.\n", i, real_f);
}
}
puts("\nLetting the PWM pins oscillate now...");
while (1) {
for (unsigned i = 0; i < PCA9685_NUMOF; i++) {
for (uint8_t chan = 0; chan < pca9685_pwm_channels(&pca9685_dev[i]); chan++) {
pca9685_pwm_set(&pca9685_dev[i], chan, state);
}
}
state += step;
if (state <= 0 || state >= (int)OSC_STEPS) {
step = -step;
}
xtimer_periodic_wakeup(&last_wakeup, OSC_INTERVAL);
}
return 0;
}
static int _power(int argc, char** argv)
{
if (argc != 3) {
printf("usage: %s <dev> <state>\n", argv[0]);
printf("\tdev: device by number between 0 and %d\n", PCA9685_NUMOF - 1);
puts("\tstate:\n");
puts("\t\t0: power off\n");
puts("\t\t1: power on\n");
return 1;
}
unsigned dev = _get_dev(argv[1]);
if (dev == UINT_MAX) {
return 1;
}
switch (atoi(argv[2])) {
case (0):
puts("Powering down PWM device.\n");
pca9685_pwm_poweroff(&pca9685_dev[dev]);
break;
case (1):
puts("Powering up PWM device.\n");
pca9685_pwm_poweron(&pca9685_dev[dev]);
break;
default:
puts("Error: power state not available.\n");
return 1;
}
return 0;
}
static int _power_test(int argc, char** argv)
{
if (argc != 2) {
printf("usage: %s <dev>\n", argv[0]);
printf("\tdev: device by number between 0 and %d\n", PCA9685_NUMOF - 1);
return 1;
}
unsigned dev = _get_dev(argv[1]);
if (dev == UINT_MAX) {
return 1;
}
printf("Powering down PWM device and sleeping for %u second(s)...\n",
PWR_SLEEP);
pca9685_pwm_poweroff(&pca9685_dev[dev]);
xtimer_sleep(PWR_SLEEP);
puts("Powering up PWM device.\n");
pca9685_pwm_poweron(&pca9685_dev[dev]);
return 0;
}
static const shell_command_t shell_commands[] = {
{ "init", "initial pwm configuration", _init },
{ "set", "set pwm duty cycle", _set },
{ "power", "set pwm power", _power },
{ "powertest", "test power on/off functions", _power_test },
{ "osci", "blocking, default oscillation test", _oscillate },
{ NULL, NULL, NULL }
};
int main(void)
{
puts("PWM peripheral driver test\n");
initiated = 0;
/* initialize configured PCA9685 devices */
for (unsigned i = 0; i < PCA9685_NUMOF; i++) {
if (pca9685_init(&pca9685_dev[i], &pca9685_params[i]) != PCA9685_OK) {
puts("[Failed]");
return 1;
}
}
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
return 0;
}