
// ******************************* Programmbeispiele    Versuch 2   2015 **************************

// *************************** OHNE Optimierung übersetzen **********************************

//							******** P R O G R A M M B E I S P I E L E ***********


//  ***** PROGRAMMBEISPIEL 01 ******

//  Einen Text ausgeben =  eine Zeichenkette über die serielle Schnittstelle senden

sg[0]=0xc0;						// Siebensegmentanzeige mit Werten füllen
sg[1]=0xf9;						
sg[2]=0xa4;
sg[3]=0xb0;
sg[4]=0x99;
sg[5]=0x92;
sg[6]=0x82;
sg[7]=0xf8;
sg[8]=0x80;
sg[9]=0x90;

						// Ab hier geht's los

uint8_t q;						// Hilfsvariable

char stringbuffer[100];			// Array für Zeichenausgabe


//							******* Hierher kommt die Initialisierung *********


usart_init();					// Die UART initialisieren

asm volatile("sei"::);			// Interrupts zulassen. Das geht nur mit einem Assemblerbefehl.
								// daß dessen Syntax so schauderös aussieht, dafür können wir nichts...


				// Jetzt wird die Zeichenkette eingerichtet und gesendet
								
strcpy (stringbuffer, "Hello, World"); // Begrüßungstext


for (q=0; q<=strlen(stringbuffer); q++) // Den Text senden
senden (stringbuffer[q]);
	
								
while (1)						// Die allgemeine Endlosschleife
{





}



//  ***** PROGRAMMBEISPIEL 02 ******

//  Verschönerung: Den Cursor an den Anfang der nächsten Zeile setzen


strcpy (stringbuffer, "Hello, World"); // Begrüßungstext


for (q=0; q<=strlen(stringbuffer); q++) // Den Text senden
senden (stringbuffer[q]);

senden (CR);							// Wir senden zusätzlich Wagenrücklauf und Zeilenschaltung;
senden (LF);


// 					Hier können Sie weitere Texte nach eigener Wahl senden (ausprobieren...)




//  ***** PROGRAMMBEISPIEL 03 ******

//  Echofunktionen. Wir bauen die Interruptserviceroutine (ISR) um


ISR(USART_RXC_vect)				// Muß so heißen
{

received_byte = UDR;			// Das Byte abholen

senden (received_byte);			// Zurücksenden (Echo)

switch (received_byte)
{

	case CR:
	{
	 	senden (LF); 			// CR behandeln
		break;					// zusätzlich LF senden (Zeilenvorschub)
	}

 	case BS: 					// BACKSPACE behandeln
	{
		senden (SP);			// Das aktuelle Byte löschen 
		senden (BS);			// zum aktuellen Byte zurück		
		break;
	}

}

}



//  ***** PROGRAMMBEISPIEL 04 ******

//  Die LCD-Anzeige initalisieren und erproben


// Diese Definitionen kommen an den Anfang (nach den Includes):

#define function_set 	0b00111000		// Das sind Initialsierungskommandos
#define	clear_display 	0b00000001
#define onoffctl		0b00001111
#define entry_mode		0b00000110
#define lcd_shift 		0b00011000
#define lcd_row1 		0x80			// An den Anfang der ersten Zeile
#define lcd_row2 		0xc0			// An den Anfang der zweiten zeile
				

				//  ***** Jetzt sind die elementaren Kommandofunktionen einzubauen 

					// Die Signalfolgen werden ganz elementar ausprogrammiert

void lcd_kdo (uint8_t command)			// Ein LCD-Kommando ausführen
{

	PORTB = 0b00000000;					// Kommandoübertragung. RS = 0					
	PORTC = command;					// Das Kommando auf den Bus
	PORTB = 0b00000100;					// Der E-Impuls: Bit ein...
	PORTB = 0b00000000;					//				und wieder aus
	lcdtime();							// Die Ausführungszeit abwarten					
}


void lcd_dat (uint8_t data)				// Ein Datenbyte übertragen
{
	PORTB = 0b00000001;					// Datenübertragung. RS = 1
	PORTC = data;						// Das Datenbyte auf den Bus
	PORTB = 0b00000101;					// Der E-Impuls
	PORTB = 0b00000001;
	lcdtime();							// Die Ausführungszeit abwarten	
}


