/* IrOS_ver07.c
Betriebssystem für IR LCD
(c) 2003 by Malte Marwedel, www.marwedels.de/malte, m.marwedel@onlinehome.de
Diese Datei darf frei kopiert und compiliert werden.
It's allowed to copy and compile this file freely.
Änderungen an der Datei sind erlaubt, solange kenntlich gemacht wird,
dass es sich dann nicht mehr um die original Datei handelt.
It's allowed to modify this file as long it is mentioned that it isn't the original
file any longer.
Es muss immer erkennbar sein, dass die original Datei von mir stammt
It must always be recognizable that the original file is written by me
Die Benutzung des Programmes erfolgt auf eigene Gefahr, ich hafte nicht für Schäden jeglicher art, falls dieses Programm welche verursachen sollte.
The use of this program it at your own risk, I'm not responsible for damage of any kind, if this program should cause which.
Sie sollten das programm nur benutzen, wenn sie es auch verstehen.
You should only use the program, if you understand it.
Programm für gcc, MCU: AT90S2313, 2MHZ Quarz
Program for gcc, MCU AT90S2313 with a frequency of 2MHZ
Zum Kompilieren habe ich winavr20030913 verwendet, Optimierung -Os
I've used winavr20030913 for compiling, optimization -Os

Funktionsweise:
Per Infrarot werden 16Bit Datenblöcke empfangen und an das LCD weiter geleitet.
Der MCU nimmt Daten per Infrarot entgegen, initialisiert das Display und sendet
die kombination der gedrückten Tasten per Infrarot. Nach einer gewissen Zeit ohne
Aktivität schaltet er die IR Empfänger und das Display aus und geht selber in
den idle Modus. Durch Druck auf die 3. Taste (INT0) wacht er wieder auf, schaltet den
IR Empfänger ein und initialisiert das LCD.
Da das Sonderzeichen ß nicht im Displayzeichensatz vorhanden ist, wird dieses als
selbstdefiniertes Zeichen im ersten cg RAM abgelegt.

Empfangen
Mit jedem Datenblock wird genau ein LCD Befehl ausgeführt.
Die ersten und letzten zwei Bits sind Start und Stopp Bits.
Von den Restlichen 12 Bit sind 4 Bit zur Steuerung des MCU und des LCDs da.
Die anderen 8Bit werden an das LCD weiter geleitet.
Sind die 4 Steuerbits Null, so werden bestimmte Werte der 8Bit verändert.
So werden im LCD Zeichensatz ungenutzte Werte für spezielle LCD Befehle wie
2. Zeile, Display Löschen, Return Home u.s.w. gehalten.
Auch die Sonderzeichen ä,ö,ü,Ä,Ö,Ü,ß werden entsprechend korrigiert.
Ergeben die 4 Steuerbits 1, so werden die 8 Datenbits ohne Modifikationen an das
LCD als Datenbits übertragen.
Ergeben die 4 Steuerbits 2, so werden die 8 Datenbits ohne Modifikationen an das
LCD als Steuerbits übertragen.
Ansonsten haben die 4 Steuerbits keine weitere Funktion.
Geschwindigkeit: c.a 114Baud, -> 7 Displaybefehle pro Sekunde
Codierung: 10SSSSDDDDDDDD01; S = Steuerbit, D = Datenbit

Senden
Sobald eine oder mehrere Tasten gedrückt werden, wird ein 8 Bit Datenblock gesendet.
Die ersten und letzten zwei Bits sind Start und Stopp Bits.
Die verbleibenden 4 Bits repräsentieren je eine der 4 Tasten.
Wird gerade ein Wert empfangen, so werden die Tastendrücke nicht gesendet.
Codierung: 10TTTT01; T steht für jeweils eine Taste

Änderungen gegenüber Version 06:
Code Optimierung: aus 1758 Byte wurden 1628 Byte
Falsche IR Signale zögern den Power Down Modus nicht mehr hinaus (spart nebenbei 2 Byte)
Watchdog benutzen (benötgt 18 Byte Flash)
Kalibrierungsmodus


Todo:
Schneller Übertragungsmodus von Zahlen


PinD0 : Eingang, IR Empfänger
PinD1 : Ausgang, IR Sender
PinD2 : Eingang, Taster 3
PinD3 : Eingang, Taster 4
PinD4 : Eingang, Taster 2
PinD5 : Eingang, Taster 1
PinD6 : Ausgang, IR Empfänger Aktivieren/Deaktivieren
PinD7 : Auf AT2313 nicht vorhanden
PinB0 : Ausgang, IR Sender Aktivieren, grüne LED
PinB1 : Ausgang, LCD D7
PinB2 : Ausgang, LCD D6
PinB3 : Ausgang, LCD D5
PinB4 : Ausgang, LCD D4
PinB5 : Ausgang, LCD E
PinB6 : Ausgang, LCD RS
PinB7 : Ausgang, LCD Aktiviere/Deaktivieren
*/

