ESP32 单片机学习笔记 - 07 - TCP连接

前言 :之前只用了Wifi和Ethernet的连接,例程一下载就能连接的,但是没有讲到通讯。所以我还是很不懂。这次教程接触到了TCP/IP协议了,在使用例程时,就明显感受到,起始wifi和以太网在其中扮演什么角色了。

ESP32 单片机学习笔记 - 07 - TCP连接

开源教程:第十六章 ESP32的TCP连接
编程指南:lwIP,lwip是嵌入式的简约版tcp/ip协议,开源且轻量级(个人理解)。

一、例程实践

官方例程:examples/protocols/sockets/目录下的tcp_client/github传送门链接。
官方例程②:examples/protocols/asio/目录下的tcp_echo_server/github传送门链接。

这次我选择直接上手例程,先看看实验现象再看分析步骤。因为我发现步骤代码里没什么东西……这esp32封装的也太彻底了。灵异事件:我昨晚还能在 VS Code 的esp插件例程中看到上面2个例程的页面。今天早上我起来写笔记时却怎么也找不到了。最后无奈到系统目录搜索文件夹才知道原来是分别在两个目录下的。不知道一晚改了什么,插件居然不找子目录下的例程了。又浪费了几小时找问题,不知道到底是哪里出现问题导致例程目录不会找子目录了,这下完蛋了,之后找例程都要去本地目录里找了。

1)建立TCP客户端 - tcp_client

  • 先拷贝项目工程,然后看README.md的说明文档。得知例程的功能如下:

应用程序创建一个 TCP 接口并尝试使用预定义的 IP 地址和端口号连接到服务器。
成功建立连接后,应用程序发送消息并等待应答。 服务器回复后,应用程序将收到的回复打印为 ASCII 文本,等待 2 秒并发送另一条消息。

  • 单片机esp32建立了预设的IP和端口的客户端,那么我们还需要一个服务器来与它对接。可以选择一个终端工具 netcat ,也可以选择一些上位机(网络调试助手)。

在这里插入图片描述

小插曲 - Netcat 工具

  1. 下载本体:Netcat,点击最新版本,即第二个。(我目前显示的是1.12)
  2. 解压文件,将本体存好,路径最好不要有中文,且要短。然后在系统变量PAth里添加该本体文件路径。
  3. 网传还有另一种方法,就是单纯将本体中的一个nc.exe文件放到系统目录内。个人不喜欢,且操作时也没成功。
  4. 经历一二步就已经算安装完了,然后就可以开始用了,看不懂的教程
  5. 测试,打开2个终端,一个输入nc -l -p 99999,一个输入nc localhost 99999,这样2个终端就链接起来了。任意一一个终端输入东西按回车都会发送另一个终端里。
  • 例程中还提供了一个python脚本,可以运行该脚本来建立服务器与客户端连接。使用前需要先安装python软件包,和把$IDF_PATH/tools/ci/python_packages 添加到 PYTHONPATH中。

该脚本有2个新的软件包需要安装netifacesttfw_idf,前者倒是安装好了,但是后者怎么也找不到,我怀疑是不是idf内的脚本了。然后PYTHONPATH路径我也不知道在哪里添加……所以我没使用这个脚本。

  • 在下载程序之前要先配置网络idf.py menuconfig,选择使用WiFi联网,还是Ethernet联网,后者和上一个笔记中的操作一样,选择协议、模块、引脚、选择时钟频率。前者,也是之前笔记记载过的,不过当时没太在意界面配置,而是直接修改代码。
  • 除了在idf终端使用界面配置外,我发现原来VS code内也可以配置,按F1后,在输入ESP-IDF:SDK Configuration editor指令,就进入到了SDK 配置编辑器。然后发觉之前生成的配置文件名也确实是sdkconfig。无论在终端还是IDE,打开这个配置编辑器都要加载一会,对比之下,为了稳定快速。果然还是终端比较好用……
  1. Top - Example Connection Configuration中可以配置使用WiFi还是Ethernet,这个wifi和ethernet都是让esp32连接上网络的。不要理解成ethernet转wifi。(另外,不可以2个模式都选择打开,不然esp32会一直报错,然后一直复位)
  2. 如果设置了ethernet模式,别忘记了还需要到Top - Component config - Ethernet - Support ESP32 internal EMAC controller修改以太网的引脚、时钟频率。
  3. Top - Example Configuration中配置esp32客户端要连接IP地址和端口。这里我设置我自己电脑的IP地址和端口,那就可以实现互连了。如下图。

在这里插入图片描述

  • 如果你设置没有问题,然后连接也没问题,那监视器就会有以下输出:

在这里插入图片描述

  • 然后上位机就会有以下输出。

在这里插入图片描述

  • 注意,上位机要提前打开连接,这样方便esp32刚上电连接上网络后直接连接。
  • 例程里有写着,如果超时没连接就会退出程序,不会持续等待连接。所以如果你发现监视器显示esp32只获取了IP地址后没有下一步了。就可以尝试复位单片机重新运行程序看看。
  • 在监视器模式下单片机好像无法复位,所以我是选择在终端里退出监视器,然后再退出监视器。
  • 而且程序里设定每次收到信息后打印会有2秒的延时,所以不要想着做压力测试(收发速度测试)。

2)总结

  • 经过wifi和ethernet的连接例程,终于到了能通讯例程。我原本以为wifi和ethernet会像uart那样,连接上就是可以直接获取信息。现在看来,前者只是底层的接口通讯协议,然后往上一层,网络端也有另外一套通讯协议……
  • 类比uart的使用,本身有通讯协议,然后不同使用者又会另外定制通讯协议,再次打包解包uart的数据。比如我现在使用的蓝牙调试器app,其通讯协议就规定了帧头帧尾和校验位。
  • tcp/ip协议就不只是帧头帧尾校验位这种形式了,还有多次握手模式等定义。

第二个例程的实现功能类似,在第一个例程的互连基础上相互通讯,第一个例子只能单向通讯(服务端到客户端)。

二、TCP/IP协议 - 科普

1)科普了解

相关资料

  1. 互联网知识基础-TCP/IP协议簇
  2. 互联网知识基础-传输层
  3. TCP和UDP的区别和优缺点
  4. 百科:TCP/IP协议
  • 总结:
  • TCP/IP协议不是专门指tcp或ip,而是一个模型,具有4层结构,包含多个协议。不过取其中具有代表性的tcp和ip来命名。
  • 这个模型中,wifi/ethernet属于接口协议,tcp/udp属于网络通讯协议,ip属于地址协议,再往上还有准备学的http等协议。
  • 其中传输层中的tcpudp协议分别针对准确和快速两种特点,在不同场景使用。

2)三次握手/四次握手

我本来并不在意这个定义的,只是看介绍时没看懂。然后查小破站,发现原来这还是个面试常考题目?那就得了解一下了。
在小破站上找到一个讲解视频,不过视频我还没看,我发现评论区就有很好的比喻。然后我就把评论保存下来,视频直接不看了。白嫖

三次握手: TCP/IP 协议是传输层的一个面向连接的安全可靠的一个传输协议,三次握手的机制是为了保证能建立一个安全可靠的连接,那么第一次握手是由客户端发起,客户端会向服务端发送一个报文,在报文里面:SYN标志位置为1,表示发起新的连接。当服务端收到这个报文之后就知道客户端要和我建立一个新的连接,于是服务端就向客户端发送一个确认消息包,在这个消息包里面:ack标志位置为1,表示确认客户端发起的第一次连接请求。以上两次握手之后,对于客户端而言:已经明确了我既能给服务端成功发消息,也能成功收到服务端的响应。但是对于服务端而言:两次握手是不够的,因为到目前为止,服务端只知道一件事,客户端发给我的消息我能收到,但是我响应给客户端的消息,客户端能不能收到我是不知道的。所以,还需要进行第三次握手,第三次握手就是当客户端收到服务端发送的确认响应报文之后,还要继续去给服务端进行回应,也是一个ack标志位置1的确认消息。通过以上三次连接,不管是客户端还是服务端,都知道我既能给对方发送消息,也能收到对方的响应。那么,这个连接就被安全的建了。

