Top.Mail.Ru

STM32 и Ethernet. Часть 2. ENC28J60. Прием и передача кадров.

Всех приветствую, продолжаем с STM32 и Ethernet, это получается будет часть 2 (часть 1 доступна по ссылке). И задача будет простая – надо добить драйвер ENC28J60. Большую часть мы сделали, но не охвачен остался самый важный процесс – отправка и прием кадров. Это и есть наш план на сегодня 👍

Формат кадра Ethernet мы уже разбирали, освежим в памяти список полей:

Формат кадра Ethernet
  • MAC-адрес получателя
  • MAC-адрес отправителя
  • EtherType
  • Данные
  • CRC

Кроме того, для стандарта IEEE 802.3 при передаче пакета в линию к нему добавляются поля Preamble (7 байт) и Start-of-Frame (1 байт):

Ethernet кадр ENC28J60

И в дополнение, мы здесь наблюдаем поле Padding, которое пристраивается к передаваемым данным. У него простое функциональное назначение. По стандарту длина кадра не должна быть менее 64 байт (с учетом 4-х байт контрольной суммы), которые складываются из:

  • 6 байт - MAC-адрес получателя
  • 6 байт - MAC-адрес отправителя
  • 2 байта - EtherType
  • Данные
  • 4 байта – CRC

Именно поэтому на нашей схеме отмечено, что минимальный размер данных равен 46-ти байтам (64 – (6 + 6 + 2 + 4)). И именно из-за этого требуется дополнительное поле Padding, которое представляет из себя набор нулевых байт. Если суммарная длина кадра для данного набора данных составляет менее 64 байт, то добавляется Padding с таким количеством байт, чтобы суммарно получить 64 байта, которые требует стандарт.

Контроллер ENC28J60 среди своих возможностей имеет в том числе и аппаратную поддержу для упомянутых частей пакета:

  • Preamble
  • Start-of-Frame
  • Padding
  • CRC

То есть при отправке ENC28J60 может добавить эти поля к кадру, а при приеме, соответственно, "изъять". Хотя Padding и контрольная сумма тем не менее будут записаны в приемный буфер, так что они доступны для анализа при необходимости.

И прием, и отправка кадров в ENC28J60 осуществляются при помощи кольцевых буферов, расположенных в RAM-памяти. Суммарно для этих целей присутствует в наличии 8 КБ памяти:

Архитектура ENC28J60

Распределение памяти между приемным и передающим буферами возложено на нас. В проекте из предыдущей статьи я сделал для примера следующим образом:

Приемный и передающий буферы

Соответствующим образом задано в дефайнах в файле enc28j60.h:

#define ENC28J60_TX_BUF_START                                   0x0000
#define ENC28J60_RX_BUF_START                                   0x0600
#define ENC28J60_RX_BUF_END                                     0x1FFF

Для управления этими процессами ENC28J60 предоставляет 4 пары регистров:

  • ETXSTL / ETXSTH – адрес начала буфера для передачи
  • ETXNDL / ETXNDH – адрес конца буфера для передачи
  • ERXSTL / ERXSTH – адрес начала буфера для приема
  • ERXNDL / ERXNDH – адрес конца буфера для приема

Последние два из них используются для задания области для Rx-буфера, а все остальное по умолчанию отводится под Tx-буфер. В регистры ETXST и ETXND при передаче будем записывать адреса начального и конечного байтов конкретного кадра Ethernet, который собираемся передавать. Наглядно увидим в процессе реализации.

Но при всем при этом ENC28J60 не проверяет адреса, заданные в ETXST и ETXND, на предмет перекрывания адресов Rx-буфера, об этом должен позаботиться управляющий контроллер.

Резюмируя вышесказанное, в функции инициализации задаем граничные значения для приемного буфера:

// Rx/Tx buffers
WriteControlRegPair(ERXSTL, ENC28J60_RX_BUF_START);
WriteControlRegPair(ERXNDL, ENC28J60_RX_BUF_END);

Передача кадра Ethernet.

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

  • Проверить, что предыдущий процесс передачи завершен при помощи бита TXRTS регистра ECON1.
  • Записать в регистр ETXST свободный адрес памяти, куда впоследствии мы поместим данные передаваемого фрейма. Этот регистр как и большинство других представляет из себя комплементарную пару регистров ETXSTL / ETXSTH, но я буду опускать в тексте этот момент и писать просто ETXST для стройности и краткости повествования.
  • При помощи команды Write Buffer Memory помещаем в буфер данные. В коде у нас для этого функция static void WriteBufferMem(uint8_t *data, uint16_t size).
  • В соответствии с размером передаваемых данных записываем в регистр ETXND конечный адрес.
  • Запускаем процесс установкой бита TXRTS регистра ECON1.

