Каталог товарів
Клієнту
Тема сайту:
+38 (066) 305-77-25
Наша адреса
Україна, тимчасово - доставка тільки Новою Поштою, УкрПоштою, МістПошта, ROZETKA Delivery
Телефони
Графік роботи
  • Пн-Пт: з 9 до 18
  • Сб: з 10 до 17
  • Нд: з 11 до 16
E-mail
Ми в соцмережах
Перейти до контактів
0 0
Каталог
Головна
Закладки
0
Порівняти
0
Контакти

Arduino працює — поки не перестає: 12 помилок, яких не бачить компілятор

Чи була корисною ця стаття?

Arduino Uno R3 · системна діагностика

Arduino працює — поки не перестає: 12 помилок, яких не бачить компілятор

Скетч може бути правильним, а пристрій — уже ні. Розбираємо, як Uno R3 перегріває стабілізатор, втрачає PWM, живиться через GPIO, переповнює буфери й залишає налаштування напівзаписаними — з прив’язкою до документації, схеми та конкретної версії Core.

Автор: Jazzzman Технічна редакція: Платформа: Uno R3 / ATmega328P / AVR Core 1.8.8

Стилізована плата Uno із зеленим кодом і п’ятьма прихованими зонами ризику: живленням, часом, таймерами, буфером та пам’яттю
Компілятор бачить скетч. Живлення, струмові петлі, спільні таймери та переповнені черги залишаються поза його полем зору.
Зміст статті

Де закінчується влада компілятора

Двигун стартує — плата перезавантажується. Після підключення Servo зникає PWM на іншому піні. Вимкнений модуль продовжує тьмяно жити через UART. Відкриття Serial Monitor на мить активує реле. А налаштування, які «надійно лежать в EEPROM», після невдалого вимкнення стають сумішшю двох версій.

У кожній із цих історій скетч може компілюватися без єдиного попередження. Компілятор перевіряє синтаксис, типи й частину статичних правил. Він не бачить опору спільного дроту GND, теплового опору плати, заряду внутрішнього конденсатора ADC, чужої конфігурації Timer1 або байтів, які прибувають у UART, поки loop() зайнятий своїми справами.

Тому корисно розрізняти чотири різні твердження: «компілюється», «запустилося на столі», «працює в гарантованих режимах» і «довго працює надійно». Між першим і четвертим — ціла інженерна професія.

Електрика

Струм, нагрів, просідання, шлях повернення.

Час

ISR, блокування, швидкість потоку, переповнення лічильника.

Ресурси

Таймери, PWM-канали, UART, TWI, SPI.

Пам’ять

Атомарність, SRAM, EEPROM і формат запису.

Сигнал

Рівні, фронти, pull-up, імпеданс джерела.

Карта п’яти прихованих бюджетів Arduino: електрика, час, апаратні ресурси, пам’ять і сигнал
П’ять бюджетів, які не перевіряє компілятор: електрика, час, апаратні ресурси, пам’ять і якість сигналу.

Електрика, якої не видно в коді

Живлення — це не число «5.00» на мультиметрі, а мережа зі струмом, опором, індуктивністю, теплом і короткими перехідними процесами.

Помилка 1«Uno приймає 12 В» не означає «від Uno можна живити все»

Міф: якщо 7–12 В рекомендовані для VIN або роз’єму живлення, то на 5V можна під’єднати сервопривід, реле й дисплей — стабілізатор якось розбереться.

На офіційній схемі Uno R3 п’ятивольтову шину від зовнішнього входу формує лінійний NCP1117. Перед ним у шляху від barrel jack також стоїть діод M7.[2] Лінійний стабілізатор не переносить зайву напругу в окрему баночку на потім. Різниця між входом і виходом переважно стає теплом.

Pheat ≈ (VREG_IN − 5 В) × IREG

Якщо саме на вході стабілізатора є 12 В, а весь струм через нього становить 0,2 А, розрахунок дає 1,4 Вт. За 7 В і тих самих 0,2 А — 0,4 Вт. Струм однаковий, але теплове навантаження відрізняється у 3,5 раза. Це розрахунок, а не прогноз температури конкретної плати: результат залежить від міді, навколишньої температури, повітря й корпусу.

NCP1117 має обмеження струму, компенсацію безпечної області й теплове вимкнення приблизно біля 175 °C, однак виробник прямо вимагає належного відведення тепла, щоб захист не активувався.[3] Thermal shutdown — це аварійний ремінь, а не штатний круїз-контроль.

