libDaisy
Hardware Library for Daisy
Loading...
Searching...
No Matches
leddriver.h
Go to the documentation of this file.
1#pragma once
2#ifndef SA_LED_DRIVER_H
3#define SA_LED_DRIVER_H
5#ifdef __cplusplus
6
7#include <stdint.h>
8#include "per/i2c.h"
9#include "per/gpio.h"
10
11namespace daisy
12{
31template <int numDrivers, bool persistentBufferContents = true>
33{
34 public:
36 struct __attribute__((packed)) PCA9685TransmitBuffer
37 {
39 uint8_t registerAddr = PCA9685_LED0;
40 struct __attribute__((packed))
41 {
43 uint16_t on;
45 uint16_t off;
46 } leds[16];
48 static constexpr uint16_t size = 16 * 4 + 1;
49 };
51 using DmaBuffer = PCA9685TransmitBuffer[numDrivers];
52
65 void Init(I2CHandle i2c,
66 const uint8_t (&addresses)[numDrivers],
67 DmaBuffer dma_buffer_a,
68 DmaBuffer dma_buffer_b,
69 Pin oe_pin = Pin(PORTX, 0))
70 {
71 i2c_ = i2c;
72 draw_buffer_ = dma_buffer_a;
73 transmit_buffer_ = dma_buffer_b;
74 oe_pin_ = oe_pin;
75 for(int d = 0; d < numDrivers; d++)
76 addresses_[d] = addresses[d];
77 current_driver_idx_ = -1;
78
79 InitializeBuffers();
80 InitializeDrivers();
81 }
82
84 constexpr int GetNumLeds() const { return numDrivers * 16; }
85
87 void SetAllTo(float brightness)
88 {
89 const uint8_t intBrightness
90 = (uint8_t)(clamp(brightness * 255.0f, 0.0f, 255.0f));
91 SetAllTo(intBrightness);
92 }
93
95 void SetAllTo(uint8_t brightness)
96 {
97 const uint16_t cycles = gamma_table_[brightness];
98 SetAllToRaw(cycles);
99 }
100
102 void SetAllToRaw(uint16_t rawBrightness)
103 {
104 for(int led = 0; led < GetNumLeds(); led++)
105 SetLedRaw(led, rawBrightness);
106 }
107
109 void SetLed(int ledIndex, float brightness)
110 {
111 const uint8_t intBrightness
112 = (uint8_t)(clamp(brightness * 255.0f, 0.0f, 255.0f));
113 SetLed(ledIndex, intBrightness);
114 }
115
117 void SetLed(int ledIndex, uint8_t brightness)
118 {
119 const uint16_t cycles = gamma_table_[brightness];
120 SetLedRaw(ledIndex, cycles);
121 }
122
124 void SetLedRaw(int ledIndex, uint16_t rawBrightness)
125 {
126 const auto d = GetDriverForLed(ledIndex);
127 const auto ch = GetDriverChannelForLed(ledIndex);
128 // mask away the "full on" bit
129 const auto on = draw_buffer_[d].leds[ch].on & (0x0FFF);
130 draw_buffer_[d].leds[ch].off = (on + rawBrightness) & (0x0FFF);
131 // full on condition
132 if(rawBrightness >= 0x0FFF)
133 draw_buffer_[d].leds[ch].on = 0x1000 | on; // set "full on" bit
134 else
135 draw_buffer_[d].leds[ch].on = on; // clear "full on" bit
136 }
137
142 {
143 // wait for current transmission to complete
144 while(current_driver_idx_ >= 0) {};
145
146 // swap buffers
147 auto tmp = transmit_buffer_;
148 transmit_buffer_ = draw_buffer_;
149 draw_buffer_ = tmp;
150
151 // copy current transmit buffer contents to the new draw buffer
152 // to keep the led settings (if required)
153 if(persistentBufferContents)
154 {
155 for(int d = 0; d < numDrivers; d++)
156 for(int ch = 0; ch < 16; ch++)
157 draw_buffer_[d].leds[ch].off
158 = transmit_buffer_[d].leds[ch].off;
159 }
160
161 // start transmission
162 current_driver_idx_ = -1;
163 ContinueTransmission();
164 }
165
166 private:
167 void ContinueTransmission()
168 {
169 current_driver_idx_ = current_driver_idx_ + 1;
170 if(current_driver_idx_ >= numDrivers)
171 {
172 current_driver_idx_ = -1;
173 return;
174 }
175
176 const auto d = current_driver_idx_;
177 const uint8_t address = PCA9685_I2C_BASE_ADDRESS | addresses_[d];
178 const auto status = i2c_.TransmitDma(address,
179 (uint8_t*)&transmit_buffer_[d],
180 PCA9685TransmitBuffer::size,
181 &TxCpltCallback,
182 this);
183 if(status != I2CHandle::Result::OK)
184 {
185 // TODO: fix this :-)
186 // Reinit I2C (probably a flag to kill, but hey this works fairly well for now.)
187 i2c_.Init(i2c_.GetConfig());
188 }
189 }
190 uint16_t GetStartCycleForLed(int ledIndex) const
191 {
192 return (ledIndex << 2) & 0x0FFF; // shift each led by 4 cycles
193 }
194
195 uint8_t GetDriverForLed(int ledIndex) const { return ledIndex >> 4; }
196
197 uint8_t GetDriverChannelForLed(int ledIndex) const
198 {
199 return ledIndex & 0x0F;
200 }
201
202 void InitializeBuffers()
203 {
204 for(int led = 0; led < GetNumLeds(); led++)
205 {
206 const auto d = GetDriverForLed(led);
207 const auto ch = GetDriverChannelForLed(led);
208 const auto startCycle = GetStartCycleForLed(led);
209 draw_buffer_[d].registerAddr = PCA9685_LED0;
210 draw_buffer_[d].leds[ch].on = startCycle;
211 draw_buffer_[d].leds[ch].off = startCycle;
212 transmit_buffer_[d].registerAddr = PCA9685_LED0;
213 transmit_buffer_[d].leds[ch].on = startCycle;
214 transmit_buffer_[d].leds[ch].off = startCycle;
215 }
216 }
217
218 void InitializeDrivers()
219 {
220 // init OE pin and pull low to enable outputs
221 if(oe_pin_.port != PORTX)
222 {
223 oe_pin_gpio_.Init(oe_pin_, GPIO::Mode::OUTPUT);
224 oe_pin_gpio_.Write(0);
225 }
226
227 // init the individual drivers
228 for(int d = 0; d < numDrivers; d++)
229 {
230 const uint8_t address = PCA9685_I2C_BASE_ADDRESS | addresses_[d];
231 uint8_t buffer[2];
232 buffer[0] = PCA9685_MODE1;
233 buffer[1] = 0x00;
234 i2c_.TransmitBlocking(address, buffer, 2, 1);
235 System::Delay(20);
236 buffer[0] = PCA9685_MODE1;
237 buffer[1] = 0x00;
238 i2c_.TransmitBlocking(address, buffer, 2, 1);
239 System::Delay(20);
240 buffer[0] = PCA9685_MODE1;
241 // auto increment on
242 buffer[1] = 0b00100000;
243 i2c_.TransmitBlocking(address, buffer, 2, 1);
244 System::Delay(20);
245 buffer[0] = PCA9685_MODE2;
246 // OE-high = high Impedance
247 // Push-Pull outputs
248 // outputs change on STOP
249 // outputs inverted
250 buffer[1] = 0b000110110;
251 i2c_.TransmitBlocking(address, buffer, 2, 5);
252 }
253 }
254
255 // no std::clamp available in C++14.... remove this when C++17 is available
256 template <typename T>
257 T clamp(T in, T low, T high)
258 {
259 return (in < low) ? low : (high < in) ? high : in;
260 }
261
262 // an internal function to handle i2c callbacks
263 // called when an I2C transmission completes and the next driver must be updated
264 static void TxCpltCallback(void* context, I2CHandle::Result result)
265 {
266 auto drv_ptr = reinterpret_cast<
267 LedDriverPca9685<numDrivers, persistentBufferContents>*>(context);
268 drv_ptr->ContinueTransmission();
269 }
270
271 I2CHandle i2c_;
272 PCA9685TransmitBuffer* draw_buffer_;
273 PCA9685TransmitBuffer* transmit_buffer_;
274 uint8_t addresses_[numDrivers];
275 Pin oe_pin_;
276 GPIO oe_pin_gpio_;
277 // index of the dirver that is currently updated.
278 volatile int8_t current_driver_idx_;
279 const uint16_t gamma_table_[256] = {
280 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
281 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 5,
282 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 15, 16,
283 17, 18, 20, 21, 23, 25, 26, 28, 30, 32, 34, 36,
284 38, 40, 43, 45, 48, 50, 53, 56, 59, 62, 65, 68,
285 71, 75, 78, 82, 85, 89, 93, 97, 101, 105, 110, 114,
286 119, 123, 128, 133, 138, 143, 149, 154, 159, 165, 171, 177,
287 183, 189, 195, 202, 208, 215, 222, 229, 236, 243, 250, 258,
288 266, 273, 281, 290, 298, 306, 315, 324, 332, 341, 351, 360,
289 369, 379, 389, 399, 409, 419, 430, 440, 451, 462, 473, 485,
290 496, 508, 520, 532, 544, 556, 569, 582, 594, 608, 621, 634,
291 648, 662, 676, 690, 704, 719, 734, 749, 764, 779, 795, 811,
292 827, 843, 859, 876, 893, 910, 927, 944, 962, 980, 998, 1016,
293 1034, 1053, 1072, 1091, 1110, 1130, 1150, 1170, 1190, 1210, 1231, 1252,
294 1273, 1294, 1316, 1338, 1360, 1382, 1404, 1427, 1450, 1473, 1497, 1520,
295 1544, 1568, 1593, 1617, 1642, 1667, 1693, 1718, 1744, 1770, 1797, 1823,
296 1850, 1877, 1905, 1932, 1960, 1988, 2017, 2045, 2074, 2103, 2133, 2162,
297 2192, 2223, 2253, 2284, 2315, 2346, 2378, 2410, 2442, 2474, 2507, 2540,
298 2573, 2606, 2640, 2674, 2708, 2743, 2778, 2813, 2849, 2884, 2920, 2957,
299 2993, 3030, 3067, 3105, 3143, 3181, 3219, 3258, 3297, 3336, 3376, 3416,
300 3456, 3496, 3537, 3578, 3619, 3661, 3703, 3745, 3788, 3831, 3874, 3918,
301 3962, 4006, 4050, 4095};
302
303 static constexpr uint8_t PCA9685_I2C_BASE_ADDRESS = 0b01000000;
304 static constexpr uint8_t PCA9685_MODE1
305 = 0x00; // location for Mode1 register address
306 static constexpr uint8_t PCA9685_MODE2
307 = 0x01; // location for Mode2 reigster address
308 static constexpr uint8_t PCA9685_LED0
309 = 0x06; // location for start of LED0 registers
310 static constexpr uint8_t PRE_SCALE_MODE
311 = 0xFE; //location for setting prescale (clock speed)
312};
313
314} // namespace daisy
315
316#endif
317#endif
void Write(bool state)
Changes the state of the GPIO hardware when configured as an OUTPUT.
void Init()
Initialize the GPIO using the internal Config struct.
Definition i2c.h:26
const Config & GetConfig() const
Result TransmitDma(uint16_t address, uint8_t *data, uint16_t size, CallbackFunctionPtr callback, void *callback_context)
Result Init(const Config &config)
Result
Definition i2c.h:72
Result TransmitBlocking(uint16_t address, uint8_t *data, uint16_t size, uint32_t timeout)
Definition leddriver.h:33
void SetLedRaw(int ledIndex, uint16_t rawBrightness)
Definition leddriver.h:124
void SetLed(int ledIndex, uint8_t brightness)
Definition leddriver.h:117
constexpr int GetNumLeds() const
Definition leddriver.h:84
void SetAllToRaw(uint16_t rawBrightness)
Definition leddriver.h:102
void Init(I2CHandle i2c, const uint8_t(&addresses)[numDrivers], DmaBuffer dma_buffer_a, DmaBuffer dma_buffer_b, Pin oe_pin=Pin(PORTX, 0))
Definition leddriver.h:65
struct __attribute__((packed)) PCA9685TransmitBuffer
Definition leddriver.h:36
void SetAllTo(uint8_t brightness)
Definition leddriver.h:95
void SetLed(int ledIndex, float brightness)
Definition leddriver.h:109
void SwapBuffersAndTransmit()
Definition leddriver.h:141
PCA9685TransmitBuffer[numDrivers] DmaBuffer
Definition leddriver.h:51
void SetAllTo(float brightness)
Definition leddriver.h:87
static void Delay(uint32_t delay_ms)
Pin
Definition max11300.h:37
Hardware defines and helpers for daisy field platform.
Definition index.h:2
@ PORTX
Definition daisy_core.h:188
representation of hardware port/pin combination
Definition daisy_core.h:193
GPIOPort port
Definition daisy_core.h:194