四次握手:四次握手机制也是由客户端去发起,客户端会发送一个报文,在报文里面FIN位标志位置一,当服务端收到这个报文之后,我就知道了客户端想要和我断开连接,但是此时服务端不一定能做好准备,因为当客户端发起断开连接的这个消息的时候,对于服务端而言,他和还有可能有未发送完的消息,他还要继续发送,所以呢,此时对于服务端而言,我只能进行一个消息确认,就是我先告诉服务端,我知道你要给我断开连接了,但是我这里边还可能没有做好准备,你需要等我一下,等会儿我会告诉你,于是呢,发完这个消息确认包之后,可能稍过片刻它就会继续发送一个断开连接的一个报文啊,也是一个FIN位置1的报文也是由服务端发给客户端的啊,这个报文表示服务端已经做好了断开连接的准备,那么当这个报文发给客户端的时候,客户端同样要给服务端继续发送一个消息确认的报文一共有四次,那么,通过这四次的相互沟通和连接,我就知道了,不管是服务端还是客户端都已经做好了断开连接的准备,于是连接就可以被断开啊,这是我对三次握手和四次挥手的一个理解。

巧妙比喻
| 【三次握手】|
| 男:我们在一起吧|
| 女:好的啊|
| 男:好的,从现在开始吧|
| 【四次挥手】|
| 男:我们分手吧|
| 女:我想一下|
| 女:我们分手吧|
| 男:好的,现在就结束吧|

  • 个人理解:连接时,客户端和服务器都需要知道对方能收能发。所以三次握手,2个来回。断连时,服务器不一定能立刻断开,所以需要发一个中间等待的握手。所以断连时需要多一次握手。

三、编程指南

我发现原来个例程的内容是归属到了API 指南 - lwIP TCP/IP 协议栈传送门,为了防止中文版比英文版缺失东西,我们还是直接看英文版吧。因为中文版也是全英文……

  • ESP-IDF使用开源lwIP轻量级TCP/IP栈。ESP-IDF版本的lwIP (esp-lwip)与上游项目相比有一些修改和补充。
  • 百科介绍:lwIP,lwip提供三种API:1)RAW API 2)lwip API 3)BSD API。esp32支持的就是第三种,BSD API

1. BSD 接口 API - BSD Sockets API

BSD Sockets API是一个通用的跨平台TCP/IP Sockets API,起源于UNIXBerkeley标准发行版,但现在已在POSIX规范的一部分中标准化。BSD Sockets有时被称为POSIX SocketsBerkeley Sockets。 示例:protocols/sockets/tcp_server 、 tcp_client 、 udp_server 、 udp_client 、 udp_multicast 、 http_request。

2. 常用函数

ESP32的TCP接口介绍
新建socket函数:socket();
连接函数:connect();
关闭socket函数:close();
获取socket错误代码:getsocketopt();
接收数据函数:recv();
发送数据函数:send();
绑定函数:bing();
监听函数:listen();
获取连接函数:accept();

ESP32使用的是LwIP,LwIP是特别适用于嵌入式设备的小型开源TCP/IP协议栈,对内存资源占用很小。ESP-IDF即是移植了LwIP协议栈。学习了解LwIP,给大家推荐本书,《嵌入式网络那些事:LwIP协议深度剖析与实战演练》。

我们的这个例程是直接怼的是标准socket接口(内部是LwIP封装的),没有用LwIP的,关于LwIP的接口讲解在Websocket中讲解,用法都是一样,知道流程后,API调用即可,处理好异常。流程+接口,打遍无敌手。LwIP的教程可以参考安富莱、野火的文档。

src/include/lwip/socket.h文件中可以看到下面的宏定义,lwip的socket也提供标准的socket接口函数。

3. Socket 错误处理

  • BSD Socket错误处理代码对于稳定的socket应用程序非常重要。socket错误处理通常涉及以下几个方面:①检测错误。②获取错误原因代码。③根据原因代码处理错误。
  • 在lwIP中,我们有两种不同的场景来处理socket错误:一种是返回错误代码,socket API返回值,不含错误原因;一种是返回错误信息,不能直接感觉错误代码来判断。
  • 常用的Socket错误原因码:传送门链接

4. Socket 配置

  • 使用getsockopt()setsockopt()函数获取/设置每个Socket选项。

5. Netconn API

  • (略)

6. lwIP FreeRTOS Task

  • (略)

7. 总结

  • 后面越来越看不懂了……暂时跳过吧,先看看代码。

四、代码解析

  1. 第一步开始熟悉的nvs_flash_init()初始化,esp_netif_init()初始化,还有默认事件循环。
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
  1. 然后是一个很特别的打包函数example_connect(),在examples/protocols/README.md说明文档中对它有详细说明。
  • 简单点说就是个专门为例程打包的初始化调用函数。其原型在examples/common_components/protocol_examples/common/connect.c中。是为了省略例程中非重点的初始化。配合idf.py menuconfig指令可实现快速修改配置。
  • 简单的example_connect()函数不能处理超时,不能优雅地处理各种错误条件,只适合在示例中使用。真正开发时应该参考基础配置例程,要求完整稳定的初始化过程。
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
    * Read "Establishing Wi-Fi or Ethernet Connection" section in
    * examples/protocols/README.md for more information about this function.
    */
ESP_ERROR_CHECK(example_connect());
  1. 最后一步建立任务,简单粗暴死循环运行tcp部分,包含创建,接收,发送。貌似没有初始化。只需要配置参数创建就可以了。
  • 这次的例程显得没那么专业(繁琐)打包,简单(方便)的排列几个函数,然后死循环调用。
  • 创建socket、连接服务器、发送数据、接收数据、关闭socket。一气呵成。每一步后还跟有错误检查,处理异常。
xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);

static void tcp_client_task(void *pvParameters)
{
    char rx_buffer[128];
    char host_ip[] = HOST_IP_ADDR;
    int addr_family = 0;
    int ip_protocol = 0;

    while (1) 
    {
        //配置连接服务器信息:端口+ip
        struct sockaddr_in dest_addr;
        dest_addr.sin_addr.s_addr = inet_addr(host_ip);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(PORT);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;

        //新建socket
        int sock =  socket(addr_family, SOCK_STREAM, ip_protocol);
        if (sock < 0) 
        {
            //打印报错信息
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            //新建失败后,直接退出,懒得关闭新建的socket,也不再准备下次新建
            break;
        }
        ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT);

        //连接服务器
        int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6));
        if (err != 0) 
        {
            //打印报错信息
            ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
            //新建失败后,直接退出,懒得关闭新建的socket,也不再准备下次新建
            break;
        }
        ESP_LOGI(TAG, "Successfully connected");

        while (1) 
        {
            //发送数据函数
            int err = send(sock, payload, strlen(payload), 0);
            if (err < 0) 
            {
                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                break;
            }

            //读取接收数据
            int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
            // Error occurred during receiving 接收时出错
            if (len < 0) 
            {
                //打印错误信息
                ESP_LOGE(TAG, "recv failed: errno %d", errno);
                break;
            }
            // Data received 收到的数据
            else 
            {
                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string 无论接收到什么,都以null结束,并将其视为字符串
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);
            }

            // 每次循环都有2秒的时间间隔
            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }

        // 如果连接成功,又退出来,那么会执行到这里,会关闭,再重新进入循环,再次开启
        if (sock != -1) 
        {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            //关闭之前新建的socket,等待下次新建
            close(sock);
        }
    }
    // 可以通过传递 NULL 值以 vTaskDelete ()来删除自己,但是为了纯粹的演示,传递的是任务自己的句柄。
    vTaskDelete(NULL);           
}
  • 最后一行的vTaskPrioritySet()API函数 ,传送门

  • 第二个例程的代码居然是c++写的,然后我临时2小时快速入门c++的类对象后,感觉差不多了。再看例程发现,自己还是太菜了。第二个例程暂时看不懂,以后再补上。

五、总结

  • 那到这里也算简单的完成了网络连接和通讯了,那我简单的把电机控制写上,再写个上位机接收,那是不是就完了?
  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 要将ESP8266-01S连接到32单片机,需要进行以下步骤: 1. 准备好ESP8266-01S模块和32单片机。 2. 将ESP8266-01S模块的VCC引脚连接到32单片机的3.3V电源引脚,GND引脚连接到32单片机的GND引脚。 3. 将ESP8266-01S模块的TX引脚连接到32单片机的RX引脚,RX引脚连接到32单片机的TX引脚。 4. 将ESP8266-01S模块的CH_PD引脚连接到32单片机的任意IO引脚,将ESP8266-01S模块的RST引脚连接到32单片机的任意IO引脚。 5. 在32单片机上编写代码,使用串口通信与ESP8266-01S模块进行通信,实现WiFi功能。 需要注意的是,ESP8266-01S模块的工作电压为3.3V,不能直接连接到5V的单片机引脚上,否则可能会损坏模块。同时,ESP8266-01S模块的RX引脚和TX引脚需要使用逻辑电平转换器进行转换,以保证与32单片机的通信正常。 ### 回答2: ESP8266-01S是一款Wi-Fi模块,它可以实现通过无线方式连接互联网。32单片机是一种低功耗、高性能的单片机,它可以用来控制各种电子元件进行各种任务。如果将ESP8266-01S与32单片机连接起来,可以实现通过Wi-Fi控制32单片机,将其连入互联网。 首先,将ESP8266-01S连接到32单片机的串口上,使用串口通信协议进行通信,以便在32单片机程序中进行对ESP8266-01S的控制。然后,在32单片机程序中使用类似AT指令的代码来对ESP8266-01S进行配置,包括设置Wi-Fi SSID和密码等。这些配置信息可以在开机时自动加载,以便ESP8266-01S可以自动连接到Wi-Fi网络。 接下来,可以用32单片机来控制ESP8266-01S输出HTTP请求,以便向外部的Web服务器访问各种资源。在32单片机中,可以使用HTTP客户端库来实现HTTP请求的发送和接收,以便接收来自服务器的响应。在此过程中,ESP8266-01S负责将数据通过Wi-Fi传输到外部服务器,并将服务器的响应传回给32单片机。 最后,可以通过32单片机程序来实现设备的控制和数据的收集/发送。例如,可以将32单片机用于控制家庭设备,然后通过ESP8266-01S连接到互联网,以便远程访问和控制这些设备,或者以便发送数据到外部服务器以进行数据分析等操作。 总之,ESP8266-01S和32单片机连接为物联网的实现提供了便利的方案。可以实现无线连接到互联网世界,并将嵌入式设备带到新的高度。 ### 回答3: ESP8266-01S模块是一种低成本的Wi-Fi模块,主要用于连接无线网络。同时,ESP8266-01S模块也可以作为一个智能芯片,通过串口通信控制其他单片机。其中,32单片机是一种低功耗、高性能的ARM架构微控制器,通常用于嵌入式系统中。在实际应用中,我们可以通过以下步骤将ESP8266-01S模块和32单片机连接起来。 第一步,准备好硬件连接。ESP8266-01S模块和32单片机之间可以通过串口连接。我们可以通过将ESP8266-01S模块的TX引脚连接到32单片机的RX引脚,将ESP8266-01S模块的RX引脚连接到32单片机的TX引脚来实现串口连接。 第二步,编写ESP8266-01S模块的代码。我们可以使用AT指令来控制ESP8266-01S模块,从而实现与32单片机之间的通信。首先,我们需要将ESP8266-01S模块设置为AP模式或STA模式,以实现Wi-Fi连接。接着,我们可以使用AT+CIPSTART指令来建立TCP连接,然后使用AT+CIPSEND指令发送数据。发送的数据可以是字符串、二进制数据等等。 第三步,编写32单片机的代码。我们需要使用32单片机的串口模块来接收ESP8266-01S模块发送的数据。在接收到数据后,我们可以对数据进行处理,执行一些特定的操作。 总之,通过以上步骤,我们可以将ESP8266-01S模块与32单片机连接起来,从而实现无线网络通信和其他应用。在实际应用中,我们可以将32单片机作为主控芯片,连接多个ESP8266-01S模块,实现更加复杂的控制和通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值