Всех приветствую, продолжаем с 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 задействован. Так что дальнейшее на откуп читателям ) Если будет желание протестить и в сообществе поделиться результатами - то вообще огонь, было бы интересно.