Skip to content

Embedded IPv4 / IPv6 Stack

Introduction

The CycloneTCP Embedded IPv4 / IPv6 stack (Ethernet.CycloneTCP) provides an advanced and efficient interface for handling network-related functionalities on embedded systems. It supports both IPv4 and IPv6 protocols, offering a full-fledged TCP/IP stack that is essential for enabling network connectivity in a variety of embedded applications.

This API abstracts the complexities of low-level network operations such as socket management, protocol handling, and packet transmission, providing a standardized interface for working with Ethernet and wireless networks on microcontroller-based systems. It supports key networking protocols such as TCP, UDP, ICMP, and more, making it an essential library for applications involving data communication, IoT connectivity, and network-enabled devices.

With CycloneTCP integrated into the mikroSDK v2.0 environment, developers can easily incorporate networking features into their embedded projects without worrying about the intricate details of networking protocols. This library is crucial for projects that require tasks like HTTP server/client implementation, MQTT communication, SNMP monitoring, or simply creating network interfaces for real-time data exchange.

API Name

  • Ethernet.CycloneTCP

Location of Files and Directories

Prerequisites

  • Hardware Configuration (Setup)

    • Open Setups wizard in NECTO Studio (left-hand side navigation bar); start creating new hardware setup; go to MCU tab; select ADVANCED MCU options; change Config scheme from Default to ETH option which sets all the MCU clock values necessary for ethernet connection to function properly.
  • Library Manager:

    • Select Ethernet.CycloneTCP, Ethernet.CycloneTCP.Config and Board to add the Embedded IPv4 / IPv6 stack to your project using the Library Manager in NECTO Studio.
  • Headers:

    • Include the hw_eth.h, mcu.h headers in your source files to access the Ethernet.CycloneTCP functions.
    • This header pops up in the lower right part of NECTO Studio once you perform the previous task (while selecting Ethernet.CycloneTCP in Library Manager)
  • CMakeLists:

    • You do not have to perform anything, configuration (CMake) file is already taken care of automatically while you performed the first task in this Prerequisites!
    • You can ensure that your CMakeLists.txt includes the necessary configurations to link against the Ethernet.CycloneTCP library.

Code Examples

  • Simple TCP Client Example
/* Project name:
 *   HTTP Client code example demo with NECTO Studio IDE
 * Copyright:
 *   (c) MIKROE, 2024.
 * Description:
 *   Example is meant for demonstrating Embedded IPv4 / IPv6 stack
 *   functionality using NECTO Studio IDE via mikroSDK v2.0 framework.
 * Library/headers dependencies?
 *   - Make sure `Ethernet.CycloneTCP`, `Ethernet.CycloneTCP.Config`,
 *     `Log`, `Driver.GPIO.Out` and `Board` libraries are enabled
 *     in NECTO's Library Manager to ensure a successful build.
 *   - Make sure to download and insert
 *     [systick.h](https://github.com/MikroElektronika/mikrosdk_v2/blob/master/tests/ethernet/systick.h)
 *     into root of the project's folder.
 * What hardware/software to use?
 *   - [UNI-DS v8](https://www.mikroe.com/uni-ds-v8) development board
 *   - [SiBRAIN for STM32F407ZG](https://www.mikroe.com/mcu-card-for-stm32-stm32f407zg) MCU card
 *   - [Ethernet cable](https://www.mikroe.com/eth-cable-cat6-1m)
 *   - [NECTO Studio IDE](https://www.mikroe.com/necto)
 * How to run this project?
 *   - Run Debug Mode (F9 on your keyboard);
 *   - Read the Application Output.
 */

// ------------------------------------------------------------------ INCLUDES
#ifdef PREINIT_SUPPORTED
#include "preinit.h"
#endif

#include "MikroSDK.Ethernet.CycloneTCP"
#include "MikroSDK.Driver.GPIO.Out"
#include "MikroSDK.Board"
#include "MikroSDK.Log"

#include "mcu.h"
#include "debug.h"
#include "hw_eth.h"
#include "systick.h"
#include "http/http_client.h"

