Skip to content

Commit e96dcc2

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 4eb4f14 commit e96dcc2

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
@@ -311,6 +306,8 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
311306
serial_write_compressed(translate("WARNING: Your code filename has two extensions\n"));
312307
}
313308
}
309+
#else
310+
(void) found_main;
314311
#endif
315312

316313
cleanup_after_vm(heap);
@@ -327,42 +324,64 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
327324
}
328325

329326
// Program has finished running.
330-
331327
bool printed_press_any_key = false;
332328
#if CIRCUITPY_DISPLAYIO
333-
bool refreshed_epaper_display = false;
329+
size_t time_to_epaper_refresh = 1;
334330
#endif
335331

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

342370
// If a reload was requested by the supervisor or autoreload, return
343371
if (reload_requested) {
344-
#if CIRCUITPY_ALARM
345-
if (fake_sleeping) {
346-
board_init();
347-
}
348-
#endif
349372
reload_requested = false;
350-
return true;
373+
skip_repl = true;
374+
break;
351375
}
352376

353377
// If interrupted by keyboard, return
354378
if (serial_connected() && serial_bytes_available()) {
355-
#if CIRCUITPY_ALARM
356-
if (fake_sleeping) {
357-
board_init();
358-
}
359-
#endif
360379
// Skip REPL if reload was requested.
361-
bool ctrl_d = serial_read() == CHAR_CTRL_D;
362-
if (ctrl_d) {
380+
skip_repl = serial_read() == CHAR_CTRL_D;
381+
if (skip_repl) {
363382
supervisor_set_run_reason(RUN_REASON_REPL_RELOAD);
364383
}
365-
return ctrl_d;
384+
break;
366385
}
367386

368387
// Check for a deep sleep alarm and restart the VM. This can happen if
@@ -371,9 +390,9 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
371390
#if CIRCUITPY_ALARM
372391
if (fake_sleeping && common_hal_alarm_woken_from_sleep()) {
373392
serial_write_compressed(translate("Woken up by alarm.\n"));
374-
board_init();
375393
supervisor_set_run_reason(RUN_REASON_STARTUP);
376-
return true;
394+
skip_repl = true;
395+
break;
377396
}
378397
#endif
379398

@@ -393,25 +412,21 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
393412
printed_press_any_key = false;
394413
}
395414

