c++socketsudpethernetmbed

Lwip on mbed-os 5 doesn't make a proper ethernet connection


My goal is to send UDP packets from the microcontroller (nucleo board) to the PC - as simple as that. Update: I reached the goal successfully. I'll explain here how I did it, and the problems I stumbled upon initially. Many thanks to the StackOverflow community and people on LinkedIn ARM group to help me out!

1. SYSTEM OVERVIEW

My system is as follows:

1.1 Microcontroller side

1.2 PC side 🖳

1.3 Connections

Initially I connected the nucleo board directly to my computer, without a router or switch involved. Such a setup requires "static IP configuration". I would configure the PC to have IP address "192.168.1.10" and the microcontroller to have "192.168.1.20". I know this can work, because I've done it before with a PIC microcontroller.

But this situation is a little different. I got a little operating system running on the microcontroller: mbed-os v5. This operating system makes use of Lwip (Light-weight ip stack). Although it should be possible to configure a "static IP address", the software currently doesn't support it. More information can be found here: https://developer.mbed.org/questions/74145/EthernetInterfaceinit-mbed-os-5x-not-wor/. Because there is no way to configure a static IP address on the microcontroller, you need a DHCP server. The microcontroller connects to the network without an IP address, and broadcasts a packet with the question: "What IP address should I use on this network?". If the microcontroller doesn't get an answer, it will broadcast the question a few times, and eventually give up.

If there is a DHCP server connected to the network, it will receive the question and hand over a free IP address. So that is what you need. There are several ways to provide such a DHCP server:

I decided to go for the third option. The following figure shows the connections to my Netgear R7000 router:

Netgear R7000 router

2. ROUTER SETUP

The DHCP server inside the router will hand out IP addresses to all devices that connect to it. My router will hand out the IP address "192.168.1.2" to the first device, "192.168.1.3" to the second, and so forth all the way up to "192.168.1.254". But that's a bit problematic. How can the PC and the microcontroller know each others IP addresses? Perhaps there are more clever solutions, but I've figured out the following approach.

Each DHCP server has a table with "reserved IP addresses". In this table, you can add devices and assign a static IP address to them. So whenever that device asks for an IP address, the DHCP server takes a look in the table and hands over the one you've configured. But how does the DHCP server recognizes a device? It can recognize a device by its MAC-address. Every device has a globally unique MAC-address of 6 bytes.

This is how I've added my Windows PC and my microcontroller to the table:

Netgear R7000 router configuration

2.1 Disconnect all WiFi (*)

This should be a simple step, but Windows makes it hard. Windows can be quite stubborn and reconnect automatically to WiFi networks around, even if you've unchecked the connect automatically box! To force Windows to listen to you, you can use the cmd terminal. Open a cmd terminal with admin privileges. Next, type the following command to see all your WiFi profiles:

> netsh wlan show profiles

Apply the following command to those WiFi profiles that your PC is stubbornly connecting to:

> netsh wlan set profileparameter name="someWifiName" connectionmode=manual

Now you can disconnect from that WiFi network, and Windows won't reconnect automatically anymore.

2.2 Find the MAC address of your PC (**)

This is how I found the MAC-address of my computer. Keep in mind that a computer can have several MAC-addresses. The one for your ethernet port will be different to the one for your wireless connection! Type ipconfig /all in the Windows cmd terminal. I got the following output:

# 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

Make sure you're looking at the correct item in the list of connections. Ethernet adapter Local Area Connection is the correct one for my computer, because the network card description fits what is expected: Intel(R) Ethernet Connection (2) I219-LM. Before, I was looking at another item in the list, labelled 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

Mr. Joel C (see his answer below) notified me that the network card from that item is TAP-Windows Adapter V9. Apparently that is a virtual network card. That made me lose a lot of time. Thank you Mr. Joel C for helping me out!
Please make also sure that DHCP Enabled and Autoconfiguration Enabled are turned on!

2.3 Find the MAC address of your Nucleo board (**)

I found the MAC-address of my Nucleo board with the following code:

#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());

The print output I got over my serial port is (some numbers I replaced by x for security):

Controller MAC Address is: 00:xx:xx:xx:xx:40

3. MICROCONTROLLER CODE

This is the code that runs on the microcontroller. My main.cpp file is inspired on the code found on the Mbed-os forum at 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(&eth);
    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(&eth);
    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] = '\0';
        //logger.printf("Packet from \"%s\": %s\n", sock_addr.get_ip_address(), buffer);

        Thread::wait(500);
    }

}

4. RESULTS

4.1 Wireshark results

In Wireshark I can see the UDP packets flowing in on the Local Area Connection! Huray!

4.2 Python code

The python code to catch the UDP packets looks like this:

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. LET WIFI AND ETHERNET COEXIST

The UDP packets from the microcontroller flow in on your ethernet port (passing along the router). Meanwhile you might want to connect to some WiFi network for internet access. The problem is that any browser will try to get access through your ethernet port - ignoring the WiFi.

The solution is to make your browsers FIRST attempt to use the WiFi to reach an IP address, next attempt through the Ethernet port. This is done with the "Interface metric" in the Control Panel. Increase this number slightly:

enter image description here


Solution

  • The connection you have labelled Ethernet is actually a TAP connection (eg. a virtual ethernet card). Your actual ethernet connection is labelled Local Area Connection; that is the connection you will need to be configuring and watching with Wireshark, etc.

    As to everything else Mbed-OS related, I personally have not dealt with it.