5.4 SENSORIK

 

 

EINFÜHRUNG

 

Ein Sensor ist ein Messgerät für eine physikalische Grösse, beispielsweise der Temperatur, der Lichtintensität, des Drucks, des Abstands, usw. Meist kann der vom Sensor gelieferte Wert eine beliebige Zahl innerhalb des Messbereichs sein. Es gibt allerdings auch Sensoren, die nur zwei Zustände ähnlich einem Schalter kennen, beispielsweise Füllstanddetektoren, Berührungssensoren, usw.

Die physikalische Grösse wird im Sensor üblicherweise in eine elektrische Spannung umgewandelt und von einer Auswertelektronik weiter verarbeitet [mehr... Für die Verarbeitung mit einem Computer muss die Spannung mit einem
Analog-Digital-Konverter (ADC) in eine Zahl konvertiert werden
]. Sensoren können intern kompliziert aufgebaut sein, wie beispielsweise Ultraschallsensoren, Gyrosensoren oder Laserdistanzmesser. Unter der Sensorkennlinie versteht man den Zusammenhang zwischen dem physikalischen Messwert und dem vom Sensor abgegebenen Wert. Für viele Sensoren verläuft die Kennlinie einigermassen linear, es muss aber der Umrechnungsfaktor und die Nullpunktverschiebung bestimmt werden. Dazu wird der Sensor in einer Messserie mit bekannten Grössen geeicht.

Der Ultraschallsensor bestimmt die Distanz zu einem Objekt durch die Laufzeit, die ein kurzer Ultraschall-Puls benötigt, um vom Sensor zum Objekt und wieder zurück zu laufen. Der Sensor liefert für Distanzen im Bereich von ca. 30 cm und 2 m Werte zwischen 0 und 255, wobei 255 (in der Simulation -1) dann abgegeben wird, wenn kein Objekt im Messbereich ist.

Ultraschallsensor
Kennlinie

In den meisten Anwendungen wird ein Sensor so in das Programm eingebunden, dass dieses periodisch den Sensorwert abfragt. Man nennt dies Pollen des Sensors. Der in einer Wiederholschleife abgefragte Werte dann im Programm weiter verarbeitet. Die Anzahl Messwerte pro Sekunde (zeitliche Auflösung) hängt vom Sensortyp, der Geschwindigkeit des Rechners und der Datenübertragung vom Brick zum Programm ab. Der Ultraschallsensor liefert nur ungefähr 2 Messwerte pro Sekunde.

Der Zustand von Sensoren, die nur zwei Zustände haben, kann ebenfalls durch Pollen abgefragt werden. Oft ist es aber einfacher, den Wechsel des Zustands, also die Zustandsänderung, als einen Event aufzufassen und ihn programmtechnisch mit einem Callback zu verarbeiten.

PROGRAMMIERKONZEPTE: Sensor, Sensorkennlinie, Sensoreichung, Pollen&Event, Triggerpegel

 

 

POLLEN ODER EVENTS VERWENDEN?

 

In vielen Fällen kannst du dich entscheiden, ob du einen Sensor lieber mit Pollen oder mit Events einbinden willst. Dies hängt etwas von der Anwendung ab. Du kannst die beiden Verfahren miteinander vergleichen, wenn du an einem Brick einen Motor und einen Berührungssensor anschliesst. Beim Klicken des Berührungssensors soll der Motor eingeschaltet, beim nächsten Klicken ausgeschaltet werden.

Für diese Anwendung sind Events viel eleganter, da sie dir das Drücken des Berührungssensors als Aufruf einer Funktion mitteilen. Du musst beim Erzeugen des TouchSensors diese Funktion als benannten Parameter angeben. Beim Pollen ist es nötig, mit einem Flag dafür sorgen, dass du nur den Übergang vom nicht gedrückten zum gedrückten Zustand verarbeitest.

Mit Pollen:

from nxtrobot import *
#from ev3robot import *

def switchMotorState():
    if motor.isMoving():
        motor.stop()
    else:
        motor.forward()

robot = LegoRobot()
motor = Motor(MotorPort.A)
robot.addPart(motor)
ts = TouchSensor(SensorPort.S3)
robot.addPart(ts)

isOff = True
while not robot.isEscapeHit():
    if ts.isPressed() and isOff:
        isOff = False
        switchMotorState()
    if not ts.isPressed() and not isOff:
        isOff = True
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

Mit Events:

#from nxtrobot import *
from ev3robot import *

