diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..da6009a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +patreon: nachtrave diff --git a/.gitignore b/.gitignore index df8bb78..6eddc09 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ Icon? ehthumbs.db Thumbs.db +pad # Generic: ############### diff --git a/.vscode/arduino.json b/.vscode/arduino.json new file mode 100644 index 0000000..82734bd --- /dev/null +++ b/.vscode/arduino.json @@ -0,0 +1,3 @@ +{ + "sketch": "examples\\ModuleInfo\\ModuleInfo.ino" +} \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..251a9ff --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,31 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**", + "${LocalAppData}/Arduino15/packages/arduino/hardware/megaavr/1.8.6/**", + "${LocalAppData}/Arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino5/avr/include/**", + "${LocalAppData}/Arduino15/packages/esp8266/tools/**", + "${LocalAppData}/Arduino15/packages/esp8266/hardware/esp8266/2.7.4/**", + "${USERPROFILE}/Documents/Arduino/libraries/**", + "${vcpkgRoot}/x86-windows/include/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE", + "ARDUINO=100", + "F_CPU=64000000", + "PCA9685_ENABLE_DEBUG_OUTPUT" + ], + "windowsSdkVersion": "10.0.19041.0", + "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.27.29110/bin/Hostx64/x64/cl.exe", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "msvc-x64", + "forcedInclude": [] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..73d5092 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,27 @@ +{ + "files.associations": { + "cstddef": "cpp", + "array": "cpp", + "deque": "cpp", + "initializer_list": "cpp", + "list": "cpp", + "vector": "cpp", + "xhash": "cpp", + "xstring": "cpp", + "xtree": "cpp", + "xutility": "cpp", + "future": "cpp", + "istream": "cpp", + "memory": "cpp", + "type_traits": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "algorithm": "cpp", + "regex": "cpp", + "tuple": "cpp", + "utility": "cpp", + "variant": "cpp" + } +} \ No newline at end of file diff --git a/PCA9685 datasheet.pdf b/PCA9685 datasheet.pdf deleted file mode 100644 index 4e16abf..0000000 Binary files a/PCA9685 datasheet.pdf and /dev/null differ diff --git a/PCA9685.cpp b/PCA9685.cpp deleted file mode 100644 index 0efb024..0000000 --- a/PCA9685.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* PCA9685 LED library for Arduino - Copyright (C) 2012 Kasper Skårhøj - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ -#include "PCA9685.h" - -void PCA9685::begin(int i2cAddress) { - _i2cAddress = PCA9685_I2C_BASE_ADDRESS | (i2cAddress & B00111111); -} - -void PCA9685::init(byte mode) { - //Reset SWRST - Wire.beginTransmission(0); - Wire.write((byte)0x06); - Wire.endTransmission(); - //Init - Wire.beginTransmission(_i2cAddress); - Wire.write(PCA9685_MODE1); - Wire.write(0b00100000); //Autoincrement - Wire.write(mode); - Wire.endTransmission(); -} - -void PCA9685::setPwmFrequency(int hz){ - uint32_t val = 6104L / (uint32_t)hz; - if(val > 255)val = 255; - if(val < 3)val = 3; - Wire.beginTransmission(_i2cAddress); - Wire.write(PCA9685_PRE_SCALE); - Wire.write((byte)val); - Wire.endTransmission(); -} - -void PCA9685::on(int nr) { - writeChBegin(nr); - writeChannel(PCA9685_PWM_FULL, 0); - writeEnd(); -} - -void PCA9685::off(int nr) { - writeChBegin(nr); - writeChannel(0, PCA9685_PWM_FULL); - writeEnd(); -} - -void PCA9685::setChannel(byte nr, word amount) { // Amount from 0-100 (off-on) - writeChBegin(nr); - if (amount==PCA9685_CH_OFF){ - writeChannel(0, PCA9685_PWM_FULL); - } - else if (amount>=PCA9685_CH_ON){ - writeChannel(PCA9685_PWM_FULL, 0); - } - else { - //int randNumber = (int)random(4096); // Randomize the phaseshift to distribute load. Good idea? Hope so. - word phase = (4096 / 16) * nr; - writeChannel(phase, (amount+phase) & 0xFFF); - } - writeEnd(); -} - -// IN:avr/libraries/Wire.h -// avr/libraries/utility/twi.h -// BUFFER_LENGTH should be increased to 64 + 2 at least, default is 32 so only 7 lines could be written in one transaction -void PCA9685::setChannel(byte startch, byte count, const word* amount) { // Amount from 0-100 (off-on) - writeChBegin(startch); - word onval = 0, offval = 0; - for(byte i = 0; i < count; i++){ - if (amount[i]==PCA9685_CH_OFF){ - onval = 0; - offval = PCA9685_PWM_FULL; - } - else if (amount[i]>=PCA9685_CH_ON){ - onval = PCA9685_PWM_FULL; - offval = 0; - } - else { - //int randNumber = (int)random(4096); // Randomize the phaseshift to distribute load. Good idea? Hope so. - byte nr = startch+i; - word phase = (4096 / 16) * nr; - onval = phase; - offval = (amount[i]+phase) & 0xFFF; - } - writeChannel(onval, offval); - } - writeEnd(); -} - -void PCA9685::writeChBegin(int nr) { // LED_ON and LED_OFF are 12bit values (0-4095); ledNumber is 0-15 - byte addr = PCA9685_LED0 + 4*nr; - Wire.beginTransmission(_i2cAddress); - Wire.write(addr); -} - -void PCA9685::writeEnd() { // LED_ON and LED_OFF are 12bit values (0-4095); ledNumber is 0-15 - Wire.endTransmission(); -} - -void PCA9685::writeChannel(word LED_ON, word LED_OFF) { // LED_ON and LED_OFF are 12bit values (0-4095); ledNumber is 0-15 - Wire.write(lowByte(LED_ON)); - Wire.write(highByte(LED_ON)); - Wire.write(lowByte(LED_OFF)); - Wire.write(highByte(LED_OFF)); -} - diff --git a/PCA9685.h b/PCA9685.h deleted file mode 100644 index 60d52da..0000000 --- a/PCA9685.h +++ /dev/null @@ -1,81 +0,0 @@ -/* PCA9685 library for Arduino - Copyright (C) 2012 Kasper Skårhøj - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ -#ifndef PCA9685_H -#define PCA9685_H - -#if defined(ARDUINO) && ARDUINO >= 100 -#include "Arduino.h" -#else -#include "WProgram.h" -#endif - -#include "Wire.h" - -/** - Version 1.0.0 - (Semantic Versioning) -**/ - -//Register defines from data sheet -#define PCA9685_MODE1 (byte)0x00 // location for Mode1 register address -#define PCA9685_MODE2 (byte)0x01 // location for Mode2 reigster address -#define PCA9685_LED0 (byte)0x06 // location for start of LED0 registers -// The maximum PWM frequency is 1526 Hz if the PRE_SCALE register is set "0x03h". -// The minimum PWM frequency is 24 Hz if the PRE_SCALE register is set "0xFFh". -#define PCA9685_PRE_SCALE (byte)0xfe //prescaler to program the PWM output frequency (default is 200 Hz) -#define PCA9685_ALL_LED (byte)0xFA -#define PCA9685_MAX_CHANNEL 15 -#define PCA9685_MIN_CHANNEL 0 -#define PCA9685_CHANNEL_COUNT 16 -#define PCA9685_ALL_LED_CHANNEL (PCA9685_ALL_LED-PCA9685_LED0) >> 2 - -#define PCA9685_MODE_INVRT 0x10 -#define PCA9685_MODE_OUTPUT_ON_ACK 0x08 -#define PCA9685_MODE_OUTPUT_POLE 0x04 - -#define PCA9685_CH_ON 4096 -#define PCA9685_CH_OFF 0 -#define PCA9685_PWM_FULL 0x1000 - -#define PCA9685_I2C_BASE_ADDRESS 0x40 - -class PCA9685 -{ - public: - //NB the i2c address here is the value of the A0, A1, A2, A3, A4 and A5 pins ONLY - //as the class takes care of its internal base address. - //so i2cAddress should be between 0 and 63 - PCA9685(){} - void begin(int i2cAddress); - void init(byte mode = PCA9685_MODE_OUTPUT_ON_ACK | PCA9685_MODE_OUTPUT_POLE); - - void on(int nr); - void off(int nr); - // 0: instant OFF(0) - // 4096: instant ON(1) - // 1 - 4095: duty sicle - void setChannel(byte nr, word amount); - void setChannel(byte startch, byte count, const word* amount); - void setPwmFrequency(int hz); - private: - void writeChannel(word outputStart, word outputEnd); - void writeChBegin(int nr); - void writeEnd(); - // Our actual i2c address: - byte _i2cAddress; -}; -#endif diff --git a/README.md b/README.md index 55971a2..852cc89 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,524 @@ -# PCA9685-Arduino-Library -Arduino Library for the PCA9685 LED Driver chip - -## Example -```Arduino -#include "PCA9685.h" - -PCA9685 pwmDriver; - -void setup() { - Wire.begin(); // Wire must be started! - Wire.setClock(400000L); - pwmDriver.begin(B000000); // Address pins A5-A0 set to B111000 - pwmDriver.init(); - pwmDriver.setPwmFrequency(1500); -} - -void loop() { - word pwms[PCA9685_CHANNEL_COUNT]; - pwms[0] = PCA9685_CH_OFF; - pwms[1] = PCA9685_CH_OFF; - pwms[2] = PCA9685_CH_OFF; - pwms[3] = PCA9685_CH_OFF; - pwms[4] = PCA9685_CH_OFF; - pwms[5] = PCA9685_CH_OFF; - pwms[6] = PCA9685_CH_OFF; - pwms[7] = PCA9685_CH_OFF; - pwms[8] = PCA9685_CH_OFF; - pwms[9] = PCA9685_CH_OFF; - pwms[10] = PCA9685_CH_OFF; - pwms[11] = PCA9685_CH_OFF; - pwms[12] = PCA9685_CH_OFF; - pwms[13] = PCA9685_CH_OFF; - pwms[14] = PCA9685_CH_OFF; - pwms[15] = PCA9685_CH_OFF; - pwmDriver.setChannel(PCA9685_MIN_CHANNEL, PCA9685_CHANNEL_COUNT, pwms); - delay(250); - - pwms[0] = PCA9685_CH_ON; - pwms[1] = PCA9685_CH_ON; - pwms[2] = PCA9685_CH_ON; - pwms[3] = PCA9685_CH_ON; - pwms[4] = PCA9685_CH_ON; - pwms[5] = PCA9685_CH_ON; - pwms[6] = PCA9685_CH_ON; - pwms[7] = PCA9685_CH_ON; - pwms[8] = PCA9685_CH_ON; - pwms[9] = PCA9685_CH_ON; - pwms[10] = PCA9685_CH_ON; - pwms[11] = PCA9685_CH_ON; - pwms[12] = PCA9685_CH_ON; - pwms[13] = PCA9685_CH_ON; - pwms[14] = PCA9685_CH_ON; - pwms[15] = PCA9685_CH_ON; - pwmDriver.setChannel(PCA9685_MIN_CHANNEL, PCA9685_CHANNEL_COUNT, pwms); - delay(250); -} - -``` +# PCA9685-Arduino +Arduino Library for the PCA9685 16-Channel PWM Driver Module. + +**PCA9685-Arduino - Version 1.2.15** + +Library to control a PCA9685 16-channel PWM driver module from an Arduino board. +Licensed under the copy-left GNU GPL v3 license. + +Created by Kasper Skårhøj, August 3rd, 2012. +Forked by Vitska, June 18th, 2016. +Forked by NachtRaveVL, July 29th, 2016. + +This library allows communication with boards running a PCA6985 16-channel PWM driver module. It supports a wide range of available functionality, from setting the output PWM frequecy, allowing multi-device proxy addressing, and provides an assistant class for working with Servos. + +Made primarily for Arduino microcontrollers, but should work with PlatformIO, ESP32/8266, Teensy, and others - although one might want to ensure BUFFER_LENGTH (or I2C_BUFFER_LENGTH) and WIRE_INTERFACES_COUNT is properly defined for any architecture used. + +The datasheet for the IC is available at . + +## Library Setup + +### Installation + +The easiest way to install this library is to utilize the Arduino IDE library manager, or through a package manager such as PlatformIO. Otherwise, simply download this library and extract its files into a `PCA9685-Arduino` folder in your Arduino custom libraries folder, typically found in your `[My ]Documents\Arduino\libraries` folder (Windows), or `~/Documents/Arduino/libraries/` folder (Linux/OSX). + +### Header Defines + +There are several defines inside of the library's main header file that allow for more fine-tuned control of the library. You may edit and uncomment these lines directly, or supply them via custom build flags. While editing the main header file isn't ideal, it is often the easiest given the Arduino IDE's limited custom build flag support. Note that editing the library's main header file directly will affect all projects compiled on your system using those modified library files. + +Alternatively, you may also refer to on how to define custom build flags manually via modifying the platform[.local].txt file. Note that editing such directly will affect all other projects compiled on your system using those modified platform framework files. + +From PCA9685.h: +```Arduino +// Uncomment or -D this define to enable use of the software i2c library (min 4MHz+ processor). +//#define PCA9685_ENABLE_SOFTWARE_I2C // http://playground.arduino.cc/Main/SoftwareI2CLibrary + +// Uncomment or -D this define to swap PWM low(begin)/high(end) phase values in register reads/writes (needed for some chip manufacturers). +//#define PCA9685_SWAP_PWM_BEG_END_REGS + +// Uncomment or -D this define to enable debug output. +//#define PCA9685_ENABLE_DEBUG_OUTPUT +``` + +### Library Initialization + +There are several initialization mode settings exposed through this library that are used for more fine-tuned control. + +#### Class Instantiation + +The library's class object must first be instantiated, commonly at the top of the sketch where pin setups are defined (or exposed through some other mechanism), which makes a call to the library's class constructor. The constructor allows one to set the module's i2c address, i2c Wire class instance, and lastly i2c clock speed (most i2c parameters being ommitted when in software i2c mode). The default constructor values of the library, if left unspecified, is i2c address `B000000`, and i2c Wire class instance `Wire` @`400k`Hz. + +From PCA9685.h, in class PCA9685, when in hardware i2c mode: +```Arduino + // Library constructor. Typically called during class instantiation, before setup(). + // The i2c address should be the value of the A5-A0 pins, as the class handles the + // module's base i2c address. It should be a value between 0 and 61, which gives a + // maximum of 62 modules that can be addressed on the same i2c line. + // Boards with more than one i2c line (e.g. Due/Mega/etc.) can supply a different + // Wire instance, such as Wire1 (using SDA1/SCL1), Wire2 (using SDA2/SCL2), etc. + // Supported i2c clock speeds are 100kHz, 400kHz (default), and 1000kHz. + PCA9685(byte i2cAddress = B000000, TwoWire& i2cWire = Wire, uint32_t i2cSpeed = 400000); + + // Convenience constructor for custom Wire instance. See main constructor. + PCA9685(TwoWire& i2cWire, uint32_t i2cSpeed = 400000, byte i2cAddress = B000000); +``` + +From PCA9685.h, in class PCA9685, when in software i2c mode (see examples for sample usage): +```Arduino + // Library constructor. Typically called during class instantiation, before setup(). + // The i2c address should be the value of the A5-A0 pins, as the class handles the + // module's base i2c address. It should be a value between 0 and 61, which gives a + // maximum of 62 modules that can be addressed on the same i2c line. + // Minimum supported i2c clock speed is 100kHz, which sets minimum processor speed at + // 4MHz+ running in i2c standard mode. For up to 400kHz i2c clock speeds, minimum + // processor speed is 16MHz+ running in i2c fast mode. + PCA9685(byte i2cAddress = B000000); +``` + +#### Device Initialization + +Additionally, a call is expected to be provided to the library class object's `init(...)` or `initAsProxyAddresser()` methods, commonly called inside of the sketch's `setup()` function. The `init(...)` method allows one to set the module's driver mode, enabled/disabled output settings, channel update mode, and phase balancer scheme, while the `initAsProxyAddresser()` method allows one to setup the object as a proxy addresser (see examples for sample usage). The default init values of the library, if left unspecified, is `PCA9685_OutputDriverMode_TotemPole`, `PCA9685_OutputEnabledMode_Normal`, `PCA9685_OutputDisabledMode_Low`, `PCA9685_ChannelUpdateMode_AfterStop`, and `PCA9685_PhaseBalancer_None` which seems to work for most of the PCA9685 breakouts on market, but should be set according to your setup. + +See Section 7.3.2 of the datasheet for more details. + +From PCA9685.h, in class PCA9685, for standard init: +```Arduino + // Initializes module. Typically called in setup(). + // See individual enums for more info. + void init(PCA9685_OutputDriverMode driverMode = PCA9685_OutputDriverMode_TotemPole, + PCA9685_OutputEnabledMode enabledMode = PCA9685_OutputEnabledMode_Normal, + PCA9685_OutputDisabledMode disabledMode = PCA9685_OutputDisabledMode_Low, + PCA9685_ChannelUpdateMode updateMode = PCA9685_ChannelUpdateMode_AfterStop, + PCA9685_PhaseBalancer phaseBalancer = PCA9685_PhaseBalancer_None); + + // Convenience initializer for custom phase balancer. See main init method. + void init(PCA9685_PhaseBalancer phaseBalancer, + PCA9685_OutputDriverMode driverMode = PCA9685_OutputDriverMode_TotemPole, + PCA9685_OutputEnabledMode enabledMode = PCA9685_OutputEnabledMode_Normal, + PCA9685_OutputDisabledMode disabledMode = PCA9685_OutputDisabledMode_Low, + PCA9685_ChannelUpdateMode updateMode = PCA9685_ChannelUpdateMode_AfterStop); +``` + +From PCA9685.h, in class PCA9685, for init as a proxy addresser (see examples for sample usage): +```Arduino + // Initializes module as a proxy addresser. Typically called in setup(). Used when + // instance talks through to AllCall/Sub1-Sub3 instances as a proxy object. Using + // this method will disable any method that performs a read or conflicts with certain + // states. Proxy addresser i2c addresses must be >= 0xE0, with defaults provided via + // PCA9685_I2C_DEF_[ALLCALL|SUB[1-3]]_PROXYADR defines. + void initAsProxyAddresser(); +``` + +From PCA9685.h: +```Arduino +// Output driver control mode (see datasheet Table 12 and Fig 13, 14, and 15 concerning correct +// usage of OUTDRV). +enum PCA9685_OutputDriverMode { + PCA9685_OutputDriverMode_OpenDrain, // Module outputs in an open-drain (aka direct connection) style structure with 400mA @5v total sink current, useful for LEDs and low-power Servos + PCA9685_OutputDriverMode_TotemPole, // Module outputs in a totem-pole (aka push-pull) style structure with 400mA @5v total sink current and 160mA total source current, useful for external drivers (default) +}; +// NOTE: Totem-pole mode should be used when an external N-type or P-type driver is in +// use, which provides actual sourcing current while open-drain mode doesn't. At max +// channel capacity, the sink current limit is 25mA@5v per channel while the source +// current limit, in totem-pole mode, is 10mA@5v per channel. However, from datasheet +// Table 6. subnote [1]: "Some newer LEDs include integrated Zener diodes to limit +// voltage transients, reduce EMI, and protect the LEDs, and these -MUST- be driven only +// in the open-drain mode to prevent over-heating the IC." Also from datasheet, Section +// 10. question 5: "in the push-pull architecture there is a low resistance path to GND +// through the Zener and this [causes] the IC to overheat." + +// Output-enabled/active-low-OE-pin=LOW driver output mode (see datasheet Table 12 and +// Fig 13, 14, and 15 concerning correct usage of INVRT). +enum PCA9685_OutputEnabledMode { + PCA9685_OutputEnabledMode_Normal, // When OE is enabled/LOW, channels output a normal signal, useful for N-type external drivers (default) + PCA9685_OutputEnabledMode_Inverted, // When OE is enabled/LOW, channels output an inverted signal, useful for P-type external drivers or direct connection +}; +// NOTE: Polarity inversion is often set according to if an external N-type driver +// (should not use INVRT) or external P-type driver/direct connection (should use INVRT) +// is used. Most breakouts have just a 220Ω resistor between the individual channel +// outputs of the IC and PWM output pins, which is useful when powering LEDs. The V+ rail +// of most breakouts can connect through a 10v 1000μF decoupling capacitor, typically +// already installed on most breakouts, which can reduce voltage spikes and ground bounce +// during phase shifts at the start/end of the PWM high phase when many channel devices +// are connected together. See https://forums.adafruit.com/viewtopic.php?f=8&t=127421 and +// https://forums.adafruit.com/viewtopic.php?f=8&t=162688 for information on installing +// a decoupling capacitor if need arises. + +// Output-not-enabled/active-low-OE-pin=HIGH driver output mode (see datasheet Section +// 7.4 concerning correct usage of OUTNE). +enum PCA9685_OutputDisabledMode { + PCA9685_OutputDisabledMode_Low, // When OE is disabled/HIGH, channels output a LOW signal (default) + PCA9685_OutputDisabledMode_High, // When OE is disabled/HIGH, channels output a HIGH signal (only available in totem-pole mode) + PCA9685_OutputDisabledMode_Floating, // When OE is disabled/HIGH, channel outputs go into a floating (aka high-impedance/high-Z) state, which may be further refined via external pull-up/pull-down resistors +}; +// NOTE: Active-low-OE pin is typically used to synchronize multiple PCA9685 devices +// together, but can also be used as an external dimming control signal. + +// Channel update strategy used when multiple channels are being updated in batch. +enum PCA9685_ChannelUpdateMode { + PCA9685_ChannelUpdateMode_AfterStop, // Channel updates commit after full-transmission STOP signal (default) + PCA9685_ChannelUpdateMode_AfterAck, // Channel updates commit after individual channel update ACK signal +}; + +// Software-based phase balancing scheme. +enum PCA9685_PhaseBalancer { + PCA9685_PhaseBalancer_None, // Disables software-based phase balancing, relying on installed hardware to handle current sinkage (default) + PCA9685_PhaseBalancer_Linear, // Uses linear software-based phase balancing, with each channel being a preset 16 steps (out of the 4096/12-bit value range) away from previous channel (may cause LED flickering/skipped-cycles on PWM changes) +}; +// NOTE: Software-based phase balancing attempts to further mitigate ground bounce and +// voltage spikes during phase shifts at the start/end of the PWM high phase by shifting +// the leading edge of each successive PWM high phase by some amount. This helps make +// the current sinks occur over the entire duty cycle range instead of all together at +// once. Software-based phase balancing can be useful in certain situations, but in +// practice has been the source of many problems, including the case whereby the PCA9685 +// will skip a cycle between PWM changes when the leading/trailing edge is shifted past a +// certain point. While we may revisit this idea in the future, for now we're content on +// leaving None as the default, and limiting the shift that Linear applies. +``` + +#### Device Reset + +If you are constantly re-building and re-uploading during development, it may be wise to include a call to the library's `resetDevices()` method in order to reset all devices shared across the supplied Wire instance. This way you can ensure all devices on that i2c line start from a clean state. + +From PCA9685.h, in class PCA9685: +```Arduino + // Resets modules. Typically called in setup(), before any init()'s. Calling will + // perform a software reset on all PCA9685 devices on the Wire instance, ensuring + // that all PCA9685 devices on that line are properly reset. + void resetDevices(); +``` + +## Hookup Callouts + +### Servo Control + +* Many digital servos run on a 20ms pulse width (50Hz update frequency) based duty cycle, and do not utilize the entire pulse width for their control. +* Typically, 2.5% of the 20ms pulse width (0.5ms) represents -90° offset, and 12.5% of the 20ms pulse width (2.5ms) represents +90° offset. + * This roughly translates to raw PCA9685 PWM values of 102 and 512 (out of the 4096/12-bit value range) for their -90°/+90° offset control. + * However, these may need to be adjusted to fit your specific servo (e.g. some we've tested run ~130 to ~525 for their -90°/+90° offset control). +* Be aware that driving some 180° servos too far past their -90°/+90° operational range can cause a little plastic limiter pin to break off and get stuck inside of the servo's gearing, which could potentially cause the servo to become jammed and no longer function. +* Continuous servos operate in much the same fashion as 180° servos, but instead of the 2.5%/12.5% pulse width controlling a -90°/+90° offset it controls a -1x/+1x speed multiplier, with 0x being parked/no-movement and -1x/+1x being maximum speed in either direction. + +See the `PCA9685_ServoEval` class to assist with calculating PWM values from Servo angle/speed values, if you desire that level of fine tuning. + +## Example Usage + +Below are several examples of library usage. + +### Simple Example + +```Arduino +#include "PCA9685.h" + +PCA9685 pwmController; // Library using default B000000 (A5-A0) i2c address, and default Wire @400kHz + +void setup() { + Serial.begin(115200); // Begin Serial and Wire interfaces + Wire.begin(); + + pwmController.resetDevices(); // Resets all PCA9685 devices on i2c line + + pwmController.init(); // Initializes module using default totem-pole driver mode, and default disabled phase balancer + + pwmController.setPWMFrequency(100); // Set PWM freq to 100Hz (default is 200Hz, supports 24Hz to 1526Hz) + + pwmController.setChannelPWM(0, 128 << 4); // Set PWM to 128/255, shifted into 4096-land + + Serial.println(pwmController.getChannelPWM(0)); // Should output 2048, which is 128 << 4 +} + +void loop() { +} + +``` + +### Batching Example + +In this example, we randomly select PWM frequencies on all 12 outputs and allow them to drive for 5 seconds before changing them. + +```Arduino +#include "PCA9685.h" + +PCA9685 pwmController(B010101); // Library using B010101 (A5-A0) i2c address, and default Wire @400kHz + +void setup() { + Serial.begin(115200); // Begin Serial and Wire interfaces + Wire.begin(); + + pwmController.resetDevices(); // Resets all PCA9685 devices on i2c line + + pwmController.init(); // Initializes module using default totem-pole driver mode, and default phase balancer + + pwmController.setPWMFrequency(500); // Set PWM freq to 500Hz (default is 200Hz, supports 24Hz to 1526Hz) + + randomSeed(analogRead(0)); // Use white noise for our randomness +} + +void loop() { + uint16_t pwms[12]; + pwms[0] = random(0, 4096); + pwms[1] = random(0, 4096); + pwms[2] = random(0, 4096); + pwms[3] = random(0, 4096); + pwms[4] = random(0, 4096); + pwms[5] = random(0, 4096); + pwms[6] = random(0, 4096); + pwms[7] = random(0, 4096); + pwms[8] = random(0, 4096); + pwms[9] = random(0, 4096); + pwms[10] = random(0, 4096); + pwms[11] = random(0, 4096); + pwmController.setChannelsPWM(0, 12, pwms); + delay(5000); + + // NOTE: Many chips use a BUFFER_LENGTH size of 32, and in that case writing 12 + // channels will take 2 i2c transactions because only 7 channels can fit in a single + // i2c buffer transaction at a time. This may cause a slight offset flicker between + // the first 7 and remaining 5 channels, but can be offset by experimenting with a + // channel update mode of PCA9685_ChannelUpdateMode_AfterAck. This will make each + // channel update immediately upon sending of the Ack signal after each PWM command + // is executed rather than at the Stop signal at the end of the i2c transaction. +} + +``` + +### Multi-Device Proxy Example + +In this example, we use a special instance to control other modules attached to it via the `ALL_CALL` register. + +```Arduino +#include "PCA9685.h" + +PCA9685 pwmController1(B000000); // Library using B000000 (A5-A0) i2c address, and default Wire @400kHz +PCA9685 pwmController2(B000001); // Library using B000001 (A5-A0) i2c address, and default Wire @400kHz + +// Not a real device, will act as a proxy to pwmController1 and pwmController2, using all-call i2c address 0xE0, and default Wire @400kHz +PCA9685 pwmControllerAll(PCA9685_I2C_DEF_ALLCALL_PROXYADR); + +void setup() { + Serial.begin(115200); // Begin Serial and Wire interfaces + Wire.begin(); + + pwmControllerAll.resetDevices(); // Resets all PCA9685 devices on i2c line + + pwmController1.init(); // Initializes first module using default totem-pole driver mode, and default disabled phase balancer + pwmController2.init(); // Initializes second module using default totem-pole driver mode, and default disabled phase balancer + + pwmControllerAll.initAsProxyAddresser(); // Initializes 'fake' module as all-call proxy addresser + + // Enables all-call support to module from 'fake' all-call proxy addresser + pwmController1.enableAllCallAddress(pwmControllerAll.getI2CAddress()); + pwmController2.enableAllCallAddress(pwmControllerAll.getI2CAddress()); // On both + + pwmController1.setChannelOff(0); // Turn channel 0 off + pwmController2.setChannelOff(0); // On both + + pwmControllerAll.setChannelPWM(0, 4096); // Enables full on on both pwmController1 and pwmController2 + + Serial.println(pwmController1.getChannelPWM(0)); // Should output 4096 + Serial.println(pwmController2.getChannelPWM(0)); // Should also output 4096 + + // Note: Various parts of functionality of the proxy class instance are actually + // disabled - typically anything that involves a read command being issued. +} + +void loop() { +} + +``` + +### Servo Evaluator Example + +In this example, we utilize the `PCA9685_ServoEval` class to assist with setting PWM frequencies when working with servos. + +We will be using `Wire1`, which is only available on boards with SDA1/SCL1 (e.g. Due/Mega/etc.) - change to `Wire` if `Wire1` is unavailable. + +```Arduino +#include "PCA9685.h" + +PCA9685 pwmController(Wire1); // Library using Wire1 @400kHz, and default B000000 (A5-A0) i2c address + +// Linearly interpolates between standard 2.5%/12.5% phase length (102/512) for -90°/+90° +PCA9685_ServoEval pwmServo1; + +// Testing our second servo has found that -90° sits at 128, 0° at 324, and +90° at 526. +// Since 324 isn't precisely in the middle, a cubic spline will be used to smoothly +// interpolate PWM values, which will account for said discrepancy. Additionally, since +// 324 is closer to 128 than 526, there is slightly less resolution in the -90° to 0° +// range while slightly more in the 0° to +90° range. +PCA9685_ServoEval pwmServo2(128,324,526); + +void setup() { + Serial.begin(115200); // Begin Serial and Wire1 interfaces + Wire1.begin(); + + pwmController.resetDevices(); // Resets all PCA9685 devices on i2c line + + pwmController.init(); // Initializes module using default totem-pole driver mode, and default disabled phase balancer + + pwmController.setPWMFreqServo(); // 50Hz provides standard 20ms servo phase length + + pwmController.setChannelPWM(0, pwmServo1.pwmForAngle(-90)); + Serial.println(pwmController.getChannelPWM(0)); // Should output 102 for -90° + + // Showing linearity for midpoint, 205 away from both -90° and 90° + Serial.println(pwmServo1.pwmForAngle(0)); // Should output 307 for 0° + + pwmController.setChannelPWM(0, pwmServo1.pwmForAngle(90)); + Serial.println(pwmController.getChannelPWM(0)); // Should output 512 for +90° + + pwmController.setChannelPWM(1, pwmServo2.pwmForAngle(-90)); + Serial.println(pwmController.getChannelPWM(1)); // Should output 128 for -90° + + // Showing less resolution in the -90° to 0° range + Serial.println(pwmServo2.pwmForAngle(-45)); // Should output 225 for -45°, 97 away from -90° + + pwmController.setChannelPWM(1, pwmServo2.pwmForAngle(0)); + Serial.println(pwmController.getChannelPWM(1)); // Should output 324 for 0° + + // Showing more resolution in the 0° to +90° range + Serial.println(pwmServo2.pwmForAngle(45)); // Should output 424 for +45°, 102 away from +90° + + pwmController.setChannelPWM(1, pwmServo2.pwmForAngle(90)); + Serial.println(pwmController.getChannelPWM(1)); // Should output 526 for +90° +} + +void loop() { +} + +``` + +### Software i2c Example + +In this example, we utilize a popular software i2c library for chips that do not have a hardware i2c bus, available at . + +If one uncomments the line below inside the main header file (or defines it via custom build flag), software i2c mode for the library will be enabled. Additionally, you will need to correctly define `SCL_PIN`, `SCL_PORT`, `SDA_PIN`, and `SDA_PORT` according to your setup. `I2C_FASTMODE=1` should be set for 16MHz+ processors. Lastly note that, while in software i2c mode, the i2c clock speed returned by the library (via `getI2CSpeed()`) is only an upper bound and may not represent the actual i2c clock speed set nor achieved. + +In PCA9685.h: +```Arduino +// Uncomment or -D this define to enable use of the software i2c library (min 4MHz+ processor). +#define PCA9685_ENABLE_SOFTWARE_I2C // http://playground.arduino.cc/Main/SoftwareI2CLibrary +``` +Alternatively, in platform[.local].txt: +```Arduino +build.extra_flags=-DPCA9685_ENABLE_SOFTWARE_I2C +``` + +In main sketch: +```Arduino +#include "PCA9685.h" + +// Setup defines for SoftI2CMaster are written before library include. That is because +// its header contains the full code definition, and should thus be included only once. +// The values for SCL_PORT and SDA_PORT are dependent upon which pins are used - refer to +// http://www.arduino.cc/en/Reference/PortManipulation to determine what you should use. +#define SCL_PIN 2 +#define SCL_PORT PORTD +#define SDA_PIN 0 +#define SDA_PORT PORTC + +#if F_CPU >= 16000000 +#define I2C_FASTMODE 1 // Running a 16MHz processor allows us to use i2c fast mode +#endif + +#include "SoftI2CMaster.h" // Include must come after setup defines (see library setup) + +PCA9685 pwmController; // Library using default B000000 (A5-A0) i2c address + +void setup() { + Serial.begin(115200); // Begin Serial and SoftI2C interfaces + i2c_init(); + + pwmController.resetDevices(); // Resets all PCA9685 devices on i2c line + + // Initializes module using software linear phase balancer, and open-drain style driver mode + pwmController.init(PCA9685_PhaseBalancer_Linear, + PCA9685_OutputDriverMode_OpenDrain); + + pwmController.setChannelPWM(0, 2048); // Should see a 50% duty cycle along the 5ms phase width +} + +void loop() { +} + +``` + +## Module Info + +In this example, we enable debug output support to print out module diagnostic information. + +If one uncomments the line below inside the main header file (or defines it via custom build flag), debug output support will be enabled and the `printModuleInfo()` method will become available. Calling this method will display information about the module itself, including initalized states, register values, current settings, etc. Additionally, all library calls being made will display internal debug information about the structure of the call itself. An example of this output is shown below. + +In PCA9685.h: +```Arduino +// Uncomment or -D this define to enable debug output. +#define PCA9685_ENABLE_DEBUG_OUTPUT +``` +Alternatively, in platform[.local].txt: +```Arduino +build.extra_flags=-DPCA9685_ENABLE_DEBUG_OUTPUT +``` + +In main sketch: +```Arduino +#include "PCA9685.h" + +PCA9685 pwmController; // Library using default B000000 (A5-A0) i2c address, and default Wire @400kHz + +void setup() { + Serial.begin(115200); // Begin Serial and Wire interfaces + Wire.begin(); + + pwmController.init(); // Initializes module using default totem-pole driver mode, and default disabled phase balancer + + pwmController.printModuleInfo(); // Prints module diagnostic information +} + +void loop() { +} + +``` + +In serial monitor: +``` + ~~~ PCA9685 Module Info ~~~ + +i2c Address: 0x40 +i2c Instance: 0: Wire +i2c Speed: 400kHz + +Phase Balancer: 0: PCA9685_PhaseBalancer_None + +Proxy Addresser: false + +Mode1 Register: + PCA9685::readRegister regAddress: 0x0 + PCA9685::readRegister retVal: 0x20 +0x20, Bitset: PCA9685_MODE1_AUTOINC + +Mode2 Register: + PCA9685::readRegister regAddress: 0x1 + PCA9685::readRegister retVal: 0x4 +0x4, Bitset: PCA9685_MODE2_OUTDRV_TPOLE + +SubAddress1 Register: + PCA9685::readRegister regAddress: 0x2 + PCA9685::readRegister retVal: 0xE2 +0xE2 + +SubAddress2 Register: + PCA9685::readRegister regAddress: 0x3 + PCA9685::readRegister retVal: 0xE4 +0xE4 + +SubAddress3 Register: + PCA9685::readRegister regAddress: 0x4 + PCA9685::readRegister retVal: 0xE8 +0xE8 + +AllCall Register: + PCA9685::readRegister regAddress: 0x5 + PCA9685::readRegister retVal: 0xE0 +0xE0 + +``` diff --git a/Screen Shot 2012-02-18 at 16.55.07.png b/Screen Shot 2012-02-18 at 16.55.07.png deleted file mode 100644 index d7a19e9..0000000 Binary files a/Screen Shot 2012-02-18 at 16.55.07.png and /dev/null differ diff --git a/examples/BasicLEDTest/BasicLEDTest.ino b/examples/BasicLEDTest/BasicLEDTest.ino deleted file mode 100644 index b029e23..0000000 --- a/examples/BasicLEDTest/BasicLEDTest.ino +++ /dev/null @@ -1,48 +0,0 @@ -/***************** - * This example demonstrates the library assuming that LEDs are connected to the outputs - * It will run through all 16 LEDs turning them up and down in 10 steps. - * The PCA9685 chip is assumed to have PINS A0,A1 and A2 grounded (LOW) and A3,A4 and A5 set to VCC (5V, HIGH) - * - * - kasper - */ - -#include -#include - - -PCA9685 ledDriver; - -void setup() -{ - Serial.begin(9600); // set up serial - Serial.println("Serial Started"); - - Wire.begin(); // Wire must be started! - ledDriver.begin(B111000); // Address pins A5-A0 set to B111000 - ledDriver.init(); - - -} - -void loop() -{ - // Notice: setLEDDimmed cannot be used in a loop to give you a pleasant - // "turning-up" of the LED. This is because each time you set a level for a LED - // it will calculate random timing intervals for the PWM function in the chip - // This is done in order to distribute current consumptions of the full time period. - - for(int level=5;level>=0;level--) { - for(int i=0; i<16; i++) { - ledDriver.setLEDDimmed(i,level*20); - delay(100); - } - } - for(int level=0;level<=5;level++) { - for(int i=0; i<16; i++) { - ledDriver.setLEDDimmed(i,level*20); - delay(100); - } - } -} - - diff --git a/examples/BatchingExample/BatchingExample.ino b/examples/BatchingExample/BatchingExample.ino new file mode 100644 index 0000000..701b72a --- /dev/null +++ b/examples/BatchingExample/BatchingExample.ino @@ -0,0 +1,46 @@ +// PCA9685-Arduino Batching Example +// In this example, we randomly select PWM frequencies on all 12 outputs and allow them +// to drive for 5 seconds before changing them. + +#include "PCA9685.h" + +PCA9685 pwmController(B010101); // Library using B010101 (A5-A0) i2c address, and default Wire @400kHz + +void setup() { + Serial.begin(115200); // Begin Serial and Wire interfaces + Wire.begin(); + + pwmController.resetDevices(); // Resets all PCA9685 devices on i2c line + + pwmController.init(); // Initializes module using default totem-pole driver mode, and default phase balancer + + pwmController.setPWMFrequency(500); // Set PWM freq to 500Hz (default is 200Hz, supports 24Hz to 1526Hz) + + randomSeed(analogRead(0)); // Use white noise for our randomness +} + +void loop() { + uint16_t pwms[12]; + pwms[0] = random(0, 4096); + pwms[1] = random(0, 4096); + pwms[2] = random(0, 4096); + pwms[3] = random(0, 4096); + pwms[4] = random(0, 4096); + pwms[5] = random(0, 4096); + pwms[6] = random(0, 4096); + pwms[7] = random(0, 4096); + pwms[8] = random(0, 4096); + pwms[9] = random(0, 4096); + pwms[10] = random(0, 4096); + pwms[11] = random(0, 4096); + pwmController.setChannelsPWM(0, 12, pwms); + delay(5000); + + // NOTE: Many chips use a BUFFER_LENGTH size of 32, and in that case writing 12 + // channels will take 2 i2c transactions because only 7 channels can fit in a single + // i2c buffer transaction at a time. This may cause a slight offset flicker between + // the first 7 and remaining 5 channels, but can be offset by experimenting with a + // channel update mode of PCA9685_ChannelUpdateMode_AfterAck. This will make each + // channel update immediately upon sending of the Ack signal after each PWM command + // is executed rather than at the Stop signal at the end of the i2c transaction. +} diff --git a/examples/ModuleInfo/ModuleInfo.ino b/examples/ModuleInfo/ModuleInfo.ino new file mode 100644 index 0000000..5704513 --- /dev/null +++ b/examples/ModuleInfo/ModuleInfo.ino @@ -0,0 +1,33 @@ +// PCA9685-Arduino Module Info +// In this example, we enable debug output support to print out module diagnostic +// information. If one uncomments the line below inside the main header file (or defines +// it via custom build flag), debug output support will be enabled and the +// printModuleInfo() method will become available. Calling this method will display +// information about the module itself, including initalized states, register values, +// current settings, etc. Additionally, all library calls being made will display +// internal debug information about the structure of the call itself. You may refer to +// https://forum.arduino.cc/index.php?topic=602603.0 on how to define custom build flags +// manually via modifying platform[.local].txt. +// +// In PCA9685.h: +// // Uncomment or -D this define to enable debug output. +// #define PCA9685_ENABLE_DEBUG_OUTPUT +// +// Alternatively, in platform[.local].txt: +// build.extra_flags=-DPCA9685_ENABLE_DEBUG_OUTPUT + +#include "PCA9685.h" + +PCA9685 pwmController; // Library using default B000000 (A5-A0) i2c address, and default Wire @400kHz + +void setup() { + Serial.begin(115200); // Begin Serial and Wire interfaces + Wire.begin(); + + pwmController.init(); // Initializes module using default totem-pole driver mode, and default disabled phase balancer + + pwmController.printModuleInfo(); // Prints module diagnostic information +} + +void loop() { +} diff --git a/examples/MultiDeviceProxyExample/MultiDeviceProxyExample.ino b/examples/MultiDeviceProxyExample/MultiDeviceProxyExample.ino new file mode 100644 index 0000000..b8e0c76 --- /dev/null +++ b/examples/MultiDeviceProxyExample/MultiDeviceProxyExample.ino @@ -0,0 +1,41 @@ +// PCA9685-Arduino Multi-Device Proxy Example +// In this example, we use a special instance to control other modules attached to it via +// the ALL_CALL register. + +#include "PCA9685.h" + +PCA9685 pwmController1(B000000); // Library using B000000 (A5-A0) i2c address, and default Wire @400kHz +PCA9685 pwmController2(B000001); // Library using B000001 (A5-A0) i2c address, and default Wire @400kHz + +// Not a real device, will act as a proxy to pwmController1 and pwmController2, using all-call i2c address 0xE0, and default Wire @400kHz +PCA9685 pwmControllerAll(PCA9685_I2C_DEF_ALLCALL_PROXYADR); + +void setup() { + Serial.begin(115200); // Begin Serial and Wire interfaces + Wire.begin(); + + pwmControllerAll.resetDevices(); // Resets all PCA9685 devices on i2c line + + pwmController1.init(); // Initializes first module using default totem-pole driver mode, and default disabled phase balancer + pwmController2.init(); // Initializes second module using default totem-pole driver mode, and default disabled phase balancer + + pwmControllerAll.initAsProxyAddresser(); // Initializes 'fake' module as all-call proxy addresser + + // Enables all-call support to module from 'fake' all-call proxy addresser + pwmController1.enableAllCallAddress(pwmControllerAll.getI2CAddress()); + pwmController2.enableAllCallAddress(pwmControllerAll.getI2CAddress()); // On both + + pwmController1.setChannelOff(0); // Turn channel 0 off + pwmController2.setChannelOff(0); // On both + + pwmControllerAll.setChannelPWM(0, 4096); // Enables full on on both pwmController1 and pwmController2 + + Serial.println(pwmController1.getChannelPWM(0)); // Should output 4096 + Serial.println(pwmController2.getChannelPWM(0)); // Should also output 4096 + + // Note: Various parts of functionality of the proxy class instance are actually + // disabled - typically anything that involves a read command being issued. +} + +void loop() { +} diff --git a/examples/Scanning/Scanning.ino b/examples/Scanning/Scanning.ino deleted file mode 100644 index 1cf0b50..0000000 --- a/examples/Scanning/Scanning.ino +++ /dev/null @@ -1,52 +0,0 @@ -/***************** - - - * - kasper - */ - -#include -#include - - -// no-cost stream operator as described at -// http://arduiniana.org/libraries/streaming/ -template -inline Print &operator <<(Print &obj, T arg) -{ - obj.print(arg); - return obj; -} - -PCA9685 ledDriver; - - - -void setup() -{ - Serial.begin(9600); // set up serial - Serial << F("\n- - - - - - - -\nSerial Started\n"); - - Wire.begin(); // Wire must be started! - -} - -void loop() -{ - Serial << "\n"; - - for(uint8_t i=0;i<=63;i++) { - // Set up each board: - ledDriver.begin(i); // Address pins A5-A0 set to B111000 - - if (ledDriver.init()) { - Serial << "\nBoard #" << i << " found\n"; - } else { - Serial << "."; - } - delay(10); - } -} - - - - diff --git a/examples/ServoEvaluatorExample/ServoEvaluatorExample.ino b/examples/ServoEvaluatorExample/ServoEvaluatorExample.ino new file mode 100644 index 0000000..c3513fa --- /dev/null +++ b/examples/ServoEvaluatorExample/ServoEvaluatorExample.ino @@ -0,0 +1,56 @@ +// PCA9685-Arduino Servo Evaluator Example +// In this example, we utilize the ServoEvaluator class to assist with setting PWM +// frequencies when working with servos. We will be using Wire1, which is only available +// on boards with SDA1/SCL1 (e.g. Due/Mega/etc.) - change to Wire if Wire1 is unavailable. + +#include "PCA9685.h" + +PCA9685 pwmController(Wire1); // Library using Wire1 @400kHz, and default B000000 (A5-A0) i2c address + +// Linearly interpolates between standard 2.5%/12.5% phase length (102/512) for -90°/+90° +PCA9685_ServoEval pwmServo1; + +// Testing our second servo has found that -90° sits at 128, 0° at 324, and +90° at 526. +// Since 324 isn't precisely in the middle, a cubic spline will be used to smoothly +// interpolate PWM values, which will account for said discrepancy. Additionally, since +// 324 is closer to 128 than 526, there is slightly less resolution in the -90° to 0° +// range while slightly more in the 0° to +90° range. +PCA9685_ServoEval pwmServo2(128,324,526); + +void setup() { + Serial.begin(115200); // Begin Serial and Wire1 interfaces + Wire1.begin(); + + pwmController.resetDevices(); // Resets all PCA9685 devices on i2c line + + pwmController.init(); // Initializes module using default totem-pole driver mode, and default disabled phase balancer + + pwmController.setPWMFreqServo(); // 50Hz provides standard 20ms servo phase length + + pwmController.setChannelPWM(0, pwmServo1.pwmForAngle(-90)); + Serial.println(pwmController.getChannelPWM(0)); // Should output 102 for -90° + + // Showing linearity for midpoint, 205 away from both -90° and 90° + Serial.println(pwmServo1.pwmForAngle(0)); // Should output 307 for 0° + + pwmController.setChannelPWM(0, pwmServo1.pwmForAngle(90)); + Serial.println(pwmController.getChannelPWM(0)); // Should output 512 for +90° + + pwmController.setChannelPWM(1, pwmServo2.pwmForAngle(-90)); + Serial.println(pwmController.getChannelPWM(1)); // Should output 128 for -90° + + // Showing less resolution in the -90° to 0° range + Serial.println(pwmServo2.pwmForAngle(-45)); // Should output 225 for -45°, 97 away from -90° + + pwmController.setChannelPWM(1, pwmServo2.pwmForAngle(0)); + Serial.println(pwmController.getChannelPWM(1)); // Should output 324 for 0° + + // Showing more resolution in the 0° to +90° range + Serial.println(pwmServo2.pwmForAngle(45)); // Should output 424 for +45°, 102 away from +90° + + pwmController.setChannelPWM(1, pwmServo2.pwmForAngle(90)); + Serial.println(pwmController.getChannelPWM(1)); // Should output 526 for +90° +} + +void loop() { +} diff --git a/examples/SimpleExample/SimpleExample.ino b/examples/SimpleExample/SimpleExample.ino new file mode 100644 index 0000000..05e8e04 --- /dev/null +++ b/examples/SimpleExample/SimpleExample.ino @@ -0,0 +1,23 @@ +// PCA9685-Arduino Simple Example + +#include "PCA9685.h" + +PCA9685 pwmController; // Library using default B000000 (A5-A0) i2c address, and default Wire @400kHz + +void setup() { + Serial.begin(115200); // Begin Serial and Wire interfaces + Wire.begin(); + + pwmController.resetDevices(); // Resets all PCA9685 devices on i2c line + + pwmController.init(); // Initializes module using default totem-pole driver mode, and default disabled phase balancer + + pwmController.setPWMFrequency(100); // Set PWM freq to 100Hz (default is 200Hz, supports 24Hz to 1526Hz) + + pwmController.setChannelPWM(0, 128 << 4); // Set PWM to 128/255, shifted into 4096-land + + Serial.println(pwmController.getChannelPWM(0)); // Should output 2048, which is 128 << 4 +} + +void loop() { +} diff --git a/examples/SoftwareI2CExample/SoftwareI2CExample.ino b/examples/SoftwareI2CExample/SoftwareI2CExample.ino new file mode 100644 index 0000000..b02ac82 --- /dev/null +++ b/examples/SoftwareI2CExample/SoftwareI2CExample.ino @@ -0,0 +1,50 @@ +// PCA9685-Arduino Software i2c Example +// In this example, we utilize a popular software i2c library for chips that do not have +// a hardware i2c bus. If one uncomments the line below inside the main header file (or +// defines it via custom build flag), software i2c mode for the library will be enabled. +// Additionally, you will need to correctly define SCL_PIN, SCL_PORT, SDA_PIN, and +// SDA_PORT according to your setup. I2C_FASTMODE=1 should be set for 16MHz+ processors. +// You may refer to https://forum.arduino.cc/index.php?topic=602603.0 on how to define +// custom build flags manually via modifying platform[.local].txt. +// +// In PCA9685.h: +// // Uncomment or -D this define to enable use of the software i2c library (min 4MHz+ processor). +// #define PCA9685_ENABLE_SOFTWARE_I2C // http://playground.arduino.cc/Main/SoftwareI2CLibrary +// +// Alternatively, in platform[.local].txt: +// build.extra_flags=-DPCA9685_ENABLE_SOFTWARE_I2C + +#include "PCA9685.h" + +// Setup defines for SoftI2CMaster are written before library include. That is because +// its header contains the full code definition, and should thus be included only once. +// The values for SCL_PORT and SDA_PORT are dependent upon which pins are used - refer to +// http://www.arduino.cc/en/Reference/PortManipulation to determine what you should use. +#define SCL_PIN 2 +#define SCL_PORT PORTD +#define SDA_PIN 0 +#define SDA_PORT PORTC + +#if F_CPU >= 16000000 +#define I2C_FASTMODE 1 // Running a 16MHz processor allows us to use i2c fast mode +#endif + +#include "SoftI2CMaster.h" // Include must come after setup defines (see library setup) + +PCA9685 pwmController; // Library using default B000000 (A5-A0) i2c address + +void setup() { + Serial.begin(115200); // Begin Serial and SoftI2C interfaces + i2c_init(); + + pwmController.resetDevices(); // Resets all PCA9685 devices on i2c line + + // Initializes module using software linear phase balancer, and open-drain style driver mode + pwmController.init(PCA9685_PhaseBalancer_Linear, + PCA9685_OutputDriverMode_OpenDrain); + + pwmController.setChannelPWM(0, 2048); // Should see a 50% duty cycle along the 5ms phase width +} + +void loop() { +} diff --git a/library.json b/library.json new file mode 100644 index 0000000..49cff25 --- /dev/null +++ b/library.json @@ -0,0 +1,62 @@ +{ + "name": "PCA9685-Arduino", + "version": "1.2.15", + "keywords": "PCA9685, pwm, i2c, controller, driver", + "description": "Library to control a PCA9685 16-channel PWM driver module from an Arduino board.", + "license": "GPL-3.0-or-later", + "authors": [ + { + "name": "NachtRaveVL", + "email": "nachtravevl@gmail.com", + "url": "https://github.com/NachtRaveVL", + "maintainer": true + }, + { + "name": "Vitska", + "url": "https://github.com/vitska" + }, + { + "name": "Kasper Skårhøj", + "url": "https://github.com/kasperskaarhoj" + } + ], + "homepage": "https://github.com/NachtRaveVL/PCA9685-Arduino/", + "repository": { + "type": "git", + "url": "https://github.com/NachtRaveVL/PCA9685-Arduino.git" + }, + "examples": [ + { + "name": "SimpleExample", + "base": "examples/SimpleExample", + "files": ["SimpleExample.ino"] + }, + { + "name": "BatchingExample", + "base": "examples/BatchingExample", + "files": ["BatchingExample.ino"] + }, + { + "name": "MultiDeviceProxyExample", + "base": "examples/MultiDeviceProxyExample", + "files": ["MultiDeviceProxyExample.ino"] + }, + { + "name": "ServoEvaluatorExample", + "base": "examples/ServoEvaluatorExample", + "files": ["ServoEvaluatorExample.ino"] + }, + { + "name": "SoftwareI2CExample", + "base": "examples/SoftwareI2CExample", + "files": ["SoftwareI2CExample.ino"] + }, + { + "name": "ModuleInfo", + "base": "examples/ModuleInfo", + "files": ["ModuleInfo.ino"] + } + ], + "frameworks": "arduino", + "platforms": "*" +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..8a28f23 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=PCA9685 16-Channel PWM Driver Module Library +version=1.2.15 +author=NachtRaveVL , Vitska, Kasper Skårhøj +maintainer=NachtRaveVL +sentence=Library to control a PCA9685 16-channel PWM driver module from an Arduino board. +paragraph=This library allows communication with boards running a PCA6985 16-channel PWM driver module. It supports a wide range of available functionality, from setting the output PWM frequecy, allowing multi-device proxy addressing, and provides an assistant class for working with Servos. +category=Device Control +url=https://github.com/NachtRaveVL/PCA9685-Arduino +architectures=* diff --git a/src/PCA9685.cpp b/src/PCA9685.cpp new file mode 100644 index 0000000..4167460 --- /dev/null +++ b/src/PCA9685.cpp @@ -0,0 +1,1079 @@ +/* Arduino Library for the PCA9685 16-Channel PWM Driver Module. + Copyright (C) 2016-2020 NachtRaveVL + Copyright (C) 2012 Kasper Skårhøj + PCA9685 Main +*/ + +#include "PCA9685.h" +#include + +#define PCA9685_I2C_BASE_MODULE_ADDRESS (byte)0x40 +#define PCA9685_I2C_BASE_MODULE_ADRMASK (byte)0x3F +#define PCA9685_I2C_BASE_PROXY_ADDRESS (byte)0xE0 +#define PCA9685_I2C_BASE_PROXY_ADRMASK (byte)0xFE + +// Register addresses from data sheet +#define PCA9685_MODE1_REG (byte)0x00 +#define PCA9685_MODE2_REG (byte)0x01 +#define PCA9685_SUBADR1_REG (byte)0x02 +#define PCA9685_SUBADR2_REG (byte)0x03 +#define PCA9685_SUBADR3_REG (byte)0x04 +#define PCA9685_ALLCALL_REG (byte)0x05 +#define PCA9685_LED0_REG (byte)0x06 // Start of LEDx regs, 4B per reg, 2B on phase, 2B off phase, little-endian +#define PCA9685_PRESCALE_REG (byte)0xFE +#define PCA9685_ALLLED_REG (byte)0xFA + +// Mode1 register values +#define PCA9685_MODE1_RESTART (byte)0x80 +#define PCA9685_MODE1_EXTCLK (byte)0x40 +#define PCA9685_MODE1_AUTOINC (byte)0x20 +#define PCA9685_MODE1_SLEEP (byte)0x10 +#define PCA9685_MODE1_SUBADR1 (byte)0x08 +#define PCA9685_MODE1_SUBADR2 (byte)0x04 +#define PCA9685_MODE1_SUBADR3 (byte)0x02 +#define PCA9685_MODE1_ALLCALL (byte)0x01 + +// Mode2 register values +#define PCA9685_MODE2_OUTDRV_TPOLE (byte)0x04 +#define PCA9685_MODE2_INVRT (byte)0x10 +#define PCA9685_MODE2_OUTNE_TPHIGH (byte)0x01 +#define PCA9685_MODE2_OUTNE_HIGHZ (byte)0x02 +#define PCA9685_MODE2_OCH_ONACK (byte)0x08 + +#define PCA9685_SW_RESET (byte)0x06 // Sent to address 0x00 to reset all devices on Wire line +#define PCA9685_PWM_FULL (uint16_t)0x1000 // Special value for full on/full off LEDx modes +#define PCA9685_PWM_MASK (uint16_t)0x0FFF // Mask for 12-bit/4096 possible phase positions + +#define PCA9685_CHANNEL_COUNT 16 +#define PCA9685_MIN_CHANNEL 0 +#define PCA9685_MAX_CHANNEL (PCA9685_CHANNEL_COUNT - 1) +#define PCA9685_ALLLED_CHANNEL -1 // Special value for ALLLED registers + + +#ifdef PCA9685_USE_SOFTWARE_I2C +boolean __attribute__((noinline)) i2c_init(void); +bool __attribute__((noinline)) i2c_start(uint8_t addr); +void __attribute__((noinline)) PCA9685_i2c_stop(void) asm("ass_i2c_stop"); +bool __attribute__((noinline)) PCA9685_i2c_write(uint8_t value) asm("ass_i2c_write"); +uint8_t __attribute__((noinline)) i2c_read(bool last); +#endif + +#ifndef PCA9685_USE_SOFTWARE_I2C + +PCA9685::PCA9685(byte i2cAddress, TwoWire& i2cWire, uint32_t i2cSpeed) + // I2C 7-bit address is B 1 A5 A4 A3 A2 A1 A0 + // RW lsb bit added by Arduino core TWI library + : _i2cAddress(i2cAddress), + _i2cWire(&i2cWire), + _i2cSpeed(i2cSpeed), + _driverMode(PCA9685_OutputDriverMode_Undefined), + _enabledMode(PCA9685_OutputEnabledMode_Undefined), + _disabledMode(PCA9685_OutputDisabledMode_Undefined), + _updateMode(PCA9685_ChannelUpdateMode_Undefined), + _phaseBalancer(PCA9685_PhaseBalancer_Undefined), + _isProxyAddresser(false), + _lastI2CError(0) +{ } + +PCA9685::PCA9685(TwoWire& i2cWire, uint32_t i2cSpeed, byte i2cAddress) + : _i2cAddress(i2cAddress), + _i2cWire(&i2cWire), + _i2cSpeed(i2cSpeed), + _driverMode(PCA9685_OutputDriverMode_Undefined), + _enabledMode(PCA9685_OutputEnabledMode_Undefined), + _disabledMode(PCA9685_OutputDisabledMode_Undefined), + _updateMode(PCA9685_ChannelUpdateMode_Undefined), + _phaseBalancer(PCA9685_PhaseBalancer_Undefined), + _isProxyAddresser(false), + _lastI2CError(0) +{ } + +#else + +PCA9685::PCA9685(byte i2cAddress) + : _i2cAddress(i2cAddress), + _readBytes(0), + _driverMode(PCA9685_OutputDriverMode_Undefined), + _enabledMode(PCA9685_OutputEnabledMode_Undefined), + _disabledMode(PCA9685_OutputDisabledMode_Undefined), + _updateMode(PCA9685_ChannelUpdateMode_Undefined), + _phaseBalancer(PCA9685_PhaseBalancer_Undefined), + _isProxyAddresser(false), + _lastI2CError(0) +{ } + +#endif // /ifndef PCA9685_USE_SOFTWARE_I2C + +void PCA9685::resetDevices() { +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::resetDevices"); +#endif + + i2cWire_begin(); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif + + i2cWire_beginTransmission(0x00); + i2cWire_write(PCA9685_SW_RESET); + i2cWire_endTransmission(); + + delayMicroseconds(10); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif +} + +void PCA9685::init(PCA9685_OutputDriverMode driverMode, + PCA9685_OutputEnabledMode enabledMode, + PCA9685_OutputDisabledMode disabledMode, + PCA9685_ChannelUpdateMode updateMode, + PCA9685_PhaseBalancer phaseBalancer) { + if (_isProxyAddresser) return; + + _i2cAddress = PCA9685_I2C_BASE_MODULE_ADDRESS | (_i2cAddress & PCA9685_I2C_BASE_MODULE_ADRMASK); + + _driverMode = driverMode; + _enabledMode = enabledMode; + _disabledMode = disabledMode; + _updateMode = updateMode; + _phaseBalancer = phaseBalancer; + + assert(!(_driverMode == PCA9685_OutputDriverMode_OpenDrain && _disabledMode == PCA9685_OutputDisabledMode_High && "Unsupported combination")); + + byte mode2Val = getMode2Value(); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print("PCA9685::init mode2Val: 0x"); + Serial.print(mode2Val, HEX); + Serial.print(", i2cAddress: 0x"); + Serial.print(_i2cAddress, HEX); + Serial.print(", i2cWire#: "); + Serial.print(getWireInterfaceNumber()); + Serial.print(", i2cSpeed: "); + Serial.print(roundf(getI2CSpeed() / 1000.0f)); Serial.print("kHz"); + Serial.print(", driverMode: "); + switch(_driverMode) { + case PCA9685_OutputDriverMode_OpenDrain: Serial.print("OpenDrain"); break; + case PCA9685_OutputDriverMode_TotemPole: Serial.print("TotemPole"); break; + case PCA9685_OutputDriverMode_Count: + case PCA9685_OutputDriverMode_Undefined: + Serial.print(_driverMode); break; + } + Serial.print(", enabledMode: "); + switch (_enabledMode) { + case PCA9685_OutputEnabledMode_Normal: Serial.print("Normal"); break; + case PCA9685_OutputEnabledMode_Inverted: Serial.print("Inverted"); break; + case PCA9685_OutputEnabledMode_Count: + case PCA9685_OutputEnabledMode_Undefined: + Serial.print(_enabledMode); break; + } + Serial.print(", disabledMode: "); + switch (_disabledMode) { + case PCA9685_OutputDisabledMode_Low: Serial.print("Low"); break; + case PCA9685_OutputDisabledMode_High: Serial.print("High"); break; + case PCA9685_OutputDisabledMode_Floating: Serial.print("Floating"); break; + case PCA9685_OutputDisabledMode_Count: + case PCA9685_OutputDisabledMode_Undefined: + Serial.print(_disabledMode); break; + } + Serial.print(", updateMode: "); + switch (_updateMode) { + case PCA9685_ChannelUpdateMode_AfterStop: Serial.print("AfterStop"); break; + case PCA9685_ChannelUpdateMode_AfterAck: Serial.print("AfterAck"); break; + case PCA9685_ChannelUpdateMode_Count: + case PCA9685_ChannelUpdateMode_Undefined: + Serial.print(_updateMode); break; + } + Serial.print(", phaseBalancer: "); + switch(_phaseBalancer) { + case PCA9685_PhaseBalancer_None: Serial.print("None"); break; + case PCA9685_PhaseBalancer_Linear: Serial.print("Linear"); break; + case PCA9685_PhaseBalancer_Count: + case PCA9685_PhaseBalancer_Undefined: + Serial.print(_phaseBalancer); break; + } + Serial.println(""); +#endif + + i2cWire_begin(); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif + + writeRegister(PCA9685_MODE1_REG, PCA9685_MODE1_RESTART | PCA9685_MODE1_AUTOINC); + writeRegister(PCA9685_MODE2_REG, mode2Val); +} + +void PCA9685::init(PCA9685_PhaseBalancer phaseBalancer, + PCA9685_OutputDriverMode driverMode, + PCA9685_OutputEnabledMode enabledMode, + PCA9685_OutputDisabledMode disabledMode, + PCA9685_ChannelUpdateMode updateMode) { + init(driverMode, enabledMode, disabledMode, updateMode, phaseBalancer); +} + +void PCA9685::initAsProxyAddresser() { + if (_driverMode != PCA9685_OutputDriverMode_Undefined) return; + + _i2cAddress = PCA9685_I2C_BASE_PROXY_ADDRESS | (_i2cAddress & PCA9685_I2C_BASE_PROXY_ADRMASK); + _isProxyAddresser = true; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::initAsProxyAddresser i2cAddress: 0x"); + Serial.print(_i2cAddress, HEX); + Serial.print(", i2cWire#: "); + Serial.print(getWireInterfaceNumber()); + Serial.print(", i2cSpeed: "); + Serial.print(roundf(getI2CSpeed() / 1000.0f)); Serial.print("kHz"); + Serial.println(""); +#endif + + i2cWire_begin(); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif +} + +byte PCA9685::getI2CAddress() { + return _i2cAddress; +} + +uint32_t PCA9685::getI2CSpeed() { +#ifndef PCA9685_USE_SOFTWARE_I2C + return _i2cSpeed; +#else +#if I2C_FASTMODE || F_CPU >= 16000000 + return 400000; +#else + return 100000; +#endif +#endif // ifndef PCA9685_USE_SOFTWARE_I2C +} + +PCA9685_OutputDriverMode PCA9685::getOutputDriverMode() { + return _driverMode; +} + +PCA9685_OutputEnabledMode PCA9685::getOutputEnabledMode() { + return _enabledMode; +} + +PCA9685_OutputDisabledMode PCA9685::getOutputDisabledMode() { + return _disabledMode; +} + +PCA9685_ChannelUpdateMode PCA9685::getChannelUpdateMode() { + return _updateMode; +} + +PCA9685_PhaseBalancer PCA9685::getPhaseBalancer() { + return _phaseBalancer; +} + +byte PCA9685::getMode2Value() { + byte mode2Val = (byte)0x00; + + if (_driverMode == PCA9685_OutputDriverMode_TotemPole) { + mode2Val |= PCA9685_MODE2_OUTDRV_TPOLE; + } + + if (_enabledMode == PCA9685_OutputEnabledMode_Inverted) { + mode2Val |= PCA9685_MODE2_INVRT; + } + + if (_disabledMode == PCA9685_OutputDisabledMode_High) { + mode2Val |= PCA9685_MODE2_OUTNE_TPHIGH; + } + else if (_disabledMode == PCA9685_OutputDisabledMode_Floating) { + mode2Val |= PCA9685_MODE2_OUTNE_HIGHZ; + } + + if (_updateMode == PCA9685_ChannelUpdateMode_AfterAck) { + mode2Val |= PCA9685_MODE2_OCH_ONACK; + } + + return mode2Val; +} + +void PCA9685::setPWMFrequency(float pwmFrequency) { + if (pwmFrequency < 0 || _isProxyAddresser) return; + + // This equation comes from section 7.3.5 of the datasheet, but the rounding has been + // removed because it isn't needed. Lowest freq is 23.84, highest is 1525.88. + int preScalerVal = (25000000 / (4096 * pwmFrequency)) - 1; + if (preScalerVal > 255) preScalerVal = 255; + if (preScalerVal < 3) preScalerVal = 3; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print("PCA9685::setPWMFrequency pwmFrequency: "); + Serial.print(pwmFrequency); + Serial.print(", preScalerVal: 0x"); + Serial.println(preScalerVal, HEX); +#endif + + // The PRE_SCALE register can only be set when the SLEEP bit of MODE1 register is set to logic 1. + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg = (mode1Reg & ~PCA9685_MODE1_RESTART) | PCA9685_MODE1_SLEEP)); + writeRegister(PCA9685_PRESCALE_REG, (byte)preScalerVal); + + // It takes 500us max for the oscillator to be up and running once SLEEP bit has been set to logic 0. + writeRegister(PCA9685_MODE1_REG, (mode1Reg = (mode1Reg & ~PCA9685_MODE1_SLEEP) | PCA9685_MODE1_RESTART)); + delayMicroseconds(500); +} + +void PCA9685::setPWMFreqServo() { + setPWMFrequency(50); +} + +void PCA9685::setChannelOn(int channel) { + if (channel < 0 || channel > 15) return; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::setChannelOn"); +#endif + + writeChannelBegin(channel); + writeChannelPWM(PCA9685_PWM_FULL, 0); // time_on = FULL; time_off = 0; + writeChannelEnd(); +} + +void PCA9685::setChannelOff(int channel) { + if (channel < 0 || channel > 15) return; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::setChannelOff"); +#endif + + writeChannelBegin(channel); + writeChannelPWM(0, PCA9685_PWM_FULL); // time_on = 0; time_off = FULL; + writeChannelEnd(); +} + +void PCA9685::setChannelPWM(int channel, uint16_t pwmAmount) { + if (channel < 0 || channel > 15) return; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::setChannelPWM"); +#endif + + writeChannelBegin(channel); + + uint16_t phaseBegin, phaseEnd; + getPhaseCycle(channel, pwmAmount, &phaseBegin, &phaseEnd); + + writeChannelPWM(phaseBegin, phaseEnd); + + writeChannelEnd(); +} + +void PCA9685::setChannelsPWM(int begChannel, int numChannels, const uint16_t *pwmAmounts) { + if (begChannel < 0 || begChannel > 15 || numChannels < 0) return; + if (begChannel + numChannels > 16) numChannels -= (begChannel + numChannels) - 16; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print("PCA9685::setChannelsPWM numChannels: "); + Serial.println(numChannels); +#endif + + // From avr/libraries/Wire.h and avr/libraries/utility/twi.h, BUFFER_LENGTH controls + // how many channels can be written at once. Therefore, we loop around until all + // channels have been written out into their registers. I2C_BUFFER_LENGTH is used in + // other architectures, so we rely on PCA9685_I2C_BUFFER_LENGTH logic to sort it out. + + while (numChannels > 0) { + writeChannelBegin(begChannel); + +#ifndef PCA9685_USE_SOFTWARE_I2C + int maxChannels = min(numChannels, (PCA9685_I2C_BUFFER_LENGTH - 1) / 4); +#else // TODO: Software I2C doesn't have buffer length restrictions? -NR + int maxChannels = numChannels; +#endif + while (maxChannels-- > 0) { + uint16_t phaseBegin, phaseEnd; + getPhaseCycle(begChannel++, *pwmAmounts++, &phaseBegin, &phaseEnd); + + writeChannelPWM(phaseBegin, phaseEnd); + --numChannels; + } + + writeChannelEnd(); + if (_lastI2CError) return; + } +} + +void PCA9685::setAllChannelsPWM(uint16_t pwmAmount) { +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::setAllChannelsPWM"); +#endif + + writeChannelBegin(PCA9685_ALLLED_CHANNEL); + + uint16_t phaseBegin, phaseEnd; + getPhaseCycle(PCA9685_ALLLED_CHANNEL, pwmAmount, &phaseBegin, &phaseEnd); + + writeChannelPWM(phaseBegin, phaseEnd); + + writeChannelEnd(); +} + +uint16_t PCA9685::getChannelPWM(int channel) { + if (channel < 0 || channel > 15 || _isProxyAddresser) return 0; + + byte regAddress = PCA9685_LED0_REG + (channel << 2); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print("PCA9685::getChannelPWM channel: "); + Serial.print(channel); + Serial.print(", regAddress: 0x"); + Serial.println(regAddress, HEX); +#endif + + i2cWire_beginTransmission(_i2cAddress); + i2cWire_write(regAddress); + if (i2cWire_endTransmission()) { +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif + return 0; + } + + int bytesRead = i2cWire_requestFrom((uint8_t)_i2cAddress, 4); + if (bytesRead != 4) { + while (bytesRead-- > 0) + i2cWire_read(); +#ifdef PCA9685_USE_SOFTWARE_I2C + PCA9685_i2c_stop(); // Manually have to send stop bit in software i2c mode +#endif + _lastI2CError = 4; +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif + return 0; + } + +#ifndef PCA9685_SWAP_PWM_BEG_END_REGS + uint16_t phaseBegin = (uint16_t)i2cWire_read(); + phaseBegin |= (uint16_t)i2cWire_read() << 8; + uint16_t phaseEnd = (uint16_t)i2cWire_read(); + phaseEnd |= (uint16_t)i2cWire_read() << 8; +#else + uint16_t phaseEnd = (uint16_t)i2cWire_read(); + phaseEnd |= (uint16_t)i2cWire_read() << 8; + uint16_t phaseBegin = (uint16_t)i2cWire_read(); + phaseBegin |= (uint16_t)i2cWire_read() << 8; +#endif + +#ifdef PCA9685_USE_SOFTWARE_I2C + PCA9685_i2c_stop(); // Manually have to send stop bit in software i2c mode +#endif + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print(" PCA9685::getChannelPWM phaseBegin: "); + Serial.print(phaseBegin); + Serial.print(", phaseEnd: "); + Serial.println(phaseEnd); +#endif + + // See datasheet section 7.3.3 + uint16_t retVal; + if (phaseEnd >= PCA9685_PWM_FULL) + // Full OFF + // Figure 11 Example 4: full OFF takes precedence over full ON + // See also remark after Table 7 + retVal = 0; + else if (phaseBegin >= PCA9685_PWM_FULL) + // Full ON + // Figure 9 Example 3 + retVal = PCA9685_PWM_FULL; + else if (phaseBegin <= phaseEnd) + // start and finish in same cycle + // Section 7.3.3 example 1 + retVal = phaseEnd - phaseBegin; + else + // span cycles + // Section 7.3.3 example 2 + retVal = (phaseEnd + PCA9685_PWM_FULL) - phaseBegin; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print(" PCA9685::getChannelPWM retVal: "); + Serial.println(retVal); +#endif + + return retVal; +} + +void PCA9685::enableAllCallAddress(byte i2cAddressAllCall) { + if (_isProxyAddresser) return; + + byte i2cAddress = PCA9685_I2C_BASE_PROXY_ADDRESS | (i2cAddressAllCall & PCA9685_I2C_BASE_PROXY_ADRMASK); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print("PCA9685::enableAllCallAddress i2cAddressAllCall: 0x"); + Serial.println(i2cAddress, HEX); +#endif + + writeRegister(PCA9685_ALLCALL_REG, i2cAddress); + + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE1_ALLCALL)); +} + +void PCA9685::enableSub1Address(byte i2cAddressSub1) { + if (_isProxyAddresser) return; + + byte i2cAddress = PCA9685_I2C_BASE_PROXY_ADDRESS | (i2cAddressSub1 & PCA9685_I2C_BASE_PROXY_ADRMASK); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print("PCA9685::enableSub1Address i2cAddressSub1: 0x"); + Serial.println(i2cAddress, HEX); +#endif + + writeRegister(PCA9685_SUBADR1_REG, i2cAddress); + + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE1_SUBADR1)); +} + +void PCA9685::enableSub2Address(byte i2cAddressSub2) { + if (_isProxyAddresser) return; + + byte i2cAddress = PCA9685_I2C_BASE_PROXY_ADDRESS | (i2cAddressSub2 & PCA9685_I2C_BASE_PROXY_ADRMASK); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print("PCA9685::enableSub2Address i2cAddressSub2: 0x"); + Serial.println(i2cAddress, HEX); +#endif + + writeRegister(PCA9685_SUBADR2_REG, i2cAddress); + + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE1_SUBADR2)); +} + +void PCA9685::enableSub3Address(byte i2cAddressSub3) { + if (_isProxyAddresser) return; + + byte i2cAddress = PCA9685_I2C_BASE_PROXY_ADDRESS | (i2cAddressSub3 & PCA9685_I2C_BASE_PROXY_ADRMASK); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print("PCA9685::enableSub3Address i2cAddressSub3: 0x"); + Serial.println(i2cAddress, HEX); +#endif + + writeRegister(PCA9685_SUBADR3_REG, i2cAddress); + + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE1_SUBADR3)); +} + +void PCA9685::disableAllCallAddress() { + if (_isProxyAddresser) return; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::disableAllCallAddress"); +#endif + + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg &= ~PCA9685_MODE1_ALLCALL)); +} + +void PCA9685::disableSub1Address() { + if (_isProxyAddresser) return; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::disableSub1Address"); +#endif + + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg &= ~PCA9685_MODE1_SUBADR1)); +} + +void PCA9685::disableSub2Address() { + if (_isProxyAddresser) return; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::disableSub2Address"); +#endif + + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg &= ~PCA9685_MODE1_SUBADR2)); +} + +void PCA9685::disableSub3Address() { + if (_isProxyAddresser) return; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::disableSub3Address"); +#endif + + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg &= ~PCA9685_MODE1_SUBADR3)); +} + +void PCA9685::enableExtClockLine() { +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.println("PCA9685::enableExtClockLine"); +#endif + + // The PRE_SCALE register can only be set when the SLEEP bit of MODE1 register is set to logic 1. + byte mode1Reg = readRegister(PCA9685_MODE1_REG); + writeRegister(PCA9685_MODE1_REG, (mode1Reg = (mode1Reg & ~PCA9685_MODE1_RESTART) | PCA9685_MODE1_SLEEP)); + writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE1_EXTCLK)); + + // It takes 500us max for the oscillator to be up and running once SLEEP bit has been set to logic 0. + writeRegister(PCA9685_MODE1_REG, (mode1Reg = (mode1Reg & ~PCA9685_MODE1_SLEEP) | PCA9685_MODE1_RESTART)); + delayMicroseconds(500); +} + +byte PCA9685::getLastI2CError() { + return _lastI2CError; +} + +void PCA9685::getPhaseCycle(int channel, uint16_t pwmAmount, uint16_t *phaseBegin, uint16_t *phaseEnd) { + if (channel == PCA9685_ALLLED_CHANNEL) { + *phaseBegin = 0; // ALLLED should not receive a phase shifted begin value + } else { + // Get phase delay begin + switch(_phaseBalancer) { + case PCA9685_PhaseBalancer_None: + case PCA9685_PhaseBalancer_Count: + case PCA9685_PhaseBalancer_Undefined: + *phaseBegin = 0; + break; + + case PCA9685_PhaseBalancer_Linear: + // Distribute high phase area over more of the duty cycle range to balance load + *phaseBegin = (channel * ((4096 / 16) / 16)) & PCA9685_PWM_MASK; + break; + } + } + + // See datasheet section 7.3.3 + if (pwmAmount == 0) { + // Full OFF -> time_end[bit12] = 1 + *phaseEnd = PCA9685_PWM_FULL; + } + else if (pwmAmount >= PCA9685_PWM_FULL) { + // Full ON -> time_beg[bit12] = 1, time_end[bit12] = + *phaseBegin |= PCA9685_PWM_FULL; + *phaseEnd = 0; + } + else { + *phaseEnd = (*phaseBegin + pwmAmount) & PCA9685_PWM_MASK; + } +} + +void PCA9685::writeChannelBegin(int channel) { + byte regAddress; + + if (channel != PCA9685_ALLLED_CHANNEL) + regAddress = PCA9685_LED0_REG + (channel * 0x04); + else + regAddress = PCA9685_ALLLED_REG; + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print(" PCA9685::writeChannelBegin channel: "); + Serial.print(channel); + Serial.print(", regAddress: 0x"); + Serial.println(regAddress, HEX); +#endif + + i2cWire_beginTransmission(_i2cAddress); + i2cWire_write(regAddress); +} + +void PCA9685::writeChannelPWM(uint16_t phaseBegin, uint16_t phaseEnd) { +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print(" PCA9685::writeChannelPWM phaseBegin: "); + Serial.print(phaseBegin); + Serial.print(", phaseEnd: "); + Serial.println(phaseEnd); +#endif + +#ifndef PCA9685_SWAP_PWM_BEG_END_REGS + i2cWire_write(lowByte(phaseBegin)); + i2cWire_write(highByte(phaseBegin)); + i2cWire_write(lowByte(phaseEnd)); + i2cWire_write(highByte(phaseEnd)); +#else + i2cWire_write(lowByte(phaseEnd)); + i2cWire_write(highByte(phaseEnd)); + i2cWire_write(lowByte(phaseBegin)); + i2cWire_write(highByte(phaseBegin)); +#endif +} + +void PCA9685::writeChannelEnd() { + i2cWire_endTransmission(); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif +} + +void PCA9685::writeRegister(byte regAddress, byte value) { +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print(" PCA9685::writeRegister regAddress: 0x"); + Serial.print(regAddress, HEX); + Serial.print(", value: 0x"); + Serial.println(value, HEX); +#endif + + i2cWire_beginTransmission(_i2cAddress); + i2cWire_write(regAddress); + i2cWire_write(value); + i2cWire_endTransmission(); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif +} + +byte PCA9685::readRegister(byte regAddress) { +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print(" PCA9685::readRegister regAddress: 0x"); + Serial.println(regAddress, HEX); +#endif + + i2cWire_beginTransmission(_i2cAddress); + i2cWire_write(regAddress); + if (i2cWire_endTransmission()) { +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif + return 0; + } + + int bytesRead = i2cWire_requestFrom((uint8_t)_i2cAddress, 1); + if (bytesRead != 1) { + while (bytesRead-- > 0) + i2cWire_read(); +#ifdef PCA9685_USE_SOFTWARE_I2C + PCA9685_i2c_stop(); // Manually have to send stop bit in software i2c mode +#endif + _lastI2CError = 4; +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + checkForErrors(); +#endif + return 0; + } + + byte retVal = i2cWire_read(); + +#ifdef PCA9685_USE_SOFTWARE_I2C + PCA9685_i2c_stop(); // Manually have to send stop bit in software i2c mode +#endif + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + Serial.print(" PCA9685::readRegister retVal: 0x"); + Serial.println(retVal, HEX); +#endif + + return retVal; +} + +void PCA9685::i2cWire_begin() { + _lastI2CError = 0; +#ifndef PCA9685_USE_SOFTWARE_I2C + _i2cWire->setClock(getI2CSpeed()); +#endif +} + +void PCA9685::i2cWire_beginTransmission(uint8_t addr) { + _lastI2CError = 0; +#ifndef PCA9685_USE_SOFTWARE_I2C + _i2cWire->beginTransmission(addr); +#else + i2c_start(addr); +#endif +} + +uint8_t PCA9685::i2cWire_endTransmission(void) { +#ifndef PCA9685_USE_SOFTWARE_I2C + return (_lastI2CError = _i2cWire->endTransmission()); +#else + PCA9685_i2c_stop(); // Manually have to send stop bit in software i2c mode + return (_lastI2CError = 0); +#endif +} + +uint8_t PCA9685::i2cWire_requestFrom(uint8_t addr, uint8_t len) { +#ifndef PCA9685_USE_SOFTWARE_I2C + return _i2cWire->requestFrom(addr, (size_t)len); +#else + i2c_start(addr | 0x01); + return (_readBytes = len); +#endif +} + +size_t PCA9685::i2cWire_write(uint8_t data) { +#ifndef PCA9685_USE_SOFTWARE_I2C + return _i2cWire->write(data); +#else + return (size_t)PCA9685_i2c_write(data); +#endif +} + +uint8_t PCA9685::i2cWire_read(void) { +#ifndef PCA9685_USE_SOFTWARE_I2C + return (uint8_t)(_i2cWire->read() & 0xFF); +#else + if (_readBytes > 1) { + _readBytes -= 1; + return (uint8_t)(i2c_read(false) & 0xFF); + } + else { + _readBytes = 0; + return (uint8_t)(i2c_read(true) & 0xFF); + } +#endif +} + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + +int PCA9685::getWireInterfaceNumber() { +#ifndef PCA9685_USE_SOFTWARE_I2C + if (_i2cWire == &Wire) return 0; +#if WIRE_INTERFACES_COUNT > 1 + if (_i2cWire == &Wire1) return 1; +#endif +#if WIRE_INTERFACES_COUNT > 2 + if (_i2cWire == &Wire2) return 2; +#endif +#if WIRE_INTERFACES_COUNT > 3 + if (_i2cWire == &Wire3) return 3; +#endif +#if WIRE_INTERFACES_COUNT > 4 + if (_i2cWire == &Wire4) return 4; +#endif +#if WIRE_INTERFACES_COUNT > 5 + if (_i2cWire == &Wire5) return 5; +#endif +#endif // /ifndef PCA9685_USE_SOFTWARE_I2C + return -1; +} + +static const char *textForWireInterfaceNumber(int wireNum) { +#ifndef PCA9685_USE_SOFTWARE_I2C + switch (wireNum) { + case 0: return "Wire"; + case 1: return "Wire1"; + case 2: return "Wire2"; + case 3: return "Wire3"; + case 4: return "Wire4"; + case 5: return "Wire5"; + default: return ""; + } +#else + return "SoftwareI2C"; +#endif // /ifndef PCA9685_USE_SOFTWARE_I2C +} + +void PCA9685::printModuleInfo() { + Serial.println(""); Serial.println(" ~~~ PCA9685 Module Info ~~~"); + + Serial.println(""); Serial.print("i2c Address: "); + Serial.print("0x"); Serial.println(_i2cAddress, HEX); + Serial.print("i2c Instance: "); + Serial.print(getWireInterfaceNumber()); Serial.print(": "); + Serial.println(textForWireInterfaceNumber(getWireInterfaceNumber())); + Serial.print("i2c Speed: "); + Serial.print(roundf(getI2CSpeed() / 1000.0f)); Serial.println("kHz"); + + Serial.println(""); Serial.print("Phase Balancer: "); + Serial.print(_phaseBalancer); Serial.print(": "); + switch (_phaseBalancer) { + case PCA9685_PhaseBalancer_None: + Serial.println("PCA9685_PhaseBalancer_None"); break; + case PCA9685_PhaseBalancer_Linear: + Serial.println("PCA9685_PhaseBalancer_Linear"); break; + case PCA9685_PhaseBalancer_Count: + case PCA9685_PhaseBalancer_Undefined: + Serial.println(""); break; + } + + if (!_isProxyAddresser) { + Serial.println(""); Serial.println("Proxy Addresser: false"); + + Serial.println(""); Serial.println("Mode1 Register:"); + const byte mode1Reg = readRegister(PCA9685_MODE1_REG); + Serial.print("0x"); Serial.print(mode1Reg, HEX); + Serial.print(", Bitset:"); + if (mode1Reg & PCA9685_MODE1_RESTART) + Serial.print(" PCA9685_MODE1_RESTART"); + if (mode1Reg & PCA9685_MODE1_EXTCLK) + Serial.print(" PCA9685_MODE1_EXTCLK"); + if (mode1Reg & PCA9685_MODE1_AUTOINC) + Serial.print(" PCA9685_MODE1_AUTOINC"); + if (mode1Reg & PCA9685_MODE1_SLEEP) + Serial.print(" PCA9685_MODE1_SLEEP"); + if (mode1Reg & PCA9685_MODE1_SUBADR1) + Serial.print(" PCA9685_MODE1_SUBADR1"); + if (mode1Reg & PCA9685_MODE1_SUBADR2) + Serial.print(" PCA9685_MODE1_SUBADR2"); + if (mode1Reg & PCA9685_MODE1_SUBADR3) + Serial.print(" PCA9685_MODE1_SUBADR3"); + if (mode1Reg & PCA9685_MODE1_ALLCALL) + Serial.print(" PCA9685_MODE1_ALLCALL"); + Serial.println(""); + + Serial.println(""); Serial.println("Mode2 Register:"); + const byte mode2Reg = readRegister(PCA9685_MODE2_REG); + Serial.print("0x"); Serial.print(mode2Reg, HEX); + Serial.print(", Bitset:"); + if (mode2Reg & PCA9685_MODE2_OUTDRV_TPOLE) + Serial.print(" PCA9685_MODE2_OUTDRV_TPOLE"); + if (mode2Reg & PCA9685_MODE2_INVRT) + Serial.print(" PCA9685_MODE2_INVRT"); + if (mode2Reg & PCA9685_MODE2_OUTNE_TPHIGH) + Serial.print(" PCA9685_MODE2_OUTNE_TPHIGH"); + if (mode2Reg & PCA9685_MODE2_OUTNE_HIGHZ) + Serial.print(" PCA9685_MODE2_OUTNE_HIGHZ"); + if (mode2Reg & PCA9685_MODE2_OCH_ONACK) + Serial.print(" PCA9685_MODE2_OCH_ONACK"); + Serial.println(""); + + Serial.println(""); Serial.println("SubAddress1 Register:"); + const byte subAdr1Reg = readRegister(PCA9685_SUBADR1_REG); + Serial.print("0x"); Serial.println(subAdr1Reg, HEX); + + Serial.println(""); Serial.println("SubAddress2 Register:"); + const byte subAdr2Reg = readRegister(PCA9685_SUBADR2_REG); + Serial.print("0x"); Serial.println(subAdr2Reg, HEX); + + Serial.println(""); Serial.println("SubAddress3 Register:"); + const byte subAdr3Reg = readRegister(PCA9685_SUBADR3_REG); + Serial.print("0x"); Serial.println(subAdr3Reg, HEX); + + Serial.println(""); Serial.println("AllCall Register:"); + const byte allCallReg = readRegister(PCA9685_ALLCALL_REG); + Serial.print("0x"); Serial.println(allCallReg, HEX); + } else { + Serial.println(""); Serial.println("Proxy Addresser: true"); + } +} + +static const char *textForI2CError(byte errorCode) { + switch (errorCode) { + case 0: + return "Success"; + case 1: + return "Data too long to fit in transmit buffer"; + case 2: + return "Received NACK on transmit of address"; + case 3: + return "Received NACK on transmit of data"; + default: + return "Other error"; + } +} + +void PCA9685::checkForErrors() { + if (_lastI2CError) { + Serial.print(" PCA9685::checkErrors lastI2CError: "); + Serial.print(_lastI2CError); + Serial.print(": "); + Serial.println(textForI2CError(getLastI2CError())); + } +} + +#endif // /ifdef PCA9685_ENABLE_DEBUG_OUTPUT + +PCA9685_ServoEval::PCA9685_ServoEval(uint16_t minPWMAmount, uint16_t maxPWMAmount) + : _coeff(NULL), _isCSpline(false) +{ + minPWMAmount = min(minPWMAmount, PCA9685_PWM_FULL); + maxPWMAmount = constrain(maxPWMAmount, minPWMAmount, PCA9685_PWM_FULL); + + _coeff = new float[2]; + _isCSpline = false; + + _coeff[0] = minPWMAmount; + _coeff[1] = (maxPWMAmount - minPWMAmount) / 180.0f; +} + +PCA9685_ServoEval::PCA9685_ServoEval(uint16_t minPWMAmount, uint16_t midPWMAmount, uint16_t maxPWMAmount) + : _coeff(NULL), _isCSpline(false) +{ + minPWMAmount = min(minPWMAmount, PCA9685_PWM_FULL); + midPWMAmount = constrain(midPWMAmount, minPWMAmount, PCA9685_PWM_FULL); + maxPWMAmount = constrain(maxPWMAmount, midPWMAmount, PCA9685_PWM_FULL); + + if (maxPWMAmount - midPWMAmount != midPWMAmount - minPWMAmount) { + _coeff = new float[8]; + _isCSpline = true; + + // Cubic spline code adapted from: https://shiftedbits.org/2011/01/30/cubic-spline-interpolation/ + /* "THE BEER-WARE LICENSE" (Revision 42): Devin Lane wrote this [part]. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, and you + * think this stuff is worth it, you can buy me a beer in return. */ + // TODO: Looks like I owe Devin Lane a beer. -NR + + float x[3] = { 0, 90, 180 }; + float y[3] = { (float)minPWMAmount, (float)midPWMAmount, (float)maxPWMAmount }; + float c[3], b[2], d[2], h[2], l[1], u[2], a[1], z[2]; // n = 3 + + h[0] = x[1] - x[0]; + u[0] = z[0] = 0; + c[2] = 0; + + for (int i = 1; i < 2; ++i) { + h[i] = x[i + 1] - x[i]; + l[i - 1] = (2 * (x[i + 1] - x[i - 1])) - h[i - 1] * u[i - 1]; + u[i] = h[i] / l[i - 1]; + a[i - 1] = (3 / h[i]) * (y[i + 1] - y[i]) - (3 / h[i - 1]) * (y[i] - y[i - 1]); + z[i] = (a[i - 1] - h[i - 1] * z[i - 1]) / l[i - 1]; + } + + for (int i = 1; i >= 0; --i) { + c[i] = z[i] - u[i] * c[i + 1]; + b[i] = (y[i + 1] - y[i]) / h[i] - (h[i] * (c[i + 1] + 2 * c[i])) / 3; + d[i] = (c[i + 1] - c[i]) / (3 * h[i]); + + _coeff[4 * i + 0] = y[i]; // a + _coeff[4 * i + 1] = b[i]; // b + _coeff[4 * i + 2] = c[i]; // c + _coeff[4 * i + 3] = d[i]; // d + } + } + else { + _coeff = new float[2]; + _isCSpline = false; + + _coeff[0] = minPWMAmount; + _coeff[1] = (maxPWMAmount - minPWMAmount) / 180.0f; + } +} + +PCA9685_ServoEval::~PCA9685_ServoEval() { + if (_coeff) { delete[] _coeff; _coeff = NULL; } +} + +uint16_t PCA9685_ServoEval::pwmForAngle(float angle) { + float retVal; + angle = constrain(angle + 90, 0, 180); + + if (!_isCSpline) { + retVal = _coeff[0] + (_coeff[1] * angle); + } + else { + if (angle <= 90) { + retVal = _coeff[0] + (_coeff[1] * angle) + (_coeff[2] * angle * angle) + (_coeff[3] * angle * angle * angle); + } + else { + angle -= 90; + retVal = _coeff[4] + (_coeff[5] * angle) + (_coeff[6] * angle * angle) + (_coeff[7] * angle * angle * angle); + } + } + + return (uint16_t)min((uint16_t)roundf(retVal), PCA9685_PWM_FULL); +}; + +uint16_t PCA9685_ServoEval::pwmForSpeed(float speed) { + return pwmForAngle(speed * 90.0f); +} diff --git a/src/PCA9685.h b/src/PCA9685.h new file mode 100644 index 0000000..893b9e3 --- /dev/null +++ b/src/PCA9685.h @@ -0,0 +1,338 @@ +/* Arduino Library for the PCA9685 16-Channel PWM Driver Module. + Copyright (C) 2016-2020 NachtRaveVL + Copyright (C) 2012 Kasper Skårhøj + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Created by Kasper Skårhøj, August 3rd, 2012. + Forked by Vitska, June 18th, 2016. + Forked by NachtRaveVL, July 29th, 2016. + + PCA9685-Arduino - Version 1.2.15 +*/ + +#ifndef PCA9685_H +#define PCA9685_H + +// Library Setup + +// NOTE: While editing the main header file isn't ideal, it is often the easiest given +// the Arduino IDE's limited custom build flag support. Editing this header file directly +// will affect all projects compiled on your system using these library files. + +// Uncomment or -D this define to enable use of the software i2c library (min 4MHz+ processor). +//#define PCA9685_ENABLE_SOFTWARE_I2C // http://playground.arduino.cc/Main/SoftwareI2CLibrary + +// Uncomment or -D this define to swap PWM low(begin)/high(end) phase values in register reads/writes (needed for some chip manufacturers). +//#define PCA9685_SWAP_PWM_BEG_END_REGS + +// Uncomment or -D this define to enable debug output. +//#define PCA9685_ENABLE_DEBUG_OUTPUT + +// Hookup Callouts +// -PLEASE READ- +// Many digital servos run on a 20ms pulse width (50Hz update frequency) based duty cycle, +// and do not utilize the entire pulse width for their control. Typically, 2.5% of the +// 20ms pulse width (0.5ms) represents -90° offset, and 12.5% of the 20ms pulse width +// (2.5ms) represents +90° offset. This roughly translates to raw PCA9685 PWM values of +// 102 and 512 (out of the 4096/12-bit value range) for their -90°/+90° offset control. +// However, these may need to be adjusted to fit your specific servo (e.g. some we've +// tested run ~130 to ~525 for their -90°/+90° offset control). Be aware that driving +// some 180° servos too far past their -90°/+90° operational range can cause a little +// plastic limiter pin to break off and get stuck inside of the servo's gearing, which +// could potentially cause the servo to become jammed and no longer function. Continuous +// servos operate in much the same fashion as 180° servos, but instead of the 2.5%/12.5% +// pulse width controlling a -90°/+90° offset it controls a -1x/+1x speed multiplier, +// with 0x being parked/no-movement and -1x/+1x being maximum speed in either direction. + +#if defined(ARDUINO) && ARDUINO >= 100 +#include +#else +#include +#endif + +#ifndef PCA9685_ENABLE_SOFTWARE_I2C +#include +#if BUFFER_LENGTH +#define PCA9685_I2C_BUFFER_LENGTH BUFFER_LENGTH +#elif I2C_BUFFER_LENGTH +#define PCA9685_I2C_BUFFER_LENGTH I2C_BUFFER_LENGTH +#else +#warning "i2c buffer length not defined - using default value of 32, which may not be correct for your microcontroller. Check Wire.h (or similar) for your hardware and manually define BUFFER_LENGTH or I2C_BUFFER_LENGTH to remove this warning." +#define PCA9685_I2C_BUFFER_LENGTH 32 +#endif // /if BUFFER_LENGTH +#else +#include +#define PCA9685_USE_SOFTWARE_I2C +#endif // /ifndef PCA9685_ENABLE_SOFTWARE_I2C + + +// Default proxy addresser i2c addresses +#define PCA9685_I2C_DEF_ALLCALL_PROXYADR (byte)0xE0 // Default AllCall i2c proxy address +#define PCA9685_I2C_DEF_SUB1_PROXYADR (byte)0xE2 // Default Sub1 i2c proxy address +#define PCA9685_I2C_DEF_SUB2_PROXYADR (byte)0xE4 // Default Sub2 i2c proxy address +#define PCA9685_I2C_DEF_SUB3_PROXYADR (byte)0xE8 // Default Sub3 i2c proxy address + + +// Output driver control mode (see datasheet Table 12 and Fig 13, 14, and 15 concerning correct +// usage of OUTDRV). +enum PCA9685_OutputDriverMode { + PCA9685_OutputDriverMode_OpenDrain, // Module outputs in an open-drain (aka direct connection) style structure with 400mA @5v total sink current, useful for LEDs and low-power Servos + PCA9685_OutputDriverMode_TotemPole, // Module outputs in a totem-pole (aka push-pull) style structure with 400mA @5v total sink current and 160mA total source current, useful for external drivers (default) + + PCA9685_OutputDriverMode_Count, // Internal use only + PCA9685_OutputDriverMode_Undefined = -1 // Internal use only +}; +// NOTE: Totem-pole mode should be used when an external N-type or P-type driver is in +// use, which provides actual sourcing current while open-drain mode doesn't. At max +// channel capacity, the sink current limit is 25mA@5v per channel while the source +// current limit, in totem-pole mode, is 10mA@5v per channel. However, from datasheet +// Table 6. subnote [1]: "Some newer LEDs include integrated Zener diodes to limit +// voltage transients, reduce EMI, and protect the LEDs, and these -MUST- be driven only +// in the open-drain mode to prevent over-heating the IC." Also from datasheet, Section +// 10. question 5: "in the push-pull architecture there is a low resistance path to GND +// through the Zener and this [causes] the IC to overheat." + +// Output-enabled/active-low-OE-pin=LOW driver output mode (see datasheet Table 12 and +// Fig 13, 14, and 15 concerning correct usage of INVRT). +enum PCA9685_OutputEnabledMode { + PCA9685_OutputEnabledMode_Normal, // When OE is enabled/LOW, channels output a normal signal, useful for N-type external drivers (default) + PCA9685_OutputEnabledMode_Inverted, // When OE is enabled/LOW, channels output an inverted signal, useful for P-type external drivers or direct connection + + PCA9685_OutputEnabledMode_Count, // Internal use only + PCA9685_OutputEnabledMode_Undefined = -1 // Internal use only +}; +// NOTE: Polarity inversion is often set according to if an external N-type driver +// (should not use INVRT) or external P-type driver/direct connection (should use INVRT) +// is used. Most breakouts have just a 220Ω resistor between the individual channel +// outputs of the IC and PWM output pins, which is useful when powering LEDs. The V+ rail +// of most breakouts can connect through a 10v 1000μF decoupling capacitor, typically +// already installed on most breakouts, which can reduce voltage spikes and ground bounce +// during phase shifts at the start/end of the PWM high phase when many channel devices +// are connected together. See https://forums.adafruit.com/viewtopic.php?f=8&t=127421 and +// https://forums.adafruit.com/viewtopic.php?f=8&t=162688 for information on installing +// a decoupling capacitor if need arises. + +// Output-not-enabled/active-low-OE-pin=HIGH driver output mode (see datasheet Section +// 7.4 concerning correct usage of OUTNE). +enum PCA9685_OutputDisabledMode { + PCA9685_OutputDisabledMode_Low, // When OE is disabled/HIGH, channels output a LOW signal (default) + PCA9685_OutputDisabledMode_High, // When OE is disabled/HIGH, channels output a HIGH signal (only available in totem-pole mode) + PCA9685_OutputDisabledMode_Floating, // When OE is disabled/HIGH, channel outputs go into a floating (aka high-impedance/high-Z) state, which may be further refined via external pull-up/pull-down resistors + + PCA9685_OutputDisabledMode_Count, // Internal use only + PCA9685_OutputDisabledMode_Undefined = -1 // Internal use only +}; +// NOTE: Active-low-OE pin is typically used to synchronize multiple PCA9685 devices +// together, but can also be used as an external dimming control signal. + +// Channel update strategy used when multiple channels are being updated in batch. +enum PCA9685_ChannelUpdateMode { + PCA9685_ChannelUpdateMode_AfterStop, // Channel updates commit after full-transmission STOP signal (default) + PCA9685_ChannelUpdateMode_AfterAck, // Channel updates commit after individual channel update ACK signal + + PCA9685_ChannelUpdateMode_Count, // Internal use only + PCA9685_ChannelUpdateMode_Undefined = -1 // Internal use only +}; + +// Software-based phase balancing scheme. +enum PCA9685_PhaseBalancer { + PCA9685_PhaseBalancer_None, // Disables software-based phase balancing, relying on installed hardware to handle current sinkage (default) + PCA9685_PhaseBalancer_Linear, // Uses linear software-based phase balancing, with each channel being a preset 16 steps (out of the 4096/12-bit value range) away from previous channel (may cause LED flickering/skipped-cycles on PWM changes) + + PCA9685_PhaseBalancer_Count, // Internal use only + PCA9685_PhaseBalancer_Undefined = -1 // Internal use only +}; +// NOTE: Software-based phase balancing attempts to further mitigate ground bounce and +// voltage spikes during phase shifts at the start/end of the PWM high phase by shifting +// the leading edge of each successive PWM high phase by some amount. This helps make +// the current sinks occur over the entire duty cycle range instead of all together at +// once. Software-based phase balancing can be useful in certain situations, but in +// practice has been the source of many problems, including the case whereby the PCA9685 +// will skip a cycle between PWM changes when the leading/trailing edge is shifted past a +// certain point. While we may revisit this idea in the future, for now we're content on +// leaving None as the default, and limiting the shift that Linear applies. + + +class PCA9685 { +public: +#ifndef PCA9685_USE_SOFTWARE_I2C + + // Library constructor. Typically called during class instantiation, before setup(). + // The i2c address should be the value of the A5-A0 pins, as the class handles the + // module's base i2c address. It should be a value between 0 and 61, which gives a + // maximum of 62 modules that can be addressed on the same i2c line. + // Boards with more than one i2c line (e.g. Due/Mega/etc.) can supply a different + // Wire instance, such as Wire1 (using SDA1/SCL1), Wire2 (using SDA2/SCL2), etc. + // Supported i2c clock speeds are 100kHz, 400kHz (default), and 1000kHz. + PCA9685(byte i2cAddress = B000000, TwoWire& i2cWire = Wire, uint32_t i2cSpeed = 400000); + + // Convenience constructor for custom Wire instance. See main constructor. + PCA9685(TwoWire& i2cWire, uint32_t i2cSpeed = 400000, byte i2cAddress = B000000); + +#else + + // Library constructor. Typically called during class instantiation, before setup(). + // The i2c address should be the value of the A5-A0 pins, as the class handles the + // module's base i2c address. It should be a value between 0 and 61, which gives a + // maximum of 62 modules that can be addressed on the same i2c line. + // Minimum supported i2c clock speed is 100kHz, which sets minimum processor speed at + // 4MHz+ running in i2c standard mode. For up to 400kHz i2c clock speeds, minimum + // processor speed is 16MHz+ running in i2c fast mode. + PCA9685(byte i2cAddress = B000000); + +#endif + + // Resets modules. Typically called in setup(), before any init()'s. Calling will + // perform a software reset on all PCA9685 devices on the Wire instance, ensuring + // that all PCA9685 devices on that line are properly reset. + void resetDevices(); + + // Initializes module. Typically called in setup(). + // See individual enums for more info. + void init(PCA9685_OutputDriverMode driverMode = PCA9685_OutputDriverMode_TotemPole, + PCA9685_OutputEnabledMode enabledMode = PCA9685_OutputEnabledMode_Normal, + PCA9685_OutputDisabledMode disabledMode = PCA9685_OutputDisabledMode_Low, + PCA9685_ChannelUpdateMode updateMode = PCA9685_ChannelUpdateMode_AfterStop, + PCA9685_PhaseBalancer phaseBalancer = PCA9685_PhaseBalancer_None); + + // Convenience initializer for custom phase balancer. See main init method. + void init(PCA9685_PhaseBalancer phaseBalancer, + PCA9685_OutputDriverMode driverMode = PCA9685_OutputDriverMode_TotemPole, + PCA9685_OutputEnabledMode enabledMode = PCA9685_OutputEnabledMode_Normal, + PCA9685_OutputDisabledMode disabledMode = PCA9685_OutputDisabledMode_Low, + PCA9685_ChannelUpdateMode updateMode = PCA9685_ChannelUpdateMode_AfterStop); + + // Initializes module as a proxy addresser. Typically called in setup(). Used when + // instance talks through to AllCall/Sub1-Sub3 instances as a proxy object. Using + // this method will disable any method that performs a read or conflicts with certain + // states. Proxy addresser i2c addresses must be >= 0xE0, with defaults provided via + // PCA9685_I2C_DEF_[ALLCALL|SUB[1-3]]_PROXYADR defines. + void initAsProxyAddresser(); + + // Mode accessors + byte getI2CAddress(); + uint32_t getI2CSpeed(); + PCA9685_OutputDriverMode getOutputDriverMode(); + PCA9685_OutputEnabledMode getOutputEnabledMode(); + PCA9685_OutputDisabledMode getOutputDisabledMode(); + PCA9685_ChannelUpdateMode getChannelUpdateMode(); + PCA9685_PhaseBalancer getPhaseBalancer(); + + // Min: 24Hz, Max: 1526Hz, Default: 200Hz. As Hz increases channel resolution + // diminishes, as raw pre-scaler value, computed per datasheet, starts to require + // much larger frequency increases for single-digit increases of the raw pre-scaler + // value that ultimately controls the PWM frequency produced. + void setPWMFrequency(float pwmFrequency = 200); + // Sets standard servo frequency of 50Hz. + void setPWMFreqServo(); + + // Turns channel either full on or full off + void setChannelOn(int channel); + void setChannelOff(int channel); + + // PWM amounts 0 - 4096, 0 full off, 4096 full on + void setChannelPWM(int channel, uint16_t pwmAmount); + void setChannelsPWM(int begChannel, int numChannels, const uint16_t *pwmAmounts); + + // Sets all channels, but won't distribute phases + void setAllChannelsPWM(uint16_t pwmAmount); + + // Returns PWM amounts 0 - 4096, 0 full off, 4096 full on + uint16_t getChannelPWM(int channel); + + // Enables multiple talk-through paths via i2c bus (lsb/bit0 must stay 0). To use, + // create a new proxy instance using initAsProxyAddresser() with proper proxy i2c + // address >= 0xE0, and pass that instance's i2c address into desired method below. + void enableAllCallAddress(byte i2cAddressAllCall = PCA9685_I2C_DEF_ALLCALL_PROXYADR); + void enableSub1Address(byte i2cAddressSub1 = PCA9685_I2C_DEF_SUB1_PROXYADR); + void enableSub2Address(byte i2cAddressSub2 = PCA9685_I2C_DEF_SUB2_PROXYADR); + void enableSub3Address(byte i2cAddressSub3 = PCA9685_I2C_DEF_SUB3_PROXYADR); + void disableAllCallAddress(); + void disableSub1Address(); + void disableSub2Address(); + void disableSub3Address(); + + // Allows external clock line to be utilized (power reset required to disable) + void enableExtClockLine(); + + byte getLastI2CError(); + +#ifdef PCA9685_ENABLE_DEBUG_OUTPUT + int getWireInterfaceNumber(); + void printModuleInfo(); + void checkForErrors(); +#endif + +protected: + byte _i2cAddress; // Module's i2c address (default: B000000) +#ifndef PCA9685_USE_SOFTWARE_I2C + TwoWire* _i2cWire; // Wire class instance (unowned) (default: Wire) + uint32_t _i2cSpeed; // Module's i2c clock speed (default: 400000) +#endif + PCA9685_OutputDriverMode _driverMode; // Output driver mode + PCA9685_OutputEnabledMode _enabledMode; // OE enabled output mode + PCA9685_OutputDisabledMode _disabledMode; // OE disabled output mode + PCA9685_ChannelUpdateMode _updateMode; // Channel update mode + PCA9685_PhaseBalancer _phaseBalancer; // Phase balancer scheme + bool _isProxyAddresser; // Proxy addresser flag (disables certain functionality) + byte _lastI2CError; // Last module i2c error + + byte getMode2Value(); + void getPhaseCycle(int channel, uint16_t pwmAmount, uint16_t *phaseBegin, uint16_t *phaseEnd); + + void writeChannelBegin(int channel); + void writeChannelPWM(uint16_t phaseBegin, uint16_t phaseEnd); + void writeChannelEnd(); + + void writeRegister(byte regAddress, byte value); + byte readRegister(byte regAddress); + +#ifdef PCA9685_USE_SOFTWARE_I2C + uint8_t _readBytes; +#endif + void i2cWire_begin(); + void i2cWire_beginTransmission(uint8_t); + uint8_t i2cWire_endTransmission(void); + uint8_t i2cWire_requestFrom(uint8_t, uint8_t); + size_t i2cWire_write(uint8_t); + uint8_t i2cWire_read(void); +}; + +// Class to assist with calculating Servo PWM values from angle/speed values +class PCA9685_ServoEval { +public: + // Uses a linear interpolation method to quickly compute PWM output value. Uses + // default values of 2.5% and 12.5% of phase length for -90/+90 (or -1x/+1x). + PCA9685_ServoEval(uint16_t minPWMAmount = 102, uint16_t maxPWMAmount = 512); + + // Uses a cubic spline to interpolate due to an offsetted zero point that isn't + // exactly between -90/+90 (or -1x/+1x). This takes more time to compute, but gives a + // smoother PWM output value along the entire range. + PCA9685_ServoEval(uint16_t minPWMAmount, uint16_t midPWMAmount, uint16_t maxPWMAmount); + + ~PCA9685_ServoEval(); + + // Returns the PWM value to use given the angle offset (-90 to +90) + uint16_t pwmForAngle(float angle); + + // Returns the PWM value to use given the speed multiplier (-1 to +1) + uint16_t pwmForSpeed(float speed); + +private: + float *_coeff; // a,b,c,d coefficient values + bool _isCSpline; // Cubic spline tracking, for _coeff length +}; + +#endif // /ifndef PCA9685_H diff --git a/support files/PCA9685.pdf b/support files/PCA9685.pdf new file mode 100644 index 0000000..a484c1f Binary files /dev/null and b/support files/PCA9685.pdf differ