关于51单片机串口通信的问题
如图是PC从串口助手发送一个字符到单片机,再由单片机返回该字符的例程结果。该字符'1'的数据流图应该是从PC端进入到单片机的SBUF寄存器中去,当单片机接收到该字符的结束位时,单片机将RI置1,表示接收完毕。同理,在发送到该字符的结束位时,单片机将TI置1,表示发送完毕。
但当发送多个字符的情况时,我对串口通信的数据传输过程有了疑惑:
如图,当从PC发送多个字符时(问题1:这里是否可以理解为字符串的串口通信),仍然可以完美的实现PC与单片机之间的数据互通。而程序仍然与之前一样没有改动(总程序见下)。这时我产生了疑惑,就单片机而言,无论接收还是发送,都是以一个字符为单位进行操作,当面对多个字符的传输时,它是先将所有字符依次接收到SBUF中去,然后将接收到的多个字符再依次发送出去?还是每接收一个字符,就立马发送一个字符?(问题2:是实时传输单个字符还是缓存所有数据后再进行处理),比如在处理“1a!”时,当单片机SBUF接收到'1',立刻就将'1'从SBUF中发送出去,然后再对'a'进行接收......
我认为是后者的情况,因此我想利用LCD1602进行验证。我的思路是:既然单片机是每接收一个字符,就发送一个字符,那我可以定义一个数组,在单片机的接收中断中(由RI==1触发的中断)存储每次SBUF接收的数据,为了验证方便,我假设每次传输的都是3个字符。中断程序如下:
#define DATA_LENGTH 3
uchar Receivedata[];
uchar ReData,Flag1,Flag2;
uint i,j;
void ser_int (void) interrupt 4
{
if(RI == 1)
{
RI = 0;
ReData = SBUF;
Receivedata=ReData;
i++;
if(i==DATA_LENGTH)
{
i=0;
Flag1=1; //代表3个字符接收完毕
}
Flag2=1; //代表当前字符接收完毕
}
}
如果说单片机是每接收到一个字符就发送它,按照这个中断,最后Receivedata[]应该是一个存储了“1a!”长度为3的字符类型的数组。
接下来我们只要将数组中的字符元素依次显示在LCD1602上就可以证明猜想,以下为主程序:
void main (void)
{
init_time(); //初始化定时器中断
init_1602(); //初始化LCD1602
write_com(0x80); //选定开始显示的位置是第一行第一列
while(1)
{
if(Flag2==1)
{
SBUF = ReData;
while(TI==0);
TI=0;
Flag2=0;
if(Flag1==1)
{
for(j=0;j<3;j++)
{
write_dat(Receivedata[j]);
}
Flag1=0;
}
}
}
}
但最后程序烧录后结果并不理想,当从PC送入三个字符时,LCD并不能正常显示这三个字符,而且串口助手接收也出现了问题。
验证行动就此夭折,出现这种情况,首先说明数组Receivedata中并没有正确的存储SBUF中的字符。另外在增加LCD显示模块的过程中,影响了串口通信的准确度。
在此,我希望仔细看过该问答的前辈们指出我的错误,或者回答上面红字标出的问题。另外,新的问题也从验证的过程中产生(问题3,如何将多个字符或者字符串完整的存储下来或进一步显示在1602上?)
在我编程的过程中一定忽视了一些致命的问题,恳请赐教!
应该是
uchar Receivedata[8];
uchar Receive_number; 接收数组的下标
在接收中断里是
Receivedata[Receive_number]=ReData;
Receive_number ++;
if (Receive_number>7) Receive_number = 0; 避免下标超出数组长度
在Flag1=1;? ?? ? //代表3个字符接收完毕
后面加
Receive_number = 0; 准备好下一次的接收
在主程序里,接收多个字符的同时返回接收的字符有可能会出错,在于Flag2标志的处理,当主程序发送完数据后清理Flag2标志时,有可能会出现已经接收完下一个字符并设置了Flag2标志,结果主程序清理了Flag2标志,就无法判断后一个字符是否已接收了
串口每接收1个字符就产生中断请求,中断请求位清0后才能接收下1个字符,发送回传数据同理,如果你想接收一串字符后再全部回传就得用缓存把接收到的一串字符保存起来,然后关闭串口中断(使用的是中断法),一个一个的读取缓存发送回传,完后再打开串口中断,等待接收下一串字符。
这里有两个概念,一个是发送数据的帧结构,一个是发送数据的数据包结构,串行通讯是按帧发送和接收的,也就是每次发送和接收都是一个字节。数据包的结构是有一定约束的,比如数据包的包头字符,代表数据包的开始,数据包的结束字符,代笔数据包的接收结束。
对于数据包的结构,可以参考modbus-rtu的报文格式来理解。
串口发送接收多字节时,你一开始的理解是对的,按照你的程序逻辑,确实是接收一个字节就会处理并发送一个字节。想要接收一串字符串之后再做处理的话,需要加软件处理,一种方式是加分包延时,即超过一定延时(一般是几毫秒到几十毫秒)没有接收到数据,就对之前接收的所有数据进行一次处理,当然在这之前需要开辟一块接收缓冲区,每个字节收到时要按顺序放到缓冲区内;另一种方式是增加数据结尾标志,这个标志可以自己定义,比如回车,比如空格,或者其他特定的字符,单片机在接收到这个字符时,就认为已经传完一个数据包,开始对其进行处理。
至于你的验证,中断里面的数组忘记加下标了。
有满意的答案,记得采纳最佳答案哦!
问题1:这里是否可以理解为字符串的串口通信
我觉得不可以,你之前也说了是一个字节的数据发送接收处理
问题2:是实时传输单个字符还是缓存所有数据后再进行处理
我认为应该是实时传输单个字符,原因通问题1.要确定的话需要判断你的SBUF是多少位的。你还可以通过判断进入发送中断的次数来判断“1a!”是一次发出还是多次发出。
问题3,如何将多个字符或者字符串完整的存储下来或进一步显示在1602上?
确定上面两个之后,数据存储就能实现了。
代码里面的Receivedata=ReData;有问题的..
应该是 Receivedata = ReData 改过之后仍然是这种情况
您关于问题2的回答给了我一些启发!谢谢!
您回复中的“处理”我是否可以认为成这里我想将接受到的几个字符显示在LCD上?
我对您提到的第二种实现方式很感兴趣。您的意思是否可以通过下面的做法实现:可以再接受串口终端(RI==1)中加一个判断语句,if(SBUF接收到的数据为'\n'),则关闭串口中断(ES==0;),然后在主程序中对之前在串口中接收到的缓冲区数据(Receivedata[])显示在LCD上,显示完毕后再将ES打开。
至于我在文中的验证,实际上我程序中已经加上下标了,但仍然是文中图片的症状。
感谢您的回复!
问题最终解决了,的确像前面几位所言。
首先,51单片机的串口通信的确是以单个字节为单位处理的,也就是接收和发送都是对一个字节进行处理。但这里我出现最大的错误就是,忽视了没有关闭中断而导致的“时序混淆”。“时序混淆"的解释是:在主程序中,如果检测到Flag2为1,说明单片机接收到了1个字节的数据,如果说此时没有立刻关闭中断(ES=0;)则会出现 人中狼 前辈最后说的,单片机第二次发送字节数据的时候可能直接进入接收字节中断。这种混淆破坏了Flag2位的时效性,直接导致单片机无法正确发送数据。
但并不是缺少中断关闭就一定会造成混淆,如果主程序中仅有:
if(Flag==1)
{
SBUF = ReData;
while(TI==0);
TI=0;
Flag=0;
}
在这种情况下,不要求加入lcd显示的指令,主程序的程序周期大大缩短。因此可以在下一个字节接收完毕之前完成当前字节的发送,并正常将Flag位置0;
但如果加上LCD显示指令,完成一次字节的发送的时间周期就会大大增加,从而出现该问答中的问题!
是的,“处理”就是你需要对接收到的数据做的动作,在这里就是显示。你说的做法是可以的,但是关中断去处理的话,有可能导致数据丢包。推荐做法是将缓冲区处理(Receivedata[]的赋值)放到中断里,在满足条件if(SBUF接收到的数据为'\n')后,置一个标志位,主程序在检测到该标志位之后进行显示,在该过程中是允许数据继续接收的。因为如果需要的数据处理比较繁琐,或者主程序中其他内容比较多,来不及处理,按照你的关中断的方法,就会丢失数据包,不关中断,同时将缓冲区开辟的略大,则只是导致两条信息同时处理,而不会丢包。