mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-06 06:04:33 +02:00
Merge branch 'bugfix/enc28j60' into 'master'
enc28j60: fix stability of the ENC28J60 Ethernet driver example Closes IDFGH-5412, IDFGH-5368, and IDFGH-2673 See merge request espressif/esp-idf!14475
This commit is contained in:
@@ -16,6 +16,13 @@ If you have a more complicated application to go (for example, connect to some I
|
||||
To run this example, you need to prepare following hardwares:
|
||||
* [ESP32 board](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/modules-and-boards.html) (e.g. ESP32-PICO, ESP32 DevKitC, etc)
|
||||
* ENC28J60 module (the latest revision should be 6)
|
||||
* **!! IMPORTANT !!** Proper input power source since ENC28J60 is quite power consuming device (it consumes more than 200 mA in peaks when transmitting). If improper power source is used, input voltage may drop and ENC28J60 may either provide nonsense response to host controller via SPI (fail to read registers properly) or it may enter to some strange state in the worst case. There are several options how to resolve it:
|
||||
* Power ESP32 board from `USB 3.0`, if board is used as source of power to ENC board.
|
||||
* Power ESP32 board from external 5V power supply with current limit at least 1 A, if board is used as source of power to ENC board.
|
||||
* Power ENC28J60 from external 3.3V power supply with common GND to ESP32 board. Note that there might be some ENC28J60 boards with integrated voltage regulator on market and so powered by 5 V. Please consult documentation of your board for details.
|
||||
|
||||
If a ESP32 board is used as source of power to ENC board, ensure that that particular board is assembled with voltage regulator capable to deliver current up to 1 A. This is a case of ESP32 DevKitC or ESP-WROVER-KIT, for example. Such setup was tested and works as expected. Other boards may use different voltage regulators and may perform differently.
|
||||
**WARNING:** Always consult documentation/schematics associated with particular ENC28J60 and ESP32 boards used in your use-case first.
|
||||
|
||||
#### Pin Assignment
|
||||
|
||||
@@ -35,9 +42,9 @@ To run this example, you need to prepare following hardwares:
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
In the `Example Configuration` menu, set SPI specific configuration, such as SPI host number, GPIO used for MISO/MOSI/CS signal, GPIO for interrupt event and the SPI clock rate.
|
||||
In the `Example Configuration` menu, set SPI specific configuration, such as SPI host number, GPIO used for MISO/MOSI/CS signal, GPIO for interrupt event and the SPI clock rate, duplex mode.
|
||||
|
||||
**Note:** According to ENC28J60 data sheet, SPI clock could reach up to 20MHz, but in practice, the clock speed will depend on your PCB layout (in this example, the default clock rate is set to 6MHz, just to make sure that most modules on the market can work at this speed).
|
||||
**Note:** According to ENC28J60 data sheet and our internal testing, SPI clock could reach up to 20MHz, but in practice, the clock speed may depend on your PCB layout/wiring/power source. In this example, the default clock rate is set to 8 MHz since some ENC28J60 silicon revisions may not properly work at frequencies less than 8 MHz.
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
@@ -78,7 +85,16 @@ Now you can ping your ESP32 in the terminal by entering `ping 192.168.2.34` (it
|
||||
|
||||
**Notes:**
|
||||
1. ENC28J60 hasn't burned any valid MAC address in the chip, you need to write an unique MAC address into its internal MAC address register before any traffic happened on TX and RX line.
|
||||
2. ENC28J60 does not support automatic duplex negotiation. If it is connected to an automatic duplex negotiation enabled network switch or Ethernet controller, then ENC28J60 will be detected as a half-duplex device. To communicate in Full-Duplex mode, ENC28J60 and the remote node (switch, router or Ethernet controller) must be manually configured for full-duplex operation.
|
||||
2. It is recommended to operate the ENC28J60 in full-duplex mode since various errata exist to the half-duplex mode (even though addressed in the example) and due to its poor performance in the half-duplex mode (especially in TCP connections). However, ENC28J60 does not support automatic duplex negotiation. If it is connected to an automatic duplex negotiation enabled network switch or Ethernet controller, then ENC28J60 will be detected as a half-duplex device. To communicate in Full-Duplex mode, ENC28J60 and the remote node (switch, router or Ethernet controller) **must be manually configured for full-duplex operation**:
|
||||
* The ENC28J60 can be set to full-duplex in the `Example Configuration` menu.
|
||||
* On Ubuntu/Debian Linux distribution use:
|
||||
```
|
||||
sudo ethtool -s YOUR_INTERFACE_NAME speed 10 duplex full autoneg off
|
||||
```
|
||||
* On Windows, go to `Network Connections` -> `Change adapter options` -> open `Properties` of selected network card -> `Configure` -> `Advanced` -> `Link Speed & Duplex` -> select `10 Mbps Full Duplex in dropdown menu`.
|
||||
3. Ensure that your wiring between ESP32 board and the ENC28J60 board is realized by short wires with the same length and no wire crossings.
|
||||
4. CS Hold Time needs to be configured to be at least 210 ns to properly read MAC and MII registers as defined by ENC28J60 Data Sheet. This is automatically configured in the example based on selected SPI clock frequency by computing amount of SPI bit-cycles the CS should stay active after the transmission. However, if your PCB design/wiring requires different value, please update `cs_ena_posttrans` member of `devcfg` structure per your actual needs.
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "esp_eth_mac_enc28j60.c"
|
||||
"esp_eth_phy_enc28j60.c"
|
||||
INCLUDE_DIRS ".")
|
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
@@ -18,10 +18,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_eth_mac.h"
|
||||
#include "esp_eth_phy.h"
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
/**
|
||||
* @brief SPI Instruction Set
|
||||
*
|
||||
@@ -237,48 +233,6 @@ extern "C" {
|
||||
#define EFLOCON_FCEN1 (1<<1) // Flow Control Enable 1
|
||||
#define EFLOCON_FCEN0 (1<<0) // Flow Control Enable 0
|
||||
|
||||
/**
|
||||
* @brief ENC28J60 specific configuration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
spi_device_handle_t spi_hdl; /*!< Handle of SPI device driver */
|
||||
int int_gpio_num; /*!< Interrupt GPIO number */
|
||||
} eth_enc28j60_config_t;
|
||||
|
||||
/**
|
||||
* @brief Default ENC28J60 specific configuration
|
||||
*
|
||||
*/
|
||||
#define ETH_ENC28J60_DEFAULT_CONFIG(spi_device) \
|
||||
{ \
|
||||
.spi_hdl = spi_device, \
|
||||
.int_gpio_num = 4, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create ENC28J60 Ethernet MAC instance
|
||||
*
|
||||
* @param[in] enc28j60_config: ENC28J60 specific configuration
|
||||
* @param[in] mac_config: Ethernet MAC configuration
|
||||
*
|
||||
* @return
|
||||
* - instance: create MAC instance successfully
|
||||
* - NULL: create MAC instance failed because some error occurred
|
||||
*/
|
||||
esp_eth_mac_t *esp_eth_mac_new_enc28j60(const eth_enc28j60_config_t *enc28j60_config, const eth_mac_config_t *mac_config);
|
||||
|
||||
/**
|
||||
* @brief Create a PHY instance of ENC28J60
|
||||
*
|
||||
* @param[in] config: configuration of PHY
|
||||
*
|
||||
* @return
|
||||
* - instance: create PHY instance successfully
|
||||
* - NULL: create PHY instance failed because some error occurred
|
||||
*/
|
||||
esp_eth_phy_t *esp_eth_phy_new_enc28j60(const eth_phy_config_t *config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,125 @@
|
||||
// Copyright 2021 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_eth_phy.h"
|
||||
#include "esp_eth_mac.h"
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
#define CS_HOLD_TIME_MIN_NS 210
|
||||
|
||||
/**
|
||||
* @brief ENC28J60 specific configuration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
spi_device_handle_t spi_hdl; /*!< Handle of SPI device driver */
|
||||
int int_gpio_num; /*!< Interrupt GPIO number */
|
||||
} eth_enc28j60_config_t;
|
||||
|
||||
/**
|
||||
* @brief ENC28J60 Supported Revisions
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
ENC28J60_REV_B1 = 0b00000010,
|
||||
ENC28J60_REV_B4 = 0b00000100,
|
||||
ENC28J60_REV_B5 = 0b00000101,
|
||||
ENC28J60_REV_B7 = 0b00000110
|
||||
} eth_enc28j60_rev_t;
|
||||
|
||||
/**
|
||||
* @brief Default ENC28J60 specific configuration
|
||||
*
|
||||
*/
|
||||
#define ETH_ENC28J60_DEFAULT_CONFIG(spi_device) \
|
||||
{ \
|
||||
.spi_hdl = spi_device, \
|
||||
.int_gpio_num = 4, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute amount of SPI bit-cycles the CS should stay active after the transmission
|
||||
* to meet ENC28J60 CS Hold Time specification.
|
||||
*
|
||||
* @param clock_speed_mhz SPI Clock frequency in MHz (valid range is <1, 20>)
|
||||
* @return uint8_t
|
||||
*/
|
||||
static inline uint8_t enc28j60_cal_spi_cs_hold_time(int clock_speed_mhz)
|
||||
{
|
||||
if (clock_speed_mhz <= 0 || clock_speed_mhz > 20) {
|
||||
return 0;
|
||||
}
|
||||
int temp = clock_speed_mhz * CS_HOLD_TIME_MIN_NS;
|
||||
uint8_t cs_posttrans = temp / 1000;
|
||||
if (temp % 1000) {
|
||||
cs_posttrans += 1;
|
||||
}
|
||||
|
||||
return cs_posttrans;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create ENC28J60 Ethernet MAC instance
|
||||
*
|
||||
* @param[in] enc28j60_config: ENC28J60 specific configuration
|
||||
* @param[in] mac_config: Ethernet MAC configuration
|
||||
*
|
||||
* @return
|
||||
* - instance: create MAC instance successfully
|
||||
* - NULL: create MAC instance failed because some error occurred
|
||||
*/
|
||||
esp_eth_mac_t *esp_eth_mac_new_enc28j60(const eth_enc28j60_config_t *enc28j60_config, const eth_mac_config_t *mac_config);
|
||||
|
||||
/**
|
||||
* @brief Create a PHY instance of ENC28J60
|
||||
*
|
||||
* @param[in] config: configuration of PHY
|
||||
*
|
||||
* @return
|
||||
* - instance: create PHY instance successfully
|
||||
* - NULL: create PHY instance failed because some error occurred
|
||||
*/
|
||||
esp_eth_phy_t *esp_eth_phy_new_enc28j60(const eth_phy_config_t *config);
|
||||
|
||||
// todo: the below functions should be accessed through ioctl in the future
|
||||
/**
|
||||
* @brief Set ENC28J60 Duplex mode. It sets Duplex mode first to the PHY and then
|
||||
* MAC is set based on what PHY indicates.
|
||||
*
|
||||
* @param phy ENC28J60 PHY Handle
|
||||
* @param duplex Duplex mode
|
||||
*
|
||||
* @return esp_err_t
|
||||
* - ESP_OK when PHY registers were correctly written.
|
||||
*/
|
||||
esp_err_t enc28j60_set_phy_duplex(esp_eth_phy_t *phy, eth_duplex_t duplex);
|
||||
|
||||
/**
|
||||
* @brief Get ENC28J60 silicon revision ID
|
||||
*
|
||||
* @param mac ENC28J60 MAC Handle
|
||||
* @return eth_enc28j60_rev_t
|
||||
* - returns silicon revision ID read during initialization
|
||||
*/
|
||||
eth_enc28j60_rev_t emac_enc28j60_get_chip_info(esp_eth_mac_t *mac);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -26,6 +26,7 @@
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "hal/cpu_hal.h"
|
||||
#include "esp_eth_enc28j60.h"
|
||||
#include "enc28j60.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
@@ -41,9 +42,21 @@ static const char *TAG = "enc28j60";
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define MAC_CHECK_NO_RET(a, str, goto_tag, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (!(a)) \
|
||||
{ \
|
||||
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
goto goto_tag; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define ENC28J60_SPI_LOCK_TIMEOUT_MS (50)
|
||||
#define ENC28J60_PHY_OPERATION_TIMEOUT_US (1000)
|
||||
#define ENC28J60_REG_TRANS_LOCK_TIMEOUT_MS (150)
|
||||
#define ENC28J60_PHY_OPERATION_TIMEOUT_US (150)
|
||||
#define ENC28J60_SYSTEM_RESET_ADDITION_TIME_US (1000)
|
||||
#define ENC28J60_TX_READY_TIMEOUT_MS (2000)
|
||||
|
||||
#define ENC28J60_BUFFER_SIZE (0x2000) // 8KB built-in buffer
|
||||
/**
|
||||
@@ -60,6 +73,7 @@ static const char *TAG = "enc28j60";
|
||||
#define ENC28J60_BUF_TX_END (ENC28J60_BUFFER_SIZE - 1)
|
||||
|
||||
#define ENC28J60_RSV_SIZE (6) // Receive Status Vector Size
|
||||
#define ENC28J60_TSV_SIZE (6) // Transmit Status Vector Size
|
||||
|
||||
typedef struct {
|
||||
uint8_t next_packet_low;
|
||||
@@ -70,30 +84,70 @@ typedef struct {
|
||||
uint8_t status_high;
|
||||
} enc28j60_rx_header_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t byte_cnt;
|
||||
|
||||
uint8_t collision_cnt:4;
|
||||
uint8_t crc_err:1;
|
||||
uint8_t len_check_err:1;
|
||||
uint8_t len_out_range:1;
|
||||
uint8_t tx_done:1;
|
||||
|
||||
uint8_t multicast:1;
|
||||
uint8_t broadcast:1;
|
||||
uint8_t pkt_defer:1;
|
||||
uint8_t excessive_defer:1;
|
||||
uint8_t excessive_collision:1;
|
||||
uint8_t late_collision:1;
|
||||
uint8_t giant:1;
|
||||
uint8_t underrun:1;
|
||||
|
||||
uint16_t bytes_on_wire;
|
||||
|
||||
uint8_t ctrl_frame:1;
|
||||
uint8_t pause_ctrl_frame:1;
|
||||
uint8_t backpressure_app:1;
|
||||
uint8_t vlan_frame:1;
|
||||
} enc28j60_tsv_t;
|
||||
|
||||
typedef struct {
|
||||
esp_eth_mac_t parent;
|
||||
esp_eth_mediator_t *eth;
|
||||
spi_device_handle_t spi_hdl;
|
||||
SemaphoreHandle_t spi_lock;
|
||||
SemaphoreHandle_t reg_trans_lock;
|
||||
SemaphoreHandle_t tx_ready_sem;
|
||||
TaskHandle_t rx_task_hdl;
|
||||
uint32_t sw_reset_timeout_ms;
|
||||
uint32_t next_packet_ptr;
|
||||
uint32_t last_tsv_addr;
|
||||
int int_gpio_num;
|
||||
uint8_t addr[6];
|
||||
uint8_t last_bank;
|
||||
bool packets_remain;
|
||||
eth_enc28j60_rev_t revision;
|
||||
} emac_enc28j60_t;
|
||||
|
||||
static inline bool enc28j60_lock(emac_enc28j60_t *emac)
|
||||
static inline bool enc28j60_spi_lock(emac_enc28j60_t *emac)
|
||||
{
|
||||
return xSemaphoreTake(emac->spi_lock, pdMS_TO_TICKS(ENC28J60_SPI_LOCK_TIMEOUT_MS)) == pdTRUE;
|
||||
}
|
||||
|
||||
static inline bool enc28j60_unlock(emac_enc28j60_t *emac)
|
||||
static inline bool enc28j60_spi_unlock(emac_enc28j60_t *emac)
|
||||
{
|
||||
return xSemaphoreGive(emac->spi_lock) == pdTRUE;
|
||||
}
|
||||
|
||||
static inline bool enc28j60_reg_trans_lock(emac_enc28j60_t *emac)
|
||||
{
|
||||
return xSemaphoreTake(emac->reg_trans_lock, pdMS_TO_TICKS(ENC28J60_REG_TRANS_LOCK_TIMEOUT_MS)) == pdTRUE;
|
||||
}
|
||||
|
||||
static inline bool enc28j60_reg_trans_unlock(emac_enc28j60_t *emac)
|
||||
{
|
||||
return xSemaphoreGive(emac->reg_trans_lock) == pdTRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ERXRDPT need to be set always at odd addresses
|
||||
*/
|
||||
@@ -137,12 +191,12 @@ static esp_err_t enc28j60_do_register_write(emac_enc28j60_t *emac, uint8_t reg_a
|
||||
[0] = value
|
||||
}
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -161,14 +215,14 @@ static esp_err_t enc28j60_do_register_read(emac_enc28j60_t *emac, bool is_eth_re
|
||||
.length = is_eth_reg ? 8 : 16, // read operation is different for ETH register and non-ETH register
|
||||
.flags = SPI_TRANS_USE_RXDATA
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
} else {
|
||||
*value = is_eth_reg ? trans.rx_data[0] : trans.rx_data[1];
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -191,12 +245,12 @@ static esp_err_t enc28j60_do_bitwise_set(emac_enc28j60_t *emac, uint8_t reg_addr
|
||||
[0] = mask
|
||||
}
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -219,12 +273,12 @@ static esp_err_t enc28j60_do_bitwise_clr(emac_enc28j60_t *emac, uint8_t reg_addr
|
||||
[0] = mask
|
||||
}
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -243,12 +297,12 @@ static esp_err_t enc28j60_do_memory_write(emac_enc28j60_t *emac, uint8_t *buffer
|
||||
.length = len * 8,
|
||||
.tx_buffer = buffer
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -268,12 +322,12 @@ static esp_err_t enc28j60_do_memory_read(emac_enc28j60_t *emac, uint8_t *buffer,
|
||||
.rx_buffer = buffer
|
||||
};
|
||||
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -290,12 +344,12 @@ static esp_err_t enc28j60_do_reset(emac_enc28j60_t *emac)
|
||||
.cmd = ENC28J60_SPI_CMD_SRC, // Soft reset
|
||||
.addr = 0x1F,
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -329,11 +383,18 @@ out:
|
||||
static esp_err_t enc28j60_register_write(emac_enc28j60_t *emac, uint16_t reg_addr, uint8_t value)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK,
|
||||
"switch bank failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_register_write(emac, reg_addr & 0xFF, value) == ESP_OK,
|
||||
"write register failed", out, ESP_FAIL);
|
||||
if (enc28j60_reg_trans_lock(emac)) {
|
||||
MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK,
|
||||
"switch bank failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_register_write(emac, reg_addr & 0xFF, value) == ESP_OK,
|
||||
"write register failed", out, ESP_FAIL);
|
||||
enc28j60_reg_trans_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
return ret;
|
||||
out:
|
||||
enc28j60_reg_trans_unlock(emac);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -343,11 +404,18 @@ out:
|
||||
static esp_err_t enc28j60_register_read(emac_enc28j60_t *emac, uint16_t reg_addr, uint8_t *value)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK,
|
||||
"switch bank failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_register_read(emac, !(reg_addr & 0xF000), reg_addr & 0xFF, value) == ESP_OK,
|
||||
"read register failed", out, ESP_FAIL);
|
||||
if (enc28j60_reg_trans_lock(emac)) {
|
||||
MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK,
|
||||
"switch bank failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_register_read(emac, !(reg_addr & 0xF000), reg_addr & 0xFF, value) == ESP_OK,
|
||||
"read register failed", out, ESP_FAIL);
|
||||
enc28j60_reg_trans_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
return ret;
|
||||
out:
|
||||
enc28j60_reg_trans_unlock(emac);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -393,10 +461,10 @@ static esp_err_t emac_enc28j60_write_phy_reg(esp_eth_mac_t *mac, uint32_t phy_ad
|
||||
/* polling the busy flag */
|
||||
uint32_t to = 0;
|
||||
do {
|
||||
esp_rom_delay_us(100);
|
||||
esp_rom_delay_us(15);
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK,
|
||||
"read MISTAT failed", out, ESP_FAIL);
|
||||
to += 100;
|
||||
to += 15;
|
||||
} while ((mii_status & MISTAT_BUSY) && to < ENC28J60_PHY_OPERATION_TIMEOUT_US);
|
||||
MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_TIMEOUT);
|
||||
out:
|
||||
@@ -423,19 +491,17 @@ static esp_err_t emac_enc28j60_read_phy_reg(esp_eth_mac_t *mac, uint32_t phy_add
|
||||
/* tell the PHY address to read */
|
||||
MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIREGADR, phy_reg & 0xFF) == ESP_OK,
|
||||
"write MIREGADR failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MICMD, &mii_cmd) == ESP_OK,
|
||||
"read MICMD failed", out, ESP_FAIL);
|
||||
mii_cmd |= MICMD_MIIRD;
|
||||
mii_cmd = MICMD_MIIRD;
|
||||
MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MICMD, mii_cmd) == ESP_OK,
|
||||
"write MICMD failed", out, ESP_FAIL);
|
||||
|
||||
/* polling the busy flag */
|
||||
uint32_t to = 0;
|
||||
do {
|
||||
esp_rom_delay_us(100);
|
||||
esp_rom_delay_us(15);
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK,
|
||||
"read MISTAT failed", out, ESP_FAIL);
|
||||
to += 100;
|
||||
to += 15;
|
||||
} while ((mii_status & MISTAT_BUSY) && to < ENC28J60_PHY_OPERATION_TIMEOUT_US);
|
||||
MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_TIMEOUT);
|
||||
|
||||
@@ -468,16 +534,15 @@ out:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verify chip ID
|
||||
* @brief Verify chip revision ID
|
||||
*/
|
||||
static esp_err_t enc28j60_verify_id(emac_enc28j60_t *emac)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint8_t id;
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_EREVID, &id) == ESP_OK,
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_EREVID, (uint8_t *)&emac->revision) == ESP_OK,
|
||||
"read EREVID failed", out, ESP_FAIL);
|
||||
ESP_LOGI(TAG, "revision: %d", id);
|
||||
MAC_CHECK(id > 0 && id < 7, "wrong chip ID", out, ESP_ERR_INVALID_VERSION);
|
||||
ESP_LOGI(TAG, "revision: %d", emac->revision);
|
||||
MAC_CHECK(emac->revision >= ENC28J60_REV_B1 && emac->revision <= ENC28J60_REV_B7, "wrong chip ID", out, ESP_ERR_INVALID_VERSION);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
@@ -583,7 +648,7 @@ static esp_err_t emac_enc28j60_start(esp_eth_mac_t *mac)
|
||||
/* enable interrupt */
|
||||
MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, 0xFF) == ESP_OK,
|
||||
"clear EIR failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_PKTIE | EIE_INTIE) == ESP_OK,
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_PKTIE | EIE_INTIE | EIE_TXERIE) == ESP_OK,
|
||||
"set EIE.[PKTIE|INTIE] failed", out, ESP_FAIL);
|
||||
/* enable rx logic */
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_RXEN) == ESP_OK,
|
||||
@@ -635,6 +700,11 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline esp_err_t emac_enc28j60_get_tsv(emac_enc28j60_t *emac, enc28j60_tsv_t *tsv)
|
||||
{
|
||||
return enc28j60_read_packet(emac, emac->last_tsv_addr, (uint8_t *)tsv, ENC28J60_TSV_SIZE);
|
||||
}
|
||||
|
||||
static void enc28j60_isr_handler(void *arg)
|
||||
{
|
||||
emac_enc28j60_t *emac = (emac_enc28j60_t *)arg;
|
||||
@@ -646,19 +716,48 @@ static void enc28j60_isr_handler(void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main ENC28J60 Task. Mainly used for Rx processing. However, it also handles other interrupts.
|
||||
*
|
||||
*/
|
||||
static void emac_enc28j60_task(void *arg)
|
||||
{
|
||||
emac_enc28j60_t *emac = (emac_enc28j60_t *)arg;
|
||||
uint8_t status = 0;
|
||||
uint8_t mask = 0;
|
||||
uint8_t *buffer = NULL;
|
||||
uint32_t length = 0;
|
||||
|
||||
while (1) {
|
||||
// block indefinitely until some task notifies me
|
||||
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||
/* clear interrupt status */
|
||||
enc28j60_do_register_read(emac, true, ENC28J60_EIR, &status);
|
||||
/* packet received */
|
||||
loop_start:
|
||||
// block until some task notifies me or check the gpio by myself
|
||||
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 && // if no notification ...
|
||||
gpio_get_level(emac->int_gpio_num) != 0) { // ...and no interrupt asserted
|
||||
continue; // -> just continue to check again
|
||||
}
|
||||
// the host controller should clear the global enable bit for the interrupt pin before servicing the interrupt
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIE_INTIE) == ESP_OK,
|
||||
"clear EIE_INTIE failed", loop_start);
|
||||
// read interrupt status
|
||||
MAC_CHECK_NO_RET(enc28j60_do_register_read(emac, true, ENC28J60_EIR, &status) == ESP_OK,
|
||||
"read EIR failed", loop_end);
|
||||
MAC_CHECK_NO_RET(enc28j60_do_register_read(emac, true, ENC28J60_EIE, &mask) == ESP_OK,
|
||||
"read EIE failed", loop_end);
|
||||
status &= mask;
|
||||
|
||||
// When source of interrupt is unknown, try to check if there is packet waiting (Errata #6 workaround)
|
||||
if (status == 0) {
|
||||
uint8_t pk_counter;
|
||||
MAC_CHECK_NO_RET(enc28j60_register_read(emac, ENC28J60_EPKTCNT, &pk_counter) == ESP_OK,
|
||||
"read EPKTCNT failed", loop_end);
|
||||
if (pk_counter > 0) {
|
||||
status = EIR_PKTIF;
|
||||
} else {
|
||||
goto loop_end;
|
||||
}
|
||||
}
|
||||
|
||||
// packet received
|
||||
if (status & EIR_PKTIF) {
|
||||
do {
|
||||
length = ETH_MAX_PACKET_SIZE;
|
||||
@@ -677,6 +776,53 @@ static void emac_enc28j60_task(void *arg)
|
||||
}
|
||||
} while (emac->packets_remain);
|
||||
}
|
||||
|
||||
// transmit error
|
||||
if (status & EIR_TXERIF) {
|
||||
// Errata #12/#13 workaround - reset Tx state machine
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRST) == ESP_OK,
|
||||
"set TXRST failed", loop_end);
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_ECON1, ECON1_TXRST) == ESP_OK,
|
||||
"clear TXRST failed", loop_end);
|
||||
|
||||
// Clear Tx Error Interrupt Flag
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXERIF) == ESP_OK,
|
||||
"clear TXERIF failed", loop_end);
|
||||
|
||||
// Errata #13 workaround (applicable only to B5 and B7 revisions)
|
||||
if (emac->revision == ENC28J60_REV_B5 || emac->revision == ENC28J60_REV_B7) {
|
||||
__attribute__((aligned(4))) enc28j60_tsv_t tx_status; // SPI driver needs the rx buffer 4 byte align
|
||||
MAC_CHECK_NO_RET(emac_enc28j60_get_tsv(emac, &tx_status) == ESP_OK,
|
||||
"get Tx Status Vector failed", loop_end);
|
||||
// Try to retransmit when late collision is indicated
|
||||
if (tx_status.late_collision) {
|
||||
// Clear Tx Interrupt status Flag (it was set along with the error)
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXIF) == ESP_OK,
|
||||
"clear TXIF failed", loop_end);
|
||||
// Enable global interrupt flag and try to retransmit
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_INTIE) == ESP_OK,
|
||||
"set INTIE failed", loop_end);
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRTS) == ESP_OK,
|
||||
"set TXRTS failed", loop_end);
|
||||
continue; // no need to handle Tx ready interrupt nor to enable global interrupt at this point
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// transmit ready
|
||||
if (status & EIR_TXIF) {
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXIF) == ESP_OK,
|
||||
"clear TXIF failed", loop_end);
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIE, EIE_TXIE) == ESP_OK,
|
||||
"clear TXIE failed", loop_end);
|
||||
|
||||
xSemaphoreGive(emac->tx_ready_sem);
|
||||
}
|
||||
loop_end:
|
||||
// restore global enable interrupt bit
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_INTIE) == ESP_OK,
|
||||
"clear INTIE failed", loop_start);
|
||||
// Note: Interrupt flag PKTIF is cleared when PKTDEC is set (in receive function)
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
@@ -762,9 +908,12 @@ static esp_err_t emac_enc28j60_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32
|
||||
emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent);
|
||||
uint8_t econ1 = 0;
|
||||
|
||||
/* Check if last transmit complete */
|
||||
/* ENC28J60 may be a bottle neck in Eth communication. Hence we need to check if it is ready. */
|
||||
if (xSemaphoreTake(emac->tx_ready_sem, pdMS_TO_TICKS(ENC28J60_TX_READY_TIMEOUT_MS)) == pdFALSE) {
|
||||
ESP_LOGW(TAG, "tx_ready_sem expired");
|
||||
}
|
||||
MAC_CHECK(enc28j60_do_register_read(emac, true, ENC28J60_ECON1, &econ1) == ESP_OK,
|
||||
"read ECON1 failed", out, ESP_FAIL);
|
||||
"read ECON1 failed", out, ESP_FAIL);
|
||||
MAC_CHECK(!(econ1 & ECON1_TXRTS), "last transmit still in progress", out, ESP_ERR_INVALID_STATE);
|
||||
|
||||
/* Set the write pointer to start of transmit buffer area */
|
||||
@@ -785,6 +934,14 @@ static esp_err_t emac_enc28j60_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32
|
||||
"write packet control byte failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_memory_write(emac, buf, length) == ESP_OK,
|
||||
"buffer memory write failed", out, ESP_FAIL);
|
||||
emac->last_tsv_addr = ENC28J60_BUF_TX_START + length + 1;
|
||||
|
||||
/* enable Tx Interrupt to indicate next Tx ready state */
|
||||
MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXIF) == ESP_OK,
|
||||
"set EIR_TXIF failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_TXIE) == ESP_OK,
|
||||
"set EIE_TXIE failed", out, ESP_FAIL);
|
||||
|
||||
/* issue tx polling command */
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRTS) == ESP_OK,
|
||||
"set ECON1.TXRTS failed", out, ESP_FAIL);
|
||||
@@ -832,6 +989,15 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get chip info
|
||||
*/
|
||||
eth_enc28j60_rev_t emac_enc28j60_get_chip_info(esp_eth_mac_t *mac)
|
||||
{
|
||||
emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent);
|
||||
return emac->revision;
|
||||
}
|
||||
|
||||
static esp_err_t emac_enc28j60_init(esp_eth_mac_t *mac)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
@@ -881,6 +1047,8 @@ static esp_err_t emac_enc28j60_del(esp_eth_mac_t *mac)
|
||||
emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent);
|
||||
vTaskDelete(emac->rx_task_hdl);
|
||||
vSemaphoreDelete(emac->spi_lock);
|
||||
vSemaphoreDelete(emac->reg_trans_lock);
|
||||
vSemaphoreDelete(emac->tx_ready_sem);
|
||||
free(emac);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -919,7 +1087,12 @@ esp_eth_mac_t *esp_eth_mac_new_enc28j60(const eth_enc28j60_config_t *enc28j60_co
|
||||
emac->parent.receive = emac_enc28j60_receive;
|
||||
/* create mutex */
|
||||
emac->spi_lock = xSemaphoreCreateMutex();
|
||||
MAC_CHECK(emac->spi_lock, "create lock failed", err, NULL);
|
||||
MAC_CHECK(emac->spi_lock, "create spi lock failed", err, NULL);
|
||||
emac->reg_trans_lock = xSemaphoreCreateMutex();
|
||||
MAC_CHECK(emac->reg_trans_lock, "create register transaction lock failed", err, NULL);
|
||||
emac->tx_ready_sem = xSemaphoreCreateBinary();
|
||||
MAC_CHECK(emac->tx_ready_sem, "create pkt transmit ready semaphore failed", err, NULL);
|
||||
xSemaphoreGive(emac->tx_ready_sem); // ensures the first transmit is performed without waiting
|
||||
/* create enc28j60 task */
|
||||
BaseType_t core_num = tskNO_AFFINITY;
|
||||
if (mac_config->flags & ETH_MAC_FLAG_PIN_TO_CORE) {
|
||||
@@ -938,6 +1111,12 @@ err:
|
||||
if (emac->spi_lock) {
|
||||
vSemaphoreDelete(emac->spi_lock);
|
||||
}
|
||||
if (emac->reg_trans_lock) {
|
||||
vSemaphoreDelete(emac->reg_trans_lock);
|
||||
}
|
||||
if (emac->tx_ready_sem) {
|
||||
vSemaphoreDelete(emac->tx_ready_sem);
|
||||
}
|
||||
free(emac);
|
||||
}
|
||||
return ret;
|
@@ -17,6 +17,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_eth.h"
|
||||
#include "eth_phy_regs_struct.h"
|
||||
#include "esp_eth_enc28j60.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
@@ -34,6 +35,24 @@ static const char *TAG = "enc28j60";
|
||||
|
||||
/***************Vendor Specific Register***************/
|
||||
|
||||
/**
|
||||
* @brief PHCON2(PHY Control Register 2)
|
||||
*
|
||||
*/
|
||||
typedef union {
|
||||
struct {
|
||||
uint32_t reserved_7_0 : 8; // Reserved
|
||||
uint32_t pdpxmd : 1; // PHY Duplex Mode bit
|
||||
uint32_t reserved_10_9: 2; // Reserved
|
||||
uint32_t ppwrsv: 1; // PHY Power-Down bit
|
||||
uint32_t reserved_13_12: 2; // Reserved
|
||||
uint32_t ploopbk: 1; // PHY Loopback bit
|
||||
uint32_t prst: 1; // PHY Software Reset bit
|
||||
};
|
||||
uint32_t val;
|
||||
} phcon1_reg_t;
|
||||
#define ETH_PHY_PHCON1_REG_ADDR (0x00)
|
||||
|
||||
/**
|
||||
* @brief PHCON2(PHY Control Register 2)
|
||||
*
|
||||
@@ -188,6 +207,35 @@ err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t enc28j60_set_phy_duplex(esp_eth_phy_t *phy, eth_duplex_t duplex)
|
||||
{
|
||||
phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent);
|
||||
esp_eth_mediator_t *eth = enc28j60->eth;
|
||||
phcon1_reg_t phcon1;
|
||||
|
||||
PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, 0, &phcon1.val) == ESP_OK,
|
||||
"read PHCON1 failed", err);
|
||||
switch (duplex) {
|
||||
case ETH_DUPLEX_HALF:
|
||||
phcon1.pdpxmd = 0;
|
||||
break;
|
||||
case ETH_DUPLEX_FULL:
|
||||
phcon1.pdpxmd = 1;
|
||||
break;
|
||||
default:
|
||||
PHY_CHECK(false, "unknown duplex", err);
|
||||
break;
|
||||
}
|
||||
|
||||
PHY_CHECK(eth->phy_reg_write(eth, enc28j60->addr, 0, phcon1.val) == ESP_OK,
|
||||
"write PHCON1 failed", err);
|
||||
|
||||
PHY_CHECK(enc28j60_update_link_duplex_speed(enc28j60) == ESP_OK, "update link duplex speed failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t enc28j60_pwrctl(esp_eth_phy_t *phy, bool enable)
|
||||
{
|
||||
phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent);
|
@@ -1,6 +1,4 @@
|
||||
set(srcs "enc28j60_example_main.c"
|
||||
"esp_eth_mac_enc28j60.c"
|
||||
"esp_eth_phy_enc28j60.c")
|
||||
set(srcs "enc28j60_example_main.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ".")
|
||||
|
@@ -38,7 +38,7 @@ menu "Example Configuration"
|
||||
config EXAMPLE_ENC28J60_SPI_CLOCK_MHZ
|
||||
int "SPI clock speed (MHz)"
|
||||
range 5 20
|
||||
default 6
|
||||
default 8
|
||||
help
|
||||
Set the clock speed (MHz) of SPI interface.
|
||||
|
||||
@@ -47,4 +47,22 @@ menu "Example Configuration"
|
||||
default 4
|
||||
help
|
||||
Set the GPIO number used by ENC28J60 interrupt.
|
||||
|
||||
choice EXAMPLE_ENC28J60_DUPLEX_MODE
|
||||
prompt "Duplex Mode"
|
||||
default EXAMPLE_ENC28J60_DUPLEX_HALF
|
||||
help
|
||||
Select ENC28J60 Duplex operation mode.
|
||||
|
||||
config EXAMPLE_ENC28J60_DUPLEX_FULL
|
||||
bool "Full Duplex"
|
||||
help
|
||||
Set ENC28J60 to Full Duplex mode. Do not forget to manually set the remote node (switch, router
|
||||
or Ethernet controller) to full-duplex operation mode too.
|
||||
|
||||
config EXAMPLE_ENC28J60_DUPLEX_HALF
|
||||
bool "Half Duplex"
|
||||
help
|
||||
Set ENC28J60 to Half Duplex mode.
|
||||
endchoice # EXAMPLE_ENC28J60_DUPLEX_MODE
|
||||
endmenu
|
||||
|
@@ -16,7 +16,7 @@
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "enc28j60.h"
|
||||
#include "esp_eth_enc28j60.h"
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
static const char *TAG = "eth_example";
|
||||
@@ -87,7 +87,7 @@ void app_main(void)
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ENC28J60_SPI_HOST, &buscfg, 1));
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ENC28J60_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
/* ENC28J60 ethernet driver is based on spi driver */
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 3,
|
||||
@@ -95,8 +95,10 @@ void app_main(void)
|
||||
.mode = 0,
|
||||
.clock_speed_hz = CONFIG_EXAMPLE_ENC28J60_SPI_CLOCK_MHZ * 1000 * 1000,
|
||||
.spics_io_num = CONFIG_EXAMPLE_ENC28J60_CS_GPIO,
|
||||
.queue_size = 20
|
||||
.queue_size = 20,
|
||||
.cs_ena_posttrans = enc28j60_cal_spi_cs_hold_time(CONFIG_EXAMPLE_ENC28J60_SPI_CLOCK_MHZ),
|
||||
};
|
||||
|
||||
spi_device_handle_t spi_handle = NULL;
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ENC28J60_SPI_HOST, &devcfg, &spi_handle));
|
||||
|
||||
@@ -124,8 +126,20 @@ void app_main(void)
|
||||
0x02, 0x00, 0x00, 0x12, 0x34, 0x56
|
||||
});
|
||||
|
||||
// ENC28J60 Errata #1 check
|
||||
if (emac_enc28j60_get_chip_info(mac) < ENC28J60_REV_B5 && CONFIG_EXAMPLE_ENC28J60_SPI_CLOCK_MHZ < 8) {
|
||||
ESP_LOGE(TAG, "SPI frequency must be at least 8 MHz for chip revision less than 5");
|
||||
ESP_ERROR_CHECK(ESP_FAIL);
|
||||
}
|
||||
|
||||
/* attach Ethernet driver to TCP/IP stack */
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));
|
||||
/* start Ethernet driver state machine */
|
||||
ESP_ERROR_CHECK(esp_eth_start(eth_handle));
|
||||
|
||||
/* It is recommended to use ENC28J60 in Full Duplex mode since multiple errata exist to the Half Duplex mode */
|
||||
#if CONFIG_EXAMPLE_ENC28J60_DUPLEX_FULL
|
||||
/* Set duplex needs to be called after esp_eth_start since the driver is started with auto-negotiation by default */
|
||||
enc28j60_set_phy_duplex(phy, ETH_DUPLEX_FULL);
|
||||
#endif
|
||||
}
|
||||
|
@@ -3,7 +3,8 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components
|
||||
$ENV{IDF_PATH}/examples/common_components/iperf)
|
||||
$ENV{IDF_PATH}/examples/common_components/iperf
|
||||
$ENV{IDF_PATH}/examples/ethernet/enc28j60/components/eth_enc28j60)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(ethernet_iperf)
|
||||
|
@@ -8,6 +8,7 @@ PROJECT_NAME := ethernet_iperf
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/system/console/advanced/components
|
||||
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/wifi/iperf/components
|
||||
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/common_components/iperf
|
||||
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/ethernet/enc28j60/components/eth_enc28j60
|
||||
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
@@ -39,6 +39,14 @@ menu "Example Configuration"
|
||||
select ETH_SPI_ETHERNET_W5500
|
||||
help
|
||||
Select external SPI-Ethernet module (W5500).
|
||||
|
||||
config EXAMPLE_USE_ENC28J60
|
||||
bool "ENC28J60 Module"
|
||||
select EXAMPLE_USE_SPI_ETHERNET
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_ENC28J60
|
||||
help
|
||||
Select external SPI-Ethernet module (ENC28J60).
|
||||
endchoice # EXAMPLE_ETHERNET_TYPE
|
||||
|
||||
if EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
@@ -131,6 +139,7 @@ menu "Example Configuration"
|
||||
config EXAMPLE_ETH_SPI_CLOCK_MHZ
|
||||
int "SPI clock speed (MHz)"
|
||||
range 5 80
|
||||
default 8 if EXAMPLE_USE_ENC28J60
|
||||
default 36
|
||||
help
|
||||
Set the clock speed (MHz) of SPI interface.
|
||||
@@ -155,4 +164,24 @@ menu "Example Configuration"
|
||||
default 1
|
||||
help
|
||||
Set PHY address according your board schematic.
|
||||
|
||||
if EXAMPLE_USE_ENC28J60
|
||||
choice EXAMPLE_ENC28J60_DUPLEX_MODE
|
||||
prompt "Duplex Mode"
|
||||
default EXAMPLE_ENC28J60_DUPLEX_HALF
|
||||
help
|
||||
Select ENC28J60 Duplex operation mode.
|
||||
|
||||
config EXAMPLE_ENC28J60_DUPLEX_FULL
|
||||
bool "Full Duplex"
|
||||
help
|
||||
Set ENC28J60 to Full Duplex mode. Do not forget to manually set the remote node (switch, router
|
||||
or Ethernet controller) to full-duplex operation mode too.
|
||||
|
||||
config EXAMPLE_ENC28J60_DUPLEX_HALF
|
||||
bool "Half Duplex"
|
||||
help
|
||||
Set ENC28J60 to Half Duplex mode.
|
||||
endchoice # EXAMPLE_ENC28J60_DUPLEX_MODE
|
||||
endif # ETH_SPI_ETHERNET_ENC28J60
|
||||
endmenu
|
||||
|
@@ -21,6 +21,9 @@
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_ETH_USE_SPI_ETHERNET
|
||||
#include "driver/spi_master.h"
|
||||
#if CONFIG_EXAMPLE_USE_ENC28J60
|
||||
#include "esp_eth_enc28j60.h"
|
||||
#endif //CONFIG_EXAMPLE_USE_ENC28J60
|
||||
#endif // CONFIG_ETH_USE_SPI_ETHERNET
|
||||
|
||||
static esp_netif_ip_info_t ip;
|
||||
@@ -218,7 +221,7 @@ void register_ethernet(void)
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
#if CONFIG_EXAMPLE_USE_DM9051
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 1,
|
||||
@@ -249,6 +252,29 @@ void register_ethernet(void)
|
||||
w5500_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_USE_ENC28J60
|
||||
/* ENC28J60 ethernet driver is based on spi driver */
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 3,
|
||||
.address_bits = 5,
|
||||
.mode = 0,
|
||||
.clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
|
||||
.spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
|
||||
.queue_size = 20,
|
||||
.cs_ena_posttrans = enc28j60_cal_spi_cs_hold_time(CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ)
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
|
||||
|
||||
eth_enc28j60_config_t enc28j60_config = ETH_ENC28J60_DEFAULT_CONFIG(spi_handle);
|
||||
enc28j60_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
|
||||
|
||||
mac_config.smi_mdc_gpio_num = -1; // ENC28J60 doesn't have SMI interface
|
||||
mac_config.smi_mdio_gpio_num = -1;
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_enc28j60(&enc28j60_config, &mac_config);
|
||||
|
||||
phy_config.autonego_timeout_ms = 0; // ENC28J60 doesn't support auto-negotiation
|
||||
phy_config.reset_gpio_num = -1; // ENC28J60 doesn't have a pin to reset internal PHY
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_enc28j60(&phy_config);
|
||||
#endif
|
||||
#endif // CONFIG_ETH_USE_SPI_ETHERNET
|
||||
esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
|
||||
@@ -264,6 +290,10 @@ void register_ethernet(void)
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));
|
||||
ESP_ERROR_CHECK(esp_eth_start(eth_handle));
|
||||
|
||||
#if CONFIG_EXAMPLE_USE_ENC28J60 && CONFIG_EXAMPLE_ENC28J60_DUPLEX_FULL
|
||||
enc28j60_set_phy_duplex(phy, ETH_DUPLEX_FULL);
|
||||
#endif
|
||||
|
||||
eth_control_args.control = arg_str1(NULL, NULL, "<info>", "Get info of Ethernet");
|
||||
eth_control_args.end = arg_end(1);
|
||||
const esp_console_cmd_t cmd = {
|
||||
|
Reference in New Issue
Block a user