Skip to content

Commit f49a1af

Browse files
committed
Optimized for Arduino Library
1 parent 903ca08 commit f49a1af

10 files changed

Lines changed: 134 additions & 73 deletions

File tree

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ Library that provides the simple function ws2812Write(pin, color) to set the col
55
## Features
66
- No initalization of library or ws2812 LED required
77
- Simple api: `ws2812Write(uint8_t pin, uint32_t color_rgb)` Color 0xRRGGBB
8-
- Extended api: `ws2812Write(uint8_t pin, uint32_t color, ws2812_write_led_type_t led_type)` if you need other color orders
9-
- Minimal Footprint but time sensitive, works only for certain MCUs and clock frequencies
8+
- Extended api: `ws2812Write(uint8_t pin, uint32_t color, uint8_t led_count, ws2812_write_led_type_t led_type)` if you want to set color of more than one LED or need other color orders
9+
- Minimal Footprint but time sensitive, works only for certain MCUs (tested on ESP8266, ESP32 and ESP32S3) and clock frequencies
10+
11+
## Info
12+
- Blocks interrupts while pushing out data to LED (around 30us per LED)
13+
- Data latching pause is not generated by this library, so wait at least 50us between ws2812Write calls to same port (usually no need to do something here as you don't update the LED that often and the start of the function takes quite some time to calculate timing and resort color values). In extreme case add `delayMicroseconds(50);` manually to allow latching of data into LEDs.
14+
- With the third argument more than one LED can be set if you are using LED stripes, but all will be set to the same color.
1015

1116
## API
1217
```c
13-
void ws2812Write(uint8_t pin, uint32_t color_rgb, ws2812_write_led_type_t led_type = WS2812WRITE_GRB);
18+
void ws2812Write(uint8_t pin, uint32_t color_rgb, uint8_t led_count = 1, ws2812_write_led_type_t led_type = WS2812WRITE_GRB);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.pio
2+
.vscode/.browse.c_cpp.db*
3+
.vscode/c_cpp_properties.json
4+
.vscode/launch.json
5+
.vscode/ipch
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
BlockWS2812Blink.ino - Example sketch to blink a line of 8 WS2812 LEDs
3+
Created by Lutz Lisseck, December 2025.
4+
5+
Colors the WS2812 LEDs in pink for one second,
6+
then turns it green for one second in an endless loop.
7+
8+
LEDs should be a WS2812 or WS2812B type.
9+
10+
Add a fourth argument to ws2812Write() if your LED uses a
11+
different color order then GRB (as most WS2812 types do).
12+
13+
This example code is in the public domain.
14+
*/
15+
16+
#include <WS2812Write.h>
17+
#ifndef WS2812_LED_PIN // can be defined in platformio.ini
18+
#define WS2812_LED_PIN 6 // Pin where the WS2812 LED is connected
19+
#endif
20+
21+
#define LED_BLOCK_COUNT 8 // Number of WS2812 LEDs in the block
22+
23+
24+
// the setup function runs once when you press reset or power the board
25+
void setup() {
26+
// no need to initialize the ws2812 library or pin before :-)
27+
//Serial.begin(74880); // Start serial for debugging (optional)
28+
//Serial.println("Single WS2812 Blink Example");
29+
}
30+
31+
// the loop function runs over and over again forever
32+
void loop() {
33+
ws2812Write(WS2812_LED_PIN, 0xFF00FF, LED_BLOCK_COUNT); // turn the LEDs pink (RGB: 255, 0, 255)
34+
delay(1000); // wait for a second
35+
ws2812Write(WS2812_LED_PIN, 0x00FF00, LED_BLOCK_COUNT); // turn the LEDs green (RGB: 0, 255, 0)
36+
delay(1000); // wait for a second
37+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dependencies:
2+
idf: '>=5.1'

examples/SingleWS2812Blink/SingleWS2812Blink.ino

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
88
LED should be a WS2812 or WS2812B type.
99
10-
Add a third argument to ws2812Write() if your LED uses a
10+
Add a third argument if you want to set more than one LED - all will get the same color.
11+
Add a fourth argument to ws2812Write() if your LED uses a
1112
different color order then GRB (as most WS2812 types do).
1213
1314
This example code is in the public domain.
1415
*/
1516

1617
#include "WS2812Write.h"
17-
#ifndef WS2812_LED_PIN
18+
#ifndef WS2812_LED_PIN // can be defined in platformio.ini
1819
#define WS2812_LED_PIN 6 // Pin where the WS2812 LED is connected
1920
#endif
2021

keywords.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Syntax Coloring Map
2+
# Datatypes (KEYWORD1)
3+
4+
# Methods and Functions (KEYWORD2)
5+
ws2812Write KEYWORD2
6+
7+
# Instances (KEYWORD2)
8+
9+
# Constants (LITERAL1)

library.properties

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
name=Single_WS2812_Bitbang_Write
22
version=0.1.0
33
author=Lutz Lisseck (Hackerspace FFM e.V.)
4-
maintainer=Lutz Lisseck (Hackerspace FFM e.V.)
5-
sentence=Single WS2812 LED driver using bitbang (no MCU peripheral).
6-
paragraph=Minimal library to control a single WS2812 LED using bitbanged GPIO.
4+
maintainer=Lutz Lisseck <tutgit@ikmail.com>
5+
sentence=Single WS2812 LED driver using bitbang (no MCU peripheral) for ESP MCUs.
6+
paragraph=Minimal library that provides a ws2812Write(pin, color) to control single WS2812 LEDs using bitbanged GPIO without using any MCU peripheral like RMT, SPI, etc. Works on ESP8266 and ESP32.
7+
license=MIT
78
category=Display
89
url=https://github.com/hackffm/Single_WS2812_Bitbang_Write
9-
architectures=*
10+
architectures=espressif8266, espressif32

platformio.ini

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
; PlatformIO Project Configuration File
1+
; PlatformIO Project Configuration File, open in PlatformIO and select src_dir to run different examples
22
[platformio]
3+
lib_dir = .
34
src_dir = examples/SingleWS2812Blink
4-
;src_dir = examples/MultiWS2812Blink
5+
;src_dir = examples/BlockWS2812Blink
56

67
[env]
78
; Special pioarduino cores
89
;platform = https://github.com/pioarduino/platform-espressif32.git#develop
910
;platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
1011
platform = espressif32
1112
framework = arduino
12-
;board = esp32dev
13-
board = esp32-s3-devkitc-1
13+
board = esp32dev
14+
;board = esp32-s3-devkitc-1
1415
monitor_speed = 115200
1516

1617
build_flags =
@@ -28,6 +29,7 @@ upload_speed = 230400
2829
;upload_speed = 57600
2930
board_build.flash_mode = dout
3031
upload_resetmethod = nodemcu
32+
; F_CPU is 80 MHz by default, enable line below to set to 160 MHz
3133
;board_build.f_cpu = 160000000L
3234
build_flags = ${env.build_flags}
3335
-DWS2812_LED_PIN=13
@@ -36,7 +38,8 @@ build_flags = ${env.build_flags}
3638
platform = espressif32
3739
board = esp32dev
3840
monitor_filters = esp32_exception_decoder
39-
board_build.f_cpu = 80000000L
41+
; 80 / 160 / 240 MHz supported, default is 240 MHz
42+
;board_build.f_cpu = 16000000L
4043
build_flags = ${env.build_flags}
4144
-DWS2812_LED_PIN=33
4245

@@ -45,7 +48,7 @@ build_flags = ${env.build_flags}
4548
platform = espressif32
4649
board = esp32-s3-devkitc-1
4750
build_type = debug
48-
board_build.f_cpu = 80000000L
51+
board_build.f_cpu = 240000000L
4952
board_build.arduino.memory_type = qio_qspi
5053
board_build.flash_mode = qio
5154
board_build.psram_type = qio

lib/Single_WS2812_Bitbang_Write/WS2812Write.cpp renamed to src/WS2812Write.cpp

Lines changed: 53 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,9 @@
2121

2222
portMUX_TYPE mySpinlock = portMUX_INITIALIZER_UNLOCKED;
2323

24-
void IRAM_ATTR setColor(uint32_t color, uint8_t pin, uint8_t time1H, uint8_t time1L, uint8_t time0H, uint8_t time0L) {
25-
uint32_t bitmask = 0x800000;
26-
volatile uint8_t count = 24;
27-
uint32_t start;
24+
void IRAM_ATTR setColor(uint32_t color, uint8_t pin, uint8_t time1H, uint8_t time1L, uint8_t time0H, uint8_t time0L, uint8_t count) {
2825
uint32_t tc_reg = GPIO_OUT_W1TC_REG;
2926
uint32_t ts_reg = GPIO_OUT_W1TS_REG;
30-
uint32_t cycles;
3127

3228
if(pin > 32) {
3329
tc_reg = GPIO_OUT1_W1TC_REG;
@@ -37,20 +33,23 @@
3733
uint32_t reg_val = (1UL << (pin));
3834

3935
vPortEnterCritical(&mySpinlock); // Interrupts off on current core
40-
4136
while(count--) {
42-
cycles = (color & bitmask) ? time1H : time0H;
43-
WRITE_PERI_REG(ts_reg, reg_val);
44-
start = GET_CPU_CYCLES();
45-
while (GET_CPU_CYCLES() - start < cycles) { __asm__ __volatile__("nop"); }
46-
47-
cycles = (color & bitmask) ? time1L : time0L;
48-
bitmask = bitmask >> 1;
49-
WRITE_PERI_REG(tc_reg, reg_val);
50-
start = GET_CPU_CYCLES();
51-
while (GET_CPU_CYCLES() - start < cycles) { __asm__ __volatile__("nop"); }
37+
uint32_t bitmask = 0x800000;
38+
uint32_t start;
39+
uint32_t cycles;
40+
while(bitmask) {
41+
cycles = (color & bitmask) ? time1H : time0H;
42+
WRITE_PERI_REG(ts_reg, reg_val);
43+
start = GET_CPU_CYCLES();
44+
while (GET_CPU_CYCLES() - start < cycles) { __asm__ __volatile__("nop"); }
45+
46+
cycles = (color & bitmask) ? time1L : time0L;
47+
bitmask = bitmask >> 1;
48+
WRITE_PERI_REG(tc_reg, reg_val);
49+
start = GET_CPU_CYCLES();
50+
while (GET_CPU_CYCLES() - start < cycles) { __asm__ __volatile__("nop"); }
51+
}
5252
}
53-
5453
vPortExitCritical(&mySpinlock); // Interrupts on
5554

5655
}
@@ -63,41 +62,39 @@
6362

6463
#define GET_CPU_CYCLES() esp_get_cycle_count()
6564

66-
void IRAM_ATTR setColor(uint32_t color, uint8_t pin, uint8_t time1H, uint8_t time1L, uint8_t time0H, uint8_t time0L) {
67-
uint32_t bitmask = 0x800000;
68-
volatile uint8_t count = 24;
69-
uint32_t start;
70-
uint32_t cycles;
71-
65+
void IRAM_ATTR setColor(uint32_t color, uint8_t pin, uint8_t time1H, uint8_t time1L, uint8_t time0H, uint8_t time0L, uint8_t count) {
7266
if(pin > 32) return;
7367
uint32_t reg_val = (1UL << (pin));
7468

7569
ETS_INTR_LOCK(); // Interrupts off
76-
7770
while(count--) {
78-
cycles = (color & bitmask) ? time1H : time0H;
79-
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, reg_val);
80-
start = GET_CPU_CYCLES();
81-
while (GET_CPU_CYCLES() - start < cycles) { __asm__ __volatile__("nop"); }
82-
83-
cycles = (color & bitmask) ? time1L : time0L;
84-
bitmask = bitmask >> 1;
85-
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, reg_val);
86-
start = GET_CPU_CYCLES();
87-
while (GET_CPU_CYCLES() - start < cycles) { __asm__ __volatile__("nop"); }
71+
uint32_t bitmask = 0x800000;
72+
uint32_t start;
73+
uint32_t cycles;
74+
while(bitmask) {
75+
cycles = (color & bitmask) ? time1H : time0H;
76+
GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, reg_val);
77+
start = GET_CPU_CYCLES();
78+
while (GET_CPU_CYCLES() - start < cycles) { __asm__ __volatile__("nop"); }
79+
80+
cycles = (color & bitmask) ? time1L : time0L;
81+
bitmask = bitmask >> 1;
82+
GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, reg_val);
83+
start = GET_CPU_CYCLES();
84+
while (GET_CPU_CYCLES() - start < cycles) { __asm__ __volatile__("nop"); }
85+
}
8886
}
89-
9087
ETS_INTR_UNLOCK(); // Interrupts on
9188
}
9289

9390
#else
94-
#error Target CONFIG_IDF_TARGET is not supported
91+
#error Target is not yet supported
9592
#endif
9693

9794

9895

9996

100-
void ws2812Write(uint8_t pin, uint32_t color_rgb, ws2812_write_led_type_t led_type)
97+
void ws2812Write(uint8_t pin, uint32_t color_rgb, uint8_t led_count, ws2812_write_led_type_t led_type)
10198
{
10299
pinMode(pin, OUTPUT);
103100

@@ -129,44 +126,44 @@ void ws2812Write(uint8_t pin, uint32_t color_rgb, ws2812_write_led_type_t led_ty
129126
uint32_t Freq;
130127
uint8_t time1H, time1L, time0H, time0L;
131128

132-
// WS2812B: 1: H_800ns L_450ns (Send a '1' bit)
133-
// 0: H_400ns L_850ns (Send a '0' bit)
129+
// WS2812B: 1: H_800ns L_450ns (Send a '1' bit) +/-150ns
130+
// 0: H_400ns L_850ns (Send a '0' bit) +/-150ns
134131

135132
#ifdef ARDUINO_ARCH_ESP32
136133
Freq = getCpuFrequencyMhz();
137134
#if defined(CONFIG_IDF_TARGET_ESP32)
138-
// Tested for ESP32
139-
time1H = (800 * Freq) / 1000 - 11;
140-
time0H = (400 * Freq) / 1000 - 15;
141-
time1L = (450 * Freq) / 1000 - 25;
142-
time0L = (850 * Freq) / 1000 - 28;
135+
// Tested for ESP32 ns @ MHz 240 160 80
136+
time1H = (800 * Freq) / 1000 - 15; // 800 774 836
137+
time0H = (400 * Freq) / 1000 - 15; // 400 424 312
138+
time1L = (450 * Freq) / 1000 - 25; // 426 464 402
139+
time0L = (850 * Freq) / 1000 - 28; // 838 812 920
143140
if(Freq <= 80) {
144141
// Adjust timing for 80 MHz
145-
time1L -= 4;
146-
time0L -= 4;
142+
time0H -= 2;
143+
time0L += 4;
147144
}
148145
#else
149-
// Tested for ESP32-S3 80/160/240 MHz
150-
time1H = (800 * Freq) / 1000 - 11;
151-
time0H = (400 * Freq) / 1000 - 15;
152-
time1L = (450 * Freq) / 1000 - 18;
153-
time0L = (850 * Freq) / 1000 - 26;
146+
// Tested for ESP32-S3 ns @ MHz 240 160 80
147+
time1H = (800 * Freq) / 1000 - 11; // 812 824 774
148+
time0H = (400 * Freq) / 1000 - 15; // 400 412 400
149+
time1L = (450 * Freq) / 1000 - 15; // 450 438 450
150+
time0L = (850 * Freq) / 1000 - 24; // 814 838 788
154151
#endif
155152
#elif defined(ARDUINO_ARCH_ESP8266)
156153
Freq = (uint32_t)system_get_cpu_freq();
157154
// Tested ESP8266 at 80 and 160 MHz
158-
time1H = (800 * Freq) / 1000 - 11;
159-
time0H = (400 * Freq) / 1000 - 15;
160-
time1L = (450 * Freq) / 1000 - 21;
161-
time0L = (850 * Freq) / 1000 - 24;
155+
time1H = (800 * Freq) / 1000 - 13; // 850 824
156+
time0H = (400 * Freq) / 1000 - 15; // 350 374
157+
time1L = (450 * Freq) / 1000 - 21; // 424 438
158+
time0L = (850 * Freq) / 1000 - 21; // 876 830
162159
#else
163160
return; // Unsupported architecture
164161
#endif
165162

166163
//Serial.printf("ws2812Write: %u MHz, time1H=%u, time1L=%u, time0H=%u, time0L=%u\r\n",
167164
// (unsigned int)Freq, (unsigned int)time1H, (unsigned int)time1L,
168165
// (unsigned int)time0H, (unsigned int)time0L);
169-
setColor(tx_color, pin, time1H, time1L, time0H, time0L);
166+
setColor(tx_color, pin, time1H, time1L, time0H, time0L, led_count);
170167

171168

172169

lib/Single_WS2812_Bitbang_Write/WS2812Write.h renamed to src/WS2812Write.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
*
1212
* @param pin GPIO number
1313
* @param color_rgb 24-bit color format 0xRRGGBB
14+
* @param led_count Number of LEDs to write the same color to (default: 1)
15+
* @param led_type Color order type (default: WS2812WRITE_GRB)
1416
*/
1517

1618

@@ -24,5 +26,4 @@ typedef enum {
2426
WS2812WRITE_GRB /* Most WS2812 types */
2527
} ws2812_write_led_type_t;
2628

27-
void ws2812Write(uint8_t pin, uint32_t color_rgb, ws2812_write_led_type_t led_type = WS2812WRITE_GRB);
28-
29+
void ws2812Write(uint8_t pin, uint32_t color_rgb, uint8_t led_count = 1, ws2812_write_led_type_t led_type = WS2812WRITE_GRB);

0 commit comments

Comments
 (0)