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 с адаптером можно здесь USBASP USBISP 3.3 с адаптером

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

 

 

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

 

AVR LAN. ENC28J60. ARP

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

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

*