17 极物科技 | DALI协议栈 - 物理层位编解码详解

极物科技 | DALI协议栈 - 物理层位编解码详解

前言

在智能控制领域,"协议壁垒"与"系统稳定性"始终是绕不开的难题。极物科技(Zeewo)以 "重构空间交互体验" 为使命,通过自研的 极物 OS,实现了 KNX、DALI、CAN、RS485 等多种总线协议在单一主机上的深度融合。

我们认为,一个真正优秀的智能系统,不仅要实现灯光、遮阳、温控、音乐、安防等全生态设备的无界融合 ,更要确保本地运行、断网可用 ,并具备WEB 远程调试OTA 固件升级的能力。基于此底层架构,我们推出了诸如带双路 DALI 的 KNX 主机、HomeKit 网桥等核心硬件,并将极物 OS 成功接入了 Apple HomeKit、小度、纯血鸿蒙及 HomeAssistant 等主流生态系统。

一句话概述:本文深入解析极物 DALI 协议栈物理层的位编解码实现,展示从总线信号到数字数据的完整转换过程。

本文聚焦于 DALI 物理层的曼彻斯特编解码机制,涵盖 TE (Time Element) 时序规范、外部中断驱动的接收解码、定时器驱动的发送编码、电平容差处理等核心内容。通过源码级剖析,帮助读者理解 DALI 2400bps 半双工通信的底层实现细节。


1. DALI 物理层概述

1.1 通信参数

参数 规格
通信速率 1200 bit/s (每 bit 由 2 个 TE 组成,等效 2400 TE/s)
编码方式 曼彻斯特编码 (Manchester II)
传输模式 半双工
帧结构 起始位(1 TE) + 地址字节(8 bit) + 数据字节(8 bit) + 停止位(4~5 TE)
TE 时长 416.67μs (1/2400 Hz)
Bit 时长 833.33μs (2 × TE)

1.2 曼彻斯特编码规则

DALI 采用曼彻斯特 II 编码,每个 bit 由两个连续的 TE 组成:

Bit 值 前半 TE 后半 TE 电平变化
0 低电平 高电平 上升沿
1 高电平 低电平 下降沿

起始位:固定为低电平 (1 TE),后接高电平 (1 TE)

停止位:固定为高电平 (4~5 TE)

1.3 帧结构

复制代码
前向帧 (Forward Frame) - 主设备 → 从设备:
┌─────────┬──────────────┬──────────────┬──────────┐
│ 起始位  │  地址字节    │  数据字节    │ 停止位   │
│  1 TE   │   8 bit      │   8 bit      │  4~5 TE  │
│  (低)   │  (YAAA AAAS) │  (XXXXXXXX)  │  (高)    │
└─────────┴──────────────┴──────────────┴──────────┘
  16 bit 数据 = 地址 + 数据

后向帧 (Backward Frame) - 从设备 → 主设备:
┌─────────┬──────────────┬──────────┐
│ 起始位  │  数据字节    │ 停止位   │
│  1 TE   │   8 bit      │  4~5 TE  │
│  (低)   │  (XXXXXXXX)  │  (高)    │
└─────────┴──────────────┴──────────┘
  8 bit 数据 = 应答数据

2. 硬件接口定义

2.1 GPIO 引脚配置

c 复制代码
/* 接收引脚 - 高阻输入 */
#define DALI_RX_PORT_PIN_VALUE (P32?0:1)

/* 发送引脚 - 推挽输出 */
// P27 为 DALI TX 引脚
P2M1 = 0x00 | P2M1;  // 清除推挽配置位
P2M0 = 0x80 | P2M0;  // 设置 P27 为推挽输出

/* RX 引脚 - 高阻输入 */
P3M1 = 0x04;  // P32 高阻输入
P3M0 = 0x00;  // 设置为准双向口

2.2 电平控制宏

c 复制代码
#define DALI_TX_HIGHT  // 发送高电平
#define DALI_TX_LOW   // 发送低电平

注意 :代码中 TX 电平控制存在硬件反相逻辑,DALI_TX_LOW 实际输出高电平,DALI_TX_HIGHT 实际输出低电平。

2.3 引脚初始化

c 复制代码
void dali_bit_init(void) {
    // TX P27 - 推挽输出
    P2M1 = 0x00 | P2M1;
    P2M0 = 0x80 | P2M0;
    
    // RX P32 - 高阻输入
    P3M1 = 0x04;
    P3M0 = 0x00;
    
    DALI_TX_HIGHT;  // 空闲状态保持高电平
}

3. TE 时序参数

