文章目录

  • 1. 模块选型
  • 2. 启动SoftAP模式
  • 3. 创建TCP服务端
  • 4.总结

 最近实现了一个无线数据采集模块,可以通过无线方式传输采集到的数据到手机或者PC,免除了连线的烦恼。使用手机作为上位机可以接收数据及发送控制命令,不用带着沉重的PC,在现场调试或者不方便连线的情况下方便快捷。

 模块使用stm32作为主控采集数据,ESP32作为无线模块,芯片间使用SPI交互数据,数据量小可以使用蓝牙BLE/SPP,数据量大使用Wifi,并且ESP32价格便宜使用方便。如果要采集的数据少可以直接使用ESP32作主控,又省掉一颗stm32。本模块的结构如下:

 本文主要介绍ESP32模块这部分,如何启动SoftAP模式以及TCP服务器,如何处理连接断开。

1. 模块选型

 支持Wifi、BT的芯片很多,本模块选用ESP32的原因主要有:

  1. ESP32同时支持Wifi、BT,并且大部分资料都是中文,在物联网领域应用广泛,稳定性有保证。

  2. 性价比高,作为一个20多元人民币的模块,带有双核最高160Mhz主频内核,还有外部Flash和RAM,单纯作为一个MCU使用都是绰绰有余的。

  3. 提供了模组形式,外围电路少,使用方便

  4. 本模块选用了ESP32-WROVER模组, 自带了4MB SPI Flash和8MB PSRAM,可以缓存长时间的采集数据。

 当然这个模块也有一些缺点,比如IO比较少单纯作为主控不太够,编译下载速度比较慢。

 选用ESP32-WROVER模组一个主要原因是自带的8M PSRAM可以缓存长时间的数据,STM32自带的SRAM比较少,而采集的数据大约是5Mbps,缓存在主控端显然是不现实的。而ESP32引出的IO可以使用SPI接口,最高速率10Mbps,可以满足采集数据要求。ESP32官方数据BT SPP速率1.6Mbps,而Wifi可以达到20Mbps,所以只能使用SPI + Wifi的组合。并且实测BT SPP在最高速率下手机端收到的包序不固定,导致难以解析,只有在SPP发送包中间加上至少5ms间隔,才可以让接收和发送的包序一致。

 本模块整体流程为,STM32采集数据,满一包后立即通过SPI发送到ESP32,而ESP32等待数据的Task收到数据后立刻填充到一个大的Ring buffer中,发送数据Task将buffer中数据分包发送出去。实测整个过程数据稳定,上位机解析也没有丢数据的情况。

2. 启动SoftAP模式

 ESP32使用SoftAP模式,这样上位机PC或手机只需要连接到对应的AP,就可以直接通信,省去了Wifi配网的步骤,而且后续TCP通信Server是固定的IP,上位机也比较好实现。稍有不便的是上位机连到ESP32的AP后就无法再通过Wifi连接外网。

Note:本文代码都基于esp-idf-v4.3

 SoftAP参考了IDF的"examples\wifi\getting_started\softAP",改动了几个地方。首先SSID根据本身mac变化,这样多个模块在一起可以根据SSID区分,代码如下:

#define ESP_WIFI_SSID      "ESP32"
#define ESP_WIFI_PASS      "12345678"void Esp_WifiApInit(void)
{......uint8_t ApMac[6];esp_wifi_get_mac(ESP_IF_WIFI_AP, ApMac);sprintf((char *)wifi_config.ap.ssid , "%s_%02X%02X" , ESP_WIFI_SSID , ApMac[4] , ApMac[5]);wifi_config.ap.ssid_len = strlen(ESP_WIFI_SSID) + 5;ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
}

 另一个修改和TCP服务端有关,在Wifi station连接后建立IP和MAC地址的映射表,目的是station断开Wifi连接后关闭socket。因为测试中发现,如果在连接socket的情况下,直接断开Wifi连接,socket不会立刻断开。网络上找到的查询IP和MAC映射都是ARP协议,但是没有找到ESP32如何使用,所以就自行实现了一个简单的映射表,主要利用分配IP的Event,将IP和MAC保存到一组表中。主要代码为:

typedef struct
{uint32_t ip;        //IPv4uint8_t mac[6];uint8_t connect;
} AP_TaskDef;AP_TaskDef APInfo[MAX_STA_CONN];
int8_t APIndex = -1;/*加入IP*/
int32_t Esp_PutAPClientIP(uint32_t addr)
{if (APIndex < 0){return -1;}APInfo[APIndex].ip = addr;APIndex = -1;ESP_LOGI(WIFI_TAG, "Put AP:%d", addr);return 0;
}/*加入MAC*/
int32_t Esp_PutAPClinetMAC(uint8_t* mac)
{for (size_t i = 0; i < MAX_STA_CONN; i++){if (APInfo[i].connect == 0){memcpy(APInfo[i].mac , mac, 6);APInfo[i].connect = 1;APIndex = i;return 0;}}return -1;
}/*移除IP*/
__attribute__((__weak__)) int32_t Esp_PopClientIP(uint32_t addr)
{return 0;
}/*移除MAC*/
int32_t Esp_PopAPClinetMAC(uint8_t* mac)
{for (size_t i = 0; i < MAX_STA_CONN; i++){if (memcmp(mac, APInfo[i].mac, 6) == 0){if (APInfo[i].connect == 1){APInfo[i].connect = 0;Esp_PopClientIP(APInfo[i].ip);ESP_LOGI(WIFI_TAG, "Pop AP:%d", APInfo[i].ip);APInfo[i].ip = 0;}return 0;}}return -1;
}static void wifi_event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{if (event_base == WIFI_EVENT){if (event_id == WIFI_EVENT_AP_STACONNECTED) {wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;ESP_LOGI(WIFI_TAG, "station "MACSTR" join, AID=%d",MAC2STR(event->mac), event->aid);Esp_PutAPClinetMAC(event->mac);} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;ESP_LOGI(WIFI_TAG, "station "MACSTR" leave, AID=%d",MAC2STR(event->mac), event->aid);Esp_PopAPClinetMAC(event->mac);}} else if (event_base == IP_EVENT) {if (event_id == IP_EVENT_AP_STAIPASSIGNED) {ip_event_ap_staipassigned_t* event = (ip_event_ap_staipassigned_t*) event_data;ESP_LOGI(WIFI_TAG, "IP assigned");// Add to tableEsp_PutAPClientIP(event->ip.addr);}}
}void Esp_WifiApInit(void)
{......wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));/*添加IP分配的Event*/ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,IP_EVENT_AP_STAIPASSIGNED,&wifi_event_handler,NULL,NULL));ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));......
}

Note:自行建立映射的方法比较麻烦,也许有更好的办法

 连接断开Wifi的log如下:

3. 创建TCP服务端

 TCP server参考了IDF的"examples\protocols\sockets\tcp_server",ESP32创建TCP server并等待客户端连接,需要修改支持多客户端,并且数据将可以同时发送到多个客户端。

 启动TCP server相关任务,Esp_TCPServerTask初始化并等待socket连接,Esp_TCPSendTask用于从数据buffer中取数据并发送到TCP客户端:

int32_t Esp_TCPServerStart(void)
{......ret = xTaskCreate(Esp_TCPServerTask, TCP_TAG, 4096,NULL, tskIDLE_PRIORITY + 1,&TCPTaskHandle);if (ret != pdPASS){ESP_LOGE(TCP_TAG, "Task create error");return ret;}ESP_LOGI(TCP_TAG, "Create RW task");ret = xTaskCreate(Esp_TCPSendTask, "Send", 4096,NULL, tskIDLE_PRIORITY + 1,&TCPSendHandle);if (ret != pdPASS){ESP_LOGE(TCP_TAG, "Task create error");return ret;}return 0;
}

Esp_TCPServerTask获取到一个连接客户端后,将客户端socket和IP地址放到客户端列表,同时新建Task接收数据。客户端断开后recv接口返回错误,服务器端也将关闭对应socket,并移除客户端列表。

