micro-ROS / micro_ros_espidf_component

micro-ROS ESP32 IDF component and sample code
Apache License 2.0
229 stars 53 forks source link

Issues compiling with W5500 enabled #234

Open K-hastings opened 3 months ago

K-hastings commented 3 months ago

Issue template

Steps to reproduce the issue

Create a new project using the pub/sub example. Configure project to use W5500

Expected behavior

Code compiles and is able to run on the ESP32

Actual behavior

I instead get the following when compling:

/home/kevin/projects/esp32/hello_world/components/micro_ros_espidf_component/network_interfaces/uros_ethernet_netif.c: In function 'uros_network_interface_initialize':
/home/kevin/projects/esp32/hello_world/components/micro_ros_espidf_component/network_interfaces/uros_ethernet_netif.c:78:21: error: implicit declaration of function 'esp_eth_set_default_handlers' [-Werror=implicit-function-declaration]
   78 |     ESP_ERROR_CHECK(esp_eth_set_default_handlers(eth_netif));
      |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/kevin/esp/v5.2/esp-idf/components/esp_common/include/esp_err.h:117:30: note: in definition of macro 'ESP_ERROR_CHECK'
  117 |         esp_err_t err_rc_ = (x);                                        \
      |                              ^
/home/kevin/projects/esp32/hello_world/components/micro_ros_espidf_component/network_interfaces/uros_ethernet_netif.c:139:74: error: macro "ETH_W5500_DEFAULT_CONFIG" requires 2 arguments, but only 1 given
  139 |     eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
      |                                                                          ^
In file included from /home/kevin/esp/v5.2/esp-idf/components/esp_eth/include/esp_eth_driver.h:9,
                 from /home/kevin/esp/v5.2/esp-idf/components/esp_eth/include/esp_eth.h:15,
                 from /home/kevin/projects/esp32/hello_world/components/micro_ros_espidf_component/network_interfaces/uros_ethernet_netif.c:6:
/home/kevin/esp/v5.2/esp-idf/components/esp_eth/include/esp_eth_mac.h:623: note: macro "ETH_W5500_DEFAULT_CONFIG" defined here
  623 | #define ETH_W5500_DEFAULT_CONFIG(spi_host, spi_devcfg_p) \
      | 
/home/kevin/projects/esp32/hello_world/components/micro_ros_espidf_component/network_interfaces/uros_ethernet_netif.c:139:39: error: 'ETH_W5500_DEFAULT_CONFIG' undeclared (first use in this function)
  139 |     eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
      |                                       ^~~~~~~~~~~~~~~~~~~~~~~~`

Additional information

Only change to the basic component it to allow pin 35 to be selected for MISO.

pablogs9 commented 3 months ago

Those features were added here by @uLipe. Not sure if they are still valid or maintained.

Please, open a PR with the fix if you find a solution.

Foxei commented 3 months ago

Espressif updated the ethernet handling stack here. I made some changes to get microROS to work with ethernet on 5.2 but had no time to test the results yet.

ESP NETIF Glue Event Handlers

esp_eth_set_default_handlers() and esp_eth_clear_default_handlers() functions are removed. Registration of the default IP layer handlers for Ethernet is now handled automatically. If you have already followed the suggestion to fully initialize the Ethernet driver and network interface before registering their Ethernet/IP event handlers, then no action is required (except for deleting the affected functions). Otherwise, you may start the Ethernet driver right after they register the user event handler.

pablogs9 commented 3 months ago

Could you please open a PR with the solution?

uLipe commented 3 months ago

Please mind the esp-idf v4 is not compatible with v5.

So the PR for it would add support for v5 and above, not for replace the v4.x since they are in LTS and production periods.

Foxei commented 3 months ago

I've been experimenting with an Olimex ESP32 PoE board equipped with an internal Lan8720 chip and have tested the provided code with both middleware: embeddedRTPS and XRCE-DDS in Espressif IDF v5.2.1.

Here are the changes made:

  1. Removed esp_eth_set_default_handlers as it's now handled automatically. Refer to this documentation for more details.

    // Removed: Set default handlers to process TCP/IP stuffs
    //ESP_ERROR_CHECK(esp_eth_set_default_handlers(eth_netif));
  2. Moved GPIO initialization from MAC to EMAC configuration.

    // Initialize MAC and PHY configs to default
    eth_esp32_emac_config_t emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
    mac_config.sw_reset_timeout_ms = 250; // Required for some Ethernet controllers to initiate
    
    emac_config.smi_mdc_gpio_num = CONFIG_MICRO_ROS_ETH_MDC_GPIO;
    emac_config.smi_mdio_gpio_num = CONFIG_MICRO_ROS_ETH_MDIO_GPIO;
    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&emac_config, &mac_config);
  3. Consideration for adding an EventGroupHandle_t so that app_main only exits if an IP was obtained. This concept is already implemented in the WiFi code. Currently, I've applied a workaround by adding a 5-second delay in the main after network initialization, which hasn't caused any failures so far.

  4. Changed esp_eth_phy_new_lan8720 to esp_eth_phy_new_lan87xx as the support was extended to the entire 87 family of chips.

Please note that I do not have a W5500 on hand to test and fix the rest.

The final version of uros_ethernet_netif.c reflects these changes:

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_netif.h"
#include "esp_eth.h"
#include "esp_event.h"
#include "esp_log.h"
#include "sdkconfig.h"
#if CONFIG_ETH_USE_SPI_ETHERNET
#include "driver/spi_master.h"
#endif
#include "uros_network_interfaces.h"

#ifdef CONFIG_MICRO_ROS_ESP_NETIF_ENET

uint8_t IP_ADDRESS[4];

static const char *TAG = "eth_interface";

static void eth_event_handler(void *arg, esp_event_base_t event_base,
                              int32_t event_id, void *event_data)
{
    uint8_t mac_addr[6] = {0};
    /* we can get the ethernet driver handle from event data */
    esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;

    switch (event_id) {
    case ETHERNET_EVENT_CONNECTED:
        esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
        ESP_LOGI(TAG, "Ethernet Link Up");
        ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
                 mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
        break;
    case ETHERNET_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "Ethernet Link Down");
        break;
    case ETHERNET_EVENT_START:
        ESP_LOGI(TAG, "Ethernet Started");
        break;
    case ETHERNET_EVENT_STOP:
        ESP_LOGI(TAG, "Ethernet Stopped");
        break;
    default:
        break;
    }
}

static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
                                 int32_t event_id, void *event_data)
{
    ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
    const esp_netif_ip_info_t *ip_info = &event->ip_info;

    ESP_LOGI(TAG, "Ethernet Got IP Address");
    ESP_LOGI(TAG, "~~~~~~~~~~~");
    ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip));
    ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
    ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
    ESP_LOGI(TAG, "~~~~~~~~~~~");

    IP_ADDRESS[0] = esp_ip4_addr_get_byte(&event->ip_info.ip, 0);
    IP_ADDRESS[1] = esp_ip4_addr_get_byte(&event->ip_info.ip, 1);
    IP_ADDRESS[2] = esp_ip4_addr_get_byte(&event->ip_info.ip, 2);
    IP_ADDRESS[3] = esp_ip4_addr_get_byte(&event->ip_info.ip, 3);
}

esp_err_t uros_network_interface_initialize(void)
{
    // Initialize TCP/IP network interface (should be called only once in application)
    ESP_ERROR_CHECK(esp_netif_init());
    // Create default event loop that running in background
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
    esp_netif_t *eth_netif = esp_netif_new(&cfg);
    // Set default handlers to process TCP/IP stuffs
    //ESP_ERROR_CHECK(esp_eth_set_default_handlers(eth_netif));
    // Register user defined event handers
    ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));

    eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
    eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
    phy_config.phy_addr = CONFIG_MICRO_ROS_ETH_PHY_ADDR;
    phy_config.reset_gpio_num = CONFIG_MICRO_ROS_ETH_PHY_RST_GPIO;
#if CONFIG_MICRO_ROS_USE_INTERNAL_ETHERNET
      // Init MAC and PHY configs to default
    eth_esp32_emac_config_t emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
    mac_config.sw_reset_timeout_ms = 250; // Needed for some ethernet controllers to initiate

    emac_config.smi_mdc_gpio_num = CONFIG_MICRO_ROS_ETH_MDC_GPIO;
    emac_config.smi_mdio_gpio_num = CONFIG_MICRO_ROS_ETH_MDIO_GPIO;
    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&emac_config, &mac_config);
#if CONFIG_MICRO_ROS_ETH_PHY_IP101
    esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
#elif CONFIG_MICRO_ROS_ETH_PHY_RTL8201
    esp_eth_phy_t *phy = esp_eth_phy_new_rtl8201(&phy_config);
#elif CONFIG_MICRO_ROS_ETH_PHY_LAN8720
    esp_eth_phy_t *phy = esp_eth_phy_new_lan87xx(&phy_config);
#elif CONFIG_MICRO_ROS_ETH_PHY_DP83848
    esp_eth_phy_t *phy = esp_eth_phy_new_dp83848(&phy_config);
#elif CONFIG_MICRO_ROS_ETH_PHY_KSZ8041
    esp_eth_phy_t *phy = esp_eth_phy_new_ksz8041(&phy_config);
#endif
#elif CONFIG_ETH_USE_SPI_ETHERNET
    gpio_install_isr_service(0);
    spi_device_handle_t spi_handle = NULL;
    spi_bus_config_t buscfg = {
        .miso_io_num = CONFIG_MICRO_ROS_ETH_SPI_MISO_GPIO,
        .mosi_io_num = CONFIG_MICRO_ROS_ETH_SPI_MOSI_GPIO,
        .sclk_io_num = CONFIG_MICRO_ROS_ETH_SPI_SCLK_GPIO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
    };
    ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_MICRO_ROS_ETH_SPI_HOST, &buscfg, 1));
#if CONFIG_MICRO_ROS_USE_DM9051
    spi_device_interface_config_t devcfg = {
        .command_bits = 1,
        .address_bits = 7,
        .mode = 0,
        .clock_speed_hz = CONFIG_MICRO_ROS_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
        .spics_io_num = CONFIG_MICRO_ROS_ETH_SPI_CS_GPIO,
        .queue_size = 20
    };
    ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_MICRO_ROS_ETH_SPI_HOST, &devcfg, &spi_handle));
    /* dm9051 ethernet driver is based on spi driver */
    eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle);
    dm9051_config.int_gpio_num = CONFIG_MICRO_ROS_ETH_SPI_INT_GPIO;
    esp_eth_mac_t *mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
    esp_eth_phy_t *phy = esp_eth_phy_new_dm9051(&phy_config);
#elif CONFIG_MICRO_ROS_USE_W5500
    spi_device_interface_config_t devcfg = {
        .command_bits = 16, // Actually it's the address phase in W5500 SPI frame
        .address_bits = 8,  // Actually it's the control phase in W5500 SPI frame
        .mode = 0,
        .clock_speed_hz = CONFIG_MICRO_ROS_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
        .spics_io_num = CONFIG_MICRO_ROS_ETH_SPI_CS_GPIO,
        .queue_size = 20
    };
    ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_MICRO_ROS_ETH_SPI_HOST, &devcfg, &spi_handle));
    /* w5500 ethernet driver is based on spi driver */
    eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
    w5500_config.int_gpio_num = CONFIG_MICRO_ROS_ETH_SPI_INT_GPIO;
    esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
    esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config);
#endif
#endif // CONFIG_ETH_USE_SPI_ETHERNET
    esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
    esp_eth_handle_t eth_handle = NULL;
    ESP_ERROR_CHECK(esp_eth_driver_install(&config, &eth_handle));
#if CONFIG_ETH_USE_SPI_ETHERNET
    /* The SPI Ethernet module might doesn't have a burned factory MAC address, we cat to set it manually.
       02:00:00 is a Locally Administered OUI range so should not be used except when testing on a LAN under your control.
    */
    ESP_ERROR_CHECK(esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, (uint8_t[]) {
        0x02, 0x00, 0x00, 0x12, 0x34, 0x56
    }));
#endif
    /* attach Ethernet driver to TCP/IP stack */
    ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));
    /* start Ethernet driver state machine */
    ESP_ERROR_CHECK(esp_eth_start(eth_handle));

    return ESP_OK;
}

#endif
Foxei commented 3 months ago

@uLipe, regarding compatibility with version 4.4, we might not necessarily need to break compatibility. One approach could be parsing the CONFIG_IDF_INIT_VERSION and checking if the ASCII value of the first character is greater than 52. This way, we could differentiate between versions 4 and beyond, enabling us to handle compatibility gracefully.