#include <io.h> //Befehle mit Beschreibung der io.h (commands with a description of io.h): http://users.rcn.com/rneswold/avr/x1540.html
#include <inttypes.h>
#include <interrupt.h>
#include <sig-avr.h>

typedef unsigned char u08;
typedef unsigned short u16;
typedef short s16;
typedef char s08;
typedef unsigned long u32;


//Variablen für Senden/Empfangen
u08 volatile commode; //0 = warte, 1 = empfange, 2 = sende

u08 precalc;//Empfang uncalculated, empfang calculated
u08 empdurch;//Empfang durchlauf
u08 volatile senddurch;//Sende durchlauf, wird im timer0 interrupt verwendet
u16 volatile empfang;//empfangener Wert, wird jedesmal ausgewertet, ob was vernünfiges drinsteht
u16 volatile empfangbuff;
u08 sendebuff;//Sendebuffer
//Variablen für die Hauptschleife
u32 durchlauf;//Hier u16 würde 58Byte Code sparen, der controller würde jedoch zuschnell in den idle Modus gehen

u16 volatile temp01; //Wird nur für die Warteschleife verwendet

u08 entrymode,displayonoff,neuzumdisplay;//Variablen für die Display Ansteuerung


SIGNAL(SIG_OVERFLOW1) {//Timer abfrage
//wird c.a. 703 mal pro sec ausgeführt
//c.a. 140,6 mal pro sec gibt es ein neues bit
//Ausführung erfolgt also alle 11379 takte (bei 8 MHZ Chip Frequenz)
//Ausführung erfolgt also alle 2845 takte (bei 2 MHZ Chip Frequenz)
//der Timer1 läuft immer, es sei denn, es werden selber von dem Device Daten per IR gesendet
//bei 8 MHZ
//256² - 11379 = 54157
//51312, hat sich als nahezu optimal erwiesen
//bei 2 MHZ
//256² - 2845 = 62691, theoretischer Wert
//256² - 2301 = 63235, in der Praxis optimaler Wert
//Versuche, die Geschwindigkeit der Datenübertragung zu steigern schlugen fehl
//50% mehr : Übertragung nur vereinzelt korrekt
//100% mehr : Übertragung immer fehlerhaft
//möglicherweise zu niedrige Taktfrequenz (2MHZ) auf Empfängerseite
outp(0xf7,TCNT1H);//Das hohe Bit muss zuerst beschrieben werden
outp(0x03,TCNT1L);//2301;63235; 2MHZ
if (commode == 0) { //wenn auf Empfang gewartet wird,
if bit_is_clear(PIND,0) {//gehe in Empfangsmodus
commode = 1;
empdurch = 0;
precalc = 0;
empfang = 0;
}
}
if (commode == 1){
if bit_is_clear(PIND,0) {//wenn Eins, dann erhöhen
precalc++;
}
if (empdurch == 5) { //wenn 5 Sampels
//least sicnificant bit wird zuerst gesendet
empfang = empfang /2;
if (precalc > 2) { //wenn Eins empfangen
empfang = empfang | 0x8000;//oberstes Bit setzen
}

precalc = 0;
empdurch = 0;
if ((empfang == 0) || ((empfang & 1) == 1)) { //ende der Übertragung
commode = 0;
}
}//ende wenn 5 Samples
empdurch++;
}//ende wenn empfangsmode

}

void warte(u16 zeit) { //warte 2857~20ms
temp01 = 0;
while (temp01 < zeit){
  temp01++;
}
}

