Wiznet W5500 — это Ethernet-контроллер с интерфейсом SPI . Чип поддерживает стандарты 10baseT и 100baseT. Характерной особенностью контроллера является то, что он имеет аппаратную реализацию TCP/IPv4. Это позволяет существенно разгрузить работающий с ним микроконтроллер. Wiznet W5500 поддерживает до 8 сокетов и имеет суммарно 16 Кб памяти на прием и еще 16 Кб на передачу. Эта память может быть распределена между сокетами произвольным образом. Давайте же разберемся, как работать с этим чипом на примере МК STM32.
Примечание: В этом контексте вас может заинтересовать статья Изучаем Ethernet-фреймы с помощью осциллографа , если вдруг вы ее пропустили.
В свои экспериментах я использовал такой модуль:
Цена устройства на eBay составляет около 4$. На модуле есть стабилизатор напряжения, благодаря чему его можно питать как от 5 В, так и от 3.3 В. Сам же чип прекрасно работает как с 3.3-вольтовой, так и 5-иволтовой логикой. Таким образом, его можно без труда использовать с любым другим микроконтроллером, не обязательно STM32, или даже с FPGA . В частности, для Arduino существует библиотека Ethernet2 от Adafruit.
Fun fact! На момент написания этих строк, Sigrok не имел декодера протокола для Wiznet W5500. Между тем, протокол этот довольно несложен, и описан в даташите W5500 [PDF] . Отличная возможность законтрибьютить в опенсорс 😉
Существует также официальная библиотека от самого производителя под названием ioLibrary_Driver , которой мы и воспользуемся. Библиотека имеет лицензию MIT. Она не завязана на конкретный микроконтроллер, и может быть с тем же успехом использована с STM8, AVR, 8051 или PIC.
Код библиотеки был скопирован в каталог Lib/ioLibrary_Driver проекта. В Makefile было дописано следующее:
C_SOURCES =
Lib / ioLibrary_Driver / Ethernet / wizchip_conf.c
Lib / ioLibrary_Driver / Ethernet / socket.c
Lib / ioLibrary_Driver / Ethernet / W5500 / w5500.c
Lib / ioLibrary_Driver / Internet / DHCP / dhcp.c
Lib / ioLibrary_Driver / Internet / DNS / dns.c
# … прочие файлы были здесь и раньше …
# …
C_INCLUDES =
-ILib / ioLibrary_Driver / Ethernet
-ILib / ioLibrary_Driver / Internet / DHCP
-ILib / ioLibrary_Driver / Internet / DNS
# … прочие каталоги были здесь и раньше …
# …
Правим Lib/ioLibrary_Driver/Ethernet/wizchip_conf.h:
Основной код проекта рассмотрим по частям. Первым делом добавляем необходимые инклудники:
#include «dhcp.h»
#include «dns.h»
Мы будем использовать три сокета — один для DHCP, один для DNS, и еще один для хождения по HTTP:
#define DNS_SOCKET 1
#define HTTP_SOCKET 2
Для работы DHCP и DNS нам понадобятся временные буферы:
uint8_t dhcp_buffer [ 1024 ] ;
// 1K seems to be enough for this buffer as well
uint8_t dns_buffer [ 1024 ] ;
Также объявим процедуры, через которые библиотека будет ходить в SPI:
HAL_GPIO_WritePin ( W5500_CS_GPIO_Port , W5500_CS_Pin ,
GPIO_PIN_RESET ) ;
}
void W5500_Unselect ( void ) {
HAL_GPIO_WritePin ( W5500_CS_GPIO_Port , W5500_CS_Pin ,
GPIO_PIN_SET ) ;
}
void W5500_ReadBuff ( uint8_t * buff , uint16_t len ) {
HAL_SPI_Receive ( & hspi1 , buff , len , HAL_MAX_DELAY ) ;
}
void W5500_WriteBuff ( uint8_t * buff , uint16_t len ) {
HAL_SPI_Transmit ( & hspi1 , buff , len , HAL_MAX_DELAY ) ;
}
uint8_t W5500_ReadByte ( void ) {
uint8_t byte ;
W5500_ReadBuff ( & byte , sizeof ( byte ) ) ;
return byte ;
}
void W5500_WriteByte ( uint8_t byte ) {
W5500_WriteBuff ( & byte , sizeof ( byte ) ) ;
}
Эти процедуры должны быть зарегистрированы таким образом:
reg_wizchip_cs_cbfunc ( W5500_Select , W5500_Unselect ) ;
reg_wizchip_spi_cbfunc ( W5500_ReadByte , W5500_WriteByte ) ;
reg_wizchip_spiburst_cbfunc ( W5500_ReadBuff , W5500_WriteBuff ) ;
Далее распределяем память между сокетами. Пусть у каждого сокета будет по 2 Кб на прием и передачу:
uint8_t rx_tx_buff_sizes [ ] = { 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 } ;
wizchip_init ( rx_tx_buff_sizes , rx_tx_buff_sizes ) ;
Инициализируем клиент DHCP:
wiz_NetInfo net_info = {
. mac = { 0xEA , 0x11 , 0x22 , 0x33 , 0x44 , 0xEA } ,
. dhcp = NETINFO_DHCP
} ;
// set MAC address before using DHCP
setSHAR ( net_info. mac ) ;
DHCP_init ( DHCP_SOCKET , dhcp_buffer ) ;
Ему также нужно несколько колбэков, роль которых очевидна из названия:
void Callback_IPAssigned ( void ) {
UART_Printf ( «Callback: IP assigned! Leased time: %d sec r n » ,
getDHCPLeasetime ( ) ) ;
ip_assigned = true ;
}
void Callback_IPConflict ( void ) {
UART_Printf ( «Callback: IP conflict! r n » ) ;
}
Эти колбэки регистрируются следующим образом:
reg_dhcp_cbfunc (
Callback_IPAssigned ,
Callback_IPAssigned ,
Callback_IPConflict
) ;
Согласно документации, мы также должны раз в секунду вызывать процедуру DHCP_time_handler
. Для этого проще всего отредактировать Src/stm32f4xx_it.c:
#include «dhcp.h»
# …
void SysTick_Handler ( void ) {
/* USER CODE BEGIN SysTick_IRQn 0 */
static uint16_t ticks = 0 ;
ticks ++;
if ( ticks == 1000 ) {
DHCP_time_handler ( ) ;
ticks = 0 ;
}
/* USER CODE END SysTick_IRQn 0 */
/* … прочий код был здесь и раньше … */
}
Возвращаемся к основному коду программы. Получаем IP-адрес и информацию о сети по DHCP:
// actually should be called in a loop, e.g. by timer
uint32_t ctr = 10000 ;
while ( ( ! ip_assigned ) && ( ctr > 0 ) ) {
DHCP_run ( ) ;
ctr —;
}
if ( ! ip_assigned ) {
UART_Printf ( » r n IP was not assigned 🙁 r n » ) ;
return ;
}
В общем случае, DHCP_run
должна вызываться регулярно, например, по таймеру .
Выводим полученную информацию, а также передаем ее самой библиотеке:
getGWfromDHCP ( net_info. gw ) ;
getSNfromDHCP ( net_info. sn ) ;
uint8_t dns [ 4 ] ;
getDNSfromDHCP ( dns ) ;
UART_Printf (
«IP: %d.%d.%d.%d r n »
«GW: %d.%d.%d.%d r n »
«Net: %d.%d.%d.%d r n »
«DNS: %d.%d.%d.%d r n » ,
net_info. ip [ 0 ] , net_info. ip [ 1 ] , net_info. ip [ 2 ] , net_info. ip [ 3 ] ,
net_info. gw [ 0 ] , net_info. gw [ 1 ] , net_info. gw [ 2 ] , net_info. gw [ 3 ] ,
net_info. sn [ 0 ] , net_info. sn [ 1 ] , net_info. sn [ 2 ] , net_info. sn [ 3 ] ,
dns [ 0 ] , dns [ 1 ] , dns [ 2 ] , dns [ 3 ]
) ;
UART_Printf ( «Calling wizchip_setnetinfo()… r n » ) ;
wizchip_setnetinfo ( & net_info ) ;
Далее резолвим доменное имя «remontka.com»:
DNS_init ( DNS_SOCKET , dns_buffer ) ;
uint8_t addr [ 4 ] ;
{
char domain_name [ ] = «remontka.com» ;
UART_Printf ( «Resolving domain name » %s «» … r n «» domain_name ) ;
int8_t res = DNS_run ( dns ( uint8_t * ) & domain_name addr ) ;
if ( res != 1 ) {
UART_Printf ( «»DNS_run() failed