☰
嵌入式架构学习5--Brilliant Labs Halo(TODO)
2026/10/19 7:00:05 网站建设 项目流程

1 硬件架构

目标板:Alif Balletto B1

特性规格
CPUARM Cortex-M55(Helium SIMD)
NPUArm Ethos-U55(ML 推理加速)
蓝牙BLE 5.3(LE Audio / LC3 编解码)
内存内部 ITCM/DTCM + 外部 SRAM(458KB 配置给 Halo 堆)
存储Flash(MCUboot 分区 + LittleFS)

Board 定义位于:alif/boards/arm/halo/

  • halo.dts— 设备树(外设引脚、flash 分区、SRAM 映射)
  • halo_defconfig— Kconfig 基础配置(Cortex-M55 + Ethos-U55 使能)
外设器件Zephyr别名
显示VGA020,MIPI-DSIzephyr,panel
摄像头PAG7982 + 低功耗lpcamzephyr,camera
麦克风T5838 PDM(带Always-on Audio Detect,可单独唤醒)zephyr,micphone
扬声器MAX98357A(I2S功放)zephyr,speaker
加速度计BMA580zephyr,accel
磁力计QMC6308zephyr,magn
其他唤醒按键(LPGPIO)、PWM LED、电池计量(vbat+ADC)、MRAM分区(mcuboot / image-0)—

2 软件架构

代码工作区用Zephyr West工具管理,west.yml是唯一的版本锁定文件。

构建方式:west build --sysbuild -b halo alif/applications/halo -p,

产出build/halo/zephyr/zephyr.signed.bin + MCUboot引导镜像。

目录内容来源
alif/Halo 应用主仓库(manifest 仓库,self)brilliantlabsAR/halo-firmware
zephyr/Zephyr RTOS(含 Alif 补丁,Brilliant Labs fork)brilliantlabsAR/halo-zephyr-alif
modules/hal/alif/Alif HAL(含 Halo 补丁)brilliantlabsAR/halo-hal-alif
modules/hal/cmsis/ARM CMSISalifsemi
bootloader/mcuboot/MCUboot(含 OTA 相关扩展)brilliantlabsAR/halo-mcuboot
modules/lib/matter/Matter 协议alifsemi/matter_alif
modules/drivers/dave2d/Dave2D 2D 图形加速驱动alifsemi
modules/lib/aipl/Alif 图像处理库(NPU 后端)alifsemi
modules/lib/libmpix/像素格式转换库libmpix/libmpix
modules/lib/lz4/LZ4 压缩zephyrproject-rtos

真正值得关注的只有 alif/applications/halo(应用)+ alif/modules/(4 个自研模块:halo、canvas、lua、linker)。

一切业务跑在 Zephyr 线程里:Lua 有专属 REPL 线程(优先级 7,64KB 栈),BLE 有 host 线程,传感器有 work 队列。外设靠 devicetree 描述(halo.dts),功能靠 Kconfig 裁剪(CONFIG_HALO_*),每个外设还有独立 sample(samples/halo/ 下 18 个:battery/bma580/button/max98357a/t5838/vga020/wakeup…)——看 samples 是学 Halo 外设最快的路。

主要功能模块(alif/modules/halo/src/)

文件职责
lua_runtime.cLua VM 生命周期、REPL 线程、异步事件分发(统一 Hook 机制)
ble_manager.cBLE 服务注册/断开调度、事件分发、电源管理集成
ble_lua.cLua BLE 服务(REPL 通道 + 数据 framed 队列 + 视频/音频)
ble_ota.cOTA 固件升级(SMP over BLE → MCUboot)
ble_audio.cLE Audio 流媒体(BAP Unicast,LC3)
ble_connection.c/ble_security.cBLE 连接管理与配对安全
ble_ancs.c/lua_ancs.cApple 通知中心服务(手机通知转发)
audio_aec.c声学回声消除 (AEC),FDAF 算法,状态放在 ITCM(64KB),256ms 参考环
audio_eq.c均衡器
audio_stream.c音频流管道(Mic → AEC → EQ → BLE)
battery_manager.c电池电量监控(充电检测、低电告警事件)
led_manager.cLED 呼吸灯 / 状态指示控制
file_manager.cFlash 文件系统(LittleFS)封装
mem_manager.c内部/外部 SRAM 堆管理
pm_manager.c系统电源管理(suspend/resume 策略)
watchdog_manager.c看门狗
se_mgmt.c安全元件管理(SE 固件更新)
sfxr.c音效合成器(Luaframe.soundAPI 后端)

