Tiny-WS2812  1.0.0
A tiny cross-platform WS2812 LED Strip driver
ws2812_avr.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2021 Patrick Pedersen
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 3 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 <https://www.gnu.org/licenses/>.
16  *
17  */
18 
30 #if defined(WS2812_TARGET_PLATFORM_AVR) || defined(WS2812_TARGET_PLATFORM_ARDUINO_AVR)
31 
32 #include <stdint.h>
33 #include <stdbool.h>
34 
35 #include <avr/interrupt.h>
36 #include <avr/io.h>
37 #include <util/delay.h>
38 
39 #include <ws2812.h>
40 
41 #ifdef WS2812_TARGET_PLATFORM_ARDUINO_AVR
42 #include <Arduino.h>
43 #endif
44 
45 // Timing in ns
46 #define w_zeropulse 350
47 #define w_onepulse 900
48 #define w_totalperiod 1250
49 
50 // Fixed cycles used by the inner loop
51 #define w_fixedlow 3
52 #define w_fixedhigh 6
53 #define w_fixedtotal 10
54 
55 // Insert NOPs to match the timing, if possible
56 #define w_zerocycles (((F_CPU/1000)*w_zeropulse )/1000000)
57 #define w_onecycles (((F_CPU/1000)*w_onepulse +500000)/1000000)
58 #define w_totalcycles (((F_CPU/1000)*w_totalperiod +500000)/1000000)
59 
60 // w1 - nops between rising edge and falling edge - low
61 #define w1 (w_zerocycles-w_fixedlow)
62 // w2 nops between fe low and fe high
63 #define w2 (w_onecycles-w_fixedhigh-w1)
64 // w3 nops to complete loop
65 #define w3 (w_totalcycles-w_fixedtotal-w1-w2)
66 
67 #if w1>0
68  #define w1_nops w1
69 #else
70  #define w1_nops 0
71 #endif
72 
73 // The only critical timing parameter is the minimum pulse length of the "0"
74 // Warn or throw error if this timing can not be met with current F_CPU settings.
75 #define w_lowtime ((w1_nops+w_fixedlow)*1000000)/(F_CPU/1000)
76 #if w_lowtime>550
77  #error "Light_ws2812: Sorry, the clock speed is too low. Did you set F_CPU correctly?"
78 #elif w_lowtime>450
79  #warning "Light_ws2812: The timing is critical and may only work on WS2812B, not on WS2812(S)."
80  #warning "Please consider a higher clockspeed, if possible"
81 #endif
82 
83 #if w2>0
84 #define w2_nops w2
85 #else
86 #define w2_nops 0
87 #endif
88 
89 #if w3>0
90 #define w3_nops w3
91 #else
92 #define w3_nops 0
93 #endif
94 
95 #define w_nop1 "nop \n\t"
96 #define w_nop2 "rjmp .+0 \n\t"
97 #define w_nop4 w_nop2 w_nop2
98 #define w_nop8 w_nop4 w_nop4
99 #define w_nop16 w_nop8 w_nop8
100 
101 static uint8_t _sreg_prev;
102 static bool _prep = false;
103 
104 #ifdef WS2812_TARGET_PLATFORM_AVR
116 void delay_us(uint8_t us)
117 {
118  cli();
119  for (uint8_t i = 0; i < us; i++)
120  _delay_us(1);
121  sei();
122 }
123 #endif
124 
125 // Refer to header for documentation
126 uint8_t ws2812_config(ws2812 *dev, ws2812_cfg *cfg)
127 {
128  if (cfg->n_dev == 0)
129  return 1; // No devices to be driven!
130 
131  uint8_t pin_msk = 0;
132 
133 #ifdef WS2812_TARGET_PLATFORM_ARDUINO_AVR
134  for (uint8_t i = 0; i < cfg->n_dev; i++) {
135  if (i+1 < cfg->n_dev &&
136  digitalPinToPort(cfg->pins[i]) != digitalPinToPort(cfg->pins[i+1]))
137  return 2; // Pins do not share same port!
138  pinMode(cfg->pins[i], OUTPUT);
139  pin_msk |= digitalPinToBitMask(cfg->pins[i]);
140  }
141  dev->port = portOutputRegister(digitalPinToPort(cfg->pins[0]));
142 #else
143  for (uint8_t i = 0; i < cfg->n_dev; i++)
144  pin_msk |= 1 << cfg->pins[i];
145 
146  *cfg->ddr = pin_msk;
147  dev->port = cfg->port;
148 #endif
149  dev->rst_time_us = cfg->rst_time_us;
150  dev->masklo = ~pin_msk & *(dev->port);
151  dev->maskhi = pin_msk | *(dev->port);
152 
153  _ws2812_get_rgbmap(&dev->rgbmap, cfg->order);
154 
155  return 0;
156 }
157 
158 // Refer to header for documentation
160 {
161  if (_prep == false) {
162  _sreg_prev = SREG;
163  _prep = true;
164  }
165 }
166 
167 // Refer to header for documentation
169 {
170 #ifdef WS2812_TARGET_PLATFORM_ARDUINO_AVR
171  delayMicroseconds(dev->rst_time_us);
172 #else
173  delay_us(dev->rst_time_us);
174 #endif
175 }
176 
177 // Disable optimizations as they may interfere with timing
178 #pragma GCC push_options
179 #pragma GCC optimize ("O0")
180 
192 void ws2812_tx_byte(ws2812 *dev, uint8_t byte)
193 {
194  cli();
195  uint8_t ctr;
196 
197  asm volatile(
198  " ldi %0,8 \n\t"
199  "loop%=: \n\t"
200  " st X,%3 \n\t" // '1' [02] '0' [02] - re
201  #if (w1_nops&1)
202  w_nop1
203  #endif
204  #if (w1_nops&2)
205  w_nop2
206  #endif
207  #if (w1_nops&4)
208  w_nop4
209  #endif
210  #if (w1_nops&8)
211  w_nop8
212  #endif
213  #if (w1_nops&16)
214  w_nop16
215  #endif
216  " sbrs %1,7 \n\t" // '1' [04] '0' [03]
217  " st X,%4 \n\t" // '1' [--] '0' [05] - fe-low
218  " lsl %1 \n\t" // '1' [05] '0' [06]
219  #if (w2_nops&1)
220  w_nop1
221  #endif
222  #if (w2_nops&2)
223  w_nop2
224  #endif
225  #if (w2_nops&4)
226  w_nop4
227  #endif
228  #if (w2_nops&8)
229  w_nop8
230  #endif
231  #if (w2_nops&16)
232  w_nop16
233  #endif
234  " brcc skipone%= \n\t" // '1' [+1] '0' [+2] -
235  " st X,%4 \n\t" // '1' [+3] '0' [--] - fe-high
236  "skipone%=: " // '1' [+3] '0' [+2] -
237 
238  #if (w3_nops&1)
239  w_nop1
240  #endif
241  #if (w3_nops&2)
242  w_nop2
243  #endif
244  #if (w3_nops&4)
245  w_nop4
246  #endif
247  #if (w3_nops&8)
248  w_nop8
249  #endif
250  #if (w3_nops&16)
251  w_nop16
252  #endif
253  " dec %0 \n\t" // '1' [+4] '0' [+3]
254  " brne loop%=\n\t" // '1' [+5] '0' [+4]
255  : "=&d" (ctr)
256  : "r" (byte), "x" ((uint8_t *) dev->port), "r" (dev->maskhi), "r" (dev->masklo)
257  );
258  sei();
259 }
260 
261 #pragma GCC pop_options
262 
263 // Refer to header for documentation
264 void ws2812_tx(ws2812 *dev, ws2812_rgb *leds, size_t n_leds)
265 {
266  for (size_t i = 0; i < n_leds; i++) {
267  for (uint8_t j = 0; j < sizeof(dev->rgbmap); j++) {
268  uint8_t *pxl = (uint8_t *) &(leds[i]);
269  ws2812_tx_byte(dev, pxl[dev->rgbmap[j]]);
270  }
271  }
272 }
273 
274 // Refer to header for documentation
276 {
277  if (_prep == true) {
278  SREG = _sreg_prev;
279  _prep = false;
280  ws2812_wait_rst(dev);
281  }
282 }
283 
284 #endif
ALL PLATFORMS: Data structure to configure a WS2812 device struct.
Definition: ws2812_avr.h:64
volatile uint8_t * ddr
Data Direction Register (ex. DDRB, DDRC, DDRD...)
Definition: ws2812_avr.h:68
uint8_t n_dev
Number of WS2812 device to drive.
Definition: ws2812_avr.h:78
volatile uint8_t * port
PORT Register (ex. PORTB, PORTC, PORTD...)
Definition: ws2812_avr.h:67
uint8_t rst_time_us
Time required for the WS2812 device(s) to reset in us.
Definition: ws2812_avr.h:76
ws2812_order order
CoColor order of the WS2812 device(s) (ex. rgb, grb, bgr...)
Definition: ws2812_avr.h:77
uint8_t * pins
Array of pins used to program WS2812 devices (Must share the same PORT! (ex. PB0, PB1,...
Definition: ws2812_avr.h:69
Data structure to hold RGB color values.
Definition: ws2812_common.h:67
ALL PLATFORMS: WS2812 device struct to drive one or more WS2812 devices.
Definition: ws2812_avr.h:107
uint8_t rgbmap[3]
RGB map to map/convert RGB values to another color order.
Definition: ws2812_avr.h:112
uint8_t rst_time_us
Time required for WS2812 to reset in us.
Definition: ws2812_avr.h:109
uint8_t masklo
PORT masks to toggle the data pins low.
Definition: ws2812_avr.h:111
uint8_t maskhi
PORT masks to toggle the data pins high.
Definition: ws2812_avr.h:110
volatile uint8_t * port
PORT register of pins used to drive the WS2812 device(s)
Definition: ws2812_avr.h:108
Exposes the Tiny-WS2812 library interface.
void ws2812_close_tx(ws2812 *dev)
Closes a WS2812 transmission.
Definition: ws2812_avr.c:275
void ws2812_wait_rst(ws2812 *dev)
Waits for the WS2812 device to reset.
Definition: ws2812_avr.c:168
uint8_t ws2812_config(ws2812 *dev, ws2812_cfg *cfg)
Configures a WS2812 device struct.
Definition: ws2812_avr.c:126
void delay_us(uint8_t us)
Halts the program for a given ammount of microseconds.
Definition: ws2812_avr.c:116
void ws2812_prep_tx(ws2812 *dev)
Prepares the host device for data transmission.
Definition: ws2812_avr.c:159
void ws2812_tx(ws2812 *dev, ws2812_rgb *leds, size_t n_leds)
Transmits RGB values to the provided WS2812 device.
Definition: ws2812_avr.c:264
void ws2812_tx_byte(ws2812 *dev, uint8_t byte)
Transmits a byte of data to the WS2812 device.
Definition: ws2812_avr.c:192
void _ws2812_get_rgbmap(uint8_t(*rgbmap)[3], ws2812_order order)
Initializes a rgb map for a given color order.
Definition: ws2812_common.c:43