STM Урок 96. LAN8720. LWIP. TCP Client. Часть 2



В предыдущей части урока мы познакомились с микросхемой LAN8720, с межканальными интерфейсами и со стеком протоколов LWIP.

 

Ну, что ж. Давайте наконец-то перейдём к нашему проекту.

Наша задача — создать клиент TCP, который будет уметь по своей инициативе соединяться и разъединяться с сервером TCP, а также наша программа должна уметь принимать и видеть пришедшие данные в виде текста, а также отправлять аналогичные данные на сервер TCP. То есть должен будет получиться своего рода чат с использованием протокола TCP.

Проект мы создадим новый. В качестве контроллера выберем, соответственно, наш контроллер STM32F407VGTx

 

image07

 

Затем настроим наш проект.

Первым делом RCC

 

image08

 

Затем программатор

 

image09

 

Включим USART

 

image10

 

Включим таймер на будущее, возможно в данном проекте он нам и не пригодится

 

image11

 

Теперь Ethernet

 

image12

 

Затем подключим библиотеку стека протоколов LWIP

 

image13

 

Включим ножки светодиодов на выход, которые внесут некоторое удобство и наглядность в отладке проекта

 

image14

 

Перейдём в Clock Configuration и настроим делители и умножители для установки требуемых частот (нажмите на картинку для увеличения изображения)

 

image15_0500

 

Переходим в Configuration.

Если у вас в USART6 такие параметры, то здесь ничего не трогаем

 

image16

 

Перейдём в USART6 закладку с прерываниями и включим их

 

image17

 

Затем давайте настроим наш таймер

 

image18

 

Также включим у таймера прерывания

 

image19

 

Теперь ETH. Закладка с параметрами

 

image20

 

Устанавливаем там желаемый MAC-адрес, а также адрес регистра для доступа к микросхеме по RMII. Оставляем 1. А вот оно и отличие!!! 1 — это в случае использования модуля от WaveShare, а 0 — в случае использования платы расширения STM32F4DIS-BB.

Теперь настроим LWIP. Сначала закладка General Settings. Отключим DHCP, использовать будем статическую адресацию, чтобы постоянно не узнавать на другом узле адрес нашего узла. Ну и также в будующих целях. Мы же когда-то будем напрямую друг к другу подключать два контроллера по интерфейсу LAN и вряд ли другой контроллер корректно раздаст IP нашему контроллеру

 

image21

 

Затем перейдём в закладку Key Options, проследим, чтобы галка с расширенными настройками была установлены и изменим здесь некоторые параметры, чтобы сдерать размер окна привычный для нас — 2048

 

image22

 

image23

 

Никакие протоколы больше не трогаем и не подключаем.

Зайдём в настройки проекта, увеличим стек и кучу и настроим генерацию проекта для System Workbench, ну и дадим нашему проекту имя

 

image24

 

Применим настройки, сгенерируем проект, перейдём в System Workbench, подключим там наш сгенерированный проект, зайдём в его настройки, уберём всю отладочную конфигурацию, если таковая имеется, а также уровень оптимизации, как обычно, установим в 1.

Попробуем собрать наш проект.

Перейдём в файл main.c и подключим структуру для нашего сетевого интерфейса

 

/* USER CODE BEGIN PV */

/* Private variables ---------------------------------------------------------*/

extern struct netif gnetif;

/* USER CODE END PV */

 

Впишем вот такой стандартный код в бесконечный цикл для обеспечения постоянной работы нашего стека

 

/* USER CODE BEGIN 3 */

  ethernetif_input(&gnetif);

  sys_check_timeouts();

}

/* USER CODE END 3 */

 

Подключим нашу схему, соберём код, прошьём контроллер и попробуем попинговать наш модуль

 

image25

 

Создадим два файла для работы с сетью — net.h и net.c со следующим первоначальным содержимым

 

net.h:

#ifndef NET_H_

#define NET_H_

//-----------------------------------------------

#include "stm32f4xx_hal.h"

#include <string.h>

#include <stdlib.h>

#include <stdint.h>

#include "lwip.h"

#include "lwip/tcp.h"

//-----------------------------------------------

#endif /* NET_H_ */

 

net.c

#include "net.h"

//-----------------------------------------------

extern UART_HandleTypeDef huart6;

//-----------------------------------------------

 

