Урок 68
Часть 5
LAN. ENC28J60
В предыдущей части нашего урока мы закончили писать функцию инициализации микросхемы, а также написали функцию приёма сетевых пакетов.
В файле net.c создадим функцию постоянного опроса сети
//--------------------------------------------------
void net_pool(void)
{
}
//--------------------------------------------------
Ну и, раз у нас опрос постоянный, то и вызовем данную функцию в бесконечном цикле функции main(), создав предварительно прототип в net.h
while (1)
{
net_pool();
/* USER CODE END WHILE */
Мы знаем, что битовый поток, поступающий на микросхему на её физический уровень, впоследствии в канальном уровне делится на кадры, по английски — фреймы.
Фреймы из себя представляют вполне себе уже осознанный поток, имеющий определённый протокол.
В настоящее время в большинстве случаев используется пртокол канального уровня Ethernet Version 2. Он состоит из физического (MAC) адреса приёмного устройства, физического адреса устройства-отправителя, 2 байта либо длины поля данных кадра, либо идентификатор протокла (ARP,IP и т.д.), собственно данные, которые будут также на других уровнях иметь свои протоколы, и контрольную сумму в размере 4 байтов, которую рассчитывать мы научимся чуть позже.
Вот так выглядит фрейм более наглядно
Создадим структуру для фрейма в файле net.h
#include "enc28j60.h"
//--------------------------------------------------
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_pool(void)
{
uint16_t len;
enc28j60_frame_ptr *frame=(void*)net_buf;
Создадим также функцию чтения фрейма чуть выше функции net_poll
//--------------------------------------------------
void eth_read(enc28j60_frame_ptr *frame, uint16_t len)
{
}
//--------------------------------------------------
Тип протокола в кадре передаётся в перевёрнутом виде, не так как обычно, в формате big endian. Поэтому напишем определённый дефайн для преобразогвания в файле net.h
} enc28j60_frame_ptr;
//--------------------------------------------------
#define be16toword(a) ((((a)>>8)&0xff)|(((a)<<8)&0xff00))
В файле net.c добавим глобальный строковый массив
uint8_t net_buf[ENC28J60_MAXFRAME];
char str1[60]={0};
Подключим шину USART в net.c
#include "net.h"
//--------------------------------------------------
extern UART_HandleTypeDef huart1;
Добавим код в функцию eth_read, который выведет в терминальную программу адрес источника, отправившего кадр, адрес приёмника и тип протокола (пока в цифровом виде)
void eth_read(enc28j60_frame_ptr *frame, uint16_t len)
{
if (len>=sizeof(enc28j60_frame_ptr))
{
sprintf(str1,"%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; %04Xrn",
frame->addr_src[0],frame->addr_src[1],frame->addr_src[2],frame->addr_src[3],frame->addr_src[4],frame->addr_src[5],
frame->addr_dest[0],frame->addr_dest[1],frame->addr_dest[2],frame->addr_dest[3],frame->addr_dest[4],frame->addr_dest[5],len,be16toword(frame->type));
HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);
}
}
В файле enc28j60.h добавим прототип функции enc28j60_packetReceive
void enc28j60_ini(void);
uint16_t enc28j60_packetReceive(uint8_t *buf,uint16_t buflen);
В файле net.c в функции обработки приёма и передачи из микросхемы net_poll добавим код для приёма информации из буфера чтения
enc28j60_frame_ptr *frame=(void*)net_buf;
while ((len=enc28j60_packetReceive(net_buf,sizeof(net_buf)))>0)
{
eth_read(frame,len);
}
}
Наконец-то мы соберём код и прошьём контроллер, а то, я думаю все уже соскучились и хотят увидеть хоть какой-то результат нашей работы.
Запустим терминальную программу и увидим определённые строки, которые покажут нам адрес источника, пославший широковещательный запрос, также широковещательный адрес, так как наш физический адрес ещё неизвестен ни одному сетевому устройству (кстати модуль у меня подключен в общую сеть, в котором находятся несколько различных устройств), а также видим мы длину фрейма и идентификатор протокола
Нас на данный момент интересуют два типа сетевых протокола — IP (0x0800) и ARP (0x0806), причём пока поконкретней мы разберёмся именно с ARP.
Поэтому добавим макросы в заголовочный файл net.h
#define be16toword(a) ((((a)>>8)&0xff)|(((a)<<8)&0xff00))
//--------------------------------------------------
#define ETH_ARP be16toword(0x0806)
#define ETH_IP be16toword(0x0800)
Немного исправим код в функции eth_read, чтобы тип в терминальной программе отображался не в цифровом, а в строчном виде
if (len>=sizeof(enc28j60_frame_ptr))
{
if(frame->type==ETH_ARP)
{
sprintf(str1,"%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; arprn",
frame->addr_src[0],frame->addr_src[1],frame->addr_src[2],frame->addr_src[3],frame->addr_src[4],frame->addr_src[5],
frame->addr_dest[0],frame->addr_dest[1],frame->addr_dest[2],frame->addr_dest[3],frame->addr_dest[4],frame->addr_dest[5],
len);
HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);
}
else if(frame->type==ETH_IP)
{
sprintf(str1,"%02X:%02X:%02X:%02X:%02X:%02X-%02X:%02X:%02X:%02X:%02X:%02X; %d; iprn",
frame->addr_src[0],frame->addr_src[1],frame->addr_src[2],frame->addr_src[3],frame->addr_src[4],frame->addr_src[5],
frame->addr_dest[0],frame->addr_dest[1],frame->addr_dest[2],frame->addr_dest[3],frame->addr_dest[4],frame->addr_dest[5],
len);
HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);
}
}
Соберём код и посмотрим
Теперь мы уже наглядно видим, какой у нас протокол и кто прислал кадр.
Дальнейшая задача — прочитать структуру заголовка ARP, а это у нас уже следующий уровень — не канальный, а сетевой.
ARP (Address Resolution Protocol) или протокол разрешения адресов — это протокол, который служит для того, чтобы сетевое устройство могло определить чей-то физический адрес (MAC) по сетевому адресу (IP). Для этого устройство посылает широковещательный запрос (с адресом приёмника FF-FF-FF-FF-FF-FF, соостоящим из всех единиц), в котором спрашивает: «Какой физический адрес имеет устройство с таким-то IP-адресом?». Требуется это для того, чтобы устройства могли общаться по физическому уровню, который ничего не знает об IP-адресах. Поэтому устройство и отправляет такой запрос. Его получают все компьютеры (или какие-то другие устройства) в данной локальной сети, и устройство, узнавшее свой сетевой адрес, обязано отправить ответ.
Теперь разберём конкретно, как это делается. Структура заголовка ARP:
В следующей части нашего урока мы мы напишем структуру для заголовка ARP, а также написав функцию исследования данного протокола, увидим некоторые части заголовка принятого ARP-запроса в терминальной программе.
Предыдущая часть Программирование МК STM32 Следующая часть
Техническая документация:
Документация на микросхему ENC28J60
Перечень ошибок ENC28J60 (Errata)
Отладочную плату можно приобрести здесь STM32F103C8T6
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN
Переходник USB to TTL можно приобрести здесь USB to TTL ftdi ft232rl
Смотреть ВИДЕОУРОК (нажмите на картинку)
Здравствуйте. У меня ругается на строчкуif (frame->type==ETH_ARP). Говорит что будет всегда значение false. При чем компилятор не ругается. Но всегда работает по условию else if (frame->type==ETH_IP и не ругается на него.
В моём проекте ругается после сборки кубом или Вы пишете свой проект?
Да я пишу свой проект на stm32f407zet. Keil подчеркивает сравнение…
#define be16toword(a) ((((a)>>8)&0xff)|(((a)<<8)&0xff00))
за такие макросы надо убивать медленно и мучительно)
можно же прибегнуть к помощи ассемблерной команды REV16 R1, R2 — перестановка байтов каждого полуслова в 32-х битном слове. Если R2=0x0123 4567, то R1=0x2301 6745
Так что макрос be16toword(a) можно с успехом заменить на __REV16(a)
Пользуйтесь)
«медленно и мучительно убивать» надо за ассемблерные вставки в _учебном_ коде. Если, конечно, урок не посвящен этим самым вставкам.
Осваиваю работу с сетью на связке STM32F030F4P6. Пока всё идет гладко. Автору — огромная благодарность!