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
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_SET
macro.
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.
¿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_TABLE
y GPIO_OUT_REG_OFFSET
está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.
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.
el fotón
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 dey
.Shantanu Mhapankar
el fotón