Подключим также нашу библиотеку в файле main.c

 

/* USER CODE BEGIN Includes */

#include "net.h"

/* USER CODE END Includes */

 

Перейдём теперь в заголовочный файл net.h.

 

 

Так как мы пишем клиент TCP, то нам как-то надо будет дать команду на соединение с сервером TCP. Для этого мы должны будем знать его IP-адрес, а также адрес порта. Узнать их, конечно, не тяжело, но они могут каждый раз различаться, поэтому мы не можем жёстко занести их в наш проект. Мы должны их как-то каждый раз проекту передавать в виде параметров команды на соединение. Для этого нам будет служить наша шина USART и делать мы это будем из терминальной программы, в последствии в проекте производя разбор пришедшей оттуда строки. Поэтому для начала давайте напишем структуру со свойствами нашего интерфейса USART

 

#include "lwip/tcp.h"

//-----------------------------------------------

typedef struct USART_prop{

uint8_t usart_buf[26];

uint8_t usart_cnt;

uint8_t is_tcp_connect;//статус попытки создать соединение TCP с сервером

uint8_t is_text;//статус попытки передать текст серверу

} USART_prop_ptr;

//-----------------------------------------------

 

Перейдём теперь в файл net.c и создадим переменную типа нашей структуры

 

extern UART_HandleTypeDef huart6;

USART_prop_ptr usartprop;

 

Также создадим функцию инициализации нашей сети, обнулив в ней значения полей структуры

 

USART_prop_ptr usartprop;

//-----------------------------------------------

void net_ini(void)

{

  usartprop.usart_buf[0]=0;

  usartprop.usart_cnt=0;

  usartprop.is_tcp_connect=0;

  usartprop.is_text=0;

}

//-----------------------------------------------

 

Добавим для нашей функции прототип в заголовочном файле и вызовем её в main() в файле main.c, заодно включим там наш таймер

 

/* USER CODE BEGIN 2 */

HAL_TIM_Base_Start_IT(&htim2);

net_ini();

/* USER CODE END 2 */

 

Вернёмся в net.c и добавим функцию-обработчик прерывания от USART по приёму данных

 

//-----------------------------------------------

void UART6_RxCpltCallback(void)

{

}

//-----------------------------------------------

 

Создадим на эту функцию прототип, перейдём опять в main.c и добавим там настоящий обработчик данного прерывания, в котором мы нашу функцию и вызовем

 

/* USER CODE BEGIN 4 */

//-----------------------------------------------

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

  if(huart==&huart6)

  {

    UART6_RxCpltCallback();

  }

}

//-----------------------------------------------

/* USER CODE END 4 */

 

Вернёмся в файл net.c и добавим глобальную переменную в виде строчного массива

 

USART_prop_ptr usartprop;

char str[30];

 

Опять вернёмся в файл main.c и подключим там наш строчный массив

 

extern struct netif gnetif;

extern char str[30];

 

В функции main() вызовем команду на приём символа, иначе наш USART никогда не начнёт приём

 

net_ini();

HAL_UART_Receive_IT(&huart6,(uint8_t*)str,1);

 

Затем вернёмся в файл net.c, перейдём в наш обработчик, создадим там переменную и обработаем случай превышения размера строчного буфера, а также по окончанию проинкрементируем наш счётчик байтов в буфере и вызовем опять команду приёма данных

 

void UART6_RxCpltCallback(void)

{

  uint8_t b;

  b = str[0];

  //если вдруг случайно превысим длину буфера

  if (usartprop.usart_cnt>25)

  {

    usartprop.usart_cnt=0;

    HAL_UART_Receive_IT(&huart6,(uint8_t*)str,1);

    return;

  }

  usartprop.usart_cnt++;

  HAL_UART_Receive_IT(&huart6,(uint8_t*)str,1);

}

 

После проверки превышения буфера занесём принятый символ в буфер

 

  return;

}

usartprop.usart_buf[usartprop.usart_cnt] = b;

usartprop.usart_cnt++;

 

Если мы встретим символ перевода строки, то занесём в следующий за ним элемент строчного массива символ окончания строки

 

usartprop.usart_buf[usartprop.usart_cnt] = b;

if(b==0x0A)

{

  usartprop.usart_buf[usartprop.usart_cnt+1]=0;

}

 

Выше добавим функцию разбора нашей принятой строки

 

//-----------------------------------------------

void string_parse(char* buf_str)

{

}

//-----------------------------------------------

 

Вернёмся в наш обработчик и вызовем данную функцию в условии

 

if(b==0x0A)

{

  usartprop.usart_buf[usartprop.usart_cnt+1]=0;

  string_parse((char*)usartprop.usart_buf);

 

Затем в этом же условии мы после разбора строки обнуляем наш счётчик, посылаем команду приёма символа в шину USART, которая будет ждать следующего символа из шины, и выходим из нашей функции

 

  string_parse((char*)usartprop.usart_buf);

  usartprop.usart_cnt=0;

  HAL_UART_Receive_IT(&huart6,(uint8_t*)str,1);

  return;

}

 

В функции разбора строки отобразим принятую строку в терминальной программе, чтобы увидеть, что мы всё правильно приняли

 

void string_parse(char* buf_str)

{

  HAL_UART_Transmit(&huart6, (uint8_t*)buf_str,strlen(buf_str),0x1000);

}

 

Соберём код, прошьём контроллер, запустим терминальную программу. Я использовал в этот раз Cool Term, так как там есть возможность гибко настроить передачу данных.

Зайдём в настройки данной программы, настроим наш порт и скорость (это, я думаю, не стоит показывать — справитесь самостоятельно), а также настроим передачу по окончанию строки символов возврата каретки и перевода строки

 

image26

 

Соединимся с портом и попробуем что-нибудь передать

 

image27

 

Как мы видим, эхо у нас рабоает, значит строку мы поймали на контроллере правильно. Уточнить, что нам также приходят, а также что мы их не теряем — символ возврата каретки и перевода строки можно, передав следующую строку, она должна будет отобразиться эхом с новой строки, а также нажав кнопку View Hex в тулбаре терминальной программы, и тогда мы увидим коды пришедших символов

 

image28

 

Мы видим, что символы приходят.

 

 

Вернёмся в нашу функцию разбору строки и продолжим писать её тело.

А прежде чем писать код тела функции, давайте договоримся, что мы будем передавать следующую команду на соединение с сервером

t:IP-адрес сервера:адрес порта сервера

А разъединяться с помощью подобной команды

c:IP-адрес сервера:адрес порта сервера

По-идее для разъёдинения не нужно указывать адреса, но вдруг у нас будет несколько соединений и мы захотим разорвать именно определённое из них. Пока у нас будет только одно, но в будущем всё возможно.

А теперь тело

 

  HAL_UART_Transmit(&huart6, (uint8_t*)buf_str,strlen(buf_str),0x1000);

  // если команда попытки соединения ("t:")

  if (strncmp(buf_str,"t:", 2) == 0)

  {

  }

  //статус попытки разорвать соединение ("c:")

  else if (strncmp(buf_str,"c:", 2) == 0)

  {

  }

  else

  {

  }

}

 

Пока у нас будет только три варианта строк — команда на соединение, команда на разъединение, а также обычный текст для чата.

Выше добавим функцию обработки команд

 

//-----------------------------------------------

void net_cmd(char* buf_str)

{

}

//-----------------------------------------------

 

Обработаем команду на соединение с сервером, занеся определённый код в тело соответствующего условия в функции разбора строки

 

if (strncmp(buf_str,"t:", 2) == 0)

{

  usartprop.usart_cnt-=1;

  usartprop.is_tcp_connect=1;//статус попытки создать соединение TCP с сервером

  net_cmd(buf_str+2);

  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);

}

 

Подобную операцию проделаем и в условии для разъединения с сервером

 

else if (strncmp(buf_str,"c:", 2) == 0)

{

  usartprop.usart_cnt-=1;

  usartprop.is_tcp_connect=2;//статус попытки разорвать соединение TCP с сервером

  net_cmd(buf_str+2);

  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);

}

 

Со строкой разберёмся позже. А пока перейдём в функцию обработки команд, добавим там массив из четырёх элементов для IP-адреса, переменную и два условия

 

void net_cmd(char* buf_str)

{

  uint8_t ip[4];

  uint16_t port;

  if(usartprop.is_tcp_connect==1)//статус попытки создать соединение TCP с сервером

  {

  }

  if(usartprop.is_tcp_connect==2)//статус попытки разорвать соединение TCP с сервером

  {

  }

}

 

Выше добавим функции разбора строк со значением IP-адреса и порта, Мы такие функции уже использовали в ранних проектах, поэтому осуждать их не имеет никакого смысла. Поэтому я просто приведу их код

 

port_extract

//-----------------------------------------------

uint16_t port_extract(char* ip_str, uint8_t len)

{

  uint16_t port=0;

  int ch1=':';

  char *ss1;

  uint8_t offset = 0;

  ss1=strchr(ip_str,ch1);

  offset=ss1-ip_str+1;

  ip_str+=offset;

  port = atoi(ip_str);

  return port;

}

//--------------------------------------------------

 

ip_extract

void ip_extract(char* ip_str, uint8_t len, uint8_t* ipextp)

{

  uint8_t offset = 0;

  uint8_t i;

  char ss2[5] = {0};

  char *ss1;

  int ch1 = '.';

  int ch2 = ':';

  for(i=0;i<3;i++)

  {

    ss1 = strchr(ip_str,ch1);

    offset = ss1-ip_str+1;

    strncpy(ss2,ip_str,offset);

    ss2[offset]=0;

    ipextp[i] = atoi(ss2);

    ip_str+=offset;

    len-=offset;

  }

  ss1=strchr(ip_str,ch2);

  if (ss1!=NULL)

  {

    offset=ss1-ip_str+1;

    strncpy(ss2,ip_str,offset);

    ss2[offset]=0;

    ipextp[3] = atoi(ss2);

    return;

  }

  strncpy(ss2,ip_str,len);

  ss2[len]=0;

  ipextp[3] = atoi(ss2);

}

//-----------------------------------------------

 

Создадим также глобальные переменные для хранения адресов нашего сервера, к которому мы, надеюсь, вскоре подключимся

 

#include "net.h"

//-----------------------------------------------

uint8_t ipaddr_dest[4];

uint16_t port_dest;

 

Также добавим ещё один строчный массив для подготовки и вывода в USART различных служебных данных

 

char str[30];

char str1[100];

 

В самом верху после объявления различных переменных добавим функцию соединения с сервером по протоколу TCP

 

char str1[100];

//-----------------------------------------------

void tcp_client_connect(void)

{

}

//-----------------------------------------------

 

Пусть пока лежит пустотелая, просто нам нужно что-то вызывать в нашей функции обработки команд net_cmd, в которую мы и вернёмся, чтобы написать тело условия соединения с сервером

 

if(usartprop.is_tcp_connect==1)//статус попытки создать соединение TCP с сервером

{

  ip_extract(buf_str,usartprop.usart_cnt-1,ipaddr_dest);

  port_dest=port_extract(buf_str,usartprop.usart_cnt-1);

  usartprop.usart_cnt=0;

  usartprop.is_tcp_connect=0;

  tcp_client_connect();

  sprintf(str1,"%d.%d.%d.%d:%u\r\n", ipaddr_dest[0],ipaddr_dest[1],ipaddr_dest[2],ipaddr_dest[3],port_dest);

  HAL_UART_Transmit(&huart6,(uint8_t*)str1,strlen(str1),0x1000);

  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);

}

 

В теле данного условия мы получим адреса сервера из строки, записав их в глобальные переменные, сбросим статусные переменные, вызовем функцию соединения с сервером, отобразим в терминальной программе адреса, и зажжём светодиод.

Над функцией инициализации добавим ещё одну функцию, которая будет вызываться при создании соединения с сервером, так называемая callback-функция)

 

//-----------------------------------------------

static err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err)

{

  return err;

}

//-----------------------------------------------

 

В качестве аргументов в данную функцию придет указатель на аргументы, также указатель на структуру TCP-соединения и статус возможной ошибки, который мы также вернём обратно по выходу из данной функции.

Добавим для данной функции прототип в этом же файле (не в хедере) повыше

 

char str1[100];

//-----------------------------------------------

static err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err);

//-----------------------------------------------

 

Добавим также глобальную переменную типа структуры соединения TCP и счётчик сообщений

 

char str1[100];

struct tcp_pcb *client_pcb;

__IO uint32_t message_count=0;

 

А также добавим массив для данных

 

char str1[100];

u8_t data[100];

 

Вернёмся теперь писать тело функции соединения с сервером tcp_client_connect

 

void tcp_client_connect(void)

{

  ip_addr_t DestIPaddr;

  client_pcb = tcp_new();

  if (client_pcb != NULL)

  {

    IP4_ADDR( &DestIPaddr, ipaddr_dest[0], ipaddr_dest[1], ipaddr_dest[2], ipaddr_dest[3]);

    tcp_connect(client_pcb,&DestIPaddr,port_dest,tcp_client_connected);

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);

  }

}

 

Мы создали экземпляр структуры соединения, затем занесли в переменную IP-адрес сервера и вызвали функцию соединения с сервером, в качестве параметров мы в данную функцию передали экземпляр структуры, адрес сервера, порт сервера, а также callback-функцию, которая будет вызываться в случае соединения с сервером. Хоть нам ещё писать много функционала, обеспечивающего надёжную работу клиента, но если мы сейчас соберём код и попробуем соединиться с сервером, то нам скорее всего это уже удастся. Давайте попробуем.

Собираем код, прошиваем контроллер, затем запустим программку netcat на ПК для того чтобы создать TCP-сервер, который будет слушать определённый порт

 

image29

 

Запустим также анализатор трафика Wireshark и дадим команду в терминальной программе на соединение с портом 3333 сервера

 

image30

 

Посмотрим в WireShark

 

image31

 

Мы успешно соединились с сервером. Значит идём по правильному пути.

 

В следующей части урока мы напишем ряд функций и научимся также корректно разъединяться с клиентом, а также передавать ему и принимать с него данные.

 

 

Предыдущая часть Программирование МК STM32 Следующая часть

 

 

Отладочную плату можно приобрести здесь: STM32F4-DISCOVERY

Модуль LAN можно приобрести здесь: LAN8720

Плату расширения можно приобрести здесь: STM32F4DIS-BB

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM LAN8720. LWIP. TCP Client

