¿Recursos para escribir código C eficiente para microcontroladores? [cerrado]

15

Se necesita ayuda seria aquí. Amo la programación. Últimamente he estado leyendo muchos libros (como K&R) y artículos / foros en línea para lenguaje C. Incluso intenté buscar en el código de Linux (aunque, estaba perdido por dónde empezar, pero ¿me ayudó mirar en pequeñas bibliotecas?).

Comencé como programador de Java y en Java es bastante sencillo; Si los programas se hacen demasiado grandes, divídalos en clases y luego en funciones. Pautas como mantener el código legible y agregar comentarios. Use técnicas de ocultamiento de información y OOP. Algunos de los cuales aún se aplican para C.

He estado codificando en C ahora y hasta ahora hago que los programas funcionen de una manera u otra. Mucha gente habla sobre rendimiento / eficiencia, algoritmo / diseño, optimización y mantenibilidad. Algunas personas enfatizan una más que la otra, pero para los ingenieros de software no profesionales a menudo escuchas algo como, por ejemplo: Linux kernel dev no tomará ningún código.

Mi pregunta es esta: planeo escribir un código para el microcontrolador de 8 bits sin desperdiciar ningún recurso . Ten en cuenta que vengo de Java, así que las cosas ya no son lo mismo ... los recursos / libros / enlaces / consejos serán muy apreciados. El rendimiento y el tamaño ahora son importantes. Recursos / trucos sobre el código C eficiente (dentro de las mejores prácticas) para microcontroladores de 8 bits?

Además, inline assemblyjuega un papel vital y se adhiere al estándar de los microcontroladores. Pero, ¿hay una regla general para la eficiencia que se aplique a todos?

Por ejemplo: register unsigned int variable_name;se prefiere sobre charcualquier momento. O usar uint8_t si no necesitas números grandes.

EDITAR: Muchas gracias por todas las respuestas y sugerencias. Agradezco el esfuerzo de todos por compartir el conocimiento.

As de espadas
fuente
2
Este no suele ser el caso en un procesador x86, pero en un microcontrolador, si desea asegurarse de obtener la última caída de rendimiento, es probable que desee utilizar el ensamblaje.
Rei Miyasaka
3
@Rei: El ensamblaje hecho a mano rara vez, si alguna vez, usa menos memoria y es más rápido que los compiladores de C modernos. Es una pérdida de tiempo codificar en el ensamblaje cuando puede (como debería) hacerse en C.
mattnz
1
@mattnz Por moderno, ¿de qué novedad estás hablando? Honestamente, no he escrito código para un microcontrolador en casi una década.
Rei Miyasaka
2
Un consejo simple: en microncontrolles, todavía es generalmente cierto que "si es más simple, es más rápido". En chips complejos (ARM y hacia arriba), el hardware realiza tantas optimizaciones que nunca sabrá hasta que realice la prueba.
Javier
1
@ReiMiyasaka con un gran aumento del costo de mantenimiento. Un buen compilador de C puede producir casi el mismo código que un programador altamente experimentado.

Respuestas:

33

Tengo más de 20 años de sistemas integrados, en su mayoría 8 y 16 micros. La respuesta breve a su pregunta es la misma que cualquier otro desarrollo de software: no optimice hasta que sepa que lo necesita, y luego no optimice hasta que sepa lo que necesita optimizar. Escriba su código para que sea confiable, legible y mantenible primero. La optimización prematura es tanto, si no más, un problema en los sistemas integrados

Cuando programa "sin desperdiciar ningún recurso", ¿considera su tiempo como un recurso? Si no, quién le está pagando por su tiempo, y si no hay nadie, ¿tiene algo mejor que ver con eso? Una vez que el diseñador de sistemas embebidos tiene que elegir es el costo del hardware versus el costo del tiempo de ingeniería. Si va a enviar 100 unidades, use un micro más grande, a 100,000 unidades, un ahorro de $ 1.00 por unidad es lo mismo que 1 año de desarrollo de software (ignorando el tiempo de comercialización, el costo de oportunidad, etc.), a 1 millón de unidades, comienza obtener ROI por ser obsesivo con el uso de recursos, pero tenga cuidado porque muchos proyectos integrados nunca lograron la marca de 1 millón porque se diseñaron para vender 1 millón (Alta inversión inicial con bajo costo de producción), y se arruinaron antes de llegar allí.

Dicho esto, hay que tener en cuenta y tener en cuenta los sistemas embebidos (pequeños), ya que estos dejarán de funcionar, de manera inesperada, no solo harán que vaya lento.

a) Pila: por lo general, solo tiene un tamaño de pila pequeño y, a menudo, tamaños de marco de pila limitados. Debe ser consciente de cuál es la utilización de su pila en todo momento. Tenga en cuenta que los problemas de apilamiento causan algunos de los defectos más insidiosos.

b) Montón: nuevamente, tamaños de montón pequeños, así que tenga cuidado con la asignación de memoria injustificada. La fragmentación se convierte en un problema. Con estos dos, debe saber qué hace cuando se agota: no sucede en un sistema grande debido a la paginación proporcionada por el sistema operativo. es decir, cuando malloc devuelve NULL, ¿lo verifica y qué hace? Cada malva necesita un cheque y un controlador, ¿hincha de código? Como guía, no lo use si hay una alternativa. La mayoría de los sistemas pequeños no usan memoria dinámica por estas razones.

c) Interrupciones de hardware: debe saber cómo manejarlas de manera segura y oportuna. También necesita saber cómo hacer un código seguro para el reingreso. Por ejemplo, las bibliotecas estándar de C generalmente no son reentrantes, por lo que no deben usarse dentro de los controladores de interrupciones.

d) Montaje: casi siempre optimización prematura. Como máximo, se necesita una pequeña cantidad (en línea) para lograr algo que C simplemente no puede hacer. Como ejercicio, escriba un pequeño método en ensamblaje hecho a mano (desde cero). Haga lo mismo en C. Mida el rendimiento. Apuesto a que el C será más rápido, sé que será más legible, mantenible y ampliable. Ahora, para la parte 2 del ejercicio: escriba un programa útil en ensamblador y C.
Como otro ejercicio, eche un vistazo a la cantidad de kernel de Linux que es ensamblador, luego lea el siguiente párrafo sobre el kernel de Linux.

Vale la pena saber cómo hacerlo, incluso puede valer la pena dominar los idiomas para uno o dos micros comunes.

e) "register unsigned int variable_name", "register" es, y siempre ha sido, una pista para el compilador, no una instrucción, a principios de los años 70 (hace 40 años), tenía sentido. En 2012, es una pérdida de pulsaciones de teclas, ya que los compiladores son tan inteligentes y los conjuntos de instrucciones de micros tan complejos.

Volviendo a su comentario de Linux: el problema que tiene aquí es que no estamos hablando de solo 1 millón de unidades, estamos hablando de cientos de millones, con un tiempo de vida para siempre. Vale la pena el tiempo de ingeniería y el costo para que sea lo más óptimo posible. Aunque es un buen ejemplo de la mejor práctica de ingeniería, sería un suicidio comercial para la mayoría de los desarrolladores de sistemas integrados ser tan pedantes como lo requiere el linux kernal.

Mattnz
fuente
44
mattnz: esta es una de las respuestas más encantadoras en los sitios .stackexchange.
Ahmed Masud el
1
No puedo mejorar esta respuesta. Solo podría agregar que insertar código de ensamblaje rara vez tiene sentido para el rendimiento, pero podría tener sentido para cosas como pinchar chips de E / S u otros trucos de hardware que podrían no ser fáciles de hacer en C.
Mike Dunlavey
@mattnz Gracias por la respuesta bien puesta. +1
AceofSpades
1
El ensamblador @MikeDunlavey a veces es necesario para el tiempo exacto. Acaba de terminar una superposición de vídeo que utiliza bit DE golpeando para generar vídeo NTSC en un pin de E / S, el momento es en términos de - de alta tensión para los ciclos 3clock, entonces baja para 6 entonces ....
Martin Beckett
@ Martin: Eso tiene mucho sentido. Hace mucho tiempo que no codifico a ese nivel.
Mike Dunlavey
3

Su pregunta ("sin desperdiciar recursos") es demasiado general, por lo que es difícil dar muchos consejos. Tomado literalmente, si no desea desperdiciar recursos, tal vez debería dar un paso atrás y evaluar si necesita hacer algo, es decir, si puede resolver el problema de otras maneras.

Además, los consejos útiles dependen mucho de sus limitaciones: ¿qué tipo de sistema está creando y qué tipo de CPU está utilizando? ¿Es un sistema difícil en tiempo real? ¿Cuánta memoria tiene para código y datos? ¿Admite de forma nativa todas las operaciones de C (especialmente, multiplicación y división) y para qué tipos? Más en general: lea la hoja de datos completa y comprenda .

El consejo más importante: que sea sencillo.

Por ejemplo: olvídate de estructuras de datos complejas (hashes, árboles, posiblemente incluso listas vinculadas) y usa matrices de tamaño fijo. El uso de estructuras de datos más complicadas está garantizado solo después de que haya demostrado mediante mediciones que las matrices son demasiado lentas.

