news 2026/9/15 4:36:25

Flipper Zero 固件中的 BLE LLD:基于 BLE 射频硬件的专有协议无线电抽象层解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flipper Zero 固件中的 BLE LLD:基于 BLE 射频硬件的专有协议无线电抽象层解析

Flipper Zero 固件中的 BLE LLD:基于 BLE 射频硬件的专有协议无线电抽象层解析

【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware

本指南以 lib/stm32wb_copro/wpan/ble_lld/README.md 为骨架,结合lib/stm32wb_copro/wpan/ble_lld/目录下的头文件与实现源码,讲解 BLE LLD 的架构设计、Action Packet 链接机制、双核(appli core / radio core)协作方式,以及 HAL/LLD 两套 API 的配置与通信流程。读完本文,你将理解如何利用 BLE 射频硬件实现自定义专有无线协议,并掌握从初始化、事件处理到发射/接收动作链配置的完整调用链。

什么是 BLE LLD

BLE LLD(BLE Low Level Driver)是 STM32WB 平台上的一层专有射频通信抽象层:它依赖 BLE 射频硬件,但本身并不是一个 BLE 协议栈。正如 README 开篇所述,它的定位是"light and simple layer to develop proprietary protocols and applications"——即为开发者提供一套轻量、简单的接口,用来在 2.4GHz BLE 射频硬件之上构建自定义的专有协议,而不是实现 BLE 标准通信。

BLE LLD 对外提供两层接口:

  • LLD(全功能层):暴露射频核心支持的全部特性,API 较为复杂,支持自定义动作包(Action Packet)链式调度;
  • HAL(简单 API 层):建立在 LLD 之上,把常见通信场景封装成简单函数,适合不需要自定义动作链的场合。

对应源码分布在 lib/stm32wb_copro/wpan/ble_lld/ 下,其中lld/ble_lld.hlld/ble_lld.c是 LLD 实现,hal/ble_hal.hhal/ble_hal.c是 HAL 封装,lld/ipBLE_lld_public.h定义了动作包、事件枚举等公共数据结构,lld/ble_lld_transport.h则定义了两核之间传输层使用的命令码与参数结构。

双核架构与 IPCC 通信

appli core 与 radio core 的分工

BLE LLD 专为双核硬件设计(见 README "Dual core" 一节):

  • Application core(应用核):运行用户代码;
  • Radio core(射频核):运行专用于射频管理的私有代码。

两核之间的软件通信层称为IPCC(Inter-Processor Communication Controller)。README 特别强调:IPCC 这一传输层与 BLE LLD 本身解耦,也就是说 BLE LLD 不关心底层消息如何送达射频核,具体如何"接线"由应用层负责。在仓库中,与传输层相关的基础设施位于 lib/stm32wb_copro/wpan/interface/patterns/(如tl/目录下的 hci_tl、shci_tl 等模块)。

README 中的架构示意图清晰地展示了两个核上的软件栈:

appli core: HAL -> LLD -> LLD proxy radio core: LLD proxy -> LLD -> BLE radio 中间以 IPCC 分隔
  • appli core上:Application 是使用自定义射频协议的用户程序;HAL 是基于 LLD 的简单通信封装;LLD 是全功能通信层;LLD proxy 负责把数据和命令打包/解包,与射频核往来。
  • radio core上:LLD proxy 与 appli core 侧对应打包/解包;LLD 提供射频抽象;BLE radio 是 RF 硬件。

双核架构带来的约束

这一架构带来两个重要约束:

  1. 射频核上不运行任何应用代码
  2. 射频事件发生后,应用代码的响应耗时较长(因为要跨 IPCC 往返)。

为了在约束下依然能实现快速的射频操作序列,射频核支持将action packets(动作包)链接(chain)起来执行,而链接方式由应用侧配置。这两个约束会直接影响协议设计以及 LLD/HAL 的选择——如果需要极低延迟的连续收发,就需要用 LLD 精心编排动作链;如果时序要求不苛刻,用 HAL 即可。

Action Packet:射频核执行 Tx/Rx 的最小单位

Action packet 是射频核用来控制无线电数据包**发射(Tx)与接收(Rx)**的结构(README "Action Packet" 一节)。在源码中,它的定义见 lib/stm32wb_copro/wpan/ble_lld/lld/ble_lld.h 的ActionPacket结构体:

typedef struct ActPac_s { uint8_t StateMachineNo; /* 状态机编号 (0 - 7) */ uint8_t ActionTag; /* 动作包配置位域: PLL_TRIG, TXRX, TIMER_WAKEUP, TIMESTAMP_POSITION 等 */ uint32_t WakeupTime; /* 执行动作包前的唤醒时间 (us),仅 TIMER_WAKEUP 置位时有效 */ uint32_t ReceiveWindowLength; /* Rx 窗口大小 (us),仅 Rx 有效 */ void *data; /* 待发送的载荷,仅 Tx 有效 */ uint8_t dataSize; /* 载荷大小,仅 Tx 有效 */ uint32_t status; /* 来自硬件的中断状态寄存器 */ int32_t rssi; /* 收到数据包的 RSSI,仅 Rx 有效 */ uint8_t nextTrue; /* 操作成功时执行的下一个动作包 */ uint8_t nextFalse; /* 操作失败时执行的下一个动作包 */ uint8_t actionPacketNb; /* 动作包编号 (0 - 7) */ void (*callback)(radioEventType, struct ActPac_s *, void *, uint8_t); /* 动作包结束时运行,可为 NULL */ } ActionPacket;

动作链:成功/失败双分支

动作包可以链式执行以完成复杂射频序列。链接通过两个字段配置(见 README):

  • nextTrue:本次操作(Tx 或 Rx)成功时接下来执行的动作包;
  • nextFalse:操作失败时接下来执行的动作包。

射频核会根据动作包配置与操作结果自动沿分支跳转。仓库中ipBLE_lld_public.h还定义了每个动作包编号的取值范围:APACKET_0~APACKET_7共 8 个(ACTION_PACKET_NB 8),以及用于终止链的APACKET_STOP=0xFF("to be placed at the end",放在链末尾用于停止)。同时每个动作包还挂在一个状态机上,StateMachine_t枚举定义了STATE_MACHINE_0~STATE_MACHINE_7共 8 个状态机,LLD 的BLE_LLD_SetChannel()BLE_LLD_SetTxAttributes()等配置函数都以StateMachineNo为参数——不同状态机可以持有各自独立的信道、网络 ID、PHY 等配置。

Back-to-back 与 wake-up 两种模式

动作包之间(或第一个动作包之前)的延迟可用两种模式配置(README "Back-to-back vs wake-up" 一节):

  • Back-to-back 模式:射频全程保持供电,动作包间延迟最低。该时间是全局参数,通过BLE_LLD_SetBackToBackTime()配置,不能为单个动作包单独设置。源码注释补充了更精确的信息:该模式对应TIMER_WAKEUP位为 0 时使用的 back-to-back 定时器,是全局定时器、所有动作包共用同一个值,且"can be used for low values (down to ~150us)",即最低可到约 150 微秒。
  • Wake-up 模式:射频在等待期间进入睡眠,因此动作包间延迟不可能像 back-to-back 那样短。唤醒时间是每个动作包单独配置的(对应ActionPacket.WakeupTime字段)。源码注释同样给出了量级参考:wakeup 是本地定时器、每个动作包可有不同值,但"cannot be used for low values (minimum is ~700us)",即最小约 700 微秒。

序列中的第一个动作包必须使用 wake-up 模式(README 明确说明),因为此时射频处于空闲未运行状态,需要先被唤醒。

ActionTag位域在 lib/stm32wb_copro/wpan/ble_lld/lld/ipBLE_lld_public.h 中给出了完整的位掩码定义:

