Initial commit
This commit is contained in:
commit
b09ec43e98
|
@ -0,0 +1,4 @@
|
|||
.pio
|
||||
*.sublime-workspace
|
||||
avrdude.*
|
||||
flash.bin
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"folders":
|
||||
[
|
||||
{
|
||||
"path": ".",
|
||||
"file_exclude_patterns": ["*.sublime-project"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
& .\avrdude -c usbtiny -p t85 -e -U lfuse:w:0x62:m -U hfuse:w:0xDF:m -U efuse:w:0x62:m -U lock:w:0xFF:m
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,206 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="60mm"
|
||||
height="60mm"
|
||||
viewBox="0 0 60 60"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="PCB.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="81.627849"
|
||||
inkscape:cy="110.46143"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1">
|
||||
<sodipodi:guide
|
||||
position="-19.087798,29.954613"
|
||||
orientation="0,1"
|
||||
id="guide870"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="30.049107,34.584822"
|
||||
orientation="1,0"
|
||||
id="guide872"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-237)">
|
||||
<circle
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.95254982;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path823"
|
||||
cx="105.71244"
|
||||
cy="236.65585"
|
||||
r="1.75"
|
||||
transform="rotate(21.849053)" />
|
||||
<circle
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.95254982;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path823-8"
|
||||
cx="148.71245"
|
||||
cy="236.65585"
|
||||
r="1.75"
|
||||
transform="rotate(21.849053)" />
|
||||
<g
|
||||
id="g4609"
|
||||
inkscape:transform-center-x="17.292411">
|
||||
<g
|
||||
id="g4629"
|
||||
inkscape:transform-center-x="18.804316"
|
||||
inkscape:tile-cx="30.054316"
|
||||
inkscape:tile-cy="30"
|
||||
inkscape:tile-w="14.5"
|
||||
inkscape:tile-h="3.5"
|
||||
inkscape:tile-x0="4"
|
||||
inkscape:tile-y0="28.25">
|
||||
<circle
|
||||
style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.95254982;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path823-86"
|
||||
cx="5.75"
|
||||
cy="267"
|
||||
r="1.75"
|
||||
inkscape:transform-center-x="24.274553" />
|
||||
<circle
|
||||
style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.95254982;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path823-86-0"
|
||||
cx="16.75"
|
||||
cy="267"
|
||||
r="1.75"
|
||||
inkscape:transform-center-x="10.274553"
|
||||
inkscape:tile-cx="30.024553"
|
||||
inkscape:tile-cy="30"
|
||||
inkscape:tile-w="3.5"
|
||||
inkscape:tile-h="3.5"
|
||||
inkscape:tile-x0="18"
|
||||
inkscape:tile-y0="28.25" />
|
||||
</g>
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
inkscape:tiled-clone-of="#g4629"
|
||||
xlink:href="#g4629"
|
||||
id="use4631"
|
||||
inkscape:transform-center-x="-3.2981288"
|
||||
inkscape:transform-center-y="22.0625"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
inkscape:tiled-clone-of="#g4629"
|
||||
xlink:href="#g4629"
|
||||
transform="rotate(45,30.054316,267)"
|
||||
id="use4633"
|
||||
inkscape:transform-center-x="-8.8057855"
|
||||
inkscape:transform-center-y="8.7658406"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
inkscape:tiled-clone-of="#g4629"
|
||||
xlink:href="#g4629"
|
||||
transform="rotate(90,30.054316,267)"
|
||||
id="use4635"
|
||||
inkscape:transform-center-x="-22.102445"
|
||||
inkscape:transform-center-y="3.258184"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
inkscape:tiled-clone-of="#g4629"
|
||||
xlink:href="#g4629"
|
||||
transform="rotate(135,30.054316,267)"
|
||||
id="use4637"
|
||||
inkscape:transform-center-x="-35.399104"
|
||||
inkscape:transform-center-y="8.7658406"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
inkscape:tiled-clone-of="#g4629"
|
||||
xlink:href="#g4629"
|
||||
transform="rotate(180,30.054316,267)"
|
||||
id="use4639"
|
||||
inkscape:transform-center-x="-40.906761"
|
||||
inkscape:transform-center-y="22.0625"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
inkscape:tiled-clone-of="#g4629"
|
||||
xlink:href="#g4629"
|
||||
transform="rotate(-135,30.054316,267)"
|
||||
id="use4641"
|
||||
inkscape:transform-center-x="-35.399104"
|
||||
inkscape:transform-center-y="35.359159"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
inkscape:tiled-clone-of="#g4629"
|
||||
xlink:href="#g4629"
|
||||
transform="rotate(-90,30.054316,267)"
|
||||
id="use4643"
|
||||
inkscape:transform-center-x="-22.102445"
|
||||
inkscape:transform-center-y="40.866816"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
inkscape:tiled-clone-of="#g4629"
|
||||
xlink:href="#g4629"
|
||||
transform="rotate(-45,30.054316,267)"
|
||||
id="use4645"
|
||||
inkscape:transform-center-x="-8.8057855"
|
||||
inkscape:transform-center-y="35.359159"
|
||||
width="100%"
|
||||
height="100%" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.3 KiB |
|
@ -0,0 +1,26 @@
|
|||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; http://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:attiny85]
|
||||
platform = atmelavr
|
||||
board = attiny85
|
||||
board_build.f_cpu = 1000000L
|
||||
framework = arduino
|
||||
|
||||
upload_protocol = usbtiny
|
||||
upload_flags = -e
|
||||
upload_speed = 19200
|
||||
|
||||
; Use 'pio run -t fuses' to set these fuses
|
||||
board_fuses.lfuse = 0x62
|
||||
board_fuses.hfuse = 0xDF
|
||||
board_fuses.efuse = 0x62
|
||||
|
||||
lib_deps = tinySPI
|
|
@ -0,0 +1,242 @@
|
|||
/*!
|
||||
* @file Adafruit_TLC59711.cpp
|
||||
*
|
||||
* @mainpage Adafruit TLC59711 PWM/LED driver
|
||||
*
|
||||
* @section intro_sec Introduction
|
||||
*
|
||||
* This is a library for our Adafruit 12-channel PWM/LED driver
|
||||
*
|
||||
* Pick one up today in the adafruit shop!
|
||||
* ------> http://www.adafruit.com/products/1455
|
||||
*
|
||||
* Two SPI Pins are required to send data: clock and data pin.
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
* @section author Author
|
||||
*
|
||||
* Written by Limor Fried/Ladyada for Adafruit Industries.
|
||||
*
|
||||
* @section license License
|
||||
*
|
||||
* BSD license, all text above must be included in any redistribution
|
||||
*/
|
||||
|
||||
// Modified for tinySPI
|
||||
#include <Adafruit_TLC59711.h>
|
||||
|
||||
/*!
|
||||
* @brief SPI settings
|
||||
* @return Returns nothing
|
||||
*/
|
||||
//SPISettings SPI_SETTINGS(500000, MSBFIRST, SPI_MODE0);
|
||||
|
||||
/*!
|
||||
* @brief Instantiates a new Adafruit_TLC59711 class
|
||||
* @param n
|
||||
* number of connected drivers
|
||||
* @param c
|
||||
* clock pin
|
||||
* @param d
|
||||
* data pin
|
||||
*/
|
||||
Adafruit_TLC59711::Adafruit_TLC59711(uint8_t n, uint8_t c, uint8_t d) {
|
||||
numdrivers = n;
|
||||
_clk = c;
|
||||
_dat = d;
|
||||
|
||||
BCr = BCg = BCb = 0x7F; // default 100% brigthness
|
||||
|
||||
pwmbuffer = (uint16_t *)calloc(2, 12 * n);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Instantiates a new Adafruit_TLC59711 class using provided SPI
|
||||
* @param n
|
||||
* number of connected drivers
|
||||
* @param *theSPI
|
||||
* spi object
|
||||
*/
|
||||
Adafruit_TLC59711::Adafruit_TLC59711(uint8_t n, SPIClass *theSPI) {
|
||||
numdrivers = n;
|
||||
_clk = -1;
|
||||
_dat = -1;
|
||||
_spi = theSPI;
|
||||
|
||||
BCr = BCg = BCb = 0x7F; // default 100% brigthness
|
||||
|
||||
pwmbuffer = (uint16_t *)calloc(2, 12 * n);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Write data throught SPI at MSB
|
||||
* @param d
|
||||
* data
|
||||
*/
|
||||
void Adafruit_TLC59711::spiwriteMSB(uint8_t d) {
|
||||
if (_clk >= 0) {
|
||||
uint32_t b = 0x80;
|
||||
// b <<= (bits-1);
|
||||
for (; b != 0; b >>= 1) {
|
||||
digitalWrite(_clk, LOW);
|
||||
if (d & b)
|
||||
digitalWrite(_dat, HIGH);
|
||||
else
|
||||
digitalWrite(_dat, LOW);
|
||||
digitalWrite(_clk, HIGH);
|
||||
}
|
||||
} else {
|
||||
_spi->transfer(d);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Writes PWM buffer to board
|
||||
*/
|
||||
void Adafruit_TLC59711::write() {
|
||||
//if (_clk < 0) {
|
||||
// _spi->beginTransaction(SPI_SETTINGS);
|
||||
//}
|
||||
|
||||
uint32_t command;
|
||||
|
||||
// Magic word for write
|
||||
command = 0x25;
|
||||
|
||||
command <<= 5;
|
||||
// OUTTMG = 1, EXTGCK = 0, TMGRST = 1, DSPRPT = 1, BLANK = 0 -> 0x16
|
||||
command |= 0x16;
|
||||
|
||||
command <<= 7;
|
||||
command |= BCr;
|
||||
|
||||
command <<= 7;
|
||||
command |= BCg;
|
||||
|
||||
command <<= 7;
|
||||
command |= BCb;
|
||||
|
||||
noInterrupts();
|
||||
for (uint8_t n = 0; n < numdrivers; n++) {
|
||||
spiwriteMSB(command >> 24);
|
||||
spiwriteMSB(command >> 16);
|
||||
spiwriteMSB(command >> 8);
|
||||
spiwriteMSB(command);
|
||||
|
||||
// 12 channels per TLC59711
|
||||
for (int8_t c = 11; c >= 0; c--) {
|
||||
// 16 bits per channel, send MSB first
|
||||
spiwriteMSB(pwmbuffer[n * 12 + c] >> 8);
|
||||
spiwriteMSB(pwmbuffer[n * 12 + c]);
|
||||
}
|
||||
}
|
||||
|
||||
if (_clk >= 0)
|
||||
delayMicroseconds(200);
|
||||
else
|
||||
delayMicroseconds(2);
|
||||
//_spi->endTransaction();
|
||||
|
||||
interrupts();
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Set PWM value on selected channel
|
||||
* @param chan
|
||||
* one from 12 channel (per driver) so there is 12 * number of drivers
|
||||
* @param pwm
|
||||
* pwm value
|
||||
*/
|
||||
void Adafruit_TLC59711::setPWM(uint8_t chan, uint16_t pwm) {
|
||||
if (chan > 12 * numdrivers)
|
||||
return;
|
||||
pwmbuffer[chan] = pwm;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Set RGB value for selected LED
|
||||
* @param lednum
|
||||
* selected LED number that for which value will be set
|
||||
* @param r
|
||||
* red value
|
||||
* @param g
|
||||
* green value
|
||||
* @param b
|
||||
* blue value
|
||||
*/
|
||||
void Adafruit_TLC59711::setLED(uint8_t lednum, uint16_t r, uint16_t g,
|
||||
uint16_t b) {
|
||||
setPWM(lednum * 3, r);
|
||||
setPWM(lednum * 3 + 1, g);
|
||||
setPWM(lednum * 3 + 2, b);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Set the brightness of LED channels to same value
|
||||
* @param BC
|
||||
* Brightness Control value
|
||||
*/
|
||||
void Adafruit_TLC59711::simpleSetBrightness(uint8_t BC) {
|
||||
if (BC > 127) {
|
||||
BC = 127; // maximum possible value since BC can only be 7 bit
|
||||
} else if (BC < 0) {
|
||||
BC = 0;
|
||||
}
|
||||
|
||||
BCr = BCg = BCb = BC;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Set the brightness of LED channels to specific value
|
||||
* @param bcr
|
||||
* Brightness Control Red value
|
||||
* @param bcg
|
||||
* Brightness Control Green value
|
||||
* @param bcb
|
||||
* Brightness Control Blue value
|
||||
*/
|
||||
void Adafruit_TLC59711::setBrightness(uint8_t bcr, uint8_t bcg, uint8_t bcb) {
|
||||
if (bcr > 127) {
|
||||
bcr = 127; // maximum possible value since BC can only be 7 bit
|
||||
} else if (bcr < 0) {
|
||||
bcr = 0;
|
||||
}
|
||||
|
||||
BCr = bcr;
|
||||
|
||||
if (bcg > 127) {
|
||||
bcg = 127; // maximum possible value since BC can only be 7 bit
|
||||
} else if (bcg < 0) {
|
||||
bcg = 0;
|
||||
}
|
||||
|
||||
BCg = bcg;
|
||||
|
||||
if (bcb > 127) {
|
||||
bcb = 127; // maximum possible value since BC can only be 7 bit
|
||||
} else if (bcb < 0) {
|
||||
bcb = 0;
|
||||
}
|
||||
|
||||
BCb = bcb;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Begins SPI connection if there is not empty PWM buffer
|
||||
* @return If successful returns true, otherwise false
|
||||
*/
|
||||
boolean Adafruit_TLC59711::begin() {
|
||||
if (!pwmbuffer)
|
||||
return false;
|
||||
|
||||
if (_clk >= 0) {
|
||||
pinMode(_clk, OUTPUT);
|
||||
pinMode(_dat, OUTPUT);
|
||||
} else {
|
||||
_spi->begin();
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*!
|
||||
* @file Adafruit_TLC59711.h
|
||||
*
|
||||
* This is a library for the Adafruit 12-channel PWM/LED driver
|
||||
*
|
||||
* Designed specifically to work with the Adafruit 12-channel PWM/LED driver
|
||||
*
|
||||
* Pick one up today in the adafruit shop!
|
||||
* ------> https://www.adafruit.com/product/1455
|
||||
*
|
||||
* Two SPI Pins are required to send data: clock and data pin.
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit andopen-source hardware by purchasing products
|
||||
* from Adafruit!
|
||||
*
|
||||
* Limor Fried/Ladyada (Adafruit Industries).
|
||||
*
|
||||
* BSD license, all text above must be included in any redistribution
|
||||
*/
|
||||
|
||||
#ifndef _ADAFRUIT_TLC59711_H
|
||||
#define _ADAFRUIT_TLC59711_H
|
||||
|
||||
// Modified for tinySPI
|
||||
#include <Arduino.h>
|
||||
#include <tinySPI.h>
|
||||
|
||||
#define SPIClass tinySPI
|
||||
|
||||
/*!
|
||||
* @brief Class that stores state and functions for interacting with
|
||||
* TLC59711 Senor
|
||||
*/
|
||||
class Adafruit_TLC59711 {
|
||||
public:
|
||||
Adafruit_TLC59711(uint8_t n, uint8_t c, uint8_t d);
|
||||
Adafruit_TLC59711(uint8_t n, SPIClass *theSPI = &SPI);
|
||||
|
||||
boolean begin();
|
||||
|
||||
void setPWM(uint8_t chan, uint16_t pwm);
|
||||
void setLED(uint8_t lednum, uint16_t r, uint16_t g, uint16_t b);
|
||||
void write();
|
||||
void spiwriteMSB(uint8_t d);
|
||||
void setBrightness(uint8_t bcr, uint8_t bcg, uint8_t bcb);
|
||||
void simpleSetBrightness(uint8_t BC);
|
||||
|
||||
private:
|
||||
uint16_t *pwmbuffer;
|
||||
|
||||
uint8_t BCr, BCg, BCb;
|
||||
int8_t numdrivers, _clk, _dat;
|
||||
SPIClass *_spi;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,864 @@
|
|||
/* Arduino Library for the PCA9685 16-Channel PWM Driver Module.
|
||||
Copyright (C) 2016 NachtRaveVL <nachtravevl@gmail.com>
|
||||
Copyright (C) 2012 Kasper Skårhøj <kasperskaarhoj@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.14
|
||||
*/
|
||||
|
||||
#include "PCA9685.h"
|
||||
|
||||
#define PCA9685_I2C_BASE_ADDRESS (byte)0x40
|
||||
|
||||
// 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 pin layout
|
||||
#define PCA9685_MODE_RESTART (byte)0x80
|
||||
#define PCA9685_MODE_EXTCLK (byte)0x40
|
||||
#define PCA9685_MODE_AUTOINC (byte)0x20
|
||||
#define PCA9685_MODE_SLEEP (byte)0x10
|
||||
#define PCA9685_MODE_SUBADR1 (byte)0x08
|
||||
#define PCA9685_MODE_SUBADR2 (byte)0x04
|
||||
#define PCA9685_MODE_SUBADR3 (byte)0x02
|
||||
#define PCA9685_MODE_ALLCALL (byte)0x01
|
||||
|
||||
#define PCA9685_SW_RESET (byte)0x06 // Sent to address 0x00 to reset all devices on Wire line
|
||||
#define PCA9685_PWM_FULL (uint16_t)0x01000 // Special value for full on/full off LEDx modes
|
||||
|
||||
// To balance the load out in a weaved fashion, we use this offset table to distribute
|
||||
// the load on the outputs in a more interleaving fashion than just a simple 16 offset
|
||||
// per channel. We can set the off cycle value to be lower than the on cycle, which will
|
||||
// put the high edge across the 0-4095 phase cycle range, which is supported by device.
|
||||
static uint16_t phaseDistTable[16] = { 0, 2048, 1024, 3072, 512, 3584, 1536, 2560, 256, 3840, 1280, 2304, 3328, 768, 2816, 1792 };
|
||||
|
||||
#ifndef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
PCA9685::PCA9685(TwoWire& i2cWire, PCA9685_PhaseBalancer phaseBalancer) {
|
||||
_i2cWire = &i2cWire;
|
||||
#else
|
||||
PCA9685::PCA9685(PCA9685_PhaseBalancer phaseBalancer) {
|
||||
#endif
|
||||
_i2cAddress = 0;
|
||||
_phaseBalancer = phaseBalancer;
|
||||
_isProxyAddresser = false;
|
||||
_lastI2CError = 0;
|
||||
}
|
||||
|
||||
void PCA9685::resetDevices() {
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
Serial.println("PCA9685::resetDevices");
|
||||
#endif
|
||||
|
||||
i2cWire_beginTransmission(0x00);
|
||||
i2cWire_write(PCA9685_SW_RESET);
|
||||
i2cWire_endTransmission();
|
||||
|
||||
delayMicroseconds(10);
|
||||
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
checkForErrors();
|
||||
#endif
|
||||
}
|
||||
|
||||
void PCA9685::init(byte i2cAddress, byte mode) {
|
||||
if (_isProxyAddresser) return;
|
||||
|
||||
// I2C 7-bit address is B 1 A5 A4 A3 A2 A1 A0
|
||||
// RW bit added by Arduino core TWI library
|
||||
_i2cAddress = PCA9685_I2C_BASE_ADDRESS | (i2cAddress & 0x3F);
|
||||
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
Serial.print("PCA9685::init i2cAddress: 0x");
|
||||
Serial.println(_i2cAddress, HEX);
|
||||
#endif
|
||||
|
||||
writeRegister(PCA9685_MODE1_REG, PCA9685_MODE_RESTART | PCA9685_MODE_AUTOINC);
|
||||
writeRegister(PCA9685_MODE2_REG, mode);
|
||||
}
|
||||
|
||||
#ifndef PCA9685_EXCLUDE_EXT_FUNC
|
||||
|
||||
void PCA9685::initAsProxyAddresser(byte i2cAddress) {
|
||||
_i2cAddress = i2cAddress & 0xFE;
|
||||
_isProxyAddresser = true;
|
||||
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
Serial.print("PCA9685::initAsProxyAddresser i2cAddress: 0x");
|
||||
Serial.println(_i2cAddress, HEX);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
byte PCA9685::getI2CAddress() {
|
||||
return _i2cAddress;
|
||||
}
|
||||
|
||||
PCA9685_PhaseBalancer PCA9685::getPhaseBalancer() {
|
||||
return _phaseBalancer;
|
||||
}
|
||||
|
||||
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_MODE_RESTART) | PCA9685_MODE_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_MODE_SLEEP) | PCA9685_MODE_RESTART));
|
||||
delayMicroseconds(500);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// In 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.
|
||||
|
||||
while (numChannels > 0) {
|
||||
writeChannelBegin(begChannel);
|
||||
|
||||
int maxChannels = min(numChannels, (BUFFER_LENGTH - 1) / 4);
|
||||
while (maxChannels-- > 0) {
|
||||
uint16_t phaseBegin, phaseEnd;
|
||||
getPhaseCycle(begChannel++, *pwmAmounts++, &phaseBegin, &phaseEnd);
|
||||
|
||||
writeChannelPWM(phaseBegin, phaseEnd);
|
||||
--numChannels;
|
||||
}
|
||||
|
||||
writeChannelEnd();
|
||||
if (_lastI2CError) return;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef PCA9685_EXCLUDE_EXT_FUNC
|
||||
|
||||
void PCA9685::setAllChannelsPWM(uint16_t pwmAmount) {
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
Serial.println("PCA9685::setAllChannelsPWM");
|
||||
#endif
|
||||
|
||||
writeChannelBegin(-1); // Special value for ALLLED registers
|
||||
|
||||
uint16_t phaseBegin, phaseEnd;
|
||||
getPhaseCycle(-1, 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, (uint8_t)4);
|
||||
if (bytesRead != 4) {
|
||||
while (bytesRead-- > 0)
|
||||
i2cWire_read();
|
||||
#ifdef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
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_ENABLE_SOFTWARE_I2C
|
||||
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 i2cAddress) {
|
||||
if (_isProxyAddresser) return;
|
||||
|
||||
i2cAddress &= 0xFE;
|
||||
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
Serial.print("PCA9685::enableAllCallAddress i2cAddress: 0x");
|
||||
Serial.println(i2cAddress, HEX);
|
||||
#endif
|
||||
|
||||
writeRegister(PCA9685_ALLCALL_REG, i2cAddress);
|
||||
|
||||
byte mode1Reg = readRegister(PCA9685_MODE1_REG);
|
||||
writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE_ALLCALL));
|
||||
}
|
||||
|
||||
void PCA9685::enableSub1Address(byte i2cAddress) {
|
||||
if (_isProxyAddresser) return;
|
||||
|
||||
i2cAddress &= 0xFE;
|
||||
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
Serial.print("PCA9685::enableSub1Address i2cAddress: 0x");
|
||||
Serial.println(i2cAddress, HEX);
|
||||
#endif
|
||||
|
||||
writeRegister(PCA9685_SUBADR1_REG, i2cAddress);
|
||||
|
||||
byte mode1Reg = readRegister(PCA9685_MODE1_REG);
|
||||
writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE_SUBADR1));
|
||||
}
|
||||
|
||||
void PCA9685::enableSub2Address(byte i2cAddress) {
|
||||
if (_isProxyAddresser) return;
|
||||
|
||||
i2cAddress &= 0xFE;
|
||||
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
Serial.print("PCA9685::enableSub2Address i2cAddress: 0x");
|
||||
Serial.println(i2cAddress, HEX);
|
||||
#endif
|
||||
|
||||
writeRegister(PCA9685_SUBADR2_REG, i2cAddress);
|
||||
|
||||
byte mode1Reg = readRegister(PCA9685_MODE1_REG);
|
||||
writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE_SUBADR2));
|
||||
}
|
||||
|
||||
void PCA9685::enableSub3Address(byte i2cAddress) {
|
||||
if (_isProxyAddresser) return;
|
||||
|
||||
i2cAddress &= 0xFE;
|
||||
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
Serial.print("PCA9685::enableSub3Address i2cAddress: 0x");
|
||||
Serial.println(i2cAddress, HEX);
|
||||
#endif
|
||||
|
||||
writeRegister(PCA9685_SUBADR3_REG, i2cAddress);
|
||||
|
||||
byte mode1Reg = readRegister(PCA9685_MODE1_REG);
|
||||
writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE_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_MODE_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_MODE_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_MODE_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_MODE_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_MODE_RESTART) | PCA9685_MODE_SLEEP));
|
||||
writeRegister(PCA9685_MODE1_REG, (mode1Reg |= PCA9685_MODE_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_MODE_SLEEP) | PCA9685_MODE_RESTART));
|
||||
delayMicroseconds(500);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
byte PCA9685::getLastI2CError() {
|
||||
return _lastI2CError;
|
||||
}
|
||||
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
|
||||
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
|
||||
|
||||
void PCA9685::getPhaseCycle(int channel, uint16_t pwmAmount, uint16_t *phaseBegin, uint16_t *phaseEnd) {
|
||||
// Set delay
|
||||
if (channel < 0) {
|
||||
// All channels
|
||||
*phaseBegin = 0;
|
||||
}
|
||||
else if (_phaseBalancer == PCA9685_PhaseBalancer_Linear) {
|
||||
// Distribute high phase area over entire phase range to balance load.
|
||||
*phaseBegin = channel * (4096 / 16);
|
||||
}
|
||||
else if (_phaseBalancer == PCA9685_PhaseBalancer_Weaved) {
|
||||
// Distribute high phase area over entire phase range to balance load.
|
||||
*phaseBegin = phaseDistTable[channel];
|
||||
}
|
||||
else {
|
||||
*phaseBegin = 0;
|
||||
}
|
||||
|
||||
// See datasheet section 7.3.3
|
||||
if (pwmAmount == 0) {
|
||||
// Full OFF => time_off[12] = 1;
|
||||
*phaseEnd = PCA9685_PWM_FULL;
|
||||
}
|
||||
else if (pwmAmount >= PCA9685_PWM_FULL) {
|
||||
// Full ON => time_on[12] = 1; time_off = ignored;
|
||||
*phaseBegin |= PCA9685_PWM_FULL;
|
||||
*phaseEnd = 0;
|
||||
}
|
||||
else {
|
||||
*phaseEnd = *phaseBegin + pwmAmount;
|
||||
if (*phaseEnd >= PCA9685_PWM_FULL)
|
||||
*phaseEnd -= PCA9685_PWM_FULL;
|
||||
}
|
||||
}
|
||||
|
||||
void PCA9685::writeChannelBegin(int channel) {
|
||||
byte regAddress;
|
||||
|
||||
if (channel != -1)
|
||||
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, (uint8_t)1);
|
||||
if (bytesRead != 1) {
|
||||
while (bytesRead-- > 0)
|
||||
i2cWire_read();
|
||||
#ifdef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
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_ENABLE_SOFTWARE_I2C
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
bool __attribute__((noinline)) i2c_start(uint8_t addr);
|
||||
void __attribute__((noinline)) i2c_stop(void) asm("ass_i2c_stop");
|
||||
bool __attribute__((noinline)) i2c_write(uint8_t value) asm("ass_i2c_write");
|
||||
uint8_t __attribute__((noinline)) i2c_read(bool last);
|
||||
#endif
|
||||
|
||||
void PCA9685::i2cWire_beginTransmission(uint8_t addr) {
|
||||
_lastI2CError = 0;
|
||||
#ifndef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
_i2cWire->beginTransmission(addr);
|
||||
#else
|
||||
i2c_start(addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t PCA9685::i2cWire_endTransmission(void) {
|
||||
#ifndef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
return (_lastI2CError = _i2cWire->endTransmission());
|
||||
#else
|
||||
i2c_stop();
|
||||
return (_lastI2CError = 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t PCA9685::i2cWire_requestFrom(uint8_t addr, uint8_t len) {
|
||||
#ifndef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
return _i2cWire->requestFrom(addr, len);
|
||||
#else
|
||||
i2c_start(addr | 0x01);
|
||||
return (_readBytes = len);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t PCA9685::i2cWire_write(uint8_t data) {
|
||||
#ifndef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
return _i2cWire->write(data);
|
||||
#else
|
||||
return (size_t)i2c_write(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t PCA9685::i2cWire_read(void) {
|
||||
#ifndef PCA9685_ENABLE_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
|
||||
|
||||
void PCA9685::printModuleInfo() {
|
||||
Serial.println(""); Serial.println(" ~~~ PCA9685 Module Info ~~~");
|
||||
|
||||
Serial.println(""); Serial.println("i2c Address:");
|
||||
Serial.print("0x");
|
||||
Serial.println(_i2cAddress, HEX);
|
||||
|
||||
Serial.println(""); Serial.println("Phase Balancer:");
|
||||
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_Weaved:
|
||||
Serial.println("PCA9685_PhaseBalancer_Weaved"); break;
|
||||
default:
|
||||
Serial.println(""); break;
|
||||
}
|
||||
|
||||
if (!_isProxyAddresser) {
|
||||
Serial.println(""); Serial.println("Proxy Addresser:");
|
||||
Serial.println("false");
|
||||
|
||||
Serial.println(""); Serial.println("Mode1 Register:");
|
||||
byte mode1Reg = readRegister(PCA9685_MODE1_REG);
|
||||
Serial.print("0x");
|
||||
Serial.print(mode1Reg, HEX);
|
||||
Serial.print(", Bitset:");
|
||||
if (mode1Reg & PCA9685_MODE_RESTART)
|
||||
Serial.print(" PCA9685_MODE_RESTART");
|
||||
if (mode1Reg & PCA9685_MODE_EXTCLK)
|
||||
Serial.print(" PCA9685_MODE_EXTCLK");
|
||||
if (mode1Reg & PCA9685_MODE_AUTOINC)
|
||||
Serial.print(" PCA9685_MODE_AUTOINC");
|
||||
if (mode1Reg & PCA9685_MODE_SLEEP)
|
||||
Serial.print(" PCA9685_MODE_SLEEP");
|
||||
if (mode1Reg & PCA9685_MODE_SUBADR1)
|
||||
Serial.print(" PCA9685_MODE_SUBADR1");
|
||||
if (mode1Reg & PCA9685_MODE_SUBADR2)
|
||||
Serial.print(" PCA9685_MODE_SUBADR2");
|
||||
if (mode1Reg & PCA9685_MODE_SUBADR3)
|
||||
Serial.print(" PCA9685_MODE_SUBADR3");
|
||||
if (mode1Reg & PCA9685_MODE_ALLCALL)
|
||||
Serial.print(" PCA9685_MODE_ALLCALL");
|
||||
Serial.println("");
|
||||
|
||||
Serial.println(""); Serial.println("Mode2 Register:");
|
||||
byte mode2Reg = readRegister(PCA9685_MODE2_REG);
|
||||
Serial.print("0x");
|
||||
Serial.print(mode2Reg, HEX);
|
||||
Serial.print(", Bitset:");
|
||||
if (mode2Reg & PCA9685_MODE_INVRT)
|
||||
Serial.print(" PCA9685_MODE_INVRT");
|
||||
if (mode2Reg & PCA9685_MODE_OCH_ONACK)
|
||||
Serial.print(" PCA9685_MODE_OCH_ONACK");
|
||||
if (mode2Reg & PCA9685_MODE_OUTDRV_TPOLE)
|
||||
Serial.print(" PCA9685_MODE_OUTDRV_TPOLE");
|
||||
if (mode2Reg & PCA9685_MODE_OUTNE_HIGHZ)
|
||||
Serial.print(" PCA9685_MODE_OUTNE_HIGHZ");
|
||||
if (mode2Reg & PCA9685_MODE_OUTNE_TPHIGH)
|
||||
Serial.print(" PCA9685_MODE_OUTNE_TPHIGH");
|
||||
Serial.println("");
|
||||
|
||||
Serial.println(""); Serial.println("SubAddress1 Register:");
|
||||
byte subAdr1Reg = readRegister(PCA9685_SUBADR1_REG);
|
||||
Serial.print("0x");
|
||||
Serial.println(subAdr1Reg, HEX);
|
||||
|
||||
Serial.println(""); Serial.println("SubAddress2 Register:");
|
||||
byte subAdr2Reg = readRegister(PCA9685_SUBADR2_REG);
|
||||
Serial.print("0x");
|
||||
Serial.println(subAdr2Reg, HEX);
|
||||
|
||||
Serial.println(""); Serial.println("SubAddress3 Register:");
|
||||
byte subAdr3Reg = readRegister(PCA9685_SUBADR3_REG);
|
||||
Serial.print("0x");
|
||||
Serial.println(subAdr3Reg, HEX);
|
||||
|
||||
Serial.println(""); Serial.println("AllCall Register:");
|
||||
byte allCallReg = readRegister(PCA9685_ALLCALL_REG);
|
||||
Serial.print("0x");
|
||||
Serial.println(allCallReg, HEX);
|
||||
}
|
||||
else {
|
||||
Serial.println(""); Serial.println("Proxy Addresser:");
|
||||
Serial.println("true");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef PCA9685_EXCLUDE_SERVO_EVAL
|
||||
|
||||
PCA9685_ServoEvaluator::PCA9685_ServoEvaluator(uint16_t n90PWMAmount, uint16_t p90PWMAmount) {
|
||||
n90PWMAmount = constrain(n90PWMAmount, 0, PCA9685_PWM_FULL);
|
||||
p90PWMAmount = constrain(p90PWMAmount, n90PWMAmount, PCA9685_PWM_FULL);
|
||||
|
||||
_coeff = new float[2];
|
||||
_isCSpline = false;
|
||||
|
||||
_coeff[0] = n90PWMAmount;
|
||||
_coeff[1] = (p90PWMAmount - n90PWMAmount) / 180.0f;
|
||||
}
|
||||
|
||||
PCA9685_ServoEvaluator::PCA9685_ServoEvaluator(uint16_t n90PWMAmount, uint16_t zeroPWMAmount, uint16_t p90PWMAmount) {
|
||||
n90PWMAmount = constrain(n90PWMAmount, 0, PCA9685_PWM_FULL);
|
||||
zeroPWMAmount = constrain(zeroPWMAmount, n90PWMAmount, PCA9685_PWM_FULL);
|
||||
p90PWMAmount = constrain(p90PWMAmount, zeroPWMAmount, PCA9685_PWM_FULL);
|
||||
|
||||
if (p90PWMAmount - zeroPWMAmount != zeroPWMAmount - n90PWMAmount) {
|
||||
_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. */
|
||||
|
||||
float x[3] = { 0, 90, 180 };
|
||||
float y[3] = { (float)n90PWMAmount, (float)zeroPWMAmount, (float)p90PWMAmount };
|
||||
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] = n90PWMAmount;
|
||||
_coeff[1] = (p90PWMAmount - n90PWMAmount) / 180.0f;
|
||||
}
|
||||
}
|
||||
|
||||
PCA9685_ServoEvaluator::~PCA9685_ServoEvaluator() {
|
||||
if (_coeff) delete[] _coeff;
|
||||
}
|
||||
|
||||
uint16_t PCA9685_ServoEvaluator::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)constrain((uint16_t)roundf(retVal), 0, PCA9685_PWM_FULL);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,245 @@
|
|||
/* Arduino Library for the PCA9685 16-Channel PWM Driver Module.
|
||||
Copyright (C) 2016 NachtRaveVL <nachtravevl@gmail.com>
|
||||
Copyright (C) 2012 Kasper Skårhøj <kasperskaarhoj@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.14
|
||||
*/
|
||||
|
||||
#ifndef PCA9685_H
|
||||
#define PCA9685_H
|
||||
|
||||
// Library Setup
|
||||
|
||||
// NOTE: It is recommended to avoid editing library files directly and instead use custom
|
||||
// build flags. While most custom build systems support such, the Arduino IDE does not.
|
||||
// Be aware that editing this file directly will affect all projects using this library.
|
||||
|
||||
// Uncomment this define to enable use of the software i2c library (min 4MHz+ processor required).
|
||||
//#define PCA9685_ENABLE_SOFTWARE_I2C 1 // http://playground.arduino.cc/Main/SoftwareI2CLibrary
|
||||
|
||||
// Uncomment this define if wanting to exclude extended functionality from compilation.
|
||||
//#define PCA9685_EXCLUDE_EXT_FUNC 1
|
||||
|
||||
// Uncomment this define if wanting to exclude ServoEvaluator assistant from compilation.
|
||||
//#define PCA9685_EXCLUDE_SERVO_EVAL 1
|
||||
|
||||
// Uncomment 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 1
|
||||
|
||||
// Uncomment this define to enable debug output.
|
||||
//#define PCA9685_ENABLE_DEBUG_OUTPUT 1
|
||||
|
||||
// Hookup Callout: Servo Control
|
||||
// -PLEASE READ-
|
||||
// Many 180 degree controlled digital servos run on a 20ms pulse width (50Hz update
|
||||
// frequency) based duty cycle, and do not utilize the entire pulse width for their
|
||||
// -90/+90 degree control. Typically, 2.5% of the 20ms pulse width (0.5ms) is considered
|
||||
// -90 degrees, and 12.5% of the 20ms pulse width (2.5ms) is considered +90 degrees.
|
||||
// This roughly translates to raw PCA9685 PWM values of 102 and 512 (out of the 4096
|
||||
// value range) for -90 to +90 degree control, but may need to be adjusted to fit your
|
||||
// specific servo (e.g. some I've tested run ~130 to ~525 for their -90/+90 degree
|
||||
// control).
|
||||
//
|
||||
// -ALSO-
|
||||
// Please be aware that driving some servos past their -90/+90 degrees of movement can
|
||||
// cause a little plastic limiter pin to break off and get stuck inside of the gearing,
|
||||
// which could potentially cause the servo to become jammed and no longer function.
|
||||
//
|
||||
// See the PCA9685_ServoEvaluator class to assist with calculating PWM values from Servo
|
||||
// angle values, if you desire that level of fine tuning.
|
||||
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <WProgram.h>
|
||||
#endif
|
||||
#ifndef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
#include <Wire.h>
|
||||
|
||||
// Define BUFFER_LENGTH on platforms that don't natively define such.
|
||||
#ifndef BUFFER_LENGTH
|
||||
#ifdef I2C_BUFFER_LENGTH
|
||||
#define BUFFER_LENGTH I2C_BUFFER_LENGTH
|
||||
#else
|
||||
#warning "i2c BUFFER_LENGTH not defined - using default of 32, which may not be supported by your microcontroller's hardware. Check Wire.h (or similar) file for your hardware and manually define to remove this warning."
|
||||
#define BUFFER_LENGTH 32
|
||||
#endif
|
||||
#endif // /ifndef BUFFER_LENGTH
|
||||
|
||||
#endif // /ifndef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
|
||||
// Channel update strategy used when multiple channels are being updated in batch:
|
||||
#define PCA9685_MODE_OCH_ONACK (byte)0x08 // Channel updates commit after individual channel update ACK signal, instead of after full-transmission STOP signal
|
||||
|
||||
// Output-enabled/active-low-OE-pin=LOW driver control modes (see datasheet Table 12 and Fig 13, 14, and 15 concerning correct usage of INVRT and OUTDRV):
|
||||
#define PCA9685_MODE_INVRT (byte)0x10 // Enables channel output polarity inversion (applicable only when active-low-OE-pin=LOW)
|
||||
#define PCA9685_MODE_OUTDRV_TPOLE (byte)0x04 // Enables totem-pole (instead of open-drain) style structure to be used for driving channel output, allowing use of an external output driver
|
||||
// NOTE: 1) Chipset's breakout must support this feature (most do, some don't)
|
||||
// 2) When in this mode, INVRT mode should be set according to if an external N-type external driver (should use INVRT) or P-type external driver (should not use INVRT) is more optimal
|
||||
// 3) 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 overheating the IC."
|
||||
|
||||
// Output-not-enabled/active-low-OE-pin=HIGH driver control modes (see datasheet Section 7.4 concerning correct usage of OUTNE):
|
||||
// NOTE: Active-low-OE pin is typically used to synchronize multiple PCA9685 devices together, or as an external dimming control signal.
|
||||
#define PCA9685_MODE_OUTNE_HIGHZ (byte)0x02 // Sets all channel outputs to high-impedance state (applicable only when active-low-OE-pin=HIGH)
|
||||
#define PCA9685_MODE_OUTNE_TPHIGH (byte)0x01 // Sets all channel outputs to HIGH (applicable only when in totem-pole mode and active-low-OE-pin=HIGH)
|
||||
|
||||
#define PCA9685_MIN_CHANNEL 0
|
||||
#define PCA9685_MAX_CHANNEL 15
|
||||
#define PCA9685_CHANNEL_COUNT 16
|
||||
|
||||
typedef enum {
|
||||
PCA9685_PhaseBalancer_None = -1, // Disables phase balancing, all high phase areas start at begining of cycle
|
||||
PCA9685_PhaseBalancer_Linear = 0, // Balances all outputs linearly, 256 steps away from previous output
|
||||
PCA9685_PhaseBalancer_Weaved, // Balances first few outputs better, steps away from previous shorten towards last output
|
||||
|
||||
PCA9685_PhaseBalancer_Count
|
||||
} PCA9685_PhaseBalancer;
|
||||
// NOTE: Phase balancing essentially means that the start of the high phase cycle, for each channel, is shifted by some amount, so that a large voltage sink doesn't occur all at once.
|
||||
|
||||
class PCA9685 {
|
||||
public:
|
||||
#ifndef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
// May use a different Wire instance than Wire. Some chipsets, such as Due/Zero/etc.,
|
||||
// have a Wire1 class instance that uses the SDA1/SCL1 lines instead.
|
||||
// Supported i2c baud rates are 100kHz, 400kHz, and 1000kHz.
|
||||
PCA9685(TwoWire& i2cWire = Wire, PCA9685_PhaseBalancer phaseBalancer = PCA9685_PhaseBalancer_Linear);
|
||||
#else
|
||||
// Minimum supported i2c baud rate is 100kHz, which means minimum supported processor
|
||||
// speed is 4MHz+ while running i2c standard mode. For 400kHz i2c baud rate, minimum
|
||||
// supported processor speed is 16MHz+ while running i2c fast mode.
|
||||
PCA9685(PCA9685_PhaseBalancer phaseBalancer = PCA9685_PhaseBalancer_Linear);
|
||||
#endif
|
||||
|
||||
// Should be called only once in setup(), before any init()'s, but after Wire.begin().
|
||||
// Only should be called once on any Wire instance to do a software reset, which
|
||||
// will affect all devices on that line. This helps when you're constantly rebuilding
|
||||
// and reuploading to ensure all the devices on that line are reset properly.
|
||||
void resetDevices();
|
||||
|
||||
// Called in setup(). 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. i2cAddress
|
||||
// should be a value between 0 and 61, since only 62 boards can be addressed.
|
||||
void init(byte i2cAddress = 0, byte mode = PCA9685_MODE_OUTDRV_TPOLE);
|
||||
|
||||
#ifndef PCA9685_EXCLUDE_EXT_FUNC
|
||||
// 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 certain states.
|
||||
void initAsProxyAddresser(byte i2cAddress = 0xE0);
|
||||
#endif
|
||||
|
||||
byte getI2CAddress();
|
||||
PCA9685_PhaseBalancer getPhaseBalancer();
|
||||
|
||||
// Min: 24Hz, Max: 1526Hz, Default: 200Hz (as Hz increases channel resolution widens, but raw pre-scaler value, as computed per datasheet, also becomes less affected inversly)
|
||||
void setPWMFrequency(float pwmFrequency);
|
||||
|
||||
// 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);
|
||||
|
||||
#ifndef PCA9685_EXCLUDE_EXT_FUNC
|
||||
// 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 class instance using initAsSubAddressed() with said address
|
||||
void enableAllCallAddress(byte i2cAddress = 0xE0);
|
||||
void enableSub1Address(byte i2cAddress = 0xE2);
|
||||
void enableSub2Address(byte i2cAddress = 0xE4);
|
||||
void enableSub3Address(byte i2cAddress = 0xE8);
|
||||
void disableAllCallAddress();
|
||||
void disableSub1Address();
|
||||
void disableSub2Address();
|
||||
void disableSub3Address();
|
||||
|
||||
// Allows external clock line to be utilized (once enabled cannot be disabled)
|
||||
void enableExtClockLine();
|
||||
#endif
|
||||
|
||||
byte getLastI2CError();
|
||||
|
||||
#ifdef PCA9685_ENABLE_DEBUG_OUTPUT
|
||||
void printModuleInfo();
|
||||
void checkForErrors();
|
||||
#endif
|
||||
|
||||
private:
|
||||
#ifndef PCA9685_ENABLE_SOFTWARE_I2C
|
||||
TwoWire *_i2cWire; // Wire class instance to use
|
||||
#endif
|
||||
byte _i2cAddress; // Module's i2c address
|
||||
PCA9685_PhaseBalancer _phaseBalancer; // Phase balancer scheme to distribute load across phase range
|
||||
bool _isProxyAddresser; // Instance is a proxy for sub addressing (disables certain functionality)
|
||||
byte _lastI2CError; // Last i2c error
|
||||
|
||||
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_ENABLE_SOFTWARE_I2C
|
||||
uint8_t _readBytes;
|
||||
#endif
|
||||
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);
|
||||
};
|
||||
|
||||
#ifndef PCA9685_EXCLUDE_SERVO_EVAL
|
||||
|
||||
// Class to assist with calculating Servo PWM values from angle values
|
||||
class PCA9685_ServoEvaluator {
|
||||
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.
|
||||
PCA9685_ServoEvaluator(uint16_t n90PWMAmount = 102, uint16_t p90PWMAmount = 512);
|
||||
|
||||
// Uses a cubic spline to interpolate due to an offsetted zero angle that isn't
|
||||
// exactly between -90/+90. This takes more time to compute, but gives a more
|
||||
// accurate PWM output value along the entire range.
|
||||
PCA9685_ServoEvaluator(uint16_t n90PWMAmount, uint16_t zeroPWMAmount, uint16_t p90PWMAmount);
|
||||
|
||||
~PCA9685_ServoEvaluator();
|
||||
|
||||
// Returns the PWM value to use given the angle (-90 to +90)
|
||||
uint16_t pwmForAngle(float angle);
|
||||
|
||||
private:
|
||||
float *_coeff; // a,b,c,d coefficient values
|
||||
bool _isCSpline; // Cubic spline tracking, for _coeff length
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,156 @@
|
|||
#include <stdint.h>
|
||||
#include <avr/sleep.h>
|
||||
#include "./Adafruit_TLC59711.h"
|
||||
|
||||
|
||||
void sleepUntilInterrupt();
|
||||
|
||||
Adafruit_TLC59711 controller(1);
|
||||
#define SegmentCount 8
|
||||
|
||||
#define PinTiltSwitch 3
|
||||
#define PinTiltSwitchMask PB3
|
||||
//#define PinDebugLED 4
|
||||
#define Timeout 2000
|
||||
|
||||
/* For PCB layout purposes the LED segments are connected to different pins. */
|
||||
uint8_t channels[SegmentCount] = {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9
|
||||
};
|
||||
|
||||
uint16_t pwmValues[SegmentCount] = {
|
||||
65535,
|
||||
16384,
|
||||
8192,
|
||||
4096,
|
||||
2048,
|
||||
1024,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
void debugBlink(uint8_t count)
|
||||
{
|
||||
for (uint8_t i = 0; i < count; i++)
|
||||
{
|
||||
digitalWrite(PinDebugLED, HIGH);
|
||||
delay(150);
|
||||
digitalWrite(PinDebugLED, LOW);
|
||||
delay(150);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
volatile bool tiltChanged = false;
|
||||
uint8_t currentSegment = 0;
|
||||
uint32_t animationStart = 0;
|
||||
|
||||
|
||||
void setup()
|
||||
{
|
||||
pinMode(PinTiltSwitch, INPUT_PULLUP);
|
||||
//pinMode(PinDebugLED, OUTPUT);
|
||||
|
||||
controller.begin();
|
||||
for (uint8_t segment = 0; segment < SegmentCount; segment++)
|
||||
controller.setPWM(channels[segment], 0);
|
||||
|
||||
|
||||
// Clear outstanding interrupts
|
||||
GIFR |= _BV(PCIF);
|
||||
|
||||
// Enable pin change interrupts
|
||||
GIMSK |= _BV(PCIE);
|
||||
|
||||
// Set up pin change mask
|
||||
PCMSK = _BV(PinTiltSwitchMask);
|
||||
|
||||
sleepUntilInterrupt();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (tiltChanged)
|
||||
{
|
||||
animationStart = millis();
|
||||
tiltChanged = false;
|
||||
}
|
||||
else if (animationStart == 0)
|
||||
{
|
||||
sleepUntilInterrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (millis() - animationStart >= Timeout)
|
||||
{
|
||||
for (uint8_t segment = 0; segment < SegmentCount; segment++)
|
||||
controller.setPWM(channels[segment], 0);
|
||||
|
||||
controller.write();
|
||||
animationStart = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
uint8_t segment = currentSegment;
|
||||
for (uint8_t channel = 0; channel < SegmentCount; channel++)
|
||||
{
|
||||
controller.setPWM(channels[segment], pwmValues[channel]);
|
||||
|
||||
if (segment == 0)
|
||||
segment = SegmentCount - 1;
|
||||
else
|
||||
segment--;
|
||||
}
|
||||
|
||||
controller.write();
|
||||
delay(75);
|
||||
|
||||
currentSegment++;
|
||||
if (currentSegment == SegmentCount)
|
||||
currentSegment = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void sleepUntilInterrupt()
|
||||
{
|
||||
// Turn ADC off
|
||||
ADCSRA &= ~_BV(ADEN);
|
||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||
|
||||
// Set sleep bit and halt the CPU
|
||||
sleep_enable();
|
||||
sei();
|
||||
sleep_cpu();
|
||||
|
||||
// ...goooood morning!
|
||||
cli();
|
||||
|
||||
|
||||
sleep_disable();
|
||||
ADCSRA |= _BV(ADEN);
|
||||
|
||||
sei();
|
||||
}
|
||||
|
||||
|
||||
// Interrupt for pin changes
|
||||
ISR(PCINT0_vect)
|
||||
{
|
||||
tiltChanged = true;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
& platformio run --target upload
|
Loading…
Reference in New Issue