1 导入系统
- AT 组件中 AT Client 功能占用资源体积为 4.6K ROM 和 2.0K RAM;
- AT Server 功能占用资源体积为 4.0K ROM 和 2.5K RAM;
- AT CLI 功能占用资源体积为 1.5K ROM 几乎没有使用 RAM
组件代码位置 : rt-thread/components/net/at/
1.1 server导入
可以使用ENV导入,也可以通过在rtconfig.h中添加宏定义来使用。
如果使用ENV:
RT-Thread Components --->
Network --->
AT commands --->
[*] Enable AT commands
[*] Enable debug log output
[*] Enable AT commands server
(uart3) Server device name
(256) The maximum length of server data accepted
The commands new line sign (\r\n) --->
[ ] Enable AT commands client
[*] Enable command-line interface for AT commands
[ ] Enable print RAW format AT command communication data
如果添加宏定义:
//rtconfig.h
#define RT_USING_AT //开启关闭组件
#define AT_USING_SERVER //选择模式
#define AT_SERVER_DEVICE "uart3"
#define AT_SERVER_RECV_BUFF_LEN 256
#define AT_CMD_END_MARK_CRLF //"\r\n"
#define AT_USING_CLI //用于开启或关闭服务器命令行交互模式
如果使用server模式,则需要在初始化任务中调用
int at_server_init(void);
但如果被组件自动初始化则就不需要额外执行了,在at_server.c中548行,默认进行了自动初始化。
1.2 client导入
rtconfig.h中添加配置
#define RT_USING_AT//用于开启或关闭 AT 组件
#define AT_USING_CLIENT//模式
#define AT_CLIENT_NUM_MAX 1//最大同时支持的 AT 客户端数量
#define AT_USING_SOCKET//用于 AT 客户端支持标准 BSD Socket API,开启 AT Socket 功能。
#define AT_USING_CLI// 用于开启或关闭客户端命令行交互模式。
管理工具 ENV 配置:
RT-Thread Components --->
Network --->
AT commands --->
[*] Enable AT commands
[ ] Enable debug log output
[ ] Enable AT commands server
[*] Enable AT commands client
(uart2) Client device name
(512) The maximum length of client data accepted
[*] Enable command-line interface for AT commands
[ ] Enable print RAW format AT command communication data
如果没有被自动启动则需要执行下列函数
int at_client_init(const char *dev_name, rt_size_t recv_bufsz);
2 AT server使用
server中有一些默认命令
- AT:AT 测试命令;
- ATZ:设备恢复出厂设置;
- AT+RST:设备重启;
- ATE:ATE1 开启回显,ATE0 关闭回显;
- AT&L:列出全部命令列表;
- AT+UART:设置串口设置信息。
重启和回复出厂设置的命令,组件使用弱函数进行实现,如果需要自定义功能,则直接在外部重写函数即可
复位: AT+RST
void at_port_reset(void);
回复出厂值 :ATZ
void at_port_factory_reset(void);
2.1 添加命令
每个AT命令分为四个功能:
功能 | 写法 | 说明 |
---|---|---|
测试功能 | AT+ < x > =? | 用于查询参数格式及取值范围 |
查询功能 | AT+< x > ? | 用于返回命令参数值 |
设置功能 | A+ < x >=... | 用于用户自定义参数值 |
执行功能 | AT+ < x > | 用于执行相关操作 |
AT_CMD_EXPORT(AT命令名称,参数表达式,测试函数,查询函数,设置函数,执行函数)
AT_CMD_EXPORT("AT+TEST", "=<value1>,[value2]", at_test_t, at_test_find, at_test_set, at_test_do);
其中<>表示必填参数,[]表示可选参数,在传入参数时,如果小于了必填参数,则命令被解析失败。其中执行函数的类型均为静态at_result_t。
示例:
//需要引入头文件"at.h"
int value1,value2;
//测试函数
static at_result_t at_test_t(void)
{
at_server_printfln("test");
return 0;
}
static at_result_t at_test_find(void)
{
at_server_printfln("value1 = %d",value1);
return 0;
}
static at_result_t at_test_set(const char *args)
{
int argc;
at_server_printfln("%s",args);
argc = at_req_parse_args(args, "=%d,%d", &value1, &value2);
//判断接收到了几个参数 将参数分解为两个int
if ( argc == 2)
{
at_server_printfln("value1 : %d, value2 : %d", value1, value2);
at_server_print_result(AT_RESULT_OK);
}
else
{
at_server_printfln("argc %d",argc);
at_server_print_result(AT_RESULT_PARSE_FAILE);
}
return 0;
}
static at_result_t at_test_do(void)
{
at_server_printfln("doing");
at_server_print_result(AT_RESULT_CHECK_FAILE);
return 0;
}
AT_CMD_EXPORT("AT+TEST", "=<value1>,[value2]", at_test_t, at_test_find, at_test_set, at_test_do);
3 AT server API接口
3.1 通过AT接口打印
void at_server_printf(const char *format, ...);
3.2 通过AT接口换行打印
void at_server_printfln(const char *format, ...);
3.3 打印执行结果:
void at_server_print_result(at_result_t result);
结果有6种,也是通过AT结果打印结果,主要用于在自定义AT命令时进行反馈
命令执行结果 | 说明 |
---|---|
AT_RESULT_OK | 命令执行成功 |
AT_RESULT_FAILE | 命令执行失败 |
AT_RESULT_NULL | 命令无返回结果 |
AT_RESULT_CMD_ERR | 输入命令错误 |
AT_RESULT_CHECK_FAILE | 参数表达式匹配错误 |
AT_RESULT_PARSE_FAILE | 参数解析错误 |
例如:
执行:
at_server_print_result(AT_RESULT_CHECK_FAILE);
输出:
ERR CHECK ARGS FORMAT FAILED!
ERROR
3.4 解析输入参数
int at_req_parse_args(const char *req_args, const char *req_expr, ...);
一个 AT 命令的四种功能函数中,只有设置函数有入参,该入参为去除 AT 命令剩余部分,例如一个命令输入为 "AT+TEST=1,2,3,4",则设置函数的入参为参数字符串 "=1,2,3,4" 部分
例如
int value1,value2;
static at_result_t at_test_set(const char *args)
{
int argc;
at_server_printfln("%s",args);
argc = at_req_parse_args(args, "=%d,%d", &value1, &value2);
//判断接收到了几个参数 将参数分解为两个int
if ( argc == 2)
{
at_server_printfln("value1 : %d, value2 : %d", value1, value2);
at_server_print_result(AT_RESULT_OK);
}
else
{
at_server_printfln("argc %d",argc);
at_server_print_result(AT_RESULT_PARSE_FAILE);
}
return 0;
}
4 AT client API
4.1 创建响应结构体
at_response_t at_create_resp(rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout);
参数 | 描述 |
---|---|
buf_size | 本次响应最大支持的接收数据的长度 |
line_num | 本次响应需要返回数据的行数,行数是以标准结束符划分 若为 0 ,则接收到 "OK" 或 "ERROR" 数据后结束本次响应接收 若大于 0,接收完当前设置行号的数据后返回成功 |
timeout | 本次响应数据最大响应时间,数据接收超时返回错误 |
返回 | |
!=NULL | 返回结构体指针 |
=NULL | 失败,内存不足 |
每次数据发送需要绑定一个响应结构体,用于解析应答信息,该函数就是生产这个结构体的。
4.2 删除响应结构体
因为创建了响应结构体是需要消耗内存空间的,如果不需要了可以删除
void at_delete_resp(at_response_t resp);
4.3 设置响应结构体参数
at_response_t at_resp_set_info(at_response_t resp, rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout);
4.4 发送命令并接收响应
rt_err_t at_exec_cmd(at_response_t resp, const char *cmd_expr, ...);
返回 | |
---|---|
>=0 | 成功 |
-1 | 失败 |
-2 | 失败,接收回应超时 |
如下使用,其中resp是提前创建的响应结构体,ATE0便是要发送的AT命令,当resp为NULL时表示该次传输不需要等待响应,直接返回结果
at_exec_cmd(resp, "ATE0");
4.5 获取指定行号的响应数据
当响应结构体中保存了多行数据时使用,返回为空则说明行号是错误的
const char *at_resp_get_line(at_response_t resp, rt_size_t resp_line);
4.6 通过关键字得到某行数据
返回为空则表示未找到该关键字
const char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword);
4.7 解析指定行数据
int at_resp_parse_line_args(at_response_t resp, rt_size_t resp_line, const char *resp_expr, ...);
解析[UART=115200,8,1,0,0\r\n] 的应答,使用方式如下
at_resp_parse_line_args(resp, 1,"%*[^=]=%d,%d,%d,%d,%d", &baudrate, &databits, &stopbits, &parity, &control);
4.8 解析关键字行的数据
int at_resp_parse_line_args_by_kw(at_response_t resp, const char *keyword, const char *resp_expr, ...);
5 AT client使用
5.1 调试
在msh窗口输入at client可以直接对AT对应的串口进行操作,退出如果无法键入ESC可以通过16进制发送0X1B
在rtconfig.h中添加AT_DEBUG宏定义能够帮助输出LOG_D等级的调试信息,添加AT_PRINT_RAW_CMD能够看到输入的16进制数据
5.2 添加一条AT命令
//读取AT的串口信息
#define LOG_TAG "at.test"
#include <at_log.h>
//获取串口信息
int at_radio_find_uart_config(AtUartCfg_t *UartCfg)
{
at_response_t resp = RT_NULL;
//建立一个能接收64字节大小响应,当5s未接收到响应则超时
resp = at_create_resp(64, 0, rt_tick_from_millisecond(5000));
if(resp ==RT_NULL)
return 1;
if (at_exec_cmd(resp, "AT+UART?") <0) //发送命令等待响应
return 2;
//解析响应 %*[^=]=表示忽律到=号为止,然后在忽律=号
if( at_resp_parse_line_args(resp, 1,"%*[^=]=%d,%d,%d,%d,%d", \
&(UartCfg->baudrate), &(UartCfg->databits), &(UartCfg->stopbits), &(UartCfg->parity), &(UartCfg->control) ) ==RT_NULL)
return 3;
LOG_D("baudrate=%d, databits=%d, stopbits=%d, parity=%d, control=%d",\
UartCfg->baudrate, UartCfg->databits, UartCfg->stopbits, UartCfg->parity, UartCfg->control);
at_delete_resp(resp);
return 0;
}
使用
at_client_init("uart2",512) ;
AtUartCfg_t UartCfg;
at_radio_find_uart_config(&UartCfg);
6 AT server代码
在测试STM32的stop模式唤醒后发现AT出问题了,只有回显,没有判断处理,于是来分析下代码
首先由int at_server_init(void)函数启动AT server,一进来就判断了at_server_local是否为空,该指针是指向了一个at_server结构体,其实就是判断是否已经初始化过了,是就直接退出,不在二次初始化。at_server结构体如下,名称一目了然
struct at_server
{
rt_device_t device;
at_status_t status;
char (*get_char)(void);
rt_bool_t echo_mode;
char recv_buffer[AT_SERVER_RECV_BUFF_LEN];
rt_size_t cur_recv_len;
rt_sem_t rx_notice;
char end_mark[AT_END_MARK_LEN];
rt_thread_t parser;
void (*parser_entry)(struct at_server *server);
};
下面的一堆操作大概意思就是建立用于存放at命令的表。再然后就是动态给at_server的结构体动态分配内存。
默认打开回显模式
at_server_local->echo_mode = 1;
AT状态置为未完成初始化
at_server_local->status = AT_STATUS_UNINITIALIZED;
初始化接收buffer
memset(at_server_local->recv_buffer, 0x00, AT_SERVER_RECV_BUFF_LEN);
at_server_local->cur_recv_len = 0;
初始化接收的信号量
at_server_local->rx_notice = rt_sem_create("at_svr", 0, RT_IPC_FLAG_FIFO);
if (!at_server_local->rx_notice)
{
LOG_E("AT server session initialize failed! at_rx_notice semaphore create failed!");
result = -RT_ENOMEM;
goto __exit;
}
打开串口设备,在rt_config.h中定义的串口名称宏AT_SERVER_DEVICE是在这里使用的。
at_server_local->device = rt_device_find(AT_SERVER_DEVICE);
然后是选择串口是用DMA还是中断方式,这里写法是直接先尝试DMA,不行在使用中断。
/* using DMA mode first */
open_result = rt_device_open(at_server_local->device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_DMA_RX);
/* using interrupt mode when DMA mode not supported */
if (open_result == -RT_EIO)
{
open_result = rt_device_open(at_server_local->device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
}
绑定读函数,该函数实际就是调用设定的串口一直等待数据到来,然后重置rx_notice信号量,在获取一个信号量
at_server_local->get_char = at_server_gerchar;
static char at_server_gerchar(void)
{
char ch;
while (rt_device_read(at_server_local->device, 0, &ch, 1) == 0)
{
rt_sem_control(at_server_local->rx_notice, RT_IPC_CMD_RESET, RT_NULL);
rt_sem_take(at_server_local->rx_notice, RT_WAITING_FOREVER);
}
return ch;
}
设置结束标志位,也就是AT命令是以什么来作为结束符的,这里使用的是"\r\n"
memcpy(at_server_local->end_mark, AT_CMD_END_MARK, sizeof(AT_CMD_END_MARK));
之后开启了一个线程,线程入口是server_parser,之后再从server_parser函数开始继续分析。
static void server_parser(at_server_t server)
进入后是一个串口不断读取的while循环,只要当接收到0X1B退出循环,也就是AT通信的串口如果接收到0X1B后,AT组件就相当于停止数据接收了,且是不可逆的,这是得注意一下的。
每次循环都是在接收到一个字符后,第一个判断是回显,默认是打开的,那么就会首先判断得到字符是否是/r,或者/n,但/n上一个字符不是/r,如果是则通过AT串口输出/r/n
if (ch == AT_CMD_CR || (ch == AT_CMD_LF && last_ch != AT_CMD_CR))
{
at_server_printf("%c%c", AT_CMD_CR, AT_CMD_LF);
}
然后判断输入字符是不是删除,如果是则将之前保存在recv_buffer中的最后一个字符删掉
···
else if (ch == BACKSPACE_KEY || ch == DELECT_KEY)
{
if (server->cur_recv_len)
{
server->recv_buffer[--server->cur_recv_len] = 0;
at_server_printf("\b \b");
}
continue;
}
···
以上均不是,则直接原样显示,注意的是这里的回显是来一个输出一个,不是接收完成后一下输出的
else
{
at_server_printf("%c", ch);
}
之后将数据保存到buffer中,并且保存一下当前的字符,用于上面if中的判断
server->recv_buffer[server->cur_recv_len++] = ch;
last_ch = ch;
每次保存一个字符就查找一下buffer中有无结束符,没有就跳入下一次接收
if(!strstr(server->recv_buffer, server->end_mark))
{
continue;
}
下面的代码是在收到结束符才会被执行的。就是根据命令来找到对应函数,就不在描述了,接下里就是处理本来的目的,找到为啥在休眠唤醒后AT无法工作了。
由于休眠时无法调试的,只能通过打印来判断。我在如图设置了两个打印。if里面之所以没有使用LOG,因为这里使用打印影响了唤醒 - -|,只能用AT的串口打印了,这个问题之后在解决。
首先在休眠前使用AT测试,AT串口接收到如下,最后一个/n未显示是因为已经在buffer找到结束符,没有进if,然后观察打印串口输出了lalalala,测试正常
ch=41 size: 1 end=0D,0A
ch=54 size: 2 end=0D,0A
ch=0D size: 3 end=0D,0A
OK
使用msh进入stop模式,然后通过打印串口顺便发送,触发下降沿唤醒设备。
[16:35:26.148]发→◇enter_stop_exti
[16:35:26.152]收←◆enter_stop_exti
[16:35:29.584]发→◇enter_stop_exti
[16:35:29.691]收←◆ WK UP!!msh >
唤醒成功,测试list_thread,能够正常识别并且返回。然后再次测试AT
ch=41 size: 2 end=0D,0A
ch=54 size: 3 end=0D,0A
ch=0D size: 4 end=0D,0A
ch=0A size: 5 end=0D,0A
然后在打印看看buffer中保存是否有问题。
[16:53:46.406]发→◇AT
[16:53:46.413]收←◆
ch=41 size: 2 end=0D,0A
00 41
ch=54 size: 3 end=0D,0A
00 41 54
ch=0D size: 4 end=0D,0A
00 41 54 0D
ch=0A size: 5 end=0D,0A
00 41 54 0D 0A
[16:54:22.819]发→◇AT
[16:54:22.823]收←◆
ch=41 size: 6 end=0D,0A
00 41 54 0D 0A 41 T
ch=54 size: 7 end=0D,0A
00 41 54 0D 0A 41 54
ch=0D size: 8 end=0D,0A
00 41 54 0D 0A 41 54 0D
ch=0A size: 9 end=0D,0A
00 41 54 0D 0A 41 54 0D 0A
由此得出结论,strstr函数在判断时,会查看字符串的结束标志/0,但是buffer首字节已经被0x00占据,所以每次判断都为1。
于是进行一次0X00的筛选后问题解决
f(ch !=0X00)
{
server->recv_buffer[server->cur_recv_len++] = ch;
last_ch = ch;
}
else
{
continue;
}