void lcd_lkdo (uint8_t command)			// Ein LCD-Kommand ausführen, das länger dauert
{
PORTB = 0b00000000;
PORTC = command;
PORTB = 0b00000100;
PORTB = 0b00000000;
millisecs(2);							// 2 ms Ausführungszeit abwarten
}


void lcdtime (void)						// Die normale Ausführungszeit abwarten
{										// Es sind mindestens 40 µs

	uint16_t x;
	x= 10;

	while (x) x-- ;

return ;
}

				// ****** Das ist die Initialisierung  

				// Das kommt alles noch vor die Endlosschleife (While (1))

lcd_lkdo (function_set);
millisecs (40);
lcd_lkdo (function_set);
millisecs (20);
lcd_lkdo (function_set);
millisecs (10);

lcd_lkdo (function_set);
lcd_lkdo (clear_display);
lcd_kdo (onoffctl);
lcd_kdo (entry_mode);


			// ****** Jetzt senden wir das Probezeichen:

lcd_dat (0x41);




//  ***** PROGRAMMBEISPIEL 05 ******

//  Eine Zeichenkette zur LCD-Anzeige senden


//					***** Damit ersetzen wir das Senden des Probezeichens


strcpy (stringbuffer, "Borussia Dortmund");
for (q=0; q<strlen(stringbuffer); q++) 	// Den Text senden
lcd_dat (stringbuffer[q]);



//  ***** PROGRAMMBEISPIEL 06 ******

//  Zwei Zeichenketten zur LCD-Anzeige senden


strcpy (stringbuffer, "Borussia Dortmund");
for (q=0; q<strlen(stringbuffer); q++) 	// Den Text senden
lcd_dat (stringbuffer[q]);

lcd_kdo(lcd_row2);			// Die Zeilenschaltung geht nicht von allein.
							// Wir brauchen ein Adreßkommando

strcpy (stringbuffer, "Schalke Nullvier"); // Begrüßungstext
for (q=0; q<strlen(stringbuffer); q++) // Den Text senden
lcd_dat (stringbuffer[q]);


//  ***** PROGRAMMBEISPIEL 07 ******

//  Die Anzeige zyklisch umlaufen lassen (Laufschrift)

//  Die Textdarstellung bleibt, wie sie ist

//  Das Verschiebekommando kommt in die Endlosschleife

								
while (1)						// Die allgemeine Endlosschleife
{

lcd_kdo (lcd_shift);
millisecs (300);


}

// Jede umlaufende Zeile ist 40 Zeichen lang.
// Sie können die Anzeigetexte so ändern, daß ansehnliche Laufschriftanzeigen herauskommen... 


//  ***** PROGRAMMBEISPIEL 08 ******

// Die LCD-Anzeige von der seriellen Schnittstelle aus unterstützen

// Empfangene Daten werden auch auf dem LCD angezeigt

// Zunächst ganz einfach -- ohne CR und BS. Wir bauen die ISR um.


ISR(USART_RXC_vect)				// Muß so heißen
{

received_byte = UDR;			// Das Byte abholen

senden (received_byte);			// Zurücksenden (Echo)

switch (received_byte)
{

	case CR:
	{
	 	senden (LF); 			// CR behandeln
		break;					// zusätzlich LF senden (Zeilenvorschub)
	}

 	case BS: 					// BACKSPACE behandeln
	{
		senden (SP);			// Das aktuelle Byte löschen 
		senden (BS);			// zum aktuellen Byte zurück		
		break;
	}

	default:
	lcd_dat (received_byte);	// alle Bytes außer CR und BS zum LCD übertragen
}

}

// 			Das Senden der zeichenkettem muß raus.
//			Die Laufschriftfunktion muß raus:

while (1)						// Die allgemeine Endlosschleife
{


}

// Am Anfang muß die Anzeige leer sein, und der Cursor muß oben links stehen.
// Zur Probe über das Terminal einen kurzen Text eingeben.




//  ***** PROGRAMMBEISPIEL 09 ******

// Erweiterung der Anzeigeunterstützung (1). Wrap Around bei Zeilenende.

// Hierzu schreiben wir eine Zeichenausgabefunktion ENTER, 
// die das Zeilenende erkennt und berücksichtigt

// Ganz oben brauchen wir zwei neue globale Variable zur Adreßzählung

