Tue. Aug 16th, 2022

Este artículo analiza las características básicas de C, un lenguaje sencillo que todavía se usa ampliamente para la programación de microcontroladores.

información de soporte

Según los estándares de la tecnología moderna, C es un lenguaje bastante antiguo. El desarrollo original tuvo lugar a principios de los 70, seguido de revisiones a finales de los 70 y la estandarización en los 80. Sin embargo, en mi opinión no ha perdido nada de su vigor. Sigue siendo un gran lenguaje para aplicaciones integradas y, según mi experiencia, es un entorno de programación adecuado para todo, desde dispositivos simples basados ​​en microcontroladores hasta procesamiento sofisticado de señales digitales.

La necesidad de C

No tengo ninguna duda de que hay al menos algunos ingenieros eléctricos que no saben cómo escribir un programa en C y nunca necesitarán escribir un programa en C. Si usted es el tipo de persona que prefiere el hardware al software, podría considerar a estos individuos "los afortunados".

Sin embargo, nos guste o no, la programación es una parte cada vez más importante de la ingeniería eléctrica, y en realidad he encontrado mucha satisfacción al no solo diseñar tableros de circuitos, sino también escribir el firmware para esos tableros. Estos dos aspectos del desarrollo del sistema están estrechamente relacionados, y sospecho que el resultado final suele ser superior cuando la misma persona lleva a cabo el diseño de la placa y el desarrollo del firmware.

C vs Asamblea

En teoría, soy un defensor del lenguaje ensamblador. En realidad, he llegado a un punto en mi vida en el que el lenguaje ensamblador es una amenaza tanto para mi seguridad financiera como para mi salud mental. Escribir el firmware en el ensamblaje es lento y propenso a errores, y mantener un nivel adecuado de organización en programas largos y complejos es desesperadamente difícil.

Sin embargo, insistiré en que realmente no puede entender los idiomas de alto nivel si no entiende el ensamblaje. Si nunca ha tenido la oportunidad de obtener una experiencia sólida con el lenguaje ensamblador, al menos debe familiarizarse con algunos de los conceptos básicos antes de sumergirse en C. Los artículos que se enumeran anteriormente en la sección Información de apoyo son un buen lugar para comenzar. .

¿Qué entienden los procesadores?

Sólo lenguaje de máquina. Unos y ceros. Todos los aspectos "amigables con el programador" del lenguaje C deben eventualmente traducirse a la realidad de bajo nivel del hardware digital del procesador, es decir, aritmética binaria, operaciones lógicas, transferencia de datos, registros y ubicaciones de memoria.

Ciertamente, es posible escribir con éxito un programa en C sin saber nada sobre el hardware real, pero en el contexto del desarrollo de sistemas integrados, es útil y, a veces, necesario entender su hardware y cómo su código C interactúa con ese hardware.

Los basicos

Los programas en C van desde aquellos que son bastante simples hasta aquellos que son muy complejos. En el mundo integrado, muchos programas tenderán hacia el lado simple del espectro, y los elementos de programación básicos que se describen a continuación proporcionan una buena base para un estudio más profundo del desarrollo de firmware en lenguaje C

Incluir declaraciones

Un programa C incorporado comenzará con al menos una instrucción #include. Estas declaraciones se utilizan para introducir el contenido de un archivo separado en su archivo fuente. Esta es una forma práctica de mantener su código organizado y también le permite usar la funcionalidad de la biblioteca, las rutinas de configuración de hardware y registrar las definiciones proporcionadas por el fabricante.

El extracto del código a continuación muestra las declaraciones de inclusión que usé en uno de mis proyectos de microcontroladores. Tenga en cuenta que el archivo "Project_DefsVarsFuncs.h" es un archivo de encabezado personalizado creado por el programador (es decir, yo). Lo estaba utilizando como una forma conveniente de incorporar definiciones de preprocesador, variables y prototipos de funciones en múltiples archivos de origen.

                    // ------------------------------------------------ -----------------------------
// Incluye
// ------------------------------------------------ -----------------------------
#incluir                   // Declaraciones SFR
#include "Project_DefsVarsFuncs.h"
#include "InitDevice.h"
#include "cslib_config.h"
#include "cslib.h"
                  

Definiciones de preprocesador

Puede usar una declaración #define para crear una cadena que será reemplazada por un número. Las definiciones de preprocesador no son necesarias, pero en algunas situaciones son extremadamente útiles porque le permiten modificar fácilmente un valor que aparece en varias partes diferentes de su programa.

Por ejemplo, digamos que está utilizando el ADC del microcontrolador y que su código utiliza la frecuencia de muestreo del ADC en varios cálculos separados. Una definición de preprocesador le permite usar una cadena intuitiva (como SAMPLE_RATE) en lugar del número en el código de cálculo, y si está experimentando con diferentes frecuencias de muestreo, solo necesita cambiar el valor numérico en la definición del preprocesador.

                    # define SAMPLE_RATE 100000
                  

Puede cambiar 100000 a cualquier otro número, y este nuevo número se utilizará para reemplazar todas las instancias de la cadena SAMPLE_RATE.

Las definiciones de preprocesador también son una excelente manera de hacer que el código sea más legible. La siguiente es una lista de declaraciones #define útiles que incorporo en todos mis proyectos de firmware.

                    #define BIT7 0x80
