Servosturing

Een servo wordt aangestuurd met een PWM (pulse width modulation = pulsbreedtemodulatie) signaal. De vereisten voor dit signaal zijn: frequentie tussen 20 en 50Hz, en pulsbreedte van 1ms tot 2ms.

Dit betekent dat een servo constant voorzien moet worden van deze signalen om te blijven werken. Wanneer deze pulsen niet meer gestuurd worden zal men de as met de hand kunnen verdraaien.

Een standaard servomotor zoals we deze gebruiken in het labo is een positiemotor. Afhankelijk van de pulsbreedte zal de as een positie innemen die kan lopen over een bereik van ongeveer 180°.

Om met de Arduino een servo aan te sturen maken we ook hier gebruik van een bibliotheek: Servo.

Deze bibliotheek kent volgende functies:

In een basisprogramma zoals het sweep-voorbeeld wordt de servo over zijn volledig bereik heen en weer gestuurd. We bekijken even de code:

#include <Servo.h>
Servo myservo;
int pos = 0;
void setup() {
  myservo.attach(9);
}

void loop() {
  for (pos = 0; pos <= 180; pos += 1) {
    myservo.write(pos);
    delay(15);
  }
  for (pos = 180; pos >= 0; pos -= 1) {
    myservo.write(pos);
    delay(15);
  }
}
De code begint met het toevoegen van de bibliotheek. Daarna wordt een Servo object geïnstantieerd met als naam myservo.
In de setup wordt dit object gekoppeld aan pin 9. We veronderstellen dus dat de datalijn van de servo met pin 9 wordt verbonden.
Vervolgens bestaat het hoofdprogramma uit twee lussen. In een eerste lus gaat een variabele pos van 0 naar 180, en wordt via myservo.write(pos) deze waarde als een PWM-signaal op pin 9 aangeboden.
De delay van 15 milliseconden is nodig om er voor te zorgen dat de pulsen niet te snel na elkaar gestuurd worden. Een volledige sweep van 0° naar 180° zal dus ongeveer 180*15ms = 2,7s duren.
Daarna gebeurt hetzelfde, maar in omgekeerde richting in de tweede lus, waardoor de as terugkeert van de 180° positie naar de 0° positie.
Merk op dat men ook kan gebruik maken van de writeMicroseconds() functie. Bij deze functie geeft men de breedte van de gewenste puls aan in microseconden. Zo zal de waarde 1000 overeenkomen met de 0° positie en 2000 met de 180° positie.
 

Theorie

LCD basischakeling 4-bit mode

 

In bovenstaande afbeelding zie je de basisschakeling om een LCD aan te sluiten op een Arduino. Bekijken we de aansluitpinnen van het LCD van links naar rechts, dan zien we bij de eerste 3 pinnen de massa, Vcc en contrast. Deze derde pin is de contrastregeling voor het scherm en wordt aangestuurd m.b.v. een potentiometer die als spanningsdeler geschakeld is tussen de voedingslijnen.

Vervolgens hebben we de RS, R/W en E lijnen. Dit zijn de stuursignalen. Het LCD bevat namelijk zelf een microcontroller die de individuele pixels van het display aanstuurt. Eigenlijk communiceert de Arduino controller met de LCD controller. Enerzijds moet men de data die op het display moeten verschijnen kunnen doorgeven, maar anderzijds moeten ook instelcommando's kunnen doorgegeven worden.

Dit onderscheid wordt gemaakt door middel van de RS-pin (RS staat voor register-select). Een waarde 0 geeft aan dat de gegevens displaydata zijn, terwijl een 1 aangeeft dat een instelcommando bedoeld wordt.

De R/W pin geeft de richting van de communicatie aan: Read (lezen) of Write (schrijven).

De E-pin staat voor enable. Een flank op deze pin geeft aan dat de niveau's op D0..D7 mogen overgenomen worden.

De volgende 8 pinnen zijn de datapinnen D0..D8. In de eenvoudigste versie kan het display in 4-bit mode gebruikt worden. In dat geval worden enkel D4..D8 gebruikt en wordt elke byte in 2 stappen doorgegeven. Er wordt dus gecommuniceerd op nibble-niveau.

Uiteraard kan er ook in 8-bit mode gewerkt worden. Dit neemt 4 extra pinnen in beslag, en aangezien de Leonardo al geen pinnen op overschot heeft wordt meestal de 4-bit versie gebruikt. Een andere mogelijke oplossing is het gebruik van een I²C interface, maar deze zit niet standaard op het display en moet dan hardwarematig aan het circuit worden toegevoegd.