驱动列表(alif/drivers/)

子目录驱动
audio/PDM 麦克风、I2S DAC/ADC
codec/音频编解码器
display/MIPI-DSI 显示驱动
i2s/I2S 总线驱动
input/按键输入
led_pwm/LED PWM 驱动
sensor/IMU(加速度计/陀螺仪)
video/摄像头驱动(DVP/MIPI CSI)
clock/时钟管理
regulator/电源调节器
sm/状态机驱动框架

子系统层(alif/subsys/)

子系统功能
bluetooth/Halo 专用 BLE 协议扩展
dbuf_display/双缓冲显示管理
img_assets/启动 Logo、图片资源
powermgr/细粒度电源状态机
matter/Matter 智能家居协议支持
modules/Lua 模块 autoload 支持

输入和输出

输入系统(共 7 个)

输入源对应器件 / 接口驱动与底层处理链路应用层 / Lua 映射
1. 声音输入T5838数字 PDM 麦克风 (pdm_audio)通过 PL330 DMA 搬移到 16kHz 16-bit PCM 环形缓冲;剥离前 20ms Pop 冲击音;经过 **AEC(声学回声消除)**滤除喇叭回音;支持实时压缩为LC3。frame.microphone.read()获取音频;支持AAD 硬件语音活动唤醒中断。
2. 图像输入PAG7982超低功耗摄像头 (lpcam8-bit DVP)硬件 CPI DMA 传输;由libmpix进行软件 ISP 与调色板处理;内置软件压缩为JPEG(存入 115KB 静态缓冲)。frame.camera.capture()直接拍图返回 JPEG 字节流。
3. 动作敲击BMA580三轴加速度计 (I2C0,0x18)硬件内部算法直接识别单敲 / 双敲 / 三敲手势;硬件中断触发无锁环形队列tap_ring。触发frame.imu.tap_callback('single'/'double'/'triple')。
4. 地磁罗盘QMC6308三轴磁力计 (I2C0,0x2C)驱动层定时采集 X/Y/Z 三轴磁场强度并进行坐标转换。frame.imu.direction()获取当前朝向方位角。
5. 实体按键镜腿WAKEUP微动按键 (lpgpio 1)30ms 硬件消抖;驱动状态机精确区分:单击、双击(400ms)、1s长按、2s休眠、5s配对、15s运输模式。触发frame.button.callback(...)。
6. 供电检测VBAT电量采样电路 (ADC0 Ch4 + 充电GPIO)分压采样读取电池毫伏电压并查表映射百分比;监控充电中/放电中/严重低电量状态。产生事件同步至 BLE Battery 服务或供 Lua 查询。
7. 蓝牙下行BLE RX射频接口 (UART HCI)接收控制指令(0x02重启、0x03中断、0x04VM重启等);拆包组包用户数据帧;支持标准LE Audio BAP接收。驱动 Lua REPL 控制台或触发frame.bluetooth.receive_callback()。

输出系统(共 5 个)