位掩码宏名含义
0x01PLL_TRIG使能射频 PLL 校准(0 关闭 / 1 开启)
0x02TXRX动作类型:1 = Tx,0 = Rx
0x04TIMER_WAKEUP定时器选择:0 = back-to-back 全局定时器,1 = wake-up 本地定时器
0x08NS_EN自动 NS(Network Select)使能
0x10INC_CHAN自动信道递增(0 不递增 / 1 自动递增)
0x80TIMESTAMP_POSITION时间戳采样位置(仅 Rx):0 = 包尾,1 = 包头

无线数据包的细节

README "Radio packet details" 一节给出了数据包的三个关键属性:

  1. 地址匹配:每个数据包包含一个地址,接收时必须与接收方配置的地址匹配才会被接受;
  2. 最大载荷 255 字节ipBLE_lld_public.h中的IPBLE_LLDANT_MAX_PAYLOAD_SIZE 255与之对应,且"less if using encryption"——启用加密后载荷上限会减小;
  3. CRC 校验:数据包包含 CRC,接收时会被检查错误。

从源码的ipBLE_lld_txrxdata_Type结构(见ipBLE_lld_public.h)可以看到实际的数据包内存布局:

typedef struct { uint8_t header; // 被硬件按 BLE flags 解释,LLD 中保留,默认安全值 LLD_HEADER = 0x55 uint8_t length; // 实际载荷大小 uint8_t payload[IPBLE_LLDANT_MAX_PAYLOAD_SIZE]; // 用户数据 } ipBLE_lld_txrxdata_Type;

注意两点实现细节:

  • header 字段不能随意改动ble_lld.c中定义了#define LLD_HEADER 0x55,并注明"Header field is interpreted by hardware and has an impact on some flags, so user should not change it",因此封装数据包时应使用默认安全值。
  • 加密时尾部预留 MIC。启用加密后,payload[length]起始的 4 字节(MIC_SIZE 4)会保留给 MIC(消息完整性校验码)。BLE_LLD_packetPrepareCopy()encrypt为真时会把actual_size = size + MIC_SIZE写入length。加密相关参数(AES_KEY_SIZE 16AES_IV_SIZE 8AES_BLOCK_SIZE 16)同样定义在该头文件中。

数据包的准备与解析有四个配套函数(ble_lld.h):

  • BLE_LLD_packetPrepareCopy()/BLE_LLD_packetPrepareInPlace():将用户数据写入 Tx 缓冲(Copy 版带拷贝,InPlace 版就地在共享内存缓冲中构建);
  • BLE_LLD_packetExtractCopy()/BLE_LLD_packetExtractInPlace():从 Rx 缓冲提取载荷;
  • BLE_LLD_packetGetSize():查询包大小(encrypt 参数决定是否包含 MIC)。

使用指南:从初始化到事件处理

阻塞式 API 语义

README "Blocking functions" 一节指出:所有 API 函数都是阻塞的——它们会等待射频核完成处理后才返回(但不等候实际的数据包发射/接收完成)。换句话说,调用返回只代表命令已送达射频核并被处理,真正的空中收发结果要通过事件回调获知。

Radio proxy 配置(无论 HAL/LLD 都必须先做)

由于双核架构,用户无法直接访问射频核,必须通过proxy控制射频。而 BLE LLD 与两核间的通信层解耦,因此需要应用侧把 proxy "接上线"——这些接线函数统一以BLE_LLD_PRX_为前缀(README "Radio proxy configuration" 一节):

  • BLE_LLD_PRX_Init():必须第一个调用,用于配置射频核 proxy。函数签名(见ble_lld.h)为:
    void BLE_LLD_PRX_Init(param_BLE_LLD_t *parameters, ipBLE_lld_txrxdata_Type *transmitBuffer, ipBLE_lld_txrxdata_Type *receiveBuffer, uint8_t (*callbackSend)(BLE_LLD_Code_t bleCmd));

    它需要传入命令参数联合体param_BLE_LLD_t、Tx/Rx 共享内存缓冲,以及一个发送回调callbackSend——这个回调把BLE_LLD_Code_t命令码实际投递到两核通信层,正是"BLE LLD 与传输层解耦、由应用接线"的体现。

  • BLE_LLD_PRX_EventProcessInter():在射频事件中断内部调用,负责记录事件数据(见下文事件机制);
  • BLE_LLD_PRX_EventProcessTask():在中断之后的某个任务上下文中调用,负责执行用户回调。

README 特别提醒:无论使用 LLD 还是 HAL API,proxy 配置都是必需的

射频事件与回调机制

用户在启动/配置一次射频操作时可以注册回调函数(README "Radio events" 一节)。当射频核发生事件(如发送成功、接收失败等),BLE LLD proxy 被通知,进而运行针对该事件注册的回调,从而让用户应用对射频事件做出反应。

事件类型在ipBLE_lld_public.h中完整定义,共 12 种,命名规律为[TX|RX]_[OK|FAIL|TIMEOUT|CRC_KO]_[BUSY|READY]

TX_OK_BUSY / TX_OK_READY /* 发送成功,radio busy / ready */ TX_FAIL_BUSY / TX_FAIL_READY /* 发送失败,radio busy / ready */ RX_OK_BUSY / RX_OK_READY /* 接收成功,radio busy / ready */ RX_TIMEOUT_BUSY / RX_TIMEOUT_READY /* 接收超时,radio busy / ready */ RX_CRC_KO_BUSY / RX_CRC_KO_READY /* 接收 CRC 错误,radio busy / ready */ RX_FAIL_BUSY / RX_FAIL_READY /* 接收失败(其他原因),radio busy / ready */

radioEventType枚举从 1 开始(0 被保留),且最多 32 个事件(与中断过滤实现相关)。ble_lld.c实现了两阶段事件处理:

  • 中断阶段BLE_LLD_PRX_EventProcessInter():根据params->reply.actionPacketNb找到对应的ActionPacket,保存事件与状态,并在RADIO_IS_RX_OKRX_OK_BUSYRX_OK_READY)时记录 RSSI;
  • 任务阶段BLE_LLD_PRX_EventProcessTask():若该动作包注册了回调,则在RADIO_IS_RX_OK时先从 Rx 缓冲就地提取数据,然后调用radioEventAp->callback(radioEvent, radioEventAp, data, size)

