Skip to content

Commit 1a0b419

Browse files
committed
Simplify the status LED to save power
This also removes the need to pin share because we don't use the status LED while user code is running. The status flashes fallback to the HW_STATUS LED if no RGB LED is present. Each status has a unique blink pattern as well. One caveat is the REPL state. In order to not pin share, we set the RGB color once. PWM and single color will be shutoff immediately but DotStars and NeoPixels will hold the color until the user overrides it. Fixes #4133
1 parent 35ee4ad commit 1a0b419

File tree

33 files changed

+463
-1147
lines changed

33 files changed

+463
-1147
lines changed

main.c

Lines changed: 122 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
#include "supervisor/port.h"
5454
#include "supervisor/serial.h"
5555
#include "supervisor/shared/autoreload.h"
56-
#include "supervisor/shared/rgb_led_status.h"
5756
#include "supervisor/shared/safe_mode.h"
5857
#include "supervisor/shared/stack.h"
5958
#include "supervisor/shared/status_leds.h"
@@ -114,7 +113,6 @@ static void reset_devices(void) {
114113
}
115114

116115
STATIC void start_mp(supervisor_allocation* heap) {
117-
reset_status_led();
118116
autoreload_stop();
119117
supervisor_workflow_reset();
120118

@@ -251,7 +249,6 @@ STATIC void cleanup_after_vm(supervisor_allocation* heap) {
251249
#endif
252250
reset_port();
253251
reset_board();
254-
reset_status_led();
255252
}
256253

257254
STATIC void print_code_py_status_message(safe_mode_t safe_mode) {
@@ -284,8 +281,6 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
284281
bool found_main = false;
285282

286283
if (safe_mode == NO_SAFE_MODE) {
287-
new_status_color(MAIN_RUNNING);
288-
289284
static const char * const supported_filenames[] = STRING_LIST(
290285
"code.txt", "code.py", "main.py", "main.txt");
291286
#if CIRCUITPY_FULL_BUILD
@@ -315,6 +310,8 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
315310
serial_write_compressed(translate("WARNING: Your code filename has two extensions\n"));
316311
}
317312
}
313+
#else
314+
(void) found_main;
318315
#endif
319316

320317
// Finished executing python code. Cleanup includes a board reset.
@@ -332,42 +329,64 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
332329
}
333330

334331
// Program has finished running.
335-
336332
bool printed_press_any_key = false;
337333
#if CIRCUITPY_DISPLAYIO
338-
bool refreshed_epaper_display = false;
334+
size_t time_to_epaper_refresh = 1;
339335
#endif
340336

341-
rgb_status_animation_t animation;
342-
prep_rgb_status_animation(&result, found_main, safe_mode, &animation);
337+
// Setup LED blinks.
338+
#if CIRCUITPY_STATUS_LED
339+
uint32_t color;
340+
uint8_t blink_count;
341+
#if CIRCUITPY_ALARM
342+
if (result.return_code & PYEXEC_DEEP_SLEEP) {
343+
color = BLACK;
344+
blink_count = 0;
345+
} else
346+
#endif
347+
if (result.return_code != PYEXEC_EXCEPTION) {
348+
if (safe_mode == NO_SAFE_MODE) {
349+
color = ALL_DONE;
350+
blink_count = ALL_DONE_BLINKS;
351+
} else {
352+
color = SAFE_MODE;
353+
blink_count = SAFE_MODE_BLINKS;
354+
}
355+
} else {
356+
color = EXCEPTION;
357+
blink_count = EXCEPTION_BLINKS;
358+
}
359+
size_t pattern_start = supervisor_ticks_ms32();
360+
size_t single_blink_time = (OFF_ON_RATIO + 1) * BLINK_TIME_MS;
361+
size_t blink_time = single_blink_time * blink_count;
362+
size_t total_time = blink_time + LED_SLEEP_TIME_MS;
363+
if (blink_count > 0) {
364+
status_led_init();
365+
}
366+
#endif
367+
368+
#if CIRCUITPY_ALARM
343369
bool fake_sleeping = false;
370+
#endif
371+
bool skip_repl = false;
344372
while (true) {
345373
RUN_BACKGROUND_TASKS;
346374

347375
// If a reload was requested by the supervisor or autoreload, return
348376
if (reload_requested) {
349-
#if CIRCUITPY_ALARM
350-
if (fake_sleeping) {
351-
board_init();
352-
}
353-
#endif
354377
reload_requested = false;
355-
return true;
378+
skip_repl = true;
379+
break;
356380
}
357381

358382
// If interrupted by keyboard, return
359383
if (serial_connected() && serial_bytes_available()) {
360-
#if CIRCUITPY_ALARM
361-
if (fake_sleeping) {
362-
board_init();
363-
}
364-
#endif
365384
// Skip REPL if reload was requested.
366-
bool ctrl_d = serial_read() == CHAR_CTRL_D;
367-
if (ctrl_d) {
385+
skip_repl = serial_read() == CHAR_CTRL_D;
386+
if (skip_repl) {
368387
supervisor_set_run_reason(RUN_REASON_REPL_RELOAD);
369388
}
370-
return ctrl_d;
389+
break;
371390
}
372391

373392
// Check for a deep sleep alarm and restart the VM. This can happen if
@@ -376,9 +395,9 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
376395
#if CIRCUITPY_ALARM
377396
if (fake_sleeping && common_hal_alarm_woken_from_sleep()) {
378397
serial_write_compressed(translate("Woken up by alarm.\n"));
379-
board_init();
380398
supervisor_set_run_reason(RUN_REASON_STARTUP);
381-
return true;
399+
skip_repl = true;
400+
break;
382401
}
383402
#endif
384403

@@ -398,25 +417,21 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
398417
printed_press_any_key = false;
399418
}
400419

401-
// Refresh the ePaper display if we have one. That way it'll show an error message.
402-
#if CIRCUITPY_DISPLAYIO
403-
// Don't refresh the display if we're about to deep sleep.
404-
#if CIRCUITPY_ALARM
405-
refreshed_epaper_display = refreshed_epaper_display || result.return_code & PYEXEC_DEEP_SLEEP;
406-
#endif
407-
if (!refreshed_epaper_display) {
408-
refreshed_epaper_display = maybe_refresh_epaperdisplay();
409-
}
410-
#endif
411-
412420
// Sleep until our next interrupt.
413421
#if CIRCUITPY_ALARM
414422
if (result.return_code & PYEXEC_DEEP_SLEEP) {
415423
// Make sure we have been awake long enough for USB to connect (enumeration delay).
416424
int64_t connecting_delay_ticks = CIRCUITPY_USB_CONNECTED_SLEEP_DELAY * 1024 - port_get_raw_ticks(NULL);
417-
// Until it's safe to decide whether we're real/fake sleeping, just run the RGB
418-
if (connecting_delay_ticks < 0 && !fake_sleeping) {
419-
fake_sleeping = true;
425+
// Until it's safe to decide whether we're real/fake sleeping
426+
if (fake_sleeping) {
427+
// This waits until a pretend deep sleep alarm occurs. They are set
428+
// during common_hal_alarm_set_deep_sleep_alarms. On some platforms
429+
// it may also return due to another interrupt, that's why we check
430+
// for deep sleep alarms above. If it wasn't a deep sleep alarm,
431+
// then we'll idle here again.
432+
common_hal_alarm_pretending_deep_sleep();
433+
} else if (connecting_delay_ticks < 0) {
434+
// Entering deep sleep (may be fake or real.)
420435
new_status_color(BLACK);
421436
board_deinit();
422437
if (!supervisor_workflow_active()) {
@@ -426,27 +441,71 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
426441
// Does not return.
427442
} else {
428443
serial_write_compressed(translate("Pretending to deep sleep until alarm, CTRL-C or file write.\n"));
444+
fake_sleeping = true;
429445
}
446+
} else {
447+
// Loop while checking the time. We can't idle because we don't want to override a
448+
// time alarm set for the deep sleep.
430449
}
431-
}
450+
} else
432451
#endif
452+
{
453+
// Refresh the ePaper display if we have one. That way it'll show an error message.
454+
#if CIRCUITPY_DISPLAYIO
455+
if (time_to_epaper_refresh > 0) {
456+
time_to_epaper_refresh = maybe_refresh_epaperdisplay();
457+
}
433458

434-
if (!fake_sleeping) {
435-
tick_rgb_status_animation(&animation);
436-
} else {
437-
// This waits until a pretend deep sleep alarm occurs. They are set
438-
// during common_hal_alarm_set_deep_sleep_alarms. On some platforms
439-
// it may also return due to another interrupt, that's why we check
440-
// for deep sleep alarms above. If it wasn't a deep sleep alarm,
441-
// then we'll idle here again.
459+
#if !CIRCUITPY_STATUS_LED
460+
port_interrupt_after_ticks(time_to_epaper_refresh);
461+
#endif
462+
#endif
442463

443-
#if CIRCUITPY_ALARM
444-
common_hal_alarm_pretending_deep_sleep();
445-
#else
446-
port_idle_until_interrupt();
464+
#if CIRCUITPY_STATUS_LED
465+
uint32_t tick_diff = supervisor_ticks_ms32() - pattern_start;
466+
467+
// By default, don't sleep.
468+
size_t time_to_next_change = 0;
469+
if (tick_diff < blink_time) {
470+
uint32_t blink_diff = tick_diff % (single_blink_time);
471+
if (blink_diff >= BLINK_TIME_MS) {
472+
new_status_color(BLACK);
473+
time_to_next_change = single_blink_time - blink_diff;
474+
} else {
475+
new_status_color(color);
476+
time_to_next_change = BLINK_TIME_MS - blink_diff;
477+
}
478+
} else if (tick_diff > total_time) {
479+
pattern_start = supervisor_ticks_ms32();
480+
} else {
481+
time_to_next_change = total_time - tick_diff;
482+
}
483+
#if CIRCUITPY_DISPLAYIO
484+
if (time_to_epaper_refresh > 0 && time_to_next_change > 0) {
485+
time_to_next_change = MIN(time_to_next_change, time_to_epaper_refresh);
486+
}
447487
#endif
488+
if (time_to_next_change > 0) {
489+
// time_to_next_change is in ms and ticks are slightly shorter so
490+
// we'll undersleep just a little. It shouldn't matter.
491+
port_interrupt_after_ticks(time_to_next_change);
492+
}
493+
#endif
494+
port_idle_until_interrupt();
448495
}
449496
}
497+
// Done waiting, start the board back up.
498+
#if CIRCUITPY_STATUS_LED
499+
new_status_color(BLACK);
500+
status_led_deinit();
501+
#endif
502+
503+
#if CIRCUITPY_ALARM
504+
if (fake_sleeping) {
505+
board_init();
506+
}
507+
#endif
508+
return skip_repl;
450509
}
451510

