EMS 知识库
首页 / knowledge/代码解析/hmi/led

led_dev.c

源文件路径:hmi/led/led_dev.c 所属模块:hmi/led(人机界面 - WS2812 灯带编码/驱动层) 代码行数:197

1. 文件职责

本文件是 WS2812 灯带编码/驱动层的实现,负责把"第几颗灯珠、什么颜色"翻译成定时器比较值序列(bit 流),并启动一次 DMA 发送。它不直接写寄存器之外的业务逻辑:定时器 PWM 初始化在 hardware/timer/timer4.c、DMA 通道初始化在 hardware/dma/dma.c,本文件只做数据编码 + 发送触发 + 互斥标志维护。

数据流位置:上游由 hmi/led/app_led.c:156-207 的 led_build_full_buffer() 逐颗调用 ws2812_set_color_hex() 填缓冲、最后调用 ws2812_update() 启动发送;下游是 DMA0 通道 0(源地址 g_u16_ws2812_data、目标 TIMER_CH3CV(TIMER4))与 DMA0_Channel0_IRQHandler(hardware/dma/dma.c:266-283)。

被谁驱动:ws2812_init() 由 system/board_init/board_init.c:242 在上电初始化阶段调用一次;ws2812_update() 与各写函数只可能由 led_display_task() 的调用链驱动(当前该链未接线,详见第 7 节)。

编码缓冲 g_u16_ws2812_data 被 __attribute__((at(SDRAM_WS2812_DATA_ADDR))) 固定到 SDRAM,因此本文件隐含依赖"SDRAM_Init() 必须先于任何写操作"的初始化顺序。

2. 关键数据结构与宏

名称类型(结构体/枚举/宏/联合体)说明
WS2812_FLAG_U联合体(定义于 hmi/led/led_dev.h:52-67)读写互斥标志,本文件使用 read_flag(自身读写)与 write_flag(DMA 忙,只读)
g_u_ws2812_data全局数组(u32[LED_BUFFER_SIZE + 3],hmi/led/led_dev.c:61)DMA 编码缓冲,LED_BUFFER_SIZE = 121×24 = 2904,加 3 个帧尾表项共 2907 项(11628 B),固定地址 SDRAM_WS2812_DATA_ADDR
g_u_ws2812_flag全局变量(volatile WS2812_FLAG_U,hmi/led/led_dev.c:62)任务/中断共用的握手标志
LED_BITS宏(= 24,led_dev.h:33)每颗灯珠的 bit 数,同时是每个灯珠在缓冲中占用的表项数
RKP_LED_NUM宏(= 121,led_dev.h:32)灯珠总数
T0_PULSE / T1_PULSE宏(54 / 134,led_dev.h:38-39)数据"0"/"1"对应的定时器比较值
SDRAM_WS2812_DATA_ADDR宏(hardware/sdram/sdram.h:64)编码缓冲的固定存放地址(预留 16 KB)
TRUE / FALSE宏(includes.h)用于 write_flag/read_flag 赋值与比较

3. 函数清单

函数名作用域(static/全局)说明(一句话)
ws2812_init全局(hmi/led/led_dev.c:72)初始化 TIMER4 的 FL3512 位时序与 DMA0 通道 0,清零编码缓冲并打印一条调试信息
ws2812_update全局(hmi/led/led_dev.c:86)等待写缓冲空闲 → 置 write_flag → 把 PA3 切回复用功能 → 使能 DMA 与 TIMER4,启动一帧发送
ws2812_set_color_rgb全局(hmi/led/led_dev.c:101)以 R/G/B 参数写入第 led_idx 颗的 24 bit(不转 GRB,按 RGB 顺序出码,全工程无调用点)
ws2812_set_color_hex全局(hmi/led/led_dev.c:118)以 0xRRGGBB 写入第 led_idx 颗,内部转成 GRB 后逐 bit 生成比较值
ws2812_set_color_buffer_clear全局(hmi/led/led_dev.c:153)循环 RKP_LED_NUM 次调用 ws2812_set_color_hex(i, 0) 清空整条灯带
ws2812_set_on_off_color_light全局(hmi/led/led_dev.c:170)先清屏,再按 u8_flag 把全部灯珠置成同一颜色或全灭;未在 led_dev.h 中声明,全工程无调用点

本文件共 6 个函数(1 个未在头文件声明),已全部列出。文件内无 static 函数。

4. 核心逻辑解析

4.1 帧结构与位编码

WS2812(灯珠型号 FL3512SRGBICK-8MA-3T)的时序规范在文件头注释中给出(hmi/led/led_dev.c:9-41):每位固定周期 1.20 µs(实际实现 1.25 µs),位"0"高 0.32 µs、位"1"高 0.67 µs,帧尾 >200 µs 低电平作为 Reset。

本文件的编码策略是"每位一个定时器比较值":

u32 *pdata = &g_u16_ws2812_data[led_idx * LED_BITS];   // 每颗灯珠占 24 个 u32
for (int i = 0; i < LED_BITS; i++)
    *pdata++ = (grb_color & (0x800000 >> i)) ? T1_PULSE : T0_PULSE;

4.2 颜色字节序转换

ws2812_set_color_hex()(hmi/led/led_dev.c:118-145)内部有 #if 1(GRB)与 #else(RGB)两套:

u8 r = (u32_color >> 16) & 0xFF;
u8 g = (u32_color >> 8)  & 0xFF;
u8 b =  u32_color        & 0xFF;
u32 grb_color = (g << 16) | (r << 8) | b;     // 生效:GRB

