¿Por qué es malo printf () para depurar sistemas integrados?

16

Supongo que es algo malo tratar de depurar un proyecto basado en microcontrolador utilizando printf() .

Puedo entender que no tiene un lugar predefinido para la salida y que podría consumir pines valiosos. Al mismo tiempo, he visto a personas consumir un pin UART TX para enviar al terminal IDE con una macro DEBUG_PRINT() personalizada.

    
pregunta tarabyte

8 respuestas

24

Puedo encontrar algunas desventajas de usar printf (). Tenga en cuenta que el "sistema integrado" puede ir desde algo con unos pocos cientos de bytes de memoria de programa a un sistema QNX RTOS de montaje en rack completo con gigabytes de RAM y terabytes de memoria no volátil.

  • Se requiere algún lugar para enviar los datos. Tal vez ya tienes un Depuración o puerto de programación en el sistema, tal vez no lo haga. Si tu no (o el que tienes no funciona) no es muy útil.

  • No es una función liviana en todos los contextos. Esto podría ser un gran problema si tiene un microcontrolador con solo unos pocos K de memoria, porque enlazar en printf puede consumir 4K por sí solo. Si tiene un microcontrolador de 32K o 256K, probablemente no sea un problema, y mucho menos si tiene un gran sistema embebido.

  • Es de poca o ninguna utilidad para encontrar ciertos tipos de problemas relacionados con la asignación de memoria o las interrupciones, y puede cambiar el comportamiento del programa cuando se incluyen o no las declaraciones.

  • Es bastante inútil para lidiar con cosas sensibles al tiempo. Estaría mejor con un analizador lógico y un osciloscopio o un analizador de protocolo, o incluso un simulador.

  • Si tiene un programa grande y tiene que volver a compilar muchas veces a medida que cambia las declaraciones de printf y las cambia, puede perder mucho tiempo.

Para qué es bueno: es una forma rápida de generar datos de una forma preformateada para que cada programador de C sepa cómo usar la curva de aprendizaje de cero. Si necesita escupir una matriz para el filtro de Kalman que está depurando, podría ser bueno escupirlo en un formato que MATLAB pueda leer. Sin duda, es mejor que mirar las ubicaciones de la RAM una a la vez en un depurador o emulador. .

No creo que sea una flecha inútil en el carcaj, pero debe usarse con moderación, junto con gdb u otros depuradores, emuladores, analizadores lógicos, osciloscopios, herramientas de análisis de código estático, herramientas de cobertura de código, etc.

    
respondido por el Spehro Pefhany
18

Además de algunas otras respuestas precisas, el hecho de enviar datos a un puerto a velocidades en baudios en serie puede ser francamente lento con respecto a su tiempo de bucle, y tener un impacto en la forma en que el resto de su programa funciona (como puede CUALQUIER proceso de depuración).

Como otras personas le han dicho, no hay nada "malo" en el uso de esta técnica, pero sí, como muchas otras técnicas de depuración, tiene sus limitaciones. Siempre que sepa y pueda lidiar con estas limitaciones, puede ser una opción extremadamente conveniente para ayudarlo a que su código sea correcto.

Los sistemas integrados tienen una cierta opacidad que, en general, hace que la depuración sea un problema.

    
respondido por el Scott Seidman
5

Hay dos problemas principales con los que intentará usar printf en un microcontrolador.

Primero, puede ser una molestia canalizar la salida al puerto correcto. No siempre. Pero algunas plataformas son más difíciles que otras. Algunos de los archivos de configuración pueden estar mal documentados y puede ser necesaria mucha experimentación.

El segundo es la memoria. Una biblioteca printf completa puede ser GRANDE. A veces no necesita todos los especificadores de formato y pueden estar disponibles versiones especializadas. Por ejemplo, el stdio.h proporcionado por AVR contiene tres diferentes% code% de diferentes tamaños y funcionalidades.

  

Desde la implementación completa de todas las características mencionadas se convierte en   bastante grande, se pueden seleccionar tres sabores diferentes de printf   utilizando las opciones del enlazador. El valor predeterminado vfprintf() implementa todas las   Funcionalidad mencionada excepto conversiones de punto flotante. Un minimizado   La versión de vfprintf() está disponible que solo implementa lo muy básico.   recursos de conversión de enteros y cadenas, pero solo el vfprintf() adicional   La opción se puede especificar utilizando indicadores de conversión (estos indicadores se analizan   correctamente de la especificación de formato, pero luego simplemente se ignora).

Tuve una instancia en la que no había ninguna biblioteca disponible y tenía poca memoria. Así que no tuve más remedio que usar una macro personalizada. Pero el uso de # o no es realmente uno de los que se ajustarán a sus requisitos.

    
respondido por el embedded.kyle
4

