Top.Mail.Ru

STM32 и Ethernet. Часть 1. Подключение и настройка ENC28J60.

Всем доброго времени суток! Начинаем долгожданный цикл статей по практической работе с Ethernet. Пройдемся с самого низкого уровня по всему стеку протоколов - ARP, IP, UDP, TCP... В качестве целевой платформы возьмем традиционно STM32 и в качестве микросхемы физического уровня Ethernet контроллер ENC28J60, как один из наиболее распространенных и доступных.

В этой, первой, статье рассмотрим физическое подключение, инициализацию интерфейсов, настройку ENC28J60 и, пожалуй, напишем сразу драйвер для работы с вышеупомянутым контроллером. Все буду описывать по возможности подробнее, но если вдруг возникнут вопросы, добро пожаловать в комментарии, а еще лучше к нам на форум.

Подключение ENC28J60.

Итак, STM32... Я возьму STM32F103C8T6, опять же в силу его невероятной распространенности и популярности, чему мы можем быть благодарны плате Blue Pill:

Отладочная плата Blue Pill.

Для ENC28J60 у меня среди прочих есть такой модуль, пусть будет он:

Модуль ENC28J60.

Мы будем писать код исходя из соображений возможности его использования и с любым другим железом, так что выбор в данном случае не критичен.

Вспоминаем сетевую модель OSI (здесь в ближайшее время я помещу ссылку на статью об OSI):

Сетевая модель OSI.

Самый низкий уровень, физический, в данном случае это 10BASE-T полностью берет на себя ENC28J60. Переходим на уровень выше - канальный уровень, который отвечает за передачу фреймов между узлами сети, основан на использовании MAC-адресов. Его также отчасти возьмет на себя ENC28J60, благо возможности позволяют. А вот работу на уровнях выше мы будем реализовывать программно - к примеру, на сетевом уровне, будет протокол IP, на транспортном - UDP/TCP, но не будем забегать вперед. Возвращаемся к подключению модулей.

И подключать контроллер мы будем по интерфейсу SPI. Собственно, схематично это представляет из себя следующее:

В разных проектах бывает по-разному, но в данном случае я не вижу ни малейших причин не использовать STM32CubeMx и HAL, так что создаем новый проект и сразу включаем "базово-необходимые" вещи, такие как внешнее тактирование и SWD интерфейс. Кроме того, пара выводов для работы с ENC28J60, в данном случае, это сигналы Chip Select, Reset и, конечно, SPI1:

Настройка периферии в STM32CubeMx.

У меня внешний кварцевый резонатор на 8 МГц, поэтому в окне тактирования:

Тактирование в STM32CubeMx.

Ну и напоследок, конечно же, настраиваем SPI. Как изображено на схеме выше, используем ножки PA5 - SPI SCK, PA6 - SPI MISO и PA7 - SPI MOSI. Настройки для ENC28J60 такие:

Настройка SPI.

После этого можно переходить к генерации проекта, больше в 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

Подписаться
Уведомить о
guest

21 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Павел
Павел
3 лет назад

А почему не w5100 или w5500?

Андрей
Андрей
Ответ на комментарий  Aveal
2 лет назад

Можете помочь пожалуйста наладить соединение stm32 с ноутбуком при помощи ethernet? (не за бесплатно) Сейчас есть проект при котором аналоговый сигнал снимается с датчиков по 4 пинам и преобразовывается со скоростью 20 000 значений по каждому канала, все это пишется на sd карту, потом ксрта перетыкается в ноут, где строятся графики, нужно чтобы значения сразу сыпались в ноут
Плата китайская на базе stm32f407vet6, есть w5100

Heinrich
Heinrich
2 лет назад

А, вот есть ли STM32 со встроенным Gigabit Ethernet? Или на худой конец внешний модуль, но именно Gigabit?

Родион
2 лет назад

Здравствуйте! Подскажите, пожалуйста, почему в функции ReadPhyReg читается младший байт MIRDL, когда нужно читать старший байт, а когда сразу два? В документации написано, что данные лежат в MIRDL и MIRDH регистрах.

Родион
Ответ на комментарий  Aveal
2 лет назад

Спасибо, разобрался

Тимур
1 год назад

Всем привет, нашел маленькую ошибку в коде:

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 байт данными корректно.

Ivan
Ivan
1 год назад

Подскажите, с Ethernet встроенным существует готовая плата уровня F405 и выше?

Pavel
Pavel
Ответ на комментарий  Aveal
1 год назад

IPv6 работает на этой плате? На STM32F74x, STM32F76x силиконовые баги в части IPv6

usr
usr
Ответ на комментарий  Aveal
6 дней назад

не факт! в зов-ти от того какие уровни реализованы в "сетевом чипе", если в enc28j60 все делается "руками", то W5500 почти все рутины реализованы в самом чипе

usr
usr
Ответ на комментарий  Aveal
5 дней назад

итого, зовисит:

  • от железной реализации сетевого чипа
  • от программной реализации сетевого стека
usr
usr
Ответ на комментарий  Aveal
5 дней назад

что за баги упоминаются?

силиконовые баги в части IPv6

Александр
Александр
3 месяцев назад

Здравствуйте, во всех схемах, питание подключают к 3,3В. Почему Вы подключили к 5 В?

21
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x