• 基于FreeRTOS编写ESP8266的AT命令收发解析器


    1. 代码框架

    前面文章已经介绍了AT命令的组成,以及通讯过程。《AT命令使用和简单介绍》。

    现在写代码实现AT命令的发送,以及响应数据、URC数据的解析。

    代码框架主要是有两个线程。一个线程负责命令发送,并阻塞等待命令响应结果和响应数据;还有一个是数据解析线程,主要是解析AT命令的响应数据已经URC数据,解析的结果和数据会传递给命令发送线程,然后唤醒命令发送线程。大致流程如下:
    在这里插入图片描述

    数据解析线程会调用一个读取一行数据的函数,这个函数会去读取串口的数据,如果读取不到串口数据,那么这个线程就会一直阻塞,直到等到有串口数据的时候,才会发送信号量唤醒这个线程。

    然后下面的代码把接收到的数据进行解析。解析完成之后,会发送信号量唤醒AT命令的发送线程。

    2. 核心代码介绍

    主要核心代码就是这两个线程的代码,以及所调用的函数。

    2.1 数据解析线程函数

    void at_recv_parser(void *parameter)
    {
    	char recv_line_buff[128] = {0};
    	int read_len = 0;
    	at_resp_t tmp_resp = {{0}, 0, 0};
    	const at_urc_t *urc = NULL;
    	
    	while (1)
    	{
    		read_len = at_recv_readln(recv_line_buff, sizeof(recv_line_buff));
    		if ( read_len > 0)
    		{									
    			if ((urc = at_get_urc(recv_line_buff, read_len)) != NULL)
    			{
    				/* URC数据处理 */
    				if (urc->func != NULL)
    				{
    					urc->func(recv_line_buff, read_len);
    				}
    			}
    			else
    			{
    				/* 命令响应数据处理 */
    				if (tmp_resp.buf_len < AT_MAX_RESP_LEN)
    				{
    					recv_line_buff[++read_len] = '\0';
    					memcpy(tmp_resp.buf + tmp_resp.buf_len, recv_line_buff, read_len);
    					tmp_resp.buf_len += read_len;
    					tmp_resp.line_counts++;
    				}
    				else
    				{	
    					at_set_resp_status(AT_RESP_BUFF_FULL);
    				}
    				
    				if (strstr(recv_line_buff, "OK"))
    				{
    					memset(&gs_resp, 0, sizeof(gs_resp));
    					memcpy(&gs_resp, &tmp_resp, sizeof(gs_resp));
    					at_set_resp_status(AT_RESP_OK);
    				}
    				else if (strstr(recv_line_buff, "ERROR"))
    				{
    					memset(&gs_resp, 0, sizeof(gs_resp));
    					memcpy(&gs_resp, &tmp_resp, sizeof(gs_resp));
    					at_set_resp_status(AT_RESP_ERROR);
    				}
    				else
    				{
    					continue;
    				}
    				
    				memset(&tmp_resp, 0, sizeof(tmp_resp));
    				xSemaphoreGive(at_resp_sem);
    			}			
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    1、在这个线程函数中,有一个 at_recv_readln 函数,会一直去读取串口环形缓冲区的数据,如果没有数据那么就会阻塞等待,直到串口接收数据中断释放的信号量去唤醒它。这个函数实现如下,这个函数读取到一行数据或者有匹配的URC数据,那么就返回给数据解析线程。

    static int at_recv_readln(char *buff, unsigned int buff_len)
    {
    	char ch = 0, last_ch = 0;
    	unsigned int read_len = 0;
    
    	memset(buff, 0, buff_len);
    	
    	while (1)
    	{
    		at_client_getchar(&ch, portMAX_DELAY);
    		if (read_len < buff_len)
    		{
    			buff[read_len++] = ch;
    		}
    		else
    		{/* buff溢出错误 */
    			memset(buff, 0x00, buff_len);
    			return -1;
    		}
    
    		/* 读到一行数据,或者接收到URC数据 */
    		if ((ch == '\n' && last_ch == '\r') || at_get_urc(buff, read_len))
    		{
    			break;
    		}
    		last_ch = ch; 	/* 暂存前一个字符 */
    	}
    	
    	return read_len;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2、数据解析线程主要分为两个部分的情况进行解析。一个是URC数据解析,一个是命令响应数据的解析。

    其中URC数据处理,我定义了一个URC的数据结构体:

    typedef struct _at_urc_t
    {
        const char *cmd_prefix;		/* 前缀 */
        const char *cmd_suffix;		/* 后缀 */
        void (*func)(const char *data, unsigned int size);	/* 对应执行函数 */
    } at_urc_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后定义一个URC数据表格:

    static at_urc_t esp8266_urc_table[] = 
    {
    	{"SEND OK",          "\r\n",           urc_send_func},
        {"SEND FAIL",        "\r\n",           urc_send_func},
        {"+IPD",             ":",              urc_recv_func},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我这里暂时只实现数据收发的URC数据的处理函数。在这个 at_recv_parser 函数中,会调用 at_get_urc 函数去匹配判断是否有接收到和我们表格中定义的URC数据,如果匹配上了,就会调用对应的函数去处理,处理完成之后,就会释放信号量唤醒AT命令发送线程。

    at_get_urc 函数实现如下:

    static const at_urc_t *at_get_urc(const char *recv_line_buf, unsigned int recv_line_len)
    {
    	unsigned char prefix_len = 0, suffix_len = 0;
    
    	for (int i = 0; i < sizeof(esp8266_urc_table) / sizeof(esp8266_urc_table[0]); i++)
    	{
    		prefix_len = strlen(esp8266_urc_table[i].cmd_prefix);
    		suffix_len = strlen(esp8266_urc_table[i].cmd_suffix);
    		
    		if ((prefix_len ? !strncmp(recv_line_buf, esp8266_urc_table[i].cmd_prefix, prefix_len) : 1)
                    && (suffix_len ? !strncmp(recv_line_buf + recv_line_len - suffix_len, esp8266_urc_table[i].cmd_suffix, suffix_len) : 1))
            {
                return &esp8266_urc_table[i];		
            }
    	}
    	
    	return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3、命令响应数据解析

    对于命令响应数据,因为都会有响应的状态回复,要么回复 “OK” ,要么是 “ERROR” ,所以我们只需要判断匹配这两个字符串即可。判断完成之后,把响应的数据和状态通过全局变量传递给AT命令发送线程,然后再发送信号量去唤醒这个线程。

    2.2 AT命令发送线程

    主要是实现了 at_exce_cmd 这个发送AT命令的函数,并返回命令响应的状态和响应数据。

    /**
     * 发送命令给AT服务器,和等待回应
     *
     * @param resp : 输出AT服务器回应的数据
     *
     * @return 0 : success
     *        -1 : response status error
     *        -2 : wait timeout
     */
    int at_exce_cmd(const char *cmd, at_resp_t *resp, unsigned int timeout)
    {
    	/* 发送命令给AT服务器 */
    	at_client_sendcmd(cmd);
    
    	/* 获取解析AT服务器回应数据的任务释放的信号量 */
    	if (xSemaphoreTake(at_resp_sem, pdMS_TO_TICKS(timeout)) == pdTRUE)
    	{
    		if (resp != NULL)
    		{
    			memcpy(resp, &gs_resp, sizeof(at_resp_t));
    		}
    		return at_get_resp_status();
    	}
    	else
    	{
    		return AT_RESP_TIMEOUT;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    其中调用的 at_client_sendcmd 函数,就是通过串口发送数据给AT Server(即ESP8266模块)的。发送命令之后,就阻塞等待数据解析线程的回应,如果解析成功或者超时,都会唤醒这个线程。

    以上就是主要代码的介绍,完整的工程代码可以去下面的链接下载。

    https://download.csdn.net/download/luobeihai/86403607?spm=1001.2014.3001.5503

  • 相关阅读:
    vue-cli2 与vue-cli3,vue2与vue3 初始化项目,本地vue项目,详细解析区别(2024-04-19)
    IDEA去除代码和XML中的波浪线(黄色警告线)
    【人工智能】Mindspore框架中保存加载模型
    『忘了再学』Shell基础 — 31、字符处理相关命令
    传奇出现黑屏卡屏不动是怎么回事
    Vue入门简介(带你打开Vue的大门)
    线性代数 --- 向量的长度
    Python数据分析--Numpy常用函数介绍(8)--Numpy中几中常见的图形
    【产品经理】分享产品认知方法论
    Inpaint Anything:一键进行多种图像修补
  • 原文地址:https://blog.csdn.net/luobeihai/article/details/126407744