// Include currently active driver header.
#include "eth_driver.h"

// User defined PHY driver.
#include "drivers/phy/lan8720_driver.h"

// -------------------------------------------------------------------- MACROS
// Defined test name.
#define TEST_NAME "HTTP Client Demo"

// Define debug console settings.
#define LED_SYSCTICK_CHECK HAL_PIN_NC  // TODO
#define LED_SANITY_CHECK HAL_PIN_NC    // TODO
#define LED_DEBUG_CHECK HAL_PIN_NC     // TODO

// Timeout value in ms.
// Used to exit an infinite loop if there is any net error.
#define TIMEOUT_VALUE_MS 10000

// Ethernet interface configuration.
#define APP_IF_NAME "eth0"
#define APP_HOST_NAME "http-client-demo"
#define APP_MAC_ADDR "00-AB-CD-EF-07-69"

// Application configuration.
#define APP_HTTP_SERVER_NAME "www.httpbin.org"
#define APP_HTTP_SERVER_PORT 80
#define APP_HTTP_URI "/anything"

// ----------------------------------------------------------------- VARIABLES
// Pointer function typedef.
typedef int (*fun_ptr)(void);
fun_ptr fun_ptr_client_init;
fun_ptr fun_ptr_slac_configure;

// Global variables.
static NetInterface *interface;
static DhcpClientSettings dhcpClientSettings;
static DhcpClientContext dhcpClientContext;
static SlaacSettings slaacSettings;
static SlaacContext slaacContext;
static HttpClientContext httpClientContext;

// ETH state signal.
static bool ethInitialized = false;
static uint32_t msCount = 0;
uint32_t timeout = 0;

// Debug array with pins.
static digital_out_t debugErrArray[3];
static pin_name_t debugPins[3] = {LED_SYSCTICK_CHECK, LED_SANITY_CHECK, LED_DEBUG_CHECK};

// Debug handle.
log_t console;
static log_cfg_t console_cfg;

// ----------------------------------------------------------------- USER CODE
// API prototypes.
static void testInitDebug(void);
static void testSetPointers(void);
static int testInitStack(void);
static int testRun(void);
static int testInitClientDhcp(void);
static int testIpv6SlaacEnable(void);
// Task prototypes.
static error_t httpClientTest(void);

int main(void) {
    #ifdef PREINIT_SUPPORTED
    preinit();
    #endif

    testInitDebug();
    // Configure SYSTICK to 1ms interrupt.
    if (!sysTickConfig(GET_TICK_NUMBER_PER_CLOCK)) {
        sysTickInit(15); // Maximum priority - level 15.
    } else {
        while(1);
    }

    // Set appropriate function pointers.
    // TODO - Map different APIs if needed.
    testSetPointers();

    // Initialize net stack.
    if (testInitStack()) {
        while(1);
    }

    // Initialize DHCP client.
    if ((*fun_ptr_client_init)()) {
        while(1);
    }

    // Initialize SLAAC.
    if ((*fun_ptr_slac_configure)()) {
        while(1);
    }

    // If we get here, ETH module is considered initialized.
    ethInitialized = true;

    // Run the test.
    if (testRun()) {
        TRACE_INFO("Test failed.\r\n");
    } else {
        TRACE_INFO("Test successful.\r\n");
    }

    return 0;
}

static void testInitDebug(void) {
    for (int i = 0; i < (sizeof(debugErrArray) / sizeof(digital_out_t)); i++) {
        digital_out_init(&debugErrArray[i], debugPins[i]);
        digital_out_low(&debugErrArray[i]);
    }

    LOG_MAP_USB_UART( console_cfg );
    log_init( &console, &console_cfg );

    // Start-up message
    TRACE_INFO("\r\n***************************************\r\n");
    TRACE_INFO("******* %s *******\r\n", TEST_NAME);
    TRACE_INFO("***************************************\r\n");
    TRACE_INFO("Compiled: %s %s\r\n", __DATE__, __TIME__);
}

static void testSetPointers(void) {
    fun_ptr_client_init = testInitClientDhcp;
    fun_ptr_slac_configure = testIpv6SlaacEnable;
}