Характерний слід: від USB пристрій працює, а від 12-вольтового адаптера через кілька хвилин починає перезапускатися; сервопривід смикається; дисплей гасне на піку; у закритому корпусі проблема приходить швидше.

Інженерне рішення: силове навантаження живити окремою гілкою; для великої різниці напруг використовувати відповідний імпульсний DC-DC; рахувати весь струм через регулятор, а не лише «струм модуля»; не подавати потужне навантаження з GPIO. Якщо регульовані 5 В заводяться безпосередньо на шину 5V, це обходить частину штатного вхідного захисту плати й потребує окремої перевірки джерел та зворотного живлення.

Для огляду класів понижувальних модулів є окремий матеріал «LM2596 проти всіх». Конкретний модуль усе одно потребує перевірки власного регулювання, охолодження й реального навантаження.

Arduino приймає 12 В. Але частину цих вольтів вона змушена буквально опалювати.

Помилка 2Спільна земля не означає однаковий нуль у кожній точці

Міф: GND Arduino з’єднаний із GND двигуна, отже проблема із землею виключена.

Доріжка, макетна шина і провід мають опір та індуктивність. Коли струм змінюється, між двома точками того самого «GND» виникає напруга:

V ≈ I × R + L × dI/dt

Microchip у рекомендаціях AVR042 пояснює, що навіть одночасне перемикання цілого порту AVR створює дуже короткі імпульси в сотні міліамперів. Довгі лінії живлення не встигають забезпечити їх; тому локальний розв’язувальний конденсатор має стояти близько до виводів живлення й замикати малу струмову петлю.[5] Пусковий струм двигуна або сервопривода додає до цієї історії значно більші й повільніші імпульси, індуктивний шум та падіння на спільному зворотному проводі.

Характерний слід: reset відбувається саме під час старту двигуна; ADC стрибає синхронно з рухом сервопривода; I²C зникає під час перемикання реле; великий конденсатор «лікує» симптом, але схема залишається чутливою до довжини дротів.

Інженерне рішення: вести силовий струм коротким і достатньо товстим шляхом до джерела; не пропускати повернення двигуна через тонку макетну шину логіки; з’єднати логічну й силову частини у визначеній опорній точці; поставити 100 нФ біля кожної цифрової мікросхеми та накопичувальну ємність біля драйвера; для котушки забезпечити коректне придушення індуктивного викиду. «Зірка» — не магічний символ, а спосіб не змішувати падіння від великих струмів із опорною напругою логіки.

Дві концептуальні схеми: розсіювання потужності лінійним стабілізатором і окремі зворотні провідники логіки та двигуна
Ліворуч — розрахункова різниця теплових втрат лінійного стабілізатора. Праворуч — чому струм двигуна не повинен повертатися до джерела тією самою тонкою ділянкою, що й логіка.

Це може бути не баг бібліотеки двигуна. У момент запуску змінилася сама точка, яку код вважав нулем.

Помилка 3Плата не перезавантажилася, але вже вийшла з гарантованого режиму

Міф: якщо brown-out reset не спрацював, живлення достатнє.

ATmega328P має залежність максимальної частоти від напруги. Datasheet задає лінію між 10 МГц при 2,7 В і 20 МГц при 4,5 В. Для 16 МГц лінійна інтерполяція дає приблизно:

V16 MHz ≈ 2,7 + (16 − 10) × (4,5 − 2,7) / (20 − 10) ≈ 3,78 В

Це розрахунок за офіційною speed-grade кривою, а не окремо гарантована таблична точка. Водночас ArduinoCore-avr 1.8.8 задає для Uno рецепт extended fuse 0xFD.[7] Молодші біти відповідають BODLEVEL=101: мінімальний, типовий і максимальний пороги 2,5; 2,7 і 2,9 В.[4]

Отже існує проміжок, у якому BOD уже не обов’язково тримає reset, але 16 МГц ще не лежать у повністю гарантованій області. Це не обіцянка помилки за 3,7 В. Це точніша й важливіша річ: виробник уже не гарантує штатну роботу для всіх екземплярів, температур і умов.

Інженерне рішення: узгодити мінімальну напругу, частоту й BOD як одну систему; тримати 5-вольтову шину з достатнім запасом; для низьковольтного живлення знижувати частоту; у власному пристрої розглянути вищий BOD, врахувавши його діапазон і вимоги до старту. Зміна fuse-бітів потребує ISP і уважності: невдалий рецепт може зробити звичайне завантаження недоступним.

Шкала напруги ATmega328P із межею близько 3,78 В для 16 МГц та діапазоном порога BOD 2,5–2,9 В
Для 16 МГц межа speed grade становить приблизно 3,78 В. За конфігурації BODLEVEL=101 поріг лежить у діапазоні 2,5–2,9 В, тому між цими обмеженнями існує зона без повної гарантії швидкодії.

Reset — не єдиний стан між «усе добре» й «живлення зникло».

Помилка 4Вимкнений модуль може живитися через дріт даних

Міф: VCC від’єднано, отже пристрій гарантовано знеструмлений.

Для звичайних I/O ATmega328P допустимий абсолютний діапазон напруги обмежений приблизно від GND − 0,5 В до VCC + 0,5 В. Еквівалентна структура входу містить захисні шляхи до шин живлення.[4] Якщо цільова плата вимкнена, а інший активний пристрій подає HIGH на її GPIO, струм може піти в VCC цілі. Це називають back-powering або фантомним живленням.

Той самий клас проблеми виникає в UART, SPI та I²C, якщо pull-up лишається під напругою, а один учасник шини вимкнений. Конкретна напруга на VCC залежить від навантаження, захисної структури й усієї схеми, тому універсальні «там завжди буде 2 В» не мають сенсу.

Характерний слід: вимкнений модуль тьмяно світиться; порядок увімкнення двох плат змінює запуск; вимкнений пристрій навантажує лінію; батарея розряджається швидше; після повного знеструмлення проблема зникає.

Інженерне рішення: перевести джерело сигналу у високоімпедансний стан до вимкнення приймача; узгодити послідовність живлення; застосувати шинний ключ, буфер або перетворювач рівнів із документованим відсіканням при вимкненому живленні (power-off isolation / Ioff); за потреби розривати лінію апаратно. Послідовний резистор може обмежити паразитний струм, але не робить електричного роз’єднання.

Про межі 3,3/5 В і те, чому HIGH одного пристрою може бути забороненою напругою для іншого, докладніше розповідають «Таємниці логічних рівнів».

Дріт із назвою TX не перестає бути шляхом струму лише тому, що ми думаємо про нього як про дані.

Помилка 5Serial Monitor може дістатися реле раніше за setup()

Міф: безпечний стан записаний у першому рядку setup(), тому до команди реле не ввімкнеться.

На схемі Uno R3 сигнал DTR від USB-UART пов’язаний із RESET через конденсатор 100 нФ.[2] Arduino окремо документує, що Uno й Mega без native USB автоматично перезавантажуються, коли послідовний порт відкривається.[6]

Під час апаратного reset GPIO стають входами з високим імпедансом. Потім виконується bootloader, і лише після нього керування отримує скетч. Отже навіть найперший рядок setup() приходить після короткого, але цілком реального проміжку, коли виконавчий вихід контролює не програма, а зовнішня схема.

Для active-LOW входу правильний порядок у коді прибирає зайвий імпульс під час переходу піна в OUTPUT:

constexpr uint8_t RELAY_PIN = 7;
constexpr uint8_t RELAY_OFF = HIGH;

void setup()
{
  digitalWrite(RELAY_PIN, RELAY_OFF); // спочатку вихідний latch
  pinMode(RELAY_PIN, OUTPUT);         // потім драйвер піна
}

Але цей порядок не закриває reset-вікно. Безпечний стан критичного механізму задають зовнішнім pull-up/pull-down, входом enable, інвертором або силовою архітектурою, в якій floating GPIO означає OFF.

Концептуальний шлях фантомного живлення через захист GPIO і часова послідовність DTR, RESET, bootloader та setup
Ліворуч активна лінія даних подає струм у знеструмлену VCC через захисну структуру входу. Праворуч порт відкривається, плата скидається, а setup() отримує керування лише після апаратного reset і bootloader.

setup() не виконується під час reset. Для критичного виходу це не дрібна деталь, а межа відповідальності між кодом і схемою.

Ресурси, черги й стани, яких не видно у функціях

Назви Servo, analogRead() і Serial виглядають незалежними. Усередині вони ділять три таймери, один ADC, обмежені буфери та 8-бітний процесор.

Помилка 6Два способи втратити PWM: бібліотека забрала таймер або Core від’єднав канал

Міф: Servo генерує імпульси лише на вибраному піні й не впливає на PWM деінде.

ATmega328P має три апаратні таймери. Вони не знають назв бібліотек — лише регістри, лічильники, prescaler і вихідні канали. Servo 1.3.0 на Uno використовує Timer1, переводить його в normal counting mode і задає prescaler 8.[8] На Uno Timer1 одночасно обслуговує PWM D9/D10. Функція tone() використовує Timer2, до якого прив’язані D3/D11.[9]

