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响应的回复无法区分对应哪个指令。
网络连接的驱动:
(gprs_start==) =uint8_t gprs_connect(uint8_t *= strBuf[, (gprs_mgr.stat == GPRS_GET_CSQ) (gprs_get_csq() > = (gprs_mgr.stat == GPRS_WAIT_REGNET) (!= (gprs_mgr.stat == GPRS_CONFIG_PARA) (!= (gprs_mgr.stat == GPRS_CONFIG_SOCKET) (!gprs_config_socket()) gprs_mgr.stat = (num=;num<;num++ (gprs_mgr.stat == GPRS_DATA_RW) =,len+=
发送函数,串口输出加上查询式解析:
//---------------------------------------------------------// 函数名称:uint8 gprs_send_cmd(char* pcmd)// 函数功能:gprs命令字发送函数// 输入参数: pcmd,要发送的命令// 返回参数:// 0 ,命令发送成功// 1 ,命令发送失败//---------------------------------------------------------uint8_t gprs_send_cmd(char* pcmd) { uint16_t i; uint8_t ret=0, *GSM_ReturnInfo; memset(GSM_info, 0, sizeof(GSM_info)); // 清除串口缓冲区 GSM_Info_CNT=0; // 清除串口接收计数 debug_print(pcmd); //发送的命令,调试输出 while(*pcmd) // 发送命令 { while(USART_GetFlagStatus(GPRS_USART, USART_FLAG_TXE)==0); USART_SendData(GPRS_USART, *pcmd++); } delay_ms(1000); GSM_ReturnInfo=GPRS_Get_Info(); for (i = 0; i < 15; i++) //15s 等待 { delay_ms(500); if (strstr(GSM_ReturnInfo, "OK")) // 命令发送成功 { ret = 0; break; } else if (strstr(GSM_ReturnInfo, "CONNECT")) { ret = 0; break; } else if (strstr(GSM_ReturnInfo, "ERROR")) // 命令发送失败 { ret = 1; break; } else ret = 1; delay_ms(500); } 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 == 0) //命令发送成功 { uart_buf = get_gprs_rsp(); ret = 1; if (strstr(uart_buf, "+CGREG: 0,5")) // 已注册,本地网 ret = 0; if (strstr(uart_buf, "+CGREG: 1,5")) // 已注册,本地网 ret = 0; if (strstr(uart_buf, "+CGREG: 0,1")) // 已注册,漫游 ret = 0; if (strstr(uart_buf, "+CGREG: 1,1")) // 已注册,漫游 ret = 0; return ret; } else return 1; }
或者接收响应的buffer不使用全局变量,而在发送函数参数中直接传入接收数组指针。
gprs_check_sim( err = rsp_buf[= gprs_send_atcmd(,rsp_buf,(strstr(rsp_buf,)== =(retry++ > =(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 *******************************************************************************/
(SIM800_WaitResponse(,
例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 = 0; SIM800_SendATCmd("AT+CREG?\r\n"); SIM800_ReadResponse(gprs_rsp_buffer, sizeof(gprs_rsp_buffer), 500); if (strstr(gprs_rsp_buffer, "+CREG: 0,1") != NULL) { rtn = 1; } else if (strstr(gprs_rsp_buffer, "+CREG: 0,5") != NULL) { rtn = 1; } else rtn = 0; vTaskDelay(200); return rtn; }
例3:AT指令的发送,不需要等待硬件的响应,启动硬件发送标识即可,具体发送由中断或DMA去操作,代码更加高效。
剩下的发送和接收都是CPU硬件操作,在这一层次CPU并不识别数据的含义,仅是把数据从串口输出或读入。
SIM800_SendATCmd( uint8_t *= (uint8_t *==
例4:如果模块已经连接到服务器,那么需要应用数据的发送。
常见的过程分3步:
1)应用数据预处理打包;
2)GPRS模块发送数据可能需要一个特殊的指令来启动,作用是告诉模块,下面发过来的是用户数据,不是控制字了;
3)启动数据包发送(实际是初始化发送过程逻辑控制相关的标志和启动硬件发送标志)。
下面驱动函数处理了用户数据的发送,在发送AT+CIPSEND后,2s内收到">" 回复就可以开始发送数据。
http://www.cnblogs.com/pingwen/p/6681955.html