AVR Урок 41. LAN. ENC28J60. ARP. Часть 3



 

Урок 41

 

Часть 3

 

LAN. ENC28J60. ARP

 

В предыдущей части нашего занятия мы обработали приём строки с IP-адресом из шины USART и произвели её преобразование в числовую величину.

 

Теперь перейдём в файл arp.с и начнём писать там функцию отправки ARP-запроса, так как для этого у нас теперь всё готово

 

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

uint8_t arp_request(uint32_t ip_addr)

{

  uint8_t i;

 

  return 1;

}

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

 

Добавим прототип этой функции и вызовем её в net.c в функции net_pool

 

USART_TX((uint8_t*)"rn",2);

arp_request(ip);

usartprop.is_ip = 0;

 

Также зайдём в файл net.h и добавим там макросы для нулевого и широковещательного MAC-адресов

 

#define IP_ADDR ip_join(192,168,1,193)

#define MAC_BROADCAST {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}

#define MAC_NULL {0x00,0x00,0x00,0x00,0x00,0x00}

 

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

 

extern char str1[60];

extern uint8_t net_buf[ENC28J60_MAXFRAME];

extern uint8_t macaddr[6];

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);

msg->ipaddr_src = IP_ADDR;

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

msg->ipaddr_dst = ip_addr;

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)&&(msg->ipaddr_dst==IP_ADDR))

{

  ...

}

 

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

 

if (msg->ipaddr_dst==IP_ADDR)

{

  if (msg->op==ARP_REQUEST)

  {

    ...

  }

}

 

 

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

 

          res=1;

        }

        else if (msg->op==ARP_REPLY)

        {

          sprintf(str1,"replyrnmac_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]);

          USART_TX((uint8_t*)str1,strlen(str1));

          sprintf(str1,"ip_src %ld.%ld.%ld.%ldrn",

            msg->ipaddr_src & 0x000000FF,(msg->ipaddr_src>>8) & 0x000000FF,

            (msg->ipaddr_src>>16) & 0x000000FF, msg->ipaddr_src>>24);

          USART_TX((uint8_t*)str1,strlen(str1));

          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]);

          USART_TX((uint8_t*)str1,strlen(str1));

          sprintf(str1,"ip_dst %ld.%ld.%ld.%ldrn",

            msg->ipaddr_dst & 0x000000FF,(msg->ipaddr_dst>>8) & 0x000000FF,

            (msg->ipaddr_dst>>16) & 0x000000FF, msg->ipaddr_dst>>24);

          USART_TX((uint8_t*)str1,strlen(str1));

          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)

  {

    USART_TX((uint8_t*)"; arp",5);

    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(uint32_t ip_addr);

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

typedef struct arp_record{

  uint32_t ipaddr;

  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;

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

 

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

 

 

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

 

 

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

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

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

 

 

Приобрести плату Atmega 328p Pro Mini можно здесь.

Программатор (продавец надёжный) USBASP USBISP 2.0

Ethernet LAN Сетевой Модуль можно купить здесь (модуль SD SPI в подарок) ENC28J60 Ethernet LAN Сетевой Модуль.

 

 

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

 

AVR LAN. ENC28J60. ARP

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

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

*