void zumsenden(void) { //Sendet die Daten an den Empfänger
if (commode == 0) {//es wird grade nicht empfangen
//PORTD = b00TTTT00
sendebuff = inp(PIND) & 0x3c; //extrahiert nur die Tasten
if (sendebuff > 0) {//Taste gedrückt
commode = 2;//aus Sicherheit
outp(0,TCCR1B);//stoppt den Timer1 (stop timer)
durchlauf = 0; //standby mode hinauszögern
//Sendetechnik b10TTTT01
sendebuff = sendebuff | 0x81; //setzt das erste und letzte Bit
//starte Timer0
senddurch = 1;
outp(0,   TCNT0); //setzt den Timer0 auf null (reset timer)
outp(1,   TCCR0); //startet den Timer0 (start timer)
while (sendebuff > 0) { //schiebt Bit für Bit nach draußen
if ((sendebuff & 1) == 0) { //wenn Bit null, setze Ausgang
sbi(PORTD,PIND1);
}else { //wenn Bit eins
cbi(PORTD,PIND1);
}
sendebuff = sendebuff /2;
while (senddurch != 0) {
asm volatile("nop");
}//Warteschleife
senddurch++;
}//ende Bit für Bit schieben
outp(0,   TCCR0); //stoppt den Timer0 (stop timer)
cbi(PORTB,PINB0);//nur zur Sicherheit
warte(10000); //damit der Empfänger Zeit hat, die Daten zu verarbeiten
outp(0,TCNT1H);//Das hohe Bit muss zuerst beschrieben werden
outp(0,TCNT1L);//so wird nicht sofort losgesendet
outp(1,TCCR1B);//startet den Timer1 (start timer)
commode = 0;
}//ende Taste gedrückt
}//ende, nicht empfangen
}//ende Prozedur


void pulse(void) { //Zur LCD Ansteuerung, Enabled Leitung des Displays
warte(30);
sbi(PORTB,PINB5); //Set Enabled Leitung
warte(30);
cbi(PORTB,PINB5); //Reset Enabled Leitung
warte(30);
}

SIGNAL(SIG_OVERFLOW0) { //36 khz Erzeugung auf pin b0, alle 55,5 Takte
//das Ausführen des Interrupts dauert 4 Takte
outp(204,TCNT0); //setzt den Timer0 auf 204 (256-55,5+4=~204)
sbi(PORTB,PINB0); //IRLEDs An
senddurch++;
while (inp(TCNT0) < 216){ //wartet 33 (122-89) Takte ab, ermöglicht code bis zu 30 Taken Länge auszuführen und spart 72 Byte ROM gegenüber reinen 33 nops
asm volatile("nop");
}
cbi(PORTB,PINB0);//IRLEDs Aus
}

void sendelcd(u08 zeichen,u08 typ) { //typ = 1 bedeutet Daten, 2 = initialisierung, langsam
u08 temp02;
if ((typ == 0)||(typ == 2)) {
warte(1000); //warte 5ms
}
else {
warte(200);//nur bei Daten Befehlen
}
temp02 = 0;
sbi(temp02,7);//Das Display darf schließlich nicht ausgeschaltet werden
temp02 = (temp02 & 0xfd)|((zeichen /64)& 0x2);
temp02 = (temp02 & 0xf6)|((zeichen /16)& 0x4);
temp02 = (temp02 & 0xf7)|((zeichen /4)& 0x8);
temp02 = (temp02 & 0xef)|(zeichen & 0x10);
if (typ == 1) { //Daten oder Steuer Befehl (RS Leitung)
sbi(temp02,6);
}
outp(temp02,PORTB);
pulse();
if (typ == 2) { //init mode brauchts langsam
warte(1000);
}
temp02 = 0;
sbi(temp02,7);//Das Display darf schließlich nicht ausgeschaltet werden
temp02 = (temp02 & 0xfd)|((zeichen /4)& 0x2);
temp02 = (temp02 & 0xf6)|(zeichen & 0x4);
temp02 = (temp02 & 0xf7)|((zeichen * 4)& 0x8);
temp02 = (temp02 & 0xef)|((zeichen * 16)& 0x10);
if (typ == 1) { //Daten oder Steuer Befehl (RS Leitung)
sbi(temp02,6);
}
outp(temp02,PORTB);
pulse();
}

void initlcd(void){
outp(0,PORTB);
sbi (PORTB,PINB7);//aktiviere LCD Spannung
warte(9000);//warte 2857=20ms
sendelcd(0x33,2);//langsaaaaam (sloooooow)
sendelcd(0x32,2);//langsaaaaam
sendelcd(0x28,2);//2 Zeilen und 5x7
sendelcd(0xc,0);//Display an
sendelcd(0x1,0);//Display löschen
sendelcd(0x6,0);//entry mode set
sendelcd(0x2,0);//return home
//definiere Zeichen ß
sendelcd(0x40,0);//erste CG RAM Adresse
sendelcd(0x1e,1);//11110
sendelcd(0x41,0);//2. CG RAM Adresse
sendelcd(0x11,1);//10001
sendelcd(0x42,0);//3. Cg RAM Adresse
sendelcd(0x11,1);//10001
sendelcd(0x43,0);//4. CG RAM Adresse
sendelcd(0x16,1);//10110
sendelcd(0x44,0);//5. CG RAM Adresse
sendelcd(0x11,1);//10001
sendelcd(0x45,0);//6. CG RAM Adresse
sendelcd(0x15,1);//10101
sendelcd(0x46,0);//7. CG RAM Adresse
sendelcd(0x12,1);//10010
sendelcd(0x47,0);//8. CG RAM Adresse
sendelcd(0x10,1);//10000
sendelcd(0x80,0);//gehe wieder in DD RAM
//ende definiere Zeichen
//Als Begrüßung und Funktionskontrolle Anzeigen:
sendelcd(0x48,1);//H
sendelcd(0x65,1);//e
sendelcd(0x6c,1);//l
sendelcd(0x6c,1);//l
sendelcd(0x6f,1);//o
entrymode = 6;
displayonoff = 0xc;
}

