Урок 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 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Здравствуйте. У меня ругается на строчку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. Пока всё идет гладко. Автору — огромная благодарность!