书接上回。。。
6 Zeypher集成
6.1 整体内容
整体来说是基于Zephyr 3.6.0。核心的实现如下:
文件夹内容如下(GitHub - brilliantlabsAR/halo-firmware: Firmware for Halo, Brilliant Labs' AI glasses — Zephyr RTOS on the Alif Balletto, with a Lua runtime for on-device apps and BLE OTA updates · GitHub):
| Path | What it is |
|---|---|
applications/halo/ | The Halo application (boot, BLE, Lua runtime, audio, display) |
modules/halo/ | Reusable Halo C modules + Lua bindings (AEC, codecs, sensors, BLE services) |
modules/lua/,modules/canvas/ | Lua 5.4 and the display/canvas + font stack |
boards/arm/halo/ | ThehaloZephyr board definition |
drivers/,subsys/ | Device drivers and subsystems (Halo-specific + inherited Alif SDK) |
samples/halo/ | Small single-peripheral samples (mic, display, sensors, …) |
west.yml | The west manifest pinning Zephyr, HALs, and MCUboot |
applications/halo/PROTOCOL.md | BLE protocol reference for host apps |
因为芯片用的是Alif Semiconductor的,所以板级驱动是原厂直接在Zephyr包里面提供了。对于Halo产品来说实现的核心内容,就是app,modules,drivers。他们的顺序如下:
app应用,就是一个main.c,也没有太多东西。
6.2 Modules
modules,服务器上的目录包括canvas、halo、linker和lua:
modules/halo/:Halo 产品功能,包括 BLE 管理与 BLE 服务、Lua runtime 及其硬件 API、电源/电池/LED/watchdog/file/memory manager,以及音频流、AEC、EQ 和音效处理。modules/lua/:Lua 解释器本身。modules/canvas/:画布绘图和字体资源。modules/linker/:链接脚本片段等构建辅助内容。
6.3 Drivers
drivers。大概有以下这些:
| 路径 | 大致用途 |
|---|---|
audio/max98357a/ | MAX98357A I²S 扬声器功放及扬声器保护 |
audio/t5838/ | T5838 PDM 麦克风输入及音频活动检测(AAD) |
audio/pwm_audio/ | PWM 音频输出 |
clock/si570/ | Si570 可编程时钟 |
codec/wm8904/ | WM8904 音频编解码器 |
display/display_vga020.c | VGA020 显示器 |
i2s/i2s_sync/ | 同步 I²S 音频接口 |
input/input_gpio_button.c | GPIO 按键 |
regulator/regulator_tps65132.c | TPS65132 电源/偏置管理 |
sensor/alif_vbat.c | Alif 电池电压采集 |
sensor/bma580/ | BMA580 加速度计 |
sensor/qmc6308.c | QMC6308 磁力计 |
sm/sm.c | GPIO 控制的 shutdown/电源状态机制 |
video/pag7982.c | PAG7982 摄像头 |
6.4 小结
从上面的内容可以看出,如果是基于zeypher开发,步骤大概就是:
- west init + 挪一个改名的manifest
- 依样画
boards/foo/board.dts把外设别名摆好(一个也叫产品角色) - 建一个跑
main()的纯初始化骨架程序——log出一个Hello + 一个k_work`心跳 - 外设一个一个加进
samples/式的最小例,然后接入应用 - 以sysbuild+mcuboot为底线,第一周就跑通“通过BLE掉固件→回滚”——OTA机制不是产品后期加的,是最早基础设施
7 音频子系统
整体来看,音频系统如下:
这里只看看驱动层,stream层,以及算法。再上面的app不在范围。
7.1 驱动
从硬件上来看,音频系统涉及的硬件是:
| 麦克风 | T5838 PDM(带Always-on Audio Detect,可单独唤醒) | zephyr,micphone |
| 扬声器 | MAX98357A(I2S功放) | zephyr,speaker |
关于98357,之前其实写过一篇:总线学习5--I2S_max98357数据手册-CSDN博客
在代码中有以下驱动:
作用如下:
| 路径 | 大致用途 |
|---|---|
audio/max98357a/ | MAX98357A I²S 扬声器功放及扬声器保护 |
audio/t5838/ | T5838 PDM 麦克风输入及音频活动检测(AAD) |
audio/pwm_audio/ | PWM 音频输出 |
codec/wm8904/ | WM8904 音频编解码器 |
i2s/i2s_sync/ | 同步 I²S 音频接口 |
播放的DTS定义
/delete-node/ i2s0@49014000; i2s0: i2s0@49014000 { compatible = "alif,i2s-sync"; reg = <0x49014000 0x1000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2s0>; interrupts = <141 3>; bit-depth = <16>; sample-rate = <32000>; mono-mode; status = "okay"; }; max98357a: max98357a { compatible = "maxim,max98357a"; i2s = <&i2s0>; sd-mode-gpios = <&gpio8 5 GPIO_ACTIVE_HIGH>; }; pdm_audio: pdm_audio@43002000 { compatible = "alif,t5838-alif-pdm"; reg = <0x43002000 0x1000>; /* priority 2, above I2S0's 3: the 8-deep PDM FIFO at * 16kHz gives the ISR ~250us from the watermark to * overflow; at priority 4 under duplex load it missed * ~10x/second (732 counted overflows in 72s, EC * 2026-07-13). Bisect-exonerated for the round-2 * wedge (that was the unacked sticky ERROR status). */ interrupts = <49 2>; pinctrl-0 = < &pinctrl_pdm_audio >; pinctrl-names = "default"; /* almost-full IRQ threshold on the 8-deep FIFO: slack * to overflow is (8 - watermark) samples = 62.5us * each at 16kHz. 6 left 125us of ISR headroom and * overflowed under duplex BLE+LC3 load (capture * losses that slid the AEC window off its ring); * 4 doubles the headroom for a ~1.3x ISR rate. */ fifo-watermark = <4>; queue-depth = <128>; iir-bypass; fir-bypass; /* Snip the PDM decimator start-of-stream settling * transient (the "pop"): the front-end slews to the * ±full-scale rail for the first ~5-12ms after trigger * (measured on cold-start captures, tests/aec/pop_*.py), * a window with no recoverable audio. In the DMA drain * path the discard drops whole MIC_BLOCK_INTERVAL_MS * (20ms) blocks, so 20 = drop exactly one block, clearing * the rail with margin at ~20ms of startup latency. The * DC-settling tail past the rail is real audio and is * left to the downstream DC blocker (lua_microphone.c * mic_dc_block). The driver default is 0 (was regressed * 50->0 in 15bc6a1, which re-exposed the pop). Do NOT * un-bypass the IIR to fix this - the AEC relies on the * raw full-band mic (see mic_dc_block rationale). */ discard-duration-ms = <20>; /* LPPDM RX DMA drain (prototype): dma2 channel 4, * DMA request line 30 / group 2 = LPPDM_DMA_REQ * (B1 HWRM Table 14-95 + the DMA request MUX table). * The hardware FIFO watermark drives the DMA handshake * (PDM_CTL1 USE_DMA bit), replacing the almost-full ISR * that overflows on >250us CPU blackouts. Drop this * property to fall back to the ISR drain. */ dmas = <&dma2 4 30>; dma-names = "rxdma"; status = "okay"; #address-cells = < 0x1 >; #size-cells = < 0x0 >; t5838: t5838@53 { status = "okay"; compatible = "invensense,t5838-alif-pdm"; thsel-gpios = <&gpio3 0 GPIO_ACTIVE_HIGH>, <&gpio5 2 GPIO_ACTIVE_HIGH>; wake-gpios = <&lpgpio 0 GPIO_ACTIVE_HIGH>; pdmclk-gpios = <&gpio5 5 GPIO_ACTIVE_HIGH>; reg = <0x53>; }; };录音的DTS定义:
tps65132: tps65132@3e { compatible = "ti,tps65132"; reg = <0x3e>; vin-supply = <&cam_1v8>; supply-gpios = <&gpio2 1 GPIO_ACTIVE_HIGH>; apps = "40ma"; veng: VENG { regulator-name = "veng"; regulator-init-microvolt = <4500000>; enable-gpios = <&gpio5 6 GPIO_ACTIVE_HIGH>; startup-delay-us = <5000>; status = "okay"; };在这里,驱动把接口封装成zepyher的接口给上层使用。接口可以看Zephyr API Documentation: Audio
比如读取麦克风数据,alif_pdm_read(),就是封装成标准的zepyher接口dmic_read()供上层使用。但是在驱动中,也有一些也是HALO项目自己定义的。比如播放接口。max98357a_audio_write_impl。最后是封装成一个自己的接口audio_write()。
7.2 流抽象层
主要涉及的代码是audio_stream.c。封装了驱动,并且提供以下接口。
| 功能 | 上层接口 | 作用 |
|---|---|---|
| 扬声器播放 | audio_speaker_init()、audio_speaker_start()、audio_speaker_write()、audio_speaker_stop()、audio_speaker_destroy() | 初始化并控制播放会话、写入 PCM 数据、结束会话 |
| 播放控制与保护 | audio_speaker_set_volume()、audio_speaker_set_volume_transient()、audio_speaker_get_volume()、audio_speaker_set_stream_gain()、audio_speaker_set_stream_budget()、audio_speaker_notify_display_active() | 调节音量和每路增益、功耗预算,并根据显示状态调整音频保护 |
| 麦克风录音 | audio_microphone_init()、audio_microphone_start()、audio_microphone_read()、audio_microphone_free_buffer()、audio_microphone_stop()、audio_microphone_destroy() | 初始化录音、读取数据,并释放驱动交付的缓冲区 |
| 麦克风控制与资源管理 | audio_microphone_set_gain()、audio_microphone_get_gain()、audio_microphone_check_owner()、audio_microphone_read_owned()、audio_microphone_free_buffer_owned() | 控制增益;在指定资源拥有者的上下文中读取或释放数据 |
| LC3 编解码 | audio_lc3_decoder_create()、audio_lc3_decode_frame()、audio_lc3_encoder_create()、audio_lc3_encode_frame(),以及对应的销毁和帧大小查询函数 | 在 LC3 数据与 PCM 之间转换 |
| 音频活动检测 | audio_aad_set_callback()、audio_aad_rearm()、audio_aad_disable()、audio_aad_get_current_settings() | 注册音频活动检测回调、重新布防、禁用或查询设置 |
| 音频流源与接收端 | audio_stream_register_source()、audio_stream_source_read()、audio_stream_unregister_source();audio_stream_register_sink()、audio_stream_sink_write()、audio_stream_unregister_sink() | 用回调注册数据源和接收端,让不同组件通过统一的数据流接口交换音频 |
7.3 算法
Halo 的 AEC、EQ 等算法主要由 Halo 自己实现,放在音频模块或 MAX98357A 驱动里;Zephyr 提供设备与数据流接口,算法本身不是 Zephyr 通用音频 API。
| 处理 | 主要实现位置 | 实现方式与所在链路 |
|---|---|---|
| AEC 回声消除 | modules/halo/src/audio_aec.c、modules/halo/include/halo/audio_aec.h | 默认使用分区块频域自适应滤波(FDAF);可配置为时域 NLMS。以扬声器输出作为参考,估算泄漏到麦克风的回声并从麦克风信号中扣除;还包括双讲检测、抑制滤波等机制。 |
| AEC 接入录音链路 | modules/halo/src/lua_microphone.c | 麦克风数据经过audio_aec_process()后才进入后续编码。AEC 需要的扬声器参考信号则由 MAX98357A 驱动在发送路径上提供。 |
| 扬声器保护 DSP:HPF、音色 EQ、限幅 | drivers/audio/max98357a/speaker_protect.c、drivers/audio/max98357a/speaker_protect.h | 驱动在 PCM 播放路径中、音量处理之后应用保护链,包括高通滤波、分频和音色调整、能量限制、起播渐入及削波保护。配置MAX98357A_AUDIO_PROTECTION_EQ默认启用。 |
| 可选播放端 HPF | modules/halo/src/audio_eq.c、modules/halo/include/halo/audio_eq.h | audio_speaker_write()可选地对 PCM 原地应用二阶或四阶 Butterworth 高通滤波。HALO_AUDIO_HPF默认关闭;Kconfig 说明 Halo 的扬声器保护主要由驱动里的 DSP 链负责。 |
| 录音端 DC 和语音频带处理 | modules/halo/src/lua_microphone.c | AEC 之后还会做 DC 偏置抑制;语音模式下可再做带通处理,然后编码。 |
8 IMU子系统
对于系统来说,IMU包括了BMA580加速度计 + QMC6308磁力计。
驱动都在alif/drivers/sensor/,
这里是直接封装成zeypher的接口。接口描述是在Zephyr API Documentation: sensor_driver_api Struct Reference
在HALO项目中,IMU的数据全部都是上层控制读取,使用的接口是frame.imu.direction() / frame.imu.raw()。驱动或者下层不会去自动推送或者缓存。只有敲击手势,是产生中断,由imu_trigger_handler捕获。
| 数据 | 模式 | 频率归属 | MCU消耗 |
|---|---|---|---|
| 姿态 pitch/roll | Lua调用时拉取(现场I2C读) | Lua想读多快读多快(≤~几十Hz现实上限) | 每次~几十µs I2C |
| 磁场 raw | 同上,且QMC6308是现场触发转换 | 同上 | 每次含最长100ms DRDY等待(实际~几ms) |
| 敲击手势 | 中断推送(芯片检测→事件入环) | 事件频率(人类敲击速度) | 每次一个中断+一个字节搬运 |
| 原始804Hz样本流 | 不存在(DATA_READY口子预留未用) | 芯片内部800Hz自转 | 零(feature engine在芯片内消化) |