Inhaltsverzeichnis

Dokumentation µCompozzel

µCompozzel ist ein Kürzel für: Mikrocontrollergestütztes kompaktes Oszilloskop mit LCD-Anzeige. Für das Fach ESL6c an der TFH in Berlin möchten wir dieses Gerät implementieren und die Arbeitsergebnisse / Erkenntnisse hier dokumentieren.

Ziel des Projekts ist es, unter Verwendung eines vorher unbekannten µCs (µC = micro contoller) ein real einsetzbares Gerät zu bauen. Dessen Eigenschaften und Funktionsweise werden im Folgenden beschrieben.

Hardware

Für die Umsetzung haben wir uns für ein ATMega 128 Minimodul inklusive 1.5" TFT-Display entschieden, da hier auch schon das nötige Modul zur LCD Ansteuerung integriert ist. Das Board ist nur ein wenig größer als das Display selbst. Alle nicht direkt vom Board benutzten Ports und Schnittstellen sind per Pfostenleiste nach außen gelegt. Wir betreiben den µC über einen 16Mhz Quartz.

Das Board hat 6 Buttons, die alle auf Port D liegen. Im Auslieferungszustand sind davon nur 5 Buttons für benutzerdefinierte Funktionen nutzbar, denn der sechste Button ist als Reset-Schalter vorbelegt. Um alle Buttons frei nutzen zu können haben wir die Belegung hardwareseitig verändert. Das Display ist über Port B angeschlossen. Es benötigt eine Betriebsspannung von 3V um vollständig, d.h. mit Hintergrundbeleuchtung zu arbeiten. Erhält es weniger Spannung, schaltet es zunächst nur die Hintergrundbeleuchtung ab. Die Spannungsversorgung des Boards erfolgt über ein externes Netzteil, das etwa 7 Volt liefert. Das Board verfügt über eine PowerBoost Schaltung, die sich um eine saubere Spannungsversorgung der Komponenten kümmert. Positiv daran ist, dass man mit einer Versorgungsspannung von 5-20V an das Board gehen kann, ohne es zu beschädigen.

Im Folgenden eine kurze Zusammenfassung über die vorhandenen und geplanten Hardwarefeatures:

Nicht alle dieser Eigenschaften haben wir auch so umgesetzt, weil es uns wichtiger war, im gegebenen Zeitrahmen die wesentlichen Funktionen einzubauen. Hardwareseitig blieb das µCompozzel ein Prototyp.

Digitalisieren der Eingangswerte

Um das analoge Eingangssignal im µC verarbeiten und auf dem Display anzeigen zu können, muss es zunächst digitalisiert werden. Die Aufgabe wird vom ADC des µC übernommen. Der ADC hat eine maximal Auflösung von 10 Bit, also 1024 Stufen. Über das Beschreiben des Registers ADMUX kann man festlegen, wie man die acht Eingänge des ADC verwenden möchte. Dabei gibt es zwei grobe Unterscheidungen:

  1. Differentieller Betrieb: Einer der ADC - Eingänge dient als unteres differentielles Level und ein weiterer als oberes Level. Der Wert am ADC berechnet sich wie folgt: ADC = {(V_Pos - V_Neg) * GAIN * 512} / {V_Ref}
  2. Single Ended Input: Nur ADCx digitalisiert Werte. Der Wert am ADC berechnet sich wie folgt: ADC = { V_IN * 1024 } / { V_Ref }

Beim ausprobieren der Modi haben wir festgestellt, dass der differentielle Betrieb nicht für unseren Zweck geeignet ist, da er nur Spannungen zwischen -0,7 und +0,7 Volt wandelte. Darum haben wir uns für den Single Ended Input entschieden. Daraus ergab sich, dass wir darauf achten mussten, dem ADC keine negativen Spannungen zuzuführen, um ihn nicht zu zerstören. Trotzdem sollte es möglich sein, ein Wechselspannungssignal, das negative Halbwellen enthält, darzustellen.

Zur Lösung dieses Problems haben wir eine Vorschaltung entwickelt, die eine Spannungsbegrenzung und -anhebung auf den Bereich zwischen 0 und 5 Volt herbeiführt.

Mit dem freien Softwaretool SwitcherCad III haben wir folgendes Schematic erstellt und getestet:

vorbeschaltung.jpg

Die Spannungsquelle auf der linken Bildseite repräsentiert unser Eingangssignal. Die Spannungsquelle rechts oben ist die Versorgungsspannung des OPV aus dem µC und unser negativer Spannungspol für den Spannungsteiler. Die Konstruktion der Dioden begrenzt zusätzlich die Eingangsspannung des OPVs auf -5.5V bis +5.5V. Der OPV ist als Spannungsfolger geschaltet und begrenzt die Eingangsspannung für den µC auf 0-5V.

