Classes C ++ pour l'abstraction des broches d'E / S


13

Je recherche des abstractions C ++ pour les points d'E / S matérielles ou les broches. Des choses comme in_pin, out_pin, inout_pin, peut-être open_collector_pin, etc.

Je peux sûrement trouver un tel ensemble d'abstractions moi-même, donc je ne cherche pas des réponses de type `` hé, vous pourriez le faire de cette façon '', mais plutôt le `` regardez cette bibliothèque qui a été utilisée dans ceci et cela et ce projet'.

Google n'a rien révélé, peut-être parce que je ne sais pas comment les autres appellent cela.

Mon objectif est de créer des bibliothèques d'E / S basées sur de tels points, mais également de fournir de tels points, il serait donc facile, par exemple, de connecter un LCd HD44780 aux broches IO de la puce ou à un I2C (ou SPI) Extenseur d'E / S, ou tout autre point qui peut être contrôlé d'une manière ou d'une autre, sans aucune modification de la classe LCD.

Je sais que c'est à la pointe de l'électronique / du logiciel, désolé s'il n'appartient pas ici.

@leon: câblage C'est un gros sac de logiciels, je vais devoir regarder de plus près. Mais il semble qu'ils n'utilisent pas une abstraction de broches comme je le souhaite. Par exemple, dans la mise en œuvre du clavier, je vois

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

Cela implique qu'il existe une fonction (digitalWrite) qui sait écrire sur une broche d'E / S. Cela rend impossible l'ajout d'un nouveau type de broche d'E / S (par exemple, qui se trouve sur un MCP23017, il doit donc être écrit via I2C) sans réécrire la fonction digitalWrite.

@Oli: J'ai googlé un exemple Arduino IO, mais il semble utiliser à peu près la même approche que la bibliothèque de câblage:

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

De quel microcontrôleur parlons-nous ici?
Majenko

Ce n'est pas pertinent; pour un microcontrôleur particulier, les broches io de cette uC implémenteront les interfaces appropriées. Mais c'est pour C ++, alors pensez aux puces 32 bits comme ARM, Cortex et MIPS.
Wouter van Ooijen

1
Je n'en ai jamais utilisé, mais Arduino ne résume-t-il pas toutes les broches comme ça? Vous pouvez (ou non) obtenir des informations utiles sur la façon dont ils ont fait les choses.
Oli Glaser

1
Et comme pour la réécriture de la fonction digitalWrite - regardez "surcharge" en C ++. Il y a quelques instants, j'ai écrit une fonction digitalWrite surchargée pour une carte d'extension IO pour l'Arduino. Tant que vous utilisez différents paramètres (j'ai remplacé le premier "int" par un "struct"), il choisira votre digitalWrite de préférence à celui par défaut.
Majenko

1
J'ai fait un exposé sur la rencontre du C ++ à Berlin à propos de mon travail sur ce sujet. Il peut être trouvé sur youtube: youtube.com/watch?v=k8sRQMx2qUw Depuis lors, je suis passé à une approche légèrement différente, mais la discussion pourrait encore être intéressante.
Wouter van Ooijen

Réponses:


3

Réponse courte: malheureusement, il n'y a pas de bibliothèque pour faire ce que vous voulez. Je l'ai fait moi-même de nombreuses fois mais toujours dans des projets non open-source. J'envisage de mettre quelque chose sur github mais je ne sais pas quand je peux.

Pourquoi C ++?

  1. Le compilateur est libre d'utiliser une évaluation d'expression de taille de mot dynamique. C se propage à int. Votre masque d'octet / décalage peut être effectué plus rapidement / plus petit.
  2. Inline.
  3. Les opérations de création de modèles vous permettent de varier la taille des mots et d'autres propriétés, avec une sécurité de type.

5

Permettez-moi de brancher sans vergogne mon projet open source https://Kvasir.io . La partie Kvasir :: Io fournit des fonctions de manipulation des broches. Vous devez d'abord définir votre code PIN en utilisant un Kvasir :: Io :: PinLocation comme ceci:

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

Notez que cela n'utilise pas réellement de RAM car ce sont des variables constexpr.

Tout au long de votre code, vous pouvez utiliser ces emplacements de broches dans les fonctions «usine d'action» comme makeOpenDrain, set, clear, makeOutput et ainsi de suite. Une «fabrique d'actions» n'exécute pas réellement l'action, mais renvoie plutôt une action Kvasir :: Register :: qui peut être exécutée à l'aide de Kvasir :: Register :: apply (). La raison en est que apply () fusionne les actions qui lui sont transmises lorsqu'elles agissent sur un seul et même registre, ce qui génère un gain d'efficacité.

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

Étant donné que la création et la fusion d'actions sont effectuées au moment de la compilation, cela devrait produire le même code assembleur que l'équivalent codé à la main typique:

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

3

Le projet Wiring utilise une abstraction comme ça:

http://wiring.org.co/

et le compilateur est écrit en C ++. Vous devriez trouver de nombreux exemples dans le code source. Le logiciel Arduino est basé sur le câblage.


réponse dans le corps de la question
Wouter van Ooijen

2

En C ++, il est possible d'écrire une classe afin que vous puissiez utiliser les ports d'E / S comme s'il s'agissait de variables, par exemple

  PORTB = 0x12; / * Écrire sur un port 8 bits * /
  si (RB3) LATB4 = 1; / * Lire un bit d'E / S et en écrire conditionnellement un autre * /

sans égard à la mise en œuvre sous-jacente. Par exemple, si l'on utilise une plate-forme matérielle qui ne prend pas en charge les opérations au niveau du bit mais prend en charge les opérations de registre au niveau des octets, on pourrait (probablement à l'aide de certaines macros) définir une classe statique IO_PORTS avec une lecture / écriture en ligne propriétés appelées bbRB3 et bbLATB4, de sorte que la dernière instruction ci-dessus se transforme en

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

qui serait à son tour transformé en quelque chose comme:

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

Un compilateur doit pouvoir remarquer l'expression constante dans l'opérateur?: Et simplement inclure la partie "true". Il peut être possible de réduire le nombre de propriétés créées en élargissant les macros à quelque chose comme:

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

ou

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

mais je ne suis pas sûr qu'un compilateur puisse aussi bien aligner le code.

Je ne veux nullement laisser entendre que l'utilisation des ports d'E / S comme s'il s'agissait de variables est nécessairement une bonne idée, mais puisque vous mentionnez C ++, c'est une astuce utile à savoir. Ma propre préférence en C ou C ++, si la compatibilité avec le code qui utilise le style susmentionné n'était pas requise, serait probablement de définir un type de macro pour chaque bit d'E / S, puis de définir des macros pour "readBit", "writeBit", "setBit" et "clearBit" à condition que l'argument d'identification de bit transmis à ces macros soit le nom d'un port d'E / S destiné à être utilisé avec de telles macros. L'exemple ci-dessus, par exemple, serait écrit comme

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

et traduit comme

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

Ce serait un peu plus de travail pour le préprocesseur que le style C ++, mais ce serait moins de travail pour le compilateur. Il permettrait également une génération de code optimale pour de nombreuses implémentations d'E / S et une implémentation de code décente pour presque toutes.


3
Une citation de la question: "Je ne cherche pas des réponses de type" hé, vous pourriez le faire de cette façon "" ...
Wouter van Ooijen

Je suppose que je ne sais pas exactement ce que vous recherchez. Je m'attendrais certainement à ce que de nombreuses personnes intéressées par les classes de reconstruction des broches d'E / S soient également intéressées de savoir qu'en utilisant des propriétés, on peut faire en sorte que le code écrit pour un style d'E / S utilise à peu près n'importe quoi d'autre. J'ai utilisé des propriétés pour faire une déclaration comme "LATB3 = 1;" envoyer une demande d'E / S à un flux TCP.
supercat

J'ai essayé d'être clair dans ma question: je veux pouvoir accueillir de nouveaux types de broches IO sans réécrire le code qui utilise les broches IO. Vous écrivez sur les conversions de type définies par l'utilisateur et les opérateurs d'affectation, qui sont sûrement intéressants, je les utilise tout le temps, mais pas une solution à mon problème.
Wouter van Ooijen

@Wouter van Ooijen: Quels "nouveaux types de broches d'E / S" prévoyez-vous? Si le code source est écrit avec une syntaxe comme "if (BUTTON_PRESSED) MOTOR_OUT = 1;", je m'attendrais à ce que pour à peu près n'importe quel mécanisme par lequel le processeur pourrait lire une commande de bouton ou un moteur, on pourrait écrire une bibliothèque de sorte que la source ci-dessus le code allumerait le moteur si le bouton est enfoncé. Une telle bibliothèque n'est peut-être pas le moyen le plus efficace de mettre le moteur sous tension, mais cela devrait fonctionner.
supercat

@Wouter van Ooijen: On pourrait peut-être améliorer l'efficacité si l'on exigeait que le code source invoque une macro UPDATE_IO () ou UPDATE_INPUTS () quelque temps avant de lire n'importe quelle entrée et effectuer une UPDATE_IO () ou UPDATE_OUTPUTS () quelque temps après n'importe quelle sortie, avec la sémantique que les entrées peuvent être échantillonnées soit au code qui les lit, soit à l'invocation UPDATE_INPUTS () / UPDATE_IO () précédente. De même, les sorties peuvent se produire immédiatement ou être différées. Si une E / S est implémentée en utilisant quelque chose comme un registre à décalage, le report des actions permettrait de consolider plusieurs opérations.
supercat

1

Si vous cherchez quelque chose de vraiment génial pour abstraire le matériel et que vous avez confiance en vos compétences en C ++, alors vous devriez essayer ce modèle:

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

Je l'ai utilisé dans une tentative d'abstraction du matériel d'une puce Cortex-M0. Je n'ai encore rien écrit sur cette expérience (je le ferai un jour), mais croyez-moi, elle a été très utile en raison de sa nature polymorphe statique: la même méthode pour différentes puces, sans frais (par rapport au polymorphisme dynamique).


Dans les années qui ont suivi cette publication, je me suis installé sur des "classes" distinctes pour pin_in, pin_out, pin_oc et pin_in_out. Pour des performances optimales (taille et vitesse) j'utilise des classes statiques, passées comme paramètres de modèle. J'en ai parlé sur Meeting C ++ à Berlin
Wouter van Ooijen
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.