AskSin++
PCA9685.h
1 //- -----------------------------------------------------------------------------------------------------------------------
2 // AskSin++
3 // 2018-11-01 papa Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
4 // 2020-11-17 trilu2000 Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
5 //- -----------------------------------------------------------------------------------------------------------------------
6 
7 #ifndef __PCA9685PWM_H__
8 #define __PCA9685PWM_H__
9 
10 #include <Arduino.h>
11 #include <Wire.h>
12 #include <PCA9685.h>
13 
14 // REGISTER ADDRESSES
15 #define PCA9685_MODE1 0x00
16 #define PCA9685_MODE2 0x01
17 #define PCA9685_LED0_ON_L_REG 0x06
18 #define PCA9685_LED0_ON_H_REG 0x07
19 #define PCA9685_LED0_OFF_L_REG 0x08
20 #define PCA9685_LED0_OFF_H_REG 0x09
21 
22 // MODE1 REGISTERS
23 #define PCA9685_RESTART 0x80
24 #define PCA9685_EXTCLK 0x40
25 #define PCA9685_AUTOINCR 0x20
26 #define PCA9685_SLEEP 0x10
27 #define PCA9685_SUB1 0x08
28 #define PCA9685_SUB2 0x04
29 #define PCA9685_SUB3 0x02
30 #define PCA9685_ALLCALL 0x01
31 
32 // MODE2 REGISTERS
33 #define PCA9685_INVERT 0x10
34 #define PCA9685_OCH 0x08
35 #define PCA9685_OUTDRV 0x04
36 #define PCA9685_OUTNE 0x03
37 
38 #define PCA9685_MODE1_RESTART 0x80
39 #define GENERAL_CALL 0x00
40 #define PCA9685_SWRST 0b110
41 
42 namespace as {
43 
44  // reduced 12 bit PWM table, holds only every second value
45  static const uint16_t pwm_12bit_table[101] PROGMEM = {
46  0, 2, 18, 28, 37, 47, 58, 68, 79, 90, 101, 113, 125, 137, 149, 162, 175, 189,203, 217, 231, 246, 262,
47  277, 293, 310, 327, 344, 362, 381, 399, 419, 438, 459, 479, 501, 523, 545, 568, 592, 616, 641, 666,
48  693, 719, 747, 775, 804, 834, 864, 896, 928, 961, 994, 1029, 1064, 1101, 1138, 1177, 1216, 1256,
49  1298, 1340, 1384, 1428, 1474, 1521, 1569, 1619, 1669, 1721, 1775, 1830, 1886, 1943, 2002, 2063, 2125,
50  2189, 2254, 2322, 2390, 2461, 2534, 2608, 2684, 2762, 2843, 2925, 3009, 3096, 3185, 3276, 3369, 3465,
51  3563, 3664, 3768, 3874, 3983, 4095
52  };
53 
54 
55  static uint8_t _init_done = 0;
56 
57  template<uint8_t STEPS = 200, bool LINEAR = false, bool INVERSE = false, byte I2C_ADDRESS = 0x40>
58  class PCA9685PWM {
59  uint8_t _channel;
60 
61  uint8_t read_register(uint8_t address) { // read register byte
62  Wire.beginTransmission(I2C_ADDRESS);
63  Wire.write(address);
64  Wire.endTransmission(false);
65  Wire.requestFrom(I2C_ADDRESS, (uint8_t)1);
66  if (Wire.available() < 1)
67  return 0;
68  return (Wire.read());
69  }
70  void write_register(uint8_t address, uint8_t value) { // writes a byte to a register
71  Wire.beginTransmission(I2C_ADDRESS);
72  Wire.write((uint8_t)address);
73  Wire.write((uint8_t)(value));
74  Wire.endTransmission();
75  }
76  void reset() {
77  Wire.beginTransmission(GENERAL_CALL);
78  Wire.write(PCA9685_SWRST);
79  Wire.endTransmission();
80  delay(10);
81  }
82  void set_channel_value(uint8_t channel, uint16_t value) {
83  //DPRINT(F("set channel: ")); DPRINT(channel); DPRINT(F(" to ")); DPRINTLN(value);
84  write_register(PCA9685_LED0_ON_L_REG + channel * 4, 0x00);
85  write_register(PCA9685_LED0_ON_H_REG + channel * 4, 0x00);
86  write_register(PCA9685_LED0_OFF_L_REG + channel * 4, (value & 0xFF));
87  write_register(PCA9685_LED0_OFF_H_REG + channel * 4, (value >> 8));
88  }
89 
90  public:
91  PCA9685PWM() : _channel(0) {}
92  ~PCA9685PWM() {}
93 
94  void init(uint8_t channel) {
95  _channel = channel;
96  if (_init_done == 0) {
97 
98  Wire.begin();
99  reset();
100 
101  // set default values
102  write_register(PCA9685_MODE1, PCA9685_ALLCALL | PCA9685_AUTOINCR);
103  write_register(PCA9685_MODE2, PCA9685_OUTDRV);
104 
105  // check if the pca9685 is there
106  uint8_t r = read_register(PCA9685_MODE1);
107  DPRINT(F("PCA9685 "));
108  if (r == 0x21) {
109  _init_done = 2;
110  DPRINT(F("found at 0x")); DHEXLN(I2C_ADDRESS);
111  }
112  else {
113  _init_done = 1;
114  DPRINTLN(F(" not found"));
115  }
116  }
117  }
118 
119  void set(uint8_t value) {
120  uint16_t pwm = 0;
121  if (LINEAR) {
122  // https://www.arduino.cc/reference/en/language/functions/math/map/
123  pwm = map(value, 0, STEPS, 0, 4096);
124  }
125  else {
126  // the table holds only every second value to save space, therefore
127  // we have to calculate the odd value as average of the word above and below
128  pwm = pgm_read_word(&pwm_12bit_table[value / 2]);
129 
130  if (value & 1) { //odd
131  uint16_t x_high = pgm_read_word(&pwm_12bit_table[(value / 2) + 1]);
132  pwm = (x_high + pwm) / 2;
133  }
134  }
135  if (INVERSE) pwm = ~pwm ^ 0xF000;
136  //DPRINT(F("PWM value ")); DPRINT(value); DPRINT(F(", pwm ")); DPRINTLN(pwm);
137  set_channel_value(_channel, pwm);
138  }
139  };
140 
141 
142 }
143 
144 #endif
as::PCA9685PWM
Definition: PCA9685.h:58