452511
FIL* boot_output_file;
@@ -463,7 +522,6 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
463522
bool skip_boot_output = false;
464523

465524
if (ok_to_run) {
466-
new_status_color(BOOT_RUNNING);
467525

468526
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
469527
FIL file_pointer;
@@ -574,7 +632,16 @@ STATIC int run_repl(void) {
574632
#endif
575633

576634
autoreload_suspend();
635+
636+
// Set the status LED to the REPL color before running the REPL. For
637+
// NeoPixels and DotStars this will be sticky but for PWM or single LED it
638+
// won't. This simplifies pin sharing because they won't be in use when
639+
// actually in the REPL.
640+
#if CIRCUITPY_STATUS_LED
641+
status_led_init();
577642
new_status_color(REPL_RUNNING);
643+
status_led_deinit();
644+
#endif
578645
if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
579646
exit_code = pyexec_raw_repl();
580647
} else {
@@ -589,9 +656,8 @@ int __attribute__((used)) main(void) {
589656
// initialise the cpu and peripherals
590657
safe_mode_t safe_mode = port_init();
591658

592-
// Turn on LEDs
593-
init_status_leds();
594-
rgb_led_status_init();
659+
// Turn on RX and TX LEDs if we have them.
660+
init_rxtx_leds();
595661

596662
// Wait briefly to give a reset window where we'll enter safe mode after the reset.
597663
if (safe_mode == NO_SAFE_MODE) {

ports/atmel-samd/boards/picoplanet/mpconfigboard.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@
3333
#define DEFAULT_SPI_BUS_SCK (&pin_PA17)
3434
#define DEFAULT_SPI_BUS_MOSI (&pin_PA16)
3535

36-
// #define CP_RGB_STATUS_R (&pin_PA06)
37-
// #define CP_RGB_STATUS_G (&pin_PA05)
38-
// #define CP_RGB_STATUS_B (&pin_PA07)
39-
// #define CP_RGB_STATUS_INVERTED_PWM
40-
// #define CP_RGB_STATUS_LED
36+
// #define CIRCUITPY_RGB_STATUS_R (&pin_PA06)
37+
// #define CIRCUITPY_RGB_STATUS_G (&pin_PA05)
38+
// #define CIRCUITPY_RGB_STATUS_B (&pin_PA07)
39+
// #define CIRCUITPY_RGB_STATUS_INVERTED_PWM
4140

4241
#define MICROPY_HW_LED_STATUS (&pin_PA06)

ports/atmel-samd/common-hal/busio/SPI.c

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
#include "hal/include/hal_gpio.h"
3737
#include "hal/include/hal_spi_m_sync.h"
3838
#include "hal/include/hpl_spi_m_sync.h"
39-
#include "supervisor/shared/rgb_led_status.h"
4039

4140
#include "samd/dma.h"
4241
#include "samd/sercom.h"
@@ -72,11 +71,6 @@ void reset_sercoms(void) {
7271
if (never_reset_sercoms[i]) {
7372
continue;
7473
}
75-
#ifdef MICROPY_HW_APA102_SERCOM
76-
if (sercom_instances[i] == MICROPY_HW_APA102_SERCOM) {
77-
continue;
78-
}
79-
#endif
8074
// SWRST is same for all modes of SERCOMs.
8175
sercom_instances[i]->SPI.CTRLA.bit.SWRST = 1;
8276
}
@@ -122,15 +116,7 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self,
122116
continue;
123117
}
124118
Sercom *potential_sercom = sercom_insts[sercom_index];
125-
if (
126-
#if defined(MICROPY_HW_APA102_SCK) && defined(MICROPY_HW_APA102_MOSI) && !CIRCUITPY_BITBANG_APA102
127-
(potential_sercom->SPI.CTRLA.bit.ENABLE != 0 &&
128-
potential_sercom != status_apa102.spi_desc.dev.prvt &&
129-
!apa102_sck_in_use)
130-
#else
131-
potential_sercom->SPI.CTRLA.bit.ENABLE != 0
132-
#endif
133-
) {
119+
if (potential_sercom->SPI.CTRLA.bit.ENABLE != 0) {
134120
continue;
135121
}
136122
clock_pinmux = PINMUX(clock->number, (i == 0) ? MUX_C : MUX_D);
@@ -181,10 +167,6 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self,
181167
// Set up SPI clocks on SERCOM.
182168
samd_peripherals_sercom_clock_init(sercom, sercom_index);
183169

184-
#if defined(MICROPY_HW_APA102_SCK) && defined(MICROPY_HW_APA102_MOSI) && !CIRCUITPY_BITBANG_APA102
185-
// if we're re-using the dotstar sercom, make sure it is disabled or the init will fail out
186-
hri_sercomspi_clear_CTRLA_ENABLE_bit(sercom);
187-
#endif
188170
if (spi_m_sync_init(&self->spi_desc, sercom) != ERR_NONE) {
189171
mp_raise_OSError(MP_EIO);
190172
}

0 commit comments

Comments
 (0)