def onPressed(port):
    if motor.isMoving():
        motor.stop()
    else:
        motor.forward()
        
robot = LegoRobot()

motor = Motor(MotorPort.A)
robot.addPart(motor)
ts = TouchSensor(SensorPort.S1, 
         pressed = onPressed)
robot.addPart(ts)
while not robot.isEscapeHit():
    pass
robot.exit()
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Sensoren können mit Pollen oder Events programmiert werden. Du musst beide Verfahren kennen und in der Lage sein zu entscheiden, welches in einer bestimmten Situation das zweckmässigere ist.

Im Eventmodell definierst du Funktionen, deren Name üblicherweise mit "on" beginnt. Diese werden Callbacks genannt, weil sie vom System beim Auftreten des Events automatisch aufgerufen ("zurückgerufen") werden. Du musst Callbacks bei der Erzeugung des Sensorobjekts mit benannten Parametern registrieren.

 

 

POLLEN EINES ULTRASCHALL-SENSORS

 

Vorbemerkung: Falls du in deinem EV3-Bausatz keinen Ultraschall-Sensor hast, so kannst du auch den EV3 Infrarotsensor einsetzen.

Einen Sensor musst du immer dann pollen, wenn du laufend seine Messwerte benötigst. Du stellst dir die Aufgabe, dass der Roboter, den du irgendwo auf den Boden stellst, einen Gegenstand (ein Ziel, Target) finden soll und zu diesem hinfährt.

Zum Erfassen eines Targets verwendest du einen Ultraschall-Sensor, der ähnlich wie ein Radar-Zielerfassungssystem eingesetzt wird. Um die Eigenschaften eines Sensors kennen zu lernen und ihn auszuprobieren, solltest du den Aufwand nicht scheuen, ein kurzes Testprogramm zu schreiben, das du später nicht mehr brauchen wirst. Dabei ist es zweckmässig, die Sensorwerte  auszuschreiben und eventuell auch hörbar zu machen, da du dann Hände und Augen frei hast, um den Roboter und den Sensor zu bewegen. In einer Schleife fragst du die Sensorwerte ab, wobei die Schleifenperiode angepasst wird, je nachdem ob du im autonomen oder fremdgesteuerten Modus bist.

# from nxtrobot import *
from ev3robot import *

robot = LegoRobot()
us = UltrasonicSensor(SensorPort.S1)
robot.addPart(us)
isAutonomous = robot.isAutonomous()
while not robot.isEscapeHit():  
    dist = us.getDistance()
    print("d = ", dist)
    robot.drawString("d=" + str(dist), 0, 3)
    robot.playTone(10 * dist + 100, 50)
    if dist == 255:
        robot.playTone(10 * dist + 100, 50)
    if isAutonomous:
       Tools.delay(1000)
    else:
       Tools.delay(200)
robot.exit()
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

Um ein Target zu finden und zu ihm hinzufahren rotierst du den Roboter wie eine Radarantenne und suchst dabei ständig mit dem Ultraschallsensor nach dem Target. Hast du das Ziel erfasst, so merkst du dir die Richtung und drehst noch weiter, bis kein Echo mehr auftritt. Damit kannst du die scheinbare Grösse des Targets (den Winkelbereich, in dem das Target "sichtbar" ist) bestimmen. Du fährst dann mit dem Roboter in der Mitte des Winkelbereichs und hältst in einer bestimmten Distanz an.

In der Simulation kannst du mit setBeamAreaColor() und setProximityCircleColor() die Distanzmessung anschaulich machen. Das angezeigte Target entspricht der Bilddatei, welche in RobotContext.useTarget() angegeben wird.


Für die Erfassung des Targets durch den simulierten Sensor wird aber nicht dieses Bild, sondern ein Netz aus Dreiecksmaschen (mesh) verwendet. Diese bestehen aus dem gemeinsamen Zentralpunkt und zwei Eckpunkten. Das angezeigte Target besitzt die Maschen
PP0P1, PP1P2, PP2P3, PP3P4, PP4P0.

Im Programm gibt du die die Eckpunkte der Maschen als Parameter der Methode useTarget() an. Die Koordinaten beziehen sich auf ein Pixelkoordinatensystem mit Nullpunkt im Zentrum und positiver x- Achse nach rechts und positiver y-Achse nach unten.

Für ein Sechseck mit dem Durchmesser 100 lauten die Maschenkoordinaten  

[50, 0] , [25, 43], [-25, 43], [-50, 0], [-25, -43], [25, -43].

 

 

from simrobot import *
#from nxtrobot import *
#from ev3robot import *

mesh = [[50, 0], [25, 43], [-25, 43], [-50, 0], 
          [-25, -43], [25, -43]] 
RobotContext.useTarget("sprites/redtarget.gif", mesh, 400, 400)

def searchTarget():
    global left, right
    found = False
    step = 0
    while not robot.isEscapeHit():  
        gear.right(50)
        step = step + 1
        dist = us.getDistance()
        print("d = ", dist)
        if dist != -1:  # simulation
        #if dist < 80:   # real
            if not found:
                found = True
                left = step
                print("Left at", left)
                robot.playTone(880, 500)
        else:
            if found:    
                right = step
                print("Right at ", right)
                robot.playTone(440, 5000)
                break

left = 0
right = 0
robot = LegoRobot()
gear = Gear()
robot.addPart(gear)
us = UltrasonicSensor(SensorPort.S1)
robot.addPart(us)
us.setBeamAreaColor(makeColor("green"))  
us.setProximityCircleColor(makeColor("lightgray"))
gear.setSpeed(5)
print("Searching...")
searchTarget()
gear.left((right - left) * 25)   # simulation
#gear.left((right - left) * 100)  # real
print("Moving forward...")
gear.forward()
while not robot.isEscapeHit() and gear.isMoving(): 
    dist = us.getDistance()
    print("d =", dist)
    robot.playTone(10 * dist + 100, 100)
    if dist < 40:
        gear.stop()
print("All done")        
robot.exit()
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Meist bestimmt man die Sensorwert durch wiederholtes Abfragen (Pollen) mit einer getter-Methode (getValue(), getDistance(), usw.)

Du musst gewisse Werte beim Wechsel zwischen Simulationsmodus und Realmodus anpassen, insbesondere Zeitintervalle. Zudem musst du berücksichtigen, dass der Sensor im Simulationsmodus -1 und im Realmodus 255 zurückgibt, wenn er kein Target findet.

In der Simulation bestimmt der verwendete Sensorport die Blickrichtung des Ultraschallsensors:

Sensorport Detektionsrichtung
S1 Vorwärts
S2 Links
S3 Rückwärts

 

 

EVENTS MIT EINEM TRIGGERPEGEL

 
Auch Sensoren, die kontinuierliche Werte liefern, können mit dem Eventmodell programmiert werden. Dabei definiert man einen bestimmten Messwert als Schwellenwert, meist Triggerpegel genannt. Ein Event wird dann ausgelöst, wenn dieser Pegel überquert wird, entweder von kleineren zu grösseren Werten oder umgekehrt.
 

Die Sensoren besitzen einen Standardwert für den Triggerpegel. Diesen kannst du aber mit setTriggerLevel() verändern.

Dein Programm wacht darüber, dass der fahrende Roboter innerhalb eines kreisförmigen Gebiets bleibt (und damit beispielsweise nicht von einem Tisch fällt). Dabei verwendest du den Lichtsensor, der hier nur auf hell und dunkel reagieren muss. Ist die Unterlage dunkel, so wird der Callback onDark ausgelöst.

Für den Realmodus mit dem NXT ist es wichtig, dass du mit activate(True) die LED-Beleuchtung des Sensors einschaltest.


 
from simrobot import *
#from nxtrobot import *
#from ev3robot import *

RobotContext.setStartPosition(250, 200)
RobotContext.setStartDirection(-90)
RobotContext.useBackground("sprites/circle.gif")
  
def onDark(port, level):
    gear.backward(1500)
    gear.left(545)
    gear.forward()

robot = LegoRobot()
gear = Gear()
robot.addPart(gear)
ls = LightSensor(SensorPort.S3, 
      dark = onDark)
robot.addPart(ls)
ls.setTriggerLevel(100)  # adapt value
gear.forward()
while not robot.isEscapeHit():
    pass
robot.exit()
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Des Überqueren eines bestimmten Messwerts kann als ein Event aufgefasst werden. Man spricht dabei von Triggerung.

Standardwerte des Triggerpegels:

Sensor

Triggerpegel (Standard)

Soundsensor 50
Lichtsensor 500
Ultraschallsensor 10

Die Vor- und Nachteile des Event-Modells gegenüber Pollens lassen sich wie folgt zusammenfassen:

