Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

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?

UserRevisionLine numberNew 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