写在前面
这篇博客记录下最近一个星期研究的两块板通过一个口线实现的数据传输。
我用的两块板上各有一个单片机,此处称为控制板和电源板。两单片机各出一个I/O口,一个输入一个输出,然后通过规定此I/O口高电平和低电平的持续时间来传输“0”和“1”,这种传输方式非常普遍,红外遥控基本原理与这个十分类似,不过红外遥控是通过A发出红外光,然后B接受红外光,根据是否接收到红外光和他的持续时间来编码为“0”或“1”,再把不同的“0”“1”组合来规定不同的键。
我此处的工程较为简单,两块板通过导线直接连接,然后电源板只需要根据其I/O口的高低电平的持续时间即可判断出控制板发出的信号为“0”还是“1”。
此处的代码为汇编代码,语言可能有读者会看不懂,我大致会注释一下:
#include<reg52.h>
sbit send_data = P1^1;
unsigned char bit_step,bit_num,bit_step;
unsigned char trans_buf1,trans_data1,trans_buf2,trans_data2,bit_trans;
unsigned char flag0;
void transmit();
void int_init(){
EA = 0;
TR0 = 0;
TMOD = 0x01;
TH0 = 0xFF; //此处由于我原工程用的中断为125us中断一次,算出来结果近似FF,所以直接用FF
TL0 = 0x00;
}
void timer0() interrupt 1 using 0{
TH0 = 0xFF;
TL0 = 0x00;
transmit();
if(++timer_12ms >= 96){
timer_12ms = 0;
trans_buf1 = flag0;
}
}
void transmit(){
bit_step++;
if((bit_step >= 40) && (bit_num == 0)){
bit_step = 0;
bit_num=1;
trans_data1 = trans_buf1;
trans_data2 = trans_buf2;
send_data = 0;
}
else if((bit_step < 40) && (bit_num == 0)){ //发5ms高电平作为开始标志
send_data = 1;
}
if((bit_num > 0) && (bit_num <= 8)){
bit_trans = trans_data1&0x80; //储存trans_data1的第7位
if((bit_trans != 0)&&(bit_step >= 12)){ //判断储存trans_data1的第7位为0还是1
bit_num++; //传输位数变量加一
send_data = ~send_data; //取反口线的电平,传输下一位(如果这里只是简单的置0或1的话,连续传输多位"0"或"1",
bit_step = 0; //清零计时 //则波形就连续了,接收板就无法分辨出此时发送的是1位还是多位数据)
trans_data1 = trans_data1<<1; //C51由于不清楚是否有进位位,这里暂时只送八位数据了
}
else if((bit_trans == 0) && (bit_step > 4)){
send_data = ~send_data;
bit_num++;
bit_step = 0;
trans_data1 = trans_data1<<1;
}
}
else if(bit_num >= 9){
if(bit_step >= 4){
bit_num = 0;
}
else{
send_data = 0; //结束标志:0.5ms低电平
}
}
}
void main(){
int_init();
while(1);
}
原汇编代码为:
TRANSMIT:
SZINCR BIT_STEP ;BIT_STEP变量自增
NOP
LD A,BIT_NUM ;传值
SNZB STATUS,Z ;判断BIT_NUM是否为空,为空跳过下一条指令
JP TRANSMIT1
TRANSMIT0: ; 5ms高电平
LD A,BIT_STEP
HSUBIA D'40' ;BIT_STEP减40
SNZB STATUS,C ;判断是否溢出,不溢出则跳过下一条指令
JP SEND1
CLR BIT_STEP ;清BIT_STEP
LDIA D'1' ;赋值1给ACC
LD BIT_NUM,A ;传值
LD A,TRANS_BUF1
LD TRANS_DATA1,A
/*将TRANS_BUF1的值传给TRANS_DATA1 此处的TRANS_BUF1的值是在另一段函数中由flag0赋的,
而flag0中每一位储存的各设为我单片机的标志位,包括了开关标志位,高档,中档,低档等(产品为塔式风扇),
传输flag0即可告诉电源板 这边控制板的状态和命令,以便电源板输出相应的命令,如将高档位导通,则电机以高档频率运行*/
LD A,TRANS_BUF2 ;将TRANS_BUF2的值传给TRANS_DATA2
LD TRANS_DATA2,A
JP SEND0 ;置成低电平,否则发送的第一位数据的波形一直持续为高
TRANSMIT1: ; 16位代码
LD A,BIT_NUM
HSUBIA D'17'
SZB STATUS,C ;判断是否溢出,溢出则跳过下一条
JP STOP_TRANSMIT
SNZB TRANS_DATA1,7 ;判断TRANS_DATA1的第7位,为1则跳过,为0不跳过下一条代码
JP BIT0 ;跳转到发送“0”
BIT1: ; “1”:1.5ms脉宽
LD A,BIT_STEP
HSUBIA D'12' ;12*125us=1500us
SNZB STATUS,C ;判断是否溢出,溢出则不跳过
JP END_TRANSMIT ;离开这段代码
JP BIT_JUD ;跳转到后续处理
BIT0: ; 0:0.5ms脉宽
LD A,BIT_STEP
HSUBIA D'4' ;4*125us = 500us
SNZB STATUS,C
JP END_TRANSMIT
BIT_JUD: ;后续处理
CLR BIT_STEP ;清掉脉冲计时变量
SZINCR BIT_NUM ;增加发送位数变量
NOP
RLCR TRANS_DATA2 ;带进位左移
RLCR TRANS_DATA1
/*带进位左移,此处通过进位标志位C来传值,TRANS_DATA2最高位左移会传给进位标志位C,
TRANS_DATA1左移会把C中的值移到最低位*/
LDIA B'00000100' ; P2.2为数据发送口
XORR P2 ;异或,确保波形随着位数的增加而取反
JP END_TRANSMIT
STOP_TRANSMIT: ; 结束0.5ms低电平
LD A,BIT_STEP
HSUBIA D'4'
SZB STATUS,C
CLR BIT_NUM ;结束时清除发送位数变量
SEND0:
CLRB OUT_DATA ;OUT_DATA端口输出低电平
JP END_TRANSMIT
SEND1:
SETB OUT_DATA ;OUT_DATA端口输出高电平
END_TRANSMIT: ;离开函数
上文中的C代码是我根据汇编代码翻译过来的,没有经过仿真或实物运行,所以不保证代码的正确性,只是为了自己和读者以后能更好的理解汇编代码的原意而写,如有错误,请见谅。
然后还需要提到的一点是汇编相比于C51而言,可以单独操作自定义变量的每一位,其逻辑与C51中的特殊寄存器类似,即f_onoff = flag0^0 与 button1 = P3^0 类似,我此处没有给出我汇编中关于flag0的定义,解释起来有点麻烦。我在C51中是用先与0x80来保存第7位然后判断其值,然后再跳到相应的输出电平程序,比如第7位为1,则跳到输出“1”的段,让其时间增加到1.5ms再跳到后续处理代码段,先清掉计1.5ms的变量,然后让计算已传位数的变量+1,然后再取反该I/O口的电平。而在汇编中则是通过判断TRANS_DATA1的第7位,然后跳到对应的发“0”或发“1”的延时程序中,等到延时时间到了,首先将TRANS_DATA2的第7位移到进位标志位C中,因为第7位再左移就会将原值直接移入进位标志位C中(C是单片机状态寄存器中的某一位),紧接着再让TRANS_DATA1左移,就会把C左移到TRANS_DATA1中的第0位,同时把TRANS_DATA1的最高位移入进位标志位C中,下次再进入程序时TRANS_DATA2的第0位就是TRANS_DATA1的最高位。这样就把TRANS_DATA2的值逐个传到了TRANS_DATA1中,同时TRANS_DATA1的值已经通过每次进入函数的检测第7位的值然后输出相应高低电平传输到了电源板,当传了8次之后,TRANS_DATA1的值已经完全发送到了电源板中,此时TRANS_DATA1中储存的是TRANS_DATA2的值,再移8次,即可把TRANS_DATA2的值也发送到电源板中。接收端通过连接的导线可以接收到发送端发送过去的高低电平信号,并通过计算高低电平的持续时间来判断发送端想要送的是“0”还是“1”。(此段较为难以理解,如果有实物结合示波器看发送端的I/O口的波形就可以很好的理解)
写到这里,大致理清了这几天一直没想明白的地方,下一篇会分享接收端是如何解码其收到的高低电平信号,再过段时间会做红外遥控的工程并分享红外的发送和接收。