在双碳目标下,具有调光功能的LED驱动电源是重要的分支。 DALI通信常用在LED的数字调光控制中,下文将通过C语言与单片机结合,解释DALI的原理及实现方法。
一、通信原理
1.1 DALI 的物理电平信号定义如下:
9.5~22.5V: 高电平或者DALI 空闲状态
6.5~9.5V: 未定义
-6.5V~6.5V:定义为低电平
1.2 波特率:1200bps + 10%
1.3 DALI 负载最大短路电流<250mA
当从机发生故障,例如短路时,DALI总线上的电流,需要限制在250mA以下。
1.4 编码方式
使用曼切斯特编码,即上升沿为信号1,下降沿为信号0。
1.5 主机发送指令结构
主机发送的包含1个起始位、1个地址位类型位、6个地址位、一个选择位、8个数据位和两个停止位。
1.6 从机回复指令结构
从机向主机回复包含1个起始位、8个数据位和两个停止位。
1.7 前向帧与后向帧时序约束
Te表示半个位的时间,即4.16.67uS;
两个前向帧时间间隔大于22个Te;
前向帧与后向帧之间时间间隔为7~22个Te;
后向帧与前向帧之间时间间隔大于22个Te;
1.8 主机与从机的握手
主机在发送指令后,在等候响应阶段,
如果收到从机发送的”0xFF“,就会认为从机接收成功;
如果在这个阶段处于空闲状态,就会认为从机没有接收成功;
二、实现方法
2.1 硬件原理图
下面的硬件主要是将DALI的电平信号,转为单片机能够接受的电平,下面那张是微芯公司DALI的参考通信电路。
2.1 从机接收思路及实现
本次从机的接收端,主要使用了一个边沿检测中断和一个定时器中断。
代码思路:
1)由于空闲状态,接收端的电平为高电平,产生起始信号时,需要从产生一个上升沿。 于是,使用了外部下降沿触发中断,并关闭边沿触发中断;
2)检测到第一个下降沿后,定时器定时到0.75个周期,0.75个周期后读取第一位数据,并修改定时器周期为1个数据位时长。
3)第二次定时结束时读取第二位数据,依次读取后面的数据
4)当读到最后一位数据的时候,也就是LSB后的两位时,停止定时器,并初始化定时器为0.75个数据周期,然后开启边沿触发中断。
C语言程序:
1 //配置边沿触发及中断
2 void IO_Change_Init(void){
3
4 //Set the CN2 as the IO state change flag
5 CNEN1bits.CN2IE=1;//Open the IO state interrupt
6 CNPU1bits.CN2PUE=0;//Disable the weak up
7
8 IFS1bits.CNIF=0;//Clear the interrupt flag
9 IPC4bits.CNIP=7;//Configure the interrupt level 7
10 IEC1bits.CNIE=1;//Enable this interrupt
11 }
12 //检测到第一个下降沿
13 void __attribute__ ((__interrupt__,__no_auto_psv__)) _CNInterrupt(void){
14
15 IFS1bits.CNIF = 0; //Clear the interrupt flag
16
17 //Disable the IO State Interrupt and start the Time
18 T1CONbits.TON = 1;
19 CNEN1bits.CN2IE = 0;
20
21 }
22 //配置定时器初使周期为0.75个数据位时长
23 void Tim1_Init(void){
24 T1CON = 0x0020;
25
26 IEC0bits.T1IE = 1;
27 IPC0bits.T1IP = 7;
28 IFS0bits.T1IF = 0;
29
30 TMR1 = 0;
31 PR1 = 390;
32 T1CONbits.TON = 0;
33 }
34 //在定时器中断里面读取数据
35 void __attribute__((__interrupt__,auto_psv,__shadow__)) _T1Interrupt(void)
36 {
37 IFS0bits.T1IF = 0;
38
39 if(LLC_DALI_Rx_Mode == 1)
40 {
41 switch(Timer_Num)
42 {
43 case 0:
44 Timer_Num++;
45 T1CONbits.TON = 0; //关闭定时器
46 PR1 = 520; //设置下一个定时时长为1个周期
47 TMR1 = 0;//初使化定时器初始值
48 T1CONbits.TON = 1;//开启定时器
49 break;
50 case 1:
51 if(_RB0 == 1 )Address_temp |= (1<<7);
52 Timer_Num++;
53 break;
54 case 2:
55 if(_RB0 == 1 )Address_temp |= (1<<6);
56 Timer_Num++;
57 break;
58 case 3:
59 if(_RB0 == 1 )Address_temp |= (1<<5);
60 Timer_Num++;
61 break;
62 case 4:
63 if(_RB0 == 1 )Address_temp |= (1<<4);
64 Timer_Num++;
65 break;
66 case 5:
67 if(_RB0 == 1 )Address_temp |= (1<<3);
68 Timer_Num++;
69 break;
70 case 6:
71 if(_RB0 == 1 )Address_temp |= (1<<2);
72 Timer_Num++;
73 break;
74 case 7:
75 if(_RB0 == 1 )Address_temp |= (1<<1);
76 Timer_Num++;
77 break;
78 case 8:
79 if(_RB0 == 1 )Address_temp |= (1<<0);
80 Timer_Num++;
81 break;
82 case 9:
83 if(_RB0 == 1 )Command_temp |= (1<<7);
84 Timer_Num++;
85 break;
86 case 10:
87 if(_RB0 == 1 )Command_temp |= (1<<6);
88 Timer_Num++;
89 break;
90 case 11:
91 if(_RB0 == 1 )Command_temp |= (1<<5);
92 Timer_Num++;
93 break;
94 case 12:
95 if(_RB0 == 1 )Command_temp |= (1<<4);
96 Timer_Num++;
97 break;
98 case 13:
99 if(_RB0 == 1 )Command_temp |= (1<<3);
100 Timer_Num++;
101 break;
102 case 14:
103 if(_RB0 == 1 )Command_temp |= (1<<2);
104 Timer_Num++;
105 break;
106 case 15:
107 if(_RB0 == 1 )Command_temp |= (1<<1);
108 Timer_Num++;
109 break;
110 case 16:
111 if(_RB0 == 1 )Command_temp |= (1<<0);
112 Timer_Num++;
113 break;
114 case 17:
115 if(_RB0 == 1 )StopBit_temp |= (1<<1);
116 Timer_Num++;
117 break;
118 case 18:
119 if(_RB0 == 1 )StopBit_temp |= (1<<0);
120 Timer_Num++;
121 break;
122 case 19:
123 T1CONbits.TON = 0;//关闭定时器
124 PR1 = 390;//设置下一个定时器周期为0.75个数据位时长
125 TMR1 = 0;//定时器计数初始值置0
126 CNEN1bits.CN2IE = 1;//开启边沿检测中断
127 //数据获取,这里还可以添加数据包检验程序
128 Command = Command_temp;
129 Address = Address_temp;
130 StopBit = StopBit_temp;
131
132 Command_temp = 0;
133 Address_temp = 0;
134 StopBit_temp = 0;
135 Timer_Num = 0;
136 break;
137 }
138 }
139
140
141
2.2 从机思路及实现
从机的回复相对较简单,只需要在每半个数据位修改输出引脚的电平。 分别发送1个起始位、8个数据位和两个停止位。
代码思路:
1)接收数据完成并定时等待8Te
2)发送一个引脚低电平,并设置下一个定时周期为Te,定时器初使值为0,并开启定时器;
3)在后面的定时器中断里面,发送起始位;
4)在后面的定时器中断里面,发送数据位;
5)在后面的定时器中断里面,发送停止位;
6)初使化发送数据计数变量,初始化定时器计数值为零,关闭定时器;
7)开启边沿触发中断;
C语言程序实现
1 if(LLC_DALI_Tx_Mode == 1){
2 switch(Timer_Num)
3 {
4 case 0:
5 //Send the Start Bit
6 _RF3 = 0;
7 T1CONbits.TON = 0;
8 PR1 = 260;
9 TMR1 = 0;
10 T1CONbits.TON = 1;
11 Timer_Num++;
12 break;
13 case 1:
14 _RF3 = 1;
15 Timer_Num++;
16 break;
17 case 2:
18 //Send the Data Bits
19 _RF3 = ((Transfer_Data & 0x80)>0)?0:1;
20 Timer_Num++;
21 break;
22 case 3:
23 _RF3 = ((Transfer_Data & 0x80)>0)?1:0;
24 Timer_Num++;
25 break;
26 case 4:
27 _RF3 = ((Transfer_Data & 0x40)>0)?0:1;
28 Timer_Num++;
29 break;
30 case 5:
31 _RF3 = ((Transfer_Data & 0x40)>0)?1:0;
32 Timer_Num++;
33 break;
34 case 6:
35 _RF3 = ((Transfer_Data & 0x20)>0)?0:1;
36 Timer_Num++;
37 break;
38 case 7:
39 _RF3 = ((Transfer_Data & 0x20)>0)?1:0;
40 Timer_Num++;
41 break;
42 case 8:
43 _RF3 = ((Transfer_Data & 0x10)>0)?0:1;
44 Timer_Num++;
45 break;
46 case 9:
47 _RF3 = ((Transfer_Data & 0x10)>0)?1:0;
48 Timer_Num++;
49 break;
50 case 10:
51 _RF3 = ((Transfer_Data & 0x08)>0)?0:1;
52 Timer_Num++;
53 break;
54 case 11:
55 _RF3 = ((Transfer_Data & 0x08)>0)?1:0;
56 Timer_Num++;
57 break;
58 case 12:
59 _RF3 = ((Transfer_Data & 0x04)>0)?0:1;
60 Timer_Num++;
61 break;
62 case 13:
63 _RF3 = ((Transfer_Data & 0x04)>0)?1:0;
64 Timer_Num++;
65 break;
66 case 14:
67 _RF3 = ((Transfer_Data & 0x02)>0)?0:1;
68 Timer_Num++;
69 break;
70 case 15:
71 _RF3 = ((Transfer_Data & 0x02)>0)?1:0;
72 Timer_Num++;
73 break;
74 case 16:
75 _RF3 = ((Transfer_Data & 0x01)>0)?0:1;
76 Timer_Num++;
77 break;
78 case 17:
79 _RF3 = ((Transfer_Data & 0x01)>0)?1:0;
80 Timer_Num++;
81 break;
82 case 18:
83 //Send the stop bit;
84 T1CONbits.TON = 0;
85 TMR1 = 0;
86 PR1 = 260<<2;
87 T1CONbits.TON = 1;
88 _RF3 = 1;
89 Timer_Num++;
90 break;
91 case 19:
92 T1CONbits.TON = 0; //关闭定时器
93 CNEN1bits.CN2IE = 1;//开启边沿检测中断
94 TMR1 = 0; //定时器初始值置0
95 PR1 = 260; //定时器周期设置为Te
96 Timer_Num = 0;//初使定时器数据位计数
97 break;
98 }
99 }
三、测试结果
3.1 从机发送测试
从机发送数据100,对应二进制为0b0110 0100,实际发送波形见下图:
实际发送数据为0b01100100,发送正常。
3.2 主机发送从机识别测试
主机通过上位机发送调光指令为239,从机在线调试识别出来的数据为239。 接收正常。
四、小结
从机的接收程序,定时器的定时步长先是1.5个Te,然后是2个Te;
从机的发送程序,定时器的定时步长为1个Te;
从机的接收程序,边沿触发只触发依次就关闭了。
从机的发送程序,发送完毕开启边沿触发。
在定时器中断里面,修改下一个定时时长,理论上可以做到每一个定时周期都不一样,这思维可以用于实现更加复杂的功能。
注意:
1)以上代码发送和接收是独立的,没有遵循通信的时序。 1.7节里面有具体时序要求,根据时序稍做修改就可以啦。
2)本次代码,没有考虑到时序有10%的误差。 有待改善。
全部0条评论
快来发表一下你的评论吧 !