mbed-os 5 上的 Lwip 没有建立正确的以太网连接
Lwip on mbed-os 5 doesn't make a proper ethernet connection
我的目标是将 UDP 数据包从微控制器(核板)发送到 PC - 就这么简单。 更新:我成功达到了目标。我将在这里解释我是如何做到的,以及我最初偶然发现的问题。非常感谢 Whosebug 社区和 LinkedIn ARM 小组的人员帮助我!
1.系统概览
我的系统如下:
1.1 微控制器端
- Nucleo_F767ZI 带有 STM32F767ZI 微控制器的开发板。该板有一个以太网连接器。
- Mbed-OS5操作系统。
- lwip 库(它是 Mbed-OS 5 操作系统的一部分)。
1.2 PC端 🖳
- Windows10、64 位。
- 空闲以太网端口(互联网通过 WiFi,这就是未使用此以太网端口的原因)。
- Wireshark 看微控制器是否真的发出UDP 数据包。稍后将在 Python 中对它们进行解释。首先我想在 Wireshark 中查看数据包。
1.3 连接
最初我将 nucleo 板直接连接到我的电脑,没有涉及路由器或交换机。这样的设置需要 "static IP configuration"。我会将 PC 配置为 IP 地址为“192.168.1.10”,将微控制器配置为“192.168.1.20”。我知道这行得通,因为我以前用 PIC 微控制器做过。
但这种情况有点不同。我在微控制器上安装了一个小操作系统 运行ning:mbed-os v5。该操作系统使用 Lwip(轻量级 ip 堆栈)。虽然应该可以os配置一个"static IP address",但是目前软件不支持。可以在此处找到更多信息:https://developer.mbed.org/questions/74145/EthernetInterfaceinit-mbed-os-5x-not-wor/。因为没有办法在单片机上配置静态IP地址,所以需要一个DHCP服务器。微控制器在没有IP地址的情况下连接到网络,并广播一个带有问题的数据包:"What IP address should I use on this network?"。如果单片机没有得到答案,它会广播几次问题,最后放弃。
如果有DHCP服务器连接到网络,它会收到问题并交出一个免费的IP地址。这就是你所需要的。有几种方法可以提供这样的 DHCP 服务器:
- 在您的 Windows PC 上安装 DHCP 服务器。这是将交换机和路由器排除在外的唯一方法。 nucleo 板直接连接到 PC。当微控制器广播问题时,Windows PC 交出一个 IP 地址。之后就可以开始通信了。
- 扔一个Raspberry PI进入游戏。 Raspberry PI 应该 运行 一个 DHCP 服务器。将三个设备(Windows PC、Raspberry PI 和 Nucleo 板)连接到交换机。
- 买一个简单的路由器。家用路由器内置了 DHCP 服务器。将 Windows PC 和 Nucleo 板连接到此路由器的 LAN 端口。
我决定选择第三个选项。下图显示了与我的 Netgear R7000 路由器的连接:
2。路由器设置
路由器内的 DHCP 服务器将向连接到它的所有设备分配 IP 地址。我的路由器会将 IP 地址“192.168.1.2”分配给第一台设备,“192.168.1.3”分配给第二台设备,依此类推,直到“192.168.1.254”。但这有点问题。 PC 和微控制器如何知道彼此的 IP 地址?也许有更聪明的解决方案,但我想出了以下方法。
每个 DHCP 服务器都有一个 table 和 "reserved IP addresses"。在此 table 中,您可以添加设备并为其分配静态 IP 地址。因此,每当该设备请求 IP 地址时,DHCP 服务器都会查看 table 并移交您配置的地址。但是 DHCP 服务器如何识别设备呢?它可以通过 MAC 地址识别设备。每个设备都有一个全球唯一的 MAC-6 字节地址。
这就是我将 Windows PC 和我的微控制器添加到 table:
的方式
- 第 1 步:将计算机连接到路由器的 LAN 端口之一。打开路由器。
- 第 2 步:确保计算机上的所有 WiFi 已断开连接。我遇到过很多这方面的问题。 (*)
- 第 3 步:打开浏览器,然后浏览“192.168.1.1”。这是我的路由器主页。您的路由器可能有所不同。
- 第 4 步:登录(我的路由器默认登录用户名是 "admin",密码是 "password")
- 第 5 步:将 Windows PC 和微控制器板添加到 table。您必须先查找他们的 MAC-地址 (**):
2.1 断开所有 WiFi (*)
这应该是一个简单的步骤,但是 Windows 让它变得困难。 Windows 可能会非常顽固并自动重新连接到周围的 WiFi 网络,即使您取消选中 自动连接 框也是如此!要强制 Windows 听你的,你可以使用 cmd 终端。打开具有管理员权限的 cmd 终端。接下来,键入以下命令以查看您所有的 WiFi 配置文件:
> netsh wlan show profiles
将以下命令应用于您的 PC 顽固连接的 ose WiFi 配置文件:
> netsh wlan set profileparameter name="someWifiName" connectionmode=manual
现在您可以断开与该 WiFi 网络的连接,Windows 将不再自动重新连接。
2.2 找到您电脑的 MAC 地址 (**)
我就是这样找到我电脑的 MAC-地址的。请记住,一台计算机可以有多个 MAC 地址。用于以太网端口的端口与用于无线连接的端口不同!在 Windows cmd 终端输入 ipconfig /all
。我得到以下输出:
# Note: this is the correct item!
# --------------------------------
Ethernet adapter Local Area Connection:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
Description . . . . . . . . . . . : Intel(R) Ethernet Connection (2) I219-LM
Physical Address. . . . . . . . . : C8-xx-xx-xx-xx-01 # Replaced some numbers by x for security :-)
DHCP Enabled. . . . . . . . . . . : Yes
Autoconfiguration Enabled . . . . : Yes
确保您查看的是联系人列表中的正确项目。 Ethernet adapter Local Area Connection
对我的电脑来说是正确的,因为网卡描述符合预期:Intel(R) Ethernet Connection (2) I219-LM
。之前,我正在查看列表中的另一个项目,标记为 Ethernet adapter Ethernet
:
# Note: this is the wrong item!
# ------------------------------
Ethernet adapter Ethernet:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
Description . . . . . . . . . . . : TAP-Windows Adapter V9
Physical Address. . . . . . . . . : 00-xx-xx-xx-xx-F7
DHCP Enabled. . . . . . . . . . . : No
Autoconfiguration Enabled . . . . : Yes
先生Joel C(请参阅下面的回答)通知我该项目的网卡是 TAP-Windows Adapter V9
。显然那是一个虚拟网卡。这让我花了很多时间os。谢谢 Joel C 先生帮助我!
另请确保 DHCP Enabled 和 Autoconfiguration Enabled 已打开!
2.3 找到您的 Nucleo 开发板的 MAC 地址 (**)
我使用以下代码找到了我的 Nucleo 板的 MAC-地址:
#include "lwip-interface/EthernetInterface.h"
//1. Make an ethernet object
EthernetInterface eth;
//2. Try to connect
eth.connect(); // <- This line will not work now,
// but at least it will help you to find out your
// own MAC-address.
//3. Print the MAC-address
logger.printf("Controller MAC Address is: %s\r\n", eth.get_mac_address());
我通过串口得到的打印输出是(为了安全我用 x 替换了一些数字):
Controller MAC Address is: 00:xx:xx:xx:xx:40
3。微控制器代码
这是微控制器上 运行 的代码。我的 main.cpp
文件的灵感来自 https://forums.mbed.com/t/udp-receive-with-nucleo-f767zi/1806.
的 Mbed-os 论坛上的代码。
#include <string>
using std::string;
#include "mbed.h"
#include "lwip-interface/EthernetInterface.h"
static Serial logger(USBTX, USBRX);
static DigitalOut led1(LED1);
// IP addresses
#define IP_COMPUTER "192.168.1.10" // Make sure these IP addresses correspond to the
#define IP_NUCLEO "192.168.1.20" // table of 'reserved IP addresses' you have setup in
// your routers DHCP server!
// Ethernet settings
const int PORT_T = 50000;
const int PORT_R = 50001;
EthernetInterface eth;
static void udp_tx_thread_func();
static void udp_rx_thread_func();
static Thread udp_tx_thread;
static Thread udp_rx_thread;
int main()
{
// 1. Initialize the serial logger
logger.baud(115200);
logger.printf("\r\n\r\nApplication started\r\n");
// 2. Initialize and start the UDP connection
eth.connect();
logger.printf("Controller MAC Address is: %s\r\n", eth.get_mac_address());
logger.printf("Controller IP Address is: %s\r\n", eth.get_ip_address());
Thread::wait(200);
udp_tx_thread.start(udp_tx_thread_func);
udp_rx_thread.start(udp_rx_thread_func);
while (true)
{
led1 = !led1;
Thread::wait(500);
}
}
//------------------ Ethernet --------------------------------------------------
static void udp_tx_thread_func()
{
UDPSocket socket(ð);
SocketAddress sock_addr(IP_COMPUTER, PORT_T);
static uint32_t out_buffer[3];
while(true)
{
Thread::wait(100);
// Send 3 values of 32-bit each
out_buffer[0] = 150500;
out_buffer[1] = 255300;
out_buffer[2] = 54;
int ret = socket.sendto(sock_addr, &out_buffer[0], 12); // 3 values of 32-bit equals 12 bytes
//logger.printf("sendto return: %d\r\n", ret);
}
}
static void udp_rx_thread_func()
{
UDPSocket socket(ð);
SocketAddress sock_addr;
int bind = socket.bind(PORT_R);
logger.printf("bind return: %d\n", bind);
char buffer[256];
while(true)
{
//logger.printf("\nWait for packet...\n");
int n = socket.recvfrom(&sock_addr, buffer, sizeof(buffer));
buffer[n] = '[=17=]';
//logger.printf("Packet from \"%s\": %s\n", sock_addr.get_ip_address(), buffer);
Thread::wait(500);
}
}
4.结果
4.1 Wireshark 结果
在 Wireshark 中,我可以看到 UDP 数据包在 Local Area Connection
上流入!好哇!
4.2 Python代码
python 捕获 UDP 数据包的代码如下所示:
import sys
import os
import socket
import dataprocessing.datastruct as datastruct
def main():
# 1. Configure the IP address
# -----------------------------
myAddr = ('192.168.1.10', 50000)
# 2. Create a UDP socket
# -----------------------
sock = None
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(1.5)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(myAddr)
print('UDPsock @: ' + str(sock.getsockname()))
sys.stdout.flush()
except Exception as e:
print('Could not bind to the UDP socket.')
print(e)
sys.stdout.flush()
while (true):
try:
data, address = sock.recvfrom(256) # buffer size is 256 bytes
bytedata = bytearray(data)
# Each 32-bit number is cut in 8-bit pieces. Bring them back together.
value_01 = bytedata[0] + 256*bytedata[1] + 65536*bytedata[2] + 16777216*bytedata[3]
value_02 = bytedata[4] + 256*bytedata[5] + 65536*bytedata[6] + 16777216*bytedata[7]
value_03 = bytedata[8] + 256*bytedata[9] + 65536*bytedata[10] + 16777216*bytedata[11]
print("Value 01: %d", value_01)
print("Value 02: %d", value_02)
print("Value 03: %d", value_03)
except socket.error as err:
print(err)
if __name__== '__main__':
print("")
print("Start UDP catcher")
print("-----------------")
main()
5.让WIFI和以太网共存
来自微控制器的 UDP 数据包流入您的以太网端口(通过路由器)。同时,您可能想连接到某些 WiFi 网络以访问互联网。问题是任何浏览器都会尝试通过您的以太网端口访问 - 忽略 WiFi。
解决方案是让您的浏览器首先尝试使用 WiFi 访问 IP 地址,然后再尝试通过以太网端口。这是通过控制面板中的 "Interface metric" 完成的。稍微增加这个数字:
您标记为 Ethernet
的连接实际上是 TAP 连接(例如虚拟以太网卡)。您的实际以太网连接标记为 Local Area Connection
;这是您需要使用 Wireshark 等配置和监视的连接。
至于其他所有与mbed-OS相关的,我个人没有处理过。
我的目标是将 UDP 数据包从微控制器(核板)发送到 PC - 就这么简单。 更新:我成功达到了目标。我将在这里解释我是如何做到的,以及我最初偶然发现的问题。非常感谢 Whosebug 社区和 LinkedIn ARM 小组的人员帮助我!
1.系统概览
我的系统如下:
1.1 微控制器端
- Nucleo_F767ZI 带有 STM32F767ZI 微控制器的开发板。该板有一个以太网连接器。
- Mbed-OS5操作系统。
- lwip 库(它是 Mbed-OS 5 操作系统的一部分)。
1.2 PC端 🖳
- Windows10、64 位。
- 空闲以太网端口(互联网通过 WiFi,这就是未使用此以太网端口的原因)。
- Wireshark 看微控制器是否真的发出UDP 数据包。稍后将在 Python 中对它们进行解释。首先我想在 Wireshark 中查看数据包。
1.3 连接
最初我将 nucleo 板直接连接到我的电脑,没有涉及路由器或交换机。这样的设置需要 "static IP configuration"。我会将 PC 配置为 IP 地址为“192.168.1.10”,将微控制器配置为“192.168.1.20”。我知道这行得通,因为我以前用 PIC 微控制器做过。
但这种情况有点不同。我在微控制器上安装了一个小操作系统 运行ning:mbed-os v5。该操作系统使用 Lwip(轻量级 ip 堆栈)。虽然应该可以os配置一个"static IP address",但是目前软件不支持。可以在此处找到更多信息:https://developer.mbed.org/questions/74145/EthernetInterfaceinit-mbed-os-5x-not-wor/。因为没有办法在单片机上配置静态IP地址,所以需要一个DHCP服务器。微控制器在没有IP地址的情况下连接到网络,并广播一个带有问题的数据包:"What IP address should I use on this network?"。如果单片机没有得到答案,它会广播几次问题,最后放弃。
如果有DHCP服务器连接到网络,它会收到问题并交出一个免费的IP地址。这就是你所需要的。有几种方法可以提供这样的 DHCP 服务器:
- 在您的 Windows PC 上安装 DHCP 服务器。这是将交换机和路由器排除在外的唯一方法。 nucleo 板直接连接到 PC。当微控制器广播问题时,Windows PC 交出一个 IP 地址。之后就可以开始通信了。
- 扔一个Raspberry PI进入游戏。 Raspberry PI 应该 运行 一个 DHCP 服务器。将三个设备(Windows PC、Raspberry PI 和 Nucleo 板)连接到交换机。
- 买一个简单的路由器。家用路由器内置了 DHCP 服务器。将 Windows PC 和 Nucleo 板连接到此路由器的 LAN 端口。
我决定选择第三个选项。下图显示了与我的 Netgear R7000 路由器的连接:
2。路由器设置
路由器内的 DHCP 服务器将向连接到它的所有设备分配 IP 地址。我的路由器会将 IP 地址“192.168.1.2”分配给第一台设备,“192.168.1.3”分配给第二台设备,依此类推,直到“192.168.1.254”。但这有点问题。 PC 和微控制器如何知道彼此的 IP 地址?也许有更聪明的解决方案,但我想出了以下方法。
每个 DHCP 服务器都有一个 table 和 "reserved IP addresses"。在此 table 中,您可以添加设备并为其分配静态 IP 地址。因此,每当该设备请求 IP 地址时,DHCP 服务器都会查看 table 并移交您配置的地址。但是 DHCP 服务器如何识别设备呢?它可以通过 MAC 地址识别设备。每个设备都有一个全球唯一的 MAC-6 字节地址。
这就是我将 Windows PC 和我的微控制器添加到 table:
- 第 1 步:将计算机连接到路由器的 LAN 端口之一。打开路由器。
- 第 2 步:确保计算机上的所有 WiFi 已断开连接。我遇到过很多这方面的问题。 (*)
- 第 3 步:打开浏览器,然后浏览“192.168.1.1”。这是我的路由器主页。您的路由器可能有所不同。
- 第 4 步:登录(我的路由器默认登录用户名是 "admin",密码是 "password")
- 第 5 步:将 Windows PC 和微控制器板添加到 table。您必须先查找他们的 MAC-地址 (**):
2.1 断开所有 WiFi (*)
这应该是一个简单的步骤,但是 Windows 让它变得困难。 Windows 可能会非常顽固并自动重新连接到周围的 WiFi 网络,即使您取消选中 自动连接 框也是如此!要强制 Windows 听你的,你可以使用 cmd 终端。打开具有管理员权限的 cmd 终端。接下来,键入以下命令以查看您所有的 WiFi 配置文件:
> netsh wlan show profiles
将以下命令应用于您的 PC 顽固连接的 ose WiFi 配置文件:
> netsh wlan set profileparameter name="someWifiName" connectionmode=manual
现在您可以断开与该 WiFi 网络的连接,Windows 将不再自动重新连接。
2.2 找到您电脑的 MAC 地址 (**)
我就是这样找到我电脑的 MAC-地址的。请记住,一台计算机可以有多个 MAC 地址。用于以太网端口的端口与用于无线连接的端口不同!在 Windows cmd 终端输入 ipconfig /all
。我得到以下输出:
# Note: this is the correct item!
# --------------------------------
Ethernet adapter Local Area Connection:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
Description . . . . . . . . . . . : Intel(R) Ethernet Connection (2) I219-LM
Physical Address. . . . . . . . . : C8-xx-xx-xx-xx-01 # Replaced some numbers by x for security :-)
DHCP Enabled. . . . . . . . . . . : Yes
Autoconfiguration Enabled . . . . : Yes
确保您查看的是联系人列表中的正确项目。 Ethernet adapter Local Area Connection
对我的电脑来说是正确的,因为网卡描述符合预期:Intel(R) Ethernet Connection (2) I219-LM
。之前,我正在查看列表中的另一个项目,标记为 Ethernet adapter Ethernet
:
# Note: this is the wrong item!
# ------------------------------
Ethernet adapter Ethernet:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
Description . . . . . . . . . . . : TAP-Windows Adapter V9
Physical Address. . . . . . . . . : 00-xx-xx-xx-xx-F7
DHCP Enabled. . . . . . . . . . . : No
Autoconfiguration Enabled . . . . : Yes
先生Joel C(请参阅下面的回答)通知我该项目的网卡是 TAP-Windows Adapter V9
。显然那是一个虚拟网卡。这让我花了很多时间os。谢谢 Joel C 先生帮助我!
另请确保 DHCP Enabled 和 Autoconfiguration Enabled 已打开!
2.3 找到您的 Nucleo 开发板的 MAC 地址 (**)
我使用以下代码找到了我的 Nucleo 板的 MAC-地址:
#include "lwip-interface/EthernetInterface.h"
//1. Make an ethernet object
EthernetInterface eth;
//2. Try to connect
eth.connect(); // <- This line will not work now,
// but at least it will help you to find out your
// own MAC-address.
//3. Print the MAC-address
logger.printf("Controller MAC Address is: %s\r\n", eth.get_mac_address());
我通过串口得到的打印输出是(为了安全我用 x 替换了一些数字):
Controller MAC Address is: 00:xx:xx:xx:xx:40
3。微控制器代码
这是微控制器上 运行 的代码。我的 main.cpp
文件的灵感来自 https://forums.mbed.com/t/udp-receive-with-nucleo-f767zi/1806.
#include <string>
using std::string;
#include "mbed.h"
#include "lwip-interface/EthernetInterface.h"
static Serial logger(USBTX, USBRX);
static DigitalOut led1(LED1);
// IP addresses
#define IP_COMPUTER "192.168.1.10" // Make sure these IP addresses correspond to the
#define IP_NUCLEO "192.168.1.20" // table of 'reserved IP addresses' you have setup in
// your routers DHCP server!
// Ethernet settings
const int PORT_T = 50000;
const int PORT_R = 50001;
EthernetInterface eth;
static void udp_tx_thread_func();
static void udp_rx_thread_func();
static Thread udp_tx_thread;
static Thread udp_rx_thread;
int main()
{
// 1. Initialize the serial logger
logger.baud(115200);
logger.printf("\r\n\r\nApplication started\r\n");
// 2. Initialize and start the UDP connection
eth.connect();
logger.printf("Controller MAC Address is: %s\r\n", eth.get_mac_address());
logger.printf("Controller IP Address is: %s\r\n", eth.get_ip_address());
Thread::wait(200);
udp_tx_thread.start(udp_tx_thread_func);
udp_rx_thread.start(udp_rx_thread_func);
while (true)
{
led1 = !led1;
Thread::wait(500);
}
}
//------------------ Ethernet --------------------------------------------------
static void udp_tx_thread_func()
{
UDPSocket socket(ð);
SocketAddress sock_addr(IP_COMPUTER, PORT_T);
static uint32_t out_buffer[3];
while(true)
{
Thread::wait(100);
// Send 3 values of 32-bit each
out_buffer[0] = 150500;
out_buffer[1] = 255300;
out_buffer[2] = 54;
int ret = socket.sendto(sock_addr, &out_buffer[0], 12); // 3 values of 32-bit equals 12 bytes
//logger.printf("sendto return: %d\r\n", ret);
}
}
static void udp_rx_thread_func()
{
UDPSocket socket(ð);
SocketAddress sock_addr;
int bind = socket.bind(PORT_R);
logger.printf("bind return: %d\n", bind);
char buffer[256];
while(true)
{
//logger.printf("\nWait for packet...\n");
int n = socket.recvfrom(&sock_addr, buffer, sizeof(buffer));
buffer[n] = '[=17=]';
//logger.printf("Packet from \"%s\": %s\n", sock_addr.get_ip_address(), buffer);
Thread::wait(500);
}
}
4.结果
4.1 Wireshark 结果
在 Wireshark 中,我可以看到 UDP 数据包在 Local Area Connection
上流入!好哇!
4.2 Python代码
python 捕获 UDP 数据包的代码如下所示:
import sys
import os
import socket
import dataprocessing.datastruct as datastruct
def main():
# 1. Configure the IP address
# -----------------------------
myAddr = ('192.168.1.10', 50000)
# 2. Create a UDP socket
# -----------------------
sock = None
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(1.5)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(myAddr)
print('UDPsock @: ' + str(sock.getsockname()))
sys.stdout.flush()
except Exception as e:
print('Could not bind to the UDP socket.')
print(e)
sys.stdout.flush()
while (true):
try:
data, address = sock.recvfrom(256) # buffer size is 256 bytes
bytedata = bytearray(data)
# Each 32-bit number is cut in 8-bit pieces. Bring them back together.
value_01 = bytedata[0] + 256*bytedata[1] + 65536*bytedata[2] + 16777216*bytedata[3]
value_02 = bytedata[4] + 256*bytedata[5] + 65536*bytedata[6] + 16777216*bytedata[7]
value_03 = bytedata[8] + 256*bytedata[9] + 65536*bytedata[10] + 16777216*bytedata[11]
print("Value 01: %d", value_01)
print("Value 02: %d", value_02)
print("Value 03: %d", value_03)
except socket.error as err:
print(err)
if __name__== '__main__':
print("")
print("Start UDP catcher")
print("-----------------")
main()
5.让WIFI和以太网共存
来自微控制器的 UDP 数据包流入您的以太网端口(通过路由器)。同时,您可能想连接到某些 WiFi 网络以访问互联网。问题是任何浏览器都会尝试通过您的以太网端口访问 - 忽略 WiFi。
解决方案是让您的浏览器首先尝试使用 WiFi 访问 IP 地址,然后再尝试通过以太网端口。这是通过控制面板中的 "Interface metric" 完成的。稍微增加这个数字:
您标记为 Ethernet
的连接实际上是 TAP 连接(例如虚拟以太网卡)。您的实际以太网连接标记为 Local Area Connection
;这是您需要使用 Wireshark 等配置和监视的连接。
至于其他所有与mbed-OS相关的,我个人没有处理过。