Урок 91
Часть 2
LAN. W5500. HTTP Server
В предыдущей части урока мы познакомились с модулем W5500, изучили организацию памяти и обмена данными микросхемы W5500, создали и настроили проект.
Инициализация начинается с перезагрузки модуля. Я это взял из примера, написанного для оценочной платы и выложенного на официальном сайте, в технической документации я этого не нашел.
Поэтому начнём писать функцию инициализации в файле w5500.c
//-----------------------------------------------
void w5500_ini(void)
{
uint8_t dtt=0;
uint8_t opcode=0;
//Hard Reset
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
HAL_Delay(70);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
HAL_Delay(70);
}
//-----------------------------------------------
Напишем прототип для этой функции и подключим наш модуль в файле net.h
#include <stdint.h>
#include "w5500.h"
В файле net.c в соответствующей функции вызовем нашу функцию инициализации
void net_ini(void)
{
w5500_ini();
}
После аппаратной перезагрузки с помощью ножки RESET сделаем перезагрузку программную.
Для этого сначала напишем некоторые макросы в файле w5500.h
#define MAC_ADDR {0x00,0x15,0x42,0xBF,0xF0,0x51}
//--------------------------------------------------
#define BSB_COMMON 0x00
#define BSB_S0 0x01
#define BSB_S0_TX 0x02
#define BSB_S0_RX 0x03
//--------------------------------------------------
#define RWB_WRITE 1
#define RWB_READ 0
//--------------------------------------------------
#define OM_FDM0 0x00//режим передачи данных переменной длины
#define OM_FDM1 0x01//режим передачи данных по одному байту
#define OM_FDM2 0x02//режим передачи данных по два байта
#define OM_FDM3 0x03//режим передачи данных по четыре байта
//--------------------------------------------------
#define MR 0x0000//Mode Register
//--------------------------------------------------
Над функцией инициализации в файле w5500.c напишем функцию записи байта в регистр
//-----------------------------------------------
void w5500_writeReg(uint8_t op, uint16_t addres, uint8_t data)
{
uint8_t buf[] = {addres >> 8, addres, op|(RWB_WRITE<<2), data};
SS_SELECT();
HAL_SPI_Transmit(&hspi1, buf, 4, 0xFFFFFFFF);
SS_DESELECT();
}
//-----------------------------------------------
Мы последовательно записываем в 4-байтный буфер адрес регистра (ячейки памяти), затем код операции с присоединённым к нему с помощью операции ИЛИ бита записи и собственно байт данных. Затем мы опускаем ножку выбора, передаём эти 4 байта, используя соответствующую функцию библиотеки HAL, а затем поднимаем ножку выбора устройства.
А теперь напишем код программной перезагрузки модуля в функцию инициализации
HAL_Delay(70);
//Soft Reset
opcode = (BSB_COMMON<<3)|OM_FDM1;
w5500_writeReg(opcode, MR, 0x80);
HAL_Delay(100);
Регистр мы используем общий MR, а режим фиксированной передачи одного байта данных
Мы в нём взводим только 7-й бит. После этого нужно по идее дождаться когда данный бит сбросится. Таким образом мы узнаём, что перезагрузка произошла. Но вполне достаточно просто подождать несколько милисекунд, не часто же мы инициализируем микросхему.
Далее мы сконфигурируем все адреса для нашей микросхемы, занеся их также в определённые регистры. Перед этим мы добавим для этого макросы в заголовочный файл w5500.h
#define MR 0x0000//Mode Register
//--------------------------------------------------
#define SHAR0 0x0009//Source Hardware Address Register MSB
#define SHAR1 0x000A
#define SHAR2 0x000B
#define SHAR3 0x000C
#define SHAR4 0x000D
#define SHAR5 0x000E// LSB
#define GWR0 0x0001//Gateway IP Address Register MSB
#define GWR1 0x0002
#define GWR2 0x0003
#define GWR3 0x0004// LSB
#define SUBR0 0x0005//Subnet Mask Register MSB
#define SUBR1 0x0006
#define SUBR2 0x0007
#define SUBR3 0x0008// LSB
#define SIPR0 0x000F//Source IP Address Register MSB
#define SIPR1 0x0010
#define SIPR2 0x0011
#define SIPR3 0x0012// LSB
//--------------------------------------------------
Какой регистр за что отвечает, видно из комментариев к нему.
Перейдём в функцию инициализации в файл w5500.c и сконфигурируем данные регистры
HAL_Delay(100);
//Configute Net
w5500_writeReg(opcode, SHAR0,macaddr[0]);
w5500_writeReg(opcode, SHAR1,macaddr[1]);
w5500_writeReg(opcode, SHAR2,macaddr[2]);
w5500_writeReg(opcode, SHAR3,macaddr[3]);
w5500_writeReg(opcode, SHAR4,macaddr[4]);
w5500_writeReg(opcode, SHAR5,macaddr[5]);
w5500_writeReg(opcode, GWR0,ipgate[0]);
w5500_writeReg(opcode, GWR1,ipgate[1]);
w5500_writeReg(opcode, GWR2,ipgate[2]);
w5500_writeReg(opcode, GWR3,ipgate[3]);
w5500_writeReg(opcode, SUBR0,ipmask[0]);
w5500_writeReg(opcode, SUBR1,ipmask[1]);
w5500_writeReg(opcode, SUBR2,ipmask[2]);
w5500_writeReg(opcode, SUBR3,ipmask[3]);
w5500_writeReg(opcode, SIPR0,ipaddr[0]);
w5500_writeReg(opcode, SIPR1,ipaddr[1]);
w5500_writeReg(opcode, SIPR2,ipaddr[2]);
w5500_writeReg(opcode, SIPR3,ipaddr[3]);
Так как регистры данные тоже являются общими, то опкод у нас не меняется.
Теперь настроим порт для сокета 0.
Для этого добавим регистры для номера порта в файл w5500.h
#define SIPR3 0x0012// LSB
//--------------------------------------------------
#define Sn_PORT0 0x0004 // Socket 0 Source Port Register MSB
#define Sn_PORT1 0x0005 // Socket 0 Source Port Register LSB
//--------------------------------------------------
Теперь в функции инициализации в файле w5500.h занесём данные нашего порта в регистры
w5500_writeReg(opcode, SIPR3,ipaddr[3]);
//Настраиваем сокет 0
opcode = (BSB_S0<<3)|OM_FDM1;
w5500_writeReg(opcode, Sn_PORT0,local_port>>8);
w5500_writeReg(opcode, Sn_PORT1,local_port);
Здесь у нас уже меняется тип регистра, поэтому мы опкод инициализируем заново.
В принципе, на этом шаге мы можем уже собрать код и прошить контроллер.
И у нас уже наш модуль будет пинговоаться. То есть будет работать ICMP и ARP.
Давайте так и сделаем.
Прежде чем прошивать контроллер, давайте посмотрим нашу схему (нажмите на картинку для увеличения изображения)
Прошьём контроллер и проверим доступность нашего модуля
Всё работает! Можно продолжать дальше.
В файле w5500.h добавим структуру для свойств соединения
#define be16toword(a) ((((a)>>8)&0xff)|(((a)<<8)&0xff00))
//--------------------------------------------------
typedef struct tcp_prop {
volatile uint8_t cur_sock;//активный сокет
} tcp_prop_ptr;
//--------------------------------------------------
Добавим глобальную переменную типа нашей структуры в файле w5500.c
extern char str1[60];
tcp_prop_ptr tcpprop;
В функции инициализации инициализируем активный (текущий) сокет
w5500_writeReg(opcode, Sn_PORT1,local_port);
//инициализируем активный сокет
tcpprop.cur_sock = 0;
После функции записи регистра добавим функцию чтения данных из регистра
//-----------------------------------------------
uint8_t w5500_readReg(uint8_t op, uint16_t addres)
{
uint8_t data;
uint8_t wbuf[] = {addres >> 8, addres, op, 0x0};
uint8_t rbuf[4];
SS_SELECT();
HAL_SPI_TransmitReceive(&hspi1, wbuf, rbuf, 4, 0xFFFFFFFF);
SS_DESELECT();
data = rbuf[3];
return data;
}
//-----------------------------------------------
Здесь мы работаем несколько по-другому. В буфер набираем то же самое, только бит записи не включаем, а байт данных можно использовать любой, запишем в буфер 0. Затем мы организовываем ещё один буфер и вызываем функцию кольцевого обмена данными по шине SPI, которая и вернёт нам в последнем элементе массива необходимый нам байт из регистра.
В файле w5500.h мы добавим ещё несколько макросов с адресами регистров и некоторыми параметрами и состояниями, с которыми мы познакомимся позже
#define Sn_PORT1 0x0005 // Socket 1 Source Port Register 1 LSB
//--------------------------------------------------
#define Sn_MR 0x0000 // Socket 0 Mode Register
#define Sn_CR 0x0001 // Socket 0 Command Register
#define Sn_SR 0x0003 // Socket 0 Status Register
//--------------------------------------------------
//Socket mode
#define Mode_CLOSED 0x00
#define Mode_TCP 0x01
#define Mode_UDP 0x02
#define Mode_MACRAV 0x04
//--------------------------------------------------
//Socket states
#define SOCK_CLOSED 0x00
#define SOCK_INIT 0x13
#define SOCK_LISTEN 0x14
#define SOCK_ESTABLISHED 0x17
//-------------------------------------------
#define Sn_MSSR0 0x0012
#define Sn_MSSR1 0x0013
#define Sn_TX_FSR0 0x0020
#define Sn_TX_FSR1 0x0021
#define Sn_TX_RD0 0x0022
#define Sn_TX_RD1 0x0023
#define Sn_TX_WR0 0x0024
#define Sn_TX_WR1 0x0025
#define Sn_RX_RSR0 0x0026
#define Sn_RX_RSR1 0x0027
#define Sn_RX_RD0 0x0028
#define Sn_RX_RD1 0x0029
//--------------------------------------------------
Перейдём в файл w5500.c и добавим ещё две функции для инициализации и ожидания окончания инициализации сокета
//-----------------------------------------------
void OpenSocket(uint8_t sock_num, uint16_t mode)
{
uint8_t opcode=0;
opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;
w5500_writeReg(opcode, Sn_MR, mode);
w5500_writeReg(opcode, Sn_CR, 0x01);
}
//-----------------------------------------------
void SocketInitWait(uint8_t sock_num)
{
uint8_t opcode=0;
opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;
while(1)
{
if(w5500_readReg(opcode, Sn_SR)==SOCK_INIT)
{
break;
}
}
}
//-----------------------------------------------
Добавим прототипы для данных функций.
В первой функции мы в регистр режима сокета, соответствующий сокету с номером, пришедшим во входящем параметре, заносим тип режима работы, который также прийдёт во входящем параметре. Затем мы устанавливаем в управляющем регистре сокета бит, соответствующий команде открытия сокета. Вот эта табличка сразу нам обо всём этом и расскажет
А во второй функции мы опрашиваем регистр состояния сокета, и ждём там значения 0x13, соответствующего состоянию «INIT»
Вызовем данные функции в функции инициализации
tcpprop.cur_sock = 0;
//Открываем сокет 0
OpenSocket(0,Mode_TCP);
SocketInitWait(0);
Теперь мы должны будем служать данный сокет, то есть следить за тем, когда к нам прийдёт какой-нибудь TCP-пакет от клиента. Для этого мы напишем также две функции
//-----------------------------------------------
void ListenSocket(uint8_t sock_num)
{
uint8_t opcode=0;
opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;
w5500_writeReg(opcode, Sn_CR, 0x02); //LISTEN SOCKET
}
//-----------------------------------------------
void SocketListenWait(uint8_t sock_num)
{
uint8_t opcode=0;
opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;
while(1)
{
if(w5500_readReg(opcode, Sn_SR)==SOCK_LISTEN)
{
break;
}
}
}
//-----------------------------------------------
Добавим также для данных функций прототипы в заголовочном файле.
Здесь у нас аналогичная ситуация. В первой функции мы передаём команду 0x02, которая переводит сокет в состояние «LISTEN«
А вторая функция ждёт, когда данное состояние установится, читая байт регистра состояния
Вызовем данные функции в функции инициализации
SocketInitWait(0);
//Начинаем слушать сокет
ListenSocket(0);
SocketListenWait(0);
На всякий случай, немного подождём и посмотрим состояние микросхемы (хотя этого делать и необязательно). Я слышал про случаи, что прогамма всё-таки дожидалась нужного состояния в функции, а затем это состояние сбрасывалось само собой. Вот это мы и проконтролируем
SocketListenWait(0);
HAL_Delay(500);
//Посмотрим статусы
opcode = (BSB_S0<<3)|OM_FDM1;
dtt = w5500_readReg(opcode, Sn_SR);
sprintf(str1,"First Status Sn0: 0x%02X\r\n",dtt);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
}
//-----------------------------------------------
Соберём код, прошьём контроллер и посмотрим вывод информации в терминальной программе
На этом инициализацию можно считать законченной.
Теперь нам нужно будет попробовать принять какой-нибудь пакет из сети.
Ниже функции инициализации создадим функцию приёма пакета
//-----------------------------------------------
void w5500_packetReceive(void)
{
uint16_t point;
uint16_t len;
}
//-----------------------------------------------
Создадим для данной функции прототип в заголовочном файле и вызовем её в соответствующей функции в файле net.c
void packet_receive(void)
{
w5500_packetReceive();
}
В файле w5500.c над функцией инициализации добавим функцию определения текущего состояния сокета
//-----------------------------------------------
uint8_t GetSocketStatus(uint8_t sock_num)
{
uint8_t dt;
uint8_t opcode=0;
opcode = (((sock_num<<2)|BSB_S0)<<3)|OM_FDM1;
dt = w5500_readReg(opcode, Sn_SR);
return dt;
}
//-----------------------------------------------
Чтобы узнать состояние сокета, мы опрашиваем регистр состояния сокета, номер которого мы передаём во входящем аргументе.
Теперь создадим условие состояния текущего сокета в функции приёма пакета. Оно должно быть в статусе «Соединено»
uint16_t len;
if(GetSocketStatus(tcpprop.cur_sock)==SOCK_ESTABLISHED)
{
}
В следующей части урока мы исследуем пришедший от клиента запрос HTTP и начнём формировать на него ответ.
Предыдущая часть Программирование МК STM32 Следующая часть
Отладочную плату можно приобрести здесь Nucleo STM32F401RE
Ethernet LAN Сетевой Модуль можно купить здесь W5500 Ethernet LAN
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Почему не пингуется w5500 микросхемой stm32f051r8, не формирует COM порт(64 Kbytes of Flash memory, 8 Kbytes of SRAM), (установлена на STM32F0 Discovery)? Это может быть связано с особенностями микросхемы?
Здравствуйте!
Я дошел до места добавления в файле w5500.h структуры:
typedef struct tcp_prop {
volatile uint8_t cur_sock; //активный сокет
} tcp_prop_ptr;
Мне компилятор выдает две ошибки:
1) error: redefinition of 'tcp_prop'
ошибка: переопределение 'tcp_prop'
2) error: typedef redefinition with different tipes ('struct tcp_prop_ptr' vs 'struct tcp_prop')
ошибка: переопределение typedef с разными подсказками ('struct tcp_prop_ptr' против 'struct tcp_prop')
Почему у вас нет этих ошибок? Не знаю что делать, не подскажете в чем проблема?
До этого момента все повторял за вами и все работало.
Мой косяк. Я удалил строки:
#ifndef W5500_H_
#define W5500_H_
…
#endif /* W5500_H_ */
Вернул на место, все заработало.