STM Урок 71. LAN. ENC28J60. ARP. Часть 3



 

Урок 71

 

Часть 3

 

LAN. ENC28J60. ARP

 

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

 

Вернёмся в arp.c и подключим глобальную переменную нашего буфера, чтобы опять же сэкономить на памяти, а также добавим нулевой и широковещательный MAC-адреса

 

extern char str1[60];

extern uint8_t net_buf[ENC28J60_MAXFRAME];

uint8_t macbroadcast[6]=MAC_BROADCAST;

uint8_t macnull[6]=MAC_NULL;

 

Продолжим нашу функцию отправки ARP-запроса.

После того, как мы создали переменную, мы должны будем сначала определить, что в нашей ARP-таблице, которую мы создадим чуть позже нет пока записи соответствия исследуемому IP-адреса готового MAC-адреса, который мы уже когда-то определили и сохранили в таблицу. Но пока у нас таблицы нет, мы пропустим этот код, а добавим мы его несколько позднее, так как заполняться таблица будет несколько в другом случае, и, соответственно нам там пока брать нечего. Поэтому сформируем пакет для ARP-запроса и отправим его в сеть

 

  uint8_t i;

  enc28j60_frame_ptr *frame=(void*)net_buf;

  arp_msg_ptr *msg = (void*)frame->data;

  msg->net_tp = ARP_ETH;

  msg->proto_tp = ARP_IP;

  msg->macaddr_len = 6;

  msg->ipaddr_len = 4;

  msg->op = ARP_REQUEST;

  memcpy(msg->macaddr_src,macaddr,6);

  memcpy(msg->ipaddr_src,ipaddr,4);

  memcpy(msg->macaddr_dst,macnull,6);

  memcpy(msg->ipaddr_dst,ip_addr,4);

  memcpy(frame->addr_dest,macbroadcast,6);

  memcpy(frame->addr_src,macaddr,6);

  frame->type = ETH_ARP;

  enc28j60_packetSend((void*)frame,sizeof(arp_msg_ptr) + sizeof(enc28j60_frame_ptr));

  return 1;

 

В принципе, мы уже можем проверить отправку пакета с помощью программы Wireshark. Для этого соберём код, прошьём контроллер и повторим отправку строки с адресом из терминальной программы, перед этим не забыв запустить программу Wireshark и применить там фильтр arp так же, как мы делали на прошлом занятии. Вот что мы должны увидеть

 

image02

 

Мы видим, что запрос наш дошёл благополучно, а также компьютер послал ответ, который мы пока не обрабатывали. Посмотрим, что ответ отправлен именно нам

 

image03

 

Мы видим в качестве приёмника наши физический и локальный адреса. Отлично!

Теперь нам нужно будет обработать ответ ПК.

Для этого зайдём в файле arp.c и в функции arp_read мы несколько изменим вот этот код

 

if((msg->op==ARP_REQUEST)&&(!memcmp(msg->ipaddr_dst,ipaddr,4)))

{

  . . .

}

 

Мы отделим каждое условие друг от друга. Получится вот так

 

if(!memcmp(msg->ipaddr_dst,ipaddr,4))

{

  if(msg->op==ARP_REQUEST)

  {

    . . .

  }

}

 

 

После тела условия наличия ARP-запроса мы создадим ещё одно условие наличия ARP-ответа. Код будет несколько похожим, только переменную res мы выставим в 2

 

        res=1;

      }

      else if(msg->op==ARP_REPLY)

      {

        sprintf(str1,"rnreplyrnmac_src %02X:%02X:%02X:%02X:%02X:%02Xrn",

        msg->macaddr_src[0],msg->macaddr_src[1],msg->macaddr_src[2],

        msg->macaddr_src[3],msg->macaddr_src[4],msg->macaddr_src[5]);

        HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

        sprintf(str1,"ip_src %d.%d.%d.%drn",

        msg->ipaddr_src[0],msg->ipaddr_src[1],msg->ipaddr_src[2],msg->ipaddr_src[3]);

        HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

        sprintf(str1,"mac_dst %02X:%02X:%02X:%02X:%02X:%02Xrn",

        msg->macaddr_dst[0],msg->macaddr_dst[1],msg->macaddr_dst[2],

        msg->macaddr_dst[3],msg->macaddr_dst[4],msg->macaddr_dst[5]);

        HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

        sprintf(str1,"ip_dst %d.%d.%d.%drn",

        msg->ipaddr_dst[0],msg->ipaddr_dst[1],msg->ipaddr_dst[2],msg->ipaddr_dst[3]);

        HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

        res=2;

      }

    }

  }

}

return res;

 

Соберём код, прошьём контроллер, отправим ту же строку в терминальной программе, в результате чего мы должны наблюдать вот такую вот картину

 

image04

 

Отлично! Мы получили ответ с ПК, ну или с любого устройства, который умеет отвечать на ARP-запросы.

Вернёмся в файл net.c и несколько подправим функцию чтения пакетов Ethernet eth_read. Сначала добавим в неё локальную переменную

 

void eth_read(enc28j60_frame_ptr *frame, uint16_t len)