void gotobed(void) {   //entering power down mode
//preparing for sleep...
outp(0,TCCR0); //stoppt den Timer0 (stop timer0)
outp(0,TCCR1B);//stoppt den Timer1 (stop timer1)
outp(0,PORTB);//Ausgänge aus
outp(0,PORTD);//Ausgänge aus
outp(0x1f,WDTCR);//Watchdog Disable für die näschten 4 Takte möglich
outp(0x0,WDTCR);//Watchdog Disable komplett
outp(0x30,MCUCR);//power down enabled
//nun gehts in den tiefschlaf
asm volatile("sleep");
}

void wakeup(void) { //guten Morgen MCU
asm volatile("wdr");
outp(0x0f,WDTCR);//Aktiviert den Wachdog mit 1,9sec bis zu einem Reset
initlcd();
outp(0x82,TIMSK);//aktiviert timer0 und timer1 Interrupt
outp(0,TCNT1H);  //setzt den Timer1 auf null (reset timer)
outp(0,TCNT1L);  //setzt den Timer1 auf null (reset timer)
outp(1,TCCR1B);  //startet timer1
sei();           //aktiviert die Interrupts (activate the Interrupts)
commode = 0;     //Warte auf senden oder empfangen
durchlauf = 0;   //Zeit bis zum power down Mode zurücksetzen
sbi(PORTD,PIND1);//doppeldeaktivierung des IR Senders
sbi(PORTD,PIND6);//schaltet IR Empfang an
}

void checkempfang(void) { //untersucht, ob es was anzuzeigen gibt
u08 zumdisplay;
u08 datatype;//0000: simple mode,0001:Zeichen,0010:Display Steuer Befehl
//prüfe Standartbits 10ssssdddddddd01
empfangbuff = empfang; //So kann empfang schon wieder bescheiben werden, während der letzte Befehl noch abgearbeitet wird
if ((empfangbuff | 0x3ffc ) == 0xbffd) { //Prüft die ersten und letzten beiden Bits auf ihre Richtigkeit
durchlauf = 0;//Nach erfolgreichem Empfang, Standby Modus hinauszögern
zumdisplay = (empfangbuff & 0x03fc)/4; //extrahiere das Display Byte
datatype =(empfangbuff &0x3c00)/1024;//was sind das für Daten???
if (datatype == 0) { //simple Mode
neuzumdisplay = 0;
if ((zumdisplay == 0x1b)||(zumdisplay == 136)) {//ESCAPE Taste -> return home
neuzumdisplay = 0x2;
}
if (zumdisplay == 0xd) {//Enter Taste -> zweite Zeile
neuzumdisplay = 0xc0;
}
if (zumdisplay == 0x8) {//Backspace -> cursor links shift
neuzumdisplay = 0x10;
}
if (zumdisplay == 128) {//Cursor On
displayonoff = displayonoff & 0xfd;
neuzumdisplay = displayonoff;
}
if (zumdisplay == 129) {//Cursor Off
displayonoff = displayonoff | 0x2;
neuzumdisplay = displayonoff;
}
if (zumdisplay == 130) {//Cursor blinkt
displayonoff = displayonoff | 0x1;
neuzumdisplay = displayonoff;
}
if (zumdisplay == 131) {//Cursor dauer an
displayonoff = displayonoff & 0xfe;
neuzumdisplay = displayonoff;
}
if (zumdisplay == 132) {//Cursor rechts shift
neuzumdisplay = 0x14;
}
if (zumdisplay == 133) {//Cursor links shift
neuzumdisplay = 0x10;
}
if (zumdisplay == 134) {//Display rechts shift
neuzumdisplay = 0x1c;
}
if (zumdisplay == 135) {//Display links shift
neuzumdisplay = 0x18;
}
if (zumdisplay == 137) {//Display löschen
neuzumdisplay = 0x1;
}
if (zumdisplay == 138) {//Display on
displayonoff = displayonoff | 0x4;
neuzumdisplay = displayonoff;
}
if (zumdisplay == 139) {//Display off
displayonoff = displayonoff & 0xfb;
neuzumdisplay = displayonoff;
}
if (zumdisplay == 140) {//Incr beim Zeichen schreiben
entrymode = entrymode | 0x2;
neuzumdisplay = entrymode;
}
if (zumdisplay == 141) {//Decr beim Zeichen schreiben
entrymode = entrymode & 0xfd;
neuzumdisplay = entrymode;
}
if (zumdisplay == 142) {//Display schieben
entrymode = entrymode | 1;
neuzumdisplay = entrymode;
}
if (zumdisplay == 143) {//Display halten
entrymode = entrymode & 0xfe;
neuzumdisplay = entrymode;
}
if ((zumdisplay == 228)||(zumdisplay == 196)) {//Ää
zumdisplay = 0xe1;
}
if ((zumdisplay == 246)||(zumdisplay == 214)) {//Öö
zumdisplay = 0xef;
}
if ((zumdisplay == 252)||(zumdisplay == 220)) {//Üü
zumdisplay = 0xf5;
}
if (zumdisplay == 223) {//ß
zumdisplay = 0;
}

if (neuzumdisplay == 0) { //normales Zeichen
sendelcd(zumdisplay,1);
}else {
sendelcd(neuzumdisplay,0);
}
}//Ende Simple Mode
if (datatype == 1) { //Buchstabe
sendelcd(zumdisplay,1);
}
if (datatype == 2) {
sendelcd(zumdisplay,0); //Befehl
}
if (commode != 1) { //wenn es keine neuen Daten gibt, werden die alten Daten nicht überschrieben und
                   // möglicherweise immer wieder als neue Daten angesehen, daher löschen
empfang = 0;
}
}
}

void checkkalibempfang(void) { //untersucht, ob es was anzuzeigen gibt
u08 durchinabfrage;

empfangbuff = empfang;
//zeigt den Inhalt von empbuff auf dem LCD an
if ((empfangbuff & 1) > 0) { //LCD nur aktualisieren, wenn niedrigstes Bit gesetzt (da das höchse Bit zuerst übertragen wird
                             //bedeutet dies, dass sobald das niedrigste Bit gesetzt ist, alle 16Bit übertragen wurden
empfang = 0; //damit nicht jedesmal alle Zeichen neu geschrieben werden
sendelcd(0xc0,0);//zweite Zeile
for (durchinabfrage = 0;durchinabfrage<16;durchinabfrage++){
if ((empfangbuff & 1) == 1) { //Bit ist ne eins
sendelcd(0x31,1);//"1" an LCD senden
}else{
sendelcd(0x30,1);//"0" an LCD senden
}
empfangbuff = empfangbuff / 2; //nächstes Bit (empfangbuff wird dadurch gleichzeitig gelöscht)
}
}
}


void kalibmode(void) {
sendelcd(0x2,0);//1. Buchstabe erste Zeile
sendelcd(67,1);//K
sendelcd(97,1);//a
sendelcd(108,1);//l
sendelcd(105,1);//i
sendelcd(98,1);//b
sendelcd(114,1);//r
sendelcd(97,1);//a
sendelcd(116,1);//t
sendelcd(105,1);//i
sendelcd(111,1);//o
sendelcd(110,1);//n
sendelcd(32,1);//
sendelcd(77,1);//M
sendelcd(111,1);//o
sendelcd(100,1);//d
sendelcd(101,1);//e
for (;;) {//Endlosschleife
asm volatile("wdr");
if ((inp(PIND) & 0x1c) != 0) { //Kalib Mode verlassen
  gotobed();
}
checkkalibempfang();
}
}

int main( void ) {
outp(0x42,DDRD);
outp(0xff,DDRB);
sbi(ACSR,ACD); //deaktiviert den Analog Comparator -> geringerer Stromverbrauch
//temp02 = inp(PIND) & 0x3c; //extrahiert nur die Tasten
wakeup();
if (bit_is_set(PIND,5)) {//Gehe in kalibrierungsmodus
kalibmode();
}
for (;;) { //Endlosschleife
asm volatile("wdr");
durchlauf++;
zumsenden();
checkempfang();
warte(100);
if (durchlauf > 200000) { //wann der MCU in den Standby Modus gehen soll, 200000 sind einige Minuten
gotobed();
wakeup();
}
}//ende Endlosschleife
}