Карта спільних таймерів Arduino Uno R3
ТаймерСистемна або бібліотечна рольPWM-виводи UnoТиповий конфлікт
Timer0millis(), micros(), системний часD5, D6Пряме переналаштування таймера спотворює час і PWM
Timer1Servo на AVRD9, D10Servo.attach() переналаштовує Timer1
Timer2tone()D3, D11Тон і PWM ділять той самий таймер

Найкраща частина цієї пастки — сервопривід може бути підключений, скажімо, до D6. Timer1 все одно використовується бібліотекою, а PWM зникає на D9/D10. Конфлікт стався не між дротами, а всередині MCU.

Другий механізм ще менш очевидний. У AVR Core 1.8.8 і digitalWrite(), і digitalRead() викликають turnOffPWM() для вибраного timer-backed pin.[10]

void checkPwmPin()
{
  analogWrite(9, 128);
  int state = digitalRead(9); // на AVR Core від'єднає PWM саме від D9
  (void)state;
}

Це не зупиняє весь Timer1, але апаратний PWM на цьому каналі більше не керує піном. Наступний analogWrite() може під’єднати його знову.

Інженерне рішення: перед об’єднанням бібліотек вести карту таймерів; перевіряти вихідний код саме зафіксованої версії, а не лише API; не читати пін апаратного PWM «для перевірки», якщо модуляція має залишатися активною; після сторонньої бібліотеки перевіряти конфігурацію спільного ресурсу. Основи коефіцієнта заповнення (duty cycle) і фільтрації PWM винесені в окрему статтю про ШІМ (PWM).

Карта таймерів Arduino Uno: Timer0 для часу й PWM D5/D6, Timer1 для Servo та D9/D10, Timer2 для tone та D3/D11
На Uno функції з різними назвами можуть ділити той самий таймер. Окремо digitalRead() або digitalWrite() на PWM-піні від’єднує відповідний апаратний PWM у AVR Core.

Бібліотека — це не лише набір C++-функцій. Іноді це новий господар апаратного таймера.

Помилка 7ADC «пам’ятає» попередній канал, бо конденсатор не встиг перезарядитися

Міф: analogRead() — ідеальний вольтметр із нескінченним вхідним опором.

Усередині ADC ATmega328P є sample-and-hold конденсатор приблизно 14 пФ. Після перемикання мультиплексора джерело сигналу повинно зарядити його до нової напруги за обмежений час. Datasheet рекомендує вихідний опір джерела близько 10 кОм або менше.[4]

У подільника важлива не сума двох резисторів, а опір Тевенена, який бачить ADC. Для 10 кОм + 10 кОм це 5 кОм. Для 1 МОм + 1 МОм — уже 500 кОм. Другий варіант не лише заряджає внутрішню ємність значно повільніше, а й стає чутливішим до струмів витоку та забруднення плати.

Після переходу з каналу з однією напругою на високоомний канал з іншою перший результат може містити залишковий вплив попереднього заряду. Це не таємний цифровий зв’язок між A0 й A1. Це звичайна аналогова схема, якій не дали встановитися.

Інженерне рішення: зменшити еквівалентний опір джерела; додати біля входу конденсатор, врахувавши його вплив на швидкість сигналу; використати буферний операційний підсилювач; збільшити час вибірки (acquisition time) або знизити швидкість ADC; після перемикання виконати пробне перетворення лише там, де цього справді потребують імпеданс і точність.

Правило «завжди читайте двічі» занадто грубе: воно витрачає час, маскує завеликий вихідний імпеданс джерела й не гарантує встановлення другого відліку. Зміна джерела опорної напруги також потребує часу, а зовнішній AREF не можна бездумно поєднувати з внутрішнім джерелом.

Базову логіку ADC, розрядність і опорну напругу розібрано в матеріалі «Перекладачі реальності: АЦП та ЦАП».

Мультиплексор ADC заряджає 14-пікофарадний конденсатор швидко від низькоомного та повільно від високоомного джерела
Після перемикання каналу джерело має перезарядити приблизно 14-пФ sample-and-hold конденсатор. Високий еквівалентний опір джерела збільшує час встановлення й залишає вплив попередньої напруги.

ADC вимірює датчик через маленький конденсатор. Якщо джерело слабке, попередній канал іще встигає сказати останнє слово.

Помилка 8volatile повідомляє компілятору про зміни, але не замикає дані

Міф: змінну змінює interrupt — достатньо додати volatile.

volatile забороняє компілятору вважати значення стабільним між доступами. Але ATmega328P — 8-бітний процесор. Читання 16- або 32-бітної змінної потребує кількох операцій, і ISR може оновити її між окремими байтами. Основний код отримає гібрид двох моментів.

AVR-LibC прямо показує, що volatile не робить багатобайтовий доступ атомарним, і надає ATOMIC_BLOCK для короткої критичної секції.[11] Сам Arduino Core читає 32-бітний timer0_millis зі збереженням SREG і коротким вимкненням переривань.[12]

#include <util/atomic.h>

volatile uint32_t pulseCount = 0;

uint32_t readPulseCount()
{
  uint32_t snapshot;

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    snapshot = pulseCount;
  }

  return snapshot;
}

ATOMIC_RESTORESTATE важливий: він відновлює попередній стан переривань, а не безумовно вмикає їх наприкінці.

Друга помилка — використовувати ISR як другий loop(). Звичайний AVR ISR виконується з вимкненими глобальними перериваннями. У ньому не слід чекати delay(), оновлювати дисплей, друкувати довгі повідомлення або запускати повільне опитування датчика. ISR має зафіксувати критичний стан, збільшити лічильник чи поставити прапорець і завершитися.

volatile — записка для компілятора, а не навісний замок для спільної змінної.

Помилка 9На 115200 бод у Serial є лише кілька мілісекунд запасу

Міф: байти почекають, поки loop() закінчить delay(), а потім парсер усе прочитає.

У стандартній конфігурації HardwareSerial для Uno RX- і TX-масиви мають по 64 байти. Але кільце head/tail залишає одну позицію порожньою, щоб розрізняти «порожньо» й «повно», тому програмний RX-ring зберігає 63 байти.[13]

У форматі 8N1 один байт займає приблизно 10 біт. Для порожнього software buffer і безперервного потоку маємо:

Розрахунковий час для 63 байтів RX
ШвидкістьРозрахунокЧас
9600 бод63 × 10 / 9600≈65,6 мс
115200 бод63 × 10 / 115200≈5,47 мс

Апаратні регістри USART додають ще невеликий запас, а реальний потік може мати паузи. Проте порядок величини незмінний: двадцятимілісекундне блокування легко довше за всю програмну чергу на 115200. Коли кільце повне, новий байт не доходить до парсера, тому збільшення тайм-ауту вже нічого не поверне.

Serial.flush() теж не є гумкою для RX. У сучасній реалізації він чекає завершення вихідної передачі.[13]

Інженерне рішення: читати потік при кожній можливості; замінювати довгі блокування на скінченний автомат; обмежувати довжину кадру; мати поле довжини, номер послідовності й контроль цілісності; відокремлювати тайм-аут протоколу від факту переповнення локальної черги.

Високий baud rate швидше передає дані — і так само швидше забирає час на реакцію.

Шини — це одночасно протокол, електрика й реалізація Core

Навіть правильна адреса I²C або режим SPI не компенсує надто сильні pull-up, змішані шини живлення, замалу чергу чи апаратний SS у неправильному стані.

Помилка 10Чотири I²C-модулі можуть додати не лише чотири адреси, а й вісім резисторів

Міф: SDA і SCL з’єднуються паралельно, тому кількість готових модулів електрично неважлива.

I²C використовує двонапрямні open-drain/open-collector лінії. Пристрої активно тягнуть SDA/SCL до LOW, а HIGH створює pull-up до позитивної шини. Швидкість фронту визначають опір підтягування й сумарна ємність; рівень HIGH визначає напруга тієї шини.[14]

Припустімо, кожна з чотирьох плат має по 4,7 кОм на SDA і SCL, і всі резистори під’єднані до однієї напруги:

Req = 4700 / 4 = 1175 Ом

Розрахунковий LOW-струм становить приблизно 2,81 мА при 3,3 В і 4,26 мА при 5 В, якщо знехтувати ненульовим VOL. Специфікація I²C наводить 3 мА при VOL = 0,4 В як важливий Standard/Fast-mode орієнтир, але точну здатність стягування треба брати з datasheet кожного учасника.[14] Менший опір прискорює фронт, але збільшує LOW-струм — «більше pull-up» не завжди краще.

