极物科技 | 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. 注意事项
- TE 定时器精度:定时器中断频率必须精确匹配 2400Hz / TE_CNT,否则会导致解码错误
- 中断优先级:DALI 接收中断优先级应高于普通定时器中断,确保边沿捕获及时
- 电平反相:注意硬件电路的电平反相逻辑,软件需做适配
- 总线空闲检测:停止位后需等待足够时间才能接收新帧
- 容差范围:±4 的 TE 容差适应不同晶振频率,但极端情况下仍可能解码失败
- 发送切换:前后向帧切换时需正确禁用/启用接收中断,避免干扰
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 运维。
我们的市场覆盖:
服务网点已覆盖全国核心城市(含长三角、珠三角、成渝等),并以高品质的方案深耕家居生活、酒店民宿、企业办公、疗愈康养、餐馆会所等多个细分领域。
(如果您在开发或落地中遇到技术问题,欢迎通过官网或后台私信与我交流探讨)