Vorteile des Event-Modells Nachteile des Event-Modells
Vereinfachte übersichtlichere Programmierung, da der Code im Callback vom übrigen Programm losgelöst ist
Das laufende Programm wird zu unvorhersehbarer Zeit (asynchron) unterbrochen. Dies kann den übrigen Programmablauf stören
Der Event wird immer erfasst, auch wenn der PC langsam ist

Callbacks können unerwünschte Nebenwirkungen haben, z.B. wenn sie globale Variablen oder den Zustand des Roboters verändern

Das Hauptprogramm kann normal weiterlaufen und braucht sich nicht um den Sensor zu "kümmern"

Callbacks laufen in einem eigenen Prozess, daher kann es zu Konflikten zwischen Prozessen (Threads) kommen

Triggerung ist ein zentraler Begriff der Messtechnik
Es kann nur ein bestimmter Wert (Triggerpegel) erfasst werden
Das Event-Modell ist dem Denken in Zuständen angepasst (der Event setzt das System in einen neuen Zustand)
Callbacks sollten in der Regel nur kurz dauernden Code enthalten, da sonst weitere Events verloren gehen können

 

 

AUFGABEN

 

1.


Der Roboter soll sich beim ersten Klatschen in Bewegung setzen und bei weiterem Klatschen seine Richtung ändern. Löse das Problem im Realmodus und im Simulationsmodus. Im Realmodus verwendest du den Soundsensor. Im Simulationsmodus brauchst du an deinem PC ein Mikrofon und du musst in der Systemsteuerung den Mikrofonpegel richtig einstellen.

2.

Schliesse am Brick einen Motor und einen Berührungssensor an und schreibe ein Programm so, dass beim Drücken des Sensorknopfs der Motor eingeschaltet und beim Loslassen der Motor wieder ausgeschaltet wird.

3.

Ein Roboter mit einem Ultraschallsensor und einem Berührungssensor soll 3 höhere Gegenstände (Kerzen, Büchsen...) finden, auf sie losfahren und sie umstossen.

Im Simulationsmodus kannst du das Umstossen als Touchevent auffassen und squaretarget.gif verwenden, um die Gegenstände darzustellen. Das Bild ist 60x60 Pixel gross. Für den RobotContext kannst du folgende Vorlage verwenden. Versuche die Angaben unter mesh zu verstehen.

 

mesh = [[-30, -30], [-30, 30], [30, -30], [30, 30]]
RobotContext.useTarget("sprites/squaretarget.gif", mesh, 350, 250)
RobotContext.useObstacle("sprites/squaretarget.gif", 350, 250) 
RobotContext.useTarget("sprites/squaretarget.gif", mesh, 100, 150)
RobotContext.useObstacle("sprites/squaretarget.gif", 100, 150) 
RobotContext.useTarget("sprites/squaretarget.gif" ,mesh, 200, 450)
RobotContext.useObstacle("sprites/squaretarget.gif", 200, 450) 
RobotContext.setStartPosition(40, 450)  


4*.

Ein Roboter mit einem Ultraschallsensor wird zu Beginn an eine zufällige Position in ein rechteckiges Feld gesetzt. Seine Aufgabe ist es, möglichst rasch und genau die Mitte des Feldes zu finden. Die Aufgabe kann im Simulations- und/oder Realmodus gelöst werden.

Als Target können die Bilddatei bar0.gif und bar1.gif verwendet werden.

 

 

   

ZUSATZSTOFF


 

ARDUINO-SENSOREN VERWENDEN

 
Im Unterschied zum EV3 besitzt das  bekannte Arduino-Microcontroller-System ein klassisches I/O-System mit digitalen Ein- und Ausgngsports und analogen Eingängen (Analog-Digital-Wandler). Damit lässt sich eine Vielzahl von billigen Sensoren und Aktoren betreiben und es ist leicht, selbstgebaute elektronische Schaltungen anzuschliessen.  Diese Vorteile gegenüber dem EV3 kann man so nutzen, dass man beide Geräte zusammenschliesst und die Sensoren und Aktoren des Arduino mit dem EV3 anspricht.  

Die Verbindung der beiden Geräte erfolgt am einfachsten über einen I2C-Link, da beide Geräte über eine I2C-Schnittstelle verfügen. Dabei ist der EV3 ein I2C-Masters und der Arduino ein I2C-Slave. Die zusätzlich benötigten Python-Module sind bereits in der Distribution von TigerJython enthalten. Der EV3 kann dabei  im autonomen oder direkten Modus betrieben werden. Du kannst dich hier genauer informieren.