From 50e46552c0c83b0c8570aba098a0ba4e68ae9eac Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 30 Jun 2022 16:01:02 +1000 Subject: [PATCH] rp2: Integrate CYW43xx WiFi driver. This includes: - Configuration file for the cyw43-driver. - Integration of cyw43-driver into the build, using lwIP. - Enhancements to machine.Pin to support extension IO pins provided by the CYW43xx. - More mp-hal pin helper functions. - mp_hal_get_mac_ascii MAC address helper function. - Addition of rp2.country() function to set the country code. A board can enable this driver by setting MICROPY_PY_NETWORK_CYW43 in their cmake snippet. Work done in collaboration with Graham Sanderson and Peter Harper. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 29 +++ ports/rp2/Makefile | 4 +- ports/rp2/README.md | 7 + ports/rp2/cyw43_configport.h | 98 ++++++++++ ports/rp2/machine_pin.c | 354 ++++++++++++++++++++++++++++------- ports/rp2/main.c | 29 +++ ports/rp2/modrp2.c | 26 +++ ports/rp2/mpconfigport.h | 22 +++ ports/rp2/mphalport.c | 23 +++ ports/rp2/mphalport.h | 13 ++ ports/rp2/mpnetworkport.c | 35 ++++ ports/rp2/pendsv.c | 13 ++ ports/rp2/pendsv.h | 3 + ports/rp2/rp2_pio.c | 5 + 14 files changed, 597 insertions(+), 64 deletions(-) create mode 100644 ports/rp2/cyw43_configport.h diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 50afa55a97..9185c7d649 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -16,6 +16,10 @@ endif() # Use the local tinyusb instead of the one in pico-sdk set(PICO_TINYUSB_PATH ${MICROPY_DIR}/lib/tinyusb) +# Use the local cyw43_driver instead of the one in pico-sdk +set(PICO_CYW43_DRIVER_PATH ${MICROPY_DIR}/lib/cyw43-driver) +# Use the local lwip instead of the one in pico-sdk +set(PICO_LWIP_PATH ${MICROPY_DIR}/lib/lwip) # Set the location of this port's directory. set(MICROPY_PORT_DIR ${CMAKE_SOURCE_DIR}) @@ -73,6 +77,7 @@ set(MICROPY_SOURCE_LIB ${MICROPY_DIR}/lib/mbedtls_errors/mp_mbedtls_errors.c ${MICROPY_DIR}/lib/oofatfs/ff.c ${MICROPY_DIR}/lib/oofatfs/ffunicode.c + ${MICROPY_DIR}/shared/netutils/dhcpserver.c ${MICROPY_DIR}/shared/netutils/netutils.c ${MICROPY_DIR}/shared/netutils/trace.c ${MICROPY_DIR}/shared/readline/readline.c @@ -216,6 +221,30 @@ if(MICROPY_BLUETOOTH_NIMBLE) list(APPEND MICROPY_INC_CORE ${NIMBLE_INCLUDE}) endif() +if (MICROPY_PY_NETWORK_CYW43) + string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/cyw43-driver) + + target_compile_definitions(${MICROPY_TARGET} PRIVATE + MICROPY_PY_NETWORK_CYW43=1 + MICROPY_PY_NETWORK_CYW43_USE_LIB_DRIVER=1 + MICROPY_PY_SOCKET_DEFAULT_TIMEOUT_MS=30000 # default socket timeout + ) + if (CMAKE_BUILD_TYPE MATCHES Debug) + target_compile_definitions(${MICROPY_TARGET} PRIVATE + CYW43_USE_STATS=1 + ) + endif() + + list(APPEND MICROPY_SOURCE_EXTMOD + ${MICROPY_DIR}/extmod/network_cyw43.c + ) + + target_link_libraries(${MICROPY_TARGET} + cyw43_driver_picow + cmsis_core + ) +endif() + if (MICROPY_PY_NETWORK_NINAW10) target_compile_definitions(${MICROPY_TARGET} PRIVATE MICROPY_PY_NETWORK_NINAW10=1 diff --git a/ports/rp2/Makefile b/ports/rp2/Makefile index c2138a340a..87c1d650e0 100644 --- a/ports/rp2/Makefile +++ b/ports/rp2/Makefile @@ -29,9 +29,11 @@ all: clean: $(RM) -rf $(BUILD) -GIT_SUBMODULES += lib/mbedtls lib/pico-sdk lib/tinyusb +GIT_SUBMODULES += lib/mbedtls lib/tinyusb submodules: + # lib/pico-sdk is required for the cmake build to function (as used for boards other than PICO below) + $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="lib/pico-sdk" submodules ifeq ($(BOARD),PICO) # Run the standard submodules target with minimum required submodules above $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="$(GIT_SUBMODULES)" submodules diff --git a/ports/rp2/README.md b/ports/rp2/README.md index 5836ac0cd8..078919ce81 100644 --- a/ports/rp2/README.md +++ b/ports/rp2/README.md @@ -37,6 +37,13 @@ You can also build the standard CMake way. The final firmware is found in the top-level of the CMake build directory (`build` by default) and is called `firmware.uf2`. +If you are using a different board other than a Rasoberry Pi Pico, then you should +pass the board name to the build; e.g. for Raspberry Pi Pico W: + + $ make BOARD=PICO_W submodules + $ make BOARD=PICO_W clean + $ make BOARD=PICO_W + ## Deploying firmware to the device Firmware can be deployed to the device by putting it into bootloader mode diff --git a/ports/rp2/cyw43_configport.h b/ports/rp2/cyw43_configport.h new file mode 100644 index 0000000000..6173964cb2 --- /dev/null +++ b/ports/rp2/cyw43_configport.h @@ -0,0 +1,98 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 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. + */ +#ifndef MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H +#define MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H + +// The board-level config will be included here, so it can set some CYW43 values. +#include "py/mpconfig.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "pendsv.h" + +#define CYW43_IOCTL_TIMEOUT_US (1000000) +#define CYW43_SLEEP_MAX (10) +#define CYW43_NETUTILS (1) +#define CYW43_USE_OTP_MAC (1) + +#define CYW43_EPERM MP_EPERM // Operation not permitted +#define CYW43_EIO MP_EIO // I/O error +#define CYW43_EINVAL MP_EINVAL // Invalid argument +#define CYW43_ETIMEDOUT MP_ETIMEDOUT // Connection timed out + +#define CYW43_THREAD_ENTER MICROPY_PY_LWIP_ENTER +#define CYW43_THREAD_EXIT MICROPY_PY_LWIP_EXIT +#define CYW43_THREAD_LOCK_CHECK + +#define CYW43_SDPCM_SEND_COMMON_WAIT __WFI(); +#define CYW43_DO_IOCTL_WAIT __WFI(); + +#define CYW43_ARRAY_SIZE(a) MP_ARRAY_SIZE(a) + +#define CYW43_HAL_PIN_MODE_INPUT MP_HAL_PIN_MODE_INPUT +#define CYW43_HAL_PIN_MODE_OUTPUT MP_HAL_PIN_MODE_OUTPUT +#define CYW43_HAL_PIN_PULL_NONE MP_HAL_PIN_PULL_NONE +#define CYW43_HAL_PIN_PULL_UP MP_HAL_PIN_PULL_UP +#define CYW43_HAL_PIN_PULL_DOWN MP_HAL_PIN_PULL_DOWN + +#define CYW43_HAL_MAC_WLAN0 MP_HAL_MAC_WLAN0 + +// set in SDK board header +#define CYW43_NUM_GPIOS CYW43_WL_GPIO_COUNT + +#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); + +#define cyw43_hal_ticks_us mp_hal_ticks_us +#define cyw43_hal_ticks_ms mp_hal_ticks_ms + +#define cyw43_hal_pin_obj_t mp_hal_pin_obj_t +#define cyw43_hal_pin_config mp_hal_pin_config +#define cyw43_hal_pin_read mp_hal_pin_read +#define cyw43_hal_pin_low mp_hal_pin_low +#define cyw43_hal_pin_high mp_hal_pin_high + +#define cyw43_hal_get_mac mp_hal_get_mac +#define cyw43_hal_get_mac_ascii mp_hal_get_mac_ascii +#define cyw43_hal_generate_laa_mac mp_hal_generate_laa_mac + +#define cyw43_schedule_internal_poll_dispatch(func) pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, func) + +void cyw43_post_poll_hook(void); + +static inline void cyw43_delay_us(uint32_t us) { + uint32_t start = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - start < us) { + } +} + +static inline void cyw43_delay_ms(uint32_t ms) { + uint32_t us = ms * 1000; + int32_t start = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - start < us) { + __WFI(); + } +} + +#endif // MICROPY_INCLUDED_RP2_CYW43_CONFIGPORT_H diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index ca8de6ce25..a0c5dead90 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -58,9 +58,18 @@ // Open drain behaviour is simulated. #define GPIO_IS_OPEN_DRAIN(id) (machine_pin_open_drain_mask & (1 << (id))) +#ifndef MICROPY_HW_PIN_RESERVED +#define MICROPY_HW_PIN_RESERVED(i) (0) +#endif + typedef struct _machine_pin_obj_t { mp_obj_base_t base; - uint32_t id; + uint8_t id; + #if MICROPY_HW_PIN_CYW43_COUNT + bool is_cyw43; + bool is_output; + bool last_output_value; + #endif } machine_pin_obj_t; typedef struct _machine_pin_irq_obj_t { @@ -104,9 +113,60 @@ STATIC const machine_pin_obj_t machine_pin_obj[NUM_BANK0_GPIOS] = { {{&machine_pin_type}, 29}, }; +#if MICROPY_HW_PIN_CYW43_COUNT +#include "lib/cyw43-driver/src/cyw43.h" +#define CYW43_PIN_NAME_PREFIX "WL_GPIO" +STATIC machine_pin_obj_t cyw43_pin_obj[MICROPY_HW_PIN_CYW43_COUNT]; +#endif + +#define LED_PIN_NAME "LED" + +#ifndef MICROPY_HW_PIN_ENABLE_LED_PIN +#if defined(MICROPY_HW_PIN_CYW43_LED_PIN_NUM) || defined(PICO_DEFAULT_LED_PIN) +#define MICROPY_HW_PIN_ENABLE_LED_PIN 1 +#endif +#endif + +#ifdef MICROPY_HW_PIN_ENABLE_LED_PIN +#ifdef MICROPY_HW_PIN_CYW43_LED_PIN_NUM +STATIC machine_pin_obj_t *led_pin_obj = &cyw43_pin_obj[MICROPY_HW_PIN_CYW43_LED_PIN_NUM]; +#elif defined(MICROPY_HW_PIN_LED_PIN_NUM) +STATIC machine_pin_obj_t *led_pin_obj = &machine_pin_obj[MICROPY_HW_PIN_LED_PIN_NUM]; +#elif defined(PICO_DEFAULT_LED_PIN) +STATIC const machine_pin_obj_t *led_pin_obj = &machine_pin_obj[PICO_DEFAULT_LED_PIN]; +#else +#error MICROPY_HW_PIN_ENABLE_LED_PIN defined but there is no LED pin +#endif +#endif + // Mask with "1" indicating that the corresponding pin is in simulated open-drain mode. uint32_t machine_pin_open_drain_mask; +#if MICROPY_HW_PIN_CYW43_COUNT +STATIC inline bool is_cyw43_pin(__unused const machine_pin_obj_t *self) { + return self->is_cyw43; +} +#else +#define is_cyw43_pin(x) false +#endif + +#if MICROPY_HW_PIN_CYW43_COUNT +STATIC inline void update_cyw43_value(__unused machine_pin_obj_t *self, bool value) { + if (value != self->last_output_value || !self->is_output) { + cyw43_gpio_set(&cyw43_state, self->id, value); + } + self->last_output_value = value; +} +#endif + +#if MICROPY_HW_PIN_CYW43_COUNT +STATIC inline bool get_cyw43_value(__unused machine_pin_obj_t *self) { + bool value = false; + cyw43_gpio_get(&cyw43_state, self->id, &value); + return value; +} +#endif + STATIC void gpio_irq(void) { for (int i = 0; i < 4; ++i) { uint32_t intr = iobank0_hw->intr[i]; @@ -129,15 +189,24 @@ STATIC void gpio_irq(void) { void machine_pin_init(void) { memset(MP_STATE_PORT(machine_pin_irq_obj), 0, sizeof(MP_STATE_PORT(machine_pin_irq_obj))); - irq_set_exclusive_handler(IO_IRQ_BANK0, gpio_irq); + irq_add_shared_handler(IO_IRQ_BANK0, gpio_irq, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); irq_set_enabled(IO_IRQ_BANK0, true); + #if MICROPY_HW_PIN_CYW43_COUNT + for (uint i = 0; i < count_of(cyw43_pin_obj); i++) { + cyw43_pin_obj[i].id = i; + cyw43_pin_obj[i].base.type = &machine_pin_type; + cyw43_pin_obj[i].is_cyw43 = true; + } + #endif } void machine_pin_deinit(void) { for (int i = 0; i < NUM_BANK0_GPIOS; ++i) { + if (MICROPY_HW_PIN_RESERVED(i)) { + continue; + } gpio_set_irq_enabled(i, GPIO_IRQ_ALL, false); } - irq_set_enabled(IO_IRQ_BANK0, false); irq_remove_handler(IO_IRQ_BANK0, gpio_irq); } @@ -145,45 +214,112 @@ STATIC void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_prin machine_pin_obj_t *self = self_in; uint funcsel = GPIO_GET_FUNCSEL(self->id); qstr mode_qst; - if (funcsel == GPIO_FUNC_SIO) { - if (GPIO_IS_OPEN_DRAIN(self->id)) { - mode_qst = MP_QSTR_OPEN_DRAIN; - } else if (GPIO_IS_OUT(self->id)) { - mode_qst = MP_QSTR_OUT; + if (!is_cyw43_pin(self)) { + if (funcsel == GPIO_FUNC_SIO) { + if (GPIO_IS_OPEN_DRAIN(self->id)) { + mode_qst = MP_QSTR_OPEN_DRAIN; + } else if (GPIO_IS_OUT(self->id)) { + mode_qst = MP_QSTR_OUT; + } else { + mode_qst = MP_QSTR_IN; + } } else { - mode_qst = MP_QSTR_IN; + mode_qst = MP_QSTR_ALT; + } + mp_printf(print, "Pin(%u, mode=%q", self->id, mode_qst); + bool pull_up = false; + if (GPIO_IS_PULL_UP(self->id)) { + mp_printf(print, ", pull=%q", MP_QSTR_PULL_UP); + pull_up = true; + } + if (GPIO_IS_PULL_DOWN(self->id)) { + if (pull_up) { + mp_printf(print, "|%q", MP_QSTR_PULL_DOWN); + } else { + mp_printf(print, ", pull=%q", MP_QSTR_PULL_DOWN); + } + } + if (funcsel != GPIO_FUNC_SIO) { + mp_printf(print, ", alt=%u", funcsel); } } else { - mode_qst = MP_QSTR_ALT; + #if MICROPY_HW_PIN_CYW43_COUNT + mode_qst = self->is_output ? MP_QSTR_OUT : MP_QSTR_IN; + mp_printf(print, "Pin(%s%u, mode=%q", CYW43_PIN_NAME_PREFIX, self->id, mode_qst); + #endif + } + mp_printf(print, ")"); +} + +enum { + ARG_mode, ARG_pull, ARG_value, ARG_alt +}; +static const mp_arg_t allowed_args[] = { + {MP_QSTR_mode, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + {MP_QSTR_pull, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + {MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, + {MP_QSTR_alt, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = GPIO_FUNC_SIO}}, +}; + +#if MICROPY_HW_PIN_CYW43_COUNT +// pin.init(mode, pull=None, *, value=None, alt=FUNC_SIO) +STATIC mp_obj_t machine_pin_cyw43_obj_init_helper(machine_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (args[ARG_pull].u_obj != mp_const_none) { + int pull = mp_obj_get_int(args[ARG_pull].u_obj); + if (pull) { + mp_raise_ValueError("Pulls are not supported for this pin"); + } + } + + if (args[ARG_alt].u_int != GPIO_FUNC_SIO) { + mp_raise_ValueError("Alternate functions are not supported for this pin"); } - mp_printf(print, "Pin(%u, mode=%q", self->id, mode_qst); - bool pull_up = false; - if (GPIO_IS_PULL_UP(self->id)) { - mp_printf(print, ", pull=%q", MP_QSTR_PULL_UP); - pull_up = true; + + int value = -1; + if (args[ARG_value].u_obj != mp_const_none) { + value = mp_obj_is_true(args[ARG_value].u_obj); } - if (GPIO_IS_PULL_DOWN(self->id)) { - if (pull_up) { - mp_printf(print, "|%q", MP_QSTR_PULL_DOWN); + + if (args[ARG_mode].u_obj != mp_const_none) { + mp_int_t mode = mp_obj_get_int(args[ARG_mode].u_obj); + if (mode == GPIO_MODE_IN) { + if (self->is_output) { + // todo need to disable output + } + self->is_output = false; + } else if (mode == GPIO_MODE_OUT) { + if (!self->is_output) { + // todo need to enable output + // for now we just set the value + if (value == -1) { + value = self->last_output_value; + } + self->last_output_value = !self->last_output_value; // defeat shortcircuit + update_cyw43_value(self, value); + self->is_output = true; + } } else { - mp_printf(print, ", pull=%q", MP_QSTR_PULL_DOWN); + mp_raise_ValueError("only Pin.OUT and Pin.IN are supported for this pin"); } } - if (funcsel != GPIO_FUNC_SIO) { - mp_printf(print, ", alt=%u", funcsel); + + if (value != -1) { + if (self->is_output) { + update_cyw43_value(self, value); + } else { + // figure if you pass a value to IN it should still remember it (this is what regular GPIO does) + self->last_output_value = value; + } } - mp_printf(print, ")"); + + return mp_const_none; } +#endif -// pin.init(mode, pull=None, *, value=None, alt=FUNC_SIO) STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode, ARG_pull, ARG_value, ARG_alt }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_mode, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, - { MP_QSTR_pull, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, - { MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}}, - { MP_QSTR_alt, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = GPIO_FUNC_SIO}}, - }; // parse args mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -216,7 +352,6 @@ STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ pull = mp_obj_get_int(args[ARG_pull].u_obj); } gpio_set_pulls(self->id, pull & GPIO_PULL_UP, pull & GPIO_PULL_DOWN); - return mp_const_none; } @@ -224,21 +359,57 @@ STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); - // get the wanted pin object - int wanted_pin = mp_obj_get_int(args[0]); - if (!(0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(machine_pin_obj))) { - mp_raise_ValueError("invalid pin"); - } - const machine_pin_obj_t *self = &machine_pin_obj[wanted_pin]; + const machine_pin_obj_t *self = NULL; + if (mp_obj_is_str(args[0])) { + const char *name = mp_obj_str_get_str(args[0]); + #if MICROPY_HW_PIN_ENABLE_LED_PIN + if (!strcmp(name, LED_PIN_NAME)) { + self = led_pin_obj; + } + #endif + #if MICROPY_HW_PIN_CYW43_COUNT + static_assert(MICROPY_HW_PIN_CYW43_COUNT < 10, ""); // makes parsing name easy! + if (!self && !strncmp(name, CYW43_PIN_NAME_PREFIX, strlen(CYW43_PIN_NAME_PREFIX)) && strlen(name) == strlen(CYW43_PIN_NAME_PREFIX) + 1) { + int num = name[strlen(CYW43_PIN_NAME_PREFIX)] - '0'; + if (num < MICROPY_HW_PIN_CYW43_COUNT) { + self = &cyw43_pin_obj[num]; + } + } + #endif + if (!self) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("Unknown named pin \"%s\""), name); + } + } + if (!self) { + // get the wanted pin object + int wanted_pin = mp_obj_get_int(args[0]); + if (!(0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(machine_pin_obj))) { + mp_raise_ValueError("invalid pin"); + } + self = &machine_pin_obj[wanted_pin]; + } + // note we have different init args based on the type of pin. so Pin("LED", Pin.OUT) may not always make sense + if (!is_cyw43_pin(self)) { + if (n_args > 1 || n_kw > 0) { + // pin mode given, so configure this GPIO + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_pin_obj_init_helper(self, n_args - 1, args + 1, &kw_args); + } + return MP_OBJ_FROM_PTR(self); + } + #if MICROPY_HW_PIN_CYW43_COUNT if (n_args > 1 || n_kw > 0) { // pin mode given, so configure this GPIO mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - machine_pin_obj_init_helper(self, n_args - 1, args + 1, &kw_args); + // The regular Pins are const, but the CYW43 pins are mutable. + machine_pin_obj_t *mutable_self = (machine_pin_obj_t *)self; + machine_pin_cyw43_obj_init_helper(mutable_self, n_args - 1, args + 1, &kw_args); } - return MP_OBJ_FROM_PTR(self); + #endif } // fast method for getting/setting pin value @@ -247,23 +418,39 @@ STATIC mp_obj_t machine_pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, c machine_pin_obj_t *self = self_in; if (n_args == 0) { // get pin - return MP_OBJ_NEW_SMALL_INT(gpio_get(self->id)); + if (!is_cyw43_pin(self)) { + return MP_OBJ_NEW_SMALL_INT(gpio_get(self->id)); + } + #if MICROPY_HW_PIN_CYW43_COUNT + return MP_OBJ_NEW_SMALL_INT(get_cyw43_value(self)); + #endif } else { // set pin bool value = mp_obj_is_true(args[0]); - if (GPIO_IS_OPEN_DRAIN(self->id)) { - MP_STATIC_ASSERT(GPIO_IN == 0 && GPIO_OUT == 1); - gpio_set_dir(self->id, 1 - value); - } else { - gpio_put(self->id, value); + if (!is_cyw43_pin(self)) { + if (GPIO_IS_OPEN_DRAIN(self->id)) { + MP_STATIC_ASSERT(GPIO_IN == 0 && GPIO_OUT == 1); + gpio_set_dir(self->id, 1 - value); + } else { + gpio_put(self->id, value); + } + return mp_const_none; } - return mp_const_none; + #if MICROPY_HW_PIN_CYW43_COUNT + update_cyw43_value(self, value); + #endif } + return mp_const_none; } // pin.init(mode, pull) STATIC mp_obj_t machine_pin_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - return machine_pin_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); + if (!is_cyw43_pin(args[0])) { + return machine_pin_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); + } + #if MICROPY_HW_PIN_CYW43_COUNT + return machine_pin_cyw43_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); + #endif } MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_init_obj, 1, machine_pin_obj_init); @@ -276,40 +463,59 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pin_value_obj, 1, 2, machine_ // pin.low() STATIC mp_obj_t machine_pin_low(mp_obj_t self_in) { machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (GPIO_IS_OPEN_DRAIN(self->id)) { - gpio_set_dir(self->id, GPIO_OUT); - } else { - gpio_clr_mask(1u << self->id); + if (!is_cyw43_pin(self)) { + if (GPIO_IS_OPEN_DRAIN(self->id)) { + gpio_set_dir(self->id, GPIO_OUT); + } else { + gpio_clr_mask(1u << self->id); + } + return mp_const_none; } + #if MICROPY_HW_PIN_CYW43_COUNT + update_cyw43_value(self, 0); return mp_const_none; + #endif } STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_low_obj, machine_pin_low); // pin.high() STATIC mp_obj_t machine_pin_high(mp_obj_t self_in) { machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (GPIO_IS_OPEN_DRAIN(self->id)) { - gpio_set_dir(self->id, GPIO_IN); - } else { - gpio_set_mask(1u << self->id); + if (!is_cyw43_pin(self)) { + if (GPIO_IS_OPEN_DRAIN(self->id)) { + gpio_set_dir(self->id, GPIO_IN); + } else { + gpio_set_mask(1u << self->id); + } + return mp_const_none; } + #if MICROPY_HW_PIN_CYW43_COUNT + update_cyw43_value(self, 1); return mp_const_none; + #endif } + STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_high_obj, machine_pin_high); // pin.toggle() STATIC mp_obj_t machine_pin_toggle(mp_obj_t self_in) { machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (GPIO_IS_OPEN_DRAIN(self->id)) { - if (GPIO_IS_OUT(self->id)) { - gpio_set_dir(self->id, GPIO_IN); + if (!is_cyw43_pin(self)) { + if (GPIO_IS_OPEN_DRAIN(self->id)) { + if (GPIO_IS_OUT(self->id)) { + gpio_set_dir(self->id, GPIO_IN); + } else { + gpio_set_dir(self->id, GPIO_OUT); + } } else { - gpio_set_dir(self->id, GPIO_OUT); + gpio_xor_mask(1u << self->id); } - } else { - gpio_xor_mask(1u << self->id); + return mp_const_none; } + #if MICROPY_HW_PIN_CYW43_COUNT + update_cyw43_value(self, self->last_output_value ^ 1); return mp_const_none; + #endif } STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_toggle_obj, machine_pin_toggle); @@ -357,6 +563,10 @@ STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_ { MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} }, }; machine_pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + if (is_cyw43_pin(self)) { + mp_raise_ValueError(MP_ERROR_TEXT("expecting a regular GPIO Pin")); + } + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -400,16 +610,31 @@ STATIC mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, i (void)errcode; machine_pin_obj_t *self = self_in; + if (!is_cyw43_pin(self)) { + switch (request) { + case MP_PIN_READ: { + return gpio_get(self->id); + } + case MP_PIN_WRITE: { + gpio_put(self->id, arg); + return 0; + } + } + return -1; + } + + #if MICROPY_HW_PIN_CYW43_COUNT switch (request) { case MP_PIN_READ: { - return gpio_get(self->id); + return get_cyw43_value(self); } case MP_PIN_WRITE: { - gpio_put(self->id, arg); + update_cyw43_value(self, arg); return 0; } } return -1; + #endif } STATIC const mp_pin_p_t pin_pin_p = { @@ -457,5 +682,8 @@ mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t obj) { mp_raise_ValueError(MP_ERROR_TEXT("expecting a Pin")); } machine_pin_obj_t *pin = MP_OBJ_TO_PTR(obj); + if (is_cyw43_pin(pin)) { + mp_raise_ValueError(MP_ERROR_TEXT("expecting a regular GPIO Pin")); + } return pin->id; } diff --git a/ports/rp2/main.c b/ports/rp2/main.c index da21e0b394..664bb18109 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -46,12 +46,16 @@ #include "pico/stdlib.h" #include "pico/binary_info.h" +#include "pico/unique_id.h" #include "hardware/rtc.h" #include "hardware/structs/rosc.h" #if MICROPY_PY_LWIP #include "lwip/init.h" #include "lwip/apps/mdns.h" #endif +#if MICROPY_PY_NETWORK_CYW43 +#include "lib/cyw43-driver/src/cyw43.h" +#endif #ifndef MICROPY_GC_HEAP_SIZE #if MICROPY_PY_LWIP @@ -90,6 +94,10 @@ int main(int argc, char **argv) { mp_thread_init(); #endif + #ifndef NDEBUG + stdio_init_all(); + #endif + // Start and initialise the RTC datetime_t t = { .year = 2021, @@ -118,6 +126,27 @@ int main(int argc, char **argv) { #endif #endif + #if MICROPY_PY_NETWORK_CYW43 + { + cyw43_init(&cyw43_state); + cyw43_irq_init(); + cyw43_post_poll_hook(); // enable the irq + uint8_t buf[8]; + memcpy(&buf[0], "PICO", 4); + + // MAC isn't loaded from OTP yet, so use unique id to generate the default AP ssid. + const char hexchr[16] = "0123456789ABCDEF"; + pico_unique_board_id_t pid; + pico_get_unique_board_id(&pid); + buf[4] = hexchr[pid.id[7] >> 4]; + buf[5] = hexchr[pid.id[6] & 0xf]; + buf[6] = hexchr[pid.id[5] >> 4]; + buf[7] = hexchr[pid.id[4] & 0xf]; + cyw43_wifi_ap_set_ssid(&cyw43_state, 8, buf); + cyw43_wifi_ap_set_password(&cyw43_state, 8, (const uint8_t *)"picoW123"); + } + #endif + for (;;) { // Initialise MicroPython runtime. diff --git a/ports/rp2/modrp2.c b/ports/rp2/modrp2.c index 5dc066d651..19c7ad6083 100644 --- a/ports/rp2/modrp2.c +++ b/ports/rp2/modrp2.c @@ -28,6 +28,28 @@ #include "drivers/dht/dht.h" #include "modrp2.h" +#if MICROPY_PY_NETWORK_CYW43 +#include "lib/cyw43-driver/src/cyw43_country.h" + +extern uint32_t cyw43_country_code; + +STATIC mp_obj_t rp2_country(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + char code[2] = {cyw43_country_code, cyw43_country_code >> 8}; + return mp_obj_new_str(code, 2); + } else { + size_t len; + const char *str = mp_obj_str_get_data(args[0], &len); + if (len != 2) { + mp_raise_ValueError(NULL); + } + cyw43_country_code = CYW43_COUNTRY(str[0], str[1], 0); + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(rp2_country_obj, 0, 1, rp2_country); +#endif + STATIC const mp_rom_map_elem_t rp2_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_rp2) }, { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&rp2_flash_type) }, @@ -35,6 +57,10 @@ STATIC const mp_rom_map_elem_t rp2_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_StateMachine), MP_ROM_PTR(&rp2_state_machine_type) }, { MP_ROM_QSTR(MP_QSTR_dht_readinto), MP_ROM_PTR(&dht_readinto_obj) }, + + #if MICROPY_PY_NETWORK_CYW43 + { MP_ROM_QSTR(MP_QSTR_country), MP_ROM_PTR(&rp2_country_obj) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(rp2_module_globals, rp2_module_globals_table); diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 62388c6bce..9143b5f6c5 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -154,6 +154,20 @@ struct _mp_bluetooth_nimble_malloc_t; #define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_NIMBLE #endif +#if MICROPY_PY_NETWORK_CYW43 +extern const struct _mp_obj_type_t mp_network_cyw43_type; +#define MICROPY_HW_NIC_CYW43 \ + { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&mp_network_cyw43_type) }, \ + { MP_ROM_QSTR(MP_QSTR_STAT_IDLE), MP_ROM_INT(CYW43_LINK_DOWN) }, \ + { MP_ROM_QSTR(MP_QSTR_STAT_CONNECTING), MP_ROM_INT(CYW43_LINK_JOIN) }, \ + { MP_ROM_QSTR(MP_QSTR_STAT_WRONG_PASSWORD), MP_ROM_INT(CYW43_LINK_BADAUTH) }, \ + { MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND), MP_ROM_INT(CYW43_LINK_NONET) }, \ + { MP_ROM_QSTR(MP_QSTR_STAT_CONNECT_FAIL), MP_ROM_INT(CYW43_LINK_FAIL) }, \ + { MP_ROM_QSTR(MP_QSTR_STAT_GOT_IP), MP_ROM_INT(CYW43_LINK_UP) }, +#else +#define MICROPY_HW_NIC_CYW43 +#endif + #if MICROPY_PY_NETWORK_NINAW10 // This Network interface requires the extended socket state. #ifndef MICROPY_PY_USOCKET_EXTENDED_STATE @@ -184,6 +198,7 @@ extern const struct _mod_network_nic_type_t mod_network_nic_type_wiznet5k; #endif #define MICROPY_PORT_NETWORK_INTERFACES \ + MICROPY_HW_NIC_CYW43 \ MICROPY_HW_NIC_NINAW10 \ MICROPY_HW_NIC_WIZNET5K \ MICROPY_BOARD_NETWORK_INTERFACES \ @@ -258,3 +273,10 @@ 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); + +extern uint32_t cyw43_country_code; +extern void cyw43_irq_init(void); +extern void cyw43_post_poll_hook(void); + +#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); +#define MICROPY_CYW43_COUNTRY cyw43_country_code diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index 4ed648de07..aec4a96724 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -35,6 +35,10 @@ #include "hardware/rtc.h" #include "pico/unique_id.h" +#if MICROPY_PY_NETWORK_CYW43 +#include "lib/cyw43-driver/src/cyw43.h" +#endif + #if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_ENABLE_USBDEV #ifndef MICROPY_HW_STDIN_BUFFER_LEN @@ -168,6 +172,9 @@ uint64_t mp_hal_time_ns(void) { // Generate a random locally administered MAC address (LAA) void mp_hal_generate_laa_mac(int idx, uint8_t buf[6]) { + #ifndef NDEBUG + printf("Warning: No MAC in OTP, generating MAC from board id\n"); + #endif pico_unique_board_id_t pid; pico_get_unique_board_id(&pid); buf[0] = 0x02; // LAA range @@ -180,5 +187,21 @@ void mp_hal_generate_laa_mac(int idx, uint8_t buf[6]) { // A board can override this if needed MP_WEAK void mp_hal_get_mac(int idx, uint8_t buf[6]) { + #if MICROPY_PY_NETWORK_CYW43 + // The mac should come from cyw43 otp when CYW43_USE_OTP_MAC is defined + // This is loaded into the state after the driver is initialised + // cyw43_hal_generate_laa_mac is only called by the driver to generate a mac if otp is not set + memcpy(buf, cyw43_state.mac, 6); + #else mp_hal_generate_laa_mac(idx, buf); + #endif +} + +void mp_hal_get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest) { + static const char hexchr[16] = "0123456789ABCDEF"; + uint8_t mac[6]; + mp_hal_get_mac(idx, mac); + for (; chr_len; ++chr_off, --chr_len) { + *dest++ = hexchr[mac[chr_off >> 1] >> (4 * (1 - (chr_off & 1))) & 0xf]; + } } diff --git a/ports/rp2/mphalport.h b/ports/rp2/mphalport.h index 88eef1308d..88683d9360 100644 --- a/ports/rp2/mphalport.h +++ b/ports/rp2/mphalport.h @@ -30,6 +30,7 @@ #include "pico/time.h" #include "hardware/clocks.h" #include "hardware/structs/systick.h" +#include "RP2040.h" // cmsis, for __WFI #define SYSTICK_MAX (0xffffff) @@ -74,6 +75,11 @@ static inline mp_uint_t mp_hal_get_cpu_freq(void) { #define MP_HAL_PIN_FMT "%u" #define mp_hal_pin_obj_t uint +#define MP_HAL_PIN_MODE_INPUT (GPIO_IN) +#define MP_HAL_PIN_MODE_OUTPUT (GPIO_OUT) +#define MP_HAL_PIN_PULL_NONE (0) +#define MP_HAL_PIN_PULL_UP (1) +#define MP_HAL_PIN_PULL_DOWN (2) extern uint32_t machine_pin_open_drain_mask; @@ -102,6 +108,12 @@ static inline void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin) { machine_pin_open_drain_mask |= 1 << pin; } +static inline void mp_hal_pin_config(mp_hal_pin_obj_t pin, uint32_t mode, uint32_t pull, uint32_t alt) { + assert((mode == MP_HAL_PIN_MODE_INPUT || mode == MP_HAL_PIN_MODE_OUTPUT) && alt == 0); + gpio_set_dir(pin, mode); + gpio_set_pulls(pin, pull == MP_HAL_PIN_PULL_UP, pull == MP_HAL_PIN_PULL_DOWN); +} + static inline int mp_hal_pin_read(mp_hal_pin_obj_t pin) { return gpio_get(pin); } @@ -143,6 +155,7 @@ enum { }; void mp_hal_get_mac(int idx, uint8_t buf[6]); +void mp_hal_get_mac_ascii(int idx, size_t chr_off, size_t chr_len, char *dest); void mp_hal_generate_laa_mac(int idx, uint8_t buf[6]); #endif // MICROPY_INCLUDED_RP2_MPHALPORT_H diff --git a/ports/rp2/mpnetworkport.c b/ports/rp2/mpnetworkport.c index 2b01b62192..0cd6eb14ce 100644 --- a/ports/rp2/mpnetworkport.c +++ b/ports/rp2/mpnetworkport.c @@ -38,6 +38,41 @@ static alarm_id_t lwip_alarm_id = -1; +#if MICROPY_PY_NETWORK_CYW43 +#include "lib/cyw43-driver/src/cyw43.h" +#include "lib/cyw43-driver/src/cyw43_country.h" +#include "lib/cyw43-driver/src/cyw43_stats.h" +#include "hardware/irq.h" + +#define CYW43_IRQ_LEVEL GPIO_IRQ_LEVEL_HIGH +#define CYW43_SHARED_IRQ_HANDLER_PRIORITY PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY + +uint32_t cyw43_country_code = CYW43_COUNTRY_WORLDWIDE; + +static void gpio_irq_handler(void) { + uint32_t events = gpio_get_irq_event_mask(CYW43_PIN_WL_HOST_WAKE); + if (events & CYW43_IRQ_LEVEL) { + // As we use a high level interrupt, it will go off forever until it's serviced. + // So disable the interrupt until this is done. It's re-enabled again by + // CYW43_POST_POLL_HOOK which is called at the end of cyw43_poll_func. + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, CYW43_IRQ_LEVEL, false); + pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll); + CYW43_STAT_INC(IRQ_COUNT); + } +} + +void cyw43_irq_init(void) { + gpio_add_raw_irq_handler_with_order_priority(IO_IRQ_BANK0, gpio_irq_handler, CYW43_SHARED_IRQ_HANDLER_PRIORITY); + irq_set_enabled(IO_IRQ_BANK0, true); + NVIC_SetPriority(PendSV_IRQn, PICO_LOWEST_IRQ_PRIORITY); +} + +void cyw43_post_poll_hook(void) { + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, CYW43_IRQ_LEVEL, true); +} + +#endif + #if MICROPY_PY_NETWORK_WIZNET5K void wiznet5k_poll(void); void wiznet5k_deinit(void); diff --git a/ports/rp2/pendsv.c b/ports/rp2/pendsv.c index 006303d46f..91b133c069 100644 --- a/ports/rp2/pendsv.c +++ b/ports/rp2/pendsv.c @@ -28,6 +28,10 @@ #include "pendsv.h" #include "RP2040.h" +#if MICROPY_PY_NETWORK_CYW43 +#include "lib/cyw43-driver/src/cyw43_stats.h" +#endif + static pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS]; static int pendsv_lock; @@ -54,12 +58,21 @@ void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) { pendsv_dispatch_table[slot] = f; if (pendsv_lock == 0) { SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; + } else { + #if MICROPY_PY_NETWORK_CYW43 + CYW43_STAT_INC(PENDSV_DISABLED_COUNT); + #endif } } // PendSV interrupt handler to perform background processing. void PendSV_Handler(void) { assert(pendsv_lock == 0); + + #if MICROPY_PY_NETWORK_CYW43 + CYW43_STAT_INC(PENDSV_RUN_COUNT); + #endif + for (size_t i = 0; i < PENDSV_DISPATCH_NUM_SLOTS; ++i) { if (pendsv_dispatch_table[i] != NULL) { pendsv_dispatch_t f = pendsv_dispatch_table[i]; diff --git a/ports/rp2/pendsv.h b/ports/rp2/pendsv.h index 7bb43208ab..1da3d22e53 100644 --- a/ports/rp2/pendsv.h +++ b/ports/rp2/pendsv.h @@ -32,6 +32,9 @@ enum { #if MICROPY_PY_LWIP PENDSV_DISPATCH_LWIP, #endif + #if MICROPY_PY_NETWORK_CYW43 + PENDSV_DISPATCH_CYW43, + #endif #if MICROPY_PY_NETWORK_WIZNET5K PENDSV_DISPATCH_WIZNET, #endif diff --git a/ports/rp2/rp2_pio.c b/ports/rp2/rp2_pio.c index fe09ebe249..9955cc5440 100644 --- a/ports/rp2/rp2_pio.c +++ b/ports/rp2/rp2_pio.c @@ -103,8 +103,13 @@ STATIC void pio1_irq0(void) { void rp2_pio_init(void) { // Reset all PIO instruction memory. + #if MICROPY_PY_NETWORK_CYW43 + // TODO: cannot reset PIO memory when CYW43 driver is enabled and active + // because it uses a PIO for the bus interface. + #else pio_clear_instruction_memory(pio0); pio_clear_instruction_memory(pio1); + #endif // Set up interrupts. memset(MP_STATE_PORT(rp2_pio_irq_obj), 0, sizeof(MP_STATE_PORT(rp2_pio_irq_obj)));