summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorDamien George <damien@micropython.org>2021-04-25 22:30:19 +1000
committerDamien George <damien@micropython.org>2021-06-06 21:57:06 +1000
commit80e79a777de8a43a37964be672da9e2449f4ec53 (patch)
tree81779d82efac8f1865a0c3d88acae2889fa17b9b
parent5cb2ade65b9540dd1abdbfee944bcb9f1f1c52aa (diff)
downloadmicropython-80e79a777de8a43a37964be672da9e2449f4ec53.tar.gz
micropython-80e79a777de8a43a37964be672da9e2449f4ec53.zip
zephyr: Add initial ubluetooth module integration.
Currently only advertising and scanning are supported, using the ring buffer for events (ie not synchronous events at this stage). The ble_gap_advertise.py multi-test passes (tested on a nucleo_wb55rg board). Signed-off-by: Damien George <damien@micropython.org>
-rw-r--r--ports/zephyr/CMakeLists.txt1
-rw-r--r--ports/zephyr/main.c4
-rw-r--r--ports/zephyr/modbluetooth_zephyr.c410
-rw-r--r--ports/zephyr/mpconfigport.h11
-rw-r--r--ports/zephyr/src/zephyr_start.c4
5 files changed, 429 insertions, 1 deletions
diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt
index e30759e064..80adea5d86 100644
--- a/ports/zephyr/CMakeLists.txt
+++ b/ports/zephyr/CMakeLists.txt
@@ -40,6 +40,7 @@ set(MICROPY_SOURCE_PORT
machine_i2c.c
machine_pin.c
machine_uart.c
+ modbluetooth_zephyr.c
modmachine.c
moduos.c
modusocket.c
diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c
index 7172f37824..38285ad944 100644
--- a/ports/zephyr/main.c
+++ b/ports/zephyr/main.c
@@ -49,6 +49,7 @@
#include "py/stackctrl.h"
#include "lib/utils/pyexec.h"
#include "lib/mp-readline/readline.h"
+#include "extmod/modbluetooth.h"
#if MICROPY_VFS
#include "extmod/vfs.h"
@@ -168,6 +169,9 @@ soft_reset:
printf("soft reboot\n");
+ #if MICROPY_PY_BLUETOOTH
+ mp_bluetooth_deinit();
+ #endif
#if MICROPY_PY_MACHINE
machine_pin_deinit();
#endif
diff --git a/ports/zephyr/modbluetooth_zephyr.c b/ports/zephyr/modbluetooth_zephyr.c
new file mode 100644
index 0000000000..6b3a10d471
--- /dev/null
+++ b/ports/zephyr/modbluetooth_zephyr.c
@@ -0,0 +1,410 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019-2021 Damien P. George
+ * Copyright (c) 2019-2020 Jim Mussared
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "py/runtime.h"
+#include "py/mperrno.h"
+#include "py/mphal.h"
+
+#if MICROPY_PY_BLUETOOTH
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include "extmod/modbluetooth.h"
+
+#define DEBUG_printf(...) // printk("BLE: " __VA_ARGS__)
+
+#define BLE_HCI_SCAN_ITVL_MIN 0x10
+#define BLE_HCI_SCAN_ITVL_MAX 0xffff
+#define BLE_HCI_SCAN_WINDOW_MIN 0x10
+#define BLE_HCI_SCAN_WINDOW_MAX 0xffff
+
+#define ERRNO_BLUETOOTH_NOT_ACTIVE MP_ENODEV
+
+enum {
+ MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF,
+ MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE,
+ MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED,
+};
+
+enum {
+ MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE,
+ MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_DEACTIVATING,
+ MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE,
+};
+
+typedef struct _mp_bluetooth_zephyr_root_pointers_t {
+ // Characteristic (and descriptor) value storage.
+ mp_gatts_db_t gatts_db;
+} mp_bluetooth_zephyr_root_pointers_t;
+
+STATIC int mp_bluetooth_zephyr_ble_state;
+
+#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
+STATIC int mp_bluetooth_zephyr_gap_scan_state;
+STATIC struct k_timer mp_bluetooth_zephyr_gap_scan_timer;
+STATIC struct bt_le_scan_cb mp_bluetooth_zephyr_gap_scan_cb_struct;
+#endif
+
+STATIC int bt_err_to_errno(int err) {
+ // Zephyr uses errno codes directly, but they are negative.
+ return -err;
+}
+
+// modbluetooth (and the layers above it) work in BE for addresses, Zephyr works in LE.
+STATIC void reverse_addr_byte_order(uint8_t *addr_out, const bt_addr_le_t *addr_in) {
+ for (int i = 0; i < 6; ++i) {
+ addr_out[i] = addr_in->a.val[5 - i];
+ }
+}
+
+#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
+void gap_scan_cb_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf) {
+ DEBUG_printf("gap_scan_cb_recv: adv_type=%d\n", info->adv_type);
+
+ if (!mp_bluetooth_is_active()) {
+ return;
+ }
+
+ if (mp_bluetooth_zephyr_gap_scan_state != MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE) {
+ return;
+ }
+
+ uint8_t addr[6];
+ reverse_addr_byte_order(addr, info->addr);
+ mp_bluetooth_gap_on_scan_result(info->addr->type, addr, info->adv_type, info->rssi, buf->data, buf->len);
+}
+
+STATIC mp_obj_t gap_scan_stop(mp_obj_t unused) {
+ (void)unused;
+ mp_bluetooth_gap_scan_stop();
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(gap_scan_stop_obj, gap_scan_stop);
+
+void gap_scan_cb_timeout(struct k_timer *timer_id) {
+ DEBUG_printf("gap_scan_cb_timeout\n");
+ // Cannot call bt_le_scan_stop from a timer callback because this callback may be
+ // preempting the BT stack. So schedule it to be called from the main thread.
+ while (!mp_sched_schedule(MP_OBJ_FROM_PTR(&gap_scan_stop_obj), mp_const_none)) {
+ k_yield();
+ }
+ // Indicate scanning has stopped so that no more scan result events are generated
+ // (they may still come in until bt_le_scan_stop is called by gap_scan_stop).
+ mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_DEACTIVATING;
+}
+#endif
+
+int mp_bluetooth_init(void) {
+ DEBUG_printf("mp_bluetooth_init\n");
+
+ // Clean up if necessary.
+ mp_bluetooth_deinit();
+
+ // Allocate memory for state.
+ MP_STATE_PORT(bluetooth_zephyr_root_pointers) = m_new0(mp_bluetooth_zephyr_root_pointers_t, 1);
+ mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db);
+
+ #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
+ mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE;
+ k_timer_init(&mp_bluetooth_zephyr_gap_scan_timer, gap_scan_cb_timeout, NULL);
+ mp_bluetooth_zephyr_gap_scan_cb_struct.recv = gap_scan_cb_recv;
+ mp_bluetooth_zephyr_gap_scan_cb_struct.timeout = NULL; // currently not implemented in Zephyr
+ bt_le_scan_cb_register(&mp_bluetooth_zephyr_gap_scan_cb_struct);
+ #endif
+
+ if (mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF) {
+ // bt_enable can only be called once.
+ int ret = bt_enable(NULL);
+ if (ret) {
+ return bt_err_to_errno(ret);
+ }
+ }
+
+ mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE;
+
+ DEBUG_printf("mp_bluetooth_init: ready\n");
+
+ return 0;
+}
+
+void mp_bluetooth_deinit(void) {
+ DEBUG_printf("mp_bluetooth_deinit %d\n", mp_bluetooth_zephyr_ble_state);
+ if (mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF
+ || mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED) {
+ return;
+ }
+
+ mp_bluetooth_gap_advertise_stop();
+
+ #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
+ mp_bluetooth_gap_scan_stop();
+ bt_le_scan_cb_unregister(&mp_bluetooth_zephyr_gap_scan_cb_struct);
+ #endif
+
+ // There is no way to turn off the BT stack in Zephyr, so just set the
+ // state as suspended so it can be correctly reactivated later.
+ mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED;
+
+ MP_STATE_PORT(bluetooth_zephyr_root_pointers) = NULL;
+}
+
+bool mp_bluetooth_is_active(void) {
+ return mp_bluetooth_zephyr_ble_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE;
+}
+
+void mp_bluetooth_get_current_address(uint8_t *addr_type, uint8_t *addr) {
+ if (!mp_bluetooth_is_active()) {
+ mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE);
+ }
+ bt_addr_le_t le_addr;
+ size_t count = 1;
+ bt_id_get(&le_addr, &count);
+ if (count == 0) {
+ mp_raise_OSError(EIO);
+ }
+ reverse_addr_byte_order(addr, &le_addr);
+ *addr_type = le_addr.type;
+}
+
+void mp_bluetooth_set_address_mode(uint8_t addr_mode) {
+ // TODO: implement
+}
+
+size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) {
+ const char *name = bt_get_name();
+ *buf = (const uint8_t *)name;
+ return strlen(name);
+}
+
+int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len) {
+ char tmp_buf[CONFIG_BT_DEVICE_NAME_MAX + 1];
+ if (len + 1 > sizeof(tmp_buf)) {
+ return MP_EINVAL;
+ }
+ memcpy(tmp_buf, buf, len);
+ tmp_buf[len] = '\0';
+ return bt_err_to_errno(bt_set_name(tmp_buf));
+}
+
+// Zephyr takes advertising/scan data as an array of (type, len, payload) packets,
+// and this function constructs such an array from raw advertising/scan data.
+STATIC void mp_bluetooth_prepare_bt_data(const uint8_t *data, size_t len, struct bt_data *bt_data, size_t *bt_len) {
+ size_t i = 0;
+ const uint8_t *d = data;
+ while (d < data + len && i < *bt_len) {
+ bt_data[i].type = d[1];
+ bt_data[i].data_len = d[0] - 1;
+ bt_data[i].data = &d[2];
+ i += 1;
+ d += 1 + d[0];
+ }
+ *bt_len = i;
+}
+
+int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+
+ mp_bluetooth_gap_advertise_stop();
+
+ struct bt_data bt_ad_data[8];
+ size_t bt_ad_len = 0;
+ if (adv_data) {
+ bt_ad_len = MP_ARRAY_SIZE(bt_ad_data);
+ mp_bluetooth_prepare_bt_data(adv_data, adv_data_len, bt_ad_data, &bt_ad_len);
+ }
+
+ struct bt_data bt_sd_data[8];
+ size_t bt_sd_len = 0;
+ if (sr_data) {
+ bt_sd_len = MP_ARRAY_SIZE(bt_sd_data);
+ mp_bluetooth_prepare_bt_data(sr_data, sr_data_len, bt_sd_data, &bt_sd_len);
+ }
+
+ struct bt_le_adv_param param = {
+ .id = 0,
+ .sid = 0,
+ .secondary_max_skip = 0,
+ .options = (connectable ? BT_LE_ADV_OPT_CONNECTABLE : 0)
+ | BT_LE_ADV_OPT_ONE_TIME
+ | BT_LE_ADV_OPT_USE_IDENTITY
+ | BT_LE_ADV_OPT_SCANNABLE,
+ .interval_min = interval_us / 625,
+ .interval_max = interval_us / 625 + 1, // min/max cannot be the same value
+ .peer = NULL,
+ };
+
+ return bt_err_to_errno(bt_le_adv_start(&param, bt_ad_data, bt_ad_len, bt_sd_data, bt_sd_len));
+}
+
+void mp_bluetooth_gap_advertise_stop(void) {
+ // Note: bt_le_adv_stop returns 0 if adv is already stopped.
+ int ret = bt_le_adv_stop();
+ if (ret != 0) {
+ mp_raise_OSError(bt_err_to_errno(ret));
+ }
+}
+
+int mp_bluetooth_gatts_register_service_begin(bool append) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+
+ if (append) {
+ // Don't support append yet (modbluetooth.c doesn't support it yet anyway).
+ return MP_EOPNOTSUPP;
+ }
+
+ // Reset the gatt characteristic value db.
+ mp_bluetooth_gatts_db_reset(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db);
+
+ return MP_EOPNOTSUPP;
+}
+
+int mp_bluetooth_gatts_register_service_end(void) {
+ return MP_EOPNOTSUPP;
+}
+
+int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint16_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint16_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) {
+ return MP_EOPNOTSUPP;
+}
+
+int mp_bluetooth_gap_disconnect(uint16_t conn_handle) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ return MP_EOPNOTSUPP;
+}
+
+int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ return mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len);
+}
+
+int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->gatts_db, value_handle, value, value_len);
+}
+
+int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ return MP_EOPNOTSUPP;
+}
+
+int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ return MP_EOPNOTSUPP;
+}
+
+int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ return MP_EOPNOTSUPP;
+}
+
+int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ return MP_EOPNOTSUPP;
+}
+
+int mp_bluetooth_get_preferred_mtu(void) {
+ if (!mp_bluetooth_is_active()) {
+ mp_raise_OSError(ERRNO_BLUETOOTH_NOT_ACTIVE);
+ }
+ mp_raise_OSError(MP_EOPNOTSUPP);
+}
+
+int mp_bluetooth_set_preferred_mtu(uint16_t mtu) {
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ return MP_EOPNOTSUPP;
+}
+
+#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
+
+int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us, bool active_scan) {
+ // Stop any ongoing GAP scan.
+ int ret = mp_bluetooth_gap_scan_stop();
+ if (ret) {
+ return ret;
+ }
+
+ struct bt_le_scan_param param = {
+ .type = active_scan ? BT_HCI_LE_SCAN_ACTIVE : BT_HCI_LE_SCAN_PASSIVE,
+ .options = BT_LE_SCAN_OPT_NONE,
+ .interval = MAX(BLE_HCI_SCAN_ITVL_MIN, MIN(BLE_HCI_SCAN_ITVL_MAX, interval_us / 625)),
+ .window = MAX(BLE_HCI_SCAN_WINDOW_MIN, MIN(BLE_HCI_SCAN_WINDOW_MAX, window_us / 625)),
+ };
+ k_timer_start(&mp_bluetooth_zephyr_gap_scan_timer, K_MSEC(duration_ms), K_NO_WAIT);
+ mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_ACTIVE;
+ int err = bt_le_scan_start(&param, NULL);
+ return bt_err_to_errno(err);
+}
+
+int mp_bluetooth_gap_scan_stop(void) {
+ DEBUG_printf("mp_bluetooth_gap_scan_stop\n");
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ if (mp_bluetooth_zephyr_gap_scan_state == MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE) {
+ // Already stopped.
+ return 0;
+ }
+ mp_bluetooth_zephyr_gap_scan_state = MP_BLUETOOTH_ZEPHYR_GAP_SCAN_STATE_INACTIVE;
+ k_timer_stop(&mp_bluetooth_zephyr_gap_scan_timer);
+ int err = bt_le_scan_stop();
+ if (err == 0) {
+ mp_bluetooth_gap_on_scan_complete();
+ return 0;
+ }
+ return bt_err_to_errno(err);
+}
+
+int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) {
+ DEBUG_printf("mp_bluetooth_gap_peripheral_connect\n");
+ if (!mp_bluetooth_is_active()) {
+ return ERRNO_BLUETOOTH_NOT_ACTIVE;
+ }
+ return MP_EOPNOTSUPP;
+}
+
+#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
+
+#endif // MICROPY_PY_BLUETOOTH
diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h
index 162ac8a576..c7f4653003 100644
--- a/ports/zephyr/mpconfigport.h
+++ b/ports/zephyr/mpconfigport.h
@@ -52,6 +52,7 @@
#define MICROPY_PY_BUILTINS_REVERSED (0)
#define MICROPY_PY_BUILTINS_SET (0)
#define MICROPY_PY_BUILTINS_STR_COUNT (0)
+#define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
#define MICROPY_PY_BUILTINS_HELP (1)
#define MICROPY_PY_BUILTINS_HELP_TEXT zephyr_help_text
#define MICROPY_PY_ARRAY (0)
@@ -69,6 +70,13 @@
#define MICROPY_PY_UERRNO (1)
#define MICROPY_PY_USOCKET (1)
#endif
+#ifdef CONFIG_BT
+#define MICROPY_PY_BLUETOOTH (1)
+#ifdef CONFIG_BT_CENTRAL
+#define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1)
+#endif
+#define MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT (0)
+#endif
#define MICROPY_PY_UBINASCII (1)
#define MICROPY_PY_UHASHLIB (1)
#define MICROPY_PY_UOS (1)
@@ -123,7 +131,8 @@ typedef long mp_off_t;
#define MICROPY_PORT_ROOT_POINTERS \
const char *readline_hist[8]; \
- void *machine_pin_irq_list; /* Linked list of pin irq objects */
+ void *machine_pin_irq_list; /* Linked list of pin irq objects */ \
+ struct _mp_bluetooth_zephyr_root_pointers_t *bluetooth_zephyr_root_pointers;
extern const struct _mp_obj_module_t mp_module_machine;
extern const struct _mp_obj_module_t mp_module_time;
diff --git a/ports/zephyr/src/zephyr_start.c b/ports/zephyr/src/zephyr_start.c
index 2e0993451b..62ee922a86 100644
--- a/ports/zephyr/src/zephyr_start.c
+++ b/ports/zephyr/src/zephyr_start.c
@@ -36,4 +36,8 @@ void main(void) {
zephyr_getchar_init();
#endif
real_main();
+
+ // This is needed so the linker includes k_timer_init, z_impl_k_timer_start
+ // and z_impl_k_timer_stop, as used by libmicropython.a.
+ k_timer_start(NULL, K_MSEC(0), K_MSEC(0));
}