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

 

 

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

 

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

*