Skip to content

Commit

Permalink
0.3.0 fix #4 #5
Browse files Browse the repository at this point in the history
- fix digitalWrite()
- fix setFrequency()
- add allOff()
- add examples
- update readme.md
- minor refactor
  • Loading branch information
RobTillaart committed Nov 22, 2020
1 parent 09a47d3 commit ec57d1b
Show file tree
Hide file tree
Showing 11 changed files with 486 additions and 40 deletions.
76 changes: 57 additions & 19 deletions PCA9685.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// FILE: PCA9685.cpp
// AUTHOR: Rob Tillaart
// DATE: 24-apr-2016
// VERSION: 0.2.2
// VERSION: 0.3.0
// PURPOSE: Arduino library for I2C PCA9685 16 channel PWM
// URL: https://github.com/RobTillaart/PCA9685_RT
//
Expand All @@ -12,16 +12,17 @@
// 0.2.0 2020-05-25 refactor; ESP32 begin(sda,scl)
// 0.2.1 2020-06-19 fix library.json
// 0.2.2 2020-09-21 fix #1 + add getFrequency()
// 0.2.3 2020-11-21 fix digitalWrite (internal version only)
// 0.3.0 2020-11-22 fix setting frequency


#include <Wire.h>
#include "PCA9685.h"


// check datasheet for details
// REGISTERS CONFIGURATION - check datasheet for details
#define PCA9685_MODE1 0x00
#define PCA9685_MODE2 0x01


// MODE1 REGISTERS
#define PCA9685_RESTART 0x80
#define PCA9685_EXTCLK 0x40
Expand All @@ -32,22 +33,30 @@
#define PCA9685_SUB3 0x02
#define PCA9685_ALLCALL 0x01


// MODE2 REGISTERS (see datasheet)
#define PCA9685_INVERT 0x10
#define PCA9685_OCH 0x08
#define PCA9685_OUTDRV 0x04
#define PCA9685_OUTNE 0x03

// REGISTERS - CHANNELS
#define PCA9685_CHANNEL_0 0x06 // 0x06 + 4*channel is base per channel

// SPECIAL REGISTER - FREQUENCY
#define PCA9685_PRE_SCALE 0xFE
// REGISTERS - FREQUENCY
#define PCA9685_PRE_SCALER 0xFE


// NOT IMPLEMENTED YET
// REGISTERS - Subaddressing I2C - not implemented
#define PCA9685_SUBADR(x) (0x01+(x)) // x = 1..3
#define PCA9685_ALLCALLADR 0x05
#define PCA9685_TESTMODE 0xFF

// REGISTERS - ALL_ON ALL_OFF - partly implemented
#define PCA9685_ALL_ON_L 0xFA
#define PCA9685_ALL_ON_H 0xFB
#define PCA9685_ALL_OFF_L 0xFC
#define PCA9685_ALL_OFF_H 0xFD // used for allOFF()

// NOT IMPLEMENTED YET
#define PCA9685_TESTMODE 0xFF // do not be use. see datasheet.


//////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -114,7 +123,8 @@ void PCA9685::setPWM(uint8_t channel, uint16_t onTime, uint16_t offTime)
_error = PCA9685_ERR_CHANNEL;
return;
}
uint8_t reg = 0x06 + (channel << 2);
offTime &= 0x0FFFF; // non-doc feature - to easy set figure 8 P.17
uint8_t reg = PCA9685_CHANNEL_0 + (channel << 2);
writeReg2(reg, onTime, offTime);
}

Expand All @@ -134,7 +144,7 @@ void PCA9685::getPWM(uint8_t channel, uint16_t* onTime, uint16_t* offTime)
_error = PCA9685_ERR_CHANNEL;
return;
}
uint8_t reg = 0x06 + (channel << 2);
uint8_t reg = PCA9685_CHANNEL_0 + (channel << 2);
Wire.beginTransmission(_address);
Wire.write(reg);
_error = Wire.endTransmission();
Expand All @@ -151,25 +161,53 @@ void PCA9685::getPWM(uint8_t channel, uint16_t* onTime, uint16_t* offTime)


// set update frequency for all channels
void PCA9685::setFrequency(uint16_t freq)
void PCA9685::setFrequency(uint16_t freq, int offset)
{
_freq = freq;
if (_freq < 24) _freq = 24; // page 25 datasheet
if (_freq > 1526) _freq = 1526;
// removed float operation for speed
// faster but equal accurate
// uint8_t scaler = round(25e6 / (freq * 4096)) - 1;
// uint8_t scaler = round(25e6 / (_freq * 4096)) - 1;
uint8_t scaler = 48828 / (_freq * 8) - 1;
writeReg(PCA9685_PRE_SCALE, scaler);

uint8_t mode1 = readMode(PCA9685_MODE1);
writeMode(PCA9685_MODE1, mode1 | PCA9685_SLEEP);
scaler += offset;
writeReg(PCA9685_PRE_SCALER, scaler);
writeMode(PCA9685_MODE1, mode1);
}

int PCA9685::getFrequency(bool cache)
{
if (cache) return _freq;
uint8_t scaler = readReg(PCA9685_PRE_SCALER);
scaler++;
_freq = 48828 / scaler;
_freq /= 8;
return _freq;
}


// datasheet P.18 - fig. 9:
// Note: bit[11-0] ON should NOT equal timer OFF in ON mode
// in OFF mode it doesn't matter.
void PCA9685::digitalWrite(uint8_t channel, uint8_t mode)
{
if (mode != LOW) setPWM(channel, 0x1000, 0x0000);
else setPWM(channel, 0x0000, 0x1000);
if (channel > 15)
{
_error = PCA9685_ERR_CHANNEL;
return;
}
uint8_t reg = PCA9685_CHANNEL_0 + (channel << 2);
if (mode != LOW) writeReg2(reg, 0x1000, 0x0000);
else writeReg2(reg, 0x0000, 0x0000);
}

void PCA9685::allOFF()
{
writeReg(PCA9685_ALL_OFF_H, 0x10);
}

int PCA9685::lastError()
{
Expand All @@ -196,9 +234,9 @@ void PCA9685::writeReg2(uint8_t reg, uint16_t a, uint16_t b)
Wire.beginTransmission(_address);
Wire.write(reg);
Wire.write(a & 0xFF);
Wire.write((a >> 8) & 0x0F);
Wire.write((a >> 8) & 0x1F);
Wire.write(b & 0xFF);
Wire.write((b >> 8) & 0x0F);
Wire.write((b >> 8) & 0x1F);
_error = Wire.endTransmission();
}

Expand Down
14 changes: 10 additions & 4 deletions PCA9685.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// FILE: PCA9685.H
// AUTHOR: Rob Tillaart
// DATE: 24-apr-2016
// VERSION: 0.2.2
// VERSION: 0.3.0
// PURPOSE: Arduino library for I2C PCA9685 16 channel PWM
// URL: https://github.com/RobTillaart/PCA9685_RT
//
Expand All @@ -12,8 +12,9 @@
//

#include "Arduino.h"
#include "Wire.h"

#define PCA9685_LIB_VERSION "0.2.2"
#define PCA9685_LIB_VERSION "0.3.0"

// ERROR CODES
#define PCA9685_OK 0x00
Expand Down Expand Up @@ -49,8 +50,10 @@ class PCA9685

// set update frequency for all channels
// freq = 24 - 1526 Hz
void setFrequency(uint16_t freq);
int getFrequency() { return _freq; };
// note: as the frequency is converted to an 8 bit prescaler
// the frequency set will seldom be exact, but best effort.
void setFrequency(uint16_t freq, int offset = 0);
int getFrequency(bool cache = true);

// set channel HIGH or LOW (effectively no PWM)
void digitalWrite(uint8_t channel, uint8_t mode);
Expand All @@ -59,6 +62,9 @@ class PCA9685
void setON(uint8_t channel) { digitalWrite(channel, HIGH); };
void setOFF(uint8_t channel) { digitalWrite(channel, LOW); };

// experimental for 0.3.0
void allOFF();

int lastError();

private:
Expand Down
47 changes: 34 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ Arduino library for I2C PCA9685 16 channel PWM extender
# Description

This library is to control the I2C PCA9685 PWM extender.
The 16 channels are independently configurable is steps of 1/4096.
this allows for better than 0.1% finetuning of the duty-cycle
of the PWM signal. Furthermore the PWM's of the different signals
have individual start and stop moments. This can be used to distribute the
power more evenly over multiple servo's or give special effects when
used in an RGB LED.
The 16 channels are independently configurable in steps of 1/4096.
This allows for better than 0.1% finetuning of the duty-cycle
of the PWM signal.

The PWM's of the different channels have individual start and stop moments.
This can be used to distribute the power more evenly over multiple servo's
or give special effects when used in an RGB LED.

The frequency of the PWM can be set from 24 to 1526 according to the datasheet, however in practice not all frequencies are set accurate.
Lower frequencies do better than higher frequencies.


### interface
Expand Down Expand Up @@ -39,27 +42,45 @@ channels do not need to start at the same moment with HIGH.

**getPWM(channel, ontime, offtime)** read back the configuration of the channel.

**setFrequency(freq)** set the update speed of the channels.
This is for all channels at once.
The frequency is constrained to be between 24 and 1526 Hz,
**setFrequency(freq, int offset = 0)** set the update speed of the channels.
This value is set the same for all channels at once.
The frequency is constrained to be between 24 and 1526 Hz.
As the frequency is converted to an 8 bit **prescaler**,
the frequency set will seldom be exact.
After changing the frequency, one must set all channels (again),
so one should set the frequency in **setup()**

The parameter offset can be used to tune the **prescaler** to get a frequency
closer to the requested value. See **PCA9685_setFrequency_offset** example.
Default the offset = 0. As the prescaler is smaller at higher frequencies
higher frequencies are less accurate.
Making offset too large can result in very incorrect frequencies.

**getFrequency()** get the current update frequency of the channels.
This is same for all channels.
When using offset, the **getFrequency(false)** will return the adjusted prescaler.

**getFrequency(cache = true)** get the current update frequency of the channels.
This is same for all channels. If cache is false, the frequency is fetched and
calculated from the **prescaler** register and will probably differ from the
value set with **setFrequency()**.

**digitalWrite(channel, mode)** mode = HIGH or LOW, just use the PCA9685 as
a digitalpin.
This single function replaces the setON() and setOFF() that will become
obsolete in the future.

**lastERror()** returns **PCA9685_OK = 0** if all is OK, and
**allOFF()** switches all PWM channels OFF. **Experimental** in 0.3.0
To "undo" the allOFF one can call the **reset()** function and set all
PWM channels again.

**lastError()** returns **PCA9685_OK = 0** if all is OK, and

| Error code | Value | Description |
|:----|:----:|:----|
| PCA9685_OK | 0x00 | Everything went well
| PCA9685_ERROR | 0xFF | generic error
| PCA9685_ERR_CHANNEL | 0xFE | Channel out of range
| PCA9685_ERR_MODE | 0xFD | Invalid mode register chosen |
| PCA9685_ERR_I2C | 0xFF | PCA9685 I2C communication error
| PCA9685_ERR_I2C | 0xFC | PCA9685 I2C communication error



Expand Down
53 changes: 53 additions & 0 deletions examples/PCA9685_allOFF_test/PCA9685_allOFF_test.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// FILE: PCA9685_allOFF_test.ino
// AUTHOR: Rob Tillaart
// DATE: 2020-11-22
// VERSION: 0.1.0
// PUPROSE: test PCA9685 library
//

/*
sets all channels to a PWM
then switches them all off
you can check it by testing all channels.
*/

#include "PCA9685.h"

PCA9685 PCA(0x40);

const uint8_t PIN = 2;

void setup()
{
Wire.begin();
PCA.begin();

Serial.begin(115200);
Serial.print("PCA9685 LIB version: ");
Serial.println(PCA9685_LIB_VERSION);
Serial.println();

pinMode(PIN, INPUT_PULLUP);
for (int channel = 0; channel < 16; channel++)
{
PCA.setPWM(channel, 0, 1000);
}
delay(100); // to be sure they started.
PCA.allOFF();

// delay(100);
// PCA.reset(); // needed to reset the allOFF()
// for (int channel = 0; channel < 16; channel++)
// {
// PCA.digitalWrite(channel, HIGH);
// }
}


void loop()
{
Serial.println(digitalRead(PIN)); // you can measure all pins
}

// -- END OF FILE --
66 changes: 66 additions & 0 deletions examples/PCA9685_digitalWrite_test/PCA9685_digitalWrite_test.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// FILE: PCA9685_digitalWrite_test.ino
// AUTHOR: Rob Tillaart
// DATE: 2020-11-21
// VERSION: 0.1.0
// PUPROSE: test PCA9685 library
//

/*
sets one channel to max PWM 0..4095
and connect the output to an interrupt pin 2
to see the frequency of the PWM
*/

#include "PCA9685.h"

PCA9685 PCA(0x40);

const uint8_t IRQ_PIN = 2;
volatile uint16_t count = 0;
uint32_t lastTime = 0;

void setup()
{
Wire.begin();
PCA.begin();

Serial.begin(115200);
Serial.print("PCA9685 LIB version: ");
Serial.println(PCA9685_LIB_VERSION);
Serial.println();

pinMode(IRQ_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(IRQ_PIN), irq, CHANGE);

// PCA.setPWM(15, 0, 1000); // works OK - reference to test irq()
// PCA.digitalWrite(15, LOW); // works OK
PCA.digitalWrite(15, HIGH); // works OK
}


// INTERRUPT ROUTINE TO COUNT THE PULSES
void irq()
{
count++;
}


void loop()
{
uint32_t now = millis();
if (now - lastTime >= 1000)
{
lastTime = now;
// make a copy
noInterrupts();
uint16_t t = count;
count = 0;
interrupts();

Serial.print(t);
Serial.print("\t");
Serial.println(digitalRead(IRQ_PIN));
}
}
// -- END OF FILE --
Loading

0 comments on commit ec57d1b

Please sign in to comment.