单片机串口通信关键词:单片机,串口通信单片机应用中,串口通信是不可缺少的部分。
如何编写有效的串口通信程序对程序的结构、可靠性都有很大的影响。
串口控制程序一般分为查询和中断两者方式。
查询方式适用于简单的应用,简单可靠,但是缺点是需要占用处理器资源,在发送或者接收数据的时候不能做其它的事情,处理器利用率低。
中断方式下,在发送或者接受数据的时候处理器还可以做其它的工作,效率较高。
对于稍微复杂的系统来说,中断方式管理串口程序将会更加有效。
中断处理方式也可分为几种,其中采用循环缓冲区的方式比较高效。
循环缓冲区为定义的一定长度的RAM区间,对于接受数据来说,中断中收到的数据将存入RAM中,然后等待主程序来读取。
其中会涉及到数据见的协调问题,写数据的时候不能把还没有读取的数据覆盖掉,读数据的时候应该读取的是缓冲区中最老的数据。
当缓冲区已满的时候,写入的新数据应该覆盖掉最老的数据。
这些问题的处理可以使用两个指针来实现。
初始化时两个指针均指向RAM区间的底部,如图1所示。
当中断中接收到一个数据的时候,将这个数据写入写指针WPTR指向的存储单元,然后调整写指针指向下一个空余的RAM区间,程序上处理就是把写指针加一,如图2所示。
同理,写入N个数据后写指针同步更新,如图3所示。
当读数据的时候,首先判断缓冲区中是否有数据,方法是判断读指针和写指针是否相等,如果相等表明没有数据,如图5所示。
如果读指针和写指针不等,那么读取缓冲区中的数据,然后调整读指针,当写指针和读指针相等的时候,表明缓冲区中的有效数据已经读取完,此时读指针和写指针相等。
当有数据再次写入的时候,继续紧接着上次写入的地址后写入新的数据,如果数据长度超过缓冲区的长度,写指针重新返回缓冲区的底部重新开始(这是循环缓冲的由来),如图6所示。
此时如果有数据读出,读指针做同样的更新。
如果没有数据读出,一直有数据写入,可能会出现缓冲区写满的情况,如图7所示。
此时如果仍然没有数据读取,继续有数据写入的时候,为了保留新的数据,必须丢弃老的数据,即写指针可能超过读指针,此时,读指针必须和谐指针同步更新,这样才能保证读取的是没有被覆盖的最老的数据,如图8所示。
需要注意的是,读指针在中断过程中也可能被更改,因此,读数据的子程序需要对读指针的更改进行保护,方法是在读数据的时候关闭串行口中断。
下面是循环缓冲区接收数据的程序实例。
FT, 尽然连文本都不能上传,代码只好贴出来吧。
/** FileName: uart.h* Description: header file for SerialPort* Author: SangWei, HUST-CEEE-2004* Contact: swkyer@, swkyer@* Date: 2005-09-22** Platform: P89C6102(Philips), KeilC51(ver: 7.20)** (C)All Rights Reserved.*/#ifndef __UART_H__#define __UART_H__#define UARTBUFFLEN 64 /* 串口缓冲区64个字节*/void UartInit(void);void SendChar(unsigned char ch);unsigned char IsUartReceived(void);unsigned char ReadChar(unsigned char idata *buf);#endif /* __UART_H__ *//** FileName: uart.c* Description: Implementation of SerialPort* Author: SangWei, HUST-CEEE-2004* Contact: swkyer@, swkyer@ * Date: 2005-09-22** Platform: P89C6102(Philips), KeilC51(ver: 7.20) ** (C)All Rights Reserved.*/#include#include "hardware.h"#include "uart.h"unsigned char data ramuse;static unsigned char idata uartbuf;static unsigned char idata bufwptr, bufrptr;extern unsigned char xdata uartbuff[UARTBUFFLEN];/** 初始化串口, 波特率9600*/void UartInit(void){CLR_DOG;status = 0;bufwptr = 0; // 写指针bufrptr = 0; // 读指针MAX485CTL = 0; // 接收数据PCON = 0x00; // 设置串口波特率, 时钟频率30MHZT2CON = 0x30; // 定时器2作为波特率发生器SCON = 0x50; // 模式1// n = 65536 - [fosc/baud*32]// n = 65536 - 30000000/(19200*32) = 65536 - 49 = 654 90 = 0xffd2// n = 65536 - 22118400/(19200*32) = 65536 - 36 = 655 00 = 0xffdc#ifdef _DEBUG_AT_HOMERCAP2H = 0xff;RCAP2L = 0xb8; // baud rate 9600TH2 = 0xff;TL2 = 0xb8;#elseRCAP2H = 0xff;RCAP2L = 0x9e; // baud rate 9600TH2 = 0xff;TL2 = 0x9e;#endifIP = 0x10; // 串口中断优先级高 REN = 1;TI = 0;RI = 0;TR2 = 1; // 启动定时器2ES0 = 1; // 使能串口中断CLR_DOG;}/** 发送一个字节数据*/void SendChar(unsigned char ch) {SBUF = ch;while(TI == 0);TI = 0;}/** 判断是否接收到数据,如果没有返回0,如果有,返回数据长度 */unsigned char IsUartReceived(void){unsigned char num;CLR_DOG;EA = 0; // 关中断if(bufwptr == bufrptr)num = 0;else if(bufwptr > bufrptr)num = (bufwptr-bufrptr);else // if(bufwptr < bufrptr)num = (UARTBUFFLEN-bufwptr+bufrptr);EA = 1;return num;}/** 从缓冲区中读一个字节数据* 返回剩下的字节数*/unsigned char ReadChar(unsigned char idata *buf) {unsigned char ch, num;CLR_DOG;USE_ERAM;EA = 0;if(bufwptr == bufrptr){*buf = 0;num = 0;}else if(bufwptr > bufrptr){ch = uartbuff[bufrptr];*buf = ch;bufrptr++;num = (bufwptr-bufrptr);}else // if(bufwptr < bufrptr){ch = uartbuff[bufrptr];*buf = ch;bufrptr++;if(bufrptr == UARTBUFFLEN)bufrptr = 0;num = (UARTBUFFLEN-bufrptr+bufwptr);}EA = 1;return num;}/** 串口中断接受*/void UartInt(void) interrupt 4 using 1{ // use eramif(RI){ES0 = 0;ramuse = AUXR; // 保存进入中断时的ram区间状态 ramuse &= 0x03;AUXR = 0x00;uartbuf = SBUF;RI = 0; // 清中断标志CLR_DOG;uartbuff[bufwptr] = uartbuf; // 数据写入缓冲区if(bufwptr >= UARTBUFFLEN-1) // 循环缓冲指针bufwptr = 0;elsebufwptr++;if(bufwptr == bufrptr) // 未读数据被覆盖,读指针更新到写指针的上一个单元bufrptr = bufwptr + 1;AUXR = ramuse;ES0 = 1;}}注:转载请说明出处。