static int testInitStack(void) {
    error_t error;
    MacAddr macAddr;

    // TCP/IP stack initialization.
    error = netInit();
    // Any error to report?
    if(error) {
        TRACE_INFO("Failed to initialize TCP/IP stack!\r\n");
        return error;
    }

    // Configure the first Ethernet interface.
    interface = &netInterface[0];

    // Set interface name.
    netSetInterfaceName(interface, APP_IF_NAME);
    // Set host name.
    netSetHostname(interface, APP_HOST_NAME);
    // Set host MAC address.
    macStringToAddr(APP_MAC_ADDR, &macAddr);
    netSetMacAddr(interface, &macAddr);

    /**
     * @brief Select the relevant network adapter.
     * @note Set automatically by mikroSDK v2.0 based on
     * active setup.
     * @warning Selected setup MUST have ETHERNET module.
     */
    netSetDriver(interface, &ETHERNET_DRIVER_HANDLER);

    /**
     * @brief Map PHY chip.
     * @note Set automatically by mikroSDK v2.0 based on
     * active setup.
     * @note Set to NULL if you wish to use
     * internal PHY.
     * @warning Selected setup MUST have ETHERNET module.
     */
    netSetPhyDriver(interface, ETHERNET_PHY_CHIP);

    // Initialize network interface.
    error = netConfigInterface(interface);
    // Any error to report?
    if(error) {
        TRACE_INFO("Failed to configure interface %s!\r\n", interface->name);
        return error;
    }

    error = netStartInterface(interface);
    // Any error to report?
    if(error) {
        TRACE_INFO("Failed to start interface %s!\r\n", interface->name);
        return error;
    }

    return NO_ERROR;
}

static int testInitClientDhcp(void) {
    // Error code.
    error_t error;

    // Get default settings.
    dhcpClientGetDefaultSettings(&dhcpClientSettings);
    // Set the network interface to be configured by DHCP.
    dhcpClientSettings.interface = interface;
    // Disable rapid commit option.
    dhcpClientSettings.rapidCommit = FALSE;

    // DHCP client initialization.
    error = dhcpClientInit(&dhcpClientContext, &dhcpClientSettings);
    // Failed to initialize DHCP client?
    if(error) {
        TRACE_INFO("Failed to initialize DHCP client!\r\n");
        return error;
    }

    // Start DHCP client.
    error = dhcpClientStart(&dhcpClientContext);
    // Failed to start DHCP client?
    if(error) {
        TRACE_INFO("Failed to start DHCP client!\r\n");
        return error;
    }

    return NO_ERROR;
}

static int testIpv6SlaacEnable(void) {
    // Error code.
    error_t error;

    // Get default settings.
    slaacGetDefaultSettings(&slaacSettings);
    // Set the network interface to be configured.
    slaacSettings.interface = interface;

    // SLAAC initialization.
    error = slaacInit(&slaacContext, &slaacSettings);
    // Failed to initialize SLAAC?
    if(error) {
        TRACE_INFO("Failed to initialize SLAAC!\r\n");
        return error;
    }

    // Start IPv6 address autoconfiguration process.
    error = slaacStart(&slaacContext);
    // Failed to start SLAAC process?
    if(error) {
        TRACE_INFO("Failed to start SLAAC!\r\n");
        return error;
    }

    return NO_ERROR;
}

static int testRun(void) {
    // Error code.
    error_t error;

    // Run HTTP Client test.
    error = httpClientTest();

    return (int)error;
}

