├── examples ├── pio_example │ ├── .gitignore │ ├── test │ │ └── README │ ├── platformio.ini │ ├── lib │ │ └── README │ ├── include │ │ └── README │ └── src │ │ └── main.cpp ├── FOTA │ ├── bin │ │ ├── firmware.gz │ │ ├── firmware.zz │ │ ├── firmware.bin │ │ ├── firmware.gz.json │ │ ├── firmware.json │ │ └── firmware.zz.json │ └── FOTA.ino └── ETH_ENC28J60 │ └── ETH_ENC28J60.ino ├── extras └── logo.png ├── library.properties ├── README.md ├── .github └── workflows │ └── lint.yml ├── license └── src ├── extmod ├── esp_eth_spi_enc28j60.c ├── esp_eth_enc28j60.h ├── enc28j60.h ├── esp_eth_phy_enc28j60.c └── esp_eth_mac_enc28j60.c ├── ESP32-ENC28J60.h └── ESP32-ENC28J60.cpp /examples/pio_example/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | -------------------------------------------------------------------------------- /extras/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njh/ESP32-ENC28J60/main/extras/logo.png -------------------------------------------------------------------------------- /examples/FOTA/bin/firmware.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njh/ESP32-ENC28J60/main/examples/FOTA/bin/firmware.gz -------------------------------------------------------------------------------- /examples/FOTA/bin/firmware.zz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njh/ESP32-ENC28J60/main/examples/FOTA/bin/firmware.zz -------------------------------------------------------------------------------- /examples/FOTA/bin/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njh/ESP32-ENC28J60/main/examples/FOTA/bin/firmware.bin -------------------------------------------------------------------------------- /examples/FOTA/bin/firmware.gz.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "esp32-fota-http", 3 | "version": "1.1", 4 | "host": "github.com", 5 | "port": 443, 6 | "bin": "/tobozo/ESP32-ENC28J60/blob/main/examples/FOTA/bin/firmware.gz?raw=true" 7 | } 8 | -------------------------------------------------------------------------------- /examples/FOTA/bin/firmware.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "esp32-fota-http", 3 | "version": "1.1", 4 | "host": "github.com", 5 | "port": 443, 6 | "bin": "/tobozo/ESP32-ENC28J60/blob/main/examples/FOTA/bin/firmware.bin?raw=true" 7 | } 8 | 9 | -------------------------------------------------------------------------------- /examples/FOTA/bin/firmware.zz.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "esp32-fota-http", 3 | "version": "1.1", 4 | "host": "github.com", 5 | "port": 443, 6 | "bin": "/tobozo/ESP32-ENC28J60/blob/main/examples/FOTA/bin/firmware.zz?raw=true" 7 | } 8 | 9 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP32-ENC28J60 2 | version=1.0.1 3 | author=Tobozo 4 | maintainer=tobozo@noreply.github.com 5 | sentence=Enables network connection (local and Internet) with ENC28J60 using the ESP32 Ethernet. 6 | paragraph=With this library you can instantiate Servers, Clients and send/receive UDP packets through Ethernet. The IP address can be assigned statically or through a DHCP. The library can also manage DNS. 7 | category=Communication 8 | url=https://github.com/tobozo/ENC28J60-ESP32 9 | architectures=esp32 10 | includes=ESP32-ENC28J60.h 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32-ENC28J60 2 | 3 | This Ethernet Library is a copy of the official [esp32-arduino Ethernet Library](https://github.com/espressif/arduino-esp32/tree/master/libraries/Ethernet) with added support for ENC28J60 boards and all other board types removed. 4 | 5 | ![image](https://user-images.githubusercontent.com/1893754/192626089-dc36f546-d212-4f6b-b0bb-90da6538e291.png) 6 | 7 | 8 | Low level code comes from the [eth_enc28j60 component](https://github.com/espressif/esp-idf/tree/master/examples/ethernet/enc28j60/components/eth_enc28j60) found in the esp-idf examples folder. 9 | -------------------------------------------------------------------------------- /examples/pio_example/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.ino' 7 | - '**.ini' 8 | - '**.cpp' 9 | - '**.hpp' 10 | - '**.h' 11 | - '**.c' 12 | - '**lint.yml' 13 | 14 | pull_request: 15 | branches: 16 | - main 17 | 18 | workflow_dispatch: 19 | 20 | jobs: 21 | 22 | lint: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: arduino/arduino-lint-action@v1 27 | with: 28 | project-type: library 29 | library-manager: false 30 | recursive: true 31 | -------------------------------------------------------------------------------- /examples/pio_example/platformio.ini: -------------------------------------------------------------------------------- 1 | [platformio] 2 | description = ESP32-ENC28J60 test sketch for platformio 3 | default_envs = esp32_2_0_5 4 | 5 | [env] 6 | framework = arduino 7 | lib_deps = ESP32-ENC28J60 8 | 9 | 10 | [env:esp32_2_0_5] 11 | board = esp32dev 12 | ; using tasmota platform instead of the official to ensure all latest patches are applied 13 | platform = https://github.com/tasmota/platform-espressif32 14 | ; using package 2.0.5 as eth_* support is instable before this version 15 | platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32/releases/download/2.0.5/esp32-2.0.5.zip 16 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 tobozo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/pio_example/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /examples/pio_example/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /src/extmod/esp_eth_spi_enc28j60.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "sdkconfig.h" 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | #include "esp_netif.h" 7 | #include "esp_eth.h" 8 | #include "esp_event.h" 9 | #include "driver/gpio.h" 10 | #include "esp_eth_enc28j60.h" 11 | #include "driver/spi_master.h" 12 | 13 | 14 | 15 | esp_eth_mac_t* enc28j60_new_mac( spi_device_handle_t *spi_handle, int INT_GPIO ) 16 | { 17 | eth_enc28j60_config_t enc28j60_config = ETH_ENC28J60_DEFAULT_CONFIG( *spi_handle ); 18 | enc28j60_config.int_gpio_num = INT_GPIO; 19 | 20 | eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); 21 | mac_config.smi_mdc_gpio_num = -1; // ENC28J60 doesn't have SMI interface 22 | mac_config.smi_mdio_gpio_num = -1; 23 | // mac_config.rx_task_prio = 1; 24 | return esp_eth_mac_new_enc28j60( &enc28j60_config, &mac_config ); 25 | } 26 | 27 | 28 | esp_eth_mac_t* enc28j60_begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, int SPI_HOST) 29 | { 30 | if(ESP_OK !=gpio_install_isr_service(0)) return NULL; 31 | /* ENC28J60 ethernet driver is based on spi driver */ 32 | spi_bus_config_t buscfg = 33 | { 34 | .miso_io_num = MISO_GPIO, 35 | .mosi_io_num = MOSI_GPIO, 36 | .sclk_io_num = SCLK_GPIO, 37 | .quadwp_io_num = -1, 38 | .quadhd_io_num = -1, 39 | }; 40 | if( ESP_OK != spi_bus_initialize( SPI_HOST, &buscfg, SPI_DMA_CH_AUTO )) return NULL; 41 | 42 | spi_device_interface_config_t devcfg = 43 | { 44 | .command_bits = 3, 45 | .address_bits = 5, 46 | .mode = 0, 47 | .clock_speed_hz = SPI_CLOCK_MHZ * 1000 * 1000, 48 | .spics_io_num = CS_GPIO, 49 | .queue_size = 1, 50 | .cs_ena_posttrans = enc28j60_cal_spi_cs_hold_time(SPI_CLOCK_MHZ), 51 | }; 52 | 53 | spi_device_handle_t spi_handle = NULL; 54 | if(ESP_OK != spi_bus_add_device( SPI_HOST, &devcfg, &spi_handle )) return NULL; 55 | 56 | return enc28j60_new_mac( &spi_handle, INT_GPIO ); 57 | } 58 | -------------------------------------------------------------------------------- /src/ESP32-ENC28J60.h: -------------------------------------------------------------------------------- 1 | /* 2 | ENC28J60-ETH.h - ETH PHY support for ENC28J60 3 | Based on ETH.h from Arduino-esp32 and ENC28J60 component from esp-idf. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #ifndef _ENC28J60_ESP32_ETH_H_ 21 | #define _ENC28J60_ESP32_ETH_H_ 22 | 23 | #include "WiFi.h" 24 | #include "esp_system.h" 25 | #include "esp_eth.h" 26 | 27 | #if ESP_IDF_VERSION_MAJOR < 4 || ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,4,0) 28 | #error "This version of Arduino is too old" 29 | #endif 30 | 31 | 32 | class ENC28J60Class { 33 | private: 34 | bool initialized; 35 | bool staticIP; 36 | 37 | esp_eth_handle_t eth_handle; 38 | 39 | protected: 40 | bool started; 41 | eth_link_t eth_link; 42 | static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); 43 | 44 | public: 45 | ENC28J60Class(); 46 | ~ENC28J60Class(); 47 | 48 | bool begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, int SPI_HOST, bool use_mac_from_efuse=false); 49 | 50 | bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = (uint32_t)0x00000000, IPAddress dns2 = (uint32_t)0x00000000); 51 | 52 | const char * getHostname(); 53 | bool setHostname(const char * hostname); 54 | 55 | bool fullDuplex(); 56 | bool linkUp(); 57 | uint8_t linkSpeed(); 58 | 59 | bool enableIpV6(); 60 | IPv6Address localIPv6(); 61 | 62 | IPAddress localIP(); 63 | IPAddress subnetMask(); 64 | IPAddress gatewayIP(); 65 | IPAddress dnsIP(uint8_t dns_no = 0); 66 | 67 | IPAddress broadcastIP(); 68 | IPAddress networkID(); 69 | uint8_t subnetCIDR(); 70 | 71 | uint8_t * macAddress(uint8_t* mac); 72 | String macAddress(); 73 | 74 | friend class WiFiClient; 75 | friend class WiFiServer; 76 | }; 77 | 78 | extern ENC28J60Class ETH; 79 | 80 | #endif /* _ETH_H_ */ 81 | -------------------------------------------------------------------------------- /examples/pio_example/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This sketch shows the Ethernet event usage for ENC28J60 3 | 4 | */ 5 | 6 | #include 7 | 8 | #define SPI_HOST 1 9 | #define SPI_CLOCK_MHZ 8 10 | #define INT_GPIO 4 11 | // 12 | #if CONFIG_IDF_TARGET_ESP32 13 | #define MISO_GPIO 12 14 | #define MOSI_GPIO 13 15 | #define SCLK_GPIO 14 16 | #define CS_GPIO 15 17 | #elif CONFIG_IDF_TARGET_ESP32C3 18 | #define MISO_GPIO 2 19 | #define MOSI_GPIO 7 20 | #define SCLK_GPIO 6 21 | #define CS_GPIO 10 22 | #else 23 | #define MISO_GPIO 13 24 | #define MOSI_GPIO 11 25 | #define SCLK_GPIO 12 26 | #define CS_GPIO 10 27 | #endif 28 | 29 | 30 | static bool eth_connected = false; 31 | 32 | 33 | void WiFiEvent(WiFiEvent_t event) 34 | { 35 | switch (event) { 36 | case ARDUINO_EVENT_ETH_START: 37 | Serial.println("ETH Started"); 38 | //set eth hostname here 39 | ETH.setHostname("esp32-ethernet"); 40 | break; 41 | case ARDUINO_EVENT_ETH_CONNECTED: 42 | Serial.println("ETH Connected"); 43 | break; 44 | case ARDUINO_EVENT_ETH_GOT_IP: 45 | Serial.print("ETH MAC: "); 46 | Serial.print(ETH.macAddress()); 47 | Serial.print(", IPv4: "); 48 | Serial.print(ETH.localIP()); 49 | if (ETH.fullDuplex()) { 50 | Serial.print(", FULL_DUPLEX"); 51 | } 52 | Serial.print(", "); 53 | Serial.print(ETH.linkSpeed()); 54 | Serial.println("Mbps"); 55 | eth_connected = true; 56 | break; 57 | case ARDUINO_EVENT_ETH_DISCONNECTED: 58 | Serial.println("ETH Disconnected"); 59 | eth_connected = false; 60 | break; 61 | case ARDUINO_EVENT_ETH_STOP: 62 | Serial.println("ETH Stopped"); 63 | eth_connected = false; 64 | break; 65 | default: 66 | break; 67 | } 68 | } 69 | 70 | #include 71 | #include 72 | 73 | HTTPClient http; 74 | WiFiClientSecure client; 75 | 76 | 77 | void testClient(const char * url) 78 | { 79 | Serial.printf("\nConnecting to %s\n", url); 80 | 81 | http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); 82 | 83 | if( String(url).startsWith("https") ) { 84 | client.setInsecure(); 85 | http.begin( client, url ); 86 | } else { 87 | http.begin( url ); 88 | } 89 | 90 | int httpCode = http.GET(); 91 | 92 | if( httpCode != HTTP_CODE_OK && httpCode != HTTP_CODE_MOVED_PERMANENTLY ) { 93 | Serial.printf("Error: Status %i\n", httpCode ); 94 | } 95 | 96 | Stream* stream = http.getStreamPtr(); 97 | 98 | while (stream->available()) { 99 | Serial.write(stream->read()); 100 | } 101 | 102 | Serial.println("closing connection\n"); 103 | http.end(); 104 | } 105 | 106 | 107 | void setup() 108 | { 109 | Serial.begin( 115200 ); 110 | WiFi.onEvent( WiFiEvent ); 111 | ETH.begin( MISO_GPIO, MOSI_GPIO, SCLK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, SPI_HOST ); 112 | 113 | while( !eth_connected) { 114 | Serial.println("Connecting..."); 115 | delay( 1000 ); 116 | } 117 | 118 | testClient("https://google.com"); 119 | 120 | } 121 | 122 | 123 | void loop() 124 | { 125 | } 126 | -------------------------------------------------------------------------------- /examples/ETH_ENC28J60/ETH_ENC28J60.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This sketch shows the Ethernet event usage for ENC28J60 3 | 4 | */ 5 | 6 | #include 7 | 8 | #define SPI_HOST 1 9 | #define SPI_CLOCK_MHZ 8 10 | #define INT_GPIO 4 11 | // 12 | #if CONFIG_IDF_TARGET_ESP32 13 | #define MISO_GPIO 12 14 | #define MOSI_GPIO 13 15 | #define SCLK_GPIO 14 16 | #define CS_GPIO 15 17 | #elif CONFIG_IDF_TARGET_ESP32C3 18 | #define MISO_GPIO 2 19 | #define MOSI_GPIO 7 20 | #define SCLK_GPIO 6 21 | #define CS_GPIO 10 22 | #else 23 | #define MISO_GPIO 13 24 | #define MOSI_GPIO 11 25 | #define SCLK_GPIO 12 26 | #define CS_GPIO 10 27 | #endif 28 | 29 | 30 | static bool eth_connected = false; 31 | 32 | 33 | void WiFiEvent(WiFiEvent_t event) 34 | { 35 | switch (event) { 36 | case ARDUINO_EVENT_ETH_START: 37 | Serial.println("ETH Started"); 38 | //set eth hostname here 39 | ETH.setHostname("esp32-ethernet"); 40 | break; 41 | case ARDUINO_EVENT_ETH_CONNECTED: 42 | Serial.println("ETH Connected"); 43 | break; 44 | case ARDUINO_EVENT_ETH_GOT_IP: 45 | Serial.print("ETH MAC: "); 46 | Serial.print(ETH.macAddress()); 47 | Serial.print(", IPv4: "); 48 | Serial.print(ETH.localIP()); 49 | if (ETH.fullDuplex()) { 50 | Serial.print(", FULL_DUPLEX"); 51 | } 52 | Serial.print(", "); 53 | Serial.print(ETH.linkSpeed()); 54 | Serial.println("Mbps"); 55 | eth_connected = true; 56 | break; 57 | case ARDUINO_EVENT_ETH_DISCONNECTED: 58 | Serial.println("ETH Disconnected"); 59 | eth_connected = false; 60 | break; 61 | case ARDUINO_EVENT_ETH_STOP: 62 | Serial.println("ETH Stopped"); 63 | eth_connected = false; 64 | break; 65 | default: 66 | break; 67 | } 68 | } 69 | 70 | #include 71 | #include 72 | 73 | HTTPClient http; 74 | WiFiClientSecure client; 75 | 76 | 77 | void testClient(const char * url) 78 | { 79 | Serial.printf("\nConnecting to %s\n", url); 80 | 81 | http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); 82 | 83 | if( String(url).startsWith("https") ) { 84 | client.setInsecure(); 85 | http.begin( client, url ); 86 | } else { 87 | http.begin( url ); 88 | } 89 | 90 | int httpCode = http.GET(); 91 | 92 | if( httpCode != HTTP_CODE_OK && httpCode != HTTP_CODE_MOVED_PERMANENTLY ) { 93 | Serial.printf("Error: Status %i\n", httpCode ); 94 | } 95 | 96 | Stream* stream = http.getStreamPtr(); 97 | 98 | while (stream->available()) { 99 | Serial.write(stream->read()); 100 | } 101 | 102 | Serial.println("closing connection\n"); 103 | http.end(); 104 | } 105 | 106 | 107 | void setup() 108 | { 109 | Serial.begin( 115200 ); 110 | WiFi.onEvent( WiFiEvent ); 111 | ETH.begin( MISO_GPIO, MOSI_GPIO, SCLK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, SPI_HOST ); 112 | 113 | while( !eth_connected) { 114 | Serial.println("Connecting..."); 115 | delay( 1000 ); 116 | } 117 | 118 | testClient("https://google.com"); 119 | 120 | } 121 | 122 | 123 | void loop() 124 | { 125 | } 126 | -------------------------------------------------------------------------------- /examples/FOTA/FOTA.ino: -------------------------------------------------------------------------------- 1 | /* 2 | This sketch shows the Ethernet event usage for ENC28J60 3 | 4 | */ 5 | 6 | #include 7 | 8 | #define SPI_HOST 1 9 | #define SPI_CLOCK_MHZ 20 10 | #define INT_GPIO 4 11 | // 12 | #if CONFIG_IDF_TARGET_ESP32 13 | #define MISO_GPIO 12 14 | #define MOSI_GPIO 13 15 | #define SCLK_GPIO 14 16 | #define CS_GPIO 15 17 | #elif CONFIG_IDF_TARGET_ESP32C3 18 | #define MISO_GPIO 2 19 | #define MOSI_GPIO 7 20 | #define SCLK_GPIO 6 21 | #define CS_GPIO 10 22 | #else 23 | #define MISO_GPIO 13 24 | #define MOSI_GPIO 11 25 | #define SCLK_GPIO 12 26 | #define CS_GPIO 10 27 | #endif 28 | 29 | 30 | // test regular firmware (no compression) 31 | // #define FOTA_URL "https://github.com/tobozo/ESP32-ENC28J60/raw/main/examples/FOTA/bin/firmware.json" 32 | 33 | // test with gzip compression 34 | #include // https://github.com/tobozo/ESP32-targz.h 35 | #define FOTA_URL "https://github.com/tobozo/ESP32-ENC28J60/raw/main/examples/FOTA/bin/firmware.gz.json" 36 | 37 | 38 | // test with zlib compression 39 | //#include // https://github.com/vortigont/esp32-flashz 40 | //#define FOTA_URL "https://github.com/tobozo/ESP32-ENC28J60/raw/main/examples/FOTA/bin/firmware.zz.json" 41 | 42 | 43 | #include // https://github.com/chrisjoyce911/esp32FOTA 44 | #include 45 | 46 | // esp32fota settings 47 | int firmware_version_major = 0; 48 | int firmware_version_minor = 1; 49 | int firmware_version_patch = 0; 50 | const char* firmware_name = "esp32-fota-http"; 51 | const bool check_signature = false; 52 | const bool disable_security = true; 53 | // for debug only 54 | const char* title = "0.1"; 55 | const char* description = "Basic Ethernet example with no security and no filesystem"; 56 | 57 | 58 | esp32FOTA FOTA; 59 | 60 | 61 | static bool eth_connected = false; 62 | 63 | 64 | static bool EthernetConnected() 65 | { 66 | return eth_connected; 67 | } 68 | 69 | 70 | // using WiFiEvent to handle Ethernet events :-) 71 | void WiFiEvent(WiFiEvent_t event) 72 | { 73 | switch (event) { 74 | case ARDUINO_EVENT_ETH_START: 75 | Serial.println("ETH Started"); 76 | ETH.setHostname("esp32-ethernet"); 77 | break; 78 | case ARDUINO_EVENT_ETH_CONNECTED: 79 | Serial.println("ETH Connected"); 80 | break; 81 | case ARDUINO_EVENT_ETH_GOT_IP: 82 | Serial.printf("ETH MAC: %s, IPv4: %s%s, %dMbps\n", ETH.macAddress().c_str(), ETH.localIP().toString().c_str(), ETH.fullDuplex()?", FULL_DUPLEX":"", ETH.linkSpeed() ); 83 | eth_connected = true; 84 | break; 85 | case ARDUINO_EVENT_ETH_DISCONNECTED: 86 | Serial.println("ETH Disconnected"); 87 | eth_connected = false; 88 | break; 89 | case ARDUINO_EVENT_ETH_STOP: 90 | Serial.println("ETH Stopped"); 91 | eth_connected = false; 92 | break; 93 | default: 94 | break; 95 | } 96 | } 97 | 98 | 99 | void setup() 100 | { 101 | Serial.begin(115200); 102 | 103 | PrintFOTAInfo(); 104 | 105 | { 106 | auto cfg = FOTA.getConfig(); 107 | cfg.name = firmware_name; 108 | cfg.manifest_url = FOTA_URL; 109 | cfg.sem = SemverClass( firmware_version_major, firmware_version_minor, firmware_version_patch ); 110 | cfg.check_sig = check_signature; 111 | cfg.unsafe = disable_security; 112 | //cfg.root_ca = MyRootCA; 113 | //cfg.pub_key = MyRSAKey; 114 | FOTA.setConfig( cfg ); 115 | FOTA.setStatusChecker( EthernetConnected ); 116 | //GzUpdateClass::getInstance().useDict( true ); 117 | } 118 | 119 | WiFi.onEvent( WiFiEvent ); 120 | ETH.begin( MISO_GPIO, MOSI_GPIO, SCLK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, SPI_HOST ); 121 | 122 | } 123 | 124 | 125 | void loop() 126 | { 127 | if( !eth_connected) { 128 | Serial.println("Connecting..."); 129 | delay(1000); 130 | return; 131 | } 132 | FOTA.handle(); 133 | delay(20000); 134 | } 135 | 136 | -------------------------------------------------------------------------------- /src/extmod/esp_eth_enc28j60.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include "esp_eth_phy.h" 22 | #include "esp_eth_mac.h" 23 | #include "driver/spi_master.h" 24 | 25 | #define CS_HOLD_TIME_MIN_NS 210 26 | 27 | /** 28 | * @brief ENC28J60 specific configuration 29 | * 30 | */ 31 | typedef struct { 32 | spi_device_handle_t spi_hdl; /*!< Handle of SPI device driver */ 33 | int int_gpio_num; /*!< Interrupt GPIO number */ 34 | } eth_enc28j60_config_t; 35 | 36 | /** 37 | * @brief ENC28J60 Supported Revisions 38 | * 39 | */ 40 | typedef enum { 41 | ENC28J60_REV_B1 = 0b00000010, 42 | ENC28J60_REV_B4 = 0b00000100, 43 | ENC28J60_REV_B5 = 0b00000101, 44 | ENC28J60_REV_B7 = 0b00000110 45 | } eth_enc28j60_rev_t; 46 | 47 | /** 48 | * @brief Default ENC28J60 specific configuration 49 | * 50 | */ 51 | #define ETH_ENC28J60_DEFAULT_CONFIG(spi_device) \ 52 | { \ 53 | .spi_hdl = spi_device, \ 54 | .int_gpio_num = 4, \ 55 | } 56 | 57 | /** 58 | * @brief Compute amount of SPI bit-cycles the CS should stay active after the transmission 59 | * to meet ENC28J60 CS Hold Time specification. 60 | * 61 | * @param clock_speed_mhz SPI Clock frequency in MHz (valid range is <1, 20>) 62 | * @return uint8_t 63 | */ 64 | static inline uint8_t enc28j60_cal_spi_cs_hold_time(int clock_speed_mhz) 65 | { 66 | if (clock_speed_mhz <= 0 || clock_speed_mhz > 20) { 67 | return 0; 68 | } 69 | int temp = clock_speed_mhz * CS_HOLD_TIME_MIN_NS; 70 | uint8_t cs_posttrans = temp / 1000; 71 | if (temp % 1000) { 72 | cs_posttrans += 1; 73 | } 74 | 75 | return cs_posttrans; 76 | } 77 | 78 | /** 79 | * @brief Create ENC28J60 Ethernet MAC instance 80 | * 81 | * @param[in] enc28j60_config: ENC28J60 specific configuration 82 | * @param[in] mac_config: Ethernet MAC configuration 83 | * 84 | * @return 85 | * - instance: create MAC instance successfully 86 | * - NULL: create MAC instance failed because some error occurred 87 | */ 88 | esp_eth_mac_t *esp_eth_mac_new_enc28j60(const eth_enc28j60_config_t *enc28j60_config, const eth_mac_config_t *mac_config); 89 | 90 | /** 91 | * @brief Create a PHY instance of ENC28J60 92 | * 93 | * @param[in] config: configuration of PHY 94 | * 95 | * @return 96 | * - instance: create PHY instance successfully 97 | * - NULL: create PHY instance failed because some error occurred 98 | */ 99 | esp_eth_phy_t *esp_eth_phy_new_enc28j60(const eth_phy_config_t *config); 100 | 101 | // todo: the below functions should be accessed through ioctl in the future 102 | /** 103 | * @brief Set ENC28J60 Duplex mode. It sets Duplex mode first to the PHY and then 104 | * MAC is set based on what PHY indicates. 105 | * 106 | * @param phy ENC28J60 PHY Handle 107 | * @param duplex Duplex mode 108 | * 109 | * @return esp_err_t 110 | * - ESP_OK when PHY registers were correctly written. 111 | */ 112 | esp_err_t enc28j60_set_phy_duplex(esp_eth_phy_t *phy, eth_duplex_t duplex); 113 | 114 | /** 115 | * @brief Get ENC28J60 silicon revision ID 116 | * 117 | * @param mac ENC28J60 MAC Handle 118 | * @return eth_enc28j60_rev_t 119 | * - returns silicon revision ID read during initialization 120 | */ 121 | eth_enc28j60_rev_t emac_enc28j60_get_chip_info(esp_eth_mac_t *mac); 122 | 123 | #ifdef __cplusplus 124 | } 125 | #endif 126 | -------------------------------------------------------------------------------- /src/ESP32-ENC28J60.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ESP32-ENC28J60.h - ETH PHY support for ENC28J60 3 | Based on ETH.h from Arduino-esp32 and ENC28J60 component from esp-idf. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #include "ESP32-ENC28J60.h" 21 | 22 | 23 | extern "C" { 24 | esp_eth_mac_t* enc28j60_begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, int SPI_HOST); 25 | #include "extmod/esp_eth_enc28j60.h" 26 | } 27 | 28 | #include "esp_event.h" 29 | #include "esp_eth_phy.h" 30 | #include "esp_eth_mac.h" 31 | #include "esp_eth_com.h" 32 | #if CONFIG_IDF_TARGET_ESP32 33 | #include "soc/emac_ext_struct.h" 34 | #include "soc/rtc.h" 35 | //#include "soc/io_mux_reg.h" 36 | //#include "hal/gpio_hal.h" 37 | #endif 38 | 39 | #include "lwip/err.h" 40 | #include "lwip/dns.h" 41 | 42 | extern void tcpipInit(); 43 | 44 | /** 45 | * @brief Callback function invoked when lowlevel initialization is finished 46 | * 47 | * @param[in] eth_handle: handle of Ethernet driver 48 | * 49 | * @return 50 | * - ESP_OK: process extra lowlevel initialization successfully 51 | * - ESP_FAIL: error occurred when processing extra lowlevel initialization 52 | */ 53 | 54 | //static eth_clock_mode_t eth_clock_mode = ETH_CLK_MODE; 55 | 56 | /** 57 | * @brief Callback function invoked when lowlevel deinitialization is finished 58 | * 59 | * @param[in] eth_handle: handle of Ethernet driver 60 | * 61 | * @return 62 | * - ESP_OK: process extra lowlevel deinitialization successfully 63 | * - ESP_FAIL: error occurred when processing extra lowlevel deinitialization 64 | */ 65 | //static esp_err_t on_lowlevel_deinit_done(esp_eth_handle_t eth_handle){ 66 | // return ESP_OK; 67 | //} 68 | 69 | 70 | 71 | 72 | 73 | ENC28J60Class::ENC28J60Class() 74 | :initialized(false) 75 | ,staticIP(false) 76 | ,eth_handle(NULL) 77 | ,started(false) 78 | ,eth_link(ETH_LINK_DOWN) 79 | { 80 | } 81 | 82 | ENC28J60Class::~ENC28J60Class() 83 | {} 84 | 85 | //bool ENC28J60Class::begin(uint8_t phy_addr, int power, int mdc, int mdio, eth_phy_type_t type, eth_clock_mode_t clock_mode, bool use_mac_from_efuse) 86 | bool ENC28J60Class::begin(int MISO_GPIO, int MOSI_GPIO, int SCLK_GPIO, int CS_GPIO, int INT_GPIO, int SPI_CLOCK_MHZ, int SPI_HOST, bool use_mac_from_efuse) 87 | { 88 | tcpipInit(); 89 | 90 | uint8_t ENC28J60_Default_Mac[6] = { 0x02, 0x00, 0x00, 0x12, 0x34, 0x56 }; 91 | 92 | if (use_mac_from_efuse) { 93 | esp_efuse_mac_get_custom( ENC28J60_Default_Mac ); 94 | } 95 | esp_base_mac_addr_set( ENC28J60_Default_Mac ); 96 | 97 | tcpip_adapter_set_default_eth_handlers(); 98 | 99 | esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH(); 100 | esp_netif_t *eth_netif = esp_netif_new(&cfg); 101 | 102 | esp_eth_mac_t *eth_mac = enc28j60_begin(MISO_GPIO, MOSI_GPIO, SCLK_GPIO, CS_GPIO, INT_GPIO, SPI_CLOCK_MHZ, SPI_HOST); 103 | 104 | if(eth_mac == NULL){ 105 | log_e("esp_eth_mac_new_esp32 failed"); 106 | return false; 107 | } 108 | 109 | eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); 110 | phy_config.autonego_timeout_ms = 0; // ENC28J60 doesn't support auto-negotiation 111 | phy_config.reset_gpio_num = -1; // ENC28J60 doesn't have a pin to reset internal PHY 112 | esp_eth_phy_t *eth_phy = esp_eth_phy_new_enc28j60(&phy_config); 113 | 114 | if(eth_phy == NULL){ 115 | log_e("esp_eth_phy_new failed"); 116 | return false; 117 | } 118 | 119 | eth_handle = NULL; 120 | esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(eth_mac, eth_phy); 121 | //eth_config.on_lowlevel_init_done = on_lowlevel_init_done; 122 | //eth_config.on_lowlevel_deinit_done = on_lowlevel_deinit_done; 123 | if(esp_eth_driver_install(ð_config, ð_handle) != ESP_OK || eth_handle == NULL){ 124 | log_e("esp_eth_driver_install failed"); 125 | return false; 126 | } 127 | 128 | /* ENC28J60 doesn't burn any factory MAC address, we need to set it manually. 129 | 02:00:00 is a Locally Administered OUI range so should not be used except when testing on a LAN under your control. 130 | */ 131 | eth_mac->set_addr(eth_mac, ENC28J60_Default_Mac); 132 | 133 | // ENC28J60 Errata #1 check 134 | if (emac_enc28j60_get_chip_info(eth_mac) < ENC28J60_REV_B5 && SPI_CLOCK_MHZ < 8) { 135 | log_e("SPI frequency must be at least 8 MHz for chip revision less than 5"); 136 | ESP_ERROR_CHECK(ESP_FAIL); 137 | } 138 | 139 | /* attach Ethernet driver to TCP/IP stack */ 140 | if(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)) != ESP_OK){ 141 | log_e("esp_netif_attach failed"); 142 | return false; 143 | } 144 | 145 | if(esp_eth_start(eth_handle) != ESP_OK){ 146 | log_e("esp_eth_start failed"); 147 | return false; 148 | } 149 | 150 | // holds a few microseconds to let DHCP start and enter into a good state 151 | // FIX ME -- adresses issue https://github.com/espressif/arduino-esp32/issues/5733 152 | delay(50); 153 | 154 | return true; 155 | } 156 | 157 | bool ENC28J60Class::config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1, IPAddress dns2) 158 | { 159 | esp_err_t err = ESP_OK; 160 | tcpip_adapter_ip_info_t info; 161 | 162 | if(static_cast(local_ip) != 0){ 163 | info.ip.addr = static_cast(local_ip); 164 | info.gw.addr = static_cast(gateway); 165 | info.netmask.addr = static_cast(subnet); 166 | } else { 167 | info.ip.addr = 0; 168 | info.gw.addr = 0; 169 | info.netmask.addr = 0; 170 | } 171 | 172 | err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_ETH); 173 | if(err != ESP_OK && err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED){ 174 | log_e("DHCP could not be stopped! Error: %d", err); 175 | return false; 176 | } 177 | 178 | err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_ETH, &info); 179 | if(err != ERR_OK){ 180 | log_e("STA IP could not be configured! Error: %d", err); 181 | return false; 182 | } 183 | 184 | if(info.ip.addr){ 185 | staticIP = true; 186 | } else { 187 | err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_ETH); 188 | if(err != ESP_OK && err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STARTED){ 189 | log_w("DHCP could not be started! Error: %d", err); 190 | return false; 191 | } 192 | staticIP = false; 193 | } 194 | 195 | ip_addr_t d; 196 | d.type = IPADDR_TYPE_V4; 197 | 198 | if(static_cast(dns1) != 0) { 199 | // Set DNS1-Server 200 | d.u_addr.ip4.addr = static_cast(dns1); 201 | dns_setserver(0, &d); 202 | } 203 | 204 | if(static_cast(dns2) != 0) { 205 | // Set DNS2-Server 206 | d.u_addr.ip4.addr = static_cast(dns2); 207 | dns_setserver(1, &d); 208 | } 209 | 210 | return true; 211 | } 212 | 213 | IPAddress ENC28J60Class::localIP() 214 | { 215 | tcpip_adapter_ip_info_t ip; 216 | if(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)){ 217 | return IPAddress(); 218 | } 219 | return IPAddress(ip.ip.addr); 220 | } 221 | 222 | IPAddress ENC28J60Class::subnetMask() 223 | { 224 | tcpip_adapter_ip_info_t ip; 225 | if(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)){ 226 | return IPAddress(); 227 | } 228 | return IPAddress(ip.netmask.addr); 229 | } 230 | 231 | IPAddress ENC28J60Class::gatewayIP() 232 | { 233 | tcpip_adapter_ip_info_t ip; 234 | if(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)){ 235 | return IPAddress(); 236 | } 237 | return IPAddress(ip.gw.addr); 238 | } 239 | 240 | IPAddress ENC28J60Class::dnsIP(uint8_t dns_no) 241 | { 242 | const ip_addr_t * dns_ip = dns_getserver(dns_no); 243 | return IPAddress(dns_ip->u_addr.ip4.addr); 244 | } 245 | 246 | IPAddress ENC28J60Class::broadcastIP() 247 | { 248 | tcpip_adapter_ip_info_t ip; 249 | if(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)){ 250 | return IPAddress(); 251 | } 252 | return WiFiGenericClass::calculateBroadcast(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr)); 253 | } 254 | 255 | IPAddress ENC28J60Class::networkID() 256 | { 257 | tcpip_adapter_ip_info_t ip; 258 | if(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)){ 259 | return IPAddress(); 260 | } 261 | return WiFiGenericClass::calculateNetworkID(IPAddress(ip.gw.addr), IPAddress(ip.netmask.addr)); 262 | } 263 | 264 | uint8_t ENC28J60Class::subnetCIDR() 265 | { 266 | tcpip_adapter_ip_info_t ip; 267 | if(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)){ 268 | return (uint8_t)0; 269 | } 270 | return WiFiGenericClass::calculateSubnetCIDR(IPAddress(ip.netmask.addr)); 271 | } 272 | 273 | const char * ENC28J60Class::getHostname() 274 | { 275 | const char * hostname; 276 | if(tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_ETH, &hostname)){ 277 | return NULL; 278 | } 279 | return hostname; 280 | } 281 | 282 | bool ENC28J60Class::setHostname(const char * hostname) 283 | { 284 | return tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, hostname) == 0; 285 | } 286 | 287 | bool ENC28J60Class::fullDuplex() 288 | { 289 | return true;//todo: do not see an API for this 290 | } 291 | 292 | bool ENC28J60Class::linkUp() 293 | { 294 | return eth_link == ETH_LINK_UP; 295 | } 296 | 297 | uint8_t ENC28J60Class::linkSpeed() 298 | { 299 | eth_speed_t link_speed; 300 | esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &link_speed); 301 | return (link_speed == ETH_SPEED_10M)?10:100; 302 | } 303 | 304 | bool ENC28J60Class::enableIpV6() 305 | { 306 | return tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_ETH) == 0; 307 | } 308 | 309 | IPv6Address ENC28J60Class::localIPv6() 310 | { 311 | static ip6_addr_t addr; 312 | if(tcpip_adapter_get_ip6_linklocal(TCPIP_ADAPTER_IF_ETH, &addr)){ 313 | return IPv6Address(); 314 | } 315 | return IPv6Address(addr.addr); 316 | } 317 | 318 | uint8_t * ENC28J60Class::macAddress(uint8_t* mac) 319 | { 320 | if(!mac){ 321 | return NULL; 322 | } 323 | esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac); 324 | return mac; 325 | } 326 | 327 | String ENC28J60Class::macAddress(void) 328 | { 329 | uint8_t mac[6] = {0,0,0,0,0,0}; 330 | char macStr[18] = { 0 }; 331 | macAddress(mac); 332 | sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 333 | return String(macStr); 334 | } 335 | 336 | ENC28J60Class ETH; 337 | -------------------------------------------------------------------------------- /src/extmod/enc28j60.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | /** 22 | * @brief SPI Instruction Set 23 | * 24 | */ 25 | #define ENC28J60_SPI_CMD_RCR (0x00) // Read Control Register 26 | #define ENC28J60_SPI_CMD_RBM (0x01) // Read Buffer Memory 27 | #define ENC28J60_SPI_CMD_WCR (0x02) // Write Control Register 28 | #define ENC28J60_SPI_CMD_WBM (0x03) // Write Buffer Memory 29 | #define ENC28J60_SPI_CMD_BFS (0x04) // Bit Field Set 30 | #define ENC28J60_SPI_CMD_BFC (0x05) // Bit Field Clear 31 | #define ENC28J60_SPI_CMD_SRC (0x07) // Soft Reset 32 | 33 | /** 34 | * @brief Shared Registers in ENC28J60 (accessible on each bank) 35 | * 36 | */ 37 | #define ENC28J60_EIE (0x1B) // Ethernet Interrupt Enable 38 | #define ENC28J60_EIR (0x1C) // Ethernet Interrupt flags 39 | #define ENC28J60_ESTAT (0x1D) // Ethernet Status 40 | #define ENC28J60_ECON2 (0x1E) // Ethernet Control Register2 41 | #define ENC28J60_ECON1 (0x1F) // Ethernet Control Register1 42 | 43 | /** 44 | * @brief Per-bank Registers in ENC28J60 45 | * @note Address[15:12]: Register Type, 0 -> ETH, 1 -> MII/MAC 46 | * Address[11:8] : Bank address 47 | * Address[7:0] : Register Index 48 | */ 49 | // Bank 0 Registers 50 | #define ENC28J60_ERDPTL (0x0000) // Read Pointer Low Byte ERDPT<7:0>) 51 | #define ENC28J60_ERDPTH (0x0001) // Read Pointer High Byte (ERDPT<12:8>) 52 | #define ENC28J60_EWRPTL (0x0002) // Write Pointer Low Byte (EWRPT<7:0>) 53 | #define ENC28J60_EWRPTH (0x0003) // Write Pointer High Byte (EWRPT<12:8>) 54 | #define ENC28J60_ETXSTL (0x0004) // TX Start Low Byte (ETXST<7:0>) 55 | #define ENC28J60_ETXSTH (0x0005) // TX Start High Byte (ETXST<12:8>) 56 | #define ENC28J60_ETXNDL (0x0006) // TX End Low Byte (ETXND<7:0>) 57 | #define ENC28J60_ETXNDH (0x0007) // TX End High Byte (ETXND<12:8>) 58 | #define ENC28J60_ERXSTL (0x0008) // RX Start Low Byte (ERXST<7:0>) 59 | #define ENC28J60_ERXSTH (0x0009) // RX Start High Byte (ERXST<12:8>) 60 | #define ENC28J60_ERXNDL (0x000A) // RX End Low Byte (ERXND<7:0>) 61 | #define ENC28J60_ERXNDH (0x000B) // RX End High Byte (ERXND<12:8>) 62 | #define ENC28J60_ERXRDPTL (0x000C) // RX RD Pointer Low Byte (ERXRDPT<7:0>) 63 | #define ENC28J60_ERXRDPTH (0x000D) // RX RD Pointer High Byte (ERXRDPT<12:8>) 64 | #define ENC28J60_ERXWRPTL (0x000E) // RX WR Pointer Low Byte (ERXWRPT<7:0>) 65 | #define ENC28J60_ERXWRPTH (0x000F) // RX WR Pointer High Byte (ERXWRPT<12:8>) 66 | #define ENC28J60_EDMASTL (0x0010) // DMA Start Low Byte (EDMAST<7:0>) 67 | #define ENC28J60_EDMASTH (0x0011) // DMA Start High Byte (EDMAST<12:8>) 68 | #define ENC28J60_EDMANDL (0x0012) // DMA End Low Byte (EDMAND<7:0>) 69 | #define ENC28J60_EDMANDH (0x0013) // DMA End High Byte (EDMAND<12:8>) 70 | #define ENC28J60_EDMADSTL (0x0014) // DMA Destination Low Byte (EDMADST<7:0>) 71 | #define ENC28J60_EDMADSTH (0x0015) // DMA Destination High Byte (EDMADST<12:8>) 72 | #define ENC28J60_EDMACSL (0x0016) // DMA Checksum Low Byte (EDMACS<7:0>) 73 | #define ENC28J60_EDMACSH (0x0017) // DMA Checksum High Byte (EDMACS<15:8>) 74 | 75 | // Bank 1 Registers 76 | #define ENC28J60_EHT0 (0x0100) // Hash Table Byte 0 (EHT<7:0>) 77 | #define ENC28J60_EHT1 (0x0101) // Hash Table Byte 1 (EHT<15:8>) 78 | #define ENC28J60_EHT2 (0x0102) // Hash Table Byte 2 (EHT<23:16>) 79 | #define ENC28J60_EHT3 (0x0103) // Hash Table Byte 3 (EHT<31:24>) 80 | #define ENC28J60_EHT4 (0x0104) // Hash Table Byte 4 (EHT<39:32>) 81 | #define ENC28J60_EHT5 (0x0105) // Hash Table Byte 5 (EHT<47:40>) 82 | #define ENC28J60_EHT6 (0x0106) // Hash Table Byte 6 (EHT<55:48>) 83 | #define ENC28J60_EHT7 (0x0107) // Hash Table Byte 7 (EHT<63:56>) 84 | #define ENC28J60_EPMM0 (0x0108) // Pattern Match Mask Byte 0 (EPMM<7:0>) 85 | #define ENC28J60_EPMM1 (0x0109) // Pattern Match Mask Byte 1 (EPMM<15:8>) 86 | #define ENC28J60_EPMM2 (0x010A) // Pattern Match Mask Byte 2 (EPMM<23:16>) 87 | #define ENC28J60_EPMM3 (0x010B) // Pattern Match Mask Byte 3 (EPMM<31:24>) 88 | #define ENC28J60_EPMM4 (0x010C) // Pattern Match Mask Byte 4 (EPMM<39:32>) 89 | #define ENC28J60_EPMM5 (0x010D) // Pattern Match Mask Byte 5 (EPMM<47:40>) 90 | #define ENC28J60_EPMM6 (0x010E) // Pattern Match Mask Byte 6 (EPMM<55:48>) 91 | #define ENC28J60_EPMM7 (0x010F) // Pattern Match Mask Byte 7 (EPMM<63:56>) 92 | #define ENC28J60_EPMCSL (0x0110) // Pattern Match Checksum Low Byte (EPMCS<7:0>) 93 | #define ENC28J60_EPMCSH (0x0111) // Pattern Match Checksum High Byte (EPMCS<15:0>) 94 | #define ENC28J60_EPMOL (0x0114) // Pattern Match Offset Low Byte (EPMO<7:0>) 95 | #define ENC28J60_EPMOH (0x0115) // Pattern Match Offset High Byte (EPMO<12:8>) 96 | #define ENC28J60_ERXFCON (0x0118) // Receive Fileter Control 97 | #define ENC28J60_EPKTCNT (0x0119) // Ethernet Packet Count 98 | 99 | // Bank 2 Register 100 | #define ENC28J60_MACON1 (0x1200) // MAC Control Register 1 101 | #define ENC28J60_MACON2 (0x1201) // MAC Control Register 2 102 | #define ENC28J60_MACON3 (0x1202) // MAC Control Register 3 103 | #define ENC28J60_MACON4 (0x1203) // MAC Control Register 4 104 | #define ENC28J60_MABBIPG (0x1204) // Back-to-Back Inter-Packet Gap (BBIPG<6:0>) 105 | #define ENC28J60_MAIPGL (0x1206) // Non-Back-to-Back Inter-Packet Gap Low Byte (MAIPGL<6:0>) 106 | #define ENC28J60_MAIPGH (0x1207) // Non-Back-to-Back Inter-Packet Gap High Byte (MAIPGH<6:0>) 107 | #define ENC28J60_MACLCON1 (0x1208) // Retransmission Maximum (RETMAX<3:0>) 108 | #define ENC28J60_MACLCON2 (0x1209) // Collision Window (COLWIN<5:0>) 109 | #define ENC28J60_MAMXFLL (0x120A) // Maximum Frame Length Low Byte (MAMXFL<7:0>) 110 | #define ENC28J60_MAMXFLH (0x120B) // Maximum Frame Length High Byte (MAMXFL<15:8>) 111 | #define ENC28J60_MICMD (0x1212) // MII Command Register 112 | #define ENC28J60_MIREGADR (0x1214) // MII Register Address (MIREGADR<4:0>) 113 | #define ENC28J60_MIWRL (0x1216) // MII Write Data Low Byte (MIWR<7:0>) 114 | #define ENC28J60_MIWRH (0x1217) // MII Write Data High Byte (MIWR<15:8>) 115 | #define ENC28J60_MIRDL (0x1218) // MII Read Data Low Byte (MIRD<7:0>) 116 | #define ENC28J60_MIRDH (0x1219) // MII Read Data High Byte(MIRD<15:8>) 117 | 118 | // Bank 3 Registers 119 | #define ENC28J60_MAADR5 (0x1300) // MAC Address Byte 5 (MAADR<15:8>) 120 | #define ENC28J60_MAADR6 (0x1301) // MAC Address Byte 6 (MAADR<7:0>) 121 | #define ENC28J60_MAADR3 (0x1302) // MAC Address Byte 3 (MAADR<31:24>), OUI Byte 3 122 | #define ENC28J60_MAADR4 (0x1303) // MAC Address Byte 4 (MAADR<23:16>) 123 | #define ENC28J60_MAADR1 (0x1304) // MAC Address Byte 1 (MAADR<47:40>), OUI Byte 1 124 | #define ENC28J60_MAADR2 (0x1305) // MAC Address Byte 2 (MAADR<39:32>), OUI Byte 2 125 | #define ENC28J60_EBSTSD (0x0306) // Built-in Self-Test Fill Seed (EBSTSD<7:0>) 126 | #define ENC28J60_EBSTCON (0x0307) // Built-in Self-Test Control 127 | #define ENC28J60_EBSTCSL (0x0308) // Built-in Self-Test Checksum Low Byte (EBSTCS<7:0>) 128 | #define ENC28J60_EBSTCSH (0x0309) // Built-in Self-Test Checksum High Byte (EBSTCS<15:8>) 129 | #define ENC28J60_MISTAT (0x130A) // MII Status Register 130 | #define ENC28J60_EREVID (0x0312) // Ethernet Revision ID (EREVID<4:0>) 131 | #define ENC28J60_ECOCON (0x0315) // Clock Output Control Register 132 | #define ENC28J60_EFLOCON (0x0317) // Ethernet Flow Control 133 | #define ENC28J60_EPAUSL (0x0318) // Pause Timer Value Low Byte (EPAUS<7:0>) 134 | #define ENC28J60_EPAUSH (0x0319) // Pause Timer Value High Byte (EPAUS<15:8>) 135 | 136 | /** 137 | * @brief status and flag of ENC28J60 specific registers 138 | * 139 | */ 140 | // EIE bit definitions 141 | #define EIE_INTIE (1<<7) // Global INT Interrupt Enable 142 | #define EIE_PKTIE (1<<6) // Receive Packet Pending Interrupt Enable 143 | #define EIE_DMAIE (1<<5) // DMA Interrupt Enable 144 | #define EIE_LINKIE (1<<4) // Link Status Change Interrupt Enable 145 | #define EIE_TXIE (1<<3) // Transmit Enable 146 | #define EIE_TXERIE (1<<1) // Transmit Error Interrupt Enable 147 | #define EIE_RXERIE (1<<0) // Receive Error Interrupt Enable 148 | 149 | // EIR bit definitions 150 | #define EIR_PKTIF (1<<6) // Receive Packet Pending Interrupt Flag 151 | #define EIR_DMAIF (1<<5) // DMA Interrupt Flag 152 | #define EIR_LINKIF (1<<4) // Link Change Interrupt Flag 153 | #define EIR_TXIF (1<<3) // Transmit Interrupt Flag 154 | #define EIR_TXERIF (1<<1) // Transmit Error Interrupt Flag 155 | #define EIR_RXERIF (1<<0) // Receive Error Interrupt Flag 156 | 157 | // ESTAT bit definitions 158 | #define ESTAT_INT (1<<7) // INT Interrupt Flag 159 | #define ESTAT_BUFER (1<<6) // Buffer Error Status 160 | #define ESTAT_LATECOL (1<<4) // Late Collision Error 161 | #define ESTAT_RXBUSY (1<<2) // Receive Busy 162 | #define ESTAT_TXABRT (1<<1) // Transmit Abort Error 163 | #define ESTAT_CLKRDY (1<<0) // Clock Ready 164 | 165 | // ECON2 bit definitions 166 | #define ECON2_AUTOINC (1<<7) // Automatic Buffer Pointer Increment Enable 167 | #define ECON2_PKTDEC (1<<6) // Packet Decrement 168 | #define ECON2_PWRSV (1<<5) // Power Save Enable 169 | #define ECON2_VRPS (1<<3) // Voltage Regulator Power Save Enable 170 | 171 | // ECON1 bit definitions 172 | #define ECON1_TXRST (1<<7) // Transmit Logic Reset 173 | #define ECON1_RXRST (1<<6) // Receive Logic Reset 174 | #define ECON1_DMAST (1<<5) // DMA Start and Busy Status 175 | #define ECON1_CSUMEN (1<<4) // DMA Checksum Enable 176 | #define ECON1_TXRTS (1<<3) // Transmit Request to Send 177 | #define ECON1_RXEN (1<<2) // Receive Enable 178 | #define ECON1_BSEL1 (1<<1) // Bank Select1 179 | #define ECON1_BSEL0 (1<<0) // Bank Select0 180 | 181 | // ERXFCON bit definitions 182 | #define ERXFCON_UCEN (1<<7) // Unicast Filter Enable 183 | #define ERXFCON_ANDOR (1<<6) // AND/OR Filter Select 184 | #define ERXFCON_CRCEN (1<<5) // Post-Filter CRC Check Enable 185 | #define ERXFCON_PMEN (1<<4) // Pattern Match Filter Enable 186 | #define ERXFCON_MPEN (1<<3) // Magic Packet Filter Enable 187 | #define ERXFCON_HTEN (1<<2) // Hash Table Filter Enable 188 | #define ERXFCON_MCEN (1<<1) // Multicast Filter Enable 189 | #define ERXFCON_BCEN (1<<0) // Broadcast Filter Enable 190 | 191 | // MACON1 bit definitions 192 | #define MACON1_TXPAUS (1<<3) // Pause Control Frame Transmission Enable 193 | #define MACON1_RXPAUS (1<<2) // Pause Control Frame Reception Enable 194 | #define MACON1_PASSALL (1<<1) // Pass All Received Frames Enable 195 | #define MACON1_MARXEN (1<<0) // MAC Receive Enable 196 | 197 | // MACON3 bit definitions 198 | #define MACON3_PADCFG2 (1<<7) // Automatic Pad and CRC Configuration bit 2 199 | #define MACON3_PADCFG1 (1<<6) // Automatic Pad and CRC Configuration bit 1 200 | #define MACON3_PADCFG0 (1<<5) // Automatic Pad and CRC Configuration bit 0 201 | #define MACON3_TXCRCEN (1<<4) // Transmit CRC Enable 202 | #define MACON3_PHDRLEN (1<<3) // Proprietary Header Enable 203 | #define MACON3_HFRMLEN (1<<2) // Huge Frame Enable 204 | #define MACON3_FRMLNEN (1<<1) // Frame Length Checking Enable 205 | #define MACON3_FULDPX (1<<0) // MAC Full-Duplex Enable 206 | 207 | // MACON4 bit definitions 208 | #define MACON4_DEFER (1<<6) // Defer Transmission Enable 209 | #define MACON4_BPEN (1<<5) // No Backoff During Backpressure Enable 210 | #define MACON4_NOBKFF (1<<4) // No Backoff Enable 211 | 212 | // MICMD bit definitions 213 | #define MICMD_MIISCAN (1<<1) // MII Scan Enable 214 | #define MICMD_MIIRD (1<<0) // MII Read Enable 215 | 216 | // EBSTCON bit definitions 217 | #define EBSTCON_PSV2 (1<<7) // Pattern Shift Value 2 218 | #define EBSTCON_PSV1 (1<<6) // Pattern Shift Value 1 219 | #define EBSTCON_PSV0 (1<<5) // Pattern Shift Value 0 220 | #define EBSTCON_PSEL (1<<4) // Port Select 221 | #define EBSTCON_TMSEL1 (1<<3) // Test Mode Select 1 222 | #define EBSTCON_TMSEL0 (1<<2) // Test Mode Select 0 223 | #define EBSTCON_TME (1<<1) // Test Mode Enable 224 | #define EBSTCON_BISTST (1<<0) // Built-in Self-Test Start/Busy 225 | 226 | // MISTAT bit definitions 227 | #define MISTAT_NVALID (1<<2) // MII Management Read Data Not Valid 228 | #define MISTAT_SCAN (1<<1) // MII Management Scan Operation in Progress 229 | #define MISTAT_BUSY (1<<0) // MII Management Busy 230 | 231 | // EFLOCON bit definitions 232 | #define EFLOCON_FULDPXS (1<<2) // Full-Duplex Shadown 233 | #define EFLOCON_FCEN1 (1<<1) // Flow Control Enable 1 234 | #define EFLOCON_FCEN0 (1<<0) // Flow Control Enable 0 235 | 236 | #ifdef __cplusplus 237 | } 238 | #endif 239 | -------------------------------------------------------------------------------- /src/extmod/esp_eth_phy_enc28j60.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | #include 17 | #include "esp_log.h" 18 | #include "esp_eth.h" 19 | #include "eth_phy_regs_struct.h" 20 | #include "esp_eth_enc28j60.h" 21 | #include "freertos/FreeRTOS.h" 22 | #include "freertos/task.h" 23 | #include "driver/gpio.h" 24 | 25 | static const char *TAG = "enc28j60"; 26 | #define PHY_CHECK(a, str, goto_tag, ...) \ 27 | do \ 28 | { \ 29 | if (!(a)) \ 30 | { \ 31 | ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ 32 | goto goto_tag; \ 33 | } \ 34 | } while (0) 35 | 36 | /***************Vendor Specific Register***************/ 37 | 38 | /** 39 | * @brief PHCON2(PHY Control Register 2) 40 | * 41 | */ 42 | typedef union { 43 | struct { 44 | uint32_t reserved_7_0 : 8; // Reserved 45 | uint32_t pdpxmd : 1; // PHY Duplex Mode bit 46 | uint32_t reserved_10_9: 2; // Reserved 47 | uint32_t ppwrsv: 1; // PHY Power-Down bit 48 | uint32_t reserved_13_12: 2; // Reserved 49 | uint32_t ploopbk: 1; // PHY Loopback bit 50 | uint32_t prst: 1; // PHY Software Reset bit 51 | }; 52 | uint32_t val; 53 | } phcon1_reg_t; 54 | #define ETH_PHY_PHCON1_REG_ADDR (0x00) 55 | 56 | /** 57 | * @brief PHCON2(PHY Control Register 2) 58 | * 59 | */ 60 | typedef union { 61 | struct { 62 | uint32_t reserved_7_0 : 8; // Reserved 63 | uint32_t hdldis : 1; // Half-Duplex Loopback Disable 64 | uint32_t reserved_9: 1; // Reserved 65 | uint32_t jabber: 1; // Disable Jabber Correction 66 | uint32_t reserved_12_11: 2; // Reserved 67 | uint32_t txdis: 1; // Disable Twist-Pair Transmitter 68 | uint32_t frclnk: 1; // Force Linkup 69 | uint32_t reserved_15: 1; //Reserved 70 | }; 71 | uint32_t val; 72 | } phcon2_reg_t; 73 | #define ETH_PHY_PHCON2_REG_ADDR (0x10) 74 | 75 | /** 76 | * @brief PHSTAT2(PHY Status Register 2) 77 | * 78 | */ 79 | typedef union { 80 | struct { 81 | uint32_t reserved_4_0 : 5; // Reserved 82 | uint32_t plrity : 1; // Polarity Status 83 | uint32_t reserved_8_6 : 3; // Reserved 84 | uint32_t dpxstat : 1; // PHY Duplex Status 85 | uint32_t lstat : 1; // PHY Link Status (non-latching) 86 | uint32_t colstat : 1; // PHY Collision Status 87 | uint32_t rxstat : 1; // PHY Receive Status 88 | uint32_t txstat : 1; // PHY Transmit Status 89 | uint32_t reserved_15_14 : 2; // Reserved 90 | }; 91 | uint32_t val; 92 | } phstat2_reg_t; 93 | #define ETH_PHY_PHSTAT2_REG_ADDR (0x11) 94 | 95 | typedef struct { 96 | esp_eth_phy_t parent; 97 | esp_eth_mediator_t *eth; 98 | uint32_t addr; 99 | uint32_t reset_timeout_ms; 100 | eth_link_t link_status; 101 | int reset_gpio_num; 102 | } phy_enc28j60_t; 103 | 104 | static esp_err_t enc28j60_update_link_duplex_speed(phy_enc28j60_t *enc28j60) 105 | { 106 | esp_eth_mediator_t *eth = enc28j60->eth; 107 | eth_speed_t speed = ETH_SPEED_10M; // enc28j60 speed is fixed to 10Mbps 108 | eth_duplex_t duplex = ETH_DUPLEX_HALF; 109 | phstat2_reg_t phstat; 110 | PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_PHSTAT2_REG_ADDR, &(phstat.val)) == ESP_OK, 111 | "read PHSTAT2 failed", err); 112 | eth_link_t link = phstat.lstat ? ETH_LINK_UP : ETH_LINK_DOWN; 113 | /* check if link status changed */ 114 | if (enc28j60->link_status != link) { 115 | /* when link up, read result */ 116 | if (link == ETH_LINK_UP) { 117 | if (phstat.dpxstat) { 118 | duplex = ETH_DUPLEX_FULL; 119 | } else { 120 | duplex = ETH_DUPLEX_HALF; 121 | } 122 | PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *)speed) == ESP_OK, 123 | "change speed failed", err); 124 | PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *)duplex) == ESP_OK, 125 | "change duplex failed", err); 126 | } 127 | PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)link) == ESP_OK, 128 | "change link failed", err); 129 | enc28j60->link_status = link; 130 | } 131 | return ESP_OK; 132 | err: 133 | return ESP_FAIL; 134 | } 135 | 136 | static esp_err_t enc28j60_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth) 137 | { 138 | PHY_CHECK(eth, "can't set mediator for enc28j60 to null", err); 139 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 140 | enc28j60->eth = eth; 141 | return ESP_OK; 142 | err: 143 | return ESP_ERR_INVALID_ARG; 144 | } 145 | 146 | static esp_err_t enc28j60_get_link(esp_eth_phy_t *phy) 147 | { 148 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 149 | /* Updata information about link, speed, duplex */ 150 | PHY_CHECK(enc28j60_update_link_duplex_speed(enc28j60) == ESP_OK, "update link duplex speed failed", err); 151 | return ESP_OK; 152 | err: 153 | return ESP_FAIL; 154 | } 155 | 156 | static esp_err_t enc28j60_reset(esp_eth_phy_t *phy) 157 | { 158 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 159 | enc28j60->link_status = ETH_LINK_DOWN; 160 | esp_eth_mediator_t *eth = enc28j60->eth; 161 | bmcr_reg_t bmcr = {.reset = 1}; 162 | PHY_CHECK(eth->phy_reg_write(eth, enc28j60->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, 163 | "write BMCR failed", err); 164 | /* Wait for reset complete */ 165 | uint32_t to = 0; 166 | for (to = 0; to < enc28j60->reset_timeout_ms / 10; to++) { 167 | vTaskDelay(pdMS_TO_TICKS(10)); 168 | PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, 169 | "read BMCR failed", err); 170 | if (!bmcr.reset) { 171 | break; 172 | } 173 | } 174 | PHY_CHECK(to < enc28j60->reset_timeout_ms / 10, "PHY reset timeout", err); 175 | return ESP_OK; 176 | err: 177 | return ESP_FAIL; 178 | } 179 | 180 | static esp_err_t enc28j60_reset_hw(esp_eth_phy_t *phy) 181 | { 182 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 183 | // set reset_gpio_num minus zero can skip hardware reset phy chip 184 | if (enc28j60->reset_gpio_num >= 0) { 185 | gpio_reset_pin(enc28j60->reset_gpio_num); 186 | gpio_set_direction(enc28j60->reset_gpio_num, GPIO_MODE_OUTPUT); 187 | gpio_set_level(enc28j60->reset_gpio_num, 0); 188 | gpio_set_level(enc28j60->reset_gpio_num, 1); 189 | } 190 | return ESP_OK; 191 | } 192 | 193 | static esp_err_t enc28j60_negotiate(esp_eth_phy_t *phy) 194 | { 195 | /** 196 | * ENC28J60 does not support automatic duplex negotiation. 197 | * If it is connected to an automatic duplex negotiation enabled network switch, 198 | * ENC28J60 will be detected as a half-duplex device. 199 | * To communicate in Full-Duplex mode, ENC28J60 and the remote node 200 | * must be manually configured for full-duplex operation. 201 | */ 202 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 203 | /* Updata information about link, speed, duplex */ 204 | PHY_CHECK(enc28j60_update_link_duplex_speed(enc28j60) == ESP_OK, "update link duplex speed failed", err); 205 | return ESP_OK; 206 | err: 207 | return ESP_FAIL; 208 | } 209 | 210 | esp_err_t enc28j60_set_phy_duplex(esp_eth_phy_t *phy, eth_duplex_t duplex) 211 | { 212 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 213 | esp_eth_mediator_t *eth = enc28j60->eth; 214 | phcon1_reg_t phcon1; 215 | 216 | PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, 0, &phcon1.val) == ESP_OK, 217 | "read PHCON1 failed", err); 218 | switch (duplex) { 219 | case ETH_DUPLEX_HALF: 220 | phcon1.pdpxmd = 0; 221 | break; 222 | case ETH_DUPLEX_FULL: 223 | phcon1.pdpxmd = 1; 224 | break; 225 | default: 226 | PHY_CHECK(false, "unknown duplex", err); 227 | break; 228 | } 229 | 230 | PHY_CHECK(eth->phy_reg_write(eth, enc28j60->addr, 0, phcon1.val) == ESP_OK, 231 | "write PHCON1 failed", err); 232 | 233 | PHY_CHECK(enc28j60_update_link_duplex_speed(enc28j60) == ESP_OK, "update link duplex speed failed", err); 234 | return ESP_OK; 235 | err: 236 | return ESP_FAIL; 237 | } 238 | 239 | static esp_err_t enc28j60_pwrctl(esp_eth_phy_t *phy, bool enable) 240 | { 241 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 242 | esp_eth_mediator_t *eth = enc28j60->eth; 243 | bmcr_reg_t bmcr; 244 | PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, 245 | "read BMCR failed", err); 246 | if (!enable) { 247 | /* Enable IEEE Power Down Mode */ 248 | bmcr.power_down = 1; 249 | } else { 250 | /* Disable IEEE Power Down Mode */ 251 | bmcr.power_down = 0; 252 | } 253 | PHY_CHECK(eth->phy_reg_write(eth, enc28j60->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, 254 | "write BMCR failed", err); 255 | PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, 256 | "read BMCR failed", err); 257 | if (!enable) { 258 | PHY_CHECK(bmcr.power_down == 1, "power down failed", err); 259 | } else { 260 | PHY_CHECK(bmcr.power_down == 0, "power up failed", err); 261 | } 262 | return ESP_OK; 263 | err: 264 | return ESP_FAIL; 265 | } 266 | 267 | static esp_err_t enc28j60_set_addr(esp_eth_phy_t *phy, uint32_t addr) 268 | { 269 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 270 | enc28j60->addr = addr; 271 | return ESP_OK; 272 | } 273 | 274 | static esp_err_t enc28j60_get_addr(esp_eth_phy_t *phy, uint32_t *addr) 275 | { 276 | PHY_CHECK(addr, "addr can't be null", err); 277 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 278 | *addr = enc28j60->addr; 279 | return ESP_OK; 280 | err: 281 | return ESP_ERR_INVALID_ARG; 282 | } 283 | 284 | static esp_err_t enc28j60_del(esp_eth_phy_t *phy) 285 | { 286 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 287 | free(enc28j60); 288 | return ESP_OK; 289 | } 290 | 291 | static esp_err_t enc28j60_init(esp_eth_phy_t *phy) 292 | { 293 | phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); 294 | esp_eth_mediator_t *eth = enc28j60->eth; 295 | /* Power on Ethernet PHY */ 296 | PHY_CHECK(enc28j60_pwrctl(phy, true) == ESP_OK, "power control failed", err); 297 | /* Reset Ethernet PHY */ 298 | PHY_CHECK(enc28j60_reset(phy) == ESP_OK, "reset failed", err); 299 | /* Check PHY ID */ 300 | phyidr1_reg_t id1; 301 | phyidr2_reg_t id2; 302 | PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_IDR1_REG_ADDR, &(id1.val)) == ESP_OK, 303 | "read ID1 failed", err); 304 | PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_IDR2_REG_ADDR, &(id2.val)) == ESP_OK, 305 | "read ID2 failed", err); 306 | PHY_CHECK(id1.oui_msb == 0x0083 && id2.oui_lsb == 0x05 && id2.vendor_model == 0x00, 307 | "wrong chip ID", err); 308 | /* Disable half duplex loopback */ 309 | phcon2_reg_t phcon2; 310 | PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_PHCON2_REG_ADDR, &(phcon2.val)) == ESP_OK, 311 | "read PHCON2 failed", err); 312 | phcon2.hdldis = 1; 313 | PHY_CHECK(eth->phy_reg_write(eth, enc28j60->addr, ETH_PHY_PHCON2_REG_ADDR, phcon2.val) == ESP_OK, 314 | "write PHCON2 failed", err); 315 | return ESP_OK; 316 | err: 317 | return ESP_FAIL; 318 | } 319 | 320 | static esp_err_t enc28j60_deinit(esp_eth_phy_t *phy) 321 | { 322 | /* Power off Ethernet PHY */ 323 | PHY_CHECK(enc28j60_pwrctl(phy, false) == ESP_OK, "power off Ethernet PHY failed", err); 324 | return ESP_OK; 325 | err: 326 | return ESP_FAIL; 327 | } 328 | 329 | esp_eth_phy_t *esp_eth_phy_new_enc28j60(const eth_phy_config_t *config) 330 | { 331 | PHY_CHECK(config, "can't set phy config to null", err); 332 | phy_enc28j60_t *enc28j60 = calloc(1, sizeof(phy_enc28j60_t)); 333 | PHY_CHECK(enc28j60, "calloc enc28j60 failed", err); 334 | enc28j60->addr = config->phy_addr; // although PHY addr is meaningless to ENC28J60 335 | enc28j60->reset_timeout_ms = config->reset_timeout_ms; 336 | enc28j60->reset_gpio_num = config->reset_gpio_num; 337 | enc28j60->link_status = ETH_LINK_DOWN; 338 | enc28j60->parent.reset = enc28j60_reset; 339 | enc28j60->parent.reset_hw = enc28j60_reset_hw; 340 | enc28j60->parent.init = enc28j60_init; 341 | enc28j60->parent.deinit = enc28j60_deinit; 342 | enc28j60->parent.set_mediator = enc28j60_set_mediator; 343 | enc28j60->parent.negotiate = enc28j60_negotiate; 344 | enc28j60->parent.get_link = enc28j60_get_link; 345 | enc28j60->parent.pwrctl = enc28j60_pwrctl; 346 | enc28j60->parent.get_addr = enc28j60_get_addr; 347 | enc28j60->parent.set_addr = enc28j60_set_addr; 348 | enc28j60->parent.del = enc28j60_del; 349 | return &(enc28j60->parent); 350 | err: 351 | return NULL; 352 | } 353 | -------------------------------------------------------------------------------- /src/extmod/esp_eth_mac_enc28j60.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | #include 17 | #include "driver/gpio.h" 18 | #include "esp_attr.h" 19 | #include "esp_log.h" 20 | #include "esp_eth.h" 21 | #include "esp_system.h" 22 | #include "esp_intr_alloc.h" 23 | #include "esp_heap_caps.h" 24 | #include "esp_rom_sys.h" 25 | #include "freertos/FreeRTOS.h" 26 | #include "freertos/task.h" 27 | #include "freertos/semphr.h" 28 | #include "hal/cpu_hal.h" 29 | #include "esp_eth_enc28j60.h" 30 | #include "enc28j60.h" 31 | #include "sdkconfig.h" 32 | 33 | static const char *TAG = "enc28j60"; 34 | #define MAC_CHECK(a, str, goto_tag, ret_value, ...) \ 35 | do \ 36 | { \ 37 | if (!(a)) \ 38 | { \ 39 | ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ 40 | ret = ret_value; \ 41 | goto goto_tag; \ 42 | } \ 43 | } while (0) 44 | 45 | #define MAC_CHECK_NO_RET(a, str, goto_tag, ...) \ 46 | do \ 47 | { \ 48 | if (!(a)) \ 49 | { \ 50 | ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ 51 | goto goto_tag; \ 52 | } \ 53 | } while (0) 54 | 55 | #define ENC28J60_SPI_LOCK_TIMEOUT_MS (50) 56 | #define ENC28J60_REG_TRANS_LOCK_TIMEOUT_MS (150) 57 | #define ENC28J60_PHY_OPERATION_TIMEOUT_US (150) 58 | #define ENC28J60_SYSTEM_RESET_ADDITION_TIME_US (1000) 59 | #define ENC28J60_TX_READY_TIMEOUT_MS (2000) 60 | 61 | #define ENC28J60_BUFFER_SIZE (0x2000) // 8KB built-in buffer 62 | /** 63 | * ______ 64 | * |__TX__| TX: 2 KB : [0x1800, 0x2000) 65 | * | | 66 | * | RX | RX: 6 KB : [0x0000, 0x1800) 67 | * |______| 68 | * 69 | */ 70 | #define ENC28J60_BUF_RX_START (0) 71 | #define ENC28J60_BUF_RX_END (ENC28J60_BUF_TX_START - 1) 72 | #define ENC28J60_BUF_TX_START ((ENC28J60_BUFFER_SIZE / 4) * 3) 73 | #define ENC28J60_BUF_TX_END (ENC28J60_BUFFER_SIZE - 1) 74 | 75 | #define ENC28J60_RSV_SIZE (6) // Receive Status Vector Size 76 | #define ENC28J60_TSV_SIZE (6) // Transmit Status Vector Size 77 | 78 | typedef struct { 79 | uint8_t next_packet_low; 80 | uint8_t next_packet_high; 81 | uint8_t length_low; 82 | uint8_t length_high; 83 | uint8_t status_low; 84 | uint8_t status_high; 85 | } enc28j60_rx_header_t; 86 | 87 | typedef struct { 88 | uint16_t byte_cnt; 89 | 90 | uint8_t collision_cnt:4; 91 | uint8_t crc_err:1; 92 | uint8_t len_check_err:1; 93 | uint8_t len_out_range:1; 94 | uint8_t tx_done:1; 95 | 96 | uint8_t multicast:1; 97 | uint8_t broadcast:1; 98 | uint8_t pkt_defer:1; 99 | uint8_t excessive_defer:1; 100 | uint8_t excessive_collision:1; 101 | uint8_t late_collision:1; 102 | uint8_t giant:1; 103 | uint8_t underrun:1; 104 | 105 | uint16_t bytes_on_wire; 106 | 107 | uint8_t ctrl_frame:1; 108 | uint8_t pause_ctrl_frame:1; 109 | uint8_t backpressure_app:1; 110 | uint8_t vlan_frame:1; 111 | } enc28j60_tsv_t; 112 | 113 | typedef struct { 114 | esp_eth_mac_t parent; 115 | esp_eth_mediator_t *eth; 116 | spi_device_handle_t spi_hdl; 117 | SemaphoreHandle_t spi_lock; 118 | SemaphoreHandle_t reg_trans_lock; 119 | SemaphoreHandle_t tx_ready_sem; 120 | TaskHandle_t rx_task_hdl; 121 | uint32_t sw_reset_timeout_ms; 122 | uint32_t next_packet_ptr; 123 | uint32_t last_tsv_addr; 124 | int int_gpio_num; 125 | uint8_t addr[6]; 126 | uint8_t last_bank; 127 | bool packets_remain; 128 | eth_enc28j60_rev_t revision; 129 | } emac_enc28j60_t; 130 | 131 | static inline bool enc28j60_spi_lock(emac_enc28j60_t *emac) 132 | { 133 | return xSemaphoreTake(emac->spi_lock, pdMS_TO_TICKS(ENC28J60_SPI_LOCK_TIMEOUT_MS)) == pdTRUE; 134 | } 135 | 136 | static inline bool enc28j60_spi_unlock(emac_enc28j60_t *emac) 137 | { 138 | return xSemaphoreGive(emac->spi_lock) == pdTRUE; 139 | } 140 | 141 | static inline bool enc28j60_reg_trans_lock(emac_enc28j60_t *emac) 142 | { 143 | return xSemaphoreTake(emac->reg_trans_lock, pdMS_TO_TICKS(ENC28J60_REG_TRANS_LOCK_TIMEOUT_MS)) == pdTRUE; 144 | } 145 | 146 | static inline bool enc28j60_reg_trans_unlock(emac_enc28j60_t *emac) 147 | { 148 | return xSemaphoreGive(emac->reg_trans_lock) == pdTRUE; 149 | } 150 | 151 | /** 152 | * @brief ERXRDPT need to be set always at odd addresses 153 | */ 154 | static inline uint32_t enc28j60_next_ptr_align_odd(uint32_t next_packet_ptr, uint32_t start, uint32_t end) 155 | { 156 | uint32_t erxrdpt; 157 | 158 | if ((next_packet_ptr - 1 < start) || (next_packet_ptr - 1 > end)) { 159 | erxrdpt = end; 160 | } else { 161 | erxrdpt = next_packet_ptr - 1; 162 | } 163 | 164 | return erxrdpt; 165 | } 166 | 167 | /** 168 | * @brief Calculate wrap around when reading beyond the end of the RX buffer 169 | */ 170 | static inline uint32_t enc28j60_rx_packet_start(uint32_t start_addr, uint32_t off) 171 | { 172 | if (start_addr + off > ENC28J60_BUF_RX_END) { 173 | return (start_addr + off) - (ENC28J60_BUF_RX_END - ENC28J60_BUF_RX_START + 1); 174 | } else { 175 | return start_addr + off; 176 | } 177 | } 178 | 179 | /** 180 | * @brief SPI operation wrapper for writing ENC28J60 internal register 181 | */ 182 | static esp_err_t enc28j60_do_register_write(emac_enc28j60_t *emac, uint8_t reg_addr, uint8_t value) 183 | { 184 | esp_err_t ret = ESP_OK; 185 | spi_transaction_t trans = { 186 | .cmd = ENC28J60_SPI_CMD_WCR, // Write control register 187 | .addr = reg_addr, 188 | .length = 8, 189 | .flags = SPI_TRANS_USE_TXDATA, 190 | .tx_data = { 191 | [0] = value 192 | } 193 | }; 194 | if (enc28j60_spi_lock(emac)) { 195 | if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) { 196 | ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); 197 | ret = ESP_FAIL; 198 | } 199 | enc28j60_spi_unlock(emac); 200 | } else { 201 | ret = ESP_ERR_TIMEOUT; 202 | } 203 | return ret; 204 | } 205 | 206 | /** 207 | * @brief SPI operation wrapper for reading ENC28J60 internal register 208 | */ 209 | static esp_err_t enc28j60_do_register_read(emac_enc28j60_t *emac, bool is_eth_reg, uint8_t reg_addr, uint8_t *value) 210 | { 211 | esp_err_t ret = ESP_OK; 212 | spi_transaction_t trans = { 213 | .cmd = ENC28J60_SPI_CMD_RCR, // Read control register 214 | .addr = reg_addr, 215 | .length = is_eth_reg ? 8 : 16, // read operation is different for ETH register and non-ETH register 216 | .flags = SPI_TRANS_USE_RXDATA 217 | }; 218 | if (enc28j60_spi_lock(emac)) { 219 | if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) { 220 | ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); 221 | ret = ESP_FAIL; 222 | } else { 223 | *value = is_eth_reg ? trans.rx_data[0] : trans.rx_data[1]; 224 | } 225 | enc28j60_spi_unlock(emac); 226 | } else { 227 | ret = ESP_ERR_TIMEOUT; 228 | } 229 | return ret; 230 | } 231 | 232 | /** 233 | * @brief SPI operation wrapper for bitwise setting ENC28J60 internal register 234 | * @note can only be used for ETH registers 235 | */ 236 | static esp_err_t enc28j60_do_bitwise_set(emac_enc28j60_t *emac, uint8_t reg_addr, uint8_t mask) 237 | { 238 | esp_err_t ret = ESP_OK; 239 | spi_transaction_t trans = { 240 | .cmd = ENC28J60_SPI_CMD_BFS, // Bit field set 241 | .addr = reg_addr, 242 | .length = 8, 243 | .flags = SPI_TRANS_USE_TXDATA, 244 | .tx_data = { 245 | [0] = mask 246 | } 247 | }; 248 | if (enc28j60_spi_lock(emac)) { 249 | if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) { 250 | ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); 251 | ret = ESP_FAIL; 252 | } 253 | enc28j60_spi_unlock(emac); 254 | } else { 255 | ret = ESP_ERR_TIMEOUT; 256 | } 257 | return ret; 258 | } 259 | 260 | /** 261 | * @brief SPI operation wrapper for bitwise clearing ENC28J60 internal register 262 | * @note can only be used for ETH registers 263 | */ 264 | static esp_err_t enc28j60_do_bitwise_clr(emac_enc28j60_t *emac, uint8_t reg_addr, uint8_t mask) 265 | { 266 | esp_err_t ret = ESP_OK; 267 | spi_transaction_t trans = { 268 | .cmd = ENC28J60_SPI_CMD_BFC, // Bit field clear 269 | .addr = reg_addr, 270 | .length = 8, 271 | .flags = SPI_TRANS_USE_TXDATA, 272 | .tx_data = { 273 | [0] = mask 274 | } 275 | }; 276 | if (enc28j60_spi_lock(emac)) { 277 | if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) { 278 | ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); 279 | ret = ESP_FAIL; 280 | } 281 | enc28j60_spi_unlock(emac); 282 | } else { 283 | ret = ESP_ERR_TIMEOUT; 284 | } 285 | return ret; 286 | } 287 | 288 | /** 289 | * @brief SPI operation wrapper for writing ENC28J60 internal memory 290 | */ 291 | static esp_err_t enc28j60_do_memory_write(emac_enc28j60_t *emac, uint8_t *buffer, uint32_t len) 292 | { 293 | esp_err_t ret = ESP_OK; 294 | spi_transaction_t trans = { 295 | .cmd = ENC28J60_SPI_CMD_WBM, // Write buffer memory 296 | .addr = 0x1A, 297 | .length = len * 8, 298 | .tx_buffer = buffer 299 | }; 300 | if (enc28j60_spi_lock(emac)) { 301 | if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) { 302 | ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); 303 | ret = ESP_FAIL; 304 | } 305 | enc28j60_spi_unlock(emac); 306 | } else { 307 | ret = ESP_ERR_TIMEOUT; 308 | } 309 | return ret; 310 | } 311 | 312 | /** 313 | * @brief SPI operation wrapper for reading ENC28J60 internal memory 314 | */ 315 | static esp_err_t enc28j60_do_memory_read(emac_enc28j60_t *emac, uint8_t *buffer, uint32_t len) 316 | { 317 | esp_err_t ret = ESP_OK; 318 | spi_transaction_t trans = { 319 | .cmd = ENC28J60_SPI_CMD_RBM, // Read buffer memory 320 | .addr = 0x1A, 321 | .length = len * 8, 322 | .rx_buffer = buffer 323 | }; 324 | 325 | if (enc28j60_spi_lock(emac)) { 326 | if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) { 327 | ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); 328 | ret = ESP_FAIL; 329 | } 330 | enc28j60_spi_unlock(emac); 331 | } else { 332 | ret = ESP_ERR_TIMEOUT; 333 | } 334 | return ret; 335 | } 336 | 337 | /** 338 | * @brief SPI operation wrapper for resetting ENC28J60 339 | */ 340 | static esp_err_t enc28j60_do_reset(emac_enc28j60_t *emac) 341 | { 342 | esp_err_t ret = ESP_OK; 343 | spi_transaction_t trans = { 344 | .cmd = ENC28J60_SPI_CMD_SRC, // Soft reset 345 | .addr = 0x1F, 346 | }; 347 | if (enc28j60_spi_lock(emac)) { 348 | if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) { 349 | ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); 350 | ret = ESP_FAIL; 351 | } 352 | enc28j60_spi_unlock(emac); 353 | } else { 354 | ret = ESP_ERR_TIMEOUT; 355 | } 356 | 357 | // After reset, wait at least 1ms for the device to be ready 358 | esp_rom_delay_us(ENC28J60_SYSTEM_RESET_ADDITION_TIME_US); 359 | 360 | return ret; 361 | } 362 | 363 | /** 364 | * @brief Switch ENC28J60 register bank 365 | */ 366 | static esp_err_t enc28j60_switch_register_bank(emac_enc28j60_t *emac, uint8_t bank) 367 | { 368 | esp_err_t ret = ESP_OK; 369 | if (bank != emac->last_bank) { 370 | MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_ECON1, 0x03) == ESP_OK, 371 | "clear ECON1[1:0] failed", out, ESP_FAIL); 372 | MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, bank & 0x03) == ESP_OK, 373 | "set ECON1[1:0] failed", out, ESP_FAIL); 374 | emac->last_bank = bank; 375 | } 376 | out: 377 | return ret; 378 | } 379 | 380 | /** 381 | * @brief Write ENC28J60 register 382 | */ 383 | static esp_err_t enc28j60_register_write(emac_enc28j60_t *emac, uint16_t reg_addr, uint8_t value) 384 | { 385 | esp_err_t ret = ESP_OK; 386 | if (enc28j60_reg_trans_lock(emac)) { 387 | MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK, 388 | "switch bank failed", out, ESP_FAIL); 389 | MAC_CHECK(enc28j60_do_register_write(emac, reg_addr & 0xFF, value) == ESP_OK, 390 | "write register failed", out, ESP_FAIL); 391 | enc28j60_reg_trans_unlock(emac); 392 | } else { 393 | ret = ESP_ERR_TIMEOUT; 394 | } 395 | return ret; 396 | out: 397 | enc28j60_reg_trans_unlock(emac); 398 | return ret; 399 | } 400 | 401 | /** 402 | * @brief Read ENC28J60 register 403 | */ 404 | static esp_err_t enc28j60_register_read(emac_enc28j60_t *emac, uint16_t reg_addr, uint8_t *value) 405 | { 406 | esp_err_t ret = ESP_OK; 407 | if (enc28j60_reg_trans_lock(emac)) { 408 | MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK, 409 | "switch bank failed", out, ESP_FAIL); 410 | MAC_CHECK(enc28j60_do_register_read(emac, !(reg_addr & 0xF000), reg_addr & 0xFF, value) == ESP_OK, 411 | "read register failed", out, ESP_FAIL); 412 | enc28j60_reg_trans_unlock(emac); 413 | } else { 414 | ret = ESP_ERR_TIMEOUT; 415 | } 416 | return ret; 417 | out: 418 | enc28j60_reg_trans_unlock(emac); 419 | return ret; 420 | } 421 | 422 | /** 423 | * @brief Read ENC28J60 internal memroy 424 | */ 425 | static esp_err_t enc28j60_read_packet(emac_enc28j60_t *emac, uint32_t addr, uint8_t *packet, uint32_t len) 426 | { 427 | esp_err_t ret = ESP_OK; 428 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERDPTL, addr & 0xFF) == ESP_OK, 429 | "write ERDPTL failed", out, ESP_FAIL); 430 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERDPTH, (addr & 0xFF00) >> 8) == ESP_OK, 431 | "write ERDPTH failed", out, ESP_FAIL); 432 | MAC_CHECK(enc28j60_do_memory_read(emac, packet, len) == ESP_OK, 433 | "read memory failed", out, ESP_FAIL); 434 | out: 435 | return ret; 436 | } 437 | 438 | /** 439 | * @brief Write ENC28J60 internal PHY register 440 | */ 441 | static esp_err_t emac_enc28j60_write_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, 442 | uint32_t phy_reg, uint32_t reg_value) 443 | { 444 | esp_err_t ret = ESP_OK; 445 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 446 | uint8_t mii_status; 447 | 448 | /* check if phy access is in progress */ 449 | MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK, 450 | "read MISTAT failed", out, ESP_FAIL); 451 | MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_INVALID_STATE); 452 | 453 | /* tell the PHY address to write */ 454 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIREGADR, phy_reg & 0xFF) == ESP_OK, 455 | "write MIREGADR failed", out, ESP_FAIL); 456 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIWRL, reg_value & 0xFF) == ESP_OK, 457 | "write MIWRL failed", out, ESP_FAIL); 458 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIWRH, (reg_value & 0xFF00) >> 8) == ESP_OK, 459 | "write MIWRH failed", out, ESP_FAIL); 460 | 461 | /* polling the busy flag */ 462 | uint32_t to = 0; 463 | do { 464 | esp_rom_delay_us(15); 465 | MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK, 466 | "read MISTAT failed", out, ESP_FAIL); 467 | to += 15; 468 | } while ((mii_status & MISTAT_BUSY) && to < ENC28J60_PHY_OPERATION_TIMEOUT_US); 469 | MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_TIMEOUT); 470 | out: 471 | return ret; 472 | } 473 | 474 | /** 475 | * @brief Read ENC28J60 internal PHY register 476 | */ 477 | static esp_err_t emac_enc28j60_read_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, 478 | uint32_t phy_reg, uint32_t *reg_value) 479 | { 480 | esp_err_t ret = ESP_OK; 481 | MAC_CHECK(reg_value, "can't set reg_value to null", out, ESP_ERR_INVALID_ARG); 482 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 483 | uint8_t mii_status; 484 | uint8_t mii_cmd; 485 | 486 | /* check if phy access is in progress */ 487 | MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK, 488 | "read MISTAT failed", out, ESP_FAIL); 489 | MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_INVALID_STATE); 490 | 491 | /* tell the PHY address to read */ 492 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIREGADR, phy_reg & 0xFF) == ESP_OK, 493 | "write MIREGADR failed", out, ESP_FAIL); 494 | mii_cmd = MICMD_MIIRD; 495 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MICMD, mii_cmd) == ESP_OK, 496 | "write MICMD failed", out, ESP_FAIL); 497 | 498 | /* polling the busy flag */ 499 | uint32_t to = 0; 500 | do { 501 | esp_rom_delay_us(15); 502 | MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK, 503 | "read MISTAT failed", out, ESP_FAIL); 504 | to += 15; 505 | } while ((mii_status & MISTAT_BUSY) && to < ENC28J60_PHY_OPERATION_TIMEOUT_US); 506 | MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_TIMEOUT); 507 | 508 | mii_cmd &= (~MICMD_MIIRD); 509 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MICMD, mii_cmd) == ESP_OK, 510 | "write MICMD failed", out, ESP_FAIL); 511 | 512 | uint8_t value_l = 0; 513 | uint8_t value_h = 0; 514 | MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MIRDL, &value_l) == ESP_OK, 515 | "read MIRDL failed", out, ESP_FAIL); 516 | MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MIRDH, &value_h) == ESP_OK, 517 | "read MIRDH failed", out, ESP_FAIL); 518 | *reg_value = (value_h << 8) | value_l; 519 | out: 520 | return ret; 521 | } 522 | 523 | /** 524 | * @brief Set mediator for Ethernet MAC 525 | */ 526 | static esp_err_t emac_enc28j60_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth) 527 | { 528 | esp_err_t ret = ESP_OK; 529 | MAC_CHECK(eth, "can't set mac's mediator to null", out, ESP_ERR_INVALID_ARG); 530 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 531 | emac->eth = eth; 532 | out: 533 | return ret; 534 | } 535 | 536 | /** 537 | * @brief Verify chip revision ID 538 | */ 539 | static esp_err_t enc28j60_verify_id(emac_enc28j60_t *emac) 540 | { 541 | esp_err_t ret = ESP_OK; 542 | MAC_CHECK(enc28j60_register_read(emac, ENC28J60_EREVID, (uint8_t *)&emac->revision) == ESP_OK, 543 | "read EREVID failed", out, ESP_FAIL); 544 | ESP_LOGI(TAG, "revision: %d", emac->revision); 545 | MAC_CHECK(emac->revision >= ENC28J60_REV_B1 && emac->revision <= ENC28J60_REV_B7, "wrong chip ID", out, ESP_ERR_INVALID_VERSION); 546 | out: 547 | return ret; 548 | } 549 | 550 | /** 551 | * @brief Write mac address to internal registers 552 | */ 553 | static esp_err_t enc28j60_set_mac_addr(emac_enc28j60_t *emac) 554 | { 555 | esp_err_t ret = ESP_OK; 556 | 557 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR6, emac->addr[5]) == ESP_OK, 558 | "write MAADR6 failed", out, ESP_FAIL); 559 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR5, emac->addr[4]) == ESP_OK, 560 | "write MAADR5 failed", out, ESP_FAIL); 561 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR4, emac->addr[3]) == ESP_OK, 562 | "write MAADR4 failed", out, ESP_FAIL); 563 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR3, emac->addr[2]) == ESP_OK, 564 | "write MAADR3 failed", out, ESP_FAIL); 565 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR2, emac->addr[1]) == ESP_OK, 566 | "write MAADR2 failed", out, ESP_FAIL); 567 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR1, emac->addr[0]) == ESP_OK, 568 | "write MAADR1 failed", out, ESP_FAIL); 569 | out: 570 | return ret; 571 | } 572 | 573 | /** 574 | * @brief Clear multicast hash table 575 | */ 576 | static esp_err_t enc28j60_clear_multicast_table(emac_enc28j60_t *emac) 577 | { 578 | esp_err_t ret = ESP_OK; 579 | 580 | for (int i = 0; i < 7; i++) { 581 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_EHT0 + i, 0x00) == ESP_OK, 582 | "write ENC28J60_EHT%d failed", out, ESP_FAIL, i); 583 | } 584 | out: 585 | return ret; 586 | } 587 | 588 | /** 589 | * @brief Default setup for ENC28J60 internal registers 590 | */ 591 | static esp_err_t enc28j60_setup_default(emac_enc28j60_t *emac) 592 | { 593 | esp_err_t ret = ESP_OK; 594 | 595 | // set up receive buffer start + end 596 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXSTL, ENC28J60_BUF_RX_START & 0xFF) == ESP_OK, 597 | "write ERXSTL failed", out, ESP_FAIL); 598 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXSTH, (ENC28J60_BUF_RX_START & 0xFF00) >> 8) == ESP_OK, 599 | "write ERXSTH failed", out, ESP_FAIL); 600 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXNDL, ENC28J60_BUF_RX_END & 0xFF) == ESP_OK, 601 | "write ERXNDL failed", out, ESP_FAIL); 602 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXNDH, (ENC28J60_BUF_RX_END & 0xFF00) >> 8) == ESP_OK, 603 | "write ERXNDH failed", out, ESP_FAIL); 604 | uint32_t erxrdpt = enc28j60_next_ptr_align_odd(ENC28J60_BUF_RX_START, ENC28J60_BUF_RX_START, ENC28J60_BUF_RX_END); 605 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXRDPTL, erxrdpt & 0xFF) == ESP_OK, 606 | "write ERXRDPTL failed", out, ESP_FAIL); 607 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXRDPTH, (erxrdpt & 0xFF00) >> 8) == ESP_OK, 608 | "write ERXRDPTH failed", out, ESP_FAIL); 609 | 610 | // set up transmit buffer start + end 611 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ETXSTL, ENC28J60_BUF_TX_START & 0xFF) == ESP_OK, 612 | "write ETXSTL failed", out, ESP_FAIL); 613 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ETXSTH, (ENC28J60_BUF_TX_START & 0xFF00) >> 8) == ESP_OK, 614 | "write ETXSTH failed", out, ESP_FAIL); 615 | 616 | // set up default filter mode: (unicast OR broadcast) AND crc valid 617 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_BCEN) == ESP_OK, 618 | "write ERXFCON failed", out, ESP_FAIL); 619 | 620 | // enable MAC receive, enable pause control frame on Tx and Rx path 621 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MACON1, MACON1_MARXEN | MACON1_RXPAUS | MACON1_TXPAUS) == ESP_OK, 622 | "write MACON1 failed", out, ESP_FAIL); 623 | // enable automatic padding, append CRC, check frame length, half duplex by default (can update at runtime) 624 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MACON3, MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN) == ESP_OK, "write MACON3 failed", out, ESP_FAIL); 625 | // enable defer transmission (effective only in half duplex) 626 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MACON4, MACON4_DEFER) == ESP_OK, 627 | "write MACON4 failed", out, ESP_FAIL); 628 | // set inter-frame gap (back-to-back) 629 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MABBIPG, 0x12) == ESP_OK, 630 | "write MABBIPG failed", out, ESP_FAIL); 631 | // set inter-frame gap (non-back-to-back) 632 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAIPGL, 0x12) == ESP_OK, 633 | "write MAIPGL failed", out, ESP_FAIL); 634 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAIPGH, 0x0C) == ESP_OK, 635 | "write MAIPGH failed", out, ESP_FAIL); 636 | 637 | out: 638 | return ret; 639 | } 640 | 641 | /** 642 | * @brief Start enc28j60: enable interrupt and start receive 643 | */ 644 | static esp_err_t emac_enc28j60_start(esp_eth_mac_t *mac) 645 | { 646 | esp_err_t ret = ESP_OK; 647 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 648 | /* enable interrupt */ 649 | MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, 0xFF) == ESP_OK, 650 | "clear EIR failed", out, ESP_FAIL); 651 | MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_PKTIE | EIE_INTIE | EIE_TXERIE) == ESP_OK, 652 | "set EIE.[PKTIE|INTIE] failed", out, ESP_FAIL); 653 | /* enable rx logic */ 654 | MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_RXEN) == ESP_OK, 655 | "set ECON1.RXEN failed", out, ESP_FAIL); 656 | 657 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERDPTL, 0x00) == ESP_OK, 658 | "write ERDPTL failed", out, ESP_FAIL); 659 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERDPTH, 0x00) == ESP_OK, 660 | "write ERDPTH failed", out, ESP_FAIL); 661 | out: 662 | return ret; 663 | } 664 | 665 | /** 666 | * @brief Stop enc28j60: disable interrupt and stop receiving packets 667 | */ 668 | static esp_err_t emac_enc28j60_stop(esp_eth_mac_t *mac) 669 | { 670 | esp_err_t ret = ESP_OK; 671 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 672 | /* disable interrupt */ 673 | MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_EIE, 0xFF) == ESP_OK, 674 | "clear EIE failed", out, ESP_FAIL); 675 | /* disable rx */ 676 | MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_ECON1, ECON1_RXEN) == ESP_OK, 677 | "clear ECON1.RXEN failed", out, ESP_FAIL); 678 | out: 679 | return ret; 680 | } 681 | 682 | static esp_err_t emac_enc28j60_set_addr(esp_eth_mac_t *mac, uint8_t *addr) 683 | { 684 | esp_err_t ret = ESP_OK; 685 | MAC_CHECK(addr, "can't set mac addr to null", out, ESP_ERR_INVALID_ARG); 686 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 687 | memcpy(emac->addr, addr, 6); 688 | MAC_CHECK(enc28j60_set_mac_addr(emac) == ESP_OK, "set mac address failed", out, ESP_FAIL); 689 | out: 690 | return ret; 691 | } 692 | 693 | static esp_err_t emac_enc28j60_get_addr(esp_eth_mac_t *mac, uint8_t *addr) 694 | { 695 | esp_err_t ret = ESP_OK; 696 | MAC_CHECK(addr, "can't set mac addr to null", out, ESP_ERR_INVALID_ARG); 697 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 698 | memcpy(addr, emac->addr, 6); 699 | out: 700 | return ret; 701 | } 702 | 703 | static inline esp_err_t emac_enc28j60_get_tsv(emac_enc28j60_t *emac, enc28j60_tsv_t *tsv) 704 | { 705 | return enc28j60_read_packet(emac, emac->last_tsv_addr, (uint8_t *)tsv, ENC28J60_TSV_SIZE); 706 | } 707 | 708 | static void enc28j60_isr_handler(void *arg) 709 | { 710 | emac_enc28j60_t *emac = (emac_enc28j60_t *)arg; 711 | BaseType_t high_task_wakeup = pdFALSE; 712 | /* notify enc28j60 task */ 713 | vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup); 714 | if (high_task_wakeup != pdFALSE) { 715 | portYIELD_FROM_ISR(); 716 | } 717 | } 718 | 719 | /** 720 | * @brief Main ENC28J60 Task. Mainly used for Rx processing. However, it also handles other interrupts. 721 | * 722 | */ 723 | static void emac_enc28j60_task(void *arg) 724 | { 725 | emac_enc28j60_t *emac = (emac_enc28j60_t *)arg; 726 | uint8_t status = 0; 727 | uint8_t mask = 0; 728 | uint8_t *buffer = NULL; 729 | uint32_t length = 0; 730 | 731 | while (1) { 732 | loop_start: 733 | // block until some task notifies me or check the gpio by myself 734 | if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 && // if no notification ... 735 | gpio_get_level(emac->int_gpio_num) != 0) { // ...and no interrupt asserted 736 | continue; // -> just continue to check again 737 | } 738 | // the host controller should clear the global enable bit for the interrupt pin before servicing the interrupt 739 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIE_INTIE) == ESP_OK, 740 | "clear EIE_INTIE failed", loop_start); 741 | // read interrupt status 742 | MAC_CHECK_NO_RET(enc28j60_do_register_read(emac, true, ENC28J60_EIR, &status) == ESP_OK, 743 | "read EIR failed", loop_end); 744 | MAC_CHECK_NO_RET(enc28j60_do_register_read(emac, true, ENC28J60_EIE, &mask) == ESP_OK, 745 | "read EIE failed", loop_end); 746 | status &= mask; 747 | 748 | // When source of interrupt is unknown, try to check if there is packet waiting (Errata #6 workaround) 749 | if (status == 0) { 750 | uint8_t pk_counter; 751 | MAC_CHECK_NO_RET(enc28j60_register_read(emac, ENC28J60_EPKTCNT, &pk_counter) == ESP_OK, 752 | "read EPKTCNT failed", loop_end); 753 | if (pk_counter > 0) { 754 | status = EIR_PKTIF; 755 | } else { 756 | goto loop_end; 757 | } 758 | } 759 | 760 | // packet received 761 | if (status & EIR_PKTIF) { 762 | do { 763 | length = ETH_MAX_PACKET_SIZE; 764 | buffer = heap_caps_malloc(length, MALLOC_CAP_DMA); 765 | if (!buffer) { 766 | ESP_LOGE(TAG, "no mem for receive buffer"); 767 | } else if (emac->parent.receive(&emac->parent, buffer, &length) == ESP_OK) { 768 | /* pass the buffer to stack (e.g. TCP/IP layer) */ 769 | if (length) { 770 | emac->eth->stack_input(emac->eth, buffer, length); 771 | } else { 772 | free(buffer); 773 | } 774 | } else { 775 | free(buffer); 776 | } 777 | } while (emac->packets_remain); 778 | } 779 | 780 | // transmit error 781 | if (status & EIR_TXERIF) { 782 | // Errata #12/#13 workaround - reset Tx state machine 783 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRST) == ESP_OK, 784 | "set TXRST failed", loop_end); 785 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_ECON1, ECON1_TXRST) == ESP_OK, 786 | "clear TXRST failed", loop_end); 787 | 788 | // Clear Tx Error Interrupt Flag 789 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXERIF) == ESP_OK, 790 | "clear TXERIF failed", loop_end); 791 | 792 | // Errata #13 workaround (applicable only to B5 and B7 revisions) 793 | if (emac->revision == ENC28J60_REV_B5 || emac->revision == ENC28J60_REV_B7) { 794 | __attribute__((aligned(4))) enc28j60_tsv_t tx_status; // SPI driver needs the rx buffer 4 byte align 795 | MAC_CHECK_NO_RET(emac_enc28j60_get_tsv(emac, &tx_status) == ESP_OK, 796 | "get Tx Status Vector failed", loop_end); 797 | // Try to retransmit when late collision is indicated 798 | if (tx_status.late_collision) { 799 | // Clear Tx Interrupt status Flag (it was set along with the error) 800 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXIF) == ESP_OK, 801 | "clear TXIF failed", loop_end); 802 | // Enable global interrupt flag and try to retransmit 803 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_INTIE) == ESP_OK, 804 | "set INTIE failed", loop_end); 805 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRTS) == ESP_OK, 806 | "set TXRTS failed", loop_end); 807 | continue; // no need to handle Tx ready interrupt nor to enable global interrupt at this point 808 | } 809 | } 810 | } 811 | 812 | // transmit ready 813 | if (status & EIR_TXIF) { 814 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXIF) == ESP_OK, 815 | "clear TXIF failed", loop_end); 816 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIE, EIE_TXIE) == ESP_OK, 817 | "clear TXIE failed", loop_end); 818 | 819 | xSemaphoreGive(emac->tx_ready_sem); 820 | } 821 | loop_end: 822 | // restore global enable interrupt bit 823 | MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_INTIE) == ESP_OK, 824 | "clear INTIE failed", loop_start); 825 | // Note: Interrupt flag PKTIF is cleared when PKTDEC is set (in receive function) 826 | } 827 | vTaskDelete(NULL); 828 | } 829 | 830 | static esp_err_t emac_enc28j60_set_link(esp_eth_mac_t *mac, eth_link_t link) 831 | { 832 | esp_err_t ret = ESP_OK; 833 | switch (link) { 834 | case ETH_LINK_UP: 835 | MAC_CHECK(mac->start(mac) == ESP_OK, "enc28j60 start failed", out, ESP_FAIL); 836 | break; 837 | case ETH_LINK_DOWN: 838 | MAC_CHECK(mac->stop(mac) == ESP_OK, "enc28j60 stop failed", out, ESP_FAIL); 839 | break; 840 | default: 841 | MAC_CHECK(false, "unknown link status", out, ESP_ERR_INVALID_ARG); 842 | break; 843 | } 844 | out: 845 | return ret; 846 | } 847 | 848 | static esp_err_t emac_enc28j60_set_speed(esp_eth_mac_t *mac, eth_speed_t speed) 849 | { 850 | esp_err_t ret = ESP_OK; 851 | switch (speed) { 852 | case ETH_SPEED_10M: 853 | ESP_LOGI(TAG, "working in 10Mbps"); 854 | break; 855 | default: 856 | MAC_CHECK(false, "100Mbps unsupported", out, ESP_ERR_NOT_SUPPORTED); 857 | break; 858 | } 859 | out: 860 | return ret; 861 | } 862 | 863 | static esp_err_t emac_enc28j60_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex) 864 | { 865 | esp_err_t ret = ESP_OK; 866 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 867 | uint8_t mac3 = 0; 868 | MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MACON3, &mac3) == ESP_OK, 869 | "read MACON3 failed", out, ESP_FAIL); 870 | switch (duplex) { 871 | case ETH_DUPLEX_HALF: 872 | mac3 &= ~MACON3_FULDPX; 873 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MABBIPG, 0x12) == ESP_OK, 874 | "write MABBIPG failed", out, ESP_FAIL); 875 | ESP_LOGI(TAG, "working in half duplex"); 876 | break; 877 | case ETH_DUPLEX_FULL: 878 | mac3 |= MACON3_FULDPX; 879 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MABBIPG, 0x15) == ESP_OK, 880 | "write MABBIPG failed", out, ESP_FAIL); 881 | ESP_LOGI(TAG, "working in full duplex"); 882 | break; 883 | default: 884 | MAC_CHECK(false, "unknown duplex", out, ESP_ERR_INVALID_ARG); 885 | break; 886 | } 887 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MACON3, mac3) == ESP_OK, 888 | "write MACON3 failed", out, ESP_FAIL); 889 | out: 890 | return ret; 891 | } 892 | 893 | static esp_err_t emac_enc28j60_set_promiscuous(esp_eth_mac_t *mac, bool enable) 894 | { 895 | esp_err_t ret = ESP_OK; 896 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 897 | if (enable) { 898 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXFCON, 0x00) == ESP_OK, 899 | "write ERXFCON failed", out, ESP_FAIL); 900 | } 901 | out: 902 | return ret; 903 | } 904 | 905 | static esp_err_t emac_enc28j60_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length) 906 | { 907 | esp_err_t ret = ESP_OK; 908 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 909 | uint8_t econ1 = 0; 910 | 911 | /* ENC28J60 may be a bottle neck in Eth communication. Hence we need to check if it is ready. */ 912 | if (xSemaphoreTake(emac->tx_ready_sem, pdMS_TO_TICKS(ENC28J60_TX_READY_TIMEOUT_MS)) == pdFALSE) { 913 | ESP_LOGW(TAG, "tx_ready_sem expired"); 914 | } 915 | MAC_CHECK(enc28j60_do_register_read(emac, true, ENC28J60_ECON1, &econ1) == ESP_OK, 916 | "read ECON1 failed", out, ESP_FAIL); 917 | MAC_CHECK(!(econ1 & ECON1_TXRTS), "last transmit still in progress", out, ESP_ERR_INVALID_STATE); 918 | 919 | /* Set the write pointer to start of transmit buffer area */ 920 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_EWRPTL, ENC28J60_BUF_TX_START & 0xFF) == ESP_OK, 921 | "write EWRPTL failed", out, ESP_FAIL); 922 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_EWRPTH, (ENC28J60_BUF_TX_START & 0xFF00) >> 8) == ESP_OK, 923 | "write EWRPTH failed", out, ESP_FAIL); 924 | 925 | /* Set the end pointer to correspond to the packet size given */ 926 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ETXNDL, (ENC28J60_BUF_TX_START + length) & 0xFF) == ESP_OK, 927 | "write ETXNDL failed", out, ESP_FAIL); 928 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ETXNDH, ((ENC28J60_BUF_TX_START + length) & 0xFF00) >> 8) == ESP_OK, 929 | "write ETXNDH failed", out, ESP_FAIL); 930 | 931 | /* copy data to tx memory */ 932 | uint8_t per_pkt_control = 0; // MACON3 will be used to determine how the packet will be transmitted 933 | MAC_CHECK(enc28j60_do_memory_write(emac, &per_pkt_control, 1) == ESP_OK, 934 | "write packet control byte failed", out, ESP_FAIL); 935 | MAC_CHECK(enc28j60_do_memory_write(emac, buf, length) == ESP_OK, 936 | "buffer memory write failed", out, ESP_FAIL); 937 | emac->last_tsv_addr = ENC28J60_BUF_TX_START + length + 1; 938 | 939 | /* enable Tx Interrupt to indicate next Tx ready state */ 940 | MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXIF) == ESP_OK, 941 | "set EIR_TXIF failed", out, ESP_FAIL); 942 | MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_TXIE) == ESP_OK, 943 | "set EIE_TXIE failed", out, ESP_FAIL); 944 | 945 | /* issue tx polling command */ 946 | MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRTS) == ESP_OK, 947 | "set ECON1.TXRTS failed", out, ESP_FAIL); 948 | out: 949 | return ret; 950 | } 951 | 952 | static esp_err_t emac_enc28j60_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) 953 | { 954 | esp_err_t ret = ESP_OK; 955 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 956 | uint8_t pk_counter = 0; 957 | uint16_t rx_len = 0; 958 | uint32_t next_packet_addr = 0; 959 | __attribute__((aligned(4))) enc28j60_rx_header_t header; // SPI driver needs the rx buffer 4 byte align 960 | 961 | // read packet header 962 | MAC_CHECK(enc28j60_read_packet(emac, emac->next_packet_ptr, (uint8_t *)&header, sizeof(header)) == ESP_OK, 963 | "read header failed", out, ESP_FAIL); 964 | 965 | // get packets' length, address 966 | rx_len = header.length_low + (header.length_high << 8); 967 | next_packet_addr = header.next_packet_low + (header.next_packet_high << 8); 968 | 969 | // read packet content 970 | MAC_CHECK(enc28j60_read_packet(emac, enc28j60_rx_packet_start(emac->next_packet_ptr, ENC28J60_RSV_SIZE), buf, rx_len) == ESP_OK, 971 | "read packet content failed", out, ESP_FAIL); 972 | 973 | // free receive buffer space 974 | uint32_t erxrdpt = enc28j60_next_ptr_align_odd(next_packet_addr, ENC28J60_BUF_RX_START, ENC28J60_BUF_RX_END); 975 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXRDPTL, (erxrdpt & 0xFF)) == ESP_OK, 976 | "write ERXRDPTL failed", out, ESP_FAIL); 977 | MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXRDPTH, (erxrdpt & 0xFF00) >> 8) == ESP_OK, 978 | "write ERXRDPTH failed", out, ESP_FAIL); 979 | emac->next_packet_ptr = next_packet_addr; 980 | 981 | MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON2, ECON2_PKTDEC) == ESP_OK, 982 | "set ECON2.PKTDEC failed", out, ESP_FAIL); 983 | MAC_CHECK(enc28j60_register_read(emac, ENC28J60_EPKTCNT, &pk_counter) == ESP_OK, 984 | "read EPKTCNT failed", out, ESP_FAIL); 985 | 986 | *length = rx_len - 4; // substract the CRC length 987 | emac->packets_remain = pk_counter > 0; 988 | out: 989 | return ret; 990 | } 991 | 992 | /** 993 | * @brief Get chip info 994 | */ 995 | eth_enc28j60_rev_t emac_enc28j60_get_chip_info(esp_eth_mac_t *mac) 996 | { 997 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 998 | return emac->revision; 999 | } 1000 | 1001 | static esp_err_t emac_enc28j60_init(esp_eth_mac_t *mac) 1002 | { 1003 | esp_err_t ret = ESP_OK; 1004 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 1005 | esp_eth_mediator_t *eth = emac->eth; 1006 | 1007 | /* init gpio used for reporting enc28j60 interrupt */ 1008 | gpio_reset_pin(emac->int_gpio_num); 1009 | gpio_set_direction(emac->int_gpio_num, GPIO_MODE_INPUT); 1010 | gpio_set_pull_mode(emac->int_gpio_num, GPIO_PULLUP_ONLY); 1011 | gpio_set_intr_type(emac->int_gpio_num, GPIO_INTR_NEGEDGE); 1012 | gpio_intr_enable(emac->int_gpio_num); 1013 | gpio_isr_handler_add(emac->int_gpio_num, enc28j60_isr_handler, emac); 1014 | MAC_CHECK(eth->on_state_changed(eth, ETH_STATE_LLINIT, NULL) == ESP_OK, 1015 | "lowlevel init failed", out, ESP_FAIL); 1016 | 1017 | /* reset enc28j60 */ 1018 | MAC_CHECK(enc28j60_do_reset(emac) == ESP_OK, "reset enc28j60 failed", out, ESP_FAIL); 1019 | /* verify chip id */ 1020 | MAC_CHECK(enc28j60_verify_id(emac) == ESP_OK, "vefiry chip ID failed", out, ESP_FAIL); 1021 | /* default setup of internal registers */ 1022 | MAC_CHECK(enc28j60_setup_default(emac) == ESP_OK, "enc28j60 default setup failed", out, ESP_FAIL); 1023 | /* clear multicast hash table */ 1024 | MAC_CHECK(enc28j60_clear_multicast_table(emac) == ESP_OK, "clear multicast table failed", out, ESP_FAIL); 1025 | 1026 | return ESP_OK; 1027 | out: 1028 | gpio_isr_handler_remove(emac->int_gpio_num); 1029 | gpio_reset_pin(emac->int_gpio_num); 1030 | eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); 1031 | return ret; 1032 | } 1033 | 1034 | static esp_err_t emac_enc28j60_deinit(esp_eth_mac_t *mac) 1035 | { 1036 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 1037 | esp_eth_mediator_t *eth = emac->eth; 1038 | mac->stop(mac); 1039 | gpio_isr_handler_remove(emac->int_gpio_num); 1040 | gpio_reset_pin(emac->int_gpio_num); 1041 | eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); 1042 | return ESP_OK; 1043 | } 1044 | 1045 | static esp_err_t emac_enc28j60_del(esp_eth_mac_t *mac) 1046 | { 1047 | emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); 1048 | vTaskDelete(emac->rx_task_hdl); 1049 | vSemaphoreDelete(emac->spi_lock); 1050 | vSemaphoreDelete(emac->reg_trans_lock); 1051 | vSemaphoreDelete(emac->tx_ready_sem); 1052 | free(emac); 1053 | return ESP_OK; 1054 | } 1055 | 1056 | esp_eth_mac_t *esp_eth_mac_new_enc28j60(const eth_enc28j60_config_t *enc28j60_config, const eth_mac_config_t *mac_config) 1057 | { 1058 | esp_eth_mac_t *ret = NULL; 1059 | emac_enc28j60_t *emac = NULL; 1060 | MAC_CHECK(enc28j60_config, "can't set enc28j60 specific config to null", err, NULL); 1061 | MAC_CHECK(mac_config, "can't set mac config to null", err, NULL); 1062 | emac = calloc(1, sizeof(emac_enc28j60_t)); 1063 | MAC_CHECK(emac, "calloc emac failed", err, NULL); 1064 | /* enc28j60 driver is interrupt driven */ 1065 | MAC_CHECK(enc28j60_config->int_gpio_num >= 0, "error interrupt gpio number", err, NULL); 1066 | emac->last_bank = 0xFF; 1067 | emac->next_packet_ptr = ENC28J60_BUF_RX_START; 1068 | /* bind methods and attributes */ 1069 | emac->sw_reset_timeout_ms = mac_config->sw_reset_timeout_ms; 1070 | emac->int_gpio_num = enc28j60_config->int_gpio_num; 1071 | emac->spi_hdl = enc28j60_config->spi_hdl; 1072 | emac->parent.set_mediator = emac_enc28j60_set_mediator; 1073 | emac->parent.init = emac_enc28j60_init; 1074 | emac->parent.deinit = emac_enc28j60_deinit; 1075 | emac->parent.start = emac_enc28j60_start; 1076 | emac->parent.stop = emac_enc28j60_stop; 1077 | emac->parent.del = emac_enc28j60_del; 1078 | emac->parent.write_phy_reg = emac_enc28j60_write_phy_reg; 1079 | emac->parent.read_phy_reg = emac_enc28j60_read_phy_reg; 1080 | emac->parent.set_addr = emac_enc28j60_set_addr; 1081 | emac->parent.get_addr = emac_enc28j60_get_addr; 1082 | emac->parent.set_speed = emac_enc28j60_set_speed; 1083 | emac->parent.set_duplex = emac_enc28j60_set_duplex; 1084 | emac->parent.set_link = emac_enc28j60_set_link; 1085 | emac->parent.set_promiscuous = emac_enc28j60_set_promiscuous; 1086 | emac->parent.transmit = emac_enc28j60_transmit; 1087 | emac->parent.receive = emac_enc28j60_receive; 1088 | /* create mutex */ 1089 | emac->spi_lock = xSemaphoreCreateMutex(); 1090 | MAC_CHECK(emac->spi_lock, "create spi lock failed", err, NULL); 1091 | emac->reg_trans_lock = xSemaphoreCreateMutex(); 1092 | MAC_CHECK(emac->reg_trans_lock, "create register transaction lock failed", err, NULL); 1093 | emac->tx_ready_sem = xSemaphoreCreateBinary(); 1094 | MAC_CHECK(emac->tx_ready_sem, "create pkt transmit ready semaphore failed", err, NULL); 1095 | xSemaphoreGive(emac->tx_ready_sem); // ensures the first transmit is performed without waiting 1096 | /* create enc28j60 task */ 1097 | BaseType_t core_num = tskNO_AFFINITY; 1098 | if (mac_config->flags & ETH_MAC_FLAG_PIN_TO_CORE) { 1099 | core_num = cpu_hal_get_core_id(); 1100 | } 1101 | BaseType_t xReturned = xTaskCreatePinnedToCore(emac_enc28j60_task, "enc28j60_tsk", mac_config->rx_task_stack_size, emac, 1102 | mac_config->rx_task_prio, &emac->rx_task_hdl, core_num); 1103 | MAC_CHECK(xReturned == pdPASS, "create enc28j60 task failed", err, NULL); 1104 | 1105 | return &(emac->parent); 1106 | err: 1107 | if (emac) { 1108 | if (emac->rx_task_hdl) { 1109 | vTaskDelete(emac->rx_task_hdl); 1110 | } 1111 | if (emac->spi_lock) { 1112 | vSemaphoreDelete(emac->spi_lock); 1113 | } 1114 | if (emac->reg_trans_lock) { 1115 | vSemaphoreDelete(emac->reg_trans_lock); 1116 | } 1117 | if (emac->tx_ready_sem) { 1118 | vSemaphoreDelete(emac->tx_ready_sem); 1119 | } 1120 | free(emac); 1121 | } 1122 | return ret; 1123 | } 1124 | --------------------------------------------------------------------------------