396-
// Refresh the ePaper display if we have one. That way it'll show an error message.
397-
#if CIRCUITPY_DISPLAYIO
398-
// Don't refresh the display if we're about to deep sleep.
399-
#if CIRCUITPY_ALARM
400-
refreshed_epaper_display = refreshed_epaper_display || result.return_code & PYEXEC_DEEP_SLEEP;
401-
#endif
402-
if (!refreshed_epaper_display) {
403-
refreshed_epaper_display = maybe_refresh_epaperdisplay();
404-
}
405-
#endif
406-
407415
// Sleep until our next interrupt.
408416
#if CIRCUITPY_ALARM
409417
if (result.return_code & PYEXEC_DEEP_SLEEP) {
410418
// Make sure we have been awake long enough for USB to connect (enumeration delay).
411419
int64_t connecting_delay_ticks = CIRCUITPY_USB_CONNECTED_SLEEP_DELAY * 1024 - port_get_raw_ticks(NULL);
412-
// Until it's safe to decide whether we're real/fake sleeping, just run the RGB
413-
if (connecting_delay_ticks < 0 && !fake_sleeping) {
414-
fake_sleeping = true;
420+
// Until it's safe to decide whether we're real/fake sleeping
421+
if (fake_sleeping) {
422+
// This waits until a pretend deep sleep alarm occurs. They are set
423+
// during common_hal_alarm_set_deep_sleep_alarms. On some platforms
424+
// it may also return due to another interrupt, that's why we check
425+
// for deep sleep alarms above. If it wasn't a deep sleep alarm,
426+
// then we'll idle here again.
427+
common_hal_alarm_pretending_deep_sleep();
428+
} else if (connecting_delay_ticks < 0) {
429+
// Entering deep sleep (may be fake or real.)
415430
new_status_color(BLACK);
416431
board_deinit();
417432
if (!supervisor_workflow_active()) {
@@ -421,27 +436,71 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
421436
// Does not return.
422437
} else {
423438
serial_write_compressed(translate("Pretending to deep sleep until alarm, CTRL-C or file write.\n"));
439+
fake_sleeping = true;
424440
}
441+
} else {
442+
// Loop while checking the time. We can't idle because we don't want to override a
443+
// time alarm set for the deep sleep.
425444
}
426-
}
445+
} else
427446
#endif
447+
{
448+
// Refresh the ePaper display if we have one. That way it'll show an error message.
449+
#if CIRCUITPY_DISPLAYIO
450+
if (time_to_epaper_refresh > 0) {
451+
time_to_epaper_refresh = maybe_refresh_epaperdisplay();
452+
}
428453

429-
if (!fake_sleeping) {
430-
tick_rgb_status_animation(&animation);
431-
} else {
432-
// This waits until a pretend deep sleep alarm occurs. They are set
433-
// during common_hal_alarm_set_deep_sleep_alarms. On some platforms
434-
// it may also return due to another interrupt, that's why we check
435-
// for deep sleep alarms above. If it wasn't a deep sleep alarm,
436-
// then we'll idle here again.
454+
#if !CIRCUITPY_STATUS_LED
455+
port_interrupt_after_ticks(time_to_epaper_refresh);
456+
#endif
457+
#endif
437458

438-
#if CIRCUITPY_ALARM
439-
common_hal_alarm_pretending_deep_sleep();
440-
#else
441-
port_idle_until_interrupt();
459+
#if CIRCUITPY_STATUS_LED
460+
uint32_t tick_diff = supervisor_ticks_ms32() - pattern_start;
461+
462+
// By default, don't sleep.
463+
size_t time_to_next_change = 0;
464+
if (tick_diff < blink_time) {
465+
uint32_t blink_diff = tick_diff % (single_blink_time);
466+
if (blink_diff >= BLINK_TIME_MS) {
467+
new_status_color(BLACK);
468+
time_to_next_change = single_blink_time - blink_diff;
469+
} else {
470+
new_status_color(color);
471+
time_to_next_change = BLINK_TIME_MS - blink_diff;
472+
}
473+
} else if (tick_diff > total_time) {
474+
pattern_start = supervisor_ticks_ms32();
475+
} else {
476+
time_to_next_change = total_time - tick_diff;
477+
}
478+
#if CIRCUITPY_DISPLAYIO
479+
if (time_to_epaper_refresh > 0 && time_to_next_change > 0) {
480+
time_to_next_change = MIN(time_to_next_change, time_to_epaper_refresh);
481+
}
442482
#endif
483+
if (time_to_next_change > 0) {
484+
// time_to_next_change is in ms and ticks are slightly shorter so
485+
// we'll undersleep just a little. It shouldn't matter.
486+
port_interrupt_after_ticks(time_to_next_change);
487+
}
488+
#endif
489+
port_idle_until_interrupt();
443490
}
444491
}
492+
// Done waiting, start the board back up.
493+
#if CIRCUITPY_STATUS_LED
494+
new_status_color(BLACK);
495+
status_led_deinit();
496+
#endif
497+
498+
#if CIRCUITPY_ALARM
499+
if (fake_sleeping) {
500+
board_init();
501+
}
502+
#endif
503+
return skip_repl;
445504
}
446505

447506
FIL* boot_output_file;
@@ -458,7 +517,6 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
458517
bool skip_boot_output = false;
459518

460519
if (ok_to_run) {
461-
new_status_color(BOOT_RUNNING);
462520

463521
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
464522
FIL file_pointer;
@@ -569,7 +627,16 @@ STATIC int run_repl(void) {
569627
#endif
570628

571629
autoreload_suspend();
630+
631+
// Set the status LED to the REPL color before running the REPL. For
632+
// NeoPixels and DotStars this will be sticky but for PWM or single LED it
633+
// won't. This simplifies pin sharing because they won't be in use when
634+
// actually in the REPL.
635+
#if CIRCUITPY_STATUS_LED
636+
status_led_init();
572637
new_status_color(REPL_RUNNING);
638+
status_led_deinit();
639+
#endif
573640
if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
574641
exit_code = pyexec_raw_repl();
575642
} else {
@@ -584,9 +651,8 @@ int __attribute__((used)) main(void) {
584651
// initialise the cpu and peripherals
585652
safe_mode_t safe_mode = port_init();
586653

587-
// Turn on LEDs
588-
init_status_leds();
589-
rgb_led_status_init();
654+
// Turn on RX and TX LEDs if we have them.
655+
init_rxtx_leds();
590656

591657
// Wait briefly to give a reset window where we'll enter safe mode after the reset.
592658
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)