Переходим к более конкретному примеру. Допустим, необходимо передать 64 байта данных, тогда размещение информации в буфере будет иметь следующий вид:

Отправляемый Ethernet пакет

Стартовый адрес у нас задан в ENC28J60_TX_BUF_START (0x0000), его и используем. Далее записываем дополнительный контрольный байт. О нем расскажу подробнее:

Статусный байт

Как видите, здесь 4 значимых бита, которые включают, либо отключают тот или иной функционал. Как обычно подробное описание битов есть в даташите, не буду загружать статью банальным переводом. Только по сути и ничего лишнего, а именно обратим внимание на бит POVERRIDE. Если он установлен в "1", то при передаче ENC28J60 будет руководствоваться значениями, содержащимися в оставшихся трех битах контрольного байта (PHUGEEN, PPADEN, PCRCEN).

Если же в  POVERRIDE записать "0", то будут использоваться настройки, сохраненные в регистре MACON3. То есть аналогичные настройки наличествуют в двух местах, и бит POVERRIDE решает, какие именно использовать.

Мы пойдем по второму пути – в POVERRIDE запишем "0". Соответственно, остальные биты этого регистра при таком раскладе уже роли не играют, обнулим и их. А в регистре MACON3 у нас уже сохранены нужные настройки, которые мы задали при инициализации модуля в enc28j60.c:

WriteControlReg(MACON3, MACON3_PADCFG0_BIT | MACON3_TXCRCEN_BIT | MACON3_FRMLNEN_BIT);

В частности, к примеру, бит PADCFG0 позволяет ENC28J60 добавлять необходимые нулевые байты, если размер кадра слишком мал, а TXCRCEN – добавляет к передаваемому пакету контрольную сумму.

Это все, что касается контрольного байта. Резюме – он будет равен 0x00 и настройки будут браться из MACON3.

Далее помещаем в память отправляемые данные кадра Ethernet, включающие в себя и заголовок кадра, и 64 байта данных. И в завершение следует набор байтов статуса, их ENC28J60 также добавляет самостоятельно.

Финишируем данную часть созданием функции в файле enc28j60.c. По итогу нам осталось просто осуществить в коде то, что обсудили:

/*----------------------------------------------------------------------------*/
void ENC28J60_TransmitFrame(uint8_t *data, uint16_t size)
{
  while((ReadControlReg(ECON1) & ECON1_TXRTS_BIT) != 0)
  {
    if((ReadControlReg(EIR) & EIR_TXERIF_BIT) != 0)
    {
      BitFieldSet(ECON1, ECON1_TXRST_BIT);
      BitFieldClear(ECON1, ECON1_TXRST_BIT);
    }
  }

  WriteControlRegPair(EWRPTL, ENC28J60_TX_BUF_START);
  
  uint8_t controlByte = 0x00;
  WriteBufferMem(&controlByte, 1);
  WriteBufferMem(data, size);

  WriteControlRegPair(ETXSTL, ENC28J60_TX_BUF_START);
  WriteControlRegPair(ETXNDL, ENC28J60_TX_BUF_START + size);

  BitFieldSet(ECON1, ECON1_TXRTS_BIT);
}



/*----------------------------------------------------------------------------*/

Нюанс здесь только один. Бит TXRTS может не сбрасываться аппаратно при возникновении ошибки передачи, поэтому рекомендуется проверять бит TXERIF регистра EIR. Если бит в "1", это сигнализирует о возникшей ошибке при предыдущем процессе передачи. В этом случае осуществляем последовательно установку и сброс бита TXRST. После чего переходим к заполнению буфера данными.

Прием кадра Ethernet.

На очереди прием, который позволит нам завершить работу над драйвером для ENC28J60 и перейти к практической обработке реальных кадров. Приемный буфер мы уже проинициализировали:

WriteControlRegPair(ERXSTL, ENC28J60_RX_BUF_START);
WriteControlRegPair(ERXNDL, ENC28J60_RX_BUF_END);

ENC28J60 включает в спектр своих возможностей также и аппаратную фильтрацию принимаемых сообщений. Настройки данного функционала – в регистре ERXFCON:

Фильтрация Ethernet кадров

В даташите есть не только описание, но и графическая схема, расписывающая работу фильтрующего механизма, так что копировать не буду. В нашей инициализации мы значение этого регистра не меняли, оставив его дефолтным – 0xA1 (0b10100001). Но при необходимости под конкретную задачу можно подобрать конкретные оптимальные настройки, все через этот регистр.

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

/*----------------------------------------------------------------------------*/
void ENC28J60_StartReceiving()
{
  BitFieldSet(ECON1, ECON1_RXEN_BIT);
}



