瀏覽代碼

first version

褚仕成 2 年之前
當前提交
adc6928baa

+ 31 - 0
README.md

@@ -0,0 +1,31 @@
+# cyw43439 for RT-Thread
+
+## 1. 简介
+
+**cyw43439 for RT-Thread**是由英飞凌基于 WIFI SOC的高速wifi模块。该仓库为**cyw43439**在 RT-Thread 上的移植。目前主要适用于 pico-w。
+
+### 1.1. 文件结构
+
+| 文件夹 | 说明 |
+| ---- | ---- |
+| source | 相关的驱动库文件      |
+| drv_wifi_cyw43439.c | 对接 rtt 设备驱动框架 |
+
+### 1.2 许可证
+
+cyw43012 for RT-Thread 遵循 Apache 2.0 许可,详见 `LICENSE` 文件。
+
+### 1.3 依赖
+
+- RT-Thread 4.0+
+- RT-Thread LWIP 组件
+- RT-Thread WIFI 组件
+
+## 2. 注意事项
+
+无
+
+## 3. 联系方式
+
+- 维护:[Z8MAN8](https://github.com/Z8MAN8)    1468559561@qq.com
+

+ 30 - 0
SConscript

@@ -0,0 +1,30 @@
+from building import *
+
+cwd = GetCurrentDir()
+src = []
+path = [cwd]
+CPPDEFINES = []
+
+if GetDepend('PKG_USING_WLAN_CYW43439'):
+    src += [
+        cwd + '/drv_wifi_cyw43439.c',
+        cwd + '/pico-source/src/async_context_rtthread.c',
+        cwd + '/pico-source/src/cyw43_arch.c',
+        cwd + '/pico-source/src/cyw43_arch_rtthread.c',
+        cwd + '/pico-source/src/lwip_rtthread.c',
+    ]
+    path += [
+        cwd + '/pico-source/inc',
+    ]
+
+    CPPDEFINES = [
+        'PICO_CYW43_ARCH_RTTHREAD',
+        'PICO_CYW43_SUPPORTED',
+        'PICO_BOARD=pico_w',
+        'CYW43_LWIP=1',
+        'PICO_CONFIG_HEADER=boards/pico_w.h',
+    ]
+
+group = DefineGroup('cyw43439', src, depend = [''], CPPPATH = path,  CPPDEFINES = CPPDEFINES)
+
+Return('group')

+ 332 - 0
drv_wifi_cyw43439.c

@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date             Author           Notes
+ * 2023-11-14       ChuShicheng      first version
+ */
+#include <rtdevice.h>
+#include <rtthread.h>
+#include "board.h"
+#include "cyw43_arch.h"
+
+#ifdef PKG_USING_WLAN_CYW43439
+
+#define DBG_LEVEL   DBG_LOG
+#include <rtdbg.h>
+#define LOG_TAG "DRV.CYW43439"
+
+struct ifx_wifi
+{
+    /* inherit from ethernet device */
+    struct rt_wlan_device *wlan;
+};
+
+static struct ifx_wifi wifi_sta, wifi_ap;
+
+rt_inline struct ifx_wifi *_GET_DEV(struct rt_wlan_device *wlan)
+{
+    if (wlan == wifi_sta.wlan)
+    {
+        return &wifi_sta;
+    }
+    if (wlan == wifi_ap.wlan)
+    {
+        return &wifi_ap;
+    }
+    return RT_NULL;
+}
+
+static uint32_t get_security(rt_wlan_security_t security)
+{
+    /* security type */
+    switch (security)
+    {
+    case SECURITY_OPEN:
+        return CYW43_AUTH_OPEN;
+    case SECURITY_WPA_TKIP_PSK:
+        return CYW43_AUTH_WPA_TKIP_PSK;
+    case SECURITY_WPA2_AES_PSK:
+        return CYW43_AUTH_WPA2_AES_PSK;
+    case SECURITY_WPA2_MIXED_PSK:
+        return CYW43_AUTH_WPA2_MIXED_PSK;
+    default:
+        return CYW43_AUTH_OPEN;
+    }
+}
+
+static int _ifx_scan_info2rtt(const cyw43_ev_scan_result_t *result_ptr, struct rt_wlan_info *wlan_info)
+{
+    /* security type */
+    switch (result_ptr->auth_mode)
+    {
+    case CYW43_AUTH_OPEN:
+        wlan_info->security = SECURITY_OPEN;
+        break;
+    case CYW43_AUTH_WPA_TKIP_PSK:
+        wlan_info->security = SECURITY_WPA_TKIP_PSK;
+        break;
+    case CYW43_AUTH_WPA2_AES_PSK:
+        wlan_info->security = SECURITY_WPA2_AES_PSK;
+        break;
+    case CYW43_AUTH_WPA2_MIXED_PSK:
+        wlan_info->security = SECURITY_WPA2_MIXED_PSK;
+        break;
+    default:
+        wlan_info->security = SECURITY_UNKNOWN;
+        break;
+    }
+    /* radio channel */
+    wlan_info->channel = result_ptr->channel;
+    /* signal strength */
+    wlan_info->rssi = -result_ptr->rssi;
+    /* ssid */
+    rt_strncpy(wlan_info->ssid.val, result_ptr->ssid, RT_WLAN_SSID_MAX_LENGTH);
+    wlan_info->ssid.len = result_ptr->ssid_len > RT_WLAN_SSID_MAX_LENGTH ? RT_WLAN_SSID_MAX_LENGTH : result_ptr->ssid_len;
+    /* hwaddr */
+    rt_strncpy(wlan_info->bssid, result_ptr->bssid, RT_WLAN_BSSID_MAX_LENGTH);
+    wlan_info->hidden = RT_TRUE;
+    return 0;
+}
+
+#define SCAN_BSSI_ARR_MAX 30
+
+#define CMP_MAC( a, b )  (((((unsigned char*)a)[0])==(((unsigned char*)b)[0]))&& \
+                          ((((unsigned char*)a)[1])==(((unsigned char*)b)[1]))&& \
+                          ((((unsigned char*)a)[2])==(((unsigned char*)b)[2]))&& \
+                          ((((unsigned char*)a)[3])==(((unsigned char*)b)[3]))&& \
+                          ((((unsigned char*)a)[4])==(((unsigned char*)b)[4]))&& \
+                          ((((unsigned char*)a)[5])==(((unsigned char*)b)[5])))
+
+typedef struct
+{
+    uint8_t octet[6]; /**< Unique 6-byte MAC address */
+}_mac_t;
+static _mac_t mac_addr_arr[SCAN_BSSI_ARR_MAX];
+rt_uint8_t current_bssid_arr_length = 0;
+
+bool scan_bssi_has(const uint8_t *BSSID)
+{
+    _mac_t *mac_iter = NULL;
+    /* Check for duplicate SSID */
+    for (mac_iter = mac_addr_arr; (mac_iter < mac_addr_arr + current_bssid_arr_length); ++mac_iter)
+    {
+        if (CMP_MAC(mac_iter->octet, BSSID))
+        {
+            /* The scanned result is a duplicate; just return */
+            return true;
+        }
+    }
+    /* If scanned Wi-Fi is not a duplicate then populate the array */
+    if (current_bssid_arr_length < SCAN_BSSI_ARR_MAX)
+    {
+        memcpy(mac_iter->octet, BSSID, sizeof(_mac_t));
+        current_bssid_arr_length++;
+    }
+    return false;
+}
+
+int scan_callback(void *env, const cyw43_ev_scan_result_t *result)
+{
+    if (result->ssid_len != 0)
+    {
+        /* parse scan report event data */
+        struct rt_wlan_buff buff;
+        struct rt_wlan_info wlan_info;
+        if (scan_bssi_has(result->bssid) == false)
+        {
+            _ifx_scan_info2rtt(result, &wlan_info);
+
+            buff.data = &wlan_info;
+            buff.len = sizeof(struct rt_wlan_info);
+
+            /* indicate scan report event */
+            rt_wlan_dev_indicate_event_handle(wifi_sta.wlan, RT_WLAN_DEV_EVT_SCAN_REPORT, &buff);
+        }
+    }
+    return RT_EOK;
+}
+
+static rt_err_t wlan_init(struct rt_wlan_device *wlan)
+{
+    rt_int8_t res = cyw43_arch_init();;
+
+    if (res == 0)
+    {
+        return RT_EOK;
+    }
+    LOG_E("cyw43_arch_init failed...! error code: %d\n", res);
+    return -RT_ERROR;
+}
+
+static rt_err_t wlan_scan(struct rt_wlan_device *wlan, struct rt_scan_info *scan_info)
+{
+    memset(mac_addr_arr, 0, sizeof(_mac_t) * SCAN_BSSI_ARR_MAX);
+    cyw43_wifi_scan_options_t scan_options = {0};
+    int err = cyw43_wifi_scan(&cyw43_state, &scan_options, NULL, scan_callback);
+
+    if (err == 0)
+    {
+        rt_wlan_dev_indicate_event_handle(wifi_sta.wlan, RT_WLAN_DEV_EVT_SCAN_DONE, 0);
+        return RT_EOK;
+    }
+    return -RT_ERROR;
+}
+
+static rt_err_t wlan_join(struct rt_wlan_device *wlan, struct rt_sta_info *sta_info)
+{
+    uint32_t res;
+    /** Join to Wi-Fi AP **/
+    res = cyw43_wifi_join(&cyw43_state, sta_info->ssid.len, sta_info->ssid.val, sta_info->key.len, sta_info->key.val, CYW43_AUTH_WPA2_AES_PSK, RT_NULL, RT_NULL);
+
+    if (res == 0)
+    {
+        return RT_EOK;
+    }
+    return -RT_ERROR;
+}
+
+rt_err_t wlan_mode(struct rt_wlan_device *wlan, rt_wlan_mode_t mode)
+{
+    switch (mode)
+    {
+    case RT_WLAN_STATION:
+        LOG_D("wlan_mode RT_WLAN_STATION\n");
+        cyw43_arch_enable_sta_mode();
+        break;
+    case RT_WLAN_AP:
+        LOG_D("wlan_mode RT_WLAN_AP\n");
+        break;
+    }
+
+    return 0;
+}
+
+rt_err_t wlan_softap(struct rt_wlan_device *wlan, struct rt_ap_info *ap_info)
+{
+    LOG_D("wlan_softap");
+    cyw43_arch_enable_ap_mode(ap_info->ssid.val, ap_info->key.val, get_security(ap_info->security));
+    LOG_D("ap start ok");
+    rt_wlan_dev_indicate_event_handle(wifi_ap.wlan, RT_WLAN_DEV_EVT_AP_START, 0);
+
+    return RT_EOK;
+}
+
+rt_err_t wlan_disconnect(struct rt_wlan_device *wlan)
+{
+    LOG_D("wlan_disconnect");
+    cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA);
+    return RT_EOK;
+}
+
+rt_err_t wlan_ap_stop(struct rt_wlan_device *wlan)
+{
+    LOG_D("wlan_ap_stop");
+    cyw43_wifi_leave(&cyw43_state, CYW43_ITF_AP);
+    return RT_EOK;
+}
+
+int wlan_get_rssi(struct rt_wlan_device *wlan)
+{
+    int32_t rssi;
+    cyw43_wifi_get_rssi(&cyw43_state, &rssi);
+    return rssi;
+}
+rt_err_t wlan_set_channel(struct rt_wlan_device *wlan, int channel)
+{
+    LOG_D("wlan_set_channel");
+    cyw43_wifi_ap_set_channel(&cyw43_state, channel);
+    return 0;
+}
+int wlan_get_channel(struct rt_wlan_device *wlan)
+{
+    LOG_D("wlan_get_channel");
+    return cyw43_state.ap_channel;
+}
+rt_err_t wlan_get_mac(struct rt_wlan_device *wlan, rt_uint8_t mac[])
+{
+    int res = cyw43_wifi_get_mac(&cyw43_state, CYW43_ITF_STA, mac);
+    if (res == 0)
+    {
+        LOG_D("WLAN MAC Address : %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2],
+                mac[3], mac[4], mac[5]);
+        return RT_EOK;
+    }
+    return -RT_ERROR;
+}
+rt_err_t wlan_set_country(struct rt_wlan_device *wlan, rt_country_code_t country_code){
+    rt_int8_t res = cyw43_arch_init_with_country(country_code);
+    if (res == 0)
+    {
+        return RT_EOK;
+    }
+    return -RT_ERROR;
+}
+rt_country_code_t wlan_get_country(struct rt_wlan_device *wlan){
+    return cyw43_arch_get_country_code();
+}
+
+static int wlan_send(struct rt_wlan_device *wlan, void *buff, int len)
+{
+    if(wlan == RT_NULL)
+    {
+        LOG_E("wlan is null!!!");
+        return -RT_ERROR;
+    }
+
+    if (wlan == wifi_sta.wlan)
+    {
+        cyw43_send_ethernet(&cyw43_state, CYW43_ITF_STA, len, buff, false);
+    }
+    else
+    {
+        cyw43_send_ethernet(&cyw43_state, CYW43_ITF_AP, len, buff, false);
+    }
+    return len;
+}
+
+const static struct rt_wlan_dev_ops ops =
+{
+    .wlan_init          = wlan_init,
+    .wlan_mode          = wlan_mode,
+    .wlan_scan          = wlan_scan,
+    .wlan_join          = wlan_join,
+    .wlan_softap        = wlan_softap,
+    .wlan_disconnect    = wlan_disconnect,
+    .wlan_ap_stop       = wlan_ap_stop,
+    .wlan_get_rssi      = wlan_get_rssi,
+    .wlan_set_channel   = wlan_set_channel,
+    .wlan_get_channel   = wlan_get_channel,
+    .wlan_set_country   = wlan_set_country,
+    .wlan_get_country   = wlan_get_country,
+    .wlan_get_mac       = wlan_get_mac,
+    .wlan_send          = wlan_send,
+};
+
+int rt_hw_wifi_init(void)
+{
+    static struct rt_wlan_device wlan_sta, wlan_ap;
+    rt_err_t ret;
+    wifi_sta.wlan = &wlan_sta;
+    wifi_ap.wlan = &wlan_ap;
+
+    /* register wlan device for ap */
+    ret = rt_wlan_dev_register(&wlan_ap, RT_WLAN_DEVICE_AP_NAME, &ops, 0, &wifi_ap);
+    if (ret != RT_EOK)
+    {
+        return ret;
+    }
+
+    /* register wlan device for sta */
+    ret = rt_wlan_dev_register(&wlan_sta, RT_WLAN_DEVICE_STA_NAME, &ops, 0, &wifi_sta);
+    if (ret != RT_EOK)
+    {
+        return ret;
+    }
+
+}
+INIT_DEVICE_EXPORT(rt_hw_wifi_init);
+
+#endif /* PKG_USING_WLAN_CYW43439 */

+ 20 - 0
source/inc/arch_rtthread.h

@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef _PICO_CYW43_ARCH_ARCH_RTTHREAD_H
+#define _PICO_CYW43_ARCH_ARCH_RTTHREAD_H
+
+// PICO_CONFIG: CYW43_TASK_STACK_SIZE, Stack size for the CYW43 RTTHREAD task in 4-byte words, type=int, default=1024, group=pico_cyw43_arch
+#ifndef CYW43_TASK_STACK_SIZE
+#define CYW43_TASK_STACK_SIZE 2048
+#endif
+
+// PICO_CONFIG: CYW43_TASK_PRIORITY, Priority for the CYW43 RTTHREAD task, type=int, default=tskIDLE_PRIORITY + 4, group=pico_cyw43_arch
+#ifndef CYW43_TASK_PRIORITY
+#define CYW43_TASK_PRIORITY 8
+#endif
+
+#endif

+ 123 - 0
source/inc/async_context_rtthread.h

@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef _PICO_ASYNC_CONTEXT_RTTHREAD_H
+#define _PICO_ASYNC_CONTEXT_RTTHREAD_H
+
+/** \file pico/async_context.h
+ *  \defgroup async_context_rtthread async_context_rtthread
+ *  \ingroup pico_async_context
+ *
+ * async_context_rtthread provides an implementation of \ref async_context that handles asynchronous
+ * work in a separate RTThread task.
+ */
+#include "pico/async_context.h"
+
+// RTThread includes
+#include "rtthread.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef pdMS_TO_TICKS
+#define pdMS_TO_TICKS( xTimeInMs )    ( ( rt_uint32_t ) rt_tick_from_millisecond( (rt_int32_t) xTimeInMs ) )
+#endif
+
+#ifndef ASYNC_CONTEXT_DEFAULT_RTTHREAD_TASK_PRIORITY
+#define ASYNC_CONTEXT_DEFAULT_RTTHREAD_TASK_PRIORITY 8
+#endif
+
+#ifndef ASYNC_CONTEXT_DEFAULT_RTTHREAD_TASK_STACK_SIZE
+#define ASYNC_CONTEXT_DEFAULT_RTTHREAD_TASK_STACK_SIZE 2048
+#endif
+
+typedef struct async_context_rtthread async_context_rtthread_t;
+
+/**
+ * \brief Configuration object for async_context_rtthread instances.
+ */
+typedef struct async_context_rtthread_config {
+    /**
+     * Task priority for the async_context task
+     */
+    rt_uint8_t task_priority;
+    /**
+     * Stack size for the async_context task
+     */
+    rt_uint32_t task_stack_size;
+    /**
+     * the core ID (see \ref portGET_CORE_ID()) to pin the task to.
+     * This is only relevant in SMP mode.
+     */
+#if configUSE_CORE_AFFINITY && configNUM_CORES > 1
+    rt_uint8_t task_core_id;
+#endif
+} async_context_rtthread_config_t;
+
+struct async_context_rtthread {
+    async_context_t core;
+    rt_mutex_t lock_mutex;
+    rt_sem_t work_needed_sem;
+    rt_event_t notify_event;
+    rt_timer_t timer_handle;
+    rt_thread_t task_handle;
+    uint8_t nesting;
+    volatile bool task_should_exit;
+};
+
+/*!
+ * \brief Initialize an async_context_rtthread instance using the specified configuration
+ * \ingroup async_context_rtthread
+ *
+ * If this method succeeds (returns true), then the async_context is available for use
+ * and can be de-initialized by calling async_context_deinit().
+ *
+ * \param self a pointer to async_context_rtthread structure to initialize
+ * \param config the configuration object specifying characteristics for the async_context
+ * \return true if initialization is successful, false otherwise
+ */
+bool async_context_rtthread_init(async_context_rtthread_t *self, async_context_rtthread_config_t *config);
+
+/*!
+ * \brief Return a copy of the default configuration object used by \ref async_context_rtthread_init_with_defaults()
+ * \ingroup async_context_rtthread
+ *
+ * The caller can then modify just the settings it cares about, and call \ref async_context_rtthread_init()
+ * \return the default configuration object
+ */
+ static inline async_context_rtthread_config_t async_context_rtthread_default_config(void) {
+    async_context_rtthread_config_t config = {
+            .task_priority = ASYNC_CONTEXT_DEFAULT_RTTHREAD_TASK_PRIORITY,
+            .task_stack_size = ASYNC_CONTEXT_DEFAULT_RTTHREAD_TASK_STACK_SIZE,
+#if configUSE_CORE_AFFINITY && configNUM_CORES > 1
+            .task_core_id = (rt_uint8_t)-1, // none
+#endif
+    };
+    return config;
+
+}
+
+/*!
+ * \brief Initialize an async_context_rtthread instance with default values
+ * \ingroup async_context_rtthread
+ *
+ * If this method succeeds (returns true), then the async_context is available for use
+ * and can be de-initialized by calling async_context_deinit().
+ *
+ * \param self a pointer to async_context_rtthread structure to initialize
+ * \return true if initialization is successful, false otherwise
+ */
+ static inline bool async_context_rtthread_init_with_defaults(async_context_rtthread_t *self) {
+    async_context_rtthread_config_t config = async_context_rtthread_default_config();
+    return async_context_rtthread_init(self, &config);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 506 - 0
source/inc/cyw43_arch.h

@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef _PICO_CYW43_ARCH_H
+#define _PICO_CYW43_ARCH_H
+
+#include "pico.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "cyw43.h"
+#include "cyw43_country.h"
+#include "pico/async_context.h"
+
+#ifdef PICO_CYW43_ARCH_HEADER
+#include __XSTRING(PICO_CYW43_ARCH_HEADER)
+#else
+#if PICO_CYW43_ARCH_POLL
+#include "pico/cyw43_arch/arch_poll.h"
+#elif PICO_CYW43_ARCH_THREADSAFE_BACKGROUND
+#include "pico/cyw43_arch/arch_threadsafe_background.h"
+#elif PICO_CYW43_ARCH_FREERTOS
+#include "pico/cyw43_arch/arch_freertos.h"
+#elif PICO_CYW43_ARCH_RTTHREAD
+#include "arch_rtthread.h"
+#else
+#error must specify support pico_cyw43_arch architecture type or set PICO_CYW43_ARCH_HEADER
+#endif
+#endif
+
+/**
+ * \defgroup cyw43_driver cyw43_driver
+ * \ingroup pico_cyw43_arch
+ * \brief Driver used for Pico W wireless
+*/
+
+/**
+ * \defgroup cyw43_ll cyw43_ll
+ * \ingroup cyw43_driver
+ * \brief Low Level CYW43 driver interface
+*/
+
+/** \file pico/cyw43_arch.h
+ *  \defgroup pico_cyw43_arch pico_cyw43_arch
+ *
+ * Architecture for integrating the CYW43 driver (for the wireless on Pico W) and lwIP (for TCP/IP stack) into the SDK. It is also necessary for accessing the on-board LED on Pico W
+ *
+ * Both the low level \c cyw43_driver and the lwIP stack require periodic servicing, and have limitations
+ * on whether they can be called from multiple cores/threads.
+ *
+ * \c pico_cyw43_arch attempts to abstract these complications into several behavioral groups:
+ *
+ * * \em 'poll' - This not multi-core/IRQ safe, and requires the user to call \ref cyw43_arch_poll periodically from their main loop
+ * * \em 'thread_safe_background' - This is multi-core/thread/task safe, and maintenance of the driver and TCP/IP stack is handled automatically in the background
+ * * \em 'freertos' - This is multi-core/thread/task safe, and uses a separate FreeRTOS task to handle lwIP and and driver work.
+ *
+ * As of right now, lwIP is the only supported TCP/IP stack, however the use of \c pico_cyw43_arch is intended to be independent of
+ * the particular TCP/IP stack used (and possibly Bluetooth stack used) in the future. For this reason, the integration of lwIP
+ * is handled in the base (\c pico_cyw43_arch) library based on the #define \ref CYW43_LWIP used by the \c cyw43_driver.
+ *
+ * \note As of version 1.5.0 of the Raspberry Pi Pico SDK, the \c pico_cyw43_arch library no longer directly implements
+ * the distinct behavioral abstractions. This is now handled by the more general \ref pico_async_context library. The
+ * user facing behavior of pico_cyw43_arch has not changed as a result of this implementation detail, however pico_cyw43_arch
+ * is now just a thin wrapper which creates an appropriate async_context and makes a simple call to add lwIP or cyw43_driver support
+ * as appropriate. You are free to perform this context creation and adding of lwIP, cyw43_driver or indeed any other additional
+ * future protocol/driver support to your async_context, however for now pico_cyw43_arch does still provide a few cyw43_ specific (i.e. Pico W)
+ * APIs for connection management, locking and GPIO interaction.
+ *
+ * \note The connection management APIs at least may be moved
+ * to a more generic library in a future release. The locking methods are now backed by their \ref pico_async_context equivalents, and
+ * those methods may be used interchangeably (see \ref cyw43_arch_lwip_begin, \ref cyw43_arch_lwip_end and \ref cyw43_arch_lwip_check for more details).
+ *
+ * \note For examples of creating of your own async_context and addition of \c cyw43_driver and \c lwIP support, please
+ * refer to the specific source files \c cyw43_arch_poll.c, \c cyw43_arch_threadsafe_background.c and \c cyw43_arch_freertos.c.
+ *
+ * Whilst you can use the \c pico_cyw43_arch library directly and specify \ref CYW43_LWIP (and other defines) yourself, several
+ * other libraries are made available to the build which aggregate the defines and other dependencies for you:
+ *
+ * * \b pico_cyw43_arch_lwip_poll - For using the RAW lwIP API (in `NO_SYS=1` mode) without any background processing or multi-core/thread safety.
+ *
+ *    The user must call \ref cyw43_arch_poll periodically from their main loop.
+ *
+ *    This wrapper library:
+ *    - Sets \c CYW43_LWIP=1 to enable lwIP support in \c pico_cyw43_arch and \c cyw43_driver.
+ *    - Sets \c PICO_CYW43_ARCH_POLL=1 to select the polling behavior.
+ *    - Adds the \c pico_lwip as a dependency to pull in lwIP.
+ *
+ * * \b pico_cyw43_arch_lwip_threadsafe_background - For using the RAW lwIP API (in `NO_SYS=1` mode) with multi-core/thread safety, and automatic servicing of the \c cyw43_driver and
+ * lwIP in background.
+ *
+ *    Calls into the \c cyw43_driver high level API (cyw43.h) may be made from either core or from lwIP callbacks, however calls into lwIP (which
+ * is not thread-safe) other than those made from lwIP callbacks, must be bracketed with \ref cyw43_arch_lwip_begin and \ref cyw43_arch_lwip_end. It is fine to bracket
+ * calls made from within lwIP callbacks too; you just don't have to.
+ *
+ *    \note lwIP callbacks happen in a (low priority) IRQ context (similar to an alarm callback), so care should be taken when interacting
+ *    with other code.
+ *
+ *    This wrapper library:
+ *    - Sets \c CYW43_LWIP=1 to enable lwIP support in \c pico_cyw43_arch and \c cyw43_driver
+ *    - Sets \c PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1 to select the thread-safe/non-polling behavior.
+ *    - Adds the pico_lwip as a dependency to pull in lwIP.
+ *
+ *
+ *    This library \em can also be used under the RP2040 port of FreeRTOS with lwIP in `NO_SYS=1` mode (allowing you to call \c cyw43_driver APIs
+ * from any task, and to call lwIP from lwIP callbacks, or from any task if you bracket the calls with \ref cyw43_arch_lwip_begin and \ref cyw43_arch_lwip_end. Again, you should be
+ * careful about what you do in lwIP callbacks, as you cannot call most FreeRTOS APIs from within an IRQ context. Unless you have good reason, you should probably
+ * use the full FreeRTOS integration (with `NO_SYS=0`) provided by \c pico_cyw43_arch_lwip_sys_freertos.
+ *
+ * * \b pico_cyw43_arch_lwip_sys_freertos - For using the full lwIP API including blocking sockets in OS (`NO_SYS=0`) mode, along with with multi-core/task/thread safety, and automatic servicing of the \c cyw43_driver and
+ * the lwIP stack.
+ *
+ *    This wrapper library:
+ *    - Sets \c CYW43_LWIP=1 to enable lwIP support in \c pico_cyw43_arch and \c cyw43_driver.
+ *    - Sets \c PICO_CYW43_ARCH_FREERTOS=1 to select the NO_SYS=0 lwip/FreeRTOS integration
+ *    - Sets \c LWIP_PROVIDE_ERRNO=1 to provide error numbers needed for compilation without an OS
+ *    - Adds the \c pico_lwip as a dependency to pull in lwIP.
+ *    - Adds the lwIP/FreeRTOS code from lwip-contrib (in the contrib directory of lwIP)
+ *
+ *    Calls into the \c cyw43_driver high level API (cyw43.h) may be made from any task or from lwIP callbacks, but not from IRQs. Calls into the lwIP RAW API (which is not thread safe)
+ *    must be bracketed with \ref cyw43_arch_lwip_begin and \ref cyw43_arch_lwip_end. It is fine to bracket calls made from within lwIP callbacks too; you just don't have to.
+ *
+ *    \note this wrapper library requires you to link FreeRTOS functionality with your application yourself.
+ *
+ * * \b pico_cyw43_arch_none - If you do not need the TCP/IP stack but wish to use the on-board LED.
+ *
+ *    This wrapper library:
+ *    - Sets \c CYW43_LWIP=0 to disable lwIP support in \c pico_cyw43_arch and \c cyw43_driver
+ */
+
+// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_CYW43_ARCH, Enable/disable assertions in the pico_cyw43_arch module, type=bool, default=0, group=pico_cyw43_arch
+#ifndef PARAM_ASSERTIONS_ENABLED_CYW43_ARCH
+#define PARAM_ASSERTIONS_ENABLED_CYW43_ARCH 0
+#endif
+
+// PICO_CONFIG: PICO_CYW43_ARCH_DEBUG_ENABLED, Enable/disable some debugging output in the pico_cyw43_arch module, type=bool, default=1 in debug builds, group=pico_cyw43_arch
+#ifndef PICO_CYW43_ARCH_DEBUG_ENABLED
+#ifndef NDEBUG
+#define PICO_CYW43_ARCH_DEBUG_ENABLED 1
+#else
+#define PICO_CYW43_ARCH_DEBUG_ENABLED 0
+#endif
+#endif
+
+// PICO_CONFIG: PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE, Default country code for the cyw43 wireless driver, default=CYW43_COUNTRY_WORLDWIDE, group=pico_cyw43_arch
+#ifndef PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE
+#define PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE CYW43_COUNTRY_WORLDWIDE
+#endif
+
+/*!
+ * \brief Initialize the CYW43 architecture
+ * \ingroup pico_cyw43_arch
+ *
+ * This method initializes the `cyw43_driver` code and initializes the lwIP stack (if it
+ * was enabled at build time). This method must be called prior to using any other \c pico_cyw43_arch,
+ * \c cyw43_driver or lwIP functions.
+ *
+ * \note this method initializes wireless with a country code of \c PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE
+ * which defaults to \c CYW43_COUNTRY_WORLDWIDE. Worldwide settings may not give the best performance; consider
+ * setting PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE to a different value or calling \ref cyw43_arch_init_with_country
+ *
+ * By default this method initializes the cyw43_arch code's own async_context by calling
+ * \ref cyw43_arch_init_default_async_context, however the user can specify use of their own async_context
+ * by calling \ref cyw43_arch_set_async_context() before calling this method
+ *
+ * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes
+ */
+int cyw43_arch_init(void);
+
+/*!
+ * \brief Initialize the CYW43 architecture for use in a specific country
+ * \ingroup pico_cyw43_arch
+ *
+ * This method initializes the `cyw43_driver` code and initializes the lwIP stack (if it
+ * was enabled at build time). This method must be called prior to using any other \c pico_cyw43_arch,
+ * \c cyw43_driver or lwIP functions.
+ *
+ * By default this method initializes the cyw43_arch code's own async_context by calling
+ * \ref cyw43_arch_init_default_async_context, however the user can specify use of their own async_context
+ * by calling \ref cyw43_arch_set_async_context() before calling this method
+ *
+ * \param country the country code to use (see \ref CYW43_COUNTRY_)
+ * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes
+ */
+int cyw43_arch_init_with_country(uint32_t country);
+
+/*!
+ * \brief De-initialize the CYW43 architecture
+ * \ingroup pico_cyw43_arch
+ *
+ * This method de-initializes the `cyw43_driver` code and de-initializes the lwIP stack (if it
+ * was enabled at build time). Note this method should always be called from the same core (or RTOS
+ * task, depending on the environment) as \ref cyw43_arch_init.
+ *
+ * Additionally if the cyw43_arch is using its own async_context instance, then that instance is de-initialized.
+ */
+void cyw43_arch_deinit(void);
+
+/*!
+ * \brief Return the current async_context currently in use by the cyw43_arch code
+ * \ingroup pico_cyw43_arch
+ *
+ * \return the async_context.
+ */
+async_context_t *cyw43_arch_async_context(void);
+
+/*!
+ * \brief Set the async_context to be used by the cyw43_arch_init
+ * \ingroup pico_cyw43_arch
+ *
+ * \note This method must be called before calling cyw43_arch_init or cyw43_arch_init_with_country
+ * if you wish to use a custom async_context instance.
+ *
+ * \param context the async_context to be used
+ */
+void cyw43_arch_set_async_context(async_context_t *context);
+
+/*!
+ * \brief Initialize the default async_context for the current cyw43_arch type
+ * \ingroup pico_cyw43_arch
+ *
+ * This method initializes and returns a pointer to the static async_context associated
+ * with cyw43_arch. This method is called by \ref cyw43_arch_init automatically
+ * if a different async_context has not been set by \ref cyw43_arch_set_async_context
+ *
+ * \return the context or NULL if initialization failed.
+ */
+async_context_t *cyw43_arch_init_default_async_context(void);
+
+/*!
+ * \brief Perform any processing required by the \c cyw43_driver or the TCP/IP stack
+ * \ingroup pico_cyw43_arch
+ *
+ * This method must be called periodically from the main loop when using a
+ * \em polling style \c pico_cyw43_arch (e.g. \c pico_cyw43_arch_lwip_poll ). It
+ * may be called in other styles, but it is unnecessary to do so.
+ */
+void cyw43_arch_poll(void);
+
+/*!
+ * \brief Sleep until there is cyw43_driver work to be done
+ * \ingroup pico_cyw43_arch
+ *
+ * This method may be called by code that is waiting for an event to
+ * come from the cyw43_driver, and has no work to do, but would like
+ * to sleep without blocking any background work associated with the cyw43_driver.
+ *
+ * \param until the time to wait until if there is no work to do.
+ */
+void cyw43_arch_wait_for_work_until(absolute_time_t until);
+
+/*!
+ * \fn cyw43_arch_lwip_begin
+ * \brief Acquire any locks required to call into lwIP
+ * \ingroup pico_cyw43_arch
+ *
+ * The lwIP API is not thread safe. You should surround calls into the lwIP API
+ * with calls to this method and \ref cyw43_arch_lwip_end. Note these calls are not
+ * necessary (but harmless) when you are calling back into the lwIP API from an lwIP callback.
+ * If you are using single-core polling only (pico_cyw43_arch_poll) then these calls are no-ops
+ * anyway it is good practice to call them anyway where they are necessary.
+ *
+ * \note as of SDK release 1.5.0, this is now equivalent to calling \ref async_context_acquire_lock_blocking
+ * on the async_context associated with cyw43_arch and lwIP.
+ *
+ * \sa cyw43_arch_lwip_end
+ * \sa cyw43_arch_lwip_protect
+ * \sa async_context_acquire_lock_blocking
+ * \sa cyw43_arch_async_context
+ */
+static inline void cyw43_arch_lwip_begin(void) {
+    cyw43_thread_enter();
+}
+
+/*!
+ * \fn void cyw43_arch_lwip_end(void)
+ * \brief Release any locks required for calling into lwIP
+ * \ingroup pico_cyw43_arch
+ *
+ * The lwIP API is not thread safe. You should surround calls into the lwIP API
+ * with calls to \ref cyw43_arch_lwip_begin and this method. Note these calls are not
+ * necessary (but harmless) when you are calling back into the lwIP API from an lwIP callback.
+ * If you are using single-core polling only (pico_cyw43_arch_poll) then these calls are no-ops
+ * anyway it is good practice to call them anyway where they are necessary.
+ *
+ * \note as of SDK release 1.5.0, this is now equivalent to calling \ref async_context_release_lock
+ * on the async_context associated with cyw43_arch and lwIP.
+ *
+ * \sa cyw43_arch_lwip_begin
+ * \sa cyw43_arch_lwip_protect
+ * \sa async_context_release_lock
+ * \sa cyw43_arch_async_context
+ */
+static inline void cyw43_arch_lwip_end(void) {
+    cyw43_thread_exit();
+}
+
+/*!
+ * \fn int cyw43_arch_lwip_protect(int (*func)(void *param), void *param)
+ * \brief sad Release any locks required for calling into lwIP
+ * \ingroup pico_cyw43_arch
+ *
+ * The lwIP API is not thread safe. You can use this method to wrap a function
+ * with any locking required to call into the lwIP API. If you are using
+ * single-core polling only (pico_cyw43_arch_poll) then there are no
+ * locks to required, but it is still good practice to use this function.
+ *
+ * \param func the function ta call with any required locks held
+ * \param param parameter to pass to \c func
+ * \return the return value from \c func
+ * \sa cyw43_arch_lwip_begin
+ * \sa cyw43_arch_lwip_end
+ */
+static inline int cyw43_arch_lwip_protect(int (*func)(void *param), void *param) {
+    cyw43_arch_lwip_begin();
+    int rc = func(param);
+    cyw43_arch_lwip_end();
+    return rc;
+}
+
+/*!
+ * \fn void cyw43_arch_lwip_check(void)
+ * \brief Checks the caller has any locks required for calling into lwIP
+ * \ingroup pico_cyw43_arch
+ *
+ * The lwIP API is not thread safe. You should surround calls into the lwIP API
+ * with calls to \ref cyw43_arch_lwip_begin and this method. Note these calls are not
+ * necessary (but harmless) when you are calling back into the lwIP API from an lwIP callback.
+ *
+ * This method will assert in debug mode, if the above conditions are not met (i.e. it is not safe to
+ * call into the lwIP API)
+ *
+ * \note as of SDK release 1.5.0, this is now equivalent to calling \ref async_context_lock_check
+ * on the async_context associated with cyw43_arch and lwIP.
+ *
+ * \sa cyw43_arch_lwip_begin
+ * \sa cyw43_arch_lwip_protect
+ * \sa async_context_lock_check
+ * \sa cyw43_arch_async_context
+ */
+
+/*!
+ * \brief Return the country code used to initialize cyw43_arch
+ * \ingroup pico_cyw43_arch
+ *
+ * \return the country code (see \ref CYW43_COUNTRY_)
+ */
+uint32_t cyw43_arch_get_country_code(void);
+
+/*!
+ * \brief Enables Wi-Fi STA (Station) mode.
+ * \ingroup pico_cyw43_arch
+ *
+ * This enables the Wi-Fi in \em Station mode such that connections can be made to other Wi-Fi Access Points
+ */
+void cyw43_arch_enable_sta_mode(void);
+
+/*!
+ * \brief Disables Wi-Fi STA (Station) mode.
+ * \ingroup pico_cyw43_arch
+ *
+ * This disables the Wi-Fi in \em Station mode, disconnecting any active connection.
+ * You should subsequently check the status by calling \ref cyw43_wifi_link_status.
+ */
+void cyw43_arch_disable_sta_mode(void);
+
+/*!
+ * \brief Enables Wi-Fi AP (Access point) mode.
+ * \ingroup pico_cyw43_arch
+ *
+ * This enables the Wi-Fi in \em Access \em Point mode such that connections can be made to the device by other Wi-Fi clients
+ * \param ssid the name for the access point
+ * \param password the password to use or NULL for no password.
+ * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK,
+ *             \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_)
+ */
+void cyw43_arch_enable_ap_mode(const char *ssid, const char *password, uint32_t auth);
+
+/*!
+ * \brief Disables Wi-Fi AP (Access point) mode.
+ * \ingroup pico_cyw43_arch
+ *
+ * This Disbles the Wi-Fi in \em Access \em Point mode.
+ */
+void cyw43_arch_disable_ap_mode(void);
+
+/*!
+ * \brief Attempt to connect to a wireless access point, blocking until the network is joined or a failure is detected.
+ * \ingroup pico_cyw43_arch
+ *
+ * \param ssid the network name to connect to
+ * \param pw the network password or NULL if there is no password required
+ * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK,
+ *             \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_)
+ *
+ * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes
+ */
+int cyw43_arch_wifi_connect_blocking(const char *ssid, const char *pw, uint32_t auth);
+
+/*!
+ * \brief Attempt to connect to a wireless access point specified by SSID and BSSID, blocking until the network is joined or a failure is detected.
+ * \ingroup pico_cyw43_arch
+ *
+ * \param ssid the network name to connect to
+ * \param bssid the network BSSID to connect to or NULL if ignored
+ * \param pw the network password or NULL if there is no password required
+ * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK,
+ *             \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_)
+ *
+ * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes
+ */
+int cyw43_arch_wifi_connect_bssid_blocking(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth);
+
+/*!
+ * \brief Attempt to connect to a wireless access point, blocking until the network is joined, a failure is detected or a timeout occurs
+ * \ingroup pico_cyw43_arch
+ *
+ * \param ssid the network name to connect to
+ * \param pw the network password or NULL if there is no password required
+ * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK,
+ *             \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_)
+ * \param timeout how long to wait in milliseconds for a connection to succeed before giving up
+ *
+ * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes
+ */
+int cyw43_arch_wifi_connect_timeout_ms(const char *ssid, const char *pw, uint32_t auth, uint32_t timeout);
+
+/*!
+ * \brief Attempt to connect to a wireless access point specified by SSID and BSSID, blocking until the network is joined, a failure is detected or a timeout occurs
+ * \ingroup pico_cyw43_arch
+ *
+ * \param ssid the network name to connect to
+ * \param bssid the network BSSID to connect to or NULL if ignored
+ * \param pw the network password or NULL if there is no password required
+ * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK,
+ *             \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_)
+ * \param timeout how long to wait in milliseconds for a connection to succeed before giving up
+ *
+ * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes
+ */
+int cyw43_arch_wifi_connect_bssid_timeout_ms(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth, uint32_t timeout);
+
+/*!
+ * \brief Start attempting to connect to a wireless access point
+ * \ingroup pico_cyw43_arch
+ *
+ * This method tells the CYW43 driver to start connecting to an access point. You should subsequently check the
+ * status by calling \ref cyw43_wifi_link_status.
+ *
+ * \param ssid the network name to connect to
+ * \param pw the network password or NULL if there is no password required
+ * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK,
+ *             \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_)
+ *
+ * \return 0 if the scan was started successfully, an error code otherwise \see pico_error_codes
+ */
+int cyw43_arch_wifi_connect_async(const char *ssid, const char *pw, uint32_t auth);
+
+/*!
+ * \brief Start attempting to connect to a wireless access point specified by SSID and BSSID
+ * \ingroup pico_cyw43_arch
+ *
+ * This method tells the CYW43 driver to start connecting to an access point. You should subsequently check the
+ * status by calling \ref cyw43_wifi_link_status.
+ *
+ * \param ssid the network name to connect to
+ * \param bssid the network BSSID to connect to or NULL if ignored
+ * \param pw the network password or NULL if there is no password required
+ * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK,
+ *             \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_)
+ *
+ * \return 0 if the scan was started successfully, an error code otherwise \see pico_error_codes
+ */
+int cyw43_arch_wifi_connect_bssid_async(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth);
+
+/*!
+ * \brief Set a GPIO pin on the wireless chip to a given value
+ * \ingroup pico_cyw43_arch
+ * \note this method does not check for errors setting the GPIO. You can use the lower level \ref cyw43_gpio_set instead if you wish
+ * to check for errors.
+ *
+ * \param wl_gpio the GPIO number on the wireless chip
+ * \param value true to set the GPIO, false to clear it.
+ */
+void cyw43_arch_gpio_put(uint wl_gpio, bool value);
+
+/*!
+ * \brief Read the value of a GPIO pin on the wireless chip
+ * \ingroup pico_cyw43_arch
+ * \note this method does not check for errors setting the GPIO. You can use the lower level \ref cyw43_gpio_get instead if you wish
+ * to check for errors.
+ *
+ * \param wl_gpio the GPIO number on the wireless chip
+ * \return true if the GPIO is high, false otherwise
+ */
+bool cyw43_arch_gpio_get(uint wl_gpio);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 153 - 0
source/inc/cyw43_bus_pio_spi.pio.h

@@ -0,0 +1,153 @@
+// -------------------------------------------------- //
+// This file is autogenerated by pioasm; do not edit! //
+// -------------------------------------------------- //
+
+#pragma once
+
+#if !PICO_NO_HARDWARE
+#include "hardware/pio.h"
+#endif
+
+// ---------------- //
+// spi_gap0_sample1 //
+// ---------------- //
+
+#define spi_gap0_sample1_wrap_target 0
+#define spi_gap0_sample1_wrap 4
+
+#define spi_gap0_sample1_offset_lp1_end 2u
+#define spi_gap0_sample1_offset_end 5u
+
+static const uint16_t spi_gap0_sample1_program_instructions[] = {
+            //     .wrap_target
+    0x6001, //  0: out    pins, 1         side 0
+    0x1040, //  1: jmp    x--, 0          side 1
+    0xe080, //  2: set    pindirs, 0      side 0
+    0x5001, //  3: in     pins, 1         side 1
+    0x0083, //  4: jmp    y--, 3          side 0
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program spi_gap0_sample1_program = {
+    .instructions = spi_gap0_sample1_program_instructions,
+    .length = 5,
+    .origin = -1,
+};
+
+static inline pio_sm_config spi_gap0_sample1_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + spi_gap0_sample1_wrap_target, offset + spi_gap0_sample1_wrap);
+    sm_config_set_sideset(&c, 1, false, false);
+    return c;
+}
+#endif
+
+// ----------------- //
+// spi_gap01_sample0 //
+// ----------------- //
+
+#define spi_gap01_sample0_wrap_target 0
+#define spi_gap01_sample0_wrap 5
+
+#define spi_gap01_sample0_offset_lp1_end 2u
+#define spi_gap01_sample0_offset_end 6u
+
+static const uint16_t spi_gap01_sample0_program_instructions[] = {
+            //     .wrap_target
+    0x6001, //  0: out    pins, 1         side 0
+    0x1040, //  1: jmp    x--, 0          side 1
+    0xe080, //  2: set    pindirs, 0      side 0
+    0xb042, //  3: nop                    side 1
+    0x4001, //  4: in     pins, 1         side 0
+    0x1084, //  5: jmp    y--, 4          side 1
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program spi_gap01_sample0_program = {
+    .instructions = spi_gap01_sample0_program_instructions,
+    .length = 6,
+    .origin = -1,
+};
+
+static inline pio_sm_config spi_gap01_sample0_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + spi_gap01_sample0_wrap_target, offset + spi_gap01_sample0_wrap);
+    sm_config_set_sideset(&c, 1, false, false);
+    return c;
+}
+#endif
+
+// ------------------ //
+// spi_gap010_sample1 //
+// ------------------ //
+
+#define spi_gap010_sample1_wrap_target 0
+#define spi_gap010_sample1_wrap 6
+
+#define spi_gap010_sample1_offset_lp1_end 2u
+#define spi_gap010_sample1_offset_end 7u
+
+static const uint16_t spi_gap010_sample1_program_instructions[] = {
+            //     .wrap_target
+    0x6001, //  0: out    pins, 1         side 0
+    0x1040, //  1: jmp    x--, 0          side 1
+    0xe080, //  2: set    pindirs, 0      side 0
+    0xb042, //  3: nop                    side 1
+    0xa042, //  4: nop                    side 0
+    0x5001, //  5: in     pins, 1         side 1
+    0x0085, //  6: jmp    y--, 5          side 0
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program spi_gap010_sample1_program = {
+    .instructions = spi_gap010_sample1_program_instructions,
+    .length = 7,
+    .origin = -1,
+};
+
+static inline pio_sm_config spi_gap010_sample1_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + spi_gap010_sample1_wrap_target, offset + spi_gap010_sample1_wrap);
+    sm_config_set_sideset(&c, 1, false, false);
+    return c;
+}
+#endif
+
+// ------------------------ //
+// spi_gap0_sample1_regular //
+// ------------------------ //
+
+#define spi_gap0_sample1_regular_wrap_target 0
+#define spi_gap0_sample1_regular_wrap 4
+
+#define spi_gap0_sample1_regular_offset_lp1_end 2u
+#define spi_gap0_sample1_regular_offset_end 5u
+
+static const uint16_t spi_gap0_sample1_regular_program_instructions[] = {
+            //     .wrap_target
+    0x6001, //  0: out    pins, 1         side 0
+    0x1040, //  1: jmp    x--, 0          side 1
+    0xe080, //  2: set    pindirs, 0      side 0
+    0x5001, //  3: in     pins, 1         side 1
+    0x0083, //  4: jmp    y--, 3          side 0
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program spi_gap0_sample1_regular_program = {
+    .instructions = spi_gap0_sample1_regular_program_instructions,
+    .length = 5,
+    .origin = -1,
+};
+
+static inline pio_sm_config spi_gap0_sample1_regular_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + spi_gap0_sample1_regular_wrap_target, offset + spi_gap0_sample1_regular_wrap);
+    sm_config_set_sideset(&c, 1, false, false);
+    return c;
+}
+#endif
+

+ 51 - 0
source/inc/lwip_rtthread.h

@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef _PICO_LWIP_RTTHREAD_H
+#define _PICO_LWIP_RTTHREAD_H
+
+#include "pico.h"
+#include "pico/async_context.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \file pico/lwip_rtthread.h
+* \defgroup pico_lwip_rtthread pico_lwip_rtthread
+* \ingroup pico_lwip
+* \brief Glue library for integration lwIP in \c NO_SYS=0 mode with the SDK. Simple \c init and \c deinit
+* are all that is required to hook up lwIP (with full blocking API support) via an \ref async_context instance.
+*/
+
+/*! \brief Initializes lwIP (NO_SYS=0 mode) support support for rtthread using the provided async_context
+ *  \ingroup pico_lwip_rtthread
+ *
+ * If the initialization succeeds, \ref lwip_rtthread_deinit() can be called to shutdown lwIP support
+ *
+ * \param context the async_context instance that provides the abstraction for handling asynchronous work. Note in general
+ * this would be an \ref async_context_rtthread instance, though it doesn't have to be.
+ *
+ * \return true if the initialization succeeded
+*/
+bool lwip_rtthread_init(async_context_t *context);
+
+/*! \brief De-initialize lwIP (NO_SYS=0 mode) support for rtthread
+ *  \ingroup pico_lwip_rtthread
+ *
+ * Note that since lwIP may only be initialized once, and doesn't itself provide a shutdown mechanism, lwIP
+ * itself may still consume resources.
+ *
+ * It is however safe to call \ref lwip_rtthread_init again later.
+ *
+ * \param context the async_context the lwip_rtthread support was added to via \ref lwip_rtthread_init
+*/
+void lwip_rtthread_deinit(async_context_t *context);
+
+#ifdef __cplusplus
+}
+#endif
+#endif

+ 309 - 0
source/src/async_context_rtthread.c

@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <string.h>
+#include "async_context_rtthread.h"
+#include "pico/async_context_base.h"
+#include "pico/sync.h"
+#include "hardware/irq.h"
+
+#if configNUM_CORES > 1 && !defined(configUSE_CORE_AFFINITY)
+#error async_context_rtthread requires configUSE_CORE_AFFINITY under SMP
+#endif
+
+static const async_context_type_t template;
+
+static void async_context_rtthread_acquire_lock_blocking(async_context_t *self_base);
+static void async_context_rtthread_release_lock(async_context_t *self_base);
+static void async_context_rtthread_lock_check(async_context_t *self_base);
+
+static rt_uint32_t sensible_ticks_until(absolute_time_t until) {
+    rt_uint32_t ticks;
+    int64_t delay_us = absolute_time_diff_us(get_absolute_time(), until);
+    if (delay_us <= 0) {
+        ticks = 0;
+    } else {
+        static const uint32_t max_delay = 60000000;
+        uint32_t delay_us_32 = delay_us > max_delay ? max_delay : (uint32_t) delay_us;
+        ticks = pdMS_TO_TICKS((delay_us_32+999)/1000);
+        // we want to round up, as both rounding down to zero is wrong (may produce no delays
+        // where delays are needed), but also we don't want to wake up, and then realize there
+        // is no work to do yet!
+        ticks++;
+    }
+    return ticks;
+}
+
+static void process_under_lock(async_context_rtthread_t *self) {
+#ifndef NDEBUG
+    async_context_rtthread_lock_check(&self->core);
+#endif
+    bool repeat;
+    do {
+        repeat = false;
+        absolute_time_t next_time = async_context_base_execute_once(&self->core);
+        rt_uint32_t ticks;
+        if (is_at_the_end_of_time(next_time)) {
+            ticks = RT_TICK_MAX / 2 - 1;
+        } else {
+            ticks = sensible_ticks_until(next_time);
+        }
+        if (ticks) {
+            // last parameter (timeout) is also 'ticks', since there is no point waiting to change the period
+            // for longer than the period itself!
+            repeat = RT_EOK != rt_timer_control(self->timer_handle, RT_TIMER_CTRL_SET_TIME, &ticks);
+        } else {
+            repeat = true;
+        }
+    } while (repeat);
+}
+
+static void async_context_task(void *param) {
+    async_context_rtthread_t *self = (async_context_rtthread_t *)param;
+    rt_uint32_t e;
+    do {
+        rt_event_recv(self->notify_event, 1, RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND, RT_TICK_MAX / 2 - 1, &e);
+        if (self->task_should_exit) break;
+        async_context_rtthread_acquire_lock_blocking(&self->core);
+        process_under_lock(self);
+        async_context_rtthread_release_lock(&self->core);
+        __sev(); // it is possible regular code is waiting on a WFE on the other core
+    } while (!self->task_should_exit);
+    rt_thread_delete(rt_thread_self());
+}
+
+static void async_context_rtthread_wake_up(async_context_t *self_base) {
+    async_context_rtthread_t *self = (async_context_rtthread_t *)self_base;
+    if (self->task_handle) {
+        rt_bool_t in_isr = rt_interrupt_get_nest() > 0;
+        if (in_isr) {
+            rt_sem_release(self->work_needed_sem);
+            rt_event_send(self->notify_event, 1);
+        } else {
+            // We don't want to wake ourselves up (we will only ever be called
+            // from the async_context_task if we own the lock, in which case processing
+            // will already happen when the lock is finally unlocked.
+            if (rt_thread_self() != self->task_handle) {
+                rt_sem_release(self->work_needed_sem);
+                rt_event_send(self->notify_event, 1);
+            } else {
+    #ifndef NDEBUG
+                async_context_rtthread_lock_check(self_base);
+    #endif
+            }
+        }
+    }
+}
+
+static void timer_handler(void *parameter)
+{
+    async_context_rtthread_t *self = (async_context_rtthread_t *)parameter;
+    async_context_rtthread_wake_up(&self->core);
+}
+
+bool async_context_rtthread_init(async_context_rtthread_t *self, async_context_rtthread_config_t *config) {
+    memset(self, 0, sizeof(*self));
+    self->core.type = &template;
+    self->core.flags = ASYNC_CONTEXT_FLAG_CALLBACK_FROM_NON_IRQ;
+    self->core.core_num = get_core_num();
+    self->lock_mutex = rt_mutex_create("async_lock", RT_IPC_FLAG_PRIO);
+    self->work_needed_sem = rt_sem_create("async_sem", 0, RT_IPC_FLAG_PRIO);
+    self->notify_event = rt_event_create("notify_event", RT_IPC_FLAG_PRIO);
+    self->task_handle = rt_thread_create("async_context_task", async_context_task, self, config->task_stack_size, config->task_priority, 20);
+    self->timer_handle = rt_timer_create("async_context_timer", timer_handler, self, RT_TICK_MAX / 2 - 1, RT_TIMER_FLAG_PERIODIC);
+    rt_timer_start(self->timer_handle);
+    rt_thread_startup(self->task_handle);
+
+    if (!self->lock_mutex ||
+        !self->work_needed_sem ||
+        !self->notify_event ||
+        !self->timer_handle ||
+        !self->task_handle
+        ) {
+        async_context_deinit(&self->core);
+        return false;
+    }
+#if configNUM_CORES > 1
+    rt_uint8_t core_id = config->task_core_id;
+    if (core_id == (rt_uint8_t)-1) {
+        core_id = portGET_CORE_ID();
+    }
+    // we must run on a single core
+    // vTaskCoreAffinitySet(self->task_handle, 1u << core_id);
+#endif
+    return true;
+}
+
+static uint32_t end_task_func(void *param) {
+    async_context_rtthread_t *self = (async_context_rtthread_t *)param;
+    // we will immediately exit
+    self->task_should_exit = true;
+    return 0;
+}
+
+void async_context_rtthread_deinit(async_context_t *self_base) {
+    async_context_rtthread_t *self = (async_context_rtthread_t *)self_base;
+    if (self->task_handle) {
+        async_context_execute_sync(self_base, end_task_func, self_base);
+    }
+    if (self->timer_handle) {
+        rt_timer_stop(self->timer_handle);
+        rt_timer_delete(self->timer_handle);
+    }
+    if (self->lock_mutex) {
+        rt_mutex_delete(self->lock_mutex);
+    }
+    if (self->work_needed_sem) {
+        rt_sem_delete(self->work_needed_sem);
+    }
+    if (self->notify_event) {
+        rt_event_delete(self->notify_event);
+    }
+    memset(self, 0, sizeof(*self));
+}
+
+void async_context_rtthread_acquire_lock_blocking(async_context_t *self_base) {
+    async_context_rtthread_t *self = (async_context_rtthread_t *)self_base;
+    // Lock the other core and stop low_prio_irq running
+    RT_ASSERT(!rt_interrupt_get_nest());
+    rt_mutex_take(self->lock_mutex, RT_TICK_MAX / 2 - 1);
+    self->nesting++;
+}
+
+void async_context_rtthread_lock_check(__unused async_context_t *self_base) {
+#ifndef NDEBUG
+    async_context_rtthread_t *self = (async_context_rtthread_t *)self_base;
+    // Lock the other core and stop low_prio_irq running
+    RT_ASSERT(self->lock_mutex->owner == rt_thread_self());
+#endif
+}
+
+typedef struct sync_func_call{
+    async_when_pending_worker_t worker;
+    rt_sem_t sem;
+    uint32_t (*func)(void *param);
+    void *param;
+    uint32_t rc;
+} sync_func_call_t;
+
+static void handle_sync_func_call(async_context_t *context, async_when_pending_worker_t *worker) {
+    sync_func_call_t *call = (sync_func_call_t *)worker;
+    call->rc = call->func(call->param);
+    rt_sem_release(call->sem);
+    async_context_remove_when_pending_worker(context, worker);
+}
+
+uint32_t async_context_rtthread_execute_sync(async_context_t *self_base, uint32_t (*func)(void *param), void *param) {
+    async_context_rtthread_t *self = (async_context_rtthread_t*)self_base;
+    // Use RT-Thread's assertion mechanism
+    RT_ASSERT(self->lock_mutex->owner != rt_thread_self());
+
+    sync_func_call_t call;
+    call.worker.do_work = handle_sync_func_call;
+    call.func = func;
+    call.param = param;
+    call.sem = rt_sem_create("sync_sem", 0, RT_IPC_FLAG_PRIO);
+    async_context_add_when_pending_worker(self_base, &call.worker);
+    async_context_set_work_pending(self_base, &call.worker);
+    rt_sem_take(call.sem, RT_TICK_MAX / 2 - 1);
+    rt_sem_delete(call.sem);
+    return call.rc;
+}
+
+void async_context_rtthread_release_lock(async_context_t *self_base) {
+    async_context_rtthread_t *self = (async_context_rtthread_t *)self_base;
+    bool do_wakeup = false;
+
+    if (self->nesting == 1) {
+        // note that we always do a processing on outermost lock exit, to facilitate cases
+        // like lwIP where we have no notification when lwIP timers are added.
+        //
+        // this operation must be done from the right task
+        if (self->task_handle != rt_thread_self()) {
+            // note we defer the wakeup until after we release the lock, otherwise it can be wasteful
+            // (waking up the task, but then having it block immediately on us)
+            do_wakeup = true;
+        } else {
+            process_under_lock(self);
+        }
+    }
+
+    --self->nesting;
+    rt_mutex_release(self->lock_mutex);
+
+    if (do_wakeup) {
+        async_context_rtthread_wake_up(self_base);
+    }
+}
+
+static bool async_context_rtthread_add_at_time_worker(async_context_t *self_base, async_at_time_worker_t *worker) {
+    async_context_rtthread_acquire_lock_blocking(self_base);
+    bool rc = async_context_base_add_at_time_worker(self_base, worker);
+    async_context_rtthread_release_lock(self_base);
+    return rc;
+}
+
+static bool async_context_rtthread_remove_at_time_worker(async_context_t *self_base, async_at_time_worker_t *worker) {
+    async_context_rtthread_acquire_lock_blocking(self_base);
+    bool rc = async_context_base_remove_at_time_worker(self_base, worker);
+    async_context_rtthread_release_lock(self_base);
+    return rc;
+}
+
+static bool async_context_rtthread_add_when_pending_worker(async_context_t *self_base, async_when_pending_worker_t *worker) {
+    async_context_rtthread_acquire_lock_blocking(self_base);
+    bool rc = async_context_base_add_when_pending_worker(self_base, worker);
+    async_context_rtthread_release_lock(self_base);
+    return rc;
+}
+
+static bool async_context_rtthread_remove_when_pending_worker(async_context_t *self_base, async_when_pending_worker_t *worker) {
+    async_context_rtthread_acquire_lock_blocking(self_base);
+    bool rc = async_context_base_remove_when_pending_worker(self_base, worker);
+    async_context_rtthread_release_lock(self_base);
+    return rc;
+}
+
+static void async_context_rtthread_set_work_pending(async_context_t *self_base, async_when_pending_worker_t *worker) {
+    worker->work_pending = true;
+    async_context_rtthread_wake_up(self_base);
+}
+
+static void async_context_rtthread_wait_until(async_context_t *self_base, absolute_time_t until) {
+    RT_ASSERT(!rt_interrupt_get_nest());
+
+    rt_int32_t ticks = sensible_ticks_until(until);
+    rt_thread_delay(ticks);
+    // rt_thread_delay(ticks * RT_TICK_PER_SECOND / configTICK_RATE_HZ);
+}
+
+static void async_context_rtthread_wait_for_work_until(async_context_t *self_base, absolute_time_t until) {
+    async_context_rtthread_t *self = (async_context_rtthread_t *)self_base;
+    // Use RT-Thread's RT_ASSERT
+    RT_ASSERT(!rt_interrupt_get_nest());
+
+    while (!time_reached(until)) {
+        rt_int32_t ticks = sensible_ticks_until(until);
+        if (!ticks || rt_sem_take(self->work_needed_sem, ticks)) return;
+    }
+}
+
+static const async_context_type_t template = {
+        .type = ASYNC_CONTEXT_FREERTOS,
+        .acquire_lock_blocking = async_context_rtthread_acquire_lock_blocking,
+        .release_lock = async_context_rtthread_release_lock,
+        .lock_check = async_context_rtthread_lock_check,
+        .execute_sync = async_context_rtthread_execute_sync,
+        .add_at_time_worker = async_context_rtthread_add_at_time_worker,
+        .remove_at_time_worker = async_context_rtthread_remove_at_time_worker,
+        .add_when_pending_worker = async_context_rtthread_add_when_pending_worker,
+        .remove_when_pending_worker = async_context_rtthread_remove_when_pending_worker,
+        .set_work_pending = async_context_rtthread_set_work_pending,
+        .poll = 0,
+        .wait_until = async_context_rtthread_wait_until,
+        .wait_for_work_until = async_context_rtthread_wait_for_work_until,
+        .deinit = async_context_rtthread_deinit,
+};

+ 188 - 0
source/src/cyw43_arch.c

@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "pico/unique_id.h"
+#include "cyw43.h"
+#include "cyw43_arch.h"
+#include "cyw43_ll.h"
+#include "cyw43_stats.h"
+
+#if PICO_CYW43_ARCH_DEBUG_ENABLED
+#define CYW43_ARCH_DEBUG(...) printf(__VA_ARGS__)
+#else
+#define CYW43_ARCH_DEBUG(...) ((void)0)
+#endif
+
+static uint32_t country_code = PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE;
+
+static async_context_t *async_context;
+
+void cyw43_arch_set_async_context(async_context_t *context) {
+    async_context = context;
+}
+
+void cyw43_arch_enable_sta_mode(void) {
+    assert(cyw43_is_initialized(&cyw43_state));
+    cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_STA, true, cyw43_arch_get_country_code());
+}
+
+void cyw43_arch_disable_sta_mode(void) {
+    assert(cyw43_is_initialized(&cyw43_state));
+    if (cyw43_state.itf_state & (1 << CYW43_ITF_STA)) {
+        cyw43_cb_tcpip_deinit(&cyw43_state, CYW43_ITF_STA);
+        cyw43_state.itf_state &= ~(1 << CYW43_ITF_STA);
+    }
+    if (cyw43_state.wifi_join_state) {
+        cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA);
+    }
+}
+
+void cyw43_arch_enable_ap_mode(const char *ssid, const char *password, uint32_t auth) {
+    assert(cyw43_is_initialized(&cyw43_state));
+    cyw43_wifi_ap_set_ssid(&cyw43_state, strlen(ssid), (const uint8_t *) ssid);
+    if (password) {
+        cyw43_wifi_ap_set_password(&cyw43_state, strlen(password), (const uint8_t *) password);
+        cyw43_wifi_ap_set_auth(&cyw43_state, auth);
+    } else {
+        cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_OPEN);
+    }
+    cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, true, cyw43_arch_get_country_code());
+}
+
+void cyw43_arch_disable_ap_mode(void) {
+    assert(cyw43_is_initialized(&cyw43_state));
+    cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, false, cyw43_arch_get_country_code());
+    cyw43_state.itf_state &= ~(1 << CYW43_ITF_AP);
+}
+
+#if PICO_CYW43_ARCH_DEBUG_ENABLED
+// Return a string for the wireless state
+static const char* cyw43_tcpip_link_status_name(int status)
+{
+    switch (status) {
+    case CYW43_LINK_DOWN:
+        return "link down";
+    case CYW43_LINK_JOIN:
+        return "joining";
+    case CYW43_LINK_NOIP:
+        return "no ip";
+    case CYW43_LINK_UP:
+        return "link up";
+    case CYW43_LINK_FAIL:
+        return "link fail";
+    case CYW43_LINK_NONET:
+        return "network fail";
+    case CYW43_LINK_BADAUTH:
+        return "bad auth";
+    }
+    return "unknown";
+}
+#endif
+
+
+int cyw43_arch_wifi_connect_bssid_async(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth) {
+    if (!pw) auth = CYW43_AUTH_OPEN;
+    // Connect to wireless
+    return cyw43_wifi_join(&cyw43_state, strlen(ssid), (const uint8_t *)ssid, pw ? strlen(pw) : 0, (const uint8_t *)pw, auth, bssid, CYW43_CHANNEL_NONE);
+}
+
+int cyw43_arch_wifi_connect_async(const char *ssid, const char *pw, uint32_t auth) {
+    return cyw43_arch_wifi_connect_bssid_async(ssid, NULL, pw, auth);
+}
+
+static int cyw43_arch_wifi_connect_bssid_until(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth, absolute_time_t until) {
+    int err = cyw43_arch_wifi_connect_bssid_async(ssid, bssid, pw, auth);
+    if (err) return err;
+
+    int status = CYW43_LINK_UP + 1;
+    while(status >= 0 && status != CYW43_LINK_UP) {
+        int new_status = cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA);
+        // If there was no network, keep trying
+        if (new_status == CYW43_LINK_NONET) {
+            new_status = CYW43_LINK_JOIN;
+            err = cyw43_arch_wifi_connect_bssid_async(ssid, bssid, pw, auth);
+            if (err) return err;
+        }
+        if (new_status != status) {
+            status = new_status;
+            CYW43_ARCH_DEBUG("connect status: %s\n", cyw43_tcpip_link_status_name(status));
+        }
+        if (time_reached(until)) {
+            return PICO_ERROR_TIMEOUT;
+        }
+        // Do polling
+        cyw43_arch_poll();
+        cyw43_arch_wait_for_work_until(until);
+    }
+    // Turn status into a pico_error_codes, CYW43_LINK_NONET shouldn't happen as we fail with PICO_ERROR_TIMEOUT instead
+    assert(status == CYW43_LINK_UP || status == CYW43_LINK_BADAUTH || status == CYW43_LINK_FAIL);
+    if (status == CYW43_LINK_UP) {
+        return PICO_OK; // success
+    } else if (status == CYW43_LINK_BADAUTH) {
+        return PICO_ERROR_BADAUTH;
+    } else {
+        return PICO_ERROR_CONNECT_FAILED;
+    }
+}
+
+// Connect to wireless, return with success when an IP address has been assigned
+static int cyw43_arch_wifi_connect_until(const char *ssid, const char *pw, uint32_t auth, absolute_time_t until) {
+    return cyw43_arch_wifi_connect_bssid_until(ssid, NULL, pw, auth, until);
+}
+
+int cyw43_arch_wifi_connect_blocking(const char *ssid, const char *pw, uint32_t auth) {
+    return cyw43_arch_wifi_connect_until(ssid, pw, auth, at_the_end_of_time);
+}
+
+int cyw43_arch_wifi_connect_bssid_blocking(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth) {
+    return cyw43_arch_wifi_connect_bssid_until(ssid, bssid, pw, auth, at_the_end_of_time);
+}
+
+int cyw43_arch_wifi_connect_timeout_ms(const char *ssid, const char *pw, uint32_t auth, uint32_t timeout_ms) {
+    return cyw43_arch_wifi_connect_until(ssid, pw, auth, make_timeout_time_ms(timeout_ms));
+}
+
+int cyw43_arch_wifi_connect_bssid_timeout_ms(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth, uint32_t timeout_ms) {
+    return cyw43_arch_wifi_connect_bssid_until(ssid, bssid, pw, auth, make_timeout_time_ms(timeout_ms));
+}
+
+uint32_t cyw43_arch_get_country_code(void) {
+    return country_code;
+}
+
+int cyw43_arch_init_with_country(uint32_t country) {
+    country_code = country;
+    return cyw43_arch_init();
+}
+
+void cyw43_arch_gpio_put(uint wl_gpio, bool value) {
+    invalid_params_if(CYW43_ARCH, wl_gpio >= CYW43_WL_GPIO_COUNT);
+    cyw43_gpio_set(&cyw43_state, (int)wl_gpio, value);
+}
+
+bool cyw43_arch_gpio_get(uint wl_gpio) {
+    invalid_params_if(CYW43_ARCH, wl_gpio >= CYW43_WL_GPIO_COUNT);
+    bool value = false;
+    cyw43_gpio_get(&cyw43_state, (int)wl_gpio, &value);
+    return value;
+}
+
+async_context_t *cyw43_arch_async_context(void) {
+    return async_context;
+}
+
+void cyw43_arch_poll(void)
+{
+    async_context_poll(async_context);
+}
+
+void cyw43_arch_wait_for_work_until(absolute_time_t until) {
+    async_context_wait_for_work_until(async_context, until);
+}

+ 84 - 0
source/src/cyw43_arch_rtthread.c

@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#if PICO_CYW43_ARCH_RTTHREAD
+
+#include "cyw43_arch.h"
+#include "pico/cyw43_driver.h"
+#include "async_context_rtthread.h"
+
+#if CYW43_LWIP
+#include "lwip_rtthread.h"
+#include <lwip/tcpip.h>
+#endif
+
+#if CYW43_ENABLE_BLUETOOTH
+#include "pico/btstack_cyw43.h"
+#endif
+
+#if NO_SYS
+#error example_cyw43_arch_rtthread_sys requires NO_SYS=0
+#endif
+
+static async_context_rtthread_t cyw43_async_context_rtthread;
+
+async_context_t *cyw43_arch_init_default_async_context(void) {
+    async_context_rtthread_config_t config = async_context_rtthread_default_config();
+#ifdef CYW43_TASK_PRIORITY
+    config.task_priority = CYW43_TASK_PRIORITY;
+#endif
+#ifdef CYW43_TASK_STACK_SIZE
+    config.task_stack_size = CYW43_TASK_STACK_SIZE;
+#endif
+    if (async_context_rtthread_init(&cyw43_async_context_rtthread, &config))
+        return &cyw43_async_context_rtthread.core;
+    return NULL;
+}
+
+int cyw43_arch_init(void) {
+    async_context_t *context = cyw43_arch_async_context();
+    if (!context) {
+        context = cyw43_arch_init_default_async_context();
+        if (!context) return PICO_ERROR_GENERIC;
+        cyw43_arch_set_async_context(context);
+    }
+    bool ok = cyw43_driver_init(context);
+#if CYW43_LWIP
+//    ok &= lwip_rtthread_init(context);
+#endif
+#if CYW43_ENABLE_BLUETOOTH
+    ok &= btstack_cyw43_init(context);
+#endif
+    if (!ok) {
+        cyw43_arch_deinit();
+        return PICO_ERROR_GENERIC;
+    } else {
+        return 0;
+    }
+}
+
+void cyw43_arch_deinit(void) {
+    async_context_t *context = cyw43_arch_async_context();
+#if CYW43_ENABLE_BLUETOOTH
+    btstack_cyw43_deinit(context);
+#endif
+    // there is a bit of a circular dependency here between lwIP and cyw43_driver. We
+    // shut down cyw43_driver first as it has IRQs calling back into lwIP. Also lwIP itself
+    // does not actually get shut down.
+    // todo add a "pause" method to async_context if we need to provide some atomicity (we
+    //      don't want to take the lock as these methods may invoke execute_sync()
+    cyw43_driver_deinit(context);
+#if CYW43_LWIP
+//    lwip_rtthread_deinit(context);
+#endif
+    // if it is our context, then we de-init it.
+    if (context == &cyw43_async_context_rtthread.core) {
+        async_context_deinit(context);
+        cyw43_arch_set_async_context(NULL);
+    }
+}
+
+#endif

