diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 2a3b14d04..99d2248fd 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -252,6 +252,7 @@ SRC_C = \ softtimer.c \ powerctrl.c \ powerctrlboot.c \ + rfcore.c \ pybthread.c \ factoryreset.c \ timer.c \ diff --git a/ports/stm32/boards/stm32wb55xg.ld b/ports/stm32/boards/stm32wb55xg.ld index bdbf7e447..77596d775 100644 --- a/ports/stm32/boards/stm32wb55xg.ld +++ b/ports/stm32/boards/stm32wb55xg.ld @@ -8,6 +8,7 @@ MEMORY FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K /* sectors 0-127 */ FLASH_FS (r) : ORIGIN = 0x08080000, LENGTH = 256K /* sectors 128-191 */ RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K /* SRAM1 */ + RAM2A (xrw) : ORIGIN = 0x20030020, LENGTH = 8K /* SRAM2A */ } /* produce a link error if there is not this amount of RAM for these sections */ @@ -31,3 +32,13 @@ _heap_end = _sstack; _flash_fs_start = ORIGIN(FLASH_FS); _flash_fs_end = ORIGIN(FLASH_FS) + LENGTH(FLASH_FS); + +SECTIONS +{ + .ram2a_bss : + { + . = ALIGN(4); + *rfcore.o(.bss.ipcc_mem_*) + . = ALIGN(4); + } >RAM2A +} diff --git a/ports/stm32/main.c b/ports/stm32/main.c index a00bdf3bd..3c6420d50 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -48,6 +48,7 @@ #endif #include "mpu.h" +#include "rfcore.h" #include "systick.h" #include "pendsv.h" #include "powerctrl.h" @@ -466,6 +467,9 @@ void stm32_main(uint32_t reset_mode) { #endif // basic sub-system init + #if defined(STM32WB) + rfcore_init(); + #endif #if MICROPY_HW_SDRAM_SIZE sdram_init(); #if MICROPY_HW_SDRAM_STARTUP_TEST diff --git a/ports/stm32/nimble_hci_uart.c b/ports/stm32/nimble_hci_uart.c index 251f15e3b..defda1581 100644 --- a/ports/stm32/nimble_hci_uart.c +++ b/ports/stm32/nimble_hci_uart.c @@ -26,15 +26,54 @@ #include "py/runtime.h" #include "py/mphal.h" -#include "uart.h" -#include "pendsv.h" #include "extmod/nimble/nimble/hci_uart.h" -#include "drivers/cyw43/cywbt.h" #if MICROPY_BLUETOOTH_NIMBLE +#if defined(STM32WB) + /******************************************************************************/ -// UART +// HCI over IPCC + +#include "rfcore.h" + +int nimble_hci_uart_configure(uint32_t port) { + (void)port; + return 0; +} + +int nimble_hci_uart_set_baudrate(uint32_t baudrate) { + (void)baudrate; + return 0; +} + +int nimble_hci_uart_activate(void) { + rfcore_ble_init(); + return 0; +} + +void nimble_hci_uart_rx(hal_uart_rx_cb_t rx_cb, void *rx_arg) { + // Protect in case it's called from ble_npl_sem_pend at thread-level + MICROPY_PY_LWIP_ENTER + rfcore_ble_check_msg(rx_cb, rx_arg); + MICROPY_PY_LWIP_EXIT +} + +void nimble_hci_uart_tx_strn(const char *str, uint len) { + MICROPY_PY_LWIP_ENTER + rfcore_ble_hci_cmd(len, (const uint8_t*)str); + MICROPY_PY_LWIP_EXIT +} + +#else + +/******************************************************************************/ +// HCI over UART + +#include "pendsv.h" +#include "uart.h" +#include "drivers/cyw43/cywbt.h" + pyb_uart_obj_t bt_hci_uart_obj; static uint8_t hci_uart_rxbuf[512]; @@ -130,4 +169,6 @@ void nimble_hci_uart_tx_strn(const char *str, uint len) { uart_tx_strn(&bt_hci_uart_obj, str, len); } +#endif // defined(STM32WB) + #endif // MICROPY_BLUETOOTH_NIMBLE diff --git a/ports/stm32/rfcore.c b/ports/stm32/rfcore.c new file mode 100644 index 000000000..2b45bb2ee --- /dev/null +++ b/ports/stm32/rfcore.c @@ -0,0 +1,425 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/mperrno.h" +#include "py/mphal.h" +#include "rtc.h" +#include "rfcore.h" + +#if defined(STM32WB) + +// Define to 1 to print traces of HCI packets +#define HCI_TRACE (0) + +#define IPCC_CH_BLE (0x01) // BLE HCI command and response +#define IPCC_CH_SYS (0x02) // system HCI command and response +#define IPCC_CH_MM (0x08) // release buffer +#define IPCC_CH_HCI_ACL (0x20) // HCI ACL outgoing data + +#define OGF_VENDOR (0x3f) +#define OCF_WRITE_CONFIG (0x0c) +#define OCF_SET_TX_POWER (0x0f) +#define OCF_BLE_INIT (0x66) + +#define HCI_OPCODE(ogf, ocf) ((ogf) << 10 | (ocf)) + +typedef struct _tl_list_node_t { + volatile struct _tl_list_node_t *next; + volatile struct _tl_list_node_t *prev; + uint8_t body[0]; +} tl_list_node_t; + +typedef struct _parse_hci_info_t { + int (*cb_fun)(void*, uint8_t); + void *cb_env; + bool was_hci_reset_evt; +} parse_hci_info_t; + +static volatile uint32_t ipcc_mem_dev_info_tab[8]; +static volatile uint32_t ipcc_mem_ble_tab[4]; +static volatile uint32_t ipcc_mem_sys_tab[2]; +static volatile uint32_t ipcc_mem_memmgr_tab[7]; + +static volatile uint32_t ipcc_mem_sys_cmd_buf[272 / 4]; +static volatile tl_list_node_t ipcc_mem_sys_queue; + +static volatile tl_list_node_t ipcc_mem_memmgr_free_buf_queue; +static volatile uint32_t ipcc_mem_memmgr_ble_spare_evt_buf[272 / 4]; +static volatile uint32_t ipcc_mem_memmgr_sys_spare_evt_buf[272 / 4]; +static volatile uint32_t ipcc_mem_memmgr_evt_pool[6 * 272 / 4]; + +static volatile uint32_t ipcc_mem_ble_cmd_buf[272 / 4]; +static volatile uint32_t ipcc_mem_ble_cs_buf[272 / 4]; +static volatile tl_list_node_t ipcc_mem_ble_evt_queue; +static volatile uint32_t ipcc_mem_ble_hci_acl_data_buf[272 / 4]; + +/******************************************************************************/ +// Transport layer linked list + +STATIC void tl_list_init(volatile tl_list_node_t *n) { + n->next = n; + n->prev = n; +} + +STATIC volatile tl_list_node_t *tl_list_unlink(volatile tl_list_node_t *n) { + volatile tl_list_node_t *next = n->next; + volatile tl_list_node_t *prev = n->prev; + prev->next = next; + next->prev = prev; + return next; +} + +STATIC void tl_list_append(volatile tl_list_node_t *head, volatile tl_list_node_t *n) { + n->next = head; + n->prev = head->prev; + head->prev->next = n; + head->prev = n; +} + +/******************************************************************************/ +// IPCC interface + +STATIC uint32_t get_ipccdba(void) { + return *(uint32_t*)(OPTION_BYTE_BASE + 0x68) & 0x3fff; +} + +STATIC volatile void **get_buffer_table(void) { + return (volatile void**)(SRAM2A_BASE + get_ipccdba()); +} + +void ipcc_init(uint32_t irq_pri) { + // Setup buffer table pointers + volatile void **tab = get_buffer_table(); + tab[0] = &ipcc_mem_dev_info_tab[0]; + tab[1] = &ipcc_mem_ble_tab[0]; + tab[3] = &ipcc_mem_sys_tab[0]; + tab[4] = &ipcc_mem_memmgr_tab[0]; + + // Start IPCC peripheral + __HAL_RCC_IPCC_CLK_ENABLE(); + + // Enable wanted IRQs + IPCC->C1CR = 0;//IPCC_C1CR_RXOIE; + IPCC->C1MR = 0xffffffff; + NVIC_SetPriority(IPCC_C1_RX_IRQn, irq_pri); + HAL_NVIC_EnableIRQ(IPCC_C1_RX_IRQn); + + // Device info table will be populated by FUS/WS + + // Populate system table + tl_list_init(&ipcc_mem_sys_queue); + ipcc_mem_sys_tab[0] = (uint32_t)&ipcc_mem_sys_cmd_buf[0]; + ipcc_mem_sys_tab[1] = (uint32_t)&ipcc_mem_sys_queue; + + // Populate memory manager table + tl_list_init(&ipcc_mem_memmgr_free_buf_queue); + ipcc_mem_memmgr_tab[0] = (uint32_t)&ipcc_mem_memmgr_ble_spare_evt_buf[0]; + ipcc_mem_memmgr_tab[1] = (uint32_t)&ipcc_mem_memmgr_sys_spare_evt_buf[0]; + ipcc_mem_memmgr_tab[2] = (uint32_t)&ipcc_mem_memmgr_evt_pool[0]; + ipcc_mem_memmgr_tab[3] = sizeof(ipcc_mem_memmgr_evt_pool); + ipcc_mem_memmgr_tab[4] = (uint32_t)&ipcc_mem_memmgr_free_buf_queue; + ipcc_mem_memmgr_tab[5] = 0; + ipcc_mem_memmgr_tab[6] = 0; + + // Populate BLE table + tl_list_init(&ipcc_mem_ble_evt_queue); + ipcc_mem_ble_tab[0] = (uint32_t)&ipcc_mem_ble_cmd_buf[0]; + ipcc_mem_ble_tab[1] = (uint32_t)&ipcc_mem_ble_cs_buf[0]; + ipcc_mem_ble_tab[2] = (uint32_t)&ipcc_mem_ble_evt_queue; + ipcc_mem_ble_tab[3] = (uint32_t)&ipcc_mem_ble_hci_acl_data_buf[0]; +} + +STATIC int ipcc_wait_ack(unsigned int ch, uint32_t timeout_ms) { + uint32_t t0 = mp_hal_ticks_ms(); + while (IPCC->C1TOC2SR & ch) { + if (mp_hal_ticks_ms() - t0 > timeout_ms) { + printf("ipcc_wait_ack: timeout\n"); + return -MP_ETIMEDOUT; + } + } + // C2 cleared IPCC flag + return 0; +} + +STATIC int ipcc_wait_msg(unsigned int ch, uint32_t timeout_ms) { + uint32_t t0 = mp_hal_ticks_ms(); + while (!(IPCC->C2TOC1SR & ch)) { + if (mp_hal_ticks_ms() - t0 > timeout_ms) { + printf("ipcc_wait_msg: timeout\n"); + return -MP_ETIMEDOUT; + } + } + // C2 set IPCC flag + return 0; +} + +/******************************************************************************/ +// Transport layer HCI interface + +STATIC void tl_parse_hci_msg(const uint8_t *buf, parse_hci_info_t *parse) { + const char *kind; + size_t len = 3 + buf[2]; + switch (buf[0]) { + case 0x02: { + // Standard BT HCI ACL packet + kind = "HCI_ACL"; + if (parse != NULL) { + for (size_t i = 0; i < len; ++i) { + parse->cb_fun(parse->cb_env, buf[i]); + } + } + break; + } + case 0x04: { + // Standard BT HCI event packet + kind = "HCI_EVT"; + if (parse != NULL) { + bool fix = false; + if (buf[1] == 0x0e && len == 7 && buf[3] == 0x01 && buf[4] == 0x63 && buf[5] == 0x0c && buf[6] == 0x01) { + len -= 1; + fix = true; + } + for (size_t i = 0; i < len; ++i) { + parse->cb_fun(parse->cb_env, buf[i]); + } + if (fix) { + len += 1; + parse->cb_fun(parse->cb_env, 0x00); // success + } + // Check for successful HCI_Reset event + parse->was_hci_reset_evt = buf[1] == 0x0e && buf[2] == 0x04 && buf[3] == 0x01 + && buf[4] == 0x03 && buf[5] == 0x0c && buf[6] == 0x00; + } + break; + } + case 0x11: { + // Response packet + // assert(buf[1] == 0x0e); + kind = "VEND_RESP"; + //uint16_t cmd = buf[4] | buf[5] << 8; + //uint8_t status = buf[6]; + break; + } + case 0x12: { + // Event packet + // assert(buf[1] == 0xff); + kind = "VEND_EVT"; + //uint16_t evt = buf[3] | buf[4] << 8; + break; + } + default: + kind = "HCI_UNKNOWN"; + break; + } + + #if HCI_TRACE + printf("[% 8d] %s(%02x", mp_hal_ticks_ms(), kind, buf[0]); + for (int i = 1; i < len; ++i) { + printf(":%02x", buf[i]); + } + printf(")\n"); + #else + (void)kind; + #endif +} + +STATIC void tl_check_msg(volatile tl_list_node_t *head, unsigned int ch, parse_hci_info_t *parse) { + if (IPCC->C2TOC1SR & ch) { + // Message available on CH2 + volatile tl_list_node_t *cur = head->next; + bool free = false; + while (cur != head) { + tl_parse_hci_msg((uint8_t*)cur->body, parse); + volatile tl_list_node_t *next = tl_list_unlink(cur); + if ((void*)&ipcc_mem_memmgr_evt_pool[0] <= (void*)cur + && (void*)cur < (void*)&ipcc_mem_memmgr_evt_pool[MP_ARRAY_SIZE(ipcc_mem_memmgr_evt_pool)]) { + // Place memory back in free pool + tl_list_append(&ipcc_mem_memmgr_free_buf_queue, cur); + free = true; + } + cur = next; + } + if (free) { + // Notify change in free pool + IPCC->C1SCR = IPCC_CH_MM << 16; + } + // Clear receive channel + IPCC->C1SCR = ch; + } +} + +STATIC void tl_hci_cmd(uint8_t *cmd, unsigned int ch, uint8_t hdr, uint16_t opcode, size_t len, const uint8_t *buf) { + tl_list_node_t *n = (tl_list_node_t*)cmd; + n->next = n; + n->prev = n; + cmd[8] = hdr; + cmd[9] = opcode; + cmd[10] = opcode >> 8; + cmd[11] = len; + memcpy(&cmd[12], buf, len); + // IPCC indicate + IPCC->C1SCR = ch << 16; +} + +STATIC void tl_sys_wait_resp(const uint8_t *buf, unsigned int ch) { + if (ipcc_wait_ack(ch, 250) == 0) { + tl_parse_hci_msg(buf, NULL); + } +} + +STATIC void tl_sys_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) { + tl_hci_cmd((uint8_t*)&ipcc_mem_sys_cmd_buf, IPCC_CH_SYS, 0x10, opcode, len, buf); + tl_sys_wait_resp((uint8_t*)&ipcc_mem_sys_cmd_buf, IPCC_CH_SYS); +} + +STATIC void tl_ble_hci_cmd_resp(uint16_t opcode, size_t len, const uint8_t *buf) { + tl_hci_cmd((uint8_t*)&ipcc_mem_ble_cmd_buf[0], IPCC_CH_BLE, 0x01, opcode, len, buf); + ipcc_wait_msg(IPCC_CH_BLE, 250); + tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); +} + +/******************************************************************************/ +// RF core interface + +void rfcore_init(void) { + // Ensure LSE is running + rtc_init_finalise(); + + // Select LSE as RF wakeup source + RCC->CSR = (RCC->CSR & ~RCC_CSR_RFWKPSEL) | 1 << RCC_CSR_RFWKPSEL_Pos; + + // Initialise IPCC and shared memory structures + ipcc_init(IRQ_PRI_SDIO); + + // Boot the second core + __SEV(); + __WFE(); + PWR->CR4 |= PWR_CR4_C2BOOT; +} + +static const struct { + uint8_t* pBleBufferAddress; // unused + uint32_t BleBufferSize; // unused + uint16_t NumAttrRecord; + uint16_t NumAttrServ; + uint16_t AttrValueArrSize; + uint8_t NumOfLinks; + uint8_t ExtendedPacketLengthEnable; + uint8_t PrWriteListSize; + uint8_t MblockCount; + uint16_t AttMtu; + uint16_t SlaveSca; + uint8_t MasterSca; + uint8_t LsSource; // 0=LSE 1=internal RO + uint32_t MaxConnEventLength; + uint16_t HsStartupTime; + uint8_t ViterbiEnable; + uint8_t LlOnly; // 0=LL+Host, 1=LL only + uint8_t HwVersion; +} ble_init_params = { + 0, + 0, + 0, // NumAttrRecord + 0, // NumAttrServ + 0, // AttrValueArrSize + 1, // NumOfLinks + 1, // ExtendedPacketLengthEnable + 0, // PrWriteListSize + 0x79, // MblockCount + 0, // AttMtu + 0, // SlaveSca + 0, // MasterSca + 1, // LsSource + 0xffffffff, // MaxConnEventLength + 0x148, // HsStartupTime + 0, // ViterbiEnable + 1, // LlOnly + 0, // HwVersion +}; + +void rfcore_ble_init(void) { + // Clear any outstanding messages from ipcc_init + tl_check_msg(&ipcc_mem_sys_queue, IPCC_CH_SYS, NULL); + tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, NULL); + + // Configure and reset the BLE controller + tl_sys_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_BLE_INIT), sizeof(ble_init_params), (const uint8_t*)&ble_init_params); + tl_ble_hci_cmd_resp(HCI_OPCODE(0x03, 0x0003), 0, NULL); +} + +void rfcore_ble_hci_cmd(size_t len, const uint8_t *src) { + #if HCI_TRACE + printf("[% 8d] HCI_CMD(%02x", mp_hal_ticks_ms(), src[0]); + for (int i = 1; i < len; ++i) { + printf(":%02x", src[i]); + } + printf(")\n"); + #endif + + tl_list_node_t *n; + uint32_t ch; + if (src[0] == 0x01) { + n = (tl_list_node_t*)&ipcc_mem_ble_cmd_buf[0]; + ch = IPCC_CH_BLE; + } else if (src[0] == 0x02) { + n = (tl_list_node_t*)&ipcc_mem_ble_hci_acl_data_buf[0]; + ch = IPCC_CH_HCI_ACL; + } else { + printf("** UNEXPECTED HCI HDR: 0x%02x **\n", src[0]); + return; + } + + n->next = n; + n->prev = n; + memcpy(n->body, src, len); + + // IPCC indicate + IPCC->C1SCR = ch << 16; +} + +void rfcore_ble_check_msg(int (*cb)(void*, uint8_t), void *env) { + parse_hci_info_t parse = { cb, env, false }; + tl_check_msg(&ipcc_mem_ble_evt_queue, IPCC_CH_BLE, &parse); + + // Intercept HCI_Reset events and reconfigure the controller following the reset + if (parse.was_hci_reset_evt) { + uint8_t buf[8]; + buf[0] = 0; // config offset + buf[1] = 6; // config length + mp_hal_get_mac(MP_HAL_MAC_BDADDR, &buf[2]); + #define SWAP_UINT8(a, b) { uint8_t temp = a; a = b; b = temp; } + SWAP_UINT8(buf[2], buf[7]); + SWAP_UINT8(buf[3], buf[6]); + SWAP_UINT8(buf[4], buf[5]); + tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_WRITE_CONFIG), 8, buf); // set BDADDR + tl_ble_hci_cmd_resp(HCI_OPCODE(OGF_VENDOR, OCF_SET_TX_POWER), 2, (const uint8_t*)"\x00\x06"); // 0 dBm + } +} + +#endif // defined(STM32WB) diff --git a/ports/stm32/rfcore.h b/ports/stm32/rfcore.h new file mode 100644 index 000000000..ef12707b7 --- /dev/null +++ b/ports/stm32/rfcore.h @@ -0,0 +1,37 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_STM32_RFCORE_H +#define MICROPY_INCLUDED_STM32_RFCORE_H + +#include + +void rfcore_init(void); + +void rfcore_ble_init(void); +void rfcore_ble_hci_cmd(size_t len, const uint8_t *src); +void rfcore_ble_check_msg(int (*cb)(void*, uint8_t), void *env); + +#endif // MICROPY_INCLUDED_STM32_RFCORE_H