Урок 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 так же, как мы делали на прошлом занятии. Вот что мы должны увидеть
Мы видим, что запрос наш дошёл благополучно, а также компьютер послал ответ, который мы пока не обрабатывали. Посмотрим, что ответ отправлен именно нам
Мы видим в качестве приёмника наши физический и локальный адреса. Отлично!
Теперь нам нужно будет обработать ответ ПК.
Для этого зайдём в файле 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;
Соберём код, прошьём контроллер, отправим ту же строку в терминальной программе, в результате чего мы должны наблюдать вот такую вот картину
Отлично! Мы получили ответ с ПК, ну или с любого устройства, который умеет отвечать на 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 Сетевой Модуль.
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий