一、解析AT命令与底层抽象

1. 封装AT命令模块

我们接下来需要实现核心的AT命令处理函数。建议对相关代码再封装一层,以提升通用性和可维护性。

  1. 新建一个AT命令模块,便于后续管理和扩展。

2. 脱离底层依赖

  1. 新建一个与底层硬件解耦的模块,这样无论串口几号被修改,代码都能灵活适配。

3. 信号量与数据读取流程

  1. 创建信号量,在每次发送AT命令后,通过信号量机制读取返回数据。

SemaphoreHandle_t xSemaphore = NULL;

  1. 进入信号量创建函数,实现同步控制。

4. 抽象系统依赖

  1. 目前代码仍依赖FreeRTOS,若以后更换操作系统,需大量更改。建议再抽象一层,仅需更改一个接口。

  1. 新建HAL(硬件抽象层)和KAL(内核抽象层),便于系统切换和模块复用。

  1. platform_mutex.c 已实现FreeRTOS相关抽象,无需重复造轮子。

5. 平台互斥量定义与初始化

  1. 定义平台相关的互斥量对象。

  1. 初始化互斥量,简化同步控制。

6. 互斥量获取与超时机制

  1. 获取互斥量时,建议设置超时时间,避免死等,提升实时性和资源利用率。

  1. 增加带超时检测的代码。

  1. 在.h文件中声明相关接口。

  1. 定义宏,统一配置。

7. 返回值处理与状态管理

  1. 处理不同的返回值,保证兼容性与可读性。

8. 全局变量与Buffer管理

  1. 定义全局变量,便于状态和数据管理。

  1. 指定Buffer长度,防止越界和内存泄漏。

  1. 在.h文件中声明,提升AT命令灵活性。

9. 互斥量初始化细节

  1. 互斥量初始值为1,需先锁定一次,确保后续流程正常。

10. 任务创建与数据解析

  1. 创建任务函数指针,并实现任务函数。

  1. 针对ESP8266,不同查询返回不同数据,需根据回车换行判断数据类型,将OK或ERROR等信息存储并返回。

  1. 对特殊前缀数据进行特殊处理。

  1. 封装HAL相关代码,读取并解析结果,设置状态。

  1. 成功时唤醒任务并记录数据。

  1. 若函数未实现,调试时可先注释。

二、中断函数唤醒任务

1. 串口读取与初始化

  1. 创建串口1读取接口函数,实现串口数据读取和初始化。

2. AT命令发送与返回值处理

  1. 流程:先发送AT命令,等待返回值并上锁;另一个线程解析任务、保存数据、返回状态并解锁,供上层判断。

3. 内核初始化顺序

  1. FreeRTOS内核初始化后程序不会继续向下执行,因此相关初始化需提前完成。

4. 线程与测试

  1. 创建线程及测试线程,验证功能。

三、编写接收数据包函数

1. 收包与特殊字符处理

  1. 解包流程:创建函数判断是否收到特殊字符,设置数据包,定义并初始化锁和数据包长度。

2. 参考代码实现

#include "at_command.h"

/* 超时时间,时间根据tick频率计算,如tick=1000,那么时间就是1S */

#define AT_CMD_TIMEOUT  1000

/* resp缓冲区长度 */

#define AT_RESP_LEN  128

/* 别人封装好的抽象层我们定义使用,这个互斥量用于AT命令返回结果 */

static platform_mutex_t at_ret_mutex;

static platform_mutex_t at_packet_mutex;

/* 设置AT命令状态,不要暴露全局变量 */

static int g_at_stastus;

/* 设置全局at,buffer */

static char g_at_resp[AT_RESP_LEN];

/* 设置数据包 */

static char g_at_packet[AT_RESP_LEN];

/* AT命令数据包长度 */

static char g_at_packet_len;

/* 设置全局变量函数 */

/* stutas

 * 0  - ok

 * -1 - error

 * -2 - timeout

 */

void Set_AT_Status(int stutas)

{

    g_at_stastus = stutas;

}

/* 获得全局变量函数 */

/* stutas

 * 0  - ok

 * -1 - error

 * -2 - timeout

 */

void Get_AT_Status(int stutas)

{

    return g_at_stastus;

}

/* 初始化AT命令 */

int AT_Init(void)

{

    /* 初始化我们创建的互斥量 */

    platform_mutex_init(&at_ret_mutex);

    /* 因为mutex初始化是1,所以我们需要先锁定一次 */

    platform_mutex_lock(&at_ret_mutex); /* 之后mutex就 = 0 */

   

    platform_mutex_init(&at_packet_mutex);

    platform_mutex_lock (&at_packet_mutex); /* 之后mutex就 = 0 */

}

/* buf: "AT+CIPMOOE=1"

 * len: strlen(buf);

 * timeout: ms

*/

/* 发送AT命令 */

/* resp:传入指针,给null代表不关心返回数据,只关心返回值。

   非空的话就需要吧数据保存进resp里面 */

int AT_Send_Cmd(char *buf,int len ,char *resp , int resp_len,int timeout_ms)

