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

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

И в дополнение, мы здесь наблюдаем поле 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.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 байта данных, тогда размещение информации в буфере будет иметь следующий вид:

Стартовый адрес у нас задан в 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:

В даташите есть не только описание, но и графическая схема, расписывающая работу фильтрующего механизма, так что копировать не буду. В нашей инициализации мы значение этого регистра не меняли, оставив его дефолтным – 0xA1 (0b10100001). Но при необходимости под конкретную задачу можно подобрать конкретные оптимальные настройки, все через этот регистр.
Итак, адреса и фильтрацию мы настроили. Поскольку я не использую прерывания ENC28J60 для отслеживания изменений его состояния, то для начала приема достаточно лишь выставить бит RXEN все того же регистра ECON1. Что у нас осуществляется в функции:
/*----------------------------------------------------------------------------*/
void ENC28J60_StartReceiving()
{
BitFieldSet(ECON1, ECON1_RXEN_BIT);
}
/*----------------------------------------------------------------------------*/
После запуска все принятые кадры (миновавшие аппаратную фильтрацию) будут попадать в кольцевой буфер в соответствующей области памяти:

Помимо непосредственно байт данных 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




Подскажите, а какую скорость передачи вам удалось достичь?
Да я только на работоспособность проверял, у меня никаких вообще нет актуальных задач, где Ethernet задействован. Так что дальнейшее на откуп читателям ) Если будет желание протестить и в сообществе поделиться результатами - то вообще огонь, было бы интересно.