From 888a15cda3c7094009563fc65f19c6d8f76cc201 Mon Sep 17 00:00:00 2001 From: Peter Harper Date: Wed, 13 Jul 2022 13:08:09 +0100 Subject: [PATCH] rp2: Add Bluetooth support via cyw43. Using BTstack with CYW43 for Pico W. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 41 ++++++- ports/rp2/btstack_inc/btstack_config.h | 9 ++ ports/rp2/cyw43_configport.h | 10 +- ports/rp2/main.c | 2 +- ports/rp2/mpbthciport.c | 26 ++-- ports/rp2/mpbthciport.h | 2 +- ports/rp2/mpbtstackport.c | 157 +++++++++++++++++++++++++ ports/rp2/mpconfigport.h | 6 + ports/rp2/mpnetworkport.c | 2 +- 9 files changed, 238 insertions(+), 17 deletions(-) create mode 100644 ports/rp2/btstack_inc/btstack_config.h create mode 100644 ports/rp2/mpbtstackport.c diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 72f44b19e2..4334a0aba9 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -18,6 +18,8 @@ endif() set(PICO_TINYUSB_PATH ${MICROPY_DIR}/lib/tinyusb) # Use the local lwip instead of the one in pico-sdk set(PICO_LWIP_PATH ${MICROPY_DIR}/lib/lwip) +# Use the local btstack instead of the one in pico-sdk +set(PICO_BTSTACK_PATH ${MICROPY_DIR}/lib/btstack) # Set the location of this port's directory. set(MICROPY_PORT_DIR ${CMAKE_CURRENT_LIST_DIR}) @@ -217,11 +219,40 @@ if(MICROPY_PY_BLUETOOTH) MICROPY_PY_BLUETOOTH=1 MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS=1 MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1 - MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING=1 - MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS=1 ) endif() +if (MICROPY_PY_BLUETOOTH_CYW43) + target_compile_definitions(${MICROPY_TARGET} PRIVATE + CYW43_ENABLE_BLUETOOTH=1 + MICROPY_PY_BLUETOOTH_CYW43=1 + ) + + if (MICROPY_BLUETOOTH_BTSTACK) + target_link_libraries(${MICROPY_TARGET} + pico_btstack_hci_transport_cyw43 + ) + endif() +endif() + +if (MICROPY_BLUETOOTH_BTSTACK) + string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/btstack) + + list(APPEND MICROPY_SOURCE_PORT mpbtstackport.c) + + include(${MICROPY_DIR}/extmod/btstack/btstack.cmake) + target_link_libraries(${MICROPY_TARGET} micropy_extmod_btstack) + + target_compile_definitions(${MICROPY_TARGET} PRIVATE + MICROPY_BLUETOOTH_BTSTACK=1 + MICROPY_BLUETOOTH_BTSTACK_CONFIG_FILE=\"btstack_inc/btstack_config.h\" + ) + + # For modbluetooth_btstack.c includes + get_target_property(BTSTACK_INCLUDE micropy_extmod_btstack INTERFACE_INCLUDE_DIRECTORIES) + list(APPEND MICROPY_INC_CORE ${BTSTACK_INCLUDE}) +endif() + if(MICROPY_BLUETOOTH_NIMBLE) string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/mynewt-nimble) if(NOT (${ECHO_SUBMODULES}) AND NOT EXISTS ${MICROPY_DIR}/lib/mynewt-nimble/nimble/host/include/host/ble_hs.h) @@ -232,6 +263,8 @@ if(MICROPY_BLUETOOTH_NIMBLE) target_compile_definitions(${MICROPY_TARGET} PRIVATE MICROPY_BLUETOOTH_NIMBLE=1 MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY=0 + MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING=1 + MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS=1 ) target_compile_options(${MICROPY_TARGET} PRIVATE # TODO: This flag is currently needed to make nimble build. @@ -272,6 +305,10 @@ if (MICROPY_PY_NETWORK_CYW43) cyw43_driver_picow cmsis_core ) + target_include_directories(${MICROPY_TARGET} PRIVATE + ${MICROPY_DIR}/lib/cyw43-driver/ + ) + endif() if (MICROPY_PY_NETWORK_NINAW10) diff --git a/ports/rp2/btstack_inc/btstack_config.h b/ports/rp2/btstack_inc/btstack_config.h new file mode 100644 index 0000000000..ec7cd7fb72 --- /dev/null +++ b/ports/rp2/btstack_inc/btstack_config.h @@ -0,0 +1,9 @@ +#ifndef MICROPY_INCLUDED_BTSTACK_BTSTACK_CONFIG_H +#define MICROPY_INCLUDED_BTSTACK_BTSTACK_CONFIG_H + +#define HCI_OUTGOING_PRE_BUFFER_SIZE 4 +#define HCI_ACL_CHUNK_SIZE_ALIGNMENT 4 + +#include "btstack_config_common.h" + +#endif diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h index 08cded6f1f..0c4a032100 100644 --- a/ports/rp2/cyw43_configport.h +++ b/ports/rp2/cyw43_configport.h @@ -33,7 +33,6 @@ #include "extmod/modnetwork.h" #include "pendsv.h" -#define CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE "w43439A0_7_95_49_00_combined.h" #define CYW43_WIFI_NVRAM_INCLUDE_FILE "wifi_nvram_43439.h" #define CYW43_IOCTL_TIMEOUT_US (1000000) #define CYW43_SLEEP_MAX (10) @@ -91,6 +90,15 @@ #define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) +// Bluetooth uses the C heap to load its firmware (provided by pico-sdk). +// Space is reserved for this, see MICROPY_C_HEAP_SIZE. +#ifndef cyw43_malloc +#define cyw43_malloc malloc +#endif +#ifndef cyw43_free +#define cyw43_free free +#endif + void cyw43_post_poll_hook(void); extern volatile int cyw43_has_pending; diff --git a/ports/rp2/main.c b/ports/rp2/main.c index c5118857a4..ff0384b952 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -122,7 +122,7 @@ int main(int argc, char **argv) { #endif #endif - #if MICROPY_PY_NETWORK_CYW43 + #if MICROPY_PY_NETWORK_CYW43 || MICROPY_PY_BLUETOOTH_CYW43 { cyw43_init(&cyw43_state); cyw43_irq_init(); diff --git a/ports/rp2/mpbthciport.c b/ports/rp2/mpbthciport.c index 8840fe52f1..ac05e4607e 100644 --- a/ports/rp2/mpbthciport.c +++ b/ports/rp2/mpbthciport.c @@ -50,11 +50,6 @@ void mp_bluetooth_hci_init(void) { events_task_is_scheduled = false; } -STATIC void mp_bluetooth_hci_start_polling(void) { - events_task_is_scheduled = false; - mp_bluetooth_hci_poll_now(); -} - static int64_t mp_bluetooth_hci_timer_callback(alarm_id_t id, void *user_data) { poll_timer_id = 0; mp_bluetooth_hci_poll_now(); @@ -70,7 +65,7 @@ void mp_bluetooth_hci_poll_in_ms(uint32_t ms) { STATIC mp_obj_t run_events_scheduled_task(mp_obj_t none_in) { (void)none_in; events_task_is_scheduled = false; - // This will process all buffered HCI UART data, and run any callouts or events. + // This will process all HCI data, and run any callouts or events. mp_bluetooth_hci_poll(); return mp_const_none; } @@ -88,8 +83,15 @@ void mp_bluetooth_hci_poll_now(void) { } } +#if defined(MICROPY_HW_BLE_UART_ID) + mp_obj_t mp_bthci_uart; +STATIC void mp_bluetooth_hci_start_polling(void) { + events_task_is_scheduled = false; + mp_bluetooth_hci_poll_now(); +} + int mp_bluetooth_hci_uart_init(uint32_t port, uint32_t baudrate) { debug_printf("mp_bluetooth_hci_uart_init\n"); @@ -121,11 +123,6 @@ int mp_bluetooth_hci_uart_deinit(void) { return 0; } -int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { - debug_printf("mp_bluetooth_hci_uart_set_baudrate(%lu)\n", baudrate); - return 0; -} - int mp_bluetooth_hci_uart_any(void) { int errcode = 0; const mp_stream_p_t *proto = (mp_stream_p_t *)MP_OBJ_TYPE_GET_SLOT(&machine_uart_type, protocol); @@ -171,6 +168,13 @@ int mp_bluetooth_hci_uart_readchar(void) { } } +int mp_bluetooth_hci_uart_set_baudrate(uint32_t baudrate) { + debug_printf("mp_bluetooth_hci_uart_set_baudrate(%lu)\n", baudrate); + return 0; +} + +#endif // defined(MICROPY_HW_BLE_UART_ID) + // Default (weak) implementation of the HCI controller interface. // A driver (e.g. cywbt43.c) can override these for controller-specific // functionality (i.e. power management). diff --git a/ports/rp2/mpbthciport.h b/ports/rp2/mpbthciport.h index ac5263aa11..fdf46c506c 100644 --- a/ports/rp2/mpbthciport.h +++ b/ports/rp2/mpbthciport.h @@ -34,7 +34,7 @@ void mp_bluetooth_hci_poll_now(void); void mp_bluetooth_hci_poll_in_ms(uint32_t ms); // Must be provided by the stack bindings (e.g. mpnimbleport.c or mpbtstackport.c). -// Request new data from the uart and pass to the stack, and run pending events/callouts. +// Request new HCI data and pass to the stack, and run pending events/callouts. // This is a low-level function and should not be called directly, use // mp_bluetooth_hci_poll_now/mp_bluetooth_hci_poll_in_ms instead. void mp_bluetooth_hci_poll(void); diff --git a/ports/rp2/mpbtstackport.c b/ports/rp2/mpbtstackport.c new file mode 100644 index 0000000000..4d907be76f --- /dev/null +++ b/ports/rp2/mpbtstackport.c @@ -0,0 +1,157 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#define DEBUG_printf(...) +#define BTSTACK_ENABLE_HCI_DUMP (0) + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK + +#include "lib/btstack/src/btstack.h" +#include "lib/btstack/platform/embedded/hci_dump_embedded_stdout.h" +#include "extmod/mpbthci.h" +#include "extmod/btstack/modbluetooth_btstack.h" +#include "mpbthciport.h" +#include "pico/btstack_hci_transport_cyw43.h" + +void mp_bluetooth_hci_poll_in_ms(uint32_t ms); + +static btstack_linked_list_t mp_btstack_runloop_timers; + +static void mp_btstack_runloop_init(void) { + mp_btstack_runloop_timers = NULL; +} + +static void mp_btstack_runloop_set_timer(btstack_timer_source_t *tim, uint32_t timeout_ms) { + DEBUG_printf("mp_btstack_runloop_set_timer %lu\n", timeout_ms); + tim->timeout = mp_hal_ticks_ms() + timeout_ms + 1; +} + +static void mp_btstack_runloop_add_timer(btstack_timer_source_t *tim) { + DEBUG_printf("mp_btstack_runloop_add_timer %lu\n", tim->timeout); + btstack_linked_item_t **node = &mp_btstack_runloop_timers; + for (; *node; node = &(*node)->next) { + btstack_timer_source_t *node_tim = (btstack_timer_source_t *)*node; + if (node_tim == tim) { + // Timer is already in the list, don't add it. + return; + } + int32_t delta = btstack_time_delta(tim->timeout, node_tim->timeout); + if (delta < 0) { + // Found sorted location in list. + break; + } + } + + // Insert timer into list in sorted location. + tim->item.next = *node; + *node = &tim->item; + + // Reschedule the HCI poll if this timer is at the head of the list. + if (mp_btstack_runloop_timers == &tim->item) { + int32_t delta_ms = btstack_time_delta(tim->timeout, mp_hal_ticks_ms()); + mp_bluetooth_hci_poll_in_ms(delta_ms); + } +} + +static bool mp_btstack_runloop_remove_timer(btstack_timer_source_t *tim) { + return btstack_linked_list_remove(&mp_btstack_runloop_timers, (btstack_linked_item_t *)tim); +} + +static uint32_t mp_btstack_runloop_get_time_ms(void) { + return mp_hal_ticks_ms(); +} + +static const btstack_run_loop_t mp_btstack_runloop_rp2 = { + &mp_btstack_runloop_init, + &btstack_run_loop_base_add_data_source, + &btstack_run_loop_base_remove_data_source, + &btstack_run_loop_base_enable_data_source_callbacks, + &btstack_run_loop_base_disable_data_source_callbacks, + &mp_btstack_runloop_set_timer, + &mp_btstack_runloop_add_timer, + &mp_btstack_runloop_remove_timer, + NULL, // execute + NULL, // dump_timer + &mp_btstack_runloop_get_time_ms, + &mp_bluetooth_hci_poll_now, // poll_data_sources_from_irq + NULL, // execute_on_main_thread + NULL, // trigger_exit +}; + +void mp_bluetooth_hci_poll(void) { + if (mp_bluetooth_btstack_state == MP_BLUETOOTH_BTSTACK_STATE_OFF) { + return; + } + + // Process BT data. + if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_HALTING) { + btstack_run_loop_base_poll_data_sources(); + } + + // Process any BTstack timers. + while (mp_btstack_runloop_timers != NULL) { + btstack_timer_source_t *tim = (btstack_timer_source_t *)mp_btstack_runloop_timers; + int32_t delta_ms = btstack_time_delta(tim->timeout, mp_hal_ticks_ms()); + if (delta_ms > 0) { + // Timer has not expired yet, reschedule HCI poll for this timer. + mp_bluetooth_hci_poll_in_ms(delta_ms); + break; + } + btstack_linked_list_pop(&mp_btstack_runloop_timers); + tim->process(tim); + } +} + +void mp_bluetooth_btstack_port_init(void) { + btstack_run_loop_init(&mp_btstack_runloop_rp2); + + #if BTSTACK_ENABLE_HCI_DUMP + hci_dump_init(hci_dump_embedded_stdout_get_instance()); + #endif + + const hci_transport_t *transport = hci_transport_cyw43_instance(); + hci_init(transport, NULL); +} + +void mp_bluetooth_btstack_port_deinit(void) { + hci_power_control(HCI_POWER_OFF); + // We really need hci to be closed or else we might get a BT interrupt going off and + // this will cause a crash when calling btstack_run_loop_poll_data_sources_from_irq after the btstack run loop has been deleted + if (mp_bluetooth_btstack_state != MP_BLUETOOTH_BTSTACK_STATE_OFF) { + hci_transport_cyw43_instance()->close(); + } + hci_close(); +} + +void mp_bluetooth_btstack_port_start(void) { + hci_power_control(HCI_POWER_ON); +} + +#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 28b06fa3a8..df44831662 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -282,3 +282,9 @@ typedef intptr_t mp_off_t; extern uint32_t rosc_random_u32(void); extern void lwip_lock_acquire(void); extern void lwip_lock_release(void); + +#if MICROPY_PY_BLUETOOTH_CYW43 +// Bluetooth code only runs in the scheduler, no locking/mutex required. +#define MICROPY_PY_BLUETOOTH_ENTER uint32_t atomic_state = 0; +#define MICROPY_PY_BLUETOOTH_EXIT (void)atomic_state; +#endif diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index d8e33e229d..dac04760b8 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -62,7 +62,7 @@ static void gpio_irq_handler(void) { } void cyw43_irq_init(void) { - gpio_add_raw_irq_handler_with_order_priority(IO_IRQ_BANK0, gpio_irq_handler, CYW43_SHARED_IRQ_HANDLER_PRIORITY); + gpio_add_raw_irq_handler_with_order_priority(CYW43_PIN_WL_HOST_WAKE, gpio_irq_handler, CYW43_SHARED_IRQ_HANDLER_PRIORITY); irq_set_enabled(IO_IRQ_BANK0, true); NVIC_SetPriority(PendSV_IRQn, PICO_LOWEST_IRQ_PRIORITY); }