Thursday, 7 January 2016

Half-a-day project:προγραμματιζόμενη έξοδος 0-10V με arduino

Βρέθηκα να συζητάω για το πώς θα μπορούσε να φτιαχτεί ένα σύστημα που να βγάζει προγραμματιζόμενα μία μεταβλητή τάση από 0 έως 10V.

Σκέφτηκα ότι δεν δεν πρέπει να είναι τίποτα δύσκολο, σίγουρα θα έχει φτιαχτεί πολλές φορές και αν το "γκουγκλάρω" σίγουρα θα βρεθεί κάτι.
Αλλά δεν βρήκα! Βρήκα ένα κάρο κουβέντες αλλά όχι κάποια σαφή πρόταση..

πχ στο arduino forum υπάρχει η συζήτηση 
Topic: analog out 0-10V With Arduino for lighting control (Read 22823 times)" που όμως εκεί γίνεται κουβέντα για OpAmps, LMs, ADCs κλπ κλπ χωρίς να έχουν καταλήξει κάπου ή να υπάρχει κάτι συγκεκριμένο.
To ίδιο εδώ, (0-10V needed) σε ένα παλιό forum για PICs, ξαναρωτάνε τα ίδια πράγματα 10 χρόνια μετά το αρχικό post...
ή εδώ, και εδώ πάλι κουβέντες για OpAmps, που πρέπει να είναι non-inverting, να οδηγηθούν από μία αναλογική έξοδο, να είναι rail-to-rail, να μην χρειάζονται συμμετρική τροφοδοσία κλπ κλπ κλπ

Μου έκανε εντύπωση που δεν βρήκα μία ξεκάθαρη λύση, ένα step-by-step guide και είπα να το ψάξω να δω αν είναι τελικά τόσο δύσκολο ή τόσο πολύπλοκο.

Ας δούμε τι χρειαζόμαστε:

θέλουμε μία τάση που να κυμαίνεται από 0 έως 10V για να οδηγήσει κάποιο άλλο σύστημα, κυρίως dimmers.

H τάση που θα παίρνουμε θέλουμε να μεταβάλλεται προγραμματιζόμενα, πχ η μετάβαση από 0 στα 10V να γίνεται ομαλά και να ολοκληρώνεται σε μισή ώρα, το σύστημα να κρατάει τα 10V για 12 ώρες και μετά να μειώνεται ομαλά η τάση έως ότου φτάσει τα 0V.

τα προβλήματα που έχουμε είναι:

  1. τα arduino δεν έχουν μετατροπέα ψηφιακού σε αναλογικό.
  2. αν προσθέσουμε ή αν φτιάξουμε ένα D-to-A με αντιστάσεις και πάλι δεν μπορούμε να βγάλουμε 10V, μπορούμε να βγάλουμε έως 5 (-ακραία-)
  3. θα μπορούσαμε να διπλασιάσουμε την τάση με ένα τελεστικό ενισχυτή (OpAmp) με θετική ανάδραση και gain 2 αλλά είναι πολύπλοκη λύση και δημιουργεί άλλα θέματα όπως τροφοδοσίας του OpΑmp, κόστος, πολυπλοκότητα κατασκευής κ.α.

Βεβαίως το Arduino έχει μία συνάρτηση analogWrite() που όμως δίνει έξοδο Pulse Width Modulation και όχι αναλογική:
Το σήμα PWM μπορεί να γίνει εύκολα αναλογικό προσθέτοντας ένα low pass φίλτρο που απαρτίζεται από μία αντίσταση και ένα πυκνωτή, οι τιμές των οποίων μπορούν να υπολογιστούν με το εργαλείο που εμφανίζεται χαμηλά σε αυτό το λινκ.

Καλά όλα αυτά, στο δια ταύτα όμως:

Αυτό που έκανα εγώ (και που είναι τόσο απλό που σίγουρα θα έχει ξαναγίνει αλλά δεν μπόρεσα να το βρω στο google και για αυτό γράφω αυτό εδώ...) είναι αντί να κάνω το PWM σήμα αναλογικό και μετά να το ενισχύσω για να πάρει τιμές από 0 έως 10V, ή να δοκιμάσω να ελέγξω ένα variable voltage regulator, αυτό που έκανα είναι να μετασχηματίσω το πλάτος του PWM σήματος από 0-5V σε 0-10V και μετά να κάνω το σήμα αναλογικό χρησιμοποιώντας ένα φίλτρο RC.

Επίσης, επειδή για λόγους απλότητας με βόλευε να αντιστρέψω το σήμα, κάνω αντιστροφή της τιμής του PWM σε επίπεδο λογισμικού, με χρήση του τελεστή ~ (bitwise NOT). Ή απλούστερα, λέω ότι το νούμερο 255 αντιστοιχεί σε 0V και το  νούμερο 0 αντιστοιχεί σε 10V με όλα τα ενδιάμεσα να μοιράζονται ομαλά. 
Αν σε κάποια άλλη εφαρμογή αυτό δεν παίζει, τότε μπορεί να μπει ένα δεύτερο transistor που θα κάνει μία ακόμα αντιστροφή επαναφέροντας το PWM στην αρχική του μορφή.
Στην εφαρμογή που περιγράφω δεν μας πειράζει αν είναι ανάποδα το PWM, το ελέγχουμε από το software.

Οπότε:

το κύκλωμα είναι αυτό:



Τροφοδοτείται από 12VDC, και το arduino παίρνει ρεύμα από το RAW. To τρανσιστορ είναι ένα BC547 (ξέχασα να το σημειώσω στο χαρτί).

Η υλοποίηση σε breadboard είναι αυτή:


έχω χρησιμοποιήσει ένα Arduino Pro Mini που προγραμματίζεται μέσω σειριακής πόρτας, η πορτοκαλί πλακετίτσα που φαίνεται αριστερά του arduino είναι για τον προγραμματισμό/debugging και μετά αφαιρείται.

Ακολουθούν δύο βίντεο. Και στα δύο τρέχω μία διαδικασία Sunrize/Sunset, δηλαδή ξεκινώ από την κατάσταση που δίνει έξοδο 0 Volt, πάω στο max και μετά ξανακατεβαίνω σταδιακά στα 0V.

Στο πρώτο φαίνεται τα σήματα PWM όπως βγαίνουν από το arduino (κίτρινο) και από το transistor (μπλε), έχοντας συνδέσει τον παλμογράφο στα σημεία Α & Β αντίστοιχα:
Το ύψος του παλμού είναι ανάλογο της τάσης του και βλέπουμε το σήμα στο Α (κίτρινο) να έχει πλάτος 5V ενώ το σήμα στο Β έχει 11.2V.

Στο δεύτερο βίντεο φαίνεται η μεταβολή της τάσης (Μπλε, σημείο C) καθώς αλλάζει το duty cycle του σήματος PWM στο σημείο B (κίτρινο).

Η μέγιστη τάση που βγάζει το σύστημα είναι η τάση τροφοδοσίας μειωμένη κατά ~ 0.7V.
Αν η μέγιστη τάση που χρειαζόμαστε είναι μικρότερη, τότε μπορούμε να μην φτάσουμε στο 100% duty cycle αλλά να σταματήσουμε πχ στο 90%.
με αυτό τον τρόπο ακόμα και αν το σύστημα μπορεί να βγάλει 11.2V, για εμάς η μέγιστη έξοδος θα είναι 10V.

O κώδικας που χρησιμοποιήθηκε είναι αυτός:

char  fromConsole;
int   i,j;
void setup() {
  // put your setup code here, to run once:
  analogWrite(10, 255);
  Serial.begin(9600);
  delay(1000);
  sunRize();
  sunSet();
}
void sunRize(){
      for (i=16;i>=0;i--){
      j=i*16; if (j<0) j=0; if (j>255) j=255;
      analogWrite(10, j);
      Serial.println(j);
      delay (500);
    }
}
void sunSet(){
    for (i=0;i<=16;i++){
      j=i*16; if (j<0) j=0; if (j>255) j=255;
      analogWrite(10, j);
      Serial.println(j);
      delay (500);
    }
}
void loop() {
  //check if a character on serial
  if (Serial.available()) {
    fromConsole=Serial.read();
    switch (fromConsole) {
      case '0':
        Serial.println("I received 0, setting PWD to 255, ie output 1:");
        analogWrite(10, 255);
        break;
      case '1':
        Serial.println("I received 1, setting PWD to 192, ie output to 1/4:");
        analogWrite(10, 192);
        break;
      case '2':
        Serial.println("I received 2, setting PWD to 128, ie output to 2/4:");
        analogWrite(10, 128);
        break;
      case '3':
        Serial.println("I received 3, setting PWD to 64, ie output to 3/4:");
        analogWrite(10, 64);
        break;
      case '4':
        Serial.println("I received 4, setting PWD to 0, ie output to 4/4:");
        analogWrite(10, 0);
        break;
      case '5':
        Serial.println("I received 5, doing a sunrize sequence");
        sunRize();
        break;
      case '6':
        Serial.println("I received 6, doing a sunset sequence");
        sunSet();
        break;
      default:
        Serial.println("Undefined button pressed");
        break;
      }
    }
  else {
    delay (500);
  }

}
Αυτά...
  








No comments:

Post a Comment