Clases de C ++ para abstracción de pines de E / S


13

Estoy buscando abstracciones en C ++ para puntos o pines de E / S de hardware. Cosas como in_pin, out_pin, inout_pin, quizás open_collector_pin, etc.

Seguramente puedo encontrarme con un conjunto de abstracciones, así que no estoy buscando el tipo de respuestas 'hey, podrías hacerlo de esta manera', sino más bien 'mira esta biblioteca que se ha utilizado en esto y esto y este proyecto'.

Google no apareció nada, tal vez porque no sé cómo otros llamarían esto.

Mi objetivo es construir bibliotecas de E / S que se basen en dichos puntos, pero también proporcionen dichos puntos, por lo que sería fácil, por ejemplo, conectar un HD44780 LCd a los pines IO del chip o a un I2C (o SPI) Extensor de E / S, o cualquier otro punto que de alguna manera se pueda controlar, sin ningún cambio en la clase de LCD.

Sé que esto está en el borde de la electrónica / software, lo siento si no pertenece aquí.

@leon: cableado Esa es una gran bolsa de software, tendré que mirar más de cerca. Pero parece que no usan una abstracción pin como yo quiero. Por ejemplo, en la implementación del teclado veo

digitalWrite(columnPins[c], LOW);   // Activate the current column.

Esto implica que hay una función (digitalWrite) que sabe cómo escribir en un pin de E / S. Esto hace que sea imposible agregar un nuevo tipo de pin de E / S (por ejemplo, uno que esté en un MCP23017, por lo que debe escribirse a través de I2C) sin reescribir la función digitalWrite.

@Oli: busqué en Google un ejemplo de Arduino IO, pero parece que usan el mismo enfoque que la biblioteca Wiring:

int ledPin = 13;                 // LED connected to digital pin 13
void setup(){
    pinMode(ledPin, OUTPUT);      // sets the digital pin as output
}

¿De qué microcontrolador estamos hablando aquí?
Majenko

Eso es irrelevante; para un microcontrolador particular, los pines io de ese uC implementarán las interfaces apropiadas. Pero esto es para C ++, así que piense en chips de 32 bits como ARM, Cortex y MIPS.
Wouter van Ooijen

1
Nunca he usado uno, pero ¿Arduino no abstrae todos los pines como este? Puede (o no) obtener información útil sobre cómo han hecho las cosas.
Oli Glaser

1
Y en cuanto a reescribir la función digitalWrite, observe la "sobrecarga" en C ++. Hace unos momentos escribí una función digitalWrite sobrecargada para una placa expansora de E / S para el Arduino. Siempre que use diferentes parámetros (reemplacé el primer "int" por un "struct"), elegirá su DigitalWrite con preferencia al predeterminado.
Majenko

1
Hice una charla sobre la reunión de C ++ en Berlín sobre mi trabajo en este tema. Se puede encontrar en youtube: youtube.com/watch?v=k8sRQMx2qUw Desde entonces cambié a un enfoque ligeramente diferente, pero la charla aún puede ser interesante.
Wouter van Ooijen

Respuestas:


3

Respuesta corta: lamentablemente, no hay una biblioteca para hacer lo que quieres. Lo he hecho muchas veces, pero siempre en proyectos que no son de código abierto. Estoy considerando poner algo en Github, pero no estoy seguro de cuándo puedo.

¿Por qué C ++?

  1. El compilador es libre de usar una evaluación de expresión dinámica de tamaño de palabra. C se propaga a int. Su máscara / cambio de bytes se puede hacer más rápido / más pequeño.
  2. En línea.
  3. Las operaciones de plantilla le permiten variar el tamaño de las palabras y otras propiedades, con seguridad de tipos.

5

Permítanme conectar descaradamente mi proyecto de código abierto https://Kvasir.io . La porción Kvasir :: Io proporciona funciones de manipulación de pin. Primero debe definir su pin usando un Kvasir :: Io :: PinLocation de esta manera:

constexpr PinLocation<0,4> led1;    //port 0 pin 4
constexpr PinLOcation<0,8> led2;

Tenga en cuenta que esto en realidad no usa RAM porque estas son variables constexpr.

A lo largo de su código, puede usar estas ubicaciones de pin en funciones de 'fábrica de acciones' como makeOpenDrain, set, clear, makeOutput, etc. Una 'fábrica de acciones' en realidad no ejecuta la acción, sino que devuelve un Kvasir :: Register :: Action que se puede ejecutar usando Kvasir :: Register :: apply (). La razón de esto es que apply () fusiona las acciones que se le pasan cuando actúan en un mismo registro para que haya una ganancia de eficiencia.

apply(makeOutput(led1),
    makeOutput(led2),
    makeOpenDrain(led1),
    makeOpenDrain(led2));

Dado que la creación y fusión de acciones se realiza en tiempo de compilación, esto debería generar el mismo código de ensamblador que el equivalente codificado a mano típico:

PORT0DIR |= (1<<4) | (1<<8);
PORT0OD |= (1<<4) | (1<<8);


2

En C ++, es posible escribir una clase para que pueda usar los puertos de E / S como si fueran variables, p. Ej.

  PORTB = 0x12; / * Escribir en un puerto de 8 bits * /
  si (RB3) LATB4 = 1; / * Leer un bit de E / S y escribir condicionalmente otro * /

