51单片机 串口接收导航电文

  • 设计方案
    • 硬件构成
    • 软件构成
  • 测试情况
    • 接收承载能力测试
    • 传统接收方式测试
  • 程序代码
    • 传统程序中断部分代码
    • 采用了读头写尾方法的程序
  • 结论

传统的串口接收程序是采用设立中断接收缓存区,主程序处理后及时将接收长度指针清零,此种方式对于单语句或者速度要求不高的数据段的接收没有问题,但是对于速度较高的一堆语句接收因为受到单片机处理速读的限制,容易造成丢失数据的问题。本文探讨在单片机速度一定的情况下,怎样设计接收程序,能尽可能的不丢包。

设计方案

硬件构成

STC89C52RC作为控制模块,LCD1602作为显示屏,将串口接收的NMEA0183中GGA语句的经纬度信息显示出来。

软件构成

1、中断采用了设立两个指针,分别是head(头)和tail(尾)两个指针,接收时写尾,处理时读头。tail指针始终指向中断缓存区数据写入的下一个位置(注意是下一个位置),head指针始终指向待读取数据的位置。

2、当head或者tail指针指向缓存区最后一个位置时,指针变为0,即跳转到缓存区的头部,新数据覆盖老数据,循环往复,当缓存区满的这种情况就是


遇到这种情况说明单片机的处理速度不够了,赶不上接收的速度,为了避免丢失数据包,令head指针+1,即将老数据覆盖,以此达到了循环接收的目的。并且LED2灯亮,来指示缓存区满。

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。
另外,此函数中第一个while循环中,这条语句的作用是增加系统的容错性

if(cc == '\n') {             //增加容错性 考虑到2个\n 一个$的这种情况if(recline>0) recline--;

在找寻$符号的过程中,如果先碰到了\n那么,缓存区的语句就要-1,就是为了防止\n比dollar符号多的这种情况。

测试情况

接收承载能力测试

使用导航数据发送模拟软件来模拟导航数据的接收。一次发送多条语句来测试串口的接收承载能力。如果缓存区满 LED2就会闪烁。如果遇到缓存区中没有$ 接收错误的这种情况。LED3就会闪烁。

测试软件如图,一秒钟发送三条指令,接收正常


一秒钟发送25条指令,LED2开始疯狂闪烁,但是经纬度的显示还是一秒钟换一次。可以看到 系统的承载能力还是可以的。

传统接收方式测试

由测试结果可以看到 传统方式 仅仅每秒钟两条语句,就出现了过载,不能正确显示经纬度了,图中1602显示的画面已经不动了。

程序代码

传统程序中断部分代码

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*xx\r\n"(\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] = '\0';                //字符串结束符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++;              //换行标志加1LED1 = ~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] = '\0';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] = '\0';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] = '\0';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] = '\0';
}/****************************************************** */
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 }}}}
}

结论

由测试结果可以看到,采用了读头写尾方法,在不改变单片机速度的情况下,显著提高了系统的接收能力,对于设计一次接收很多的字符串,具有很高的意义。
本人不是计算机和嵌入式专业的,这个方式是我们这学期有一门涉及到单片机的课程中,教员讲的一个方式,我就是觉得这个方式挺有意义的,总结下来,提供给大家,以供参考,如果大家觉得有的地方写的不对,欢迎批评指正。