uint8_t x, y;							// Adresse in der Zeile und Zeilenadresse


void enter (uint8_t character)
{

	lcd_dat (character);				// das Zeichen ausgeben

	x++;								// die Zeilenadresse erhöhen
	if (x==16)							//  die 16. Position wurde überschritten
		{x=0;							// An den Anfang der neuen Zeile
		if (y==1) {y=0; lcd_kdo (lcd_row1); }
		else {y=1; lcd_kdo (lcd_row2);}
		}								// Die Zeilenschaltung ist hier ganz primitiv gelöst.
										// Das klappt deshalb, weil es nur zwei Zeilen sind
}



// Jetzt bauen wir noch die ENTER-Funktion in die ISR ein



ISR(USART_RXC_vect)				// Muß so heißen
{

received_byte = UDR;			// Das Byte abholen

senden (received_byte);			// Zurücksenden (Echo)

switch (received_byte)
{

	case CR:
	{
	 	senden (LF); 			// CR behandeln
		break;					// zusätzlich LF senden (Zeilenvorschub)
	}

 	case BS: 					// BACKSPACE behandeln
	{
		senden (SP);			// Das aktuelle Byte löschen 
		senden (BS);			// zum aktuellen Byte zurück		
		break;
	}

	default:
	enter (received_byte);		// alle Bytes außer CR und BS zum LCD übertragen
}

}

//   Mit dem Terminal ausprobieren...





//  ***** PROGRAMMBEISPIEL 10 ******

// Erweiterung auf Unterstützung der Tasten bzw. Terminalcodes 
// ENTER (CCR) und BACKSPACE (BS)

der Anzeigeunterstützung (1). Wrap Around bei Zeilenende.

// Hierzu schreiben wir eine Zeichenausgabefunktion ENTER, 
// die das Zeilenende erkennt und berücksichtigt

// Ganz oben brauchen wir zwei neue globale Variable zur Adreßzählung


// Den Rückschritt unterstützen wir mit einer weiteren Funktion:


void backspace ()
{

	if (x==0)								// Wenn am Zeilenanfang, geht's in die nächste Zeile
		{
		if (y==1) {x=15; y=0; lcd_kdo (lcd_row1+x);}
		else {x=15; y=1; lcd_kdo (lcd_row2+x);}
		}
	else 
		{
		x--;								// sonst geht's eine Position nach links
		if (y==0) lcd_kdo (lcd_row1+x); 
		else lcd_kdo (lcd_row2+x);
		}
}		// Jede neue Adresse muß mit einem Adreßkommando zum LCD gemeldet werden.




//  Das alles ist in die ISR einzubauen:


ISR(USART_RXC_vect)				// Interrupthandler zum Empfangen
{

received_byte = UDR;				// Byte abholen

switch (received_byte)
{

	case CR:
	{
	senden (CR);
	senden (LF); 						// CR behandeln

	x=0;
	if (y==1) {y=0; lcd_kdo (lcd_row1);} // Zeilenschaltung im LCD
	else {y=1; lcd_kdo (lcd_row2);}
	break;
	}

	case BS:
	{
	senden (received_byte);			// Zurücksenden (Echo Backspace)
	senden (SP);					// Rückschritt aufs letzte Zeichen, das wird gelöscht
	senden (BS);					// Nochmals Rückscritt aufs letzte Zeichen
	
					// Das gleiche nochmals, aber für das LCD:
	backspace ();					// Rückschritt zum letzten Zeichen	
	enter (SP);						// das Zeichen löschen
	backspace ();					// nochmals Rückschritt zum gelöschten Zeichen
	break;
	}


	default:						// Alle anderen Zeichen
	{								
	senden (received_byte);			// Zurücksenden (Echo)
	enter (received_byte);			// Zum LCD senden
	}

}

}


//  ... Ausprobieren ....




//  ***** PROGRAMMBEISPIEL 11 ******

//  Den A-D-Wandler in Betrieb nehmen

// 			 Die E-A-Initialsierung abändern. DAs Analogsignal kommt  über Port A, Bit 0

PORTA = 0xfe;					// Port A Bit 0 auf Eeingang
PORTB = 0b00000010;				// Das TX-:Signal auf 1
PORTC = 0xff;
PORTD = 0xff;
DDRA  = 0xfe; 					// Port A, Bit 0: Pullup abschalten
DDRB  = 0xff;
DDRC  = 0xff;
DDRD  = 0xff;         