+ 64 - 0
source/src/lwip_rtthread.c

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2022 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+// todo graham #ifdef for LWIP inclusion?
+
+#include "pico/async_context.h"
+#include "pico/time.h"
+#include "lwip/tcpip.h"
+#include "lwip/timeouts.h"
+
+#include "rtthread.h"
+
+#if NO_SYS
+#error lwip_rtthread_async_context_bindings requires NO_SYS=0
+#endif
+
+static async_context_t * volatile lwip_context;
+// lwIP tcpip_task cannot be shutdown, so we block it when we are de-initialized.
+static rt_sem_t tcpip_task_blocker;
+
+static void tcpip_init_done(void *param) {
+    rt_sem_release((rt_sem_t)param);
+}
+
+bool lwip_rtthread_init(async_context_t *context) {
+    RT_ASSERT(!lwip_context);
+    lwip_context = context;
+    static bool done_lwip_init;
+    if (!done_lwip_init) {
+        done_lwip_init = true;
+        rt_sem_t init_sem = rt_sem_create("lwip_init_sem", 0, RT_IPC_FLAG_PRIO);
+        tcpip_task_blocker = rt_sem_create("tcpip_task_blocker", 0, RT_IPC_FLAG_PRIO);
+        tcpip_init(tcpip_init_done, init_sem);
+        rt_sem_take(init_sem, RT_TICK_MAX / 2 - 1);
+        rt_sem_delete(init_sem);
+    } else {
+        rt_sem_release(tcpip_task_blocker);
+    }
+    return true;
+}
+
+static uint32_t clear_lwip_context(__unused void *param) {
+    lwip_context = NULL;
+    return 0;
+}
+
+void lwip_rtthread_deinit(__unused async_context_t *context) {
+    // clear the lwip context under lock as lwIP may still be running in tcpip_task
+    async_context_execute_sync(context, clear_lwip_context, NULL);
+}
+
+void pico_lwip_custom_lock_tcpip_core(void) {
+    while (!lwip_context) {
+        rt_sem_take(tcpip_task_blocker, RT_TICK_MAX / 2 - 1);
+    }
+    async_context_acquire_lock_blocking(lwip_context);
+}
+
+void pico_lwip_custom_unlock_tcpip_core(void) {
+    async_context_release_lock(lwip_context);
+}