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



 

Урок 71

 

Часть 1

 

LAN. ENC28J60. ARP

 

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

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

Напрашивается при этом вполне логичный вопрос: «А зачем нам это, мы же это уже проделали на прjшлом занятии?». А на вопрос есть ответ. Мы это делали в одном направлении. То есть мы обеспечивали выполнение этой задачи только в том случае, если внешнему устройству нужно было узнать физический адрес нашего устройства. То есть мы создали своего рода 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 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

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

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

 

 

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

STM LAN. ENC28J60. ARP

 

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

STM LAN. ENC28J60. ARP

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

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

*