void Esp_TCPRecvTask(void * pvParameters)
{......for(;;){len = recv(sock, rx_buffer, sizeof(rx_buffer), 0);if (len < 0) {ESP_LOGE(TCP_TAG, "Error occurred during receiving: errno %d", errno);Esp_PopClient(sock);break;} else if (len == 0) {ESP_LOGW(TCP_TAG, "Connection closed");Esp_PopClient(sock);break;} else {#if TCP_DBGESP_LOGI(TCP_TAG, "Recv:%d", len);esp_log_buffer_hex(TCP_TAG, rx_buffer, len);#endif}}vTaskDelete(NULL);
}void Esp_TCPServerTask(void * pvParameters)
{......ESP_ERROR_CHECK(Esp_TCPServerInit(&serverSocket));  //初始化socket,绑定端口for(;;){clientSocket = Esp_TCPServerAccept(serverSocket, &ip_addr); //等待客户端连接if (clientSocket < 0){ESP_LOGE(TCP_TAG, "Unable to accept");} else {if (Esp_PutClient(clientSocket, ip_addr) == 0)  //添加到客户端列表{int32_t ret;ret = xTaskCreate(Esp_TCPRecvTask,          //为客户端创建Task接收数据"Recv",4096,(void*)clientSocket,    //socket作为任务参数tskIDLE_PRIORITY + 1,NULL);if (ret != pdPASS){ESP_LOGE(TCP_TAG, "Task create error");}} else {ESP_LOGE(TCP_TAG, "Too many clients");}}}
}

 严格说由于IDF基于FreeRtos,添加、移除客户端列表需要放到临界区,实测中客户端数目不多,并且不会频繁连接断开,并没有出现异常。TCP客户端连接,发送数据,断开过程如下:

4.总结

 在一些连线不便的情况下,使用ESP32 TCP方式传输数据速率快,通信稳定,十分实用。用Wifi传输功耗比较高,如果用电池供电,需要注意电量。后续再介绍模块的其他部分,如SPI通信、Hanshake方法、Ring buffer使用等。

Github: https://github.com/songdaw/esp32_tcp_server
码云:https://gitee.com/songdaw/esp32_tcp_server

基于ESP32的TCP服务器相关推荐

  1. dtu tcp java_SpringBoot 2 整合 Netty 实现基于 DTU 的 TCP 服务器 之 客户端

    使用netty不是一天两天了,但是使用netty和DTU通讯还是第一次,接下来要做DTU的通讯协议,对接工作还没有正式开始,只收到一个简单的DTU协议文档,里面的内容大概是下面表格中的样子. 位数 内 ...

  2. 【正点原子FPGA连载】 第三十二章基于lwip的TCP服务器性能测试实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

    第三十二章基于lwip的TCP服务器性能测试实验 上一章的lwip Echo Server实验让我们对lwip有一个基本的了解,而Echo Server是基于TCP协议的.TCP协议是为了在不可靠的互 ...

  3. Java 使用Socket 实现基于DTU的TCP服务器 + 数据解析 + 心跳检测

    在物联网时代,DTU的运用非常广泛 :环境监测中通过DTU将传感器的数据远程传输至云服务器也是比较常见的用法.下面我来分享一下我的项目经验 1.物理连接拓扑 2.服务器后台流程 3.代码 设备TCP服 ...

  4. 基于ESP32搭建物联网服务器十三(自已搭建一个MQTT服务器)

    在之前的文章中:ESP32搭建WEB服务器十二(使用MQTT协议与ESP32互动)_你的幻境的博客-CSDN博客 我们已经实现了ESP32通过MQTT协议连接到公共MQTT服务器上,但是公共服务器在稳 ...

  5. esp32做tcp服务器完成tcp广播

    开发板用的是NodeMCU-32,库是esp-idf的4.3版本,从tcp server的例程改过来的. #include <string.h> #include <sys/para ...

  6. linux网络编程(6)基于多进程的TCP服务器与客户端编程

    服务器端: #include <netdb.h> #include <sys/socket.h> #include <time.h> #include <un ...

  7. 使用python基于socket的tcp服务器聊天室

    # coding=utf-8 import socket,threading,time '''代码说明:1.创建一个字典用于接受客户端的用户名和信息2.创建一个类对象client用于编写客户端套接字对 ...

  8. 基于ESP32搭建物联网服务器一(AP配网)

    目录 一.WiFi.mode();设置配网模式 二.WiFi.softAP();设置ESP32的WIFI属性 三.WiFi.softAPConfig();设置ESP32的IP,网关,子网掩码,DHCP ...

  9. 基于select模型的TCP服务器

    之前的一篇博文是基于TCP的服务器和客户机程序,今天在这我要实现一个基于select模型的TCP服务器(仅实现了服务器). socket套接字编程提供了很多模型来使服务器高效的接受客户端的请求,sel ...

最新文章

  1. 报文如何截取时间_5种报文、8种邻居状态机详解OSPF工作原理
  2. R语言ggplot2可视化小提琴图(violin plot)并使用ggsignif添加分组显著性(significance)标签
  3. Struts2如何实现MVC,与Spring MVC有什么不同?
  4. 企业选择网站制作公司需要关注这几点!
  5. mysql HEX将字符串或数字转化为16进制字符串、UNHEX将16字符串转化成二进制
  6. 在 Xunit 中使用依赖注入
  7. Redis和数据库 数据同步问题
  8. Python SHA1加密算法
  9. 云小课 | 不小心删除了数据库,除了跑路还能咋办?
  10. php分享(三十六)mysql中关联表更新
  11. 应届生如何自学 Java、成功拿下腾讯 Offer?
  12. 2021副高考试成绩查询荆州,湖北荆州2021年4月自考成绩查询入口开通
  13. 大数据分析有什么特点
  14. python爬取腾讯vip_用Python批量爬取付费vip数据,竟然如此简单
  15. windows下安装wget
  16. PHPCMS 前台模板集合
  17. github100天python_GitHub - jazeyoung/Python-100-Days: Python - 100天从新手到大师
  18. 向前的快捷键_快速提高逼格的电脑快捷键你懂多少?
  19. 一线城市与三线城市的IT生活——从《机器灵 砍菜刀》说开去
  20. 计算机辅助设计基础试题,CAD基础知识自测题

热门文章

  1. 大家能不能在百忙之中 想想鸟姐的话
  2. 手把手教你用java发送邮件
  3. 「TCG 规范解读」初识基础设施工作组
  4. app逆向--美图秀秀sig参数
  5. Spring boot (21)多数据源引起的循环引用和AutoConfigureAfter失效的问题
  6. 【MATLAB】P图神器,初露锋芒:第一周作业
  7. Google Play App Signing 更换签名
  8. windows如何查看内存条型号信息cpu型号信息 # 包括 内存条个数 和 cpu个数
  9. Coverage基础知识整理
  10. android 浏览器隐藏地址,移动端隐藏手机浏览器的地址栏一下底部的菜单栏