Die oben begründete Anhebung der Eingangsspannung wird durch den Spannungsteiler geleistet. Dieser tut eigentlich nichts anderes, als die an ihm anliegende Spannung zu halbieren. Da dadurch aber noch nicht das Problem der negativen Halbwellen gelöst ist, verwenden wir die zweite Spannungsquelle, um zusammen mit der Halbierung auch einen Gleichspannungsanteil hinzuzufügen. Diese Konstellation bewirkt, dass der OPV eine Eingangsspannung von V_OPV = { V_IN + 5V } / 2 erhält.

Tasterbelegung / Menü

Wir hatten ursprüngliche die Idee einer grafischen Menüsteuerung. Diese haben wir letztendlich nicht umgesetzt, da so mehr Platz auf dem Display für die wichtigen Anzeigen verwendet werden konnte und wir zudem die wichtigen Steuerfunktionen auf den sechs vorhandenen Tastern unterbringen konnten.

Taster Funktion
Links außen Clear Screen - falls das Display falsch beschrieben wurde
Rechts außen durchschalten der Y-Zoom Einstellungen
Links verändern der X-Zoom Einstellungen (gröber)
Rechts verändern der X-Zoom Einstellungen (feiner)
Hoch Triggerlevel nach oben verschieben
Runter Triggerlevel nach unten verschieben

Displayanzeige

Das Display hat zwar laut Datenblatt eine 132×132 Pixelanzeige, allerdings geht an allen Kanten je ein Pixel verloren. Den oberen Bereich des Displays haben wir mit 100 Pixeln vertikal für die Kurvenanzeige reserviert. Der Untere Bereich kann für Zusatzinformationen verwendet werden. Die verwendete Schriftart hat eine Größe von 6×8 Pixeln. Damit passen 3 Zeilen mit je 21 Zeichen in den unteren Bereich des Displays.

Die wichtigsten darzustellenden Informationen sind die erkannte Frequenz; die Auflösung in X- und Y-Achse; maximal und minimal erkannter Spannungswert.

Der Wertebereich sowohl im Zeit-, Frequenz- als auch Spannungsbereich kann je nach Eingangssignal um Größenordnungen variieren. Für die Anzeige geeigneter Einheitenpräfixe haben wir eine Funktion erstellt, die den Ausgabewert entsprechend formatiert. Inklusive Vorzeichen und Einheit nimmt ein Wert maximal 10 Zeichenplätze ein.

Codebeispiel: Berechnung der Wertausgabe

// *swert - Charakter Array
// in - Zahl - Eingabewert
// *val - Einheit
void multi(char *swert, long double in, char *val)
{
	//swert immer 10!
	char vz = ' ';
	char pz = ' ';
	if ( in<0 ) { vz='-'; in = -in; }
 
	if (in >= 1) {
		if (in >= 1000) {
			if ( in >= 1000000) {
				if (in >= 1000000000) {
					in /= 1000000000;
					pz = 'G';
				} else {
					in /= 1000000;
					pz = 'M';
				}
			} else {
				in /= 1000;
				pz = 'k';
			}
		}
	} else {
		if (in*1000000000 < 1000000000) {
			if ( in*1000000 < 1000000) {
				if (in*1000 < 1000) {
					in *= 1000;
					pz = 'm';
				} else {
					in *= 1000000;
					pz = 'u';
				}
			} else {
				in *= 1000000000;
				pz = 'n';
			}
		}
	}
	
	sprintf(swert, "%c%ld.%d%c%s", vz, (long int)in, (int)((in-(long int)in)*100), pz, val);
}

Display (nicht final)

screen_large.jpg

Display (final)

final.jpg

Software Features

Für die Funktionen, die unser Handheld-Oszilloskop aufweisen soll, haben wir uns an den uns zur Verfügung stehenden Laborgeräten orientiert.

Probleme

Im Folgenden sollen kurz ein paar aufgetretene Probleme während der Implementierung und ihre Lösungen aufgezeigt werden.

Ausgabe von Floats

Die oben stehende Implementierung löst ein wesentliches aufgetretenes Problem. Für die Ausgabe müssen auch gebrochen rationale Zahlen in Strings verpackt werden können. Das geht in ANSI C z.B. mit der sprintf Funktion und einer formatierten Ausgabe via %f (obwohl die Funktion für den Einsatz auf einem µC aufgrund von Performancemängeln nicht so geeignet ist). Auf dem Display erschien bei derart formatierten Werten immer nur ein Fragezeichen. Die Lösung bestand in einer eigenen Funktion (siehe oben), die die gebrochen rationale Zahl in ihre Bestandteile auflöst und als String zurückgibt.

Taktung des ADC

In der ersten Testimplementierung haben wir den ADC im Freerun Mode laufen lassen und Werte per ADC-Interrupt aufgezeichnet. Für unterschiedlich schnelle Signale (von wenigen Hz bis mehreren kHz) war diese Methode ungeeignet. Der nächste Schritt war eine ADC-Steuerung per Timer. Für langsamere Signale haben wir eine interne Subcounter-Variable bis zu einem bestimmten Wert hochzählen lassen, bevor wir die Wandlung angestoßen haben. Der Nachteil hierbei war, dass ständig Interrupts ausgelöst wurden, die nichts weiter taten als den Subcounter hochzuzählen und den restlichen Programmablauf zu unterbrechen und damit eigentlich zu stören. Schlechte Reaktionszeiten der Taster waren z.B. die Folge davon. Der dritte und letzte Schritt war, den Subcounter von der Compare Unit ersetzen zu lassen. Das ermöglicht uns per Prescaler und Compare Value ein relativ genaues ADC-Timing (siehe Frequenzanzeige).

Eingangswerte

Wenn man oszilloskopiert hat die aufgenommene Funktion meistens eine positive und negative Halbwelle (es sei denn, man hat einen Offset auf dem Signal). Der ADC des µC kann aber nur Spannungswerte zwischen 0 und 5 Volt in den Wertebereich 0-1024 wandeln. Um den ADC zu schützen benötigen wir eine Vorbeschaltung, die sicherstellt, dass diese Vorgabe eingehalten wird. Unsere Lösung für diese Problematik ist oben dokumentiert.

Entprellen der Taster

Wie oben erwähnt, hat unser µC bereits fest installierte Taster, die auf dem PORT D liegen. Eine Möglichkeit, Tastendruckereignisse zu erkennen, wäre, die entsprechenden Bits ständig zu pollen, d.h. in einer Endlosschleife. Das verbraucht aber alleine schon unnötig viel Rechenzeit und wird noch inperformanter durch das Prellen der Taster. Prellende Taster bedeuten, dass wenn man den Taster betätigt nicht sofort der Wert anliegt, sondern sich innerhalb kurzer Zeit einschwingt. Bei unintelligentem ständigen Pollen könnte man dann aber mehr als einen Tastendruck registrieren.

Die erste Lösung bestand darin, einen Zähler einzusetzen, um z.B. erst nach dem fünften Mal Registrieren die Funktion auszuführen. Da wir dieses Pollen aber in der Main-Methode laufen ließen, wurde sie ständig durch den Timer für den ADC unterbrochen und die Taster reagierten träge bis gar nicht.

Eine andere Löung hätten externe Interrupts sein können, die einige Ports als alternative Funktion anbieten. Allerdings würde dies nur für zwei der sechs Taster / Pins am Port D gehen.

Die dritte Alternative ist, ebenfalls einen Timer einzusetzen, der eine höhere Priorität beim Timerüberlauf hat. Bei jedem Timerüberlauf pollen wir Port D. In Kombination mit einem Trefferzähler konnten wir ein per Software gelöstes Entprellen realisieren.

Codebesipiel: Entprellen

 
// Taster Timer Init:
TCNT0 = 0x00; // Counter auf 0
OCR0 = 150;
TCCR0 = ((TCCR0 & 0b00000000) | 0b01001111); // Takt / 256
 
ISR(TIMER0_COMP_vect) // Timer 0 fuer Keyboard
{
	int newKey = keyhit();
	if ( newKey != actualKey && newKey != 0 ) {
		actualKey = newKey;
		keyHitTimes = 1;
	} else
		keyHitTimes ++;
		
	if ( keyHitTimes >= 5 ) {
		int sreg = SREG;  // Sichern der aktuellen  Interrupteinstellungen
		cli(); // Sicherheitsabschaltung der Interrupts
 
		// Auswahl des gedrueckten Buttons + Aktion
		switch(actualKey) {
 
			case KEY_ESC:	keyESCAction();
					break;
			case KEY_CR:	keyCRAction();
					break;
			case KEY_UP:	keyUpAction();
					break;
			case KEY_LEFT:	keyLeftAction();
					break;
			case KEY_RIGHT:	keyRightAction();
					break;
			case KEY_DOWN:	keyDownAction();
					break;
		}
		
		// Zuruecksetzen aller Entprellwerte
		actualKey = -1;
		keyHitTimes = -1;
		
		SREG = sreg; // Reaktivieren der Interrupts
	}
}

Fazit

vollansicht_oszi_v.jpg

Die Abbildung zeigt den an einen Frequenzgenerator angeschlossenen µCompozzel. Es ist aber zu beachten, dass die Werteanzeige im unteren Displaybereich nicht 100%ig dem oben beschriebenen Layout entspricht. Dennoch wird die Frequenz schon korrekt angezeigt.

Zum Abschluss kann man sich hier noch ein kleines Demonstrationsvideo anschauen.

Sollten am Ende noch Fragen offen geblieben sein ist eine Mail an gerry.w@gammaproduction.de oder morgworm@gmx.de durchaus erwünscht.