基于μCOSiii的AT指令代码和使用方法详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hnxyxiaomeng/article/details/85045520

一、主要思路

项目中需要用到AT指令与模块通信,前期写了个不带操作系统的AT指令代码模块。现在需要用μCOSiii操作系统,为了提高代码健壮性,对代码进行了重构。

在网上看到RT-Thread中实现了AT组件(https://www.rt-thread.org/document/site/submodules/rtthread-manual-doc/zh/1chapters/14-chapter_at/https://www.rt-thread.org/document/site/rtthread-application-note/components/at/an0014-rtthread-system-at-client/ ),但该组件和RT-Thread系统内核耦合性太强,无法直接移植到其它操作系统。

于是仔细研读该代码,再加上个人理解,实现了基于μCOSiii的AT指令代码,与MCU和模块的耦合性极低,可用于绝大多数AT模块。

下载链接: https://pan.baidu.com/s/1veWZgQxfm8P-iUp11zAo1g    提取码: sfub 

二、类型定义

        1.  发送字符数组函数指针、接收单个字符函数指针

定义这两个函数指针类型是为了让用户在初始化AT时,能直接注册发送和接收函数。

定义如下:

typedef void (*SendDataFunc_t)(const char* buf, uint32_t len);

typedef uint8_t (*RecvByteFunc_t)(char* p_data);



static SendDataFunc_t AT_SendDataCallback = NULL;

static RecvCharFunc_t AT_RecvByteCallback = NULL;

        2. 接收缓冲区结构体

此结构体暂存每次接收到的一行字符串。

typedef struct

{

    uint16_t len;

    char buf[AT_BUF_SIZE];

}stcRecvBuf;



static stcRecvBuf recvLineBuf;

static stcRecvBuf* pRecvLineBuf = &recvLineBuf;

        3. URC结构体

URC(Unsolicited Result Code),即"非请求结果码"。一般的AT命令流程都是控制端发出命令,被控端响应结果码。但当被控端有事件需要通知控制端时,就会主动发出URC,例如有呼叫打入、收到新短信息、自动关机等。

比如移远某物联网模块,在MQTT应用中,可能会受到+QMTSTAT和+QMTRECV两种URC,分别代表网络状态变化和接收到MQTT消息。

此结构体用于配置每个URC,定义如下:

typedef struct

{

    const char *cmd_prefix; //URC开始字符

    const char *cmd_suffix; //URC结束字符

    void (*handler)(const char *data, uint16_t size);//接收到URC后的处理函数

}stcATUrc;

        4.  AT上下文结构体

此结构体用于存储AT指令运行的上下文环境,定义如下:

typedef struct

{

    char            resp_buf[AT_BUF_SIZE];//响应缓冲区

    uint8_t         resp_line_cnt;//响应的行数

    AT_RET          resp_status;//响应状态

    OS_SEM          resp_sem;//响应信号量,收到期望接收的字符串时发送

    OS_MUTEX        resp_mux;//AT互斥信号量

    const stcATUrc* urc_table;//urc表格

    uint8_t         urc_table_size;//urc表格大小

}stcATContext;



static stcATContext atContext;

static stcATContext *pATContext = &atContext;

这里响应缓冲区和第2小节接收缓冲区的区别是:后者每接到一行响应字符串后,就将字符串添加到响应缓冲区中,直到接收到期望的字符串。

        5.  AT配置结构体

此结构体用于配置每个AT指令的参数,在每次执行AT指令前均需配置,定义如下:

typedef struct

{

    const char*     resp_str;     //期望收到的字符串

    uint8_t         resp_line_num;//期望收到的行数

    uint16_t        resp_timeout100ms;//发送后查询返回信息的延时,100ms为单位

    uint8_t         max_try_times; //最大重试次数

    uint8_t         max_reset_times; //最大重启次数

} stcATConfig;



static stcATConfig atConfig;

static stcATConfig *pATConfig = &atConfig;

其中,resp_timeout100ms可配置为AT指令的最大响应时间。

凡是读写pATConfig和pATContext的代码,均需用pATContext->resp_mux保护起来。

三、全局函数

        1.  AT指令运行环境初始化函数

此函数用于初始化AT指令的运行环境,串口发送函数、串口接收函数和URC表格均需调用者在外部按照上面说的结构体的格式定义好,然后调用此函数传递到AT指令组件中。

/*************************************************************

* 初始化AT指令运行环境,并开启AT指令处理任务

* @param send_func:串口发送函数

* @param recv_func:串口接收函数

* @param urc_table:模块URC表格

* @param urc_table_size:模块URC表格尺寸

*

* @return 无

**************************************************************/

void AT_InitEnv(SendDataFunc_t send_func,RecvCharFunc_t recv_func,stcATUrc* urc_table,uint8_t urc_table_size);

        2.  AT指令配置函数

此函数用于配置AT指令的参数,即结构体pATConfig,函数输入参数和结构体内容一致。暂存每次接收到的一行字符串。

/*************************************************************

* 初始化AT指令配置

* @param resp_str:期望接收到的字符串。为NULL时,忽略此配置。不为NULL时,接收到这里配置的字符串就返回OK(line_num配置仍然有效)。

* @param line_num:期望接收到的响应行数。>0时接收到相应行数时证明AT指令执行正常;==0时仅判断是否接收到OK、ERROR、FAIL等。

* @param timeout100ms:AT指令的响应超时,单位:100ms

* @param max_try_times:指令最大重试次数

* @param max_reset_times:指令重试次数达到后,最大重启次数

*

* @return 无

**************************************************************/

void AT_InitConfig(const char* resp_str,uint8_t line_num,uint16_t timeout100ms,uint8_t max_try_times,uint8_t max_reset_times);

通过此函数配置AT指令后,返回AT指令执行结果的逻辑是:

  1. 若resp_str==NULL,则忽略resp_str的配置。仅根据line_num判断:

若line_num==0,则仅判断接收字符串中是否为”OK”、 ”FAIL”,或包含”ERROR”,当接收到”OK”时,返回OK,否则返回ERROR。

若line_num!=0,则当接收到line_num行响应时返回OK,判断函数时以”\r\n”为分隔符。

  1. 若resp_str!=NULL,则在进行上述第(1)种判断的同时,还判断接收到的字符串中是否含有resp_str。若接收到resp_str,则直接返回OK,不再进行后续判断。若未接收到resp_str,则仍然执行第(1)种判断。比如用移远某物联网模块发送MQTT消息时,发送完AT+QMTPUBEX指令后,收到”> ”就可以发送消息体了,这个响应和常规的带”\r\n”的不一样。
  2. 若达到预设时间timeout100ms后,上述两种判断均未接收到响应(只要能返回OK或ERROR都是接收到响应,而不仅仅是指返回OK),将重复执行max_try_times次,若仍失败,将重启max_reset_times次,若仍失败,则返回time_out。

        3.  AT指令执行函数

此函数用于执行每个AT指令,需要调用者自己带上”\r\n”。

/*************************************************************

* 执行AT指令

*

* @param send_str:指令字符串,需要调用者自己带上"\r\n"

*

* @return 执行结果,见宏定义

**************************************************************/

AT_RET AT_ExecCmd(const char *send_str);

        4.  通过AT指令发送字符数组函数

此函数用于通过AT指令接口发送字符数组,在向模块发送数据时可能会用到。因数组中可能包含0(字符串的结束符),所以无法跟用函数AT_ExecCmd直接发送。

/*************************************************************

* 通过AT指令口发送字符数组

*

* @param send_buf:发送的数组

* @param buf_len:数组长度

*

* @return 执行结果,见宏定义

**************************************************************/

AT_RET AT_SendData(const char *send_buf,uint16_t buf_len);

        5.  AT指令响应字符串格式化输出函数

此函数用于从AT响应缓冲区中读取指定行,并格式化输出。格式化的语法同sscanf函数。

/*************************************************************

* 通过AT指令口发送字符数组

*

* @param send_buf:发送的数组

* @param buf_len:数组长度

*

* @return 执行结果,见宏定义

**************************************************************/

AT_RET AT_SendData(const char *send_buf,uint16_t buf_len);

 

四、例子

以移远某物联网模块为例,使用步骤如下:

        1.  定义串口收发函数和URC表格

static void UART_SendData(uint8_t* Buff,uint32_t length)

{

……

}



static void UART_RecvByte(uint8_t* p_data)

{

……

}



static void IOT_RecvMsgHandler(const char *data, uint16_t size)

{

    ……

    return;

}



static stcATUrc urcTable[] = {

    {"+QMTRECV:",        "\r\n",    IOT_RecvMsgHandler},

};

      2.  初始化AT运行环境

AT_InitEnv(UART_SendData, UART_RecvByte,urcTable,1);

      3.  愉快地执行AT命令

比如,执行ATI命令,查看AT通信是否执行正常:

static IOT_RET IOT_TestATI(void)

{

    IOT_RET _ret=IOT_OK;

    AT_InitConfig(NULL,0,3,10,0);//responst time:300ms



    _ret = AT_ExecCmd("ATI\r\n");

    if(_ret!=AT_RESP_OK)

    {

        _ret = IOT_ERR_ATI;

    }

    return _ret;

}

 

再比如,执行CSQ命令,查询网络信号质量:

static IOT_RET IOT_QuerySigQuality(void)

{

    IOT_RET _ret=IOT_OK;

    int16_t _retVal1 = 0;

    int16_t _retVal2 = 0;

    char _tmpStr[100];



    AT_InitConfig(NULL,4,3,10,0);//response time:300ms

    _ret = AT_ExecCmd("AT+CSQ\r\n");

    if(_ret==AT_RESP_OK)

    {

        AT_ParseRespLineArgs(2,"%[^:]:%d,%d",_tmpStr,&_retVal1,&_retVal2);



        if(_retVal1>0 & _retVal1<=31)

        {

           _ret = IOT_OK;

        }

        else

        {

           _ret = IOT_ERR_CSQ_BAD;

        }

    }

    else

    {

        _ret = IOT_ERR_CSQ_NACK;

    }



    return _ret;

}

 

再再比如,执行QMTPUBEX命令发布MQTT消息:

AT_InitConfig("> ",2,3,10,0);//response time:300ms

_ret = AT_ExecCmd(PUB_CMD_INS);

if( _ret== AT_RESP_OK )

{

char ch1,ch2;

        AT_ParseRespLineArgs(2,"%c%*s",&ch1);

        if(ch1 == '>')

        {

           AT_SendData((const char*)(_dataArray),_dataLen);

        }

        ……

}

猜你喜欢

转载自blog.csdn.net/hnxyxiaomeng/article/details/85045520