//        Vor dem Eintritt in die Endlosschleife ist der A-D-Wandler einzuschalten

ADMUX=0x20;						// Wir nutzen nur die höherwertigen 8 Bits
ADCSRA=0x86;


while (1)						// Die allgemeine Endlosschleife
{


ADCSRA=0xc6; 					// Die Wandlung starten. Hierzu ist Bit 6 zu setzen
								// Wir zeigen hier die einfachste Lösung überhaupt.


while (!(ADCSRA & 0x10)); 		// Warten, bis die Wandlung fertig ist (Bit 4 = 1)

q=ADCH;							// das gewandelte Byte einlesen

ADCSRA = (ADCSRA | 0x10);		// Die Fertiganzeige zurücksetzen (Schreiben einer Eins)

PORTC = ~q;						// Der binären Wert auf Port C ausgeben (zwecks LED-Anzeige) 

}







//  ***** PROGRAMMBEISPIEL 12 ******

//  Digitalvoltmeter mit LCD-Anzeige


//		Am Anfang des Hauptprogramms eine neue Variable definieren:

uint16_t wert;					// ein 16-Bit_Wort, das den binären Zahlenwert aufnimmt

//        Vor dem Eintritt in die Endlosschleife ist der A-D-Wandler einzuschalten

ADMUX=0x00;						// Wir nutzen jetzt alle 10 Bits
ADCSRA=0x86;



while(1) 						// Die allgemeine Endlosschleife
{

ADCSRA=0xc6; 					// Die Wandlung starten


while (!(ADCSRA & 0x10)); 		// Warten, bis die Wandlung fertig ist (Bit 4 = 1)


q=ADCL;							// Die beiden Datenregister einlesen
wert=ADCH;						// Erst das niederwertige, dann das höherwertige
wert = (wert & 0x03)			// die überschüssigen Bits (Don't Cares) ausblenden
wert=wert<<8;					// die höherwertigen Bits ins höherwertige Byte

ADCSRA = (ADCSRA | 0x10);		// Die Fertiganzeige zurücksetzen (Schreiben einer Eins)

wert=wert+q;					// Das niederwertige Byte hinzufügen. Wert enthält jetzt díe komplette Binärzahl

wert=wert*4;					// Das ergibt den skalierten Spannungswert in mV

lcd_kdo (lcd_row1);				// An den Anfang der ersten LCD-Zeile

lcd_dat((wert/1000) +0x30);		// Die Tausenderstelle anzeigen

lcd_dat(0x2c);					// Den Dezimalpunkt anzeigen

wert=wert%1000;

lcd_dat((wert/100) +0x30);		// Die Hunderterstelle anzeigen

wert=wert%100;

lcd_dat(wert/10 +0x30);			// Die Zehnerstelle anzeigen

wert=wert%10;

lcd_dat(wert +0x30);			// Die Einerstelle anzeigen

millisecs (300);				// Etwas warten, damit die Anzeige nicht zu sehr zappelt


} 								



//  Wenn das fertig ist, die Ausgabe auf  die serielle Schnittstelle umstellen.
//  Nur dann ausgeben, wenn sich das Wandlungsergebnis geändert hat.
//  Nach der Zahlenausgabe CR + LF senden (neue Zeile)






//  ***** PROGRAMMBEISPIEL 13 ******

//  Digitalvoltmeter mit Siebensegmentanzeige


// 	Die Initialisierung der LCD und der seriellen Schnittstelle entfernen (löschen oder auskommentieren)


// 	Drei Stellen der Siebensegmentanzeige anschließen. Wir lassen die Einerstelle weg.
// 	Tausenderstelle an Port D, Hunderterstelle an Port B, Zehnerstelle an Port C.


// Es ist nur die Ausgabe zu ändern:


wert=wert*4;					// Das ergibt den skalierten Spannungswert in mV



PORTD = sg[wert/1000];			// Die Tausenderstelle anzeigen

wert=wert%1000;

PORTB =  sg[wert/100);			// Die Hunderterstelle anzeigen

wert=wert%100;

PORTC = sg[wert/10];			// Die Zehnerstelle anzeigen

