Classi C ++ per astrazione pin I / O


13

Sto cercando astrazioni C ++ per punti o pin I / O hardware. Cose come in_pin, out_pin, inout_pin, forse open_collector_pin, ecc.

Sicuramente posso inventare un tale insieme di astrazioni me stesso, quindi non sto cercando risposte tipo "hey, potresti farlo in questo modo", ma piuttosto "guarda questa libreria che è stata usata in questo e questo e questo progetto'.

Google non ha mostrato nulla, forse perché non so come gli altri lo chiamerebbero.

Il mio obiettivo è quello di costruire librerie I / O che si basano su tali punti, ma che forniscano anche tali punti, quindi sarebbe facile per esempio collegare un LCd HD44780 ai pin IO del chip, o un I2C (o SPI) Extender I / O o qualsiasi altro punto che può in qualche modo essere controllato, senza alcuna modifica alla classe LCD.

So che questo è ai margini dell'elettronica / del software, scusate se non appartiene qui.

@leon: cablaggio Questa è una grande borsa di software, dovrò guardare più da vicino. Ma sembra che non usino un'astrazione di spilli come voglio. Per esempio nell'implementazione della tastiera vedo

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

Ciò implica che esiste una funzione (digitalWrite) che sa come scrivere su un pin I / O. Ciò rende impossibile aggiungere un nuovo tipo di pin I / O (ad esempio uno che si trova su un MCP23017, quindi deve essere scritto tramite I2C) senza riscrivere la funzione digitalWrite.

@Oli: ho cercato su Google un esempio di Arduino IO, ma sembra che utilizzi lo stesso approccio della libreria Wiring:

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

Di quale microcontrollore stiamo parlando qui?
Majenko,

Questo è irrilevante; per un particolare microcontrollore, gli io pin di tale uU implementeranno le interfacce appropriate. Ma questo è per C ++, quindi pensa a chip a 32 bit come ARM, Cortex e MIPS.
Wouter van Ooijen,

1
Non ne ho mai usato uno, ma Arduino non estrae tutti i pin in questo modo? È possibile (o meno) ottenere alcune informazioni utili osservando il modo in cui hanno fatto le cose.
Oli Glaser,

1
E per quanto riguarda la riscrittura della funzione digitalWrite, guarda "sovraccarico" in C ++. Ho scritto solo pochi istanti fa una funzione sovraccarica digitalWrite per una scheda di espansione IO per Arduino. Fintanto che usi parametri diversi (ho sostituito il primo "int" con uno "struct") sceglierà il tuo digitalWrite preferibilmente a quello predefinito.
Majenko,

1
Ho fatto un discorso sull'incontro con il C ++ a Berlino sul mio lavoro su questo argomento. Può essere trovato su youtube: youtube.com/watch?v=k8sRQMx2qUw Da allora sono passato a un approccio leggermente diverso, ma il discorso potrebbe essere ancora interessante.
Wouter van Ooijen,

Risposte:


3

Risposta breve: purtroppo non esiste una biblioteca per fare ciò che vuoi. L'ho fatto io stesso numerose volte ma sempre in progetti non open-source. Sto pensando di mettere qualcosa su Github ma non sono sicuro di quando posso.

Perché C ++

  1. Il compilatore è libero di utilizzare la valutazione dinamica dell'espressione delle dimensioni di una parola. C si propaga a int. La maschera / spostamento dei byte può essere eseguita più velocemente / più piccoli.
  2. Inlining.
  3. Le operazioni di templatizzazione ti consentono di variare le dimensioni delle parole e altre proprietà, con la sicurezza del tipo.

5

Mi permetta di collegare spudoratamente il mio progetto open source https://Kvasir.io . La parte Kvasir :: Io fornisce funzioni di manipolazione dei pin. Devi prima definire il tuo pin usando un Kvasir :: Io :: PinLocation in questo modo:

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

Si noti che questo non utilizza effettivamente la RAM perché si tratta di variabili constexpr.

Durante tutto il codice è possibile utilizzare queste posizioni dei pin nelle funzioni 'action factory' come makeOpenDrain, set, clear, makeOutput e così via. Una 'factory di azioni' non esegue effettivamente l'azione, ma restituisce un Kvasir :: Register :: Action che può essere eseguito usando Kvasir :: Register :: apply (). La ragione di ciò è che apply () unisce le azioni che gli vengono passate quando agiscono sullo stesso registro e quindi si ottiene un guadagno di efficienza.

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

Poiché la creazione e l'unione delle azioni viene eseguita in fase di compilazione, ciò dovrebbe produrre lo stesso codice assembler del tipico equivalente con codifica manuale:

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


2

In C ++, è possibile scrivere una classe in modo da poter usare le porte I / O come se fossero variabili, ad es

  PORTB = 0x12; / * Scrive su una porta a 8 bit * /
  if (RB3) LATB4 = 1; / * Leggi un bit I / O e scriverne condizionalmente un altro * /

