Mit dem Roboter einen Strichcode (Barcode) dekodieren
Barcodes
Ein Strichcode ist eine Anordnung von verschieden breiten parallelen Strichen und Lücken, in welchen eine Dualzahl codiert ist. Barcodes, wie Strichcodes auch genannt werden, lassen sich nicht nur im Supermarkt auf Etiketten finden, sondern überall dort, wo eine einfach anzubringende und von Maschinen lesbare Schrift benötigt wird (z.B. ISBN-Nummer auf Büchern, Adressen auf Postsendungen, Identifikation von Gepäckstücken, Pharmazeutika, etc.).
Auch die inzwischen weitverbreiteten QR-Codes codieren Informationen mit schwarzen und weißen Feldern. Im Unterschied zum einfachen Strichcode werden hier aber die Informationen zweidimensional mit schwarzen und weißen Quadraten gespeichert. Dadurch erreichen QR-Codes eine deutlichen höheren Informationsgehalt von bis zu 3 kByte (2.956 Byte).
Dualzahlen
In einem Strichcode werden Daten oft in Dualzahlen gespeichert, da sie nur aus den Ziffern 0 und 1 bestehen. Ähnlich wie bei den Dezimalzahlen hat jede Stelle bei einer Dualzahl eine andere Wertigkeit. Während die Wertigkeit der Stellen bei Dezimalzahlen auf der Basis 10 beruht (Einer-, Zehner-, Hunderter-, Tausender-Stelle) beruht die Wertigkeit der Dualstellen auf der Basis 2. Für die kleinste Stelle folgt somit eine Wertigkeit von . Die nächst höhere Stelle weist eine Wertigkeit von auf. Dann folgen die Wertigkeiten , , usw.
Soll eine Dualzahl in eine Dezimalzahl umgewandelt werden, muss jede Stelle der Dualzahl mit ihrer entsprechenden Wertigkeit multipliziert und anschließend alle Werte aufaddiert werden. In obiger Abbildung ist beispielhaft die Umwandlung der Dualzahl 1101
in die Dezimalzahl 13 dargestellt.
Ein Strichcode für den Roboter
In einem einfachen Strichcode steht eine weiße Linie für eine 0
und eine schwarze Linie steht für eine 1
. In dem unten aufgeführten Beispiel bilden 5 Striche eine Dualzahl aus 5 Stellen (man sagt auch 5 Bit Dualzahl). Aus der Strichfolge ergibt sich dabei die Zahl 10011
.
Im Prinzip ist jede beliebige Kombination von weißen und schwarzen Strichen möglich, wobei jedoch der erste Strich immer schwarz sein muss. Dieser erste schwarze Strich ist das Synchronisierungsbit (Sync-Bit), welches notwendig ist, damit der Roboter erkennen kann, wann der Strichcode beginnt. Dementsprechend speichert ein 5 Bit langer Strichcode nur 4 Bit an Informationen, womit sich Dezimalzahlen von 0 bis 15 darstellen lassen.
Bei der Umwandlung der sich aus dem Strichcode ergebenden Dualzahl in eine Dezimalzahl ist die Reihenfolge der Bits wichtig. Für den hier verwenden Barcode wird festgelegt, dass das erste Bit nach dem Sync-Bit die höchste Wertigkeit hat. Das Sync-Bit selbst wird bei der Umwandlung in eine Dezimalzahl nicht berücksichtigt.
Für das obige Beispiel ergibt sich ohne Sync-Bit die Bitfolge 0011
, was der Dezimalzahl 3
entspricht.
Fehlererkennung
Wird ein Strichcode ausgewertet, so kann es passieren, dass nicht alle Bits korrekt gelesen wurden (ohne Sync-Bit):
Strichcode: 1110 (dezimal 14)
falsch gelesener Code: 0110 (dezimal 6)
Im obigen Beispiel wurde die Dezimalzahl 14 binär codiert. Aufgrund eines Bitfehlers wurde allerdings die Dezimalzahl 6 ausgelesen.
Eine einfache Möglichkeit Fehler in der Datenübertragung zu erkennen ist das hinzufügen des Paritätsbits. Das Paritätsbit wird an die zu übermittelnden Daten als Ergänzungsbit angehängt und ergänzt die 1
Bits auf eine gerade Anzahl. In diesem Fall wird auch oft von gerader Parität gesprochen. Im Folgenden sind ein paar Beispiele für ein gerades Paritätsbit angegeben.
| Daten | Paritätsbit (gerade)
| 0 0 0 0 | 0
| 0 0 0 1 | 1
| 0 0 1 0 | 1
| 0 1 0 0 | 1
| 0 1 0 1 | 0
| 0 1 1 0 | 0
| 0 1 1 1 | 1
Bei dem Beispiel mit dem fehlerhaft übertragenen Strichcode würde sich nun folgende Bitfolge ergeben:
Strichcode: 1110 1 (Parität mit gerade Anzahl `1`)
falsch gelesener Code: 0110 1 (FEHLER, ungerade Anzahl `1`)
Durch das Analysieren der gelesenen Bitfolge auf dessen Parität, können nun einzelne Bitfehler festgestellt werden. Jedes fehlerfrei gelesen Datenwort sollte nun eine gerade Anzahl von 1
Bits aufweisen. Da das gelesen Datenwort 01101
jedoch eine ungerade Anzahl von 1
Bits aufweist, muss in der Datenübertragung ein Fehler aufgetreten sein.
Auslesen mit den Bodensensoren
Das Auslesen des Barcodes geschieht durch den Bodensensor des Roboters. Der unten dargestellte Signalverlauf des Bodensensors ergibt sich, wenn der Zumo Roboter über über einen 5 Bit Strichcode mit dem Wert (Bits) 10011
fährt. Die auf der x- Achse angegebene zurückgelegte Strecke wird dabei mit dem Motorencoder bestimmt und ist später für die Dekodierung wichtig.
Im Diagramm ist neben dem Verlauf des Bodensensors auch die Erkennung des Schwellwertes abgebildet. Ist der ermittelte Sensorwert größer als der gegebene Schwellwert (einsLevel = 250
), so wird das in dem Verlauf der Schwellwerterkennung optisch dargestellt.
Ein paar Strichcodes (5 Bit) zum Ausprobieren können auch heruntergeladen werden.
Aufgaben
Lesen Sie mit dem Roboter einen Strichcode ein. Schreiben Sie hierzu ein Programm mit folgenden Eigenschaften:
- Der Roboter fährt zuerst nur geradeaus.
- Nachdem das Sync-Bit erkannt wurde, soll der Roboter mit dem Motorencoder bis zum Ende des Strichcodes fahren.
- Während er über den Strichcode fährt, gibt der Roboter über die serielle Schnittstelle die Werte des mittleren Bodensensors aus.
- Am Ende des Strichcodes bleibt der Roboter stehen.
Erweitern Sie das Programm aus der letzten Aufgabe um eine Schwellwerterkennung der logischen
1
bzw. der logischen0
.Wenn der Sensorwert den Schwellwert
einsLevel
überschreitet, soll eine VariablesensorZustand
den Wert1000
annehmen, ansonsten soll sie den Wert0
annehmen.Geben Sie auf dem seriellen Monitor durch ein Komma getrennt, sowohl den Sensorwert als auch die Variable
sensorZustand
aus. Mit dem seriellen Plotter kann dann der Verlauf von beiden Werte angezeigt werden.// Ausgabe der beiden Werte zur korrekten Darstellung mit dem seriellen Plotter
Serial.print(sensorWert); Serial.print(", "); Serial.println(schwellwertErkennung);
Dekodieren des Strichcodes
Soll der Barcode mit dem Bodensensor nicht nur ausgelesen, sondern auch in die entsprechenden Bits umgewandelt werden, muss der Strichcode mit dem Bodensensor an bestimmten Punkten abgetastet werden. Ist der Sensorwert an den Abtastpunkten größer als der Schwellwert, würde das einer 1
entsprechen. Die Abtastung findet sinnvollerweise genau einmal pro Strich jeweils in der Mitte statt.
Ein Programm, welches einen Barcode dekodiert, also die einzelnen Bits des Codes erkennt, muss im wesentlichen Folgendes leisten.
- Der Sensor zum Erkennen des Sync-Bits muss kontinuierlich ausgelesen werden.
- Wenn das Sync-Bit erkannt wurde (
sensorWert > einsLevel
), beginnt die Dekodierung.- Damit die einzelnen Striche möglichst in der Mitte abgetastet werden, fährt der Roboter noch die Strecke
offsetAbtastungInTicks
weiter, um dann das Sync-Bit abzutasten (1. Bit). Es wird überprüft, ob der Sensorwert größer als der Schwellwert ist. - Die nächsten Bits werden in einem, der Symbolbreite entsprechedem Intervall (
symbolBreiteInTicks
), abgetastet.
- Damit die einzelnen Striche möglichst in der Mitte abgetastet werden, fährt der Roboter noch die Strecke
- Die Dekodierung wird am Ende des Barcodes, zum Beispiel nach 5 Bits, beendet.
Die Abtastpunkte sind in der Abbildung noch einmal graphisch dargestellt.
Für eine bessere Übersichtlichkeit sollten am Anfang des Programms die wichtigsten Größen des Strichcodes angegeben werden:
#define MM_PRO_IMPULS 0.128 // Auflösung des Motorencoders Zumo 32u4 in Millimeter pro Impuls
const int barcodeSensor = 2; // Index des auszuwertenden Sensors
const int anzahlBits = 5; // Länge des Barcodes in Bits (inkl. Sync-Bit)
const int einsLevel = /* ??? */; // Schwelle zur Erkennung des Sync-Bits
const int symbolBreite = /* ??? */; // Strichbreite in mm
const int offsetAbtastungInTicks = /* ??? */; // Verschiebung des Abtastpunkt in Encoder-Impulsen
const int symbolBreiteInTicks = symbolBreite / mmPerTick; // Strichbreite in Encoder-Impulsen
const int barcodeLaengeInTicks = anzahlBits * symbolBreiteInTicks; // Länge des Barcodes in Motorimpulsen
bool datenBarcode[5] = {0,0,0,0,0}; // Array für dekodierte Barcode Bits
Im Hauptprogramm fährt der Roboter solange geradeaus, bis das Sync-Bit erkannt wurde, als der Bodensensor ein Wert ermittelt, der größer als ein bestimmter Schwellwert ist (z.B. einsLevl
). Anschließend wird mit der Funktion abtastenBarcode()
der Strichcode an den richtigen Stellen abgetastet, bzw. ausgelesen.
Die abtastenBarcode()
Funktion übernimmt den wichtigsten Teil der Arbeit. Möglichst in der Mitte der Barcode-Striche werden diese abgetastet (ausgelesen) und das Ergebnis des Abtastens (0
oder 1
) in dem globalen Array datenBarcode[]
geschrieben.
// Abtasten des Barcodes. Erkennung des Sync-Bits wird separat durchgeführt
void abtastenBarcode(){
int bitIndex = 0; // Index des aktuellen Bits des Barcodes
ledRed(1), ledGreen(0);
int encoderTicks = 0;
encoders.getCountsAndResetLeft(); // Motorencoder zurücksetzen
encoders.getCountsAndResetRight(); // Motorencoder zurücksetzen
// Über die Länge des Barcodes die einzelnen Bits abtasten.
while (encoderTicks < barcodeLaengeInTicks)
{
/************************************
* Fehlenden Code ergänzen:
* - Impulse der Motor-Encoder bestimmen (Mittelwert der beiden Motoren)
* - Werte des Liniensensors aktualisieren
* - Jedes Bit des Barcodes genau einmal in der Mitte abtasten
* - Boolschen Wert (0/1) des abgetasteten Strichs im Array `datenBarcode`
* speichern
************************************/
}
ledRed(0), ledGreen(1);
}
Barcode in Dezimalzahl umwandeln
Aufgaben
Schreiben Sie ein Funktion int binToDez()
, welche eine vom Roboter ausgelesene Bitfolgen in Dezimalzahlen umwandelt. Das erste Bit, also das Bit mit dem Index [0]
ist das Sync-Bit. Die Funktion soll zuerst überprüfen, ob das Sync-Bit 1
ist und dann die andern Bits in eine Dezimalzahl umwandeln und als Ergebnis zurückgeben. Ist das Sync-Bit nicht 1
, wird -1
zurückgegeben.
Testen Sie Ihre Funktion mit den angegeben Beispielen.
bool code1[5] = {1,0,0,0,1}; // dezimal 1
bool code2[5] = {1,1,0,0,0}; // dezimal 8
bool code3[4] = {1,1,0,0}; // dezimal 4
bool code4[4] = {1,1,1,0}; // dezimal 6
bool code5[4] = {0,1,1,0}; // dezimal 6, aber kein Sync-Bit
int codeToDec(bool pCode[], int pAnzahlBits){
/*
* Code für die Umwandlung
*/
}
Nicht-blockierender Dekoder (Mulitasking)
Damit der Roboter während des Dekodierens eines Barcodes auch andere Aufgaben ausführen kann (zum Beispiel einer Linie zu folgen), muss die Dekodierfunktion als nicht blockierender Prozess realisiert werden. Dies lässt sich u.a. mit einem endlichen Automaten realisieren, der ständig aus der loop()
Funktion heraus aufgerufen wird.
// Zustände für den Barcode-Dekoder
enum ZustaendeDekoder {
WARTEN, START, DEKODIEREN, ENDE };
// Endlicher Automat zum nicht-blockierenden dekodieren eines Barcodes
bool dekodierenAutomat() {
switch (zustandDekoder) {
case WARTEN: // Sucht das Sync-Bit des nächsten Barcodes
if ( (sensorWerte[barcodeSensor] > einsLevel)
&& (sensorWerte[nebenBarcodeSensor] < 700)) {
zustandDekoder = START;
}
return true;
break;
case START:
ledRed(1);
ledGreen(0);
resetDekoder();
zustandDekoder = DEKODIEREN;
return true;
break;
case DEKODIEREN:
if (!abtasten())
zustandDekoder = ENDE;
return true;
break;
case ENDE:
ledRed(0);
ledGreen(1);
zustandDekoder = WARTEN;
return false;
break;
}
}