1 硬件架构
目标板:Alif Balletto B1
| 特性 | 规格 |
|---|---|
| CPU | ARM Cortex-M55(Helium SIMD) |
| NPU | Arm 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-DSI | zephyr,panel |
| 摄像头 | PAG7982 + 低功耗lpcam | zephyr,camera |
| 麦克风 | T5838 PDM(带Always-on Audio Detect,可单独唤醒) | zephyr,micphone |
| 扬声器 | MAX98357A(I2S功放) | zephyr,speaker |
| 加速度计 | BMA580 | zephyr,accel |
| 磁力计 | QMC6308 | zephyr,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 CMSIS | alifsemi |
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.c | Lua VM 生命周期、REPL 线程、异步事件分发(统一 Hook 机制) |
ble_manager.c | BLE 服务注册/断开调度、事件分发、电源管理集成 |
ble_lua.c | Lua BLE 服务(REPL 通道 + 数据 framed 队列 + 视频/音频) |
ble_ota.c | OTA 固件升级(SMP over BLE → MCUboot) |
ble_audio.c | LE Audio 流媒体(BAP Unicast,LC3) |
ble_connection.c/ble_security.c | BLE 连接管理与配对安全 |
ble_ancs.c/lua_ancs.c | Apple 通知中心服务(手机通知转发) |
audio_aec.c | 声学回声消除 (AEC),FDAF 算法,状态放在 ITCM(64KB),256ms 参考环 |
audio_eq.c | 均衡器 |
audio_stream.c | 音频流管道(Mic → AEC → EQ → BLE) |
battery_manager.c | 电池电量监控(充电检测、低电告警事件) |
led_manager.c | LED 呼吸灯 / 状态指示控制 |
file_manager.c | Flash 文件系统(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=804 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.c | frame.display(MIPI-DSI 屏幕绘制) |
lua_microphone.c | frame.microphone(PDM 录音,AAD 检测) |
lua_speaker.c | frame.speaker(LC3/PCM 播放) |
lua_bluetooth.c | frame.bluetooth(数据收发) |
lua_camera.c | frame.camera(图像采集) |
lua_imu.c | frame.imu(加速度计/陀螺仪、tap 检测) |
lua_button.c | frame.button(按键事件) |
lua_file.c | frame.file(LittleFS 读写) |
lua_compression.c | frame.compression(LZ4 压缩) |
lua_time.c | frame.time(RTC 时间) |
lua_system.c | frame.system(版本、重启等) |
lua_log.c | frame.log(日志输出) |
lua_sound.c | frame.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 | 数据帧(用户数据通道) |
0x02 | Ctrl+B(重启设备) |
0x03 | Ctrl+C(中断脚本,可被 pcall 捕获) |
0x04 | Ctrl+D(重启 Lua VM,不可被捕获) |
0x05 | Ctrl+E(删除 main.lua 并重启) |
0x06 | Ctrl+F(完全退出 Lua 运行时) |
0x07 | Ctrl+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子系统