Feb
27
2010
Programar microcontroladores
Autor: UrrielluProbablemente la mayoría de los que leáis este blog estaréis acostumbrados a desarrollar software para ordenador. En ellos es bastante común que al comienzo del desarrollo de una nueva aplicación puedas escribir cientos o incluso más de mil líneas de código en un sólo día. Código bien estructurado, poco propenso a errores, documentado y fácil de entender.
Por el contrario, el desarrollo de software para MCUs (microcontroladores) es, por lo general, muy lento y tedioso. Es más que habitual dedicar varias horas para escribir un simple programa en cuya función principal sólo se terminan escribiendo un puñado de líneas de código y ni siquiera se hacen llamadas a otras funciones. El código, además, suele estar escrito a un nivel excesivamente bajo, lo que hace que el desarrollador deba tener en cuenta multitud de variables, combinaciones y detalles que en un ordenador no necesitan tenerse en cuenta, lo cual ocasiona que el programa resultante sea bastante propenso a errores, incluso en manos de programadores expertos.
Una de las tareas más tediosas suele ser la configuración de los periféricos internos del propio microcontrolador. Temporizadores y conversores analógico-digital son ejemplos muy habituales de periféricos integrados que la gran mayoría de MCUs tienen disponibles. Estos bloques funcionales son muy configurables y pueden utilizarse en infinidad de aplicaciones debido a la flexibilidad de funcionamiento que tienen. Esto hace que incluso para las tareas más simples el programador deba dedicar mucho tiempo a entender cómo funciona y cómo se configuran todos y cada uno de los bits individuales de los registros de la RAM asociados al periférico integrado que se esté utilizando.
Los compiladores normalmente incluyen funciones que pretenden simplificar la tarea de configuración de estas funcionalidades. No obstante, debido a la necesidad de ofrecer las mismas o casi tantas posibilidades y combinaciones que tendrías si lo configurases bit a bit (sin utilizar estas librerías), las funciones ofrecidas deben contarse por decenas o cientos, son tanto o más complicadas de usar que la configuración manual, requieren grandes cantidades de parámetros basados en defines y operaciones binarias, y muchas de ellas están vagamente documentadas y ejemplificadas.
Suelen existir muy pocos compiladores para cada arquitectura de microcontroladores y cada lenguaje de programación. Además, la calidad, estabilidad y fiabilidad de estos compiladores (tanto libres como comerciales) suelen ser muy inferiores a las de los compiladores para ordenador. Como ya he comentado, sus librerías suelen ser escasas, difíciles de usar y muy mal documentadas; el código máquina generado es, por lo general, bastante eficiente, pero muchas veces incluye errores que modifican la lógica del programa original. Sobre esto voy a comentar una anécdota que me ocurrió hace un tiempo:
A principios de 2009 me disponía a programar un microcontrolador en lenguaje C utilizando uno de los compiladores más conocidos que hay. Este compilador es comercial y, aunque tiene versiones tanto para Windows como para Linux, realmente nunca he conocido a nadie que lo utilizase en Linux. Mi código fuente era muy simple, ya que pretendía ser el código de ejemplo para un pequeño robot siguelíneas que más adelante utilizarían los participantes de unos talleres de introducción a la robótica. El código en C no necesitaba compilarse en más que unas pocas instrucciones en código máquina, y tras repasarlo una y otra vez todo parecía estar escrito correctamente. El compilador compilaba el código sin errores ni avisos, pero el microcontrolador actuaba erróneamente. Tras varias horas sin encontrar el error, decidí desensamblar el binario generado por el compilador y estudiar el código en lenguaje ensamblador que había sido generado. El programa estaba compuesto de unas 60 instrucciones en código máquina, y casi todas ellas se habían generado adecuadamente. No obstante, dos instrucciones no encajaban.
Mi código fuente llamaba a una función (sin parámetros ni valor de retorno) disponible en las librerías del compilador, la cual se compilaba en 5 instrucciones en código máquina. Estas instrucciones simplemente almacenaban un valor constante en dos registros de la RAM. No obstante, las direcciones de estos registros no eran las correctas. El compilador había generado código que almacenaba los valores en las posiciones 0×0F y 0×10, mientras que lo correcto eran 0×10 y 0×11 respectivamente. Es decir, las direcciones de las variables usadas estabandesplazadas una unidad hacia abajo.
Como estoy acostumbrado al funcionamiento del desarrollo de software libre, aunque este compilador fuese cerrado y comercial dediqué un buen rato a redactar un bug report en el que explicaba todo el problema y la solución. A los pocos días recibí la respuesta de la empresa, esperando al menos un agradecimiento por haber realizado su trabajo y una indicación de que estaría solucionado en la próxima versión. Para mi sorpresa, la única respuesta que obtuve fue una pregunta: “¿Cual es el número de serie de su producto?”.