/*----------------------------------------------------------------------------*/

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

Принятый Ethernet пакет

Помимо непосредственно байт данных Data[] и заголовка будет записана дополнительная служебная информация:

  • Указатель на следующий пакет (2 байта)
  • Статусные биты (всего 4 байта)
  • Последние 4 байта содержат значение контрольной суммы
  • Адрес пакета в ENC28J60 всегда должен быть четный, поэтому для гарантии четности может быть добавлено смещение размером 1 байт (дополнительный байт после CRC)

Биты статуса отвечают за следующее:

Статусные биты

Получаем, что 2 младших байта статуса - это не что иное как длина принятого пакета. И, забегая вперед, добавим структуру в файл enc28j60.h. Здесь порядок полей в точности соответствует последовательности принятых данных:

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;

Для организации работы кольцевого буфера используются два указателя - ERXRDPT и ERXWRPT. Достигнув конца буфера ENC28J60 автоматически переходит в начало. ERXWRPT хранит адрес, куда приемник начнет сохранять следующий кадр при его поступлении. А ERXRDPT, напротив, указывает на место в памяти, откуда мы будем забирать принятые данные.

В регистр ERDPT будем заносить адрес, с которого хотим прочитать данные. Если бит AUTOINC в ECON2 установлен (а он установлен по умолчанию), то значение будет инкрементироваться автоматически при считывании информации.

В коде для чтения данных из буфера у нас уже есть функция void ReadBufferMem(uint8_t *data, uint16_t size). Теперь реализуем прием Ethernet фреймов в соответствии с теми аспектами, которые мы рассмотрели. Сначала полный код, затем пройдемся по нему подробно:

/*----------------------------------------------------------------------------*/
uint16_t ENC28J60_ReceiveFrame(ENC28J60_Frame* frame)
{  
  uint16_t dataSize = 0;
  uint8_t packetsNum = ReadControlReg(EPKTCNT);
  
  if (packetsNum > 0)
  {
    WriteControlRegPair(ERDPTL, curPtr);
    
    ReadBufferMem((uint8_t*)frame, ENC28J60_HEADER_SIZE);
    
    curPtr = frame->nextPtr;
    
    if ((frame->status & ENC28J60_FRAME_RX_OK_MASK) != 0)
    {
      dataSize = frame->length - ENC28J60_CRC_SIZE;
      
      if (dataSize > ENC28J60_FRAME_DATA_MAX)
      {
        dataSize = ENC28J60_FRAME_DATA_MAX;
      }
      
      ReadBufferMem((uint8_t*)&(frame->data[0]), dataSize);
      ReadBufferMem((uint8_t*)&(frame->checkSum), ENC28J60_CRC_SIZE);
    }
    
    uint16_t nextPtr = frame->nextPtr - 1;
    if (nextPtr > ENC28J60_RX_BUF_END)
    {
      nextPtr = ENC28J60_RX_BUF_END;
    }
    
    WriteControlRegPair(ERXRDPTL, nextPtr);
    BitFieldSet(ECON2, ECON2_PKTDEC_BIT);
  }
  
  return dataSize;
}



/*----------------------------------------------------------------------------*/

Начинаем с объявления переменной для хранения размера принятого пакета, который впоследствии функция вернет, и также получаем из регистра EPKTCNT количество принятых на данный момент пакетов:

uint16_t dataSize = 0;
uint8_t packetsNum = ReadControlReg(EPKTCNT);

Поскольку в ENC28J60 есть еще один известный баг, связанный с четностью/нечетностью адресов, то текущее значение указателя мы будем дополнительно хранить в специальной переменной:

static uint16_t curPtr = ENC28J60_RX_BUF_START;

Соответственно, изначально адрес соответствует началу приемного буфера. Записываем этот сохраненный адрес в регистр ERDPTL:

WriteControlRegPair(ERDPTL, curPtr);

На этом этапе все готово для считывания данных из буфера. Как мы выяснили чуть раньше, помимо информационных данных в буфере будет храниться служебная информация. Поэтому считывать будем в уже добавленную структуру типа ENC28J60_Frame, указатель на которую принимаем в качестве аргумента в функции приема ENC28J60_ReceiveFrame(ENC28J60_Frame* frame). Считываем 6 байт (ENC28J60_HEADER_SIZE):

ReadBufferMem((uint8_t*)frame, ENC28J60_HEADER_SIZE);

Они попадают соответственно в frame->nextPtr, frame->length и frame->status. Проверим по статусным битам, что с принятым пакетом все в порядке:

if ((frame->status & ENC28J60_FRAME_RX_OK_MASK) != 0)

