| /************************************************************************ ;* 公 司: xx ;* 模 块: serial.c ;* 功 能: 串口中断服务程序,仅需做简单调用即可完成串口输入输出的处理; ;* 出入均设有缓冲区,大小可任意设置 ;* 芯 片: AMEGA16 ;* 说 明: 未利用串口发送硬件BUFF ;* 设 计: 李耀峰 ;* 时 间: 2005-11-24 ;* 版 本: V1.0 ;* 记 录: ;************************************************************************/
/************************************************************************ 可供使用的函数名: extern void PutByte(byte c); //放入一个字节到发送缓冲区 extern void PutString(byte *puts); //发送一个定义在程序存储区的字符串到串口 extern void PutBytes(byte *outplace,byte j); //发送一串数据 extern void PutHEX(byte c); //发送一个字节的hex码,分成两个字节发 extern byte GetByte (void); //从接收缓冲区取一个byte extern void SerialInit (word baud); //串口初始化
extern byte inbufsign; //接收缓冲区数据,有数据=1。
#define CR PutString("
") //发送一个回车换行 #define NUL putstring(" ") //发送一个空格 *************************************************************************/
#include <iom16V.h> #include <macros.h>
#define byte unsigned char #define word unsigned int
#define OLEN 20 //串口发送缓冲大小 #define ILEN 20 //串口接收缓冲大小
byte outbuf[OLEN]; //发送缓冲 byte inbuf[ILEN]; //接收数据缓冲 byte *outlast=outbuf; //最后由中断传输出去的字节位置 byte *putlast=outbuf; //最后放入发送缓冲区的字节位置 byte *inlast=inbuf; //最后接收到接收缓冲区的字节位置 byte *getlast=inbuf; //最后从发送缓冲区取走的字节位置
struct data //位定义 { unsigned bit0:1; unsigned bit1:1; unsigned bit2:1; unsigned bit3:1; unsigned bit4:1; unsigned bit5:1; unsigned bit6:1; unsigned bit7:1; }bit_flag; #define outbufsign0 bit_flag.bit0 //缓冲区数据发完标志 发完=0 #define outbufsign bit_flag.bit1 //发送缓冲区非空标志 有=1 #define inbufful bit_flag.bit2 //接收缓冲区满标志 满=1
//#define inbufsign bit_flag.bit3 //接收缓冲区非空标志 有=1 //byte outbufsign0; //缓冲区数据发完标志 发完=0 //byte outbufsign; //发送缓冲区非空标志 有=1 //byte inbufful; //接收缓冲区满标志 满=1
byte inbufsign; //接收缓冲区非空标志 有=1
#define CR PutString("
") //CR=回车换行 #define SPACE PutByte(0x20) //发送一个空格。
#pragma interrupt_handler SerialIncept_handler:12 //串口接收中断函数 #pragma interrupt_handler SerialSend_handler:14 //串口发送中断函数 //********************************************************************** //函 数 名: void PutByte(byte c) //功 能: 放入一个字节到发送缓冲区 //说 明: //参 数: //返 回 值: //示 范: PutByte(0x00); //*********************************************************************** void PutByte(byte c) { CLI(); //暂停串行中断,以免数据比较时出错 while((((outlast-putlast)==2)&&(outlast > putlast ))||((outlast < putlast)&&(OLEN-(putlast-outlast)==2))) { SEI(); c++;c--; CLI(); } *putlast=c; //放字节进入缓冲区 putlast++; //发送缓冲区指针加1 if (putlast==outbuf+OLEN) putlast=outbuf; //指针到了顶部换到底部 outbufsign=1; if (!outbufsign0) //缓冲区无数据 { outbufsign0=1;
UDR=*outlast; //未发送完继续发送 outlast++; //最后传出去的字节位置加1 if (outlast==outbuf+OLEN) outlast=outbuf;//地址到顶部回到底部 if (putlast==outlast) outbufsign=0; //数据发送完置发送缓冲区空标志 } //缓冲区开始为空置为有,启动发送 SEI(); }
//********************************************************************** //函 数 名: void PutString(byte *puts) //功 能: 发送字符串到串口 //说 明: //参 数: 发送的字符串 //返 回 值: //示 范: putstring("
") //*********************************************************************** void PutString(byte *puts) { for(;*puts!=0;puts++) //遇到停止符0结束 PutByte(*puts); }
//********************************************************************** //函 数 名: void PutBytes(byte *outplace,byte j) //功 能: 放一串数据到发送缓冲区,需要定义发送的字节数 //说 明: //参 数: *outplace:发送的字节数据首地址指针 j:发送的字节数量 //返 回 值: //*********************************************************************** void PutBytes(byte *outplace,byte j) { int i; for(i=0;i<j;i++) { PutByte(*outplace); outplace++; } }
//********************************************************************** //函 数 名: PutHEX(unsigned char c) //功 能: 发送一个字节的hex码,分成两个字节发。 //说 明: 发送ASSIC码 //参 数: 发送的数据 //返 回 值: 无 //示 范: PutHEX(i); //*********************************************************************** const byte hex_[]={"0123456789ABCDEF"}; void PutHEX(byte c) { word ch; ch=(c>>4)&0x0f; PutByte(hex_[ch]); ch=c&0x0f; PutByte(hex_[ch]); SPACE; }
//********************************************************************** //函 数 名: byte GetByte (void) //功 能: 从接收缓冲区取一个byte //说 明: 如不想等待则在调用前检测inbufsign是否为1 //参 数: 无 //返 回 值: 接收到的数据 //示 范: i=GetByte(); //*********************************************************************** byte GetByte (void) { char c ; while (!inbufsign); //缓冲区空等待 CLI(); c=*getlast; //取数据 getlast++; //最后取走的数据位置加1 inbufful=0; //输入缓冲区的满标志清零 if (getlast==inbuf+ILEN) getlast=inbuf; //地址到顶部回到底部 if (getlast==inlast) inbufsign=0; //地址相等置接收缓冲区空空标志,再取数前要检该标志 SEI(); return (c); //取回数据 }
//********************************************************************** //函 数 名: void SerialSend_handler (void) //功 能: 串口发送中断处理 //说 明: //参 数: //返 回 值: //*********************************************************************** void SerialSend_handler (void) { UCSRA|=(1<<TXC); //清发送中断标志 if (outbufsign) { UDR=*outlast; //未发送完继续发送 outlast++; //最后传出去的字节位置加1 if (outlast==outbuf+OLEN) outlast=outbuf;//地址到顶部回到底部 if (putlast==outlast) outbufsign=0; //数据发送完置发送缓冲区空标志 } else { outbufsign0=0; } }
//********************************************************************** //函 数 名: void SerialIncept_handler (void) //功 能: 串口接收中断处理 //说 明: //参 数: //返 回 值: //*********************************************************************** void SerialIncept_handler (void) { if(!inbufful) //接收缓冲区未满 { *inlast= UDR; //放入数据 inlast++; //最后放入的位置加1 inbufsign=1; if (inlast==inbuf+ILEN) inlast=inbuf; //地址到顶部回到底部 if (inlast==getlast) inbufful=1; //接收缓冲区满置满标志 } }
/********************************************************************** 函 数 名: void SerialInit (unsigned long) 功 能: 串口初始化 说 明: 串口初始化成指定波特率,开接收,发送并开相应中断 参 数: 需要初始化的波特率 返 回 值: 无 示 范: SerialInit (38400); ***********************************************************************/ void SerialInit (word baud) { CLI(); UCSRC&=(~(1<<URSEL)); UBRRH=(byte)(baud>>8); UBRRL=(byte)baud;
UCSRB=(1<<RXCIE)|(1<<TXCIE)|(1<<RXEN)|(1<<TXEN); //接收中断使能,发送中断使能,接收器与发送器使能 UCSRC=(1<<URSEL)|(3<<UCSZ0); //设置帧格式: 8 个数据位, 1 个停止位*/ SEI(); //开全局中断 }
我来泼几瓢冷水:
1>用两个指针和一个位域来记录队列的位置和状态极其不合理,因为这需要9个字节,而且最后的目标代码会因为指针和位域的原因产生相当长的代码。我个人只用了两个字节表示读写指针的位置加一个队列有效数据的计数器,一共3个字节,简单明了;
2>上述几个读写指针没有加volatile,有可能在执行读写时和中断冲突;
3>既然使用了队列缓冲,就不应该在程序中使用while语句来死等,而应该让程序体面的失败退出;这一点尤其体现在这个函数: byte GetByte (void) 程序有可能在这个地方死掉! while (!inbufsign); //缓冲区空等待
4>发送中断函数: void SerialSend_handler (void) 这简直是失败中的失败!当发送缓冲区为空的时候还产生多余的中断。
5>接收中断函数: void SerialIncept_handler (void) 这个函数也很失败!但数据接收缓冲区满的时候,如果再来一个数据,由于你没有把硬件中的数据取走,同样会再次产生中断!也会死掉!
6>初始化函数 void SerialInit (word baud) 你在程序中添加了CLI() / SEI() 我知道你是为了在初始化的时候关掉中断,初始化完毕后再打开,看似合理,但真的合理吗?至少在我看来是很不合理! 一个系统,要初始化的模块肯定不止一个,当你初始化这个模块后就带开了中断,由于相应中断的原因,也许过了n久,你其它模块还没有初始化! 我个人的做法是:系统开始初始化前关掉中断:CLI(),等所有模块初始化完毕再打开中断SEL()
|