1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-30 03:26:13 +01:00

Allow frequency of PWM to be set for PCA9685 drivers.

It's a parameter on the create() call, e.g.
PCA9685::create(vpin, npins, address, frequency);
This commit is contained in:
Neil McKechnie 2023-02-15 22:29:21 +00:00
parent 3292c93192
commit 21c82b37b0
3 changed files with 42 additions and 18 deletions

View File

@ -326,6 +326,7 @@ private:
// structures for setting up non-blocking writes to servo controller // structures for setting up non-blocking writes to servo controller
I2CRB requestBlock; I2CRB requestBlock;
uint8_t outputBuffer[5]; uint8_t outputBuffer[5];
uint8_t prescaler; // clock prescaler for setting PWM frequency
}; };
///////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -31,22 +31,21 @@ static const byte MODE1_AI=0x20; /**< Auto-Increment enabled */
static const byte MODE1_RESTART=0x80; /**< Restart enabled */ static const byte MODE1_RESTART=0x80; /**< Restart enabled */
static const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ static const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */
static const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
// Predeclare helper function // Predeclare helper function
static void writeRegister(byte address, byte reg, byte value); static void writeRegister(byte address, byte reg, byte value);
// Create device driver instance. // Create device driver instance.
void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress); if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress, frequency);
} }
// Configure a port on the PCA9685. // Configure a port on the PCA9685.
bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) { bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
if (configType != CONFIGURE_SERVO) return false; if (configType != CONFIGURE_SERVO) return false;
if (paramCount != 5) return false; if (paramCount != 5) return false;
#ifdef DIAG_IO #if DIAG_IO >= 3
DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"), DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
vpin, params[0], params[1], params[2], params[3], params[4]); vpin, params[0], params[1], params[2], params[3], params[4]);
#endif #endif
@ -73,10 +72,14 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
} }
// Constructor // Constructor
PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
_firstVpin = firstVpin; _firstVpin = firstVpin;
_nPins = (nPins > 16) ? 16 : nPins; _nPins = (nPins > 16) ? 16 : nPins;
_I2CAddress = i2cAddress; _I2CAddress = i2cAddress;
// Calculate prescaler value for PWM clock
if (frequency > 1526) frequency = 1526;
else if (frequency < 24) frequency = 24;
prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency;
// To save RAM, space for servo configuration is not allocated unless a pin is used. // To save RAM, space for servo configuration is not allocated unless a pin is used.
// Initialise the pointers to NULL. // Initialise the pointers to NULL.
for (int i=0; i<_nPins; i++) for (int i=0; i<_nPins; i++)
@ -98,7 +101,7 @@ void PCA9685::_begin() {
// Initialise I/O module here. // Initialise I/O module here.
if (I2CManager.exists(_I2CAddress)) { if (I2CManager.exists(_I2CAddress)) {
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI);
// In theory, we should wait 500us before sending any other commands to each device, to allow // In theory, we should wait 500us before sending any other commands to each device, to allow
@ -114,7 +117,7 @@ void PCA9685::_begin() {
// Device-specific write function, invoked from IODevice::write(). // Device-specific write function, invoked from IODevice::write().
// For this function, the configured profile is used. // For this function, the configured profile is used.
void PCA9685::_write(VPIN vpin, int value) { void PCA9685::_write(VPIN vpin, int value) {
#ifdef DIAG_IO #if DIAG_IO >= 3
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value); DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
#endif #endif
int pin = vpin - _firstVpin; int pin = vpin - _firstVpin;
@ -141,7 +144,7 @@ void PCA9685::_write(VPIN vpin, int value) {
// 4 (Bounce) Servo 'bounces' at extremes. // 4 (Bounce) Servo 'bounces' at extremes.
// //
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) { void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
#ifdef DIAG_IO #if DIAG_IO >= 3
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"), DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif #endif

View File

@ -23,6 +23,22 @@
* commands the device to set the PWM mark-to-period ratio accordingly. * commands the device to set the PWM mark-to-period ratio accordingly.
* The call to IODevice::writeAnalogue(vpin, value) specifies the * The call to IODevice::writeAnalogue(vpin, value) specifies the
* desired value in the range 0-4095 (0=0% and 4095=100%). * desired value in the range 0-4095 (0=0% and 4095=100%).
*
* This driver can be used for simple servo control by writing values between
* about 102 and 450 (extremes of movement for 9g micro servos) or 150 to 250
* for a more restricted range (corresponding to 1.5ms to 2.5ms pulse length).
* A value of zero will switch off the servo. To create the device, use
* the following syntax:
*
* PCA9685_basic::create(vpin, npins, i2caddress);
*
* For LED control, a value of 0 is fully off, and 4095 is fully on. It is
* recommended, to reduce flicker of LEDs, that the frequency be configured
* to a value higher than the default of 50Hz. To do this, create the device
* as follows, for a frequency of 200Hz.:
*
* PCA9685_basic::create(vpin, npins, i2caddress, 200);
*
*/ */
#ifndef PCA9685_BASIC_H #ifndef PCA9685_BASIC_H
@ -39,34 +55,38 @@
class PCA9685pwm : public IODevice { class PCA9685pwm : public IODevice {
public: public:
// Create device driver instance. // Create device driver instance.
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress); if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress, frequency);
} }
private: private:
// structures for setting up non-blocking writes to servo controller // structures for setting up non-blocking writes to PWM controller
I2CRB requestBlock; I2CRB requestBlock;
uint8_t outputBuffer[5]; uint8_t outputBuffer[5];
uint16_t prescaler;
// REGISTER ADDRESSES // REGISTER ADDRESSES
const uint8_t PCA9685_MODE1=0x00; // Mode Register const uint8_t PCA9685_MODE1=0x00; // Mode Register
const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first servo register ON*/ const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first PWM register ON*/
const uint8_t PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */ const uint8_t PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */
// MODE1 bits // MODE1 bits
const uint8_t MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */ const uint8_t MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */
const uint8_t MODE1_AI=0x20; /**< Auto-Increment enabled */ const uint8_t MODE1_AI=0x20; /**< Auto-Increment enabled */
const uint8_t MODE1_RESTART=0x80; /**< Restart enabled */ const uint8_t MODE1_RESTART=0x80; /**< Restart enabled */
const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ const uint32_t FREQUENCY_OSCILLATOR=25000000; /** Accurate enough for our purposes */
const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
// Constructor // Constructor
PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
_firstVpin = firstVpin; _firstVpin = firstVpin;
_nPins = (nPins>16) ? 16 : nPins; _nPins = (nPins>16) ? 16 : nPins;
_I2CAddress = i2cAddress; _I2CAddress = i2cAddress;
if (frequency > 1526) frequency = 1526;
else if (frequency < 24) frequency = 24;
prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency;
addDevice(this); addDevice(this);
// Initialise structure used for setting pulse rate // Initialise structure used for setting pulse rate
@ -83,7 +103,7 @@ private:
// Initialise I/O module here. // Initialise I/O module here.
if (I2CManager.exists(_I2CAddress)) { if (I2CManager.exists(_I2CAddress)) {
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period. writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI); writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI);
// In theory, we should wait 500us before sending any other commands to each device, to allow // In theory, we should wait 500us before sending any other commands to each device, to allow
@ -100,7 +120,7 @@ private:
// //
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override { void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
(void)param1; (void)param2; // suppress compiler warning (void)param1; (void)param2; // suppress compiler warning
#ifdef DIAG_IO #if DIAG_IO >= 3
DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"), DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"),
vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F("")); vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif #endif
@ -121,7 +141,7 @@ private:
// writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value // writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value
// between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%. // between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%.
void writeDevice(uint8_t pin, int value) { void writeDevice(uint8_t pin, int value) {
#ifdef DIAG_IO #if DIAG_IO >= 3
DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value); DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value);
#endif #endif
// Wait for previous request to complete // Wait for previous request to complete