Недавно попробовал сделать сетевое подключение к микроконтроллеру STM32 на основе недорогого модуля с чипом ENC28J60. Такие модули стоят в Интернет-магазинах от 3.5 до 6 долларов [1, 2].
Модуль подключил к плате Olimex STM32-P407 [6] через коннектор UEXT.
Кабель, который соединяет коннектор модуля ENC28J60 и коннектор UEXT:
UEXT STM32-P407 Модуль ENC28J60 1 +3.3V черный 9 VCC 2 GND белый 10 GND 3 серый 4 фиолетовый 5 PG10 синий 2 INT 6 PG12 зеленый 8 RST 7 PC11 желтый 4 S0 8 PC12 оранжевый 5 SI 9 PC10 красный 6 SCK 10 PF8 коричневый 7 CS
Реально в программе для управления чипом 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. 2. Проверьте кабель Ethernet. Когда Вы подключаете к модулю ENC28J60 кабель, то должен на коннекторе RJ45 загореться зеленый светодиод, сигнализирующий о правильном физическом подключении. 3. Попробуйте вставить и подобрать задержки в программе перед переводом сигнала CS в состояние лог. 1 (т. е. задержку надо попробовать вставить перед вызовами макросов enc28j60_release). Если в проекте включена оптимизация по скорости (например High, Balanced), то попробуйте также подобрать задержку после перевода сигнала CS в 0 (т. е. задержку надо попробовать вставить сразу после вызовов макросов enc28j60_select). 4. Попробуйте подобрать скорость SPI, поменяв её в сторону уменьшения и увеличения. 5. Проверьте настройки сети, которые Вы записываете в ENC28J60 (IP, mask, gateway) - они должны соответствовать настройкам сети, куда Вы подключаете модуль ENC28J60.
Лично у меня была проблема в п. 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. 2. Mini ENC28J60 Ethernet LAN / Network Module site:ebay.com. 3. adamdunkels/uip site:github.com. 4. enc28j60 site:coocox.org. 5. 140825STM32-ethernet.zip - проект для IAR 6.50. 6. Olimex STM32-P407. 7. STM32F407, интерфейс SPI. 8. DP83848 Ethernet Board site:emartee.com.
|
Комментарии
microsin: чтобы работал ping код, работающий на STM32F4, должен поддерживать протокол ICMP. И конечно, должен быть правильно настроен адрес IP в проекте, либо должна быть реализована поддержка протокола DHCP, если хотите получать адрес IP автоматически.
RSS лента комментариев этой записи