基于51单片机的串口中断读头写尾法接收NMEA0183经纬度信息 1602显示(循环接收)相关推荐

  1. 51单片机 (6)串口中断通信+定时器2串口中断

    [若有疑问错误或版权等问题请联系我] [转载请注明出处:http://blog.csdn.net/leytton/article/details/48442129] 51单片机默认使用定时器1作为串口 ...

  2. 51单片机学习篇-- --基于51单片机的串口通信协议

    开篇先说一句废话···· 本旺名字叫萨摩耶,,Please 叫我旺财,,,哈哈,招财进宝嘛! 开篇 计算机按照下行数据通信协议,串口发送数据,地址为自己的学号(十六进制),单片机收到后(收到的是数据, ...

  3. 基于51单片机的酒精浓度检测量仪proteus仿真程序原理图设计数码管液晶LCD1602显示

    硬件设计 (末尾附文件) 仿真文件中的酒精传感器均是用滑动变阻器来模拟 基于数码管显示: 基于LCD1602显示: 程序设计 //程序头函数 #include <reg52.h> //显示 ...

  4. 基于51单片机的智能空调控制系统设计(仿真+代码+原理图+报告+视频讲解)

    基于51单片机的智能空调控制系统 这里写目录标题 1 开发环境 讲解演示视频 2 功能说明介绍 3 仿真图 4 程序 4.1 工程文件 4.2 代码 5 原理图 6 元器件清单 7 视频讲解 8 资料 ...

  5. 声控灯程序C语言,基于51单片机的声控灯的设计.doc

    PAGE 3 基于51单片机的声控小灯 电子信息工程技术信息工程系 电子信息工程技术 信息工程系 年 10 月 20 日 诚 信 声 明 本人郑重声明:所呈交的毕业设计文本和成果,是本人在指导老师的指 ...

  6. 基于51单片机的双机串口通信排队叫号系统(LCD显示)设计

    基于51单片机的双机串口通信排队叫号系统(LCD显示)设计 1 开发环境 视频讲解 2 功能说明介绍 3 仿真图 4 程序 5 原理图 6 视频讲解 7 设计报告 7.1 设计目的 7.2 设计要求及 ...

  7. 基于51单片机的多路温度检测调节串口传输系统

    本设计基于51单片机的多路温度检测调节串口传输系统(仿真+源码+视频讲解) 仿真:proteus8.9 程序编译器:keil 4 编程语言:C语言 编号C0009 [腾讯文档]C0009 网盘链接 资 ...

  8. 51单片机stc15w204s串口通信发数据接收数据串口中断发中文字符串完美运行软件延时发送一字节函数全注释

    这里写自定义目录标题 KEIL自己先调试通了然后再说下面的事 51单片机stc15w204s串口通信直接上文件 KEIL自己先调试通了然后再说下面的自己看看就可 KEIL自己先调试通了然后再说下面的事 ...

  9. Proteus基于51单片机通过PWM脉冲调制控制电机转速_按键与串口控制转速_电机转速可测

    文章目录 原理图 驱动电路 MOTOR-ENCODER详解 串口通信 电机测速原理 PWM 软件部分 成果 临近期末,学校的单片机课程需要做课程设计,主要内容是基于51单片机的可调速电机,具体要求如下 ...

最新文章

  1. 压缩文件夹_怎样压缩文件夹并发送
  2. 执行Shell脚本的4种方法
  3. 点量OTT TV 点播软件模式为何受海外华人运营者喜爱?
  4. laravel CURD ORM
  5. pip安装库速度较慢--常用的几个国内镜像
  6. svn merger的时候 报远程主机强迫_SVN与Git比较的优缺点差异
  7. python二分法求方程的根_Python查找函数f(x)=0根的解决方法
  8. netcore 编译 html,Asp.Net Core中的@ Html.Action
  9. 34. Find First and Last Position of Element in Sorted Array
  10. Could not retrieve transaction isolation level from server
  11. Python3入门机器学习经典算法与应用 第3章 matplotlib基础
  12. Python移动应用开发
  13. ps html插件初始化失败,解决PSCC2019无法安装扩展插件怎么办?
  14. 威纶通触摸屏与西门子PLC200之间的无线通讯
  15. java 实现QQ自动登录(带验证码)
  16. 国外网站视频下载方法通通告诉你
  17. hdmi接口线_HDMI高清线注意事项
  18. 求两个数的最小公倍数及多个数的最小公倍数的求法
  19. 解释什么是啸叫,为什么会发生啸叫,啸叫的为何和如何防止啸叫
  20. (附源码)基于Python音乐分类系统 毕业设计 250858

热门文章

  1. Qgis教程14:在QGS中添加自定义的Mapbox底图
  2. linux系统查看网口流量,linux 查看网口流量
  3. Ubuntu22.04虚拟机配置及使用代理工具
  4. 使用 Vuex + Vue.js 构建单页应用
  5. Win10更新后使用相机时,提示找不到相机解决方法(方法之一)
  6. 常用稳压二极管参数表
  7. 数据挖掘幕课第四章习题
  8. vue - 比较两个日期大小、比较同一天两个时间大小(判断两个日期时间的大小)JS 解决方法
  9. H5生成二维码及保存
  10. Java 书籍阅读计划