LED 与显示控制
本文档介绍 AW33N BLE SDK 中 LED 指示灯与显示控制的完整实现:从应用层事件(连接状态、按键、电量)到指示灯状态机(led_control.c)、可配置灯效 API(led_api.c),再到 PWM/LEDC 硬件驱动的分层工作机制、数据结构、配置项与扩展方式。
Purpose and Scope
本页覆盖 LED 显示控制的完整链路:
- 应用层指示控制:
apps/app/bsp/common/led/led_control.c/led_control.h中的 LED 状态机(开机、等待连接、按键反馈、低电、关机等状态)与定时器调度; - 灯效抽象层:
apps/include_lib/cpu/led_api.h与apps/app/bsp/cpu/led_api.c提供的可配置灯效平台数据模型(布局模式、逻辑极性、控制模式)及其到 PWM LED 驱动的映射; - 硬件驱动层:
pwm_led_v2.c、pwm_led_clk.c、ledc.c、ledc_hal.h、pled_hal.h以及板级配置脚本pwmled_v1.lua。
本页不覆盖:LCD/OLED 屏显驱动、蓝牙主从通信协议本身、电源管理整体流程(仅提及与 LED 相关的低电/关机联动)。这些内容属于各自的目录页面。
Overview
在 TWS/蓝牙耳机类产品中,LED 指示灯承担两类职责:
- 状态指示:通过 GPIO 直控方式表达设备状态(开机闪烁、等待配对、连接成功、低电、关机),其行为由一个集中的状态机驱动,避免各业务模块各自操作 GPIO 导致冲突;
- 灯效表现:通过 PWM 波形实现单闪、双闪、呼吸、常亮/常灭等周期性灯效,由
led_api层将"灯效配置"翻译为底层硬件参数,与应用逻辑解耦。
系统为此设计了三层解耦结构:应用层只关心"当前处于什么状态"(调用 led_operate() / led_set_connect_flag());灯效层只关心"要呈现什么灯效"(填充 led_pdata_t);硬件层只关心"如何输出波形"(pwm_led_* / LEDC 寄存器)。这种分层使灯珠数量、IO 布局、极性变化时只需修改板级配置,而无需改动业务代码。
Architecture
flowchart TD
subgraph sg_App["应用层"]
APP["蓝牙协议栈 / 按键 / 电源模块<br/>连接事件 · 按键事件 · 电量事件"]
end
subgraph sg_Ctl["指示灯控制层"]
LC["led_control.c<br/>LED 状态机 + 定时器调度"]
LA["led_api.c<br/>灯效配置映射"]
end
subgraph sg_Drv["硬件驱动层"]
PWM["pwm_led_v2.c<br/>PWM 波形输出"]
CLK["pwm_led_clk.c<br/>PWM 时钟配置"]
LEDC["ledc.c / ledc_hal.h<br/>LEDC 外设"]
GPIO["gpio 驱动<br/>gpio_write / gpio_set_mode"]
end
subgraph sg_Cfg["板级配置"]
CFG["app_config.h<br/>TCFG_LED_ENABLE / TCFG_LED_PIN1 / TCFG_LED_PIN2"]
LUA["pwmled_v1.lua<br/>PWM LED 板级参数"]
end
subgraph sg_HW["硬件"]
LED[("LED 灯珠")]
end
APP -->|"led_set_connect_flag / led_operate / led_low_power"| LC
LC -->|"led_effect_output"| LA
LA -->|"pwm_led_pdata_t"| PWM
LA --> LEDC
LC -->|"直接 GPIO 开关"| GPIO
CFG --> LC
LUA --> PWM
PWM --> CLK
PWM --> LED
LEDC --> LED
GPIO --> LED
分层职责说明
| 层次 | 文件 | 职责 |
|---|---|---|
| 应用层 | 协议栈 / 按键 / 电源模块 | 产生系统事件,调用 LED 控制接口 |
| 指示控制层 | led_control.c | 维护 LED 状态机、闪烁定时器、低电/关机联动、主从灯控命令解析(0x66 指令) |
| 灯效层 | led_api.c / led_api.h | 定义灯效平台数据结构,将 led_pdata_t 翻译为 pwm_led_pdata_t 或软件模拟输出 |
| 驱动层 | pwm_led_v2.c、pwm_led_clk.c、ledc.c | 产生 PWM/呼吸波形、配置时钟与 IO 复用 |
| 板级配置 | app_config.h、pwmled_v1.lua | 声明灯控引脚、使能开关、灯珠布局与 PWM 参数 |
这样设计的原因:状态指示与灯效输出是两种不同的关注点。状态机需要"知道"业务上下文(是否连接、是否低电),而灯效输出只关心"引脚怎么动"。把两者分开,业务代码可以独立演进(例如新增一种状态只需在枚举中加一项并处理对应分支),硬件改动(换 IO、换极性)也不会污染业务逻辑。
指示灯控制:led_control.c 状态机
状态枚举与设计意图
led_control.h 定义了完整的指示灯状态集合。状态不是简单的"开/关",而是把产品的典型生命周期事件显式建模为状态,使各业务模块可以通过一个统一入口 led_operate() 表达意图,而由状态机内部决定实际 GPIO 行为:
enum {
LED_NULL = 0,
LED_INIT,
LED_INIT_FLASH,
LED_WAIT_CONNECT,
LED_AUTO_CONNECT,
LED_KEY_UP,
LED_KEY_HOLD,
LED_KEY_IO_VAILD,
LED_CLOSE,
LED_POWER_OFF,
LED_LOW_POWER,
LED_ON,
LED_OFF
};
Source: led_control.h
各状态含义:
| 状态 | 含义 |
|---|---|
LED_NULL | 空状态/未初始化 |
LED_INIT | 系统上电初始化,点亮并初始化灯控 IO |
LED_INIT_FLASH | 初始化闪烁(开机提示) |
LED_WAIT_CONNECT | 未连接等待配对,按 LED_FLASH_1S 周期闪灯 |
LED_AUTO_CONNECT | 自动回连进行中(AUTO_CONNECT_TIME_MS = 8s 内) |
LED_KEY_UP / LED_KEY_HOLD | 按键单击/长按反馈 |
LED_KEY_IO_VAILD | 按键 IO 有效反馈 |
LED_CLOSE | 关闭指示灯 |
LED_POWER_OFF | 关机流程 |
LED_LOW_POWER | 低电告警 |
LED_ON / LED_OFF | 常亮 / 常灭 |
状态机实现要点
led_control.c 中状态机的核心实现在 led_operate()。值得注意的设计决策是低电状态屏蔽按键灯效——在电量告警时,按键反馈属于非关键信息,让出指示灯资源以优先传达低电告警,避免用户误判:
static void led_operate(uint8_t state)
{
uint8_t prev_state = led_state;
// 低电状态时屏蔽按键led
if (prev_state == LED_LOW_POWER && (state == LED_KEY_UP || state == LED_KEY_HOLD)) {
return;
}
led_state = state;
led_io_flash = 0;
switch (state) {
case LED_INIT:
#if BLE_SLAVE_CLIENT_LED_OP_EN
LED1_INIT();
...
Source: led_control.c
实现特征:
- 模块级静态变量保存状态:
led_state(当前态)、led_next_state(下一个态,用于定时器到期后的迁移)、led_io_flash(闪烁计数)、led_ble_connect(连接标志)、led_timer_id(软件定时器句柄)、led_timeout_count(超时计数); - 每次切换状态都会清零
led_io_flash,保证闪烁相位不会在状态间串扰; LED1_INIT()/LED1_ON()/LED1_OFF()等宏直接封装gpio_write/gpio_set_mode,IO 引脚由TCFG_LED_PIN1/TCFG_LED_PIN2编译期决定:
#ifdef TCFG_LED_PIN1
#define LED1_ON() gpio_write(TCFG_LED_PIN1, 1)
#define LED1_OFF() gpio_write(TCFG_LED_PIN1, 0)
#define LED1_INIT() gpio_set_mode(IO_PORT_SPILT(TCFG_LED_PIN1), PORT_OUTPUT_LOW)
#endif
Source: led_control.c
定时器调度机制
状态机的"闪灯"与"超时迁移"依赖软件定时器(led_timer_start(time_ms) / led_timer_stop())。典型时间参数定义在 led_control.h:
| 宏 | 值 | 用途 |
|---|---|---|
LED_FLASH_1S | 1 | 未连接闪灯周期(1s 或 0.5s) |
SOFT_OFF_TIME_MS | 10 * 60 * 1000 | 未连接状态 10 分钟后软关机 |
AUTO_CONNECT_TIME_MS | 8000L | 自动回连超时窗口 |
POWER_LED_ON_TIME_MS | 1000L | 开机灯亮时长 |
Source: led_control.h
定时器到期回调中根据当前 led_state 决定下一个状态,并维护 led_timeout_count 计数以支持"闪 N 次后停止"这类时序;未连接计时到达 SOFT_OFF_TIME_MS 时联动电源模块执行软关机。
对外接口与主从灯控
void led_operate(u8 state); // 状态迁移入口
void led_low_power(bool enable); // 低电告警使能
void led_set_connect_flag(bool is_connect); // 设置连接标志(协议栈事件驱动)
void led_onoff_op(on_off_opcode_t *opcode); // 主从灯控命令执行
Source: led_control.h
其中主从灯控(蓝牙耳机双耳同步灯效)通过 on_off_opcode_t 携带的指令执行:
typedef struct {
u8 led_opcode; // 指令码,如 LED_ON_OFF_OPCODE(0x66)
u8 led_num; // LED 编号:LED1(1) / LED2(2)
u8 led_status; // 状态:LED_ON_STATUS(1) / LED_OFF_STATUS(0)
} on_off_opcode_t;
Source: led_control.h
该机制由 BLE_SLAVE_CLIENT_LED_OP_EN 控制编译开关,仅当板级同时定义了 TCFG_LED_PIN1 与 TCFG_LED_PIN2(即存在可独立控制的双灯)时使能:
#if defined(TCFG_LED_PIN1) && defined(TCFG_LED_PIN2)
#define BLE_SLAVE_CLIENT_LED_OP_EN 1 // 是否开启主从灯控效果
#else
#define BLE_SLAVE_CLIENT_LED_OP_EN 0
#endif
Source: led_control.h
设计意图:主从灯控本质上是把"本机状态"同步到"对端耳机"的过程——主机侧通过蓝牙链路把 0x66 指令发给从机,从机调用 led_onoff_op() 执行相同灯效。将命令封装为 (opcode, led_num, led_status) 三元组,使传输层只需搬运结构体而无需理解灯效细节。
灯效抽象层:led_api 平台数据模型
led_api.h 是灯效能力的"契约"层。它把千变万化的硬件布局抽象为四个枚举与两个结构体,上层只需按产品需求填表,底层驱动负责落地。
布局模式与逻辑极性
enum led_layout_mode { //根据原理图选择
ONE_IO_ONE_LED, //单IO单灯
ONE_IO_TWO_LED, //单IO双灯
TWO_IO_TWO_LED, //双IO双灯
THREE_IO_TWO_LED, //三IO双灯, 即两灯的阴极都连到第三IO
};
enum led_logic_mode {
BRIGHT_BY_LOW, //给低电平亮
BRIGHT_BY_HIGH, //给高电平亮
};
Source: led_api.h
- 布局模式回答"几根 IO 控制几颗灯":单 IO 单灯最简单;单 IO 双灯靠交替输出区分;三 IO 双灯时两灯共阴极接到第三 IO(
com_pole_port)。 - 逻辑极性回答"高电平还是低电平点亮",直接对应原理图上 LED 的接法,避免在业务层出现"反相"魔数。
控制选项与灯效模式
enum led_ctl_option {
CTL_LED0_ONLY, //只控led0
CTL_LED1_ONLY, //只控led1
CTL_LED01_ASYNC, //led0&led1异步(交替)
CTL_LED01_SYNC, //led0&led1同步
};
enum led_ctl_mode {
CYCLE_ONCE_BRIGHT, //周期单闪
CYCLE_TWICE_BRIGHT, //周期双闪
CYCLE_BREATHE_BRIGHT, //周期呼吸
ALWAYS_BRIGHT, //常亮
ALWAYS_EXTINGUISH, //常灭
};
Source: led_api.h
控制选项解决"多灯系统里让哪颗灯动",灯效模式解决"灯怎么动"。两者正交组合可覆盖绝大多数产品需求:例如"左耳呼吸、右耳常亮"= CTL_LED0_ONLY + CYCLE_BREATHE_BRIGHT。
平台数据结构
one_led_pdata_t 描述单颗灯的平台参数(端口、极性、亮度):
typedef struct one_led_platform_data {
u8 port; //控灯IO
u8 logic; //参考枚举led_logic_mode
u8 brightness; //灯的亮度0~100
} one_led_pdata_t;
led_pdata_t 则是完整的灯效请求:板级配置指针 + 控制选项 + 灯效模式 + 时序参数 + 完成回调:
typedef struct led_platform_data {
const led_board_cfg_t *board_cfg;
u8 ctl_option; //参考枚举led_ctl_option
u8 ctl_mode; //参考枚举led_ctl_mode
u8 ctl_cycle; //控制周期, 单位50ms, 比如每5s闪一次灯,那么5s就是控制周期
u8 ctl_cycle_num; //控制周期的个数,值为0时无限循环,值为n时第n次之后灯自动关闭
union {
struct { //周期单闪
u8 bright_time; //灯亮的时间,单位50ms
} once_bright;
struct { //周期双闪
u8 first_bright_time; //第一次灯亮的时间,单位50ms
u8 bright_gap_time; //间隔时间,单位50ms
u8 second_bright_time; //第二次灯亮的时间,单位50ms
} twice_bright;
struct { //周期呼吸
u8 bright_time; //灯亮的时间,单位50ms
u8 brightest_keep_time; //亮度最大时保持时间,单位50ms,需小于 bright_time
} breathe_bright;
};
void (*cbfunc)(u32 cnt); //灯效结束回调函数
} led_pdata_t;
Source: led_api.h
设计意图:
- 时间以 50ms 为基本单位(
u8即可表达最大 12.75s),用 1 字节换取低内存占用,适合小 RAM 嵌入式平台;代价是时序分辨率 50ms,对指示灯闪烁足够; ctl_cycle_num = 0表示无限循环,n表示循环 n 次后自动熄灭并触发cbfunc——这使"闪 3 次后灭"这类时序无需业务层自己数数;- union 按灯效模式共享时序字段,不同模式互不干扰,同时避免结构体膨胀。
灯效到硬件驱动的映射
led_api.c 中的 led_effect_output_by_hardware() 是核心翻译函数:把 led_pdata_t 转换为 PWM LED 驱动需要的 pwm_led_pdata_t。关键逻辑包括:
- 按布局模式分配端口:
ONE_IO_ONE_LED时port1 = -1(禁用第二路); - 按逻辑极性决定 PWM 占空比挂在
h_pwm_duty(高亮)还是l_pwm_duty(低亮),并设置first_logic; - 按控制选项决定
alternate_out(交替输出)与端口使能:CTL_LED01_ASYNC强制交替,CTL_LED01_SYNC在两灯极性一致时同步输出、不一致时自动转交替; - 把 50ms 单位的周期换算为毫秒(
ctl_cycle * 50)后传给驱动:
static void led_effect_output_by_hardware(led_pdata_t *led_effect)
{
u32 time_unit = 50;
pwm_led_pdata_t pled;
memset(&pled, 0, sizeof(pwm_led_pdata_t));
if (led_board_cfg->layout == ONE_IO_ONE_LED) {
pled.port0 = led_board_cfg->led0.port;
pled.port1 = -1;
if (led_board_cfg->led0.logic == BRIGHT_BY_HIGH) {
pled.first_logic = 0;
pled.h_pwm_duty = led_board_cfg->led0.brightness;
} else {
pled.first_logic = 1;
pled.l_pwm_duty = led_board_cfg->led0.brightness;
}
pled.alternate_out = 0;
} else {
...
}
pled.out_mode = led_effect->ctl_mode;
pled.ctl_cycle = led_effect->ctl_cycle * time_unit;
pled.ctl_cycle_num = led_effect->ctl_cycle_num;
pled.cbfunc = led_effect->cbfunc;
...
}
Source: led_api.c
映射完成后,led_effect_output() 依据板级时钟(pwm_led_clk_freq())判断是否具备硬件 PWM 能力:可用时走 pwm_led_* 硬件输出路径,否则退化为软件模拟输出,保证灯效 API 在任意板级配置下行为一致。板级初始化入口为 led_effect_board_init(const led_board_cfg_t *cfg),保存 board_cfg 全局指针供后续输出使用。
核心流程
状态指示流程(连接事件驱动)
sequenceDiagram
participant BT as 蓝牙协议栈
participant LC as led_control.c
participant TM as 软件定时器
participant PWM as pwm_led_v2.c
participant LED as LED 灯珠
BT->>LC: led_set_connect_flag(true)
LC->>LC: 更新 led_ble_connect 并迁移状态
LC->>TM: led_timer_start(周期)
TM-->>LC: 定时器回调
LC->>LC: 按当前状态切换亮/灭/闪
LC->>PWM: led_effect_output(&led_effect)
PWM->>LED: PWM 波形 / GPIO 电平
LC->>TM: led_timer_stop()(状态退出)
灯效输出流程(led_effect_output 内部)
flowchart TD
Start([调用 led_effect_output]) --> Chk{"board_cfg 已初始化?"}
Chk -->|"否"| Ret["直接返回"]
Chk -->|"是"| Map["映射 led_pdata_t 到 pwm_led_pdata_t"]
Map --> Freq{"pwm_led_clk_freq 可用?"}
Freq -->|"是"| HW["pwm_led 硬件输出"]
Freq -->|"否"| SW["软件模拟输出"]
HW --> Done([灯效周期完成])
SW --> Done
Done --> Cb{"cbfunc 非空?"}
Cb -->|"是"| Call["回调 cbfunc(cnt)"]
Cb -->|"否"| End([结束])
Call --> End
注:硬件/软件分支为
led_api.c中依据pwm_led_clk_freq()判断的时钟可用性逻辑;软件模拟路径与回调细节请以实际代码为准。
使用示例
示例 1:定义板级灯效配置并输出
业务模块按 LED_PLATFORM_DATA_BEGIN/END 宏填写灯效参数(周期、模式、时序),然后调用 led_effect_output() 生效:
LED_PLATFORM_DATA_BEGIN(led_effect)
.board_cfg = &board_cfg,
.ctl_option = CTL_LED01_SYNC,
.ctl_mode = CYCLE_TWICE_BRIGHT,
.ctl_cycle = 100, // 控制周期 100 * 50ms = 5s
.ctl_cycle_num = 0, // 无限循环
.twice_bright = {
.first_bright_time = 2, // 第一次亮 100ms
.bright_gap_time = 1, // 间隔 50ms
.second_bright_time = 2 // 第二次亮 100ms
},
LED_PLATFORM_DATA_END()
led_effect_output(&led_effect);
Sources:
该示例演示"每 5s 双闪两次(亮 100ms、间隔 50ms、再亮 100ms)且无限循环"的经典配对提示灯效。
示例 2:状态机驱动的指示灯切换
应用模块(如按键、蓝牙)通过统一入口切换指示灯状态,无需关心 GPIO 细节:
// 开机流程
led_operate(LED_INIT);
led_operate(LED_INIT_FLASH);
// 蓝牙事件
led_set_connect_flag(true); // 连接成功
led_operate(LED_WAIT_CONNECT);// 未连接时进入等待配对闪烁
// 按键反馈(低电时会被状态机自动屏蔽)
led_operate(LED_KEY_UP);
led_operate(LED_KEY_HOLD);
Sources:
- API 声明:led_control.h
- 状态枚举:led_control.h
示例 3:主从灯控命令
双耳产品中,主机把 0x66 指令通过蓝牙链路发给从机,从机执行相同灯效:
on_off_opcode_t opcode = {
.led_opcode = LED_ON_OFF_OPCODE, // 0x66
.led_num = LED1, // LED1
.led_status = LED_ON_STATUS, // 亮
};
led_onoff_op(&opcode);
Source: led_control.h
示例 4:硬件映射的端口/极性决策
以下代码展示 led_api.c 如何根据灯效的控制选项与灯珠极性,决定 PWM 双通道的端口与占空比归属(CTL_LED01_ASYNC 分支):
} else if (led_effect->ctl_option == CTL_LED01_ASYNC) {
pled.first_logic = 0;
pled.alternate_out = 1;
if (led_board_cfg->led0.logic == BRIGHT_BY_HIGH) {
pled.h_pwm_duty = led_board_cfg->led0.brightness;
pled.l_pwm_duty = led_board_cfg->led1.brightness;
} else {
pled.h_pwm_duty = led_board_cfg->led1.brightness;
pled.l_pwm_duty = led_board_cfg->led0.brightness;
}
}
Source: led_api.c
注意:alternate_out = 1 使两路 PWM 交替输出,配合 h_pwm_duty/l_pwm_duty 分别承载两灯亮度,实现"异步/交替"效果。
配置选项
| 配置项 | 类型 | 默认/示例 | 说明 |
|---|---|---|---|
TCFG_LED_ENABLE | 宏 | — | 指示灯控制总开关(led_control.c 中 #if TCFG_LED_ENABLE) |
TCFG_LED_PIN1 | 宏 | IO 编号 | 灯 1 控制引脚(led_control.c 据此生成 LED1_* 宏) |
TCFG_LED_PIN2 | 宏 | IO 编号 | 灯 2 控制引脚;与 PIN1 同时定义时使能主从灯控 |
BLE_SLAVE_CLIENT_LED_OP_EN | 宏 | 0/1(自动推导) | 主从灯控开关,由 TCFG_LED_PIN1 && TCFG_LED_PIN2 决定 |
LED_FLASH_1S | 宏 | 1 | 未连接闪灯周期(1s 或 0.5s) |
SOFT_OFF_TIME_MS | 宏 | 10*60*1000 | 未连接 10 分钟后软关机 |
AUTO_CONNECT_TIME_MS | 宏 | 8000L | 自动回连超时窗口 |
POWER_LED_ON_TIME_MS | 宏 | 1000L | 开机灯亮时长 |
one_led_pdata_t.brightness | 字段 | 0~100 | 单灯亮度百分比 |
led_pdata_t.ctl_cycle | 字段 | 单位 50ms | 灯效控制周期 |
led_pdata_t.ctl_cycle_num | 字段 | 0=无限 | 控制周期个数,n 次后自动熄灭 |
pwmled_v1.lua(板级脚本) | 文件 | bd57 板级 | PWM LED 的板级参数(时钟/IO/通道) |
API 参考
void led_operate(u8 state) — led_control.h
指示灯状态迁移统一入口。state 取 led_control.h 中状态枚举值。状态机内部会处理低电屏蔽(LED_LOW_POWER 时忽略按键类状态)、清闪烁相位、启动/停止定时器。
参数:
state(u8):目标状态,如LED_INIT、LED_WAIT_CONNECT、LED_KEY_UP、LED_LOW_POWER。
返回: 无
注意: 低电状态下传入 LED_KEY_UP/LED_KEY_HOLD 会被静默忽略。
void led_low_power(bool enable) — led_control.h
使能/关闭低电告警灯效。使能后状态机进入 LED_LOW_POWER,并触发对按键灯效的屏蔽。
参数:
enable(bool):true进入低电告警,false退出。
void led_set_connect_flag(bool is_connect) — led_control.h
由蓝牙协议栈连接事件驱动,更新 led_ble_connect 标志,供状态机决定进入 LED_WAIT_CONNECT 或连接成功后的灯效。
参数:
is_connect(bool):true已连接,false未连接。
void led_onoff_op(on_off_opcode_t *opcode) — led_control.h
执行主从灯控命令(对端耳机同步灯效)。opcode 携带指令码(0x66)、灯号(LED1/LED2)与开关状态(LED_ON_STATUS/LED_OFF_STATUS)。
参数:
opcode(on_off_opcode_t *):灯控指令结构体指针。
void led_effect_board_init(const led_board_cfg_t *cfg) — led_api.h
初始化灯效板级配置,保存 board_cfg 全局指针。必须在首次 led_effect_output() 之前调用。
参数:
cfg(const led_board_cfg_t *):板级灯珠布局配置(led0/led1 平台参数、layout、com_pole_port)。
void led_effect_output(led_pdata_t *led_effect) — led_api.h
按 led_pdata_t 描述的灯效请求输出。内部先做硬件能力判断:可用则映射为 pwm_led_pdata_t 走 PWM 硬件输出,否则软件模拟。
参数:
led_effect(led_pdata_t *):灯效平台数据,含控制选项、模式、周期、周期数与完成回调cbfunc。
故障模式、边界情况与并发
状态冲突与优先级
- 低电屏蔽按键:
led_operate()在LED_LOW_POWER状态下拒绝LED_KEY_UP/LED_KEY_HOLD,防止低电告警被按键反馈覆盖。这是有意的优先级策略——电量信息优先于交互反馈。 - 状态覆盖:
led_operate()采用"后到先得",新状态直接覆盖led_state并清零led_io_flash。若业务侧事件顺序异常(如连接事件与按键事件竞态),最终灯效由最后到达的状态决定。因此协议栈应在合适的时机(如连接建立后)调用led_set_connect_flag()重新收敛状态。
边界时序
ctl_cycle_num语义:0表示无限循环,非 0 表示第 n 个周期后自动熄灭。若上层既想循环又想随时停,应配合cbfunc回调或主动调用led_operate()切换状态,而不是依赖计数耗尽。- 呼吸模式约束:
breathe_bright.brightest_keep_time必须小于bright_time(注释明确说明),否则占空比自增自减的时间与保持时间重叠,波形会失真。上层配置时需校验。 - 50ms 粒度:所有时序字段以 50ms 为单位(
u8存储),单字段最大 12.75s。需要更长周期的场景应调整ctl_cycle(周期放大)而非单个时序字段。
并发与中断上下文
led_control.c的状态变量(led_state、led_timer_id等)均为模块级静态变量,状态机逻辑在任务/定时器回调上下文中执行。软件定时器回调与任务代码若并发访问同一状态,需保证业务侧串行调用led_operate()(典型做法:全部通过同一消息队列驱动),避免状态撕裂。- GPIO 宏(
gpio_write/gpio_set_mode)与 PWM 输出由硬件外设承担,原子性由外设保证;上层只需保证"同一时刻只下发一种灯效请求"。
硬件能力降级
led_api.c 依据 pwm_led_clk_freq() 判断 PWM 时钟是否可用:时钟不可用时灯效走软件模拟路径。这意味着同一套 led_pdata_t 配置在不同板级(有无 PWM 时钟)下表现可能略有差异(如呼吸的平滑度),上层不应依赖呼吸波形的精确时序。
性能与运维
- 低开销设计:状态机与灯效层均为纯 CPU 逻辑 + 定时器驱动,无轮询忙等;
u8紧凑结构体把 RAM 占用压到最小,适合小内存 MCU。 - 定时器资源:
led_timer_start/stop使用单个软件定时器句柄(led_timer_id),同一时刻只存在一个 LED 定时器。若未来需要"多灯独立时序",需扩展为定时器数组。 - 编译期裁剪:未定义
TCFG_LED_ENABLE时led_control.c主体不编译;未定义灯控引脚时主从灯控自动关闭。发布不同 SKU 时通过配置文件裁剪,避免无效代码进入固件。 - 调试日志:
CONFIG_DEBUG_ENABLE开启时打印[LED_CONTROL]前缀日志,可用于现场排查状态迁移异常(日志开关见led_control.c顶部)。
扩展点
- 新增灯效模式:在
led_ctl_mode枚举追加值,并在led_api.c的映射逻辑(led_effect_output_by_hardware)与pwm_led_v2.c的波形生成处同步支持;led_pdata_t的 union 中新增对应时序结构体。 - 新增指示灯状态:在
led_control.h状态枚举追加值,并在led_operate()的switch中实现 GPIO/定时器行为。 - 更换板级布局:只改
led_board_cfg_t(layout、led0/led1.port/logic/brightness、com_pole_port)即可适配单/双灯、共阴/共阳、三 IO 布局,无需改业务代码。 - 主从灯控扩展:在
on_off_opcode_t基础上扩展指令码(目前0x66),新增灯效指令只需扩展led_onoff_op()的解析分支。 - 新硬件平台:参考
apps/app/bsp/cpu/bd57/led/pwm_led_clk.c实现新平台的时钟源,并在led_api.c的时钟判断处接入,即可复用整条灯效链路。
测试与验证建议
仓库内未单独提供 LED 模块的单元测试文件;建议在集成层面验证以下场景(依据源码行为设计用例):
- 开机序列:
LED_INIT → LED_INIT_FLASH的时序与 IO 电平符合原理图极性; - 未连接闪灯:
LED_WAIT_CONNECT周期符合LED_FLASH_1S,10 分钟软关机生效(SOFT_OFF_TIME_MS); - 连接切换:
led_set_connect_flag(true/false)往返迁移正常; - 低电屏蔽:进入
LED_LOW_POWER后按键灯效不再响应; - 灯效矩阵:
ctl_mode×ctl_option全组合下(尤其CTL_LED01_ASYNC交替、CYCLE_BREATHE_BRIGHT呼吸)波形正确; - 主从灯控:主机发送
0x66指令后从机led_onoff_op()同步亮灭; - 时钟降级:屏蔽 PWM 时钟后灯效仍通过软件模拟输出,系统不卡死。
Related Links
- 指示灯控制实现:led_control.c、led_control.h
- 灯效 API 实现与定义:led_api.c、led_api.h
- PWM LED 驱动:pwm_led_v2.c、pwm_led.h、pwm_led_clk.c
- LEDC 外设:ledc.c、ledc_demo.c、ledc_hal.h、pled_hal.h
- 板级配置:pwmled_v1.lua