STM32F407 и сеть Ethernet на ENC28J60 |
![]() |
Добавил(а) microsin | ||
Недавно попробовал сделать сетевое подключение к микроконтроллеру STM32 на основе недорогого модуля с чипом ENC28J60. Такие модули стоят в Интернет-магазинах от 3.5 до 6 долларов [1, 2]. Модуль подключил к плате Olimex STM32-P407 [6] через коннектор UEXT. Кабель, который соединяет коннектор модуля ENC28J60 и коннектор UEXT: UEXT STM32-P407 Модуль ENC28J60 Реально в программе для управления чипом ENC28J60 используются пока только сигналы RST, SO, SI, SCK и CS. Проекты, которые можно найти в интернете для STM32, в основном основаны на FreeRTOS или CooCox CoOS. Проект, который рассматривается здесь, был сделан для STM32F407 на основе простейшего проекта из IAR (демонстрация работы с GPIO, оттуда взят код инициализации ядра) и исходного кода uIP [3], без использования RTOS (обычный простой бесконечный цикл main). Подпрограммы для инициализации ENC28J60 были взяты из репозитория coocox.org [4], и доработаны, чтобы они работали на SPI3 платы Olimex STM32-P407. Полностью готовый проект для IAR 6.5 можно скачать по ссылке [5]. [Описание кода для работы с ENC28J60] В библиотеке uIP [3] есть несколько отличных примеров для реализации готовых сервисов. Достаточно в файле конфигурации uip-master\unix\uip-conf.h раскомментировать нужную строчку, и соответствующий сервис начнет работать (в этом примере раскомментирован демонстрационный сервис hello-world, который выдает строку диалога при подключении клиентом telnet к порту 1000): ... /* Здесь мы подключаем заголовочный файл для нужного сервиса (или сервисов), которые будут использоваться в приложении. */ /*#include "smtp.h"*/ #include "hello-world.h" /*#include "telnetd.h"*/ /*#include "webserver.h"*/ /*#include "dhcpc.h"*/ /*#include "resolv.h"*/ /*#include "webclient.h"*/ ... Для начала инициализации подсистемы сети нужно проинициализировать чип ENC28J60 и настроить его на нужный адрес, шлюз и маску подсети. В этом примере сетевой адаптер на ENC28J60 получает MAC адрес 00-12-34-56-78-00, IP адрес 192.168.0.57, IP адрес шлюза 192.168.0.1 и маску подсети 255.255.255.0: ... struct uip_eth_addr mac = { { 0x00, 0x12, 0x34, 0x56, 0x78, 0x00 } }; uip_ipaddr_t ipaddr; enc28j60_init(mac.addr); uip_init(); uip_arp_init(); hello_world_init(); uip_setethaddr(mac); uip_ipaddr(ipaddr, 192, 168, 0, 57); uip_sethostaddr(ipaddr); uip_ipaddr(ipaddr, 192, 168, 0, 1); uip_setdraddr(ipaddr); uip_ipaddr(ipaddr, 255, 255, 255, 0); uip_setnetmask(ipaddr); ... Далее, чтобы система отвечала на ping и обрабатывала нужные сервисы (в нашем примере это сервис hello-world), нужно в бесконечном цикле вызывать подпрограмму vTask_uIP (в проектах, которые используют FreeRTOS или другие операционные системы реального времени, эта подпрограмма оформлена в виде отдельной задачи). void main() { //Тут должен быть вставлен код инициализации. ... while(1) { vTask_uIP(); } } [Как это работает] Запустите интерпретатор cmd, и попробуйте ввести команды ping 192.168.0.57, arp -a, telnet 192.168.0.57 1000. Получите примерно следующее: C:\Documents and Settings\user>ping 192.168.0.57 Обмен пакетами с 192.168.0.57 по 32 байт: Ответ от 192.168.0.57: число байт=32 время< 1мс TTL=128 Ответ от 192.168.0.57: число байт=32 время< 1мс TTL=128 Ответ от 192.168.0.57: число байт=32 время< 1мс TTL=128 Ответ от 192.168.0.57: число байт=32 время< 1мс TTL=128 Статистика Ping для 192.168.0.57: Пакетов: отправлено = 4, получено = 4, потеряно = 0 (0% потерь), Приблизительное время приема-передачи в мс: Минимальное = 0мсек, Максимальное = 0 мсек, Среднее = 0 мсек C:\Documents and Settings\user>arp -a Интерфейс: 192.168.0.56 --- 0x3 Адрес IP Физический адрес Тип 192.168.0.1 1c-7e-e5-e5-99-60 динамический 192.168.0.14 c4-85-08-b6-7e-8e динамический 192.168.0.57 00-12-34-56-78-00 динамический C:\Documents and Settings\user>telnet 192.168.0.57 1000 Hello. What is your name? microsin Hello microsin C:\Documents and Settings\user> [Устранение возможных проблем] Q001. Почему код не работает, устройство не отвечает на ping, сервис hello-word не отображает диалог? Нужно обратить внимание на следующее: 1. Проверьте правильность подключения чипа ENC28J60, лучше всего это сделать с помощью осциллографа. На разъеме платы должно быть надежное питание +3.3V. Когда проект запущен и работает, на ENC28J60 должны присутствовать все сигналы RST (в момент первого запуска должен пройти одиночный импульс лог. 0), SI, SO, SCK, CS. Лично у меня была проблема в п. 3 - мне как раз пришлось подбирать задержки в функциях enc28j60_write_op, enc28j60_write_buffer. Также мне помог п. 4 - после того, как я увеличил скорость SPI3 до максимума, задержки оказались не нужны, они были заменены циклами проверки флага BSY SPI3 (см. описание регистров и флагов SPI в [7]). Вот эта доработка (цикл while(SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_BSY));): // Обычная запись команды через SPI void enc28j60_write_op(uint8_t cmd, uint8_t adr, uint8_t data) { enc28j60_select(); enc28j60_tx(cmd | (adr & ENC28J60_ADDR_MASK)); enc28j60_tx(data); while(SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_BSY)); enc28j60_release(); } // Запись буфера Rx/Tx (в момент EWRPT) void enc28j60_write_buffer(uint8_t *buf, uint16_t len) { enc28j60_select(); enc28j60_tx(ENC28J60_SPI_WBM); while(len--) enc28j60_tx(*(buf++)); while(SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_BSY));
enc28j60_release(); } Q002. Почему после того, как я поменял MAC-адрес, перекомпилировал и перезапустил проект, мое устройство ENC28J60 перестало отвечать на ping, и его больше не видно в сети? Дело в том, что операционная система, на которой Вы запускаете команду ping (например Windows), делает кэширование MAC-адресов. Т. е. она составляет таблицу соответствия MAC:IP, которая используется для ускорения доступа к хостам сети. Посмотреть текущую таблицу на операционной системе Windows можно командой arp -a: C:\Documents and Settings\user>arp -a Интерфейс: 192.168.0.56 --- 0x3 Адрес IP Физический адрес Тип 192.168.0.1 1c-7e-e5-e5-99-60 динамический 192.168.0.14 c4-85-08-b6-7e-8e динамический 192.168.0.57 00-12-34-56-78-00 динамический Записи в таблицу попадают с помощью выдачи специального запроса по протоколу ARP, и если IP-адрес уже попал в эту таблицу, то больше не выполняется процедура arp-запроса. Поэтому после того, как Вы поменяли MAC, Windows об этом ничего не узнает, и будет пытаться обращаться по IP к старому MAC-адресу. Для того, чтобы решить проблему, достаточно очистить таблицу MAC-адресов командой arp -d. После этого по протоколу ARP будет выдан новый запрос, и запись в таблицу MAC-адресов уже попадет с новым, правильным MAC-адресом, и сетевое соединение снова заработает, устройство на ENC28J60 снова начнет отвечать на ping. Q003. Почему пример сетевого приложения hello-world.c после установки сетевого соединения принимает максимум 8 символов имени, а если ввести больше, то начинаются глюки? Это потому, что в приложении hello-world зарегистрирован входной буфер для ввода символов размером в 10 байт, и если ввести больше 8 символов, то вместе с символами возврата каретки и перевода строки получится больше 10 символов. Входной буфер задается как поле inputbuffer[10] при определении структуры uip_tcp_appstate_t в файле hello-world.h: typedef struct hello_world_state { struct psock p; char inputbuffer[10]; char name[40]; } uip_tcp_appstate_t; Затем этот буфер инициализируется как входной при инициализации сокета в тот момент, когда создается соединение: /*--------------------------------------------------------------------------- * В файле hello-world.h есть определение макроса UIP_APPCALL для * hello_world_appcall, так что эта функция является функцией приложения uIP. * Эта функция будет вызвана при любом событии uIP (например, установлено * новое соединение, пришли новые данные, отправленные данные были * подтверждены, данные нужно передать повторно и т. д.). */ void hello_world_appcall(void) { /* * Структура uip_conn содержит поле, называемое "appstate", которое * хранит состояние соединения приложения. Для упрощения доступа мы здесь * создали указатель. */ struct hello_world_state *s = &(uip_conn->appstate); /* * Если новое соединение только что было установлено, то мы должны * инициализировать protosocket в структуре состояния приложения. */ if(uip_connected()) { PSOCK_INIT(&s->p, s->inputbuffer, sizeof(s->inputbuffer)); } /* * На последнем шаге мы запускаем protosocket-функцию, которая в действительности * поддерживает обмен данными. Мы передали указатель на структуру состояния * приложения для текущего соединения. */ handle_connection(s); } [Ссылки] 1. ENC28J60 Ethernet LAN / Network Module site:dx.com. • http://www.pudn.com/downloads118/sourcecode/embed/detail500078.html |