调试时可使用eventToString()ble_lld.h/ble_lld.c)将事件枚举转为可读字符串。

HAL 接口:简单通信的正确打开方式

HAL 是 LLD 之上的一层封装,目标是让简单通信更省事。README 明确说明:HAL 提供特性受限的简单 API,适用于不需要自定义动作包链接的场景;底层它通过调用 LLD 来配置若干动作包

配置流程(README "Configuration"):先用HAL_BLE_LLD_Init()初始化,再用HAL_BLE_LLD_Configure()配置。完整函数签名见 lib/stm32wb_copro/wpan/ble_lld/hal/ble_hal.h:

uint8_t HAL_BLE_LLD_Init(uint16_t hsStartupTime, bool lsOscInternal); uint8_t HAL_BLE_LLD_Configure(txPower_t txPower, uint8_t channel, bool phy2mbps, uint32_t b2bTimeUs, uint32_t networkId);

通信接口分为两组(README "Communication"):

  • 无 ACKHAL_BLE_LLD_SendPacket()/HAL_BLE_LLD_ReceivePacket()——射频只发射/接收一个数据包;
  • 带 ACKHAL_BLE_LLD_SendPacketWithAck()/HAL_BLE_LLD_ReceivePacketWithAck()——射频发射/接收一个数据包后,另一个数据包向相反方向发送。

"带 ACK"的函数可用于检测丢包,因此可以在此基础上实现带重传的可靠通信信道(README 原话)。ACK 方向的具体实现由 README 中"ReceivePacketWithAck 动作链图"给出,见下文 LLD 部分。

