Урок 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
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий