• 实现高效的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 "); 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 ",len+4); gprs_send_cmd(strBuf); gprs_write_data(ascData); delay_ms(1000); printf("GPRS_DATA_RW;"); gprs_send_cmd("AT+CIPCLOSE "); 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?
    ");
        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? ",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? "); 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?
    ");
        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
    "; //启动数据发送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);
    }
  • 相关阅读:
    java身份证号码校验、邮箱校验、手机号码/电话号码校验
    垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回
    redis缓存机制和底层实现
    java自然语言StanfordCoreNLP入门
    java生成汉字集
    maven打包 invalid entry size Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:1.5.14.RELEASE:repackage (default) on project
    maven解决大项目打包慢的问题
    visualVM远程监控jetty
    jetty配置远程debug
    图数据库ubentu环境neo4j安装
  • 原文地址:https://www.cnblogs.com/pingwen/p/6681955.html
Copyright © 2020-2023  润新知