基于GD32E230的远程OTA的学习过程(二)

系列文章目录


前言

上次我们实现了串口发送,这次我们通过调用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;//注意这里一定要返回这个,否则解析器会无法正常工作的!
}

今天就写这么多啦,希望我能保证更新哈哈哈。
最近要去参加立创新的训练营啦,下次就写那个了。

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值