static error_t httpClientTest(void) {
    error_t error;
    size_t length;
    uint_t status;
    const char_t *value;
    IpAddr ipAddr;
    char_t buffer[128];
    Ipv4Addr ipv4Addr;

    // Initialize HTTP client context.
    httpClientInit(&httpClientContext);

    // Start of exception handling block.
    do {
        // Debug message
        TRACE_INFO("\r\n\r\nResolving server name...\r\n");

        // Resolve HTTP server name
        timeout = 0;
        do {
            if (TIMEOUT_VALUE_MS <= timeout) {
                timeout = 0;
                break;
            }
            error = getHostByName(NULL, APP_HTTP_SERVER_NAME, &ipAddr, 0);
        } while (error);

        // Select HTTP protocol version
        error = httpClientSetVersion(&httpClientContext, HTTP_VERSION_1_1);
        // Any error to report?
        if (error)
            break;

        // Set timeout value for blocking operations
        error = httpClientSetTimeout(&httpClientContext, 20000);
        // Any error to report?
        if (error)
            break;

        // Debug message.
        TRACE_INFO("Connecting to HTTP server %s...\r\n", ipAddrToString(&ipAddr, NULL));

        // Connect to the HTTP server.
        timeout = 0;
        do {
            if (TIMEOUT_VALUE_MS <= timeout) {
                timeout = 0;
                break;
            }
            error = httpClientConnect(&httpClientContext, &ipAddr,
                                      APP_HTTP_SERVER_PORT);
        } while (error);

        // Any error to report?
        if (error) {
            // Debug message.
            TRACE_INFO("Failed to connect to HTTP server!\r\n");
            break;
        }

        // Display IPv4 host address.
        ipv4GetHostAddr(interface, &ipv4Addr);
        TRACE_INFO("Assigned IP address: %s\r\n", ipv4AddrToString(ipv4Addr, buffer));

        // Create an HTTP request.
        httpClientCreateRequest(&httpClientContext);
        httpClientSetMethod(&httpClientContext, "POST");
        httpClientSetUri(&httpClientContext, APP_HTTP_URI);

        // Set query string.
        httpClientAddQueryParam(&httpClientContext, "param1", "value1");
        httpClientAddQueryParam(&httpClientContext, "param2", "value2");

        // Add HTTP header fields.
        httpClientAddHeaderField(&httpClientContext, "Host", APP_HTTP_SERVER_NAME);
        httpClientAddHeaderField(&httpClientContext, "User-Agent", "Mozilla/5.0");
        httpClientAddHeaderField(&httpClientContext, "Content-Type", "text/plain");
        httpClientAddHeaderField(&httpClientContext, "Transfer-Encoding", "chunked");

        // Send HTTP request header.
        error = httpClientWriteHeader(&httpClientContext);

        // Any error to report?
        if (error) {
            // Debug message
            TRACE_INFO("Failed to write HTTP request header!\r\n");
            break;
        }

        // Send HTTP request body.
        error = httpClientWriteBody(&httpClientContext, "Hello World!", 12,
                                    NULL, 0);
        // Any error to report?
        if (error) {
            // Debug message.
            TRACE_INFO("Failed to write HTTP request body!\r\n");
            break;
        }

        // Receive HTTP response header.
        timeout = 0;
        do {
            if (TIMEOUT_VALUE_MS <= timeout) {
                timeout = 0;
                break;
            }
            error = httpClientReadHeader(&httpClientContext);
        } while (error);

        // Any error to report?
        if (error) {
            // Debug message.
            TRACE_INFO("Failed to read HTTP response header!\r\n");
            break;
        }

        // Retrieve HTTP status code.
        status = httpClientGetStatus(&httpClientContext);

        // Debug message.
        TRACE_INFO("HTTP status code: %u\r\n", status);

        // Retrieve the value of the Content-Type header field.
        value = httpClientGetHeaderField(&httpClientContext, "Content-Type");

        // Header field found?
        if (value != NULL) {
            // Debug message.
            TRACE_INFO("Content-Type header field value: %s\r\n", value);
        } else {
            // Debug message.
            TRACE_INFO("Content-Type header field not found!\r\n");
        }

        // Receive HTTP response body.
        while (!error) {
            // Read data.
            error = httpClientReadBody(&httpClientContext, buffer,
                                       sizeof(buffer) - 1, &length, 0);

            // Check status code.
            if (!error) {
                // Properly terminate the string with a NULL character
                buffer[length] = '\0';
                // Dump HTTP response body
                TRACE_INFO("%s", buffer);
            }
        }

        // Terminate the HTTP response body with a CRLF.
        TRACE_INFO("\r\n");

        // Any error to report?
        if (error != ERROR_END_OF_STREAM)
            break;

        // Close HTTP response body.
        error = httpClientCloseBody(&httpClientContext);

        // Any error to report?
        if (error) {
            // Debug message
            TRACE_INFO("Failed to read HTTP response trailer!\r\n");
            break;
        }

        // Gracefully disconnect from the HTTP server.
        httpClientDisconnect(&httpClientContext);

        // Debug message.
        TRACE_INFO("Connection closed\r\n");

        // End of exception handling block.
    } while (0);

    // Release HTTP client context.
    httpClientDeinit(&httpClientContext);

    // Return status code.
    return error;
}

