| 49 | |
| 50 | protected: |
| 51 | void showPixels(PixelController<RGB_ORDER> &pixels) override { |
| 52 | mSPI.select(); |
| 53 | |
| 54 | // ---- Start frame: 64 bits of 0 ---- |
| 55 | // HD108 requires 8 bytes (64 bits) of zeros to initialize the strip |
| 56 | // Note: Some sources mention 128 bits (16 bytes), but 64 bits works reliably |
| 57 | for (int i = 0; i < 8; i++) mSPI.writeByte(0x00); |
| 58 | |
| 59 | while (pixels.has(1)) { |
| 60 | // Load and scale pixel data in OUTPUT order (respects RGB_ORDER template parameter) |
| 61 | fl::u8 c0_8, c1_8, c2_8; |
| 62 | pixels.loadAndScaleRGB(&c0_8, &c1_8, &c2_8); |
| 63 | |
| 64 | // Apply gamma correction (2.8) to convert 8-bit to 16-bit for HD108 |
| 65 | // This provides smooth perceptual brightness transitions across the full 65K range |
| 66 | // Note: Brightness scaling is applied by loadAndScaleRGB() before gamma correction |
| 67 | fl::u16 c0_16 = fl::gamma_2_8(c0_8); |
| 68 | fl::u16 c1_16 = fl::gamma_2_8(c1_8); |
| 69 | fl::u16 c2_16 = fl::gamma_2_8(c2_8); |
| 70 | |
| 71 | // Header bytes: HD108 per-channel gain control encoding |
| 72 | // f0: [1][RRRRR][GG] - marker bit, 5-bit R gain, 2 MSBs of G gain |
| 73 | // f1: [GGG][BBBBB] - 3 LSBs of G gain, 5-bit B gain |
| 74 | // Use maximum gain (31) for all channels to maximize precision |
| 75 | // Brightness control via 16-bit PWM values (already applied via loadAndScaleRGB) |
| 76 | constexpr fl::u8 r_gain = 31, g_gain = 31, b_gain = 31; |
| 77 | constexpr fl::u8 f0 = fl::u8(0x80 | ((r_gain & 0x1F) << 2) | ((g_gain >> 3) & 0x03)); |
| 78 | constexpr fl::u8 f1 = fl::u8(((g_gain & 0x07) << 5) | (b_gain & 0x1F)); |
| 79 | |
| 80 | // Transmit LED frame: 2 header bytes + 6 color bytes (16-bit, big-endian, in RGB_ORDER) |
| 81 | mSPI.writeByte(f0); |
| 82 | mSPI.writeByte(f1); |
| 83 | mSPI.writeByte(fl::u8(c0_16 >> 8)); mSPI.writeByte(fl::u8(c0_16 & 0xFF)); // Channel 0 MSB, LSB |
| 84 | mSPI.writeByte(fl::u8(c1_16 >> 8)); mSPI.writeByte(fl::u8(c1_16 & 0xFF)); // Channel 1 MSB, LSB |
| 85 | mSPI.writeByte(fl::u8(c2_16 >> 8)); mSPI.writeByte(fl::u8(c2_16 & 0xFF)); // Channel 2 MSB, LSB |
| 86 | |
| 87 | pixels.stepDithering(); |
| 88 | pixels.advanceData(); |
| 89 | } |
| 90 | |
| 91 | // ---- End frame: 0xFF bytes to latch data into LEDs ---- |
| 92 | // Formula: (num_leds / 2) + 4 provides sufficient clock pulses for 40 MHz operation |
| 93 | // This is more conservative than APA102's (num_leds + 15) / 16 formula |
| 94 | const int latch = pixels.size() / 2 + 4; |
| 95 | for (int i = 0; i < latch; i++) mSPI.writeByte(0xFF); |
| 96 | mSPI.endTransaction(); |
| 97 | } |
| 98 | }; |
nothing calls this directly
no test coverage detected