STM Урок 68. LAN. ENC28J60. Часть 5



 

Урок 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 байтов, которую рассчитывать мы научимся чуть позже.

Вот так выглядит фрейм более наглядно

 

image27

 

Создадим структуру для фрейма в файле 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);

  }

}

 

 

Наконец-то мы соберём код и прошьём контроллер, а то, я думаю все уже соскучились и хотят увидеть хоть какой-то результат нашей работы.

Запустим терминальную программу и увидим определённые строки, которые покажут нам адрес источника, пославший широковещательный запрос, также широковещательный адрес, так как наш физический адрес ещё неизвестен ни одному сетевому устройству (кстати модуль у меня подключен в общую сеть, в котором находятся несколько различных устройств), а также видим мы длину фрейма и идентификатор протокола

 

image28

 

Нас на данный момент интересуют два типа сетевых протокола — 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);

  }

}

 

Соберём код и посмотрим

 

image29

 

Теперь мы уже наглядно видим, какой у нас протокол и кто прислал кадр.

Дальнейшая задача — прочитать структуру заголовка ARP, а это у нас уже следующий уровень — не канальный, а сетевой.

ARP (Address Resolution Protocol) или протокол разрешения адресов — это протокол, который служит для того, чтобы сетевое устройство могло определить чей-то физический адрес (MAC) по сетевому адресу (IP). Для этого устройство посылает широковещательный запрос (с адресом приёмника FF-FF-FF-FF-FF-FF, соостоящим из всех единиц), в котором спрашивает: «Какой физический адрес имеет устройство с таким-то IP-адресом?». Требуется это для того, чтобы устройства могли общаться по физическому уровню, который ничего не знает об IP-адресах. Поэтому устройство и отправляет такой запрос. Его получают все компьютеры (или какие-то другие устройства) в данной локальной сети, и устройство, узнавшее свой сетевой адрес, обязано отправить ответ.

Теперь разберём конкретно, как это делается. Структура заголовка ARP:

 

image30

 

В следующей части нашего урока мы мы напишем структуру для заголовка ARP, а также написав функцию исследования данного протокола, увидим некоторые части заголовка принятого ARP-запроса в терминальной программе.

 

 

Предыдущая часть Программирование МК STM32 Следующая часть

 

 

Техническая документация:

Документация на микросхему ENC28J60

Перечень ошибок ENC28J60 (Errata)

 

 

Отладочную плату можно приобрести здесь STM32F103C8T6

Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN

Переходник USB to TTL можно приобрести здесь USB to TTL ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM LAN. ENC28J60

 

6 комментариев на “STM Урок 68. LAN. ENC28J60. Часть 5
  1. Smnikolay:

    Здравствуйте. У меня ругается на строчкуif (frame->type==ETH_ARP). Говорит что будет всегда значение false. При чем компилятор не ругается. Но всегда работает по условию  else if (frame->type==ETH_IP и не ругается на него.

  2. Сергей:

    #define be16toword(a) ((((a)>>8)&0xff)|(((a)<<8)&0xff00))

    за такие макросы надо убивать медленно и мучительно)

    можно же прибегнуть к помощи ассемблерной команды REV16 R1, R2 — перестановка байтов каждого полуслова в 32-х битном слове. Если R2=0x0123 4567, то R1=0x2301 6745

    Так что макрос be16toword(a) можно с успехом заменить на __REV16(a)
    Пользуйтесь)

  3. Сергей Саныч:

    «медленно и мучительно убивать» надо за ассемблерные вставки в _учебном_ коде. Если, конечно, урок не посвящен этим самым вставкам.

  4. Сергей Саныч:

    Осваиваю работу с сетью на связке STM32F030F4P6. Пока всё идет гладко. Автору — огромная благодарность!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*