Якщо частина готових модулів підтягує лінії до 3,3 В, а частина до 5 В, формула 1175 Ом узагалі непридатна: резистори утворюють шлях між двома шинами живлення. Для 3,3-вольтової плати без толерантності до 5 В це вже ризик пошкодження, а не просто дивна форма фронту.

Є й програмна межа. У Wire/TWI для AVR Core буфери мають 32 байти. Це обмеження конкретної реалізації, навіть якщо периферійний чип здатен передати довший блок. Механізм тайм-ауту також вимкнений, доки програма явно не викличе setWireTimeout().[15]

#include <Wire.h>

void setup()
{
  Wire.begin();
  Wire.setWireTimeout(25000, true); // 25 мс — приклад, не універсальна константа
}

Timeout дозволяє вийти з частини очікувань і, за обраним параметром, скинути TWI hardware. Він не прибирає неправильні pull-up, шум, зворотне живлення або пристрій, який фізично продовжує тримати SDA в LOW.

Інженерне рішення: визначити одну контрольовану мережу pull-up; перевірити, до якої напруги підключений кожен модуль; прибрати зайві резистори; рахувати ємність і час наростання фронту; знижувати частоту за довгої шини; ділити передачі за розміром буфера; мати протокол відновлення після тайм-ауту.

Основи адресації та ролей шин дивіться в окремому матеріалі «I²C, SPI, UART — що це таке».

Кожен новий I²C-модуль може принести власний датчик, власні pull-up і власне уявлення про те, якою має бути напруга HIGH.

Помилка 11«Невикористаний» D10 може вимкнути SPI master

Міф: якщо peripheral chip-select розташований на D7, пін D10 до SPI не має стосунку.

В ATmega328P апаратний SS — це PB2, тобто D10 на Uno. Якщо SPI працює як master, але SS налаштований як вхід і отримує LOW, апаратний біт MSTR очищається, а SPI переходить у slave mode.[4]

SPI.begin() захищає від цього: спочатку встановлює вихідний latch SS у HIGH, потім робить SS виходом і вмикає master.[16] Проблема повертається, якщо подальший код або стороння бібліотека знову виконує pinMode(10, INPUT).

#include <SPI.h>

void setup()
{
  digitalWrite(SS, HIGH); // спочатку latch, без короткого LOW
  pinMode(SS, OUTPUT);
  SPI.begin();
}

Робочий CS периферії може бути на D7, D8 чи іншому GPIO. D10 все одно повинен залишатися OUTPUT, якщо Uno є SPI master.

Для кількох пристроїв потрібен ще один рівень дисципліни: окремий CS кожному, неактивні CS у правильному стані й SPI.beginTransaction() із частотою, bit order та mode конкретного пристрою. Бібліотека, яка мовчки змінює SPI mode без транзакції, створює інший різновид «містики».

SPI може втратити звання master через пін, який у схемі нібито ні до чого не підключений.

Три картки прихованих ресурсів Uno: 63-байтове RX-кільце UART, pull-up і 32-байтовий Wire для I²C, апаратний SS D10 для SPI
У UART обмеженням стає черга, в I²C — одночасно електрика pull-up і 32-байтовий Wire, а в SPI апаратний SS на D10 зберігає значення навіть за іншого робочого CS.

Помилки, які накопичуються

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

Помилка 12EEPROM переживає вимкнення живлення, але окремий запис — не обов’язково

Міф: EEPROM.put() зберігає структуру як одну неподільну операцію.

Для EEPROM ATmega328P заявлено щонайменше 100 000 циклів write/erase за наведених умов, а обсяг Uno становить 1 КБ.[4] Це не дата гарантованої смерті комірки, але дуже корисний проєктний бюджет.

Коли вичерпується гарантована кількість 100 000 циклів однієї комірки
Частота записуРозрахунковий часЩо це означає
10 разів/с≈2,78 годиниЗапис у кожному швидкому loop() неприйнятний
1 раз/с≈27,78 годиниНавіть «лише раз на секунду» — агресивно
1 раз/хв≈69,44 добиДля довгого ресурсу потрібна інша стратегія

EEPROM.update() справді зменшує знос: байт записується лише тоді, коли значення змінилося. Але EEPROM.put() просто проходить об’єкт байт за байтом і для кожного використовує update-семантику.[17] Транзакції тут немає.

Якщо живлення зникне посеред структури, частина полів може бути новою, частина старою, а багатобайтове поле — змішаним. Низька напруга під час EEPROM-операції окремо згадується в datasheet як умова ризику пошкодження даних.[4]

