Un'interfaccia per la progettazione del codice Arduino

1

Spesso voglio che la scheda Arduino controlli diverse periferiche. Scrivere un programma per tutti in un documento usando #define SOME_PIN 4 costruzioni mi sembra abbastanza sconveniente. Soprattutto in questo caso quando devi modificare il vecchio codice che è stato scritto molto tempo fa.

Ho scelto di utilizzare il seguente approccio per organizzare la parte di programmazione di un progetto. Quindi scrivere un codice potrebbe essere diviso in tre parti.

1. Struttura interfaccia

In questa fase il programmatore dovrebbe descrivere tutti i dispositivi usati nel suo progetto. Naturalmente, solo quelli di loro che Arduino programmaticamente interagisce con (sensori, chip e non resistori intermedi, per esempio). Ogni dispositivo in questa fase è rappresentato da una classe - description-class . La classe descrittiva ha funzioni membro speciali - funzioni-pin . Quelle funzioni sono pure virtuali e ognuna di esse è destinata a restituire un valore intero senza segno a 8 bit dal set di numeri di pin Arduino. Le funzioni pin svolgono un ruolo di fili tra Arduino e le periferiche. La classe descrittiva dovrebbe contenere anche funzioni che sono istruzioni Arduino e destinate a questo dispositivo concreto.

Arduino ha anche una classe descrittiva, ma ora è usata come spazio dei nomi con costanti. Nessun oggetto di questa classe è necessario finora.

Di seguito è riportato un esempio di utilizzo dell'interfaccia.

  • Controller: Arduino Uno R3
  • Periferiche: sensore ultrasonico HC-SR04

1.1 Descrizione della classe Arduino Uno R3

La classe descrizione di Arduino Uno R3 consiste solo di costanti statiche che sono numeri pin.

1.1.1 Uno.h

#ifndef UNO_H
#define UNO_H

#include <inttypes.h>

class Uno
{
    public :
        //Digital pins
        static const uint8_t D0 = 0;
        static const uint8_t D1 = 1;
        static const uint8_t D2 = 2;
        static const uint8_t D3 = 3;
        static const uint8_t D4 = 4;
        static const uint8_t D5 = 5;
        static const uint8_t D6 = 6;
        static const uint8_t D7 = 7;
        static const uint8_t D8 = 8;
        static const uint8_t D9 = 9;
        static const uint8_t D10 = 10;
        static const uint8_t D11 = 11;
        static const uint8_t D12 = 12;
        static const uint8_t D13 = 13;
        //Analog pins
        static const uint8_t A0 = 0;
        static const uint8_t A1 = 1;
        static const uint8_t A2 = 2;
        static const uint8_t A3 = 3;
        static const uint8_t A4 = 4;
        static const uint8_t A5 = 5;
        //SPI
        static const uint8_t SS = D10;
        static const uint8_t MOSI = D11;
        static const uint8_t MISO = D12;
        static const uint8_t SCK = D13;
        //I2C
        static const uint8_t SDA = A4;
        static const uint8_t SCL = A5;
        //Serial
        static const uint8_t TX = D0;
        static const uint8_t RX = D1;
};
#endif

1.2. Descrizione della descrizione del sensore ultrasonico (dispositivo astratto)

Per questo esempio ho scelto di utilizzare il sensore ultrasonico HC-SR04. Ha quattro pin: VCC , GND , TRIG e ECHO . Anche se in realtà (qualunque cosa sia) tutti e quattro i pin sono collegati ad Arduino, solo TRIG e ECHO sono usati nel programma. Non so cosa fare con quegli spilli finora. Forse non dovrebbero mai apparire nel codice.

1.2.1 HC_SR04.h

#ifndef HC_SR04_H
#define HC_SR04_H

#include <inttypes.h>

#include <Arduino.h>

class HC_SR04
{
    public :
        //pin-functions
        virtual uint8_t VCC() = 0;//???
        virtual uint8_t TRIG() = 0;
        virtual uint8_t ECHO() = 0;
        virtual uint8_t GND() = 0;//???
        //device specific functions
        void setup();

        long get_distance();
};
#endif

1.2.2 HC_SR04.cpp

#include "HC_SR04.h"

void HC_SR04::setup()
{
    pinMode( TRIG(), OUTPUT );
    pinMode( ECHO(), INPUT );
}


long HC_SR04::get_distance()
{
    digitalWrite( TRIG(), LOW );
    delayMicroseconds( 2 );
    digitalWrite( TRIG(), HIGH );
    delayMicroseconds( 10 );
    digitalWrite( TRIG(), HIGH );

    return 0.017 * pulseIn( ECHO(), HIGH );
}

2. Descrizione della CPU

In questa fase il programmatore dovrebbe "collegare" tutti i dispositivi reali sul tavolo sovraccaricando le funzioni pin in una sottoclasse derivata dalla corrispondente descrizione della classe.

2.1 Sensore a ultrasuoni (dispositivo reale)

2.1.1 US.cpp

#include "Uno.h"
#include "HC_SR04.h"

class US : public HC_SR04
{
    public :
        uint8_t VCC() { return 0; }//What to do with this pin
        uint8_t GND() { return 0; }//What to do with this pin
        uint8_t TRIG() { return Uno::D10; }//wire between TRIG-pin on US and 10th digital pin on Uno
        uint8_t ECHO() { return Uno::D11; }//...
};

3. File principale (file .ino)

#include "US.cpp"//bad line?

US us;//Create ultrasonic device plugged in as descripted in US.cpp file

void setup()
{
    Serial.begin( 9600 );

    us.setup();
}


void loop()
{
    Serial.println( us.get_distance() );
    delay( 1000 );
}

4. Struttura della directory del progetto

5.Discussione

Eccolamiavistasull'approcciosopra.Certo,potrebbeesseresbagliato.

Svantaggi:

  • Livelloaggiuntivodiastrazione=(forse)negativopericontrolleramemoriabassa
  • L'interoprogrammaèdivisoinparti.Ogniparteèresponsabiledellacomunicazioneconildispositivoconcreto.(forse)Cattivoperunprogettocondispositividipendenti

Ivantaggi:

  • L'interoprogrammaèdivisoinparti.Ogniparteèresponsabiledellacomunicazioneconildispositivoconcreto.Ottimoperunprogettoconmolteperifericheindipendenti
  • Codiceauto-descrittivo
  • Codiceriutilizzabileperdispositivisimilioaltriprogetticonglistessidispositivi

Perfavore,dimmicosanepensi.Comesempre,qualsiasiidea,correzione,critica,ecc.Sarebbeapprezzata.

P.S.PercaricareilprogrammasuArduino,utilizzo Makefile per Arduino .

    
posta LRDPRDX 10.09.2018 - 08:12
fonte

1 risposta

1

L'idea di base di incapsulare il comportamento di un dispositivo è una buona idea. Tuttavia, l'utilizzo dell'ereditarietà per fornire valori di configurazione, come i pin ai quali è connesso il dispositivo, non è l'approccio corretto.

Prima di tutto, dover creare una nuova classe solo perché hai collegato un secondo sensore non corrisponde ai principi del design OO. Le classi Singleton (le classi delle quali solo una singola istanza dovrebbe mai essere creata) dovrebbero essere una rarità e generalmente disapprovate.

In secondo luogo, e più importante per i piccoli sistemi embedded, le funzioni virtuali costano un bel po 'di memoria rispetto alle funzioni non virtuali. Per un tipico compilatore, una funzione virtuale costa 1 puntatore per funzione più 1 puntatore per istanza di una classe contenente funzioni virtuali più un sovraccarico extra quando si chiama una funzione virtuale.

Un approccio migliore e più comune sarebbe passare i numeri dei pin al costruttore della classe HC_SR04, in questo modo:

#ifndef HC_SR04_H
#define HC_SR04_H

#include <inttypes.h>

#include <Arduino.h>

class HC_SR04
{
    private:
       const uint8_t pin_trig;
       const uint8_t pin_echo;

    public :
        HC_SR04(uint8_t trig, uint8_t echo) :
          pin_trig(trig), pin_echo(echo) {}

        //device specific functions
        void setup();

        long get_distance();
};
#endif

Ho eliminato i pin VCC e GND, perché in genere non dovrebbero essere controllati dal software ma collegati rispettivamente ai piani di alimentazione e di messa a terra.

La creazione di un'istanza di tale sensore sarebbe simile a

HL_SR04 us(Uno::D10, Uno::D11);
    
risposta data 10.09.2018 - 11:42
fonte

Leggi altre domande sui tag