IOT-OS之RT-Thread(十四)--- AT命令集 + ESP8266 WiFi模块

本文详细介绍了如何在RT-Thread操作系统中使用AT组件与ESP8266 WiFi模块进行通信。内容涵盖AT命令集的起源、ESP8266模块简介、AT组件与SAL组件的交互,以及如何初始化和配置AT设备层。通过实例展示了如何使用AT命令控制ESP8266连接WiFi并进行网络操作,强调了AT命令在嵌入式开发中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、AT命令集简介

AT 命令(Attention Commands)最早是由发明拨号调制解调器(MODEM)的贺氏公司(Hayes)为了控制 MODEM 而发明的控制协议。后来随着网络带宽的升级,速度很低的拨号 MODEM(比如ADSL) 基本退出一般使用市场,但是 AT 命令保留下来。当时主要的移动电话生产厂家共同为 GSM 研制了一整套 AT 命令,用于控制手机的 GSM 模块(包括对SMS短消息服务的控制),AT 命令在此基础上演化并加入 GSM 07.05 标准以及后来的 GSM 07.07 标准,实现比较健全的标准化。

在随后的 GPRS 控制、3G 模块等方面,均采用的 AT 命令来控制,AT 命令逐渐在产品开发中成为实际的标准。如今,AT 命令也广泛的应用于嵌入式开发领域,AT 命令作为主芯片和通讯模块的协议接口,硬件接口一般为串口,这样主控设备可以通过简单的命令和硬件设计完成多种操作。

1.1 AT命令集简介

AT 命令集是一种应用于 AT 服务器(AT Server)与 AT 客户端(AT Client)间的设备连接与数据通信的方式。 其基本结构如下图所示:
AT服务器与客户端通信方式

一般 AT 命令由前缀、主体、结束符三个部分组成:前缀由字符 AT构成;主体由命令、参数和可能用到的数据组成;结束符一般为 (也即"\r\n"),其中是串口终端的要求(使用串口工具发送命令时记得勾选新行模式)。每个命令一般可分为下表所示的四种命令类型(不是每条指令都具备四种类型):
指令结构描述
AT命令发出一般都有响应,或响应请求的数据,或响应命令执行的状态,响应数据前后都有一个字符,下面给出几个基础AT命令(每条命令支持上表的几种类型)的格式、响应、参数说明与示例(其中、这类不可见字符省略了):
AT基础命令示例
AT 功能的实现需要 AT Server 和 AT Client 两个部分共同完成:AT Server 主要用于接收 AT Client 发送的命令请求,判断接收的命令及参数格式,并下发对应的响应数据,或者主动下发URC数据(Unsolicited Result Code,常在AT Server出现比如 WIFI 连接断开、TCP 接收数据等特殊情况时,通知AT Client做出相应的操作);AT Client 主要用于发送命令、等待 AT Server 响应,并对 AT Server响应数据或主动发送的URC数据进行解析处理,获取相关信息。

AT Server 和 AT Client 之间支持多种数据通讯的方式(UART、SPI 等),目前最常用的是串口 UART 通讯方式(包括RS232、RS485等)。随着 AT 命令的逐渐普及,越来越多的嵌入式产品上使用了 AT 命令,AT 命令作为主芯片和通讯模块的协议接口,硬件接口一般为串口,这样主控设备可以通过简单的命令和硬件设计完成多种操作。

1.1 AT组件简介

虽然 AT 命令已经形成了一定的标准化,但是不同的芯片支持的 AT 命令并没有完全统一,这直接提高了用户使用的复杂性。对于 AT 命令的发送和接收以及数据的解析没有统一的处理方式,并且在使用 AT 设备连接网络时,只能通过命令完成简单的设备连接和数据收发功能,很难做到对上层网络应用接口的适配,不利于产品设备的开发。

为了方便用户使用 AT 命令,简单的适配不同的 AT 模块, RT-Thread 提供了 AT 组件用于 AT 设备的连接和数据通讯。AT 组件的实现也包括客户端的和服务器两部分,完成 AT 命令的发送、命令格式及参数判断、命令的响应、响应数据的接收、响应数据的解析、URC 数据处理等整个 AT 命令数据交互流程。

