Repository: georgerobotics/cyw43-driver
Pull request #137: Add Bluetooth UART HCI backend driver
Author: login: dpgeorge
Created: 2025-02-27T13:41:36Z | Merged: 2025-03-04T13:19:15Z

This allows using a UART to communicate with the cyw43's Bluetooth subsystem.  It provides basic functions to init/deinit the controller, and control its sleep state.  Once its init'd, the BT stack can communicate directly over the UART to the cyw43 BT controller.


Diff:
diff --git a/firmware/cyw43_btfw_1yn.h b/firmware/cyw43_btfw_1yn.h
index 6561a69..688b74b 100644
--- a/firmware/cyw43_btfw_1yn.h
+++ b/firmware/cyw43_btfw_1yn.h
@@ -407,3 +407,6 @@ const unsigned char cyw43_btfw_1yn[] CYW43_RESOURCE_ATTRIBUTE = {
   0x20, 0x00, 0x4e, 0xfc, 0x04, 0xff, 0xff, 0xff, 0xff
 };
 const unsigned int cyw43_btfw_1yn_len = 4857;
+
+const uint8_t *btfw_data = &cyw43_btfw_1yn[0];
+const size_t btfw_len = cyw43_btfw_1yn_len;
diff --git a/firmware/cyw43_btfw_43439.h b/firmware/cyw43_btfw_43439.h
index 5fc16e8..fe7a759 100644
--- a/firmware/cyw43_btfw_43439.h
+++ b/firmware/cyw43_btfw_43439.h
@@ -583,3 +583,6 @@ const unsigned char cyw43_btfw_43439[] CYW43_RESOURCE_ATTRIBUTE = {
   0x64, 0x00, 0xed, 0xf7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01
 };
 const unsigned int cyw43_btfw_43439_len = 6970;
+
+const uint8_t *btfw_data = &cyw43_btfw_43439[0];
+const size_t btfw_len = cyw43_btfw_43439_len;
diff --git a/firmware/cyw43_btfw_4343A1.h b/firmware/cyw43_btfw_4343A1.h
index c50ddd3..90cb8f1 100644
--- a/firmware/cyw43_btfw_4343A1.h
+++ b/firmware/cyw43_btfw_4343A1.h
@@ -2933,3 +2933,6 @@ const unsigned char cyw43_btfw_4343A1[] CYW43_RESOURCE_ATTRIBUTE = {
   0xfe, 0x00, 0x00, 0x4e, 0xfc, 0x04, 0xff, 0xff, 0xff, 0xff
 };
 const unsigned int cyw43_btfw_4343A1_len = 35182;
+
+const uint8_t *btfw_data = &cyw43_btfw_4343A1[0];
+const size_t btfw_len = cyw43_btfw_4343A1_len;
diff --git a/src/cyw43.h b/src/cyw43.h
index 3b0b03d..62fe397 100644
--- a/src/cyw43.h
+++ b/src/cyw43.h
@@ -653,6 +653,7 @@ static inline uint32_t cyw43_pm_value(uint8_t pm_mode, uint16_t pm2_sleep_ret_ms
 #define CYW43_PERFORMANCE_PM (cyw43_pm_value(CYW43_PM2_POWERSAVE_MODE, 200, 1, 1, 10))
 
 #if CYW43_ENABLE_BLUETOOTH
+
 /*!
  * \brief Initialise the Bluetooth HCI layer
  *
@@ -683,7 +684,49 @@ int cyw43_bluetooth_hci_write(uint8_t *buf, size_t len);
  * \brief Callback for the Bluetooth HCI layer to do processing
  */
 void cyw43_bluetooth_hci_process(void);
-#endif
+
+#endif // CYW43_ENABLE_BLUETOOTH
+
+#if CYW43_ENABLE_BLUETOOTH_OVER_UART
+
+/*!
+ * \brief Initialise the cyw43's Bluetooth controller.
+ *
+ * The UART must be initialised and have flow control enabled before calling this function.
+ *
+ * \return zero on success
+ */
+int cyw43_bluetooth_controller_init(void);
+
+/*!
+ * \brief Deinitialise the cyw43's Bluetooth controller.
+ *
+ * \return zero on success
+ */
+int cyw43_bluetooth_controller_deinit(void);
+
+/*!
+ * \brief Tell the Bluetooth controller to go to sleep.
+ *
+ * \return zero on success
+ */
+int cyw43_bluetooth_controller_sleep_maybe(void);
+
+/*!
+ * \brief Query whether the controller woke up.
+ *
+ * \return true if the controller woke up, false otherwise
+ */
+bool cyw43_bluetooth_controller_woken(void);
+
+/*!
+ * \brief Wake up the Bluetooth controller.
+ *
+ * \return zero on success
+ */
+int cyw43_bluetooth_controller_wakeup(void);
+
+#endif // CYW43_ENABLE_BLUETOOTH_OVER_UART
 
 //!\} // cyw43_driver doxygen group
 
diff --git a/src/cyw43_bthci_uart.c b/src/cyw43_bthci_uart.c
new file mode 100644
index 0000000..25be528
--- /dev/null
+++ b/src/cyw43_bthci_uart.c
@@ -0,0 +1,331 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth HCI-over-UART low-level driver.
+
+#include <assert.h>
+
+#include "cyw43.h"
+
+#if CYW43_ENABLE_BLUETOOTH_OVER_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#define HCI_CMD_HEADER_LEN (4)
+#define HCI_RESP_HEADER_LEN (6)
+#define HCI_PAYLOAD_LEN_MAX (256)
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+#define CYW43_BT_UART_READCHAR_BLOCKING_TIMEOUT_US (100000)
+
+#if defined(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE) || defined(CYW43_BT_UART_BAUDRATE_ACTIVE_USE)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
+
+#endif
+
+static void cyw43_bluetooth_wait_cts_low(uint32_t timeout_us) {
+    uint32_t start_us = cyw43_hal_ticks_us();
+    while (cyw43_hal_pin_read(CYW43_PIN_BT_CTS) != 0) {
+        if (cyw43_hal_ticks_us() - start_us > timeout_us) {
+            // Timeout.
+            break;
+        }
+        cyw43_delay_ms(1);
+    }
+}
+
+#if !defined(cyw43_hal_uart_readchar_blocking)
+static int cyw43_hal_uart_readchar_blocking(void) {
+    uint32_t start_us = cyw43_hal_ticks_us();
+    int c;
+    while ((c = cyw43_hal_uart_readchar()) == -1) {
+        if (cyw43_hal_ticks_us() - start_us > CYW43_BT_UART_READCHAR_BLOCKING_TIMEOUT_US) {
+            // Timeout.
+            return -CYW43_ETIMEDOUT;
+        }
+        CYW43_HAL_UART_READCHAR_BLOCKING_WAIT;
+    }
+    return c;
+}
+#endif
+
+static int cyw43_bluetooth_hci_cmd_raw(uint8_t *cmd_buf, size_t param_len, const uint8_t *param_buf) {
+    assert(param_len <= HCI_PAYLOAD_LEN_MAX);
+
+    // Construct the full HCI packet, with command header and parameters.
+    #if defined(cyw43_hal_uart_static_cmd_buf)
+    uint8_t *buf = cyw43_hal_uart_static_cmd_buf;
+    #else
+    uint8_t buf[HCI_CMD_HEADER_LEN + HCI_PAYLOAD_LEN_MAX];
+    #endif
+    memcpy(buf, cmd_buf, HCI_CMD_HEADER_LEN);
+    memcpy(buf + HCI_CMD_HEADER_LEN, param_buf, param_len);
+
+    // Send the HCI command to the BT controller.
+    cyw43_hal_uart_write((void *)buf, HCI_CMD_HEADER_LEN + param_len);
+
+    // Read in the response header from the BT controller.
+    for (size_t i = 0; i < HCI_RESP_HEADER_LEN; ++i) {
+        int c = cyw43_hal_uart_readchar_blocking();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    // Expect a command complete event (event 0x0e).
+    if (buf[0] != 0x04 || buf[1] != 0x0e) {
+        CYW43_WARN("cyw43_bluetooth_hci_cmd_raw: unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
+        return -CYW43_EIO;
+    }
+
+    // Read in the response payload from the BT controller.
+    size_t sz = buf[2] - 3;
+    for (size_t i = 0; i < sz; ++i) {
+        int c = cyw43_hal_uart_readchar_blocking();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        // Store the first few bytes in the input command buffer, for the caller to retrieve.
+        if (i < HCI_CMD_HEADER_LEN) {
+            cmd_buf[i] = c;
+        }
+    }
+
+    return sz;
+}
+
+static int cyw43_bluetooth_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) {
+    uint8_t cmd_buf[4];
+    cmd_buf[0] = 0x01;
+    cmd_buf[1] = ocf;
+    cmd_buf[2] = ogf << 2 | ocf >> 8;
+    cmd_buf[3] = param_len;
+    return cyw43_bluetooth_hci_cmd_raw(cmd_buf, param_len, param_buf);
+}
+
+#if defined(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE) || defined(CYW43_BT_UART_BAUDRATE_ACTIVE_USE)
+static int cyw43_bluetooth_hci_set_baudrate(uint32_t baudrate) {
+    uint8_t param_buf[6];
+    cyw43_bluetooth_put_le16(param_buf, 0);
+    cyw43_bluetooth_put_le32(param_buf + 2, baudrate);
+    return cyw43_bluetooth_hci_cmd(0x3f, 0x18, sizeof(param_buf), param_buf);
+}
+#endif
+
+static int cyw43_bluetooth_download_firmware(const uint8_t *firmware, size_t firmware_len) {
+    cyw43_bluetooth_hci_cmd(0x3f, 0x2e, 0, NULL);
+
+    const uint8_t *firmware_max = firmware + firmware_len;
+    bool last_packet = false;
+    while (!last_packet && firmware < firmware_max) {
+        // Validate firmware packet.
+        if (firmware[1] != 0xfc) {
+            CYW43_WARN("cyw43_bluetooth_download_firmware: unexpected packet %02x\n", firmware[1]);
+            return -CYW43_EIO;
+        }
+
+        // Construct HCI header.
+        uint8_t cmd_buf[4];
+        memcpy(cmd_buf + 1, firmware, 3);
+        firmware += 3;
+        cmd_buf[0] = 1;
+        last_packet = cmd_buf[1] == 0x4e;
+        uint8_t len = cmd_buf[3];
+
+        // Send out firmware packet.
+        int ret = cyw43_bluetooth_hci_cmd_raw(cmd_buf, len, firmware);
+        if (ret < 1 || cmd_buf[0] != 0) {
+            CYW43_WARN("cyw43_bluetooth_download_firmware: unexpected response %02x\n", cmd_buf[0]);
+            return -CYW43_EIO;
+        }
+
+        // Advance to the next firmware packet.
+        firmware += len;
+    }
+
+    #ifdef CYW43_PIN_RFSW_SELECT
+    // RF switch must select high path during BT patch boot.
+    cyw43_hal_pin_config(CYW43_PIN_RFSW_SELECT, CYW43_HAL_PIN_MODE_INPUT, CYW43_HAL_PIN_PULL_UP, 0);
+    #endif
+
+    // Delay for a little to let the patch firmware reset and pull CTS high,
+    // and then wait for it to finish booting, waiting for CTS to go low.
+    cyw43_delay_ms(10);
+    cyw43_bluetooth_wait_cts_low(400000);
+
+    #ifdef CYW43_PIN_RFSW_SELECT
+    // Select chip antenna (could also select external).
+    cyw43_hal_pin_config(CYW43_PIN_RFSW_SELECT, CYW43_HAL_PIN_MODE_INPUT, CYW43_HAL_PIN_PULL_DOWN, 0);
+    #endif
+
+    return 0;
+}
+
+int cyw43_bluetooth_controller_init(void) {
+    // Configure the BT control pins.
+    cyw43_hal_pin_config(CYW43_PIN_BT_REG_ON, CYW43_HAL_PIN_MODE_OUTPUT, CYW43_HAL_PIN_PULL_NONE, 0);
+    cyw43_hal_pin_low(CYW43_PIN_BT_REG_ON);
+    #ifdef CYW43_PIN_BT_HOST_WAKE
+    cyw43_hal_pin_config(CYW43_PIN_BT_HOST_WAKE, CYW43_HAL_PIN_MODE_INPUT, CYW43_HAL_PIN_PULL_NONE, 0);
+    #endif
+    #ifdef CYW43_PIN_BT_DEV_WAKE
+    cyw43_hal_pin_config(CYW43_PIN_BT_DEV_WAKE, CYW43_HAL_PIN_MODE_OUTPUT, CYW43_HAL_PIN_PULL_NONE, 0);
+    cyw43_hal_pin_low(CYW43_PIN_BT_DEV_WAKE);
+    #endif
+
+    #ifdef CYW43_PIN_RFSW_VDD
+    // TODO don't select antenna if wifi is enabled
+    cyw43_hal_pin_config(CYW43_PIN_RFSW_VDD, CYW43_HAL_PIN_MODE_OUTPUT, CYW43_HAL_PIN_PULL_NONE, 0); // RF-switch power
+    cyw43_hal_pin_high(CYW43_PIN_RFSW_VDD); // Turn the RF-switch on
+    #endif
+
+    // Turn on the BT controller.
+    cyw43_hal_pin_low(CYW43_PIN_BT_REG_ON);
+    cyw43_hal_uart_set_baudrate(CYW43_BT_UART_BAUDRATE_STARTUP);
+    cyw43_delay_ms(100);
+    cyw43_hal_pin_high(CYW43_PIN_BT_REG_ON);
+    cyw43_bluetooth_wait_cts_low(100000);
+
+    // Reset.
+    cyw43_bluetooth_hci_cmd(0x03, 0x0003, 0, NULL);
+
+    #ifdef CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE
+    // Change baudrate to download firmware.
+    cyw43_bluetooth_hci_set_baudrate(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE);
+    cyw43_hal_uart_set_baudrate(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE);
+    #endif
+
+    // Download the BT patch firmware.
+    cyw43_bluetooth_download_firmware(btfw_data, btfw_len);
+
+    #ifdef CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE
+    // Change UART baudrate back to default after patch firmware boots.
+    cyw43_hal_uart_set_baudrate(CYW43_BT_UART_BAUDRATE_STARTUP);
+    #endif
+
+    #ifdef CYW43_BT_UART_BAUDRATE_ACTIVE_USE
+    // Change baudrate to increase speed for main use.
+    cyw43_bluetooth_hci_set_baudrate(CYW43_BT_UART_BAUDRATE_ACTIVE_USE);
+    cyw43_hal_uart_set_baudrate(CYW43_BT_UART_BAUDRATE_ACTIVE_USE);
+    #endif
+
+    // Reset.
+    cyw43_bluetooth_hci_cmd(0x03, 0x0003, 0, NULL);
+
+    // Set BD_ADDR (sent as little endian).
+    uint8_t bdaddr[6];
+    uint8_t bdaddr_le[6];
+    cyw43_hal_get_mac(CYW43_HAL_MAC_BDADDR, bdaddr);
+    bdaddr_le[0] = bdaddr[5];
+    bdaddr_le[1] = bdaddr[4];
+    bdaddr_le[2] = bdaddr[3];
+    bdaddr_le[3] = bdaddr[2];
+    bdaddr_le[4] = bdaddr[1];
+    bdaddr_le[5] = bdaddr[0];
+    cyw43_bluetooth_hci_cmd(0x3f, 0x0001, sizeof(bdaddr_le), bdaddr_le);
+
+    // Configure sleep mode.
+    cyw43_bluetooth_hci_cmd(0x3f, 0x27, 12, (const uint8_t *)"\x01\x02\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00");
+
+    // HCI_Write_LE_Host_Support.
+    cyw43_bluetooth_hci_cmd(3, 109, 2, (const uint8_t *)"\x01\x00");
+
+    #ifdef CYW43_PIN_BT_DEV_WAKE
+    // Let the BT device sleep.
+    cyw43_hal_pin_high(CYW43_PIN_BT_DEV_WAKE);
+    #endif
+
+    return 0;
+}
+
+int cyw43_bluetooth_controller_deinit(void) {
+    cyw43_hal_pin_low(CYW43_PIN_BT_REG_ON);
+    return 0;
+}
+
+int cyw43_bluetooth_controller_sleep_maybe(void) {
+    #ifdef CYW43_PIN_BT_DEV_WAKE
+    if (cyw43_hal_pin_read(CYW43_PIN_BT_DEV_WAKE) == 0) {
+        if (cyw43_hal_ticks_ms() - cyw43_bluetooth_sleep_ticks > CYW43_BT_SLEEP_TIMEOUT_MS) {
+            cyw43_hal_pin_high(CYW43_PIN_BT_DEV_WAKE); // let sleep
+        }
+    }
+    #endif
+    return 0;
+}
+
+bool cyw43_bluetooth_controller_woken(void) {
+    #ifdef CYW43_PIN_BT_HOST_WAKE
+    return !!cyw43_hal_pin_read(CYW43_PIN_BT_HOST_WAKE);
+    #else
+    return true;
+    #endif
+}
+
+int cyw43_bluetooth_controller_wakeup(void) {
+    #ifdef CYW43_PIN_BT_DEV_WAKE
+    cyw43_bluetooth_sleep_ticks = cyw43_hal_ticks_ms();
+    if (cyw43_hal_pin_read(CYW43_PIN_BT_DEV_WAKE) != 0) {
+        cyw43_hal_pin_low(CYW43_PIN_BT_DEV_WAKE); // wake up
+        // Use delay_us rather than delay_ms to prevent running the scheduler (which
+        // might result in more BLE operations).
+        cyw43_delay_us(5000); // can't go lower than this
+    }
+    #endif
+
+    return 0;
+}
+
+#endif // CYW43_ENABLE_BLUETOOTH_OVER_UART
diff --git a/src/cyw43_config.h b/src/cyw43_config.h
index fd5c7c3..bebd156 100644
--- a/src/cyw43_config.h
+++ b/src/cyw43_config.h
@@ -57,13 +57,37 @@
 #define CYW43_INCLUDE_LEGACY_F1_OVERFLOW_WORKAROUND_VARIABLES (0)
 #endif
 
+// The following pins can be defined, depending on the configuration:
+// - CYW43_PIN_RFSW_VDD         - RF-switch power (active high)
+// - CYW43_PIN_RFSW_SELECT      - RF-switch select
+// - CYW43_PIN_WL_REG_ON        - WL power on (active high)
+// - CYW43_PIN_WL_HOST_WAKE     - for SDIO or SPI transport
+// - CYW43_PIN_WL_IRQ           - the gSPI IRQ line
+// - CYW43_PIN_WL_SDIO_1        - for SDIO transport
+// - CYW43_PIN_BT_REG_ON        - BT power on (active high)
+// - CYW43_PIN_BT_HOST_WAKE     - for BT HCI UART transport
+// - CYW43_PIN_BT_DEV_WAKE      - for BT HCI UART transport
+// - CYW43_PIN_BT_CTS           - for BT HCI UART transport, used to wait for CTS to go low
+
+// Legacy pin configuration CYW43_PIN_WL_RFSW_VDD is renamed to CYW43_PIN_RFSW_VDD.
+#ifdef CYW43_PIN_WL_RFSW_VDD
+#define CYW43_PIN_RFSW_VDD CYW43_PIN_WL_RFSW_VDD
+#endif
+
 // Firmware configuration.
 
 // Whether Bluetooth support is enabled.
+// This option uses the WiFi SPI/SDIO interface as the Bluetooth transport.
 #ifndef CYW43_ENABLE_BLUETOOTH
 #define CYW43_ENABLE_BLUETOOTH (0)
 #endif
 
+// Whether Bluetooth support is enabled.
+// This option uses a UART as the Bluetooth transport.
+#ifndef CYW43_ENABLE_BLUETOOTH_OVER_UART
+#define CYW43_ENABLE_BLUETOOTH_OVER_UART (0)
+#endif
+
 // This include should define:
 // - CYW43_WIFI_FW_LEN
 // - CYW43_CLM_LEN
@@ -102,6 +126,11 @@
 #define CYW43_SLEEP_MAX (50)
 #endif
 
+// Called while waiting for an incoming character on the BT HCI UART.
+#ifndef CYW43_HAL_UART_READCHAR_BLOCKING_WAIT
+#define CYW43_HAL_UART_READCHAR_BLOCKING_WAIT cyw43_delay_us(10)
+#endif
+
 // Miscellaneous configuration.
 
 // No other options right now for TCP/IP stack.
diff --git a/src/cyw43_ctrl.c b/src/cyw43_ctrl.c
index 9623625..8cfcc77 100644
--- a/src/cyw43_ctrl.c
+++ b/src/cyw43_ctrl.c
@@ -90,9 +90,9 @@ void cyw43_init(cyw43_t *self) {
     #endif
     cyw43_hal_pin_config(CYW43_PIN_WL_REG_ON, CYW43_HAL_PIN_MODE_OUTPUT, CYW43_HAL_PIN_PULL_NONE, 0);
     cyw43_hal_pin_low(CYW43_PIN_WL_REG_ON);
-    #ifdef CYW43_PIN_WL_RFSW_VDD
-    cyw43_hal_pin_config(CYW43_PIN_WL_RFSW_VDD, CYW43_HAL_PIN_MODE_OUTPUT, CYW43_HAL_PIN_PULL_NONE, 0); // RF-switch power
-    cyw43_hal_pin_low(CYW43_PIN_WL_RFSW_VDD);
+    #ifdef CYW43_PIN_RFSW_VDD
+    cyw43_hal_pin_config(CYW43_PIN_RFSW_VDD, CYW43_HAL_PIN_MODE_OUTPUT, CYW43_HAL_PIN_PULL_NONE, 0); // RF-switch power
+    cyw43_hal_pin_low(CYW43_PIN_RFSW_VDD);
     #endif
 
     cyw43_ll_init(&self->cyw43_ll, self);
@@ -480,9 +480,9 @@ static int cyw43_wifi_on(cyw43_t *self, uint32_t country) {
         return ret;
     }
 
-    #ifdef CYW43_PIN_WL_RFSW_VDD
+    #ifdef CYW43_PIN_RFSW_VDD
     // Turn the RF-switch on
-    cyw43_hal_pin_high(CYW43_PIN_WL_RFSW_VDD);
+    cyw43_hal_pin_high(CYW43_PIN_RFSW_VDD);
     #endif
 
     ret = cyw43_ll_wifi_on(&self->cyw43_ll, country);
diff --git a/tests/bthci_uart/Makefile b/tests/bthci_uart/Makefile
new file mode 100644
index 0000000..8c85c90
--- /dev/null
+++ b/tests/bthci_uart/Makefile
@@ -0,0 +1,6 @@
+SRC += $(SRC_BT)
+SRC += \
+	main.c \
+	tests/mock_uart.c \
+
+include ../test_common.mk
diff --git a/tests/bthci_uart/cyw43_configport.h b/tests/bthci_uart/cyw43_configport.h
new file mode 100644
index 0000000..52745e7
--- /dev/null
+++ b/tests/bthci_uart/cyw43_configport.h
@@ -0,0 +1,56 @@
+#ifndef CYW43_INCLUDED_CONFIGPORT_H
+#define CYW43_INCLUDED_CONFIGPORT_H
+
+#include <stdint.h>
+
+#define MIN(a, b)                           ((a) <= (b) ? (a) : (b))
+#define static_assert(expr, msg)            typedef int static_assert_##__LINE__[(expr) ? 1 : -1]
+
+#define CYW43_ENABLE_BLUETOOTH_OVER_UART    (1)
+#define CYW43_LWIP                          (0)
+#define CYW43_BT_FIRMWARE_INCLUDE_FILE      "firmware/cyw43_btfw_4343A1.h"
+
+#define CYW43_PIN_BT_REG_ON                 (0)
+#define CYW43_PIN_BT_CTS                    (1)
+
+#define CYW43_EPERM                         (1)
+#define CYW43_EIO                           (5)
+#define CYW43_EINVAL                        (22)
+#define CYW43_ETIMEDOUT                     (110)
+
+#define CYW43_HAL_PIN_MODE_INPUT            (0)
+#define CYW43_HAL_PIN_MODE_OUTPUT           (1)
+#define CYW43_HAL_PIN_PULL_NONE             (0)
+#define CYW43_HAL_MAC_WLAN0                 (0)
+#define CYW43_HAL_MAC_BDADDR                (1)
+
+static inline unsigned int cyw43_hal_ticks_us(void) {
+    static unsigned int t = 0;
+    return t++;
+}
+
+static inline unsigned int cyw43_hal_ticks_ms(void) {
+    static unsigned int t = 0;
+    return t++;
+}
+
+static inline void cyw43_delay_us(unsigned int us) {
+    (void)us;
+}
+
+static inline void cyw43_delay_ms(unsigned int ms) {
+    (void)ms;
+}
+
+void cyw43_hal_get_mac(int interface, uint8_t mac[6]);
+
+void cyw43_hal_pin_config(int pin, int mode, int pull, int alt);
+int cyw43_hal_pin_read(int pin);
+void cyw43_hal_pin_low(int pin);
+void cyw43_hal_pin_high(int pin);
+
+void cyw43_hal_uart_set_baudrate(int baudrate);
+int cyw43_hal_uart_write(const uint8_t *buf, int len);
+int cyw43_hal_uart_readchar(void);
+
+#endif // CYW43_INCLUDED_CONFIGPORT_H
diff --git a/tests/bthci_uart/main.c b/tests/bthci_uart/main.c
new file mode 100644
index 0000000..1fd11ca
--- /dev/null
+++ b/tests/bthci_uart/main.c
@@ -0,0 +1,34 @@
+#include "src/cyw43.h"
+
+void cyw43_hal_get_mac(int interface, uint8_t mac[6]) {
+    (void)mac;
+    printf("cyw43_hal_get_mac(%d)\n", interface);
+}
+
+void cyw43_hal_pin_config(int pin, int mode, int pull, int alt) {
+    printf("cyw43_hal_pin_config(%d, %d, %d, %d)\n", pin, mode, pull, alt);
+}
+
+int cyw43_hal_pin_read(int pin) {
+    printf("cyw43_hal_pin_read(%d)\n", pin);
+    return 0;
+}
+
+void cyw43_hal_pin_low(int pin) {
+    printf("cyw43_hal_pin_low(%d)\n", pin);
+}
+
+void cyw43_hal_pin_high(int pin) {
+    printf("cyw43_hal_pin_high(%d)\n", pin);
+}
+
+int main(int argc, char **argv) {
+    (void)argc;
+    (void)argv;
+    cyw43_bluetooth_controller_init();
+    cyw43_bluetooth_controller_sleep_maybe();
+    cyw43_bluetooth_controller_woken();
+    cyw43_bluetooth_controller_wakeup();
+    cyw43_bluetooth_controller_deinit();
+    return 0;
+}
diff --git a/tests/bthci_uart/test.exp b/tests/bthci_uart/test.exp
new file mode 100644
index 0000000..17814bd
--- /dev/null
+++ b/tests/bthci_uart/test.exp
@@ -0,0 +1,1169 @@
+cyw43_hal_pin_config(0, 1, 0, 0)
+cyw43_hal_pin_low(0)
+cyw43_hal_pin_low(0)
+cyw43_hal_uart_set_baudrate(115200)
+cyw43_hal_pin_high(0)
+cyw43_hal_pin_read(1)
+cyw43_hal_uart_write(01:03:0c:00, 4)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:2e:fc:00, 4)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:46, 74)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:ff, 259)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4c:fc:0b, 15)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:4e:fc:04, 8)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_pin_read(1)
+cyw43_hal_uart_write(01:03:0c:00, 4)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_get_mac(1)
+cyw43_hal_uart_write(01:01:fc:06, 10)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:27:fc:0c, 16)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_write(01:6d:0c:02, 6)
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_uart_readchar()
+cyw43_hal_pin_low(0)
diff --git a/tests/debug_print/Makefile b/tests/debug_print/Makefile
index 2f90705..51df4c8 100644
--- a/tests/debug_print/Makefile
+++ b/tests/debug_print/Makefile
@@ -1,3 +1,4 @@
+SRC += $(SRC_WL)
 SRC += \
 	main.c \
 	tests/mock_sdio.c \
diff --git a/tests/debug_print/cyw43_configport.h b/tests/debug_print/cyw43_configport.h
index 756bb56..23bcf3b 100644
--- a/tests/debug_print/cyw43_configport.h
+++ b/tests/debug_print/cyw43_configport.h
@@ -19,8 +19,8 @@
 #define CYW43_INFO(...)                     printf(__VA_ARGS__)
 #define CYW43_WARN(...)                     printf(__VA_ARGS__)
 
-#define CYW43_PIN_WL_REG_ON                 (1)
-#define CYW43_PIN_WL_RFSW_VDD               (2)
+#define CYW43_PIN_RFSW_VDD                  (1)
+#define CYW43_PIN_WL_REG_ON                 (2)
 #define CYW43_PIN_WL_SDIO_1                 (3)
 #define CYW43_PIN_WL_HOST_WAKE              (4)
 
diff --git a/tests/mock_uart.c b/tests/mock_uart.c
new file mode 100644
index 0000000..abd3334
--- /dev/null
+++ b/tests/mock_uart.c
@@ -0,0 +1,22 @@
+#include "src/cyw43.h"
+
+static const uint8_t mock_response[] = {
+    0x04, 0x0e, 0x04, 0x00, 0x00, 0x00, 0x00,
+};
+
+void cyw43_hal_uart_set_baudrate(int baudrate) {
+    printf("cyw43_hal_uart_set_baudrate(%d)\n", baudrate);
+}
+
+int cyw43_hal_uart_write(const uint8_t *buf, int len) {
+    printf("cyw43_hal_uart_write(%02x:%02x:%02x:%02x, %d)\n", buf[0], buf[1], buf[2], buf[3], len);
+    return 0;
+}
+
+int cyw43_hal_uart_readchar(void) {
+    static unsigned i = 0;
+    printf("cyw43_hal_uart_readchar()\n");
+    int c = mock_response[i];
+    i = (i + 1) % sizeof(mock_response);
+    return c;
+}
diff --git a/tests/sdio/Makefile b/tests/sdio/Makefile
index 75fc8a0..e052fee 100644
--- a/tests/sdio/Makefile
+++ b/tests/sdio/Makefile
@@ -1,6 +1,7 @@
 # Test with high optimisation level.
 CFLAGS += -O3
 
+SRC += $(SRC_WL)
 SRC += \
 	main.c \
 	tests/mock_sdio.c \
diff --git a/tests/sdio/cyw43_configport.h b/tests/sdio/cyw43_configport.h
index 758f131..c4033f3 100644
--- a/tests/sdio/cyw43_configport.h
+++ b/tests/sdio/cyw43_configport.h
@@ -12,8 +12,8 @@
 #define CYW43_USE_SPI                       (0)
 #define CYW43_LWIP                          (0)
 
-#define CYW43_PIN_WL_REG_ON                 (1)
-#define CYW43_PIN_WL_RFSW_VDD               (2)
+#define CYW43_PIN_RFSW_VDD                  (1)
+#define CYW43_PIN_WL_REG_ON                 (2)
 #define CYW43_PIN_WL_SDIO_1                 (3)
 #define CYW43_PIN_WL_HOST_WAKE              (4)
 
diff --git a/tests/test_common.mk b/tests/test_common.mk
index d9f51dd..2dc4651 100644
--- a/tests/test_common.mk
+++ b/tests/test_common.mk
@@ -14,7 +14,10 @@ CFLAGS += -Wall -Wextra -Wpedantic -Werror
 CFLAGS += -Wno-unused-local-typedefs
 CFLAGS += -m32
 
-SRC += \
+SRC_BT += \
+	src/cyw43_bthci_uart.c \
+
+SRC_WL += \
 	src/cyw43_ctrl.c \
 	src/cyw43_ll.c \
 	src/cyw43_lwip.c \
diff --git a/tools/ci.sh b/tools/ci.sh
index 3fe1e13..2de4fce 100755
--- a/tools/ci.sh
+++ b/tools/ci.sh
@@ -41,13 +41,15 @@ function ci_tests_setup {
 }
 
 function ci_tests_build {
-    make $MAKEOPTS -C tests/sdio
-    make $MAKEOPTS -C tests/debug_print
+    for test in `ls -d tests/*/`; do
+        make $MAKEOPTS -C $test
+    done
 }
 
 function ci_tests_run {
-    make $MAKEOPTS -C tests/sdio test
-    make $MAKEOPTS -C tests/debug_print test
+    for test in `ls -d tests/*/`; do
+        make $MAKEOPTS -C $test test
+    done
 }
 
 ########################################################################################
@@ -58,11 +60,13 @@ function ci_tests_clang_setup {
 }
 
 function ci_tests_clang_build {
-    make $MAKEOPTS CC=clang -C tests/sdio
-    make $MAKEOPTS CC=clang -C tests/debug_print
+    for test in `ls -d tests/*/`; do
+        make $MAKEOPTS CC=clang -C $test
+    done
 }
 
 function ci_tests_clang_run {
-    make $MAKEOPTS CC=clang -C tests/sdio test
-    make $MAKEOPTS CC=clang -C tests/debug_print test
+    for test in `ls -d tests/*/`; do
+        make $MAKEOPTS CC=clang -C $test test
+    done
 }


Reviews:
user: login: iabdalkader
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T14:12:50Z
user: login: dpgeorge
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-28T08:31:22Z
user: login: iabdalkader
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T08:37:13Z
user: login: iabdalkader
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T09:05:03Z
user: login: dpgeorge
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-28T11:17:10Z
user: login: iabdalkader
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-01T12:56:37Z
user: login: iabdalkader
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-01T12:58:59Z
user: login: dpgeorge
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-02T13:37:44Z
user: login: dpgeorge
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-02T13:38:34Z
user: login: iabdalkader
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-02T13:57:17Z
user: login: dpgeorge
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T00:42:16Z
user: login: iabdalkader
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T07:41:46Z
user: login: dpgeorge
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T12:08:37Z
user: login: dpgeorge
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T23:44:03Z
user: login: iabdalkader
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:14:34Z

Review comments:
diff_hunk: @@ -0,0 +1,297 @@
+/*
path: src/cyw43_btuart.c
user: login: iabdalkader
body: I think this should be called `cyw43_bthci.c` similar to https://github.com/micropython/micropython/blob/master/drivers/esp-hosted/esp_hosted_bthci.c.

There's also https://github.com/micropython/micropython/blob/master/drivers/ninaw10/nina_bt_hci.c. This one should probably be renamed to `nina_bthci.c`.
created_at: 2025-02-27T14:12:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/*
path: src/cyw43_btuart.c
user: login: dpgeorge
body: We need to distinguish this file from a BT HCI driver that uses a different transport, like the SPI/SDIO interface.  Hence the need to put "uart" in the filename.

How about `cyw43_bthci_uart.c`?
created_at: 2025-02-28T08:31:22Z
author_association: NONE
diff_hunk: @@ -0,0 +1,297 @@
+/*
path: src/cyw43_btuart.c
user: login: iabdalkader
body: Yes, `xx_bthci_uart.c` is good. The other drivers above should be renamed to match this.
created_at: 2025-02-28T08:37:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/*
path: src/cyw43_btuart.c
user: login: iabdalkader
body: I've renamed them here to match the cyw43: https://github.com/micropython/micropython/pull/16833.
created_at: 2025-02-28T09:05:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/*
path: src/cyw43_btuart.c
user: login: dpgeorge
body: I've renamed this to `cyw43_bthci_uart.c` in this PR.
created_at: 2025-02-28T11:17:10Z
author_association: NONE
diff_hunk: @@ -0,0 +1,297 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth UART HCI low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
+
+static void cyw43_bluetooth_wait_cts_low(uint32_t timeout_us) {
+    uint32_t start_us = cyw43_hal_ticks_us();
+    while (cyw43_hal_pin_read(CYW43_PIN_BT_CTS) != 0) {
+        if (cyw43_hal_ticks_us() - start_us > timeout_us) {
+            // Timeout.
+            break;
+        }
+        cyw43_delay_ms(1);
+    }
+}
+
+static int cyw43_bluetooth_hci_cmd_raw(size_t len, uint8_t *buf) {
+    cyw43_bluetooth_wait_cts_low(10000);
+
+    // Send the HCI command to the BT controller.
+    cyw43_hal_uart_write((void *)buf, len);
+
+    // Read in the response header from the BT controller.
+    for (size_t i = 0; i < 6; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    // Expect a command complete event (event 0x0e).
+    if (buf[0] != 0x04 || buf[1] != 0x0e) {
+        CYW43_WARN("cyw43_bluetooth_hci_cmd_raw: unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
+        return -CYW43_EIO;
+    }
+
+    // Read in the response payload from the BT controller.
+    size_t sz = buf[2] - 3;
+    for (size_t i = 0; i < sz; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    return 0;
+}
+
+static int cyw43_bluetooth_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) {
+    uint8_t *buf = cyw43_hal_uart_cmd_buf;
+    buf[0] = 0x01;
+    buf[1] = ocf;
+    buf[2] = ogf << 2 | ocf >> 8;
+    buf[3] = param_len;
+    if (param_len) {
+        memcpy(buf + 4, param_buf, param_len);
+    }
+    return cyw43_bluetooth_hci_cmd_raw(4 + param_len, buf);
+}
+
+#if defined(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE) || defined(CYW43_BT_UART_BAUDRATE_ACTIVE_USE)
+static int cyw43_bluetooth_hci_set_baudrate(uint32_t baudrate) {
+    uint8_t buf[6];
+    cyw43_bluetooth_put_le16(buf, 0);
+    cyw43_bluetooth_put_le32(buf + 2, baudrate);
+    return cyw43_bluetooth_hci_cmd(0x3f, 0x18, 6, buf);
+}
+#endif
+
+static int cyw43_bluetooth_download_firmware(const uint8_t *firmware, size_t firmware_len) {
+    cyw43_bluetooth_hci_cmd(0x3f, 0x2e, 0, NULL);
+
+    const uint8_t *firmware_max = firmware + firmware_len;
+    bool last_packet = false;
+    while (!last_packet && firmware < firmware_max) {
+        // Validate firmware packet.
+        if (firmware[1] != 0xfc) {
+            CYW43_WARN("cyw43_bluetooth_download_firmware: unexpected packet %02x\n", firmware[1]);
+            return -CYW43_EIO;
+        }
+
+        // Construct HCI header.
+        uint8_t *buf = cyw43_hal_uart_cmd_buf;
+        memcpy(buf + 1, firmware, 3);
+        firmware += 3;
+        buf[0] = 1;
+        last_packet = buf[1] == 0x4e;
+        uint8_t len = buf[3];
+
+        // Copy in payload.
+        memcpy(buf + 4, firmware, len);
path: src/cyw43_bthci_uart.c
user: login: iabdalkader
body: A sanity check here might be a good idea. For example: `assert(len <= sizeof(cmd_buf) - 4)`
created_at: 2025-03-01T12:56:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth UART HCI low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
+
+static void cyw43_bluetooth_wait_cts_low(uint32_t timeout_us) {
+    uint32_t start_us = cyw43_hal_ticks_us();
+    while (cyw43_hal_pin_read(CYW43_PIN_BT_CTS) != 0) {
+        if (cyw43_hal_ticks_us() - start_us > timeout_us) {
+            // Timeout.
+            break;
+        }
+        cyw43_delay_ms(1);
+    }
+}
+
+static int cyw43_bluetooth_hci_cmd_raw(size_t len, uint8_t *buf) {
+    cyw43_bluetooth_wait_cts_low(10000);
+
+    // Send the HCI command to the BT controller.
+    cyw43_hal_uart_write((void *)buf, len);
+
+    // Read in the response header from the BT controller.
+    for (size_t i = 0; i < 6; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    // Expect a command complete event (event 0x0e).
+    if (buf[0] != 0x04 || buf[1] != 0x0e) {
+        CYW43_WARN("cyw43_bluetooth_hci_cmd_raw: unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
+        return -CYW43_EIO;
+    }
+
+    // Read in the response payload from the BT controller.
+    size_t sz = buf[2] - 3;
+    for (size_t i = 0; i < sz; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    return 0;
+}
+
+static int cyw43_bluetooth_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) {
+    uint8_t *buf = cyw43_hal_uart_cmd_buf;
+    buf[0] = 0x01;
+    buf[1] = ocf;
+    buf[2] = ogf << 2 | ocf >> 8;
+    buf[3] = param_len;
+    if (param_len) {
+        memcpy(buf + 4, param_buf, param_len);
+    }
+    return cyw43_bluetooth_hci_cmd_raw(4 + param_len, buf);
+}
+
+#if defined(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE) || defined(CYW43_BT_UART_BAUDRATE_ACTIVE_USE)
+static int cyw43_bluetooth_hci_set_baudrate(uint32_t baudrate) {
+    uint8_t buf[6];
+    cyw43_bluetooth_put_le16(buf, 0);
+    cyw43_bluetooth_put_le32(buf + 2, baudrate);
+    return cyw43_bluetooth_hci_cmd(0x3f, 0x18, 6, buf);
+}
+#endif
+
+static int cyw43_bluetooth_download_firmware(const uint8_t *firmware, size_t firmware_len) {
+    cyw43_bluetooth_hci_cmd(0x3f, 0x2e, 0, NULL);
+
+    const uint8_t *firmware_max = firmware + firmware_len;
+    bool last_packet = false;
+    while (!last_packet && firmware < firmware_max) {
+        // Validate firmware packet.
+        if (firmware[1] != 0xfc) {
+            CYW43_WARN("cyw43_bluetooth_download_firmware: unexpected packet %02x\n", firmware[1]);
+            return -CYW43_EIO;
+        }
+
+        // Construct HCI header.
+        uint8_t *buf = cyw43_hal_uart_cmd_buf;
path: src/cyw43_bthci_uart.c
user: login: iabdalkader
body: This buffer can be replaced with a stack buffer `uint8_t cmd_buf[256+4]`. One less config option to worry about.  The same goes for `extmod/nimble/hal/hal_uart.c`. It seems this buffer is transient everywhere it's used, meaning `mpbthciport.c` doesn't really have to define it, which could save some data memory.
created_at: 2025-03-01T12:58:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth UART HCI low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
+
+static void cyw43_bluetooth_wait_cts_low(uint32_t timeout_us) {
+    uint32_t start_us = cyw43_hal_ticks_us();
+    while (cyw43_hal_pin_read(CYW43_PIN_BT_CTS) != 0) {
+        if (cyw43_hal_ticks_us() - start_us > timeout_us) {
+            // Timeout.
+            break;
+        }
+        cyw43_delay_ms(1);
+    }
+}
+
+static int cyw43_bluetooth_hci_cmd_raw(size_t len, uint8_t *buf) {
+    cyw43_bluetooth_wait_cts_low(10000);
+
+    // Send the HCI command to the BT controller.
+    cyw43_hal_uart_write((void *)buf, len);
+
+    // Read in the response header from the BT controller.
+    for (size_t i = 0; i < 6; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    // Expect a command complete event (event 0x0e).
+    if (buf[0] != 0x04 || buf[1] != 0x0e) {
+        CYW43_WARN("cyw43_bluetooth_hci_cmd_raw: unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
+        return -CYW43_EIO;
+    }
+
+    // Read in the response payload from the BT controller.
+    size_t sz = buf[2] - 3;
+    for (size_t i = 0; i < sz; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    return 0;
+}
+
+static int cyw43_bluetooth_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) {
+    uint8_t *buf = cyw43_hal_uart_cmd_buf;
+    buf[0] = 0x01;
+    buf[1] = ocf;
+    buf[2] = ogf << 2 | ocf >> 8;
+    buf[3] = param_len;
+    if (param_len) {
+        memcpy(buf + 4, param_buf, param_len);
+    }
+    return cyw43_bluetooth_hci_cmd_raw(4 + param_len, buf);
+}
+
+#if defined(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE) || defined(CYW43_BT_UART_BAUDRATE_ACTIVE_USE)
+static int cyw43_bluetooth_hci_set_baudrate(uint32_t baudrate) {
+    uint8_t buf[6];
+    cyw43_bluetooth_put_le16(buf, 0);
+    cyw43_bluetooth_put_le32(buf + 2, baudrate);
+    return cyw43_bluetooth_hci_cmd(0x3f, 0x18, 6, buf);
+}
+#endif
+
+static int cyw43_bluetooth_download_firmware(const uint8_t *firmware, size_t firmware_len) {
+    cyw43_bluetooth_hci_cmd(0x3f, 0x2e, 0, NULL);
+
+    const uint8_t *firmware_max = firmware + firmware_len;
+    bool last_packet = false;
+    while (!last_packet && firmware < firmware_max) {
+        // Validate firmware packet.
+        if (firmware[1] != 0xfc) {
+            CYW43_WARN("cyw43_bluetooth_download_firmware: unexpected packet %02x\n", firmware[1]);
+            return -CYW43_EIO;
+        }
+
+        // Construct HCI header.
+        uint8_t *buf = cyw43_hal_uart_cmd_buf;
+        memcpy(buf + 1, firmware, 3);
+        firmware += 3;
+        buf[0] = 1;
+        last_packet = buf[1] == 0x4e;
+        uint8_t len = buf[3];
+
+        // Copy in payload.
+        memcpy(buf + 4, firmware, len);
path: src/cyw43_bthci_uart.c
user: login: dpgeorge
body: I've refactored this and added an assert.
created_at: 2025-03-02T13:37:44Z
author_association: NONE
diff_hunk: @@ -0,0 +1,297 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth UART HCI low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
+
+static void cyw43_bluetooth_wait_cts_low(uint32_t timeout_us) {
+    uint32_t start_us = cyw43_hal_ticks_us();
+    while (cyw43_hal_pin_read(CYW43_PIN_BT_CTS) != 0) {
+        if (cyw43_hal_ticks_us() - start_us > timeout_us) {
+            // Timeout.
+            break;
+        }
+        cyw43_delay_ms(1);
+    }
+}
+
+static int cyw43_bluetooth_hci_cmd_raw(size_t len, uint8_t *buf) {
+    cyw43_bluetooth_wait_cts_low(10000);
+
+    // Send the HCI command to the BT controller.
+    cyw43_hal_uart_write((void *)buf, len);
+
+    // Read in the response header from the BT controller.
+    for (size_t i = 0; i < 6; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    // Expect a command complete event (event 0x0e).
+    if (buf[0] != 0x04 || buf[1] != 0x0e) {
+        CYW43_WARN("cyw43_bluetooth_hci_cmd_raw: unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
+        return -CYW43_EIO;
+    }
+
+    // Read in the response payload from the BT controller.
+    size_t sz = buf[2] - 3;
+    for (size_t i = 0; i < sz; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    return 0;
+}
+
+static int cyw43_bluetooth_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) {
+    uint8_t *buf = cyw43_hal_uart_cmd_buf;
+    buf[0] = 0x01;
+    buf[1] = ocf;
+    buf[2] = ogf << 2 | ocf >> 8;
+    buf[3] = param_len;
+    if (param_len) {
+        memcpy(buf + 4, param_buf, param_len);
+    }
+    return cyw43_bluetooth_hci_cmd_raw(4 + param_len, buf);
+}
+
+#if defined(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE) || defined(CYW43_BT_UART_BAUDRATE_ACTIVE_USE)
+static int cyw43_bluetooth_hci_set_baudrate(uint32_t baudrate) {
+    uint8_t buf[6];
+    cyw43_bluetooth_put_le16(buf, 0);
+    cyw43_bluetooth_put_le32(buf + 2, baudrate);
+    return cyw43_bluetooth_hci_cmd(0x3f, 0x18, 6, buf);
+}
+#endif
+
+static int cyw43_bluetooth_download_firmware(const uint8_t *firmware, size_t firmware_len) {
+    cyw43_bluetooth_hci_cmd(0x3f, 0x2e, 0, NULL);
+
+    const uint8_t *firmware_max = firmware + firmware_len;
+    bool last_packet = false;
+    while (!last_packet && firmware < firmware_max) {
+        // Validate firmware packet.
+        if (firmware[1] != 0xfc) {
+            CYW43_WARN("cyw43_bluetooth_download_firmware: unexpected packet %02x\n", firmware[1]);
+            return -CYW43_EIO;
+        }
+
+        // Construct HCI header.
+        uint8_t *buf = cyw43_hal_uart_cmd_buf;
path: src/cyw43_bthci_uart.c
user: login: dpgeorge
body: OK, you're right, this buffer could be fully stack allocated.

I've reworked this now and made it so that you can provide an optional static buffer.  If that's not defined then this driver automatically stack allocates it.
created_at: 2025-03-02T13:38:34Z
author_association: NONE
diff_hunk: @@ -0,0 +1,297 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth UART HCI low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
+
+static void cyw43_bluetooth_wait_cts_low(uint32_t timeout_us) {
+    uint32_t start_us = cyw43_hal_ticks_us();
+    while (cyw43_hal_pin_read(CYW43_PIN_BT_CTS) != 0) {
+        if (cyw43_hal_ticks_us() - start_us > timeout_us) {
+            // Timeout.
+            break;
+        }
+        cyw43_delay_ms(1);
+    }
+}
+
+static int cyw43_bluetooth_hci_cmd_raw(size_t len, uint8_t *buf) {
+    cyw43_bluetooth_wait_cts_low(10000);
+
+    // Send the HCI command to the BT controller.
+    cyw43_hal_uart_write((void *)buf, len);
+
+    // Read in the response header from the BT controller.
+    for (size_t i = 0; i < 6; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    // Expect a command complete event (event 0x0e).
+    if (buf[0] != 0x04 || buf[1] != 0x0e) {
+        CYW43_WARN("cyw43_bluetooth_hci_cmd_raw: unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
+        return -CYW43_EIO;
+    }
+
+    // Read in the response payload from the BT controller.
+    size_t sz = buf[2] - 3;
+    for (size_t i = 0; i < sz; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    return 0;
+}
+
+static int cyw43_bluetooth_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) {
+    uint8_t *buf = cyw43_hal_uart_cmd_buf;
+    buf[0] = 0x01;
+    buf[1] = ocf;
+    buf[2] = ogf << 2 | ocf >> 8;
+    buf[3] = param_len;
+    if (param_len) {
+        memcpy(buf + 4, param_buf, param_len);
+    }
+    return cyw43_bluetooth_hci_cmd_raw(4 + param_len, buf);
+}
+
+#if defined(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE) || defined(CYW43_BT_UART_BAUDRATE_ACTIVE_USE)
+static int cyw43_bluetooth_hci_set_baudrate(uint32_t baudrate) {
+    uint8_t buf[6];
+    cyw43_bluetooth_put_le16(buf, 0);
+    cyw43_bluetooth_put_le32(buf + 2, baudrate);
+    return cyw43_bluetooth_hci_cmd(0x3f, 0x18, 6, buf);
+}
+#endif
+
+static int cyw43_bluetooth_download_firmware(const uint8_t *firmware, size_t firmware_len) {
+    cyw43_bluetooth_hci_cmd(0x3f, 0x2e, 0, NULL);
+
+    const uint8_t *firmware_max = firmware + firmware_len;
+    bool last_packet = false;
+    while (!last_packet && firmware < firmware_max) {
+        // Validate firmware packet.
+        if (firmware[1] != 0xfc) {
+            CYW43_WARN("cyw43_bluetooth_download_firmware: unexpected packet %02x\n", firmware[1]);
+            return -CYW43_EIO;
+        }
+
+        // Construct HCI header.
+        uint8_t *buf = cyw43_hal_uart_cmd_buf;
path: src/cyw43_bthci_uart.c
user: login: iabdalkader
body: Note this buffer is also used in `extmod/nimble/hal/hal_uart.c` in a very similar way. Replacing that one too with a stack buffer could save data memory that was unrecoverable before.
https://github.com/micropython/micropython/blob/master/extmod/nimble/hal/hal_uart.c#L73
created_at: 2025-03-02T13:57:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth UART HCI low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
+
+static void cyw43_bluetooth_wait_cts_low(uint32_t timeout_us) {
+    uint32_t start_us = cyw43_hal_ticks_us();
+    while (cyw43_hal_pin_read(CYW43_PIN_BT_CTS) != 0) {
+        if (cyw43_hal_ticks_us() - start_us > timeout_us) {
+            // Timeout.
+            break;
+        }
+        cyw43_delay_ms(1);
+    }
+}
+
+static int cyw43_bluetooth_hci_cmd_raw(size_t len, uint8_t *buf) {
+    cyw43_bluetooth_wait_cts_low(10000);
+
+    // Send the HCI command to the BT controller.
+    cyw43_hal_uart_write((void *)buf, len);
+
+    // Read in the response header from the BT controller.
+    for (size_t i = 0; i < 6; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    // Expect a command complete event (event 0x0e).
+    if (buf[0] != 0x04 || buf[1] != 0x0e) {
+        CYW43_WARN("cyw43_bluetooth_hci_cmd_raw: unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
+        return -CYW43_EIO;
+    }
+
+    // Read in the response payload from the BT controller.
+    size_t sz = buf[2] - 3;
+    for (size_t i = 0; i < sz; ++i) {
+        int c = cyw43_hal_uart_readchar();
+        if (c < 0) {
+            return -CYW43_ETIMEDOUT;
+        }
+        buf[i] = c;
+    }
+
+    return 0;
+}
+
+static int cyw43_bluetooth_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) {
+    uint8_t *buf = cyw43_hal_uart_cmd_buf;
+    buf[0] = 0x01;
+    buf[1] = ocf;
+    buf[2] = ogf << 2 | ocf >> 8;
+    buf[3] = param_len;
+    if (param_len) {
+        memcpy(buf + 4, param_buf, param_len);
+    }
+    return cyw43_bluetooth_hci_cmd_raw(4 + param_len, buf);
+}
+
+#if defined(CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE) || defined(CYW43_BT_UART_BAUDRATE_ACTIVE_USE)
+static int cyw43_bluetooth_hci_set_baudrate(uint32_t baudrate) {
+    uint8_t buf[6];
+    cyw43_bluetooth_put_le16(buf, 0);
+    cyw43_bluetooth_put_le32(buf + 2, baudrate);
+    return cyw43_bluetooth_hci_cmd(0x3f, 0x18, 6, buf);
+}
+#endif
+
+static int cyw43_bluetooth_download_firmware(const uint8_t *firmware, size_t firmware_len) {
+    cyw43_bluetooth_hci_cmd(0x3f, 0x2e, 0, NULL);
+
+    const uint8_t *firmware_max = firmware + firmware_len;
+    bool last_packet = false;
+    while (!last_packet && firmware < firmware_max) {
+        // Validate firmware packet.
+        if (firmware[1] != 0xfc) {
+            CYW43_WARN("cyw43_bluetooth_download_firmware: unexpected packet %02x\n", firmware[1]);
+            return -CYW43_EIO;
+        }
+
+        // Construct HCI header.
+        uint8_t *buf = cyw43_hal_uart_cmd_buf;
path: src/cyw43_bthci_uart.c
user: login: dpgeorge
body: Yes, we can fix that eventually.
created_at: 2025-03-03T00:42:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,326 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth HCI-over-UART low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#define HCI_CMD_HEADER_LEN (4)
+#define HCI_RESP_HEADER_LEN (6)
+#define HCI_PAYLOAD_LEN_MAX (256)
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+#define CYW43_BT_UART_READCHAR_BLOCKING_TIMEOUT_US (100000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
path: src/cyw43_bthci_uart.c
user: login: iabdalkader
body: If `CYW43_BT_UART_BAUDRATE_DOWNLOAD_FIRMWARE` and `CYW43_BT_UART_BAUDRATE_ACTIVE_USE` are not defined, I think these static functions will be unused and issue warnings.
EDIT: maybe not, because they're inline.

Also, this is probably an overkill, but if the host byte order can change then you might want to add the following, and handle big endian:
```C
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
```
I know gcc and clang defines those but not sure about other compilers.
created_at: 2025-03-03T07:41:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,326 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth HCI-over-UART low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#define HCI_CMD_HEADER_LEN (4)
+#define HCI_RESP_HEADER_LEN (6)
+#define HCI_PAYLOAD_LEN_MAX (256)
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+#define CYW43_BT_UART_READCHAR_BLOCKING_TIMEOUT_US (100000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
path: src/cyw43_bthci_uart.c
user: login: dpgeorge
body: > maybe not, because they're inline

Right, because they are `static inline` they don't need to be used, and you won't get a warning.

> Also, this is probably an overkill, but if the host byte order can change then you might want to add the following, and handle big endian:

Actually, the reason to write these endian functions explicitly like this is so they are portable across both little and big endian archs.  The point is that the HCI command to change the baudrate is specified to take parameters in little endian order, so that's how the packet is created.
created_at: 2025-03-03T12:08:36Z
author_association: NONE
diff_hunk: @@ -0,0 +1,326 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth HCI-over-UART low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#define HCI_CMD_HEADER_LEN (4)
+#define HCI_RESP_HEADER_LEN (6)
+#define HCI_PAYLOAD_LEN_MAX (256)
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+#define CYW43_BT_UART_READCHAR_BLOCKING_TIMEOUT_US (100000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
path: src/cyw43_bthci_uart.c
user: login: dpgeorge
body: clang does indeed give a warning about `static inline` functions not being used.  So I've added the #if guard.
created_at: 2025-03-03T23:44:03Z
author_association: NONE
diff_hunk: @@ -0,0 +1,326 @@
+/*
+ * This file is part of the cyw43-driver
+ *
+ * Copyright (C) 2019-2025 George Robotics Pty Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Any redistribution, use, or modification in source or binary form is done
+ *    solely for personal benefit and not for any commercial purpose or for
+ *    monetary gain.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software is also available for use with certain devices under different
+ * terms, as set out in the top level LICENSE file.  For commercial licensing
+ * options please email contact@georgerobotics.com.au.
+ */
+
+// CYW43 Bluetooth HCI-over-UART low-level driver.
+
+#include "lib/cyw43-driver/src/cyw43_config.h"
+
+#if CYW43_BT_USE_UART
+
+// This firmware file defines btfw_data and btfw_len.
+#include CYW43_BT_FIRMWARE_INCLUDE_FILE
+
+#define HCI_CMD_HEADER_LEN (4)
+#define HCI_RESP_HEADER_LEN (6)
+#define HCI_PAYLOAD_LEN_MAX (256)
+
+#ifndef CYW43_BT_UART_BAUDRATE_STARTUP
+#define CYW43_BT_UART_BAUDRATE_STARTUP (115200)
+#endif
+
+#ifdef CYW43_PIN_BT_DEV_WAKE
+#define CYW43_BT_SLEEP_TIMEOUT_MS (500)
+static uint32_t cyw43_bluetooth_sleep_ticks;
+#endif
+
+#define CYW43_BT_WAIT_CTS_LOW_TIMEOUT_US (200000)
+#define CYW43_BT_UART_READCHAR_BLOCKING_TIMEOUT_US (100000)
+
+static inline void cyw43_bluetooth_put_le16(uint8_t *buf, uint16_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+}
+
+static inline void cyw43_bluetooth_put_le32(uint8_t *buf, uint32_t val) {
+    buf[0] = val;
+    buf[1] = val >> 8;
+    buf[2] = val >> 16;
+    buf[3] = val >> 24;
+}
path: src/cyw43_bthci_uart.c
user: login: iabdalkader
body: I've had similar issues with clang, but `inline` is merely a suggestion, isn't it? So it wouldn't know if it's called or not until later stages I guess.
created_at: 2025-03-04T07:14:34Z
author_association: CONTRIBUTOR

PR comments:
user: login: iabdalkader
created_at: 2025-02-27T14:04:05Z
body: Are you sure about this change? Wouldn't this break other drivers and almost all ports? Why not keep the old `mp_bluetooth_hci` API? The driver could just provide the implementation in `cyw43_bthci.c` as it did, similar to https://github.com/micropython/micropython/blob/master/drivers/esp-hosted/esp_hosted_bthci.c and https://github.com/micropython/micropython/blob/master/drivers/ninaw10/nina_bt_hci.c

If you do merge this change, it will require refactoring all `mpbthciport.c` files to add `ifdef`'s, and possibly `esp_hosted` driver, to rename the functions from `mp_bluetooth_hci_controller_x` to `esp_hosted_bluetooth_hci_controller_x`, for consistency. For example:
```C
int mp_bluetooth_hci_controller_init(void) {
#ifdef CYW43
    return cyw43_bluetooth_controller_init();
#elif defined(ESP_HOSTED)
    return esphosted_bluetooth_controller_init();
#elif defined(NEW_WIFI_CHIP)
    return new_wifi_chip_bluetooth_controller_init();
....
// Change every time a new driver is added.
#endif

}
```
I think the previous `weak` function was much cleaner.
author_association: CONTRIBUTOR
user: login: dpgeorge
created_at: 2025-02-28T08:30:35Z
body: > Why not keep the old `mp_bluetooth_hci` API?

Because `mp` here stands for MicroPython, and this driver is supposed to be an independent component, with its own naming scheme.

I understand it introduces the need for a shim layer, but we really can't have any MicroPython names in this repo.

author_association: NONE
user: login: iabdalkader
created_at: 2025-02-28T08:45:37Z
body: > I understand it introduces the need for a shim layer

A shim layer would minimize changes to `mpbthciport.c` files and other drivers and avoid breaking the somewhat existing patterns. I assume it will implement the weak functions to call CYW43 functions? Something like this for example?
```C
int mp_bluetooth_hci_controller_init(void) {
    return cyw43_bluetooth_controller_init();
}
```
author_association: CONTRIBUTOR
user: login: iabdalkader
created_at: 2025-02-28T09:26:41Z
body: I'd also suggest moving this submodule from `lib/cyw43-driver` to `drivers/cyw43` with the other WiFi drivers. Perhaps in the future they all could be moved to `drivers/wifi/`
author_association: CONTRIBUTOR
user: login: dpgeorge
created_at: 2025-02-28T11:19:39Z
body: > A shim layer would minimize changes to `mpbthciport.c` files and other drivers and avoid breaking the somewhat existing patterns.

You could either implement the shim layer as you suggest, like:
```c
int mp_bluetooth_hci_controller_init(void) {
    return cyw43_bluetooth_controller_init();
}
```

Or more simply, in `cyw43_configport.h`, add defines to rename the cyw43 functions:
```c
#define cyw43_bluetooth_controller_init        mp_bluetooth_hci_controller_init
#define cyw43_bluetooth_controller_deinit      mp_bluetooth_hci_controller_deinit
#define cyw43_bluetooth_controller_sleep_maybe mp_bluetooth_hci_controller_sleep_maybe
#define cyw43_bluetooth_controller_woken       mp_bluetooth_hci_controller_woken
#define cyw43_bluetooth_controller_wakeup      mp_bluetooth_hci_controller_wakeup
```
I've tested both schemes and they both work.  The latter is zero overhead.
author_association: NONE
user: login: iabdalkader
created_at: 2025-02-28T11:34:35Z
body: > I've tested both schemes and they both work. The latter is zero overhead.

I like the second option more too.
author_association: CONTRIBUTOR
user: login: dpgeorge
created_at: 2025-02-28T12:27:23Z
body: This PR has now been tested in the following scenarios:
- MicroPython stm32 port, PYBD_SF6 board: no changes required, can still use the old `drivers/cyw43/cywbt.c` code
- MicroPython rp2 port, RPI_PICO_W board: same as above, no changes required
- MicroPython stm32 port, PYBD_SF6, with changes to switch to the new HCI UART code here: needs an implementation of `mp_bluetooth_hci_uart_readchar_blocking()` and a handful of configuration options in `cyw43_configport.h`
author_association: NONE
user: login: iabdalkader
created_at: 2025-02-28T13:12:40Z
body: I tested this with mimxrt, but it's not working. I get:

```
>>> [CYW43] cyw43_bluetooth_download_firmware: unexpected response 01
```

Note `CYW43_PIN_BT_CTS` was optional, I had it commented out for a reason, but I enabled it since it's now required. Maybe it's related?


I made the following changes:

```diff
diff --git a/ports/mimxrt/cyw43_configport.h b/ports/mimxrt/cyw43_configport.h
index 619db24b1..48c6ec983 100644
--- a/ports/mimxrt/cyw43_configport.h
+++ b/ports/mimxrt/cyw43_configport.h
@@ -34,10 +34,12 @@
 #include "extmod/modnetwork.h"
 #include "pendsv.h"
 #include "sdio.h"
+#include "extmod/mpbthci.h"
 
 #define CYW43_USE_SPI                   (0)
 #define CYW43_LWIP                      (1)
 #define CYW43_USE_STATS                 (0)
+#define CYW43_BT_USE_UART               (1)
 
 #ifndef CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE
 #define CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE "lib/cyw43-driver/firmware/w4343WA1_7_45_98_50_combined.h"
@@ -47,6 +49,10 @@
 #define CYW43_WIFI_NVRAM_INCLUDE_FILE   "lib/cyw43-driver/firmware/wifi_nvram_1dx.h"
 #endif
 
+#ifndef CYW43_BT_FIRMWARE_INCLUDE_FILE
+#define CYW43_BT_FIRMWARE_INCLUDE_FILE  "lib/cyw43-driver/firmware/cyw43_btfw_4343A1.h"
+#endif
+
 #define CYW43_IOCTL_TIMEOUT_US          (1000000)
 #define CYW43_SLEEP_MAX                 (50)
 #define CYW43_NETUTILS                  (1)
@@ -75,6 +81,7 @@
 #define CYW43_HAL_PIN_PULL_DOWN         MP_HAL_PIN_PULL_DOWN
 
 #define CYW43_HAL_MAC_WLAN0             MP_HAL_MAC_WLAN0
+#define CYW43_HAL_MAC_BDADDR            MP_HAL_MAC_BDADDR
 
 #define cyw43_hal_ticks_us              mp_hal_ticks_us
 #define cyw43_hal_ticks_ms              mp_hal_ticks_ms
@@ -88,9 +95,21 @@
 #define cyw43_hal_get_mac_ascii         mp_hal_get_mac_ascii
 #define cyw43_hal_generate_laa_mac      mp_hal_generate_laa_mac
 
+extern uint8_t mp_bluetooth_hci_cmd_buf[4 + 256];
+#define cyw43_hal_uart_cmd_buf          mp_bluetooth_hci_cmd_buf
+#define cyw43_hal_uart_set_baudrate     mp_bluetooth_hci_uart_set_baudrate
+#define cyw43_hal_uart_write            mp_bluetooth_hci_uart_write
+#define cyw43_hal_uart_readchar         mp_bluetooth_hci_uart_readchar
+
 #define cyw43_delay_us                  mp_hal_delay_us
 #define cyw43_delay_ms                  mp_hal_delay_ms
 
+#define cyw43_bluetooth_controller_init        mp_bluetooth_hci_controller_init
+#define cyw43_bluetooth_controller_deinit      mp_bluetooth_hci_controller_deinit
+#define cyw43_bluetooth_controller_sleep_maybe mp_bluetooth_hci_controller_sleep_maybe
+#define cyw43_bluetooth_controller_woken       mp_bluetooth_hci_controller_woken
+#define cyw43_bluetooth_controller_wakeup      mp_bluetooth_hci_controller_wakeup
+
 #define CYW43_PIN_WL_REG_ON             MICROPY_HW_WL_REG_ON
 #define CYW43_PIN_WL_HOST_WAKE          MICROPY_HW_WL_HOST_WAKE
 #define CYW43_PIN_WL_SDIO_1             MICROPY_HW_SDIO_D1
```

Also, I enabled read (inputon) for CTS:
```diff
diff --git a/ports/mimxrt/machine_uart.c b/ports/mimxrt/machine_uart.c
index 7ae584fdd..0432938f7 100644
--- a/ports/mimxrt/machine_uart.c
+++ b/ports/mimxrt/machine_uart.c
@@ -126,7 +126,7 @@ bool lpuart_set_iomux_cts(int8_t uart) {
     int index = (uart - 1) * 2;
 
     if (CTS.muxRegister != 0) {
-        IOMUXC_SetPinMux(CTS.muxRegister, CTS.muxMode, CTS.inputRegister, CTS.inputDaisy, CTS.configRegister, 0U);
+        IOMUXC_SetPinMux(CTS.muxRegister, CTS.muxMode, CTS.inputRegister, CTS.inputDaisy, CTS.configRegister, 1U);
         IOMUXC_SetPinConfig(CTS.muxRegister, CTS.muxMode, CTS.inputRegister, CTS.inputDaisy, CTS.configRegister,
             pin_generate_config(PIN_PULL_UP_100K, PIN_MODE_IN, PIN_DRIVE_6, CTS.configRegister));
         return true;
```
author_association: CONTRIBUTOR
user: login: iabdalkader
created_at: 2025-02-28T16:07:39Z
body: I just needed a blocking read function:
```C
int mp_bluetooth_hci_uart_readchar_blocking(void) {
    int c;
    while ((c = mp_bluetooth_hci_uart_readchar()) == -1) {
        mp_event_wait_indefinite();
    }
    return c;
}
```

This will also need proper timeout. Why not implement the blocking read in `cyw43_bthci_uart.c`? This way all ports can share the same logic, and won't have to add a new function to `mpbthciport.c`.

Also, unrelated, but what's the point of `cyw43_hal_uart_cmd_buf`? It seems to be me it can just be a stack buffer, no?
author_association: CONTRIBUTOR
user: login: dpgeorge
created_at: 2025-03-01T06:39:49Z
body: So, is it working now on mimxrt?  Did you need to enable the CTS pin?  I suspect that's necessary.

> Why not implement the blocking read in `cyw43_bthci_uart.c`? This way all ports can share the same logic, and won't have to add a new function to `mpbthciport.c`.

I was thinking that each port needs different behaviour for the blocking UART read, like a different polling function when waiting for incoming chars, depending on how that port does events/irqs.  So it's easier to make the port implement the blocking read, rather than try to make a generic and configurable one here.
author_association: NONE
user: login: iabdalkader
created_at: 2025-03-01T07:35:36Z
body: > So, is it working now on mimxrt? Did you need to enable the CTS pin? I suspect that's necessary.

Yes, it's working. I only tried a simple aioble LED blinky though. CTS must be defined, otherwise it won't build. Unless you're sure that all chips support reading the pin when it's configured as AF, and that CTS will always be connected,  you might want to consider making it optional like it used to be.

> I was thinking that each port needs different behaviour for the blocking UART read

I think most of them will just have the same code. Could we provide a config option like `cyw43_hal_idle`? Or make `cyw43_hal_uart_readchar_blocking` optional? If it's not defined, default blocking behavior used. For example:
```C
int cyw43_uart_readchar() {
    #ifdef cyw43_hal_uart_readchar_blocking
    return cyw43_hal_uart_readchar_blocking();
    #else
    int c;
    uint32_t ticks = ticks_ms();
    while ((c = mp_bluetooth_hci_uart_readchar()) == -1) {        
        if (ticks> timeout) {
           return -1;
         }
         mp_event_wait_indefinite();
    }
    return c;
#endif
}
```

> but what's the point of `cyw43_hal_uart_cmd_buf`? It seems to be me it can just be a stack buffer, no?

Is this really needed?
author_association: CONTRIBUTOR
user: login: dpgeorge
created_at: 2025-03-02T13:41:30Z
body: > CTS must be defined, otherwise it won't build. Unless you're sure that all chips support reading the pin when it's configured as AF, and that CTS will always be connected, you might want to consider making it optional like it used to be.

I'm pretty sure CTS is required, because there are very long delays (70ms eg at initial start up, and then longer than that after downloading the patch firmware), and the only way to know when the chip is ready is to wait for the CTS line to go low.

I don't know of any code that uses the old driver without CTS, so I think we need to make it mandatory.

> I think most of them will just have the same code. Could we provide a config option like `cyw43_hal_idle`? Or make `cyw43_hal_uart_readchar_blocking` optional? If it's not defined, default blocking behavior used.

OK, I've now done something like that, so you can either provide the blocking or non-blocking version.
author_association: NONE
user: login: iabdalkader
created_at: 2025-03-02T13:52:52Z
body: > I'm pretty sure CTS is required, because there are very long delays (70ms eg at initial start up
> I don't know of any code that uses the old driver without CTS, so I think we need to make it mandatory.

FWIW I use the old HCI UART driver on mimxrt port without CTS (we have a board with RT1062 + CYW43), and it works fine. Note that the old driver already had large delays when CTS was not defined and `wait_cts` was only enabled for stm32 port (as it needed to configure AF).

I enabled CTS when I tested this PR and it works fine, so either way is fine with me.
author_association: CONTRIBUTOR
user: login: iabdalkader
created_at: 2025-03-02T14:01:55Z
body: Looks good. I can retest on mimxrt when it's merged. I can also send a PR to switch mimxrt when the submodule is updated.
author_association: CONTRIBUTOR
user: login: dpgeorge
created_at: 2025-03-03T03:50:17Z
body: Renamed `CYW43_PIN_WL_RFSW_VDD` to `CYW43_PIN_RFSW_VDD` because it's now part of the BT driver (so not just for WL).  A backwards-compatible macro is provided for this so no user code needs to be changed.

Also added some documentation about the pins that can be defined.

Retested with the same method described in https://github.com/georgerobotics/cyw43-driver/pull/137#issuecomment-2690523795
author_association: NONE
user: login: dpgeorge
created_at: 2025-03-03T12:10:09Z
body: I've added a commit here which adds a test for the new BT HCI UART code.  It's a very simple test but ensures that it compiles with a minimal configuration, and runs in a basic capacity.

As part of this I found an issue enabling the BT option, so I renamed `CYW43_BT_USE_UART` to `CYW43_ENABLE_BLUETOOTH_OVER_UART`.  And you enable this and not `CYW43_ENABLE_BLUETOOTH` (which is for the SPI/SDIO transport only).
author_association: NONE
user: login: iabdalkader
created_at: 2025-03-03T17:10:17Z
body: Tested again, still working fine. The changes to mimxrt are here: https://github.com/micropython/micropython/pull/16848
author_association: CONTRIBUTOR
user: login: dpgeorge
created_at: 2025-03-04T06:15:54Z
body: Minor update to remove the "wait cts low" before sending out a HCI command (it's not needed because the UART has flow control), and speed up the default `readchar_blocking()` implementation by using `delay_us(10)` instead of `delay_ms(1)`.
author_association: NONE