Para agregar a lo que Spehro Pefhany estaba diciendo sobre "cosas sensibles al tiempo": tomemos un ejemplo. Digamos que tiene un giroscopio desde el cual su sistema integrado está tomando 1,000 mediciones por segundo. Desea depurar estas medidas, por lo que necesita imprimirlas. Problema: al imprimirlos, el sistema está demasiado ocupado para leer 1.000 mediciones por segundo, lo que hace que el búfer del giroscopio se desborde, lo que hace que los datos dañados se lean (e impriman). Y así, al imprimir los datos, ha corrompido los datos, lo que le hace pensar que hay un error al leer los datos cuando tal vez realmente no lo hay. Un llamado Heisenbug.

    
respondido por el njahnke
3

El motivo principal para no depurar con printf () es que generalmente es ineficiente, inadecuado e innecesario.

Ineficiente: printf () y los parientes usan una gran cantidad de flash y RAM en relación con lo que está disponible en un pequeño microcontrolador, pero la mayor ineficiencia se encuentra en la depuración real. Cambiar lo que se está registrando requiere volver a compilar y reprogramar el objetivo, lo que ralentiza el proceso. También utiliza un UART que de otro modo podría estar usando para hacer un trabajo útil.

Inadecuado: solo hay tantos detalles que puede generar a través de un enlace serial. Si el programa se cuelga, no sabe exactamente dónde, solo la última salida que se completó.

No es necesario: muchos microcontroladores se pueden depurar remotamente. JTAG o los protocolos propietarios se pueden usar para pausar el procesador, echar un vistazo a los registros y la RAM, e incluso alterar el estado del procesador en ejecución sin tener que recompilarlo. Esta es la razón por la que los depuradores son generalmente una mejor forma de depurar que las declaraciones impresas, incluso en una PC con mucho espacio y poder.

Es desafortunado que la plataforma de microcontroladores más común para los novatos, Arduino, no tenga un depurador. El AVR admite la depuración remota, pero el protocolo debugWIRE de Atmel es propietario y no está documentado. Puedes usar un tablero de desarrollo oficial para depurar con GDB, pero si lo tienes, probablemente ya no estés demasiado preocupado por Arduino.

    
respondido por el Theran
3

printf () no funciona por sí solo. Llama a muchas otras funciones, y si tiene poco espacio en la pila, es posible que no pueda utilizarla para depurar problemas cercanos al límite de su pila. Dependiendo del compilador y del microcontrolador, la cadena de formato también se puede colocar en la memoria, en lugar de hacer referencia a la memoria flash. Esto puede aumentar significativamente si salpica su código con declaraciones printf. Este es un gran problema en el entorno de Arduino: los principiantes que usan docenas o cientos de declaraciones de printf se enfrentan repentinamente a problemas aparentemente aleatorios porque están sobrescribiendo su montón con su pila.

    
respondido por el Adam Davis
3

Incluso si uno quiere escupir datos a algún tipo de consola de registro, la función printf generalmente no es una buena manera de hacerlo, ya que necesita examinar la cadena de formato pasada y analizarla en tiempo de ejecución; incluso si el código nunca utiliza ningún especificador de formato que no sea %04X , el controlador generalmente deberá incluir todo el código que se requeriría para analizar cadenas de formato arbitrarias. Dependiendo del controlador exacto que esté usando uno, puede ser mucho más eficiente usar el código algo como:

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); }

En algunos microcontroladores PIC, log_hexi32(l) tomaría 9 instrucciones y podría tomar 17 (si l está en el segundo banco), mientras que log_hexi32p(&l) tomaría 2. La función log_hexi32p en sí podría escribirse tendrá aproximadamente 14 instrucciones, por lo que se pagaría solo si se le llama dos veces.

    
respondido por el supercat
2

Un punto que ninguna de las otras respuestas ha mencionado: en un micro básico (IE, solo hay el bucle main () y tal vez un par de ISR que se ejecutan en cualquier momento, no un sistema operativo de múltiples hilos) si se bloquea / se detiene / se queda atascado en un bucle, su función de impresión simplemente no ocurrirá .

Además, las personas han dicho "no usar printf" o "stdio.h ocupa mucho espacio", pero no se les ha dado muchas alternativas: embedded.kyle hace mención de alternativas simplificadas, y eso es exactamente el tipo de cosas que Probablemente debería estar haciendo como una cuestión de rutina en un sistema embebido básico. Una rutina básica para expulsar a unos pocos caracteres del UART podría ser unos pocos bytes de código.

    
respondido por el John U

Lea otras preguntas en las etiquetas