3.1 定时器配置

c 复制代码
#define TE_CNT 13                    // 半个 bit 的定时器计数
#define DALI_BIT_TC_TOP (CLK_CPU / 2400 / TE_CNT)  // 定时器周期

// TE 容差范围
#define MIN_TE_CNT (TE_CNT - 4)      // TE 下限 = 9
#define MAX_TE_CNT (TE_CNT + 4)      // TE 上限 = 17
#define MIN_2TE_CNT (2 * MIN_TE_CNT) // 2TE 下限 = 18
#define MAX_2TE_CNT (2 * MAX_TE_CNT) // 2TE 上限 = 34
#define STOP_CNT (4 * TE_CNT)        // 停止位 = 52

3.2 容差设计原理

DALI 标准允许 ±10% 的时序容差。协议栈通过 ±4 个计数单位实现容差:

  • 理论 TE:13 个计数
  • 容差范围:9 ~ 17 个计数 (约 ±30%)
  • 2TE 范围:18 ~ 34 个计数

这种设计确保在不同晶振频率下仍能正确解码。


4. 接收解码机制

4.1 接收状态机

c 复制代码
/* 接收状态枚举 */
#define BIT_START  0xFF    // 起始位
#define BIT_0      0x01    // 数据位 0
#define BIT_1      0x02    // 数据位 1
#define BIT_2      0x03    // 数据位 2
// ... 依次到 BIT_15 (0x10)
#define BIT_STOP1  0x11    // 停止位 1
#define BIT_STOP2  0x12    // 停止位 2

4.2 核心变量

c 复制代码
volatile bool pin_level;              // 当前引脚电平
volatile uint8_t level_time = 0;      // 电平保持时间 (TE 计数)
volatile uint8_t status_receive = 0;  // 接收状态
volatile uint8_t dali_rec_addr = 0;   // 接收地址字节
volatile uint8_t dali_rec_data = 0;   // 接收数据字节

4.3 中断接收处理函数

c 复制代码
void dali_rx_IRQHandler(void) {
    static uint8_t bit_index;
    uint8_t dali_bit_rx;
    
    pin_level = DALI_RX_PORT_PIN_VALUE;
    
    // 状态 0:等待起始位
    if (status_receive == 0) {
        if (pin_level == LOW) {
            // 下降沿 = 起始位开始
            bit_index = 0;
            status_receive = BIT_START;
            dali_rec_addr = 0;
            dali_rec_data = 0;
        }
    }
    // 状态 BIT_START:验证起始位
    else if (status_receive == BIT_START) {
        if ((level_time >= MIN_TE_CNT) && (level_time <= MAX_TE_CNT)) {
            // 起始位正确 (1 TE 低 + 1 TE 高)
            status_receive = BIT_0;
            bit_index += 1;
        } else {
            // 起始位错误,重置
            status_receive = 0;
        }
    }
    // 状态 BIT_0 ~ BIT_15:数据位解码
    else if (status_receive < BIT_STOP1) {
        // 检测 2TE 长电平
        if (level_time >= MIN_2TE_CNT) {
            if (bit_index & 0x01) {
                // 奇数索引:合法的 2TE 电平
                bit_index += 2;
            } else {
                // 偶数索引:错误 (前半 bit 不应出现 2TE)
                status_receive = 0;
            }
        }
        // 检测 1TE 短电平
        else if ((level_time >= MIN_TE_CNT) && (level_time <= MAX_TE_CNT)) {
            // 1TE 电平:bit 中间跳变
            bit_index += 1;
        } else {
            // 时序错误
            status_receive = 0;
        }
        
        // 检查是否接收完 16 bit
        if (bit_index >= 34) {
            status_receive = BIT_STOP1;
        }
        
        // 每两个 TE 解码一个 bit
        if (bit_index & 0x01) {
            dali_bit_rx = bit_index >> 1;
            if (dali_bit_rx <= BIT_7) {
                // 地址字节 (bit 0~7)
                dali_rec_addr <<= 1;
                if (pin_level) {
                    dali_rec_addr |= 0x01;
                }
            } else {
                // 数据字节 (bit 8~15)
                dali_rec_data <<= 1;
                if (pin_level) {
                    dali_rec_data |= 0x01;
                    // 最后一个 bit 为 1 时,需要额外等待 1TE
                    if (dali_bit_rx == BIT_15) {
                        status_receive = BIT_STOP2;
                    }
                }
            }
        }
    }
    // 停止位处理
    else {
        status_receive = 0;
    }
    
    level_time = 0;  // 重置计时
}

4.4 解码逻辑详解

4.4.1 Bit 值判断原理

曼彻斯特编码中,每个 bit 的中间点电平决定 bit 值:

复制代码
Bit 0:  Low(1TE) → High(1TE)   中间点 = 上升沿 → bit_index 为偶数时电平为 Low
Bit 1:  High(1TE) → Low(1TE)   中间点 = 下降沿 → bit_index 为偶数时电平为 High

协议栈通过 bit_index 追踪当前处于第几个 TE:

  • bit_index = 1, 3, 5, ... (奇数):bit 中间点
  • bit_index = 2, 4, 6, ... (偶数):bit 结束点
4.4.2 地址字节解码
c 复制代码
if (dali_bit_rx <= BIT_7) {
    dali_rec_addr <<= 1;
    if (pin_level) {
        dali_rec_addr |= 0x01;
    }
}
  • 左移一位,空出最低位
  • 根据当前电平设置最低位
4.4.3 数据字节解码
c 复制代码
else {
    dali_rec_data <<= 1;
    if (pin_level) {
        dali_rec_data |= 0x01;
        if (dali_bit_rx == BIT_15) {
            // 最后一个 bit 为 1 时,停止位前多一个 1TE 高电平
            status_receive = BIT_STOP2;
        }
    }
}

4.5 定时器解码处理

c 复制代码
void dali_decode_IRQHandler(void) {
    // 接收状态处理
    if (status_receive) {
        level_time++;  // TE 计数递增
        
        // BIT_STOP2:最后一个 bit 为 1 时的停止位
        if (status_receive == BIT_STOP2) {
            if (pin_level == HIGH) {
                if (level_time > (STOP_CNT + TE_CNT)) {
                    // 停止位完成 (5 TE 高电平)
                    status_receive = 0;
                    dali_set_addr_to_stack(dali_rec_addr);
                    dali_set_data_to_stack(dali_rec_data);
                    dali_set_received_flag_to_stack(true);
                }
            } else {
                status_receive = 0;  // 停止位错误
            }
        }
        // BIT_STOP1:普通停止位
        else if (status_receive == BIT_STOP1) {
            if (pin_level == HIGH) {
                if (level_time > STOP_CNT) {
                    // 停止位完成 (4 TE 高电平)
                    status_receive = 0;
                    dali_set_addr_to_stack(dali_rec_addr);
                    dali_set_data_to_stack(dali_rec_data);
                    dali_set_received_flag_to_stack(true);
                }
            } else {
                status_receive = 0;  // 停止位错误
            }
        }
        // 错误检测
        else {
            if (level_time >= MAX_2TE_CNT) {
                status_receive = 0;  // 超时错误
            }
        }
    }
    // ... 发送编码处理 (见下文)
}

4.6 接收完成标志

c 复制代码
dali_set_addr_to_stack(dali_rec_addr);    // 地址写入协议栈
dali_set_data_to_stack(dali_rec_data);    // 数据写入协议栈
dali_set_received_flag_to_stack(true);    // 设置接收完成标志

5. 发送编码机制

5.1 发送状态

c 复制代码
enum {
    SENDING_STOP,  // 发送停止
    SENDING_START  // 发送开始
};

volatile uint8_t send_status = 0;
volatile uint8_t dali_send_data = 0;

5.2 发送编码逻辑

c 复制代码
void dali_decode_IRQHandler(void) {
    static uint8_t dali_bit_index;
    static uint8_t bit_length;
    uint8_t dali_bit_tx;
    uint8_t bit_length_temp;
    
    send_status = dali_get_sent_status_from_stack();
    
    // 发送编码
    if (send_status) {
        dali_send_data = dali_get_sent_data_from_stack();
        dali_bit_tx = dali_bit_index;
        bit_length_temp = bit_length + 1;
        
        if (send_status != BIT_STOP1) {
            // 发送数据位 (反向帧,从 bit7 到 bit0)
            if (dali_bit_tx == 0) {
                // 起始位 (1 TE 低)
                if (bit_length_temp == 1) {
                    DALI_TX_LOW;
                } else if (bit_length_temp == (TE_CNT + 1)) {
                    DALI_TX_HIGHT;
                }
            }
            else if (dali_send_data & (1 << (BIT_7 - dali_bit_tx))) {
                // Bit 1: High(1TE) → Low(1TE)
                if (bit_length_temp == 1) {
                    DALI_TX_LOW;   // 前半 TE 低 (硬件反相)
                } else if (bit_length_temp == (TE_CNT + 1)) {
                    DALI_TX_HIGHT; // 后半 TE 高 (硬件反相)
                }
            } else {
                // Bit 0: Low(1TE) → High(1TE)
                if (bit_length_temp == 1) {
                    DALI_TX_HIGHT; // 前半 TE 高 (硬件反相)
                } else if (bit_length_temp == (TE_CNT + 1)) {
                    DALI_TX_LOW;   // 后半 TE 低 (硬件反相)
                }
            }
            
            // 移动到下一个 bit
            if (bit_length_temp == (2 * TE_CNT)) {
                dali_bit_tx++;
                if (dali_bit_tx > BIT_7) {
                    send_status = BIT_STOP1;
                    dali_bit_tx = 0;
                }
                bit_length_temp = 0;
            }
        } else {
            // 停止位 (4~5 TE 高)
            if (bit_length_temp < (4 * TE_CNT)) {
                DALI_TX_HIGHT;
            } else if (bit_length_temp >= (4.5 * TE_CNT)) {
                // 发送完成
                send_status = SENDING_STOP;
                bit_length_temp = 0;
            }
        }
        
        dali_set_sent_status_to_stack(send_status);
        bit_length = bit_length_temp;
        dali_bit_index = dali_bit_tx;
    }
}

