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:
commit
6c95081e42
@ -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
|
||||||
|
|||||||
@ -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
322
drivers/include/pca9685.h
Normal 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
1
drivers/pca9685/Makefile
Normal file
@ -0,0 +1 @@
|
|||||||
|
include $(RIOTBASE)/Makefile.base
|
||||||
143
drivers/pca9685/include/pca9685_params.h
Normal file
143
drivers/pca9685/include/pca9685_params.h
Normal 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 */
|
||||||
|
/** @} */
|
||||||
98
drivers/pca9685/include/pca9685_regs.h
Normal file
98
drivers/pca9685/include/pca9685_regs.h
Normal 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
369
drivers/pca9685/pca9685.c
Normal 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));
|
||||||
|
}
|
||||||
36
drivers/pca9685/pca9685_saul.c
Normal file
36
drivers/pca9685/pca9685_saul.c
Normal 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 */
|
||||||
@ -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();
|
||||||
|
|||||||
95
sys/auto_init/saul/auto_init_pca9685.c
Normal file
95
sys/auto_init/saul/auto_init_pca9685.c
Normal 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 */
|
||||||
12
tests/driver_pca9685/Makefile
Normal file
12
tests/driver_pca9685/Makefile
Normal 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
|
||||||
30
tests/driver_pca9685/README.md
Normal file
30
tests/driver_pca9685/README.md
Normal 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
286
tests/driver_pca9685/main.c
Normal 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;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user