AT 组件构造-RT thread源码解析

AT 组件构造client,server-RT thread源码解析


AT命令的框架如下图,在官方手册中有详细介绍:https://www.rt-thread.org/document/site/programming-manual/at/at/


AT 命令框架

分析源码的时候,可能一团乱麻,无从着手.首先整理下心情,从调用关系着手.rt-thread是类linux风格,所以里面驱动的普遍在初始化时注册方法(函数)指针.这样做的目的是在更高层的屏蔽不同硬件特征,统一流程.


AT server

直接看函数 static rt_err_t at_cmd_process(at_cmd_t cmd, const char *cmd_args)

是不是比较熟悉, 跟int main(int argc, char* argv[]),有点点像.

看传入的第一个参数的类型

struct at_cmd

{

    char name[AT_CMD_NAME_LEN];

    char *args_expr;

    at_result_t (*test)(void);

    at_result_t (*query)(void);

    at_result_t (*setup)(const char *args);

    at_result_t (*exec)(void);

};

typedef struct at_cmd *at_cmd_t;

先猜测:看起来该方法是想使用参数cmd中的方法test 先去test解析第二个参数,符合某种条件,再去执行exec.

再看实现

看多条的if检测条件cmd_args[0] == AT_CMD_EQUAL_MARK cmd_args[0] == AT_CMD_QUESTION_MARK

看起来猜的不对.去看宏定义

#define AT_CMD_CHAR_0 '0'

#define AT_CMD_CHAR_9                  '9'

#define AT_CMD_QUESTION_MARK          '?'

#define AT_CMD_EQUAL_MARK              '='

#define AT_CMD_L_SQ_BRACKET            '['

#define AT_CMD_R_SQ_BRACKET            ']'

#define AT_CMD_L_ANGLE_BRACKET        '<'

#define AT_CMD_R_ANGLE_BRACKET        '>'

#define AT_CMD_COMMA_MARK              ','

#define AT_CMD_SEMICOLON              ';'

#define AT_CMD_CR                      '\r'

#define AT_CMD_LF                      '\n'

那么可以确认是命令下面的不同分支方法.

这个时候就是提醒我们去熟悉AT指令的规则了.

AT Server 目前默认支持的基础命令如下:

• AT:AT 测试命令;

• ATZ:设备恢复出厂设置;

• AT+RST:设备重启;

• ATE:ATE1 开启回显,ATE0 关闭回显;

• AT&L:列出全部命令列表;

• AT+UART:设置串口设置信息。

AT 命令根据传入的参数格式不同可以实现不同的功能,对于每个 AT 命令最多包含四种功能,如下所述:

• 测试功能:AT+=? 用于查询命令参数格式及取值范围;

• 查询功能:AT+? 用于返回命令参数当前值;

• 设置功能:AT+=... 用于用户自定义参数值;

• 执行功能:AT+ 用于执行相关操作。

这个函数实现了每个AT指令功能统一调用,这就是结构体指针,函数指针的好处,类似对象的处理.可以看出这是基层的核心

接下来直接看server服务的主流程函数 static void server_parser(at_server_t server)

同样先分析传入参数,先猜测一下实现,或者思考换作自己该怎么做.

struct at_server

{

    rt_device_t device; //这个是rt 自己的设备类,实现通用的open close read write ctrl 跟linux open close接口一样

    at_status_t status; //状态

    char (*get_char)(void); //获取字符串

    rt_bool_t echo_mode; //回显

    char recv_buffer[AT_SERVER_RECV_BUFF_LEN]; //接受buffer

    rt_size_t cur_recv_len;//接收长度

    rt_sem_t rx_notice;//信号

    char end_mark[AT_END_MARK_LEN];//结束mask集合

    rt_thread_t parser; //解析的线程指针? 似乎是可以创建多个解析线程

    void (*parser_entry)(struct at_server *server);//解析函数入口指针??指向自己

};

typedef struct at_server *at_server_t;

再来看主流程:

while(getcahr())

{

    //echo

    //判断是否到了endmark

    //获取位于字符串头部cmd的名字 at_cmd_get_name

    //根据cmd的字符串, 找到cmd的结构体对象at_cmd_t

    //调用at_cmd_process 处理, 通过at_server_print_result 发送结果

}

那么什么时候调用server_parser,或者说怎么让它成为一个名副其实的SERVER呢?看at_server_init

at_server_init将结构体at_server_t的成员初始化.里面有几个重要的成员将自己关联到其他机制中

device /* Find and open command device */

get_char /*使用device读取字符,同时使用信号量唤醒机制 */

parser /* 创建线程 rt_thread_create  "at_svr", (void (*)(void *parameter))server_parser, at_server_local */

这样成就了一个线程服务,再然后client怎么到server, 一会client再说




AT client

AT client承担承上启下的功能, 发送 AT 命令、接收数据并解析数据(server发回的字符串,转化为status)

需要结合官方手册进行分析

核心函数 rt_err_t at_exec_cmd(at_response_t resp, const char *cmd_expr, ...);

第一个参数 跟server类似

struct at_client

{

    rt_device_t device;

    at_status_t status;

    char end_sign;

    /* the current received one line data buffer */

    char *recv_line_buf;

    /* The length of the currently received one line data */

    rt_size_t recv_line_len;

    /* The maximum supported receive data length */

    rt_size_t recv_bufsz;

    rt_sem_t rx_notice;

    rt_mutex_t lock;

    at_response_t resp;

    rt_sem_t resp_notice;

    at_resp_status_t resp_status;

    struct at_urc_table *urc_table;

    rt_size_t urc_table_size;

    rt_thread_t parser;

};

typedef struct at_client *at_client_t;

后面是可变参数,这部分处理print中一样的机制 va_list,将字符串写到设备中

va_start(args, cmd_expr);

at_vprintfln(client->device, cmd_expr, args);

va_end(args);

再有

if (resp != RT_NULL)

{

    if (rt_sem_take(client->resp_notice, resp->timeout) != RT_EOK)

    ...

}

resp != RT_NULL时候表明需要等待响应. rt_sem_take 实现了超时功能.

综上这个函数功能是发送给设备的AT指令字符串,并等待响应字符串,解析成status code码返回.

结合上面server创建时,需要打开设备.所以 client和server的通信是通过访问同一个device实现的.

client的初始化函数 int at_client_init(const char *dev_name, rt_size_t recv_bufsz)

可以看出,作者想设计的机制是一个client 对应多个server.

另外还有个URC并不熟悉.可以参考手册.

你可能感兴趣的:(AT 组件构造-RT thread源码解析)