Ethernet over USB on STM32F4



    Recently, an idea came up to make the board based on the STM32F4 MK work over the network. Since there was no Ethernet PHY controller on board, the only option was to use the USB FullSpeed ​​interface to emulate the Ethernet device. A common USB class standard that implements this function is called RNDIS.
    Unfortunately, the search for the RNDIS driver for the STM32 was unsuccessful. However, this was not surprising, because Open STM32 USB port examples are limited only to what the manufacturer provided us.
    I wanted to correct this injustice. And at the same time to fuck the necessary sources, the benefit in the future they will be useful.
    Now that the demo version of the library is ready, I post it on the light as a MIT license. Therefore, all who are interested in the library - use "on health." The library is called LRNDIS, the first letter of which means the use of the network stack for embedded systems "LwIP".
    To demonstrate the capabilities of the library, an example was created on the stm32f4discovery board. His job is to support basic services (DHCP and DNS servers) and transfer the requested WEB pages to the usb host. Thus, our discovery has turned into an almost full-fledged WEB server running on the USB port!
    A few words about where this applies.
    In everyday life, RNDIS devices are usually USB modems for accessing the Internet. Perhaps such an application will indeed prove useful if the developer chooses STM32 as the connecting chain between the PC and the radio frequency (or other) transceiver. Or maybe he wants to expand his own network to the Ethernet segment?
    Another application in which I find the main benefit for myself is the interface for managing complex devices. A typical solution in this area is the creation of terminal software. At the same time, you have to deal with its support along with device support, which is inconvenient. Actually, the rejection of such a scheme in favor of the managing Web-interface is the meaning of the possible use of the library. Remember the web-based interfaces for configuring routers. Conveniently. Handsomely. Without unnecessary software.

    So, if you are interested, read on ...

    1. RNDIS driver


    At the writing stage, two tasks were solved: sign our device and support the RNDIS standard.
    The device signature comes down to compiling valid USB descriptors. The VID value is 0x0483 (STMicroelectronics), the PID value is 0x0123 (arbitrary). Of course, this should not be done in commercial applications.
    View descriptors
    Device descriptor
    OffsetFieldSizeValueDescription
    0bLengthone12h
    onebDescriptorTypeone01hDevice
    2bcdUSB20200hUSB Spec 2.0
    4bDeviceClassone02hCdc control
    5bDeviceSubClassone00h
    6bDeviceProtocolone00h
    7bMaxPacketSize0one40h64 bytes
    8idVendor20483hSGS Thomson Microelectronics
    10idProduct20123h
    12bcdDevice20001h0.01
    14iManufacturerone01h"Fetisov Sergey"
    fifteeniProductone02h"STM32F4 RNDIS"
    sixteeniSerialNumberone03h"00000000123C"
    17bNumConfigurationsone01h
    Configuration Descriptor 1
    OffsetFieldSizeValueDescription
    0bLengthone09h
    onebDescriptorTypeone02hConfiguration
    2wTotalLength20043h
    4bNumInterfacesone02h
    5bConfigurationValueone01h
    6iConfigurationone00h
    7bmAttributesone40hSelf powered
    8bMaxPowerone01h2 mA
    Interface Descriptor 0/0 CDC Control, 1 Endpoint
    OffsetFieldSizeValueDescription
    0bLengthone09h
    onebDescriptorTypeone04hInterface
    2bInterfaceNumberone00h
    3bAlternateSettingone00h
    4bNumEndpointsone01h
    5bInterfaceClassone02hCdc control
    6bInterfaceSubClassone02hAbstract Control Model
    7bInterfaceProtocoloneFfhVendor-specific
    8iInterfaceone00h
    Header functional descriptor
    OffsetFieldSizeValueDescription
    0bFunctionLengthone05h
    onebDescriptorTypeone24hCS Interface
    2bDescriptorSubtypeone00hHeader
    3bcdCDC20110h1.10
    Call Management Functional Descriptor
    OffsetFieldSizeValueDescription
    0bFunctionLengthone05h
    onebDescriptorTypeone24hCS Interface
    2bDescriptorSubtypeone01hCall management
    3bmCapabilitiesone00h
    4bDataInterfaceone01h
    Abstract Control Management Functional Descriptor
    OffsetFieldSizeValueDescription
    0bFunctionLengthone04h
    onebDescriptorTypeone24hCS Interface
    2bDescriptorSubtypeone02hAbstract control management
    3bmCapabilitiesone00hRequests / notifications not supported
    Union Functional Descriptor
    OffsetFieldSizeValueDescription
    0bFunctionLengthone05h
    onebDescriptorTypeone24hCS Interface
    2bDescriptorSubtypeone06hUnion
    3bControlInterfaceone00h
    4bSubordinateInterface0one01hCdc data
    Endpoint Descriptor 81 1 In, Interrupt, 80 ms
    OffsetFieldSizeValueDescription
    0bLengthone07h
    onebDescriptorTypeone05hEndpoint
    2bEndpointAddressone81h1 In
    3bmAttributesone03hInterrupt
    4wMaxPacketSize20008h8 bytes
    6bIntervalone50h80 ms
    Interface Descriptor 1/0 CDC Data, 2 Endpoints
    OffsetFieldSizeValueDescription
    0bLengthone09h
    onebDescriptorTypeone04hInterface
    2bInterfaceNumberone01h
    3bAlternateSettingone00h
    4bNumEndpointsone02h
    5bInterfaceClassone0ahCdc data
    6bInterfaceSubClassone00h
    7bInterfaceProtocolone00h
    8iInterfaceone00h
    Endpoint Descriptor 82 2 In, Bulk, 64 bytes
    OffsetFieldSizeValueDescription
    0bLengthone07h
    onebDescriptorTypeone05hEndpoint
    2bEndpointAddressone82h2 In
    3bmAttributesone02hBulk
    4wMaxPacketSize20040h64 bytes
    6bIntervalone00h
    Endpoint Descriptor 03 3 Out, Bulk, 64 bytes
    OffsetFieldSizeValueDescription
    0bLengthone07h
    onebDescriptorTypeone05hEndpoint
    2bEndpointAddressone03h3 out
    3bmAttributesone02hBulk
    4wMaxPacketSize20040h64 bytes
    6bIntervalone00h


    Support for the standard is carried out in accordance with the documentation .

    Also on the network there are many RNDIS drivers for other platforms ( 1 , 2 , 3 ), which greatly simplified the development.
    In terms of exchange, the driver repeats the peculiarity of the CDC class, except that the transmitted packets are wrapped with overhead information. There is also a special interface for sending requests from the usb host in order to configure the device or obtain its status. The driver code can be studied in detail in the usbd_rndis_core.c module.
    The minimum driver setup for embedding is to change the definitions in the usbd_rndis_core.h file.
    - MAC address of the device (PERMANENT_HWADDR, STATION_HWADDR)
    - Manufacturer ID (RNDIS_VENDOR)
    - MTU value (ETH_MTU):

    #define ETH_MTU          1500                           // MTU value
    #define ETH_LINK_SPEED   250000                         // bits per sec
    #define RNDIS_VENDOR     "fetisov"                      // NIC vendor name
    #define STATION_HWADDR   0x20,0x89,0x84,0x6A,0x96,0xAA  // station MAC
    #define PERMANENT_HWADDR 0x20,0x89,0x84,0x6A,0x96,0xAA  // permanent MAC
    

    Also in the usbd_desc.h file you should change the name of the product (USBD_PRODUCT_STRING) and the manufacturer (USBD_MANUFACTURER_STRING).

    The process of installing the driver in Windows:



    1. The menu item "update the driver";
    2. Perform a driver search;
    3. Select a driver from the list;
    4. In the list of device classes, select "Network Adapters";
    5. In the list of manufacturers select “Microsoft Corporation”;
    6. And the product “Remote NDIS based Internet Sharing Device”.

    In more detail, the installation process of a standard RNDIS device is described here .

    2. We fasten LwIP




    The RNDIS driver provides only the transport function for 802.3 (Ethernet) frames. Obviously, support for the full variety of packages and standards needs to be laid on the network stack. In his role, it was decided to use the popular stack for embedded lwip systems of the latest (currently) version 1.4.1. We must pay tribute to the authors of the stack, it turned out to be very easy to integrate. For all that, the stack code is rich in useful comments and instructions.

    lwIP is a small independent implementation of the TCP / IP protocol suite that has been initially developed by Adam Dunkels and is now continued here.
    The focus of the lwIP TCP / IP implementation is to reduce resource usage while still having a full scale TCP. This makes lwIP suitable for use in embedded systems with tens of kilobytes of free RAM and room for around 40 kilobytes of code ROM.
    Main features include:
    - Protocols: IP, ICMP, UDP, TCP, IGMP, ARP, PPPoS, PPPoE
    - DHCP client, DNS client, AutoIP / APIPA (Zeroconf), SNMP agent (private MIB support)
    - APIs: specialized APIs for enhanced performance, optional Berkeley-alike socket API
    - Extended features: IP forwarding over multiple network interfaces, TCP congestion control, RTT estimation and fast recovery / fast retransmit
    - Addon applications: HTTP server, SNTP client, SMTP client, ping, NetBIOS nameserver

    Running the stack under stm32 was limited to including a specific set of source files in the build process and entering definitions in the lwipopts.h file:

    #define NO_SYS                          1
    #define LWIP_RAW                        1
    #define LWIP_NETCONN                    0
    #define LWIP_SOCKET                     0
    #define LWIP_DHCP                       0
    #define LWIP_ICMP                       1
    #define LWIP_UDP                        1
    #define LWIP_TCP                        1
    #define ETH_PAD_SIZE                    0
    #define LWIP_IP_ACCEPT_UDP_PORT(p)      ((p) == PP_NTOHS(67))
    #define MEM_SIZE                        10000
    #define TCP_MSS                         (1500 /*mtu*/ - 14 /*ethhdr*/ - 20 /*iphdr*/ - 20 /*tcphhr*/)
    #define ETHARP_SUPPORT_STATIC_ENTRIES   1
    

    It should be noted that work on porting the stack to STM32F4 was performed earlier by STMicroelectronics (application STSW-STM32070).

    We start the DHCP server




    Adding a DHCP server to the library is associated with the need to initialize the network interface on the host side. By default, the interface created when the device is connected is configured to automatically obtain an IP address. The library also works successfully with static addressing, but this is not entirely convenient.

    Unfortunately, among the tools provided by lwip, there is no DHCP server.

    However, this is not a significant problem, as in its minimal implementation, the DHCP server is very minimalistic.

    Perhaps the only example is onlineDHCP server working with the lwip stack. This source turned out to be very useful for study, although not suitable for embedding according to the “as-is” principle due to the lack of the ability to configure and use socket-api.

    Therefore, it was decided to write a DHCP server.

    And here are its modest capabilities:
    - issuing addresses for an arbitrary time
    - reserving addresses by MAC address
    - setting up a DNS server

    Connecting a server in a test project:

    #define NUM_DHCP_ENTRY 3
    static dhcp_entry_t entries[NUM_DHCP_ENTRY] =
    {
    	// mac    ip address        subnet mask        lease time
    	{ {0}, {192, 168, 7, 2}, {255, 255, 255, 0}, 24 * 60 * 60 },
    	{ {0}, {192, 168, 7, 3}, {255, 255, 255, 0}, 24 * 60 * 60 },
    	{ {0}, {192, 168, 7, 4}, {255, 255, 255, 0}, 24 * 60 * 60 }
    };
    static dhcp_config_t dhcp_config =
    {
    	{192, 168, 7, 1}, 67, // server address, port
    	{192, 168, 7, 1},     // dns server
    	"stm",                // dns suffix
    	NUM_DHCP_ENTRY,       // num entry
    	entries               // entries
    };
    int main(void)
    {
    ...
    	while (dhserv_init(&dhcp_config) != ERR_OK) ;
    ...
    }
    


    We start the DNS server




    The desired result of our work is the display of a web page when you enter a certain resource name in the browser. However, this is only possible with a DNS server that will “know” about our host. Of course, this result is available if you directly enter the ip address in the address bar: 192.168.7.1 . This address has our device by default. However, we will be more skilled and run the DNS server.

    Unlike DHCP, the current DNS server implementation is even thinner. At the moment, it allows you to process only standard DNS queries per record.

    Starting the server in the project:

    bool dns_query_proc(const char *name, ip_addr_t *addr)
    {
    	if (strcmp(name, "run.stm") == 0 || strcmp(name, "www.run.stm") == 0)
    	{
    		addr->addr = *(uint32_t *)ipaddr;
    		return true;
    	}
    	return false;
    }
    int main(void)
    {
    ...
    	while (dnserv_init(PADDR(ipaddr), 53, dns_query_proc) != ERR_OK) ;
    ...
    }
    


    We start the HTTP server


    For a long time and to no avail, I had to struggle with the launch of the well-known server from the lwip package “contrib-1.4.1”. Until now, the mysterious HardFault, which appears on a seemingly flat ground, remains a mystery to me. All settings were checked, read and write addresses, stack depth ... But, alas.

    Later I started writing a working http server and HardFault repeated when I accessed the memory area allocated by mem_malloc. At the same time, the addresses were valid and related to the internal RAM. In general, he happily said goodbye to dynamic allocation and started using statics in the server. However, the question about the reason for HardFault remained open, and therefore it must be borne in mind that it is not safe to use the lwip mem_ * functions in the current version of the library.

    So, the result was still obtained.

    HTTP server enable code:

    static const my_page_t my_pages[] =
    {
    	{ "/",          200, MIME_TEXT_HTML, page1_html,     page1_html_size      },
    	{ "/page2.htm", 200, MIME_TEXT_HTML, page2_html,     page2_html_size      },
    	{ "/page3.htm", 200, MIME_TEXT_HTML, page3_html,     page3_html_size      },
    	{ "/check.gif", 200, MIME_IMAGE_GIF, check_png,      check_png_size       },
    	{ NULL,         404, MIME_TEXT_HTML, page_not_found, page_not_found_size  }
    };
    bool on_http_req(const http_req_t *req, http_resp_t *resp, void **arg)
    {
    	const my_page_t *page;
    	for (page = my_pages; page->uri != NULL; page++)
    		if (strcmp(page->uri, req->uri) == 0) break;
    	resp->code = page->code;
    	resp->cont_len = page->size;
    	resp->mime = page->mime;
    	resp->conn_type = CT_CLOSE;
    	*arg = (void *)page;
    	return true;
    }
    void http_write_data()
    {
    	for (int i = 0; i < HTTP_SERVER_MAX_CON; i++)
    	{
    		int n;
    		const htcon_t *con;
    		my_page_t *page;
    		con = htcon(i);
    		if (con == NULL) continue;
    		page = (my_page_t *)con->arg;
    		if (con->state == CON_CLOSED)
    		{
    			htcon_free(i);
    			continue;
    		}
    		if (con->state != CON_ACTIVE) continue;
    		n = page->size - con->writed;
    		htcon_write(i, (char *)page->data + con->writed, n);
    	}
    }
    int main(void)
    {
    	...
    	htserv_on_req = on_http_req;
    	while (htserv_init(80) != ERR_OK) ;
    	while (1)
    	{
    		stmr();            // call software timers
    		usb_polling();     // usb device polling
    		http_write_data(); // writes http response
    	}
    }
    


    Unresolved issues


    1. The nuances of relicensing the lwip stack, which may have its own conditions for inclusion in other software;
    2. The problem with working under Linux;
    3. Adding POST request processing;
    4. Finalization of the DNS server for processing "multi-query" packets;
    5. The problem with mem_malloc.

    Also popular now: