¿Hay alguna forma de escribir un código GPIO genérico para MSP430?

Actualmente estoy trabajando en una placa MSP430 y estoy tratando de crear bibliotecas para poder usarlas fácilmente en mi proyecto. Estoy empezando con las funciones básicas de E/S digital.

Digamos, por ejemplo, que necesito configurar P1.0 en ON. En ese caso, normalmente lo que hago es:

P1SEL &= (~BIT0);           // Set P1.0 SEL for GPIO
P1DIR |= BIT0;              // Set P1.0 as Output
P1OUT |= BIT0;              // Set P1.0 HIGH

Pero necesito escribir una función que solo tome Port Num, Pin y Value y establezca los registros anteriores. Algo como esto:

void setVal(int x, int y) {
    PxSEL &= (~BITy);
    PxDIR |= BITy;
    PxOUT |= BITy;
}

Donde x es Puerto e Y es Pin. ¿Hay alguna manera de que pueda implementar tal función? ¿O se ha hecho esto antes? En caso afirmativo, comparta el enlace para el mismo. Estaba pensando en usar una tabla de búsqueda y seleccionar el Registro a través de la indexación. Pero no estoy seguro de si ese es un buen enfoque. Cualquier ayuda sería apreciada.

Gracias

La parte de la máscara de bits es fácil: uint16_t BITy = 1 << y;. Para obtener los registros del puerto, probablemente tendrá que mirar las direcciones sin procesar y descubrir cómo calcularlas a partir de y.
Gracias @ThePhoton. ¿No hay mejor manera de hacerlo para la parte del número de PUERTO?
Sería más portátil hacer una declaración de caso grande usando las macros PxSEL/PxDIR/PxOUT. Con suerte, su compilador será lo suficientemente inteligente como para reducir eso a algo razonablemente rápido en tiempo de ejecución.

Respuestas (4)

Esto se puede hacer con un conjunto de macros:

#define GPIO_MODE_GPIO(...)  GPIO_MODE_GPIO_SUB(__VA_ARGS__)
#define GPIO_MODE_GPIO_SUB(port, pin) (P##port##SEL &= ~(1<<pin)) // making an assumption about register layout here

#define GPIO_OUT_SET(...)  GPIO_OUT_SET_SUB(__VA_ARGS__)
#define GPIO_OUT_SET_SUB(port, pin) (P##port##OUT &= ~(1<<pin))

//etc...

El ##operador realiza la concatenación de cadenas en el preprocesador. Las macros de dos niveles se utilizan para permitir que ocurra un nivel de expansión de macros entre la primera y la segunda macros, lo que le permite hacer cosas como esta:

#define LED_IO  1,5
GIPO_OUT_SET(LED_IO);

Sin esa indirección adicional, el preprocesador se quejaría de no tener suficientes argumentos para la GPIO_OUT_SETmacro.

Este estilo de sistema se ha adaptado bastante bien a cada MCU que he usado hasta ahora, desde AVR hasta ARM, y dado que es una macro de preprocesador, se compila en el conjunto de instrucciones más pequeño posible.

Esto solo funciona si se conoce el número de puerto en el momento de la compilación. El código esqueleto de OP implica que se pasará a una función en tiempo de ejecución (aunque eso podría no ser lo que realmente necesitan).
Eso es lo que estaba asumiendo Photon
Bastante justo, eso fue una gran suposición de mi parte. Puedo imaginar algunas situaciones en las que le gustaría establecer un IO basado en una variable, pero en realidad nunca me encontré con una aplicación de este tipo (aparte de Arduino y otros HAL muy abstractos, donde el GPIO se abstrae en tiempo de ejecución por conveniencia en lugar de una necesidad o beneficio real).
@ThePhoton ¿Quiere decir que esto solo no funcionará si quiero configurar un puerto basado en una variable que he recibido durante el tiempo de ejecución, verdad? ¿Pero funcionará si mi LED_IO y otros IO son constantes?

¿Hay alguna manera de que pueda implementar tal función?

Claro, esto se puede hacer. Pero el código ya no será portátil.
Tendrá que validar el comportamiento cuando cambie la pieza.

Los registros periféricos GPIO se asignan en el bus en los siguientes rangos:

MODULE       BASE  SIZE
Port P1, P2  0200h 0020h  
Port P3, P4  0220h 0020h  
Port P5, P6  0240h 0020h

Esta es una implementación poco tradicional de TI. Han fusionado puertos pares e impares en un bloque. Por lo general, todos los registros de un bloque GPIO son consecutivos, lo que haría más factible un enfoque aritmético.
Sin embargo, el diseño anterior da los siguientes registros:

P1OUT = 0200h + 02h  
P2OUT = 0200h + 03h  
P3OUT = 0220h + 02h  
P4OUT = 0220h + 03h  
P5OUT = 0240h + 02h  
P6OUT = 0240h + 03h  

Con esos puedes hacer el siguiente código, usando la tabla de arriba:

void setVal(int x, int y) {
    const unsigned char *PxOUT = {0x202, 0x203, 0x222, 0x223, 0x242, 0x243};
    if(y)
        *PxOUT[x] |= (1<<y);
    else
        *PxOUT[x] &= ~(1<<y);
}

Una tabla es el método más rápido disponible. A costa de alguna ROM.
Esto se puede cambiar para permitir el acceso a otros registros también.
Por ejemplo, PxOUT[] -2 accedió al registro PxIN y +2 es el registro PxDIR.

Tomé mi información de la hoja de datos del msp430fr2153 .
Tabla 6-33 y Tabla 6-41. Es posible que esto no coincida con su parte, verifique .

Mi enfoque sobre MCU que ofrecen > 8kB de memoria es empaquetar GPIO en estructuras:

//
// GPIO Single Pin Descriptor 
//
struct GPIO_SPD      {      const char *    Description;
                            uint8_t         Port;
                            uint16_t        Pin;
                            bool            Initialzied;
};

Luego puede definir un GPIO dentro de su módulo:

struct GPIO_SPD GpioBlinkingLed= {"Simply blinking LED", GPIO_PORT_P1, GPIO_PIN0, false};

Y use esta estructura dentro de funciones simples de gpio similares a las suyas. Esta estructura, por ejemplo, se puede usar directamente dentro de TI driverlib , de la siguiente manera:

GPIO_setAsOutputPin(GpioBlinkingLed.Port, GpioBlinkingLed.Pin);
GPIO_setOutputHighOnPin(GpioBlinkingLed.Port, GpioBlinkingLed.Pin);
GPIO_toggleOutputOnPin(GpioBlinkingLed.Port, GpioBlinkingLed.Pin);
// etc.

Estas funciones residen en el llamado controlador gpio.c y, por supuesto, se basan en una tabla de búsqueda:

void GPIO_setOutputHighOnPin (uint8_t port, uint16_t pin) 
{
    uint16_t portAddress = GPIO_PORT_ADDRESS_TABLE[port];

    (*((volatile uint16_t *)(portAddress + GPIO_OUT_REG_OFFSET))) |= pin;
}

Lo simplifiqué específicamente, pero obtendrás la idea. GPIO_PORT_ADDRESS_TABLEy GPIO_OUT_REG_OFFSETestán predefinidos y los valores se toman de la hoja de datos de MCU. Afortunadamente, TI proporciona MSP430WARE que hace esos últimos bits por usted.

Hola @GAttuso, ¡gracias por esto! ¿Sin embargo, no puedo encontrar GPIO_PORT_ADDRESS_TABLE y GPIO_OUT_REG_OFFSET para mi placa MSP430G2553 en MSP430WARE?
@Shantanu Mhapankar Revisé driverlib y no encontré archivos de encabezado para la serie MSP430G2x. Entonces, probablemente, necesitará hacer su propia implementación.

Sería bueno si el código del microcontrolador pudiera usar la sustitución como lo harían los lenguajes de secuencias de comandos típicos de su computadora, pero eso requeriría una capa adicional de complejidad que solo podría hacerse en el microcontrolador, usando memoria y espacio de código y procesamiento.

Puede hacer esto con declaraciones if o case, y su compilador debería poder optimizarlo según sea necesario. Puede tomar el valor de registro que PxSel o lo que sea que realmente signifique, y usarlo en su lugar. Todavía requeriría si o declaraciones de caso o comparación. Como menciona Photon en los comentarios, básicamente estarías construyendo una tabla de búsqueda. Afortunadamente, estos son bastante limitados.

Dicho esto, estas funciones se han hecho hasta la saciedad, la rueda ha sido reescrita por innumerables desarrolladores varias veces. Una búsqueda en Google de la biblioteca gpio daría como resultado un par. TI ofrece a alguien sus ejemplos de código. Y se puede encontrar una copia bastante genérica en Energia , el puerto msp430 (y otros microcontroladores TI) del marco de código Arduino. Energia se puede usar directamente, solo para la biblioteca gpio Pin, o puede copiarlo, siguiendo la licencia correspondiente.