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 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

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

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

 

 

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

STM LAN. ENC28J60

 

 

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

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 не будет опубликован. Обязательные поля помечены *

*