/**
 * @note Using the stack without an OS requires @ref systemTicks
 * to be incremented every 1ms.
 */
extern volatile systime_t systemTicks;
__attribute__ ((interrupt("IRQ"))) void SysTick_Handler(void) {
    msCount++;
    timeout++;
    systemTicks++;

    if (ethInitialized) {
        netTask();
    }
}

// ----------------------------------------------------------------------- END

This code is a complete example demonstrating the use of an HTTP client in an embedded system using the NECTO Studio IDE with the mikroSDK v2.0 framework. The example specifically highlights the functionality of the embedded IPv4/IPv6 stack.

Here’s a breakdown of the key components:

Purpose:

The primary purpose of the code is to showcase how to establish an HTTP client using an Ethernet interface on an STM32-based system. The code involves setting up DHCP, SLAAC (for IPv6), and running an HTTP request to a server. Hardware/Software Requirements:

Hardware:
    UNI-DS v8 development board.
    SiBRAIN STM32F407ZG MCU card.
    Ethernet cable (CAT6 or similar for connectivity).

Software:
    NECTO Studio IDE.
    Libraries: Ethernet.CycloneTCP, Log, Driver.GPIO.Out, and Board from NECTO’s Library Manager.
    A specific systick.h file for system tick configuration.

Key Features and Functions:

Network Setup:
    Initializes the Ethernet interface with the MAC address and hostname.
    Configures the network stack for DHCP and IPv6 (using SLAAC).
    Connects to an HTTP server (www.httpbin.org) to make a POST request and handle the response.

HTTP Client Implementation:
    The HTTP client connects to the server, creates a request, adds headers, and sends a POST request to the /anything URI.
    The client then processes the HTTP response, including reading the response headers and body.

System Tick and Network Task Handling:
    A system tick handler is used to maintain timing (every 1ms) and manage the network task processing, which is crucial when working without an operating system.

Debugging and Logging:
    The system includes detailed debugging with log messages to trace the status of the network stack, HTTP transactions, and other operations.
    LED pins are used for debug signaling, though their configuration is marked as "TODO" in the code.

Functionality:

Initialization:
    testInitStack(): Initializes the TCP/IP stack and configures the Ethernet interface.
    testInitClientDhcp(): Initializes and starts the DHCP client to obtain an IP address dynamically.
    testIpv6SlaacEnable(): Enables SLAAC for automatic IPv6 configuration.

HTTP Client Task:
    httpClientTest(): Handles the entire HTTP client process—from DNS resolution, connecting to the server, sending a POST request, and reading the server's response.

Pointers and Tasks:
    The code uses function pointers (fun_ptr_client_init and fun_ptr_slac_configure) to initialize the network components dynamically, allowing flexibility in switching or extending network protocols.

Error Handling:

The code checks for errors at every step of network initialization and HTTP operations, with appropriate logging and trace messages (TRACE_INFO). If an error occurs, the system enters an infinite loop to prevent further operation.

Overall Flow:

  • Debugging is initialized.
  • The network stack is set up.
  • DHCP and SLAAC are configured for IPv4 and IPv6, respectively.
  • The HTTP client connects to the server, sends a request, and processes the response.
  • If any step fails, error messages are logged and the system halts.

This example provides a foundational approach to implementing Ethernet-based communication in embedded systems, integrating IPv4/IPv6 and handling HTTP client transactions.