通过 AT 组件,设备可以作为 AT Client 使用串口连接其他设备发送并接收解析数据,可以作为 AT Server 让其他设备甚至电脑端连接完成发送数据的响应,也可以在本地 shell 启动 CLI 模式使设备同时支持 AT Server 和 AT Client 功能,该模式多用于设备开发调试。对于嵌入式设备而言,更多的情况下设备使用AT组件作为客户端连接服务器设备。

支持AT命令集的设备一般内部都集成了TCP/IP网络协议栈,我们只需要通过串口连接AT模块与主控芯片,就可以使用AT命令集控制AT模块实现我们需要的网络服务功能。类比前一篇博客介绍的网络分层结构,AT组件的协议栈架构并没有网络协议层,AT通讯模块的驱动协议框架如下图所示:
AT组件网络框架

  • APP层:开发者只需要使用标准的系统调用接口,比如BSD Socket API 开发应用即可,无需关心底层的实现,同时应用代码还拥有比较好的可移植性;
  • SAL组件层:对上层提供了BSD Socket API,对下层提供了协议簇注册接口;
  • AT组件层:对上层提供了基于AT的Socket接口,对下层提供了移植接口。AT device在初始化完成后会被作为一个AT Socket设备注册到SAL组件中,当上层应用调用BSD Socket API时,会通过注册的接口调用底层的AT device驱动,完成数据的传输;
  • AT device层:利用AT组件对AT device做的移植,主要是利用AT组件提供的接口完成对AT设备(比如ESP8266模块)的初始化工作。由于AT设备内部有MCU芯片运行射频芯片的驱动与协议栈代码(要想正常使用AT模块,需要确保里面烧录了正确的固件代码),我们在主控设备上只需要完成串口驱动的配置和AT命令集的发送解析既可以了。

二、 ESP8266 WiFi模块简介

本文使用的WiFI模块型号为ATK-ESP-01,实际上是封装的乐鑫的ESP8266芯片,该SOC 集成了Tensilica L106 超低功耗 32 位微型 MCU,带有 16 位精简模式,主频支持 80MHz 和 160MHz,支持 RTOS,集成 Wi-Fi MAC/BB/RF/PA/LNA,ESP8266芯片的体系框架图如下:
ESP8266芯片模块框架图
ESP8266 是一个完整且自成体系的 Wi-Fi 网络解决方案,能够独立运行,也可以作为从机搭载于其它主机 MCU 运行。ATK-ESP-01模块支持标准的IEEE802.11 b/g/n协议,完整的TCP/IP协议栈,该模块一般作为从机通过UART接口(默认波特率115200)连接其它主机MCU,通过AT命令集与主机MCU交互,为主机MCU提供WiFi扩展服务。ATK-ESP-01模块的引脚定义如下:
ATK-ESP-01模块引脚定义
WiFi模块要想正常工作,内部需要运行WiFi固件,负责 IEEE802.11数据帧与IEEE 802.3数据帧之间的转换,并驱动RF基带管控无线数据链路层。ATK-ESP-01模块还内置了完整的LwIP协议栈,协议栈代码也需要随WiFi固件驱动一起烧录到模块内,该模块才能正常响应AT命令集。我们买来的ATK-ESP-01模块事先已经烧录好了WiFi固件(包含TCP/IP协议栈),如果用户意外擦除了,可以使用其提供的工具ESPFlashDownloadTool按照说明文档将模块固件(两个bin文件,分别保存主程序和射频参数)烧录到ATK-ESP-01模块内即可。

三、AT device层与AT组件层

ATK-ESP-01模块使用UART总线与主机MCU连接,直接插在Pandora开发板的ATK MODULE接口即可,如下图所示:
ESP8266接口

3.1 配置UART2外设驱动

ATK-ESP-01模块连接在Pandora开发板的UART2接口上,UART标准库驱动框架在前面的博客中已经详细介绍过了。RT-Thread已经在Pandora BSP文件中,使用CubeMX帮我们配置好了UART2引脚,我们只需要在menuconfig中启用UART2相关的宏定义即可。

在AT命令集的交互关系中,ATK-ESP-01模块是提供WiFi网络服务的一方,也即AT Server;Pandora开发板是请求WiFi网络服务的一方,当需要网络服务时,向ATK-ESP-01模块发送AT命令,接收并处理ATK-ESP-01模块的响应数据,因此Pandora开发板作为AT Client,需要运行AT组件中的AT Client模块。我们在Kconfig中添加ESP8266扩展模块的宏定义配置项如下:

// .\board\Kconfig

