Arduino Uno R3 · системна діагностика
Arduino працює — поки не перестає: 12 помилок, яких не бачить компілятор
Скетч може бути правильним, а пристрій — уже ні. Розбираємо, як Uno R3 перегріває стабілізатор, втрачає PWM, живиться через GPIO, переповнює буфери й залишає налаштування напівзаписаними — з прив’язкою до документації, схеми та конкретної версії Core.
Зміст статті
Де закінчується влада компілятора
Двигун стартує — плата перезавантажується. Після підключення Servo зникає PWM на іншому піні. Вимкнений модуль продовжує тьмяно жити через UART. Відкриття Serial Monitor на мить активує реле. А налаштування, які «надійно лежать в EEPROM», після невдалого вимкнення стають сумішшю двох версій.
У кожній із цих історій скетч може компілюватися без єдиного попередження. Компілятор перевіряє синтаксис, типи й частину статичних правил. Він не бачить опору спільного дроту GND, теплового опору плати, заряду внутрішнього конденсатора ADC, чужої конфігурації Timer1 або байтів, які прибувають у UART, поки loop() зайнятий своїми справами.
Тому корисно розрізняти чотири різні твердження: «компілюється», «запустилося на столі», «працює в гарантованих режимах» і «довго працює надійно». Між першим і четвертим — ціла інженерна професія.
Струм, нагрів, просідання, шлях повернення.
ISR, блокування, швидкість потоку, переповнення лічильника.
Таймери, PWM-канали, UART, TWI, SPI.
Атомарність, SRAM, EEPROM і формат запису.
Рівні, фронти, pull-up, імпеданс джерела.
Електрика, якої не видно в коді
Живлення — це не число «5.00» на мультиметрі, а мережа зі струмом, опором, індуктивністю, теплом і короткими перехідними процесами.
Помилка 1«Uno приймає 12 В» не означає «від Uno можна живити все»
Міф: якщо 7–12 В рекомендовані для VIN або роз’єму живлення, то на 5V можна під’єднати сервопривід, реле й дисплей — стабілізатор якось розбереться.
На офіційній схемі Uno R3 п’ятивольтову шину від зовнішнього входу формує лінійний NCP1117. Перед ним у шляху від barrel jack також стоїть діод M7.[2] Лінійний стабілізатор не переносить зайву напругу в окрему баночку на потім. Різниця між входом і виходом переважно стає теплом.
Якщо саме на вході стабілізатора є 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» виникає напруга:
Microchip у рекомендаціях AVR042 пояснює, що навіть одночасне перемикання цілого порту AVR створює дуже короткі імпульси в сотні міліамперів. Довгі лінії живлення не встигають забезпечити їх; тому локальний розв’язувальний конденсатор має стояти близько до виводів живлення й замикати малу струмову петлю.[5] Пусковий струм двигуна або сервопривода додає до цієї історії значно більші й повільніші імпульси, індуктивний шум та падіння на спільному зворотному проводі.
Характерний слід: reset відбувається саме під час старту двигуна; ADC стрибає синхронно з рухом сервопривода; I²C зникає під час перемикання реле; великий конденсатор «лікує» симптом, але схема залишається чутливою до довжини дротів.
Інженерне рішення: вести силовий струм коротким і достатньо товстим шляхом до джерела; не пропускати повернення двигуна через тонку макетну шину логіки; з’єднати логічну й силову частини у визначеній опорній точці; поставити 100 нФ біля кожної цифрової мікросхеми та накопичувальну ємність біля драйвера; для котушки забезпечити коректне придушення індуктивного викиду. «Зірка» — не магічний символ, а спосіб не змішувати падіння від великих струмів із опорною напругою логіки.
Це може бути не баг бібліотеки двигуна. У момент запуску змінилася сама точка, яку код вважав нулем.
Помилка 3Плата не перезавантажилася, але вже вийшла з гарантованого режиму
Міф: якщо brown-out reset не спрацював, живлення достатнє.
ATmega328P має залежність максимальної частоти від напруги. Datasheet задає лінію між 10 МГц при 2,7 В і 20 МГц при 4,5 В. Для 16 МГц лінійна інтерполяція дає приблизно:
Це розрахунок за офіційною 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 і уважності: невдалий рецепт може зробити звичайне завантаження недоступним.
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.
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]
| Таймер | Системна або бібліотечна роль | PWM-виводи Uno | Типовий конфлікт |
|---|---|---|---|
| Timer0 | millis(), micros(), системний час | D5, D6 | Пряме переналаштування таймера спотворює час і PWM |
| Timer1 | Servo на AVR | D9, D10 | Servo.attach() переналаштовує Timer1 |
| Timer2 | tone() | 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).
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 вимірює датчик через маленький конденсатор. Якщо джерело слабке, попередній канал іще встигає сказати останнє слово.
Помилка 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 і безперервного потоку маємо:
| Швидкість | Розрахунок | Час |
|---|---|---|
| 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, і всі резистори під’єднані до однієї напруги:
Розрахунковий 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 через пін, який у схемі нібито ні до чого не підключений.
Помилки, які накопичуються
Деякі відмови не приходять під час першого запуску. Вони терпляче чекають тисячного запису або вимкнення живлення в незручний байт.
Помилка 12EEPROM переживає вимкнення живлення, але окремий запис — не обов’язково
Міф: EEPROM.put() зберігає структуру як одну неподільну операцію.
Для EEPROM ATmega328P заявлено щонайменше 100 000 циклів write/erase за наведених умов, а обсяг Uno становить 1 КБ.[4] Це не дата гарантованої смерті комірки, але дуже корисний проєктний бюджет.
| Частота запису | Розрахунковий час | Що це означає |
|---|---|---|
| 10 разів/с | ≈2,78 години | Запис у кожному швидкому loop() неприйнятний |
| 1 раз/с | ≈27,78 години | Навіть «лише раз на секунду» — агресивно |
| 1 раз/хв | ≈69,44 доби | Для довгого ресурсу потрібна інша стратегія |
EEPROM.update() справді зменшує знос: байт записується лише тоді, коли значення змінилося. Але EEPROM.put() просто проходить об’єкт байт за байтом і для кожного використовує update-семантику.[17] Транзакції тут немає.
Якщо живлення зникне посеред структури, частина полів може бути новою, частина старою, а багатобайтове поле — змішаним. Низька напруга під час EEPROM-операції окремо згадується в datasheet як умова ризику пошкодження даних.[4]
Надійніша схема: два або більше слотів і явне підтвердження завершення запису (commit).
- Залишити найновіший правильний слот недоторканим.
- У слоті для нового запису спочатку встановити маркер
INVALID. - Записати номер послідовності, версію формату, довжину, дані (payload) і CRC.
- Прочитати запис назад і перевірити формат та CRC.
- Записати
VALIDостанньою операцією. - Під час старту вибрати найновіший слот із правильними маркером, довжиною, версією та CRC.
CRC виявляє пошкодження, але не робить EEPROM атомарною. Sequence визначає новіший слот, version дозволяє змінити формат, а кілька слотів дають частковий wear leveling. Для довготривалого формату краще явно серіалізувати поля, а не вважати raw layout C++-структури вічним контрактом: padding і формат можуть змінитися.
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) не змішуються.
- Arduino UNO Rev3 — Tech Specs5 В, 16 МГц, 2 КБ SRAM, 1 КБ EEPROM, рекомендовані 7–12 В на вході та 20 мА на I/O.
- Arduino A000066 — UNO Rev3 schematicNCP1117, M7, шлях USB/зовнішнього живлення та зв’язок DTR–RESET через 100 нФ.
- onsemi NCP1117/D datasheetТепловий опір, обмеження струму, компенсація безпечної області та теплове вимкнення приблизно біля 175 °C.
- Microchip DS40002061A — ATmega48A/PA/88A/PA/168A/PA/328/P datasheetЕлектричні межі, залежність частоти від VCC, BOD, ADC, апаратний SS, EEPROM і захисні структури GPIO.
- Microchip AVR042 — power routing and decouplingКороткі імпульси струму, локальне розв’язування живлення та рекомендації щодо малих струмових петель.
- Arduino Help Center — If your board runs the sketch twiceАвтоматичний reset плат UNO/Mega під час відкриття послідовного порту.
- ArduinoCore-avr 1.8.8 — boards.txtЧастота Uno й рецепт fuse-бітів bootloader, зокрема extended fuse
0xFD. - Servo 1.3.0 — AVR timer selectionВибір Timer1; Servo.cpp задає його ініціалізацію та дільник 8.
- ArduinoCore-avr 1.8.8 — Uno timer mappingD5/D6 належать Timer0, D9/D10 — Timer1, D3/D11 — Timer2; Tone.cpp вибирає Timer2 для стандартної AVR-цілі.
- ArduinoCore-avr 1.8.8 — wiring_digital.c
digitalRead()іdigitalWrite()викликаютьturnOffPWM()для пінів, пов’язаних із таймером. - AVR-LibC — atomic and non-atomic code blocksВідмінність між
volatileта атомарним доступом, використанняATOMIC_BLOCK. - ArduinoCore-avr 1.8.8 — wiring.cСистемний час Timer0 і захищене читання 32-бітного
timer0_millis. - ArduinoCore-avr 1.8.8 — HardwareSerialТипові 64-байтові масиви; логіка приймання RX резервує одну позицію, а HardwareSerial.cpp визначає
flush()для завершення TX. - NXP UM10204 — I²C-bus specification and user manualТопологія open-drain/open-collector, pull-up, час наростання, ємність і контекст струму стягування.
- ArduinoCore-avr 1.8.8 — Wire/TWI32-байтові буфери й API тайм-ауту; twi.c ініціалізує тайм-аут нулем.
- ArduinoCore-avr 1.8.8 — SPI.cppПорядок встановлення latch/режиму SS та ініціалізація master.
- ArduinoCore-avr 1.8.8 — EEPROM.hПобайтовий
update()і шаблоннийput(). - ArduinoCore-avr 1.8.8 — utility macrosПовторне обчислення аргументів макросів; WMath.cpp визначає цілочисельний
map(), а WString.cpp — поведінку виділення пам’яті. - Arduino UNO R4 WiFi datasheet32-бітний RA4M1, 14-бітний ADC і DAC до 12 бітів.
- Arduino Nano 33 BLE Sense Rev2 datasheetI/O на 3,3 В і пряме попередження про відсутність толерантності до 5 В.
- AVR-LibC — standard integer types
int16_tвизначено як signedint, що підтверджує 16-бітнийintу стандартній реалізації AVR.
Написати коментар