Tiny-WS2812  1.0.0
A tiny cross-platform WS2812 LED Strip driver
Macros | Functions
ws2812_avr.c File Reference

Driver code for AVR chips. More...

#include <stdint.h>
#include <stdbool.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <util/delay.h>
#include <ws2812.h>
#include <Arduino.h>
Include dependency graph for ws2812_avr.c:

Go to the source code of this file.

Macros

#define w_zeropulse   350
 
#define w_onepulse   900
 
#define w_totalperiod   1250
 
#define w_fixedlow   3
 
#define w_fixedhigh   6
 
#define w_fixedtotal   10
 
#define w_zerocycles   (((F_CPU/1000)*w_zeropulse )/1000000)
 
#define w_onecycles   (((F_CPU/1000)*w_onepulse +500000)/1000000)
 
#define w_totalcycles   (((F_CPU/1000)*w_totalperiod +500000)/1000000)
 
#define w1   (w_zerocycles-w_fixedlow)
 
#define w2   (w_onecycles-w_fixedhigh-w1)
 
#define w3   (w_totalcycles-w_fixedtotal-w1-w2)
 
#define w1_nops   0
 
#define w_lowtime   ((w1_nops+w_fixedlow)*1000000)/(F_CPU/1000)
 
#define w2_nops   0
 
#define w3_nops   0
 
#define w_nop1   "nop \n\t"
 
#define w_nop2   "rjmp .+0 \n\t"
 
#define w_nop4   w_nop2 w_nop2
 
#define w_nop8   w_nop4 w_nop4
 
#define w_nop16   w_nop8 w_nop8
 

Functions

void delay_us (uint8_t us)
 Halts the program for a given ammount of microseconds. More...
 
uint8_t ws2812_config (ws2812 *dev, ws2812_cfg *cfg)
 Configures a WS2812 device struct. More...
 
void ws2812_prep_tx (ws2812 *dev)
 Prepares the host device for data transmission. More...
 
void ws2812_wait_rst (ws2812 *dev)
 Waits for the WS2812 device to reset. More...
 
void ws2812_tx_byte (ws2812 *dev, uint8_t byte)
 Transmits a byte of data to the WS2812 device. More...
 
void ws2812_tx (ws2812 *dev, ws2812_rgb *leds, size_t n_leds)
 Transmits RGB values to the provided WS2812 device. More...
 
void ws2812_close_tx (ws2812 *dev)
 Closes a WS2812 transmission. More...
 

Detailed Description

Driver code for AVR chips.

Author
Patrick Pedersen
Date
2021-04-09

The following file holds the Tiny-WS2812 library code to drive WS2812 devices on AVR chips.

Definition in file ws2812_avr.c.

Function Documentation

◆ delay_us()

void delay_us ( uint8_t  us)

Halts the program for a given ammount of microseconds.

The following function temporarily disables interrupts and pauses the program code for a provided ammount of microseconds (max 255).

Warning
This function relies on the _delay_us() function, which reserves the CPU from performing any other tasks.
Since the for loop of this function also requires time to be executed, the actual delay will always be slighly longer.

Definition at line 116 of file ws2812_avr.c.

117 {
118  cli();
119  for (uint8_t i = 0; i < us; i++)
120  _delay_us(1);
121  sei();
122 }

◆ ws2812_close_tx()

void ws2812_close_tx ( ws2812 dev)

Closes a WS2812 transmission.

The following function should be called after ending data transmission with a WS2812 device. The exact closing procedure is platform specific but will typically involve restoring stashed registers to their previous states, re-enable interrupts, wait for the WS2812 to reset by calling ws2812_wait_rst(), and potentially alter fields of the provided WS2812 device struct.

Definition at line 275 of file ws2812_avr.c.

276 {
277  if (_prep == true) {
278  SREG = _sreg_prev;
279  _prep = false;
280  ws2812_wait_rst(dev);
281  }
282 }
void ws2812_wait_rst(ws2812 *dev)
Waits for the WS2812 device to reset.
Definition: ws2812_avr.c:168

◆ ws2812_config()

uint8_t ws2812_config ( ws2812 dev,
ws2812_cfg cfg 
)

Configures a WS2812 device struct.

The following function initializes/configures a WS2812 device struct using a ws2812_cfg configuration struct.

Definition at line 126 of file ws2812_avr.c.

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 }
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
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
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

◆ ws2812_prep_tx()

void ws2812_prep_tx ( ws2812 dev)

Prepares the host device for data transmission.

The following function prepares the host device for data transmission. The exact preperation procedure is platform specific, but typically will involve disabling interrupts, stashing registers that may be modified when communicating to the WS2812 device and potentially preparing the passed WS2812 device struct for data transmission. For a more detailed understanding, please refer to the platform specific code of this function.

Warning
Always call this function before making any calls to ws2812_tx()! Not doing so may result in undefined behaivour!

Definition at line 159 of file ws2812_avr.c.

160 {
161  if (_prep == false) {
162  _sreg_prev = SREG;
163  _prep = true;
164  }
165 }

◆ ws2812_tx()

void ws2812_tx ( ws2812 dev,
ws2812_rgb pxls,
size_t  n_pxls 
)

Transmits RGB values to the provided WS2812 device.

The following function transmits RGB values to the provided WS2812 device. Calling this function consecutively for the same device will continue programming LEDs after the position where the last transmission has ended. In other words, calling the function consecutively for the same device will NOT program the device from the first LED, but rather take off from where it last ended. If this is not desired, call ws2812_wait_rst() after each transmission.

Definition at line 264 of file ws2812_avr.c.

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 }
void ws2812_tx_byte(ws2812 *dev, uint8_t byte)
Transmits a byte of data to the WS2812 device.
Definition: ws2812_avr.c:192

◆ ws2812_tx_byte()

void ws2812_tx_byte ( ws2812 dev,
uint8_t  byte 
)

Transmits a byte of data to the WS2812 device.

The following function transmits a single byte of data to the provided WS2812 device. It is primarily based on the driver code of cpldcpu's light_ws2812 library and achieves precisely timed communication with the WS2812 device through inline AVR assembly code.

Note
This function disables interrupts for the duration of the transmission.

Definition at line 192 of file ws2812_avr.c.

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 }

◆ ws2812_wait_rst()

void ws2812_wait_rst ( ws2812 dev)

Waits for the WS2812 device to reset.

The following function waits for the WS2812 device to reset, thus allowing it to be overwritten from the first LED again. The reset time is configuredin the ws2812_cfg object used to initialize/configure the passed WS2812 device struct, and is recommended to be 50us according to the WS2812 datasheet, but may be set significantly lower for some WS2812 devices. Call this function if you wish to overwritte the WS2812 device after a previous ws2812_tx() call.

Note
This function does not have to be called prior calling #ws2812_end_tx() as it already contains a call to ws2812_wait_rst().

Definition at line 168 of file ws2812_avr.c.

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 }
void delay_us(uint8_t us)
Halts the program for a given ammount of microseconds.
Definition: ws2812_avr.c:116