C a C ++ ¿Cómo hago que la inicialización de h / w se vea limpia? [cerrado]

3

Realizar una conversión experimental de algunos firmware de C a C ++ para intentar obtener una mejor estructura, ya que el código C "evolucionó" en línea con los experimentos en un período de 2 años.

La pregunta es sobre cómo empaquetar / envolver la gran cantidad de líneas de configuración de MCU que tengo, generalmente del formulario

  

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

Donde la estructura y las definiciones anteriores son parte de la biblioteca de MCU (que no quiero tocar)

¿O simplemente me coloco en una función "flotante" llamada init ()?

    
pregunta Dirk Bruere

3 respuestas

2

Estoy más o menos de acuerdo con Vicente en que si usas objetos, entonces los constructores de esos objetos son los lugares para realizar dicha inicialización de hardware.

Pero me disgustan mucho los nombres como "IOManager", porque no transmite lo que hace la cosa (los gerentes generalmente no hacen nada útil, ¿verdad?) y probablemente agrupan las acciones de manera incorrecta, lo que da el comentario clásico de las mayúsculas C indica que no pueden ver lo que se hace cuando se construye un objeto IOManager. Eso no es culpa de OO, es la consecuencia de una mala denominación y / o partición. No es el código de inicialización el que debe agruparse en conjunto, sino el uso de un periférico (inicialización y uso operativo).

Supongamos que utiliza un bus SPI, un LCD o un sensor de corriente analógico. OMI: el objeto que inicialice debe tener un nombre después de su uso, por lo tanto (por ejemplo) un LCD HD44780 que se utiliza como salida de registro. La creación del objeto debería ser algo así como

HD44780 logger( ... );

Lo que está en ... depende de qué tan flexible es el objeto HD44780. Si los pines y el tamaño (líneas x columnas) y otros detalles son totalmente fijos (podría ser el caso de un proyecto, pero es una mala idea para una biblioteca (reutilizable)) este conocimiento podría estar dentro de la clase HD44780. Si no, pase esta información como parámetros. Si tiene los recursos, pase los objetos que representan los pines. En ese caso, la inicialización de los pines se realiza en los constructores de pines (tal vez desee un puerto (= un conjunto de pines) para el d4-d7).

gpio lcd_e( 12 );
...
HD44780( lcd_e, ... );

Esto coloca la inicialización de los pines en el lugar en el que realmente debería estar: la clase de la biblioteca de pines.

Si no puede pagar la sobrecarga de objetos por pines, considere pasar números de pines y haga que el HD44780 llame a la biblioteca de pines para realizar la inicialización. Si usa macros o funciones en línea para acceder a los pines, esto puede ser tan eficiente como usar los pines directamente (algo así como la biblioteca de Arduino Wiring).

Una abstracción de OO ofrece flexibilidad, reutilización y buena estructura de código (y parece familiar para un programador de OO), pero no es gratis: esos objetos ocupan RAM, y llamar a funciones virtuales toma tiempo y bloquea las optimizaciones (pero la optimización del tiempo de enlace mejora cada vez más al eliminar dichos gastos generales). Por lo tanto, prefiero una aplicación basada en plantilla / clase estática que evite esta sobrecarga para las jerarquías conocidas en tiempo de compilación. Esto también tiene un costo: parece un poco extraño, incluso para los programadores experimentados de C ++. Pero puede ser tan eficiente como el código C dedicado. Compruebe ¿Objetos? ¡No, gracias! por una explicación más detallada.

Odin Holmes aboga por un enfoque aún más radical, donde cada módulo especifica sus requisitos de inicialización, que luego se combinan (¡en tiempo de compilación!). Múltiples escrituras en diferentes campos de bits del mismo registro se fusionan en una asignación.

    
respondido por el Wouter van Ooijen
2
  

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

Esto ya se ve bastante horrible, lo que probablemente sea culpa de la biblioteca.

El código más legible sería PORTX |= PORTX_PIN9; o alternativamente PORTX |= 1u<<PORTX_PIN9; ; este código es comprendido universalmente por todos los programadores. Las capas de abstracción solo añaden desorden.

Usted puede agregar una capa de abstracción para GPIO, en caso de que GPIO en la parte específica no sea trivial. Supongamos que necesita configurar registros de enrutamiento de pines o similar. O alternativamente, usted quiere que el código de acceso GPIO sea portátil entre proyectos. En ese caso, puede crear una HAL sobre el acceso GPIO.

Digamos que decidimos crear una HAL llamada GPIO . Si lo hacemos en un C ADT llamado gpio.h / gpio.c o en una clase de C ++ llamada class GPIO no importa. Necesitaría tener funciones como gpio_set y gpio_read para establecer o leer el estado del pin. Se pueden agregar otras características como registros de dirección de datos, registros de extracción e interrupciones.

Esta clase ADT / debe ser un singleton y no debe necesitar ninguna variable privada. Lo más probable es que todas las funciones miembro sean static .

Lo importante es que este GPIO HAL sabe cómo configurar un pin , pero no sabe cómo configurar el pin específico que hace la tarea X . Esa parte de la lógica de la aplicación debe estar en otras partes del programa.

Ahora el siguiente problema con tener una HAL como esta es que GPIO data direction & los registros de extracción deben establecerse lo antes posible para obtener el mejor rendimiento de EMI y ESD, etc. Esto significa que esos registros deben configurarse mucho antes que el "CRT" realice la inicialización de .bss /.data y llama a C ++ constructores de objetos con duración de almacenamiento estático.

Por lo tanto, usar un constructor de C ++ para establecer estos registros es un no-go, eso simplemente no es profesional, ya que significa que los registros críticos se establecerán demasiado tarde. (O lo que es peor, tenga el objeto C ++ GPIO como una variable local con almacenamiento automático).

Más bien, debe integrar la configuración de GPIO en el código "CRT" de alguna manera, o, dado que la mayoría de los "CRT" que proporcionan los compiladores son basura, escríbalos usted mismo.

Consulte estas pautas para saber cómo configurar una MCU en una aplicación profesional. Normalmente se llama a los constructores de C ++ después de la parte .bss / .data, por lo que no son adecuados para usar en cualquier forma de hardware fundamental como GPIO o la configuración de vigilancia.

    
respondido por el Lundin
1

Mi método preferido es usar constructores de clase para la configuración. Digamos que tienes una clase para la gestión de IO llamada IOManager. En IOManager.h:

class IOManager {
public:
    IOManager();
    // ... other relevant functions or variables ...
}

Luego, en IOManager.cpp:

#include "IOManager"
#include // the MCU includes

IOManager::IOManager() {
    // initialization code goes here, like the example:
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
}

Ahora, cuando llama a la iniciación de una instancia de IOManager, se ejecutará su código de constructor.

#include "IOManager"

int main() {
    IOManager myIOManager; // calling this will make the constructor be executed
    // ... rest of code using myIOManager ...
}

Uno puede argumentar que este tipo de enfoque "oculta" lo que realmente está haciendo el código, pero es una manera ordenada de especificar cuál es la inicialización requerida para cada módulo MCU diferente. En mi opinión, esto es también para lo que una "función constructora" estaba destinada a ser utilizada en el contexto de MCU.

    
respondido por el Vicente Cunha

Lea otras preguntas en las etiquetas