1 整体介绍主要特点就是软硬件开源并且使用了Zephyr系统。ZSWatch名字来自它使用的 Zephyr RTOS。项目包含手表硬件设计、固件和开发文档手表采用 Nordic nRF5340 芯片配有圆形触摸屏、蓝牙和多种传感器固件基于 Zephyr 和 LVGL也支持开发自定义应用。项目主仓库采用 GPL-3.0 许可证。硬件规格列表nRF5340 BLE chip (u-blox NORA-B10 module)128 MHz Dual core512 KB RAM1 MB Flash30 MHz SPI for display240x240 round display with touch screenIMU Bosch BMI270 for gesture navigation, wrist-wake, step countingBosch BMP581 High-performance pressure sensor (~20 cm accuracy)ST LIS2MDLTR MagnetometerMacronix MX25U51245GZ4I00 64 MB external flashBroadcom APDS-9306-065 Light Sensor for automatic brightness controlMicro Crystal RV-8263-C8 RTC for time keeping and alarm functionsKnowles SPK0641HT4H-1 I2S microphone for audio recordingNordic nPM1300 PMIC for power and system managementSpeaker (Requires HR Extension Board)硬件框图可以看出使用了一个nRF53的SoM关于SoM可以参考我之前的文章SoM的理解_soc和som-CSDN博客。搭配了非常多外围Sensor。总的来说这个项目用来学习ZephyrBLE传感器音频流和低功耗感觉还是挺不错的。。。2 Zephyr架构2.1 目录结构在代码中也创建了给AI的md文件主要内容如下 app/ # Main application (west manifest repo) ├── CMakeLists.txt # Top-level CMake ├── Kconfig # Top-level Kconfig ├── prj.conf # Main project configuration ├── west.yml # West manifest (NCS v3.1.0) ├── boards/ # Board definitions and overlays │ └── zswatch/ # ZSWatch board definitions │ ├── watchdk/ # Watch DevKit (current board) │ └── zswatch_legacy/ # Legacy hardware (v4/v5) ├── src/ │ ├── main.c # Application entry point │ ├── applications/ # All watch apps (self-registering) │ ├── managers/ # System managers (app, power, notification, etc.) │ ├── events/ # Zbus event definitions and channels │ ├── ble/ # BLE services (GadgetBridge, AMS, ANCS, CTS, etc.) │ ├── sensors/ # Sensor drivers and abstractions │ ├── sensor_fusion/ # Sensor fusion algorithms │ ├── ui/ # UI components (app picker, popups, watchfaces) │ ├── drivers/ # Custom Zephyr drivers │ ├── images/ # Image assets │ └── fuel_gauge/ # Battery fuel gauge ├── lvgl_editor/ # LVGL Editor integration (XML-based UI design) ├── drivers/ # Out-of-tree Zephyr drivers └── dts/ # Device tree overlays and bindings website/ # zswatch.dev documentation website (Docusaurus 3.7) ├── docusaurus.config.js # Site config (URL, navbar, footer, plugins) ├── sidebars.js # Sidebar auto-generated from docs/ structure ├── package.json # Node.js dependencies ├── tailwind.config.js # Tailwind CSS configuration ├── docs/ # Documentation pages (Markdown/MDX) │ ├── intro.md │ ├── getting-started/ # Quickstart, hardware, software setup │ ├── development/ # Compiling, toolchain, image resources, Linux dev │ ├── hardware-design/ # PCB design documentation │ └── hw-testing/ # Hardware testing procedures ├── blog/ # Blog posts ├── src/ │ ├── components/ # Custom React components │ │ ├── FirmwareUpdate/ # Web-based firmware update component │ │ ├── GithubNavbar/ # GitHub stars navbar widget │ │ ├── HomepageFeatures/ │ │ └── SubscriptionForm/ │ ├── pages/ # Custom pages (index, update, etc.) │ ├── theme/ # Docusaurus theme overrides │ └── css/ # Custom CSS (Tailwind) └── static/ # Static assets (images, favicon, etc.) 主要的开发内容如下可以看到基本上都是app下面。目录体积作用app/99M主固件工程也是 west 的 manifest 仓库west.yml在这里。下面单独展开hardware/680M4 个 KiCad 硬件子模块Watch-HW手表 PCB、Watch-DevKit-HW开发板、Heartrate-HW心率/音频扩展板、Dock-HW调试扩展坞。跟固件开发基本无关website/64MDocusaurus 站点 zswatch.dev 的源码docs/、blog/含 mcumgr-web 子模块网页版 OTA 工具resources/1.3MLogo SVG、硬件总览图、配色palette.pdf、ZSWatch.drawioboards/完整的 BSP板级支持包app/drivers/实现了 displaygc9a01 圆屏、inputnPM1300 按键、sensorapds9306 光感 / bmi270 / bmp581、audiodmic 模拟器。这些进的是 zephyr_library()提供标准子系统 APIdisplay_blanking_on()、sensor_sample_fetch()…。board内容如下文件作用zswatch_nrf5340_cpuapp.dts/_ns.dts/_cpunet.dts*_common.dtsi*-pinctrl.dtsi设备树描述硬件连接、引脚复用pinctrl、分区*_defconfig板级默认 Kconfig开 TrustZone、MPU、关串口 console…board.yml/*.yamlKconfig/Kconfig.defconfig/Kconfig.zswatchhwmv2 板元数据 板级 Kconfig 选项board.cmake烧录器配置J-Link 的--devicenrf5340_xxaa_app、nrfjprog 的外部 flash inipre_dt_board.cmake编译设备树之前的特殊处理这里用来压制地址冲突告警support/nrf5340_cpunet_reset.c外部 flash 编程参数、网络核复位代码还有一个driversapp/src/drivers/ 它是板级适配/封装层就是普通 C 函数它标准驱动 API。直接编进 api。2.2 代码架构主要的代码是在src中包含了下面五类应用applications/30 手表 App中间件/服务managers/电源/通知/应用管理、events/zbus 频道、ui/UI 控制器与组件、filesystem/、history/、sensor_fusion/领域封装sensors/把 Zephyr sensor API 包成手表语义如zsw_imu_fetch_num_steps()、codec/、fuel_gauge/、ble/板级适配src/drivers/见上基础设施 资源根目录的zsw_clock/settings/alarm/coredump/wdt/cpu_freq/retained_ram以及ext_3 ZBus3.1 Zbus定义zbus 是 Zephyr自带的消息总线不是第三方库ZSWatch 把它用成了整个固件的神经系统。先看它最重要的一条性质——一对多广播发布者完全不认识订阅者zbus 是什么它是 Zephyr 内核自带的发布-订阅消息总线CONFIG_ZBUSy核心就两个概念Channel频道一个带类型的消息槽比如struct accel_event类型的accel_data_chanObserver观察者订阅频道的实体分两种 ——Listener同步回调在发布者的上下文里立刻执行和Subscriber有自己的消息队列异步慢慢处理在ZSWatch项目只用了Listener搜不到任何ZBUS_SUBSCRIBER_DEFINE因为手表事件大多是轻处理 立刻响应。下面是在Zephyr框架中标准的ZBus用法。#include stdio.h #include zephyr/kernel.h #include zephyr/zbus/zbus.h /* 1) 消息类型频道里传的东西 */ struct sensor_data { int32_t value; }; /* 2) Listener 回调同步执行跑在发布者的上下文里 */ static void listener_cb(const struct zbus_channel *chan) { const struct sensor_data *d zbus_chan_const_msg(chan); printf( [listener] %s - value%d\n, chan-name, d-value); } /* 3) 另一个 Listener运行期间再挂上去 */ static void late_listener_cb(const struct zbus_channel *chan) { const struct sensor_data *d zbus_chan_const_msg(chan); printf( [late] %s - value%d\n, chan-name, d-value); } /* 4) 定义观察者两个 Listener 一个 Subscriber队列长度 4 */ ZBUS_LISTENER_DEFINE(my_listener, listener_cb); ZBUS_LISTENER_DEFINE(my_late_listener, late_listener_cb); ZBUS_SUBSCRIBER_DEFINE(my_subscriber, 4); /* 5) Subscriber 是异步的需要自己的线程去取消息 */ static void subscriber_thread(void *a, void *b, void *c) { const struct sensor_data data; const struct zbus_channel *chan; while (zbus_sub_wait(my_subscriber, chan, K_FOREVER) 0) { zbus_chan_read(chan, data, K_MSEC(100)); printf( [subscriber] %s - value%d\n, chan-name, data.value); } } K_THREAD_DEFINE(sub_tid, 2048, subscriber_thread, NULL, NULL, NULL, 5, 0, 0); /* 6) 频道定义编译期静态绑定 my_listener 和 my_subscriber */ ZBUS_CHAN_DEFINE(sensor_chan, struct sensor_data, NULL, NULL, ZBUS_OBSERVERS(my_listener, my_subscriber), ZBUS_MSG_INIT(0)); int main(void) { struct sensor_data d {0}; printf( 1. 基础发布1 listener 1 subscriber\n); for (int i 1; i 3; i) { d.value i * 10; printf([publisher] pub %d\n, d.value); zbus_chan_pub(sensor_chan, d, K_MSEC(100)); k_msleep(50); } printf( 2. 运行时追加观察者 \n); zbus_chan_add_obs(sensor_chan, my_late_listener, K_MSEC(100)); d.value 99; printf([publisher] pub %d\n, d.value); zbus_chan_pub(sensor_chan, d, K_MSEC(100)); printf( 3. 主动拉取当前值不等事件\n); struct sensor_data cur; zbus_chan_read(sensor_chan, cur, K_MSEC(100)); printf([pull] current value %d\n, cur.value); printf( 4. 移除运行时观察者 \n); zbus_chan_rm_obs(sensor_chan, my_late_listener, K_MSEC(100)); d.value 123; printf([publisher] pub %d\n, d.value); zbus_chan_pub(sensor_chan, d, K_MSEC(100)); k_msleep(200); printf( done \n); return 0; }3.2 ZSWatch的用法所有频道集中在src/events/一个目录一频道一对 .c/.h频道承载典型订阅者accel_data_chan抬手/计步/手势/静止表盘、电源管理activity_state_data_chan手表活动状态三态表盘、PMIC、应用管理器ble_comm_data_chan手机发来的数据通知管理、时间同步、表盘、音乐4 个battery_sample_data_chan电量采样表盘、电量 App、手机同步zsw_notification_mgr_chan通知到达/移除通知 App、mainperiodic_event_1s/100ms/10s_chan周期心跳时钟、传感器轮询、秒表light/pressure/magnetometer_data_chan传感器数据按需订阅music_control_data_chan音乐控制Android / iOS 各一个实现下面一个磁力的定义app/src/events/magnetometer_event.h#include sensors/zsw_magnetometer.h struct magnetometer_event { float x; float y; float z; };app/src/events/magnetometer_event.c#include zephyr/zbus/zbus.h #include magnetometer_event.h ZBUS_CHAN_DEFINE(magnetometer_data_chan, struct magnetometer_event, NULL, NULL, ZBUS_OBSERVERS_EMPTY, ZBUS_MSG_INIT() );3.3 一个例子以「抬手亮屏」IMU 中断 → zbus 事件 → 电源管理 → 显示控制 → 屏幕驱动。来说明整个事件流程。前半段硬件中断 → 统一事件结构 → zbus 广播。后半段电源管理器收到后做决策并且只在息屏状态下才响应避免重复亮屏。可以看出使用ZBus之后就将事件的产生和使用分开了。。4 BLE4.1 BLE框架nRF5340 是双核所以 HCI 不走串口也不走 USB而是走核间 IPC。网络核跑 Nordic 的 SoftDevice Controller应用核跑完整的Zephyr BLE Host。代码中涉及到BLE的大概是这些代码。组织如下层文件职责传输层ble_transport.c96 行只做一件事NUS 服务Nordic UART Service6e400001-...。一个 RXwrite-without-rsp 一个 TXnotify CCC协议层gadgetbridge/1377 行、chronos/906 行两套并存的手机 App 协议各自解析服务与客户端层文件作用ble_ancs.c/.hiOS Apple 通知中心服务客户端。手表反过来做 GATT Client 连 iPhone用bt_gatt_dm做服务发现订阅 Notification Source / Data Source解析出标题、正文、App 名后zbus_chan_pub(ble_comm_data_chan)。带发现失败重试K_WORK_DELAYABLEble_ams.c/.hApple 媒体服务客户端。订阅 iOS 的曲目/播放状态同时反向订阅music_control_data_chan—— 手表上点播放/切歌时通过 AMS 写回 iPhoneble_cts.c/.hCurrent Time Service 客户端。连上 iPhone 后读一次当前时间含时区偏移用于自动对时也订阅时间变化通知zsw_gatt_sensor_server.c/.h自定义传感器 GATT 服务。温度/湿度/气压/加速度/陀螺仪/磁力计/光照/传感器融合各一个独立服务UUID 用Adafruit 的ADAFxxxx这样 Adafruit Bluefruit 生态的 App 能直连读数据。有意思的是它不订阅传感器频道而是挂periodic_fast心跳按notify_period_counter分频、且只在客户端订阅了 CCC 时才发通知bt_gatt_is_subscribed检查——没人听就完全不占带宽连接与工具文件作用ble_comm.c/.hBLE 子系统的中枢。ble_comm.h定义了整个模块的通用语言ble_comm_data_type_t枚举notify/set_time/weather/music/http/gps… 各种载荷结构体 联合体ble_comm_cb_data_t。.c负责连接生命周期BT_CONN_CB_DEFINE、配对鉴权bt_conn_auth_cb、广播快慢切换、连接间隔切换、MTU 交换、按 MTU 分片发送。也是bt_receive_cb所在处——把一包数据同时喂给 GB 和 Chronos 两个 parserble_http.c/.hHTTP-over-BLE 代理。zsw_ble_http_get(url, cb)向手机发 HTTP 请求订阅ble_comm_data_chan等{t:http}的应答带超时ble_http_timeout_handler。手表本身没有 Wi-Fi上网全靠这个ble_log_backend.c/.hZephyr 日志后端。注册成struct log_backend把LOG_*输出经 BLE 发给手机配合官方 App 的开发者工具。连上/断开时自动启停可ble_log_backend_set_enabled()开关ble_hid.c/.hHID over GATTHOGP。让手表变成蓝牙键盘目前只实现ble_hid_next()/ble_hid_previous()——给PPT 翻页遥控器应用用。条件编译只有开了CONFIG_APPLICATIONS_USE_PPT_REMOTE才编进去ble_aoa.c/.h到达角AoA定位广播。用 BLE 5.1 的 CTEConstant Tone Extension发定向广播让外部定位基站算出手表方位。代码版权是u-blox 2022Apache 2.0用的 Eddystone NINA-B4TAG命名空间 —— 明显是从 u-blox 的 u-connectLocate 方案移植来的。注意整个文件包在#ifdef CONFIG_BT_DF里默认没开有意思的有三个苹果的服务手表做蓝牙键盘还有一个蓝牙定位。。。空了看看。4.2 蓝牙的流程蓝牙处理的流程如下启动与广播main.c调用bt_enable()加载蓝牙设置后执行ble_comm_init()后者注册连接与配对回调、初始化传输服务并开始 BLE 广播。默认广播间隔较慢建立连接后会先请求较短的连接间隔数秒后再放慢以节省功耗。位置app/src/main.c:176-194、app/src/ble/ble_comm.c:166-180, 350-425手机与手表之间的通用数据通道ble_transport.c定义了与 Nordic UART Service 兼容的 128 位 UUID手机向 RX 特征写入数据手表通过 TX 特征的通知发数据。连接后会协商 MTU再按协商出的最大负载长度拆分发送。位置app/src/ble/ble_transport.h、app/src/ble/ble_transport.c:29-95、app/src/ble/ble_comm.c:157-180, 350-375应用协议和事件分发收到的字节会同时交给 Gadgetbridge 和 Chronos 解析。Gadgetbridge 使用GB({...})消息框架支持通知、天气、音乐状态、HTTP、GPS 等消息解析出的数据通过 Zephyr zbus 事件通道分发给通知管理器、音乐界面等模块。位置app/src/ble/ble_comm.c:469-476、app/src/ble/gadgetbridge/ble_gadgetbridge.c:1014-1193、app/src/events/ble_event.c传感器 GATT 服务代码还暴露温度、加速度、湿度、气压、磁力计、陀螺仪、融合姿态和光照等服务。客户端订阅 CCC 通知后手表启动周期任务大约每200 ms推送一次传感器数据取消订阅后会停掉相关任务和传感器功能。位置app/src/ble/zsw_gatt_sensor_server.c:65-150, 258-388配对与权限app/prj.conf启用了 SMP、蓝牙设置持久化并把最大连接数设为 1。默认 GATT 特征要求链路加密项目也提供调试配置可以关闭配对加密要求。配对确认默认拒绝代码可切换为允许配对。位置app/prj.conf:171-210、app/src/ble/ble_comm.c:130-164、app/src/ble/ble_transport.c:45-704.3 Gadgetbridge和chronos这里说一下Gadgetbridge和chronos。基本上就是为了白嫖两个app。。。Gadgetbridge开源安卓手表 Appgadgetbridge.org上的 FOSS Android 应用F-Droid 可装。不过这个库只有安卓的版本。ChronosDIY ESP32 手表生态的 Appchronos.ke 的 Chronos 手机 AppAndroid iOS 都有原本配套 ChronosESP32 这个 Arduino 库 —— 给爱好者用 ESP32 自己做智能手表用的。后面官方除了app之后目前是这样的通道平台定位ZSWatch 官方 AppFlutterAndroid iOS功能最全DFU 升级、LVGL 资源上传、HTTP 代理、GPS 中继、日志/传感器开发工具GadgetBridge仅 Android开源无云官网列为推荐后台连接最稳ChronosAndroid iOS第三方生态兼容早期主力ANCS / AMS仅 iOS通知和音乐根本不需要 App系统原生为了兼容Gadgetbridge和chronos所以直接在BLE的代码中做了支持。本质可以参考之前的文章蓝牙学习3简易BLE GATT-CSDN博客就像这里的代码1是开灯0是关灯。这里的这两个其实是一样。。。5 Audio5.1 整体流程这里绿色是录音链橙色是播放链灰色是共享存储。录音管线麦克风 → Opus → 文件系统播放管线I2S → 编解码器 → 扬声器。5.2 录音流水线代码在 app/src/ 下共 12 个文件、约 2400 行另外还有一个出树驱动 app/drivers/audio/dmic_emul.c。5.2.1 MICMIC是楼氏KnowlesSiSonic™ 系列的一颗高端数字 MEMS 麦克风完整型号是 SPK0641HT4H-1输出是单 bit PDM。它是 数字 PDM 麦不是 I2S 麦—— WM8960/MAX98357A 这类 I2S Codec 一般不能直接接它。要接它主控必须有PDM 输入通道很多 SoC 自带。没有寄存器、没有 I2C/SPI 地址、没有中断脚、没有 GPIO。既然没有任何东西可读写它在 Zephyr 的驱动模型里就不是一个设备——设备树里没有节点自然也不需要驱动。对外只有两根信号线MCU 给它一个时钟1–3.25 MHz它回一串 1-bit 的脉冲密度流密度代表声压只在DTS有个描述nrf5340_cpuapp_common-pinctrl.dtsi。pdm0_default: pdm0_default { group1 { psels NRF_PSEL(PDM_CLK, 0, 25), // P0.25 出时钟给麦克风 NRF_PSEL(PDM_DIN, 0, 26); // P0.26 收 1-bit 数据流 }; };5.2.2 采样与抽取代码在zephyr/drivers/audio/dmic_nrfx_pdm.cDT_DRV_COMPAT nordic_nrf_pdm。这是 SoC 的 PDM 控制器驱动不是 SPK0641 的驱动。它基于 nrfx_pdm用 nRF5340 内部的硬件抽取滤波器把 1-bit PDM 流变成 16-bit PCM。整体还有一个src/drivers/zsw_microphone.c。做三件事上电、配置 PDM、起线程取数据。用 Zephyr 标准 dmic_configure() / dmic_trigger() / dmic_read()然后自己起一个线程1024 栈循环读while (mic_state.recording) { ret dmic_read(mic_state.mic_dev, 0, rx_block_ptr, rx_size, 100); if (ret -ENOMEM) { buffer_allocation_failures; k_msleep(10); continue; } mic_state.audio_callback(rx_block_ptr, rx_size); k_mem_slab_free(rx_mem_slab, rx_block_ptr); // 回调里必须同步用完 k_yield(); }5.2.3 录音管理器整体的录音管理调用录音的接口实现功能。代码是在zsw_recording_manager.c。实现了生产者和消费者。同时实现 Opus 编码src/codec/zsw_audio_codec.c。这里是标准 Opus 封装但有个 ZSWatch 特有的关键点——编码器跑在外部flash的XIP上。XIP eXecute In Place就地执行——CPU 直接从存储器里取指令运行不用先把代码拷贝到 RAM。最后使用了自己的文件系统将录音保存成文件放在手表闪存的 /user/recordings 目录。5.2.4 应用代码仓中还有一些录音的应用我就没有细看了。。。Mic Appapplications/mic/用kissfft做64 点实数 FFT攒满样本算一次结果经k_work_submit交给主线程更新圆形频谱 UI。FFT 在采集线程里做UI 更新切出去——因为 LVGL 不是线程安全的。Voice Memoapplications/voice_memo/录音列表、时长、电平条配 shell 命令录制/列表/删除/空间查询还有ui/overlay/zsw_quick_record.c长按按键快速录音和zsw_recording_overlay.c录音中悬浮窗。5.3 播放流水线代码是在zsw_speaker_manager.c。基本就是一个I2S的播放不过之前的硬件规格就介绍了Speaker (Requires HR Extension Board)也就说现在板载是没有硬件喇叭的。所以整个功能还是未完成的阶段暂时就不细看了。6 DisplayTODO7 Sensor之前硬件规格就说了ZSWatch硬件上有四颗传感器全挂在 i2c1。IMU Bosch BMI270 for gesture navigation, wrist-wake, step countingBosch BMP581 High-performance pressure sensor (~20 cm accuracy)ST LIS2MDLTR MagnetometerBroadcom APDS-9306-065 Light Sensor for automatic brightness control定义在app/boards/zswatch/zswatch/nrf5340_cpuapp_common.dtsi:270-306器件地址中断备注BMI2700x68GPIO0.6上升沿六轴 特性引擎swap-xyinvert-y做坐标系对齐BMP5810x47—气压 / 温度LIS2MDL0x1eGPIO1.7三轴磁力计cancel-offsetAPDS93060x52—环境光7.1 驱动BMI270app/drivers/sensor/bmi270/1999 行最重的一块。bmi270_init()会先把 8 KB 的bmi270_config_file[]在ext_drivers/BMI270-Sensor-API/bmi270.c:51烧进芯片——BMI270 内部是带特性引擎的可编程 ASIC不灌这个文件就只有裸加速度计功能。之后private/zsw_bosch_bmi270_config.c用一张feature_config_func表把 8 个特性加速度/陀螺/计步/活动识别/手势/抬手唤醒/静止/任意运动逐个配置。BMP581zswatch,bmp581项目自制。LIS2MDL / APDS9306直接用 Zephyr 上游驱动st,lis2mdl/avago,apds9306项目只补了 emul 版本给 native_sim 仿真用。7.2 应用层主要是app/src/sensors下面的代码这里主要是对传感进行一个管理同时给更上层提供接口。同时发zbus把事件推到对应频道。向下使能/挂起pm_device_action_run、配置ODR、量程、特性开关、校准磁力计 hard-iron 偏移 settings 持久化、仲裁引用计数向上同步查询zsw_imu_fetch_accel_f()这种 异步事件zbus_chan_pub7.3 传感器融合项目中使用了三个传感器但是各有各的致命伤而且伤的地方正好互补陀螺仪响应快、短期极准但积分漂移。零偏哪怕只有 0.1°/s一分钟就是 6°而且随温度变。加速度计能测重力方向所以能定 roll 和 pitch长期不漂。但两件事要命——① 任何运动加速度都叠加在上面走路摆臂、抬手、敲桌子②物理上它定不了 yaw重力始终竖直向下你绕重力轴转多少度加速度读数一模一样。磁力计能补 yaw这就是 AHRS 里那个 H Heading。但环境畸变严重。传感器的用途只用倾角的不需要磁力计、不需要融合抬手亮屏判断表盘是否转向脸垂手/转腕息屏手表是否在腕上off-wrist detection防止脱腕后计步乱计睡眠翻身检测屏幕方向切换部分 Wear OS 表需要完整姿态的指南针、地图方向箭头跌倒检测冲击波形 姿态突变漏报是要命的游泳划水识别、高尔夫挥杆分析手势控制翻腕切歌、双击3D 表盘、AR、隔空控制手机/无人机代码是src/sensor_fusion/zsw_sensor_fusion.c。使用了一个核心算法是Madgwick博士论文第 7 章的修订版 AHRS。不过代码中并未实际使用。。。Madgwick 算法是 Sebastian Madgwick 在 2011 年提出的惯性姿态解算算法用于把 IMU加速度计 陀螺仪可选磁力计融合成实时姿态四元数它用梯度下降法迭代优化四元数使陀螺仪积分出的方向响应快但随时间漂移与加速度计 / 磁力计提供的地球重力与磁场参考方向无漂移但噪声大相融合用一个可调增益 β 平衡两者权重从而同时获得快速响应和长期稳定且计算量远小于卡尔曼滤波能在普通 MCU 上实时运行因此被大量用于无人机、穿戴设备、机器人等的姿态估计AHRS/IMU。Madgwick修订版Revised AHRS, 2012是在原版算法2011基础上的官方改进版用于惯性 / 磁惯性姿态估计它保留了 陀螺仪负责快速积分、加速度计与磁力计提供无漂移参考 的融合框架但把原来单一的 β 增益换成了比例–积分PI反馈结构用可调增益kp/ki把 测量参考与实际姿态之间的误差 经比例–积分后作为修正项叠加回陀螺仪积分从而构成一个闭环、能持续抑制漂移的估计器同时它在线估计并反馈消除陀螺仪偏置bias并重新设计了磁力计的倾斜补偿处理显著改善了磁力计与加速度计融合时的偏航精度及磁场干扰下的稳健性避免原版在特定运动 / 磁场环境下四元数发散而计算量仍保持在普通 MCU 可实时运行的水平。100 Hz 自调度的k_work_delayable1000/SAMPLE_RATE_HZ - 本次耗时把 rad/s 转 deg/s、m/s² 转 g 后喂给 AHRS输出欧拉角 四元数 地系加速度。CONFIG_SENSOR_FUSION_INCLUDE_MAGNETOMETER决定走FusionAhrsUpdate()还是FusionAhrsUpdateNoMagnetometer()默认关因为磁力计未校准时会把姿态带歪。init/deinit 同样是引用计数进 Compass 开陀螺唤醒磁力计退出还原。8 其它8.1 低功耗低功耗这边这条链上最值得学的四点1 算法放在传感器里抬手/计步/手势全是 Bosch BMI270 内置 feature用 zsw_imu_feature_enable/disable()带引用计数开关MCU 可以真睡只等中断。2 XIP 和显示状态强耦合因为 LVGL 字体和不常用控件被重定位到外部 flash 上执行息屏才能关XIP省电唤醒必须第一步开 XIP —— 顺序写反就会跑飞。3 动态 CPU 频率渲染时128MHz注释解释了32MHz SPI 需要 128MHz CPU息屏降回默认。4 注释即设计文档等 100ms、延迟开背光、关 PWM 省电 —— 这些功耗优化的经验值都写在代码注释里读一遍收获很大。8.2 APP框架实现是在每个应用向应用管理器注册一个 application_t 描述其中包括名称、图标、分类以及启动、停止等回调。应用通常在启动阶段通过 SYS_INIT() 调用 zsw_app_manager_add_application() 注册。应用选择器会读取注册列表显示未隐藏的应用用户选择后管理器调用应用的启动回调退出时调用停止回调。大概是这样ZSW_LV_IMG_DECLARE(sensors_icon); // 图标来自 app/src/images/icons/ static application_t app { .name Hello, // 唯一标识shell 里 app launch Hello 用这个名字匹配 .icon ZSW_LV_IMG_USE(sensors_icon), .start_func hello_app_start, .stop_func hello_app_stop, .ui_unavailable_func hello_app_ui_unavailable, .ui_available_func hello_app_ui_available, .category ZSW_APP_CATEGORY_RANDOM, }; static int hello_app_add(void) { zsw_app_manager_add_application(app); return 0; } SYS_INIT(hello_app_add, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);基本上就是在hello_app_start里面启动hello_app_stop里面结束。ui_available/unavailabl是用来响应息屏和唤醒。目前app的接口也比较随意没有一个统一的文档。大概有这些。接口类别可用能力主要头文件应用生命周期注册 App提供启动、停止、返回键、屏幕休眠/唤醒回调设置名称、图标和分类app/src/managers/zsw_app_manager.h界面使用 LVGL 创建界面获得页面根对象和输入组显示通用弹窗或通知弹窗控制亮度、屏幕休眠和渲染app/src/ui/zsw_ui.h、app/src/drivers/zsw_display_control.h传感器读取加速度、陀螺仪、步数和运动状态读取气压、温度、光照和磁力计数据启停部分传感器功能app/src/sensors/zsw_imu.h、zsw_pressure_sensor.h、zsw_light_sensor.h、zsw_magnetometer.h系统服务查询或重置闲置计时读取、删除通知获取和设置系统时间app/src/managers/zsw_power_manager.h、zsw_notification_manager.h、app/src/zsw_clock.h事件通过 Zephyr zbus 订阅活动状态、电池、传感器、音乐和录音等异步事件也可订阅不同周期的定时事件app/src/events/、app/src/events/zsw_periodic_event.h手机通信通过 BLE 通道发送数据、查询 MTU、请求手机 GPS让手表借手机转发 HTTP GET 请求app/src/ble/ble_comm.h、app/src/ble/ble_http.h音频与录音在支持音频硬件和对应配置的固件中控制扬声器、麦克风和本地录音文件app/src/managers/zsw_speaker_manager.h、zsw_microphone_manager.h、zsw_recording_manager.h