extmod/nimble: Make stm32 and unix NimBLE ports use synchronous events.

This changes stm32 from using PENDSV to run NimBLE to use the MicroPython
scheduler instead.  This allows Python BLE callbacks to be invoked directly
(and therefore synchronously) rather than via the ringbuffer.

The NimBLE UART HCI and event processing now happens in a scheduled task
every 128ms.  When RX IRQ idle events arrive, it will also schedule this
task to improve latency.

There is a similar change for the unix port where the background thread now
queues the scheduled task.

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
This commit is contained in:
Jim Mussared
2020-11-03 23:27:47 +11:00
committed by Damien George
parent 81e92d3d6e
commit 61d1e4b01b
15 changed files with 280 additions and 157 deletions

View File

@@ -50,22 +50,67 @@
uint8_t mp_bluetooth_hci_cmd_buf[4 + 256];
STATIC int uart_fd = -1;
// Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c).
extern bool mp_bluetooth_hci_poll(void);
STATIC const useconds_t UART_POLL_INTERVAL_US = 1000;
#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS
STATIC int uart_fd = -1;
// For synchronous mode, we run all BLE stack code inside a scheduled task.
// This task is scheduled periodically (every 1ms) by a background thread.
// Allows the stack to tell us that we should stop trying to schedule.
extern bool mp_bluetooth_hci_active(void);
// Prevent double-enqueuing of the scheduled task.
STATIC volatile bool events_task_is_scheduled = false;
STATIC mp_obj_t run_events_scheduled_task(mp_obj_t none_in) {
(void)none_in;
MICROPY_PY_BLUETOOTH_ENTER
events_task_is_scheduled = false;
MICROPY_PY_BLUETOOTH_EXIT
mp_bluetooth_hci_poll();
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(run_events_scheduled_task_obj, run_events_scheduled_task);
#endif // MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS
STATIC const useconds_t UART_POLL_INTERVAL_US = 1000;
STATIC pthread_t hci_poll_thread_id;
STATIC void *hci_poll_thread(void *arg) {
(void)arg;
DEBUG_printf("hci_poll_thread: starting\n");
#if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS
events_task_is_scheduled = false;
while (mp_bluetooth_hci_active()) {
MICROPY_PY_BLUETOOTH_ENTER
if (!events_task_is_scheduled) {
events_task_is_scheduled = mp_sched_schedule(MP_OBJ_FROM_PTR(&run_events_scheduled_task_obj), mp_const_none);
}
MICROPY_PY_BLUETOOTH_EXIT
usleep(UART_POLL_INTERVAL_US);
}
#else
// In asynchronous (i.e. ringbuffer) mode, we run the BLE stack directly from the thread.
// This will return false when the stack is shutdown.
while (mp_bluetooth_hci_poll()) {
usleep(UART_POLL_INTERVAL_US);
}
#endif
DEBUG_printf("hci_poll_thread: stopped\n");
return NULL;
}
@@ -122,6 +167,11 @@ int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) {
DEBUG_printf("mp_bluetooth_hci_uart_init (unix)\n");
if (uart_fd != -1) {
DEBUG_printf("mp_bluetooth_hci_uart_init: already active\n");
return 0;
}
char uart_device_name[256] = "/dev/ttyUSB0";
char *path = getenv("MICROPYBTUART");

View File

@@ -47,38 +47,28 @@ bool mp_bluetooth_hci_poll(void) {
}
if (mp_bluetooth_nimble_ble_state >= MP_BLUETOOTH_NIMBLE_BLE_STATE_WAITING_FOR_SYNC) {
// Pretend like we're running in IRQ context (i.e. other things can't be running at the same time).
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
// Ask NimBLE to process UART data.
mp_bluetooth_nimble_hci_uart_process();
// Run pending background operations and events, but only after HCI sync.
// Run any timers.
mp_bluetooth_nimble_os_callout_process();
mp_bluetooth_nimble_os_eventq_run_all();
MICROPY_END_ATOMIC_SECTION(atomic_state);
// Process incoming UART data, and run events as they are generated.
mp_bluetooth_nimble_hci_uart_process(true);
// Run any remaining events (e.g. if there was no UART data).
mp_bluetooth_nimble_os_eventq_run_all();
}
return true;
}
bool mp_bluetooth_hci_active(void) {
return mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF;
}
// Extra port-specific helpers.
void mp_bluetooth_nimble_hci_uart_wfi(void) {
// DEBUG_printf("mp_bluetooth_nimble_hci_uart_wfi\n");
// TODO: this should do a select() on the uart_fd.
}
uint32_t mp_bluetooth_nimble_hci_uart_enter_critical(void) {
// DEBUG_printf("mp_bluetooth_nimble_hci_uart_enter_critical\n");
MICROPY_PY_BLUETOOTH_ENTER
return atomic_state; // Always 0xffffffff
}
void mp_bluetooth_nimble_hci_uart_exit_critical(uint32_t atomic_state) {
MICROPY_PY_BLUETOOTH_EXIT
// DEBUG_printf("mp_bluetooth_nimble_hci_uart_exit_critical\n");
// This is called while NimBLE is waiting in ble_npl_sem_pend, i.e. waiting for an HCI ACK.
// Do not need to run events here, only processing incoming HCI data.
mp_bluetooth_nimble_hci_uart_process(false);
}
#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE