Stm32 Nuxleo board Ethernet cable

Введение

CubeMx (или уже обновлённая Cube IDE) предоставляет удобный интерфейс для библиотеки lwip: за несколько минут можно настроить полноценный сервер или клиент.

Неожиданная проблема возникает позже, когда у вас есть готовое отлаженное приложение, и вы решаете сделать мелкую серию: оказывается у всех устройств одинаковый MAC-адрес сетевого адаптера, и соответственно в одной сети они существовать не могут. Откуда брать уникальные мак-адреса?

Правильное решение

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

Даже если вы так поступили - далее возникает вопрос как на программном уровне обеспечить "распределение" этих адресов вашим устройствам. Об этом читайте ниже.

Менее правильное решение

Библиотека lwip: сама умеет генерировать случайные мак-адреса. Достаточно зайти в настройки и нажать "перегенерировать".

Проблема заключается в том, что CubeMX генерирует код, и мак-адрес вставляет прямо в код программы. Получается что готовая прошивка привязана к конкретной плате. Т.е. если у вас 10 плат, нужно сделать 10 прошивок с отличающимся адресом. А потом при каждом обновлении следить, чтобы новая прошивка была с таким же адресом, как и старая. Очень сомнительное решение, которое трудно поддерживать.

Правильно было бы хранить MAC-адрес в EEPROM, и загружать его оттуда при старте. При этом обеспечивать уникальность либо вручную (заводить файлик с таблицей уникальных адресов уже выпущенных плат, и с каждой новой платой прошивать последний адрес инкрементированный на 1). Либо же можно проверять EEPROM и если он забит нулями, значит это первый запуск, и мак-адрес можно генерировать рандомно (если менять последние 3 байта, то вероятность случайно получить два одинаковых мака - один на 16 миллионов. А вероятность того, что заказчик купит несколько плат, воткнёт их в одну сеть, и среди них окажутся повторяющиеся адреса - совсем близка к нулю).

Так же в EEPROM придётся записывать адреса даже если вы заплатите деньги, и получите официальный собственный диапазон MAC-адресов.

Очень правильное решение

Если вы разводите STM32 на своей плате, или подключаете к макетке Nucleo плату со своей периферией, вам не составит труда добавить на плату ещё одну микросхему за 100 р.

Это внешний EEPROM, внутри которого зашит уникальный MAC-адрес. Это решает сразу две проблемы: вы используете легальный, официально выделенный MAC-адрес  и отпадает всякая необходимость самим следить за уникальностью адресов и самим вшивать их в EEPROM при выпуске.

Microchip предлагает несколько вариантов для I2C, SPI или UNI/O. Микросхема 25AA02E48-I для SPI стоит в рознице от 50 до 200 р.

Хорошее решение "Для бедных"

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

У каждого STM32 есть уникальный 32-битный номер

Идея состоит в том, чтобы использовать уникальный 96 битный ID, вшитый в каждый STM32.

Номер состоит из ID подложки + координаты чипа на этом кристалле. Очень интересно какие байты в этом уникальном ID меняются и за что отвечают.

В офисе было несколько плат STM32F7 купленных в разное время: некоторые из одной партии, некоторый гарантированно из другой. Вот какие у них адреса:

20 00 23 00 12 51 : 39 30 31 36 35 33
2d 00 46 00 12 51 : 39 30 31 36 35 33
28 00 34 00 12 51 : 39 30 31 36 35 33
28 00 19 00 12 51 : 39 30 31 36 35 33
23 00 34 00 05 51 : 39 31 33 37 37 39
18 00 38 00 0b 51 : 38 34 35 36 34 33

Адреса в hex. Для удобства разделил их на 2 половины.

Очевидно, что последние 7 байт отвечают за ID кристалла, а первые 5 - за координаты чипа на кристалле. Очень интересно, что там есть нулевые байты, могу лишь предположить, что в других сериях STM32 эти координаты могут быть не нулевыми.

Как видите, больше всего меняется байты 0, 2, 4. Их мы и возьмём.

Меняем последние 3 байта мак-адреса

Мак-адрес состоит из 6 байт: первые 3 байта это ID производителя + 2 флага, последние 3 байта - это уникальный номер самого устройства. Вот их то мы и будем перезаписывать.

Как вычитать уникальный ID для STM32

В таблице представлены начальные адреса для уникального ID процессора. По этому адресу расположено 12 байт.

Серия процессор
STM32
Адрес начала уникального
96-битного ID
 STM32F0, STM32F3 0x1FFFF7AC
 STM32F1 0x1FFFF7E8
 STM32F2, STM32F4  0x1FFF7A10
 STM32L0, STM32L1 (Cat.1, Cat.2)  0x1FF80050
 STM32L1 (Cat.3, Cat.4, Cat.5, Cat.6)  0x1FF800D0

Ниже приведён код для чтения ID для STM32F7

#define STM32F7_UNIQUE_ID_START_ADDR 0x1FF0F420;
const uint8_t *idBytes = (const uint8_t*) STM32F7_UNIQUE_ID_START_ADDR;

Куда вставить код для перезаписи MAC-адреса сетевого адаптера STM32

Мак-адрес нужно менять при инициализации адаптера. Инициализация происходит в сгенерированном файле ethernetif.c:low_level_init().
После константного задания мак-адреса там должна быть секция от /* USER CODE BEGIN MACADDRESS */ до /* USER CODE END MACADDRESS */. Напомню, что свой код можно вставлять только в пользовательские секции, в противном случае при следующем запуске CubeMX при генерации кода из *.ioc проекта ваш код перезатрётся.

Что делать если в функции low_level_init() нет USER CODE BEGIN MACADDRESS? Пользовательская секция появилась там не так давно, в FW_F7 v1.16.1 пользовательская секция уже есть. Если у вас её нет - попробуйте установить последний Firmware Package для вашей серии процессора.

Финальный код будет выглядеть как-то так (автогенерированный код может быть другим):

static void low_level_init(struct netif *netif)
{
uint32_t regvalue = 0;
HAL_StatusTypeDef hal_eth_init_status;
/* Init ETH */
uint8_t MACAddr[6] ;
heth.Instance = ETH;
heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
heth.Init.Speed = ETH_SPEED_100M;
heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
heth.Init.PhyAddress = LAN8742A_PHY_ADDRESS;
MACAddr[0] = 0x04;
MACAddr[1] = 0x45;
MACAddr[2] = 0x26;
MACAddr[3] = 0x4B;
MACAddr[4] = 0x10;
MACAddr[5] = 0x33;
heth.Init.MACAddr = &MACAddr[0];
heth.Init.RxMode = ETH_RXINTERRUPT_MODE;
heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
/* USER CODE BEGIN MACADDRESS */

#define STM32F7_UNIQUE_ID_START_ADDR 0x1FF0F420;
const uint8_t *idBytes = (const uint8_t*) STM32F7_UNIQUE_ID_START_ADDR;
// Replace 3 last bytes of autogenerated MAC by 3 bytes
// from stm32 unique id (chosen 3 bytes that really
// unique among a devices of the same seria)
MACAddr[3] = idBytes[4];
MACAddr[4] = idBytes[2];
MACAddr[5] = idBytes[0];

/* USER CODE END MACADDRESS */
hal_eth_init_status = HAL_ETH_Init(&heth);

Фиолетовым выделен фрагмент который необходимо вставить.

Заключение

Я попытался охватить все возможные подходы к заданию уникального MAC-адреса. И по моему личному мнению и правильного и удобного среди них нет: у каждого метода есть какие-то недостатки. Надеюсь данная статья сэкономила хоть немного вашего времени.