Чому printf () поганий для налагодження вбудованих систем?


16

Я думаю, що це погано намагатися налагодити проект на основі мікроконтролера, використовуючи printf().

Я можу зрозуміти, що у вас немає заздалегідь заданого місця для виходу і що воно може споживати цінні шпильки. У той же час я бачив, як люди споживають штифт UART TX для виводу на термінал IDE за допомогою спеціального користуванняDEBUG_PRINT() макросу.


12
Хто вам сказав, що це погано? "Зазвичай не найкращий" - це не те, що некваліфікований "поганий".
Spehro Pefhany

6
Все це розмова про те, скільки є накладних витрат, якщо все, що вам потрібно зробити, це вивести повідомлення "Я тут", вам зовсім не потрібен printf, а лише рутина для надсилання рядка в UART. Це, плюс код для ініціалізації UART, ймовірно, знаходиться під 100 байтами коду. Якщо додати можливість вивести пару шістнадцяткових значень, це не збільшить його настільки сильно.
tcrosley

7
@ChetanBhargava - файли заголовків C зазвичай не додають код у виконуваний файл. Вони містять декларації; якщо в решті коду не використовуються речі, які оголошуються, код для цих речей не зв'язується. Якщо ви printf, звичайно, використовуєте весь код, необхідний для реалізації, printfпов'язаний з виконуваним файлом. Але це тому, що код використовував його, а не через заголовок.
Піт Бекер

2
@ChetanBhargava Вам навіть не потрібно включати <stdio.h>, якщо ви виконайте свій простий режим, щоб вивести рядок, як я описав (виводить символи в UART, поки не з'явиться '\ 0') '
tcrosley

2
@tcrosley Я думаю, що ця порада, ймовірно, суперечить, якщо у вас є хороший сучасний компілятор, якщо ви використовуєте printf у простому випадку без рядка формату gcc і більшість інших замінюють його на більш ефективний простий дзвінок, який робить багато, що ви описуєте.
Vality

Відповіді:


24

Я можу створити кілька недоліків використання printf (). Майте на увазі, що "вбудована система" може варіюватися від чогось, що має кілька сотень байтів програмної пам'яті, до повномасштабної системи, керованої на стійці до QNX, керованої RTOS, з гігабайтами оперативної пам’яті та терабайтами енергонезалежної пам’яті.

  • Це вимагає десь для надсилання даних. Можливо, у вас вже є налагодження або порт програмування, можливо, ви цього не зробите. Якщо ви цього не зробите (або той, який у вас не працює), це не дуже зручно.

  • Це не легка функція у всіх контекстах. Це може бути великою справою, якщо у вас є мікроконтролер, що має лише кілька K пам'яті, тому що з'єднання в printf може з'їсти 4K самостійно. Якщо у вас мікроконтролер 32K або 256K, це, мабуть, не проблема, не кажучи вже про наявність у вас великої вбудованої системи.

  • Виявляти певні типи проблем, пов’язаних з розподілом пам’яті або перериваннями, мало користі, і це може змінити поведінку програми, коли заяви включені чи ні.

  • Це досить марно для роботи з чутливими до часу речами. Вам буде краще з логічним аналізатором, осцилографом або аналізатором протоколів, або навіть тренажером.

  • Якщо у вас є велика програма, і вам доведеться багато разів перекомпілювати, коли ви змінюєте заяви printf і змінюєте їх, ви можете витратити багато часу.

Що це добре - це швидкий спосіб вивести дані заздалегідь заздалегідь, що кожен програміст на C знає, як використовувати - нульову криву навчання. Якщо вам потрібно виплюнути матрицю для фільтра Kalman, на якому ви налагоджуєте, можливо, було б добре виплюнути його у форматі, який міг би прочитати MATLAB. Безумовно, краще, ніж дивитись на місця RAM по черзі у відладчику чи емуляторі .

Я не вважаю це марною стрілкою в сагайдаку, але її слід використовувати ощадливо, поряд з gdb чи іншими відладчиками, емуляторами, логічними аналізаторами, осцилоскопами, інструментами статичного аналізу коду, засобами покриття коду тощо.


3
Більшість printf()реалізацій не є безпечними для потоків (тобто, без повторного вступу), що не є вбивцею угоди, а щось, про що слід пам’ятати, використовуючи його в багатопотоковому середовищі.
JRobert

1
@JRobert дає хороший результат .. і навіть у середовищі без операційної системи важко зробити багато корисних прямих налагоджень ISR. Звичайно, якщо ви займаєтеся printf () або математикою з плаваючою точкою в ISR, підхід, ймовірно, відключений.
Spehro Pefhany

@JRobert Які інструменти для налагодження мають розробники програмного забезпечення, що працюють у багатопотоковому середовищі (у апаратному налаштуванні, де використання Логічних Аналізаторів та Осцилоскопів не є практичним)?
Мінь Тран

1
Раніше я прокручував власну безпечну нитку printf (); використані босоніж ставить () або putchar () еквіваленти, щоб виплюнути дуже стислі дані на термінал; зберігаються бінарні дані в масиві, який я скидав та інтерпретував після запуску тесту; використовував порт вводу / виводу, щоб блимати світлодіод або генерувати імпульси, щоб зробити вимірювання часу за допомогою осцилографа; виплюньте число на D / A і виміряйте за допомогою VOM ... Список довгий, ніж ваша уява і навпаки такий же великий, як ваш бюджет! :)
JRobert

19

Окрім деяких інших точних відповідей, акт надсилання даних до порту зі швидкістю передачі даних в серійній передачі може бути просто повільним щодо вашого часу циклу та впливати на спосіб, що залишиться у вашій програмі (як і будь-яка налагодження процес).

Як розповідали інші люди, у використанні цієї методики немає нічого поганого, але це має, як і багато інших методи налагодження, свої обмеження. Поки ви знаєте і можете впоратися з цими обмеженнями, це допоможе вам правильно виправити код.

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


8
+1 для "вбудовані системи мають певну непрозорість". Хоча я побоююся, що це твердження може бути зрозумілим лише тим, хто має гідний досвід роботи з вбудованими, але це може зробити гарне, стисле підсумок ситуації. Це фактично близьке до визначення "вбудованого".
njahnke

5

Є дві основні проблеми, з якими ви зіткнетеся на спробі використання printfмікроконтролера.

По-перше, це може бути болем, щоб передати висновок у потрібний порт. Не завжди. Але деякі платформи складніше, ніж інші. Деякі файли конфігурації можуть бути слабо задокументовані, і може знадобитися багато експериментів.

Друге - це пам'ять. Повна роздута printfбібліотека може бути ВЕЛИКОЮ. Іноді вам не потрібні всі специфікатори формату, хоча і спеціалізовані версії можуть бути доступні. Наприклад, stdio.h наданий AVR містить три різних printfрозміри та функціональні можливості.

Оскільки повна реалізація всіх згаданих функцій стає досить великою, vfprintf()можна вибрати три різних смаку, використовуючи параметри лінкера. За замовчуванням vfprintf()реалізовано всі згадані функції, за винятком перетворень з плаваючою комою. Доступна мінімізована версія програми vfprintf(), яка реалізує лише основні засоби цілої та рядкової конверсії, але за #допомогою прапорів перетворення можна вказати лише додатковий параметр (ці прапорці аналізуються правильно із специфікації формату, а потім просто ігноруються).

У мене був екземпляр, коли не було бібліотеки, і я мав мінімальну пам'ять. Тож у мене не було іншого вибору, як використовувати користувацький макрос. Але використання printfчи ні - це дійсно одне з тих, що відповідатиме вашим вимогам.


Чи не могли б прохачі пояснити, що в моїй відповіді невірно, щоб я міг уникнути своєї помилки у майбутніх проектах?
embedded.kyle

4

Щоб додати те, що говорив Спехро Пефхані про "чутливі до часу речі": давайте для прикладу. Скажімо, у вас є гіроскоп, з якого ваша вбудована система проводить 1000 вимірювань в секунду. Ви хочете налагодити ці вимірювання, тому їх потрібно роздрукувати. Проблема: виведення їх з друку призводить до того, що система занадто зайнята для зчитування 1000 вимірювань в секунду, що призводить до переповнення буфера гіроскопа, що призводить до зчитування (та друку) пошкоджених даних. Отож, надрукувавши дані, ви пошкодили ці дані, змусивши вас подумати, що існує помилка в читанні даних, коли, можливо, насправді їх немає. Так званий heisenbug.


Лол! Чи справді "heisenbug" - це технічний термін? Я думаю, це стосується вимірювання стану частинок та принципу Гейзенбурга ...
Zeta.Investigator

3

Більшою причиною не налагодження в printf () є те, що він зазвичай неефективний, неадекватний та непотрібний.

Неефективні: printf () та kin використовують багато спалаху та оперативної пам’яті порівняно з наявними на невеликому мікроконтролері, але більша неефективність полягає у фактичній налагодженні. Зміна того, що реєструється, вимагає перекомпілювати та перепрограмувати ціль, що уповільнює процес. Він також використовує UART, який ви в іншому випадку можете використовувати для корисної роботи.

