实现高效的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==0)       //未连接
{
    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=0;
    char strBuf[20];
    memset(strBuf,0x00,sizeof(strBuf));

    if (gprs_mgr.stat == GPRS_GET_CSQ)                // 查询信号强度
    {
        if (gprs_get_csq() > 15)
            gprs_mgr.stat = GPRS_WAIT_REGNET;
            delay_ms(1000);
            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(2000);
            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(1000);
            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=0;num<5;num++)
            {
                delay_ms(1000);
            }
        }
        delay_ms(1000);
        printf("gprs_mgr.stat = GPRS_CONFIG_SOCKET;");

    }
    else if (gprs_mgr.stat == GPRS_DATA_RW)            // 数据读写
    {
        GPRS_Connect=1;
        sprintf(strBuf,"AT+CIPSEND=%d\r\n",len+4);
        gprs_send_cmd(strBuf);
        gprs_write_data(ascData);
        delay_ms(1000);
        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=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不使用全局变量,而在发送函数参数中直接传入接收数组指针。

int gprs_check_sim(void)
{
    int err = 0;    int retry = 0;
    char rsp_buf[100];

    do{
        err = gprs_send_atcmd("AT+CPIN?\r\n",rsp_buf,"+CPIN:");
        if(strstr(rsp_buf,"+CPIN: READY")== 0){
            GPRS_Status = IN_NO_SIM;
            break ;
        }if(retry++ > 2){
            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", 500)){
     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 = 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并不识别数据的含义,仅是把数据从串口输出或读入。

/*******************************************************************************
* 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 = 0;
    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[20]="AT+CIPSEND\r\n"; //启动数据发送AT指令
    uint8_t write_len;
    uint8_t rtn = 0;

    write_len  = strlen(gprs_package);
    if(write_len>1500){
        DEBUG_PrintRxData("Data length error");
        return 0;
    }

    SIM800_SendATCmd(cmd);
    if (SIM800_WaitResponse(">", 2000) == 0) // 启动发送应用数据失败
    {
        DEBUG_PrintRxData("CIPSEND failed");
        return 0;
    }

    SIM800_SendPackage(gprs_package, write_len);// 开始接收收据
    if (SIM800_WaitResponse("SEND OK", 5000)){
        DEBUG_PrintRxData("get SEND OK");
        return 1;
    }
    else{
        DEBUG_PrintRxData("not get SEND OK");
        return 0;
    }
}

/*******************************************************************************
* 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 = 0;
    Gprs_UART_TX(ON);
}
时间: 2024-11-02 02:20:43

实现高效的GPRS驱动程序的相关文章

转 Linux设备模型 (1)

作者:wwang 出处:http://www.cnblogs.com/wwang 本文采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接. 随着计算机的周边外设越来越丰富,设备管理已经成为现代操作系统的一项重要任务,这对于Linux来说也是同样的情况.每次Linux内核新版本的发布,都会伴随着一批设备驱动进入内核.在Linux内核里,驱动程序的代码量占有了相当大的比重.下图是我在网络上搜索到的

Linux 设备模型基本概念

1.设备模型引入 Linux 2.6内核最初为了应付电源管理的需要,提出了一个设备模型来管理所有的设备.在物理上,外设之间是有一种层次关系的,比如把一个U盘插到笔记本上,实际上这个U盘是接在一个USB Hub上,USB Hub又是接在USB 2.0 Host Controller (EHCI)上,最终EHCI又是一个挂在PCI Bus上的设备.这里的一个层次关系是:PCI->EHCI->USB Hub->USB Disk.如果操作系统要进入休眠状态,首先要逐层通知所有的外设进入休眠模式,

STM32F4XX高效驱动篇2 I2C

说到I2C很多用过STMF10X硬件I2C方式的工程师,都感觉有点头痛.大部分还是使用软件模拟的方式,I2C由于一般的工作频率是400,100KHz.所以在平凡读取,或所读数据量大时,使用这模拟的方式,还是比较浪费CPU有效工作时间的. 在之前的使用I2C的经历中,主要是I2C死锁问题让我也困扰了一段时间.不过后来经过多方资料,最后还是把这个问题解决了.以下驱动程序已集成了此功能. 什么是死锁,在I2C主设备进行读写操作的过程中.主设备在开始信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为

STM32F4XX高效驱动篇1-UART

之前一直在做驱动方面的整理工作,对驱动的高效性有一些自己的理解这里和大家分享一下.并奉驱动程序,本程序覆盖uart1-8. 串口驱动,这是在每个单片机中可以说是必备接口.可以说大部分产品中都会使用,更有甚者一个产品中用到8个串口.这样一个高效的驱动是决定您产品优劣的关键因素.本文主要针对STM32F4XX系列芯片做的一个驱动接口层.以减少您在开发项目时驱动方面所花费时间,以及为程序达到高效的处理为目的. 从51,pic到现在的STM32,个人感觉STM32这方面做的非常突出,丰富的使用模式,强大

android 网络连接 wifi gprs的连接

package com.example.androidday15_network1; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo.State; import android.os.Bundle; import andr

linux驱动程序之电源管理之linux的电源管理架构(3)

设备电源管理 Copyright (c) 2010 Rafael J. Wysocki<[email protected]>, Novell Inc. Copyright (c) 2010 Alan Stern[email protected] ************************************************************* 本文由DroidPhone翻译于2011.8.5 ***************************************

3g上网卡的驱动程序的自动更新web端架构文档

几年前写的. 看是否有人用得上吧 1   简介 本文档详细描述了基于ASP.NET平台和IIS服务的T-Mobile自动更新系统的实现框架. 本文档主要从技术架构和业务架构两个方面来着手来描述T-Mobile自动更新系统的架构,以使相关人员快速了解产品的架构. 1.1   目的 本文档将从架构方面对T-Mobile自动更新系统进行综合概述,其中会使用用例视图.逻辑视图.部署视图.实施视图等多种不同的架构视图来描述系统的各个方面. 这些描述用于记录并表述已对系统的架构方面做出的重要决策,同时确定系

数据库连接池的高效性之时间提升

数据库连接池的高效性 测试数据库直接打开与使用连接池打开时间长短,连接1000次,看各自需要的时间. 结果图 1.直接打开花费时间(s):73332.连接池打开花费时间(s):693.速度提升倍数:106 一.主函数 package ch6.sql; import java.sql.*; public class Test_ConnectionPool_Time { public static void main(String[] args) { long t1=0,len1=0,len2=0;

GPRS优点介绍及GPRS上网相关知识(转)

源:http://blog.chinaunix.net/uid-20745340-id-1878732.html 单片机微控制器以其体积小.功耗低.使用方便等特点,广泛应用于各种工业.民用的嵌入式系统中:而随着互联网(Internet)的兴起与普及,使微控制器通过互联网传送数据就变得非常有意义.目前使微控制器上网的解决方案一般有两种:一种是采用微控制器驱动网卡,通过以太网连接Internet:另一种是使微控制器直接驱动调制解调器(MODEM)通过电话线向ISP拨号上网.这两种方案的缺点在于都要使