Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: mbed FastIO FastPWM USBDevice
Fork of Pinscape_Controller by
TLC59116/TLC59116.h@116:80ebb41bad94, 2021-12-22 (annotated)
- Committer:
- mjr
- Date:
- Wed Dec 22 21:48:24 2021 +0000
- Revision:
- 116:80ebb41bad94
- Parent:
- 87:8d35c74403af
Add Arnoz RigMaster and KLShield boards
Who changed what in which revision?
| User | Revision | Line number | New contents of line | 
|---|---|---|---|
| mjr | 87:8d35c74403af | 1 | // TLC59116 interface | 
| mjr | 87:8d35c74403af | 2 | // | 
| mjr | 87:8d35c74403af | 3 | // The TLC59116 is a 16-channel constant-current PWM controller chip with | 
| mjr | 87:8d35c74403af | 4 | // an I2C interface. | 
| mjr | 87:8d35c74403af | 5 | // | 
| mjr | 87:8d35c74403af | 6 | // Up to 14 of these chips can be connected to a single bus. Each chip needs | 
| mjr | 87:8d35c74403af | 7 | // a unique address, configured via four pin inputs. (The I2C address is 7 | 
| mjr | 87:8d35c74403af | 8 | // bits, but the high-order 3 bits are fixed in the hardware, leaving 4 bits | 
| mjr | 87:8d35c74403af | 9 | // to configure per chip. Two of the possible 16 addresses are reserved by | 
| mjr | 87:8d35c74403af | 10 | // the chip hardware as broadcast addresses, leaving room for 14 unique chip | 
| mjr | 87:8d35c74403af | 11 | // addresses per bus.) | 
| mjr | 87:8d35c74403af | 12 | // | 
| mjr | 87:8d35c74403af | 13 | // EXTERNAL PULL-UP RESISTORS ARE REQUIRED ON SDA AND SCL. The internal | 
| mjr | 87:8d35c74403af | 14 | // pull-ups in the KL25Z GPIO ports will only work if the bus speed is | 
| mjr | 87:8d35c74403af | 15 | // limited to 100kHz. Higher speeds require external pull-ups. Because | 
| mjr | 87:8d35c74403af | 16 | // of the relatively high data rate required, we use the maximum 1MHz bus | 
| mjr | 87:8d35c74403af | 17 | // speed, requiring external pull-ups. These are typically 2.2K. | 
| mjr | 87:8d35c74403af | 18 | // | 
| mjr | 87:8d35c74403af | 19 | // This chip is similar to the TLC5940, but has a more modern design with | 
| mjr | 87:8d35c74403af | 20 | // several advantages, including a standardized and much more robust data | 
| mjr | 87:8d35c74403af | 21 | // interface (I2C) and glitch-free startup. The only downside vs the TLC5940 | 
| mjr | 87:8d35c74403af | 22 | // is that it's only available in an SMD package, whereas the TLC5940 is | 
| mjr | 87:8d35c74403af | 23 | // available in easy-to-solder DIP format. The DIP 5940 is longer being | 
| mjr | 87:8d35c74403af | 24 | // manufactured, but it's still easy to find old stock; when those run out, | 
| mjr | 87:8d35c74403af | 25 | // though, and the choice is between SMD 5940 and 59116, the 59116 will be | 
| mjr | 87:8d35c74403af | 26 | // the clear winner. | 
| mjr | 87:8d35c74403af | 27 | // | 
| mjr | 87:8d35c74403af | 28 | |
| mjr | 87:8d35c74403af | 29 | #ifndef _TLC59116_H_ | 
| mjr | 87:8d35c74403af | 30 | #define _TLC59116_H_ | 
| mjr | 87:8d35c74403af | 31 | |
| mjr | 87:8d35c74403af | 32 | #include "mbed.h" | 
| mjr | 87:8d35c74403af | 33 | #include "BitBangI2C.h" | 
| mjr | 87:8d35c74403af | 34 | |
| mjr | 87:8d35c74403af | 35 | // Which I2C class are we using? We use this to switch between | 
| mjr | 87:8d35c74403af | 36 | // BitBangI2C and MbedI2C for testing and debugging. | 
| mjr | 87:8d35c74403af | 37 | #define I2C_Type BitBangI2C | 
| mjr | 87:8d35c74403af | 38 | |
| mjr | 87:8d35c74403af | 39 | // register constants | 
| mjr | 87:8d35c74403af | 40 | struct TLC59116R | 
| mjr | 87:8d35c74403af | 41 | { | 
| mjr | 87:8d35c74403af | 42 | // control register bits | 
| mjr | 87:8d35c74403af | 43 | static const uint8_t CTL_AIALL = 0x80; // auto-increment mode, all registers | 
| mjr | 87:8d35c74403af | 44 | static const uint8_t CTL_AIPWM = 0xA0; // auto-increment mode, PWM registers only | 
| mjr | 87:8d35c74403af | 45 | static const uint8_t CTL_AICTL = 0xC0; // auto-increment mode, control registers only | 
| mjr | 87:8d35c74403af | 46 | static const uint8_t CTL_AIPWMCTL = 0xE0; // auto-increment mode, PWM + control registers only | 
| mjr | 87:8d35c74403af | 47 | |
| mjr | 87:8d35c74403af | 48 | // register addresses | 
| mjr | 87:8d35c74403af | 49 | static const uint8_t REG_MODE1 = 0x00; // MODE1 | 
| mjr | 87:8d35c74403af | 50 | static const uint8_t REG_MODE2 = 0x01; // MODE2 | 
| mjr | 87:8d35c74403af | 51 | static const uint8_t REG_PWM0 = 0x02; // PWM 0 | 
| mjr | 87:8d35c74403af | 52 | static const uint8_t REG_PWM1 = 0x03; // PWM 1 | 
| mjr | 87:8d35c74403af | 53 | static const uint8_t REG_PWM2 = 0x04; // PWM 2 | 
| mjr | 87:8d35c74403af | 54 | static const uint8_t REG_PWM3 = 0x05; // PWM 3 | 
| mjr | 87:8d35c74403af | 55 | static const uint8_t REG_PWM4 = 0x06; // PWM 4 | 
| mjr | 87:8d35c74403af | 56 | static const uint8_t REG_PWM5 = 0x07; // PWM 5 | 
| mjr | 87:8d35c74403af | 57 | static const uint8_t REG_PWM6 = 0x08; // PWM 6 | 
| mjr | 87:8d35c74403af | 58 | static const uint8_t REG_PWM7 = 0x09; // PWM 7 | 
| mjr | 87:8d35c74403af | 59 | static const uint8_t REG_PWM8 = 0x0A; // PWM 8 | 
| mjr | 87:8d35c74403af | 60 | static const uint8_t REG_PWM9 = 0x0B; // PWM 9 | 
| mjr | 87:8d35c74403af | 61 | static const uint8_t REG_PWM10 = 0x0C; // PWM 10 | 
| mjr | 87:8d35c74403af | 62 | static const uint8_t REG_PWM11 = 0x0D; // PWM 11 | 
| mjr | 87:8d35c74403af | 63 | static const uint8_t REG_PWM12 = 0x0E; // PWM 12 | 
| mjr | 87:8d35c74403af | 64 | static const uint8_t REG_PWM13 = 0x0F; // PWM 13 | 
| mjr | 87:8d35c74403af | 65 | static const uint8_t REG_PWM14 = 0x10; // PWM 14 | 
| mjr | 87:8d35c74403af | 66 | static const uint8_t REG_PWM15 = 0x11; // PWM 15 | 
| mjr | 87:8d35c74403af | 67 | static const uint8_t REG_GRPPWM = 0x12; // Group PWM duty cycle | 
| mjr | 87:8d35c74403af | 68 | static const uint8_t REG_GRPFREQ = 0x13; // Group frequency register | 
| mjr | 87:8d35c74403af | 69 | static const uint8_t REG_LEDOUT0 = 0x14; // LED driver output status register 0 | 
| mjr | 87:8d35c74403af | 70 | static const uint8_t REG_LEDOUT1 = 0x15; // LED driver output status register 1 | 
| mjr | 87:8d35c74403af | 71 | static const uint8_t REG_LEDOUT2 = 0x16; // LED driver output status register 2 | 
| mjr | 87:8d35c74403af | 72 | static const uint8_t REG_LEDOUT3 = 0x17; // LED driver output status register 3 | 
| mjr | 87:8d35c74403af | 73 | |
| mjr | 87:8d35c74403af | 74 | // MODE1 bits | 
| mjr | 87:8d35c74403af | 75 | static const uint8_t MODE1_AI2 = 0x80; // auto-increment mode enable | 
| mjr | 87:8d35c74403af | 76 | static const uint8_t MODE1_AI1 = 0x40; // auto-increment bit 1 | 
| mjr | 87:8d35c74403af | 77 | static const uint8_t MODE1_AI0 = 0x20; // auto-increment bit 0 | 
| mjr | 87:8d35c74403af | 78 | static const uint8_t MODE1_OSCOFF = 0x10; // oscillator off | 
| mjr | 87:8d35c74403af | 79 | static const uint8_t MODE1_SUB1 = 0x08; // subaddress 1 enable | 
| mjr | 87:8d35c74403af | 80 | static const uint8_t MODE1_SUB2 = 0x04; // subaddress 2 enable | 
| mjr | 87:8d35c74403af | 81 | static const uint8_t MODE1_SUB3 = 0x02; // subaddress 3 enable | 
| mjr | 87:8d35c74403af | 82 | static const uint8_t MODE1_ALLCALL = 0x01; // all-call enable | 
| mjr | 87:8d35c74403af | 83 | |
| mjr | 87:8d35c74403af | 84 | // MODE2 bits | 
| mjr | 87:8d35c74403af | 85 | static const uint8_t MODE2_EFCLR = 0x80; // clear error status flag | 
| mjr | 87:8d35c74403af | 86 | static const uint8_t MODE2_DMBLNK = 0x20; // group blinking mode | 
| mjr | 87:8d35c74403af | 87 | static const uint8_t MODE2_OCH = 0x08; // outputs change on ACK (vs Stop command) | 
| mjr | 87:8d35c74403af | 88 | |
| mjr | 87:8d35c74403af | 89 | // LEDOUTn states | 
| mjr | 87:8d35c74403af | 90 | static const uint8_t LEDOUT_OFF = 0x00; // driver is off | 
| mjr | 87:8d35c74403af | 91 | static const uint8_t LEDOUT_ON = 0x01; // fully on | 
| mjr | 87:8d35c74403af | 92 | static const uint8_t LEDOUT_PWM = 0x02; // individual PWM control via PWMn register | 
| mjr | 87:8d35c74403af | 93 | static const uint8_t LEDOUT_GROUP = 0x03; // PWM control + group dimming/blinking via PWMn + GRPPWM | 
| mjr | 87:8d35c74403af | 94 | }; | 
| mjr | 87:8d35c74403af | 95 | |
| mjr | 87:8d35c74403af | 96 | |
| mjr | 87:8d35c74403af | 97 | // Individual unit object. We create one of these for each unit we | 
| mjr | 87:8d35c74403af | 98 | // find on the bus. This keeps track of the state of each output on | 
| mjr | 87:8d35c74403af | 99 | // a unit so that we can update outputs in batches, to reduce the | 
| mjr | 87:8d35c74403af | 100 | // amount of time we spend in I2C communications during rapid updates. | 
| mjr | 87:8d35c74403af | 101 | struct TLC59116Unit | 
| mjr | 87:8d35c74403af | 102 | { | 
| mjr | 87:8d35c74403af | 103 | TLC59116Unit() | 
| mjr | 87:8d35c74403af | 104 | { | 
| mjr | 87:8d35c74403af | 105 | // start inactive, since we haven't been initialized yet | 
| mjr | 87:8d35c74403af | 106 | active = false; | 
| mjr | 87:8d35c74403af | 107 | |
| mjr | 87:8d35c74403af | 108 | // set all brightness levels to 0 intially | 
| mjr | 87:8d35c74403af | 109 | memset(bri, 0, sizeof(bri)); | 
| mjr | 87:8d35c74403af | 110 | |
| mjr | 87:8d35c74403af | 111 | // mark all outputs as dirty to force an update after initializing | 
| mjr | 87:8d35c74403af | 112 | dirty = 0xFFFF; | 
| mjr | 87:8d35c74403af | 113 | } | 
| mjr | 87:8d35c74403af | 114 | |
| mjr | 87:8d35c74403af | 115 | // initialize | 
| mjr | 87:8d35c74403af | 116 | void init(int addr, I2C_Type &i2c) | 
| mjr | 87:8d35c74403af | 117 | { | 
| mjr | 87:8d35c74403af | 118 | // set all output drivers to individual PWM control | 
| mjr | 87:8d35c74403af | 119 | const uint8_t all_pwm = | 
| mjr | 87:8d35c74403af | 120 | TLC59116R::LEDOUT_PWM | 
| mjr | 87:8d35c74403af | 121 | | (TLC59116R::LEDOUT_PWM << 2) | 
| mjr | 87:8d35c74403af | 122 | | (TLC59116R::LEDOUT_PWM << 4) | 
| mjr | 87:8d35c74403af | 123 | | (TLC59116R::LEDOUT_PWM << 6); | 
| mjr | 87:8d35c74403af | 124 | static const uint8_t buf[] = { | 
| mjr | 87:8d35c74403af | 125 | TLC59116R::REG_LEDOUT0 | TLC59116R::CTL_AIALL, | 
| mjr | 87:8d35c74403af | 126 | all_pwm, | 
| mjr | 87:8d35c74403af | 127 | all_pwm, | 
| mjr | 87:8d35c74403af | 128 | all_pwm, | 
| mjr | 87:8d35c74403af | 129 | all_pwm | 
| mjr | 87:8d35c74403af | 130 | }; | 
| mjr | 87:8d35c74403af | 131 | int err = i2c.write(addr << 1, buf, sizeof(buf)); | 
| mjr | 87:8d35c74403af | 132 | |
| mjr | 87:8d35c74403af | 133 | // turn on the oscillator | 
| mjr | 87:8d35c74403af | 134 | static const uint8_t buf2[] = { | 
| mjr | 87:8d35c74403af | 135 | TLC59116R::REG_MODE1, | 
| mjr | 87:8d35c74403af | 136 | TLC59116R::MODE1_AI2 | TLC59116R::MODE1_ALLCALL | 
| mjr | 87:8d35c74403af | 137 | }; | 
| mjr | 87:8d35c74403af | 138 | err |= i2c.write(addr << 1, buf2, sizeof(buf)); | 
| mjr | 87:8d35c74403af | 139 | |
| mjr | 87:8d35c74403af | 140 | // mark the unit as active if the writes succeeded | 
| mjr | 87:8d35c74403af | 141 | active = !err; | 
| mjr | 87:8d35c74403af | 142 | } | 
| mjr | 87:8d35c74403af | 143 | |
| mjr | 87:8d35c74403af | 144 | // Set an output | 
| mjr | 87:8d35c74403af | 145 | void set(int idx, int val) | 
| mjr | 87:8d35c74403af | 146 | { | 
| mjr | 87:8d35c74403af | 147 | // validate the index | 
| mjr | 87:8d35c74403af | 148 | if (idx >= 0 && idx <= 15) | 
| mjr | 87:8d35c74403af | 149 | { | 
| mjr | 87:8d35c74403af | 150 | // record the new brightness | 
| mjr | 87:8d35c74403af | 151 | bri[idx] = val; | 
| mjr | 87:8d35c74403af | 152 | |
| mjr | 87:8d35c74403af | 153 | // set the dirty bit | 
| mjr | 87:8d35c74403af | 154 | dirty |= 1 << idx; | 
| mjr | 87:8d35c74403af | 155 | } | 
| mjr | 87:8d35c74403af | 156 | } | 
| mjr | 87:8d35c74403af | 157 | |
| mjr | 87:8d35c74403af | 158 | // Get an output's current value | 
| mjr | 87:8d35c74403af | 159 | int get(int idx) const | 
| mjr | 87:8d35c74403af | 160 | { | 
| mjr | 87:8d35c74403af | 161 | return idx >= 0 && idx <= 15 ? bri[idx] : -1; | 
| mjr | 87:8d35c74403af | 162 | } | 
| mjr | 87:8d35c74403af | 163 | |
| mjr | 87:8d35c74403af | 164 | // Send I2C updates | 
| mjr | 87:8d35c74403af | 165 | void send(int addr, I2C_Type &i2c) | 
| mjr | 87:8d35c74403af | 166 | { | 
| mjr | 87:8d35c74403af | 167 | // Scan all outputs. I2C sends are fairly expensive, so we | 
| mjr | 87:8d35c74403af | 168 | // minimize the send time by using the auto-increment mode. | 
| mjr | 87:8d35c74403af | 169 | // Optimizing this is a bit tricky. Suppose that the outputs | 
| mjr | 87:8d35c74403af | 170 | // are in this state, where c represents a clean output and D | 
| mjr | 87:8d35c74403af | 171 | // represents a dirty output: | 
| mjr | 87:8d35c74403af | 172 | // | 
| mjr | 87:8d35c74403af | 173 | // cccDcDccc... | 
| mjr | 87:8d35c74403af | 174 | // | 
| mjr | 87:8d35c74403af | 175 | // Clearly we want to start sending at the first dirty output | 
| mjr | 87:8d35c74403af | 176 | // so that we don't waste time sending the three clean bytes | 
| mjr | 87:8d35c74403af | 177 | // ahead of it. However, do we send output[3] as one chunk | 
| mjr | 87:8d35c74403af | 178 | // and then send output[5] as a separate chunk, or do we send | 
| mjr | 87:8d35c74403af | 179 | // outputs [3],[4],[5] as a single block to take advantage of | 
| mjr | 87:8d35c74403af | 180 | // the auto-increment mode? Based on I2C bus timing parameters, | 
| mjr | 87:8d35c74403af | 181 | // the answer is that it's cheaper to send this as a single | 
| mjr | 87:8d35c74403af | 182 | // contiguous block [3],[4],[5]. The reason is that the cost | 
| mjr | 87:8d35c74403af | 183 | // of starting a new block is a Stop/Start sequence plus another | 
| mjr | 87:8d35c74403af | 184 | // register address byte; the register address byte costs the | 
| mjr | 87:8d35c74403af | 185 | // same as a data byte, so the extra Stop/Start of the separate | 
| mjr | 87:8d35c74403af | 186 | // chunk approach makes the single continguous send cheaper. | 
| mjr | 87:8d35c74403af | 187 | // But how about this one?: | 
| mjr | 87:8d35c74403af | 188 | // | 
| mjr | 87:8d35c74403af | 189 | // cccDccDccc... | 
| mjr | 87:8d35c74403af | 190 | // | 
| mjr | 87:8d35c74403af | 191 | // This one is cheaper to send as two separate blocks. The | 
| mjr | 87:8d35c74403af | 192 | // break costs us a Start/Stop plus a register address byte, | 
| mjr | 87:8d35c74403af | 193 | // but the Start/Stop is only about 25% of the cost of a data | 
| mjr | 87:8d35c74403af | 194 | // byte, so Start/Stop+Register Address is cheaper than sending | 
| mjr | 87:8d35c74403af | 195 | // the two clean data bytes sandwiched between the dirty bytes. | 
| mjr | 87:8d35c74403af | 196 | // | 
| mjr | 87:8d35c74403af | 197 | // So: we want to look for sequences of contiguous dirty bytes | 
| mjr | 87:8d35c74403af | 198 | // and send those as a chunk. We furthermore will allow up to | 
| mjr | 87:8d35c74403af | 199 | // one clean byte in the midst of the dirty bytes. | 
| mjr | 87:8d35c74403af | 200 | uint8_t buf[17]; | 
| mjr | 87:8d35c74403af | 201 | int n = 0; | 
| mjr | 87:8d35c74403af | 202 | for (int i = 0, bit = 1 ; i < 16 ; ++i, bit <<= 1) | 
| mjr | 87:8d35c74403af | 203 | { | 
| mjr | 87:8d35c74403af | 204 | // If this one is dirty, include it in the set of outputs to | 
| mjr | 87:8d35c74403af | 205 | // send to the chip. Also include this one if it's clean | 
| mjr | 87:8d35c74403af | 206 | // and the outputs on both sides are dirty - see the notes | 
| mjr | 87:8d35c74403af | 207 | // above about optimizing for the case where we have one clean | 
| mjr | 87:8d35c74403af | 208 | // output surrounded by dirty outputs. | 
| mjr | 87:8d35c74403af | 209 | if ((dirty & bit) != 0) | 
| mjr | 87:8d35c74403af | 210 | { | 
| mjr | 87:8d35c74403af | 211 | // it's dirty - add it to the dirty set under construction | 
| mjr | 87:8d35c74403af | 212 | buf[++n] = bri[i]; | 
| mjr | 87:8d35c74403af | 213 | } | 
| mjr | 87:8d35c74403af | 214 | else if (n != 0 && n < 15 && (dirty & (bit << 1)) != 0) | 
| mjr | 87:8d35c74403af | 215 | { | 
| mjr | 87:8d35c74403af | 216 | // this one is clean, but the one before and the one after | 
| mjr | 87:8d35c74403af | 217 | // are both dirty, so keep it in the set anyway to take | 
| mjr | 87:8d35c74403af | 218 | // advantage of the auto-increment mode for faster sends | 
| mjr | 87:8d35c74403af | 219 | buf[++n] = bri[i]; | 
| mjr | 87:8d35c74403af | 220 | } | 
| mjr | 87:8d35c74403af | 221 | else | 
| mjr | 87:8d35c74403af | 222 | { | 
| mjr | 87:8d35c74403af | 223 | // This one is clean, and it's not surrounded by dirty | 
| mjr | 87:8d35c74403af | 224 | // outputs. If the set of dirty outputs so far has any | 
| mjr | 87:8d35c74403af | 225 | // members, send them now. | 
| mjr | 87:8d35c74403af | 226 | if (n != 0) | 
| mjr | 87:8d35c74403af | 227 | { | 
| mjr | 87:8d35c74403af | 228 | // set the starting register address, including the | 
| mjr | 87:8d35c74403af | 229 | // auto-increment flag, and write the block | 
| mjr | 87:8d35c74403af | 230 | buf[0] = (TLC59116R::REG_PWM0 + i - n) | TLC59116R::CTL_AIALL; | 
| mjr | 87:8d35c74403af | 231 | i2c.write(addr << 1, buf, n + 1); | 
| mjr | 87:8d35c74403af | 232 | |
| mjr | 87:8d35c74403af | 233 | // empty the set | 
| mjr | 87:8d35c74403af | 234 | n = 0; | 
| mjr | 87:8d35c74403af | 235 | } | 
| mjr | 87:8d35c74403af | 236 | } | 
| mjr | 87:8d35c74403af | 237 | } | 
| mjr | 87:8d35c74403af | 238 | |
| mjr | 87:8d35c74403af | 239 | // if we finished the loop with dirty outputs to send, send them | 
| mjr | 87:8d35c74403af | 240 | if (n != 0) | 
| mjr | 87:8d35c74403af | 241 | { | 
| mjr | 87:8d35c74403af | 242 | // fill in the starting register address, and write the block | 
| mjr | 87:8d35c74403af | 243 | buf[0] = (TLC59116R::REG_PWM15 + 1 - n) | TLC59116R::CTL_AIALL; | 
| mjr | 87:8d35c74403af | 244 | i2c.write(addr << 1, buf, n + 1); | 
| mjr | 87:8d35c74403af | 245 | } | 
| mjr | 87:8d35c74403af | 246 | |
| mjr | 87:8d35c74403af | 247 | // all outputs are now clean | 
| mjr | 87:8d35c74403af | 248 | dirty = 0; | 
| mjr | 87:8d35c74403af | 249 | } | 
| mjr | 87:8d35c74403af | 250 | |
| mjr | 87:8d35c74403af | 251 | // Is the unit active? If we have trouble writing a unit, | 
| mjr | 87:8d35c74403af | 252 | // we can mark it inactive so that we know to stop wasting | 
| mjr | 87:8d35c74403af | 253 | // time writing to it, and so that we can re-initialize it | 
| mjr | 87:8d35c74403af | 254 | // if it comes back on later bus scans. | 
| mjr | 87:8d35c74403af | 255 | bool active; | 
| mjr | 87:8d35c74403af | 256 | |
| mjr | 87:8d35c74403af | 257 | // Output states. This records the latest brightness level | 
| mjr | 87:8d35c74403af | 258 | // for each output as set by the client. We don't actually | 
| mjr | 87:8d35c74403af | 259 | // send these values to the physical unit until the client | 
| mjr | 87:8d35c74403af | 260 | // tells us to do an I2C update. | 
| mjr | 87:8d35c74403af | 261 | uint8_t bri[16]; | 
| mjr | 87:8d35c74403af | 262 | |
| mjr | 87:8d35c74403af | 263 | // Dirty output mask. Whenever the client changes an output, | 
| mjr | 87:8d35c74403af | 264 | // we record the new brightness in bri[] and set the | 
| mjr | 87:8d35c74403af | 265 | // corresponding bit here to 1. We use these bits to determine | 
| mjr | 87:8d35c74403af | 266 | // which outputs to send during each I2C update. | 
| mjr | 87:8d35c74403af | 267 | uint16_t dirty; | 
| mjr | 87:8d35c74403af | 268 | }; | 
| mjr | 87:8d35c74403af | 269 | |
| mjr | 87:8d35c74403af | 270 | // TLC59116 public interface. This provides control over a collection | 
| mjr | 87:8d35c74403af | 271 | // of units connected on a common I2C bus. | 
| mjr | 87:8d35c74403af | 272 | class TLC59116 | 
| mjr | 87:8d35c74403af | 273 | { | 
| mjr | 87:8d35c74403af | 274 | public: | 
| mjr | 87:8d35c74403af | 275 | // Initialize. The address given is the configurable part | 
| mjr | 87:8d35c74403af | 276 | // of the address, 0x0000 to 0x000F. | 
| mjr | 87:8d35c74403af | 277 | TLC59116(PinName sda, PinName scl, PinName reset) | 
| mjr | 87:8d35c74403af | 278 | : i2c(sda, scl, true), reset(reset) | 
| mjr | 87:8d35c74403af | 279 | { | 
| mjr | 87:8d35c74403af | 280 | // Use the fastest I2C speed possible, since we want to be able | 
| mjr | 87:8d35c74403af | 281 | // to rapidly update many outputs at once. The TLC59116 can run | 
| mjr | 87:8d35c74403af | 282 | // I2C at up to 1MHz. | 
| mjr | 87:8d35c74403af | 283 | i2c.frequency(1000000); | 
| mjr | 87:8d35c74403af | 284 | |
| mjr | 87:8d35c74403af | 285 | // assert !RESET until we're ready to go | 
| mjr | 87:8d35c74403af | 286 | this->reset.write(0); | 
| mjr | 87:8d35c74403af | 287 | |
| mjr | 87:8d35c74403af | 288 | // there are no units yet | 
| mjr | 87:8d35c74403af | 289 | memset(units, 0, sizeof(units)); | 
| mjr | 87:8d35c74403af | 290 | nextUpdate = 0; | 
| mjr | 87:8d35c74403af | 291 | } | 
| mjr | 87:8d35c74403af | 292 | |
| mjr | 87:8d35c74403af | 293 | void init() | 
| mjr | 87:8d35c74403af | 294 | { | 
| mjr | 87:8d35c74403af | 295 | // un-assert reset | 
| mjr | 87:8d35c74403af | 296 | reset.write(1); | 
| mjr | 87:8d35c74403af | 297 | wait_us(10000); | 
| mjr | 87:8d35c74403af | 298 | |
| mjr | 87:8d35c74403af | 299 | // scan the bus for new units | 
| mjr | 87:8d35c74403af | 300 | scanBus(); | 
| mjr | 87:8d35c74403af | 301 | } | 
| mjr | 87:8d35c74403af | 302 | |
| mjr | 87:8d35c74403af | 303 | // scan the bus | 
| mjr | 87:8d35c74403af | 304 | void scanBus() | 
| mjr | 87:8d35c74403af | 305 | { | 
| mjr | 87:8d35c74403af | 306 | // scan each possible address | 
| mjr | 87:8d35c74403af | 307 | for (int i = 0 ; i < 16 ; ++i) | 
| mjr | 87:8d35c74403af | 308 | { | 
| mjr | 87:8d35c74403af | 309 | // Address 8 and 11 are reserved - skip them | 
| mjr | 87:8d35c74403af | 310 | if (i == 8 || i == 11) | 
| mjr | 87:8d35c74403af | 311 | continue; | 
| mjr | 87:8d35c74403af | 312 | |
| mjr | 87:8d35c74403af | 313 | // Try reading register REG_MODE1 | 
| mjr | 87:8d35c74403af | 314 | int addr = I2C_BASE_ADDR | i; | 
| mjr | 87:8d35c74403af | 315 | TLC59116Unit *u = units[i]; | 
| mjr | 87:8d35c74403af | 316 | if (readReg8(addr, TLC59116R::REG_MODE1) >= 0) | 
| mjr | 87:8d35c74403af | 317 | { | 
| mjr | 87:8d35c74403af | 318 | // success - if the slot wasn't already populated, allocate | 
| mjr | 87:8d35c74403af | 319 | // a unit entry for it | 
| mjr | 87:8d35c74403af | 320 | if (u == 0) | 
| mjr | 87:8d35c74403af | 321 | units[i] = u = new TLC59116Unit(); | 
| mjr | 87:8d35c74403af | 322 | |
| mjr | 87:8d35c74403af | 323 | // if the unit isn't already marked active, initialize it | 
| mjr | 87:8d35c74403af | 324 | if (!u->active) | 
| mjr | 87:8d35c74403af | 325 | u->init(addr, i2c); | 
| mjr | 87:8d35c74403af | 326 | } | 
| mjr | 87:8d35c74403af | 327 | else | 
| mjr | 87:8d35c74403af | 328 | { | 
| mjr | 87:8d35c74403af | 329 | // failed - if the unit was previously active, mark it | 
| mjr | 87:8d35c74403af | 330 | // as inactive now | 
| mjr | 87:8d35c74403af | 331 | if (u != 0) | 
| mjr | 87:8d35c74403af | 332 | u->active = false; | 
| mjr | 87:8d35c74403af | 333 | } | 
| mjr | 87:8d35c74403af | 334 | } | 
| mjr | 87:8d35c74403af | 335 | } | 
| mjr | 87:8d35c74403af | 336 | |
| mjr | 87:8d35c74403af | 337 | // set an output | 
| mjr | 87:8d35c74403af | 338 | void set(int unit, int output, int val) | 
| mjr | 87:8d35c74403af | 339 | { | 
| mjr | 87:8d35c74403af | 340 | if (unit >= 0 && unit <= 15) | 
| mjr | 87:8d35c74403af | 341 | { | 
| mjr | 87:8d35c74403af | 342 | TLC59116Unit *u = units[unit]; | 
| mjr | 87:8d35c74403af | 343 | if (u != 0) | 
| mjr | 87:8d35c74403af | 344 | u->set(output, val); | 
| mjr | 87:8d35c74403af | 345 | } | 
| mjr | 87:8d35c74403af | 346 | } | 
| mjr | 87:8d35c74403af | 347 | |
| mjr | 87:8d35c74403af | 348 | // get an output's current value | 
| mjr | 87:8d35c74403af | 349 | int get(int unit, int output) | 
| mjr | 87:8d35c74403af | 350 | { | 
| mjr | 87:8d35c74403af | 351 | if (unit >= 0 && unit <= 15) | 
| mjr | 87:8d35c74403af | 352 | { | 
| mjr | 87:8d35c74403af | 353 | TLC59116Unit *u = units[unit]; | 
| mjr | 87:8d35c74403af | 354 | if (u != 0) | 
| mjr | 87:8d35c74403af | 355 | return u->get(output); | 
| mjr | 87:8d35c74403af | 356 | } | 
| mjr | 87:8d35c74403af | 357 | |
| mjr | 87:8d35c74403af | 358 | return -1; | 
| mjr | 87:8d35c74403af | 359 | } | 
| mjr | 87:8d35c74403af | 360 | |
| mjr | 87:8d35c74403af | 361 | // Send I2C updates to the next unit. The client must call this | 
| mjr | 87:8d35c74403af | 362 | // periodically to send pending updates. We only update one unit on | 
| mjr | 87:8d35c74403af | 363 | // each call to ensure that the time per cycle is relatively constant | 
| mjr | 87:8d35c74403af | 364 | // (rather than scaling with the number of chips). | 
| mjr | 87:8d35c74403af | 365 | void send() | 
| mjr | 87:8d35c74403af | 366 | { | 
| mjr | 87:8d35c74403af | 367 | // look for a dirty unit | 
| mjr | 87:8d35c74403af | 368 | for (int i = 0, n = nextUpdate ; i < 16 ; ++i, ++n) | 
| mjr | 87:8d35c74403af | 369 | { | 
| mjr | 87:8d35c74403af | 370 | // wrap the unit number | 
| mjr | 87:8d35c74403af | 371 | n &= 0x0F; | 
| mjr | 87:8d35c74403af | 372 | |
| mjr | 87:8d35c74403af | 373 | // if this unit is populated and dirty, it's the one to update | 
| mjr | 87:8d35c74403af | 374 | TLC59116Unit *u = units[n]; | 
| mjr | 87:8d35c74403af | 375 | if (u != 0 && u->dirty != 0) | 
| mjr | 87:8d35c74403af | 376 | { | 
| mjr | 87:8d35c74403af | 377 | // it's dirty - update it | 
| mjr | 87:8d35c74403af | 378 | u->send(I2C_BASE_ADDR | n, i2c); | 
| mjr | 87:8d35c74403af | 379 | |
| mjr | 87:8d35c74403af | 380 | // We only update one on each call, so we're done. | 
| mjr | 87:8d35c74403af | 381 | // Remember where to pick up again on the next update() | 
| mjr | 87:8d35c74403af | 382 | // call, and return. | 
| mjr | 87:8d35c74403af | 383 | nextUpdate = n + 1; | 
| mjr | 87:8d35c74403af | 384 | return; | 
| mjr | 87:8d35c74403af | 385 | } | 
| mjr | 87:8d35c74403af | 386 | } | 
| mjr | 87:8d35c74403af | 387 | } | 
| mjr | 87:8d35c74403af | 388 | |
| mjr | 87:8d35c74403af | 389 | // Enable/disable all outputs | 
| mjr | 87:8d35c74403af | 390 | void enable(bool f) | 
| mjr | 87:8d35c74403af | 391 | { | 
| mjr | 87:8d35c74403af | 392 | // visit each populated unit | 
| mjr | 87:8d35c74403af | 393 | for (int i = 0 ; i < 16 ; ++i) | 
| mjr | 87:8d35c74403af | 394 | { | 
| mjr | 87:8d35c74403af | 395 | // if this unit is populated, enable/disable it | 
| mjr | 87:8d35c74403af | 396 | TLC59116Unit *u = units[i]; | 
| mjr | 87:8d35c74403af | 397 | if (u != 0) | 
| mjr | 87:8d35c74403af | 398 | { | 
| mjr | 87:8d35c74403af | 399 | // read the current MODE1 register | 
| mjr | 87:8d35c74403af | 400 | int m = readReg8(I2C_BASE_ADDR | i, TLC59116R::REG_MODE1); | 
| mjr | 87:8d35c74403af | 401 | if (m >= 0) | 
| mjr | 87:8d35c74403af | 402 | { | 
| mjr | 87:8d35c74403af | 403 | // Turn the oscillator off to disable, on to enable. | 
| mjr | 87:8d35c74403af | 404 | // Note that the bit is kind of backwards: SETTING the | 
| mjr | 87:8d35c74403af | 405 | // OSC bit turns the oscillator OFF. | 
| mjr | 87:8d35c74403af | 406 | if (f) | 
| mjr | 87:8d35c74403af | 407 | m &= ~TLC59116R::MODE1_OSCOFF; // enable - clear the OSC bit | 
| mjr | 87:8d35c74403af | 408 | else | 
| mjr | 87:8d35c74403af | 409 | m |= TLC59116R::MODE1_OSCOFF; // disable - set the OSC bit | 
| mjr | 87:8d35c74403af | 410 | |
| mjr | 87:8d35c74403af | 411 | // update MODE1 | 
| mjr | 87:8d35c74403af | 412 | writeReg8(I2C_BASE_ADDR | i, TLC59116R::REG_MODE1, m); | 
| mjr | 87:8d35c74403af | 413 | } | 
| mjr | 87:8d35c74403af | 414 | } | 
| mjr | 87:8d35c74403af | 415 | } | 
| mjr | 87:8d35c74403af | 416 | } | 
| mjr | 87:8d35c74403af | 417 | |
| mjr | 87:8d35c74403af | 418 | protected: | 
| mjr | 87:8d35c74403af | 419 | // TLC59116 base I2C address. These chips use an address of | 
| mjr | 87:8d35c74403af | 420 | // the form 110xxxx, where the the low four bits are set by | 
| mjr | 87:8d35c74403af | 421 | // external pins on the chip. The top three bits are always | 
| mjr | 87:8d35c74403af | 422 | // the same, so we construct the full address by combining | 
| mjr | 87:8d35c74403af | 423 | // the upper three fixed bits with the four-bit unit number. | 
| mjr | 87:8d35c74403af | 424 | // | 
| mjr | 87:8d35c74403af | 425 | // Note that addresses 1101011 (0x6B) and 1101000 (0x68) are | 
| mjr | 87:8d35c74403af | 426 | // reserved (for SWRSTT and ALLCALL, respectively), and can't | 
| mjr | 87:8d35c74403af | 427 | // be used for configured device addresses. | 
| mjr | 87:8d35c74403af | 428 | static const uint8_t I2C_BASE_ADDR = 0x60; | 
| mjr | 87:8d35c74403af | 429 | |
| mjr | 87:8d35c74403af | 430 | // Units. We populate this with active units we find in | 
| mjr | 87:8d35c74403af | 431 | // bus scans. Note that units 8 and 11 can't be used because | 
| mjr | 87:8d35c74403af | 432 | // of the reserved ALLCALL and SWRST addresses, but we allocate | 
| mjr | 87:8d35c74403af | 433 | // the slots anyway to keep indexing simple. | 
| mjr | 87:8d35c74403af | 434 | TLC59116Unit *units[16]; | 
| mjr | 87:8d35c74403af | 435 | |
| mjr | 87:8d35c74403af | 436 | // next unit to update | 
| mjr | 87:8d35c74403af | 437 | int nextUpdate; | 
| mjr | 87:8d35c74403af | 438 | |
| mjr | 87:8d35c74403af | 439 | // read 8-bit register; returns the value read on success, -1 on failure | 
| mjr | 87:8d35c74403af | 440 | int readReg8(int addr, uint16_t registerAddr) | 
| mjr | 87:8d35c74403af | 441 | { | 
| mjr | 87:8d35c74403af | 442 | // write the request - register address + auto-inc mode | 
| mjr | 87:8d35c74403af | 443 | uint8_t data_write[1]; | 
| mjr | 87:8d35c74403af | 444 | data_write[0] = registerAddr | TLC59116R::CTL_AIALL; | 
| mjr | 87:8d35c74403af | 445 | if (i2c.write(addr << 1, data_write, 1, true)) | 
| mjr | 87:8d35c74403af | 446 | return -1; | 
| mjr | 87:8d35c74403af | 447 | |
| mjr | 87:8d35c74403af | 448 | // read the result | 
| mjr | 87:8d35c74403af | 449 | uint8_t data_read[1]; | 
| mjr | 87:8d35c74403af | 450 | if (i2c.read(addr << 1, data_read, 1)) | 
| mjr | 87:8d35c74403af | 451 | return -1; | 
| mjr | 87:8d35c74403af | 452 | |
| mjr | 87:8d35c74403af | 453 | // return the result | 
| mjr | 87:8d35c74403af | 454 | return data_read[0]; | 
| mjr | 87:8d35c74403af | 455 | } | 
| mjr | 87:8d35c74403af | 456 | |
| mjr | 87:8d35c74403af | 457 | // write 8-bit register; returns true on success, false on failure | 
| mjr | 87:8d35c74403af | 458 | bool writeReg8(int addr, uint16_t registerAddr, uint8_t data) | 
| mjr | 87:8d35c74403af | 459 | { | 
| mjr | 87:8d35c74403af | 460 | uint8_t data_write[2]; | 
| mjr | 87:8d35c74403af | 461 | data_write[0] = registerAddr | TLC59116R::CTL_AIALL; | 
| mjr | 87:8d35c74403af | 462 | data_write[1] = data; | 
| mjr | 87:8d35c74403af | 463 | return !i2c.write(addr << 1, data_write, 2); | 
| mjr | 87:8d35c74403af | 464 | } | 
| mjr | 87:8d35c74403af | 465 | |
| mjr | 87:8d35c74403af | 466 | // I2C bus interface | 
| mjr | 87:8d35c74403af | 467 | I2C_Type i2c; | 
| mjr | 87:8d35c74403af | 468 | |
| mjr | 87:8d35c74403af | 469 | // reset pin (active low) | 
| mjr | 87:8d35c74403af | 470 | DigitalOut reset; | 
| mjr | 87:8d35c74403af | 471 | }; | 
| mjr | 87:8d35c74403af | 472 | |
| mjr | 87:8d35c74403af | 473 | #endif | 