输出源对应器件 / 接口驱动与底层处理链路应用层 / Lua 映射
1. 微显示输出VGA020Micro-OLED 屏 (MIPI-DSI, 256×256)由CDC200控制器管理;双缓冲 Framebuffer;集成Dogica点阵字体与整数倍等比无损缩放引擎;支持LZ4 启动 Logo快速直刷。frame.display.text()、bitmap()绘制完毕后调show()经 DMA 刷屏。
2. 音频输出MAX98357AI2S D类功放 (I2S0, 32kHz)支持LC3 实时解压为 PCM;带防爆音时序保护;同时把播放的声音同步镜像拷贝至256ms AEC 参考环(用于麦克风回声对消);板载SFXR8-bit 复古音效合成器。frame.speaker.play()播放音频流;frame.sound.play()播放激光/跳跃等内置预置音效。
3. 状态指示呼吸LED(Timer3 PWM,引脚 P6.6)驱动层集成 PWM 占空比呼吸算法、脉冲闪烁与常量模式。自动绑定连接状态、充电状态与长按反馈。
4. 关机控制SM-GPIO系统电源控制器 (GPIO6.0)接收到整机关机或超低功耗运输模式(Ship Mode)指令后,拉低电平直接切断外设电源域供电。frame.system.power_off()。
5. 蓝牙上行BLE TX射频通道 (GATT Notify)LUA TX 字符通道回显 REPL 结果与print();回传电量及 OTA 升级确认包;通过LE Audio BAP上行麦克风语音。frame.bluetooth.send()发送应用业务数据。

通讯协议的官方参考文档 PROTOCOL.md

3 main

应用入口:src/main.c
核心配置:prj.conf

