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

 

 

 

 

Урок 71

 

Часть 1

 

LAN. ENC28J60. ARP

 

 

Сегодня мы продолжим начатую в уроке 68 тему по программированию модуля LAN на микросхеме ENC28J60, который позволяет нам изучить программирование сетевых протоколов вручную, преследуя при этом цель — "Понять работу локальной сети и всех её уровней и протоколов изнутри — на низком уровне". Если мы достигнем этой цели, то в дальнейшем на будет гораздо проще ориентироваться в программировании локальных сетей с использованием готовых стеков, реализованных в тех или иных библиотеках.

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

Напрашивается при этом вполне логичный вопрос: "А зачем нам это, мы же это уже проделали на пршлом занятии?". А на вопрос есть ответ. Мы это делали в одном направлении. То есть мы обеспечивали выполнение этой задачи только в том случае, если внешнему устройству нужно было узнать физический адрес нашего устройства. То есть мы создали своего рода ARP-сервер. Но нужен нам также и ARP-клиент, с помощью которого уже наш модуль будет пытаться узнать сетевой адрес какого-то внешнего устройства. Причём клиент на практике возможно будет даже нужен чаще, так как без этого мы не сможем ничего запросить у других устройств, так как ни один физический адрес внешних устройств мы не знаем. То есть мы даже передать ничего не сможем, так как, учитывая вышенаписанное, нам и передать-то некуда. Так что вот такой ответ. Поэтому продолжаем.

Проект мы создадим из проекта ENC28J60 и назовём его ENC28J60_ARP.

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

Запустим проект в Cube MX. Ничего там не исправляя, сгенерируем проект для Keil, откроем его, настроим программатор на авторезет, также подключим файлы нашей библиотеки enc28j60.c и net.c.

Попробуем собрать и прошить проект. Если всё нормально работает, то продолжим свою работу.

Сначала перейдём в хедер-файл enc28j60.h и добавим там ещё один регистр в 3 банк

 

#define MISTAT (0x0A|0x60|0x80)

#define ECOCON (0x15|0x60)

 

Перейдём в файл enc28j60.c и добавим функцию задержки в микросекундах в самом верху

 

uint8_t macaddr[6]=MAC_ADDR;

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

__STATIC_INLINE void DelayMicro(__IO uint32_t micros)

{

micros *= (SystemCoreClock / 1000000) / 5;

/* Wait till done */

while (micros--) ;

}

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

 

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

Первым делом закомментируем код включения прерываний

 

enc28j60_SetBank(ECON1);

//enc28j60_writeOp(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE);

 

Далее в самом конце мы уменьшим вдвое частоту генератора и после этого немного подождём

 

  enc28j60_writeOp(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_RXEN);//разрешаем приём пакетов

  //Включим делитель частоты генератора 2, то есть частота будет 12,5 MHz

  enc28j60_writeRegByte(ECOCON,0x02);

  DelayMicro(15);

}

 

Согласно технической документации там вообще можно подождать меньше микросекунды, но нам спешить некуда и мы подождём на всякий случай побольше.

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

 

  enc28j60_writeOp(ENC28J60_BIT_FIELD_SET, ECON1,ECON1_TXRTS);

  //небольшая задержка, почему-то без неё не работает в загруженных сетях

  HAL_Delay(1);

}

 

Пока с поправками всё.

Давайте теперь как-то весь функционал ARP вынесем в отдельный модуль, для чего создадим файлы arp.c и arp.h и подключим их к проекту.

Первоначальное содержание данных файлов стандартное:

 

arp.c:

 

#include "arp.h"

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

 

arp.h:

 

#ifndef ARP_H_

#define ARP_H_

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

#include "stm32f1xx_hal.h"

#include <string.h>

#include <stdlib.h>

#include <stdint.h>

#include "enc28j60.h"

#include "net.h"

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

#endif /* ARP_H_ */

 

Теперь нам эту новую библиотеку нам надо как-то подключить. А почему как-то, потому что необычно, иначе будут ошибки.

Перейдём в заголовочный файл net.h и и подключим наш новый модуль в самом низу файла (это очень важно)

 

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

#include "arp.h"

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

#endif /* __NET_H */

 

Теперь из файла net.c в файл arp.c перенесём сначала весь код функции arp_read, конечно удалив затем этот код из файла-источника, также в заголовочном файле arp.h создадим на данную функцию прототип. Также в файл arp.c подключим некоторые глобальные переменные

 

#include "arp.h"

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

extern UART_HandleTypeDef huart1;

extern uint8_t ipaddr[4];

extern uint8_t macaddr[6];

extern char str1[60];

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

 

То же самое проделаем и с функцией arp_send и также добавляем на неё прототип. Но так как данная функция использует вызов функции eth_send, которая осталась в другом файле, то на неё мы также сделаем прототип в файле net.h причём внизу файла перед подключением файла arp.h

 

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

void eth_send(enc28j60_frame_ptr *frame, uint16_t len);

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

#include "arp.h"

 

Теперь будем обдумывать то, как мы будем посылать ARP-запросы, то есть за счёт чего будет контроллер вообще получать такую команду. Но так как у нас уже подключен модуль USART, то давайте и будем его использовать, не будем пока подключать никаких дисплеев и кнопок. То есть воспользуемся функцией приёма данных по USART и будем эти команды посылать из ПК с помощью терминальной программы. Когда-то мы это уже делали, но всё забылось. Вот давайте заодно и повторим, как это делается.

 

 

Поэтому перейдём теперь в файл net.h и создадим там некоторую структуру со свойствами

 

} icmp_pkt_ptr;

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

typedef struct USART_prop{

  uint8_t usart_buf[20];

  uint8_t usart_cnt;

  uint8_t is_ip;

} USART_prop_ptr;

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

 

Используя тип нашей структуры создадим глобальную переменную файле net.c

 

char str1[60]={0};

USART_prop_ptr usartprop;

 

Теперь проинициализируем заранее свойства в функции инициализации net_ini

 

void net_ini(void)

{

  usartprop.usart_buf[0]=0;

  usartprop.usart_cnt=0;

  usartprop.is_ip=0;

  HAL_UART_Transmit(&huart1,(uint8_t*)"123456rn",8,0x1000);

 

Также здесь — в файле net.c — внизу добавим функцию-обработчик прерываний по окончанию приема заданного количества байт в шину USART

 

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

void UART1_RxCpltCallback(void)

{

}

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

 

Сделаем на неё прототип в заголовочном файле, перейдём в файл main.c и добавим там уже уже официальную функцию-обработчик

 

/* USER CODE BEGIN 4 */

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

  if(huart==&huart1)

  {

    UART1_RxCpltCallback();

  }

}

/* USER CODE END 4 */

 

Также добавим в main.c глобальный строковый массив

 

/* Private variables ---------------------------------------------------------*/

char str[20];

/* USER CODE END PV */

 

В main() вызовем функцию приёма USART

 

net_ini();

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

/* USER CODE END 2 */

 

Вернемся в файл net.c и подключим там тот же глобальный строковый массив

 

char str1[60]={0};

extern char str[20];

USART_prop_ptr usartprop;

 

В функции UART1_RxCpltCallback запишем принятый байт в переменную

 

void UART1_RxCpltCallback(void)

{

  uint8_t b;

  b = str[0];

 

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

 

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

 

 

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

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

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

 

 

Отладочную плату можно приобрести здесь STM32F103C8T6

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

Переходник USB to TTL можно приобрести здесь USB to TTL ftdi ft232rl

 

 

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

 

STM LAN. ENC28J60. ARP

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

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

*