单片机串口通信原理和控制程序
扫描二维码
随时随地手机看文章
我们前边学串口通信的时候,比较注重的是串口底层时序上的操作过程,所以例程都是简单的收发字符或者字符串。在实际应用中,往往串口还要和电脑上的上位机软件进行交互,实现电脑软件发送不同的指令,单片机对应执行不同操作的功能,这就要求我们组织一个比较合理的通信机制和逻辑关系,用来实现我们想要的结果。
本节所提供程序的功能是,通过电脑串口调试助手下发三个不同的命令,第一条指令:buzz on 可以让蜂鸣器响;第二条指令:buzz off 可以让蜂鸣器不响;第三条指令:showstr ,这个命令空格后边,可以添加任何字符串,让后边的字符串在 1602 液晶上显示出来,同时不管发送什么命令,单片机收到后把命令原封不动的再通过串口发送给电脑,以表示“我收到了??你可以检查下对不对”。这样的感觉是不是更像是一个小项目了呢?
对于串口通信部分来说,单片机给电脑发字符串好说,有多大的数组,我们就发送多少个字节即可,但是单片机接收数据,接收多少个才应该是一帧完整的数据呢?数据接收起始头在哪里,结束在哪里?这些我们在接收到数据前都是无从得知的。那怎么办呢?
我们的编程思路基于这样一种通常的事实:当需要发送一帧(多个字节)数据时,这些数据都是连续不断的发送的,即发送完一个字节后会紧接着发送下一个字节,期间没有间隔或间隔很短,而当这一帧数据都发送完毕后,就会间隔很长一段时间(相对于连续发送时的间隔来讲)不再发送数据,也就是通信总线上会空闲一段较长的时间。于是我们就建立这样一种程序机制:设置一个软件的总线空闲定时器,这个定时器在有数据传输时(从单片机接收角度来说就是接收到数据时)清零,而在总线空闲时(也就是没有接收到数据时)时累加,当它累加到一定时间(例程里是 30 ms)后,我们就可以认定一帧完整的数据已经传输完毕了,于是告诉其它程序可以来处理数据了,本次的数据处理完后就恢复到初始状态,再准备下一次的接收。那么这个用于判定一帧结束的空闲时间取多少合适呢?它取决于多个条件,并没有一个固定值,我们这里介绍几个需要考虑的原则:第一,这个时间必须大于波特率周期,很明显我们的单片机接收中断产生是在一个字节接收完毕后,也就是一个时刻点,而其接收过程我们的程序是无从知晓的,因此在至少一个波特率周期内你绝不能认为空闲已经时间达到了。第二,要考虑发送方的系统延时,因为不是所有的发送方都能让数据严格无间隔的发送,因为软件响应、关中断、系统临界区等等操作都会引起延时,所以还得再附加几个到十几个 ms 的时间。我们选取的 30 ms 是一个折中的经验值,它能适应大部分的波特率(大于1200)和大部分的系统延时(PC 机或其它单片机系统)情况。
我先把这个程序最重要的 UART.c 文件中的程序贴出来,一点点给大家解析,这个是实际项目开发常用的用法,大家一定要认真弄明白。
/*****************************Uart.c文件程序源代码*****************************/#includebitflagFrame=0;//帧接收完成标志,即接收到一帧新数据bitflagTxd=0;//单字节发送完成标志,用来替代TXD中断标志位unsignedcharcntRxd=0;//接收字节计数器unsignedcharpdatabufRxd[64];//接收字节缓冲区externvoidUartAction(unsignedchar*buf,unsignedcharlen);/*串口配置函数,baud-通信波特率*/voidConfigUART(unsignedintbaud){SCON=0x50;//配置串口为模式1TMOD&=0x0F;//清零T1的控制位TMOD|=0x20;//配置T1为模式2TH1=256-(11059200/12/32)/baud;//计算T1重载值TL1=TH1;//初值等于重载值ET1=0;//禁止T1中断ES=1;//使能串口中断TR1=1;//启动T1}/*串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度*/voidUartWrite(unsignedchar*buf,unsignedcharlen){while(len--){//循环发送所有字节flagTxd=0;//清零发送标志SBUF=*buf++;//发送一个字节数据while(!flagTxd);//等待该字节发送完成}}/*串口数据读取函数,buf-接收指针,len-指定的读取长度,返回值-实际读到的长度*/unsignedcharUartRead(unsignedchar*buf,unsignedcharlen){unsignedchari;//指定读取长度大于实际接收到的数据长度时,//读取长度设置为实际接收到的数据长度if(len>cntRxd){len=cntRxd;}for(i=0;i 0){//接收计数器大于零时,监控总线空闲时间if(cntbkp!=cntRxd){//接收计数器改变,即刚接收到数据时,清零空闲计时cntbkp=cntRxd;idletmr=0;}else{//接收计数器未改变,即总线空闲时,累积空闲时间if(idletmr<30){//空闲计时小于30ms时,持续累加idletmr+=ms;if(idletmr>=30){//空闲时间达到30ms时,即判定为一帧接收完毕flagFrame=1;//设置帧接收完成标志}}}}else{cntbkp=0;}}/*串口驱动函数,监测数据帧的接收,调度功能函数,需在主循环中调用*/voidUartDriver(){unsignedcharlen;unsignedcharpdatabuf[40];if(flagFrame){//有命令到达时,读取处理该命令flagFrame=0;len=UartRead(buf,sizeof(buf));//将接收到的命令读取到缓冲区中UartAction(buf,len);//传递数据帧,调用动作执行函数}}/*串口中断服务函数*/voidInterruptUART()interrupt4{if(RI){//接收到新字节RI=0;//清零接收中断标志位//接收缓冲区尚未用完时,保存接收字节,并递增计数器if(cntRxd