|
6 | 6 |
|
7 | 7 | #include "pico/status_led.h" |
8 | 8 |
|
| 9 | +#include "hardware/sync/spin_lock.h" |
| 10 | + |
9 | 11 | #if PICO_STATUS_LED_AVAILABLE && defined(CYW43_WL_GPIO_LED_PIN) && !defined(PICO_DEFAULT_LED_PIN) |
10 | 12 | #define STATUS_LED_USING_WL_GPIO 1 |
11 | 13 | #else |
@@ -33,35 +35,101 @@ static uint32_t colored_status_led_on_color = PICO_DEFAULT_COLORED_STATUS_LED_ON |
33 | 35 | static bool colored_status_led_on; |
34 | 36 |
|
35 | 37 | #if COLORED_STATUS_LED_USING_WS2812_PIO |
36 | | -#include <hardware/pio.h> |
| 38 | +#include "hardware/pio.h" |
| 39 | +#include "pico/time.h" |
37 | 40 | #include "ws2812.pio.h" |
38 | 41 |
|
39 | 42 | // 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 |
40 | 43 | #ifndef PICO_COLORED_STATUS_LED_WS2812_FREQ |
41 | 44 | #define PICO_COLORED_STATUS_LED_WS2812_FREQ 800000 |
42 | 45 | #endif |
43 | 46 |
|
| 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 | + |
44 | 56 | static PIO pio; |
45 | 57 | static uint sm; |
46 | 58 | 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) |
47 | 69 |
|
48 | 70 | // Extract from 0xWWRRGGBB |
49 | 71 | #define RED(c) (((c) >> 16) & 0xff) |
50 | 72 | #define GREEN(c) (((c) >> 8) & 0xff) |
51 | 73 | #define BLUE(c) (((c) >> 0) & 0xff) |
52 | 74 | #define WHITE(c) (((c) >> 24) && 0xff) |
53 | 75 |
|
54 | | -bool set_ws2812(uint32_t value) { |
| 76 | +static void unsafe_set_ws2812(uint32_t value, uint64_t now) { |
55 | 77 | if (pio) { |
| 78 | + pio_sm_drain_tx_fifo(pio, sm); // want to jump passed any previous queued values |
56 | 79 | #if PICO_COLORED_STATUS_LED_USES_WRGB |
57 | 80 | // Convert to 0xWWGGRRBB |
58 | 81 | pio_sm_put_blocking(pio, sm, WHITE(value) << 24 | GREEN(value) << 16 | RED(value) << 8 | BLUE(value)); |
59 | 82 | #else |
60 | 83 | // Convert to 0xGGRRBB00 |
61 | 84 | pio_sm_put_blocking(pio, sm, GREEN(value) << 24 | RED(value) << 16 | BLUE(value) << 8); |
62 | 85 | #endif |
63 | | - return true; |
| 86 | + next_safe_set_time = now + MINIMUM_WS2812_DELAY_US; |
64 | 87 | } |
| 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); |
65 | 133 | return false; |
66 | 134 | } |
67 | 135 | #endif |
@@ -165,6 +233,11 @@ void status_led_deinit(void) { |
165 | 233 | status_led_context = NULL; |
166 | 234 | #endif |
167 | 235 | #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 |
168 | 241 | if (pio) { |
169 | 242 | pio_remove_program_and_unclaim_sm(&ws2812_program, pio, sm, offset); |
170 | 243 | pio = NULL; |
|
0 commit comments