Надійніша схема: два або більше слотів і явне підтвердження завершення запису (commit).

  1. Залишити найновіший правильний слот недоторканим.
  2. У слоті для нового запису спочатку встановити маркер INVALID.
  3. Записати номер послідовності, версію формату, довжину, дані (payload) і CRC.
  4. Прочитати запис назад і перевірити формат та CRC.
  5. Записати VALID останньою операцією.
  6. Під час старту вибрати найновіший слот із правильними маркером, довжиною, версією та CRC.

CRC виявляє пошкодження, але не робить EEPROM атомарною. Sequence визначає новіший слот, version дозволяє змінити формат, а кілька слотів дають частковий wear leveling. Для довготривалого формату краще явно серіалізувати поля, а не вважати raw layout C++-структури вічним контрактом: padding і формат можуть змінитися.

Послідовність надійного EEPROM-запису: старий активний слот збережено, новий слот INVALID, дані й CRC, перевірка та VALID останнім
Старий правильний слот не чіпають, доки новий не записано й не перевірено. Commit-маркер VALID з’являється останнім; під час старту вибирають найновіший запис із правильними маркером і CRC.

EEPROM зберігає байти без живлення. Вона не обіцяє, що ваші двадцять байтів записалися одночасно.

П’ять запитань до проєкту, який «іноді глючить»

Електрика

Де тече піковий струм? Який шлях повернення? Скільки ват розсіює регулятор?

Час

Скільки мілісекунд код не читає потік? Що виконується в ISR? Чи безпечний rollover?

Ресурси

Хто володіє Timer0/1/2, UART, ADC, TWI та SPI? Чи змінює їх бібліотека?

Пам’ять

Чи атомарні спільні дані? Який максимум SRAM? Чи має EEPROM маркер завершення й перевірку?

Сигнал

Які рівні HIGH/LOW, шини pull-up, вихідний імпеданс, фронти й стани без живлення?

З чого почати перевірку за характерним симптомом
СимптомПерший бюджетПерші питання
Reset під час старту мотораЕлектрикаПік струму, LDO, спільний зворотний шлях, розв’язувальні конденсатори, BOD
PWM зник після нової бібліотекиРесурсиTimer1/Timer2, відповідність пінів, turnOffPWM()
Перший ADC-відлік дивнийСигналRth, перемикання mux, час вибірки, AREF
Serial губить частину довгого пакетаЧас63-байтове RX-кільце, блокування, формат кадру, номер послідовності
I²C погіршився після додавання модулівСигналКількість pull-up, шини живлення, ємність, 32-байтовий Wire
SPI ламається після іншої ініціалізаціїРесурсиРежим D10/SS, стани CS, налаштування транзакцій SPI
Налаштування зіпсувалися після знеструмленняПам’ятьДва слоти, commit-маркер, CRC, номер послідовності, BOD
Вимкнений модуль не зовсім вимкненийЕлектрикаАктивні GPIO, шина pull-up, Ioff, порядок живлення
  • Назвіть точну плату, MCU, версію Core й версії бібліотек.
  • Складіть таблицю таймерів, буферів, GPIO-режимів і напруг підтягування.
  • Порахуйте найбільший очікуваний струм і розсіювання, а не лише типове споживання.
  • Визначте апаратний стан кожного небезпечного виходу під час reset і вимкнення живлення.
  • Для постійних даних визначте формат, маркер завершення запису, CRC, версію та стратегію зносу.

Головний діагностичний поворот простий: замість «Arduino глючить» поставити питання «який бюджет порушено?». Після цього містика зазвичай швидко перетворюється на конкретний провід, регістр, байт або часовий інтервал.

Короткі відповіді

Чому компілятор не може попередити про більшість цих помилок?

Він не знає реальної схеми, опору дротів, струму мотора, напруги pull-up, температури плати, порядку живлення чи потоку байтів із зовнішнього пристрою. Частина конфліктів також виникає лише після того, як бібліотеки під час виконання переналаштують спільні регістри.

Чи можна навантажувати GPIO Uno струмом 40 мА?

40 мА — absolute maximum для виводу ATmega328P, не нормальний робочий режим. Arduino наводить 20 мА на I/O, а надійний дизайн враховує також просідання рівнів, групові суми портів, загальні 200 мА absolute maximum і потрібний запас.

Чи треба завжди викликати analogRead() двічі?

Ні. Додаткове перетворення іноді дає більше часу після перемикання каналу, але не виправляє довільно високий вихідний імпеданс і не гарантує потрібну точність. Спочатку треба оцінити опір Тевенена, час вибірки, струм витоку й опорну напругу.

