Урок 40
Часть 4
LAN. ENC28J60
В предыдущей части нашего урока мы написали ещё несколько служебных функций, настроили буферы микросхемы, а также её канальный уровень и написали функцию записи данных в регистры PHY.
Теперь настроим физический уровень в инициализации
enc28j60_writeRegByte(MAADR0, macaddr[5]);
//настраиваем физический уровень
enc28j60_writePhy(PHCON2, PHCON2_HDLDIS);//отключаем loopback
enc28j60_writePhy(PHLCON, PHLCON_LACFG2| // светодиоды
PHLCON_LBCFG2|PHLCON_LBCFG1|PHLCON_LBCFG0|
PHLCON_LFRQ0|PHLCON_STRCH);
Ну здесь практически всё ясно из комментариев. На одни светодиоды у нас существует целых 16 бит (отдельный регистр)
Со светодиодами всё в принципе ясно.
Ну и окончательные настройки в инициализации
PHLCON_LFRQ0|PHLCON_STRCH);
enc28j60_SetBank(ECON1);
enc28j60_writeOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
enc28j60_writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);//разрешаем прием пакетов
}
В регистре EIE установим биты, разрешающие глобальные прерывания и приём пакетов по ожиданию прерывания.
Также разрешим вообще приём пакетов.
Вот и вся инициализация. Наконец то!
Теперь нам нужно научиться принимать пакеты, так как весь обмен по локальной сети делится на пакеты.
Прием пакетов в микросхеме происходит через кольцевой буфер.
Давайте в даташите посмотрим пример приёма пакета
В буфер ENC28J60 записывает пакет следующим образом. Сначала идут 2 байта со значением указателя на следующий пакет, затем 4 байта со статусом приёма, затем собственно данные пакета с контрольной суммой.
Создадим функцию для приёма пакета в файле enc28j60.c и создадим в ней локальную переменную для подсчёта длины пакета и последующего его возврата
//—————————————-
uint16_t enc28j60_packetReceive(uint8_t *buf, uint16_t buflen)
{
uint16_t len = 0;
}
//—————————————-
В регистре EPKTCNT хранится количество принятых на данный момент пакетов, поэтому проверим его
uint16_t len = 0;
if (enc28j60_readRegByte(EPKTCNT) > 0)
{
}
Добавим глобальную переменную в файле enc28j60.c
static uint8_t Enc28j60Bank;
static int gNextPacketPtr;
В регистр ERDPT установим указатель
if (enc28j60_readRegByte(EPKTCNT) > 0)
{
enc28j60_writeReg(ERDPT, gNextPacketPtr);
Начнём считывать пакет. Объявим локльную структуру для заголовка
enc28j60_writeReg(ERDPT, gNextPacketPtr);
//считаем заголовок
struct {
uint16_t nextPacket;
uint16_t byteCount;
uint16_t status;
} header;
Теперь собственно считаем заголовок пакета, содержащий укзатель на следующий пакет, количество байтов и статус пакета
} header;
enc28j60_readBuf(sizeof header, (uint8_t*) &header);
Запишем значение указателя на следующий пакет в глобальную переменную
enc28j60_readBuf(sizeof header, (uint8_t*) &header);
gNextPacketPtr = header.nextPacket;
Инициализируем переменную длины пакеты, отрезав от неё контрольную сумму
gNextPacketPtr = header.nextPacket;
len = header.byteCount — 4; //remove the CRC count
Укоротим длину до заданной во входном параметре
len = header.byteCount — 4; //remove the CRC count
if(len > buflen) len = buflen;
Проверим статус и считаем буфер
if(len > buflen) len = buflen;
if ((header.status & 0x80)==0) len = 0;
else enc28j60_readBuf(len, buf);
Завершим буфер нулём
else enc28j60_readBuf(len, buf);
buf[len] = 0;
Инициализируем указатель буфера на адрес следующего пакета
buf[len] = 0;
if (gNextPacketPtr — 1 > RXSTOP_INIT)
enc28j60_writeReg(ERXRDPT, RXSTOP_INIT);
else
enc28j60_writeReg(ERXRDPT, gNextPacketPtr — 1);
Счётчик принятых пакетов также уменьшим на 1 и выйдем из условия
enc28j60_writeReg(ERXRDPT, gNextPacketPtr — 1);
enc28j60_writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
}
Возвратим длину принятого пакета
}
return len;
}
В файле net.c создадим функцию постоянного опроса сети
//———————————————————
void net_poll(void)
{
}
//———————————————————
Ну и, раз у нас опрос постоянный, то и вызовем данную функцию в бесконечном цикле функции main(), создав предварительно прототип в net.h
while (1)
{
net_poll();
}
Мы знаем, что битовый поток, поступающий на микросхему на её физический уровень, впоследствии в канальном уровне делится на кадры, по английски — фреймы.
Фреймы из себя представляют вполне себе уже осознанный поток, имеющий определённый протокол.
В настоящее время в большинстве случаев используется пртокол канального уровня Ethernet Version 2. Он состоит из физического (MAC) адреса приёмного устройства, физического адреса устройства-отправителя, 2 байта либо длины поля данных кадра, либо идентификатор протокла (ARP,IP и т.д.), собственно данные, которые будут также на других уровнях иметь свои протоколы, и контрольную сумму в размере 4 байтов, которую рассчитывать мы научимся чуть позже.
Вот так выглядит фрейм более наглядно
Создадим структуру для фрейма в файле net.h
void net_poll(void);
//——————————————
typedef struct enc28j60_frame {
uint8_t addr_dest[6];
uint8_t addr_src[6];
uint16_t type;
uint8_t data[];
} enc28j60_frame_ptr;
В файле net.c добавим глобальную переменную
#include «net.h»
//———————————————
uint8_t net_buf[ENC28J60_MAXFRAME];
Создадим две переменных в нашей функции net_poll
void net_poll(void)
{
uint16_t len;
enc28j60_frame_ptr *frame = (void*)net_buf;
В следующей части нашего урока мы напишем функцию приёма кадров канального уровня и проверим её на практике в терминальной программе.
Предыдущая часть Программирование МК AVR Следующая часть
Техническая документация:
Документация на микросхему ENC28J60
Перечень ошибок ENC28J60 (Errata)
Приобрести плату Atmega 328p Pro Mini можно здесь.
Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добрый день. Не могли бы Вы прокомментировать строку
«enc28j60_frame_ptr *frame = (void*)net_buf;»
в функции void net_poll(void).
Я понимаю, что здесь Вы «совмещаете адреса» буфера net_buf и структуры frame, т.о. структура и буфер — одно и тоже. Но я мегу понять, почему компилятор нормально относится к тому, что Вы указателю типа enc28j60_frame_ptr, присваиваете указатель типа void.
Вот если сделать так:
int *a = (void*)b;
то gcc выдаст предупреждение, а g++ вообще ошибку.
Но в случае со структурой, и тот и другой компилятор вполне себе всё нормально принимают!