sin tener en cuenta la implementación subyacente. Por ejemplo, si uno usa una plataforma de hardware que no admite operaciones de nivel de bit pero admite operaciones de registro de nivel de byte, uno podría (probablemente con la ayuda de algunas macros) definir una clase estática IO_PORTS con una lectura-escritura en línea propiedades llamadas bbRB3 y bbLATB4, de modo que la última declaración anterior se convertiría en

  if (IO_PORTS.bbRB3) IO_PORTS.bbLATB4 = 1;

que a su vez se procesaría en algo como:

  if (!! (PORTB & 8)) (1? (PORTB | = 16): (PORTB & = ~ 16));

Un compilador debe poder notar la expresión constante en el operador?: Y simplemente incluir la parte "verdadera". Es posible reducir la cantidad de propiedades creadas haciendo que las macros se expandan a algo como:

  if (IO_PORTS.ppPORTB [3]) IO_PORTS.ppPORTB [4] = 1;

o

  if (IO_PORTS.bb (addrPORTB, 3)) IO_PORTS.bbPORTB (addrPORTB, 4) = 1;

pero no estoy seguro de que un compilador pueda alinear el código tan bien.

De ninguna manera deseo implicar que usar puertos de E / S como si fueran variables es necesariamente una buena idea, pero como mencionas C ++ es un truco útil para saber. Mi preferencia en C o C ++, si no fuera necesaria la compatibilidad con el código que usa el estilo mencionado anteriormente, probablemente sería definir algún tipo de macro para cada bit de E / S, y luego definir macros para "readBit", "writeBit", "setBit" y "clearBit" con la condición de que el argumento de identificación de bits pasado a esas macros debe ser el nombre de un puerto de E / S destinado a usarse con tales macros. El ejemplo anterior, por ejemplo, se escribiría como

  if (readBit (RB3)) setBit (LATB4);

y traducido como

  if (!! (_ PORT_RB3 & _BITMASK_RB3)) _PORT_LATB4 | = _BITMASK_LATB4;

Eso sería un poco más de trabajo para el preprocesador que el estilo C ++, pero sería menos trabajo para el compilador. También permitiría la generación óptima de código para muchas implementaciones de E / S y una implementación de código decente para casi todos.


3
Una cita de la pregunta: "No estoy buscando el tipo de respuestas 'hey, podrías hacerlo de esta manera'" ...
Wouter van Ooijen

Supongo que no tengo muy claro lo que estás buscando. Ciertamente, esperaría que muchas personas que estén interesadas en las clases para la reconstrucción de pines de E / S también estén interesadas en saber que al usar propiedades uno puede hacer que el código escrito para un estilo de E / S use casi cualquier otra cosa. He usado propiedades para hacer una declaración como "LATB3 = 1;" enviar una solicitud de E / S a una secuencia TCP.
supercat

Traté de ser claro en mi pregunta: quiero poder acomodar nuevos tipos de pines IO sin reescribir el código que usa pines IO. Escribes sobre conversiones de tipos definidos por el usuario y operadores de asignación, que seguramente son interesantes, los uso todo el tiempo, pero no es una solución a mi problema.
Wouter van Ooijen

@Wouter van Ooijen: ¿Qué "nuevos tipos de pines de E / S" anticiparía? Si el código fuente se escribe con una sintaxis como "if (BUTTON_PRESSED) MOTOR_OUT = 1;", esperaría que para cualquier mecanismo por el cual el procesador pueda leer un control de botón o un motor, uno pueda escribir una biblioteca para que la fuente anterior el código encendería el motor si se presiona el botón. Tal biblioteca podría no representar la forma más eficiente de encender el motor, pero debería funcionar.
supercat

@Wouter van Ooijen: Quizás se podría mejorar la eficiencia si se requiere que el código fuente invoque una macro UPDATE_IO () o UPDATE_INPUTS () en algún momento antes de leer cualquier entrada, y realice una UPDATE_IO () o UPDATE_OUTPUTS () en algún momento después de cualquier salida, con la semántica de que las entradas se pueden muestrear en el código que las lee o en la invocación UPDATE_INPUTS () / UPDATE_IO () anterior. Del mismo modo, las salidas pueden ocurrir inmediatamente o diferirse. Si se implementa una E / S utilizando algo como un registro de desplazamiento, las acciones de aplazamiento permitirían consolidar múltiples operaciones.
supercat

1

Si está buscando algo realmente increíble para abstraer el hardware, y está seguro de sus habilidades en C ++, entonces debería probar este patrón:

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Lo he usado en un intento de abstraer hardware para un chip Cortex-M0. Todavía no he escrito nada sobre esta experiencia (lo haré algún día), pero créanme que ha sido muy útil debido a su naturaleza polimórfica estática: el mismo método para diferentes chips, sin costo (en comparación con el polimorfismo dinámico).


En los años transcurridos desde esta publicación, participé en "clases" separadas para pin_in, pin_out, pin_oc y pin_in_out. Para un rendimiento óptimo (tamaño y velocidad) utilizo clases estáticas, pasadas como parámetros de plantilla. Hablé sobre esto en Meeting C ++ en Berlín
Wouter van Ooijen
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.