Кассовый узел для пекарни уехал к заказчику и не подключился к сети: имя Wi-Fi у меня было зашито в скетче, а точку доступа на объекте переименовали. Ночная перепрошивка на удалённой площадке — дорогая цена за одно поле в конфиге. После такого я записываю SSID и пароль в EEPROM при первом запуске: один раз вбил с монитора порта — и плата подключается сама. Только WiFi.begin просит на вход char* — не String и не число, а честный массив байтов, который надо уложить в килобайт памяти и не задеть соседей.
Число в EEPROM кладётся байтами — механика разобрана в уроке про int и float. Строка устроена так же, только байтов много и нужен признак конца. Их два: байт длины перед символами — так делает урок про String в EEPROM — и нуль-терминатор: байт 0 сразу после последнего символа. Для Wi-Fi-конфига я беру терминатор, и вот почему: библиотеки ждут C-строку с нулём на конце, пересобирать её в промежуточный буфер перед каждым подключением не придётся.
Дальше по порядку: считаем, сколько байт просит Wi-Fi-блок, пишем три строки с защитой от свежей памяти, читаем обратно с лимитом и вписываем весь блок в карту раздела — со 128-го адреса начинается журнал, и до него надо дожить без наезда. Три поля, три константы и один лимит — вся наука; остальное дисциплина, и ниже видно, где она экономит вечера.
Сколько байт просит Wi-Fi
Стандарт IEEE 802.11 ограничивает SSID тридцатью двумя октетами: длиннее имя сети физически не существует, точка доступа такое не раздаст. Под имя хватает массива из 33 байт — 32 символа и терминатор. Пароль жёстче: WPA2-PSK принимает от 8 до 63 ASCII-символов, а вариант ровно в 64 символа — это тот же ключ в hex-записи. Отводю поле в 64 байта: обрезка чужого пароля ломает подключение тихо и без сообщений, а два сэкономленных байта в тысяче ничего не покупают. Третье поле — подпись устройства, короткое имя платы вроде kotel-01: 16 байт с запасом.
Складываем: 33 плюс 64 плюс 16 — 113 байт. В карте из урока про 1 КБ памяти под настройки отведены адреса 0-63, блок строк туда не влезает — и не надо: карта не догма, а перегородки, которые двигают под проект. Моя раскладка: SSID с нулевого адреса, пароль с 33-го, подпись с 97-го, блок заканчивается на 112-м. Журнал по-прежнему начинается со 128 — между ними пятнадцать байт зазора, куда встанет расширение парольного поля, если когда-нибудь понадобится.
Адреса я сразу записываю константами в одном месте скетча — приёмы собраны в уроке про адресное пространство; здесь зафиксирую только границы: 0, 33, 97 и конец блока на 113. Дальше этот план превратится в таблицу адресов целого проекта в финальном уроке.
#include <EEPROM.h> // Блок строк Wi-Fi: адреса 0..112, журнал начинается со 128const int A_SSID = 0; // 33 байта: до 32 символов + терминаторconst int A_PASS = 33; // 64 байта: пароль с терминаторомconst int A_SIGN = 97; // 16 байт: подпись платыconst int A_END = 113; // первый адрес за блоком void saveText(int addr, const char *text, int field) { int n = 0; while (text[n] != 0 && n < field - 1) { // field-1: место для терминатора EEPROM.update(addr + n, text[n]); n++; } EEPROM.update(addr + n, 0); // терминатор пишется последним} int loadText(int addr, char *buf, int field) { int n = 0; while (n < field - 1) { // лимит страхует от мусора 255 byte b = EEPROM.read(addr + n); if (b == 0) break; buf[n] = b; n++; } buf[n] = 0; return n; // сколько символов прочитали} void setup() { Serial.begin(9600); saveText(A_SSID, "DACHA-NET-2G", 33); saveText(A_PASS, "parol-ot-soseda-123", 64); saveText(A_SIGN, "kotel-01", 16); char ssid[33]; int len = loadText(A_SSID, ssid, 33); Serial.print("ssid="); Serial.println(ssid); Serial.print("dlina="); Serial.println(len); Serial.print("konec bloka: "); Serial.println(A_END);} void loop() { // подключение к сети и работа; строки уже в памяти}Пишем и читаем без сюрпризов
Первая грабля свежей платы: в пустых ячейках лежит 255, ни одного нуля во всём килобайте. Сканирование до терминатора уедет за границу поля и соберёт мусор из соседних адресов — на экране окажется пароль из трёхсот символов. Поэтому у моего чтения всегда второй предохранитель: жёсткий лимит на число символов, field-1. Дошли до лимита — остановились, терминатор поставили сами, а прочитанное пометили как подозрительное: например, сразу записали заводское значение.
Вторая грабля — порядок записи. Символы пишу по одному через EEPROM.update, а терминатор — самым последним: оборвётся питание посреди записи, и чтение увидит либо старую строку, либо недописанную без нуля, которую отсечёт лимит. Тот же приём с последним байтом использует и байт длины в уроке про String — защита одинаковая, отличается только маркер конца. Update вместо write экономит ресурс ячеек, арифметика износа — в уроке про update. На запись всех 113 байт уходит около 0,4 секунды процессора — по 3,3 мс на байт на AVR, и в это время не отвечают ни кнопки, ни экран: конфиг пишут по событию, а не в каждом проходе loop.
В модели нажмите D2: скетч поочерёдно пишет короткое имя DACHA-NET-2G и длинное DlinnoeImyaSetiDom на восемнадцать символов. Длинное обрежется на пятнадцатом — лимит поля в демонстрации взят меньше настоящего, чтобы кольцо клеток было видно глазами, — и терминатор встанет на шестнадцатую ячейку. Печать карты показывает адреса подряд: код символа, код символа, ноль, нули. Именно этот столбик адресов и есть ваш план памяти, только в миниатюре. Нули после терминатора — хвост поля, который на живой плате update просто не станет перезаписывать.
На реальной плате то же делают saveText и loadText из скетча выше. Если строка приходит с монитора порта, ввод собирается через Serial: приёмы разбора чисел — в уроке про parseInt и числа, строки там же читаются readStringUntil по коду разделителя — удобно принять SSID и пароль одним подключением.
| Формат строки | Байт на поле 32 символа | Совместимость | Переполнение |
|---|---|---|---|
| Байт длины | 33 (1 + 32) | строку надо пересобирать для WiFi.begin | длина правится одним байтом |
| Нуль-терминатор | 33 (32 + 0) | char* сразу для WiFi.begin и strlen | лимит при записи, терминатор резервируется |
| Фиксированная ширина без конца | 32 | ни с чем не совместима | хвост читается мусором соседнего поля |
| Терминатор плюс байт длины | 34 | обе проверки сразу: лимит и ноль | лишний байт, зато чтение дважды защищено |
Практика, которая спасла одну командировку: в блоке лежит вторая пара SSID и пароль — резервная сеть того же объекта. Адреса считаются той же арифметикой: 113 байт под основной блок, 113 под запасной, журнал при этом переезжает со 128 на 256, и свободной зоны остаётся меньше — за расширение платят адресами журнала, поэтому решать его стоит до прошивки. В моём случае запасной сетью оказалась точка доступа телефона сторожа: плата перебирает имена по очереди и подключается к тому, что живо.
Второй приём — подтверждение записи вслух. После saveText скетч читает поле обратно loadText-ом и печатает длину: если вбили SSID по Bluetooth и в поле легло пять символов вместо двадцати, вы увидите это сразу, а не в следующий приезд. Новых функций для проверки не нужно — чтение с лимитом уже написано.
И про strlen: библиотечная функция честно посчитает символы до нуля, но ничего не знает о вашем лимите — она доверяет памяти, а память после сбоя этого не заслуживает. В рабочих скетчах я всегда беру собственную loadText: по нулю видно, что поле пустое, по лимиту — что строка обрезана, а значение между ними говорит, что запись цела.
- 1. Сколько байт занимает поле SSID с нуль-терминатором?
- 2. Как в терминаторном формате ищется конец строки при чтении?
- 3. Плата свежая, поле ещё не писали, в ячейках 255. Что вернёт чтение без лимита?
- 4. Почему терминатор пишется последним?
- 5. Какой пароль примет точка доступа с WPA2-PSK?
- SSID живёт в 33 байтах: 32 символа по стандарту IEEE 802.11 плюс терминатор
- Пароль WPA2-PSK — от 8 до 63 ASCII-символов или 64 hex; поле 64 байта с запасом
- Конец строки — байт 0, пишется последним; чтение всегда с лимитом против мусора 255
- Блок Wi-Fi весит 113 байт: SSID 0-32, пароль 33-96, подпись 97-112, журнал со 128
- WiFi.begin принимает char* — терминаторный формат совместим без пересборки строки
Частые вопросы по теме урока
- Сколько байт занимает поле SSID с нуль-терминатором?
- IEEE 802.11 ограничивает SSID 32 октетами, а строке нужен байт 0 на конце — итого 33 ячейки. Проверяйте это поле первым: имя сети — самая частая строка в EEPROM. 33 — 32 символа плюс терминатор
- Как в терминаторном формате ищется конец строки при чтении?
- Скан идёт до первого нуля. Лимит field-1 нужен как предохранитель: на свежей плате нулей нет вообще, и без лимита чтение уедет в чужие адреса. По байту 0 сразу после символов
- Плата свежая, поле ещё не писали, в ячейках 255. Что вернёт чтение без лимита?
- 255 — это не конец строки, а единицы свежих ячеек. Сканирование пройдёт всё поле и полезет дальше, собрав байты соседних зон. Отсюда же правило: границы полей не двигают между прошивками без миграции. Мусор до конца EEPROM: ни одного нуля в памяти нет
- Почему терминатор пишется последним?
- Пока терминатора нет, чтение с лимитом увидит недописанную строку и отсечёт её. Запись нуля последним делает строку готовой к чтению в один момент — обрыв посреди поля не оставит её без конца. Обрыв питания посреди записи не оставит строку без конца
- Какой пароль примет точка доступа с WPA2-PSK?
- Стандарт даёт диапазон 8-63 ASCII-символа, а 64 символа — это тот же ключ в hex-записи. Под оба варианта хватает поля в 64 байта — именно поэтому его не урезают до 33. От 8 до 63 ASCII-символов или 64 hex