Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for W5500 SPI Ethernet Controller (without multiple devices using the same SPI bus) #2068

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cpplint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
pip install cpplint
- name: Linting
run: |
cpplint --repository=. --recursive --filter=-build/c++11,-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason
cpplint --repository=. --recursive --filter=-build/c++11,-runtime/references,-readability/braces,-whitespace,-legal,-build/include ./src ./include ./lib/Hoymiles ./lib/MqttSubscribeParser ./lib/TimeoutHelper ./lib/ResetReason ./lib/ETHSPI
1 change: 1 addition & 0 deletions include/NetworkSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class NetworkSettingsClass {
bool _ethConnected = false;
std::vector<NetworkEventCbList_t> _cbEventList;
bool _lastMdnsEnabled = false;
bool _spiEth = false;
};

extern NetworkSettingsClass NetworkSettings;
8 changes: 8 additions & 0 deletions include/PinMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ struct PinMapping_t {
int8_t cmt_gpio3;
int8_t cmt_sdio;

int8_t w5500_sclk;
int8_t w5500_mosi;
int8_t w5500_miso;
int8_t w5500_cs;
int8_t w5500_int;
int8_t w5500_rst;

int8_t eth_phy_addr;
bool eth_enabled;
int eth_power;
Expand All @@ -49,6 +56,7 @@ class PinMappingClass {

bool isValidNrf24Config() const;
bool isValidCmt2300Config() const;
bool isValidW5500Config() const;
bool isValidEthConfig() const;

private:
Expand Down
121 changes: 121 additions & 0 deletions lib/ETHSPI/src/ETHSPI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include "ETHSPI.h"

#include <driver/spi_master.h>

// Functions from WiFiGeneric
void tcpipInit();
void add_esp_interface_netif(esp_interface_t interface, esp_netif_t* esp_netif);

ETHSPIClass::ETHSPIClass() :
eth_handle(nullptr),
eth_netif(nullptr)
{

}

void ETHSPIClass::begin(int8_t pin_sclk, int8_t pin_mosi, int8_t pin_miso, int8_t pin_cs, int8_t pin_int, int8_t pin_rst, spi_host_device_t host_id)
{
gpio_reset_pin(static_cast<gpio_num_t>(pin_rst));
gpio_set_direction(static_cast<gpio_num_t>(pin_rst), GPIO_MODE_OUTPUT);
gpio_set_level(static_cast<gpio_num_t>(pin_rst), 0);

gpio_reset_pin(static_cast<gpio_num_t>(pin_sclk));
gpio_reset_pin(static_cast<gpio_num_t>(pin_mosi));
gpio_reset_pin(static_cast<gpio_num_t>(pin_miso));
gpio_reset_pin(static_cast<gpio_num_t>(pin_cs));
gpio_set_pull_mode(static_cast<gpio_num_t>(pin_miso), GPIO_PULLUP_ONLY);

// Workaround, because calling gpio_install_isr_service directly causes issues with attachInterrupt later
attachInterrupt(digitalPinToInterrupt(pin_int), nullptr, CHANGE);
detachInterrupt(digitalPinToInterrupt(pin_int));
gpio_reset_pin(static_cast<gpio_num_t>(pin_int));
gpio_set_pull_mode(static_cast<gpio_num_t>(pin_int), GPIO_PULLUP_ONLY);

spi_bus_config_t buscfg = {
.mosi_io_num = pin_mosi,
.miso_io_num = pin_miso,
.sclk_io_num = pin_sclk,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.data4_io_num = -1,
.data5_io_num = -1,
.data6_io_num = -1,
.data7_io_num = -1,
.max_transfer_sz = 0, // uses default value internally
.flags = 0,
.intr_flags = 0
};

ESP_ERROR_CHECK(spi_bus_initialize(host_id, &buscfg, SPI_DMA_CH_AUTO));

spi_device_interface_config_t devcfg = {
.command_bits = 16, // actually address phase
.address_bits = 8, // actually command phase
.dummy_bits = 0,
.mode = 0,
.duty_cycle_pos = 0,
.cs_ena_pretrans = 0, // only 0 supported
.cs_ena_posttrans = 0, // only 0 supported
.clock_speed_hz = 20000000, // stable with on OpenDTU Fusion Shield
.input_delay_ns = 0,
.spics_io_num = pin_cs,
.flags = 0,
.queue_size = 20,
.pre_cb = nullptr,
.post_cb = nullptr
};

spi_device_handle_t spi;
ESP_ERROR_CHECK(spi_bus_add_device(host_id, &devcfg, &spi));

// Reset sequence
delayMicroseconds(500);
gpio_set_level(static_cast<gpio_num_t>(pin_rst), 1);
delayMicroseconds(1000);

// Arduino function to start networking stack if not already started
tcpipInit();

ESP_ERROR_CHECK(tcpip_adapter_set_default_eth_handlers()); // ?

eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi);
w5500_config.int_gpio_num = pin_int;

eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
mac_config.rx_task_stack_size = 4096;
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);

eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
phy_config.reset_gpio_num = -1;
esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config);

esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
ESP_ERROR_CHECK(esp_eth_driver_install(&eth_config, &eth_handle));

// Configure MAC address
uint8_t mac_addr[6];
ESP_ERROR_CHECK(esp_efuse_mac_get_default(mac_addr));
mac_addr[5] |= 0x03; // derive ethernet MAC address from base MAC address
ESP_ERROR_CHECK(esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr));

esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH();
eth_netif = esp_netif_new(&netif_config);

ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));

// Add to Arduino
add_esp_interface_netif(ESP_IF_ETH, eth_netif);

ESP_ERROR_CHECK(esp_eth_start(eth_handle));
}

String ETHSPIClass::macAddress()
{
uint8_t mac_addr[6] = {0, 0, 0, 0, 0, 0};
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
char mac_addr_str[24];
snprintf(mac_addr_str, sizeof(mac_addr_str), "%02X:%02X:%02X:%02X:%02X:%02X", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
return String(mac_addr_str);
}

ETHSPIClass ETHSPI;
20 changes: 20 additions & 0 deletions lib/ETHSPI/src/ETHSPI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include <Arduino.h>
#include <esp_netif.h>
#include <driver/spi_master.h>

class ETHSPIClass
{
private:
esp_eth_handle_t eth_handle;
esp_netif_t *eth_netif;

public:
ETHSPIClass();

void begin(int8_t pin_sclk, int8_t pin_mosi, int8_t pin_miso, int8_t pin_cs, int8_t pin_int, int8_t pin_rst, spi_host_device_t host_id);
String macAddress();
};

extern ETHSPIClass ETHSPI;
30 changes: 25 additions & 5 deletions src/NetworkSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
* Copyright (C) 2022-2024 Thomas Basler and others
*/
#include "NetworkSettings.h"
#include <driver/spi_master.h>
#include "Configuration.h"
#include "MessageOutput.h"
#include "PinMapping.h"
#include "Utils.h"
#include "defaults.h"
#include <ESPmDNS.h>
#include <ETHSPI.h>
#include <ETH.h>
#include "__compiled_constants.h"

Expand All @@ -28,6 +30,27 @@ void NetworkSettingsClass::init(Scheduler& scheduler)
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);

WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1));

if (PinMapping.isValidW5500Config()) {
_spiEth = true;

PinMapping_t& pin = PinMapping.get();
if (PinMapping.isValidCmt2300Config() && PinMapping.isValidNrf24Config()) {
MessageOutput.println("No ETH connection possible with CMT and NRF enabled.");
} else if (PinMapping.isValidCmt2300Config()) {
#if CONFIG_IDF_TARGET_ESP32S3
ETHSPI.begin(pin.w5500_sclk, pin.w5500_mosi, pin.w5500_miso, pin.w5500_cs, pin.w5500_int, pin.w5500_rst, SPI3_HOST);
#else
MessageOutput.println("No SPI3_HOST avialable on current device.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Classic ESP32 also has SPI3, right? Also there is a typo here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Classic ESP32 Wroom only has HSPI and VSPI, ie two SPI Busses 🚌 according to my memory.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ESP32 integrates 4 SPI peripherals: SPI0, SPI1, SPI2 (commonly referred to as HSPI), and SPI3 (commonly referred to as VSPI).

Source

This code doesn't make use of two SPI buses. If CMT2300A is configured, it just gives up.

And sure enough, when I edit it to use SPI3_HOST it works just fine. On classic ESP32 (I used ESP32 mini)

#endif
} else {
ETHSPI.begin(pin.w5500_sclk, pin.w5500_mosi, pin.w5500_miso, pin.w5500_cs, pin.w5500_int, pin.w5500_rst, SPI2_HOST);
}
} else if (PinMapping.isValidEthConfig()) {
PinMapping_t& pin = PinMapping.get();
ETH.begin(pin.eth_phy_addr, pin.eth_power, pin.eth_mdc, pin.eth_mdio, pin.eth_type, pin.eth_clk_mode);
}

setupMode();

scheduler.addTask(_loopTask);
Expand Down Expand Up @@ -164,11 +187,6 @@ void NetworkSettingsClass::setupMode()
WiFi.mode(WIFI_MODE_NULL);
}
}

if (PinMapping.isValidEthConfig()) {
PinMapping_t& pin = PinMapping.get();
ETH.begin(pin.eth_phy_addr, pin.eth_power, pin.eth_mdc, pin.eth_mdio, pin.eth_type, pin.eth_clk_mode);
}
}

void NetworkSettingsClass::enableAdminMode()
Expand Down Expand Up @@ -396,6 +414,8 @@ String NetworkSettingsClass::macAddress() const
{
switch (_networkMode) {
case network_mode::Ethernet:
if (_spiEth)
return ETHSPI.macAddress();
return ETH.macAddress();
break;
case network_mode::WiFi:
Expand Down
48 changes: 48 additions & 0 deletions src/PinMapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,30 @@
#define CMT_SDIO -1
#endif

#ifndef W5500_SCLK
#define W5500_SCLK -1
#endif

#ifndef W5500_MOSI
#define W5500_MOSI -1
#endif

#ifndef W5500_MISO
#define W5500_MISO -1
#endif

#ifndef W5500_CS
#define W5500_CS -1
#endif

#ifndef W5500_INT
#define W5500_INT -1
#endif

#ifndef W5500_RST
#define W5500_RST -1
#endif

PinMappingClass PinMapping;

PinMappingClass::PinMappingClass()
Expand All @@ -103,6 +127,13 @@ PinMappingClass::PinMappingClass()
_pinMapping.cmt_gpio3 = CMT_GPIO3;
_pinMapping.cmt_sdio = CMT_SDIO;

_pinMapping.w5500_sclk = W5500_SCLK;
_pinMapping.w5500_mosi = W5500_MOSI;
_pinMapping.w5500_miso = W5500_MISO;
_pinMapping.w5500_cs = W5500_CS;
_pinMapping.w5500_int = W5500_INT;
_pinMapping.w5500_rst = W5500_RST;

#ifdef OPENDTU_ETHERNET
_pinMapping.eth_enabled = true;
#else
Expand Down Expand Up @@ -164,6 +195,13 @@ bool PinMappingClass::init(const String& deviceMapping)
_pinMapping.cmt_gpio3 = doc[i]["cmt"]["gpio3"] | CMT_GPIO3;
_pinMapping.cmt_sdio = doc[i]["cmt"]["sdio"] | CMT_SDIO;

_pinMapping.w5500_sclk = doc[i]["w5500"]["sclk"] | W5500_SCLK;
_pinMapping.w5500_mosi = doc[i]["w5500"]["mosi"] | W5500_MOSI;
_pinMapping.w5500_miso = doc[i]["w5500"]["miso"] | W5500_MISO;
_pinMapping.w5500_cs = doc[i]["w5500"]["cs"] | W5500_CS;
_pinMapping.w5500_int = doc[i]["w5500"]["int"] | W5500_INT;
_pinMapping.w5500_rst = doc[i]["w5500"]["rst"] | W5500_RST;

#ifdef OPENDTU_ETHERNET
_pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | true;
#else
Expand Down Expand Up @@ -211,6 +249,16 @@ bool PinMappingClass::isValidCmt2300Config() const
&& _pinMapping.cmt_sdio >= 0;
}

bool PinMappingClass::isValidW5500Config() const
{
return _pinMapping.w5500_sclk >= 0
&& _pinMapping.w5500_mosi >= 0
&& _pinMapping.w5500_miso >= 0
&& _pinMapping.w5500_cs >= 0
&& _pinMapping.w5500_int >= 0
&& _pinMapping.w5500_rst >= 0;
}

bool PinMappingClass::isValidEthConfig() const
{
return _pinMapping.eth_enabled;
Expand Down
8 changes: 8 additions & 0 deletions src/WebApi_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
cmtPinObj["gpio2"] = pin.cmt_gpio2;
cmtPinObj["gpio3"] = pin.cmt_gpio3;

auto w5500PinObj = curPin["w5500"].to<JsonObject>();
w5500PinObj["sclk"] = pin.w5500_sclk;
w5500PinObj["mosi"] = pin.w5500_mosi;
w5500PinObj["miso"] = pin.w5500_miso;
w5500PinObj["cs"] = pin.w5500_cs;
w5500PinObj["int"] = pin.w5500_int;
w5500PinObj["rst"] = pin.w5500_rst;

auto ethPinObj = curPin["eth"].to<JsonObject>();
ethPinObj["enabled"] = pin.eth_enabled;
ethPinObj["phy_addr"] = pin.eth_phy_addr;
Expand Down