#define BIT6 0x40
#define BIT5 0x20
#define BIT4 0x10
#define BIT3 0x08
#define BIT2 0x04
#define BIT1 0x02
#define BIT0 0x01

#define HIGH 1
#define LOW 0

#define TRUE 1
# define FALSO 0

# define SET 1
#define CLEARED 0

#define LOWBYTE (v) ((carácter sin signo) (v))
#define HIGHBYTE (v) ((unsigned char) (((unsigned int) (v)) >> 8))
                  

Es importante comprender que las definiciones de preprocesador no tienen una relación directa con el hardware. Solo le estás diciendo al preprocesador que reemplace una cadena de caracteres con otra cadena de caracteres antes de compilar el programa.

Variables

Los procesadores almacenan datos en registros y ubicaciones de memoria. Realmente no existe tal cosa como una variable en lo que respecta al hardware. Sin embargo, para el programador, escribir código es mucho más fácil cuando podemos usar variables con nombres intuitivos en lugar de direcciones de memoria o números de registro.

Los compiladores pueden administrar los detalles de bajo nivel asociados con las variables sin mucha información del programador, pero si desea optimizar su uso de las variables, necesitará saber algo sobre la configuración de memoria del dispositivo y la forma en que maneja los datos de diferentes Anchos de bits.

El siguiente extracto de código da un ejemplo de declaración de variable. Esto se escribió para el compilador Keil Cx51, que reserva un byte de memoria para una declaración de "unsigned char", dos bytes para una declaración de "unsigned int", y cuatro bytes para una declaración de "unsigned long".

                    unsigned long Accumulated_Capacitance_Sensor1;
unsigned long Accumulated_Capacitance_Sensor2;

unsigned int Sensor1_Unpressed;
unsigned int Sensor2_Unpressed;

unsigned int Sensor1_Measurement;
unsigned int Sensor2_Measurement;

unsigned int AngularPosition;

unsigned int TouchDuration;

unsigned char CurrentDigit;
unsigned int CharacterEntry;
unsigned char DisplayDivider;
                  

Operadores, declaraciones condicionales y bucles

El núcleo de la funcionalidad computacional consiste en mover datos, realizar cálculos matemáticos y operaciones lógicas con datos, y tomar decisiones programáticas basadas en el valor de los datos almacenados o generados.

Las operaciones matemáticas y la manipulación de bits se realizan mediante operadores. C tiene bastantes operadores: igual a (=), suma (+), resta (-), multiplicación (*), división (/), bitwise AND (&), bitwise OR (|), etc. Las "entradas" a una declaración de operador son variables o constantes, y el resultado se almacena en una variable.

Las declaraciones condicionales le permiten realizar o no una acción en función de si una condición dada es verdadera o falsa. Estas declaraciones usan las palabras "si" y "otra cosa"; por ejemplo:

                    si (Sensor1 
                  

Para los bucles y mientras que los bucles proporcionan un medio conveniente para ejecutar repetidamente un bloque de código. Este tipo de tareas surgen con mucha frecuencia en aplicaciones integradas. Los bucles están más orientados a situaciones en las que un bloque de código debe ejecutarse un número específico de veces, y mientras que los bucles son útiles cuando el procesador debe continuar repitiendo el mismo bloque de código hasta que una condición cambie de verdadero a falso. Aquí hay ejemplos de ambos tipos.

                    para (n = 0; n 
                  
                    while (CONVERSION_DONE == FALSE);
{
LED_STATE =! LED_STATE;
Delay_ms (100);
}
                  

Funciones

El buen código C es muy superior al código de ensamblaje en términos de organización y legibilidad, y esto se debe en gran parte al uso de las funciones.

Las funciones son bloques de código que se pueden incorporar fácilmente a otras partes del código. Hacer que el procesador ejecute las instrucciones contenidas en la función se denomina "llamar" a la función. Una función puede aceptar una o varias entradas y puede proporcionar una salida, denominada valor de retorno.

Las funciones de C, las sentencias condicionales y los bucles hacen que sea bastante fácil traducir un diagrama de flujo en código de trabajo. Este ejemplo es de un proyecto en el que usé la comunicación SPI para controlar un LCD.

El uso de funciones implica cierta sobrecarga, por lo que debemos tener cuidado de no cargar al procesador con un número excesivo de llamadas a funciones, pero en general los beneficios de las funciones superan con creces los costos.

Este es un ejemplo de una función que tiene tres entradas numéricas y utiliza estas entradas para generar un valor de retorno verdadero o falso.

                    bit Is_In_Range (int input, int LowerBound, int UpperBound)
{
if (input> = LowerBound && input 
                  

Conclusión

Una discusión exhaustiva del lenguaje C podría continuar casi indefinidamente, y este artículo solo ha arañado la superficie. Dudo incluso en publicar algo que omita tanta información importante, pero tenemos que empezar en alguna parte. Planeamos publicar muchos más artículos sobre el uso del lenguaje C en aplicaciones integradas, y completaremos este artículo introductorio con enlaces a medida que otros recursos estén disponibles.

Si tiene algún tema relacionado con C sobre el que le gustaría aprender más, no dude en hacérnoslo saber en la sección de comentarios a continuación.

By Maria Montero

Me apasiona la fotografía y la tecnología que nos permite hacer todo lo que siempre soñamos. Soñadora y luchadora. Actualmente residiendo en Madrid.