From 47b3b6d2502380c1cc579de4e4a99b8288acc7f3 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 13 Mar 2025 11:33:59 +0100 Subject: [PATCH 01/19] pbio/os: Prototype simpler and unified async OS. The goal is to: - Add error return value to all protothreads. - Reduce complexity and code size. - Avoid risk of overruning event queue. - Use single poll flag and no broadcasting between threads. - Cross-platform handling of events during sleep. - Make stm32, ev3, nxt all work the same way. For now the new OS also drives the contiki event loop so we can migrate processes one by one instead of breaking everything at once. --- bricks/_common/micropython.c | 8 +- bricks/_common/sources.mk | 1 + bricks/_common_stm32/mpconfigport.h | 26 +--- bricks/_common_stm32/mphalport.c | 23 +-- bricks/ev3/mpconfigport.h | 13 +- bricks/ev3/mphalport.c | 26 ++-- bricks/nxt/mpconfigport.h | 17 ++- bricks/nxt/mphalport.c | 24 ++- bricks/virtualhub/mp_port.c | 54 +++---- bricks/virtualhub/mpconfigvariant.h | 8 +- lib/pbio/drv/clock/clock_ev3.c | 3 + lib/pbio/drv/clock/clock_linux.c | 3 + lib/pbio/drv/clock/clock_nxt.c | 3 + lib/pbio/drv/clock/clock_stm32.c | 3 + lib/pbio/drv/clock/clock_test.c | 3 + lib/pbio/drv/core.c | 5 +- lib/pbio/include/pbio/os.h | 217 ++++++++++++++++++++++++++++ lib/pbio/src/os.c | 151 +++++++++++++++++++ lib/pbio/test/test-pbio.c | 10 ++ 19 files changed, 476 insertions(+), 122 deletions(-) create mode 100644 lib/pbio/include/pbio/os.h create mode 100644 lib/pbio/src/os.c diff --git a/bricks/_common/micropython.c b/bricks/_common/micropython.c index 8135f8057..b5769a104 100644 --- a/bricks/_common/micropython.c +++ b/bricks/_common/micropython.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -39,14 +40,9 @@ // Implementation for MICROPY_EVENT_POLL_HOOK void pb_event_poll_hook(void) { - // Drive pbio event loop. - while (pbio_do_one_event()) { - } - mp_handle_pending(true); - // Platform-specific code to run on completing the poll hook. - pb_event_poll_hook_leave(); + pbio_os_run_while_idle(); } // callback for when stop button is pressed in IDE or on hub diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index bf91fe6ff..4717aa353 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -215,6 +215,7 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\ src/motor_process.c \ src/motor/servo_settings.c \ src/observer.c \ + src/os.c \ src/parent.c \ src/port.c \ src/port_dcm_pup.c \ diff --git a/bricks/_common_stm32/mpconfigport.h b/bricks/_common_stm32/mpconfigport.h index 8a846fffb..6d965ed83 100644 --- a/bricks/_common_stm32/mpconfigport.h +++ b/bricks/_common_stm32/mpconfigport.h @@ -24,31 +24,15 @@ typedef unsigned mp_uint_t; // must be pointer size typedef long mp_off_t; -// We have inlined IRQ functions for efficiency (they are generally -// 1 machine instruction). -// -// Note on IRQ state: you should not need to know the specific -// value of the state variable, but rather just pass the return -// value from disable_irq back to enable_irq. If you really need -// to know the machine-specific values, see irq.h. +#include -static inline void enable_irq(mp_uint_t state) { - __set_PRIMASK(state); -} - -static inline mp_uint_t disable_irq(void) { - mp_uint_t state = __get_PRIMASK(); - __disable_irq(); - return state; -} - -#define MICROPY_BEGIN_ATOMIC_SECTION() disable_irq() -#define MICROPY_END_ATOMIC_SECTION(state) enable_irq(state) +#define MICROPY_BEGIN_ATOMIC_SECTION() pbio_os_hook_disable_irq() +#define MICROPY_END_ATOMIC_SECTION(state) pbio_os_hook_enable_irq(state) #define MICROPY_VM_HOOK_LOOP \ do { \ - extern int pbio_do_one_event(void); \ - pbio_do_one_event(); \ + extern bool pbio_os_run_processes_once(void); \ + pbio_os_run_processes_once(); \ } while (0); #define MICROPY_GC_HOOK_LOOP(i) do { \ diff --git a/bricks/_common_stm32/mphalport.c b/bricks/_common_stm32/mphalport.c index 19356583d..09708c554 100644 --- a/bricks/_common_stm32/mphalport.c +++ b/bricks/_common_stm32/mphalport.c @@ -24,17 +24,18 @@ void pb_stack_get_info(char **sstack, char **estack) { *estack = (char *)&_estack; } -void pb_event_poll_hook_leave(void) { - // There is a possible race condition where an interrupt occurs and sets the - // Contiki poll_requested flag after all events have been processed. So we - // have a critical section where we disable interrupts and check see if there - // are any last second events. If not, we can call __WFI(), which still wakes - // up the CPU on interrupt even though interrupts are otherwise disabled. - mp_uint_t state = disable_irq(); - if (!process_nevents()) { - __WFI(); - } - enable_irq(state); +uint32_t pbio_os_hook_disable_irq(void) { + mp_uint_t flags = __get_PRIMASK(); + __disable_irq(); + return flags; +} + +void pbio_os_hook_enable_irq(uint32_t flags) { + __set_PRIMASK(flags); +} + +void pbio_os_hook_wait_for_interrupt(void) { + __WFI(); } // using "internal" pbdrv variable diff --git a/bricks/ev3/mpconfigport.h b/bricks/ev3/mpconfigport.h index f191642e5..a62672070 100644 --- a/bricks/ev3/mpconfigport.h +++ b/bricks/ev3/mpconfigport.h @@ -79,18 +79,15 @@ typedef unsigned mp_uint_t; // must be pointer size typedef long mp_off_t; -#define CPSR_IRQ_MASK (1 << 7) // IRQ disable -#define CPSR_FIQ_MASK (1 << 6) // FIQ disable +#include -#include - -#define MICROPY_BEGIN_ATOMIC_SECTION() IntDisable() -#define MICROPY_END_ATOMIC_SECTION(state) IntEnable(state) +#define MICROPY_BEGIN_ATOMIC_SECTION() pbio_os_hook_disable_irq() +#define MICROPY_END_ATOMIC_SECTION(state) pbio_os_hook_enable_irq(state) #define MICROPY_VM_HOOK_LOOP \ do { \ - extern int pbio_do_one_event(void); \ - pbio_do_one_event(); \ + extern bool pbio_os_run_processes_once(void); \ + pbio_os_run_processes_once(); \ } while (0); #define MICROPY_GC_HOOK_LOOP(i) do { \ diff --git a/bricks/ev3/mphalport.c b/bricks/ev3/mphalport.c index 6345063a2..1dc426565 100644 --- a/bricks/ev3/mphalport.c +++ b/bricks/ev3/mphalport.c @@ -18,6 +18,8 @@ #include "py/mpconfig.h" #include "py/stream.h" +#include + void pb_stack_get_info(char **sstack, char **estack) { extern uint32_t _estack; extern uint32_t _sstack; @@ -25,26 +27,20 @@ void pb_stack_get_info(char **sstack, char **estack) { *estack = (char *)&_estack; } -static inline int arm_wfi(void) { +uint32_t pbio_os_hook_disable_irq(void) { + return IntDisable(); +} + +void pbio_os_hook_enable_irq(uint32_t flags) { + IntEnable(flags); +} + +void pbio_os_hook_wait_for_interrupt(void) { __asm volatile ( "mov r0, #0\n" "mcr p15, 0, r0, c7, c0, 4\n" /* wait for interrupt */ ::: "r0" ); - return 0; -} - -void pb_event_poll_hook_leave(void) { - // There is a possible race condition where an interrupt occurs and sets the - // Contiki poll_requested flag after all events have been processed. So we - // have a critical section where we disable interrupts and check see if there - // are any last second events. If not, we can call __WFI(), which still wakes - // up the CPU on interrupt even though interrupts are otherwise disabled. - mp_uint_t state = MICROPY_BEGIN_ATOMIC_SECTION(); - if (!process_nevents()) { - arm_wfi(); - } - MICROPY_END_ATOMIC_SECTION(state); } // Core delay function that does an efficient sleep and may switch thread context. diff --git a/bricks/nxt/mpconfigport.h b/bricks/nxt/mpconfigport.h index 3d142736c..8d1290308 100644 --- a/bricks/nxt/mpconfigport.h +++ b/bricks/nxt/mpconfigport.h @@ -79,18 +79,23 @@ typedef long mp_off_t; // We need to provide a declaration/definition of alloca() #include -uint32_t nx_interrupts_disable(void); -void nx_interrupts_enable(uint32_t); +#include -#define MICROPY_BEGIN_ATOMIC_SECTION() nx_interrupts_disable() -#define MICROPY_END_ATOMIC_SECTION(state) nx_interrupts_enable(state) +#define MICROPY_BEGIN_ATOMIC_SECTION() pbio_os_hook_disable_irq() +#define MICROPY_END_ATOMIC_SECTION(state) pbio_os_hook_enable_irq(state) #define MICROPY_VM_HOOK_LOOP \ do { \ - extern int pbio_do_one_event(void); \ - pbio_do_one_event(); \ + extern bool pbio_os_run_processes_once(void); \ + pbio_os_run_processes_once(); \ } while (0); +#define MICROPY_GC_HOOK_LOOP(i) do { \ + if (((i) & 0xf) == 0) { \ + MICROPY_VM_HOOK_LOOP \ + } \ +} while (0) + #define MICROPY_EVENT_POLL_HOOK \ do { \ extern void pb_event_poll_hook(void); \ diff --git a/bricks/nxt/mphalport.c b/bricks/nxt/mphalport.c index 87e398d6f..239b837d1 100644 --- a/bricks/nxt/mphalport.c +++ b/bricks/nxt/mphalport.c @@ -20,21 +20,17 @@ #include "py/mpconfig.h" #include "py/stream.h" -void pb_event_poll_hook_leave(void) { - // There is a possible race condition where an interrupt occurs and sets - // the Contiki poll_requested flag after all events have been processed. So - // we have a critical section where we disable interrupts and check see if - // there are any last second events. If not, we can enter Idle Mode, which - // still wakes up the CPU on interrupt even though interrupts are otherwise - // disabled. - uint32_t state = nx_interrupts_disable(); - - if (!process_nevents()) { - // disable the processor clock which puts it in Idle Mode. - AT91C_BASE_PMC->PMC_SCDR = AT91C_PMC_PCK; - } +uint32_t pbio_os_hook_disable_irq(void) { + return nx_interrupts_disable(); +} + +void pbio_os_hook_enable_irq(uint32_t flags) { + nx_interrupts_enable(flags); +} - nx_interrupts_enable(state); +void pbio_os_hook_wait_for_interrupt(void) { + // disable the processor clock which puts it in Idle Mode. + AT91C_BASE_PMC->PMC_SCDR = AT91C_PMC_PCK; } void pb_stack_get_info(char **sstack, char **estack) { diff --git a/bricks/virtualhub/mp_port.c b/bricks/virtualhub/mp_port.c index 664300470..4493c229f 100644 --- a/bricks/virtualhub/mp_port.c +++ b/bricks/virtualhub/mp_port.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -72,7 +73,8 @@ void pb_virtualhub_port_init(void) { pbsys_init(); pbsys_status_set(PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING); - while (pbio_do_one_event()) { + + while (pbio_os_run_processes_once()) { } pb_package_pybricks_init(true); @@ -84,62 +86,44 @@ void pb_virtualhub_port_deinit(void) { pb_package_pybricks_deinit(); } -// MICROPY_VM_HOOK_LOOP -void pb_virtualhub_poll(void) { - while (pbio_do_one_event()) { - } -} +// Implementation for MICROPY_EVENT_POLL_HOOK +void pb_event_poll_hook(void) { -// MICROPY_EVENT_POLL_HOOK -void pb_virtualhub_event_poll(void) { -start: mp_handle_pending(true); - int events_handled = 0; - - while (pbio_do_one_event()) { - events_handled++; - } + pbio_os_run_while_idle(); +} - // If there were any pbio events handled, don't sleep because there may - // be something waiting on one of the events that was just handled. - if (events_handled) { - return; - } +// This is used instead of the uint32_t flags used in embedded builds. +static sigset_t global_origmask; +uint32_t pbio_os_hook_disable_irq(void) { sigset_t sigmask; sigfillset(&sigmask); + pthread_sigmask(SIG_SETMASK, &sigmask, &global_origmask); + return 0; +} - // disable "interrupts" - sigset_t origmask; - pthread_sigmask(SIG_SETMASK, &sigmask, &origmask); - - if (process_nevents()) { - // something was scheduled since the event loop above - pthread_sigmask(SIG_SETMASK, &origmask, NULL); - goto start; - } +void pbio_os_hook_enable_irq(uint32_t flags) { + pthread_sigmask(SIG_SETMASK, &global_origmask, NULL); +} +void pbio_os_hook_wait_for_interrupt(void) { struct timespec timeout = { .tv_sec = 0, .tv_nsec = 100000, }; - // "sleep" with "interrupts" enabled MP_THREAD_GIL_EXIT(); - pselect(0, NULL, NULL, NULL, &timeout, &origmask); + pselect(0, NULL, NULL, NULL, &timeout, &global_origmask); MP_THREAD_GIL_ENTER(); - - // restore "interrupts" - pthread_sigmask(SIG_SETMASK, &origmask, NULL); } - void pb_virtualhub_delay_us(mp_uint_t us) { mp_uint_t start = mp_hal_ticks_us(); while (mp_hal_ticks_us() - start < us) { - pb_virtualhub_poll(); + MICROPY_VM_HOOK_LOOP; } } diff --git a/bricks/virtualhub/mpconfigvariant.h b/bricks/virtualhub/mpconfigvariant.h index 21e1fdb30..fbf37c9c2 100644 --- a/bricks/virtualhub/mpconfigvariant.h +++ b/bricks/virtualhub/mpconfigvariant.h @@ -97,8 +97,8 @@ } while (0) #define MICROPY_VM_HOOK_LOOP do { \ - extern void pb_virtualhub_poll(void); \ - pb_virtualhub_poll(); \ + extern bool pbio_os_run_processes_once(void); \ + pbio_os_run_processes_once(); \ } while (0); #define MICROPY_GC_HOOK_LOOP(i) do { \ @@ -108,6 +108,6 @@ } while (0) #define MICROPY_EVENT_POLL_HOOK do { \ - extern void pb_virtualhub_event_poll(void); \ - pb_virtualhub_event_poll(); \ + extern void pb_event_poll_hook(void); \ + pb_event_poll_hook(); \ } while (0); diff --git a/lib/pbio/drv/clock/clock_ev3.c b/lib/pbio/drv/clock/clock_ev3.c index 81d853f7b..193b24297 100644 --- a/lib/pbio/drv/clock/clock_ev3.c +++ b/lib/pbio/drv/clock/clock_ev3.c @@ -13,6 +13,8 @@ #include +#include + #include #include #include @@ -49,6 +51,7 @@ void systick_isr_C(void) { ++systick_ms; etimer_request_poll(); + pbio_os_request_poll(); /* Enable the timer interrupt */ TimerIntEnable(SOC_TMR_0_REGS, TMR_INT_TMR34_NON_CAPT_MODE); diff --git a/lib/pbio/drv/clock/clock_linux.c b/lib/pbio/drv/clock/clock_linux.c index f4f139183..a9b6760dd 100644 --- a/lib/pbio/drv/clock/clock_linux.c +++ b/lib/pbio/drv/clock/clock_linux.c @@ -9,6 +9,8 @@ #include #include +#include + // The SIGNAL option adds a timer that acts as the 1ms tick on embedded systems. #if PBDRV_CONFIG_CLOCK_LINUX_SIGNAL @@ -34,6 +36,7 @@ static void handle_signal(int sig) { // main thread is interrupted and the event poll hook runs. if (pthread_self() == main_thread) { etimer_request_poll(); + pbio_os_request_poll(); } else { pthread_kill(main_thread, TIMER_SIGNAL); } diff --git a/lib/pbio/drv/clock/clock_nxt.c b/lib/pbio/drv/clock/clock_nxt.c index 2549e4c09..891044480 100644 --- a/lib/pbio/drv/clock/clock_nxt.c +++ b/lib/pbio/drv/clock/clock_nxt.c @@ -9,6 +9,8 @@ #include +#include + #include #include #include @@ -41,6 +43,7 @@ static void systick_isr(void) { /* Do the system timekeeping. */ pbdrv_clock_ticks++; etimer_request_poll(); + pbio_os_request_poll(); /* Keeping up with the AVR link is a crucial task in the system, and * must absolutely be kept up with at all costs. diff --git a/lib/pbio/drv/clock/clock_stm32.c b/lib/pbio/drv/clock/clock_stm32.c index ffd8bbfac..75cce938a 100644 --- a/lib/pbio/drv/clock/clock_stm32.c +++ b/lib/pbio/drv/clock/clock_stm32.c @@ -8,6 +8,8 @@ #if PBDRV_CONFIG_CLOCK_STM32 +#include + #include STM32_H // NB: pbdrv_clock_ticks is intended to be private, but making it static @@ -80,6 +82,7 @@ void SysTick_Handler(void) { SysTick->CTRL; etimer_request_poll(); + pbio_os_request_poll(); } uint32_t HAL_GetTick(void) { diff --git a/lib/pbio/drv/clock/clock_test.c b/lib/pbio/drv/clock/clock_test.c index 7b6010b02..670064028 100644 --- a/lib/pbio/drv/clock/clock_test.c +++ b/lib/pbio/drv/clock/clock_test.c @@ -5,6 +5,8 @@ #if PBDRV_CONFIG_CLOCK_TEST +#include + // Clock implementation for tests. This allows tests to exactly control the // clock ticks to get repeatable tests rather than relying on a system clock. @@ -21,6 +23,7 @@ static uint32_t clock_ticks; void pbio_test_clock_tick(uint32_t ticks) { clock_ticks += ticks; etimer_request_poll(); + pbio_os_request_poll(); } void pbdrv_clock_init(void) { diff --git a/lib/pbio/drv/core.c b/lib/pbio/drv/core.c index f01f71431..c559d4fd7 100644 --- a/lib/pbio/drv/core.c +++ b/lib/pbio/drv/core.c @@ -5,6 +5,7 @@ #include #include +#include #include "core.h" #include "adc/adc.h" @@ -79,12 +80,12 @@ void pbdrv_init(void) { // Wait for all async pbdrv drivers to initialize before starting // higher level system processes. while (pbdrv_init_busy()) { - process_run(); + pbio_os_run_processes_once(); } #if PBDRV_CONFIG_IOPORT_PUP_QUIRK_POWER_CYCLE while (pbdrv_clock_get_ms() - ioport_reset_time < 500) { - process_run(); + pbio_os_run_processes_once(); } #endif pbdrv_ioport_enable_vcc(true); diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h new file mode 100644 index 000000000..fd5492741 --- /dev/null +++ b/lib/pbio/include/pbio/os.h @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2004-2005, Swedish Institute of Computer Science +// Copyright (c) 2025 The Pybricks Authors + +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUASYNCION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Adam Dunkels + * + * Implementation of local continuations based on switch() statement + * + * This implementation of local continuations uses the C switch() + * statement to resume execution of a function somewhere inside the + * function's body. The implementation is based on the fact that + * switch() statements are able to jump directly into the bodies of + * control structures such as if() or while() statements. + * + * This implementation borrows heavily from Simon Tatham's coroutines + * implementation in C: + * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + * + * --------------------------------------------------------------------------- + * + * The code in this file stems from Contiki's implementation of protothreads as + * listed above. This adaptation changes the API to work better with Pybricks. + */ + +#ifndef _PBIO_OS_H_ +#define _PBIO_OS_H_ + +#include +#include +#include + +/** + * Millisecond timer. + */ +typedef struct pbio_os_timer_t { + uint32_t start; + uint32_t duration; +} pbio_os_timer_t; + +void pbio_os_timer_set(pbio_os_timer_t *timer, uint32_t duration); + +bool pbio_os_timer_is_expired(pbio_os_timer_t *timer); + + +/** + * WARNING! LC implementation using switch() does not work if an + * PB_LC_SET() is done within another switch() statement! + */ + +#define PB_LC_FALLTHROUGH __attribute__((fallthrough)); +#define PB_LC_INIT(state) *state = 0; +#define PB_LC_RESUME(state) switch (*state) { case 0: +#define PB_LC_SET(state) *state = __LINE__; PB_LC_FALLTHROUGH; case __LINE__: +#define PB_LC_END() } + +/** + * Protothread state. Effectively the line number in the current file where it + * yields so it can jump there to resume later. + */ +typedef uint32_t pbio_os_state_t; + +typedef pbio_error_t (*pbio_os_process_func_t)(pbio_os_state_t *state); + +typedef struct _pbio_os_process_t pbio_os_process_t; + +/** + * A process. + */ +struct _pbio_os_process_t { + /** + * Pointer to the next process in the list. + */ + pbio_os_process_t *next; + /** + * State of the protothread. + */ + pbio_os_state_t state; + /** + * The protothread. + */ + pbio_os_process_func_t func; + /** + * Most recent result of running one iteration of the protothread. + */ + pbio_error_t err; +}; + +/** + * Initialize a protothread. + * + * Initializes a protothread. Initialization must be done prior to + * starting to execute the protothread. + * + * @param [in] state Protothread state. + */ +#define ASYNC_INIT(state) PB_LC_INIT(state) + +/** + * Declare the start of a protothread inside the C function + * implementing the protothread. + * + * This macro is used to declare the starting point of a + * protothread. It should be placed at the start of the function in + * which the protothread runs. All C statements above the ASYNC_BEGIN() + * invocation will be executed each time the protothread is scheduled. + * + * @param [in] state Protothread state. + */ +#define ASYNC_BEGIN(state) { PB_LC_RESUME(state) + +/** + * Declare the end of a protothread and returns with given code. + * + * This macro is used for declaring that a protothread ends. It must + * always be used together with a matching ASYNC_BEGIN() macro. + * + * NB: In contrast to Contiki, this does not call ASYNC_INIT() before exiting. + * + * @param [in] err Error code to return. + */ +#define ASYNC_END(err) PB_LC_END(); return err; } + +/** + * Yields the protothread while the specified condition is true. + * + * @param [in] state Protothread state. + * @param [in] condition The condition. + */ +#define AWAIT_WHILE(state, condition) \ + do { \ + PB_LC_SET(state); \ + if (condition) { \ + return PBIO_ERROR_AGAIN; \ + } \ + } while (0) + + +#define AWAIT(state, child, statement) \ + do { \ + ASYNC_INIT((child)); \ + PB_LC_SET(state); \ + if ((statement) == PBIO_ERROR_AGAIN) { \ + return PBIO_ERROR_AGAIN; \ + } \ + } while (0) + +#define AWAIT_MS(state, timer, duration) \ + do { \ + pbio_os_timer_set(timer, duration); \ + PB_LC_SET(state); \ + if (!pbio_os_timer_is_expired(timer)) { \ + return PBIO_ERROR_AGAIN; \ + } \ + } while (0) \ + +bool pbio_os_run_processes_once(void); + +void pbio_os_run_while_idle(void); + +void pbio_os_request_poll(void); + +void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t func); + +/** + * Disables interrupts and returns the previous interrupt state. + * + * Must be implemented by the platform. + * + * @return The previous interrupt state. + */ +uint32_t pbio_os_hook_disable_irq(void); + +/** + * Enables interrupts. + * + * Must be implemented by the platform. + * + * @param [in] flags The previous interrupt state. + */ +void pbio_os_hook_enable_irq(uint32_t flags); + +/** + * Waits for an interrupt. + * + * Must be implemented by the platform. + */ +void pbio_os_hook_wait_for_interrupt(void); + +#endif // _PBIO_OS_H_ diff --git a/lib/pbio/src/os.c b/lib/pbio/src/os.c new file mode 100644 index 000000000..3ebbea786 --- /dev/null +++ b/lib/pbio/src/os.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors + +#include + +#include +#include + +#include +#include + +/** + * Sets the timer to expire after the specified duration. + * + * The 1ms interrupt polls all processes, so no special events are needed. + * + * @param timer The timer to initialize. + * @param duration The duration in milliseconds. + */ +void pbio_os_timer_set(pbio_os_timer_t *timer, uint32_t duration) { + timer->start = pbdrv_clock_get_ms(); + timer->duration = duration; +} + +/** + * Whether the timer has expired. + * + * @param timer The timer to check. + * @return Whether the timer has expired. + */ +bool pbio_os_timer_is_expired(pbio_os_timer_t *timer) { + return pbdrv_clock_get_ms() - timer->start >= timer->duration; +} + +/** + * Whether a poll request is pending. + */ +static bool poll_request_is_pending = false; + +/** + * Request that the event loop polls all processes. + */ +void pbio_os_request_poll(void) { + poll_request_is_pending = true; +} + +static pbio_os_process_t *process_list = NULL; + +/** + * Starts a process. + * + * NB: Processes can be started only once. They cannot be restarted. + * + * @param process The process to start. + * @param func The process thread function. + */ +void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t func) { + + // Add the new process to the end of the list. + pbio_os_process_t *last = process_list; + if (!last) { + process_list = process; + } else { + while (last->next) { + last = last->next; + } + last->next = process; + } + + // Initialize the process. + process->func = func; + process->next = NULL; + process->err = PBIO_ERROR_AGAIN; + process->state = 0; +} + +/** + * Drives the event loop once: Runs one iteration of all processes. + * + * Can be used in hooks from blocking loops. + * + * @return Whether there are more pending poll requests. + */ +bool pbio_os_run_processes_once(void) { + + // DELETEME: Legacy hook to drive pbio event loop until all processes migrated. + extern int pbio_do_one_event(void); + bool pbio_event_pending = pbio_do_one_event(); + + if (!poll_request_is_pending) { + + // DELETEME: Legacy pbio event loop hook until all processes migrated. + if (pbio_event_pending) { + return true; + } + + return false; + } + + poll_request_is_pending = false; + + pbio_os_process_t *process = process_list; + while (process) { + // Run one iteration of the process if not yet completed or errored. + if (process->err == PBIO_ERROR_AGAIN) { + process->err = process->func(&process->state); + } + process = process->next; + } + + // Poll requests may have been set while running the processes. + return poll_request_is_pending || pbio_event_pending; +} + +/** + * Drives the event loop from code that is waiting or sleeping. + * + * Expected to be called in a loop. This will keep running the event loop but + * enter a low power mode when possible. It will sleep for at most one + * millisecond. + */ +void pbio_os_run_while_idle(void) { + + // Run the event loop until there is no more pending poll request. + while (pbio_os_run_processes_once()) { + ; + } + + // It is possible that an interrupt occurs *just now* and sets the + // poll_request_is_pending flag. If not, we can call wait_for_interrupt(), + // which still wakes up the CPU on interrupt even though interrupts are + // otherwise disabled. + uint32_t irq_flags = pbio_os_hook_disable_irq(); + + // DELETEME: Legacy hook for pbio event loop that plays the same role as + // the pending flag. Here it ensures we don't enter sleep if there are + // pending events. Can be removed when all processes are migrated. + extern int process_nevents(void); + if (process_nevents()) { + poll_request_is_pending = true; + } + + if (!poll_request_is_pending) { + pbio_os_hook_wait_for_interrupt(); + } + pbio_os_hook_enable_irq(irq_flags); + + // Since this function is expected to be called in a loop, pending events + // will be handled right away on the next entry. If not, then they will be + // handled "soon". +} diff --git a/lib/pbio/test/test-pbio.c b/lib/pbio/test/test-pbio.c index 80cf66bef..4356a1e72 100644 --- a/lib/pbio/test/test-pbio.c +++ b/lib/pbio/test/test-pbio.c @@ -91,6 +91,16 @@ struct testcase_setup_t pbio_test_setup = { .cleanup_fn = cleanup, }; +uint32_t pbio_os_hook_disable_irq(void) { + return 0; +} + +void pbio_os_hook_enable_irq(uint32_t flags) { +} + +void pbio_os_hook_wait_for_interrupt(void) { +} + extern struct testcase_t pbdrv_bluetooth_tests[]; extern struct testcase_t pbdrv_pwm_tests[]; extern struct testcase_t pbio_angle_tests[]; From 78977342bfdb5e2f04cbec3ef2d4699c3ec2e8f5 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 13 Mar 2025 13:47:01 +0100 Subject: [PATCH 02/19] pbio/sys/main: Wait with WFI. Idling with WFI was previously implemented in the MicroPython HAL. Now that we moved it to pbio/os in a platform agnostic way, we can fix this longstanding open REVISIT note. --- lib/pbio/sys/main.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/pbio/sys/main.c b/lib/pbio/sys/main.c index fd848019e..b64a7b736 100644 --- a/lib/pbio/sys/main.c +++ b/lib/pbio/sys/main.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -93,10 +94,8 @@ int main(int argc, char **argv) { pbsys_main_program_request_start(PBIO_PYBRICKS_USER_PROGRAM_ID_REPL, PBSYS_MAIN_PROGRAM_START_REQUEST_TYPE_BOOT); #endif - // REVISIT: this can be long waiting, so we could do a more efficient - // wait (i.e. __WFI() on embedded system) - while (pbio_do_one_event()) { - } + // Drives all processes while we wait for user input. + pbio_os_run_while_idle(); if (!pbsys_main_program_start_requested()) { continue; @@ -109,7 +108,7 @@ int main(int argc, char **argv) { // Handle pending events triggered by the status change, such as // starting status light animation. - while (pbio_do_one_event()) { + while (pbio_os_run_processes_once()) { } // Run the main application. From f4e1512d6398eaa0d5833421e2a3a53d394eefce Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 13 Mar 2025 15:06:03 +0100 Subject: [PATCH 03/19] pbio/drv/charger: Convert to pbio os thread. This is a simple example to show that we can convert the processes one at a time. --- lib/pbio/drv/charger/charger_mp2639a.c | 35 +++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/pbio/drv/charger/charger_mp2639a.c b/lib/pbio/drv/charger/charger_mp2639a.c index 16637b450..32cf45cb1 100644 --- a/lib/pbio/drv/charger/charger_mp2639a.c +++ b/lib/pbio/drv/charger/charger_mp2639a.c @@ -27,14 +27,13 @@ #include #include #include +#include #include "../core.h" #include "charger_mp2639a.h" #define platform pbdrv_charger_mp2639a_platform_data -PROCESS(pbdrv_charger_mp2639a_process, "MP2639A"); - #if PBDRV_CONFIG_CHARGER_MP2639A_MODE_PWM static pbdrv_pwm_dev_t *mode_pwm; #endif @@ -45,11 +44,6 @@ static pbdrv_pwm_dev_t *iset_pwm; static pbdrv_charger_status_t pbdrv_charger_status; static bool mode_pin_is_low; -void pbdrv_charger_init(void) { - pbdrv_init_busy_up(); - process_start(&pbdrv_charger_mp2639a_process); -} - pbio_error_t pbdrv_charger_get_current_now(uint16_t *current) { pbio_error_t err = pbdrv_adc_get_ch(platform.ib_adc_ch, current); if (err != PBIO_SUCCESS) { @@ -182,18 +176,23 @@ static bool read_chg(void) { #define PBDRV_CHARGER_MP2639A_CHARGE_TIMEOUT_MS (60 * 60 * 1000) #define PBDRV_CHARGER_MP2639A_CHARGE_PAUSE_MS (30 * 1000) -PROCESS_THREAD(pbdrv_charger_mp2639a_process, ev, data) { - PROCESS_BEGIN(); +static pbio_os_process_t pbdrv_charger_mp2639a_process; + +pbio_error_t pbdrv_charger_mp2639a_process_thread(pbio_os_state_t *state) { + + static pbio_os_timer_t timer; + + ASYNC_BEGIN(state); #if PBDRV_CONFIG_CHARGER_MP2639A_MODE_PWM while (pbdrv_pwm_get_dev(platform.mode_pwm_id, &mode_pwm) != PBIO_SUCCESS) { - PROCESS_PAUSE(); + AWAIT_MS(state, &timer, 1); } #endif #if PBDRV_CONFIG_CHARGER_MP2639A_ISET_PWM while (pbdrv_pwm_get_dev(platform.iset_pwm_id, &iset_pwm) != PBIO_SUCCESS) { - PROCESS_PAUSE(); + AWAIT_MS(state, &timer, 1); } #endif @@ -212,12 +211,10 @@ PROCESS_THREAD(pbdrv_charger_mp2639a_process, ev, data) { static bool chg_samples[7]; static uint8_t chg_index = 0; - static struct etimer timer; static uint32_t charge_count = 0; for (;;) { - etimer_set(&timer, PBDRV_CHARGER_MP2639A_STATUS_SAMPLE_TIME); - PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER && etimer_expired(&timer)); + AWAIT_MS(state, &timer, PBDRV_CHARGER_MP2639A_STATUS_SAMPLE_TIME); // Enable charger chip based on USB state. We don't need to disable it // on charger fault since the chip will automatically disable itself. @@ -275,13 +272,17 @@ PROCESS_THREAD(pbdrv_charger_mp2639a_process, ev, data) { if (charge_count > (PBDRV_CHARGER_MP2639A_CHARGE_TIMEOUT_MS / PBDRV_CHARGER_MP2639A_STATUS_SAMPLE_TIME)) { pbdrv_charger_status = PBDRV_CHARGER_STATUS_DISCHARGE; pbdrv_charger_enable(false, PBDRV_CHARGER_LIMIT_NONE); - etimer_set(&timer, PBDRV_CHARGER_MP2639A_CHARGE_PAUSE_MS); - PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&timer)); + AWAIT_MS(state, &timer, PBDRV_CHARGER_MP2639A_CHARGE_PAUSE_MS); charge_count = 0; } } - PROCESS_END(); + ASYNC_END(PBIO_SUCCESS); +} + +void pbdrv_charger_init(void) { + pbdrv_init_busy_up(); + pbio_os_start_process(&pbdrv_charger_mp2639a_process, pbdrv_charger_mp2639a_process_thread); } #endif // PBDRV_CONFIG_CHARGER_MP2639A From ea5f5cfc8dcf3b6877017609a0576f1fb17888da Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Fri, 14 Mar 2025 11:33:57 +0100 Subject: [PATCH 04/19] pbio/os: Add context to process. When using the same thread for multiple processes it can be useful to have a reference to the state that belongs to that instance. This avoids having to use several cases of CONTAINER_OF to get to the parent of a state, which is not always available. This also brings it one step closer to pbio tasks, which we might model in this way too. --- lib/pbio/drv/charger/charger_mp2639a.c | 4 ++-- lib/pbio/include/pbio/os.h | 8 ++++++-- lib/pbio/src/os.c | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/pbio/drv/charger/charger_mp2639a.c b/lib/pbio/drv/charger/charger_mp2639a.c index 32cf45cb1..67bf53d3a 100644 --- a/lib/pbio/drv/charger/charger_mp2639a.c +++ b/lib/pbio/drv/charger/charger_mp2639a.c @@ -178,7 +178,7 @@ static bool read_chg(void) { static pbio_os_process_t pbdrv_charger_mp2639a_process; -pbio_error_t pbdrv_charger_mp2639a_process_thread(pbio_os_state_t *state) { +pbio_error_t pbdrv_charger_mp2639a_process_thread(pbio_os_state_t *state, void *context) { static pbio_os_timer_t timer; @@ -282,7 +282,7 @@ pbio_error_t pbdrv_charger_mp2639a_process_thread(pbio_os_state_t *state) { void pbdrv_charger_init(void) { pbdrv_init_busy_up(); - pbio_os_start_process(&pbdrv_charger_mp2639a_process, pbdrv_charger_mp2639a_process_thread); + pbio_os_start_process(&pbdrv_charger_mp2639a_process, pbdrv_charger_mp2639a_process_thread, NULL); } #endif // PBDRV_CONFIG_CHARGER_MP2639A diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index fd5492741..e9308d376 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -87,7 +87,7 @@ bool pbio_os_timer_is_expired(pbio_os_timer_t *timer); */ typedef uint32_t pbio_os_state_t; -typedef pbio_error_t (*pbio_os_process_func_t)(pbio_os_state_t *state); +typedef pbio_error_t (*pbio_os_process_func_t)(pbio_os_state_t *state, void *context); typedef struct _pbio_os_process_t pbio_os_process_t; @@ -107,6 +107,10 @@ struct _pbio_os_process_t { * The protothread. */ pbio_os_process_func_t func; + /** + * Context passed on each call to the protothread. + */ + void *context; /** * Most recent result of running one iteration of the protothread. */ @@ -187,7 +191,7 @@ void pbio_os_run_while_idle(void); void pbio_os_request_poll(void); -void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t func); +void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t func, void *context); /** * Disables interrupts and returns the previous interrupt state. diff --git a/lib/pbio/src/os.c b/lib/pbio/src/os.c index 3ebbea786..8854c21cc 100644 --- a/lib/pbio/src/os.c +++ b/lib/pbio/src/os.c @@ -53,8 +53,9 @@ static pbio_os_process_t *process_list = NULL; * * @param process The process to start. * @param func The process thread function. + * @param context The context to pass to the process. */ -void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t func) { +void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t func, void *context) { // Add the new process to the end of the list. pbio_os_process_t *last = process_list; @@ -69,6 +70,7 @@ void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t fu // Initialize the process. process->func = func; + process->context = context; process->next = NULL; process->err = PBIO_ERROR_AGAIN; process->state = 0; @@ -103,7 +105,7 @@ bool pbio_os_run_processes_once(void) { while (process) { // Run one iteration of the process if not yet completed or errored. if (process->err == PBIO_ERROR_AGAIN) { - process->err = process->func(&process->state); + process->err = process->func(&process->state, process->context); } process = process->next; } From 9875ca0ae1ab3aef848fec204bfd87c022d653f7 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Fri, 14 Mar 2025 12:30:53 +0100 Subject: [PATCH 05/19] pbio/os: Implement thread reset and race. --- lib/pbio/drv/charger/charger_mp2639a.c | 2 +- lib/pbio/include/pbio/os.h | 29 +++++++++++++++++++++- lib/pbio/src/os.c | 34 ++++++++++++++++++++++---- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/lib/pbio/drv/charger/charger_mp2639a.c b/lib/pbio/drv/charger/charger_mp2639a.c index 67bf53d3a..f7d90f87e 100644 --- a/lib/pbio/drv/charger/charger_mp2639a.c +++ b/lib/pbio/drv/charger/charger_mp2639a.c @@ -282,7 +282,7 @@ pbio_error_t pbdrv_charger_mp2639a_process_thread(pbio_os_state_t *state, void * void pbdrv_charger_init(void) { pbdrv_init_busy_up(); - pbio_os_start_process(&pbdrv_charger_mp2639a_process, pbdrv_charger_mp2639a_process_thread, NULL); + pbio_os_process_start(&pbdrv_charger_mp2639a_process, pbdrv_charger_mp2639a_process_thread, NULL); } #endif // PBDRV_CONFIG_CHARGER_MP2639A diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index e9308d376..32d657380 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -166,6 +166,19 @@ struct _pbio_os_process_t { } \ } while (0) +/** + * Yields the protothread until the specified condition is true. + * + * @param [in] state Protothread state. + * @param [in] condition The condition. + */ +#define AWAIT_UNTIL(state, condition) \ + do { \ + PB_LC_SET(state); \ + if (!(condition)) { \ + return PBIO_ERROR_AGAIN; \ + } \ + } while (0) #define AWAIT(state, child, statement) \ do { \ @@ -176,6 +189,16 @@ struct _pbio_os_process_t { } \ } while (0) +#define AWAIT_RACE(state, child1, child2, statement1, statement2) \ + do { \ + ASYNC_INIT((child1)); \ + ASYNC_INIT((child2)); \ + PB_LC_SET(state); \ + if ((statement1) == PBIO_ERROR_AGAIN && (statement2) == PBIO_ERROR_AGAIN) { \ + return PBIO_ERROR_AGAIN; \ + } \ + } while (0) + #define AWAIT_MS(state, timer, duration) \ do { \ pbio_os_timer_set(timer, duration); \ @@ -191,7 +214,11 @@ void pbio_os_run_while_idle(void); void pbio_os_request_poll(void); -void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t func, void *context); +pbio_error_t pbio_port_process_none_thread(pbio_os_state_t *state, void *context); + +void pbio_os_process_start(pbio_os_process_t *process, pbio_os_process_func_t func, void *context); + +void pbio_os_process_reset(pbio_os_process_t *process, pbio_os_process_func_t func); /** * Disables interrupts and returns the previous interrupt state. diff --git a/lib/pbio/src/os.c b/lib/pbio/src/os.c index 8854c21cc..c7ca39bae 100644 --- a/lib/pbio/src/os.c +++ b/lib/pbio/src/os.c @@ -47,15 +47,23 @@ void pbio_os_request_poll(void) { static pbio_os_process_t *process_list = NULL; /** - * Starts a process. + * Placeholder thread that does nothing. * - * NB: Processes can be started only once. They cannot be restarted. + * @param [in] state The protothread state. + * @param [in] context The context. + */ +pbio_error_t pbio_port_process_none_thread(pbio_os_state_t *state, void *context) { + return PBIO_ERROR_AGAIN; +} + +/** + * Adds a process to the list of processes to run and starts it soon. * * @param process The process to start. * @param func The process thread function. * @param context The context to pass to the process. */ -void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t func, void *context) { +void pbio_os_process_start(pbio_os_process_t *process, pbio_os_process_func_t func, void *context) { // Add the new process to the end of the list. pbio_os_process_t *last = process_list; @@ -69,11 +77,27 @@ void pbio_os_start_process(pbio_os_process_t *process, pbio_os_process_func_t fu } // Initialize the process. - process->func = func; - process->context = context; process->next = NULL; + process->context = context; + + pbio_os_process_reset(process, func); +} + +/** + * Resets an existing process to the initial state with a new function and context. + * + * @param process The process to start. + * @param func The process thread function. Choose NULL if it does not need changing. + */ +void pbio_os_process_reset(pbio_os_process_t *process, pbio_os_process_func_t func) { process->err = PBIO_ERROR_AGAIN; process->state = 0; + if (func) { + process->func = func; + } + + // Request a poll to start the process soon, running to its first yield. + pbio_os_request_poll(); } /** From f076e012cb10b299472280c78258842b9d191e55 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Fri, 14 Mar 2025 14:40:29 +0100 Subject: [PATCH 06/19] pbio/os: Add await once. Similar to PROCESS_PAUSE() but without posting an event to itself like Contiki does. It is basically the same as awaiting 0ms, but without the need to allocate a timer. Also restore this for the charger, which used PROCESS_PAUSE() here initially. --- lib/pbio/drv/charger/charger_mp2639a.c | 4 +-- lib/pbio/include/pbio/os.h | 36 +++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/lib/pbio/drv/charger/charger_mp2639a.c b/lib/pbio/drv/charger/charger_mp2639a.c index f7d90f87e..0e5797245 100644 --- a/lib/pbio/drv/charger/charger_mp2639a.c +++ b/lib/pbio/drv/charger/charger_mp2639a.c @@ -186,13 +186,13 @@ pbio_error_t pbdrv_charger_mp2639a_process_thread(pbio_os_state_t *state, void * #if PBDRV_CONFIG_CHARGER_MP2639A_MODE_PWM while (pbdrv_pwm_get_dev(platform.mode_pwm_id, &mode_pwm) != PBIO_SUCCESS) { - AWAIT_MS(state, &timer, 1); + AWAIT_ONCE(state); } #endif #if PBDRV_CONFIG_CHARGER_MP2639A_ISET_PWM while (pbdrv_pwm_get_dev(platform.iset_pwm_id, &iset_pwm) != PBIO_SUCCESS) { - AWAIT_MS(state, &timer, 1); + AWAIT_ONCE(state); } #endif diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index 32d657380..e3db3bd5b 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -138,7 +138,7 @@ struct _pbio_os_process_t { * * @param [in] state Protothread state. */ -#define ASYNC_BEGIN(state) { PB_LC_RESUME(state) +#define ASYNC_BEGIN(state) { char do_yield_now = 0; if (do_yield_now) {;} PB_LC_RESUME(state) /** * Declare the end of a protothread and returns with given code. @@ -180,6 +180,13 @@ struct _pbio_os_process_t { } \ } while (0) +/** + * Awaits a protothread until it is done. + * + * @param [in] state Protothread state. + * @param [in] child Protothread state of the child. + * @param [in] statement The statement to await. + */ #define AWAIT(state, child, statement) \ do { \ ASYNC_INIT((child)); \ @@ -189,6 +196,15 @@ struct _pbio_os_process_t { } \ } while (0) +/** + * Awaits two protothreads until one of them is done. + * + * @param [in] state Protothread state. + * @param [in] child1 Protothread state of the first child. + * @param [in] child2 Protothread state of the second child. + * @param [in] statement1 The first statement to await. + * @param [in] statement2 The second statement to await. + */ #define AWAIT_RACE(state, child1, child2, statement1, statement2) \ do { \ ASYNC_INIT((child1)); \ @@ -199,6 +215,24 @@ struct _pbio_os_process_t { } \ } while (0) +/** + * Yields the protothread once and polls to request handling again immediately. + * + * Should be used sparingly as it can cause busy waiting. Processes will keep + * running, but there is always another event pending. + * + * @param [in] state Protothread state. + */ +#define AWAIT_ONCE(state) \ + do { \ + do_yield_now = 1; \ + PB_LC_SET(state); \ + if (do_yield_now) { \ + pbio_os_request_poll(); \ + return PBIO_ERROR_AGAIN; \ + } \ + } while (0) + #define AWAIT_MS(state, timer, duration) \ do { \ pbio_os_timer_set(timer, duration); \ From fe1245504f4fb557d351e4106905ed8e7185ce9d Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 17 Mar 2025 20:57:23 +0100 Subject: [PATCH 07/19] pbio/drv/pwm_lp50xx: Use pbio os. --- lib/pbio/drv/pwm/pwm_lp50xx_stm32.c | 146 +++++++++++++--------------- 1 file changed, 67 insertions(+), 79 deletions(-) diff --git a/lib/pbio/drv/pwm/pwm_lp50xx_stm32.c b/lib/pbio/drv/pwm/pwm_lp50xx_stm32.c index 2e01a55a1..49b6990ec 100644 --- a/lib/pbio/drv/pwm/pwm_lp50xx_stm32.c +++ b/lib/pbio/drv/pwm/pwm_lp50xx_stm32.c @@ -12,11 +12,11 @@ #include #include -#include #include STM32_HAL_H #include #include +#include #include #include "../core.h" @@ -84,8 +84,8 @@ typedef struct { DMA_HandleTypeDef hdma_rx; /** HAL Tx DMA data */ DMA_HandleTypeDef hdma_tx; - /** Protothread */ - struct pt pt; + /** Process for this device */ + pbio_os_process_t process; /** Pointer to generic PWM device instance */ pbdrv_pwm_dev_t *pwm; /** PWM values */ @@ -94,7 +94,6 @@ typedef struct { bool changed; } pbdrv_pwm_lp50xx_stm32_priv_t; -PROCESS(pwm_lp50xx_stm32, "pwm_lp50xx_stm32"); static pbdrv_pwm_lp50xx_stm32_priv_t dev_priv[PBDRV_CONFIG_PWM_LP50XX_STM32_NUM_DEV]; static pbio_error_t pbdrv_pwm_lp50xx_stm32_set_duty(pbdrv_pwm_dev_t *dev, uint32_t ch, uint32_t value) { @@ -107,7 +106,7 @@ static pbio_error_t pbdrv_pwm_lp50xx_stm32_set_duty(pbdrv_pwm_dev_t *dev, uint32 // (the data sheet says 12-bit PWM but the I2C registers are only 8-bit). priv->values[ch] = value >> 8; priv->changed = true; - process_poll(&pwm_lp50xx_stm32); + pbio_os_request_poll(); return PBIO_SUCCESS; } @@ -116,6 +115,66 @@ static const pbdrv_pwm_driver_funcs_t pbdrv_pwm_lp50xx_stm32_funcs = { .set_duty = pbdrv_pwm_lp50xx_stm32_set_duty, }; +static pbio_error_t pbdrv_pwm_lp50xx_stm32_process_thread(pbio_os_state_t *state, void *context) { + + pbdrv_pwm_lp50xx_stm32_priv_t *priv = context; + + ASYNC_BEGIN(state); + + // Need to allow all drivers to init first. + AWAIT_ONCE(state); + + static const struct { + uint8_t reg; + uint8_t values[11]; + } __attribute__((packed)) init_data = { + .reg = DEVICE_CONFIG0, + .values = { + [DEVICE_CONFIG0] = DEVICE_CONFIG0_CHIP_EN, + [DEVICE_CONFIG1] = DEVICE_CONFIG1_POWER_SAVE_EN | DEVICE_CONFIG1_PWM_DITHERING_EN | DEVICE_CONFIG1_AUTO_INCR_EN, + [LED_CONFIG0] = 0, + [BANK_BRIGHTNESS] = 0, + [BANK_A_COLOR] = 0, + [BANK_B_COLOR] = 0, + [BANK_C_COLOR] = 0, + // Official LEGO firmware has LED0_BRIGHTNESS set to 255 and LED1_BRIGHTNESS + // set to 190 but then divides the incoming PWM duty cycle by 5. By doing + // the divide by 5 here, we end up with the same max brightness while + // allowing full dynamic range of the PWM duty cycle. + [LED0_BRIGHTNESS] = 51, + [LED1_BRIGHTNESS] = 38, + [LED2_BRIGHTNESS] = 0, + [LED3_BRIGHTNESS] = 0, + } + }; + + HAL_FMPI2C_Master_Transmit_DMA(&priv->hfmpi2c, I2C_ADDR, (void *)&init_data, sizeof(init_data)); + AWAIT_UNTIL(state, HAL_FMPI2C_GetState(&priv->hfmpi2c) == HAL_FMPI2C_STATE_READY); + + // initialization is finished so consumers can use this PWM device now. + priv->pwm->funcs = &pbdrv_pwm_lp50xx_stm32_funcs; + pbdrv_init_busy_down(); + + for (;;) { + AWAIT_UNTIL(state, priv->changed); + + static struct { + uint8_t reg; + uint8_t values[LP50XX_NUM_CH]; + } __attribute__((packed)) color_data = { + .reg = OUT0_COLOR, + }; + + memcpy(color_data.values, priv->values, LP50XX_NUM_CH); + HAL_FMPI2C_Master_Transmit_DMA(&priv->hfmpi2c, I2C_ADDR, (void *)&color_data, sizeof(color_data)); + priv->changed = false; + AWAIT_UNTIL(state, HAL_FMPI2C_GetState(&priv->hfmpi2c) == HAL_FMPI2C_STATE_READY); + } + + // Unreachable. + ASYNC_END(PBIO_ERROR_FAILED); +} + void pbdrv_pwm_lp50xx_stm32_init(pbdrv_pwm_dev_t *devs) { for (int i = 0; i < PBDRV_CONFIG_PWM_LP50XX_STM32_NUM_DEV; i++) { const pbdrv_pwm_lp50xx_stm32_platform_data_t *pdata = &pbdrv_pwm_lp50xx_stm32_platform_data[i]; @@ -186,68 +245,14 @@ void pbdrv_pwm_lp50xx_stm32_init(pbdrv_pwm_dev_t *devs) { HAL_NVIC_SetPriority(pdata->i2c_er_irq, 7, 3); HAL_NVIC_EnableIRQ(pdata->i2c_er_irq); - PT_INIT(&priv->pt); priv->pwm = pwm; pwm->pdata = pdata; pwm->priv = priv; + pbio_os_process_start(&priv->process, pbdrv_pwm_lp50xx_stm32_process_thread, priv); + // don't set funcs yet since we are not fully initialized pbdrv_init_busy_up(); } - - process_start(&pwm_lp50xx_stm32); -} - -static PT_THREAD(pbdrv_pwm_lp50xx_stm32_handle_event(pbdrv_pwm_lp50xx_stm32_priv_t * priv, process_event_t ev)) { - PT_BEGIN(&priv->pt); - - static const struct { - uint8_t reg; - uint8_t values[11]; - } __attribute__((packed)) init_data = { - .reg = DEVICE_CONFIG0, - .values = { - [DEVICE_CONFIG0] = DEVICE_CONFIG0_CHIP_EN, - [DEVICE_CONFIG1] = DEVICE_CONFIG1_POWER_SAVE_EN | DEVICE_CONFIG1_PWM_DITHERING_EN | DEVICE_CONFIG1_AUTO_INCR_EN, - [LED_CONFIG0] = 0, - [BANK_BRIGHTNESS] = 0, - [BANK_A_COLOR] = 0, - [BANK_B_COLOR] = 0, - [BANK_C_COLOR] = 0, - // Official LEGO firmware has LED0_BRIGHTNESS set to 255 and LED1_BRIGHTNESS - // set to 190 but then divides the incoming PWM duty cycle by 5. By doing - // the divide by 5 here, we end up with the same max brightness while - // allowing full dynamic range of the PWM duty cycle. - [LED0_BRIGHTNESS] = 51, - [LED1_BRIGHTNESS] = 38, - [LED2_BRIGHTNESS] = 0, - [LED3_BRIGHTNESS] = 0, - } - }; - - HAL_FMPI2C_Master_Transmit_DMA(&priv->hfmpi2c, I2C_ADDR, (void *)&init_data, sizeof(init_data)); - PT_WAIT_UNTIL(&priv->pt, HAL_FMPI2C_GetState(&priv->hfmpi2c) == HAL_FMPI2C_STATE_READY); - - // initialization is finished so consumers can use this PWM device now. - priv->pwm->funcs = &pbdrv_pwm_lp50xx_stm32_funcs; - pbdrv_init_busy_down(); - - for (;;) { - PT_WAIT_UNTIL(&priv->pt, priv->changed); - - static struct { - uint8_t reg; - uint8_t values[LP50XX_NUM_CH]; - } __attribute__((packed)) color_data = { - .reg = OUT0_COLOR, - }; - - memcpy(color_data.values, priv->values, LP50XX_NUM_CH); - HAL_FMPI2C_Master_Transmit_DMA(&priv->hfmpi2c, I2C_ADDR, (void *)&color_data, sizeof(color_data)); - priv->changed = false; - PT_WAIT_UNTIL(&priv->pt, HAL_FMPI2C_GetState(&priv->hfmpi2c) == HAL_FMPI2C_STATE_READY); - } - - PT_END(&priv->pt); } /** @@ -287,24 +292,7 @@ void pbdrv_pwm_lp50xx_stm32_i2c_er_irq(uint8_t index) { } void HAL_FMPI2C_MasterTxCpltCallback(FMPI2C_HandleTypeDef *hfmpi2c) { - process_poll(&pwm_lp50xx_stm32); -} - -PROCESS_THREAD(pwm_lp50xx_stm32, ev, data) { - PROCESS_BEGIN(); - - // need to allow all drivers to init first - PROCESS_PAUSE(); - - for (;;) { - for (int i = 0; i < PBDRV_CONFIG_PWM_LP50XX_STM32_NUM_DEV; i++) { - pbdrv_pwm_lp50xx_stm32_priv_t *priv = &dev_priv[i]; - pbdrv_pwm_lp50xx_stm32_handle_event(priv, ev); - } - PROCESS_WAIT_EVENT(); - } - - PROCESS_END(); + pbio_os_request_poll(); } #endif // PBDRV_CONFIG_PWM_LP50XX_STM32 From 2c7ae8982e0b8f3ef3f0c29d7dbf1a05f2a57dac Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Tue, 18 Mar 2025 14:29:09 +0100 Subject: [PATCH 08/19] pbio/platform/nxt: Use pbio os run hook. Otherwise we don't drive the new processes. --- lib/pbio/platform/nxt/platform.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pbio/platform/nxt/platform.c b/lib/pbio/platform/nxt/platform.c index 07ca67395..b0c0b71eb 100644 --- a/lib/pbio/platform/nxt/platform.c +++ b/lib/pbio/platform/nxt/platform.c @@ -9,6 +9,8 @@ #include #include #include +#include + #include #include #include @@ -75,13 +77,13 @@ static bool bluetooth_connect(void) { return false; } - pbio_do_one_event(); + pbio_os_run_processes_once(); } nx_bt_stream_open(connection_handle); } - pbio_do_one_event(); + pbio_os_run_processes_once(); } nx_display_clear(); @@ -131,7 +133,7 @@ void SystemInit(void) { goto out; } - pbio_do_one_event(); + pbio_os_run_processes_once(); } nx_display_string("Connected. REPL.\n"); From 5f738b139c1c6d994bc86c41442cd3383e2a4bad Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Tue, 18 Mar 2025 14:31:14 +0100 Subject: [PATCH 09/19] pbio/sys/core: Use pbio os run hook. Otherwise we don't drive the new processes. Fixes the light not blinking at the end because the newly updated LED process was not driven during pbsys deinit. --- lib/pbio/sys/core.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/pbio/sys/core.c b/lib/pbio/sys/core.c index 9449b96fd..c77e454d4 100644 --- a/lib/pbio/sys/core.c +++ b/lib/pbio/sys/core.c @@ -3,9 +3,8 @@ #include -#include - #include +#include #include #include @@ -51,7 +50,7 @@ void pbsys_init(void) { process_start(&pbsys_system_process); while (pbsys_init_busy()) { - pbio_do_one_event(); + pbio_os_run_processes_once(); } } @@ -64,6 +63,6 @@ void pbsys_deinit(void) { // Wait for all relevant pbsys processes to end, but at least 500 ms so we // see a shutdown animation even if the button is released sooner. while (pbsys_init_busy() || pbdrv_clock_get_ms() - start < 500) { - pbio_do_one_event(); + pbio_os_run_processes_once(); } } From 5345969c8730d18fed2a8a88c7cf5538e1ff0758 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Fri, 14 Mar 2025 15:33:18 +0100 Subject: [PATCH 10/19] pbio/port: Migrate to pbio os model. --- lib/pbio/drv/i2c/i2c_ev3.c | 9 +- lib/pbio/drv/uart/uart_debug_first_port.c | 41 +++--- lib/pbio/drv/uart/uart_ev3.c | 68 ++++----- lib/pbio/drv/uart/uart_stm32f0.c | 75 ++++------ lib/pbio/drv/uart/uart_stm32f4_ll_irq.c | 72 ++++----- lib/pbio/drv/uart/uart_stm32l4_ll_dma.c | 71 ++++----- lib/pbio/include/pbdrv/i2c.h | 4 +- lib/pbio/include/pbdrv/uart.h | 35 ++--- lib/pbio/include/pbio/port_dcm.h | 9 +- lib/pbio/include/pbio/port_interface.h | 2 - lib/pbio/include/pbio/port_lump.h | 29 ++-- lib/pbio/src/port.c | 84 ++++------- lib/pbio/src/port_dcm_ev3.c | 114 +++++++------- lib/pbio/src/port_dcm_pup.c | 51 +++---- lib/pbio/src/port_lump.c | 172 +++++++++++----------- lib/pbio/test/src/test_lump.c | 169 +++++++++------------ lib/pbio/test/test-pbio.c | 52 +++++++ lib/pbio/test/test-pbio.h | 8 +- pybricks/iodevices/pb_type_uart_device.c | 54 +++---- 19 files changed, 506 insertions(+), 613 deletions(-) diff --git a/lib/pbio/drv/i2c/i2c_ev3.c b/lib/pbio/drv/i2c/i2c_ev3.c index fc9aa6389..9a8648abe 100644 --- a/lib/pbio/drv/i2c/i2c_ev3.c +++ b/lib/pbio/drv/i2c/i2c_ev3.c @@ -33,12 +33,6 @@ struct _pbdrv_i2c_dev_t { /** Platform-specific data */ const pbdrv_i2c_ev3_platform_data_t *pdata; - /** - * Parent process that handles incoming data. - * - * All protothreads in this module run within that process. - */ - struct process *parent_process; // // TODO: i2c state goes here. // @@ -46,7 +40,7 @@ struct _pbdrv_i2c_dev_t { static pbdrv_i2c_dev_t i2c_devs[PBDRV_CONFIG_I2C_EV3_NUM_DEV]; -pbio_error_t pbdrv_i2c_get_instance(uint8_t id, struct process *parent_process, pbdrv_i2c_dev_t **i2c_dev) { +pbio_error_t pbdrv_i2c_get_instance(uint8_t id, pbdrv_i2c_dev_t **i2c_dev) { if (id >= PBDRV_CONFIG_I2C_EV3_NUM_DEV) { return PBIO_ERROR_INVALID_ARG; } @@ -56,7 +50,6 @@ pbio_error_t pbdrv_i2c_get_instance(uint8_t id, struct process *parent_process, return PBIO_ERROR_AGAIN; } *i2c_dev = dev; - (*i2c_dev)->parent_process = parent_process; return PBIO_SUCCESS; } diff --git a/lib/pbio/drv/uart/uart_debug_first_port.c b/lib/pbio/drv/uart/uart_debug_first_port.c index 55dc2a442..6147a43f4 100644 --- a/lib/pbio/drv/uart/uart_debug_first_port.c +++ b/lib/pbio/drv/uart/uart_debug_first_port.c @@ -6,12 +6,13 @@ #if PBDRV_CONFIG_UART_DEBUG_FIRST_PORT #include + +#include + #include #include #include -PROCESS(pbdrv_uart_debug_process, "UART Debug"); - #define BUF_SIZE (256) static uint8_t ring_buf[BUF_SIZE]; @@ -50,7 +51,7 @@ void pbdrv_uart_debug_printf(const char *format, ...) { } // Request print process to write out new data. - process_poll(&pbdrv_uart_debug_process); + pbio_os_request_poll(); } /** @@ -65,36 +66,29 @@ int32_t pbdrv_uart_debug_get_char(void) { return pbdrv_uart_get_char(debug_uart); } -void pbdrv_uart_debug_init(void) { - process_start(&pbdrv_uart_debug_process); -} +static pbio_os_process_t pbdrv_uart_debug_process; -PROCESS_THREAD(pbdrv_uart_debug_process, ev, data) { +static pbio_error_t pbdrv_uart_debug_process_thread(pbio_os_state_t *state, void *context) { - static struct pt child; + static pbio_os_state_t child; static pbio_error_t err; static size_t write_size; - PROCESS_BEGIN(); + ASYNC_BEGIN(state); - PROCESS_WAIT_EVENT_UNTIL({ - process_poll(&pbdrv_uart_debug_process); - pbdrv_uart_get_instance(0, &pbdrv_uart_debug_process, &debug_uart) == PBIO_SUCCESS; - }); + while (pbdrv_uart_get_instance(0, &debug_uart) != PBIO_SUCCESS) { + AWAIT_ONCE(state); + } pbdrv_uart_set_baud_rate(debug_uart, 115200); for (;;) { - - PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_POLL); - if (ring_head == ring_tail) { - continue; - } + AWAIT_UNTIL(state, ring_head != ring_tail); // Write up to the end of the buffer without wrapping. size_t end = ring_head > ring_tail ? ring_head: BUF_SIZE; write_size = end - ring_tail; - PROCESS_PT_SPAWN(&child, pbdrv_uart_write(&child, debug_uart, &ring_buf[ring_tail], write_size, 100, &err)); + AWAIT(state, &child, pbdrv_uart_write(&child, debug_uart, &ring_buf[ring_tail], write_size, 100)); ring_tail = (ring_tail + write_size) % BUF_SIZE; // Reset on failure. @@ -106,11 +100,16 @@ PROCESS_THREAD(pbdrv_uart_debug_process, ev, data) { // Poll to write again if not fully finished, i.e. when wrapping. if (ring_head != ring_tail) { - process_poll(&pbdrv_uart_debug_process); + pbio_os_request_poll(); } } - PROCESS_END(); + // Unreachable. + ASYNC_END(PBIO_ERROR_FAILED); +} + +void pbdrv_uart_debug_init(void) { + pbio_os_process_start(&pbdrv_uart_debug_process, pbdrv_uart_debug_process_thread, NULL); } #endif // PBDRV_CONFIG_UART_DEBUG_FIRST_PORT diff --git a/lib/pbio/drv/uart/uart_ev3.c b/lib/pbio/drv/uart/uart_ev3.c index 1b5b17bc6..7ea301ab4 100644 --- a/lib/pbio/drv/uart/uart_ev3.c +++ b/lib/pbio/drv/uart/uart_ev3.c @@ -16,6 +16,7 @@ #include #include +#include #include #include "../core.h" @@ -46,9 +47,9 @@ struct _pbdrv_uart_dev_t { /** Circular buffer for caching received bytes. */ struct ringbuf rx_buf; /** Timer for read timeout. */ - struct etimer read_timer; + pbio_os_timer_t read_timer; /** Timer for write timeout. */ - struct etimer write_timer; + pbio_os_timer_t write_timer; /** The buffer passed to the read function. */ uint8_t *read_buf; /** The length of read_buf in bytes. */ @@ -61,18 +62,12 @@ struct _pbdrv_uart_dev_t { uint8_t write_length; /** The current position in write_buf. */ volatile uint8_t write_pos; - /** - * Parent process that handles incoming data. - * - * All protothreads in this module run within that process. - */ - struct process *parent_process; }; static pbdrv_uart_dev_t uart_devs[PBDRV_CONFIG_UART_EV3_NUM_UART]; static uint8_t pbdrv_uart_rx_data[PBDRV_CONFIG_UART_EV3_NUM_UART][RX_DATA_SIZE]; -pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, pbdrv_uart_dev_t **uart_dev) { +pbio_error_t pbdrv_uart_get_instance(uint8_t id, pbdrv_uart_dev_t **uart_dev) { if (id >= PBDRV_CONFIG_UART_EV3_NUM_UART) { return PBIO_ERROR_INVALID_ARG; } @@ -81,7 +76,6 @@ pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, // has not been initialized yet return PBIO_ERROR_AGAIN; } - dev->parent_process = parent_process; *uart_dev = dev; return PBIO_SUCCESS; } @@ -90,13 +84,12 @@ int32_t pbdrv_uart_get_char(pbdrv_uart_dev_t *uart) { return ringbuf_get(&uart->rx_buf); } -PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); if (uart->read_buf) { - *err = PBIO_ERROR_BUSY; - PT_EXIT(pt); + return PBIO_ERROR_BUSY; } uart->read_buf = msg; @@ -104,11 +97,11 @@ PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, u uart->read_pos = 0; if (timeout) { - etimer_set(&uart->read_timer, timeout); + pbio_os_timer_set(&uart->read_timer, timeout); } // Await completion or timeout. - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ // On every re-entry to the async read, drain the ring buffer // into the current read buffer. This ensures that we use // all available data if there have been multiple polls since our last @@ -121,19 +114,16 @@ PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, u } uart->read_buf[uart->read_pos++] = c; } - uart->read_pos == uart->read_length || (timeout && etimer_expired(&uart->read_timer)); + uart->read_pos == uart->read_length || (timeout && pbio_os_timer_is_expired(&uart->read_timer)); })); - // Set exit status based on completion condition. - if ((timeout && etimer_expired(&uart->read_timer))) { - *err = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart->read_timer); - *err = PBIO_SUCCESS; - } uart->read_buf = NULL; - PT_END(pt); + if ((timeout && pbio_os_timer_is_expired(&uart->read_timer))) { + return PBIO_ERROR_TIMEDOUT; + } + + ASYNC_END(PBIO_SUCCESS); } /** @@ -175,7 +165,7 @@ static bool pbdrv_uart_can_write(pbdrv_uart_dev_t *uart) { // interrupt clears the TX_EMPTY interrupt, which is not re-triggered. // Since UART writes on EV3 are limited to small messages like sensor // mode changes, this is acceptable. - process_poll(uart->parent_process); + pbio_os_request_poll(); return UARTSpaceAvail(pdata->base_address); } @@ -184,14 +174,13 @@ static bool pbdrv_uart_can_write(pbdrv_uart_dev_t *uart) { return pbdrv_uart_ev3_pru_can_write(pdata->peripheral_id); } -PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); // Can only write one thing at once. if (uart->write_buf) { - *err = PBIO_ERROR_BUSY; - PT_EXIT(pt); + return PBIO_ERROR_BUSY; } uart->write_buf = msg; @@ -199,11 +188,11 @@ PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uart->write_pos = 0; if (timeout) { - etimer_set(&uart->write_timer, timeout); + pbio_os_timer_set(&uart->write_timer, timeout); } // Write one byte at a time until all bytes are written. - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ // Try to write one byte if any are remaining. if (uart->write_pos < uart->write_length) { if (pbdrv_uart_try_to_write_byte(uart, uart->write_buf[uart->write_pos])) { @@ -212,19 +201,16 @@ PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, } // Completion on transmission of whole message and finishing writing or timeout. - (pbdrv_uart_can_write(uart) && uart->write_pos == uart->write_length) || (timeout && etimer_expired(&uart->write_timer)); + (pbdrv_uart_can_write(uart) && uart->write_pos == uart->write_length) || (timeout && pbio_os_timer_is_expired(&uart->write_timer)); })); uart->write_buf = NULL; - if ((timeout && etimer_expired(&uart->write_timer))) { - *err = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart->write_timer); - *err = PBIO_SUCCESS; + if (timeout && pbio_os_timer_is_expired(&uart->write_timer)) { + return PBIO_ERROR_TIMEDOUT; } - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart, uint32_t baud) { @@ -280,7 +266,7 @@ void pbdrv_uart_ev3_hw_handle_irq(pbdrv_uart_dev_t *uart) { // has no awareness of the expected length of the read operation. This is // done outside of the if statements above. We can do that since write IRQs // are not handled here. - process_poll(uart->parent_process); + pbio_os_request_poll(); } @@ -297,7 +283,7 @@ void pbdrv_uart_ev3_pru_handle_irq(pbdrv_uart_dev_t *uart) { while (pbdrv_uart_ev3_pru_read_bytes(uart->pdata->peripheral_id, &rx, 1)) { ringbuf_put(&uart->rx_buf, rx); } - process_poll(uart->parent_process); + pbio_os_request_poll(); } void pbdrv_uart_ev3_handle_irq(uint8_t id) { diff --git a/lib/pbio/drv/uart/uart_stm32f0.c b/lib/pbio/drv/uart/uart_stm32f0.c index dfd727b99..dd0dd71a1 100644 --- a/lib/pbio/drv/uart/uart_stm32f0.c +++ b/lib/pbio/drv/uart/uart_stm32f0.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2018-2020 The Pybricks Authors +// Copyright (c) 2018-2025 The Pybricks Authors // This driver is for UARTs on STM32F0 MCUs. It provides async read and write // functions for sending and receive data and allows changing the baud rate. @@ -19,6 +19,7 @@ #include #include +#include #include #include "../core.h" @@ -39,21 +40,15 @@ struct _pbdrv_uart_dev_t { uint8_t *tx_buf; uint8_t tx_buf_size; uint8_t tx_buf_index; - struct etimer rx_timer; - struct etimer tx_timer; + pbio_os_timer_t rx_timer; + pbio_os_timer_t tx_timer; uint8_t irq; bool initialized; - /** - * Parent process that handles incoming data. - * - * All protothreads in this module run within that process. - */ - struct process *parent_process; }; static pbdrv_uart_dev_t uart_devs[PBDRV_CONFIG_UART_STM32F0_NUM_UART]; -pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, pbdrv_uart_dev_t **uart_dev) { +pbio_error_t pbdrv_uart_get_instance(uint8_t id, pbdrv_uart_dev_t **uart_dev) { if (id >= PBDRV_CONFIG_UART_STM32F0_NUM_UART) { return PBIO_ERROR_INVALID_ARG; } @@ -62,22 +57,20 @@ pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, if (!dev->initialized) { return PBIO_ERROR_AGAIN; } - dev->parent_process = parent_process; *uart_dev = dev; return PBIO_SUCCESS; } -PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { + + ASYNC_BEGIN(state); - PT_BEGIN(pt); if (!msg || !length) { - *err = PBIO_ERROR_INVALID_ARG; - PT_EXIT(pt); + return PBIO_ERROR_INVALID_ARG; } if (uart->rx_buf) { - *err = PBIO_ERROR_BUSY; - PT_EXIT(pt); + return PBIO_ERROR_BUSY; } uart->rx_buf = msg; @@ -85,11 +78,11 @@ PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, u uart->rx_buf_index = 0; if (timeout) { - etimer_set(&uart->rx_timer, timeout); + pbio_os_timer_set(&uart->rx_timer, timeout); } // Await completion or timeout. - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ // On every re-entry to the async read, drain the ring buffer // into the current read buffer. This ensures that we use // all available data if there have been multiple polls since our last @@ -99,32 +92,28 @@ PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, u uart->rx_buf[uart->rx_buf_index++] = uart->rx_ring_buf[uart->rx_ring_buf_tail]; uart->rx_ring_buf_tail = (uart->rx_ring_buf_tail + 1) & (UART_RING_BUF_SIZE - 1); } - uart->rx_buf_index == uart->rx_buf_size || (timeout && etimer_expired(&uart->rx_timer)); + uart->rx_buf_index == uart->rx_buf_size || (timeout && pbio_os_timer_is_expired(&uart->rx_timer)); })); - if (timeout && etimer_expired(&uart->rx_timer)) { - *err = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart->rx_timer); - *err = PBIO_SUCCESS; - } uart->rx_buf = NULL; - PT_END(pt); + if (timeout && pbio_os_timer_is_expired(&uart->rx_timer)) { + return PBIO_ERROR_TIMEDOUT; + } + + ASYNC_END(PBIO_SUCCESS); } -PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); if (!msg || !length) { - *err = PBIO_ERROR_INVALID_ARG; - PT_EXIT(pt); + return PBIO_ERROR_INVALID_ARG; } if (uart->tx_buf) { - *err = PBIO_ERROR_BUSY; - PT_EXIT(pt); + return PBIO_ERROR_BUSY; } uart->tx_buf = msg; @@ -132,24 +121,22 @@ PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uart->tx_buf_index = 0; if (timeout) { - etimer_set(&uart->tx_timer, timeout); + pbio_os_timer_set(&uart->tx_timer, timeout); } uart->USART->CR1 |= USART_CR1_TXEIE; // Await completion or timeout. - PT_WAIT_UNTIL(pt, uart->tx_buf_index == uart->tx_buf_size || (timeout && etimer_expired(&uart->tx_timer))); + AWAIT_UNTIL(state, uart->tx_buf_index == uart->tx_buf_size || (timeout && pbio_os_timer_is_expired(&uart->tx_timer))); + + uart->tx_buf = NULL; - if (timeout && etimer_expired(&uart->tx_timer)) { + if (timeout && pbio_os_timer_is_expired(&uart->tx_timer)) { uart->USART->CR1 &= ~(USART_CR1_TXEIE | USART_CR1_TCIE); - *err = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart->tx_timer); - *err = PBIO_SUCCESS; + return PBIO_ERROR_TIMEDOUT; } - uart->tx_buf = NULL; - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart, uint32_t baud) { @@ -174,7 +161,7 @@ void pbdrv_uart_stm32f0_handle_irq(uint8_t id) { // REVISIT: Do we need to have an overrun error when the ring buffer gets full? uart->rx_ring_buf[uart->rx_ring_buf_head] = uart->USART->RDR; uart->rx_ring_buf_head = (uart->rx_ring_buf_head + 1) & (UART_RING_BUF_SIZE - 1); - process_poll(uart->parent_process); + pbio_os_request_poll(); } // transmit next byte @@ -193,7 +180,7 @@ void pbdrv_uart_stm32f0_handle_irq(uint8_t id) { // transmission complete if (uart->USART->CR1 & USART_CR1_TCIE && isr & USART_ISR_TC) { uart->USART->CR1 &= ~USART_CR1_TCIE; - process_poll(uart->parent_process); + pbio_os_request_poll(); } } diff --git a/lib/pbio/drv/uart/uart_stm32f4_ll_irq.c b/lib/pbio/drv/uart/uart_stm32f4_ll_irq.c index e64aca1a1..e932ea1d4 100644 --- a/lib/pbio/drv/uart/uart_stm32f4_ll_irq.c +++ b/lib/pbio/drv/uart/uart_stm32f4_ll_irq.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2018-2020 The Pybricks Authors +// Copyright (c) 2018-2025 The Pybricks Authors // UART driver for STM32F4x using IRQ. @@ -19,6 +19,7 @@ #include #include +#include #include #include "../core.h" @@ -32,9 +33,9 @@ struct _pbdrv_uart_dev_t { /** Circular buffer for caching received bytes. */ struct ringbuf rx_buf; /** Timer for read timeout. */ - struct etimer read_timer; + pbio_os_timer_t read_timer; /** Timer for write timeout. */ - struct etimer write_timer; + pbio_os_timer_t write_timer; /** The buffer of the ongoing async read function. */ uint8_t *read_buf; /** The length of read_buf in bytes. */ @@ -47,18 +48,12 @@ struct _pbdrv_uart_dev_t { uint8_t write_length; /** The current position in write_buf. */ volatile uint8_t write_pos; - /** - * Parent process that handles incoming data. - * - * All protothreads in this module run within that process. - */ - struct process *parent_process; }; static pbdrv_uart_dev_t uart_devs[PBDRV_CONFIG_UART_STM32F4_LL_IRQ_NUM_UART]; static uint8_t pbdrv_uart_rx_data[PBDRV_CONFIG_UART_STM32F4_LL_IRQ_NUM_UART][RX_DATA_SIZE]; -pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, pbdrv_uart_dev_t **uart_dev) { +pbio_error_t pbdrv_uart_get_instance(uint8_t id, pbdrv_uart_dev_t **uart_dev) { if (id >= PBDRV_CONFIG_UART_STM32F4_LL_IRQ_NUM_UART) { return PBIO_ERROR_INVALID_ARG; } @@ -67,19 +62,16 @@ pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, // has not been initialized yet return PBIO_ERROR_AGAIN; } - dev->parent_process = parent_process; *uart_dev = dev; return PBIO_SUCCESS; } -PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - // Actual protothread starts here. - PT_BEGIN(pt); + ASYNC_BEGIN(state); if (uart->read_buf) { - *err = PBIO_ERROR_BUSY; - PT_EXIT(pt); + return PBIO_ERROR_BUSY; } uart->read_buf = msg; @@ -87,11 +79,11 @@ PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, u uart->read_pos = 0; if (timeout) { - etimer_set(&uart->read_timer, timeout); + pbio_os_timer_set(&uart->read_timer, timeout); } // Await completion or timeout. - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ // On every re-entry to the async read, drain the ring buffer // into the current read buffer. This ensures that we use // all available data if there have been multiple polls since our last @@ -104,28 +96,24 @@ PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, u } uart->read_buf[uart->read_pos++] = c; } - uart->read_pos == uart->read_length || (timeout && etimer_expired(&uart->read_timer)); + uart->read_pos == uart->read_length || (timeout && pbio_os_timer_is_expired(&uart->read_timer)); })); - // Set exit status based on completion condition. - if (timeout && etimer_expired(&uart->read_timer)) { - *err = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart->read_timer); - *err = PBIO_SUCCESS; - } uart->read_buf = NULL; - PT_END(pt); + if (timeout && pbio_os_timer_is_expired(&uart->read_timer)) { + return PBIO_ERROR_TIMEDOUT; + } + + ASYNC_END(PBIO_SUCCESS); } -PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); if (uart->write_buf) { - *err = PBIO_ERROR_BUSY; - PT_EXIT(pt); + return PBIO_ERROR_BUSY; } uart->write_buf = msg; @@ -133,27 +121,24 @@ PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uart->write_pos = 0; if (timeout) { - etimer_set(&uart->write_timer, timeout); + pbio_os_timer_set(&uart->write_timer, timeout); } LL_USART_EnableIT_TXE(uart->pdata->uart); // Await completion or timeout. - PT_WAIT_UNTIL(pt, uart->write_pos == uart->write_length || (timeout && etimer_expired(&uart->write_timer))); + AWAIT_UNTIL(state, uart->write_pos == uart->write_length || (timeout && pbio_os_timer_is_expired(&uart->write_timer))); + + uart->write_buf = NULL; // Set exit status based on completion condition. - if ((timeout && etimer_expired(&uart->write_timer))) { + if ((timeout && pbio_os_timer_is_expired(&uart->write_timer))) { LL_USART_DisableIT_TXE(uart->pdata->uart); LL_USART_DisableIT_TC(uart->pdata->uart); - *err = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart->write_timer); - *err = PBIO_SUCCESS; + return PBIO_ERROR_TIMEDOUT; } - uart->write_buf = NULL; - - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart, uint32_t baud) { @@ -207,7 +192,8 @@ void pbdrv_uart_stm32f4_ll_irq_handle_irq(uint8_t id) { ringbuf_put(&uart->rx_buf, LL_USART_ReceiveData8(USARTx)); // Poll parent process for each received byte, since the IRQ handler // has no awareness of the expected length of the read operation. - process_poll(uart->parent_process); + pbio_os_request_poll(); + } if (sr & USART_SR_ORE) { @@ -230,7 +216,7 @@ void pbdrv_uart_stm32f4_ll_irq_handle_irq(uint8_t id) { if (USARTx->CR1 & USART_CR1_TCIE && sr & USART_SR_TC) { LL_USART_DisableIT_TC(USARTx); // Poll parent process to indicate the write operation is complete. - process_poll(uart->parent_process); + pbio_os_request_poll(); } } diff --git a/lib/pbio/drv/uart/uart_stm32l4_ll_dma.c b/lib/pbio/drv/uart/uart_stm32l4_ll_dma.c index 1e88859a2..5045c6c9b 100644 --- a/lib/pbio/drv/uart/uart_stm32l4_ll_dma.c +++ b/lib/pbio/drv/uart/uart_stm32l4_ll_dma.c @@ -17,6 +17,7 @@ #include #include +#include #include #include "./uart_stm32l4_ll_dma.h" @@ -31,24 +32,18 @@ struct _pbdrv_uart_dev_t { const pbdrv_uart_stm32l4_ll_dma_platform_data_t *pdata; - struct etimer rx_timer; - struct etimer tx_timer; + pbio_os_timer_t rx_timer; + pbio_os_timer_t tx_timer; volatile uint8_t *rx_data; uint8_t rx_tail; uint8_t *read_buf; uint8_t read_length; - /** - * Parent process that handles incoming data. - * - * All protothreads in this module run within that process. - */ - struct process *parent_process; }; static pbdrv_uart_dev_t uart_devs[PBDRV_CONFIG_UART_STM32L4_LL_DMA_NUM_UART]; static volatile uint8_t pbdrv_uart_rx_data[PBDRV_CONFIG_UART_STM32L4_LL_DMA_NUM_UART][RX_DATA_SIZE]; -pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, pbdrv_uart_dev_t **uart_dev) { +pbio_error_t pbdrv_uart_get_instance(uint8_t id, pbdrv_uart_dev_t **uart_dev) { if (id >= PBDRV_CONFIG_UART_STM32L4_LL_DMA_NUM_UART) { return PBIO_ERROR_INVALID_ARG; } @@ -57,10 +52,10 @@ pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, // has not been initialized yet return PBIO_ERROR_AGAIN; } - dev->parent_process = parent_process; *uart_dev = dev; return PBIO_SUCCESS; } + static void volatile_copy(volatile uint8_t *src, uint8_t *dst, uint8_t size) { for (int i = 0; i < size; i++) { dst[i] = src[i]; @@ -193,30 +188,28 @@ static uint32_t pbdrv_uart_get_num_available(pbdrv_uart_dev_t *uart) { return (rx_head - uart->rx_tail) & (RX_DATA_SIZE - 1); } -PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); if (uart->read_buf) { - *err = PBIO_ERROR_BUSY; - PT_EXIT(pt); + return PBIO_ERROR_BUSY; } uart->read_buf = msg; uart->read_length = length; if (timeout) { - etimer_set(&uart->rx_timer, timeout); + pbio_os_timer_set(&uart->rx_timer, timeout); } // Wait until we have enough data or timeout. If there is enough data // already, this completes right away without yielding once first. - PT_WAIT_UNTIL(pt, pbdrv_uart_get_num_available(uart) >= uart->read_length || (timeout && etimer_expired(&uart->rx_timer))); - if (timeout && etimer_expired(&uart->rx_timer)) { + AWAIT_UNTIL(state, pbdrv_uart_get_num_available(uart) >= uart->read_length || (timeout && pbio_os_timer_is_expired(&uart->rx_timer))); + if (timeout && pbio_os_timer_is_expired(&uart->rx_timer)) { uart->read_buf = NULL; uart->read_length = 0; - *err = PBIO_ERROR_TIMEDOUT; - PT_EXIT(pt); + return PBIO_ERROR_TIMEDOUT; } // Copy from ring buffer to user buffer, taking care of wrap-around. @@ -232,21 +225,17 @@ PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, u uart->read_buf = NULL; uart->read_length = 0; - etimer_stop(&uart->rx_timer); - *err = PBIO_SUCCESS; - - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } -PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { const pbdrv_uart_stm32l4_ll_dma_platform_data_t *pdata = uart->pdata; - PT_BEGIN(pt); + ASYNC_BEGIN(state); if (LL_USART_IsEnabledDMAReq_TX(pdata->uart)) { - *err = PBIO_ERROR_BUSY; - PT_EXIT(pt); + return PBIO_ERROR_BUSY; } LL_DMA_DisableChannel(pdata->tx_dma, pdata->tx_dma_ch); @@ -260,19 +249,16 @@ PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart, uint8_t *msg, LL_USART_EnableDMAReq_TX(pdata->uart); if (timeout) { - etimer_set(&uart->tx_timer, timeout); + pbio_os_timer_set(&uart->tx_timer, timeout); } - PT_WAIT_WHILE(pt, LL_USART_IsEnabledDMAReq_TX(pdata->uart) && !(timeout && etimer_expired(&uart->tx_timer))); - if ((timeout && etimer_expired(&uart->tx_timer))) { + AWAIT_WHILE(state, LL_USART_IsEnabledDMAReq_TX(pdata->uart) && !(timeout && pbio_os_timer_is_expired(&uart->tx_timer))); + if ((timeout && pbio_os_timer_is_expired(&uart->tx_timer))) { LL_USART_DisableDMAReq_TX(pdata->uart); - *err = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart->tx_timer); - *err = PBIO_SUCCESS; + return PBIO_ERROR_TIMEDOUT; } - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart, uint32_t baud) { @@ -319,17 +305,12 @@ void pbdrv_uart_flush(pbdrv_uart_dev_t *uart) { uart->rx_tail = RX_DATA_SIZE - LL_DMA_GetDataLength(uart->pdata->rx_dma, uart->pdata->rx_dma_ch); } -static void poll_process_by_id(uint8_t id) { - pbdrv_uart_dev_t *uart = &uart_devs[id]; - process_poll(uart->parent_process); -} - void pbdrv_uart_stm32l4_ll_dma_handle_tx_dma_irq(uint8_t id) { const pbdrv_uart_stm32l4_ll_dma_platform_data_t *pdata = &pbdrv_uart_stm32l4_ll_dma_platform_data[id]; if (LL_DMA_IsEnabledIT_TC(pdata->tx_dma, pdata->tx_dma_ch) && dma_is_tc(pdata->tx_dma, pdata->tx_dma_ch)) { dma_clear_tc(pdata->tx_dma, pdata->tx_dma_ch); LL_USART_DisableDMAReq_TX(pdata->uart); - poll_process_by_id(id); + pbio_os_request_poll(); } } @@ -338,12 +319,12 @@ void pbdrv_uart_stm32l4_ll_dma_handle_rx_dma_irq(uint8_t id) { if (LL_DMA_IsEnabledIT_HT(pdata->rx_dma, pdata->rx_dma_ch) && dma_is_ht(pdata->rx_dma, pdata->rx_dma_ch)) { dma_clear_ht(pdata->rx_dma, pdata->rx_dma_ch); - poll_process_by_id(id); + pbio_os_request_poll(); } if (LL_DMA_IsEnabledIT_TC(pdata->rx_dma, pdata->rx_dma_ch) && dma_is_tc(pdata->rx_dma, pdata->rx_dma_ch)) { dma_clear_tc(pdata->rx_dma, pdata->rx_dma_ch); - poll_process_by_id(id); + pbio_os_request_poll(); } } @@ -353,12 +334,12 @@ void pbdrv_uart_stm32l4_ll_dma_handle_uart_irq(uint8_t id) { if (LL_USART_IsEnabledIT_TC(pdata->uart) && LL_USART_IsActiveFlag_TC(pdata->uart)) { LL_USART_DisableIT_TC(pdata->uart); LL_USART_ClearFlag_TC(pdata->uart); - poll_process_by_id(id); + pbio_os_request_poll(); } if (LL_USART_IsEnabledIT_IDLE(pdata->uart) && LL_USART_IsActiveFlag_IDLE(pdata->uart)) { LL_USART_ClearFlag_IDLE(pdata->uart); - poll_process_by_id(id); + pbio_os_request_poll(); } } diff --git a/lib/pbio/include/pbdrv/i2c.h b/lib/pbio/include/pbdrv/i2c.h index 5d7ae6d8c..b9d478704 100644 --- a/lib/pbio/include/pbdrv/i2c.h +++ b/lib/pbio/include/pbdrv/i2c.h @@ -27,7 +27,7 @@ typedef struct _pbdrv_i2c_dev_t pbdrv_i2c_dev_t; * @param [in] parent_process The parent process. Allows I2C to poll process on IRQ events. * @param [out] i2c_dev The I2C device. */ -pbio_error_t pbdrv_i2c_get_instance(uint8_t id, struct process *parent_process, pbdrv_i2c_dev_t **i2c_dev); +pbio_error_t pbdrv_i2c_get_instance(uint8_t id, pbdrv_i2c_dev_t **i2c_dev); /** * Does an I2C operation. To be replaced. @@ -45,7 +45,7 @@ pbio_error_t pbdrv_i2c_placeholder_operation(pbdrv_i2c_dev_t *i2c_dev, const cha #else // PBDRV_CONFIG_I2C -static inline pbio_error_t pbdrv_i2c_get_instance(uint8_t id, struct process *parent_process, pbdrv_i2c_dev_t **i2c_dev) { +static inline pbio_error_t pbdrv_i2c_get_instance(uint8_t id, pbdrv_i2c_dev_t **i2c_dev) { *i2c_dev = NULL; return PBIO_ERROR_NOT_SUPPORTED; } diff --git a/lib/pbio/include/pbdrv/uart.h b/lib/pbio/include/pbdrv/uart.h index 12806f3e0..751972ffb 100644 --- a/lib/pbio/include/pbdrv/uart.h +++ b/lib/pbio/include/pbdrv/uart.h @@ -10,26 +10,24 @@ #define _PBDRV_UART_H_ #include +#include #include #include +#include #include -#include typedef struct _pbdrv_uart_dev_t pbdrv_uart_dev_t; -typedef void (*pbdrv_uart_poll_callback_t)(void *context); - #if PBDRV_CONFIG_UART /** * Get an instance of the UART driver. * * @param [in] id The ID of the UART device. - * @param [in] parent_process The parent process. Allows UART to poll process on IRQ events. * @param [out] uart_dev The UART device. */ -pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, pbdrv_uart_dev_t **uart_dev); +pbio_error_t pbdrv_uart_get_instance(uint8_t id, pbdrv_uart_dev_t **uart_dev); void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart_dev, uint32_t baud); void pbdrv_uart_stop(pbdrv_uart_dev_t *uart_dev); @@ -40,30 +38,31 @@ int32_t pbdrv_uart_get_char(pbdrv_uart_dev_t *uart_dev); /** * Asynchronously read from the UART. * - * @param [in] pt The protothread. + * @param [in] state The protothread state. * @param [in] uart_dev The UART device. * @param [out] msg The buffer to store the received message. * @param [in] length The length of the expected message. * @param [in] timeout The timeout in milliseconds or 0 for no timeout. - * @param [out] err The error code. + * @return The error code. */ -PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)); +pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout); /** * Asynchronously write to the UART. * - * @param [in] pt The protothread. + * @param [in] state The protothread state. * @param [in] uart_dev The UART device. * @param [in] msg The message to send. * @param [in] length The length of the message. * @param [in] timeout The timeout in milliseconds or 0 for no timeout. - * @param [out] err The error code. + * + * @return The error code. */ -PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)); +pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout); #else // PBDRV_CONFIG_UART -static inline pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, pbdrv_uart_dev_t **uart_dev) { +static inline pbio_error_t pbdrv_uart_get_instance(uint8_t id, pbdrv_uart_dev_t **uart_dev) { *uart_dev = NULL; return PBIO_ERROR_NOT_SUPPORTED; } @@ -98,16 +97,12 @@ static inline int32_t pbdrv_uart_get_char(pbdrv_uart_dev_t *uart_dev) { return -1; } -static inline PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { - PT_BEGIN(pt); - *err = PBIO_ERROR_NOT_SUPPORTED; - PT_END(pt); +static inline pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout) { + return PBIO_ERROR_NOT_SUPPORTED; } -static inline PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { - PT_BEGIN(pt); - *err = PBIO_ERROR_NOT_SUPPORTED; - PT_END(pt); +static inline pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout) { + return PBIO_ERROR_NOT_SUPPORTED; } #endif // PBDRV_CONFIG_UART diff --git a/lib/pbio/include/pbio/port_dcm.h b/lib/pbio/include/pbio/port_dcm.h index 505389aac..3900a387f 100644 --- a/lib/pbio/include/pbio/port_dcm.h +++ b/lib/pbio/include/pbio/port_dcm.h @@ -4,9 +4,9 @@ #ifndef _PBIO_PORT_DCM_H_ #define _PBIO_PORT_DCM_H_ -#include #include #include +#include #include typedef struct _pbio_port_dcm_t pbio_port_dcm_t; @@ -27,7 +27,7 @@ typedef struct { #if PBIO_CONFIG_PORT_DCM -PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins)); +pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins); /** * Gets device connection manager state. @@ -82,9 +82,8 @@ static inline pbio_port_dcm_analog_rgba_t *pbio_port_dcm_get_analog_rgba(pbio_po return NULL; } -static inline PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins)) { - PT_BEGIN(pt); - PT_END(pt); +static inline pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins) { + return PBIO_ERROR_NOT_SUPPORTED; } #endif // PBIO_CONFIG_PORT_DCM diff --git a/lib/pbio/include/pbio/port_interface.h b/lib/pbio/include/pbio/port_interface.h index 7e3048c42..1c793aa59 100644 --- a/lib/pbio/include/pbio/port_interface.h +++ b/lib/pbio/include/pbio/port_interface.h @@ -70,8 +70,6 @@ void pbio_port_stop_user_actions(bool reset); pbio_error_t pbio_port_get_port(pbio_port_id_t id, pbio_port_t **port); -void pbio_port_select_process(pbio_port_t *port); - pbio_error_t pbio_port_get_dcmotor(pbio_port_t *port, lego_device_type_id_t *expected_type_id, pbio_dcmotor_t **dcmotor); pbio_error_t pbio_port_get_servo(pbio_port_t *port, lego_device_type_id_t *expected_type_id, pbio_servo_t **servo); diff --git a/lib/pbio/include/pbio/port_lump.h b/lib/pbio/include/pbio/port_lump.h index 61f4ed701..dc9328fcf 100644 --- a/lib/pbio/include/pbio/port_lump.h +++ b/lib/pbio/include/pbio/port_lump.h @@ -4,9 +4,10 @@ #ifndef _PBIO_PORT_LUMP_H_ #define _PBIO_PORT_LUMP_H_ -#include #include #include +#include + #include #include @@ -30,13 +31,13 @@ typedef struct { #if PBIO_CONFIG_PORT_LUMP -pbio_port_lump_dev_t *pbio_port_lump_init_instance(uint8_t device_index, struct process *parent_process); +pbio_port_lump_dev_t *pbio_port_lump_init_instance(uint8_t device_index); -PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, struct etimer *etimer)); +pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, pbio_os_timer_t *timer); -PT_THREAD(pbio_port_lump_data_send_thread(struct pt *pt, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, struct etimer *etimer)); +pbio_error_t pbio_port_lump_data_send_thread(pbio_os_state_t *state, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, pbio_os_timer_t *timer); -PT_THREAD(pbio_port_lump_data_recv_thread(struct pt *pt, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev)); +pbio_error_t pbio_port_lump_data_recv_thread(pbio_os_state_t *state, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev); pbio_error_t pbio_port_lump_is_ready(pbio_port_lump_dev_t *lump_dev); @@ -58,7 +59,7 @@ pbio_port_power_requirements_t pbio_port_lump_get_power_requirements(pbio_port_l #else // PBIO_CONFIG_PORT_LUMP -static inline pbio_port_lump_dev_t *pbio_port_lump_init_instance(uint8_t device_index, struct process *parent_process) { +static inline pbio_port_lump_dev_t *pbio_port_lump_init_instance(uint8_t device_index) { return NULL; } @@ -101,22 +102,18 @@ static inline pbio_port_power_requirements_t pbio_port_lump_get_power_requiremen return PBIO_PORT_POWER_REQUIREMENTS_NONE; } -static inline PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, struct etimer *etimer)) { - PT_BEGIN(pt); - PT_END(pt); +static inline pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, pbio_os_timer_t *timer) { + return PBIO_ERROR_NOT_SUPPORTED; } -static inline PT_THREAD(pbio_port_lump_data_send_thread(struct pt *pt, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, struct etimer *etimer)) { - PT_BEGIN(pt); - PT_END(pt); +static inline pbio_error_t pbio_port_lump_data_send_thread(pbio_os_state_t *state, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, pbio_os_timer_t *timer) { + return PBIO_ERROR_NOT_SUPPORTED; } -static inline PT_THREAD(pbio_port_lump_data_recv_thread(struct pt *pt, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev)) { - PT_BEGIN(pt); - PT_END(pt); +static inline pbio_error_t pbio_port_lump_data_recv_thread(pbio_os_state_t *state, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev) { + return PBIO_ERROR_NOT_SUPPORTED; } - #endif // PBIO_CONFIG_PORT_LUMP #endif // _PBIO_PORT_LUMP_H_ diff --git a/lib/pbio/src/port.c b/lib/pbio/src/port.c index 6cc6d5325..09593b58c 100644 --- a/lib/pbio/src/port.c +++ b/lib/pbio/src/port.c @@ -15,11 +15,11 @@ #include #include +#include #include #include #include -#include #define DEBUG 0 #if DEBUG @@ -70,16 +70,16 @@ struct _pbio_port_t { /** * Process for this port. */ - struct process process; + pbio_os_process_t process; /** * Timer for this port process. */ - struct etimer etimer; + pbio_os_timer_t timer; /** * Parallel child protothreads used for spawning async functions such as * the device connection manager and the uart device process. */ - struct pt child1; - struct pt child2; + pbio_os_state_t child1; + pbio_os_state_t child2; /** * Device connection manager that detects passive devices in LEGO mode. */ @@ -92,42 +92,17 @@ struct _pbio_port_t { static pbio_port_t ports[PBIO_CONFIG_PORT_NUM_DEV]; -PROCESS_THREAD(pbio_port_process_none, ev, data) { - PROCESS_BEGIN(); - for (;;) { - PROCESS_WAIT_EVENT(); - } - PROCESS_END(); -} +pbio_error_t pbio_port_process_pup_thread(pbio_os_state_t *state, void *context) { -/** - * Sets the current process to that of the selected port. - * - * When user level code (outside the pbio event loop) calls protothreads that - * contain etimers (then merely used as regular timers), the contiki still - * associates them with the then-current process. Outside the event loop - * though, this has no meaning and it so it associates the etimer arbitrarily - * with the last handled. To avoid this, we set the current process to that of - * the selected port. - * - * NB: Must only be called from *outside* the event loop. - * - * @param [in] port The port instance. - */ -void pbio_port_select_process(pbio_port_t *port) { - process_current = &port->process; -} - -PROCESS_THREAD(pbio_port_process_pup, ev, data) { - - struct process *proc = PBIO_CONTAINER_OF(process_pt, struct process, pt); - pbio_port_t *port = PBIO_CONTAINER_OF(proc, pbio_port_t, process); + pbio_port_t *port = context; // NB: This same process thread definition is shared for all ports, so we // cannot use static variables across yields as is normally done in these // processes. Use the port state variables instead. - PROCESS_BEGIN(); + pbio_error_t err; + + ASYNC_BEGIN(state); // NB: Currently only implements LEGO mode and assumes that all PUP // peripherals are available and initialized. @@ -136,25 +111,29 @@ PROCESS_THREAD(pbio_port_process_pup, ev, data) { // Run passive device connection manager until UART device is detected. pbdrv_ioport_p5p6_set_mode(port->pdata->pins, port->uart_dev, PBDRV_IOPORT_P5P6_MODE_GPIO_ADC); - PROCESS_PT_SPAWN(&port->child1, pbio_port_dcm_thread(&port->child1, &port->etimer, port->connection_manager, port->pdata->pins)); + AWAIT(state, &port->child1, err = pbio_port_dcm_thread(&port->child1, &port->timer, port->connection_manager, port->pdata->pins)); // Synchronize with LUMP data stream from sensor and parse device info. pbdrv_ioport_p5p6_set_mode(port->pdata->pins, port->uart_dev, PBDRV_IOPORT_P5P6_MODE_UART); - PROCESS_PT_SPAWN(&port->child1, pbio_port_lump_sync_thread(&port->child1, port->lump_dev, port->uart_dev, &port->etimer)); + AWAIT(state, &port->child1, err = pbio_port_lump_sync_thread(&port->child1, port->lump_dev, port->uart_dev, &port->timer)); + if (err != PBIO_SUCCESS) { + // Synchronization failed. Retry. + continue; + } // Exchange sensor data with the LUMP device until it is disconnected. // The send thread detects this when the keep alive messages time out. pbio_port_p1p2_set_power(port, pbio_port_lump_get_power_requirements(port->lump_dev)); - PT_INIT(&port->child1); - PT_INIT(&port->child2); - PROCESS_WAIT_WHILE( - PT_SCHEDULE(pbio_port_lump_data_recv_thread(&port->child1, port->lump_dev, port->uart_dev)) && - PT_SCHEDULE(pbio_port_lump_data_send_thread(&port->child2, port->lump_dev, port->uart_dev, &port->etimer)) + AWAIT_RACE(state, &port->child1, &port->child2, + pbio_port_lump_data_recv_thread(&port->child1, port->lump_dev, port->uart_dev), + pbio_port_lump_data_send_thread(&port->child2, port->lump_dev, port->uart_dev, &port->timer) ); + pbio_port_p1p2_set_power(port, PBIO_PORT_POWER_REQUIREMENTS_NONE); } - PROCESS_END(); + // Unreachable. + ASYNC_END(PBIO_ERROR_FAILED); } /** @@ -382,8 +361,7 @@ pbio_error_t pbio_port_get_analog_rgba(pbio_port_t *port, lego_device_type_id_t static void pbio_port_init_one_port(pbio_port_t *port) { // Initialize all ports with the none process. - port->process.thread = process_thread_pbio_port_process_none; - process_start(&port->process); + pbio_os_process_start(&port->process, pbio_port_process_none_thread, port); // Configure motor instances if this port has them. This assumes that // all motor ports have a way to get angle information. We can add @@ -410,13 +388,13 @@ static void pbio_port_init_one_port(pbio_port_t *port) { } // If uart and gpio available, initialize device manager and uart devices. - pbdrv_uart_get_instance(port->pdata->uart_driver_index, &port->process, &port->uart_dev); - pbdrv_i2c_get_instance(port->pdata->i2c_driver_index, &port->process, &port->i2c_dev); + pbdrv_uart_get_instance(port->pdata->uart_driver_index, &port->uart_dev); + pbdrv_i2c_get_instance(port->pdata->i2c_driver_index, &port->i2c_dev); if (port->uart_dev && port->pdata->supported_modes & PBIO_PORT_MODE_LEGO_DCM) { // Initialize passive device connection manager and LEGO UART device. port->connection_manager = pbio_port_dcm_init_instance(port->pdata->external_port_index); - port->lump_dev = pbio_port_lump_init_instance(port->pdata->external_port_index, &port->process); + port->lump_dev = pbio_port_lump_init_instance(port->pdata->external_port_index); pbio_port_set_mode(port, PBIO_PORT_MODE_LEGO_DCM); return; } @@ -521,12 +499,8 @@ pbio_error_t pbio_port_set_mode(pbio_port_t *port, pbio_port_mode_t mode) { return PBIO_ERROR_NOT_SUPPORTED; } - // One port process is always running since initialization. Here we can - // reset the LC state and change the thread as relevant. Also poll to - // kick the process into action, similar to a normal process start. - port->process.thread = process_thread_pbio_port_process_none; - PT_INIT(&port->process.pt); - process_poll(&port->process); + // Disable thread activity by attaching a thread that does nothing. + pbio_os_process_reset(&port->process, pbio_port_process_none_thread); port->mode = mode; switch (mode) { @@ -537,7 +511,7 @@ pbio_error_t pbio_port_set_mode(pbio_port_t *port, pbio_port_mode_t mode) { case PBIO_PORT_MODE_LEGO_DCM: // Physical modes for this mode will be set by the process so this // is all we need to do here. - port->process.thread = process_thread_pbio_port_process_pup; + pbio_os_process_reset(&port->process, pbio_port_process_pup_thread); // Returning e-again allows user module to wait for the port to be // ready after first entering LEGO mode, avoiding NODEV errors when // switching from direct access modes back to LEGO mode. diff --git a/lib/pbio/src/port_dcm_ev3.c b/lib/pbio/src/port_dcm_ev3.c index 6650a992c..13dddbc22 100644 --- a/lib/pbio/src/port_dcm_ev3.c +++ b/lib/pbio/src/port_dcm_ev3.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2025 The Pybricks Authors -#include #include #include @@ -230,64 +229,62 @@ typedef struct { /** * Thread that reads one color sensor calibration data byte. * - * This is slower than it needs to be since 1ms is the lowest etimer resolution. + * This is slower than it needs to be since 1ms is the lowest timer resolution. * In total we read 54 bytes * 8 bits * 2 cycles = 864 cycles = 864ms. * Since this is only used during the initial sensor setup, this is not worth * optimizing with a dedicated timer. * - * @param [in] pt The process thread. - * @param [in] state The sensor state. - * @param [in] pins The ioport pins. - * @param [in] etimer The etimer to use for timing. - * @param [out] msg The message byte. + * @param [in] state The thread state. + * @param [in] sensor_state The sensor state. + * @param [in] pins The ioport pins. + * @param [in] timer The timer to use for timing. + * @param [out] msg The message byte. */ -PT_THREAD(pbio_port_dcm_nxt_color_rx_msg(struct pt *pt, pbio_port_dcm_nxt_color_sensor_state_t *state, const pbdrv_ioport_pins_t *pins, struct etimer *etimer, uint8_t *msg)) { +static pbio_error_t pbio_port_dcm_nxt_color_rx_msg(pbio_os_state_t *state, pbio_port_dcm_nxt_color_sensor_state_t *sensor_state, const pbdrv_ioport_pins_t *pins, pbio_os_timer_t *timer, uint8_t *msg) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); *msg = 0; pbdrv_gpio_input(&pins->p6); // Read 8 bits while toggling the "clock". - for (state->bit = 0; state->bit < 8; state->bit++) { + for (sensor_state->bit = 0; sensor_state->bit < 8; sensor_state->bit++) { // Clock high and wait for the data bit to settle. pbdrv_gpio_out_high(&pins->p5); - etimer_set(etimer, 1); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, 1); - *msg |= pbdrv_gpio_input(&pins->p6) << state->bit; + *msg |= pbdrv_gpio_input(&pins->p6) << sensor_state->bit; // Clock low and wait for sensor to prepare next bit. pbdrv_gpio_out_low(&pins->p5); - etimer_set(etimer, 1); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, 1); } - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } /** * Thread that writes one color sensor data byte. * - * @param [in] pt The process thread. - * @param [in] state The sensor state. - * @param [in] pins The ioport pins. - * @param [in] etimer The etimer to use for timing. - * @param [in] msg The message byte. + * @param [in] state The thread state. + * @param [in] sensor_state The sensor state. + * @param [in] pins The ioport pins. + * @param [in] timer The timer to use for timing. + * @param [in] msg The message byte. */ -PT_THREAD(pbio_port_dcm_nxt_color_tx_msg(struct pt *pt, pbio_port_dcm_nxt_color_sensor_state_t *state, const pbdrv_ioport_pins_t *pins, struct etimer *etimer, uint8_t msg)) { +static pbio_error_t pbio_port_dcm_nxt_color_tx_msg(pbio_os_state_t *state, pbio_port_dcm_nxt_color_sensor_state_t *sensor_state, const pbdrv_ioport_pins_t *pins, pbio_os_timer_t *timer, uint8_t msg) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); pbdrv_gpio_out_low(&pins->p5); pbdrv_gpio_out_low(&pins->p6); // Send 8 bits while toggling the "clock". - for (state->bit = 0; state->bit < 8; state->bit++) { + for (sensor_state->bit = 0; sensor_state->bit < 8; sensor_state->bit++) { // Set the data bit. - if (msg & (1 << state->bit)) { + if (msg & (1 << sensor_state->bit)) { pbdrv_gpio_out_high(&pins->p6); } else { pbdrv_gpio_out_low(&pins->p6); @@ -295,19 +292,17 @@ PT_THREAD(pbio_port_dcm_nxt_color_tx_msg(struct pt *pt, pbio_port_dcm_nxt_color_ // Toggle the clock high and low, giving sensor time to register bit. pbdrv_gpio_out_high(&pins->p5); - etimer_set(etimer, 1); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, 1); pbdrv_gpio_out_low(&pins->p5); - etimer_set(etimer, 1); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, 1); } - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } // Device connection manager state for each port struct _pbio_port_dcm_t { - struct pt child; + pbio_os_state_t child; uint32_t count; bool connected; pbio_port_dcm_category_t category; @@ -322,58 +317,53 @@ static pbio_port_dcm_t dcm_state[PBIO_CONFIG_PORT_DCM_NUM_DEV]; #define DCM_LOOP_STEADY_STATE_COUNT (20) #define DCM_LOOP_DISCONNECT_COUNT (5) -PT_THREAD(pbio_port_dcm_await_new_nxt_analog_sample(pbio_port_dcm_t * dcm, struct etimer *etimer, const pbdrv_ioport_pins_t *pins, uint32_t *value)) { - struct pt *pt = &dcm->child; +pbio_error_t pbio_port_dcm_await_new_nxt_analog_sample(pbio_port_dcm_t *dcm, pbio_os_timer_t *timer, const pbdrv_ioport_pins_t *pins, uint32_t *value) { + pbio_os_state_t *state = &dcm->child; - PT_BEGIN(pt); + ASYNC_BEGIN(state); // Wait for LED to settle. - etimer_set(etimer, 1); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, 1); // Request a new ADC sample. Revisit: Call back on completion instead of time. pbdrv_adc_update_soon(); - etimer_set(etimer, 4); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, 4); // Get the value. uint8_t pin = dcm->category == DCM_CATEGORY_NXT_COLOR ? 6 : 1; *value = pbio_port_dcm_get_mv(pins, pin); - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } /** * Thread that detects the device type. It monitors the ID1 and ID2 pins * on the port to see when devices are connected or disconnected. * - * @param [in] pt The process thread. - * @param [in] etimer The etimer to use for timing. + * @param [in] state The protothread state. + * @param [in] timer The timer to use for timing. * @param [in] dcm The device connection manager. * @param [in] pins The ioport pins. */ -PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins)) { +pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); for (;;) { - etimer_set(etimer, DCM_LOOP_TIME_MS); - debug_pr("Start device scan\n"); dcm->category = DCM_CATEGORY_NONE; dcm->connected = false; // Wait for any device to be connected. for (dcm->count = 0; dcm->count < DCM_LOOP_STEADY_STATE_COUNT; dcm->count++) { - pbio_port_dcm_pin_state_t state = pbio_port_dcm_get_state(pins); - pbio_port_dcm_category_t category = pbio_port_dcm_get_category(state); + pbio_port_dcm_pin_state_t pin_state = pbio_port_dcm_get_state(pins); + pbio_port_dcm_category_t category = pbio_port_dcm_get_category(pin_state); if (category != dcm->category || category == DCM_CATEGORY_NONE) { dcm->count = 0; dcm->category = category; } - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); - etimer_reset(etimer); + AWAIT_MS(state, timer, DCM_LOOP_TIME_MS); } debug_pr("Device kind detected: %d\n", dcm->category); dcm->connected = true; @@ -385,7 +375,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d debug_pr("Continue as LUMP process\n"); // Exit EV3 device manager, letting LUMP manager take over. // That process runs until the device no longer reports data. - PT_EXIT(pt); + return PBIO_SUCCESS; } if (dcm->category == DCM_CATEGORY_NXT_TEMPERATURE) { @@ -403,17 +393,16 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d // The original firmware has a reset sequence where p6 is high and // then p5 is toggled twice. It also works with 8 toggles, we can // just use the send function with 0xff to achieve the same effect. - PT_SPAWN(pt, &dcm->child, pbio_port_dcm_nxt_color_tx_msg(&dcm->child, &dcm->nxt_color_state, pins, etimer, 0xff)); - etimer_set(etimer, 100); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + AWAIT(state, &dcm->child, pbio_port_dcm_nxt_color_tx_msg(&dcm->child, &dcm->nxt_color_state, pins, timer, 0xff)); + AWAIT_MS(state, timer, 100); // Set to full color mode. - PT_SPAWN(pt, &dcm->child, pbio_port_dcm_nxt_color_tx_msg(&dcm->child, &dcm->nxt_color_state, pins, etimer, 13)); + AWAIT(state, &dcm->child, pbio_port_dcm_nxt_color_tx_msg(&dcm->child, &dcm->nxt_color_state, pins, timer, 13)); // Receive all calibration info. for (dcm->count = 0; dcm->count < sizeof(pbio_port_dcm_nxt_color_sensor_data_t); dcm->count++) { - PT_SPAWN(pt, &dcm->child, - pbio_port_dcm_nxt_color_rx_msg(&dcm->child, &dcm->nxt_color_state, pins, etimer, (uint8_t *)&dcm->nxt_color_state.data + dcm->count)); + AWAIT(state, &dcm->child, + pbio_port_dcm_nxt_color_rx_msg(&dcm->child, &dcm->nxt_color_state, pins, timer, (uint8_t *)&dcm->nxt_color_state.data + dcm->count)); } // Checksum and continue on failure. @@ -426,9 +415,9 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d while (!pbdrv_gpio_input(&pins->p2)) { pbdrv_gpio_out_low(&pins->p5); - PT_SPAWN(pt, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, etimer, pins, &dcm->nxt_rgba.a)); + AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.a)); pbdrv_gpio_out_high(&pins->p5); - PT_SPAWN(pt, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, etimer, pins, &dcm->nxt_rgba.r)); + AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.r)); if (dcm->category == DCM_CATEGORY_NXT_LIGHT) { // Light sensor doesn't have green and blue. @@ -436,9 +425,9 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d } pbdrv_gpio_out_low(&pins->p5); - PT_SPAWN(pt, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, etimer, pins, &dcm->nxt_rgba.g)); + AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.g)); pbdrv_gpio_out_high(&pins->p5); - PT_SPAWN(pt, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, etimer, pins, &dcm->nxt_rgba.b)); + AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.b)); } pbdrv_gpio_out_low(&pins->p5); @@ -455,13 +444,12 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d if (!pbdrv_gpio_input(gpio)) { dcm->count = 0; } - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); - etimer_reset(etimer); + AWAIT_MS(state, timer, DCM_LOOP_TIME_MS); } debug_pr("Device disconnected\n"); } - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } pbio_port_dcm_t *pbio_port_dcm_init_instance(uint8_t index) { diff --git a/lib/pbio/src/port_dcm_pup.c b/lib/pbio/src/port_dcm_pup.c index 2a03eec6c..452b44ffa 100644 --- a/lib/pbio/src/port_dcm_pup.c +++ b/lib/pbio/src/port_dcm_pup.c @@ -66,26 +66,26 @@ static const lego_device_type_id_t legodev_pup_type_id_lookup[3][3] = { }, }; +// This process needs 2ms between each yield point, giving the gpio tests +// enough time to settle. +#define DCM_AWAIT_MS (2) + /** * Thread that detects the device type. It monitors the ID1 and ID2 pins * on the port to see when devices are connected or disconnected. * - * @param [in] pt The process thread. - * @param [in] etimer The etimer to use for timing. + * @param [in] state The process thread state. + * @param [in] timer The timer to use for timing. * @param [in] dcm The device connection manager. * @param [in] pins The ioport pins. */ -PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins)) { +pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); dcm->prev_type_id = LEGO_DEVICE_TYPE_ID_NONE; dcm->dev_id_match_count = 0; - // This process needs 2ms between each yield point, giving the gpio tests - // enough time to settle. - etimer_set(etimer, 2); - // Keep running until a UART device is definitively found. while (dcm->dev_id_match_count < AFFIRMATIVE_MATCH_COUNT || dcm->prev_type_id != LEGO_DEVICE_TYPE_ID_LPF2_UNKNOWN_UART) { @@ -99,8 +99,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d // set ID2 as input pbdrv_gpio_input(&pins->p6); - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // save current ID2 value dcm->prev_gpio_value = pbdrv_gpio_input(&pins->p6); @@ -108,8 +107,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d // set ID1 low pbdrv_gpio_out_low(&pins->uart_tx); - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID2 dcm->gpio_value = pbdrv_gpio_input(&pins->p6); @@ -123,8 +121,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d pbdrv_gpio_out_high(&pins->uart_buf); pbdrv_gpio_input(&pins->uart_tx); - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // ID1 is inverse of touch sensor value // TODO: save this value to sensor dcm @@ -140,8 +137,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d // set ID1 high pbdrv_gpio_out_high(&pins->uart_tx); - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID1 dcm->gpio_value = pbdrv_gpio_input(&pins->p5); @@ -160,8 +156,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d pbdrv_gpio_out_high(&pins->uart_buf); pbdrv_gpio_input(&pins->uart_tx); - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID1 if (pbdrv_gpio_input(&pins->p5) == 1) { @@ -173,8 +168,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d } } - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // set ID1 as input pbdrv_gpio_out_high(&pins->uart_buf); @@ -183,8 +177,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d // set ID2 high pbdrv_gpio_out_high(&pins->p6); - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID1 dcm->prev_gpio_value = pbdrv_gpio_input(&pins->p5); @@ -192,8 +185,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d // set ID2 low pbdrv_gpio_out_low(&pins->p6); - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID1 dcm->gpio_value = pbdrv_gpio_input(&pins->p5); @@ -218,8 +210,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d // set ID2 high pbdrv_gpio_out_high(&pins->p6); - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // if ID2 is high if (pbdrv_gpio_input(&pins->uart_rx) == 1) { @@ -233,8 +224,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d // detection. pbdrv_gpio_out_low(&pins->uart_rx); - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // if ID2 is low if (pbdrv_gpio_input(&pins->uart_rx) == 0) { @@ -257,8 +247,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d } } - etimer_restart(etimer); - PT_YIELD_UNTIL(pt, etimer_expired(etimer)); + AWAIT_MS(state, timer, DCM_AWAIT_MS); // set ID2 as input pbdrv_gpio_input(&pins->p6); @@ -297,7 +286,7 @@ PT_THREAD(pbio_port_dcm_thread(struct pt *pt, struct etimer *etimer, pbio_port_d // raise. dcm->dev_id_match_count = 0; - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } /** diff --git a/lib/pbio/src/port_lump.c b/lib/pbio/src/port_lump.c index 978fe277d..3e8d647c7 100644 --- a/lib/pbio/src/port_lump.c +++ b/lib/pbio/src/port_lump.c @@ -9,6 +9,7 @@ #include +#include #include #include @@ -132,15 +133,10 @@ typedef struct { // LUMP state for each port. struct _pbio_port_lump_dev_t { - /** - * Parent port process that all protothreads here run within. - * Needed here to request polling after user requests like mode switches. - */ - struct process *parent_process; /** Child protothread of the main protothread used for reading data */ - struct pt read_pt; + pbio_os_state_t read_pt; /** Child protothread of the main protothread used for writing data */ - struct pt write_pt; + pbio_os_state_t write_pt; /** Buffer to hold messages received from the device. */ uint8_t *rx_msg; /** Buffer to hold messages transmitted to the device. */ @@ -180,8 +176,6 @@ struct _pbio_port_lump_dev_t { uint32_t err_count; /** Flag that indicates that good DATA lump_dev->msg has been received since last watchdog timeout. */ bool data_rec; - /** Return value for uart operations. */ - pbio_error_t err; /** Angle reported by the device. */ pbio_angle_t angle; #if PBIO_CONFIG_PORT_LUMP_MODE_INFO @@ -210,12 +204,11 @@ static uint8_t bufs[PBIO_CONFIG_PORT_LUMP_NUM_DEV][NUM_BUF][EV3_UART_MAX_MESSAGE static uint8_t data_read_bufs[PBIO_CONFIG_PORT_LUMP_NUM_DEV][LUMP_MAX_MSG_SIZE] __attribute__((aligned(4))); static pbdrv_legodev_lump_data_set_t data_set_bufs[PBIO_CONFIG_PORT_LUMP_NUM_DEV]; -pbio_port_lump_dev_t *pbio_port_lump_init_instance(uint8_t device_index, struct process *parent_process) { +pbio_port_lump_dev_t *pbio_port_lump_init_instance(uint8_t device_index) { if (device_index >= PBIO_CONFIG_PORT_LUMP_NUM_DEV) { return NULL; } pbio_port_lump_dev_t *lump_dev = &lump_devices[device_index]; - lump_dev->parent_process = parent_process; lump_dev->tx_msg = &bufs[device_index][BUF_TX_MSG][0]; lump_dev->rx_msg = &bufs[device_index][BUF_RX_MSG][0]; lump_dev->status = PBDRV_LEGODEV_LUMP_STATUS_ERR; @@ -230,7 +223,7 @@ static void pbio_port_lump_request_mode(pbio_port_lump_dev_t *lump_dev, uint8_t lump_dev->mode_switch.desired_mode = mode; lump_dev->mode_switch.time = pbdrv_clock_get_ms(); lump_dev->mode_switch.requested = true; - process_poll(lump_dev->parent_process); + pbio_os_request_poll(); } static void pbio_port_lump_request_data_set(pbio_port_lump_dev_t *lump_dev, uint8_t mode, const uint8_t *data, uint8_t size) { @@ -238,7 +231,7 @@ static void pbio_port_lump_request_data_set(pbio_port_lump_dev_t *lump_dev, uint lump_dev->data_set->desired_mode = mode; lump_dev->data_set->time = pbdrv_clock_get_ms(); memcpy(lump_dev->data_set->bin_data, data, size); - process_poll(lump_dev->parent_process); + pbio_os_request_poll(); } static inline bool test_and_set_bit(uint8_t bit, uint32_t *flags) { @@ -726,21 +719,22 @@ static void ev3_uart_prepare_tx_msg(pbio_port_lump_dev_t *lump_dev, lump_msg_typ lump_dev->tx_msg_size = offset + i + 2; } -#include - /** * The synchronization thread for the LEGO UART device. * * This thread receives and parses incoming messages sent by LEGO UART devices * when they are plugged in. It populates the device state accordingly. * - * @param [in] pt The protothread. + * @param [in] state The protothread state. * @param [in] lump_dev The LEGO UART device instance. * @param [in] uart_dev The UART device instance. * @param [in] etimer The timer for the protothread. */ -PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, struct etimer *etimer)) { - PT_BEGIN(pt); +pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, pbio_os_timer_t *timer) { + + pbio_error_t err; + + ASYNC_BEGIN(state); // Reset whole state except references to static buffers memset((uint8_t *)lump_dev + offsetof(pbio_port_lump_dev_t, type_id), 0, sizeof(pbio_port_lump_dev_t) - offsetof(pbio_port_lump_dev_t, type_id)); @@ -756,39 +750,39 @@ PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_d pbdrv_uart_flush(uart_dev); - PT_SPAWN(pt, &lump_dev->write_pt, pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { - debug_pr("Sp tx fail %d\n", lump_dev->err); - PT_EXIT(pt); + AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { + debug_pr("Sp tx fail %d\n", err); + return err; } pbdrv_uart_flush(uart_dev); // read one byte to check for ACK - PT_SPAWN(pt, &lump_dev->read_pt, pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, 10, &lump_dev->err)); + AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, 10)); - if ((lump_dev->err == PBIO_SUCCESS && lump_dev->rx_msg[0] != LUMP_SYS_ACK) || lump_dev->err == PBIO_ERROR_TIMEDOUT) { + if ((err == PBIO_SUCCESS && lump_dev->rx_msg[0] != LUMP_SYS_ACK) || err == PBIO_ERROR_TIMEDOUT) { // if we did not get ACK within 100ms, then switch to slow baud rate for sync pbdrv_uart_set_baud_rate(uart_dev, EV3_UART_SPEED_MIN); debug_pr("set baud: %d\n", EV3_UART_SPEED_MIN); - } else if (lump_dev->err != PBIO_SUCCESS) { + } else if (err != PBIO_SUCCESS) { debug_pr("UART Rx error during baud\n"); - PT_EXIT(pt); + return err; } -sync: +sync: // To get in sync with the data stream from the sensor, we look for a valid TYPE command. for (;;) { // If there are multiple bytes waiting to be read, this drains them one // by one without requiring additional polls. This means we won't need // exact timing to get in sync. - PT_SPAWN(pt, &lump_dev->read_pt, pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err == PBIO_ERROR_TIMEDOUT) { + AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT)); + if (err == PBIO_ERROR_TIMEDOUT) { continue; } - if (lump_dev->err != PBIO_SUCCESS) { + if (err != PBIO_SUCCESS) { debug_pr("UART Rx error during sync\n"); - PT_EXIT(pt); + return err; } if (lump_dev->rx_msg[0] == (LUMP_MSG_TYPE_CMD | LUMP_CMD_TYPE)) { @@ -796,11 +790,11 @@ PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_d } } - // then read the rest of the message - PT_SPAWN(pt, &lump_dev->read_pt, pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, 2, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { + // Then read the rest of the message. + AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, 2, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { debug_pr("UART Rx error while reading type\n"); - PT_EXIT(pt); + return err; } bool bad_id = lump_dev->rx_msg[1] < EV3_UART_TYPE_MIN || lump_dev->rx_msg[1] > EV3_UART_TYPE_MAX; @@ -811,7 +805,7 @@ PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_d debug_pr("Bad device type id or checksum\n"); if (lump_dev->err_count > 10) { lump_dev->err_count = 0; - PT_EXIT(pt); + return PBIO_ERROR_FAILED; } lump_dev->err_count++; goto sync; @@ -829,28 +823,28 @@ PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_d while (lump_dev->status == PBDRV_LEGODEV_LUMP_STATUS_INFO) { // read the message header - PT_SPAWN(pt, &lump_dev->read_pt, pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { + AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { debug_pr("UART Rx end error during info header\n"); - PT_EXIT(pt); + return err; } lump_dev->rx_msg_size = ev3_uart_get_msg_size(lump_dev->rx_msg[0]); if (lump_dev->rx_msg_size > EV3_UART_MAX_MESSAGE_SIZE) { debug_pr("Bad message size during info %d\n", lump_dev->rx_msg_size); if (lump_dev->type_id == LEGO_DEVICE_TYPE_ID_EV3_IR_SENSOR) { - // This sensor sends bad info messages. + // This sensor sends bad info messages, but we'll let it pass. continue; } - PT_EXIT(pt); + return err; } - // read the rest of the message + // Read the rest of the message. if (lump_dev->rx_msg_size > 1) { - PT_SPAWN(pt, &lump_dev->read_pt, pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, lump_dev->rx_msg_size - 1, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { + AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, lump_dev->rx_msg_size - 1, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { debug_pr("UART Rx end error during info\n"); - PT_EXIT(pt); + return err; } } @@ -860,23 +854,22 @@ PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_d // at this point we should have read all of the mode info if (lump_dev->status != PBDRV_LEGODEV_LUMP_STATUS_ACK) { - PT_EXIT(pt); + return PBIO_ERROR_FAILED; } // reply with ACK lump_dev->tx_msg[0] = LUMP_SYS_ACK; lump_dev->tx_msg_size = 1; - PT_SPAWN(pt, &lump_dev->write_pt, pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { + AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { debug_pr("UART Tx error during ack.\n"); - PT_EXIT(pt); + return err; } - // schedule baud rate change - etimer_set(etimer, 10); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + // Schedule baud rate change. + AWAIT_MS(state, timer, 10); - // change the baud rate + // Change the baud rate. pbdrv_uart_set_baud_rate(uart_dev, lump_dev->new_baud_rate); debug_pr("set baud: %" PRIu32 "\n", lump_dev->new_baud_rate); @@ -896,13 +889,12 @@ PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_d } // Reset other timers - etimer_reset_with_new_interval(etimer, EV3_UART_DATA_KEEP_ALIVE_TIMEOUT); lump_dev->data_set->time = pbdrv_clock_get_ms() - 1000; // i.e. no data set lump_dev->data_set->size = 0; lump_dev->status = PBDRV_LEGODEV_LUMP_STATUS_DATA; - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } /** @@ -910,50 +902,54 @@ PT_THREAD(pbio_port_lump_sync_thread(struct pt *pt, pbio_port_lump_dev_t *lump_d * * Responsible for sending keep alive messages and scheduled mode changes. * - * @param [in] pt The protothread. + * @param [in] state The protothread state. * @param [in] lump_dev The LEGO UART device instance. * @param [in] uart_dev The UART device instance. * @param [in] etimer The timer for the protothread. */ -PT_THREAD(pbio_port_lump_data_send_thread(struct pt *pt, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, struct etimer *etimer)) { +pbio_error_t pbio_port_lump_data_send_thread(pbio_os_state_t *state, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev, pbio_os_timer_t *timer) { if (lump_dev->status != PBDRV_LEGODEV_LUMP_STATUS_DATA) { - PT_EXIT(pt); + return PBIO_ERROR_INVALID_OP; } - PT_BEGIN(pt); + pbio_error_t err; + + ASYNC_BEGIN(state); + + pbio_os_timer_set(timer, EV3_UART_DATA_KEEP_ALIVE_TIMEOUT); for (;;) { - PT_WAIT_UNTIL(pt, etimer_expired(etimer) || lump_dev->mode_switch.requested || lump_dev->data_set->size > 0); + AWAIT_UNTIL(state, pbio_os_timer_is_expired(timer) || lump_dev->mode_switch.requested || lump_dev->data_set->size > 0); // Handle keep alive timeout - if (etimer_expired(etimer)) { + if (pbio_os_timer_is_expired(timer)) { // make sure we are receiving data if (!lump_dev->data_rec) { debug_pr("No data since last keepalive\n"); lump_dev->status = PBDRV_LEGODEV_LUMP_STATUS_ERR; - PT_EXIT(pt); + return PBIO_ERROR_TIMEDOUT; } lump_dev->data_rec = false; lump_dev->tx_msg[0] = LUMP_SYS_NACK; lump_dev->tx_msg_size = 1; - PT_SPAWN(pt, &lump_dev->write_pt, pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { + AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { debug_pr("Error during keepalive.\n"); - PT_EXIT(pt); + return err; } - etimer_reset_with_new_interval(etimer, EV3_UART_DATA_KEEP_ALIVE_TIMEOUT); + pbio_os_timer_set(timer, EV3_UART_DATA_KEEP_ALIVE_TIMEOUT); } // Handle requested mode change if (lump_dev->mode_switch.requested) { lump_dev->mode_switch.requested = false; ev3_uart_prepare_tx_msg(lump_dev, LUMP_MSG_TYPE_CMD, LUMP_CMD_SELECT, &lump_dev->mode_switch.desired_mode, 1); - PT_SPAWN(pt, &lump_dev->write_pt, pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { + AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { debug_pr("Setting requested mode failed.\n"); - PT_EXIT(pt); + return err; } } @@ -964,16 +960,15 @@ PT_THREAD(pbio_port_lump_data_send_thread(struct pt *pt, pbio_port_lump_dev_t *l ev3_uart_prepare_tx_msg(lump_dev, LUMP_MSG_TYPE_DATA, lump_dev->data_set->desired_mode, lump_dev->data_set->bin_data, lump_dev->data_set->size); lump_dev->data_set->size = 0; lump_dev->data_set->time = pbdrv_clock_get_ms(); - PT_SPAWN(pt, &lump_dev->write_pt, pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { + AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { debug_pr("Setting requested data failed.\n"); - PT_EXIT(pt); + return err; } lump_dev->data_set->time = pbdrv_clock_get_ms(); } else if (pbdrv_clock_get_ms() - lump_dev->data_set->time < 500) { // Not in the right mode yet, try again later for a reasonable amount of time. - process_poll(lump_dev->parent_process); - PT_YIELD(pt); + AWAIT_MS(state, timer, 1); } else { // Give up setting data. lump_dev->data_set->size = 0; @@ -981,7 +976,7 @@ PT_THREAD(pbio_port_lump_data_send_thread(struct pt *pt, pbio_port_lump_dev_t *l } } - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } /** @@ -989,27 +984,29 @@ PT_THREAD(pbio_port_lump_data_send_thread(struct pt *pt, pbio_port_lump_dev_t *l * * Responsible for receiving data messages and updating mode switch completion state. * - * @param [in] pt The protothread. + * @param [in] state The protothread state. * @param [in] lump_dev The LEGO UART device instance. * @param [in] uart_dev The UART device instance. */ -PT_THREAD(pbio_port_lump_data_recv_thread(struct pt *pt, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev)) { +pbio_error_t pbio_port_lump_data_recv_thread(pbio_os_state_t *state, pbio_port_lump_dev_t *lump_dev, pbdrv_uart_dev_t *uart_dev) { if (lump_dev->status != PBDRV_LEGODEV_LUMP_STATUS_DATA) { - PT_EXIT(pt); + return PBIO_ERROR_INVALID_OP; } + pbio_error_t err; + // REVISIT: This is not the greatest. We can easily get a buffer overrun and // loose data. For now, the retry after bad message size helps get back into // sync with the data stream. - PT_BEGIN(pt); + ASYNC_BEGIN(state); while (true) { - PT_SPAWN(pt, &lump_dev->read_pt, pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { + AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { debug_pr("UART Rx data header end error\n"); - break; + return err; } lump_dev->rx_msg_size = ev3_uart_get_msg_size(lump_dev->rx_msg[0]); @@ -1026,17 +1023,18 @@ PT_THREAD(pbio_port_lump_data_recv_thread(struct pt *pt, pbio_port_lump_dev_t *l continue; } - PT_SPAWN(pt, &lump_dev->read_pt, pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, lump_dev->rx_msg_size - 1, EV3_UART_IO_TIMEOUT, &lump_dev->err)); - if (lump_dev->err != PBIO_SUCCESS) { + AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, lump_dev->rx_msg_size - 1, EV3_UART_IO_TIMEOUT)); + if (err != PBIO_SUCCESS) { debug_pr("UART Rx data end error\n"); - break; + return err; } // at this point, we have a full lump_dev->msg that can be parsed pbio_port_lump_lump_parse_msg(lump_dev); } - PT_END(pt); + // Unreachable. + ASYNC_END(PBIO_ERROR_FAILED); } /** @@ -1278,7 +1276,7 @@ pbio_error_t pbio_port_lump_request_reset(pbio_port_lump_dev_t *lump_dev) { // Forces data threads to exit, and therefore port thread will eventually // call sync thread again. lump_dev->status = PBDRV_LEGODEV_LUMP_STATUS_ERR; - process_poll(lump_dev->parent_process); + pbio_os_request_poll(); return PBIO_SUCCESS; } diff --git a/lib/pbio/test/src/test_lump.c b/lib/pbio/test/src/test_lump.c index 3999ee0b9..7a47ba31d 100644 --- a/lib/pbio/test/src/test_lump.c +++ b/lib/pbio/test/src/test_lump.c @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -17,6 +16,7 @@ #include #include +#include #include #include @@ -30,12 +30,12 @@ struct _pbdrv_uart_dev_t { int baud; - struct etimer rx_timer; + pbio_os_timer_t rx_timer; uint8_t *rx_msg; uint8_t rx_msg_length; pbio_error_t rx_msg_result; uint8_t *tx_msg; - struct etimer tx_timer; + pbio_os_timer_t tx_timer; uint8_t tx_msg_length; pbio_error_t tx_msg_result; struct process *parent_process; @@ -52,11 +52,11 @@ static void simulate_uart_complete_irq(void) { process_poll(test_uart.parent_process); } -PT_THREAD(simulate_rx_msg(struct pt *pt, const uint8_t *msg, uint8_t length, bool *ok)) { - PT_BEGIN(pt); +pbio_error_t simulate_rx_msg(pbio_os_state_t *state, const uint8_t *msg, uint8_t length) { + ASYNC_BEGIN(state); // First uartdev reads one byte header - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.rx_msg_result == PBIO_ERROR_AGAIN; })); @@ -67,12 +67,11 @@ PT_THREAD(simulate_rx_msg(struct pt *pt, const uint8_t *msg, uint8_t length, boo simulate_uart_complete_irq(); if (length == 1) { - *ok = true; - PT_EXIT(pt); + return PBIO_SUCCESS; } // then read rest of message - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.rx_msg_result == PBIO_ERROR_AGAIN; })); @@ -82,18 +81,14 @@ PT_THREAD(simulate_rx_msg(struct pt *pt, const uint8_t *msg, uint8_t length, boo simulate_uart_complete_irq(); - *ok = true; - PT_END(pt); - end: - *ok = false; - PT_EXIT(pt); + ASYNC_END(PBIO_SUCCESS); } -PT_THREAD(simulate_tx_msg(struct pt *pt, const uint8_t *msg, uint8_t length, bool *ok)) { - PT_BEGIN(pt); +pbio_error_t simulate_tx_msg(pbio_os_state_t *state, const uint8_t *msg, uint8_t length) { + ASYNC_BEGIN(state); - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.tx_msg_result == PBIO_ERROR_AGAIN; })); @@ -107,28 +102,25 @@ PT_THREAD(simulate_tx_msg(struct pt *pt, const uint8_t *msg, uint8_t length, boo simulate_uart_complete_irq(); - *ok = true; - PT_END(pt); - end: - *ok = false; - PT_EXIT(pt); + ASYNC_END(PBIO_SUCCESS); } #define SIMULATE_RX_MSG(msg) do { \ - PT_SPAWN(pt, &child, simulate_rx_msg(&child, (msg), PBIO_ARRAY_SIZE(msg), &ok)); \ - tt_assert_msg(ok, #msg); \ + AWAIT(state, &child, err = simulate_rx_msg(&child, (msg), PBIO_ARRAY_SIZE(msg))); \ + tt_assert_msg(err == PBIO_SUCCESS, #msg); \ } while (0) #define SIMULATE_TX_MSG(msg) do { \ - PT_SPAWN(pt, &child, simulate_tx_msg(&child, (msg), PBIO_ARRAY_SIZE(msg), &ok)); \ - tt_assert_msg(ok, #msg); \ + AWAIT(state, &child, err = simulate_tx_msg(&child, (msg), PBIO_ARRAY_SIZE(msg))); \ + tt_assert_msg(err == PBIO_SUCCESS, #msg); \ } while (0) static const uint8_t msg_speed_115200[] = { 0x52, 0x00, 0xC2, 0x01, 0x00, 0x6E }; // SPEED 115200 static const uint8_t msg_ack[] = { 0x04 }; // ACK -static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { +static pbio_error_t test_boost_color_distance_sensor(pbio_os_state_t *state, void *context) { + // info messages captured from BOOST Color Distance Sensor with logic analyzer static const uint8_t msg0[] = { 0x40, 0x25, 0x9A }; static const uint8_t msg1[] = { 0x51, 0x07, 0x07, 0x0A, 0x07, 0xA3 }; @@ -232,8 +224,7 @@ static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { static const uint8_t msg91[] = { 0xD0, 0x00, 0x00, 0x00, 0x00, 0x2F }; // mode 8 data // used in SIMULATE_RX/TX_MSG macros - static struct pt child; - static bool ok; + static pbio_os_state_t child; static pbio_port_t *port; static pbio_port_lump_dev_t *lump_dev; @@ -241,9 +232,10 @@ static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { static pbio_port_lump_mode_info_t *mode_info; static uint8_t current_mode; static uint8_t num_modes; - static pbio_error_t err; - PT_BEGIN(pt); + pbio_error_t err; + + ASYNC_BEGIN(state); // Should be able to get port, but device won't be ready yet since it isn't // synched up. @@ -251,14 +243,14 @@ static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { tt_uint_op(pbio_port_get_port(PBIO_PORT_ID_D, &port), ==, PBIO_SUCCESS); // starting baud rate of hub - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); // this device does not support syncing at 115200 SIMULATE_TX_MSG(msg_speed_115200); - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 2400; })); @@ -351,13 +343,11 @@ static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { SIMULATE_TX_MSG(msg83); // wait for baud rate change - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); - PT_YIELD(pt); - // Simulate setting default mode SIMULATE_TX_MSG(msg83b); @@ -374,7 +364,7 @@ static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { } // Wait for default mode to complete - PT_WAIT_WHILE(pt, ({ + AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_get_lump_device(port, &expected_id, &lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -449,7 +439,7 @@ static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { // data message with new mode SIMULATE_RX_MSG(msg88); - PT_WAIT_WHILE(pt, ({ + AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_lump_is_ready(lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -462,7 +452,7 @@ static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { // also do mode 8 since it requires the extended mode flag - PT_WAIT_WHILE(pt, ({ + AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_lump_set_mode(lump_dev, 8)) == PBIO_ERROR_AGAIN; })); @@ -478,7 +468,7 @@ static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { SIMULATE_RX_MSG(msg90); SIMULATE_RX_MSG(msg91); - PT_WAIT_WHILE(pt, ({ + AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_lump_is_ready(lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -488,14 +478,13 @@ static PT_THREAD(test_boost_color_distance_sensor(struct pt *pt)) { tt_uint_op(pbio_port_lump_get_info(lump_dev, &num_modes, ¤t_mode, &mode_info), ==, PBIO_SUCCESS); tt_uint_op(current_mode, ==, 8); - PT_YIELD(pt); end: - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } -static PT_THREAD(test_boost_interactive_motor(struct pt *pt)) { +static pbio_error_t test_boost_interactive_motor(pbio_os_state_t *state, void *context) { // info messages captured from BOOST Interactive Motor with logic analyzer static const uint8_t msg0[] = { 0x40, 0x26, 0x99 }; static const uint8_t msg1[] = { 0x49, 0x03, 0x02, 0xB7 }; @@ -541,8 +530,7 @@ static PT_THREAD(test_boost_interactive_motor(struct pt *pt)) { static const uint8_t msg37[] = { 0x02 }; // NACK // used in SIMULATE_RX/TX_MSG macros - static struct pt child; - static bool ok; + static pbio_os_state_t child; static pbio_port_t *port; static pbio_port_lump_dev_t *lump_dev; @@ -552,7 +540,7 @@ static PT_THREAD(test_boost_interactive_motor(struct pt *pt)) { static uint8_t num_modes; static pbio_error_t err; - PT_BEGIN(pt); + ASYNC_BEGIN(state); // Should be able to get port, but device won't be ready yet since it isn't @@ -561,14 +549,14 @@ static PT_THREAD(test_boost_interactive_motor(struct pt *pt)) { tt_uint_op(pbio_port_get_port(PBIO_PORT_ID_D, &port), ==, PBIO_SUCCESS); // starting baud rate of hub - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); // this device does not support syncing at 115200 SIMULATE_TX_MSG(msg_speed_115200); - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 2400; })); @@ -613,12 +601,11 @@ static PT_THREAD(test_boost_interactive_motor(struct pt *pt)) { SIMULATE_TX_MSG(msg34); // wait for baud rate change - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); - PT_YIELD(pt); // Simulate setting default mode SIMULATE_TX_MSG(msg35); @@ -633,7 +620,7 @@ static PT_THREAD(test_boost_interactive_motor(struct pt *pt)) { SIMULATE_TX_MSG(msg37); } - PT_WAIT_WHILE(pt, ({ + AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_get_lump_device(port, &expected_id, &lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -663,14 +650,13 @@ static PT_THREAD(test_boost_interactive_motor(struct pt *pt)) { tt_want_uint_op(mode_info[3].data_type, ==, LUMP_DATA_TYPE_DATA16); tt_want_uint_op(mode_info[3].writable, ==, 0); - PT_YIELD(pt); end: - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } -static PT_THREAD(test_technic_large_motor(struct pt *pt)) { +static pbio_error_t test_technic_large_motor(pbio_os_state_t *state, void *context) { // info messages captured from Technic Large Linear Motor with logic analyzer static const uint8_t msg2[] = { 0x40, 0x2E, 0x91 }; static const uint8_t msg3[] = { 0x49, 0x05, 0x03, 0xB0 }; @@ -735,8 +721,7 @@ static PT_THREAD(test_technic_large_motor(struct pt *pt)) { static const uint8_t msg58[] = { 0x02 }; // NACK // used in SIMULATE_RX/TX_MSG macros - static struct pt child; - static bool ok; + static pbio_os_state_t child; static pbio_port_t *port; static pbio_port_lump_dev_t *lump_dev; @@ -746,7 +731,7 @@ static PT_THREAD(test_technic_large_motor(struct pt *pt)) { static uint8_t num_modes; static pbio_error_t err; - PT_BEGIN(pt); + ASYNC_BEGIN(state); // Expect no device at first. @@ -754,7 +739,7 @@ static PT_THREAD(test_technic_large_motor(struct pt *pt)) { tt_uint_op(pbio_port_get_port(PBIO_PORT_ID_D, &port), ==, PBIO_SUCCESS); // baud rate for sync messages - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); @@ -824,7 +809,6 @@ static PT_THREAD(test_technic_large_motor(struct pt *pt)) { // wait for ACK SIMULATE_TX_MSG(msg55); - PT_YIELD(pt); // Simulate setting default mode SIMULATE_TX_MSG(msg56); @@ -839,7 +823,7 @@ static PT_THREAD(test_technic_large_motor(struct pt *pt)) { SIMULATE_TX_MSG(msg58); } - PT_WAIT_WHILE(pt, ({ + AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_get_lump_device(port, &expected_id, &lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -879,13 +863,12 @@ static PT_THREAD(test_technic_large_motor(struct pt *pt)) { tt_want_uint_op(mode_info[5].writable, ==, 0); - PT_YIELD(pt); end: - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } -static PT_THREAD(test_technic_xl_motor(struct pt *pt)) { +static pbio_error_t test_technic_xl_motor(pbio_os_state_t *state, void *context) { // info messages captured from Technic XL Linear Motor with logic analyzer static const uint8_t msg2[] = { 0x40, 0x2F, 0x90 }; static const uint8_t msg3[] = { 0x49, 0x05, 0x03, 0xB0 }; @@ -950,8 +933,7 @@ static PT_THREAD(test_technic_xl_motor(struct pt *pt)) { static const uint8_t msg58[] = { 0x02 }; // NACK // used in SIMULATE_RX/TX_MSG macros - static struct pt child; - static bool ok; + static pbio_os_state_t child; static pbio_port_t *port; static pbio_port_lump_dev_t *lump_dev; @@ -961,7 +943,7 @@ static PT_THREAD(test_technic_xl_motor(struct pt *pt)) { static uint8_t num_modes; static pbio_error_t err; - PT_BEGIN(pt); + ASYNC_BEGIN(state); // Expect no device at first. @@ -969,7 +951,7 @@ static PT_THREAD(test_technic_xl_motor(struct pt *pt)) { tt_uint_op(pbio_port_get_port(PBIO_PORT_ID_D, &port), ==, PBIO_SUCCESS); // baud rate for sync messages - PT_WAIT_UNTIL(pt, ({ + AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); @@ -1039,7 +1021,6 @@ static PT_THREAD(test_technic_xl_motor(struct pt *pt)) { // wait for ACK SIMULATE_TX_MSG(msg55); - PT_YIELD(pt); // Simulate setting default mode SIMULATE_TX_MSG(msg56); @@ -1054,7 +1035,7 @@ static PT_THREAD(test_technic_xl_motor(struct pt *pt)) { SIMULATE_TX_MSG(msg58); } - PT_WAIT_WHILE(pt, ({ + AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_get_lump_device(port, &expected_id, &lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -1093,25 +1074,23 @@ static PT_THREAD(test_technic_xl_motor(struct pt *pt)) { tt_want_uint_op(mode_info[5].writable, ==, 0); - PT_YIELD(pt); end: - PT_END(pt); + ASYNC_END(PBIO_SUCCESS); } struct testcase_t pbio_port_lump_tests[] = { - PBIO_PT_THREAD_TEST_WITH_PBIO(test_boost_color_distance_sensor), - PBIO_PT_THREAD_TEST_WITH_PBIO(test_boost_interactive_motor), - PBIO_PT_THREAD_TEST_WITH_PBIO(test_technic_large_motor), - PBIO_PT_THREAD_TEST_WITH_PBIO(test_technic_xl_motor), + PBIO_PT_THREAD_TEST_WITH_PBIO_OS(test_boost_color_distance_sensor), + PBIO_PT_THREAD_TEST_WITH_PBIO_OS(test_boost_interactive_motor), + PBIO_PT_THREAD_TEST_WITH_PBIO_OS(test_technic_large_motor), + PBIO_PT_THREAD_TEST_WITH_PBIO_OS(test_technic_xl_motor), END_OF_TESTCASES }; -pbio_error_t pbdrv_uart_get_instance(uint8_t id, struct process *parent_process, pbdrv_uart_dev_t **uart_dev) { +pbio_error_t pbdrv_uart_get_instance(uint8_t id, pbdrv_uart_dev_t **uart_dev) { if (id != 0) { return PBIO_ERROR_NO_DEV; } - test_uart.parent_process = parent_process; *uart_dev = &test_uart; return PBIO_SUCCESS; } @@ -1131,58 +1110,50 @@ void pbdrv_uart_init(void) { void pbdrv_uart_stop(pbdrv_uart_dev_t *uart_dev) { } -PT_THREAD(pbdrv_uart_read(struct pt *pt, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); - PT_WAIT_WHILE(pt, uart_dev->rx_msg); + AWAIT_WHILE(state, uart_dev->rx_msg); uart_dev->rx_msg = msg; uart_dev->rx_msg_length = length; uart_dev->rx_msg_result = PBIO_ERROR_AGAIN; - etimer_set(&uart_dev->rx_timer, timeout); + pbio_os_timer_set(&uart_dev->rx_timer, timeout); // If read_pos is less that read_length then we have not read everything yet - PT_WAIT_WHILE(pt, uart_dev->rx_msg_result == PBIO_ERROR_AGAIN && !etimer_expired(&uart_dev->rx_timer)); - if (etimer_expired(&uart_dev->rx_timer)) { + AWAIT_WHILE(state, uart_dev->rx_msg_result == PBIO_ERROR_AGAIN && !pbio_os_timer_is_expired(&uart_dev->rx_timer)); + if (pbio_os_timer_is_expired(&uart_dev->rx_timer)) { uart_dev->rx_msg_result = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart_dev->rx_timer); } if (uart_dev->rx_msg_result != PBIO_ERROR_AGAIN) { uart_dev->rx_msg = NULL; } - *err = uart_dev->rx_msg_result; - - PT_END(pt); + ASYNC_END(uart_dev->rx_msg_result); } -PT_THREAD(pbdrv_uart_write(struct pt *pt, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout, pbio_error_t *err)) { +pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout) { - PT_BEGIN(pt); + ASYNC_BEGIN(state); // Wait while other write operation already in progress. - PT_WAIT_WHILE(pt, uart_dev->tx_msg); + AWAIT_WHILE(state, uart_dev->tx_msg); uart_dev->tx_msg = msg; uart_dev->tx_msg_length = length; uart_dev->tx_msg_result = PBIO_ERROR_AGAIN; - etimer_set(&uart_dev->tx_timer, timeout); + pbio_os_timer_set(&uart_dev->tx_timer, timeout); - PT_WAIT_WHILE(pt, uart_dev->tx_msg_result == PBIO_ERROR_AGAIN && !etimer_expired(&uart_dev->tx_timer)); - if (etimer_expired(&uart_dev->tx_timer)) { + AWAIT_WHILE(state, uart_dev->tx_msg_result == PBIO_ERROR_AGAIN && !pbio_os_timer_is_expired(&uart_dev->tx_timer)); + if (pbio_os_timer_is_expired(&uart_dev->tx_timer)) { uart_dev->tx_msg_result = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart_dev->tx_timer); } if (uart_dev->tx_msg_result != PBIO_ERROR_AGAIN) { uart_dev->tx_msg = NULL; } - *err = uart_dev->tx_msg_result; - - PT_END(pt); + ASYNC_END(uart_dev->tx_msg_result); } diff --git a/lib/pbio/test/test-pbio.c b/lib/pbio/test/test-pbio.c index 4356a1e72..9ff147682 100644 --- a/lib/pbio/test/test-pbio.c +++ b/lib/pbio/test/test-pbio.c @@ -16,6 +16,7 @@ #include #include +#include #define PBIO_TEST_TIMEOUT 1 // seconds @@ -77,6 +78,57 @@ void pbio_test_run_thread_without_pbio_processes(void *env) { pbio_test_run_thread(env, false); } +void pbio_test_run_thread_with_pbio_os_processes(void *env) { + + pbio_os_process_func_t test_thread = env; + + pbio_os_state_t state = 0; + + struct timespec start_time, now_time; + int timeout = PBIO_TEST_TIMEOUT; + + const char *pbio_test_timeout = getenv("PBIO_TEST_TIMEOUT"); + if (pbio_test_timeout) { + timeout = atoi(pbio_test_timeout); + } + + // REVISIT: we may also want to enable debug logging in non-thread tests + int debug = 0; + const char *pbio_test_debug = getenv("PBIO_TEST_DEBUG"); + if (pbio_test_debug) { + debug = atoi(pbio_test_debug); + } + + if (debug) { + hci_dump_init(hci_dump_posix_stdout_get_instance()); + } + hci_dump_enable_log_level(HCI_DUMP_LOG_LEVEL_DEBUG, debug); + hci_dump_enable_log_level(HCI_DUMP_LOG_LEVEL_INFO, debug); + hci_dump_enable_log_level(HCI_DUMP_LOG_LEVEL_ERROR, 1); + + // Pbdrv doesn't have a hook for enabling processes. The simulation driver + // throws off timing for tests that rely on the clock. So we disable it + // when not needed. + pbdrv_motor_driver_disable_process(); + + pbio_init(true); + + clock_gettime(CLOCK_MONOTONIC, &start_time); + + while ((test_thread(&state, NULL)) == PBIO_ERROR_AGAIN) { + pbio_os_run_processes_once(); + if (timeout > 0) { + clock_gettime(CLOCK_MONOTONIC, &now_time); + if (difftime(now_time.tv_sec, start_time.tv_sec) > timeout) { + tt_abort_printf(("Test timed out on line %d", state)); + } + } + } + +end:; +} + + static void *setup(const struct testcase_t *test_case) { // just passing through the protothread return test_case->setup_data; diff --git a/lib/pbio/test/test-pbio.h b/lib/pbio/test/test-pbio.h index 6deff7f52..e10f06926 100644 --- a/lib/pbio/test/test-pbio.h +++ b/lib/pbio/test/test-pbio.h @@ -23,7 +23,13 @@ #define PBIO_PT_THREAD_TEST_WITH_PBIO(name) \ { #name, pbio_test_run_thread_with_pbio_processes, TT_FORK, &pbio_test_setup, name } -void pbio_test_run_thread_with_pbio_processes(void *env); +// Use this macro to define tests that _do_ require a pbio os event loop +// with pbio processes enabled +#define PBIO_PT_THREAD_TEST_WITH_PBIO_OS(name) \ + { #name, pbio_test_run_thread_with_pbio_os_processes, TT_FORK, &pbio_test_setup, name } + +void pbio_test_run_thread_with_pbio_os_processes(void *env); // new thread format +void pbio_test_run_thread_with_pbio_processes(void *env); // legacy thread format void pbio_test_run_thread_without_pbio_processes(void *env); extern struct testcase_setup_t pbio_test_setup; diff --git a/pybricks/iodevices/pb_type_uart_device.c b/pybricks/iodevices/pb_type_uart_device.c index 7873b7f73..0ea7bee5b 100644 --- a/pybricks/iodevices/pb_type_uart_device.c +++ b/pybricks/iodevices/pb_type_uart_device.c @@ -28,10 +28,10 @@ typedef struct _pb_type_uart_device_obj_t { pbio_port_t *port; pbdrv_uart_dev_t *uart_dev; uint32_t timeout; - struct pt write_pt; + pbio_os_state_t write_pt; mp_obj_t write_obj; mp_obj_t write_awaitables; - struct pt read_pt; + pbio_os_state_t read_pt; mp_obj_t read_obj; mp_obj_t read_awaitables; } pb_type_uart_device_obj_t; @@ -74,15 +74,10 @@ static bool pb_type_uart_device_write_test_completion(mp_obj_t self_in, uint32_t pb_type_uart_device_obj_t *self = MP_OBJ_TO_PTR(self_in); GET_STR_DATA_LEN(self->write_obj, data, data_len); - // Set current process to port process even though user code does not deal - // with any processes. This ensures that any references set on etimer don't - // accidentally touch other processes. - pbio_port_select_process(self->port); - // Runs one iteration of the write protothread. - pbio_error_t err; - bool awaiting = PT_SCHEDULE(pbdrv_uart_write(&self->read_pt, self->uart_dev, (uint8_t *)data, data_len, self->timeout, &err)); - if (awaiting) { + pbio_error_t err = pbdrv_uart_write(&self->read_pt, self->uart_dev, (uint8_t *)data, data_len, self->timeout); + if (err == PBIO_ERROR_AGAIN) { + // Not done yet, so return false. return false; } @@ -106,7 +101,8 @@ static mp_obj_t pb_type_uart_device_write(size_t n_args, const mp_obj_t *pos_arg pb_assert(PBIO_ERROR_INVALID_ARG); } - PT_INIT(&self->write_pt); + // Reset protothread state. + self->write_pt = 0; // Prevents this object from being garbage collected while the write is in progress. self->write_obj = data_in; @@ -134,17 +130,12 @@ static MP_DEFINE_CONST_FUN_OBJ_1(pb_type_uart_device_in_waiting_obj, pb_type_uar static bool pb_type_uart_device_read_test_completion(mp_obj_t self_in, uint32_t end_time) { pb_type_uart_device_obj_t *self = MP_OBJ_TO_PTR(self_in); - // Set current process to port process even though user code does not deal - // with any processes. This ensures that any references set on etimer don't - // accidentally touch other processes. - pbio_port_select_process(self->port); - mp_obj_str_t *str = MP_OBJ_TO_PTR(self->read_obj); // Runs one iteration of the read protothread. - pbio_error_t err; - bool awaiting = PT_SCHEDULE(pbdrv_uart_read(&self->write_pt, self->uart_dev, (uint8_t *)str->data, str->len, self->timeout, &err)); - if (awaiting) { + pbio_error_t err = pbdrv_uart_read(&self->read_pt, self->uart_dev, (uint8_t *)str->data, str->len, self->timeout); + if (err == PBIO_ERROR_AGAIN) { + // Not done yet, so return false. return false; } @@ -160,17 +151,6 @@ static mp_obj_t pb_type_uart_device_read_return_value(mp_obj_t self_in) { return ret; } -// pybricks.iodevices.UARTDevice.set_baudrate -static mp_obj_t pb_type_uart_device_set_baudrate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args, - pb_type_uart_device_obj_t, self, - PB_ARG_REQUIRED(baudrate)); - - pbdrv_uart_set_baud_rate(self->uart_dev, pb_obj_get_int(baudrate_in)); - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_uart_device_set_baudrate_obj, 1, pb_type_uart_device_set_baudrate); - // pybricks.iodevices.UARTDevice.read static mp_obj_t pb_type_uart_device_read(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -182,6 +162,9 @@ static mp_obj_t pb_type_uart_device_read(size_t n_args, const mp_obj_t *pos_args mp_obj_t args[] = { length_in }; self->read_obj = MP_OBJ_TYPE_GET_SLOT(&mp_type_bytes, make_new)((mp_obj_t)&mp_type_bytes, MP_ARRAY_SIZE(args), 0, args); + // Reset protothread state. + self->read_pt = 0; + return pb_type_awaitable_await_or_wait( MP_OBJ_FROM_PTR(self), self->read_awaitables, @@ -193,6 +176,17 @@ static mp_obj_t pb_type_uart_device_read(size_t n_args, const mp_obj_t *pos_args } static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_uart_device_read_obj, 1, pb_type_uart_device_read); +// pybricks.iodevices.UARTDevice.set_baudrate +static mp_obj_t pb_type_uart_device_set_baudrate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args, + pb_type_uart_device_obj_t, self, + PB_ARG_REQUIRED(baudrate)); + + pbdrv_uart_set_baud_rate(self->uart_dev, pb_obj_get_int(baudrate_in)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_uart_device_set_baudrate_obj, 1, pb_type_uart_device_set_baudrate); + // pybricks.iodevices.UARTDevice.flush static mp_obj_t pb_type_uart_device_flush(mp_obj_t self_in) { pb_type_uart_device_obj_t *self = MP_OBJ_TO_PTR(self_in); From 22f782edb2422aef6e7af3e74cdfdbebd750ddd2 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Tue, 18 Mar 2025 11:12:25 +0100 Subject: [PATCH 11/19] pbio/motor_process: Migrate to pbio os model. --- lib/pbio/src/motor_process.c | 47 ++++++++++++++++++------------------ lib/pbio/test/test-pbio.c | 2 +- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/pbio/src/motor_process.c b/lib/pbio/src/motor_process.c index d82cef673..95d8772bc 100644 --- a/lib/pbio/src/motor_process.c +++ b/lib/pbio/src/motor_process.c @@ -1,30 +1,32 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2018-2023 The Pybricks Authors +// Copyright (c) 2018-2025 The Pybricks Authors + +#include #include #include #include #include -#include +#include #if PBIO_CONFIG_MOTOR_PROCESS != 0 -PROCESS(pbio_motor_process, "servo"); +static pbio_os_process_t pbio_motor_process; + +static pbio_error_t pbio_motor_process_thread(pbio_os_state_t *state, void *context) { + + static pbio_os_timer_t timer; -PROCESS_THREAD(pbio_motor_process, ev, data) { - static struct etimer timer; + ASYNC_BEGIN(state); - PROCESS_BEGIN(); + timer.start = pbdrv_clock_get_ms() - PBIO_CONFIG_CONTROL_LOOP_TIME_MS; + timer.duration = PBIO_CONFIG_CONTROL_LOOP_TIME_MS; // Initialize battery voltage. pbio_battery_init(); - etimer_set(&timer, PBIO_CONFIG_CONTROL_LOOP_TIME_MS); - for (;;) { - PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_TIMER && etimer_expired(&timer)); - // Update battery voltage. pbio_battery_update(); @@ -34,27 +36,26 @@ PROCESS_THREAD(pbio_motor_process, ev, data) { // Update servos pbio_servo_update_all(); - clock_time_t now = clock_time(); + // Increment start time instead waiting from here, making the + // loop time closer to the target on average. + timer.start += PBIO_CONFIG_CONTROL_LOOP_TIME_MS; - // If polling was delayed too long, we need to ensure that the next - // poll is a minimum of 1ms in the future. If we don't, the poll loop - // will not yield until and the next update will be called with a 0 time - // diff which causes issues. - if (now - etimer_start_time(&timer) >= 2 * PBIO_CONFIG_CONTROL_LOOP_TIME_MS) { - timer.timer.start = now - (PBIO_CONFIG_CONTROL_LOOP_TIME_MS - 1); + // In the rare case that polling was delayed too long, we need to + // ensure that the next poll is a minimum of 1ms in the future so we + // don't have 0 time deltas in the control code. + while (pbio_os_timer_is_expired(&timer)) { + timer.start++; } - // Reset timer to wait for next update. Using etimer_reset() instead - // of etimer_restart() makes average update period closer to the expected - // PBIO_CONFIG_CONTROL_LOOP_TIME_MS when occasional delays occur. - etimer_reset(&timer); + AWAIT_UNTIL(state, pbio_os_timer_is_expired(&timer)); } - PROCESS_END(); + // Unreachable. + ASYNC_END(PBIO_ERROR_FAILED); } void pbio_motor_process_start(void) { - process_start(&pbio_motor_process); + pbio_os_process_start(&pbio_motor_process, pbio_motor_process_thread, NULL); } #endif // PBIO_CONFIG_MOTOR_PROCESS diff --git a/lib/pbio/test/test-pbio.c b/lib/pbio/test/test-pbio.c index 9ff147682..b2351557f 100644 --- a/lib/pbio/test/test-pbio.c +++ b/lib/pbio/test/test-pbio.c @@ -58,7 +58,7 @@ static void pbio_test_run_thread(void *env, bool start_pbio_processes) { clock_gettime(CLOCK_MONOTONIC, &start_time); while (PT_SCHEDULE(test_thread(&pt))) { - pbio_do_one_event(); + pbio_os_run_processes_once(); if (timeout > 0) { clock_gettime(CLOCK_MONOTONIC, &now_time); if (difftime(now_time.tv_sec, start_time.tv_sec) > timeout) { From 8a528370f289298d622b030efed73006c368fdf0 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Fri, 28 Mar 2025 13:01:54 +0100 Subject: [PATCH 12/19] pbio/os: Use standard notation for unused yield variable. This is the more idiomatic way. The notation if (do_yield_now) {;} stems from Contiki, which can be a bit mysterious in the context of these macros. Its sole purpose to suppress unused variable warnings is not obvious. --- lib/pbio/include/pbio/os.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index e3db3bd5b..d37450241 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -136,9 +136,12 @@ struct _pbio_os_process_t { * which the protothread runs. All C statements above the ASYNC_BEGIN() * invocation will be executed each time the protothread is scheduled. * + * The do_yield_now variable is used only to facilitate the AWAIT_ONCE macro. + * It shouldn't take any space if unused and optimizations are enabled. + * * @param [in] state Protothread state. */ -#define ASYNC_BEGIN(state) { char do_yield_now = 0; if (do_yield_now) {;} PB_LC_RESUME(state) +#define ASYNC_BEGIN(state) { char do_yield_now = 0; (void)do_yield_now; PB_LC_RESUME(state) /** * Declare the end of a protothread and returns with given code. From b3717befb638467c2c03b25d8c58573139aa5382 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Fri, 28 Mar 2025 13:05:53 +0100 Subject: [PATCH 13/19] pbio/os: Clarify departure from Contiki. Replace specific note about differences with generic one as this is not the full picture. --- lib/pbio/include/pbio/os.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index d37450241..d16b71741 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -48,6 +48,9 @@ * * The code in this file stems from Contiki's implementation of protothreads as * listed above. This adaptation changes the API to work better with Pybricks. + * + * Caution: it is conceptually very similar to Contiki but it cannot be used as + * a drop-in replacement. */ #ifndef _PBIO_OS_H_ @@ -149,8 +152,6 @@ struct _pbio_os_process_t { * This macro is used for declaring that a protothread ends. It must * always be used together with a matching ASYNC_BEGIN() macro. * - * NB: In contrast to Contiki, this does not call ASYNC_INIT() before exiting. - * * @param [in] err Error code to return. */ #define ASYNC_END(err) PB_LC_END(); return err; } From fce8d730f046025a9410772431f012af97b31395 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Fri, 28 Mar 2025 15:49:46 +0100 Subject: [PATCH 14/19] pbio/os: Use namespace for macros. This makes the invocations a bit longer to fit, but it is probably clearer in the long run. While we are at it, cut one level of abstraction and specify macros in terms of the switch statement directly. Also, instead of LC (contiki), use the terminology "checkpoint", which is untuitive and also technically quite close to reality --- lib/pbio/drv/charger/charger_mp2639a.c | 12 +- lib/pbio/drv/pwm/pwm_lp50xx_stm32.c | 12 +- lib/pbio/drv/uart/uart_debug_first_port.c | 10 +- lib/pbio/drv/uart/uart_ev3.c | 12 +- lib/pbio/drv/uart/uart_stm32f0.c | 12 +- lib/pbio/drv/uart/uart_stm32f4_ll_irq.c | 12 +- lib/pbio/drv/uart/uart_stm32l4_ll_dma.c | 12 +- lib/pbio/include/pbio/os.h | 157 ++++++++++++---------- lib/pbio/src/motor_process.c | 6 +- lib/pbio/src/port.c | 10 +- lib/pbio/src/port_dcm_ev3.c | 48 +++---- lib/pbio/src/port_dcm_pup.c | 26 ++-- lib/pbio/src/port_lump.c | 42 +++--- lib/pbio/test/src/test_lump.c | 80 +++++------ 14 files changed, 232 insertions(+), 219 deletions(-) diff --git a/lib/pbio/drv/charger/charger_mp2639a.c b/lib/pbio/drv/charger/charger_mp2639a.c index 0e5797245..4edaec18f 100644 --- a/lib/pbio/drv/charger/charger_mp2639a.c +++ b/lib/pbio/drv/charger/charger_mp2639a.c @@ -182,17 +182,17 @@ pbio_error_t pbdrv_charger_mp2639a_process_thread(pbio_os_state_t *state, void * static pbio_os_timer_t timer; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); #if PBDRV_CONFIG_CHARGER_MP2639A_MODE_PWM while (pbdrv_pwm_get_dev(platform.mode_pwm_id, &mode_pwm) != PBIO_SUCCESS) { - AWAIT_ONCE(state); + PBIO_OS_AWAIT_ONCE_AND_POLL(state); } #endif #if PBDRV_CONFIG_CHARGER_MP2639A_ISET_PWM while (pbdrv_pwm_get_dev(platform.iset_pwm_id, &iset_pwm) != PBIO_SUCCESS) { - AWAIT_ONCE(state); + PBIO_OS_AWAIT_ONCE_AND_POLL(state); } #endif @@ -214,7 +214,7 @@ pbio_error_t pbdrv_charger_mp2639a_process_thread(pbio_os_state_t *state, void * static uint32_t charge_count = 0; for (;;) { - AWAIT_MS(state, &timer, PBDRV_CHARGER_MP2639A_STATUS_SAMPLE_TIME); + PBIO_OS_AWAIT_MS(state, &timer, PBDRV_CHARGER_MP2639A_STATUS_SAMPLE_TIME); // Enable charger chip based on USB state. We don't need to disable it // on charger fault since the chip will automatically disable itself. @@ -272,12 +272,12 @@ pbio_error_t pbdrv_charger_mp2639a_process_thread(pbio_os_state_t *state, void * if (charge_count > (PBDRV_CHARGER_MP2639A_CHARGE_TIMEOUT_MS / PBDRV_CHARGER_MP2639A_STATUS_SAMPLE_TIME)) { pbdrv_charger_status = PBDRV_CHARGER_STATUS_DISCHARGE; pbdrv_charger_enable(false, PBDRV_CHARGER_LIMIT_NONE); - AWAIT_MS(state, &timer, PBDRV_CHARGER_MP2639A_CHARGE_PAUSE_MS); + PBIO_OS_AWAIT_MS(state, &timer, PBDRV_CHARGER_MP2639A_CHARGE_PAUSE_MS); charge_count = 0; } } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } void pbdrv_charger_init(void) { diff --git a/lib/pbio/drv/pwm/pwm_lp50xx_stm32.c b/lib/pbio/drv/pwm/pwm_lp50xx_stm32.c index 49b6990ec..ca9910677 100644 --- a/lib/pbio/drv/pwm/pwm_lp50xx_stm32.c +++ b/lib/pbio/drv/pwm/pwm_lp50xx_stm32.c @@ -119,10 +119,10 @@ static pbio_error_t pbdrv_pwm_lp50xx_stm32_process_thread(pbio_os_state_t *state pbdrv_pwm_lp50xx_stm32_priv_t *priv = context; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // Need to allow all drivers to init first. - AWAIT_ONCE(state); + PBIO_OS_AWAIT_ONCE_AND_POLL(state); static const struct { uint8_t reg; @@ -149,14 +149,14 @@ static pbio_error_t pbdrv_pwm_lp50xx_stm32_process_thread(pbio_os_state_t *state }; HAL_FMPI2C_Master_Transmit_DMA(&priv->hfmpi2c, I2C_ADDR, (void *)&init_data, sizeof(init_data)); - AWAIT_UNTIL(state, HAL_FMPI2C_GetState(&priv->hfmpi2c) == HAL_FMPI2C_STATE_READY); + PBIO_OS_AWAIT_UNTIL(state, HAL_FMPI2C_GetState(&priv->hfmpi2c) == HAL_FMPI2C_STATE_READY); // initialization is finished so consumers can use this PWM device now. priv->pwm->funcs = &pbdrv_pwm_lp50xx_stm32_funcs; pbdrv_init_busy_down(); for (;;) { - AWAIT_UNTIL(state, priv->changed); + PBIO_OS_AWAIT_UNTIL(state, priv->changed); static struct { uint8_t reg; @@ -168,11 +168,11 @@ static pbio_error_t pbdrv_pwm_lp50xx_stm32_process_thread(pbio_os_state_t *state memcpy(color_data.values, priv->values, LP50XX_NUM_CH); HAL_FMPI2C_Master_Transmit_DMA(&priv->hfmpi2c, I2C_ADDR, (void *)&color_data, sizeof(color_data)); priv->changed = false; - AWAIT_UNTIL(state, HAL_FMPI2C_GetState(&priv->hfmpi2c) == HAL_FMPI2C_STATE_READY); + PBIO_OS_AWAIT_UNTIL(state, HAL_FMPI2C_GetState(&priv->hfmpi2c) == HAL_FMPI2C_STATE_READY); } // Unreachable. - ASYNC_END(PBIO_ERROR_FAILED); + PBIO_OS_ASYNC_END(PBIO_ERROR_FAILED); } void pbdrv_pwm_lp50xx_stm32_init(pbdrv_pwm_dev_t *devs) { diff --git a/lib/pbio/drv/uart/uart_debug_first_port.c b/lib/pbio/drv/uart/uart_debug_first_port.c index 6147a43f4..3b4484f92 100644 --- a/lib/pbio/drv/uart/uart_debug_first_port.c +++ b/lib/pbio/drv/uart/uart_debug_first_port.c @@ -74,21 +74,21 @@ static pbio_error_t pbdrv_uart_debug_process_thread(pbio_os_state_t *state, void static pbio_error_t err; static size_t write_size; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); while (pbdrv_uart_get_instance(0, &debug_uart) != PBIO_SUCCESS) { - AWAIT_ONCE(state); + PBIO_OS_AWAIT_ONCE_AND_POLL(state); } pbdrv_uart_set_baud_rate(debug_uart, 115200); for (;;) { - AWAIT_UNTIL(state, ring_head != ring_tail); + PBIO_OS_AWAIT_UNTIL(state, ring_head != ring_tail); // Write up to the end of the buffer without wrapping. size_t end = ring_head > ring_tail ? ring_head: BUF_SIZE; write_size = end - ring_tail; - AWAIT(state, &child, pbdrv_uart_write(&child, debug_uart, &ring_buf[ring_tail], write_size, 100)); + PBIO_OS_AWAIT(state, &child, pbdrv_uart_write(&child, debug_uart, &ring_buf[ring_tail], write_size, 100)); ring_tail = (ring_tail + write_size) % BUF_SIZE; // Reset on failure. @@ -105,7 +105,7 @@ static pbio_error_t pbdrv_uart_debug_process_thread(pbio_os_state_t *state, void } // Unreachable. - ASYNC_END(PBIO_ERROR_FAILED); + PBIO_OS_ASYNC_END(PBIO_ERROR_FAILED); } void pbdrv_uart_debug_init(void) { diff --git a/lib/pbio/drv/uart/uart_ev3.c b/lib/pbio/drv/uart/uart_ev3.c index 7ea301ab4..71b8149dd 100644 --- a/lib/pbio/drv/uart/uart_ev3.c +++ b/lib/pbio/drv/uart/uart_ev3.c @@ -86,7 +86,7 @@ int32_t pbdrv_uart_get_char(pbdrv_uart_dev_t *uart) { pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); if (uart->read_buf) { return PBIO_ERROR_BUSY; @@ -101,7 +101,7 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uin } // Await completion or timeout. - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ // On every re-entry to the async read, drain the ring buffer // into the current read buffer. This ensures that we use // all available data if there have been multiple polls since our last @@ -123,7 +123,7 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uin return PBIO_ERROR_TIMEDOUT; } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } /** @@ -176,7 +176,7 @@ static bool pbdrv_uart_can_write(pbdrv_uart_dev_t *uart) { pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // Can only write one thing at once. if (uart->write_buf) { @@ -192,7 +192,7 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, ui } // Write one byte at a time until all bytes are written. - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ // Try to write one byte if any are remaining. if (uart->write_pos < uart->write_length) { if (pbdrv_uart_try_to_write_byte(uart, uart->write_buf[uart->write_pos])) { @@ -210,7 +210,7 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, ui return PBIO_ERROR_TIMEDOUT; } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart, uint32_t baud) { diff --git a/lib/pbio/drv/uart/uart_stm32f0.c b/lib/pbio/drv/uart/uart_stm32f0.c index dd0dd71a1..cd4759fb6 100644 --- a/lib/pbio/drv/uart/uart_stm32f0.c +++ b/lib/pbio/drv/uart/uart_stm32f0.c @@ -63,7 +63,7 @@ pbio_error_t pbdrv_uart_get_instance(uint8_t id, pbdrv_uart_dev_t **uart_dev) { pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); if (!msg || !length) { return PBIO_ERROR_INVALID_ARG; @@ -82,7 +82,7 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uin } // Await completion or timeout. - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ // On every re-entry to the async read, drain the ring buffer // into the current read buffer. This ensures that we use // all available data if there have been multiple polls since our last @@ -101,12 +101,12 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uin return PBIO_ERROR_TIMEDOUT; } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); if (!msg || !length) { return PBIO_ERROR_INVALID_ARG; @@ -127,7 +127,7 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, ui uart->USART->CR1 |= USART_CR1_TXEIE; // Await completion or timeout. - AWAIT_UNTIL(state, uart->tx_buf_index == uart->tx_buf_size || (timeout && pbio_os_timer_is_expired(&uart->tx_timer))); + PBIO_OS_AWAIT_UNTIL(state, uart->tx_buf_index == uart->tx_buf_size || (timeout && pbio_os_timer_is_expired(&uart->tx_timer))); uart->tx_buf = NULL; @@ -136,7 +136,7 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, ui return PBIO_ERROR_TIMEDOUT; } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart, uint32_t baud) { diff --git a/lib/pbio/drv/uart/uart_stm32f4_ll_irq.c b/lib/pbio/drv/uart/uart_stm32f4_ll_irq.c index e932ea1d4..586d9aba0 100644 --- a/lib/pbio/drv/uart/uart_stm32f4_ll_irq.c +++ b/lib/pbio/drv/uart/uart_stm32f4_ll_irq.c @@ -68,7 +68,7 @@ pbio_error_t pbdrv_uart_get_instance(uint8_t id, pbdrv_uart_dev_t **uart_dev) { pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); if (uart->read_buf) { return PBIO_ERROR_BUSY; @@ -83,7 +83,7 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uin } // Await completion or timeout. - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ // On every re-entry to the async read, drain the ring buffer // into the current read buffer. This ensures that we use // all available data if there have been multiple polls since our last @@ -105,12 +105,12 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uin return PBIO_ERROR_TIMEDOUT; } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); if (uart->write_buf) { return PBIO_ERROR_BUSY; @@ -127,7 +127,7 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, ui LL_USART_EnableIT_TXE(uart->pdata->uart); // Await completion or timeout. - AWAIT_UNTIL(state, uart->write_pos == uart->write_length || (timeout && pbio_os_timer_is_expired(&uart->write_timer))); + PBIO_OS_AWAIT_UNTIL(state, uart->write_pos == uart->write_length || (timeout && pbio_os_timer_is_expired(&uart->write_timer))); uart->write_buf = NULL; @@ -138,7 +138,7 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, ui return PBIO_ERROR_TIMEDOUT; } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart, uint32_t baud) { diff --git a/lib/pbio/drv/uart/uart_stm32l4_ll_dma.c b/lib/pbio/drv/uart/uart_stm32l4_ll_dma.c index 5045c6c9b..3b5d7a4aa 100644 --- a/lib/pbio/drv/uart/uart_stm32l4_ll_dma.c +++ b/lib/pbio/drv/uart/uart_stm32l4_ll_dma.c @@ -190,7 +190,7 @@ static uint32_t pbdrv_uart_get_num_available(pbdrv_uart_dev_t *uart) { pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); if (uart->read_buf) { return PBIO_ERROR_BUSY; @@ -205,7 +205,7 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uin // Wait until we have enough data or timeout. If there is enough data // already, this completes right away without yielding once first. - AWAIT_UNTIL(state, pbdrv_uart_get_num_available(uart) >= uart->read_length || (timeout && pbio_os_timer_is_expired(&uart->rx_timer))); + PBIO_OS_AWAIT_UNTIL(state, pbdrv_uart_get_num_available(uart) >= uart->read_length || (timeout && pbio_os_timer_is_expired(&uart->rx_timer))); if (timeout && pbio_os_timer_is_expired(&uart->rx_timer)) { uart->read_buf = NULL; uart->read_length = 0; @@ -225,14 +225,14 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uin uart->read_buf = NULL; uart->read_length = 0; - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) { const pbdrv_uart_stm32l4_ll_dma_platform_data_t *pdata = uart->pdata; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); if (LL_USART_IsEnabledDMAReq_TX(pdata->uart)) { return PBIO_ERROR_BUSY; @@ -252,13 +252,13 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, ui pbio_os_timer_set(&uart->tx_timer, timeout); } - AWAIT_WHILE(state, LL_USART_IsEnabledDMAReq_TX(pdata->uart) && !(timeout && pbio_os_timer_is_expired(&uart->tx_timer))); + PBIO_OS_AWAIT_WHILE(state, LL_USART_IsEnabledDMAReq_TX(pdata->uart) && !(timeout && pbio_os_timer_is_expired(&uart->tx_timer))); if ((timeout && pbio_os_timer_is_expired(&uart->tx_timer))) { LL_USART_DisableDMAReq_TX(pdata->uart); return PBIO_ERROR_TIMEDOUT; } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart, uint32_t baud) { diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index d16b71741..bde42f24e 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -72,21 +72,9 @@ void pbio_os_timer_set(pbio_os_timer_t *timer, uint32_t duration); bool pbio_os_timer_is_expired(pbio_os_timer_t *timer); - -/** - * WARNING! LC implementation using switch() does not work if an - * PB_LC_SET() is done within another switch() statement! - */ - -#define PB_LC_FALLTHROUGH __attribute__((fallthrough)); -#define PB_LC_INIT(state) *state = 0; -#define PB_LC_RESUME(state) switch (*state) { case 0: -#define PB_LC_SET(state) *state = __LINE__; PB_LC_FALLTHROUGH; case __LINE__: -#define PB_LC_END() } - /** - * Protothread state. Effectively the line number in the current file where it - * yields so it can jump there to resume later. + * Protothread state. Effectively the checkpoint (line number) in the current + * file where it yields so it can jump there to resume later. */ typedef uint32_t pbio_os_state_t; @@ -107,7 +95,7 @@ struct _pbio_os_process_t { */ pbio_os_state_t state; /** - * The protothread. + * The protothread function. */ pbio_os_process_func_t func; /** @@ -121,50 +109,67 @@ struct _pbio_os_process_t { }; /** - * Initialize a protothread. + * Reset a protothread state back to the start. * - * Initializes a protothread. Initialization must be done prior to - * starting to execute the protothread. + * Must be done prior to starting to execute the protothread. * * @param [in] state Protothread state. */ -#define ASYNC_INIT(state) PB_LC_INIT(state) +#define PBIO_OS_ASYNC_RESET(state) *state = 0; /** - * Declare the start of a protothread inside the C function - * implementing the protothread. + * Declare the start of a protothread inside the C function implementing the + * protothread. + * + * This macro is used to declare the starting point of a protothread. It should + * be placed at the start of the function in which the protothread runs. All C + * statements above this macro will be executed each time the protothread is + * scheduled. * - * This macro is used to declare the starting point of a - * protothread. It should be placed at the start of the function in - * which the protothread runs. All C statements above the ASYNC_BEGIN() - * invocation will be executed each time the protothread is scheduled. + * This macro has the effect of resuming (jumping to) the most recently set + * checkpoint in the protothread and executing the code from there until the + * next yield or return statement. * * The do_yield_now variable is used only to facilitate the AWAIT_ONCE macro. * It shouldn't take any space if unused and optimizations are enabled. * * @param [in] state Protothread state. */ -#define ASYNC_BEGIN(state) { char do_yield_now = 0; (void)do_yield_now; PB_LC_RESUME(state) +#define PBIO_OS_ASYNC_BEGIN(state) { char do_yield_now = 0; (void)do_yield_now; switch (*state) { case 0: + +/** + * Sets a protothread checkpoint state to the current line number so we can + * continue from (jump) here later if we yield. + * + * This is used as part of several other yield-like macros such as yielding + * until a condition is true. This macro is not typically used directly in user + * code. + * + * NB: Do not use within another switch() statement! + * + * @param [in] state Protothread state. + */ +#define PBIO_OS_ASYNC_SET_CHECKPOINT(state) *state = __LINE__; __attribute__((fallthrough)); case __LINE__: /** * Declare the end of a protothread and returns with given code. * * This macro is used for declaring that a protothread ends. It must - * always be used together with a matching ASYNC_BEGIN() macro. + * always be used together with a matching PBIO_OS_ASYNC_BEGIN() macro. * * @param [in] err Error code to return. */ -#define ASYNC_END(err) PB_LC_END(); return err; } +#define PBIO_OS_ASYNC_END(err) }; return err; } /** * Yields the protothread while the specified condition is true. * - * @param [in] state Protothread state. - * @param [in] condition The condition. + * @param [in] state Protothread state. + * @param [in] condition The condition (or statement expression) to check. */ -#define AWAIT_WHILE(state, condition) \ +#define PBIO_OS_AWAIT_WHILE(state, condition) \ do { \ - PB_LC_SET(state); \ + PBIO_OS_ASYNC_SET_CHECKPOINT(state); \ if (condition) { \ return PBIO_ERROR_AGAIN; \ } \ @@ -174,77 +179,85 @@ struct _pbio_os_process_t { * Yields the protothread until the specified condition is true. * * @param [in] state Protothread state. - * @param [in] condition The condition. + * @param [in] condition The condition (or statement expression) to check. */ -#define AWAIT_UNTIL(state, condition) \ +#define PBIO_OS_AWAIT_UNTIL(state, condition) \ do { \ - PB_LC_SET(state); \ + PBIO_OS_ASYNC_SET_CHECKPOINT(state); \ if (!(condition)) { \ return PBIO_ERROR_AGAIN; \ } \ } while (0) /** - * Awaits a protothread until it is done. + * Awaits another (sub) protothread within the current (host) protothread until + * it completes successfully or returns an error. * - * @param [in] state Protothread state. - * @param [in] child Protothread state of the child. - * @param [in] statement The statement to await. + * @param [in] host_state State of host protothread in which this macro is used. + * @param [in] sub_state State of the sub protothread. + * @param [in] calling_statement The statement to await. */ -#define AWAIT(state, child, statement) \ - do { \ - ASYNC_INIT((child)); \ - PB_LC_SET(state); \ - if ((statement) == PBIO_ERROR_AGAIN) { \ - return PBIO_ERROR_AGAIN; \ - } \ +#define PBIO_OS_AWAIT(host_state, sub_state, calling_statement) \ + do { \ + PBIO_OS_ASYNC_RESET((sub_state)); \ + PBIO_OS_ASYNC_SET_CHECKPOINT(host_state); \ + if ((calling_statement) == PBIO_ERROR_AGAIN) { \ + return PBIO_ERROR_AGAIN; \ + } \ } while (0) /** - * Awaits two protothreads until one of them is done. + * Awaits two protothreads until one of them completes successfully or returns + * an error. * - * @param [in] state Protothread state. - * @param [in] child1 Protothread state of the first child. - * @param [in] child2 Protothread state of the second child. - * @param [in] statement1 The first statement to await. - * @param [in] statement2 The second statement to await. + * @param [in] host_state State of the host protothread in which this macro is used. + * @param [in] sub_state_1 State of the first sub protothread. + * @param [in] sub_state_2 State of the second sub protothread. + * @param [in] calling_statement_1 (Error-assigning) calling statement of the first sub protothread. + * @param [in] calling_statement_2 (Error-assigning) calling statement of the second sub protothread. */ -#define AWAIT_RACE(state, child1, child2, statement1, statement2) \ - do { \ - ASYNC_INIT((child1)); \ - ASYNC_INIT((child2)); \ - PB_LC_SET(state); \ - if ((statement1) == PBIO_ERROR_AGAIN && (statement2) == PBIO_ERROR_AGAIN) { \ - return PBIO_ERROR_AGAIN; \ - } \ +#define PBIO_OS_AWAIT_RACE(host_state, sub_state_1, sub_state_2, calling_statement_1, calling_statement_2) \ + do { \ + PBIO_OS_ASYNC_RESET((sub_state_1)); \ + PBIO_OS_ASYNC_RESET((sub_state_2)); \ + PBIO_OS_ASYNC_SET_CHECKPOINT(host_state); \ + if ((calling_statement_1) == PBIO_ERROR_AGAIN && (calling_statement_2) == PBIO_ERROR_AGAIN) { \ + return PBIO_ERROR_AGAIN; \ + } \ } while (0) /** - * Yields the protothread once and polls to request handling again immediately. + * Yields the protothread here once and polls to request handling again + * immediately. + * + * Can be useful if several protothreads are started at the same time but not + * in any particular order. PBIO_OS_AWAIT_ONCE_AND_POLL can be used in a loop + * until the other protothreads have finished initializing as defined by + * mutually defined state variables. * * Should be used sparingly as it can cause busy waiting. Processes will keep - * running, but there is always another event pending. + * running, but there will always be another event pending after this is used. * * @param [in] state Protothread state. */ -#define AWAIT_ONCE(state) \ +#define PBIO_OS_AWAIT_ONCE_AND_POLL(state) \ do { \ do_yield_now = 1; \ - PB_LC_SET(state); \ + PBIO_OS_ASYNC_SET_CHECKPOINT(state); \ if (do_yield_now) { \ pbio_os_request_poll(); \ return PBIO_ERROR_AGAIN; \ } \ } while (0) -#define AWAIT_MS(state, timer, duration) \ - do { \ - pbio_os_timer_set(timer, duration); \ - PB_LC_SET(state); \ - if (!pbio_os_timer_is_expired(timer)) { \ - return PBIO_ERROR_AGAIN; \ - } \ - } while (0) \ +#define PBIO_OS_AWAIT_MS(state, timer, duration) \ + do { \ + pbio_os_timer_set(timer, duration); \ + PBIO_OS_ASYNC_SET_CHECKPOINT(state); \ + if (!pbio_os_timer_is_expired(timer)) { \ + return PBIO_ERROR_AGAIN; \ + } \ + } while (0) \ bool pbio_os_run_processes_once(void); diff --git a/lib/pbio/src/motor_process.c b/lib/pbio/src/motor_process.c index 95d8772bc..e31b13c91 100644 --- a/lib/pbio/src/motor_process.c +++ b/lib/pbio/src/motor_process.c @@ -18,7 +18,7 @@ static pbio_error_t pbio_motor_process_thread(pbio_os_state_t *state, void *cont static pbio_os_timer_t timer; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); timer.start = pbdrv_clock_get_ms() - PBIO_CONFIG_CONTROL_LOOP_TIME_MS; timer.duration = PBIO_CONFIG_CONTROL_LOOP_TIME_MS; @@ -47,11 +47,11 @@ static pbio_error_t pbio_motor_process_thread(pbio_os_state_t *state, void *cont timer.start++; } - AWAIT_UNTIL(state, pbio_os_timer_is_expired(&timer)); + PBIO_OS_AWAIT_UNTIL(state, pbio_os_timer_is_expired(&timer)); } // Unreachable. - ASYNC_END(PBIO_ERROR_FAILED); + PBIO_OS_ASYNC_END(PBIO_ERROR_FAILED); } void pbio_motor_process_start(void) { diff --git a/lib/pbio/src/port.c b/lib/pbio/src/port.c index 09593b58c..73880f5fd 100644 --- a/lib/pbio/src/port.c +++ b/lib/pbio/src/port.c @@ -102,7 +102,7 @@ pbio_error_t pbio_port_process_pup_thread(pbio_os_state_t *state, void *context) pbio_error_t err; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // NB: Currently only implements LEGO mode and assumes that all PUP // peripherals are available and initialized. @@ -111,11 +111,11 @@ pbio_error_t pbio_port_process_pup_thread(pbio_os_state_t *state, void *context) // Run passive device connection manager until UART device is detected. pbdrv_ioport_p5p6_set_mode(port->pdata->pins, port->uart_dev, PBDRV_IOPORT_P5P6_MODE_GPIO_ADC); - AWAIT(state, &port->child1, err = pbio_port_dcm_thread(&port->child1, &port->timer, port->connection_manager, port->pdata->pins)); + PBIO_OS_AWAIT(state, &port->child1, err = pbio_port_dcm_thread(&port->child1, &port->timer, port->connection_manager, port->pdata->pins)); // Synchronize with LUMP data stream from sensor and parse device info. pbdrv_ioport_p5p6_set_mode(port->pdata->pins, port->uart_dev, PBDRV_IOPORT_P5P6_MODE_UART); - AWAIT(state, &port->child1, err = pbio_port_lump_sync_thread(&port->child1, port->lump_dev, port->uart_dev, &port->timer)); + PBIO_OS_AWAIT(state, &port->child1, err = pbio_port_lump_sync_thread(&port->child1, port->lump_dev, port->uart_dev, &port->timer)); if (err != PBIO_SUCCESS) { // Synchronization failed. Retry. continue; @@ -124,7 +124,7 @@ pbio_error_t pbio_port_process_pup_thread(pbio_os_state_t *state, void *context) // Exchange sensor data with the LUMP device until it is disconnected. // The send thread detects this when the keep alive messages time out. pbio_port_p1p2_set_power(port, pbio_port_lump_get_power_requirements(port->lump_dev)); - AWAIT_RACE(state, &port->child1, &port->child2, + PBIO_OS_AWAIT_RACE(state, &port->child1, &port->child2, pbio_port_lump_data_recv_thread(&port->child1, port->lump_dev, port->uart_dev), pbio_port_lump_data_send_thread(&port->child2, port->lump_dev, port->uart_dev, &port->timer) ); @@ -133,7 +133,7 @@ pbio_error_t pbio_port_process_pup_thread(pbio_os_state_t *state, void *context) } // Unreachable. - ASYNC_END(PBIO_ERROR_FAILED); + PBIO_OS_ASYNC_END(PBIO_ERROR_FAILED); } /** diff --git a/lib/pbio/src/port_dcm_ev3.c b/lib/pbio/src/port_dcm_ev3.c index 13dddbc22..d064d8381 100644 --- a/lib/pbio/src/port_dcm_ev3.c +++ b/lib/pbio/src/port_dcm_ev3.c @@ -242,7 +242,7 @@ typedef struct { */ static pbio_error_t pbio_port_dcm_nxt_color_rx_msg(pbio_os_state_t *state, pbio_port_dcm_nxt_color_sensor_state_t *sensor_state, const pbdrv_ioport_pins_t *pins, pbio_os_timer_t *timer, uint8_t *msg) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); *msg = 0; pbdrv_gpio_input(&pins->p6); @@ -252,16 +252,16 @@ static pbio_error_t pbio_port_dcm_nxt_color_rx_msg(pbio_os_state_t *state, pbio_ // Clock high and wait for the data bit to settle. pbdrv_gpio_out_high(&pins->p5); - AWAIT_MS(state, timer, 1); + PBIO_OS_AWAIT_MS(state, timer, 1); *msg |= pbdrv_gpio_input(&pins->p6) << sensor_state->bit; // Clock low and wait for sensor to prepare next bit. pbdrv_gpio_out_low(&pins->p5); - AWAIT_MS(state, timer, 1); + PBIO_OS_AWAIT_MS(state, timer, 1); } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } /** @@ -275,7 +275,7 @@ static pbio_error_t pbio_port_dcm_nxt_color_rx_msg(pbio_os_state_t *state, pbio_ */ static pbio_error_t pbio_port_dcm_nxt_color_tx_msg(pbio_os_state_t *state, pbio_port_dcm_nxt_color_sensor_state_t *sensor_state, const pbdrv_ioport_pins_t *pins, pbio_os_timer_t *timer, uint8_t msg) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); pbdrv_gpio_out_low(&pins->p5); pbdrv_gpio_out_low(&pins->p6); @@ -292,12 +292,12 @@ static pbio_error_t pbio_port_dcm_nxt_color_tx_msg(pbio_os_state_t *state, pbio_ // Toggle the clock high and low, giving sensor time to register bit. pbdrv_gpio_out_high(&pins->p5); - AWAIT_MS(state, timer, 1); + PBIO_OS_AWAIT_MS(state, timer, 1); pbdrv_gpio_out_low(&pins->p5); - AWAIT_MS(state, timer, 1); + PBIO_OS_AWAIT_MS(state, timer, 1); } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } // Device connection manager state for each port @@ -320,20 +320,20 @@ static pbio_port_dcm_t dcm_state[PBIO_CONFIG_PORT_DCM_NUM_DEV]; pbio_error_t pbio_port_dcm_await_new_nxt_analog_sample(pbio_port_dcm_t *dcm, pbio_os_timer_t *timer, const pbdrv_ioport_pins_t *pins, uint32_t *value) { pbio_os_state_t *state = &dcm->child; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // Wait for LED to settle. - AWAIT_MS(state, timer, 1); + PBIO_OS_AWAIT_MS(state, timer, 1); // Request a new ADC sample. Revisit: Call back on completion instead of time. pbdrv_adc_update_soon(); - AWAIT_MS(state, timer, 4); + PBIO_OS_AWAIT_MS(state, timer, 4); // Get the value. uint8_t pin = dcm->category == DCM_CATEGORY_NXT_COLOR ? 6 : 1; *value = pbio_port_dcm_get_mv(pins, pin); - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } /** @@ -347,7 +347,7 @@ pbio_error_t pbio_port_dcm_await_new_nxt_analog_sample(pbio_port_dcm_t *dcm, pbi */ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); for (;;) { @@ -363,7 +363,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer dcm->count = 0; dcm->category = category; } - AWAIT_MS(state, timer, DCM_LOOP_TIME_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_LOOP_TIME_MS); } debug_pr("Device kind detected: %d\n", dcm->category); dcm->connected = true; @@ -393,15 +393,15 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer // The original firmware has a reset sequence where p6 is high and // then p5 is toggled twice. It also works with 8 toggles, we can // just use the send function with 0xff to achieve the same effect. - AWAIT(state, &dcm->child, pbio_port_dcm_nxt_color_tx_msg(&dcm->child, &dcm->nxt_color_state, pins, timer, 0xff)); - AWAIT_MS(state, timer, 100); + PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_nxt_color_tx_msg(&dcm->child, &dcm->nxt_color_state, pins, timer, 0xff)); + PBIO_OS_AWAIT_MS(state, timer, 100); // Set to full color mode. - AWAIT(state, &dcm->child, pbio_port_dcm_nxt_color_tx_msg(&dcm->child, &dcm->nxt_color_state, pins, timer, 13)); + PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_nxt_color_tx_msg(&dcm->child, &dcm->nxt_color_state, pins, timer, 13)); // Receive all calibration info. for (dcm->count = 0; dcm->count < sizeof(pbio_port_dcm_nxt_color_sensor_data_t); dcm->count++) { - AWAIT(state, &dcm->child, + PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_nxt_color_rx_msg(&dcm->child, &dcm->nxt_color_state, pins, timer, (uint8_t *)&dcm->nxt_color_state.data + dcm->count)); } @@ -415,9 +415,9 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer while (!pbdrv_gpio_input(&pins->p2)) { pbdrv_gpio_out_low(&pins->p5); - AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.a)); + PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.a)); pbdrv_gpio_out_high(&pins->p5); - AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.r)); + PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.r)); if (dcm->category == DCM_CATEGORY_NXT_LIGHT) { // Light sensor doesn't have green and blue. @@ -425,9 +425,9 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer } pbdrv_gpio_out_low(&pins->p5); - AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.g)); + PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.g)); pbdrv_gpio_out_high(&pins->p5); - AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.b)); + PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.b)); } pbdrv_gpio_out_low(&pins->p5); @@ -444,12 +444,12 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer if (!pbdrv_gpio_input(gpio)) { dcm->count = 0; } - AWAIT_MS(state, timer, DCM_LOOP_TIME_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_LOOP_TIME_MS); } debug_pr("Device disconnected\n"); } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } pbio_port_dcm_t *pbio_port_dcm_init_instance(uint8_t index) { diff --git a/lib/pbio/src/port_dcm_pup.c b/lib/pbio/src/port_dcm_pup.c index 452b44ffa..e9724caec 100644 --- a/lib/pbio/src/port_dcm_pup.c +++ b/lib/pbio/src/port_dcm_pup.c @@ -81,7 +81,7 @@ static const lego_device_type_id_t legodev_pup_type_id_lookup[3][3] = { */ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer, pbio_port_dcm_t *dcm, const pbdrv_ioport_pins_t *pins) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); dcm->prev_type_id = LEGO_DEVICE_TYPE_ID_NONE; dcm->dev_id_match_count = 0; @@ -99,7 +99,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer // set ID2 as input pbdrv_gpio_input(&pins->p6); - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // save current ID2 value dcm->prev_gpio_value = pbdrv_gpio_input(&pins->p6); @@ -107,7 +107,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer // set ID1 low pbdrv_gpio_out_low(&pins->uart_tx); - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID2 dcm->gpio_value = pbdrv_gpio_input(&pins->p6); @@ -121,7 +121,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer pbdrv_gpio_out_high(&pins->uart_buf); pbdrv_gpio_input(&pins->uart_tx); - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // ID1 is inverse of touch sensor value // TODO: save this value to sensor dcm @@ -137,7 +137,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer // set ID1 high pbdrv_gpio_out_high(&pins->uart_tx); - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID1 dcm->gpio_value = pbdrv_gpio_input(&pins->p5); @@ -156,7 +156,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer pbdrv_gpio_out_high(&pins->uart_buf); pbdrv_gpio_input(&pins->uart_tx); - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID1 if (pbdrv_gpio_input(&pins->p5) == 1) { @@ -168,7 +168,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer } } - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // set ID1 as input pbdrv_gpio_out_high(&pins->uart_buf); @@ -177,7 +177,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer // set ID2 high pbdrv_gpio_out_high(&pins->p6); - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID1 dcm->prev_gpio_value = pbdrv_gpio_input(&pins->p5); @@ -185,7 +185,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer // set ID2 low pbdrv_gpio_out_low(&pins->p6); - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // read ID1 dcm->gpio_value = pbdrv_gpio_input(&pins->p5); @@ -210,7 +210,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer // set ID2 high pbdrv_gpio_out_high(&pins->p6); - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // if ID2 is high if (pbdrv_gpio_input(&pins->uart_rx) == 1) { @@ -224,7 +224,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer // detection. pbdrv_gpio_out_low(&pins->uart_rx); - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // if ID2 is low if (pbdrv_gpio_input(&pins->uart_rx) == 0) { @@ -247,7 +247,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer } } - AWAIT_MS(state, timer, DCM_AWAIT_MS); + PBIO_OS_AWAIT_MS(state, timer, DCM_AWAIT_MS); // set ID2 as input pbdrv_gpio_input(&pins->p6); @@ -286,7 +286,7 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer // raise. dcm->dev_id_match_count = 0; - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } /** diff --git a/lib/pbio/src/port_lump.c b/lib/pbio/src/port_lump.c index 3e8d647c7..49ea35d41 100644 --- a/lib/pbio/src/port_lump.c +++ b/lib/pbio/src/port_lump.c @@ -734,7 +734,7 @@ pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_d pbio_error_t err; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // Reset whole state except references to static buffers memset((uint8_t *)lump_dev + offsetof(pbio_port_lump_dev_t, type_id), 0, sizeof(pbio_port_lump_dev_t) - offsetof(pbio_port_lump_dev_t, type_id)); @@ -750,7 +750,7 @@ pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_d pbdrv_uart_flush(uart_dev); - AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("Sp tx fail %d\n", err); return err; @@ -759,7 +759,7 @@ pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_d pbdrv_uart_flush(uart_dev); // read one byte to check for ACK - AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, 10)); + PBIO_OS_AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, 10)); if ((err == PBIO_SUCCESS && lump_dev->rx_msg[0] != LUMP_SYS_ACK) || err == PBIO_ERROR_TIMEDOUT) { // if we did not get ACK within 100ms, then switch to slow baud rate for sync @@ -776,7 +776,7 @@ pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_d // If there are multiple bytes waiting to be read, this drains them one // by one without requiring additional polls. This means we won't need // exact timing to get in sync. - AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT)); if (err == PBIO_ERROR_TIMEDOUT) { continue; } @@ -791,7 +791,7 @@ pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_d } // Then read the rest of the message. - AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, 2, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, 2, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("UART Rx error while reading type\n"); return err; @@ -823,7 +823,7 @@ pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_d while (lump_dev->status == PBDRV_LEGODEV_LUMP_STATUS_INFO) { // read the message header - AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("UART Rx end error during info header\n"); return err; @@ -841,7 +841,7 @@ pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_d // Read the rest of the message. if (lump_dev->rx_msg_size > 1) { - AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, lump_dev->rx_msg_size - 1, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, lump_dev->rx_msg_size - 1, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("UART Rx end error during info\n"); return err; @@ -860,14 +860,14 @@ pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_d // reply with ACK lump_dev->tx_msg[0] = LUMP_SYS_ACK; lump_dev->tx_msg_size = 1; - AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("UART Tx error during ack.\n"); return err; } // Schedule baud rate change. - AWAIT_MS(state, timer, 10); + PBIO_OS_AWAIT_MS(state, timer, 10); // Change the baud rate. pbdrv_uart_set_baud_rate(uart_dev, lump_dev->new_baud_rate); @@ -894,7 +894,7 @@ pbio_error_t pbio_port_lump_sync_thread(pbio_os_state_t *state, pbio_port_lump_d lump_dev->status = PBDRV_LEGODEV_LUMP_STATUS_DATA; - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } /** @@ -915,13 +915,13 @@ pbio_error_t pbio_port_lump_data_send_thread(pbio_os_state_t *state, pbio_port_l pbio_error_t err; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); pbio_os_timer_set(timer, EV3_UART_DATA_KEEP_ALIVE_TIMEOUT); for (;;) { - AWAIT_UNTIL(state, pbio_os_timer_is_expired(timer) || lump_dev->mode_switch.requested || lump_dev->data_set->size > 0); + PBIO_OS_AWAIT_UNTIL(state, pbio_os_timer_is_expired(timer) || lump_dev->mode_switch.requested || lump_dev->data_set->size > 0); // Handle keep alive timeout if (pbio_os_timer_is_expired(timer)) { @@ -934,7 +934,7 @@ pbio_error_t pbio_port_lump_data_send_thread(pbio_os_state_t *state, pbio_port_l lump_dev->data_rec = false; lump_dev->tx_msg[0] = LUMP_SYS_NACK; lump_dev->tx_msg_size = 1; - AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("Error during keepalive.\n"); return err; @@ -946,7 +946,7 @@ pbio_error_t pbio_port_lump_data_send_thread(pbio_os_state_t *state, pbio_port_l if (lump_dev->mode_switch.requested) { lump_dev->mode_switch.requested = false; ev3_uart_prepare_tx_msg(lump_dev, LUMP_MSG_TYPE_CMD, LUMP_CMD_SELECT, &lump_dev->mode_switch.desired_mode, 1); - AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("Setting requested mode failed.\n"); return err; @@ -960,7 +960,7 @@ pbio_error_t pbio_port_lump_data_send_thread(pbio_os_state_t *state, pbio_port_l ev3_uart_prepare_tx_msg(lump_dev, LUMP_MSG_TYPE_DATA, lump_dev->data_set->desired_mode, lump_dev->data_set->bin_data, lump_dev->data_set->size); lump_dev->data_set->size = 0; lump_dev->data_set->time = pbdrv_clock_get_ms(); - AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->write_pt, err = pbdrv_uart_write(&lump_dev->write_pt, uart_dev, lump_dev->tx_msg, lump_dev->tx_msg_size, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("Setting requested data failed.\n"); return err; @@ -968,7 +968,7 @@ pbio_error_t pbio_port_lump_data_send_thread(pbio_os_state_t *state, pbio_port_l lump_dev->data_set->time = pbdrv_clock_get_ms(); } else if (pbdrv_clock_get_ms() - lump_dev->data_set->time < 500) { // Not in the right mode yet, try again later for a reasonable amount of time. - AWAIT_MS(state, timer, 1); + PBIO_OS_AWAIT_MS(state, timer, 1); } else { // Give up setting data. lump_dev->data_set->size = 0; @@ -976,7 +976,7 @@ pbio_error_t pbio_port_lump_data_send_thread(pbio_os_state_t *state, pbio_port_l } } - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } /** @@ -1000,10 +1000,10 @@ pbio_error_t pbio_port_lump_data_recv_thread(pbio_os_state_t *state, pbio_port_l // loose data. For now, the retry after bad message size helps get back into // sync with the data stream. - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); while (true) { - AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg, 1, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("UART Rx data header end error\n"); return err; @@ -1023,7 +1023,7 @@ pbio_error_t pbio_port_lump_data_recv_thread(pbio_os_state_t *state, pbio_port_l continue; } - AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, lump_dev->rx_msg_size - 1, EV3_UART_IO_TIMEOUT)); + PBIO_OS_AWAIT(state, &lump_dev->read_pt, err = pbdrv_uart_read(&lump_dev->read_pt, uart_dev, lump_dev->rx_msg + 1, lump_dev->rx_msg_size - 1, EV3_UART_IO_TIMEOUT)); if (err != PBIO_SUCCESS) { debug_pr("UART Rx data end error\n"); return err; @@ -1034,7 +1034,7 @@ pbio_error_t pbio_port_lump_data_recv_thread(pbio_os_state_t *state, pbio_port_l } // Unreachable. - ASYNC_END(PBIO_ERROR_FAILED); + PBIO_OS_ASYNC_END(PBIO_ERROR_FAILED); } /** diff --git a/lib/pbio/test/src/test_lump.c b/lib/pbio/test/src/test_lump.c index 7a47ba31d..15e59060b 100644 --- a/lib/pbio/test/src/test_lump.c +++ b/lib/pbio/test/src/test_lump.c @@ -53,10 +53,10 @@ static void simulate_uart_complete_irq(void) { } pbio_error_t simulate_rx_msg(pbio_os_state_t *state, const uint8_t *msg, uint8_t length) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // First uartdev reads one byte header - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.rx_msg_result == PBIO_ERROR_AGAIN; })); @@ -71,7 +71,7 @@ pbio_error_t simulate_rx_msg(pbio_os_state_t *state, const uint8_t *msg, uint8_t } // then read rest of message - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.rx_msg_result == PBIO_ERROR_AGAIN; })); @@ -82,13 +82,13 @@ pbio_error_t simulate_rx_msg(pbio_os_state_t *state, const uint8_t *msg, uint8_t simulate_uart_complete_irq(); end: - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } pbio_error_t simulate_tx_msg(pbio_os_state_t *state, const uint8_t *msg, uint8_t length) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.tx_msg_result == PBIO_ERROR_AGAIN; })); @@ -103,16 +103,16 @@ pbio_error_t simulate_tx_msg(pbio_os_state_t *state, const uint8_t *msg, uint8_t simulate_uart_complete_irq(); end: - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } #define SIMULATE_RX_MSG(msg) do { \ - AWAIT(state, &child, err = simulate_rx_msg(&child, (msg), PBIO_ARRAY_SIZE(msg))); \ + PBIO_OS_AWAIT(state, &child, err = simulate_rx_msg(&child, (msg), PBIO_ARRAY_SIZE(msg))); \ tt_assert_msg(err == PBIO_SUCCESS, #msg); \ } while (0) #define SIMULATE_TX_MSG(msg) do { \ - AWAIT(state, &child, err = simulate_tx_msg(&child, (msg), PBIO_ARRAY_SIZE(msg))); \ + PBIO_OS_AWAIT(state, &child, err = simulate_tx_msg(&child, (msg), PBIO_ARRAY_SIZE(msg))); \ tt_assert_msg(err == PBIO_SUCCESS, #msg); \ } while (0) @@ -235,7 +235,7 @@ static pbio_error_t test_boost_color_distance_sensor(pbio_os_state_t *state, voi pbio_error_t err; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // Should be able to get port, but device won't be ready yet since it isn't // synched up. @@ -243,14 +243,14 @@ static pbio_error_t test_boost_color_distance_sensor(pbio_os_state_t *state, voi tt_uint_op(pbio_port_get_port(PBIO_PORT_ID_D, &port), ==, PBIO_SUCCESS); // starting baud rate of hub - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); // this device does not support syncing at 115200 SIMULATE_TX_MSG(msg_speed_115200); - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 2400; })); @@ -343,7 +343,7 @@ static pbio_error_t test_boost_color_distance_sensor(pbio_os_state_t *state, voi SIMULATE_TX_MSG(msg83); // wait for baud rate change - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); @@ -364,7 +364,7 @@ static pbio_error_t test_boost_color_distance_sensor(pbio_os_state_t *state, voi } // Wait for default mode to complete - AWAIT_WHILE(state, ({ + PBIO_OS_AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_get_lump_device(port, &expected_id, &lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -439,7 +439,7 @@ static pbio_error_t test_boost_color_distance_sensor(pbio_os_state_t *state, voi // data message with new mode SIMULATE_RX_MSG(msg88); - AWAIT_WHILE(state, ({ + PBIO_OS_AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_lump_is_ready(lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -452,7 +452,7 @@ static pbio_error_t test_boost_color_distance_sensor(pbio_os_state_t *state, voi // also do mode 8 since it requires the extended mode flag - AWAIT_WHILE(state, ({ + PBIO_OS_AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_lump_set_mode(lump_dev, 8)) == PBIO_ERROR_AGAIN; })); @@ -468,7 +468,7 @@ static pbio_error_t test_boost_color_distance_sensor(pbio_os_state_t *state, voi SIMULATE_RX_MSG(msg90); SIMULATE_RX_MSG(msg91); - AWAIT_WHILE(state, ({ + PBIO_OS_AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_lump_is_ready(lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -481,7 +481,7 @@ static pbio_error_t test_boost_color_distance_sensor(pbio_os_state_t *state, voi end: - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } static pbio_error_t test_boost_interactive_motor(pbio_os_state_t *state, void *context) { @@ -540,7 +540,7 @@ static pbio_error_t test_boost_interactive_motor(pbio_os_state_t *state, void *c static uint8_t num_modes; static pbio_error_t err; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // Should be able to get port, but device won't be ready yet since it isn't @@ -549,14 +549,14 @@ static pbio_error_t test_boost_interactive_motor(pbio_os_state_t *state, void *c tt_uint_op(pbio_port_get_port(PBIO_PORT_ID_D, &port), ==, PBIO_SUCCESS); // starting baud rate of hub - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); // this device does not support syncing at 115200 SIMULATE_TX_MSG(msg_speed_115200); - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 2400; })); @@ -601,7 +601,7 @@ static pbio_error_t test_boost_interactive_motor(pbio_os_state_t *state, void *c SIMULATE_TX_MSG(msg34); // wait for baud rate change - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); @@ -620,7 +620,7 @@ static pbio_error_t test_boost_interactive_motor(pbio_os_state_t *state, void *c SIMULATE_TX_MSG(msg37); } - AWAIT_WHILE(state, ({ + PBIO_OS_AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_get_lump_device(port, &expected_id, &lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -653,7 +653,7 @@ static pbio_error_t test_boost_interactive_motor(pbio_os_state_t *state, void *c end: - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } static pbio_error_t test_technic_large_motor(pbio_os_state_t *state, void *context) { @@ -731,7 +731,7 @@ static pbio_error_t test_technic_large_motor(pbio_os_state_t *state, void *conte static uint8_t num_modes; static pbio_error_t err; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // Expect no device at first. @@ -739,7 +739,7 @@ static pbio_error_t test_technic_large_motor(pbio_os_state_t *state, void *conte tt_uint_op(pbio_port_get_port(PBIO_PORT_ID_D, &port), ==, PBIO_SUCCESS); // baud rate for sync messages - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); @@ -823,7 +823,7 @@ static pbio_error_t test_technic_large_motor(pbio_os_state_t *state, void *conte SIMULATE_TX_MSG(msg58); } - AWAIT_WHILE(state, ({ + PBIO_OS_AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_get_lump_device(port, &expected_id, &lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -865,7 +865,7 @@ static pbio_error_t test_technic_large_motor(pbio_os_state_t *state, void *conte end: - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } static pbio_error_t test_technic_xl_motor(pbio_os_state_t *state, void *context) { @@ -943,7 +943,7 @@ static pbio_error_t test_technic_xl_motor(pbio_os_state_t *state, void *context) static uint8_t num_modes; static pbio_error_t err; - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // Expect no device at first. @@ -951,7 +951,7 @@ static pbio_error_t test_technic_xl_motor(pbio_os_state_t *state, void *context) tt_uint_op(pbio_port_get_port(PBIO_PORT_ID_D, &port), ==, PBIO_SUCCESS); // baud rate for sync messages - AWAIT_UNTIL(state, ({ + PBIO_OS_AWAIT_UNTIL(state, ({ pbio_test_clock_tick(1); test_uart.baud == 115200; })); @@ -1035,7 +1035,7 @@ static pbio_error_t test_technic_xl_motor(pbio_os_state_t *state, void *context) SIMULATE_TX_MSG(msg58); } - AWAIT_WHILE(state, ({ + PBIO_OS_AWAIT_WHILE(state, ({ pbio_test_clock_tick(1); (err = pbio_port_get_lump_device(port, &expected_id, &lump_dev)) == PBIO_ERROR_AGAIN; })); @@ -1076,7 +1076,7 @@ static pbio_error_t test_technic_xl_motor(pbio_os_state_t *state, void *context) end: - ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } struct testcase_t pbio_port_lump_tests[] = { @@ -1112,9 +1112,9 @@ void pbdrv_uart_stop(pbdrv_uart_dev_t *uart_dev) { pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); - AWAIT_WHILE(state, uart_dev->rx_msg); + PBIO_OS_AWAIT_WHILE(state, uart_dev->rx_msg); uart_dev->rx_msg = msg; uart_dev->rx_msg_length = length; @@ -1122,7 +1122,7 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev, pbio_os_timer_set(&uart_dev->rx_timer, timeout); // If read_pos is less that read_length then we have not read everything yet - AWAIT_WHILE(state, uart_dev->rx_msg_result == PBIO_ERROR_AGAIN && !pbio_os_timer_is_expired(&uart_dev->rx_timer)); + PBIO_OS_AWAIT_WHILE(state, uart_dev->rx_msg_result == PBIO_ERROR_AGAIN && !pbio_os_timer_is_expired(&uart_dev->rx_timer)); if (pbio_os_timer_is_expired(&uart_dev->rx_timer)) { uart_dev->rx_msg_result = PBIO_ERROR_TIMEDOUT; } @@ -1131,22 +1131,22 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev, uart_dev->rx_msg = NULL; } - ASYNC_END(uart_dev->rx_msg_result); + PBIO_OS_ASYNC_END(uart_dev->rx_msg_result); } pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev, uint8_t *msg, uint8_t length, uint32_t timeout) { - ASYNC_BEGIN(state); + PBIO_OS_ASYNC_BEGIN(state); // Wait while other write operation already in progress. - AWAIT_WHILE(state, uart_dev->tx_msg); + PBIO_OS_AWAIT_WHILE(state, uart_dev->tx_msg); uart_dev->tx_msg = msg; uart_dev->tx_msg_length = length; uart_dev->tx_msg_result = PBIO_ERROR_AGAIN; pbio_os_timer_set(&uart_dev->tx_timer, timeout); - AWAIT_WHILE(state, uart_dev->tx_msg_result == PBIO_ERROR_AGAIN && !pbio_os_timer_is_expired(&uart_dev->tx_timer)); + PBIO_OS_AWAIT_WHILE(state, uart_dev->tx_msg_result == PBIO_ERROR_AGAIN && !pbio_os_timer_is_expired(&uart_dev->tx_timer)); if (pbio_os_timer_is_expired(&uart_dev->tx_timer)) { uart_dev->tx_msg_result = PBIO_ERROR_TIMEDOUT; } @@ -1155,5 +1155,5 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart_dev uart_dev->tx_msg = NULL; } - ASYNC_END(uart_dev->tx_msg_result); + PBIO_OS_ASYNC_END(uart_dev->tx_msg_result); } From 1710f5e4733bacd5bca652f5898336cbfd1cc4c3 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Fri, 28 Mar 2025 16:11:11 +0100 Subject: [PATCH 15/19] pbio/os: Clarify use of pbio_os_process_init. --- lib/pbio/include/pbio/os.h | 11 +++++++++-- lib/pbio/src/os.c | 10 +++++++--- lib/pbio/src/port.c | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index bde42f24e..2ed090edb 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -208,7 +208,7 @@ struct _pbio_os_process_t { /** * Awaits two protothreads until one of them completes successfully or returns - * an error. + * an error. There is no cleanup of the other protothread. * * @param [in] host_state State of the host protothread in which this macro is used. * @param [in] sub_state_1 State of the first sub protothread. @@ -250,6 +250,13 @@ struct _pbio_os_process_t { } \ } while (0) +/** + * Yields the protothread until the specified timer expires. + * + * @param [in] state Protothread state. + * @param [in] timer The timer to check. + * @param [in] duration The duration to wait for in milliseconds. + */ #define PBIO_OS_AWAIT_MS(state, timer, duration) \ do { \ pbio_os_timer_set(timer, duration); \ @@ -269,7 +276,7 @@ pbio_error_t pbio_port_process_none_thread(pbio_os_state_t *state, void *context void pbio_os_process_start(pbio_os_process_t *process, pbio_os_process_func_t func, void *context); -void pbio_os_process_reset(pbio_os_process_t *process, pbio_os_process_func_t func); +void pbio_os_process_init(pbio_os_process_t *process, pbio_os_process_func_t func); /** * Disables interrupts and returns the previous interrupt state. diff --git a/lib/pbio/src/os.c b/lib/pbio/src/os.c index c7ca39bae..df40f0d16 100644 --- a/lib/pbio/src/os.c +++ b/lib/pbio/src/os.c @@ -80,16 +80,20 @@ void pbio_os_process_start(pbio_os_process_t *process, pbio_os_process_func_t fu process->next = NULL; process->context = context; - pbio_os_process_reset(process, func); + pbio_os_process_init(process, func); } /** - * Resets an existing process to the initial state with a new function and context. + * Initializes a process to the initial state with a protothread function to run. + * + * Can also be used to reset a process to the initial state or to change the + * protothread function. Doing so should be done with caution, but can be useful + * to make a process behave in distinct operation modes. * * @param process The process to start. * @param func The process thread function. Choose NULL if it does not need changing. */ -void pbio_os_process_reset(pbio_os_process_t *process, pbio_os_process_func_t func) { +void pbio_os_process_init(pbio_os_process_t *process, pbio_os_process_func_t func) { process->err = PBIO_ERROR_AGAIN; process->state = 0; if (func) { diff --git a/lib/pbio/src/port.c b/lib/pbio/src/port.c index 73880f5fd..232944afc 100644 --- a/lib/pbio/src/port.c +++ b/lib/pbio/src/port.c @@ -500,7 +500,7 @@ pbio_error_t pbio_port_set_mode(pbio_port_t *port, pbio_port_mode_t mode) { } // Disable thread activity by attaching a thread that does nothing. - pbio_os_process_reset(&port->process, pbio_port_process_none_thread); + pbio_os_process_init(&port->process, pbio_port_process_none_thread); port->mode = mode; switch (mode) { @@ -511,7 +511,7 @@ pbio_error_t pbio_port_set_mode(pbio_port_t *port, pbio_port_mode_t mode) { case PBIO_PORT_MODE_LEGO_DCM: // Physical modes for this mode will be set by the process so this // is all we need to do here. - pbio_os_process_reset(&port->process, pbio_port_process_pup_thread); + pbio_os_process_init(&port->process, pbio_port_process_pup_thread); // Returning e-again allows user module to wait for the port to be // ready after first entering LEGO mode, avoiding NODEV errors when // switching from direct access modes back to LEGO mode. From d16e38539eb3c5ad077b89623c1c94ec8a1dc77b Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Fri, 28 Mar 2025 16:14:19 +0100 Subject: [PATCH 16/19] pbio/os: Make poll request volatile. Can be called from interrupt handlers. --- lib/pbio/src/os.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pbio/src/os.c b/lib/pbio/src/os.c index df40f0d16..b8d76dcd4 100644 --- a/lib/pbio/src/os.c +++ b/lib/pbio/src/os.c @@ -2,11 +2,11 @@ // Copyright (c) 2025 The Pybricks Authors #include +#include #include #include -#include #include /** @@ -35,7 +35,7 @@ bool pbio_os_timer_is_expired(pbio_os_timer_t *timer) { /** * Whether a poll request is pending. */ -static bool poll_request_is_pending = false; +static volatile bool poll_request_is_pending = false; /** * Request that the event loop polls all processes. From 9a6cc19d6ead42e234b64ce7a50c346f08334c4b Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 31 Mar 2025 13:51:30 +0200 Subject: [PATCH 17/19] pbio/os: Generalize IRQ flags type. Generalizes handling to work better for the virtual hub, which doesn't use uint32_t for the interrupt state. --- bricks/_common_stm32/mphalport.c | 6 +++--- bricks/ev3/mphalport.c | 6 +++--- bricks/nxt/mphalport.c | 6 +++--- bricks/virtualhub/mp_port.c | 21 +++++++++++---------- lib/pbio/include/pbio/config.h | 7 +++++++ lib/pbio/include/pbio/os.h | 9 +++++---- lib/pbio/platform/virtual_hub/pbioconfig.h | 3 +++ lib/pbio/src/os.c | 4 ++-- lib/pbio/test/test-pbio.c | 6 +++--- 9 files changed, 40 insertions(+), 28 deletions(-) diff --git a/bricks/_common_stm32/mphalport.c b/bricks/_common_stm32/mphalport.c index 09708c554..695fd0bbd 100644 --- a/bricks/_common_stm32/mphalport.c +++ b/bricks/_common_stm32/mphalport.c @@ -24,17 +24,17 @@ void pb_stack_get_info(char **sstack, char **estack) { *estack = (char *)&_estack; } -uint32_t pbio_os_hook_disable_irq(void) { +pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { mp_uint_t flags = __get_PRIMASK(); __disable_irq(); return flags; } -void pbio_os_hook_enable_irq(uint32_t flags) { +void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { __set_PRIMASK(flags); } -void pbio_os_hook_wait_for_interrupt(void) { +void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { __WFI(); } diff --git a/bricks/ev3/mphalport.c b/bricks/ev3/mphalport.c index 1dc426565..dca460b10 100644 --- a/bricks/ev3/mphalport.c +++ b/bricks/ev3/mphalport.c @@ -27,15 +27,15 @@ void pb_stack_get_info(char **sstack, char **estack) { *estack = (char *)&_estack; } -uint32_t pbio_os_hook_disable_irq(void) { +pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { return IntDisable(); } -void pbio_os_hook_enable_irq(uint32_t flags) { +void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { IntEnable(flags); } -void pbio_os_hook_wait_for_interrupt(void) { +void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { __asm volatile ( "mov r0, #0\n" "mcr p15, 0, r0, c7, c0, 4\n" /* wait for interrupt */ diff --git a/bricks/nxt/mphalport.c b/bricks/nxt/mphalport.c index 239b837d1..569d0fb04 100644 --- a/bricks/nxt/mphalport.c +++ b/bricks/nxt/mphalport.c @@ -20,15 +20,15 @@ #include "py/mpconfig.h" #include "py/stream.h" -uint32_t pbio_os_hook_disable_irq(void) { +pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { return nx_interrupts_disable(); } -void pbio_os_hook_enable_irq(uint32_t flags) { +void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { nx_interrupts_enable(flags); } -void pbio_os_hook_wait_for_interrupt(void) { +void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { // disable the processor clock which puts it in Idle Mode. AT91C_BASE_PMC->PMC_SCDR = AT91C_PMC_PCK; } diff --git a/bricks/virtualhub/mp_port.c b/bricks/virtualhub/mp_port.c index 4493c229f..7d57198b2 100644 --- a/bricks/virtualhub/mp_port.c +++ b/bricks/virtualhub/mp_port.c @@ -94,28 +94,29 @@ void pb_event_poll_hook(void) { pbio_os_run_while_idle(); } -// This is used instead of the uint32_t flags used in embedded builds. -static sigset_t global_origmask; - -uint32_t pbio_os_hook_disable_irq(void) { +pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { sigset_t sigmask; sigfillset(&sigmask); - pthread_sigmask(SIG_SETMASK, &sigmask, &global_origmask); - return 0; + + sigset_t origmask; + pthread_sigmask(SIG_SETMASK, &sigmask, &origmask); + return origmask; } -void pbio_os_hook_enable_irq(uint32_t flags) { - pthread_sigmask(SIG_SETMASK, &global_origmask, NULL); +void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { + sigset_t origmask = (sigset_t)flags; + pthread_sigmask(SIG_SETMASK, &origmask, NULL); } -void pbio_os_hook_wait_for_interrupt(void) { +void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { struct timespec timeout = { .tv_sec = 0, .tv_nsec = 100000, }; // "sleep" with "interrupts" enabled + sigset_t origmask = flags; MP_THREAD_GIL_EXIT(); - pselect(0, NULL, NULL, NULL, &timeout, &global_origmask); + pselect(0, NULL, NULL, NULL, &timeout, &origmask); MP_THREAD_GIL_ENTER(); } diff --git a/lib/pbio/include/pbio/config.h b/lib/pbio/include/pbio/config.h index 3e284c27a..340a88903 100644 --- a/lib/pbio/include/pbio/config.h +++ b/lib/pbio/include/pbio/config.h @@ -30,4 +30,11 @@ #define PBIO_CONFIG_NUM_DRIVEBASES (PBIO_CONFIG_SERVO_NUM_DEV / 2) +#ifndef PBIO_CONFIG_OS_IRQ_FLAGS_TYPE +#include +#define PBIO_CONFIG_OS_IRQ_FLAGS_TYPE uint32_t +#endif + +typedef PBIO_CONFIG_OS_IRQ_FLAGS_TYPE pbio_os_irq_flags_t; + #endif // _PBIO_CONFIG_H_ diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index 2ed090edb..74001c040 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -58,6 +58,7 @@ #include #include +#include #include /** @@ -252,7 +253,7 @@ struct _pbio_os_process_t { /** * Yields the protothread until the specified timer expires. - * + * * @param [in] state Protothread state. * @param [in] timer The timer to check. * @param [in] duration The duration to wait for in milliseconds. @@ -285,7 +286,7 @@ void pbio_os_process_init(pbio_os_process_t *process, pbio_os_process_func_t fun * * @return The previous interrupt state. */ -uint32_t pbio_os_hook_disable_irq(void); +pbio_os_irq_flags_t pbio_os_hook_disable_irq(void); /** * Enables interrupts. @@ -294,13 +295,13 @@ uint32_t pbio_os_hook_disable_irq(void); * * @param [in] flags The previous interrupt state. */ -void pbio_os_hook_enable_irq(uint32_t flags); +void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags); /** * Waits for an interrupt. * * Must be implemented by the platform. */ -void pbio_os_hook_wait_for_interrupt(void); +void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags); #endif // _PBIO_OS_H_ diff --git a/lib/pbio/platform/virtual_hub/pbioconfig.h b/lib/pbio/platform/virtual_hub/pbioconfig.h index 7e9960b17..0c4054834 100644 --- a/lib/pbio/platform/virtual_hub/pbioconfig.h +++ b/lib/pbio/platform/virtual_hub/pbioconfig.h @@ -27,3 +27,6 @@ #define PBIO_CONFIG_TACHO (1) #define PBIO_CONFIG_ENABLE_SYS (1) + +#include +#define PBIO_CONFIG_OS_IRQ_FLAGS_TYPE sigset_t diff --git a/lib/pbio/src/os.c b/lib/pbio/src/os.c index b8d76dcd4..5e6ba3b70 100644 --- a/lib/pbio/src/os.c +++ b/lib/pbio/src/os.c @@ -160,7 +160,7 @@ void pbio_os_run_while_idle(void) { // poll_request_is_pending flag. If not, we can call wait_for_interrupt(), // which still wakes up the CPU on interrupt even though interrupts are // otherwise disabled. - uint32_t irq_flags = pbio_os_hook_disable_irq(); + pbio_os_irq_flags_t irq_flags = pbio_os_hook_disable_irq(); // DELETEME: Legacy hook for pbio event loop that plays the same role as // the pending flag. Here it ensures we don't enter sleep if there are @@ -171,7 +171,7 @@ void pbio_os_run_while_idle(void) { } if (!poll_request_is_pending) { - pbio_os_hook_wait_for_interrupt(); + pbio_os_hook_wait_for_interrupt(irq_flags); } pbio_os_hook_enable_irq(irq_flags); diff --git a/lib/pbio/test/test-pbio.c b/lib/pbio/test/test-pbio.c index b2351557f..fcd036895 100644 --- a/lib/pbio/test/test-pbio.c +++ b/lib/pbio/test/test-pbio.c @@ -143,14 +143,14 @@ struct testcase_setup_t pbio_test_setup = { .cleanup_fn = cleanup, }; -uint32_t pbio_os_hook_disable_irq(void) { +pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { return 0; } -void pbio_os_hook_enable_irq(uint32_t flags) { +void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { } -void pbio_os_hook_wait_for_interrupt(void) { +void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { } extern struct testcase_t pbdrv_bluetooth_tests[]; From 78bb2d62cdc689820b5634d517c9bafdf288fa00 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 31 Mar 2025 14:31:12 +0200 Subject: [PATCH 18/19] bricks/_common/micropython: Run event loop before raising exception. This was initially removed in https://github.com/pybricks/pybricks-micropython/pull/298, but we are restoring it to be sure that it isn't a breaking change. --- bricks/_common/micropython.c | 5 ++++- bricks/virtualhub/mp_port.c | 5 ++++- lib/pbio/include/pbio/os.h | 2 +- lib/pbio/src/os.c | 2 +- lib/pbio/sys/main.c | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/bricks/_common/micropython.c b/bricks/_common/micropython.c index b5769a104..a2a0d0011 100644 --- a/bricks/_common/micropython.c +++ b/bricks/_common/micropython.c @@ -40,9 +40,12 @@ // Implementation for MICROPY_EVENT_POLL_HOOK void pb_event_poll_hook(void) { + while (pbio_os_run_processes_once()) { + } + mp_handle_pending(true); - pbio_os_run_while_idle(); + pbio_os_run_processes_and_wait_for_event(); } // callback for when stop button is pressed in IDE or on hub diff --git a/bricks/virtualhub/mp_port.c b/bricks/virtualhub/mp_port.c index 7d57198b2..983017167 100644 --- a/bricks/virtualhub/mp_port.c +++ b/bricks/virtualhub/mp_port.c @@ -89,9 +89,12 @@ void pb_virtualhub_port_deinit(void) { // Implementation for MICROPY_EVENT_POLL_HOOK void pb_event_poll_hook(void) { + while (pbio_os_run_processes_once()) { + } + mp_handle_pending(true); - pbio_os_run_while_idle(); + pbio_os_run_processes_and_wait_for_event(); } pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index 74001c040..de8067800 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -269,7 +269,7 @@ struct _pbio_os_process_t { bool pbio_os_run_processes_once(void); -void pbio_os_run_while_idle(void); +void pbio_os_run_processes_and_wait_for_event(void); void pbio_os_request_poll(void); diff --git a/lib/pbio/src/os.c b/lib/pbio/src/os.c index 5e6ba3b70..5f154a3da 100644 --- a/lib/pbio/src/os.c +++ b/lib/pbio/src/os.c @@ -149,7 +149,7 @@ bool pbio_os_run_processes_once(void) { * enter a low power mode when possible. It will sleep for at most one * millisecond. */ -void pbio_os_run_while_idle(void) { +void pbio_os_run_processes_and_wait_for_event(void) { // Run the event loop until there is no more pending poll request. while (pbio_os_run_processes_once()) { diff --git a/lib/pbio/sys/main.c b/lib/pbio/sys/main.c index b64a7b736..72504abd2 100644 --- a/lib/pbio/sys/main.c +++ b/lib/pbio/sys/main.c @@ -95,7 +95,7 @@ int main(int argc, char **argv) { #endif // Drives all processes while we wait for user input. - pbio_os_run_while_idle(); + pbio_os_run_processes_and_wait_for_event(); if (!pbsys_main_program_start_requested()) { continue; From 0663e76d23ca8fd29f1b984afc91b4fcf33f849a Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Mon, 31 Mar 2025 15:50:20 +0200 Subject: [PATCH 19/19] pbio/os: Move IRQ hooks to platform. Proceed to make pbio more independent from MicroPython. Also restore them as static inline as they used to be prior to https://github.com/pybricks/pybricks-micropython/pull/298 which reduces build size. --- bricks/_common_stm32/mpconfigport.h | 2 +- bricks/_common_stm32/mphalport.c | 14 ----------- bricks/ev3/mpconfigport.h | 2 +- bricks/ev3/mphalport.c | 18 ------------- bricks/nxt/mpconfigport.h | 2 +- bricks/nxt/mphalport.c | 15 ----------- bricks/virtualhub/mp_port.c | 2 ++ lib/pbio/include/pbio/os.h | 25 ------------------- lib/pbio/platform/city_hub/pbio_os_config.h | 19 ++++++++++++++ .../platform/essential_hub/pbio_os_config.h | 19 ++++++++++++++ lib/pbio/platform/ev3/pbio_os_config.h | 21 ++++++++++++++++ lib/pbio/platform/move_hub/pbio_os_config.h | 19 ++++++++++++++ lib/pbio/platform/nxt/pbio_os_config.h | 19 ++++++++++++++ lib/pbio/platform/prime_hub/pbio_os_config.h | 19 ++++++++++++++ .../platform/technic_hub/pbio_os_config.h | 19 ++++++++++++++ lib/pbio/platform/test/pbio_os_config.h | 13 ++++++++++ .../platform/virtual_hub/pbio_os_config.h | 9 +++++++ lib/pbio/platform/virtual_hub/platform.c | 2 ++ lib/pbio/src/os.c | 2 ++ lib/pbio/test/test-pbio.c | 10 -------- 20 files changed, 166 insertions(+), 85 deletions(-) create mode 100644 lib/pbio/platform/city_hub/pbio_os_config.h create mode 100644 lib/pbio/platform/essential_hub/pbio_os_config.h create mode 100644 lib/pbio/platform/ev3/pbio_os_config.h create mode 100644 lib/pbio/platform/move_hub/pbio_os_config.h create mode 100644 lib/pbio/platform/nxt/pbio_os_config.h create mode 100644 lib/pbio/platform/prime_hub/pbio_os_config.h create mode 100644 lib/pbio/platform/technic_hub/pbio_os_config.h create mode 100644 lib/pbio/platform/test/pbio_os_config.h create mode 100644 lib/pbio/platform/virtual_hub/pbio_os_config.h diff --git a/bricks/_common_stm32/mpconfigport.h b/bricks/_common_stm32/mpconfigport.h index 6d965ed83..e6b82d31a 100644 --- a/bricks/_common_stm32/mpconfigport.h +++ b/bricks/_common_stm32/mpconfigport.h @@ -24,7 +24,7 @@ typedef unsigned mp_uint_t; // must be pointer size typedef long mp_off_t; -#include +#include "pbio_os_config.h" #define MICROPY_BEGIN_ATOMIC_SECTION() pbio_os_hook_disable_irq() #define MICROPY_END_ATOMIC_SECTION(state) pbio_os_hook_enable_irq(state) diff --git a/bricks/_common_stm32/mphalport.c b/bricks/_common_stm32/mphalport.c index 695fd0bbd..263981851 100644 --- a/bricks/_common_stm32/mphalport.c +++ b/bricks/_common_stm32/mphalport.c @@ -24,20 +24,6 @@ void pb_stack_get_info(char **sstack, char **estack) { *estack = (char *)&_estack; } -pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { - mp_uint_t flags = __get_PRIMASK(); - __disable_irq(); - return flags; -} - -void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { - __set_PRIMASK(flags); -} - -void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { - __WFI(); -} - // using "internal" pbdrv variable extern volatile uint32_t pbdrv_clock_ticks; diff --git a/bricks/ev3/mpconfigport.h b/bricks/ev3/mpconfigport.h index a62672070..5c34a85d6 100644 --- a/bricks/ev3/mpconfigport.h +++ b/bricks/ev3/mpconfigport.h @@ -79,7 +79,7 @@ typedef unsigned mp_uint_t; // must be pointer size typedef long mp_off_t; -#include +#include "pbio_os_config.h" #define MICROPY_BEGIN_ATOMIC_SECTION() pbio_os_hook_disable_irq() #define MICROPY_END_ATOMIC_SECTION(state) pbio_os_hook_enable_irq(state) diff --git a/bricks/ev3/mphalport.c b/bricks/ev3/mphalport.c index dca460b10..d80958a13 100644 --- a/bricks/ev3/mphalport.c +++ b/bricks/ev3/mphalport.c @@ -18,8 +18,6 @@ #include "py/mpconfig.h" #include "py/stream.h" -#include - void pb_stack_get_info(char **sstack, char **estack) { extern uint32_t _estack; extern uint32_t _sstack; @@ -27,22 +25,6 @@ void pb_stack_get_info(char **sstack, char **estack) { *estack = (char *)&_estack; } -pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { - return IntDisable(); -} - -void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { - IntEnable(flags); -} - -void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { - __asm volatile ( - "mov r0, #0\n" - "mcr p15, 0, r0, c7, c0, 4\n" /* wait for interrupt */ - ::: "r0" - ); -} - // Core delay function that does an efficient sleep and may switch thread context. // If IRQs are enabled then we must have the GIL. void mp_hal_delay_ms(mp_uint_t Delay) { diff --git a/bricks/nxt/mpconfigport.h b/bricks/nxt/mpconfigport.h index 8d1290308..c4be9d091 100644 --- a/bricks/nxt/mpconfigport.h +++ b/bricks/nxt/mpconfigport.h @@ -79,7 +79,7 @@ typedef long mp_off_t; // We need to provide a declaration/definition of alloca() #include -#include +#include "pbio_os_config.h" #define MICROPY_BEGIN_ATOMIC_SECTION() pbio_os_hook_disable_irq() #define MICROPY_END_ATOMIC_SECTION(state) pbio_os_hook_enable_irq(state) diff --git a/bricks/nxt/mphalport.c b/bricks/nxt/mphalport.c index 569d0fb04..e230ad809 100644 --- a/bricks/nxt/mphalport.c +++ b/bricks/nxt/mphalport.c @@ -4,10 +4,8 @@ #include -#include #include -#include #include #include @@ -20,19 +18,6 @@ #include "py/mpconfig.h" #include "py/stream.h" -pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { - return nx_interrupts_disable(); -} - -void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { - nx_interrupts_enable(flags); -} - -void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { - // disable the processor clock which puts it in Idle Mode. - AT91C_BASE_PMC->PMC_SCDR = AT91C_PMC_PCK; -} - void pb_stack_get_info(char **sstack, char **estack) { extern uint32_t __stack_start__; extern uint32_t __stack_end__; diff --git a/bricks/virtualhub/mp_port.c b/bricks/virtualhub/mp_port.c index 983017167..04a73f32d 100644 --- a/bricks/virtualhub/mp_port.c +++ b/bricks/virtualhub/mp_port.c @@ -14,6 +14,8 @@ #include +#include "pbio_os_config.h" + #include #include #include diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index de8067800..d6ec11615 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -279,29 +279,4 @@ void pbio_os_process_start(pbio_os_process_t *process, pbio_os_process_func_t fu void pbio_os_process_init(pbio_os_process_t *process, pbio_os_process_func_t func); -/** - * Disables interrupts and returns the previous interrupt state. - * - * Must be implemented by the platform. - * - * @return The previous interrupt state. - */ -pbio_os_irq_flags_t pbio_os_hook_disable_irq(void); - -/** - * Enables interrupts. - * - * Must be implemented by the platform. - * - * @param [in] flags The previous interrupt state. - */ -void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags); - -/** - * Waits for an interrupt. - * - * Must be implemented by the platform. - */ -void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags); - #endif // _PBIO_OS_H_ diff --git a/lib/pbio/platform/city_hub/pbio_os_config.h b/lib/pbio/platform/city_hub/pbio_os_config.h new file mode 100644 index 000000000..2d6e86b50 --- /dev/null +++ b/lib/pbio/platform/city_hub/pbio_os_config.h @@ -0,0 +1,19 @@ +#include + +#include "stm32f0xx.h" + +typedef uint32_t pbio_os_irq_flags_t; + +static inline pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { + uint32_t flags = __get_PRIMASK(); + __disable_irq(); + return flags; +} + +static inline void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { + __set_PRIMASK(flags); +} + +static inline void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { + __WFI(); +} diff --git a/lib/pbio/platform/essential_hub/pbio_os_config.h b/lib/pbio/platform/essential_hub/pbio_os_config.h new file mode 100644 index 000000000..c8bcb4069 --- /dev/null +++ b/lib/pbio/platform/essential_hub/pbio_os_config.h @@ -0,0 +1,19 @@ +#include + +#include "stm32f4xx.h" + +typedef uint32_t pbio_os_irq_flags_t; + +static inline pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { + uint32_t flags = __get_PRIMASK(); + __disable_irq(); + return flags; +} + +static inline void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { + __set_PRIMASK(flags); +} + +static inline void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { + __WFI(); +} diff --git a/lib/pbio/platform/ev3/pbio_os_config.h b/lib/pbio/platform/ev3/pbio_os_config.h new file mode 100644 index 000000000..fdf4a60ea --- /dev/null +++ b/lib/pbio/platform/ev3/pbio_os_config.h @@ -0,0 +1,21 @@ +#include + +#include + +typedef uint32_t pbio_os_irq_flags_t; + +static inline pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { + return IntDisable(); +} + +static inline void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { + IntEnable(flags); +} + +static inline void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { + __asm volatile ( + "mov r0, #0\n" + "mcr p15, 0, r0, c7, c0, 4\n" + ::: "r0" + ); +} diff --git a/lib/pbio/platform/move_hub/pbio_os_config.h b/lib/pbio/platform/move_hub/pbio_os_config.h new file mode 100644 index 000000000..2d6e86b50 --- /dev/null +++ b/lib/pbio/platform/move_hub/pbio_os_config.h @@ -0,0 +1,19 @@ +#include + +#include "stm32f0xx.h" + +typedef uint32_t pbio_os_irq_flags_t; + +static inline pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { + uint32_t flags = __get_PRIMASK(); + __disable_irq(); + return flags; +} + +static inline void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { + __set_PRIMASK(flags); +} + +static inline void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { + __WFI(); +} diff --git a/lib/pbio/platform/nxt/pbio_os_config.h b/lib/pbio/platform/nxt/pbio_os_config.h new file mode 100644 index 000000000..d8ea8b1c2 --- /dev/null +++ b/lib/pbio/platform/nxt/pbio_os_config.h @@ -0,0 +1,19 @@ +#include + +#include +#include + +typedef uint32_t pbio_os_irq_flags_t; + +static inline pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { + return nx_interrupts_disable(); +} + +static inline void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { + nx_interrupts_enable(flags); +} + +static inline void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { + // disable the processor clock which puts it in Idle Mode. + AT91C_BASE_PMC->PMC_SCDR = AT91C_PMC_PCK; +} diff --git a/lib/pbio/platform/prime_hub/pbio_os_config.h b/lib/pbio/platform/prime_hub/pbio_os_config.h new file mode 100644 index 000000000..c8bcb4069 --- /dev/null +++ b/lib/pbio/platform/prime_hub/pbio_os_config.h @@ -0,0 +1,19 @@ +#include + +#include "stm32f4xx.h" + +typedef uint32_t pbio_os_irq_flags_t; + +static inline pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { + uint32_t flags = __get_PRIMASK(); + __disable_irq(); + return flags; +} + +static inline void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { + __set_PRIMASK(flags); +} + +static inline void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { + __WFI(); +} diff --git a/lib/pbio/platform/technic_hub/pbio_os_config.h b/lib/pbio/platform/technic_hub/pbio_os_config.h new file mode 100644 index 000000000..b22f13240 --- /dev/null +++ b/lib/pbio/platform/technic_hub/pbio_os_config.h @@ -0,0 +1,19 @@ +#include + +#include "stm32l4xx.h" + +typedef uint32_t pbio_os_irq_flags_t; + +static inline pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { + uint32_t flags = __get_PRIMASK(); + __disable_irq(); + return flags; +} + +static inline void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { + __set_PRIMASK(flags); +} + +static inline void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { + __WFI(); +} diff --git a/lib/pbio/platform/test/pbio_os_config.h b/lib/pbio/platform/test/pbio_os_config.h new file mode 100644 index 000000000..46fab5dec --- /dev/null +++ b/lib/pbio/platform/test/pbio_os_config.h @@ -0,0 +1,13 @@ +#include + +typedef uint32_t pbio_os_irq_flags_t; + +static inline pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { + return 0; +} + +static inline void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { +} + +static inline void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { +} diff --git a/lib/pbio/platform/virtual_hub/pbio_os_config.h b/lib/pbio/platform/virtual_hub/pbio_os_config.h new file mode 100644 index 000000000..584366100 --- /dev/null +++ b/lib/pbio/platform/virtual_hub/pbio_os_config.h @@ -0,0 +1,9 @@ +#include + +typedef sigset_t pbio_os_irq_flags_t; + +pbio_os_irq_flags_t pbio_os_hook_disable_irq(void); + +void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags); + +void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags); diff --git a/lib/pbio/platform/virtual_hub/platform.c b/lib/pbio/platform/virtual_hub/platform.c index 574903a4f..c6722bfa9 100644 --- a/lib/pbio/platform/virtual_hub/platform.c +++ b/lib/pbio/platform/virtual_hub/platform.c @@ -3,6 +3,8 @@ #include "../../drv/motor_driver/motor_driver_virtual_simulation.h" +#include "pbio_os_config.h" + #include #include #include diff --git a/lib/pbio/src/os.c b/lib/pbio/src/os.c index 5f154a3da..bf9c210cf 100644 --- a/lib/pbio/src/os.c +++ b/lib/pbio/src/os.c @@ -5,6 +5,8 @@ #include #include +#include "pbio_os_config.h" + #include #include diff --git a/lib/pbio/test/test-pbio.c b/lib/pbio/test/test-pbio.c index fcd036895..f9f22fe82 100644 --- a/lib/pbio/test/test-pbio.c +++ b/lib/pbio/test/test-pbio.c @@ -143,16 +143,6 @@ struct testcase_setup_t pbio_test_setup = { .cleanup_fn = cleanup, }; -pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { - return 0; -} - -void pbio_os_hook_enable_irq(pbio_os_irq_flags_t flags) { -} - -void pbio_os_hook_wait_for_interrupt(pbio_os_irq_flags_t flags) { -} - extern struct testcase_t pbdrv_bluetooth_tests[]; extern struct testcase_t pbdrv_pwm_tests[]; extern struct testcase_t pbio_angle_tests[];