AT 组件构造client,server-RT thread源码解析
AT命令的框架如下图,在官方手册中有详细介绍:https://www.rt-thread.org/document/site/programming-manual/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并不熟悉.可以参考手册.