传统的串口接收程序是采用设立中断接收缓存区,主程序处理后及时将接收长度指针清零,此种方式对于单语句或者速度要求不高的数据段的接收没有问题,但是对于速度较高的一堆语句接收因为受到单片机处理速读的限制,容易造成丢失数据的问题。本文探讨在单片机速度一定的情况下,怎样设计接收程序,能尽可能的不丢包。
STC89C52RC作为控制模块,LCD1602作为显示屏,将串口接收的NMEA0183中GGA语句的经纬度信息显示出来。 1、中断采用了设立两个指针,分别是head(头)和tail(尾)两个指针,接收时写尾,处理时读头。tail指针始终指向中断缓存区数据写入的下一个位置(注意是下一个位置),head指针始终指向待读取数据的位置。 2、当head或者tail指针指向缓存区最后一个位置时,指针变为0,即跳转到缓存区的头部,新数据覆盖老数据,循环往复,当缓存区满的这种情况就是 3、由于NMEA0183数据是以dollar符号开头,n结尾,程序处理一条语句方便一些,缓存区中可能存有多条的语句,我们在中断函数中设立一个标志位recline来表示缓存区中有几条语句,当接收到n时,recline++,并且LED1亮,表示接收到一帧的数据。 4、中断缓存区是接收全部数据,get_linestr_fromBuf()函数从缓存区中找到一条语句,getLATstrFromGGA()函数从一条语句中筛选经纬度信息储存到1602显示的数组中,SendstrLCD1602()函数是1602的显示函数。 5、get_linestr_fromBuf()函数的主要功能是从缓存区找到一条$开头n结尾的完整的语句。这里要充分考虑到系统的容错性,即如果缓存区中没有n和dollar符号就将recline清0,表示缓存区中没有正确的语句,并直接退出函数,返回值是0,表示有效长度是0。 在找寻$符号的过程中,如果先碰到了n那么,缓存区的语句就要-1,就是为了防止n比dollar符号多的这种情况。 使用导航数据发送模拟软件来模拟导航数据的接收。一次发送多条语句来测试串口的接收承载能力。如果缓存区满 LED2就会闪烁。如果遇到缓存区中没有$ 接收错误的这种情况。LED3就会闪烁。 由测试结果可以看到 传统方式 仅仅每秒钟两条语句,就出现了过载,不能正确显示经纬度了,图中1602显示的画面已经不动了。 由测试结果可以看到,采用了读头写尾方法,在不改变单片机速度的情况下,显著提高了系统的接收能力,对于设计一次接收很多的字符串,具有很高的意义。51单片机 串口接收导航电文
设计方案
硬件构成
软件构成
遇到这种情况说明单片机的处理速度不够了,赶不上接收的速度,为了避免丢失数据包,令head指针+1,即将老数据覆盖,以此达到了循环接收的目的。并且LED2灯亮,来指示缓存区满。
另外,此函数中第一个while循环中,这条语句的作用是增加系统的容错性if(cc == 'n') { //增加容错性 考虑到2个n 一个$的这种情况 if(recline>0) recline--;
测试情况
接收承载能力测试
测试软件如图,一秒钟发送三条指令,接收正常
一秒钟发送25条指令,LED2开始疯狂闪烁,但是经纬度的显示还是一秒钟换一次。可以看到 系统的承载能力还是可以的。传统接收方式测试
程序代码
传统程序中断部分代码
volatile char NAV_uart_p = 0; void serial_rec(void) interrupt 4 { if(NAV_uart_p < UART_BUFF_SIZE && NAV_RcvDone == 0) //在长度的范围之内 { if(RI) //内部的中断寄存器 { RI=0; NAV_rx_buf[NAV_uart_p] = SBUF; if(NAV_rx_buf[0] == '$') //导航数据帧头均以'$'开始,如果不是这个字段代表不是导航数据 { NAV_uart_p++; if(NAV_uart_p > 5) //$BDGGA,先接收个报头,判断数据是否接收完毕,数据结尾格式 "xxxx*xxrn"(r=0x0D n=0x0A) { if(NAV_rx_buf[NAV_uart_p - 1] == 0X0A && NAV_rx_buf[NAV_uart_p - 2] == 0X0D && NAV_rx_buf[NAV_uart_p - 5] == '*' ) { NAV_rx_buf[NAV_uart_p] = ' '; //字符串结束符 NAV_RcvDone = 1; //数据接收完毕 ES = 0; //关中断 } } } else { NAV_uart_p = 0; } } } }
采用了读头写尾方法的程序
//********************************************************* // 程序: 接收NMEA0183串口字符串,缓冲区收收尾指针 // 处理器 : STC90C52RC // 编译环境 : Keil5 C51 // 系统时钟 : 11.0592MHZ //********************************************************* #include <reg52.h> #include <string.h> #define uint unsigned int #define uchar unsigned char #define BUFLEN 120 //收到51单片机内存的限制,缓存区长度不能太长 #define LINE 80 //一条语句长度最多80 xdata char recBuf[BUFLEN]; //都放到拓展区中 xdata char onelineBuf[LINE]; char head=0; char tail=0; char char_in; char recline=0; //全局变量 /* ***************************************************** */ // 位定义,定义控制LCD1602的引脚 /* ***************************************************** */ sbit RS = P2^6; //数据/命令选择端(H/L) sbit RW = P2^7; //数/写选择端(H/L) sbit EN = P2^5; //使能信号 sbit LED1 = P1^0; //LED1 sbit LED2 = P1^1; //LED2 sbit LED3 = P1^2; //LED1 sbit LED4 = P1^3; //LED2 /* ***************************************************** */ // 函数名称:DelayMS() // 函数功能:毫秒延时 // 入口参数:延时毫秒数(ValMS) void DelayMS(uint ValMS) { uint uiVal,ujVal; for(uiVal = 0; uiVal < ValMS; uiVal++) for(ujVal = 0; ujVal < 113; ujVal++); } /* ***************************************************** */ // 函数名称:DectectBusyBit() // 函数功能:检测状态标志位(判断是忙/闲) void DectectBusyBit(void) { P1 = 0xff; // 读状态值时,先赋高电平 RS = 0; RW = 1; EN = 1; DelayMS(5); while(P0 & 0x80); // 若LCD忙,等待 EN = 0; // 之后将EN初始化为低电平 } /* ***************************************************** */ // 函数名称:WrComLCD() // 函数功能:为LCD写指令 // 入口参数:指令(ComVal) void WrComLCD(uchar ComVal) { DectectBusyBit(); RS = 0; RW = 0; P0 = ComVal; EN = 1; DelayMS(5); EN = 0; } /* ***************************************************** */ // 函数名称:WrDatLCD() // 函数功能:为LCD写数据 // 入口参数:数据(DatVal) void WrDatLCD(uchar DatVal) { DectectBusyBit(); RS = 1; RW = 0; P0 = DatVal; EN = 1; DelayMS(5); EN = 0; } /* ***************************************************** */ // 函数名称:LCD_Init() // 函数功能:初始化LCD void LCD_Init(void) { WrComLCD(0x38); // 16*2行显示、5*7点阵、8位数据接口 DelayMS(5); // 稍作延时 WrComLCD(0x01); // 显示清屏 WrComLCD(0x06); // AC自动+、画面不动 WrComLCD(0x0f); // 开显示、光标并闪烁 } //串口初始化 波特率9600 void Uartinit(void) { TMOD = 0x20; SCON = 0x50; TH1 = 250; TL1 = TH1; PCON = 0x80; EA = 1; ES = 1; TR1 = 1; } /*----------serial receive interrupt--------------*/ void serial_rec(void) interrupt 4 { if(RI) { char_in=SBUF; RI=0; recBuf[tail++] =char_in; if(tail == BUFLEN) tail = 0; if(char_in == 'n'){ recline++; //换行标志加1 LED1 = ~LED1; } if(tail == head){ //缓冲区满 head++; if(head == BUFLEN) head = 0; LED2 = ~LED2; } } } //--------------------------------------------------------- //让1602显示字符串,len为指定长度 void SendstrLCD1602(char str[], int len) { char i=0; for(i=0; i<len; i++) WrDatLCD(str[i]); } //----------------------------------------------------------- int getLATstrFromGGA(char *onelineBuf, char *latstr) { char i; if(onelineBuf[3] != 'G' || onelineBuf[4] != 'G' || onelineBuf[5] != 'A' ) return 0; for(i=0; i<10; i++) latstr[i] = onelineBuf[i+ 17]; latstr[i] = ' '; return 1; } //----------------------------------------------------------- int getLONstrFromGGA(char *onelineBuf,char *lonstr) { char i; if(onelineBuf[3] != 'G' || onelineBuf[4] != 'G' || onelineBuf[5] != 'A' ) return 0; for(i=0; i<11; i++) lonstr[i] = onelineBuf[i+ 28]; lonstr[i] = ' '; return 1; } //----------------------------------------------------- int get_linestr_fromBuf() //从中断缓存区中读取一条语句 { char cc = 0; char index=0; //find ‘$’ while(cc != '$') { //读空了也没有遇'$' if(head == tail) { recline = 0; return 0; } cc = recBuf[head++]; if(head == BUFLEN) head = 0; if(cc == '$') { onelineBuf[index++] = cc; break; } if(cc == 'n') { //增加容错性 考虑到2个n 一个$的这种情况 if(recline>0) recline--; } } //读取数据直至换行符 while(cc != 'n'){ //读空了也没有遇到换行符 if(head == tail) { recline = 0; LED3 = ~LED3; return 0; } cc = recBuf[head++]; if(head == BUFLEN) head = 0; onelineBuf[index++] = cc; } //正常情况,头尾完整 if(recline>0) recline--; onelineBuf[index] = ' '; return index; //返回提取串的长度 } //-------------------------------------------------------------------- //将数字转换成5位字符串 void int_to_ASCstr(char str[],uint d) { str[0] = '0' + (d / 100); str[1] = '0' + (d / 10) % 10; str[2] = '0' + d % 10; str[3] = ' '; } /****************************************************** */ void main() { idata uint i; char dispstr[20]; char len; for(i=0; i<10000; i++); //延时,让1602等外设准备好 Uartinit(); //串口初始化 LCD_Init(); //1602初始化 //1602两行显示后面要显示数据的含义 WrComLCD(0x80); //选择1602第1行,第1位 strcpy(dispstr,"Lat:"); SendstrLCD1602(dispstr,4); WrComLCD(0xC0); //选择1602第2行,第1位 strcpy(dispstr,"Lon:"); SendstrLCD1602(dispstr, 4); while(1){ if(recline > 0) //看看有没有一行的数据 { len = get_linestr_fromBuf(); if(len > 50) { if(getLATstrFromGGA(onelineBuf,dispstr) == 1) { WrComLCD(0x86); SendstrLCD1602(dispstr, 10); //显示LAT } if(getLONstrFromGGA(onelineBuf,dispstr) == 1) { WrComLCD(0xC5); //选择1602第2行,第11位 SendstrLCD1602(dispstr, 11); //显示LON } } } } }
结论
本人不是计算机和嵌入式专业的,这个方式是我们这学期有一门涉及到单片机的课程中,教员讲的一个方式,我就是觉得这个方式挺有意义的,总结下来,提供给大家,以供参考,如果大家觉得有的地方写的不对,欢迎批评指正。
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算