menu "Board extended module Drivers"
	......
    config BSP_USING_ESP8266
            bool "Enable ESP8266"
            select BSP_USING_UART2
            select RT_USING_AT
            select AT_USING_CLIENT
            default n
endmenu

保存添加的配置项,并在env工具中运行menuconfig命令,启用刚才的配置项,如下图所示:
启用ESP8266模块
我们已经在工程中启用了UART2外设与AT Client模块,接下来看AT Client的工作原理及其初始化过程。

3.2 AT Client初始化

AT Client主要是将比较直观的API转换为AT命令,并通过UART2接口发送给ESP8266模块。ESP8266执行完相应的AT命令后,将响应结果通过UART2返回给STM32L475,AT Client负责解析返回的AT响应结果及URC数据,并将解析后的数据递交给上面的应用层。

首先看AT Client 设备的数据结构描述:

// rt-thread\components\net\at\include\at.h

struct at_client
{
   
    rt_device_t device;

    at_status_t status;
    char end_sign;

    char *recv_buffer;
    rt_size_t recv_bufsz;
    rt_size_t cur_recv_len;
    rt_sem_t rx_notice;
    rt_mutex_t lock;

    at_response_t resp;
    rt_sem_t resp_notice;
    at_resp_status_t resp_status;

    const struct at_urc *urc_table;
    rt_size_t urc_table_size;

    rt_thread_t parser;
};
typedef struct at_client *at_client_t;

struct at_response
{
   
    /* response buffer */
    char *buf;
    /* the maximum response buffer size */
    rt_size_t buf_size;
    /* the number of setting response lines
     * == 0: the response data will auto return when received 'OK' or 'ERROR'
     * != 0: the response data will return when received setting lines number data */
    rt_size_t line_num;
    /* the count of received response lines */
    rt_size_t line_counts;
    /* the maximum response time */
    rt_int32_t timeout;
};
typedef struct at_response *at_response_t;

/* URC(Unsolicited Result Code) object, such as: 'RING', 'READY' request by AT server */
struct at_urc
{
   
    const char *cmd_prefix;
    const char *cmd_suffix;
    void (*func)(const char *data, rt_size_t size);
};
typedef struct at_urc *at_urc_t;

at_client结构体包含了一个设备对象指针device,指向模块通信使用的外设接口,这里指向UART2外设对象。AT Client除了向AT Server发送AT命令请求外,还要接收并解析响应数据与URC数据,因此at_client结构体还包含了指向响应结构的指针resp与执行URC数据列表的指针urc_table。

AT Client为了提高对响应数据at_response与URC数据at_urc的解析效率,还创建了一个数据解析线程,at_client结构体中的成员parser便指向该解析线程。

  • AT Client 初始化过程

在使用AT Client前需要先对其进行初始化,完成AT Client运行所需的资源配置,AT Client的初始化过程如下:

// rt-thread\components\net\at\src\at_client.c

static struct at_client at_client_table[AT_CLIENT_NUM_MAX] = {
    0 };
/**
 * AT client initialize.
 * @param dev_name AT client device name
 * @param recv_bufsz the maximum number of receive buffer length
 * @return 0 : initialize success
 *        -1 : initialize failed
 *        -5 : no memory
 */
int at_client_init(const char *dev_name,  rt_size_t recv_bufsz)
{
   
    int idx = 0;
    int result = RT_EOK;
    rt_err_t open_result = RT_EOK;
    at_client_t client = RT_NULL;

    for (idx = 0; idx < AT_CLIENT_NUM_MAX && at_client_table[idx].device; idx++);
	......
    client = &at_client_table[idx];
    client->recv_bufsz = recv_bufsz;

    result = at_client_para_init(client);
    ......
    /* find and open command device */
    client->device = rt_device_find(dev_name);
    if (client->device)
    {
   
        RT_ASSERT(client->device->type == RT_Device_Class_Char);
        /* using DMA mode first */
        open_result = rt_device_open(client->device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_DMA_RX);
        /* using interrupt mode when DMA mode not supported */
        ......
        rt_device_set_rx_indicate(client->device, at_client_rx_ind);
    }
    ......
    if (result == RT_EOK)
    {
   
        client->status = AT_STATUS_INITIALIZED;
        
        rt_thread_startup(client->parser);
    }
    ......
    return result;
}

AT Client是支持多客户端模式的,当有多个AT Server扩展模块都需要为主机提供网络服务时,主机端就需要有多个at_client对象,因此AT Client使用一个数组at_client_table来管理多个at_client对象,数组成员数AT_CLIENT_NUM_MAX默认值为1(可通过menuconfig配置),我们只有一个AT Server扩展模块,因此不需要更改该值。接下来是AT Client解析线程的初始化过程:

// rt-thread\components\net\at\src\at_client.c

/* initialize the client object parameters */
static int at_client_para_init(at_client_t client)
{
   
	......
    client->status = AT_STATUS_UNINITIALIZED;

    client->cur_recv_len = 0;
    client->recv_buffer = (char *) rt_calloc(1, client->recv_bufsz);
    ......
    client->lock = rt_mutex_create(name, RT_IPC_FLAG_FIFO);
    ......
    client->rx_notice = rt_sem_create(name, 0, RT_IPC_FLAG_FIFO);
    ......
    client->resp_notice = rt_sem_create(name, 0, RT_IPC_FLAG_FIFO);
    ......
    client->urc_table = RT_NULL;
    client->urc_table_size = 0;

    rt_snprintf(name, RT_NAME_MAX, "%s%d", AT_CLIENT_THREAD_NAME, at_client_num);
    client->parser = rt_thread_create(name,
                                     (void (*)(void *parameter))client_parser,
                                     client,
                                     1024 + 512,
                                     RT_THREAD_PRIORITY_MAX / 3 - 1,
                                     5);
    ......
    return result;
}

static void client_parser(at_client_t client)
{
   
    int resp_buf_len = 0;
    const struct at_urc *urc;
    rt_size_t line_counts = 0;

    while(1)
    {
   
        if (at_recv_readline(client) > 0)
        {
   
            if ((urc = get_urc_obj(client)) != RT_NULL)
            {
   
                /* current receive is urc, try to execute related operations */
                if (urc->func != RT_NULL)
                {
   
                    urc->func(client->recv_buffer, client->cur_recv_len);
                }
            }
            else if (client->resp != RT_NULL)
            {
   
                /* current receive is response */
                client->recv_buffer[client->cur_recv_len - 1] = '\0';
                if (resp_buf_len + client->cur_recv_len < client->resp->buf_size)
                {
   
                    /* copy response lines, separated by '\0' */
                    memcpy(client->resp->buf + resp_buf_len, client->recv_buffer, client->cur_recv_len);
                    resp_buf_len += client->cur_recv_len;
                    line_counts++;
                }
                else
                {
   
                    client->resp_status = AT_RESP_BUFF_FULL;
                }
                /* check response result */
                ......
                client->resp->line_counts = line_counts;

                client->resp = RT_NULL;
                rt_sem_release(client->resp_notice);
                resp_buf_len = 0, line_counts = 0;
            }
        }
    }
}

从上面AT Client解析线程的初始化过程可以看出,当client_parser接收到来自AT Server的数据后,会判断是at_urc数据还是at_response数据,并根据情况进行处理。如果是at_urc数据,则调用相应的执行函数;如果是at_response数据,则将接收到的数据放到at_response结构体中,检查并设置响应状态at_resp_status。待解析完接收到的数据后,client_parser线程会释放信号量client->resp_notice,通知AT命令请求函数(当发送AT命令请求后,函数会阻塞等待AT Server返回的响应数据),已接收并解析响应数据,可以继续往下执行了。

继续介绍AT Client 初始化过程,在完成解析线程client->parser的初始化后,开始配置AT命令通讯的设备client->device(这里指的是UART2设备)。我们在前面已经启用了UART2设备(RT-Thread已经帮我们配置好了UART2引脚),所以可以直接通过rt_device_find 查找到目标设备,通过rt_device_open函数完成UART2的初始化配置。WiFi 模块有主动接收网络数据的需求,因此少不了配置接收回调函数,这里接收回调函数的作用跟前面介绍的在Finsh组件中的作用一样,都是释放一个信号量,让等待获取该信号量的函数开始从UART读取并处理这些数据。接收回调过程如下:

// rt-thread\components\net\at\src\at_client.c

static rt_err_t at_client_rx_ind(rt_device_t dev, rt_size_t size)
{
   
    int idx = 0;

    for (idx = 0; idx < AT_CLIENT_NUM_MAX; idx++)
    {
   
        if (at_client_table[idx].device == dev && size > 0)
        {
   
            rt_sem_release(at_client_table[idx]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值