Неадекватний: Є лише стільки деталей, які можна вивести через послідовне посилання. Якщо програма висить, ви не знаєте, де саме, лише останній результат, який завершено.

Не потрібно: Багато мікроконтролерів можна віддалено налагодити. JTAG або власні протоколи можна використовувати для паузи процесора, зазирнути до регістрів і оперативної пам'яті і навіть змінити стан працюючого процесора без необхідності перекомпілювати. Ось чому налагоджувачі, як правило, є кращим способом налагодження, ніж друковані висловлювання, навіть на ПК з тоннами місця та потужності.

Прикро, що найпоширеніша платформа мікроконтролерів для новачків, Arduino, не має налагоджувача. AVR підтримує віддалену налагодження, але протокол debugWIRE Atmel є власницьким та недокументованим. Ви можете використовувати офіційну платформу розробників для налагодження з GDB, але якщо у вас є, ви, ймовірно, вже не надто переживаєте за Arduino.


Не могли б ви використовувати функціональні покажчики, щоб грати з тим, що реєструється, і додати цілу купу гнучкості?
Скотт Сейдман

3

printf () не працює самостійно. Він викликає багато інших функцій, і якщо у вас мало місця в стеці, ви, можливо, не зможете використовувати його для налагодження проблем, близьких до ліміту стеку. Залежно від компілятора і мікроконтролера, форматна рядок може також розміщуватися в пам'яті, а не посилатися на спалах. Це може суттєво доповнити, якщо ви приперте свій код твердженнями printf. Це велика проблема в середовищі Arduino - початківці, які використовують десятки чи сотні тверджень printf, раптом стикаються з, здавалося б, випадковими проблемами, оскільки вони перезаписують свою купу своїм стеком.


2
Хоча я вдячний за відгуки, які надає сама група, але мені та іншим було б корисніше, якби ті, хто не погоджувався, пояснили проблеми з цією відповіддю. Ми всі тут для того, щоб вчитися та ділитися знаннями.
Адам Девіс

3

Навіть якщо хочеться виплюнути дані на якусь форму консолі журналу, printfфункція, як правило, не дуже вдалий спосіб зробити це, оскільки їй потрібно вивчити рядок переданого формату та проаналізувати його під час виконання; навіть якщо код ніколи не використовує будь-якого специфікатора формату, окрім іншого %04X, контролеру, як правило, потрібно буде включити весь код, необхідний для розбору рядків довільного формату. Залежно від того, який саме контролер використовується, може бути набагато ефективніше використовувати код на кшталт:

void log_string(const char *st)
{
  int ch;
  do
  {
    ch = *st++;
    if (ch==0) break;
    log_char(ch);
  } while(1);
}
void log_hexdigit(unsigned char d)
{
  d&=15;
  if (d > 9) d+=7;
  log_char(d+'0');
}
void log_hexbyte(unsigned char b)
{ log_hexdigit(b >> 4); log_hexdigit(b); }
void log_hexi16(uint16_t s)
{ log_hexbyte(s >> 8); log_hexbyte(s); }
void log_hexi32(uint32_t i)
{ log_hexbyte(i >> 24); log_hexbyte(i >> 16); log_hexbyte(i >> 8); log_hexbyte(i); }
void log_hexi32p(uint32_t *p) // On a platform where pointers are less than 32 bits
{ log_hexi32(*p); }

На деяких мікроконтролерах PIC, log_hexi32(l)швидше за все, візьмуть 9 інструкцій, а можуть взяти 17 (якщо lє у другому банку), тоді як log_hexi32p(&l)би взяли 2. log_hexi32pСаму функцію можна було б записати приблизно 14 інструкцій, тож вона заплатила б себе, якщо дзвонить двічі .


2

Один момент, який жодна з інших відповідей не згадала: У базовому мікро (IE є лише головний цикл () і, можливо, пара запущених ISR в будь-який час, а не багатопотокова ОС), якщо він руйнується / зупиняється / отримує застрягши в циклі, ваша функція друку просто не відбудеться .

Крім того, люди сказали, що "не використовуйте printf" або "stdio.h займає багато місця", але не дається багато альтернативи - embedded.kyle згадує про спрощені альтернативи, і саме це ви, мабуть, повинні бути як звичайно, на базовій вбудованій системі. Основним розпорядником стискання декількох символів з UART може бути кілька байтів коду.


Якщо ваш printf не відбувається, ви багато дізналися про те, де ваш кодекс проблемний.
Скотт Сейдман

Якщо припустити, що у вас є лише один printf, який може статися, так. Але переривання може стріляти в сотні разів за час виклику printf (), щоб отримати що-небудь з UART
John U
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.