Als laatste twee pinnen vinden we de (optionele) aansluiting voor backlighting. Dit is een led die achter het display gemonteerd is om ook in het donker het display te kunnen aflezen.

Ook bij een LCD bestaat een bibliotheek die het grootste werk uit handen neemt, zoals te zien in onderstaand "hello World" voorbeeld.

#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("hello, world!");
}

void loop() {
  // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  lcd.setCursor(0, 1);
  // print the number of seconds since reset:
  lcd.print(millis() / 1000);
}

Na het toevoegen van de bibliotheek wordt een LCD-object geïnstantieerd (=object aangemaakt volgens typebeschrijving). Hierbij worden de verbindingen meegegeven. Tijdens de setup wordt het LCD object geïnitialiseerd als een 16x2 LCD, dus twee regels van 16 karakters.

Daarna wordt een eerste bericht op het display geplaatst: "hello world". Dit komt op de bovenste regel terecht.

In de loop()-functie wordt de cursor op de onderste lijn en op de eerste kartakterplaats gezet. Daarna wordt op die positie het aantal seconden die verlopen zijn sinds de start van het programma weergegeven.

 

Oefening basis 1

Maak een schakeling waarbij 5 drukknoppen uitgelezen worden. Elke drukknop komt overeen met één positie van de servo. Geef op de LCD aan welke positie ingenomen wordt.


 

Oefening basis 2

Lees de waarde van de potentiometer uit, geef deze weer op het LCD en zorg dat de servo de positie van de potentiometer volgt.

Oefening basis 3

Lees de waarde van de potentiometer uit en stuur een L293 zodanig aan dat de DC motor naar links draait wanneer de analoge waarde<512 en naar rechts in het ander geval. Geef op het LCD “links” of “rechts” aan.

Oefening basis 4

Maak een spanningsdeler met een weerstand en een LDR en geef op de LCD weer of het "donker" of "licht" is. Bij donker moeten de rolluiken omlaag (=DC motor aansturen in wijzerzin totdat knop1 wordt ingedrukt). Bij licht moeten de rolluiken omhoog (DC motor in tegenwijzerzin totdat knop2 wordt ingedrukt). Voorzie ook een hysteresis zodat bij halve duisternis de rolluiken niet beginnen oscilleren. Denk ook na over beveiliging van de drukknoppen, zodat er geen gevaarlijke situatie ontstaat wanneer een knop kapot gaat, of een draad breekt.

Oefening gevorderd 1

Lees de stand van een potentiometer en stuur het vermogen naar de H-brug waarop een DC motor is aangesloten, zodat de snelheid van de DC-motor kan geregeld worden met de potentiometer. Zorg dat wanneer de potentiometer in het midden staat, de motor stil staat, en afhankelijk van naar links of naar rechts draaien van de potentiometer de motor ook naar links of naar rechts draait.

Oefening gevorderd 2

Stuur een LCD aan via I²C bus en een PCF8574. Merk op dat hierbij geen liquidCrystal library gebruikt kan worden. De LCD signalen moeten dus zelf gegenereerd worden.
Laat de tekst "Hello World" verschijnen.

 

Oplossing drukknoppen gevorderd op I2C

Bij dit programma sluiten we 4 drukknoppen aan op P4..P7 van de PCF8574, en 4 leds op de P0..P3 van de I/O expander.

In het programma zetten we initieel alle uitgangen van PCF8574 hoog. Daardoor gaan de 4 leds, die in common anode configuratie aangesloten zijn uit.

Daarna lezen we de 8 lijnen van de PCF8574. We schuiven de 8 bits van het resultaat 4 posities op naar rechts, en sturen dit resultaat terug naar de PCF8574.

Het programma ziet er als volgt uit:

#include <Wire.h>

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(115200);  // start serial for output
}

void loop() {
  Wire.beginTransmission(0x38);
  Wire.write(0xFF);
  Wire.endTransmission();
 
  Wire.requestFrom(0x38, 1);    // vraag 1 byte aan de PCF8574
  while (Wire.available()) { // wacht tot byte beschikbaar is
    byte c = Wire.read();    // lees de waarde
    Wire.beginTransmission(0x38);
    Wire.write(c>>4);
    Wire.endTransmission();
  }
  delay(50);
}

Merk op dat wanneer een knop ingedrukt is, de bijhorende led gaat branden zolang de knop ingedrukt blijft, maar dat bij elke doorgang van de loop()-functie gedurende een fractie van een milliseconde de lijnen terug op 1 zullen gezet worden en alle leds dus zullen doven. Dit gaat echter zodanig snel dat we dit niet merken, en het lijkt alsof de led continu brandt.