一、主要思路
项目中需要用到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指令执行结果的逻辑是:
- 若resp_str==NULL,则忽略resp_str的配置。仅根据line_num判断:
若line_num==0,则仅判断接收字符串中是否为”OK”、 ”FAIL”,或包含”ERROR”,当接收到”OK”时,返回OK,否则返回ERROR。
若line_num!=0,则当接收到line_num行响应时返回OK,判断函数时以”\r\n”为分隔符。
- 若resp_str!=NULL,则在进行上述第(1)种判断的同时,还判断接收到的字符串中是否含有resp_str。若接收到resp_str,则直接返回OK,不再进行后续判断。若未接收到resp_str,则仍然执行第(1)种判断。比如用移远某物联网模块发送MQTT消息时,发送完AT+QMTPUBEX指令后,收到”> ”就可以发送消息体了,这个响应和常规的带”\r\n”的不一样。
- 若达到预设时间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);
}
……
}