Sun. Aug 14th, 2022

Este artículo le ayudará a comprender qué son las funciones, por qué se utilizan y cómo se implementan en el hardware integrado.

información de soporte

Cada programa C tiene una función main (). Ciertamente, es posible escribir un programa exitoso en el que la única función sea main (). Mi conjetura es que esto se ha hecho muchas veces, y es cierto que en ciertas aplicaciones simples no se necesita ninguna otra función.

Sin embargo, el uso extenso de funciones es una indicación de que la persona que escribe el código es un desarrollador de firmware con experiencia. ¿Por qué? Porque las funciones nos permiten escribir mejor código más rápido, con menos trabajo y menos errores. Para aquellos que pasan una parte significativa de su firmware de escritura de la vida profesional, estas son las ventajas que no se pueden ignorar. Incluso si inicialmente nos resistimos al uso de funciones porque parecen requerir más trabajo, la experiencia nos enseña gradualmente que los beneficios superan con creces los costos.

¿Qué es una función?

Una función C es un grupo de instrucciones que trabajan juntas para implementar un tipo específico de actividad de procesador. En muchos casos, una función realizará una tarea específica, como recuperar datos de un búfer SPI, configurar un temporizador para que genere un retraso específico, o leer un valor de la memoria y cargarlo en un registro DAC.

Sin embargo, no hay ninguna ley que establezca que una función puede realizar una sola tarea. Puede resultarle conveniente tener una función que actualice tres máquinas de estado no relacionadas, o podría escribir una función que transmita un byte a través de UART, luego verifica el bit de estado hasta que se recibe un byte, y luego incorpora el valor del byte recibido en algunos cálculos matemáticos.

Una cosa que me gusta de las funciones es que permiten una traducción bastante directa entre un diagrama de flujo y un código.

Los “componentes” de una función

Una función consta de un nombre, una lista de parámetros de entrada, las declaraciones de código que implementan la funcionalidad requerida y un tipo de retorno.

El siguiente fragmento de código le da un ejemplo.

                    char Convert_to_Lowercase (char UppercaseLetter)
{
     if (mayúscula 90)
          devuelve 0x00;
     más
          return (UppercaseLetter + 32);
}
                  

Me gusta hacer que los nombres de mis funciones sean muy descriptivos. Esto hace que el código sea más legible y le ayuda a mantener sus pensamientos organizados.

Las instrucciones están entre corchetes; esta parte de la definición de la función se llama el cuerpo de la función. La palabra clave "retorno" se usa para salir de la función e identificar qué datos deben enviarse a la parte del código que se ejecuta anteriormente.

El tipo de retorno, colocado antes del nombre de la función, identifica el tipo de datos de la información que se devolverá. Es perfectamente aceptable tener una función que simplemente realice una tarea, sin necesidad de devolver datos. En este caso, usaría la palabra clave "vacío" en lugar de un tipo de datos.

Pasando datos a una función

Los parámetros de entrada, también llamados argumentos, se incluyen entre paréntesis y se colocan después del nombre de la función. Una función C puede tener múltiples argumentos, en cuyo caso están separados por comas. Cada argumento debe ir acompañado de un tipo de datos.

En aplicaciones integradas, a menudo no es necesario utilizar argumentos. Puedo pensar en dos razones para esto.

Primero, el firmware incorporado interactúa con frecuencia directamente con el hardware del dispositivo y, por lo tanto, una función puede obtener la información que necesita de los registros de configuración, los registros de comunicación o los pines del puerto.

En segundo lugar, los programas de C simples escritos para microcontroladores pueden usar variables globales, es decir, variables que están presentes en todo el programa y pueden ser accedidas por cualquier función. Como lo entiendo, se desaconseja el uso de variables globales en la programación de aplicaciones, o tal vez incluso "condenado" sería la palabra adecuada. Pero en mi opinión, muchos proyectos de firmware, especialmente aquellos escritos completamente por un programador, pueden beneficiarse de la simplicidad de las variables globales.

Al definir una función que no tiene argumentos, puede dejar los paréntesis vacíos o insertar la palabra clave "vacío". En teoría, el enfoque "vacío" es mejor que los paréntesis vacíos, pero en el contexto del desarrollo integrado, especialmente teniendo en cuenta lo ingeniosos que son los compiladores modernos son, no sé cuánto realmente importa.

Recorrido

Examinemos brevemente la definición de función que se muestra arriba.

  • El nombre de la función, Convert_to_Lowercase, indica claramente el propósito de la función: acepta un valor de ocho bits correspondiente a una letra ASCII mayúscula, y devuelve un valor de ocho bits correspondiente a la versión en minúscula de esa misma letra.
  • Hay un parámetro de entrada. Tiene un tipo de datos de char y utiliza un identificador descriptivo.
  • El valor de retorno, al igual que el argumento de entrada, es un carácter ASCII y, por consiguiente, el tipo de retorno es char.
  • Si el valor de entrada está fuera del rango correspondiente a las letras ASCII en mayúsculas, la función devuelve 0x00, lo que indica un error. De lo contrario, agrega 32 al valor de entrada y devuelve la suma. Si no está familiarizado con los valores ASCII, la tabla que se muestra a continuación lo ayudará a comprender por qué estoy usando los números 65, 90 y 32.

Funciones en hardware

Al igual que la memoria de datos de un procesador no admite directamente los detalles adjuntos a una variable C, la memoria de código de un procesador es mucho más simple que una función C. La memoria de código es una secuencia larga de ubicaciones de almacenamiento que no están categorizadas u organizadas de ninguna manera útil. Lo único que identifica una ubicación particular en la memoria de código es la dirección.

Una función C, entonces, es una forma elaborada y fácil de programar para colocar bloques de código en la memoria y dirigir el procesador al bloque que debe ejecutarse. Si ha trabajado con lenguaje ensamblador, está familiarizado con la realidad de bajo nivel de la ejecución del código: cada instrucción tiene una dirección. Usamos una etiqueta de texto para representar una dirección dada, y si queremos que el procesador ejecute las instrucciones en esta dirección, le decimos que salte a la etiqueta.

Las funciones C son una mejora importante con respecto a las subrutinas básicas utilizadas en el lenguaje ensamblador, pero no son fundamentalmente diferentes. Cuando llama, o "invoca" a una función, el contador del programa del procesador recibe la dirección de la primera instrucción en lenguaje de máquina asociada con esa función.

La pila de llamadas

Una sección de la memoria conocida como pila de llamadas se usa para almacenar la dirección de la memoria de código a la que el procesador debería regresar después de que se haya ejecutado la función. La pila también proporciona ubicaciones de memoria para las variables locales, es decir, las variables que se crean cuando se llama a la función y se usan solo dentro de la función.

Un ejemplo de una pila de llamadas. Tenga en cuenta que el diagrama no indica cuántos bytes se requieren para cada elemento.

La pila de llamadas puede servir como un lugar para almacenar datos que se pasan a la función como un parámetro de entrada, pero, según tengo entendido, los compiladores utilizarán registros para esto en lugar de memoria siempre que sea posible (porque los registros son más rápidos).

Conclusión

Espero que este artículo le haya dado una buena visión general de la estructura y el comportamiento de una función, tanto dentro de un programa C como en el hardware real del procesador. Continuaremos nuestra discusión de las funciones de C en el próximo artículo.

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.