/* * Copyright (c) 2025 Brilliant Labs Inc. * SPDX-License-Identifier: Apache-2.0 */ #include <zephyr/kernel.h> #include <zephyr/logging/log.h> #include <zephyr/sys/printk.h> #include <zephyr/sys/reboot.h> #include <zephyr/drivers/sensor.h> #include <app_version.h> #include <halo/mem_manager.h> #include <halo/file_manager.h> #include <halo/pm_manager.h> #include <halo/led_manager.h> #include <zephyr/drivers/input/button.h> #ifdef CONFIG_SENSOR #include <halo/battery_manager.h> #endif #ifdef CONFIG_HALO_BLE_MANAGER #include <halo/ble_manager.h> #include <halo/ble_connection.h> #include <halo/ble_security.h> #ifdef CONFIG_HALO_BLE_LUA_SERVICE #include <halo/ble_lua.h> #endif #endif #ifdef CONFIG_HALO_LUA_RUNTIME #include <halo/lua_runtime.h> #endif #if defined(CONFIG_HALO_STARTUP_SOUND) || defined(CONFIG_HALO_SHUTDOWN_SOUND) #include <halo/sfxr.h> #endif #ifdef CONFIG_HALO_BOOT_LOGO #include <halo/boot_logo.h> #endif #ifdef CONFIG_HALO_WATCHDOG_MANAGER #include <halo/watchdog_manager.h> #endif #include <zephyr/drivers/regulator.h> #include <zephyr/pm/device.h> #if defined(CONFIG_BOOTLOADER_MCUBOOT) && defined(CONFIG_MCUBOOT_IMG_MANAGER) #include <zephyr/dfu/mcuboot.h> #endif #include "se_service.h" LOG_MODULE_REGISTER(halo, CONFIG_HALO_LOG_LEVEL); const char halo_banner[] = " _ _ _ \n" " | | | | | | \n" " | |__| | __ _| | ___ \n" " | __ |/ _` | |/ _ \\ \n" " | | | | (_| | | (_) |\n" " |_| |_|\\__,_|_|\\___/ \n" " Copyright (C) 2025 Brilliant Labs\n" " https://brilliant.xyz/\n"; /** * @brief Main application entry point * * Initializes all core subsystems and starts the Lua runtime. * The system is then driven by Lua scripts and BLE events. */ int main(void) { int ret; #ifdef CONFIG_HALO_WATCHDOG_MANAGER if (halo_watchdog_has_fired()) { // Reboot on watchdog timeout, make sure everything is fresh #ifdef CONFIG_HALO_BLE_MANAGER halo_ble_conn_prepare_reboot(); #endif sys_reboot(SYS_REBOOT_COLD); } #endif #ifdef CONFIG_HALO_LOG_LEVEL_DBG printk("\n%s", halo_banner); printk("Hardware Version: %s\n", CONFIG_BOARD); printk("Firmware Version: %s\n", APP_VERSION_STRING); printk("Build Date: %s\n", __DATE__); #endif #ifdef CONFIG_HALO_PM_MANAGER /* Initialize power manager */ ret = halo_pm_init(); if (ret < 0) { __ASSERT(false, "Failed to initialize power manager: %d", ret); return ret; } #endif #ifdef CONFIG_HALO_MEM_MANAGER /* Initialize memory manager */ ret = halo_mem_init(); if (ret < 0) { __ASSERT(false, "Failed to initialize memory manager: %d", ret); return ret; } #endif #ifdef CONFIG_HALO_WATCHDOG_MANAGER /* Initialize watchdog manager */ ret = halo_watchdog_init(); if (ret < 0) { __ASSERT(false, "Failed to initialize watchdog manager: %d", ret); return ret; } #endif #ifdef CONFIG_HALO_LED_MANAGER /* Initialize LED manager */ ret = halo_led_init(); if (ret < 0) { __ASSERT(false, "Failed to initialize LED manager: %d", ret); return ret; } #endif #ifdef CONFIG_HALO_BATTERY_MANAGER /* Initialize battery manager */ ret = halo_battery_init(); if (ret < 0) { __ASSERT(false, "Failed to initialize battery manager: %d", ret); return ret; } #endif #ifdef CONFIG_HALO_FILE_MANAGER /* Initialize file manager */ ret = halo_file_init(); if (ret < 0) { __ASSERT(false, "Failed to initialize file manager: %d", ret); return ret; } #endif #ifdef CONFIG_HALO_BLE_MANAGER /* Initialize BLE manager */ ret = halo_ble_init(NULL); if (ret < 0) { __ASSERT(false, "Failed to initialize BLE manager: %d", ret); return ret; } #endif /* CONFIG_HALO_BLE_MANAGER */ #if defined(CONFIG_HALO_STARTUP_SOUND) || defined(CONFIG_HALO_BOOT_LOGO) /* * Boot splash: kick off the (async) startup sound and draw the logo * BEFORE the Lua runtime starts. main.lua runs on the REPL thread as * soon as halo_lua_runtime_init() is called and would clear the display * out from under the logo. Holding here on the main thread (which only * sleeps forever afterwards) keeps the splash up with no race against * the app: we wait for the sound to finish (when enabled), then top up * to a minimum hold time. An unpaired device holds longer and shows its * BLE name under the logo so the user can identify it while pairing. */ #ifdef CONFIG_HALO_STARTUP_SOUND halo_startup_sound_schedule(); #endif #ifdef CONFIG_HALO_BOOT_LOGO bool splash_unpaired = false; const char *splash_label = NULL; #ifdef CONFIG_HALO_BLE_MANAGER splash_unpaired = !halo_ble_sec_is_bonded(); if (splash_unpaired) { splash_label = halo_ble_conn_get_device_name(); } #endif int64_t splash_start = k_uptime_get(); if (halo_display_boot_logo_show(splash_label) == 0) { #ifdef CONFIG_HALO_STARTUP_SOUND /* Bounded so a missing/stuck speaker can't stall boot. */ halo_startup_sound_wait(CONFIG_HALO_STARTUP_SOUND_TIMEOUT_MS + CONFIG_HALO_STARTUP_SOUND_DURATION_MS + 250); #endif int64_t hold_ms = splash_unpaired ? CONFIG_HALO_BOOT_LOGO_UNPAIRED_HOLD_MS : CONFIG_HALO_BOOT_LOGO_MIN_HOLD_MS; int64_t remaining_ms = hold_ms - (k_uptime_get() - splash_start); if (remaining_ms > 0) { k_sleep(K_MSEC(remaining_ms)); } halo_display_boot_logo_hide(); } #endif #endif #ifdef CONFIG_HALO_LUA_RUNTIME /* Initialize Lua runtime - this is the core of the system */ ret = halo_lua_runtime_init(); if (ret < 0) { __ASSERT(false, "Failed to initialize Lua runtime: %d", ret); return ret; } #endif #if defined(CONFIG_BOOTLOADER_MCUBOOT) && defined(CONFIG_MCUBOOT_IMG_MANAGER) /* * Mark this image as confirmed now that the full boot sequence has * succeeded. This must be the last step of bring-up: if any subsystem * above hangs or faults, the image stays unconfirmed and MCUboot reverts * to the previously good image on the next reset (the app-level watchdog * provides that reset). Confirming earlier would defeat that safety net. * boot_write_img_confirmed() is a no-op when the image is already confirmed. */ ret = boot_write_img_confirmed(); if (ret < 0) { LOG_ERR("Failed to confirm MCUboot image: %d", ret); } else { LOG_INF("MCUboot image confirmed"); } #endif #if defined(CONFIG_HALO_STARTUP_SOUND) || defined(CONFIG_HALO_SHUTDOWN_SOUND) /* Boot settled: re-arm the boot/shutdown cues for the next power-on. * Must stay after the image confirm so a boot that ends earlier keeps * the cue gate flag set (see CONFIG_HALO_CUE_GATE_DIRTY_BOOT). */ halo_sound_cue_boot_settled(); #endif while (1) { k_sleep(K_FOREVER); } return 0; }