Если условие выполнено, то продолжаем обработку. Получаем размер кадра и ограничиваем его в случае превышения заданного нами максимального размера ENC28J60_FRAME_DATA_MAX. Все, далее считываем долгожданные данные и 4 байта контрольной суммы (ENC28J60_CRC_SIZE):

ReadBufferMem((uint8_t*)&(frame->data[0]), dataSize);
ReadBufferMem((uint8_t*)&(frame->checkSum), ENC28J60_CRC_SIZE);

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

И тут вступает упомянутый нюанс-баг ENC28J60. При четном значении в ERXRDPT может случится повреждение данных, поэтому в этот регистр записываем значение, уменьшенное на 1 (оно будет нечетным, так как в frame->nextPtr гарантированно четное значение, поскольку в контроллере адрес начала пакета всегда четный):

uint16_t nextPtr = frame->nextPtr - 1;
if (nextPtr > ENC28J60_RX_BUF_END)
{
  nextPtr = ENC28J60_RX_BUF_END;
}

WriteControlRegPair(ERXRDPTL, nextPtr);

При этом проверяем, что указатель не вышел за пределы буфера. А в curPtr мы уже ранее (curPtr = frame->nextPtr) поместили корректный адрес следующего пакета, который будем использовать при дальнейшем приеме данных.

Завершающий шаг – дать знать ENC28J60, что мы получили пакет. Для этого записываем "1" в бит PKTDEC регистра ECON2:

BitFieldSet(ECON2, ECON2_PKTDEC_BIT);

В это сложно поверить, но на этом с приемом и передачей пакетов все закончено. В следующей статье пойдем дальше с 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;
static uint16_t curPtr = ENC28J60_RX_BUF_START;

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;
}



/*----------------------------------------------------------------------------*/
void ENC28J60_StartReceiving()
{
  BitFieldSet(ECON1, ECON1_RXEN_BIT);
}



/*----------------------------------------------------------------------------*/
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();
}



/*----------------------------------------------------------------------------*/
uint16_t ENC28J60_ReceiveFrame(ENC28J60_Frame* frame)
{  
  uint16_t dataSize = 0;
  uint8_t packetsNum = ReadControlReg(EPKTCNT);
  
  if (packetsNum > 0)
  {
    WriteControlRegPair(ERDPTL, curPtr);
    
    ReadBufferMem((uint8_t*)frame, ENC28J60_HEADER_SIZE);
    
    curPtr = frame->nextPtr;
    
    if ((frame->status & ENC28J60_FRAME_RX_OK_MASK) != 0)
    {
      dataSize = frame->length - ENC28J60_CRC_SIZE;
      
      if (dataSize > ENC28J60_FRAME_DATA_MAX)
      {
        dataSize = ENC28J60_FRAME_DATA_MAX;
      }
      
      ReadBufferMem((uint8_t*)&(frame->data[0]), dataSize);
      ReadBufferMem((uint8_t*)&(frame->checkSum), ENC28J60_CRC_SIZE);
    }
    
    uint16_t nextPtr = frame->nextPtr - 1;
    if (nextPtr > ENC28J60_RX_BUF_END)
    {
      nextPtr = ENC28J60_RX_BUF_END;
    }
    
    WriteControlRegPair(ERXRDPTL, nextPtr);
    BitFieldSet(ECON2, ECON2_PKTDEC_BIT);
  }
  
  return dataSize;
}



/*----------------------------------------------------------------------------*/
void ENC28J60_TransmitFrame(uint8_t *data, uint16_t size)
{
  while((ReadControlReg(ECON1) & ECON1_TXRTS_BIT) != 0)
  {
    if((ReadControlReg(EIR) & EIR_TXERIF_BIT) != 0)
    {
      BitFieldSet(ECON1, ECON1_TXRST_BIT);
      BitFieldClear(ECON1, ECON1_TXRST_BIT);
    }
  }

  WriteControlRegPair(EWRPTL, ENC28J60_TX_BUF_START);
  
  uint8_t controlByte = 0x00;
  WriteBufferMem(&controlByte, 1);
  WriteBufferMem(data, size);

  WriteControlRegPair(ETXSTL, ENC28J60_TX_BUF_START);
  WriteControlRegPair(ETXNDL, ENC28J60_TX_BUF_START + size);

  BitFieldSet(ECON1, ECON1_TXRTS_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)

#define ENC28J60_HEADER_SIZE                                    6
#define ENC28J60_CRC_SIZE                                       4 



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();



#endif // #ifndef ENC28J60_H

Ссылка на проект - MT_ENC28J60_Part_2

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

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

Подскажите, а какую скорость передачи вам удалось достичь?

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