Почему printf () плохо для отладки встроенных систем?

16

Я думаю, это плохая вещь, чтобы попытаться отладить проект на основе микроконтроллера с помощью printf().

Я могу понять, что у вас нет предопределенного места для вывода, и что оно может потреблять ценные выводы. В то же время я видел, как люди используют вывод UART TX для вывода на терминал IDE с пользовательским DEBUG_PRINT()макросом.

tarabyte
источник
12
Кто тебе сказал, что это плохо? «Обычно не самый лучший» - это не то же самое, что неквалифицированный «плохой».
Спехро Пефхани
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 мог бы прочитать. Конечно, лучше, чем смотреть на места ОЗУ по одному в отладчике или эмуляторе ,

Я не думаю, что это бесполезная стрелка в колчане, но ее следует использовать экономно, наряду с gdb или другими отладчиками, эмуляторами, логическими анализаторами, осциллографами, инструментами статического анализа кода, инструментами покрытия кода и так далее.

Спехро Пефхани
источник
3
Большинство printf()реализаций не являются поточно-ориентированными (то есть не входящими), что не является убийцей сделок, а является чем-то, что следует иметь в виду при использовании в многопоточной среде.
JRobert
1
@JRobert поднимает хороший вопрос ... и даже в среде без операционной системы трудно сделать много полезной прямой отладки ISR. Конечно, если вы выполняете printf () или математические операции с плавающей запятой в ISR, такой подход, вероятно, отключен.
Спехро Пефхани
@JRobert Какие инструменты отладки есть у разработчиков программного обеспечения, работающих в многопоточной среде (в аппаратной среде, где использование логических анализаторов и осциллографов нецелесообразно)?
Мин Чан
1
В прошлом я катал свой собственный потокобезопасный printf (); использовал босоногие эквиваленты put () или putchar () для выдачи очень кратких данных на терминал; хранит двоичные данные в массиве, который я выгружал и интерпретировал после запуска теста; использовал порт ввода / вывода для мигания светодиода или для генерации импульсов для измерения времени с помощью осциллографа; выплюнуть цифру в цифру и цифру и измерять с помощью VOM ... Этот список настолько же длинен, как ваше воображение, и обратно так же велик, как ваш бюджет! :)
JRobert
19

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

Как говорили другие люди, в использовании этой техники нет ничего «плохого», но она, как и многие другие методы отладки, имеет свои ограничения. Если вы знаете и можете справиться с этими ограничениями, это может оказаться чрезвычайно удобным, чтобы помочь вам получить правильный код.

Встраиваемые системы имеют определенную непрозрачность, что, как правило, делает отладку небольшой проблемой.

Скотт Сейдман
источник
8
+1 за "встроенные системы имеют определенную непрозрачность". Хотя я боюсь, что это утверждение может быть понятным только тем, у кого есть приличный опыт работы со встроенными системами, оно дает хорошее, краткое резюме ситуации. Это близко к определению «встроенный» на самом деле.
njahnke
5

Есть две основные проблемы, с которыми вы столкнетесь при попытке использовать printfмикроконтроллер.

Во-первых, может быть затруднительно передать вывод на правильный порт. Не всегда. Но некоторые платформы сложнее других. Некоторые из файлов конфигурации могут быть плохо документированы, и может потребоваться много экспериментов.

Второе - память. Полноценная printfбиблиотека может быть БОЛЬШОЙ. Иногда вам не нужны все спецификаторы формата, и могут быть доступны специализированные версии. Например, stdio.h предоставляемый AVR содержит три разных printfразмера и функциональности.

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

У меня был случай, когда библиотека не была доступна, и у меня было минимальное количество памяти. Поэтому у меня не было выбора, кроме как использовать собственный макрос. Но использование printfили нет на самом деле является одним из того, что будет соответствовать вашим требованиям.

embedded.kyle
источник
Может ли downvoter объяснить, что в моем ответе неверно, чтобы я мог избежать моей ошибки в будущих проектах?
embedded.kyle
4

Чтобы добавить к тому, что Spehro Pefhany говорил о «чувствительных ко времени вещах»: давайте рассмотрим пример. Допустим, у вас есть гироскоп, из которого ваша встроенная система выполняет 1000 измерений в секунду. Вы хотите отладить эти измерения, поэтому вам нужно распечатать их. Проблема: их распечатка приводит к тому, что система слишком занята, чтобы считывать 1000 измерений в секунду, что приводит к переполнению буфера гироскопа, что приводит к считыванию (и печати) поврежденных данных. Итак, печатая данные, вы повредили данные, заставив вас думать, что при чтении данных возникает ошибка, а может, и нет. Так называемый гейзенбаг.

njahnke
источник
лол! Является ли «гейзенбаг» действительно техническим термином? Я предполагаю, что это связано с измерением состояния частиц и принципа
Гейзенбурга
3

Основная причина отказа отладки с помощью printf () заключается в том, что она обычно неэффективна, неадекватна и не нужна.

Неэффективно: printf () и kin используют много флэш-памяти и оперативной памяти относительно того, что доступно на небольшом микроконтроллере, но большая неэффективность заключается в реальной отладке. Изменение того, что регистрируется, требует перекомпиляции и перепрограммирования цели, что замедляет процесс. Он также использует UART, который вы могли бы использовать для выполнения полезной работы.

Недостаточно: есть только так много деталей, которые вы можете выводить через последовательный канал. Если программа зависает, вы не знаете точно, где, только последний завершенный вывод.

Нет необходимости: многие микроконтроллеры можно удаленно отлаживать. JTAG или проприетарные протоколы могут использоваться для приостановки процессора, просмотра регистров и оперативной памяти и даже изменения состояния работающего процессора без перекомпиляции. Вот почему отладчики, как правило, являются лучшим способом отладки, чем операторы печати, даже на ПК с огромным пространством и мощностью.

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

Theran
источник
Не могли бы вы использовать указатели функций, чтобы поиграть с тем, что регистрируется, и добавить целую кучу гибкости?
Скотт Сейдман
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 инструкций, поэтому она будет окупаться, если вызываться дважды ,

Supercat
источник
2

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

Кроме того, люди говорят, что «не используйте printf» или «stdio.h занимает много места», но не предоставили много альтернатив - встраиваемый. Kyle упоминает упрощенные альтернативы, и это именно то, что вы, вероятно, должны быть делать, как само собой разумеющееся на базовой встроенной системе. Базовая процедура вытеснения нескольких символов из UART может состоять из нескольких байтов кода.

Джон У
источник
Если ваш printf не происходит, вы узнали много нового о проблемах с вашим кодом.
Скотт Сейдман
Предполагая, что у вас есть только один printf, который может произойти, да. Но прерывания могут сработать сотни раз, когда для вызова чего-либо из UART требуется вызов printf ()
John U