从代码可以看出,main还是很简单,就只有一系列初始化。只做装配:依次初始化 mem_manager → file_manager → pm_manager → led_manager → battery → watchdog → ble_manager,然后启动 Lua运行时,之后系统完全由Lua脚本和BLE事件驱动。

prj.conf

# Zephyr Project Configuration File # Zephyr Base Configuration CONFIG_ASSERT=y CONFIG_LOG=y CONFIG_LOG_DEFAULT_LEVEL=2 CONFIG_LOG_BUFFER_SIZE=8192 CONFIG_LOG_PROCESS_THREAD_CUSTOM_PRIORITY=y CONFIG_LOG_PROCESS_THREAD_PRIORITY=3 CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 # Log Filesystem Backend — persist logs to LittleFS. DISABLED on this # branch: littlefs writes hit the same flash the code executes from, so # every persisted log line (e.g. the AEC's 10-s load INF) stalls the # whole system mid-audio-session — the same mechanism as the /lfs # capture-write emission gaps the AEC harnesses had to design around. # Also returns the 2x16KB log files to /lfs. Re-enable when the audio # timing work no longer needs stall-free sessions (the `logs` skill # depends on it for post-flash diagnostics; `verify.py` still covers # boot checks). CONFIG_LOG_BACKEND_FS=n # CONFIG_LOG_BACKEND_FS_DIR="/lfs" # CONFIG_LOG_BACKEND_FS_AUTOSTART=y # CONFIG_LOG_BACKEND_FS_OVERWRITE=y # CONFIG_LOG_BACKEND_FS_FILE_PREFIX="log." # CONFIG_LOG_BACKEND_FS_FILE_SIZE=16384 # CONFIG_LOG_BACKEND_FS_FILES_LIMIT=2 # System Workqueue Configuration CONFIG_MAIN_STACK_SIZE=4096 CONFIG_ISR_STACK_SIZE=4096 CONFIG_PRIVILEGED_STACK_SIZE=2048 CONFIG_IDLE_STACK_SIZE=2048 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 # Power Management Configuration CONFIG_PM=y CONFIG_PM_DEVICE=y # Thread Monitoring and Debugging CONFIG_THREAD_NAME=y CONFIG_THREAD_MONITOR=y CONFIG_THREAD_STACK_INFO=y CONFIG_THREAD_RUNTIME_STATS=y # Halo Framework CONFIG_HALO=y # Acoustic echo cancellation (speaker leakage removed from the mic path) CONFIG_HALO_AUDIO_AEC=y CONFIG_HALO_AUDIO_AEC_TAPS=1024 CONFIG_MPIX_LOG_LEVEL_WRN=y CONFIG_FS_LOG_LEVEL_WRN=y # Memory Manager Configuration # The libc malloc arena defaults to "all RAM left after static allocations", # which had decayed to ~50 bytes - any .bss growth then fails the sys_heap # minimum-size assert in malloc_prepare() and the image panics at boot. # Pin the arena explicitly and give back one page from the internal pool so # the link keeps real headroom. CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=2048 # (also funds the AEC's 256ms reference ring and its ~3.3KB paired-capture # snapshot buffers - see audio_aec.c) CONFIG_HALO_MEM_INTERNAL_SIZE=352256 CONFIG_HALO_MEM_USE_EXTERNAL_SRAM=y CONFIG_HALO_MEM_EXTERNAL_SRAM_ADDR=0x00000000 # The first 64KB of ITCM belong to the AEC FDAF state (the linker's ITCM # section; BUILD_ASSERTed in audio_aec.c to fit below this offset) - the # region-1 heap starts above it. Was offset 4096 (NULL guard) + 520192. CONFIG_HALO_MEM_EXTERNAL_SRAM_OFFSET=65536 CONFIG_HALO_MEM_EXTERNAL_SRAM_SIZE=458752 CONFIG_HALO_MEM_MANAGER_STATS=y # Flash and Storage Configuration CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_FLASH_PAGE_LAYOUT=y CONFIG_MPU_ALLOW_FLASH_WRITE=y # LittleFS Configuration CONFIG_FILE_SYSTEM_LITTLEFS=y CONFIG_FS_LITTLEFS_FMP_DEV=y # Settings Configuration CONFIG_SETTINGS=y CONFIG_SETTINGS_FILE=y CONFIG_SETTINGS_FILE_PATH="/lfs/settings" # Sensor Configuration for Battery Service CONFIG_SENSOR=y CONFIG_SENSOR_INIT_PRIORITY=60 # Regulator Init Priority # cam_1v8 (regulator-fixed, priority 32) must initialize before tps65132 # (REGULATOR_TPS65132_COMMON_INIT_PRIORITY=61), since tps65132 has # vin-supply = <&cam_1v8> and checks device_is_ready() during init. CONFIG_REGULATOR_FIXED_INIT_PRIORITY=32 # LED Configuration for Battery Charging Indicator CONFIG_PWM=y CONFIG_LED_PWM=y # Halo BLE Manager Configuration CONFIG_HALO_BLE_MAX_SERVICES=8 CONFIG_HALO_BLE_BOND_STORAGE=y CONFIG_HALO_BLE_REQUIRE_PAIRING=y CONFIG_HALO_BLE_ALLOW_BOND_OVERWRITE=n # BLE Lua Service Configuration CONFIG_HALO_BLE_LUA_SERVICE=y CONFIG_HALO_LUA_MAX_REPL_SIZE=1024 CONFIG_HALO_LUA_MAX_DATA_SIZE=4096 CONFIG_HALO_AUDIO_MAX_DATA_SIZE=8192 CONFIG_HALO_SFXR=y CONFIG_HALO_LUA_SOUND=y CONFIG_HALO_STARTUP_SOUND=y CONFIG_HALO_STARTUP_SOUND_PRESET="powerup" CONFIG_HALO_STARTUP_SOUND_SEED=4149902225 CONFIG_HALO_STARTUP_SOUND_DURATION_MS=1000 # Boot/shutdown cues play at the app-wide SFXR level (20). CONFIG_HALO_STARTUP_SOUND_VOLUME=20 CONFIG_HALO_SHUTDOWN_SOUND=y CONFIG_HALO_SHUTDOWN_SOUND_PRESET="laser" CONFIG_HALO_SHUTDOWN_SOUND_SEED=573636714 CONFIG_HALO_SHUTDOWN_SOUND_DURATION_MS=700 CONFIG_HALO_SHUTDOWN_SOUND_VOLUME=20 # Boot logo splash (drawn pre-runtime_init, held for the startup sound duration) CONFIG_HALO_BOOT_LOGO=y # Lua Runtime Configuration CONFIG_HALO_LUA_RUNTIME=y # REPL thread: 128KB stack for Lua VM and complex user scripts CONFIG_HALO_LUA_REPL_TASK_STACK_SIZE=65536 CONFIG_HALO_LUA_REPL_TASK_PRIORITY=7 # Watchdog Configuration CONFIG_HALO_WATCHDOG_MANAGER=y CONFIG_HALO_WATCHDOG_TIMEOUT_MS=5000 CONFIG_HALO_WATCHDOG_ALLOW_CALLBACK=y CONFIG_HALO_WATCHDOG_AUTO_FEED=y CONFIG_HALO_WATCHDOG_AUTO_FEED_PRIORITY=2 CONFIG_HALO_WATCHDOG_AUTO_FEED_STACK_SIZE=512 CONFIG_HALO_WATCHDOG_FEED_INTERVAL_MS=1000 CONFIG_HALO_BLE_MANAGER=y CONFIG_HALO_SE_MGMT=y CONFIG_BOOT_BANNER=n CONFIG_HALO_BATTERY_MANAGER=y CONFIG_HALO_LED_MANAGER=y CONFIG_HALO_WATCHDOG_MANAGER=y CONFIG_HALO_LUA_RUNTIME=y CONFIG_HALO_BLE_ALLOW_BOND_OVERWRITE=n CONFIG_HALO_BLE_AUDIO_SERVICE=y CONFIG_HALO_BLE_AUDIO_SINK_ENABLE=y CONFIG_HALO_BLE_AUDIO_OUTPUT_STEREO=y CONFIG_HALO_BLE_AUDIO_SOURCE_ENABLE=y CONFIG_HALO_BLE_AUDIO_INPUT_STEREO=n # Set a current budget for the speakers to avoid tripping the battery protection IC CONFIG_MAX98357A_AUDIO_PROTECT_BUDGET_PERCENT=80

4 LUA层

启动是在main里面:

#ifdef CONFIG_HALO_LUA_RUNTIME /* Initialize Lua runtime - this is the core of the system */ ret = halo_lua_runtime_init(); if (ret < 0) { __ASSERT(false, "Failed to initialize Lua runtime: %d", ret); return ret; } #endif

在里面启动核心线程lua_repl_thread_fn。

REPL = Read–Eval–Print Loop,中文通常叫“读取—求值—输出循环”。

它是一种交互式程序运行方式:输入一条指令,系统执行它并显示结果,然后等待下一条指令。之前在AR1的项目里面,MCU基本上也是这样的模式。

在里面一个核心是启动虚拟机

lua_vm_init(lua_ctx.L);

LUA异步事件处理

Lua 绑定文件对应外设 API:

绑定文件暴露给 Lua 的frame.*命名空间
lua_display.cframe.display(MIPI-DSI 屏幕绘制)
lua_microphone.cframe.microphone(PDM 录音,AAD 检测)
lua_speaker.cframe.speaker(LC3/PCM 播放)
lua_bluetooth.cframe.bluetooth(数据收发)
lua_camera.cframe.camera(图像采集)
lua_imu.cframe.imu(加速度计/陀螺仪、tap 检测)
lua_button.cframe.button(按键事件)
lua_file.cframe.file(LittleFS 读写)
lua_compression.cframe.compression(LZ4 压缩)
lua_time.cframe.time(RTC 时间)
lua_system.cframe.system(版本、重启等)
lua_log.cframe.log(日志输出)
lua_sound.cframe.sound(SFXR 音效)

比如手机发送 frame.camera.capture() 的完整执行轨迹:

[手机 App]
│ 通过 BLE 发送字符串 "frame.camera.capture()\n"
▼
[眼镜端 BLE 接收通道 (ble_lua.c)]
│ 写入环形接收缓冲区
▼
[lua_repl_thread_fn 线程] ◄─── 你问的核心就在这里!
│
├─ 1. halo_ble_lua_repl_read() 读出这段字符串
├─ 2. 按 '\n' 识别出这是一条完整命令: "frame.camera.capture()"
├─ 3. 调用 Lua 引擎: luaL_dostring(L, "frame.camera.capture()")
│ │
│ ▼
│ [Lua 虚拟机寻址]
│ 找到 C 函数绑定表中的 lua_camera_capture() (位于 lua_camera.c)
│ │
│ ▼
├─ 4. 在 REPL 线程中执行 lua_camera_capture(L):
│ • 检查摄像头状态与分辨率 (默认 640x640)
│ • 配置视频格式与 DMA 缓冲区
│ • 释放信号量: k_sem_give(&camera_state.capture_sem) ──┐
│ • 函数立即返回,REPL 线程不会在此死等拍照完成 │ (异步唤醒)
│ ▼
[后台摄像头采集线程 (camera_thread_fn)] ◄────────────────────────┘
│
├─ 5. 等到信号量被唤醒,控制硬件拍摄:
│ • 向底层 PAG7982 驱动发抓帧指令
│ • 硬件 CPI DMA 自动将像素数据搬移至内存
│ • 硬件/软件压缩为 JPEG 并存入静态全局缓冲
│ • 标记: camera_state.ready = true

