/* IrOS_ver04.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.

Empfangen
Mit jedem Datenblock wird genau ein LCD Befehl ausgeführt.
Die empfangenen 16Bit werden als Nullen und Einsen in der Zweiten Display Zeile Angezeigt
Dies ermöglicht eine einfache Fehlersuche. In der ersten Zeile werden die gedrückten Tasten angezeigt

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


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;


//Variablen für senden/empfangen
u08 volatile commode; //0 = warte, 1 = empfange, 2 = sende

u08 volatile precalc;//empfang uncalculated, empfang calculated
u08 volatile empdurch;//empfang durchlauf
u08 volatile senddurch;//sende durchlauf
u16 volatile empfang;//empfangener Wert, wird jedesmal ausgewertet, ob was vernünfiges drinsteht
u16 volatile empfangbuff;//wird zum Anzeigen benutzt und dabei gelöscht
u08 volatile sendebuff;//Sendebuffer
//Variablen für die Hauptschleife
u16 volatile durchlauf;

u16 volatile temp01;
u08 volatile temp02;
u08 volatile temp03;

u08 volatile durchinabfrage;


SIGNAL(SIG_OVERFLOW1) {//timer abfrage
//wird c.a. 703 mal pro sec ausgeführt
//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
//256² - 2301 = 63235
//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
if (commode == 0) { //wenn auf empfang gewartet wird,
if bit_is_clear(PIND,0) {//gehe in Empfangsmodus
commode = 1;
empdurch = 0;
precalc = 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 timer1)
durchlauf = 0; //standby mode hinauszögern
//Sendetechnik b10TTTT01
sendebuff = sendebuff | 0x81;
//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 timer0)
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_INTERRUPT0) {
outp(0,GIMSK);//int0 deaktivieren
outp(0,MCUCR);//sleep mode enabled deaktivieren
}

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
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
//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
}

void gotobed(void) {   //entering idle 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(0x22,MCUCR);//Interrups zum Aufwachen aktiv, power down enabled (loslassen der Taste)
outp(0x40,GIMSK);//int0 aktiviert
//nun gehts in den halbschlaf
asm volatile("sleep");
}

void wakeup(void) { //guten Morgen CPU
initlcd();
outp(0x82,TIMSK);//aktiviert timer0 und timer1 Interrupt
outp(0,TCNT1H);  //setzt den Timer1 auf null (reset timer1)
outp(0,TCNT1L);  //setzt den Timer1 auf null (reset timer1)
outp(1,TCCR1B);  //startet Timer1
sei();           //aktiviert die Interrupts (activate the interrupts)
commode = 0;     //Warte auf senden oder empfangen
durchlauf = 0;   //Zeit bis zum idle 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
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)
}
}
}

int main( void ) {
outp(0x42,DDRD);
outp(0xff,DDRB);
sbi(ACSR,ACD); //deaktiviert den Analog Comparator -> geringerer Stromverbrauch
wakeup();
for (;;) {
//der folgende Code zeigt die Tastendrücke auf dem LCD in der ersten Zeile an.
//Übrigens wird auch etwas angezeigt, wenn eine Taste auf einer beliebigen
// IR Fernbedinung gedrückt wird
sendelcd(0x2,0);//return home
temp03 = inp(PIND);
checkempfang();
if (bit_is_clear(PIND,0)) { //IR Empfang
sendelcd(0x49,1);//I
}else{
  sendelcd(0x4e,1);//N
}
checkempfang();
if ((temp03 & 0x20 ) > 0) {//Taster1
sendelcd(0x54,1);//T
}else{
  sendelcd(0x4e,1);//N
}
checkempfang();
if ((temp03 & 0x10 ) > 0) {//Taster2
sendelcd(0x54,1);//T
}else{
  sendelcd(0x4e,1);//N
}
checkempfang();
if ((temp03 & 0x4 ) > 0) { //Taster3
sendelcd(0x54,1);//T
}else{
  sendelcd(0x4e,1);//N
}
checkempfang();
if ((temp03 & 0x8 ) > 0) {//Taster4
sendelcd(0x54,1);//T
}else{
  sendelcd(0x4e,1);//N
}


durchlauf++;
zumsenden();
checkempfang();
if (durchlauf > 36600) {
gotobed();
wakeup();
}
}//ende Endlosschleife
}

