Skip to content

Commit 15ab7ad

Browse files
committed
WS2812 style LEDs require a reset time before setting the color again. prior to this fix, setting color/on/off
in rapid succession would just send the updated values "down the string" Whilst it would be nice to have the PIO program take care of updating the latest FIFO value after the relevant delay, that costs a fair amount of PIO instrution space, so I have decided to "fix" the issue on the CPU side. Since the delays in question are non trivial (e.g. 50us), i've decided not to have the call block, but instead set a timer when necessary (using the default alarm pool) - if that isn't available or the alarm add fails, then the caller blocks instead. Note the implementation uses a spin lock, because it needs to work with multicore, but also to be fair you might expect to be able to set an LED from an IRQ
1 parent 4836053 commit 15ab7ad

1 file changed

Lines changed: 76 additions & 3 deletions

File tree

src/rp2_common/pico_status_led/status_led.c

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
#include "pico/status_led.h"
88

9+
#include "hardware/sync/spin_lock.h"
10+
911
#if PICO_STATUS_LED_AVAILABLE && defined(CYW43_WL_GPIO_LED_PIN) && !defined(PICO_DEFAULT_LED_PIN)
1012
#define STATUS_LED_USING_WL_GPIO 1
1113
#else
@@ -33,35 +35,101 @@ static uint32_t colored_status_led_on_color = PICO_DEFAULT_COLORED_STATUS_LED_ON
3335
static bool colored_status_led_on;
3436

3537
#if COLORED_STATUS_LED_USING_WS2812_PIO
36-
#include <hardware/pio.h>
38+
#include "hardware/pio.h"
39+
#include "pico/time.h"
3740
#include "ws2812.pio.h"
3841

3942
// PICO_CONFIG: PICO_COLORED_STATUS_LED_WS2812_FREQ, Frequency per bit for the WS2812 colored status LED, type=int, default=800000, group=pico_status_led
4043
#ifndef PICO_COLORED_STATUS_LED_WS2812_FREQ
4144
#define PICO_COLORED_STATUS_LED_WS2812_FREQ 800000
4245
#endif
4346

47+
// PICO_CONFIG: PICO_COLORED_STATUS_LED_RESET_DELAY_US, Required delay in microsecond to reset the WS2812 colored status LED , type=int, default=800000, group=pico_status_led
48+
#ifndef PICO_COLORED_STATUS_LED_RESET_DELAY_US
49+
#define PICO_COLORED_STATUS_LED_RESET_DELAY_US 50
50+
#endif
51+
52+
#ifndef PICO_COLORED_STATUS_LED_USE_DEFAULT_ALARM_POOL
53+
#define PICO_COLORED_STATUS_LED_USE_DEFAULT_ALARM_POOL !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
54+
#endif
55+
4456
static PIO pio;
4557
static uint sm;
4658
static uint offset;
59+
static uint32_t next_value;
60+
static uint64_t next_safe_set_time;
61+
#if PICO_COLORED_STATUS_LED_USE_DEFAULT_ALARM_POOL
62+
static alarm_id_t alarm_id;
63+
static int8_t alarm_pending;
64+
#else
65+
#define alarm_id 0
66+
#endif
67+
68+
#define MINIMUM_WS2812_DELAY_US (1 + (1000000 * (PICO_COLORED_STATUS_LED_USES_WRGB ? 32 : 24)) / PICO_COLORED_STATUS_LED_WS2812_FREQ)
4769

4870
// Extract from 0xWWRRGGBB
4971
#define RED(c) (((c) >> 16) & 0xff)
5072
#define GREEN(c) (((c) >> 8) & 0xff)
5173
#define BLUE(c) (((c) >> 0) & 0xff)
5274
#define WHITE(c) (((c) >> 24) && 0xff)
5375

54-
bool set_ws2812(uint32_t value) {
76+
static void unsafe_set_ws2812(uint32_t value, uint64_t now) {
5577
if (pio) {
78+
pio_sm_drain_tx_fifo(pio, sm); // want to jump passed any previous queued values
5679
#if PICO_COLORED_STATUS_LED_USES_WRGB
5780
// Convert to 0xWWGGRRBB
5881
pio_sm_put_blocking(pio, sm, WHITE(value) << 24 | GREEN(value) << 16 | RED(value) << 8 | BLUE(value));
5982
#else
6083
// Convert to 0xGGRRBB00
6184
pio_sm_put_blocking(pio, sm, GREEN(value) << 24 | RED(value) << 16 | BLUE(value) << 8);
6285
#endif
63-
return true;
86+
next_safe_set_time = now + MINIMUM_WS2812_DELAY_US;
6487
}
88+
}
89+
90+
static int64_t deferred_set_ws2812(__unused alarm_id_t id, __unused void *user_data) {
91+
spin_lock_t *spin_lock = spin_lock_instance(PICO_SPINLOCK_ID_ATOMIC);
92+
uint32_t save = spin_lock_blocking(spin_lock);
93+
unsafe_set_ws2812(next_value, time_us_64());
94+
alarm_id = 0;
95+
alarm_pending--;
96+
spin_unlock(spin_lock, save);
97+
return 0;
98+
}
99+
100+
static bool set_ws2812(uint32_t value) {
101+
spin_lock_t *spin_lock = spin_lock_instance(PICO_SPINLOCK_ID_ATOMIC);
102+
uint32_t save = spin_lock_blocking(spin_lock);
103+
next_value = value;
104+
while (true) {
105+
if (alarm_pending) {
106+
// we defer the set to the already waiting alarm
107+
break;
108+
} else {
109+
uint64_t now = time_us_64();
110+
if (now >= next_safe_set_time) {
111+
unsafe_set_ws2812(value, now);
112+
break;
113+
} else {
114+
// we want to defer the set until it is safe to do so
115+
//
116+
// note we use alarm_pending separate from alarm_id, as alarm_id may be returned even if the
117+
// alarm fires during the add_alarm_at. and don't use a boolean because if we fail
118+
// to add the alarm, we don't know what has happened in between since we unlock the spin lock
119+
// before adding the alarm since that is a slowish call
120+
alarm_pending++;
121+
spin_unlock(spin_lock, save);
122+
#if PICO_COLORED_STATUS_LED_USE_DEFAULT_ALARM_POOL
123+
alarm_id = add_alarm_at(next_safe_set_time, deferred_set_ws2812, NULL, true);
124+
if (alarm_id > 0) break;
125+
#endif
126+
busy_wait_until(next_safe_set_time);
127+
save = spin_lock_blocking(spin_lock);
128+
alarm_pending--;
129+
}
130+
}
131+
}
132+
spin_unlock(spin_lock, save);
65133
return false;
66134
}
67135
#endif
@@ -165,6 +233,11 @@ void status_led_deinit(void) {
165233
status_led_context = NULL;
166234
#endif
167235
#if COLORED_STATUS_LED_USING_WS2812_PIO
236+
#if PICO_COLORED_STATUS_LED_USE_DEFAULT_ALARM_POOL
237+
if (alarm_id > 0) {
238+
cancel_alarm(alarm_id);
239+
}
240+
#endif
168241
if (pio) {
169242
pio_remove_program_and_unclaim_sm(&ws2812_program, pio, sm, offset);
170243
pio = NULL;

0 commit comments

Comments
 (0)