diff --git a/bricks/_common/micropython.c b/bricks/_common/micropython.c index 8135f8057..a2a0d0011 100644 --- a/bricks/_common/micropython.c +++ b/bricks/_common/micropython.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -39,14 +40,12 @@ // Implementation for MICROPY_EVENT_POLL_HOOK void pb_event_poll_hook(void) { - // Drive pbio event loop. - while (pbio_do_one_event()) { + while (pbio_os_run_processes_once()) { } mp_handle_pending(true); - // Platform-specific code to run on completing the poll hook. - pb_event_poll_hook_leave(); + pbio_os_run_processes_and_wait_for_event(); } // 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..e6b82d31a 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 "pbio_os_config.h" -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..263981851 100644 --- a/bricks/_common_stm32/mphalport.c +++ b/bricks/_common_stm32/mphalport.c @@ -24,19 +24,6 @@ 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); -} - // using "internal" pbdrv variable extern volatile uint32_t pbdrv_clock_ticks; diff --git a/bricks/ev3/mpconfigport.h b/bricks/ev3/mpconfigport.h index f191642e5..5c34a85d6 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 "pbio_os_config.h" -#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..d80958a13 100644 --- a/bricks/ev3/mphalport.c +++ b/bricks/ev3/mphalport.c @@ -25,28 +25,6 @@ void pb_stack_get_info(char **sstack, char **estack) { *estack = (char *)&_estack; } -static inline int arm_wfi(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. // 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 3d142736c..c4be9d091 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 "pbio_os_config.h" -#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..e230ad809 100644 --- a/bricks/nxt/mphalport.c +++ b/bricks/nxt/mphalport.c @@ -4,10 +4,8 @@ #include -#include #include -#include #include #include @@ -20,23 +18,6 @@ #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; - } - - nx_interrupts_enable(state); -} - 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 664300470..04a73f32d 100644 --- a/bricks/virtualhub/mp_port.c +++ b/bricks/virtualhub/mp_port.c @@ -14,7 +14,10 @@ #include +#include "pbio_os_config.h" + #include +#include #include #include #include @@ -72,7 +75,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 +88,48 @@ 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) { + + while (pbio_os_run_processes_once()) { } -} -// 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++; - } - - // 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; - } + pbio_os_run_processes_and_wait_for_event(); +} +pbio_os_irq_flags_t pbio_os_hook_disable_irq(void) { sigset_t sigmask; sigfillset(&sigmask); - // disable "interrupts" sigset_t origmask; pthread_sigmask(SIG_SETMASK, &sigmask, &origmask); + return 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(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(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, &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/charger/charger_mp2639a.c b/lib/pbio/drv/charger/charger_mp2639a.c index 16637b450..4edaec18f 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, void *context) { + + static pbio_os_timer_t timer; + + 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) { - PROCESS_PAUSE(); + 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) { - PROCESS_PAUSE(); + PBIO_OS_AWAIT_ONCE_AND_POLL(state); } #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)); + 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. @@ -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)); + PBIO_OS_AWAIT_MS(state, &timer, PBDRV_CHARGER_MP2639A_CHARGE_PAUSE_MS); charge_count = 0; } } - PROCESS_END(); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + +void pbdrv_charger_init(void) { + pbdrv_init_busy_up(); + pbio_os_process_start(&pbdrv_charger_mp2639a_process, pbdrv_charger_mp2639a_process_thread, NULL); } #endif // PBDRV_CONFIG_CHARGER_MP2639A 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/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/pwm/pwm_lp50xx_stm32.c b/lib/pbio/drv/pwm/pwm_lp50xx_stm32.c index 2e01a55a1..ca9910677 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; + + PBIO_OS_ASYNC_BEGIN(state); + + // Need to allow all drivers to init first. + PBIO_OS_AWAIT_ONCE_AND_POLL(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)); + 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 (;;) { + PBIO_OS_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; + PBIO_OS_AWAIT_UNTIL(state, HAL_FMPI2C_GetState(&priv->hfmpi2c) == HAL_FMPI2C_STATE_READY); + } + + // Unreachable. + PBIO_OS_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 diff --git a/lib/pbio/drv/uart/uart_debug_first_port.c b/lib/pbio/drv/uart/uart_debug_first_port.c index 55dc2a442..3b4484f92 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(); + PBIO_OS_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) { + PBIO_OS_AWAIT_ONCE_AND_POLL(state); + } pbdrv_uart_set_baud_rate(debug_uart, 115200); for (;;) { - - PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_POLL); - if (ring_head == ring_tail) { - continue; - } + 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; - PROCESS_PT_SPAWN(&child, pbdrv_uart_write(&child, debug_uart, &ring_buf[ring_tail], write_size, 100, &err)); + 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. @@ -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. + PBIO_OS_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..71b8149dd 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); + PBIO_OS_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, ({ + 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 @@ -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; + } + + PBIO_OS_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); + PBIO_OS_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, ({ + 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])) { @@ -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); + PBIO_OS_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..cd4759fb6 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) { + + PBIO_OS_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, ({ + 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 @@ -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; + } + + PBIO_OS_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); + PBIO_OS_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))); + 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; - 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); + PBIO_OS_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..586d9aba0 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); + PBIO_OS_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, ({ + 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 @@ -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; + } + + PBIO_OS_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); + PBIO_OS_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))); + PBIO_OS_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); + PBIO_OS_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..3b5d7a4aa 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); + PBIO_OS_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)) { + 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; - *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); + PBIO_OS_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); + PBIO_OS_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))) { + 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); - *err = PBIO_ERROR_TIMEDOUT; - } else { - etimer_stop(&uart->tx_timer); - *err = PBIO_SUCCESS; + return PBIO_ERROR_TIMEDOUT; } - PT_END(pt); + PBIO_OS_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/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 new file mode 100644 index 000000000..d6ec11615 --- /dev/null +++ b/lib/pbio/include/pbio/os.h @@ -0,0 +1,282 @@ +// 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. + * + * Caution: it is conceptually very similar to Contiki but it cannot be used as + * a drop-in replacement. + */ + +#ifndef _PBIO_OS_H_ +#define _PBIO_OS_H_ + +#include +#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); + +/** + * 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; + +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; + +/** + * 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 function. + */ + 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. + */ + pbio_error_t err; +}; + +/** + * Reset a protothread state back to the start. + * + * Must be done prior to starting to execute the protothread. + * + * @param [in] state Protothread state. + */ +#define PBIO_OS_ASYNC_RESET(state) *state = 0; + +/** + * 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 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 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 PBIO_OS_ASYNC_BEGIN() macro. + * + * @param [in] err Error code to return. + */ +#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 (or statement expression) to check. + */ +#define PBIO_OS_AWAIT_WHILE(state, condition) \ + do { \ + PBIO_OS_ASYNC_SET_CHECKPOINT(state); \ + if (condition) { \ + return PBIO_ERROR_AGAIN; \ + } \ + } while (0) + +/** + * Yields the protothread until the specified condition is true. + * + * @param [in] state Protothread state. + * @param [in] condition The condition (or statement expression) to check. + */ +#define PBIO_OS_AWAIT_UNTIL(state, condition) \ + do { \ + PBIO_OS_ASYNC_SET_CHECKPOINT(state); \ + if (!(condition)) { \ + return PBIO_ERROR_AGAIN; \ + } \ + } while (0) + +/** + * Awaits another (sub) protothread within the current (host) protothread until + * it completes successfully or returns an error. + * + * @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 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 completes successfully or returns + * 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. + * @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 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 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 will always be another event pending after this is used. + * + * @param [in] state Protothread state. + */ +#define PBIO_OS_AWAIT_ONCE_AND_POLL(state) \ + do { \ + do_yield_now = 1; \ + PBIO_OS_ASYNC_SET_CHECKPOINT(state); \ + if (do_yield_now) { \ + pbio_os_request_poll(); \ + return PBIO_ERROR_AGAIN; \ + } \ + } 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); \ + 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); + +void pbio_os_run_processes_and_wait_for_event(void); + +void pbio_os_request_poll(void); + +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_init(pbio_os_process_t *process, pbio_os_process_func_t func); + +#endif // _PBIO_OS_H_ 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/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/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"); 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/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/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/motor_process.c b/lib/pbio/src/motor_process.c index d82cef673..e31b13c91 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; + PBIO_OS_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); + PBIO_OS_AWAIT_UNTIL(state, pbio_os_timer_is_expired(&timer)); } - PROCESS_END(); + // Unreachable. + PBIO_OS_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/src/os.c b/lib/pbio/src/os.c new file mode 100644 index 000000000..bf9c210cf --- /dev/null +++ b/lib/pbio/src/os.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors + +#include +#include + +#include +#include "pbio_os_config.h" + +#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 volatile 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; + +/** + * Placeholder thread that does nothing. + * + * @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_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; + if (!last) { + process_list = process; + } else { + while (last->next) { + last = last->next; + } + last->next = process; + } + + // Initialize the process. + process->next = NULL; + process->context = context; + + pbio_os_process_init(process, func); +} + +/** + * 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_init(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(); +} + +/** + * 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->context); + } + 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_processes_and_wait_for_event(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. + 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 + // 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(irq_flags); + } + 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/src/port.c b/lib/pbio/src/port.c index 6cc6d5325..232944afc 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; + + PBIO_OS_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)); + 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); - PROCESS_PT_SPAWN(&port->child1, pbio_port_lump_sync_thread(&port->child1, port->lump_dev, port->uart_dev, &port->etimer)); + 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; + } // 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)) + 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) ); + pbio_port_p1p2_set_power(port, PBIO_PORT_POWER_REQUIREMENTS_NONE); } - PROCESS_END(); + // Unreachable. + PBIO_OS_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_init(&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_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. diff --git a/lib/pbio/src/port_dcm_ev3.c b/lib/pbio/src/port_dcm_ev3.c index 6650a992c..d064d8381 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); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_AWAIT_MS(state, timer, 1); } - PT_END(pt); + PBIO_OS_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); + PBIO_OS_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)); + PBIO_OS_AWAIT_MS(state, timer, 1); pbdrv_gpio_out_low(&pins->p5); - etimer_set(etimer, 1); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + PBIO_OS_AWAIT_MS(state, timer, 1); } - PT_END(pt); + PBIO_OS_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); + PBIO_OS_ASYNC_BEGIN(state); // Wait for LED to settle. - etimer_set(etimer, 1); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + PBIO_OS_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)); + 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); - PT_END(pt); + PBIO_OS_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); + PBIO_OS_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); + PBIO_OS_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)); + 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. - PT_SPAWN(pt, &dcm->child, pbio_port_dcm_nxt_color_tx_msg(&dcm->child, &dcm->nxt_color_state, pins, etimer, 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++) { - 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)); + 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)); } // 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)); + 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); - PT_SPAWN(pt, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, etimer, 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. @@ -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)); + 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); - PT_SPAWN(pt, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, etimer, 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); @@ -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); + PBIO_OS_AWAIT_MS(state, timer, DCM_LOOP_TIME_MS); } debug_pr("Device disconnected\n"); } - PT_END(pt); + 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 2a03eec6c..e9724caec 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); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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)); + PBIO_OS_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); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } /** diff --git a/lib/pbio/src/port_lump.c b/lib/pbio/src/port_lump.c index 978fe277d..49ea35d41 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; + + 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)); @@ -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); + 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; } 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)); + 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 ((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) { + 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; } - 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. + 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"); - 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) { + 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"); - 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) { + 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"); - 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) { + 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"); - PT_EXIT(pt); + return err; } - // schedule baud rate change - etimer_set(etimer, 10); - PT_WAIT_UNTIL(pt, etimer_expired(etimer)); + // Schedule baud rate change. + PBIO_OS_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); + PBIO_OS_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; + + PBIO_OS_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); + 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 (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) { + 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"); - 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) { + 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"); - 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) { + 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"); - 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); + PBIO_OS_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); + PBIO_OS_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); + PBIO_OS_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) { + 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"); - 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) { + 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"); - 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. + PBIO_OS_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/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(); } } diff --git a/lib/pbio/sys/main.c b/lib/pbio/sys/main.c index fd848019e..72504abd2 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_processes_and_wait_for_event(); 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. diff --git a/lib/pbio/test/src/test_lump.c b/lib/pbio/test/src/test_lump.c index 3999ee0b9..15e59060b 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) { + PBIO_OS_ASYNC_BEGIN(state); // First uartdev reads one byte header - PT_WAIT_UNTIL(pt, ({ + PBIO_OS_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, ({ + PBIO_OS_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); + PBIO_OS_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) { + PBIO_OS_ASYNC_BEGIN(state); - PT_WAIT_UNTIL(pt, ({ + PBIO_OS_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); + PBIO_OS_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); \ + 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 { \ - PT_SPAWN(pt, &child, simulate_tx_msg(&child, (msg), PBIO_ARRAY_SIZE(msg), &ok)); \ - tt_assert_msg(ok, #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) 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; + + PBIO_OS_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, ({ + 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); - PT_WAIT_UNTIL(pt, ({ + PBIO_OS_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, ({ + PBIO_OS_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, ({ + PBIO_OS_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, ({ + PBIO_OS_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, ({ + PBIO_OS_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, ({ + PBIO_OS_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); + PBIO_OS_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); + PBIO_OS_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, ({ + 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); - PT_WAIT_UNTIL(pt, ({ + PBIO_OS_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, ({ + PBIO_OS_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, ({ + PBIO_OS_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); + PBIO_OS_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); + PBIO_OS_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, ({ + PBIO_OS_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, ({ + PBIO_OS_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); + PBIO_OS_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); + PBIO_OS_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, ({ + PBIO_OS_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, ({ + PBIO_OS_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); + PBIO_OS_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); + PBIO_OS_ASYNC_BEGIN(state); - PT_WAIT_WHILE(pt, uart_dev->rx_msg); + PBIO_OS_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)) { + 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; - } 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); + PBIO_OS_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); + PBIO_OS_ASYNC_BEGIN(state); // Wait while other write operation already in progress. - PT_WAIT_WHILE(pt, 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; - 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)) { + 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; - } 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); + PBIO_OS_ASYNC_END(uart_dev->tx_msg_result); } diff --git a/lib/pbio/test/test-pbio.c b/lib/pbio/test/test-pbio.c index 80cf66bef..f9f22fe82 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 @@ -57,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) { @@ -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);