杰理 SDK 文档中心
首页
首页
  • SDK 概述与入门

    • SDK 总览
    • 支持芯片与蓝牙认证
    • 工程结构导航
  • 开发环境与构建

    • 环境搭建与工具链安装
    • 编译指南与工程选择
    • 烧录与生产工具
  • BLE 透传/数传应用

    • 透传应用框架与处理模块
    • 透传与数传示例
    • 多连接与自定义服务示例
    • FindMy 与查找网络示例
  • HID 人机交互应用

    • 键盘与按键设备示例
    • 鼠标设备示例
    • 遥控器示例
    • HID 蓝牙应用模块
  • 公共 BSP 模块

    • 按键、编码器与红外输入
    • 传感器驱动
    • LED 与显示控制
    • 串口与 USB 通信
    • 存储、参数与时钟
    • 电源与温度管理
    • 消息、内存与系统配置
    • OTA 升级框架
  • 蓝牙协议栈与库

    • BLE 控制器与协议栈适配
    • 经典蓝牙 BR/EDR 支持
    • 第三方蓝牙协议
    • 设备管理框架
    • DUT 测试与射频认证
  • 构建系统与开发工具

    • Makefile 构建系统
    • 固件后处理与配置工具
    • 辅助脚本与库合并
  • 文档与硬件资料

    • AT 命令参考
    • 硬件参考资料
    • SDK 文档与在线资源

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 指示灯承担两类职责:

  1. 状态指示:通过 GPIO 直控方式表达设备状态(开机闪烁、等待配对、连接成功、低电、关机),其行为由一个集中的状态机驱动,避免各业务模块各自操作 GPIO 导致冲突;
  2. 灯效表现:通过 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_1S1未连接闪灯周期(1s 或 0.5s)
SOFT_OFF_TIME_MS10 * 60 * 1000未连接状态 10 分钟后软关机
AUTO_CONNECT_TIME_MS8000L自动回连超时窗口
POWER_LED_ON_TIME_MS1000L开机灯亮时长

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。关键逻辑包括:

  1. 按布局模式分配端口:ONE_IO_ONE_LED 时 port1 = -1(禁用第二路);
  2. 按逻辑极性决定 PWM 占空比挂在 h_pwm_duty(高亮)还是 l_pwm_duty(低亮),并设置 first_logic;
  3. 按控制选项决定 alternate_out(交替输出)与端口使能:CTL_LED01_ASYNC 强制交替,CTL_LED01_SYNC 在两灯极性一致时同步输出、不一致时自动转交替;
  4. 把 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:

  • 宏定义:led_api.h
  • 结构体字段注释:led_api.h

该示例演示"每 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/通道)

配置项来源:led_control.h、led_api.h、pwmled_v1.lua

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 顶部)。

扩展点

  1. 新增灯效模式:在 led_ctl_mode 枚举追加值,并在 led_api.c 的映射逻辑(led_effect_output_by_hardware)与 pwm_led_v2.c 的波形生成处同步支持;led_pdata_t 的 union 中新增对应时序结构体。
  2. 新增指示灯状态:在 led_control.h 状态枚举追加值,并在 led_operate() 的 switch 中实现 GPIO/定时器行为。
  3. 更换板级布局:只改 led_board_cfg_t(layout、led0/led1.port/logic/brightness、com_pole_port)即可适配单/双灯、共阴/共阳、三 IO 布局,无需改业务代码。
  4. 主从灯控扩展:在 on_off_opcode_t 基础上扩展指令码(目前 0x66),新增灯效指令只需扩展 led_onoff_op() 的解析分支。
  5. 新硬件平台:参考 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
Prev
传感器驱动
Next
串口与 USB 通信