系列文章目录
前言
上次我们实现了串口发送,这次我们通过调用AT开源库来实现数据的发送和接收处理
一、AT命令库
其实这些库在GITHUB上有很多,这里我用的是https://github.com/ARMmbed/ATCmdParser
它可以通过注册回调函数来实现自动处理接收信息。
二、使用步骤
1.引入库
将库的.c和.h文件都导入项目即可。
2.看库
和HAL库一样,我们先看他的头文件,也不建议大家在库文件里进行修改,这样会导致文件的可移植性降低。
库由两个文件组成,AT命令解析和发送AT命令,AT命令解析的比较简单而且只包含了一部分功能,如下(感谢GPT给我写的注释):
#ifndef _AT_PARSER_H_
#define _AT_PARSER_H_
#include <stdint.h>
#define PARSER_BUFFER_LENGTH 1024 // 解析器缓冲区长度,默认为1024字节
#define PARSER_STATE_CHAR 0 // 解析器状态:处理字符
#define PARSER_STATE_RAWDATA 1 // 解析器状态:处理原始数据
#define PARSER_RESET_NONE 0 // 无需重置
#define PARSER_RESET_BUFFER 1 // 重置缓冲区
#define PARSER_RESET_ALL 2 // 重置所有内容
#define PARSER_MATCH_MORE 0 // 匹配模式:需要更多数据
#define PARSER_MATCH_SUCCESS 1 // 匹配模式:匹配成功
#define PARSER_MATCH_FAILED 2 // 匹配模式:匹配失败
// 定义解析器处理程序类型
typedef uint8_t(*parser_handler_t)(uint8_t pattern_code, void *parameter);
// 定义AT指令模式结构体
typedef struct _at_pattern
{
uint8_t code; // 模式代码
const char *pattern; // 模式字符串
} at_pattern_t;
// 定义AT通配符结构体
typedef struct _at_wildcard
{
uint8_t flag; // 通配符标志
uint8_t count; // 通配符数量
const char *pattern; // 通配符模式字符串
} at_wildcard_t;
// 定义AT解析器结构体
typedef struct _at_parser
{
uint8_t state; // 解析器当前状态
uint16_t data_left; // 剩余数据量
uint8_t buffer[PARSER_BUFFER_LENGTH]; // 解析器缓冲区
uint16_t buffer_count; // 缓冲区中数据的数量
const at_pattern_t *pattern_list; // 模式列表
uint8_t pattern_count; // 模式数量
uint8_t pattern_index; // 当前模式索引
at_wildcard_t wildcard; // 通配符
void *parameter; // 用户参数
parser_handler_t handler; // 处理函数指针
} at_parser_t;
// 初始化AT解析器
// parser: 解析器结构体指针
// pattern_list: 模式列表
// pattern_count: 模式数量
// handler: 处理函数
// parameter: 用户参数
void at_parser_init(at_parser_t *parser,
const at_pattern_t *pattern_list, uint8_t pattern_count,
parser_handler_t handler, void *parameter);
// 处理输入的单个字符
// parser: 解析器结构体指针
// data: 输入的字符
void at_parser_feed_char(at_parser_t *parser, uint8_t data);
// 处理输入的缓冲区数据
// parser: 解析器结构体指针
// data: 输入的数据缓冲区指针
// length: 数据缓冲区长度
void at_parser_feed_buffer(at_parser_t *parser, const void *data, uint16_t length);
#endif // _AT_PARSER_H_
#ifndef _MODEM_PARSER_H_
#define _MODEM_PARSER_H_
#include "at_parser.h"
#define MODEM_COMMAND_LENGTH 72 // 调制解调器命令的最大长度
#define MODEM_DCE_WAITING 0x7E // 调制解调器等待状态标志
#define MODEM_DCE_TIMEOUT 0x7F // 调制解调器超时标志
#define MODEM_URC_MASK 0x80 // 未请求码(URC)掩码
// 调制解调器命令结构体
typedef struct _modem_cmd
{
const char *send; // 要发送的命令字符串
uint8_t wait; // 等待时间或状态
uint32_t timeout; // 超时时间(毫秒)
} modem_cmd_t;
// 接收缓冲区回调函数类型定义
typedef uint16_t (*recv_buffer_callback_t)(void *pbuffer, uint16_t size);
// 发送缓冲区回调函数类型定义
typedef uint8_t (*send_buffer_callback_t)(const void* pbuff, uint16_t size);
// 调制解调器回调函数结构体
typedef struct _modem_callback
{
recv_buffer_callback_t recv_buffer; // 接收缓冲区回调函数
send_buffer_callback_t send_buffer; // 发送缓冲区回调函数
} modem_callback_t;
// 响应处理函数类型定义
typedef uint8_t(*response_handler_t)(uint8_t response_code, void *parameter);
// 调制解调器响应处理结构体
typedef struct _modem_response_handler
{
void *parameter; // 用户参数
response_handler_t handler; // 响应处理函数
} modem_response_handler_t;
// 调制解调器解析器结构体
typedef struct _modem_parser
{
at_parser_t at_parser; // 嵌入的AT命令解析器结构体,用于解析和处理AT命令
uint32_t timeout; // 超时设置,单位可能是毫秒,用于AT命令响应超时计算
uint8_t response_code; // 响应代码,用于标识AT命令的响应结果
uint8_t response_waiting; // 响应等待标志,可能是一个布尔值,用于指示是否正在等待响应
void *urc_parameter; // 未请求码(URC)的参数,用于处理特定的URC回调
response_handler_t urc_handler; // 未请求码(URC)的回调处理函数,用于处理接收到的URC
void *response_parameter; // 响应处理的参数,这些参数将传递给响应处理函数
response_handler_t response_handler; // 响应处理函数,用于处理AT命令的响应
const modem_callback_t *callback; // 指向回调函数结构的指针,这些回调函数用于通信和事件处理
} modem_parser_t;
// 宏定义,便于访问调制解调器解析器的内部成员
#define modem_parser_buffer(modem) ((modem)->at_parser.buffer)
#define modem_parser_buffer_count(modem) ((modem)->at_parser.buffer_count)
#define modem_parser_state(modem) ((modem)->at_parser.state)
#define modem_parser_state_set(modem, state_new) ((modem)->at_parser.state = state_new)
#define modem_parser_data_left(modem) ((modem)->at_parser.data_left)
#define modem_parser_data_left_set(modem, length) ((modem)->at_parser.data_left = length)
// 发送原始数据
// parser: 解析器结构体指针
// buffer: 要发送的数据缓冲区指针
// length: 数据长度
void modem_send_rawdata(modem_parser_t *parser, const uint8_t *buffer, uint16_t length);
// 发送AT命令
// parser: 解析器结构体指针
// format: 格式化字符串(类似printf)
void modem_send_command(modem_parser_t *parser, const char *format, ...);
// 等待响应
// parser: 解析器结构体指针
// timeout: 超时时间(毫秒)
// 返回响应代码
uint8_t modem_response_wait(modem_parser_t *parser, uint32_t timeout);
// 异步等待响应
// parser: 解析器结构体指针
// timeout: 超时时间(毫秒)
// 返回响应代码
uint8_t modem_response_wait_async(modem_parser_t *parser, uint32_t timeout);
// 检查是否有响应
// parser: 解析器结构体指针
// 返回响应代码
uint8_t modem_response_wait_check(modem_parser_t *parser);
// 完成响应处理
// parser: 解析器结构体指针
// response_code: 响应代码
void modem_response_finish(modem_parser_t *parser, uint8_t response_code);
// 设置未请求码(URC)处理函数
// parser: 解析器结构体指针
// urc_handler: URC处理函数
// parameter: 用户参数
void modem_urc_handler_set(modem_parser_t *parser, response_handler_t urc_handler, void *parameter);
// 设置响应处理函数
// parser: 解析器结构体指针
// response_handler: 响应处理函数
// parameter: 用户参数
void modem_response_handler_set(modem_parser_t *parser, response_handler_t response_handler, void *parameter);
// 执行调制解调器命令
// parser: 解析器结构体指针
// modem_cmd: 调制解调器命令结构体指针
// count: 命令数量
// 返回执行结果
uint8_t modem_cmd_exec(modem_parser_t *parser, const modem_cmd_t *modem_cmd, uint8_t count);
// 解析器滴答处理函数
// parser: 解析器结构体指针
void modem_parser_tick(modem_parser_t *parser);
// 解析器运行处理函数
// parser: 解析器结构体指针
void modem_parser_run(modem_parser_t *parser);
// 初始化调制解调器解析器
// parser: 解析器结构体指针
// pattern_list: 模式列表
// pattern_count: 模式数量
// callback: 回调函数结构体指针
void modem_parser_init(modem_parser_t *parser,
const at_pattern_t *pattern_list, uint8_t pattern_count,
const modem_callback_t *callback);
#endif // _MODEM_PARSER_H_
那么非常明显啊- -我们先看到初始化,初始化需要四个参数,一个是库所提供的结构参数体,第二第三是AT命令回复的格式和数量,第四是调用的回调函数。
第二第三个函数根据库函数的定义如下定义:
#define COUNT(a) (sizeof(a)/sizeof((a)[0]))
static const at_pattern_t esp_response[] =
{
{MQTT_RCV_OK, "OK"},//AT
{MQTT_RCV_SUBMSG, "+MSUB:*\r\n"},//订阅
{MQTT_RCV_MQTTUNSUB, "+MUNSUB:*\r\n"},//取消订阅
{MQTT_RCV_MQTTPUB, "+PUBACK\r\n"},//发布成功
{MQTT_RCV_CONNECT, "CONNACK OK\r\n"},//连接成功
};
第四个函数自然是我们的读写函数了
static const modem_callback_t modem_uart_callback =
{
network_uart_read,
network_uart_write,
};
第一个函数我们先随便声明一个,然后再调用modem_response_handler_set给设置好就可以了。
这样子当接收到AT命令的时候就能够自己调用解析器对返回的信息进行处理了。
我这里用的是ESP-01S,那么需要用到的AT命令就如下:
//用于连接WIFI
AT+CWJAP="MyWiFi","MyPassword"
//连接MQTT服务器
AT+MQTTSET="TCP","MQTT_SERVER_ADDRESS",MQTT_SERVER_PORT
//订阅主题
AT+MQTTSUB=0,"test/topic",1
//向主题发布消息
AT+MQTTPUB=0,"TOPIC","MESSAGE",QOS,RETAIN
根据库函数,发送AT指令用的是modem_send_command这个指令,需要传入一个句柄和字符串,所以对应的函数就很好写出来了。不过要记得每个引号之前都要加转义符。
/**
*@brief : 配置MQTT客户端网络
*@param[in] : phandle: 句柄
*@return :
*/
uint8_t mqtt_connect_wifi(modem_parser_t *par, char *sn, uint8_t time_s)
{
modem_send_command(par, "AT+CWJAP=\"MyWiFi\",\"MyPassword\"\r\n");
return RETURN_OK;
}
/**
*@brief : 连接MQTT服务器
*@param[in] : phandle: 句柄
*@return :
*/
uint8_t mqtt_connect_wifi(modem_parser_t *par, uint8_t time_s)
{
modem_send_command(par, "AT+MQTTSET=\"TCP\",\"MQTT_SERVER_ADDRESS\",MQTT_SERVER_PORT\r\n");
return RETURN_OK;
}
/**
*@brief : 订阅主题
*@param[in] : phandle: 句柄
*@return :
*/
uint8_t mqtt_connect_wifi(modem_parser_t *par, uint8_t time_s)
{
modem_send_command(par, "AT+MQTTSUB=0,\"test/topic\",1\r\n");
return RETURN_OK;
}
向主题发布消息的下一次再写,想放在不同的地方。
在发送了以上命令之后,我们还需要对通讯模块返回的消息进行处理,确保我们真的连上了。这里我用了状态机工作流程(说是状态机其实就是通过switch语句选择要进行的程序)。那么上次我们定义的esp_response数组就用上了。
这里其实主要是通过回调函数来处理的,我只举我其中写的一个函数来举例,具体实现还是要看具体需求。
/**
*@brief : 发送连接请求解析函数
*@param[in] : phandle: 句柄
*@param[in] : response_code:响应状态代码
*@return :
*/
uint8_t connect_mqtt_handler(uint8_t response_code, void *parameter)
{
char str[100] = {0};
// Debugging: Print the modem buffer content
const char *buffer = (const char *)modem_parser_buffer((modem_parser_t *)parameter);
// Adjusted parsing logic
if (sscanf(buffer, "CONNACK %s", str) == 1)
{
if (strstr(str, "OK") != NULL)
{
network_set_work_state(SERVER_SUBSCRIBE, NETWORK_MAIN_ID); //切换到订阅状态
}
}
return PARSER_RESET_ALL;//注意这里一定要返回这个,否则解析器会无法正常工作的!
}
今天就写这么多啦,希望我能保证更新哈哈哈。
最近要去参加立创新的训练营啦,下次就写那个了。