本实验使用 ESP32 通过 AP 和 STA 两种方法实现 UDP 通信。 这个实验的代码为工程“4_6_wifi_UDP”目录。
4.6.1. 实验内容
(1) 在 AP 模式下,手机和 ESP32 开发板之间实现 UDP 通信。
(2) 在 STA 模式下,电脑和 ESP32 开发板之间实现 UDP 通信。
4.6.2. UDP 通信简介
UDP 是 User Datagram Protocol 的简称,中文名是用户数据报协议,是 OS(I Open System Interconnection,
开放式系统互联)参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768 是 UDP 的正式规范。UDP 在 IP 报文的协议号是 17。
UDP 协议全称是用户数据报协议,在网络中它与 TCP 协议一样用于处理数据包,是一种无连接的协 议。在 OSI 模型中,在第四层——传输层,处于 IP 协议的上一层。UDP 有不提供数据包分组、组装和不 能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP 用来 支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的 网络应用都需要使用 UDP 协议。UDP 协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些 类似协议所掩盖,但是即使是在今天 UDP 仍然不失为一项非常实用和可行的网络传输层协议。
与所熟知的 TCP(传输控制协议)协议一样,UDP 协议直接位于 IP(网际协议)协议的顶层。根据OSI(开放系统互连)参考模型,UDP 和 TCP 都属于传输层协议。UDP 协议的主要作用是将网络数据流量 压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前 8 个字节用 来包含报头信息,剩余字节则用来包含具体的传输数据。
UDP 是 OSI 参考模型中一种无连接的传输层协议,它主要用于不要求分组顺序到达的传输中,分组传 输顺序的检查与排序由应用层完成,提供面向事务的简单不可靠信息传送服务。UDP 协议基本上是 IP 协议 与上层协议的接口。UDP 协议适用端口分别运行在同一台设备上的多个应用程序。
UDP 提供了无连接通信,且不对传送数据包进行可靠性保证,适合于一次传输少量数据,UDP 传输的可靠 性由应用层负责。
下图是 UDP 通信的框图,从图中可以看到,服务器和客户端区别在于是否有绑定 bind 这个过程,我们在编 程中按 UDP 服务器做:
4.6.3. ESP32 函数介绍
这个实验也是使用到的标准 socket 接口,函数的参数和 window 上一致,如下:
创建 UDP socket 套接字,用 socket 函数。
设置 socket 的属性,用 setsockopt()函数,(可选)
绑定包含 IP 信息,地址信息的(IPv4)结构体。用 bind()函数
循环接收消息,用 recvfrom()函数
关闭 socket 套接字
4.6.4. 代码讲解
使用 vs code 展开本实验的工程目录,如下图:
我们的这个实验,components 文件夹下的 LCD 是显示屏的驱动代码,在 3.4 章有比较详情的讲解,文 件夹 wifi 是如何启动 AP 或者 STA 连接,另外的 wifi 就是本章要讲的 UDP 实现,程序的入口依然是在 main 下的 app_main.c 文件里。
下面按照程序启动的流程讲解。
(1) 程序启动
在 app_main 主函数里,通过宏 WIFI_AP 决定是启动 WiFi 的 AP 还是 STA 模式。
//用户函数入口,相当于 main 函数 void app_main()
{
......
#ifdef WIFI_AP wifi_init_ap();//启动 WIFI 的 AP
#else
wifi_init_sta();//启动 WIFI 的 STA
#endif
}
关于 AP 和 STA 模式的使用,我们这里不再讲解,在头文件 user_wifi.h 里,有以下的宏定义,在做实 验的时候一定要注意,需要根据实际情况去修改。
//定义就是 AP 模式,如果不定义就是 STA 模式
#define WIFI_AP
//是否使用静态 IP,如果不定义就是 DHCP 模式
//#define ESP32_STATIC_IP
#ifdef ESP32_STATIC_IP
//IP
#define DEVICE_IP "192.168.10.1"
//网关
#define DEVICE_GW "192.168.10.1"
(2) 启动 UDP 任务
在源文件 user_wifi.c 里,wifi 的事件回调函数里等待 wifi 的网络创建成功,接着使用函数 create_udp() 创建 UDP 任务,关键代码如下:
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
......
case SYSTEM_EVENT_AP_STACONNECTED:
printf("station:"MACSTR" join, AID=%d.\r\n", MAC2STR(event->event_info.sta_connected.mac), event->event_info.sta_connected.aid);
//有客户端连接,启动服务器 create_udp();
break;
......
case SYSTEM_EVENT_STA_GOT_IP:
printf("\r\nip:%s.\r\n",ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); printf("gw:%s.\r\n",ip4addr_ntoa(&event->event_info.got_ip.ip_info.gw)); printf("netmask:%s.\r\n",ip4addr_ntoa(&event->event_info.got_ip.ip_info.netmask));
s_ip_addr = event->event_info.got_ip.ip_info.ip;//保存 IP s_gw_addr = event->event_info.got_ip.ip_info.gw;//保存网关
s_netmask_addr = event->event_info.got_ip.ip_info.netmask;//保存掩码 lcd_display(1);
//连接上有客户端连接,启动服务器 create_udp();
break;
......
}
void create_udp()
{
xTaskCreate(&udp_ini_client, "udp_ini_client", 4096, NULL, 5, NULL);
(3) 创建 UDP 连接
通过 3 步实现的 UDP 连接:新建 socket、绑定 bind 和接收任务。注意一下,远程参数的 IP 默认使用 的是广播地址,当 ESP32 开发板收到第一帧数据后设置为实际的地址。
//创建 UDP 连接
void udp_ini_client(void *pvParameters){
......
//第一步:创建 socket
udp_socket = socket(AF_INET,SOCK_DGRAM,0);
......
//远端参数设置
dest_addr.sin_addr.s_addr = inet_addr("255.255.255.255"); //目标地址默认使用广播 dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(UDP_PORT);//目标端口
printf("Socket created, sending to 255.255.255.255:%d", UDP_PORT);
......
//第二步:绑定 bind
res = bind(udp_socket,(struct sockaddr *)&Loacl_addr,sizeof(Loacl_addr));
......
//第三步:创建接收任务函数 xTaskCreate(&udp_recv_data,"udp_recv_data",2048*2,NULL,10,&xUDPRecvTask);
//删除当前任务 vTaskDelete(NULL);
}
(4) 启动接收的任务
UDP 接收数据任务里调用了 recvfrom()接收数据,然后收到的数据通过 printf 输出到串口后,再使用函 数 udp_send_data()返回到发送方。
//UDP 接收任务
void udp_recv_data(void *pvParameters){ socklen_t socklen = sizeof(dest_addr); uint8_t rx_buffer[1024] = {0}; printf("create udp recv\n");
//测试 UDP 是否 OK
//可以删除
udp_send_data("udp test", strlen("udp test"));
while (1)
{
//接收数据
int len = recvfrom(udp_socket, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&dest_addr, &dest_addr_socklen);
if(len > 0){
rx_buffer[len] = 0; //未尾增加"\0"
printf("Received %d bytes: %s.\n", len, rx_buffer);
//接收到的数据在 rx_buffer 里,长度为 len 字节
//可以增加用户代码
//在这个实验里,返回数据给发送端 udp_send_data((char*)rx_buffer, len);
}
}
}
//UDP 发数据发送函数
void udp_send_data(char* data, int len){ if(udp_socket>0){
int err = sendto(udp_socket, data, len, 0, (struct sockaddr *)&dest_addr, des
t_addr_socklen);
if (err < 0) {
printf( "Error occurred during sending: errno %d", errno);
}
}
4.6.5. 实验过程
下面分别通过 ESP32 的 AP 模式和 STA 模式演示程序。
4.6.5.1. AP 模式
通过 wifi 的 AP 模式实现 TCP 服务器,首先要按下图修改 user_wifi.h,修改之后就可以按之前的方法编 绎和下载了。
配置下载串口、波特率、编绎和程序下载的详细过程请往回看 3.1.4,在这个实验里都是一笔带过。 (1) 把开发板通过 USB 线接到电脑上,通过设备管理器查看生成的串口。开发板在我们演示电脑上生
成的是 COM3。
(2) 在 menuconfig 菜单里配置下载程序串口。提供的例程配置的串口是 COM3,波特率为 921600。
(3) 通过 make all 编绎工程。
(4) 当编绎通过之后,使用命令 make flash 把程序下载到开发板上。或者参考 2.3.2 节,使用工具 下载。
(5) 使用串口工具打开开发板生成的串口,默认的波特率是 115200。 串口工具在目录:.\开发软件\串口工具-sscom32.rar。
(6) 按下开发板的复位键,让程序运行起来,显示屏显示
生成的 AP 是:taobao.com,密码是:12345678,开发板的 IP(也是 UDP 服务器的 IP):192.168.10.1, UDP 端口号:10000。
(7) 在手机上安装网络调试助手,软件位于目录:\开发软件\网络调试助手(android 版本).apk。
(8) 打开手机的 wifi,并连接上名称为“taobao.com”,密码是“12345678”,如果有修改请 按修改后的去连接。
(9) 打开手机上的网络调助手,配置和验证 UDP 通信。
4.6.5.2. STA 模式
通过 wifi 的 STA 模式实现 UDP 通信,首先要按下图修改 user_wifi.h,修改之后就可以按之前的方法编 绎和下载了。
配置下载串口、波特率、编绎和程序下载的详细过程请往回看 3.1.4,在这个实验里都是一笔带过。 (1) 把开发板通过 USB 线接到电脑上,通过设备管理器查看生成的串口。开发板在我们演示电脑上生
成的是 COM3。
(2) 在 menuconfig 菜单里配置下载程序串口。提供的例程配置的串口是 COM3,波特率为 921600。
(3) 通过 make all 编绎工程。
(4) 当编绎通过之后,使用命令 make flash 把程序下载到开发板上。或者参考 2.3.2 节,使用工具 下载。
(5) 使用串口工具打开开发板生成的串口,默认的波特率是 115200。
串口工具在目录:.\开发软件\串口工具-sscom32.rar。
(6) 打开按下开发板的复位键,让程序运行起来,当 ESP32 连接上我们设置的 AP 后,显示屏显示连接的 wifi 名称是:TP_LINK,密码是:12345678,UDP 的 IP 是(也是 ESP32 开发 板分配的 IP):192.168.1.104,端口是:10000。
(7) 要求实验的电脑和 ESP32 是连接在同一个路中器下,通过电脑可以 ping 通 ESP32 开发板。
(8) 在电脑上打开网络调试助手,软件位于目录:\开发软件\网络调试助手.rar,解压后打开设置并 如下图实验:
(9) 实验过程中,注意查看第 5 步的串口输出。
最后推荐一款开发套件,可以手淘扫码查看。