LLD 接口:全功能与自定义动作链

LLD 暴露射频核心支持的全部特性,API 更复杂,用于实现自定义动作包链接(README "LLD interface" 一节)。

配置流程:先用BLE_LLD_Init()初始化,再用下列函数配置:

  • BLE_LLD_SetChannel(StateMachineNo, channel)—— 设置状态机对应的射频信道;
  • BLE_LLD_SetTxAttributes(StateMachineNo, NetworkID)—— 设置网络 ID(即包地址匹配依据,对应 README 中"address must match configured address of the recipient");
  • BLE_LLD_SetTxPower(powerLevel)—— 设置发射功率,txPower_t枚举覆盖TX_POW_MIN_40_DB(0)到TX_POW_PLUS_6_DB(31)共 32 档,例如TX_POW_MIN_20_85_DB表示约 -20.85 dB;
  • BLE_LLD_SetTx_Rx_Phy(StateMachineNo, txPhy, rxPhy)—— 设置收发 PHY,宏定义RX_PHY_1MBPS 0x00RX_PHY_2MBPS 0x10TX_PHY_1MBPS 0x00TX_PHY_2MBPS 0x01

BLE_LLD_Init()的签名是:

void BLE_LLD_Init(uint16_t hsStartupTime, uint8_t lowSpeedOsc, FunctionalState whitening);

它对应传输层命令BLE_LLD_INIT_CMDCODE的参数param_BLE_LLD_init_tstartupTimelowSpeedOscwhitening),whitening 即数据白化开关。

通信流程:使用 LLD API 时,每个动作包都要由用户负责配置(README 原话):

  1. 为期望的动作设置ActionPacket的全部必需字段(部分字段仅 Tx 有效,部分仅 Rx 有效);
  2. 调用BLE_LLD_SetReservedArea()把动作包下发到射频核;
  3. 对链首动作包调用BLE_LLD_MakeActionPacketPending()启动执行——射频核将依据各动作包的配置与操作结果自动链接后续动作包;
  4. 每个动作包结束时,若注册了回调,会向应用发送事件。

中断动作链BLE_LLD_StopActivity()可随时终止动作包链接。README 强调:该调用会"杀掉"射频,之后任何其他操作前都必须重新初始化。源码注释同样说明BLE_LLD_StopActivity()对应BLE_LLD_STOPACTIVITY_CMDCODE

状态码ipBLE_lld_public.h定义了一组命令返回码:SUCCESS_0INVALID_PARAMETER_C0(无效参数)、WAKEUP_NOTSET_C2(唤醒未设置)、RADIO_BUSY_C4(射频忙)、COMMAND_DISALLOWED(命令不允许),可作为MakeActionPacketPending等函数的返回判断依据。

一个完整的动作链示例:带 ACK 的接收

README 用图示展示了HAL_BLE_LLD_ReceivePacketWithAck()底层的动作包编排(三个动作包组成的链):

START → Action Packet 2 (reception, wake-up, timeout) ├─ SUCCESS → Action Packet 3 (transmission, back-to-back) │ ├─ SUCCESS → STOP │ └─ FAILURE → STOP └─ FAILURE → STOP

执行逻辑(README 原话转述):

  • Action Packet 2 首先执行,配置数据包的接收;
  • 若数据正确收到(CRC OK),则执行Action Packet 3,配置 ACK 包的发射;
  • 之后射频停止;
  • 若任一动作包失败,射频停止。

可以看到,nextTrue/nextFalse的双分支字段正是这个链的控制骨架:动作包 2 的失败分支直接指向停止,成功分支指向动作包 3;动作包 3 的成功/失败分支都指向停止。这正是"自定义协议可靠通信"(如自动应答)的最小范式。

附加工具:Tone 生成(测试用)