Чи достатньо volatile для лічильника з ISR?

Для видимості змін компілятору — так. Для атомарного snapshot 16- або 32-бітного значення на 8-бітному AVR — ні. Потрібна коротка критична секція, наприклад ATOMIC_BLOCK(ATOMIC_RESTORESTATE).

Чи всі висновки стосуються будь-якої Arduino?

Ні. Електричні принципи загальні, але номери таймерів, буфери, типи, напруга GPIO, ADC, DAC, reset і реалізації API залежать від MCU, плати та Core. У цій статті точна платформа — Uno R3 з ATmega328P.

Технічні джерела

Числові й залежні від реалізації твердження прив’язані до офіційних документів та вихідного коду зафіксованих версій. Типові значення, гарантовані межі й абсолютно допустимі межі (absolute maximum) не змішуються.

  1. Arduino UNO Rev3 — Tech Specs5 В, 16 МГц, 2 КБ SRAM, 1 КБ EEPROM, рекомендовані 7–12 В на вході та 20 мА на I/O.
  2. Arduino A000066 — UNO Rev3 schematicNCP1117, M7, шлях USB/зовнішнього живлення та зв’язок DTR–RESET через 100 нФ.
  3. onsemi NCP1117/D datasheetТепловий опір, обмеження струму, компенсація безпечної області та теплове вимкнення приблизно біля 175 °C.
  4. Microchip DS40002061A — ATmega48A/PA/88A/PA/168A/PA/328/P datasheetЕлектричні межі, залежність частоти від VCC, BOD, ADC, апаратний SS, EEPROM і захисні структури GPIO.
  5. Microchip AVR042 — power routing and decouplingКороткі імпульси струму, локальне розв’язування живлення та рекомендації щодо малих струмових петель.
  6. Arduino Help Center — If your board runs the sketch twiceАвтоматичний reset плат UNO/Mega під час відкриття послідовного порту.
  7. ArduinoCore-avr 1.8.8 — boards.txtЧастота Uno й рецепт fuse-бітів bootloader, зокрема extended fuse 0xFD.
  8. Servo 1.3.0 — AVR timer selectionВибір Timer1; Servo.cpp задає його ініціалізацію та дільник 8.
  9. ArduinoCore-avr 1.8.8 — Uno timer mappingD5/D6 належать Timer0, D9/D10 — Timer1, D3/D11 — Timer2; Tone.cpp вибирає Timer2 для стандартної AVR-цілі.
  10. ArduinoCore-avr 1.8.8 — wiring_digital.cdigitalRead() і digitalWrite() викликають turnOffPWM() для пінів, пов’язаних із таймером.
  11. AVR-LibC — atomic and non-atomic code blocksВідмінність між volatile та атомарним доступом, використання ATOMIC_BLOCK.
  12. ArduinoCore-avr 1.8.8 — wiring.cСистемний час Timer0 і захищене читання 32-бітного timer0_millis.
  13. ArduinoCore-avr 1.8.8 — HardwareSerialТипові 64-байтові масиви; логіка приймання RX резервує одну позицію, а HardwareSerial.cpp визначає flush() для завершення TX.
  14. NXP UM10204 — I²C-bus specification and user manualТопологія open-drain/open-collector, pull-up, час наростання, ємність і контекст струму стягування.
  15. ArduinoCore-avr 1.8.8 — Wire/TWI32-байтові буфери й API тайм-ауту; twi.c ініціалізує тайм-аут нулем.
  16. ArduinoCore-avr 1.8.8 — SPI.cppПорядок встановлення latch/режиму SS та ініціалізація master.
  17. ArduinoCore-avr 1.8.8 — EEPROM.hПобайтовий update() і шаблонний put().
  18. ArduinoCore-avr 1.8.8 — utility macrosПовторне обчислення аргументів макросів; WMath.cpp визначає цілочисельний map(), а WString.cpp — поведінку виділення пам’яті.
  19. Arduino UNO R4 WiFi datasheet32-бітний RA4M1, 14-бітний ADC і DAC до 12 бітів.
  20. Arduino Nano 33 BLE Sense Rev2 datasheetI/O на 3,3 В і пряме попередження про відсутність толерантності до 5 В.
  21. AVR-LibC — standard integer typesint16_t визначено як signed int, що підтверджує 16-бітний int у стандартній реалізації AVR.
Повернутися до початку ↑

© 2026 Мій Проект. Автор: Jazzzman. Використання матеріалів дозволено лише з посиланням на джерело.

Рекомендовані товари