Merge pull request #315 from UncleRus/pca9685
Driver for PCA9685 + example
This commit is contained in:
commit
44124409e4
5 changed files with 409 additions and 0 deletions
3
examples/pca9685_pwm/Makefile
Normal file
3
examples/pca9685_pwm/Makefile
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
PROGRAM = pca9685_pwm
|
||||||
|
EXTRA_COMPONENTS = extras/i2c extras/pca9685
|
||||||
|
include ../../common.mk
|
43
examples/pca9685_pwm/main.c
Normal file
43
examples/pca9685_pwm/main.c
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Example of using PCA9685 PWM driver
|
||||||
|
*
|
||||||
|
* Part of esp-open-rtos
|
||||||
|
* Copyright (C) 2016 Ruslan V. Uss <unclerus@gmail.com>
|
||||||
|
* Public domain
|
||||||
|
*/
|
||||||
|
#include <esp/uart.h>
|
||||||
|
#include <espressif/esp_common.h>
|
||||||
|
#include <i2c/i2c.h>
|
||||||
|
#include <pca9685/pca9685.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define ADDR 0x40
|
||||||
|
|
||||||
|
#define SCL_PIN 5
|
||||||
|
#define SDA_PIN 4
|
||||||
|
|
||||||
|
#define PWM_FREQ 500
|
||||||
|
|
||||||
|
void user_init(void)
|
||||||
|
{
|
||||||
|
uart_set_baud(0, 115200);
|
||||||
|
printf("SDK version:%s\n", sdk_system_get_sdk_version());
|
||||||
|
|
||||||
|
i2c_init(SCL_PIN, SDA_PIN);
|
||||||
|
|
||||||
|
pca9685_init(ADDR);
|
||||||
|
|
||||||
|
pca9685_set_pwm_frequency(ADDR, 1000);
|
||||||
|
printf("Freq 1000Hz, real %d\n", pca9685_get_pwm_frequency(ADDR));
|
||||||
|
|
||||||
|
uint16_t val = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
printf("Set ch0 to %d, ch4 to %d\n", val, 4096 - val);
|
||||||
|
pca9685_set_pwm_value(ADDR, 0, val);
|
||||||
|
pca9685_set_pwm_value(ADDR, 4, 4096 - val);
|
||||||
|
|
||||||
|
if (val++ == 4096)
|
||||||
|
val = 0;
|
||||||
|
}
|
||||||
|
}
|
9
extras/pca9685/component.mk
Normal file
9
extras/pca9685/component.mk
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Component makefile for extras/pca9685
|
||||||
|
|
||||||
|
# expected anyone using this driver includes it as 'pca9685/pca9685.h'
|
||||||
|
INC_DIRS += $(pca9685_ROOT)..
|
||||||
|
|
||||||
|
# args for passing into compile rule generation
|
||||||
|
pca9685_SRC_DIR = $(pca9685_ROOT)
|
||||||
|
|
||||||
|
$(eval $(call component_compile_rules,pca9685))
|
217
extras/pca9685/pca9685.c
Normal file
217
extras/pca9685/pca9685.c
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
/**
|
||||||
|
* Driver for 16-channel, 12-bit PWM PCA9685
|
||||||
|
*
|
||||||
|
* Part of esp-open-rtos
|
||||||
|
* Copyright (C) 2016 Ruslan V. Uss <unclerus@gmail.com>
|
||||||
|
* BSD Licensed as described in the file LICENSE
|
||||||
|
*/
|
||||||
|
#include "pca9685.h"
|
||||||
|
|
||||||
|
#include <i2c/i2c.h>
|
||||||
|
#include <espressif/esp_common.h>
|
||||||
|
|
||||||
|
#define REG_MODE1 0x00
|
||||||
|
#define REG_MODE2 0x01
|
||||||
|
#define REG_SUBADR1 0x02
|
||||||
|
#define REG_SUBADR2 0x03
|
||||||
|
#define REG_SUBADR3 0x04
|
||||||
|
#define REG_ALLCALLADR 0x05
|
||||||
|
#define REG_LEDX 0x06
|
||||||
|
#define REG_ALL_LED 0xfa
|
||||||
|
#define REG_PRE_SCALE 0xfe
|
||||||
|
|
||||||
|
#define MODE1_RESTART (1 << 7)
|
||||||
|
#define MODE1_EXTCLK (1 << 6)
|
||||||
|
#define MODE1_AI (1 << 5)
|
||||||
|
#define MODE1_SLEEP (1 << 4)
|
||||||
|
|
||||||
|
#define MODE1_SUB_BIT 3
|
||||||
|
|
||||||
|
#define MODE2_INVRT (1 << 4)
|
||||||
|
#define MODE2_OUTDRV (1 << 2)
|
||||||
|
|
||||||
|
#define LED_FULL_ON_OFF (1 << 4)
|
||||||
|
|
||||||
|
#define REG_LED_N(x) (REG_LEDX + (x) * 4)
|
||||||
|
#define OFFS_REG_LED_ON 1
|
||||||
|
#define OFFS_REG_LED_OFF 3
|
||||||
|
|
||||||
|
#define INTERNAL_FREQ 25000000
|
||||||
|
|
||||||
|
#define MIN_PRESCALER 0x03
|
||||||
|
#define MAX_PRESCALER 0xff
|
||||||
|
#define MAX_CHANNEL 15
|
||||||
|
#define MAX_SUBADDR 2
|
||||||
|
|
||||||
|
#define WAKEUP_DELAY_US 500
|
||||||
|
|
||||||
|
//#define PCA9685_DEBUG
|
||||||
|
|
||||||
|
#ifdef PCA9685_DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#define debug(fmt, ...) printf("%s: " fmt "\n", "PCA9685", ## __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define debug(fmt, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline static uint32_t round_div(uint32_t x, uint32_t y)
|
||||||
|
{
|
||||||
|
return (x + y / 2) / y;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static void write_reg(uint8_t addr, uint8_t reg, uint8_t val)
|
||||||
|
{
|
||||||
|
uint8_t data[2] = { reg, val };
|
||||||
|
if (!i2c_slave_write(addr, data, 2))
|
||||||
|
debug("Could not write 0x%02x to 0x%02x, addr = 0x%02x", reg, val, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static uint8_t read_reg(uint8_t addr, uint8_t reg)
|
||||||
|
{
|
||||||
|
uint8_t res = 0;
|
||||||
|
if (!i2c_slave_read(addr, reg, &res, 1))
|
||||||
|
debug("Could not read from 0x%02x, addr = 0x%02x", reg, addr);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static void update_reg(uint8_t addr, uint8_t reg, uint8_t mask, uint8_t val)
|
||||||
|
{
|
||||||
|
write_reg(addr, reg, (read_reg(addr, reg) & ~mask) | val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pca9685_init(uint8_t addr)
|
||||||
|
{
|
||||||
|
// Enable autoincrement
|
||||||
|
update_reg(addr, REG_MODE1, MODE1_AI, MODE1_AI);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pca9685_set_subaddr(uint8_t addr, uint8_t num, uint8_t subaddr, bool enable)
|
||||||
|
{
|
||||||
|
if (num > MAX_SUBADDR)
|
||||||
|
{
|
||||||
|
debug("Invalid subaddress number: %d", num);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_reg(addr, REG_SUBADR1 + num, subaddr << 1);
|
||||||
|
|
||||||
|
uint8_t mask = 1 << (MODE1_SUB_BIT - num);
|
||||||
|
update_reg(addr, REG_MODE1, mask, enable ? mask : 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pca9685_is_sleeping(uint8_t addr)
|
||||||
|
{
|
||||||
|
return (read_reg(addr, REG_MODE1) & MODE1_SLEEP) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pca9685_sleep(uint8_t addr, bool sleep)
|
||||||
|
{
|
||||||
|
update_reg(addr, REG_MODE1, MODE1_SLEEP, sleep ? MODE1_SLEEP : 0);
|
||||||
|
if (!sleep)
|
||||||
|
sdk_os_delay_us(WAKEUP_DELAY_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pca9685_restart(uint8_t addr)
|
||||||
|
{
|
||||||
|
uint8_t mode = read_reg(addr, REG_MODE1);
|
||||||
|
if (mode & MODE1_RESTART)
|
||||||
|
{
|
||||||
|
write_reg(addr, REG_MODE1, mode & ~MODE1_SLEEP);
|
||||||
|
sdk_os_delay_us(WAKEUP_DELAY_US);
|
||||||
|
}
|
||||||
|
write_reg(addr, REG_MODE1, (mode & ~MODE1_SLEEP) | MODE1_RESTART);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pca9685_is_output_inverted(uint8_t addr)
|
||||||
|
{
|
||||||
|
return (read_reg(addr, REG_MODE2) & MODE2_INVRT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pca9685_set_output_inverted(uint8_t addr, bool inverted)
|
||||||
|
{
|
||||||
|
update_reg(addr, REG_MODE2, MODE2_INVRT, inverted ? MODE2_INVRT : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pca9685_get_output_open_drain(uint8_t addr)
|
||||||
|
{
|
||||||
|
return (read_reg(addr, REG_MODE2) & MODE2_OUTDRV) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pca9685_set_output_open_drain(uint8_t addr, bool open_drain)
|
||||||
|
{
|
||||||
|
update_reg(addr, REG_MODE2, MODE2_OUTDRV, open_drain ? 0 : MODE2_OUTDRV);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t pca9685_get_prescaler(uint8_t addr)
|
||||||
|
{
|
||||||
|
return read_reg(addr, REG_PRE_SCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pca9685_set_prescaler(uint8_t addr, uint8_t prescaler)
|
||||||
|
{
|
||||||
|
if (prescaler < MIN_PRESCALER)
|
||||||
|
{
|
||||||
|
debug("Inavlid prescaler value: %d", prescaler);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pca9685_sleep(addr, true);
|
||||||
|
write_reg(addr, REG_PRE_SCALE, prescaler);
|
||||||
|
pca9685_sleep(addr, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t pca9685_get_pwm_frequency(uint8_t addr)
|
||||||
|
{
|
||||||
|
return INTERNAL_FREQ / ((uint32_t)4096 * (read_reg(addr, REG_PRE_SCALE) + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pca9685_set_pwm_frequency(uint8_t addr, uint16_t freq)
|
||||||
|
{
|
||||||
|
uint16_t prescaler = round_div(INTERNAL_FREQ, (uint32_t)4096 * freq) - 1;
|
||||||
|
if (prescaler < MIN_PRESCALER || prescaler > MAX_PRESCALER)
|
||||||
|
{
|
||||||
|
debug("Inavlid prescaler value: %d", prescaler);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pca9685_set_prescaler(addr, prescaler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pca9685_set_pwm_value(uint8_t addr, uint8_t channel, uint16_t val)
|
||||||
|
{
|
||||||
|
uint8_t reg = channel > MAX_CHANNEL ? REG_ALL_LED : REG_LED_N(channel);
|
||||||
|
|
||||||
|
if (val == 0)
|
||||||
|
{
|
||||||
|
// Full off
|
||||||
|
write_reg(addr, reg + OFFS_REG_LED_OFF, LED_FULL_ON_OFF);
|
||||||
|
}
|
||||||
|
else if (val < 4096)
|
||||||
|
{
|
||||||
|
// Normal
|
||||||
|
uint8_t buf[5] = { reg, 0, 0, val, val >> 8 };
|
||||||
|
i2c_slave_write(addr, buf, 5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Full on
|
||||||
|
write_reg(addr, reg + OFFS_REG_LED_ON, LED_FULL_ON_OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pca9685_set_pwm_values(uint8_t addr, uint8_t first_ch, uint8_t channels, const uint16_t *values)
|
||||||
|
{
|
||||||
|
if (channels == 0 || first_ch + channels - 1 > MAX_CHANNEL)
|
||||||
|
{
|
||||||
|
debug("Invalid channels");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < channels; i ++)
|
||||||
|
pca9685_set_pwm_value(addr, first_ch + i, values [i]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
137
extras/pca9685/pca9685.h
Normal file
137
extras/pca9685/pca9685.h
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
/**
|
||||||
|
* Driver for 16-channel, 12-bit PWM PCA9685
|
||||||
|
*
|
||||||
|
* Part of esp-open-rtos
|
||||||
|
* Copyright (C) 2016 Ruslan V. Uss <unclerus@gmail.com>
|
||||||
|
* BSD Licensed as described in the file LICENSE
|
||||||
|
*/
|
||||||
|
#ifndef _EXTRAS_PCA9685_H_
|
||||||
|
#define _EXTRAS_PCA9685_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PCA9685_ADDR_BASE 0x40
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init device
|
||||||
|
* @param addr Device address
|
||||||
|
*/
|
||||||
|
void pca9685_init(uint8_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup device subaddress (see section 7.3.6 if the datasheet)
|
||||||
|
* @param addr Device address
|
||||||
|
* @param num Subaddress number, 0..2
|
||||||
|
* @param subaddr Subaddress, 7 bit
|
||||||
|
* @param enable True to enable subaddress, false to disable
|
||||||
|
* @return False if error occured
|
||||||
|
*/
|
||||||
|
bool pca9685_set_subaddr(uint8_t addr, uint8_t num, uint8_t subaddr, bool enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restart device (see section 7.3.1.1 of the datasheet)
|
||||||
|
* @param addr Device address
|
||||||
|
*/
|
||||||
|
void pca9685_restart(uint8_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if device is in sleep mode
|
||||||
|
* @param addr Device address
|
||||||
|
* @return True if device is sleeping
|
||||||
|
*/
|
||||||
|
bool pca9685_is_sleeping(uint8_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch device to low-power mode or wake it up.
|
||||||
|
* @param addr Device address
|
||||||
|
* @param sleep True for sleep mode, false for wake up
|
||||||
|
*/
|
||||||
|
void pca9685_sleep(uint8_t addr, bool sleep);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get logic inversion of the outputs
|
||||||
|
* @param addr Device address
|
||||||
|
* @return True if outputs are inverted, false otherwise
|
||||||
|
*/
|
||||||
|
bool pca9685_is_output_inverted(uint8_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logically invert outputs (see section 7.7 of the datasheet)
|
||||||
|
* @param addr Device address
|
||||||
|
* @param inverted True for inverted outputs
|
||||||
|
*/
|
||||||
|
void pca9685_set_output_inverted(uint8_t addr, bool inverted);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get outputs mode
|
||||||
|
* @param addr Device address
|
||||||
|
* @return True if outputs are in open drain mode
|
||||||
|
*/
|
||||||
|
bool pca9685_get_output_open_drain(uint8_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set outputs mode
|
||||||
|
* @param addr Device address
|
||||||
|
* @param open_drain True to set open drain mode, false to normal mode
|
||||||
|
*/
|
||||||
|
void pca9685_set_output_open_drain(uint8_t addr, bool open_drain);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current PWM frequency prescaler.
|
||||||
|
* @param addr Device address
|
||||||
|
* @return Frequency prescaler
|
||||||
|
*/
|
||||||
|
uint8_t pca9685_get_prescaler(uint8_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set PWM frequency prescaler.
|
||||||
|
* @param addr Device address
|
||||||
|
* @param prescaler Prescaler value
|
||||||
|
* @return False if error occured
|
||||||
|
*/
|
||||||
|
bool pca9685_set_prescaler(uint8_t addr, uint8_t prescaler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current PWM frequency
|
||||||
|
* @param addr Device address
|
||||||
|
* @return PWM frequency, Hz
|
||||||
|
*/
|
||||||
|
uint16_t pca9685_get_pwm_frequency(uint8_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set PWM frequency
|
||||||
|
* @param addr Device address
|
||||||
|
* @param freq PWM frequency, Hz
|
||||||
|
* @return False if error occured
|
||||||
|
*/
|
||||||
|
bool pca9685_set_pwm_frequency(uint8_t addr, uint16_t freq);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set PWM value on output channel
|
||||||
|
* @param addr Device address
|
||||||
|
* @param channel Channel number, 0..15 or >15 for all channels
|
||||||
|
* @param val PWM value, 0..4096
|
||||||
|
*/
|
||||||
|
void pca9685_set_pwm_value(uint8_t addr, uint8_t channel, uint16_t val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set PWM values on output channels
|
||||||
|
* @param addr Device address
|
||||||
|
* @param first_ch First channel, 0..15
|
||||||
|
* @param channels Number of updating channels
|
||||||
|
* @param values Array of the channel values, each 0..4096
|
||||||
|
* @return False if error occured
|
||||||
|
*/
|
||||||
|
bool pca9685_set_pwm_values(uint8_t addr, uint8_t first_ch, uint8_t channels, const uint16_t *values);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _EXTRAS_PCA9685_H_ */
|
Loading…
Reference in a new issue