实现高效的GPRS驱动程序
1. 引言
用过几款GPRS模块,也从淘宝上买过多个GPRS模块,一般的都会送一个驱动程序和使用demo,但是代码质量都较低。
回头看了下几年前使用的GPRS代码,从今天的角度来看,也就是买模块赠送一个免费demo的那种水平,甚是汗颜。
GPRS模块驱动主要是串口驱动,其本质是字符串处理,本文就从对比下几种常见的驱动方式。
2. 版本1--初学者的驱动
思路:
1. 串口接收使用中断,收到数据放到全局buffer。
2. 发送前清空接收buffer。
3. 拼接字符串,然后从串口发送出去。
4. 设定一个等待时间,然后while(1)不停的查看接收buffer里面是否有需要的字符串出现,即是否得到需要的响应。
5. 初始化过程使用一个简单的状态机轮转,一步通过再进行下一步。
下面是一个我曾经用过的例子,问题很明显:
1. 难维护,函数耦合度太高,简单的堆功能,功能模块没有划分。
2. 低效,发送需要CPU停下来一个一个字符的发送,接收还要延时一段时间等待GPRS模块回复足够多的数据。
3. 接收buffer只是简单的共享全局变量,没有双buffer切换也没有读写互斥。
比如每次发送前清空buffer然后发送命令,用来判别此次接收都是对本次发送的命令的响应。
4. 不能精细控制,AT指令响应检查全部放到一个函数里面处理,必然造成有些AT响应的回复无法区分对应哪个指令。
网络连接的驱动:
...// 这一句是嵌在应用户代码中
while(gprs_start==) //未连接
{
gprs_start=gprs_conduct();
}
......
//---------------------------------------------------------
//函数名称:void gprs_connect(void)
//函数功能:配置socket
//输入参数:无
//返回参数:返回0 表示还没完成注册,返回1表示完成所有连接
//---------------------------------------------------------
uint8_t gprs_connect(uint8_t *ascData, uint8_t len)
{
uint8_t num,GPRS_Connect=;
char strBuf[];
memset(strBuf,0x00,sizeof(strBuf)); if (gprs_mgr.stat == GPRS_GET_CSQ) // 查询信号强度
{
if (gprs_get_csq() > )
gprs_mgr.stat = GPRS_WAIT_REGNET;
delay_ms();
printf("gprs_mgr.stat = GPRS_GET_CSQ"); }
else if (gprs_mgr.stat == GPRS_WAIT_REGNET) // 等待注册到网络
{
if (!gprs_reg_network())
gprs_mgr.stat = GPRS_CONFIG_PARA;
delay_ms();
printf("gprs_mgr.stat = GPRS_WAIT_REGNET");
}
else if (gprs_mgr.stat == GPRS_CONFIG_PARA) // 配置通信参数
{
if (!gprs_config_para())
gprs_mgr.stat = GPRS_CONFIG_SOCKET;
delay_ms();
printf("gprs_mgr.stat = GPRS_CONFIG_PARA;");
}
else if (gprs_mgr.stat == GPRS_CONFIG_SOCKET) // 配置socket
{
if (!gprs_config_socket()) //0 sucess
gprs_mgr.stat = GPRS_DATA_RW;
else //不成功可能由于服务器端口被占用,
{
gprs_send_cmd("AT%IPCLOSE=1\r\n");
for(num=;num<;num++)
{
delay_ms();
}
}
delay_ms();
printf("gprs_mgr.stat = GPRS_CONFIG_SOCKET;"); }
else if (gprs_mgr.stat == GPRS_DATA_RW) // 数据读写
{
GPRS_Connect=;
sprintf(strBuf,"AT+CIPSEND=%d\r\n",len+);
gprs_send_cmd(strBuf);
gprs_write_data(ascData);
delay_ms();
printf("GPRS_DATA_RW;");
gprs_send_cmd("AT+CIPCLOSE\r\n");
printf("disconnect grps;");
gprs_mgr.stat = GPRS_GET_CSQ;
}
return GPRS_Connect;
}
发送函数,串口输出加上查询式解析:
//---------------------------------------------------------
// 函数名称:uint8 gprs_send_cmd(char* pcmd)
// 函数功能:gprs命令字发送函数
// 输入参数: pcmd,要发送的命令
// 返回参数:
// 0 ,命令发送成功
// 1 ,命令发送失败
//---------------------------------------------------------
uint8_t gprs_send_cmd(char* pcmd)
{
uint16_t i;
uint8_t ret=, *GSM_ReturnInfo;
memset(GSM_info, , sizeof(GSM_info)); // 清除串口缓冲区
GSM_Info_CNT=; // 清除串口接收计数
debug_print(pcmd); //发送的命令,调试输出 while(*pcmd) // 发送命令
{
while(USART_GetFlagStatus(GPRS_USART, USART_FLAG_TXE)==);
USART_SendData(GPRS_USART, *pcmd++);
} delay_ms();
GSM_ReturnInfo=GPRS_Get_Info();
for (i = ; i < ; i++) //15s 等待
{
delay_ms(); if (strstr(GSM_ReturnInfo, "OK")) // 命令发送成功
{
ret = ;
break;
}
else if (strstr(GSM_ReturnInfo, "CONNECT"))
{
ret = ;
break;
}
else if (strstr(GSM_ReturnInfo, "ERROR")) // 命令发送失败
{
ret = ;
break;
}
else
ret = ; delay_ms();
}
debug_print(GSM_ReturnInfo); // 打印调试信息 return ret;
}
3. 版本2--有模块化思想的驱动
大体流程和第一种差别不大,但是在几个关键点上有巨大改进,比如函数的模块化和中断的使用。
1. 发送和接收都用中断提高效率,不再使用查询方式。
2. 拼凑发送字符串处理和串口数据发送过程分开。
3. 初步的异常处理,如断线重连、重启等。
比上一版本进化很多,但是也有问题:
1. 模块已经划分,但是在逻辑层次上区分不明显,如下面例子中的发送的AT指令的响应处理,就和发送函数混在一起。
2.全局变量问题,比如记录GPRS模块当前状态标识,可以多处进行修改。
比较独立的功能做一定的提取,比如注册网络、SIM卡检查等功能函数封装起来。
uint8_t gprs_reg_network(void)
{
uint8_t ret, *uart_buf; ret = gprs_send_cmd("AT+CGREG?\r\n");
if (ret == ) //命令发送成功
{
uart_buf = get_gprs_rsp(); ret = ;
if (strstr(uart_buf, "+CGREG: 0,5")) // 已注册,本地网
ret = ;
if (strstr(uart_buf, "+CGREG: 1,5")) // 已注册,本地网
ret = ;
if (strstr(uart_buf, "+CGREG: 0,1")) // 已注册,漫游
ret = ;
if (strstr(uart_buf, "+CGREG: 1,1")) // 已注册,漫游
ret = ;
return ret;
}
else
return ;
}
或者接收响应的buffer不使用全局变量,而在发送函数参数中直接传入接收数组指针。
int gprs_check_sim(void)
{
int err = ;
int retry = 0;
char rsp_buf[]; do{
err = gprs_send_atcmd("AT+CPIN?\r\n",rsp_buf,"+CPIN:");
if(strstr(rsp_buf,"+CPIN: READY")== ){
GPRS_Status = IN_NO_SIM;
break ;
}if(retry++ > ){
err = E_TIMEOUT ;
break ;
}
}while(err != E_OK); return err ;
}
4. 版本3--按逻辑层次划分功能
分两个层次来实现需求,先是逻辑层次划分功能,然后在具体是实现层次按照功能单一原则编码。
该方法可以用在产品中,驱动代码的思路清晰,高效且易维护。
1. 使用RTOS来,提升CPU利用率,尤其是等待AT指令回复的过程中,系统可以执行其他任务。
2. GPRS操作的本质是写字符串(发AT指令),然后读回复的字符串(读指令响应),那么可以从这个角度来设计驱动。
3. 屏蔽硬件细节,在写GPRS驱动和逻辑处理的过程中,硬件读写都抽象成一个字符处理函数。
例1:查询SIM卡,发送AT指令,然后等待接收响应字符串。
函数接口就负责填充期待的字符串,如果指定时间内没等到字符串出现就认为出错,具体怎么发出去怎么收到回复都是更加底层的处理。
具体的响应由SIM800_WaitResponse函数来处理,该函数自动匹配指定字符串,参数500是超时时间,如果匹配成功会提前退出,否则等待500ms然后回复超时。
由于使用的Free RTOS,那么该函数不是阻塞性的,不会影响CPU执行其他的任务。如果模块很快响应了指令,那么还可以提前结束超时等待。
/*******************************************************************************
* Function Name : SIM800_Check_SIM
* Description : None
* Input : None
* Output : None
* Return : 1-OK, 0-NG
* Attention : None
*******************************************************************************/
uint8_t SIM800_Check_SIM(void)
{
uint8_t rtn = 0; SIM800_SendATCmd("AT+CPIN?\r\n");
if (SIM800_WaitResponse("+CPIN: READY", )){
rtn = 1;
}
return rtn;
}
例2:注册GSM网络,发送AT指令,然后等待接收响应字符串。
有些指令回复参数种类较多,如果写成上面的形式可能比较臃肿,可以把接收到的数据先放到buffer,然后从中搜索需要的字符。
SIM800_ReadResponse完成这个功能,但该函数要一直等待直至最大超时时间。
/*******************************************************************************
* Function Name : SIM800_GsmCheck
* Description : 检查是否注册到GSM网络
* Input : None
* Output : None
* Return : 1—OK, 0-NG
* Attention : None
*******************************************************************************/
uint8_t SIM800_GsmCheck(void)
{
uint8_t rtn = ; SIM800_SendATCmd("AT+CREG?\r\n");
SIM800_ReadResponse(gprs_rsp_buffer, sizeof(gprs_rsp_buffer), );
if (strstr(gprs_rsp_buffer, "+CREG: 0,1") != NULL)
{
rtn = ;
}
else if (strstr(gprs_rsp_buffer, "+CREG: 0,5") != NULL)
{
rtn = ;
}
else rtn = ; vTaskDelay();
return rtn;
}
例3:AT指令的发送,不需要等待硬件的响应,启动硬件发送标识即可,具体发送由中断或DMA去操作,代码更加高效。
剩下的发送和接收都是CPU硬件操作,在这一层次CPU并不识别数据的含义,仅是把数据从串口输出或读入。
/*******************************************************************************
* Function Name : SIM800_SendATCmd
* Description : None
* Input : _ucaBuf
* Output : None
* Return : None
* Attention : 向GSM模块发送AT命令。 命令需要自己加上<CR>字符
*******************************************************************************/
void SIM800_SendATCmd(const uint8_t *_ucaBuf)
{
SIM800_PrintRxData(_ucaBuf);
gprs_msg.msgPtr = (uint8_t *)(_ucaBuf);
gprs_msg.maxLen = strlen(_ucaBuf);
gprs_msg.length = ;
Gprs_UART_TX(ON);
} /*******************************************************************************
* Function Name : Gprs_UART_TX_Mode
* Description : Configure uart TX irq on/off
* Input : None
* Output : None
* Return : None
* Attention : 1=ON, 0=OFF.
*******************************************************************************/
void Gprs_UART_TX(uint8_t flag)
{
if(ON == flag)
{
UART_Tx_IRQ_Enable(TRUE);
}
else if(OFF == flag)
{
UART_Tx_IRQ_Enable(FALSE);
}
}
例4:如果模块已经连接到服务器,那么需要应用数据的发送。
常见的过程分3步:
1)应用数据预处理打包;
2)GPRS模块发送数据可能需要一个特殊的指令来启动,作用是告诉模块,下面发过来的是用户数据,不是控制字了;
3)启动数据包发送(实际是初始化发送过程逻辑控制相关的标志和启动硬件发送标志)。
下面驱动函数处理了用户数据的发送,在发送AT+CIPSEND后,2s内收到">" 回复就可以开始发送数据。
/*******************************************************************************
* Function Name : SIM800_Send
* Description : None
* Input : gprs_package
* Output : None
* Return : None
* Attention : None
*******************************************************************************/
uint8_t SIM800_Send(uint8_t *gprs_package)
{
uint8_t cmd[]="AT+CIPSEND\r\n"; //启动数据发送AT指令
uint8_t write_len;
uint8_t rtn = ; write_len = strlen(gprs_package);
if(write_len>){
DEBUG_PrintRxData("Data length error");
return ;
} SIM800_SendATCmd(cmd);
if (SIM800_WaitResponse(">", ) == ) // 启动发送应用数据失败
{
DEBUG_PrintRxData("CIPSEND failed");
return ;
} SIM800_SendPackage(gprs_package, write_len);// 开始接收收据
if (SIM800_WaitResponse("SEND OK", )){
DEBUG_PrintRxData("get SEND OK");
return ;
}
else{
DEBUG_PrintRxData("not get SEND OK");
return ;
}
} /*******************************************************************************
* Function Name : SIM800_SendPackage
* Description : None
* Input : 待发送的数据的指针和数据长度
* Output : None
* Return : None
* Attention : None
*******************************************************************************/
void SIM800_SendPackage(const uint8_t *_ucaBuf, uint8_t len)
{
SIM800_PrintRxData(_ucaBuf);
gprs_msg.msgPtr = (uint8_t *)(_ucaBuf);
gprs_msg.maxLen = len;
gprs_msg.length = ;
Gprs_UART_TX(ON);
}
实现高效的GPRS驱动程序的更多相关文章
- linux设备模型_转
建议原博文查看,效果更佳. 转自:http://www.cnblogs.com/wwang/category/269350.html Linux设备模型 (1) 随着计算机的周边外设越来越丰富,设备管 ...
- Linux设备模型 (1)
随着计算机的周边外设越来越丰富,设备管理已经成为现代操作系统的一项重要任务,这对于Linux来说也是同样的情况.每次Linux内核新版本的发布,都会伴随着一批设备驱动进入内核.在Linux内核里,驱动 ...
- HarmonyOS USB DDK助你轻松实现USB驱动开发
HDF(Hardware Driver Foundation)驱动框架是HarmonyOS硬件生态开放的基础,为开发者提供了驱动加载.驱动服务管理和驱动消息机制等驱动能力,让开发者能精准且高效地开发驱 ...
- PDA移动POS终端系统,实现专柜或店铺的收货、零售、盘点通过无线网络直接连接总部中央数据库,实现高效安全的移动供应链管理
利用PDA移动终端,实现专柜或店铺的收货.零售.盘点等一体化操作,通过无线网络直接连接总部中央数据库,实现高效安全的移动供应链管理. · PDA订货会应用解决方案利用PDA或电脑系统,在订货会现场直接 ...
- WDF模型驱动程序开发
WDF驱动程序开发 1. 引言 设备驱动程序是硬件设备连接到计算机系统的软件接口,任何设备都必须有相应的驱动程序才能在计算机系统上正常工作.设备驱动程序的优劣直接关系到整个系统的性能和稳定性,因此,设 ...
- linux驱动程序之电源管理之linux的电源管理架构(3)
设备电源管理 Copyright (c) 2010 Rafael J. Wysocki<rjw@sisk.pl>, Novell Inc. Copyright (c) 2010 Alan ...
- 利用WinDriver开发PCI设备驱动程序
摘要 WinDriver是Jungo公司出版的一个设备驱动程序开发组件,它可以大大加速PCI设备驱动程序的开发.作者在实际的项目中采用了WinDriver来开发设备驱动程序,取得了相当好的运行效果.从 ...
- 理解和使用NT驱动程序的执行上下文
理解Windows NT驱动程序最重要的概念之一就是驱动程序运行时所处的“执行上下文”.理解并小心地应用这个概念可以帮助你构建更快.更高效的驱动程序. NT标准内核模式驱动程序编程的一个重要观念是某个 ...
- GPRS优点介绍及GPRS上网相关知识(转)
源:http://blog.chinaunix.net/uid-20745340-id-1878732.html 单片机微控制器以其体积小.功耗低.使用方便等特点,广泛应用于各种工业.民用的嵌入式系统 ...
随机推荐
- .Net开源oss项目进度更新(含小程序接口)
和大家分享下当前OSS开源项目的进度情况: 一. OSS.Common [开源中国] [github] 经过昨天的努力,oss.common项目初步完成了对.net standard的支持,迁移过程本 ...
- Spring+SpringMVC+MyBatis+easyUI整合基础篇(五)讲一下maven
github地址,点这里. 项目效展示,点这里.账号:admin 密码:123456 下一篇文章开始,所有的项目源码都是与maven整合后的代码了,所以这一篇讲一讲maven. 1.简单介绍 我们看一 ...
- JSP +++SERVIET总复习
一. JSP基础概念 软件架构 B/S架构:Browser/Server,浏览器-服务器 最大的优点就是:一次部署,处处访问. C/S架构:Client/Server,客户端-服务器 功能.事件丰富, ...
- 使用js实现ajax的get请求步骤
(以下内容非原创,视频整合得来的) 1.创建XMLHttpRequest对象 2.浏览器与服务器建立连接 3.浏览器向服务器发送请求 4.服务器向浏览器响应请求 下面给出一个实例 1.创建一个test ...
- 在Ubuntu Linux下制作Windows 启动安装 USB盘
最近想 ,在Ubuntu上刻录个windows的安装U盘,在网上看了些资料,不过好多都说的很模糊,于是乎,我走了不少弯路.这里记录下来,希望了帮到大家. 首先你的有个USB吧,这里我们假定USB在ub ...
- [设计模式] Iterator - 迭代器模式:由一份奥利奥早餐联想到的设计模式
Iterator - 迭代器模式 目录 前言 回顾 UML 类图 代码分析 抽象的 UML 类图 思考 前言 这是一包奥利奥(数组),里面藏了很多块奥利奥饼干(数组中的元素),我将它们放在一个碟子上慢 ...
- js数组,字符串常用方法汇总(面试必备)
字符串: 1.concat() – 将两个或多个字符的文本组合起来,返回一个新的字符串. 2.indexOf() – 返回字符串中一个子串第一处出现的索引.如果没有匹配项,返回 -1 . 3.ch ...
- web第十天总结
今天进入web学习第十天,学习了xml序列化,servlet,其他的想不起来了,回顾以前的java基础面向对象,多态,继承.封装,但是还不是很理解.对于map,list,set更是理解不透,2011年 ...
- Webpack单元测试,e2e测试
此篇文章是续 webpack多入口文件.热更新等体验,主要说明单元测试与e2e测试的基本配置以及相关应用. 一.单元测试 实现单元测试框架的搭建.es6语法的应用.以及测试覆盖率的引入. 1. 需要安 ...
- 跑马灯、短信与反射EditText
1.1.跑马灯功能 Android自带支持跑马灯功能,实现此功能需要设置已下属性: android:ellipsize="marquee" // 必选,跑马灯样式 android: ...