senza riguardo per l'implementazione sottostante. Ad esempio, se si utilizza una piattaforma hardware che non supporta operazioni a livello di bit ma supporta operazioni di registro a livello di byte, è possibile (probabilmente con l'aiuto di alcune macro) definire una classe statica IO_PORTS con una lettura / scrittura in linea proprietà chiamate bbRB3 e bbLATB4, in modo che l'ultima istruzione sopra si trasformasse in

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

che a sua volta verrebbe trasformato in qualcosa del tipo:

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

Un compilatore dovrebbe essere in grado di notare l'espressione costante nell'operatore?: E includere semplicemente la parte "vera". Potrebbe essere possibile ridurre il numero di proprietà create facendo espandere le macro a qualcosa del tipo:

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

o

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

ma non sono sicuro che un compilatore sarebbe in grado di incorporare il codice altrettanto bene.

Non voglio assolutamente implicare che usare le porte I / O come se fossero variabili è necessariamente una buona idea, ma dal momento che si parla di C ++ è un trucco utile da sapere. La mia preferenza in C o C ++, se non fosse richiesta la compatibilità con il codice che utilizza lo stile di cui sopra, sarebbe probabilmente quella di definire un tipo di macro per ciascun bit I / O, e quindi definire macro per "readBit", "writeBit", "setBit" e "clearBit" con la condizione che l'argomento di identificazione dei bit passato a tali macro deve essere il nome di una porta I / O destinata all'uso con tali macro. L'esempio sopra, per esempio, sarebbe scritto come

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

e tradotto come

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

Sarebbe un po 'più lavoro per il preprocessore rispetto allo stile C ++, ma sarebbe meno lavoro per il compilatore. Consentirebbe anche una generazione ottimale del codice per molte implementazioni I / O e un'implementazione decente del codice per quasi tutti.


3
Una citazione dalla domanda: "Non sto cercando 'hey, potresti farlo in questo modo' tipo di risposte" ...
Wouter van Ooijen,

Immagino di non essere abbastanza chiaro su cosa stai cercando. Sicuramente mi aspetto che molte persone interessate alle lezioni per la ricostruzione dei pin I / O siano interessate a sapere che usando le proprietà si può fare in modo che il codice scritto per uno stile di I / O usi qualsiasi altra cosa. Ho usato proprietà per fare una dichiarazione del tipo "LATB3 = 1;" inviare una richiesta I / O a un flusso TCP.
supercat

Ho cercato di chiarire la mia domanda: voglio essere in grado di accogliere nuovi tipi di pin IO senza riscrivere il codice che utilizza pin IO. Scrivi su conversioni di tipi definiti dall'utente e operatori di assegnazione, che sono sicuramente interessanti, li uso sempre, ma non una soluzione al mio problema.
Wouter van Ooijen,

@Wouter van Ooijen: Quali "nuovi tipi di pin I / O" ti aspetteresti? Se il codice sorgente è scritto con sintassi come "if (BUTTON_PRESSED) MOTOR_OUT = 1;", mi aspetterei che per quasi tutti i meccanismi con cui il processore potrebbe leggere un controllo pulsante o un motore si potrebbe scrivere una libreria in modo che la fonte sopra il codice accenderebbe il motore se si preme il pulsante. Tale libreria potrebbe non rappresentare il modo più efficiente di accendere il motore, ma dovrebbe funzionare.
Supercat,

@Wouter van Ooijen: si potrebbe forse migliorare l'efficienza se si richiedesse che il codice sorgente invochi una macro UPDATE_IO () o UPDATE_INPUTS () prima o dopo la lettura di qualsiasi input ed esegua UPDATE_IO () o UPDATE_OUTPUTS () qualche tempo dopo qualsiasi output, con la semantica che gli input potrebbero essere campionati al codice che li legge o alla precedente chiamata UPDATE_INPUTS () / UPDATE_IO (). Allo stesso modo le uscite potrebbero verificarsi immediatamente o essere rinviate. Se un I / O viene implementato usando qualcosa come un registro a scorrimento, il differimento delle azioni consentirebbe il consolidamento di più operazioni.
Supercat,

1

Se stai cercando qualcosa di veramente fantastico per astrarre l'hardware e sei sicuro delle tue abilità in C ++, allora dovresti provare questo schema:

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

L'ho usato in un tentativo per sottrarre hardware per un chip Cortex-M0. Non ho ancora scritto nulla di questa esperienza (lo farò un giorno), ma credetemi è stato molto utile a causa della sua natura polimorfica statica: lo stesso metodo per chip diversi, a costo zero (rispetto al polimorfismo dinamico).


Negli anni successivi a questo post ho puntato su "classi" separate per pin_in, pin_out, pin_oc e pin_in_out. Per prestazioni ottimali (dimensioni e velocità) utilizzo le classi statiche, passate come parametri del modello. Ne ho parlato al Meeting C ++ di Berlino
Wouter van Ooijen, il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.