Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
TLC59116/TLC59116.h@116:7a67265d7c19, 2021-10-01 (annotated)
- Committer:
- arnoz
- Date:
- Fri Oct 01 08:19:46 2021 +0000
- Revision:
- 116:7a67265d7c19
- Parent:
- 87:8d35c74403af
- Correct information regarding your last merge
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 |