5 BLE层

官方参考文档BLE_SERVICES.md。

整体架构

核心控制服务:Halo Lua Service (7A230001-5475-A6A4-654C-8431F6AD49C4)

这是手机控制眼镜的最核心服务,包含 3 个 Characteristic:

特征值 (UUID)属性通信方向核心功能
LUA RX(7A230002...)Write / Write Without Response手机 ➔ 眼镜下行控制通道:手机向眼镜发控制指令、动态 Lua 脚本或二进制透传数据(MTU 512 字节)。
LUA TX(7A230003...)Notify眼镜 ➔ 手机上行响应通道:眼镜向手机返回执行结果、print()终端输出、主动上报的传感器事件或应用数据。
AUDIO RX(7A230005...)Write / Write Without Response手机 ➔ 眼镜独立音频下行:专用于手机向眼镜扬声器流式推送 PCM / LC3 音频数据。

BLE命令协议

Header byte含义
0x01数据帧(用户数据通道)
0x02Ctrl+B(重启设备)
0x03Ctrl+C(中断脚本,可被 pcall 捕获)
0x04Ctrl+D(重启 Lua VM,不可被捕获)
0x05Ctrl+E(删除 main.lua 并重启)
0x06Ctrl+F(完全退出 Lua 运行时)
0x07Ctrl+G(删除所有文件,保留 settings)

在蓝牙接收数据时候,对数据进行了分流。

手机写入 LUA RX 特征值
│
▼
[第一道关卡: ble_lua.c (蓝牙接收回调)]
│
┌───────────────┴───────────────┐
│ │
首字节 == 0x02 ~ 0x07 首字节 != 0x02 ~ 0x07 (ASCII 文本)
【单字节硬控制指令】 【普通 Lua 代码字符串】
│ │
▼ ▼
【在此处立刻拦截处理!】 写入 repl_rx_ring 缓冲区
• 0x02: 直接调 sys_reboot() 重启 │
• 0x03: 强行置位中断信号 (Ctrl+C) │
• 0x04: 标记 running = false 重启 VM │
• 0x05: 删除 main.lua ▼
• 根本不会传给 lua_repl_thread_fn [第二道关卡: lua_repl_thread_fn]
从缓冲区读出字符串:
"frame.camera.capture()\n"
│
▼
交给 Lua VM 解释执行:
luaL_dostring(...)

两个通道的分工如下:

【阶段 1:启动部署阶段】
手机走【文本通道 (Lua REPL)】
┌──────────────────────────────────────────────┐
│ 手机下发整个业务脚本 duplex_frame_app.lua: │
│ 1. 注册消息协议表 handlers[0x12] = show_text │
│ 2. 绑定蓝牙接收: frame.bluetooth.receive_cb │
│ 3. 绑定敲击唤醒: frame.imu.tap_callback │
└──────────────────────────────────────────────┘
│
▼ 脚本部署就绪,进入常驻监听
【阶段 2:高频实时交互阶段】
手机与眼镜走【二进制透传通道 (0x01)】
┌──────────────────────────────────────────────┐
│ • 用户说话:眼镜通过 BLE TX 高频回传音频分片 │
│ • 手机返回:首字节 0x01 + 业务码 (0x12 文字) │
│ • 零内存开销,直接触发 receive_cb 高频流式显示 │
└──────────────────────────────────────────────┘
│
▼ 发生异常/退出
【阶段 3:急刹车/生命周期重置】
手机走【单字节控制通道 (0x02~0x07)】
┌──────────────────────────────────────────────┐
│ 手机写入 0x03 (打断) 或 0x04 (重启重置) │
└──────────────────────────────────────────────┘

6 Zeypher集成

7 音频子系统

8 IMU子系统

9 Camera子系统

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询