Escribiendo un programa para diferentes microcontroladores

Obtuve 2 microcontroladores totalmente diferentes (uno es MSP de 16 bits y otro nórdico de 32 bits). Se supone que deben hacer la misma tarea y pensé que tal vez sería posible escribir un programa en lenguaje C y luego usar diferentes compiladores mientras los programaba. Obtuve algunos archivos xxx.h comunes con diferentes implementaciones de origen para cada dispositivo, por lo que parte del código se vería exactamente igual. La diferencia es cuando se trata de escribir registros internos y esa parte del código tiene que ser diferente para cada dispositivo, así que pensé en introducir alguna macro que indicara para qué microcontrolador quiero compilar el código. Entonces podría usar #ifdefdirectiva dentro de los archivos fuente para llevar compilación condicional. No tengo experiencia con este tipo de programación, así que necesito algunos consejos, ¿es buena idea o es mejor escribir un programa para cada microcontrolador?

Respuestas (2)

El truco consiste en definir una interfaz que le dé acceso a los pines de E/S (u otros periféricos y funciones que necesite, y tal vez funciones de retardo). Declare esta interfaz en un archivo .h. Ahora escriba su aplicación en uno o más archivos .c que usen este archivo .h. Escriba dos archivos .c, uno para cada plataforma, que implementen la interfaz para esa plataforma en particular. Cuando cree para una plataforma, compile y vincule con el archivo .c de implementación correcto.

Un posible archivo .h:

bool pin_read( unsigned char pin );
void pin_write( unsigned char pin, bool value );
void configure_pin_as_input(  unsigned char pin );
void configure_pin_as_output(  unsigned char pin );
void delay_us( unsigned int t );

Este enfoque no requiere ningún truco del preprocesador, pero impone una sobrecarga de llamada de función en cada acceso a una función específica de la plataforma. Si esto no es aceptable, puede implementar ambas interfaces como macros en el archivo .h (usando #ifdefs para seleccionar entre ellas).

Otro truco es seguir con el enfoque .h/.c, pero compilar todo el código de la aplicación con la implementación.c incluida y confiar en el optimizador para eliminar la sobrecarga de la llamada a la función. Esto requiere dos archivos 'principales' diferentes, uno para el destino de 16 bits

// main.c for MSP430
#include "interface.h"
#include "msp430-interface.c"
#include "application.c"

y uno para el otro objetivo

// main.c for Cortex-M0
#include "interface.h"
#include "CortexM0-interface.c"
#include "application.c"

Personalmente, prefiero C++ para abordar estos problemas, y utilizo esencialmente el último enfoque, que es bastante normal para la programación basada en plantillas de C++ (las plantillas deben estar en el archivo .h). (Vea este artículo y este video para mi enfoque)

En general, esta es una buena respuesta, pero nunca #includearchivos .c. Nunca debería haber una razón para hacerlo, si su IDE y el enlazador están algo cuerdos. Además, esto agrega un estrecho acoplamiento entre el principal y el hardware, lo cual es malo. Solo debería necesitar cambiar el msp430-interface.c en el proyecto por el cortex M0, y todo debería vincularse bien, dado que esos dos archivos C son el archivo de interfaz #includegenérico y usan los mismos nombres de función. El principal solo debe ser #includeel mismo genérico. Asegúrese de usar protectores de encabezado en cada archivo h que se haya escrito.
Acepto que incluir código (aparte de las plantillas de C++) es la opción menos deseable y, en tal caso, esos archivos no deberían llamarse .c para indicar su uso de forma explícita. En ese caso, main.c no es un main clásico, esa función ha sido asumida por la aplicación incluida, y main.c tiene la función de un archivo de configuración. No es el enfoque más común, todavía está bien en algunas situaciones. El acoplamiento entre el 'código principal' (en uno de los archivos incluidos) y el hardware es el mismo que en el enfoque normal de interfaz en archivos .h.

Bueno, me imagino que el programa que estás haciendo tiene la misma funcionalidad en ambos microcontroladores.

En primer lugar, escribiría las partes genéricas del código, las que no dependen de la arquitectura. Cuando escribe o recupera datos de un dispositivo específico del microcontrolador o incluso accede a registros particulares del procesador, llamaría a una función.

En el archivo .c que implementa esta función, haría el #ifdef con una variable que define la arquitectura para seleccionar entre la función para el microcontrolador X y la de Y.

Tal vez deba estandarizar la interfaz de comunicación entre el código genérico y la función específica para poder soportar las particularidades de ambas MCU.

Recuerde que sería eficiente para un programa que tiene muchas partes comunes y solo un poco específico para la arquitectura.

Otra ventaja se refiere a la implementación de nuevas MCU. Si tiene que introducir una tercera arquitectura en su proyecto, simplemente escribiría un par de funciones nuevas, en lugar de volver a escribir todo el código.