apa102.c (5885B)
1 /* Copyright 2020 Aldehir Rojas 2 * Copyright 2017 Mikkel (Duckle29) 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "apa102.h" 19 #include "gpio.h" 20 21 #ifndef APA102_NOPS 22 # if defined(__AVR__) 23 # define APA102_NOPS 0 // AVR at 16 MHz already spends 62.5 ns per clock, so no extra delay is needed 24 # elif defined(PROTOCOL_CHIBIOS) 25 # include "hal.h" 26 # include "chibios_config.h" 27 # if defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(AT32F415) || defined(GD32VF103) || defined(MCU_RP) 28 # define APA102_NOPS (100 / (1000000000L / (CPU_CLOCK / 4))) // This calculates how many loops of 4 nops to run to delay 100 ns 29 # else 30 # error APA102_NOPS configuration required 31 # define APA102_NOPS 0 // this just pleases the compile so the above error is easier to spot 32 # endif 33 # endif 34 #endif 35 36 #define io_wait \ 37 do { \ 38 for (int i = 0; i < APA102_NOPS; i++) { \ 39 __asm__ volatile("nop\n\t" \ 40 "nop\n\t" \ 41 "nop\n\t" \ 42 "nop\n\t"); \ 43 } \ 44 } while (0) 45 46 #define APA102_SEND_BIT(byte, bit) \ 47 do { \ 48 gpio_write_pin(APA102_DI_PIN, (byte >> bit) & 1); \ 49 io_wait; \ 50 gpio_write_pin_high(APA102_CI_PIN); \ 51 io_wait; \ 52 gpio_write_pin_low(APA102_CI_PIN); \ 53 io_wait; \ 54 } while (0) 55 56 rgb_t apa102_leds[APA102_LED_COUNT]; 57 uint8_t apa102_led_brightness = APA102_DEFAULT_BRIGHTNESS; 58 59 static void apa102_send_byte(uint8_t byte) { 60 APA102_SEND_BIT(byte, 7); 61 APA102_SEND_BIT(byte, 6); 62 APA102_SEND_BIT(byte, 5); 63 APA102_SEND_BIT(byte, 4); 64 APA102_SEND_BIT(byte, 3); 65 APA102_SEND_BIT(byte, 2); 66 APA102_SEND_BIT(byte, 1); 67 APA102_SEND_BIT(byte, 0); 68 } 69 70 static void apa102_start_frame(void) { 71 gpio_write_pin_low(APA102_DI_PIN); 72 gpio_write_pin_low(APA102_CI_PIN); 73 74 for (uint16_t i = 0; i < 4; i++) { 75 apa102_send_byte(0); 76 } 77 } 78 79 static void apa102_end_frame(uint16_t num_leds) { 80 // This function has been taken from: https://github.com/pololu/apa102-arduino/blob/master/APA102.h 81 // and adapted. The code is MIT licensed. I think thats compatible? 82 // 83 // The data stream seen by the last LED in the chain will be delayed by 84 // (count - 1) clock edges, because each LED before it inverts the clock 85 // line and delays the data by one clock edge. Therefore, to make sure 86 // the last LED actually receives the data we wrote, the number of extra 87 // edges we send at the end of the frame must be at least (count - 1). 88 // 89 // Assuming we only want to send these edges in groups of size K, the 90 // C/C++ expression for the minimum number of groups to send is: 91 // 92 // ((count - 1) + (K - 1)) / K 93 // 94 // The C/C++ expression above is just (count - 1) divided by K, 95 // rounded up to the nearest whole number if there is a remainder. 96 // 97 // We set K to 16 and use the formula above as the number of frame-end 98 // bytes to transfer. Each byte has 16 clock edges. 99 // 100 // We are ignoring the specification for the end frame in the APA102 101 // datasheet, which says to send 0xFF four times, because it does not work 102 // when you have 66 LEDs or more, and also it results in unwanted white 103 // pixels if you try to update fewer LEDs than are on your LED strip. 104 uint16_t iterations = (num_leds + 14) / 16; 105 for (uint16_t i = 0; i < iterations; i++) { 106 apa102_send_byte(0); 107 } 108 109 gpio_write_pin_low(APA102_DI_PIN); 110 gpio_write_pin_low(APA102_CI_PIN); 111 } 112 113 static void apa102_send_frame(uint8_t red, uint8_t green, uint8_t blue, uint8_t brightness) { 114 apa102_send_byte(0b11100000 | brightness); 115 apa102_send_byte(blue); 116 apa102_send_byte(green); 117 apa102_send_byte(red); 118 } 119 120 void apa102_init(void) { 121 gpio_set_pin_output(APA102_DI_PIN); 122 gpio_set_pin_output(APA102_CI_PIN); 123 } 124 125 void apa102_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) { 126 apa102_leds[index].r = red; 127 apa102_leds[index].g = green; 128 apa102_leds[index].b = blue; 129 } 130 131 void apa102_set_color_all(uint8_t red, uint8_t green, uint8_t blue) { 132 for (uint16_t i = 0; i < APA102_LED_COUNT; i++) { 133 apa102_set_color(i, red, green, blue); 134 } 135 } 136 137 void apa102_flush(void) { 138 apa102_start_frame(); 139 for (uint8_t i = 0; i < APA102_LED_COUNT; i++) { 140 apa102_send_frame(apa102_leds[i].r, apa102_leds[i].g, apa102_leds[i].b, apa102_led_brightness); 141 } 142 apa102_end_frame(APA102_LED_COUNT); 143 } 144 145 void apa102_set_brightness(uint8_t brightness) { 146 if (brightness > APA102_MAX_BRIGHTNESS) { 147 apa102_led_brightness = APA102_MAX_BRIGHTNESS; 148 } else if (brightness < 0) { 149 apa102_led_brightness = 0; 150 } else { 151 apa102_led_brightness = brightness; 152 } 153 }