ws2812_bitbang.c (5534B)
1 /* 2 * light weight WS2812 lib V2.0b 3 * 4 * Controls WS2811/WS2812/WS2812B RGB-LEDs 5 * Author: Tim (cpldcpu@gmail.com) 6 * 7 * Jan 18th, 2014 v2.0b Initial Version 8 * Nov 29th, 2015 v2.3 Added SK6812RGBW support 9 * 10 * This program is free software: you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation, either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program. If not, see <http://www.gnu.org/licenses/>. 22 */ 23 #include <avr/interrupt.h> 24 #include <avr/io.h> 25 #include <util/delay.h> 26 #include "ws2812.h" 27 #include "pin_defs.h" 28 29 #define pinmask(pin) (_BV((pin) & 0xF)) 30 31 /* 32 This routine writes an array of bytes with RGB values to the Dataout pin 33 using the fast 800kHz clockless WS2811/2812 protocol. 34 */ 35 36 // Fixed cycles used by the inner loop 37 #define w_fixedlow 2 38 #define w_fixedhigh 4 39 #define w_fixedtotal 8 40 41 // Insert NOPs to match the timing, if possible 42 #define w_zerocycles (((F_CPU / 1000) * WS2812_T0H) / 1000000) 43 #define w_onecycles (((F_CPU / 1000) * WS2812_T1H + 500000) / 1000000) 44 #define w_totalcycles (((F_CPU / 1000) * WS2812_TIMING + 500000) / 1000000) 45 46 // w1_nops - nops between rising edge and falling edge - low 47 #if w_zerocycles >= w_fixedlow 48 # define w1_nops (w_zerocycles - w_fixedlow) 49 #else 50 # define w1_nops 0 51 #endif 52 53 // w2_nops - nops between fe low and fe high 54 #if w_onecycles >= (w_fixedhigh + w1_nops) 55 # define w2_nops (w_onecycles - w_fixedhigh - w1_nops) 56 #else 57 # define w2_nops 0 58 #endif 59 60 // w3_nops - nops to complete loop 61 #if w_totalcycles >= (w_fixedtotal + w1_nops + w2_nops) 62 # define w3_nops (w_totalcycles - w_fixedtotal - w1_nops - w2_nops) 63 #else 64 # define w3_nops 0 65 #endif 66 67 // The only critical timing parameter is the minimum pulse length of the "0" 68 // Warn or throw error if this timing can not be met with current F_CPU settings. 69 #define w_lowtime ((w1_nops + w_fixedlow) * 1000000) / (F_CPU / 1000) 70 #if w_lowtime > 550 71 # error "Light_ws2812: Sorry, the clock speed is too low. Did you set F_CPU correctly?" 72 #elif w_lowtime > 450 73 # warning "Light_ws2812: The timing is critical and may only work on WS2812B, not on WS2812(S)." 74 # warning "Please consider a higher clockspeed, if possible" 75 #endif 76 77 #define w_nop1 "nop \n\t" 78 #define w_nop2 "rjmp .+0 \n\t" 79 #define w_nop4 w_nop2 w_nop2 80 #define w_nop8 w_nop4 w_nop4 81 #define w_nop16 w_nop8 w_nop8 82 83 static inline void ws2812_sendarray_mask(uint8_t *data, uint16_t datlen, uint8_t masklo, uint8_t maskhi) { 84 uint8_t curbyte, ctr, sreg_prev; 85 86 sreg_prev = SREG; 87 cli(); 88 89 while (datlen--) { 90 curbyte = (*data++); 91 92 asm volatile(" ldi %0,8 \n\t" 93 "loop%=: \n\t" 94 " out %2,%3 \n\t" // '1' [01] '0' [01] - re 95 #if (w1_nops & 1) 96 w_nop1 97 #endif 98 #if (w1_nops & 2) 99 w_nop2 100 #endif 101 #if (w1_nops & 4) 102 w_nop4 103 #endif 104 #if (w1_nops & 8) 105 w_nop8 106 #endif 107 #if (w1_nops & 16) 108 w_nop16 109 #endif 110 " sbrs %1,7 \n\t" // '1' [03] '0' [02] 111 " out %2,%4 \n\t" // '1' [--] '0' [03] - fe-low 112 " lsl %1 \n\t" // '1' [04] '0' [04] 113 #if (w2_nops & 1) 114 w_nop1 115 #endif 116 #if (w2_nops & 2) 117 w_nop2 118 #endif 119 #if (w2_nops & 4) 120 w_nop4 121 #endif 122 #if (w2_nops & 8) 123 w_nop8 124 #endif 125 #if (w2_nops & 16) 126 w_nop16 127 #endif 128 " out %2,%4 \n\t" // '1' [+1] '0' [+1] - fe-high 129 #if (w3_nops & 1) 130 w_nop1 131 #endif 132 #if (w3_nops & 2) 133 w_nop2 134 #endif 135 #if (w3_nops & 4) 136 w_nop4 137 #endif 138 #if (w3_nops & 8) 139 w_nop8 140 #endif 141 #if (w3_nops & 16) 142 w_nop16 143 #endif 144 145 " dec %0 \n\t" // '1' [+2] '0' [+2] 146 " brne loop%=\n\t" // '1' [+3] '0' [+4] 147 : "=&d"(ctr) 148 : "r"(curbyte), "I"(_SFR_IO_ADDR(PORTx_ADDRESS(WS2812_DI_PIN))), "r"(maskhi), "r"(masklo)); 149 } 150 151 SREG = sreg_prev; 152 } 153 154 ws2812_led_t ws2812_leds[WS2812_LED_COUNT]; 155 156 void ws2812_init(void) { 157 DDRx_ADDRESS(WS2812_DI_PIN) |= pinmask(WS2812_DI_PIN); 158 } 159 160 void ws2812_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) { 161 ws2812_leds[index].r = red; 162 ws2812_leds[index].g = green; 163 ws2812_leds[index].b = blue; 164 #if defined(WS2812_RGBW) 165 ws2812_rgb_to_rgbw(&ws2812_leds[index]); 166 #endif 167 } 168 169 void ws2812_set_color_all(uint8_t red, uint8_t green, uint8_t blue) { 170 for (int i = 0; i < WS2812_LED_COUNT; i++) { 171 ws2812_set_color(i, red, green, blue); 172 } 173 } 174 175 void ws2812_flush(void) { 176 uint8_t masklo = ~(pinmask(WS2812_DI_PIN))&PORTx_ADDRESS(WS2812_DI_PIN); 177 uint8_t maskhi = pinmask(WS2812_DI_PIN) | PORTx_ADDRESS(WS2812_DI_PIN); 178 179 ws2812_sendarray_mask((uint8_t *)ws2812_leds, WS2812_LED_COUNT * sizeof(ws2812_led_t), masklo, maskhi); 180 181 _delay_us(WS2812_TRST_US); 182 }