Всем доброго времени суток! Начинаем долгожданный цикл статей по практической работе с Ethernet. Пройдемся с самого низкого уровня по всему стеку протоколов - ARP, IP, UDP, TCP... В качестве целевой платформы возьмем традиционно STM32 и в качестве микросхемы физического уровня Ethernet контроллер ENC28J60, как один из наиболее распространенных и доступных.
В этой, первой, статье рассмотрим физическое подключение, инициализацию интерфейсов, настройку ENC28J60 и, пожалуй, напишем сразу драйвер для работы с вышеупомянутым контроллером. Все буду описывать по возможности подробнее, но если вдруг возникнут вопросы, добро пожаловать в комментарии, а еще лучше к нам на форум.
Подключение ENC28J60.
Итак, STM32... Я возьму STM32F103C8T6, опять же в силу его невероятной распространенности и популярности, чему мы можем быть благодарны плате Blue Pill:
Для ENC28J60 у меня среди прочих есть такой модуль, пусть будет он:
Мы будем писать код исходя из соображений возможности его использования и с любым другим железом, так что выбор в данном случае не критичен.
Вспоминаем сетевую модель OSI (здесь в ближайшее время я помещу ссылку на статью об OSI):
Самый низкий уровень, физический, в данном случае это 10BASE-T полностью берет на себя ENC28J60. Переходим на уровень выше - канальный уровень, который отвечает за передачу фреймов между узлами сети, основан на использовании MAC-адресов. Его также отчасти возьмет на себя ENC28J60, благо возможности позволяют. А вот работу на уровнях выше мы будем реализовывать программно - к примеру, на сетевом уровне, будет протокол IP, на транспортном - UDP/TCP, но не будем забегать вперед. Возвращаемся к подключению модулей.
И подключать контроллер мы будем по интерфейсу SPI. Собственно, схематично это представляет из себя следующее:
В разных проектах бывает по-разному, но в данном случае я не вижу ни малейших причин не использовать STM32CubeMx и HAL, так что создаем новый проект и сразу включаем "базово-необходимые" вещи, такие как внешнее тактирование и SWD интерфейс. Кроме того, пара выводов для работы с ENC28J60, в данном случае, это сигналы Chip Select, Reset и, конечно, SPI1:
У меня внешний кварцевый резонатор на 8 МГц, поэтому в окне тактирования:
Ну и напоследок, конечно же, настраиваем SPI. Как изображено на схеме выше, используем ножки PA5 - SPI SCK, PA6 - SPI MISO и PA7 - SPI MOSI. Настройки для ENC28J60 такие:
После этого можно переходить к генерации проекта, больше в CubeMx ничего менять не будем.
Сразу же зададим адекватную структуризацию в проекте, которой и будем придерживаться по мере его, проекта, развития. Создадим папку Modules, в которую будем помещать наши файлы для работы с тем или иным протоколом или устройством. И сразу добавляем подпапку, а также заголовочный и файл с исходным кодом для драйвера ENC28J60, который и будем реализовывать:
Проект целиком можно будет как и всегда скачать в конце статьи, так же в конце статьи размещу полный код этих файлов для максимально удобного прочтения. А пока начинаем поэтапно разбираться.
Инициализация и работа с ENC28J60.
Начнем с базовых функций для отправки и приема данных по SPI, которые сопровождаются установкой и сбросом сигнала Chip Select:
/*----------------------------------------------------------------------------*/ static void SetCS(ENC28J60_CS_State state) { HAL_GPIO_WritePin(ENC28J60_CS_PORT, ENC28J60_CS_PIN, (GPIO_PinState)state); } /*----------------------------------------------------------------------------*/
Аргументы функции, а также порты STM32, используемые для Chip Select'а и Reset'а объявлены в заголовочном файле enc28j60.h:
#define ENC28J60_CS_PORT GPIOB #define ENC28J60_CS_PIN GPIO_PIN_3 #define ENC28J60_RESET_PORT GPIOB #define ENC28J60_RESET_PIN GPIO_PIN_6 typedef enum { CS_LOW = 0, CS_HIGH = 1, } ENC28J60_CS_State;
Далее последовательно функции для записи/отправки байта, записи нескольких байт, чтения байта:
/*----------------------------------------------------------------------------*/ static void WriteByte(uint8_t data) { HAL_StatusTypeDef res = HAL_SPI_Transmit(&hspi1, &data, 1, ENC28J60_SPI_TIMEOUT); } /*----------------------------------------------------------------------------*/ static void WriteBytes(uint8_t* data, uint16_t size) { HAL_StatusTypeDef res = HAL_SPI_Transmit(&hspi1, data, size, ENC28J60_SPI_TIMEOUT); } /*----------------------------------------------------------------------------*/ static uint8_t ReadByte() { uint8_t txData = 0x00; uint8_t rxData = 0x00; HAL_StatusTypeDef res = HAL_SPI_TransmitReceive(&hspi1, &txData, &rxData, 1, ENC28J60_SPI_TIMEOUT); return rxData; } /*----------------------------------------------------------------------------*/
ENC28J60 поддерживает стандартный механизм взаимодействия с внешним контроллером - через чтение и запись значений в регистры, отвечающие за ту или иную функцию. Все регистры делятся на три основные группы:
- управляющие регистры
- регистры для работы с буфером Ethernet
- и регистры PHY
Сегодня наш акцент будет смещен на работу с первой из этих групп. Управляющие регистры распределены по 4-м банкам:
Соответственно, если у нас активен банк 0, то при записи регистра с адресом 0x00 мы изменим значение регистра ERDPTL, если банк 1 - то EHT0. В конце каждого из банков есть пять одинаковых регистров, которые являются общими, то есть, какой бы банк не был активен, эти адреса (0x1B - 0x1F) будут принадлежать одним и тем же регистрам EIE, EIR, ESTAT, ECON2, ECON1.
Первые буквы регистров указывают на принадлежность регистров:
- E - регистры Ethernet
- MA - группа регистров MAC
- MII - группа регистров MII
Это может показаться очень запутанным, непрозрачным и непонятным, но все встанет на свои места по мере того, как мы будем решать практические задачи. Так что не стоит на этом сильно зацикливаться.
Описание всех регистров и их битов я приводить не буду, поскольку это будет банальным переводом даташита, в этом толку, кроме увеличения объема статьи, никакого нет.
Работа с ENC28J60 построена на следующем формате - контроллер отправляет команду длиной один байт, за которой следуют данные. В зависимости от типа команды за ней следует либо передача данных в ENC28J60, либо чтение данных из нее же. Всего этих команд 7 штук:
Соответственно, как тут указано, байт команды состоит из кода OpCode (3 бита) и 5 битов, которые либо отвечают за адрес регистра из таблицы выше, либо имеют фиксированное значение для некоторых команд. Разберем, к примеру, команду чтения управляющего регистра (Read Control Register):
Все в точности, как мы и ожидали - байт команды (3 бита опкода + 5 битов адреса), за которыми следуют данные со значением регистра, передаваемые из ENC28J60. Команда записи аналогична, за исключением того, что после команды мы уже сами передаем в ENC28J60 данные для записи в регистр:
В общем, несложная модель, реализуем практически. Для начала определим все существующие регистры. Нюанс тут заключается в том, что помимо адреса каждый регистр имеет еще и свой собственный тип (Ethernet, MAC, MII), а также относится к какому-либо банку. Поэтому для описания каждого из регистров будем использовать 8 битов таким вот образом:
- 5 младших битов - адрес регистра
- 2 бита для хранения информации о номере банка - 0b00 - Bank0, 0b01 - Bank1, 0b10 - Bank2, 0b11 - Bank3
- и старший бит - флаг о принадлежности регистра либо к группе Ethernet (бит равен 0), либо к одной из групп MAC или MII (бит равен 1)
В коде это реализовано по следующей схеме. Напоминаю, что полный код файлов и проект целиком будут в конце статьи, здесь же для поддержания целостности изложения будут лишь отрывки для передачи основной сути. На любые возникшие дополнительные вопросы с радостью отвечу 👍
Итак, в заголовочном файле определены enum
для банков и типов регистров:
typedef enum { BANK_0, BANK_1, BANK_2, BANK_3, } ENC28J60_RegBank; typedef enum { ETH_REG, MAC_MII_REG, } ENC28J60_RegType;
И набор дефайнов:
#define ENC28J60_REG_BANK_OFFSET 5 #define ENC28J60_REG_TYPE_OFFSET 7 #define ENC28J60_REG_BANK_MASK 0x60 #define ENC28J60_REG_TYPE_MASK 0x80 #define ENC28J60_REG_ADDR_MASK 0x1F #define ENC28J60_BANK_0_BITS (BANK_0 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_BANK_1_BITS (BANK_1 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_BANK_2_BITS (BANK_2 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_BANK_3_BITS (BANK_3 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_BANK_COMMON_BITS (BANK_0 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_COMMON_REGS_ADDR 0x1B #define ENC28J60_ETH_REG_BIT (ETH_REG << ENC28J60_REG_TYPE_OFFSET) #define ENC28J60_MAC_MII_REG_BIT (MAC_MII_REG << ENC28J60_REG_TYPE_OFFSET)
Все эти определения подчиняются схеме описания регистров, которую мы разобрали. Для номера банка у нас 5-ый и 6-ой из 8 битов, соответственно, ENC28J60_REG_BANK_OFFSET = 5
и ENC28J60_REG_BANK_MASK = 0x60
(0b01100000). Аналогично для бита, который отвечает за тип регистра (он у нас 7-ой).
Также для тех 5-ти регистров, которые не относятся к конкретному банку мы будем использовать такое же значение битов, как для нулевого банка (ENC28J60_BANK_COMMON_BITS
). Но для их идентификации будем использовать не значения битов, как для других регистров, а непосредственно значение адреса. Поскольку мы знаем, что эти уникальные регистры расположены по адресам старше, чем ENC28J60_COMMON_REGS_ADDR (0x1B)
.
Битовые маски помогут извлечь из 8-ми битного значения данные о банке и типе регистра. В результате определение каждого из регистров у нас будет выглядеть следующим образом. Возьмем несколько из разных банков для наглядности:
#define EIE (0x1B | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_COMMON_BITS) #define EWRPTL (0x02 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define MACON4 (0x03 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define EBSTCSH (0x09 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS)
В итоге имеем изящную систему определения регистров: Register = Address | Type | Bank, где:
- Address - значение адреса из таблицы
- Type -
ENC28J60_ETH_REG_BIT
илиENC28J60_MAC_MII_REG_BIT
- Bank -
ENC28J60_BANK_0_BITS
-ENC28J60_BANK_3_BITS
Для работы с определенными битами регистров определяем их позиции. Возьмем, к примеру, регистр ECON1:
#define ECON1_TXRST_BIT (1 << 7) #define ECON1_RXRST_BIT (1 << 6) #define ECON1_DMAST_BIT (1 << 5) #define ECON1_CSUMEN_BIT (1 << 4) #define ECON1_TXRTS_BIT (1 << 3) #define ECON1_RXEN_BIT (1 << 2) #define ECON1_BSEL1_BIT (1 << 1) #define ECON1_BSEL0_BIT (1 << 0)
На этом все, переходим к созданию функций. Группа функций, извлекающих из 8-битного значения регистра данные о его типе, банке и адресе:
/*----------------------------------------------------------------------------*/ static ENC28J60_RegType getRegType(uint8_t reg) { ENC28J60_RegType type = (ENC28J60_RegType)((reg & ENC28J60_REG_TYPE_MASK) >> ENC28J60_REG_TYPE_OFFSET); return type; } /*----------------------------------------------------------------------------*/ static ENC28J60_RegBank getRegBank(uint8_t reg) { ENC28J60_RegBank bank = (ENC28J60_RegBank)((reg & ENC28J60_REG_BANK_MASK) >> ENC28J60_REG_BANK_OFFSET); return bank; } /*----------------------------------------------------------------------------*/ static uint8_t getRegAddr(uint8_t reg) { uint8_t addr = (reg & ENC28J60_REG_ADDR_MASK); return addr; } /*----------------------------------------------------------------------------*/
Перед любыми операциями с регистрами нам нужно будет проверять текущий активный банк в ENC28J60, номер которого хранится в двух младших битах регистра ECON1:
И если нам нужен регистр из другого банка, то банк следует переключить. И чтобы не вычитывать это значение из регистра каждый раз, будем хранить номер текущего банка в переменной:
static ENC28J60_RegBank curBank = BANK_0;
Функция проверки банка:
/*----------------------------------------------------------------------------*/ static void CheckBank(uint8_t reg) { uint8_t regAddr = getRegAddr(reg); if (regAddr < ENC28J60_COMMON_REGS_ADDR) { ENC28J60_RegBank regBank = getRegBank(reg); if (curBank != regBank) { uint8_t econ1Addr = getRegAddr(ECON1); // Clear bank bits SetCS(CS_LOW); WriteCommand(BIT_FIELD_CLEAR, econ1Addr); WriteByte(ECON1_BSEL1_BIT | ECON1_BSEL0_BIT); SetCS(CS_HIGH); // Set bank bits SetCS(CS_LOW); WriteCommand(BIT_FIELD_SET, econ1Addr); WriteByte(regBank); SetCS(CS_HIGH); curBank = regBank; } } } /*----------------------------------------------------------------------------*/
Если банк регистра, который передан аргументом, не соответствует текущему - выполняем переключение банка при помощи функции WriteCommand()
, которую обсудим чуть ниже. При этом весь этот механизм проверки нужен только для регистров, значения адресов которых численно меньше ENC28J60_COMMON_REGS_ADDR
.
Вспоминаем о тех 7-ми командах, которые предоставляет нам ENC28J60, и определяем их:
typedef enum { READ_CONTROL_REG, READ_BUFFER_MEM, WRITE_CONTROL_REG, WRITE_BUFFER_MEM, BIT_FIELD_SET, BIT_FIELD_CLEAR, SYSTEM_RESET, COMMANDS_NUM, } ENC28J60_Command;
Опкоды команд, в свою очередь, в массиве:
static uint8_t commandOpCodes[COMMANDS_NUM] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x07};
Функция отправки байта команды:
/*----------------------------------------------------------------------------*/ static void WriteCommand(ENC28J60_Command command, uint8_t argData) { uint8_t data = 0; data = (commandOpCodes[command] << ENC28J60_OP_CODE_OFFSET) | argData; WriteByte(data); } /*----------------------------------------------------------------------------*/
И на базе этой функции мы создадим все команды. Рассмотрим пару из них, чтение регистра:
/*----------------------------------------------------------------------------*/ static uint8_t ReadControlReg(uint8_t reg) { uint8_t data = 0; ENC28J60_RegType regType = getRegType(reg); uint8_t regAddr = getRegAddr(reg); CheckBank(reg); SetCS(CS_LOW); WriteCommand(READ_CONTROL_REG, regAddr); if (regType == MAC_MII_REG) { ReadByte(); } data = ReadByte(); SetCS(CS_HIGH); return data; } /*----------------------------------------------------------------------------*/
Аргумент - значение регистра в том формате, который мы определили для описания регистров контроллера. Из этого значения затем мы получаем банк и тип регистра. Обратите внимания, что для MAC/MII регистров нужно считать дополнительно байт, не несущий никакой смысловой нагрузки, так устроено в ENC28J60.
Команда сброса:
/*----------------------------------------------------------------------------*/ static void SystemReset() { SetCS(CS_LOW); WriteCommand(SYSTEM_RESET, ENC28J60_RESET_COMMAND_ARG); SetCS(CS_HIGH); curBank = BANK_0; HAL_Delay(1); } /*----------------------------------------------------------------------------*/
Вот итоговый список тех 7-ми функций-команд для взаимодействия с ENC28J60:
void BitFieldSet(uint8_t reg, uint8_t regData); void BitFieldClear(uint8_t reg, uint8_t regData); uint8_t ReadControlReg(uint8_t reg); void WriteControlReg(uint8_t reg, uint8_t regData); void WriteBufferMem(uint8_t *data, uint16_t size); void ReadBufferMem(uint8_t *data, uint16_t size); static void SystemReset();
Но этим ограничиться не получится. Во-первых, многие регистры представляют из себя комплементарную пару, то есть 16-битное значение хранится в 2-х регистрах (например, EWRPTL и EWRPTH). Старшие 8 бит - в EWRPTH, младшие - в EWRPTL. Так что нам необходимо будет работать и с ними:
/*----------------------------------------------------------------------------*/ static void WriteControlRegPair(uint8_t reg, uint16_t regData) { WriteControlReg(reg, (uint8_t)regData); WriteControlReg(reg + 1, (uint8_t)(regData >> 8)); } /*----------------------------------------------------------------------------*/ static uint16_t ReadControlRegPair(uint8_t reg) { uint16_t data = 0; data = (uint16_t)ReadControlReg(reg) | ((uint16_t)ReadControlReg(reg + 1) << 8); return data; } /*----------------------------------------------------------------------------*/
И, во-вторых, есть отдельная группа PHY регистров, не относящихся к банкам, которые мы обсудили, имеющих свое собственное адресное пространство. Их мы просто определяем адресами:
#define PHCON1 (0x00) #define PHSTAT1 (0x01) #define PHID1 (0x02) #define PHID2 (0x03) // ...
Особенность этих регистров в том, что с ними нельзя работать напрямую, а только через MIREGADR и MIRDL/MIRDH:
/*----------------------------------------------------------------------------*/ static uint16_t ReadPhyReg(uint8_t reg) { uint16_t data = 0; uint8_t regAddr = getRegAddr(reg); WriteControlReg(MIREGADR, regAddr); BitFieldSet(MICMD, MICMD_MIIRD_BIT); while((ReadControlReg(MISTAT) & MISTAT_BUSY_BIT) != 0); BitFieldClear(MICMD, MICMD_MIIRD_BIT); data = ReadControlRegPair(MIRDL); return data; } /*----------------------------------------------------------------------------*/ static void WritePhyReg(uint8_t reg, uint16_t regData) { uint8_t regAddr = getRegAddr(reg); WriteControlReg(MIREGADR, regAddr); WriteControlRegPair(MIWRL, regData); while((ReadControlReg(MISTAT) & MISTAT_BUSY_BIT) != 0); } /*----------------------------------------------------------------------------*/
Для чтения PHY регистра - заносим его адрес в MIREGADR и выставляем бит MICMD_MIIRD_BIT в регистре MICMD. Бит MISTAT_BUSY_BIT регистра MISTAT сигнализирует о выполнении операции, ожидаем его сброса. После этого очищаем MICMD_MIIRD_BIT и считываем значение PHY регистра из пары MIRDL/MIRDH. В общем, все четко по коду.
База для работы с ENC28J60 полностью готова, так что стартовую статью цикла мы сейчас завершим инициализацией модуля, которую будем использовать и в последующих проектах:
/*----------------------------------------------------------------------------*/ void ENC28J60_Init() { HAL_GPIO_WritePin(ENC28J60_RESET_PORT, ENC28J60_RESET_PIN, GPIO_PIN_RESET); HAL_Delay(50); HAL_GPIO_WritePin(ENC28J60_RESET_PORT, ENC28J60_RESET_PIN, GPIO_PIN_SET); HAL_Delay(50); SystemReset(); // Rx/Tx buffers WriteControlRegPair(ERXSTL, ENC28J60_RX_BUF_START); WriteControlRegPair(ERXNDL, ENC28J60_RX_BUF_END); WriteControlRegPair(ERDPTL, ENC28J60_RX_BUF_START); // MAC address WriteControlReg(MAADR1, macAddr[0]); WriteControlReg(MAADR2, macAddr[1]); WriteControlReg(MAADR3, macAddr[2]); WriteControlReg(MAADR4, macAddr[3]); WriteControlReg(MAADR5, macAddr[4]); WriteControlReg(MAADR6, macAddr[5]); WriteControlReg(MACON1, MACON1_TXPAUS_BIT | MACON1_RXPAUS_BIT | MACON1_MARXEN_BIT); WriteControlReg(MACON3, MACON3_PADCFG0_BIT | MACON3_TXCRCEN_BIT | MACON3_FRMLNEN_BIT); WriteControlRegPair(MAIPGL, ENC28J60_NBB_PACKET_GAP); WriteControlReg(MABBIPG, ENC28J60_BB_PACKET_GAP); WriteControlRegPair(MAMXFLL, ENC28J60_FRAME_DATA_MAX); // PHY resisters WritePhyReg(PHCON2, PHCON2_HDLDIS_BIT); ENC28J60_StartReceiving(); } /*----------------------------------------------------------------------------*/
И снова я не буду расписывать биты, копируя эту информацию из даташита, если потребуется, задавайте вопросы в комментариях, буду рад помочь. В следующей статье разберемся с приемным и передающим буферами, которые будут использоваться для приема и отправки Ethernet-фреймов.
/** ****************************************************************************** * @file : enc28j60.c * @brief : ENC28J60 driver * @author : MicroTechnics (microtechnics.ru) ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "enc28j60.h" /* Declarations and definitions ----------------------------------------------*/ uint8_t macAddr[MAC_ADDRESS_BYTES_NUM] = {0x00, 0x17, 0x22, 0xED, 0xA5, 0x01}; static uint8_t commandOpCodes[COMMANDS_NUM] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x07}; static ENC28J60_RegBank curBank = BANK_0; extern SPI_HandleTypeDef hspi1; /* Functions -----------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/ static void SetCS(ENC28J60_CS_State state) { HAL_GPIO_WritePin(ENC28J60_CS_PORT, ENC28J60_CS_PIN, (GPIO_PinState)state); } /*----------------------------------------------------------------------------*/ static void WriteBytes(uint8_t* data, uint16_t size) { HAL_StatusTypeDef res = HAL_SPI_Transmit(&hspi1, data, size, ENC28J60_SPI_TIMEOUT); } /*----------------------------------------------------------------------------*/ static void WriteByte(uint8_t data) { HAL_StatusTypeDef res = HAL_SPI_Transmit(&hspi1, &data, 1, ENC28J60_SPI_TIMEOUT); } /*----------------------------------------------------------------------------*/ static uint8_t ReadByte() { uint8_t txData = 0x00; uint8_t rxData = 0x00; HAL_StatusTypeDef res = HAL_SPI_TransmitReceive(&hspi1, &txData, &rxData, 1, ENC28J60_SPI_TIMEOUT); return rxData; } /*----------------------------------------------------------------------------*/ static ENC28J60_RegType getRegType(uint8_t reg) { ENC28J60_RegType type = (ENC28J60_RegType)((reg & ENC28J60_REG_TYPE_MASK) >> ENC28J60_REG_TYPE_OFFSET); return type; } /*----------------------------------------------------------------------------*/ static ENC28J60_RegBank getRegBank(uint8_t reg) { ENC28J60_RegBank bank = (ENC28J60_RegBank)((reg & ENC28J60_REG_BANK_MASK) >> ENC28J60_REG_BANK_OFFSET); return bank; } /*----------------------------------------------------------------------------*/ static uint8_t getRegAddr(uint8_t reg) { uint8_t addr = (reg & ENC28J60_REG_ADDR_MASK); return addr; } /*----------------------------------------------------------------------------*/ static void WriteCommand(ENC28J60_Command command, uint8_t argData) { uint8_t data = 0; data = (commandOpCodes[command] << ENC28J60_OP_CODE_OFFSET) | argData; WriteByte(data); } /*----------------------------------------------------------------------------*/ static void CheckBank(uint8_t reg) { uint8_t regAddr = getRegAddr(reg); if (regAddr < ENC28J60_COMMON_REGS_ADDR) { ENC28J60_RegBank regBank = getRegBank(reg); if (curBank != regBank) { uint8_t econ1Addr = getRegAddr(ECON1); // Clear bank bits SetCS(CS_LOW); WriteCommand(BIT_FIELD_CLEAR, econ1Addr); WriteByte(ECON1_BSEL1_BIT | ECON1_BSEL0_BIT); SetCS(CS_HIGH); // Set bank bits SetCS(CS_LOW); WriteCommand(BIT_FIELD_SET, econ1Addr); WriteByte(regBank); SetCS(CS_HIGH); curBank = regBank; } } } /*----------------------------------------------------------------------------*/ static void BitFieldSet(uint8_t reg, uint8_t regData) { uint8_t regAddr = getRegAddr(reg); CheckBank(reg); SetCS(CS_LOW); WriteCommand(BIT_FIELD_SET, regAddr); WriteByte(regData); SetCS(CS_HIGH); } /*----------------------------------------------------------------------------*/ static void BitFieldClear(uint8_t reg, uint8_t regData) { uint8_t regAddr = getRegAddr(reg); CheckBank(reg); SetCS(CS_LOW); WriteCommand(BIT_FIELD_CLEAR, regAddr); WriteByte(regData); SetCS(CS_HIGH); } /*----------------------------------------------------------------------------*/ static uint8_t ReadControlReg(uint8_t reg) { uint8_t data = 0; ENC28J60_RegType regType = getRegType(reg); uint8_t regAddr = getRegAddr(reg); CheckBank(reg); SetCS(CS_LOW); WriteCommand(READ_CONTROL_REG, regAddr); if (regType == MAC_MII_REG) { ReadByte(); } data = ReadByte(); SetCS(CS_HIGH); return data; } /*----------------------------------------------------------------------------*/ static void WriteControlReg(uint8_t reg, uint8_t regData) { uint8_t regAddr = getRegAddr(reg); CheckBank(reg); SetCS(CS_LOW); WriteCommand(WRITE_CONTROL_REG, regAddr); WriteByte(regData); SetCS(CS_HIGH); } /*----------------------------------------------------------------------------*/ static void WriteControlRegPair(uint8_t reg, uint16_t regData) { WriteControlReg(reg, (uint8_t)regData); WriteControlReg(reg + 1, (uint8_t)(regData >> 8)); } /*----------------------------------------------------------------------------*/ static uint16_t ReadControlRegPair(uint8_t reg) { uint16_t data = 0; data = (uint16_t)ReadControlReg(reg) | ((uint16_t)ReadControlReg(reg + 1) << 8); return data; } /*----------------------------------------------------------------------------*/ static void WriteBufferMem(uint8_t *data, uint16_t size) { SetCS(CS_LOW); WriteCommand(WRITE_BUFFER_MEM, ENC28J60_BUF_COMMAND_ARG); WriteBytes(data, size); SetCS(CS_HIGH); } /*----------------------------------------------------------------------------*/ static void ReadBufferMem(uint8_t *data, uint16_t size) { SetCS(CS_LOW); WriteCommand(READ_BUFFER_MEM, ENC28J60_BUF_COMMAND_ARG); for (uint16_t i = 0; i < size; i++) { *data = ReadByte(); data++; } SetCS(CS_HIGH); } /*----------------------------------------------------------------------------*/ static void SystemReset() { SetCS(CS_LOW); WriteCommand(SYSTEM_RESET, ENC28J60_RESET_COMMAND_ARG); SetCS(CS_HIGH); curBank = BANK_0; HAL_Delay(100); } /*----------------------------------------------------------------------------*/ static uint16_t ReadPhyReg(uint8_t reg) { uint16_t data = 0; uint8_t regAddr = getRegAddr(reg); WriteControlReg(MIREGADR, regAddr); BitFieldSet(MICMD, MICMD_MIIRD_BIT); while((ReadControlReg(MISTAT) & MISTAT_BUSY_BIT) != 0); BitFieldClear(MICMD, MICMD_MIIRD_BIT); data = ReadControlRegPair(MIRDL); return data; } /*----------------------------------------------------------------------------*/ static void WritePhyReg(uint8_t reg, uint16_t regData) { uint8_t regAddr = getRegAddr(reg); WriteControlReg(MIREGADR, regAddr); WriteControlRegPair(MIWRL, regData); while((ReadControlReg(MISTAT) & MISTAT_BUSY_BIT) != 0); } /*----------------------------------------------------------------------------*/ void ENC28J60_Init() { HAL_GPIO_WritePin(ENC28J60_RESET_PORT, ENC28J60_RESET_PIN, GPIO_PIN_RESET); HAL_Delay(50); HAL_GPIO_WritePin(ENC28J60_RESET_PORT, ENC28J60_RESET_PIN, GPIO_PIN_SET); HAL_Delay(50); SystemReset(); // Rx/Tx buffers WriteControlRegPair(ERXSTL, ENC28J60_RX_BUF_START); WriteControlRegPair(ERXNDL, ENC28J60_RX_BUF_END); WriteControlRegPair(ERDPTL, ENC28J60_RX_BUF_START); // MAC address WriteControlReg(MAADR1, macAddr[0]); WriteControlReg(MAADR2, macAddr[1]); WriteControlReg(MAADR3, macAddr[2]); WriteControlReg(MAADR4, macAddr[3]); WriteControlReg(MAADR5, macAddr[4]); WriteControlReg(MAADR6, macAddr[5]); WriteControlReg(MACON1, MACON1_TXPAUS_BIT | MACON1_RXPAUS_BIT | MACON1_MARXEN_BIT); WriteControlReg(MACON3, MACON3_PADCFG0_BIT | MACON3_TXCRCEN_BIT | MACON3_FRMLNEN_BIT); WriteControlRegPair(MAIPGL, ENC28J60_NBB_PACKET_GAP); WriteControlReg(MABBIPG, ENC28J60_BB_PACKET_GAP); WriteControlRegPair(MAMXFLL, ENC28J60_FRAME_DATA_MAX); // PHY resisters WritePhyReg(PHCON2, PHCON2_HDLDIS_BIT); ENC28J60_StartReceiving(); } /*----------------------------------------------------------------------------*/ void ENC28J60_StartReceiving() { BitFieldSet(ECON1, ECON1_RXEN_BIT); } /*----------------------------------------------------------------------------*/
/** ****************************************************************************** * @file : enc28j60.h * @brief : ENC28J60 driver interface * @author : MicroTechnics (microtechnics.ru) ****************************************************************************** */ #ifndef ENC28J60_H #define ENC28J60_H /* Includes ------------------------------------------------------------------*/ #include "stm32f1xx_hal.h" /* Declarations and definitions ----------------------------------------------*/ #define ENC28J60_CS_PORT GPIOB #define ENC28J60_CS_PIN GPIO_PIN_3 #define ENC28J60_RESET_PORT GPIOB #define ENC28J60_RESET_PIN GPIO_PIN_6 #define MAC_ADDRESS_BYTES_NUM 6 #define IP_ADDRESS_BYTES_NUM 4 #define ENC28J60_SPI_TIMEOUT 10 #define ENC28J60_OP_CODE_OFFSET 5 #define ENC28J60_REG_BANK_OFFSET 5 #define ENC28J60_REG_TYPE_OFFSET 7 #define ENC28J60_TX_BUF_START 0x0000 #define ENC28J60_RX_BUF_START 0x0600 #define ENC28J60_RX_BUF_END 0x1FFF #define ENC28J60_FRAME_RX_OK_MASK 0x80 #define ENC28J60_REG_BANK_MASK 0x60 #define ENC28J60_REG_TYPE_MASK 0x80 #define ENC28J60_REG_ADDR_MASK 0x1F #define ENC28J60_BUF_COMMAND_ARG 0x1A #define ENC28J60_RESET_COMMAND_ARG 0x1F #define ENC28J60_FRAME_DATA_MAX 1024 #define ENC28J60_BB_PACKET_GAP 0x15 #define ENC28J60_NBB_PACKET_GAP 0x0C12 #define ENC28J60_BANK_0_BITS (BANK_0 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_BANK_1_BITS (BANK_1 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_BANK_2_BITS (BANK_2 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_BANK_3_BITS (BANK_3 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_BANK_COMMON_BITS (BANK_0 << ENC28J60_REG_BANK_OFFSET) #define ENC28J60_COMMON_REGS_ADDR 0x1B #define ENC28J60_ETH_REG_BIT (ETH_REG << ENC28J60_REG_TYPE_OFFSET) #define ENC28J60_MAC_MII_REG_BIT (MAC_MII_REG << ENC28J60_REG_TYPE_OFFSET) // Common bank registers #define EIE (0x1B | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_COMMON_BITS) #define EIR (0x1C | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_COMMON_BITS) #define ESTAT (0x1D | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_COMMON_BITS) #define ECON2 (0x1E | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_COMMON_BITS) #define ECON1 (0x1F | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_COMMON_BITS) #define EIR_PKTIF_BIT (1 << 6) #define EIR_DMAIF_BIT (1 << 5) #define EIR_LINKIF_BIT (1 << 4) #define EIR_TXIF_BIT (1 << 3) #define EIR_TXERIF_BIT (1 << 1) #define EIR_RXERIF_BIT (1 << 0) #define ECON2_AUTOINC_BIT (1 << 7) #define ECON2_PKTDEC_BIT (1 << 6) #define ECON2_PWRSV_BIT (1 << 5) #define ECON2_VRPS_BIT (1 << 3) #define ECON1_TXRST_BIT (1 << 7) #define ECON1_RXRST_BIT (1 << 6) #define ECON1_DMAST_BIT (1 << 5) #define ECON1_CSUMEN_BIT (1 << 4) #define ECON1_TXRTS_BIT (1 << 3) #define ECON1_RXEN_BIT (1 << 2) #define ECON1_BSEL1_BIT (1 << 1) #define ECON1_BSEL0_BIT (1 << 0) // Bank 0 registers #define ERDPTL (0x00 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ERDPTH (0x01 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EWRPTL (0x02 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EWRPTH (0x03 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ETXSTL (0x04 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ETXSTH (0x05 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ETXNDL (0x06 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ETXNDH (0x07 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ERXSTL (0x08 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ERXSTH (0x09 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ERXNDL (0x0A | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ERXNDH (0x0B | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ERXRDPTL (0x0C | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ERXRDPTH (0x0D | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ERXWRPTL (0x0E | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define ERXWRPTH (0x0F | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EDMASTL (0x10 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EDMASTH (0x11 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EDMANDL (0x12 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EDMANDH (0x13 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EDMADSTL (0x14 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EDMADSTH (0x15 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EDMACSL (0x16 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) #define EDMACSH (0x17 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_0_BITS) // Bank 1 registers #define EHT0 (0x00 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EHT1 (0x01 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EHT2 (0x02 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EHT3 (0x03 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EHT4 (0x04 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EHT5 (0x05 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EHT6 (0x06 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EHT7 (0x07 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMM0 (0x08 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMM1 (0x09 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMM2 (0x0A | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMM3 (0x0B | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMM4 (0x0C | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMM5 (0x0D | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMM6 (0x0E | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMM7 (0x0F | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMCSL (0x10 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMCSH (0x11 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMOL (0x14 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define EPMOH (0x15 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define ERXFCON (0x18 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) #define ERXFCON_UCEN_BIT (1 << 7) #define ERXFCON_ANDOR_BIT (1 << 6) #define ERXFCON_CRCEN_BIT (1 << 5) #define ERXFCON_PMEN_BIT (1 << 4) #define ERXFCON_MPEN_BIT (1 << 3) #define ERXFCON_HTEN_BIT (1 << 2) #define ERXFCON_MCEN_BIT (1 << 1) #define ERXFCON_BCEN_BIT (1 << 0) #define EPKTCNT (0x19 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_1_BITS) // Bank 2 registers #define MACON1 (0x00 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MACON1_TXPAUS_BIT (1 << 3) #define MACON1_RXPAUS_BIT (1 << 2) #define MACON1_PASSALL_BIT (1 << 1) #define MACON1_MARXEN_BIT (1 << 0) #define MACON3 (0x02 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MACON3_PADCFG2_BIT (1 << 7) #define MACON3_PADCFG1_BIT (1 << 6) #define MACON3_PADCFG0_BIT (1 << 5) #define MACON3_TXCRCEN_BIT (1 << 4) #define MACON3_PHDRLEN_BIT (1 << 3) #define MACON3_HFRMEN_BIT (1 << 2) #define MACON3_FRMLNEN_BIT (1 << 1) #define MACON3_FULDPX_BIT (1 << 0) #define MACON4 (0x03 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MABBIPG (0x04 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MAIPGL (0x06 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MAIPGH (0x07 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MACLCON1 (0x08 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MACLCON2 (0x09 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MAMXFLL (0x0A | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MAMXFLH (0x0B | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MICMD (0x12 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MICMD_MIIRD_BIT (1 << 0) #define MIREGADR (0x14 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MIWRL (0x16 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MIWRH (0x17 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MIRDL (0x18 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) #define MIRDH (0x19 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_2_BITS) // Bank 3 registers #define MAADR5 (0x00 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_3_BITS) #define MAADR6 (0x01 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_3_BITS) #define MAADR3 (0x02 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_3_BITS) #define MAADR4 (0x03 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_3_BITS) #define MAADR1 (0x04 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_3_BITS) #define MAADR2 (0x05 | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_3_BITS) #define EBSTSD (0x06 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS) #define EBSTCON (0x07 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS) #define EBSTCSL (0x08 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS) #define EBSTCSH (0x09 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS) #define MISTAT (0x0A | ENC28J60_MAC_MII_REG_BIT | ENC28J60_BANK_3_BITS) #define MISTAT_BUSY_BIT (1 << 0) #define EREVID (0x12 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS) #define ECOCON (0x15 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS) #define EFLOCON (0x17 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS) #define EPAUSL (0x18 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS) #define EPAUSH (0x19 | ENC28J60_ETH_REG_BIT | ENC28J60_BANK_3_BITS) // PHY registers #define PHCON1 (0x00) #define PHSTAT1 (0x01) #define PHID1 (0x02) #define PHID2 (0x03) #define PHCON2 (0x10) #define PHCON2_FRCLNK_BIT (1 << 14) #define PHCON2_TXIS_BIT (1 << 13) #define PHCON2_JABBER_BIT (1 << 10) #define PHCON2_HDLDIS_BIT (1 << 8) #define PHSTAT2 (0x11) #define PHIE (0x12) #define PHIR (0x13) #define PHLCON (0x14) #define PHLCON_LACFG3_BIT (1 << 11) #define PHLCON_LACFG2_BIT (1 << 10) #define PHLCON_LACFG1_BIT (1 << 9) #define PHLCON_LACFG0_BIT (1 << 8) #define PHLCON_LBCFG3_BIT (1 << 7) #define PHLCON_LBCFG2_BIT (1 << 6) #define PHLCON_LBCFG1_BIT (1 << 5) #define PHLCON_LBCFG0_BIT (1 << 4) #define PHLCON_LFRQ1_BIT (1 << 3) #define PHLCON_LFRQ0_BIT (1 << 2) #define PHLCON_STRCH_BIT (1 << 1) #define MISTAT_BUSY_BIT (1 << 0) typedef enum { READ_CONTROL_REG, READ_BUFFER_MEM, WRITE_CONTROL_REG, WRITE_BUFFER_MEM, BIT_FIELD_SET, BIT_FIELD_CLEAR, SYSTEM_RESET, COMMANDS_NUM, } ENC28J60_Command; typedef enum { CS_LOW = 0, CS_HIGH = 1, } ENC28J60_CS_State; typedef enum { BANK_0, BANK_1, BANK_2, BANK_3, } ENC28J60_RegBank; typedef enum { ETH_REG, MAC_MII_REG, } ENC28J60_RegType; typedef struct ENC28J60_Frame { uint16_t nextPtr; uint16_t length; uint16_t status; uint8_t data[ENC28J60_FRAME_DATA_MAX]; uint32_t checkSum; } ENC28J60_Frame; /* Functions -----------------------------------------------------------------*/ extern void ENC28J60_Init(); extern void ENC28J60_StartReceiving(); #endif // #ifndef ENC28J60_H
Ссылка на проект: MT_ENC28J60_Part_1
А почему не w5100 или w5500?
Что-то даже и не помню... W5500 тоже где-то лежат вроде бы.
Можете помочь пожалуйста наладить соединение stm32 с ноутбуком при помощи ethernet? (не за бесплатно) Сейчас есть проект при котором аналоговый сигнал снимается с датчиков по 4 пинам и преобразовывается со скоростью 20 000 значений по каждому канала, все это пишется на sd карту, потом ксрта перетыкается в ноут, где строятся графики, нужно чтобы значения сразу сыпались в ноут
Плата китайская на базе stm32f407vet6, есть w5100
Добрый день! У меня традиционно со временем свободным плохо совсем... Попробуй в группе или на форуме клич кинуть.
А, вот есть ли STM32 со встроенным Gigabit Ethernet? Или на худой конец внешний модуль, но именно Gigabit?
Здравствуйте! Подскажите, пожалуйста, почему в функции ReadPhyReg читается младший байт MIRDL, когда нужно читать старший байт, а когда сразу два? В документации написано, что данные лежат в MIRDL и MIRDH регистрах.
Доброго дня! Там через ReadControlRegPair() читается пара регистров сразу.
Спасибо, разобрался
Всем привет, нашел маленькую ошибку в коде:
static void WriteBytes(uint8_t* data, uint8_t size){
HAL_StatusTypeDef res = HAL_SPI_Transmit(&hspi1, data, size, ENC28J60_SPI_TIMEOUT);
}
функция принимает размер пакета вида uint8_t size для последующей отправки в ENC через SPI, а это ограничивает размер пакета до 255 байт, исправил на uint16_t size, пакет отправился со всеми 1000 байт данными корректно.
Супер! Блин только проекты перезалить все придется )
Подскажите, с Ethernet встроенным существует готовая плата уровня F405 и выше?
А насколько выше? У меня лежит NUCLEO-H743ZI:
IPv6 работает на этой плате? На STM32F74x, STM32F76x силиконовые баги в части IPv6
Это же от стека зависит, а не от чипа
не факт! в зов-ти от того какие уровни реализованы в "сетевом чипе", если в enc28j60 все делается "руками", то W5500 почти все рутины реализованы в самом чипе
Тут под "чипом" имелся ввиду микроконтроллер (STM32xx).
итого, зовисит:
Именно, а про аппаратные баги непосредственно микроконтроллера я не слышал, поэтому и спрашивал.
что за баги упоминаются?
Здравствуйте, во всех схемах, питание подключают к 3,3В. Почему Вы подключили к 5 В?
Добрый день! Ошибся на картинке похоже... Спасибо, сейчас поправлю.