Además, no diseñe en exceso (algo que los desarrolladores de Java / C # tienden a hacer): escriba código de procedimiento directo, sin demasiadas capas. ¡La abstracción tiene un costo!

Póngase cómodo con la idea de usar variables globales y goto [muy útil para limpiezas en ausencia de excepciones];)

Si tiene que lidiar con interrupciones, lea sobre reentrada. Escribir código reentrante es muy no trivial.

zvrba
fuente
3

Estoy de acuerdo con la respuesta de mattnz, en su mayor parte. Comencé a programar en el 8085 hace más de 30 años, luego el Z80, luego migré rápidamente al 8031. Después de eso fui a los microcontroladores de la serie 68300, luego 80x86, XScale, MXL y (últimamente) PICS de 8 bits, que adivinar significa que he cerrado el círculo. Para el registro, puedo afirmar que los FAE en varios de los principales fabricantes de microprocesadores todavía usan ensamblador, aunque de forma orientada a objetos para la reutilización de código intencional.

Lo que no veo en la respuesta aprobada es una discusión sobre el tipo de procesador de destino y / o la arquitectura propuesta. ¿Es un 0,50 $ 8 amargo con memoria limitada? ¿Es un núcleo ARM9 con canalización y 8Meg de flash? Coprocesador de gestión de memoria? ¿Tendrá un sistema operativo? A while (1) loop? ¿Un dispositivo de consumo con una producción inicial de 100000 unidades? ¿Una empresa nueva con grandes ideas y sueños?

Si bien estoy de acuerdo con que los compiladores modernos hacen un gran trabajo de optimización, nunca he trabajado en un proyecto en 30 años en el que no detuve el depurador y no vi el código de ensamblaje generado para ver qué estaba pasando debajo del capó ( es una pesadilla cuando la canalización y la optimización entran en juego), por lo que el conocimiento del ensamblaje es importante.

Y nunca he tenido un CEO, VP de Ingeniería, un cliente que no me empujó a intentar meter un galón en un recipiente de un litro, o ahorrar .05 $ al usar una solución de software para solucionar un problema de hardware (oye, solo es software ¿Qué es tan difícil?) La optimización de la memoria (código o datos) siempre contará.

Mi punto es que si ve el proyecto desde un punto de vista de programación pura, obtendrá una solución más estrecha. Mattnz lo tiene bien: póngalo a trabajar, luego póngalo a trabajar más rápido, más pequeño, mejor, pero aún necesita pasar MUCHO tiempo en los requisitos y entregas antes de siquiera pensar en la codificación.

Gio
fuente
Hola Gio, evita HTML innecesario en tus publicaciones y utiliza la sintaxis Markdown en su lugar. Para ello <br />, simplemente presione enter, y para los párrafos simplemente deje una línea vacía entre ellos. Además, cuando haga referencia a otra respuesta, agregue un enlace. En este punto, puede haber solo unas pocas respuestas, pero podría haber más, distribuidas en muchas páginas, y no quedará muy claro a qué respuesta se refiere. Consulte el historial de revisiones para ver mis ediciones.
Yannis
@Gio Gracias por mencionar otros factores importantes. +1 :)
AceofSpades
+1 - Buena expansión de mi respuesta.
mattnz
1

La respuesta de Manttz pone muy bien los puntos clave sobre cómo hacer una programación "cercana al hardware". Esto es, después de todo, para lo que está destinado C.

Sin embargo, me gustaría agregar que si bien la palabra clave estricta de "Clase" no existe en C, es bastante sencillo pensar en términos de programación orientada a objetos en C, incluso cuando está cerca del hardware.

Puede considerar esta respuesta: las mejores prácticas de OO para programas en C que explican este punto.

Aquí hay algunos recursos que lo ayudarán a escribir un buen código orientado a objetos en C.

a. Programación orientada a objetos en C
b. Este es un buen lugar donde las personas intercambian ideas
c. y aquí está el libro completo

Otro buen recurso que me gustaría sugerirle es:

The Write Great Code Series . Este es un libro de dos volúmenes. El primer libro cubre aspectos muy esenciales de las máquinas en trabajos de nivel inferior. El segundo libro toca "Pensar bajo nivel - escribir alto nivel"

Dipan Mehta
fuente
1

Tienes algunos problemas. primero quieres que este proyecto / código sea portátil? La portabilidad le cuesta rendimiento y tamaño, ¿puede su plataforma de elección y la tarea que está implementando tolerar el exceso de tamaño y el menor rendimiento?