WS2812 系列灯珠的 24 bit 顺序为 G7..G0/R7..R0/B7..B0,所以对外统一使用 0xRRGGBB 的调用者(hmi/led/app_led_cfg.c)无需关心字节序。ws2812_set_color_rgb()(hmi/led/led_dev.c:106)则不做转换,直接 rgb = r<<16 | g<<8 | b,两者行为不一致(该函数当前无调用点)。

4.3 发送流程与互斥标志

ws2812_update()(hmi/led/led_dev.c:86-98):

while (g_u_ws2812_flag.bit.read_flag == TRUE)      // 1) 等待"没人在改缓冲"
    OSTimeDlyHMSM(0, 0, 0, 1);                     //    1 ms 粒度让出 CPU
g_u_ws2812_flag.bit.write_flag = TRUE;             // 2) 宣告"DMA 占用中"
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3);  // 3) PA3 复用为 TIMER4_CH3
delay_us(1);
dma_channel_enable(DMA0, DMA_CH0);                 // 4) 使能 DMA 通道
timer_enable(TIMER4);                              // 5) 启动定时器 → 产生位流

ws2812_init()(hmi/led/led_dev.c:72-78):

timer4_fl3512_init();                    // TIMER4 CH3 PWM,800 kHz,极性 LOW
led_timer4_pwm_dma0_channel0_init();     // DMA0 CH0:内存 g_u16_ws2812_data → TIMER_CH3CV(TIMER4),32 bit
memset(g_u16_ws2812_data, 0, sizeof(g_u16_ws2812_data));
printf("ws2812初始化成功\n");             // USMART 调试串口输出

DMA 完成中断(hardware/dma/dma.c:266-283)负责收尾:清 FTF 标志 → timer_disable(TIMER4) → dma_channel_disable(DMA0, DMA_CH0) → 把 PA3 改成下拉输出并拉低(提供 Reset 低电平)→ g_u_ws2812_flag.bit.write_flag = FALSE。

因此一次完整的"改缓冲 → 发送 → 允许再改"闭环为:

阶段read_flagwrite_flag谁在动
空闲00—
写某颗灯珠1 → 00任务(led_dev.c:109/114、131/144)
发送中01DMA + TIMER4 硬件
发送结束00DMA0_Channel0_IRQHandler(dma.c:280)

所有写函数开头都有 if (g_u_ws2812_flag.bit.write_flag == TRUE) return;(led_dev.c:103、121、155、174),即DMA 发送期间写入被静默丢弃(不返回错误码)。

4.4 ws2812_set_color_buffer_clear() 与 ws2812_set_on_off_color_light()

void ws2812_set_color_buffer_clear(void)      // led_dev.c:153
{
    if (g_u_ws2812_flag.bit.write_flag == TRUE) return;
    for (int i = 0; i < RKP_LED_NUM; i++)
        ws2812_set_color_hex(i, 0);           // 内部又各自检查一次 write_flag
}

4.5 时间与调度

4.6 关键全局变量的读写

变量本文件的动作外部读写方
g_u16_ws2812_data写(编码)、读(无)hardware/dma/dma.c:53(设 DMA 源地址)、:55(传输个数)
g_u_ws2812_flag读写 read_flag、置 write_flaghardware/dma/dma.c:280(中断清 write_flag)

5. 对外接口

本文件导出

名称类型使用方
ws2812_init全局函数(led_dev.h:79)system/board_init/board_init.c:242
ws2812_update全局函数(led_dev.h:80)hmi/led/app_led.c:207
ws2812_set_color_hex全局函数(led_dev.h:82)hmi/led/app_led.c:191/201(生效分支)、led_dev.c:160/190
ws2812_set_color_buffer_clear全局函数(led_dev.h:83)hmi/led/app_led.c:160、led_dev.c:177
ws2812_set_color_rgb全局函数(led_dev.h:81)无调用点
ws2812_set_on_off_color_light全局函数(头文件未声明)无调用点
g_u16_ws2812_data全局数组(led_dev.h:72)hardware/dma/dma.c
g_u_ws2812_flag全局变量(led_dev.h:73)hardware/dma/dma.c:280

本文件调用的外部函数/变量

名称来源模块
timer4_fl3512_inithardware/timer/timer4.c(声明 timer4.h:19)
led_timer4_pwm_dma0_channel0_init、dma_channel_enable、DMA0/DMA_CH0hardware/dma/dma.c(声明 dma.h:47)
gpio_mode_set、gpio_bit_reset、GPIOA/GPIO_PIN_3、GPIO_MODE_AF芯片库(chip.h,经 led_dev.h:15)
timer_enable芯片库
delay_ushardware/delay(delay.h)
memset、printfC 标准库 / USMART 调试串口
OSTimeDlyHMSMuCOS-II
SDRAM_WS2812_DATA_ADDRhardware/sdram/sdram.h:64
RKP_LED_NUM、LED_BITS、T0_PULSE、T1_PULSEhmi/led/led_dev.h

6. 依赖关系

依赖(本文件 include):led_dev.h(45)、includes.h(46)、timer4.h(47)、timer2.h(48)、dma.h(49)、delay.h(50)、app_led.h(51)、sdram.h(52)。

timer2.h、app_led.h 在本文件正文中没有任何符号被使用(app_led.h 反向依赖 led_dev.h,构成循环 include),属多余依赖。

被依赖:

7. 备注与风险点