RTT笔记-AT组件

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


image.png

在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的串口打印了,这个问题之后在解决。


image.png

首先在休眠前使用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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容