Sí, absolutamente en una máquina de 8 bits que devuelve caracteres sin firmar en lugar de entradas o cortos sin firmar, es una forma de mejorar el rendimiento y el tamaño. Del mismo modo, en una máquina de 16 bits, use pantalones cortos sin signo y una máquina de 32 bits con entradas sin signo. Sin embargo, puede ver fácilmente que si, por ejemplo, solo usó ints sin firmar en todas partes para la portabilidad en todo el sistema que está asumiendo el control (ARM, por ejemplo, empujando hacia los mercados de dispositivos más pequeños y de menor potencia) ese código es un gran rom rom Un micro de 8 bits. Por supuesto, puede usar unsigned sin int o short o char y dejar que el compilador elija el tamaño óptimo.

No solo ensamblaje en línea, sino lenguaje ensamblador en general. El ensamblaje en línea es muy poco portátil y más difícil de escribir bien que simplemente llamar a una función asm. Sí, graba y devuelve la llamada, pero a costa de un desarrollo más fácil, un mejor mantenimiento y control. La regla aún se aplica, solo escríbala en asm si realmente lo necesita, ¿ha hecho el trabajo para concluir que la salida del compilador es su problema en esta área y qué ganancia de rendimiento puede obtener al hacerlo a mano? Luego, vuelva a la portabilidad y el mantenimiento, tan pronto como comience a mezclar C y asm, y cada vez que mezcle C y asm, podría dañar su portabilidad y hacer que el proyecto sea menos mantenible dependiendo de quién más esté trabajando en él o si esto es un producto que está desarrollando ahora y alguien más debe mantenerlo en el futuro. Una vez que haya hecho ese análisis, automáticamente sabrá si tiene que ir en línea o con ensamblaje directo. Tengo más de 25 años en el campo, escribo mezclas C y asm todos los días, vivo en / en la capa de hardware / software y, bueno, nunca uso asm en línea. Raramente vale la pena el esfuerzo, demasiado específico del compilador, escribo código no específico del compilador siempre que sea posible (casi en todas partes).

La clave de toda su pregunta es desmontar su código C. Aprenda lo que hace el compilador con su código, y con el tiempo, si lo desea, puede aprender a manipular el compilador para generar el código que desea sin tener que recurrir a asm. Con más tiempo, puede aprender a manipular el compilador para producir código eficiente en múltiples objetivos, haciendo que el código sea más portátil sin tener que recurrir a asm. No debería tener problemas para ver por qué el carácter sin firmar funciona mejor como un retorno de estado de una función que un sin iniciar sesión en un micro de 8 bits, del mismo modo, el carácter sin signo se vuelve más costoso en sistemas de 16 y 32 bits (algunas arquitecturas lo ayudan fuera, algunos no).

Algunos microcontroladores de 8 bits, ¿todos ?, son muy poco compatibles con el compilador y ningún compilador produce un buen código. No hay suficiente demanda para crear un mercado de compiladores para esos dispositivos para crear un gran compilador para esos objetivos, por lo que los compiladores que están allí están allí para atraer a más programadores comerciales, no asm, y no porque el compilador sea mejor que los escritos a mano. . Los brazos y manos que entran en este mundo están cambiando ese modelo, ya que tiene objetivos que tienen compiladores que han trabajado mucho en ellos, compiladores que producen un código bastante bueno, etc. Para micros con procesadores como ese, por supuesto, todavía tiene en el caso de que deba desplegarse en asm, pero no es tan frecuente, es mucho más fácil decirle al compilador qué quiere que haga que no usarlo. Tenga en cuenta que manipular un compilador no es un código feo e ilegible, de hecho, es el código opuesto, limpio y directo, quizás reorganizando algunos elementos. Controlando el tamaño de sus funciones y la cantidad de parámetros, ese tipo de cosas hacen una gran diferencia en la salida del compilador. Evite las características geniales del compilador o lenguaje, KISS, manténgalo simple y estúpido, a menudo produce un código mucho mejor y más rápido.

viejo contador de tiempo
fuente
No indica el tipo de productos que produce, supongo que es un volumen muy alto, un margen bajo o un nicho de mercado específico con márgenes increíbles. Esto es fundamental para decidir a nivel empresarial si debe usar un pequeño micro ensamblador de 8 bits y un ensamblador hecho a mano, o un micro y C. más grande En respuesta a su comentario (¿eliminado?), Trabajo con micros de 8 bits, sin embargo, comience con un micro más grande de lo necesario y revise hacia abajo solo cuando el costo de la lista de materiales se convierta en un problema.
mattnz