5.3 发送时序说明

后向帧发送顺序与接收相反:

复制代码
后向帧发送顺序:
起始位 → Bit7 → Bit6 → Bit5 → Bit4 → Bit3 → Bit2 → Bit1 → Bit0 → 停止位

代码中通过 1 << (BIT_7 - dali_bit_tx) 实现从高位到低位的发送。

5.4 发送模式切换

c 复制代码
// 切换到发送模式
void dali_hal_disable_forward_enable_backward(void) {
    STC_GPIO_DISABLE;  // 禁用接收中断
}

// 切换回接收模式
void dali_hal_enable_forward_disable_backward(void) {
    STC_GPIO_ENABLE;   // 启用接收中断
}

6. 编解码时序图

6.1 前向帧接收时序

复制代码
DALI 总线:
        ┌─┐     ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐     ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
        │ │     │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │     │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
────────┘ └─────┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─────┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─────
        ↑       ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑     ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
       起始位   地址字节 (8 bit)                     数据字节 (8 bit)                   停止位

中断触发: 每个边沿触发一次
定时器:   每 TE 周期触发一次

6.2 后向帧发送时序

复制代码
DALI 总线:
        ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐     ┌─────────────────────────────────┐
        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │     │                                 │
────────┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─────┘                                 └─────
        ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑       ↑
       起始位 数据字节 (8 bit)                     停止位 (4~5 TE)

发送间隔: 前向帧结束后 5 TE 开始发送
发送完成: 后向帧结束后 9 TE 可接收新帧

7. 关键设计要点

7.1 中断与定时器协同

复制代码
外部中断 (INT0)          定时器中断 (DALI_TIMER)
     ↓                        ↓
边沿检测                 TE 计数递增
状态切换                 解码/编码处理
定时器清零               电平输出
  • 外部中断:捕获 DALI 总线边沿变化,触发解码状态机
  • 定时器中断:每 TE 周期触发,驱动解码/编码逻辑

7.2 容差处理

c 复制代码
// 1TE 容差
if ((level_time >= MIN_TE_CNT) && (level_time <= MAX_TE_CNT)) {
    // 9 ~ 17 计数范围内视为合法 1TE
}

// 2TE 容差
if (level_time >= MIN_2TE_CNT) {
    // ≥ 18 计数视为 2TE
}

7.3 停止位差异

  • BIT_STOP1:最后一个 bit 为 0 时,停止位为 4 TE 高电平
  • BIT_STOP2:最后一个 bit 为 1 时,停止位前额外增加 1 TE 高电平,总计 5 TE

7.4 硬件反相处理

代码中存在硬件反相逻辑:

c 复制代码
// Bit 1 应该 High→Low,但代码中:
DALI_TX_LOW;   // 实际输出高电平 (反相)
DALI_TX_HIGHT; // 实际输出低电平 (反相)

这是由硬件电路设计决定的,软件层面做了适配。


8. 错误处理

8.1 起始位错误

c 复制代码
if (status_receive == BIT_START) {
    if ((level_time >= MIN_TE_CNT) && (level_time <= MAX_TE_CNT)) {
        status_receive = BIT_0;  // 正确
    } else {
        status_receive = 0;      // 起始位错误,重置
    }
}

8.2 数据位错误

c 复制代码
if (level_time >= MIN_2TE_CNT) {
    if (bit_index & 0x01) {
        bit_index += 2;  // 合法 2TE
    } else {
        status_receive = 0;  // 偶数索引出现 2TE,错误
    }
}

8.3 停止位错误

c 复制代码
if (pin_level == HIGH) {
    if (level_time > STOP_CNT) {
        // 停止位正确
    }
} else {
    status_receive = 0;  // 停止位期间出现低电平,错误
}

8.4 超时错误

c 复制代码
if (level_time >= MAX_2TE_CNT) {
    status_receive = 0;  // 超过最大 2TE 时间,超时
}

9. 注意事项

  1. TE 定时器精度:定时器中断频率必须精确匹配 2400Hz / TE_CNT,否则会导致解码错误
  2. 中断优先级:DALI 接收中断优先级应高于普通定时器中断,确保边沿捕获及时
  3. 电平反相:注意硬件电路的电平反相逻辑,软件需做适配
  4. 总线空闲检测:停止位后需等待足够时间才能接收新帧
  5. 容差范围:±4 的 TE 容差适应不同晶振频率,但极端情况下仍可能解码失败
  6. 发送切换:前后向帧切换时需正确禁用/启用接收中断,避免干扰

10. 相关文档

  • 《极物科技 | DALI协议栈 - 架构总览与模块设计》
  • 《极物科技 | DALI协议栈 - 帧传输状态机与地址解析》
  • 《极物科技 | DALI协议栈 - 标准命令集解析》
  • 《极物科技 | DALI协议栈 - 特殊命令与设备发现机制》
  • 《极物科技 | DALI调光灯 - 快速接入指南》
  • 《极物科技 | DALI调光灯 - UDP 通讯协议规范》

关于极物科技(ZEEWO)

极物科技(Zeewo)致力于为用户提供智能控制系统及硬件产品。我们以总线系统为技术底座,以**"稳定、可靠、快速响应"**为产品底线,是国内少有的拥有 KNX、DALI 全套软硬件自主研发能力的厂商之一。

我们的核心能力:

  • 系统架构:自研极物 OS,支持多协议无界融合(KNX/DALI/CAN/RS485/IP)。
  • 核心硬件:带双路 DALI 的 KNX 主机、超薄全金属定制面板、各类智选传感器及网关。
  • 生态互联:深度融入 Apple HomeKit、Matter、小度、HomeAssistant 及纯血鸿蒙生态。
  • 调试交付:独家支持 ETS 导出 XML 直接导入进行 APP 免编程调试,支持远程 WEB 运维。

我们的市场覆盖:

服务网点已覆盖全国核心城市(含长三角、珠三角、成渝等),并以高品质的方案深耕家居生活、酒店民宿、企业办公、疗愈康养、餐馆会所等多个细分领域。

(如果您在开发或落地中遇到技术问题,欢迎通过官网或后台私信与我交流探讨)

相关推荐
阿钱真强道1 天前
16 极物科技 | DALI协议栈 - 架构总览与模块设计
智能家居·照明·极物·极物智能·极物科技·iec 62386·dali协议栈
阿钱真强道3 天前
11 极物科技 | DALI调光灯 - 功能接口与调试指南
智能照明·极物·极物智能·极物科技·dali调光灯·调光控制·调试指南
阿钱真强道3 天前
12 极物科技 | DALI调光灯 - 设备发现与地址分配机制
智能照明·极物·极物智能·极物科技·dali调光灯·设备发现·地址分配
阿钱真强道4 天前
09 极物科技 | DALI调光灯 - IEC 62386 底层协议解析
智能照明·总线协议·极物·极物智能·极物科技·dali调光灯·iec 62386
阿钱真强道5 天前
07 极物科技 | DALI调光灯 - 快速接入指南
智能照明·极物·极物智能·极物科技·dali调光灯·iec 62386·调光控制
阿钱真强道5 天前
08 极物科技 | DALI调光灯 - UDP 通讯协议规范
通讯协议·智能照明·极物·极物智能·极物科技·dali调光灯·iec 62386
阿钱真强道8 天前
06 极物科技 | 创明RS485窗帘 - UDP 通讯协议规范
udp·智能家居·通讯协议·极物·极物智能·极物科技·创明窗帘
阿钱真强道9 天前
04 极物科技 | 创明RS485窗帘 - 快速接入指南
智能家居·rs485·极物·极物智能·极物科技·创明窗帘·窗帘控制
PleaSure乐事2 年前
计算机网络之曼彻斯特编码和差分曼彻斯特编码
计算机网络·通信·曼彻斯特编码·差分曼彻斯特编码