为了测试目的,BLE LLD 提供单音发射功能(README "Tone generation" 一节):

  • BLE_LLD_StartTone(rfChannel, powerLevel):开始发射单音,参数为射频信道(0-39)与输出功率等级(0-31),对应传输层param_BLE_LLD_toneStart_t
  • BLE_LLD_StopTone():停止单音。

源码注释补充了两个要点:这两个函数专用于测试,且会销毁上下文与多状态配置("destroys context and multistate"),因此单音发射后、进行任何其他操作之前,射频必须重新初始化(与StopActivity后的要求一致)。

源码结构速览

若要在 Flipper Zero 固件仓库中进一步深入 BLE LLD,可沿以下路径阅读:

关注点文件
官方 README(本文骨架)lib/stm32wb_copro/wpan/ble_lld/README.md
LLD 头文件:ActionPacket、全部 API 声明lib/stm32wb_copro/wpan/ble_lld/lld/ble_lld.h
LLD 实现:proxy 事件处理、packet 准备/提取lib/stm32wb_copro/wpan/ble_lld/lld/ble_lld.c
公共定义:ActionTag 位域、事件枚举、功率/PHY 枚举lib/stm32wb_copro/wpan/ble_lld/lld/ipBLE_lld_public.h
传输层命令码与参数结构lib/stm32wb_copro/wpan/ble_lld/lld/ble_lld_transport.h
HAL 封装头文件lib/stm32wb_copro/wpan/ble_lld/hal/ble_hal.h
两核通信传输层参考实现lib/stm32wb_copro/wpan/interface/patterns/ble_thread/tl/

小结:BLE LLD 的价值在于把 STM32WB 的 BLE 射频硬件抽象成可编程的、支持动作包链接的专有协议引擎。理解ActionPacketnextTrue/nextFalse分支与TIMER_WAKEUP定时器模式,是设计低延迟自定义射频协议的关键;而BLE_LLD_PRX_系列接线函数则体现了它"与 IPCC 传输层解耦"的架构取舍——接入不同通信层时,只需替换BLE_LLD_PRX_Init()传入的callbackSend实现。

【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 4:35:56

遗传算法突变风险测试:DEAP/PIT/Hypothesis三款工具实战指南

做软件测试这些年,我接过不少“算法型”系统的测试任务,最头疼的往往不是功能逻辑,而是那种“跑十次结果十个样”的随机性系统。尤其是基于基因算法(Genetic Algorithm,国内也叫遗传算法)的程序——它靠选择…

作者头像 李华
网站建设 2026/9/15 4:35:29

CISP认证2026版考试指南与高效备考策略

1. CISP认证核心价值解析CISP(Certified Information Security Professional)作为国内信息安全领域最具权威性的专业认证之一,其含金量主要体现在三个维度:首先,它是由中国信息安全测评中心直接颁发的国家级证书&#…

作者头像 李华
网站建设 2026/9/15 4:34:58

本地运行AI助手:从成本失控到算力自主的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 4:34:48

免费企业网站模板php实战案例:5个规范救活丑站

免费企业网站模板php实战案例:5个规范救活丑站 还在忍受那种一眼假、排版乱的免费PHP模板?真的,模板网站太丑不够用是常态。很多兄弟花大价钱买的“高端模板”,上线后客户还是跑单,问题出在哪?不是代码,是设计没章法。我见过太多 实战案例…

作者头像 李华
网站建设 2026/9/15 4:33:53

豆瓣短评爬虫与可视化:从反爬应对到交互看板的完整实践

简介:一套基于Python的豆瓣网站爬虫与数据可视化完整源码,主要面向需要完成Python课程期末大作业、毕业设计或课程设计的在校学生,无论是答辩展示还是二次开发参考,都有较高价值。项目实现了豆瓣数据的自动抓取、清洗、存储与可视…

作者头像 李华
网站建设 2026/9/15 4:31:57

Spring Boot植物健康管理系统实战:从需求到部署全流程复盘

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华