Fundamentos del lenguaje de programación C Parte 1

Este articulo proporciona una introducción al lenguaje de programación C (según lo especificado por el estándar ANSI C89) en el contexto de los sistemas integrados. Cubrimos el lenguaje C desde cero desde un punto de vista no específico del hardware para centrarnos en los diversos elementos del lenguaje C en sí. Si bien no es necesario, la experiencia previa con cualquier lenguaje de programación o experiencia con microcontroladores sería útil. Las habilidades aprendidas en este post son aplicables a cualquier compilador ANSI C. Detalles específicos de hardware y compilador como interrupciones, modelos de memoria y optimización NO son discutidos.

C fue desarrollado en 1972 para escribir el sistema operativo Unix®
C es más "bajo nivel" que otros lenguajes de alto nivel como Pascal o Basic
C es compatible con compiladores para una amplia variedad de arquitecturas de MCU
C puede hacer casi cualquier cosa que el lenguaje ensamblador pueda hacer
C es generalmente más fácil y más rápido para escribir código que el lenguaje ensamblador

Antes de entrar en los detalles del lenguaje de programación C, primero necesitamos establecer los hechos sobre C y también reventar algunos de los mitos que muchos programadores siguen creyendo.
C fue desarrollado originalmente en Bell Labs por Dennis Ritchie para su uso en el desarrollo de sistemas operativos. El primer uso importante para C fue escribir el sistema operativo Unix en 1974. Debido a la naturaleza de bajo nivel de la programación del sistema operativo (pensar en los controladores de dispositivos), C tiene una serie de características que son un beneficio real para el desarrollador de sistemas embebidos. Cuando realmente lo piensas, cada vez que escribimos código para un microcontrolador integrado, básicamente estamos escribiendo un sistema operativo para esa plataforma.
Una gran ventaja de C hoy es que hay compiladores disponibles para prácticamente todos los microcontroladores y plataformas de computadoras. Entonces, si conoce C, tendrá las habilidades de programación necesarias para escribir códigos para una gran variedad de dispositivos.

Revienta los mitos

C no es tan portátil entre arquitecturas o compiladores como todo el mundo afirma
Las características del lenguaje ANSI SON portátiles
Las bibliotecas específicas del procesador NO son portátiles
Código específico del procesador (periféricos, E / S, interrupciones, características especiales) NO son portátiles
C NO es tan eficiente como el ensamblador
Un buen programador de ensamblador generalmente puede hacerlo mejor que el compilador, sin importar la optimización empleada: C usará más memoria

Ahora a los inconvenientes. A lo largo de los años, se han perpetuado muchos mitos sobre lo que C hará por ti. La razón número uno que la mayoría de la gente menciona para usar C en el mundo embebido es que es extremadamente portátil debido a la amplia gama de compiladores disponibles para todas las plataformas. El problema es que C no es realmente portátil. Por ejemplo, ¿puede un programa que simplemente alterna un pin de E / S en un PIC18F4520 alternar un pin de E / S en el microprocesador dentro de su PC? Absolutamente no. La forma en que trabajas con la memoria, manejas las interrupciones y usas el hardware en el chip son completamente diferentes. Incluso si intenta pasar de un compilador a otro para el mismo dispositivo, tendrá problemas. La forma en que declara una rutina de servicio de interrupción utilizando el compilador XC8 para un PIC18 es muy diferente de cómo lo haría utilizando el compilador CCS porque las características específicas incorporadas no son parte del estándar de lenguaje y no se pueden evitar.
El núcleo del lenguaje ANSI C en sí mismo y cualquier algoritmo general que escriba y que no interactúe con el hardware serán portátiles. Pero en cualquier sistema embebido, siempre utilizará algunos de los periféricos e interrupciones en el chip, y no hay una forma estándar de manejarlos, ni podría haberlos.
A pesar de lo que muchos programadores expertos de C podrían decirte, C NUNCA será tan eficiente como el lenguaje ensamblador. Un buen programador de lenguaje ensamblador podrá superar el mejor compilador de C en la mayoría de los casos. Debido al entorno de tiempo de ejecución de C requerido, C siempre usará más memoria que un programa de lenguaje ensamblador bien escrito.

NO HAY TAL COSA como el código de auto documentación, a pesar de lo que muchos defensores de C le dirán.
C permite escribir código muy confuso; para algunos ejemplos extremos, ver ioccc.org
Debido a los muchos accesos directos de codificación disponibles, C no siempre es amigable para los nuevos usuarios, ¡de ahí la necesidad de comentarios!

No importa qué tan bien estructurado esté un fragmento de código o qué tan bien se denominan sus variables y funciones; Si vuelve al código que escribió hace un año, tendrá problemas para averiguar exactamente lo que estaba haciendo sin comentarios.
C es un lenguaje muy flexible que le permite escribir algunas líneas de código muy potentes pero concisas. Por sí solos, no es demasiado difícil trabajar con ellos, pero una vez que combina muchas líneas de código como este, un programa puede crecer rápidamente y ser totalmente incomprensible. De hecho, hay un concurso anual (el Concurso Internacional de Código Ofuscado C) donde el código más incomprensible, pero elegante y funcional gana el premio. Es sorprendentemente fácil escribir código incomprensible en C, aunque hacerlo a nivel de los concursantes de IOCCC requiere una destreza y astucia increíbles.

Del código fuente al silicio

Comprender cómo funciona la cadena de herramientas del compilador lo convertirá en un mejor programador y en un mejor depurador. Muy poco sucede desde el momento en que presionas el botón "compilar" hasta que el dispositivo de destino ejecuta tu código. El proceso proporciona oportunidades y peligros. A continuación, veremos cada paso del proceso y explicaremos cómo interactúan entre ellos.


C Archivos de origen y archivos de encabezado

En la parte superior de la cadena de herramientas están los archivos de origen y encabezado donde se describe al compilador qué es lo que se espera que haga el hardware. Los archivos de origen y de encabezado son esencialmente la "interfaz humana" para el sistema. Están escritos para ser leídos por humanos, pero comprensibles para el programa de compilación. Contienen comentarios (o al menos deberían) y generalmente están formateados teniendo en cuenta la legibilidad humana (sangría, espacio en blanco, etc.).

MAKE

La utilidad make es un programa que controla todo el proceso de compilación. Es responsable de llamar al compilador, pasarle todos los archivos que se deben compilar y todo lo demás a llamar al enlazador. Cuando hace clic en un botón de compilación en MPLAB® X IDE, en realidad está iniciando el programa make que maneja todo el proceso de compilación en segundo plano. Asociados con el programa make son makefiles. Estos archivos definen qué archivos están en un proyecto, realizan un seguimiento de sus dependencias y solo crean partes del programa que han cambiado desde la última compilación (a menos que explícitamente se les indique que reconstruyan todo). MPLAB X genera automáticamente archivos make en función de la configuración de su proyecto, por lo que normalmente no será necesario modificarlos. Sin embargo, a algunos desarrolladores les gusta tomar más control del proceso de compilación y agregar pasos adicionales en función de sus necesidades únicas. En esos casos, se puede usar un archivo MAKE personalizado para controlar el proceso de compilación.


C compiler

El compilador de C se compone de tres componentes principales: el preprocesador, el analizador y el generador de código. Cada uno realiza una tarea distinta en el proceso general.

Preprocesador
El trabajo principal del Preprocesador es quitar todas las cosas que hacen que un archivo fuente sea legible por humanos y presentar el código en su forma más fundamental al Analizador.
Los comentarios y el espacio en blanco innecesario se eliminan.
Las etiquetas de sustitución de texto y las macros se reemplazan por sus valores reales.
Los contenidos del archivo de encabezado se fusionan en los archivos fuente C.

Analizador
El analizador realiza la mayor parte del trabajo en el compilador.
Análisis léxico: crea tokens a partir del código proporcionado por el preprocesador. (Los tokens son caracteres o una cadena de caracteres que juntos forman algo significativo, como una palabra clave, un operador matemático o un nombre de variable).
Análisis sintáctico: se asegura de que los tokens formen expresiones que se ajusten a las reglas de C.
Análisis semántico: determina qué acciones debe tomar cada expresión y pasa una lista de estas acciones al generador de código.

Generador de códigos
El Code Generator es exclusivo de cada arquitectura de microcontrolador. Lleva una lista de acciones que el programa necesita realizar desde el Analizador y las traduce a instrucciones de lenguaje de ensamblado específicas del dispositivo que están a solo un paso del código máquina (código binario) que un microcontrolador puede comprender. El código ensamblado generado es reubicable, lo que significa que no se le asignó ninguna dirección física en el dispositivo. Determinar dónde residirán el código y las variables en el espacio de memoria de un dispositivo es el trabajo del Enlazador, del que hablaremos más adelante.

Ensamblador
El ensamblador toma lo que es esencialmente una forma de código máquina legible por el ser humano y lo traduce directamente en código máquina binario. A diferencia de C, las instrucciones de ensamblador tienen una relación de uno a uno con las operaciones que puede realizar el microcontrolador de destino. Entonces, aunque el ensamblador también utiliza un preprocesador, un analizador y un generador de código, su analizador sintáctico es mucho más simple ya que no necesita realizar ningún análisis semántico. El código de ensamblador es la lista de acciones a tomar. La salida del ensamblador es un archivo de objeto. Los archivos de objeto contienen código binario en un formato casi ejecutable listo para ser procesado por el Enlazador.

Bibliotecario o Archivador
El bibliotecario (llamado el Archivador por compiladores basados en GCC) es una herramienta para colocar archivos de objetos en un contenedor llamado biblioteca (o archivo por compiladores basados en GCC). Por lo tanto, una biblioteca no es más que una colección de archivos de objetos (generalmente relacionados) que simplifica la tarea de reutilizarlos en muchos proyectos.

Enlazador
El trabajo del Enlazador es combinar un conjunto de archivos de objeto y biblioteca (en sí mismos, simplemente una colección de archivos de objeto) en un solo archivo ejecutable o, en el caso de los microcontroladores, un archivo * .hex (pronunciado simplemente 'archivo hexadecimal').
Los archivos objeto son reubicables, lo que significa que el código y los datos que contienen pueden colocarse en cualquier parte del mapa de memoria del dispositivo. En otras palabras, no hay direcciones codificadas en la fuente original (hay algunas excepciones, pero las ignoraremos por el momento). Esto hace posible mezclar archivos fuente y bibliotecas para el mismo microcontrolador en un solo proyecto. No puede haber conflictos de direcciones si las variables y los bloques de códigos no se "fuerzan" a una dirección específica en el mapa de memoria del dispositivo.
Una consecuencia de ser reubicable es que cualquier referencia a una variable o función en el código no es más que un marcador de posición. Por ejemplo, una instrucción que le dice al programa "llamar a MyFunction" no puede realizar la llamada porque "MyFunction" aún no tiene una dirección. La instrucción simplemente tiene un marcador de posición que le dice al vinculador: "Cuando descubra dónde MyFunction va a vivir en la memoria del programa, coloque su dirección aquí para que pueda acceder a ella cuando el programa se esté ejecutando".
Entonces, la tarea principal del enlazador es averiguar dónde residirán todos los códigos y datos dentro de la memoria del microcontrolador. Asistir en esta tarea es la secuencia de comandos del enlazador que define la estructura de la memoria del microcontrolador, así como también las ubicaciones que el enlazador puede o no asignar para un propósito específico. Por ejemplo, parte de la memoria está reservada específicamente para su uso por interrupciones. Con esta información, el vinculador intenta ubicar cada bloque de código y cada variable en una dirección específica en el mapa de memoria del dispositivo. Si encuentra una disposición exitosa, el enlazador volverá a cada marcador de posición y reemplazará la referencia a un nombre de variable o nombre de función con la dirección que se acaba de asignar.
En este punto, el programa está en su forma final y el enlazador genera un archivo * .hex que contiene la imagen binaria que se programará en la memoria flash del dispositivo. Si creó el proyecto para la depuración, el enlazador generará un archivo * .elf (o un archivo * .coff o * .cod para compiladores e IDE antiguos) que contenga la misma imagen binaria que el archivo * .hex, pero con código de depuración especial que se requiere para que nuestras herramientas de depuración interactúen con el dispositivo.
Finalmente, el enlazador puede producir opcionalmente un archivo * .map que mostrará cómo cada variable y bloque de código se organizó en el espacio de memoria del dispositivo.

Hex File
El archivo * .hex es la forma final ejecutable del programa, conceptualmente equivalente a un * .exe en una plataforma DOS / Windows. Este archivo se transfiere a una herramienta de programación, como un MPLAB ICD3 o un PICkit 3 (entre muchos otros), que luego "quema" la imagen binaria en la memoria flash del microcontrolador objetivo. Cuando el microcontrolador se libera del restablecimiento o se enciende, se ejecutará el código del archivo * .hex.

Ve a la segunda parte

Fuente: Fundamentals of the C Programming Language


Comentarios

Entradas más populares de este blog

Resistencias

Guía de introducción de XC8