{

    int ret;

    int err;

   

    /* 发生AT命令 */

    HAL_AT_Send(buf, len);

    HAL_AT_Send("\r\n", 2);

   

    /* 等待结果:获得信号量

     * 1 : 成功得到mutex

     * 0 : 超时返回

     */

    ret = platform_mutex_lock_timeout(&at_ret_mutex, AT_CMD_TIMEOUT);

    if(ret)

    {

        /* 判断返回值 */

        /* 保存resp */

        err = Get_AT_Status();

        if(!err && resp)

        {

            /* 如果长度大于我们的最大长度,就只能复制最大长度,否则复制原数据 */

            memcpy(resp, g_at_resp, resp_len > AT_RESP_LEN ? AT_RESP_LEN : resp_len);

        }

        return err;

    }

    else

    {

        return AT_TIMOUT;

    }

}

/* 是否获得特殊字符 */

int GetSpecialATString(char *buf)

{

    /* 收到的数据里面有+IPD, */

    if(strstr(buf, "+IPD,"))

        return 1;

    else

        return 0;

}

/* 处理特殊字符函数 */

void ProcessSpecialATString(char *buf)

{

    int i = 0;

    int len = 0;

    /* +IPD,n:xxxxxxxxxxxx */

    while(1)

    {

        /* 解析出长度 */

        i = 0;

        while(1)

        {

            /* 读取WiFi模块发来的数据:使用阻塞方式 */

            HAL_AT_Recv(&buf[i], portMAX_DELAY);

            if(buf[i] == ':')

            {

                break;

            }

            else

            {

                len = len * 10 + (buf[i] - '0'); /* 两位 */

            }

            i++;

        }

       

        /* 读取真正的数据 */

        i = 0;

        while(i < len)

        {

            /* 读取WiFi模块发来的数据:使用阻塞方式 */

            HAL_AT_Recv(&buf[i], portMAX_DELAY);

            if(i < AT_RESP_LEN)

            {

                g_at_packet[i] = buf[i];

                g_at_packet_len = i;

            }

            i++;

        }

       

        /* 解锁 */

        platform_mutex_unlock(&at_packet_mutex);

    }

}

/* 读数据包线程 */

int AT_Rend_Packet(char *buf, int len , int *resp_len, int timeout_ms)

{

    int ret;

    /* 解锁 */

    ret = platform_mutex_lock_timeout(&at_packet_mutex, timeout_ms);

   

    /* 是否得到数据 */

    if(ret)

    {

        *resp_len = len > g_at_packet_len ? g_at_packet_len : len;

        /* 实际长度大于我们定的长度 */

        memcpy(buf, g_at_packet, *resp_len);

        return AT_OK;

    }

    else

    {

        return AT_TIMOUT;

    }

}

/* 解析AT命令任务 */

void ATRecvParser( void *params)

{

    /* 数据缓冲 */

    char buf[AT_RESP_LEN] = {0};

    int i = 0;

   

    while(1)

    {

        /* 读取WiFi模块发来的数据:使用阻塞方式 */

        HAL_AT_Recv(&buf[i], portMAX_DELAY);

       

        /* 解析结果 */

        /* 1. 何时解析?

         * 1.1 收到"\r\n"

         * 1.2 收到特殊字符:"+IPD,"

         */

        /* 上一个字符是\r,当前的字符是\n i!=0和i同理 */

        if( (i) && (buf[i-1] == '\r') && (buf[i] == '\n'))

        {

            /* 得到了回车换行 先结束这一句 */

            buf[i+1] = '\0';

           

            /* 2. 怎么解析? */

            /* 检索"OK\r\n"字符串 */

            if(strstr(buf, "OK\r\n"))

            {

                /* 记录数据 */

                memcpy(g_at_resp, buf, i);

                /* 成功的话,设置状态 */

                Set_AT_Status(AT_OK);

                /* 唤醒任务 */

                platform_mutex_unlock(&at_ret_mutex);

            }

            else if(strstr(buf, "ERROR\r\n"))

            {

                /* 失败的话,设置状态 */

                Set_AT_Status(AT_ERROR);

                platform_mutex_unlock(&at_ret_mutex);

            }

            /* 如果获得了特殊AT的字符串 */

            else if(GetSpecialATString(buf))

            {

                /* 处理特殊的AT字符串 */

                ProcessSpecialATString(buf);

                i = -1;

            }

            i++;

        }           

    }

}

/* 测试程序 */

void Task_AT_Test( void *params)

{

    int ret;

    while(1)

    {

        ret = AT_Send_Cmd("AT", 2, NULL, 0, 2000);

        printf("AT_Send_Cmd = %d\r\n", ret);

    }

}

  1. 以上代码实现了AT命令的收发、数据包解析、状态管理等核心功能,便于后续调试与扩展。

四、解决ESP8266的错误

整体程序思路

  1. 先找到冒号“:”,在找到之前累加数字,作为数据长度;找到冒号后,代表长度解析结束,后续按长度读取数据。

编译与调试建议

  1. 编译程序,逐步解决编译及运行时错误。

  1. 在.h文件中声明所有需要的函数。

  1. 添加相关路径和头文件。

  1. 按照提示一步步修改,直至无错误。

最终,程序可顺利编译并调试通过。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