{

  uint8_t res=0;

 

Затем исправим код вот здесь

 

if(frame->type==ETH_ARP)

{

  . . .

  HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  res = arp_read(frame,len-sizeof(enc28j60_frame_ptr));

  if(res==1)

  {

    arp_send(frame);

  }

  else if(res==2)

  {

 

  }

}

 

Теперь нам потребуется функция заполнения arp-таблицы. Но сначала нам нужно создать данную таблицу.

В ARP-таблице хранятся IP-адреса уже известных устройств с соответствующими им MAC-адресами. Данные таблицы бывают разной размерностью. Учитывая наши запросы и наши вычислительные мощности, мы сделаем эту таблицу на 5 записей. В записи будет храниться IP-адрес и MAC-адрес устройства и время в секундах, прошедшее с момента включения нашего контроллера. Время нужно для того, чтобы не хранить слишком устаревшие записи, так как за это время IP-адрес может быть присвоен другому устройству. Таблицу мы будем хранить в обычной оперативной памяти. Нам нет нужды хранить её в энергонезависимой памяти из-за ненадобности. Также такая память менее быстрая и обращение в эту таблицу займёт уже другое время да и код получится труднее. Вы, конечно, вполне можете данный механизм реализовать, так как мы работать с такой памятью умеем.

Сначала создадим структуру для таблицы в файле arp.h

 

uint8_t arp_request(uint8_t *ip_addr);

//--------------------------------------------------

typedef struct arp_record{

  uint8_t ipaddr[4];

  uint8_t macaddr[6];

  uint32_t sec; //какое было количество секунд в переменной clock_cnt, когда была сделана запись

} arp_record_ptr;

//--------------------------------------------------

 

 

Затем создадим массив из 5 элементов в файле arp.c, а также ещё одну переменную для хранения текущего позиции заполнения в таблице

 

uint8_t macnull[6]=MAC_NULL;

arp_record_ptr arp_rec[5];

uint8_t current_arp_index=0;

//--------------------------------------------------

 

Теперь добавим функцию заполнения данной таблицы

 

//--------------------------------------------------

void arp_table_fill(enc28j60_frame_ptr *frame)

{

uint8_t i;

}

//--------------------------------------------------

 

Добавим на данную функцию прототип и вызовем её в net.c в функции eth_read в том месте, где у нас осталось незаполненным тело условия

 

else if(res==2)

{

  arp_table_fill(frame);

}

 

Вернёмся в arp.c и продолжим писать нашу функцию заполнения ARP-таблицы.

Добавим указатель на ARP-сообщение

 

uint8_t i;

arp_msg_ptr *msg = (void*)frame->data;

 

Начнём добавлять запись

 

arp_msg_ptr *msg = (void*)frame->data;

memcpy(arp_rec[current_arp_index].ipaddr,msg->ipaddr_src,4);

memcpy(arp_rec[current_arp_index].macaddr,msg->macaddr_src,6);

 

Теперь мы дошли до того момента, когда нам нужно будет в строку нашей таблицы ARP добавить количество секунд. прошедших с момента включения питания контроллера. Можно воспользоваться несколькими способами — включить RTC, включить таймер, воспользоваться переменной, которая считает циклы и т.д. Я решил воспользоваться таймером, заодно и повторим работу с таймером. Может быть впоследствии мы будем пользоваться временем из интернета, полученным с помощью NTP, но пока таймер, так как это уже другие уровни, а до них мы ещё не дошли.

Сохраним и закроем проект в Keil и зайдём в проект Cube MX. Включим там таймер 2

 

image05

 

Настроим его в «Configuration» следующим образом

 

image06

 

Включим прерывания

 

image07

 

Сгенерируем проект для Keil, откроем его и на всякий случай соберём.

Вернёмся в net.c и добавим глобальную переменную

 

uint8_t ipaddr[4]=IP_ADDR;

uint32_t clock_cnt=0;//счетчик секунд

 

Добавим функцию-обработчик прерывания по совпадению данного таймера в этом же файле

 

//-----------------------------------------------

void TIM_PeriodElapsedCallback(void)

{

  //считаем секунды и записываем их в clock_cnt

  clock_cnt++;

}

//-----------------------------------------------

 

Сделаем на данную функцию прототип и добавим ещё настоящий обработчик прерывания в файле main.c, в котором и вызовем наш обработчик

 

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  if(htim==&htim2)

  {

    TIM_PeriodElapsedCallback();

  }

}

/* USER CODE END 4 */

 

Также не забываем наш таймер запустить в функции main()

 

HAL_UART_Receive_IT(&huart1,(uint8_t*)str,1);

HAL_TIM_Base_Start_IT(&htim2);

/* USER CODE END 2 */

 

В следующей части урока мы закончим функцию отправки запроса 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. ARP

 

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

STM LAN. ENC28J60. ARP

Один комментарий на “STM Урок 71. LAN. ENC28J60. ARP. Часть 3
  1. Vi Thanh:

    How to config timer init int stm32f4. With clk APB1 Timer clk =80Mhz, the same with APB2=80Mhz. I config PSC = 4000; counter (Up) = 1999; but clock_cnt not update. Thanks

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

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

*