44 комментария на “STM Урок 96. LAN8720. LWIP. TCP Client. Часть 2
  1. Alex:

    PHY adress для F746-дискавери должен быть 0

  2. Семён:

    Добрый день. У меня плата NUCLEO-746ZG Попробовал повторить код:
    /* USER CODE BEGIN 3 */
    ethernetif_input(&gnetif);
    sys_check_timeouts();
    }
    /* USER CODE END 3 */
    И пингонуть, но почему то плата не пингуется. Ни кто не подскажет, почему это может быть?
    Проект делал в кубе, настройки для LwIP стека не менял, так как формировал проект для платы.
    Заранее спасибо.

  3. Семён:

    Огромное спасибо!
    Но 1 ставит кубик для этой платы (:

  4. Serg_vrn:

    Собрал. Светодиоды загораются. В шарке вижу четыре попытки платы соединиться с сервером:
    7 2.653839 192.168.137.254 192.168.137.101 TCP 60 49153 → 3333 [SYN] Seq=0 Win=2048 Len=0 MSS=460
    10 5.653704 192.168.137.254 192.168.137.101 TCP 60 [TCP Retransmission] 49153 → 3333 [SYN] Seq=0 Win=2048 Len=0 MSS=460
    11 8.653609 192.168.137.254 192.168.137.101 TCP 60 [TCP Retransmission] 49153 → 3333 [SYN] Seq=0 Win=2048 Len=0 MSS=460
    12 11.653494 192.168.137.254 192.168.137.101 TCP 60 [TCP Retransmission] 49153 → 3333 [SYN] Seq=0 Win=2048 Len=0 MSS=460
    а подтверждения нет. Из-за чего эт может быть? Может быть nc в 64 разрядной системе не работает? И почему Seq=0?

    • От ОС не зависит. У Вас модуль или плата расширения? Если модуль то адрес PHY надо выставлять в 1, а если плата — то в 0.
      Хотя по ходу пакеты из неё у Вас в ПК идут. странно, изучайте эти пакеты. Мы на данном этапе хорошо знаем проядок следования пакетов и вообще всё по транспортным протоколам, думаю, разберётесь.

  5. Serg_vrn:

    Ура! Это был Firewall от EsetNode

  6. Serg_vrn:

    Замечено и не только мной (нас тут двое на предприятии и Дискавери тоже несколько), что иногда Ethernet не стартует. Плата не пингуется. А потом нажмешь на кнопочку Resrt и все заработало. С прошивкой не связано. Если что — галочка Reset&Run проверено. Что это?

    • Андрей:

      У меня та же проблема: «STM32F407G-DISK1» + «LAN8720 Eth Board» + KEIL 5.26.2.0.
      «Options for Target»->»Utilities»->»Settings»->»Flash Download»->»Reset&Run»: ENABLED

      Стартует не сразу, а после нескольких нажатий на кнопку RESET.

    • АЛЕКСАНДР:

      попробуй регистры PHY почитать в начале, HAL_ETH_ReadPHYRegister() посмотри читаются ли дефолтные значения из даташита на твой phy (8720 или 8742a) если не читаются поменяй PhyAddress посмотри читается нет, далее выключи аутнегошейшин и поставь константную скорость в девайся и пк.

  7. Yes:

    Hello I have followed same procedure and generate code for stm32f407 with LAN8720A. and put below function in loop. It is compiled successfully but it did't take ip and can't ping the module. I have done proper connection between stm32f407 and LAN8720. So can you help me. If you have sample code with same lan8720a module kindly share with me.

    ethernetif_input(&gnetif);

    sys_check_timeouts();

    Thanks,
    Yesh Valiya

  8. Андрей:

    Здравствуйте! Прошу подскажите:
    Пытаюсь запустить ethernet по rmii на nucleo-f207zg, делаю по примеру проект в кубе, настраиваю сетевые параметры, прописываю
    /* USER CODE BEGIN 3 */
    ethernetif_input(&gnetif);
    sys_check_timeouts();
    }
    Собираю проект, загружаю, дебагером смотрю — код выполняется.
    При пинге вижу — заданный узел не доступен, адрес PHY ставил и 1 и 0. Джамперы проверил, тактирование тоже.
    Спасибо!

    • Александр:

      Здравствуйте, подскажите нашли ли вы в чем была проблема?
      При прохождении видео уроков наткнулся на эту же проблему и никак не могу решить.

  9. mansour:

    Dear Friends,
    Hi,
    I am using LAN8720 module, STM32f407zgt6 , STMCUBE and KEIL5. I have set the PHY address 0 and 1 and I have followed all the instructions mentioned in this post but it didn’t ping the module. Can you please tell a solution?

  10. Антон:

    Здравствуйте, прошу прощения за, возможно глупый вопрос, но почему именно такие настройки TCP?

  11. Привет владельцам STM32H7!
    Сегодня для Вас бесценная информация!
    Наверняка у Вас ничего не пингуется?

    Включите D-кэш! Без него LWIP не хочет работать. За одно и I-кэш тоже.
    Phy адрес можно не настраивать, он по умолчанию 0 если контакт RXER микросхемы подтянут к земле.
    Так же *возможно* Вам понадобится изменить настройки на компьютере если вы подключаете плату к нему напрямую, нужно использовать статический адрес 192.168.1.2 маску и шлюз как в примере. Это находится в настройках подключения по локальной сети Windows.
    Так же включить глобальные прерывания для ETH и в GPIO settings выставить всем выводам кроме ETH_MDC скорость very_high.
    В параметрах LWIP выбрать драйвер LAN8742.
    Кроме того в Key Options установите на всякий случай флажки напротив LWIP_MULTICAST_PING и LWIP_BROADCAST_PING.
    Настраивайте MPU (нужно определить эти настройки в CubeMX или просто поставить инициализацию в начале main) следующим образом:

    void MPU_Config(void)
    {
    MPU_Region_InitTypeDef MPU_InitStruct = {0};

    /* Disables the MPU */
    HAL_MPU_Disable();
    /** Initializes and configures the Region and the memory to be protected
    */
    MPU_InitStruct.Enable = MPU_REGION_ENABLE;
    MPU_InitStruct.Number = MPU_REGION_NUMBER0;
    MPU_InitStruct.BaseAddress = 0x30040000;
    MPU_InitStruct.Size = MPU_REGION_SIZE_16KB;
    MPU_InitStruct.SubRegionDisable = 0x0;
    MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
    MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    /** Initializes and configures the Region and the memory to be protected
    */
    MPU_InitStruct.Enable = MPU_REGION_ENABLE;
    MPU_InitStruct.Number = MPU_REGION_NUMBER1;
    MPU_InitStruct.BaseAddress = 0x30044000;
    MPU_InitStruct.Size = MPU_REGION_SIZE_16KB;
    MPU_InitStruct.SubRegionDisable = 0x0;
    MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
    MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    /** Initializes and configures the Region and the memory to be protected
    */
    MPU_InitStruct.Enable = MPU_REGION_ENABLE;
    MPU_InitStruct.Number = MPU_REGION_NUMBER2;
    MPU_InitStruct.BaseAddress = 0x30040000;
    MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
    MPU_InitStruct.SubRegionDisable = 0x0;
    MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
    MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    /* Enables the MPU */
    HAL_MPU_Enable(MPU_HFNMI_PRIVDEF);

    }

    Теперь контроллер начнет корректно принимать пакеты. Чтобы он начал корректно их передавать, я не нашел пока ничего лучше чем сделать так: найдите функцию low_level_output в ethernetif.c и поменяйте на это:

    static err_t low_level_output(struct netif *netif, struct pbuf *p)
    {
    uint32_t i=0, framelen = 0;
    struct pbuf *q;
    err_t errval = ERR_OK;
    ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT];

    memset(Txbuffer, 0 , ETH_TX_DESC_CNT*sizeof(ETH_BufferTypeDef));

    SCB_DisableDCache(); // Disable Instruction-cache at beginning of function

    for(q = p; q != NULL; q = q->next)
    {
    if(i >= ETH_TX_DESC_CNT)
    return ERR_IF;

    Txbuffer[i].buffer = q->payload;
    Txbuffer[i].len = q->len;
    framelen += q->len;

    if(i>0)
    {
    Txbuffer[i-1].next = &Txbuffer[i];
    }

    if(q->next == NULL)
    {
    Txbuffer[i].next = NULL;
    }

    i++;
    }

    TxConfig.Length = framelen;
    TxConfig.TxBuffer = Txbuffer;

    HAL_ETH_Transmit(&heth, &TxConfig, ETH_DMA_TRANSMIT_TIMEOUT);

    SCB_EnableDCache(); // Enable Instruction-cache at end of function

    return errval;
    }

    Еще в настройках проекта Keil, IRAM2 поставить только область памяти которая начинается с 0x24000000. Это чтобы буфер TX на отправку не образовывался в области памяти D-cash.

    Возможно, некоторые из этих действий можно не производить. Пожалуйста, пишите в комментариях если у Вас есть какие-нибудь сведения.

    Вот теперь можно компилировать и прошивать!
    После прошивки не забудьте перезагрузить устройство.
    Приятного изучения!

    • Max77:

      Михаил не поделитесь ли вы своим сэмплом под stm32h7? Я сделал всё как вы описали даже компилироваться не стал в кубе. Попробую в кейле проделать, но что-то я не уверен в успешном запуске.

    • Ivan:

      Я тоже «счастливый» владелец STM32H745Zit6 на плате Nucleo-H745ZI-Q, так что немного дополню. Будет неплохо, если кто-то, кто тоже добъется результата, сообщит об этом. Все это действительно сложные вещи, и будет неплохо помочь друг другу 🙂
      Пробовал вариант Михаила в среде Stm32CubeIde. К сожалению, не заработало.
      НО в среде Keil должно работать.
      В итоге я взял за основу это решение:
      https://github.com/AnielShri/STM32H745_Ethernet/blob/master/Documentation/lwip_nortos.md
      В принципе, здесь даны практически те же настройки, что и у Михаила, однако:
      — Здесь предполагается режим DHCP;
      — GPIO работают на низкой скорости, нужно проверить, возможно это влияет на общую скорость передачи данных;
      — Выключены прерывания ETH.
      Принципиально важно настроить работу памяти, как сказано у Михаила для Keil или в статье на гитхабе (файл STM32H745ZITX_FLASH.ld ). Без этого работать не будет.
      В STM32H745 на ядре CM-7 все отлично заработало. Тактовая ядра CM7 — 480 МГц, CM4 — 240 МГц.
      МК включен в порт роутера, фаерволл отключен, при помощи программы Wireshark и фильтра «arp» я отслеживал процесс установки IP-адреса для МК. Дальше пинговал МК, он отзывается.

  12. William Poveda:

    Я пытался сделать то, что вы сказали мне, у меня STM32H743ZI, но у меня не было хороших результатов, карта не появляется, пинг не отвечает. Можете ли вы поделиться своим кодом или как вы подключились, пожалуйста?

  13. Pavel:

    Всем привет. Если у кого-то не пингуется отладочная плата «STM32 — E407» — то проверьте следующие факты:
    1. На ней lan-разъем сидит на других ножках МК. По умолчанию CUBE подключает не те ножки.
    2. В настройках параметров адаптера пропишите в свойствах IP версии 4 следующие настройки: IP адрес 192.168.1.194 и маску подсети 255.255.255.0

  14. lev:

    Здравствуйте, какие версии MX и FW Вы использовали для этого урока?
    С уважением

  15. Роман:

    Плата не завелась (не пинговалась) с фиксиованным ip адресом. При переключении на получение адреса от DHSP сервера, все заработало. Спасибо автору!

  16. Роман:

    Забыл написать. Отладочная плата stm32_f4ve — плата Ethernet LAN8720.

  17. Serg:

    С фиксированным IP куб не инитит массивы в файле lwip.c
    uint8_t IP_ADDRESS[4];
    uint8_t NETMASK_ADDRESS[4];
    uint8_t GATEWAY_ADDRESS[4];
    проинициализировал и начало отвечать на ping
    uint8_t IP_ADDRESS[4] = {192,168,1,100};
    uint8_t NETMASK_ADDRESS[4] = {255,255,255,0};
    uint8_t GATEWAY_ADDRESS[4] = {192,168,1,1};
    недостаток, при новой генерации кода необходимо заново инициализировать, но можно и выкрутиться

  18. Александр:

    Здравствуйте. Во-первых, хочу поблагодарить вас за ваши труды, очень полезная информация. Во-вторых, хочу спросить вас в чем может быть ошибка:
    Проделал вторую часть урока до конца, мучился долго, но все отладил, кроме последнего момента, где мы посылаем запрос на соединение через UART6. В программе Wireshark вижу запрос на синхронизацию, но как понял, ответа от компа не получен и посылается попытка [TCP Retransmission]. В чем может быть проблема? Настройки сети?

    121 159.764463000 169.254.5.230 169.254.5.228 TCP 60 52032 > dec-notes [SYN] Seq=0 Win=2048 Len=0 MSS=460
    122 162.764197000 169.254.5.230 169.254.5.228 TCP 60 [TCP Retransmission] 52032 > dec-notes [SYN] Seq=0 Win=2048 Len=0 MSS=460
    125 165.763840000 169.254.5.230 169.254.5.228 TCP 60 [TCP Retransmission] 52032 > dec-notes [SYN] Seq=0 Win=2048 Len=0 MSS=460

    • Александр:

      Отбой, разобрался с этим. Виндовс определил подключение как общественная сеть и брандмауэр блокировал подключения от программы nc. Отключение брандмауэра решило эту проблему

  19. Max77:

    Ошибку он выдаёт на эту функцию в ethernetif.c :
    HAL_ETH_Transmit(&heth, &TxConfig, ETH_DMA_TRANSMIT_TIMEOUT);

  20. SergeyA100:

    У меня со старта не поехало — и lan8720 и dp83848 оба модуля приехали с браком. Два дня рака мозгов — а с программной частью все было ок.

  21. DoanMai:

    Hello Narod.
    Thanks your for this lesson.
    Could you help me with this situation?
    I did the second part of lesson to the end. When the data be sended by UART6 and i got the response on Coolterm Display, But on the WireShark, i got nothing( Don't have any request or response). I'm using STM32F407VG and LAN8720.
    Thanks for your help!

  22. Aleksandr:

    здравствуйте,
    спасибо огромное за ваши уроки. Я прошу прощения за глупый вопрос, но где происхдит вызов функции, например как tcp_client_send(); и другие функции в net.c, в int main (), если там в цикле только
    while (1)
    {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    ethernetif_input(&gnetif);
    sys_check_timeouts();
    }
    /* USER CODE END 3 */

    }

  23. Vasil Boyanov:

    У кого падает в HardFault после выполнения mem->prev = 0;, поправьте в lwipopts.h
    #define LWIP_RAM_HEAP_POINTER 0x30044000
    на
    #define LWIP_RAM_HEAP_POINTER 0x2001C000
    Почему-то (в CubeMX) по-умолчанию ставит для H7 адрес, которого не существует в F4.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*