//
//
// RS485/UART Mirror for Board Olimex AVR-P28 
// with Prozessor Atmel ATmega8-16PU
//
// Board circuits: 
//  	http://www.olimex.com/dev/avr-p28.html (origin)
// 		http://www.gfai.de/~heinz/techdocs/olimex_mod_schaltg.gif (modified)
//
// Environment: AVR-Studio4 v.4.15 with WINAVR v.1.6.4 (AVR-GCC)
//  http://savannah.nongnu.org/projects/avr-libc/
//
// PC-site using Virtual COM-Port with WindowsXP-Hyperterminal
//	Adapter USB2SERIALv10 Typ RS485 or RS232
//	RS485: OlimexP28 add-on: LTC485/75LBC176 with TXD/RXD/RSIO
//
// Echoes back the received characters on the UART, and
//  displays received character on a 8-LED chain.
//
// Pins:
// 	PC 0-5 	LED-chain	bit-enumeration: ASCII: Bit 0-5
//		PD 6-7 	LED-chain	bit-enumeration: ASCII: Bit 6-7
//		PD1 	UART-TXD 	using FTDI232 with USB-VCP
//		PD0 	UART-RXD 	using FTDI232 with USB-VCP
//		PB0 	RSIO	 	Read/Write-signal for RS485, Tx: high, Rx: low
//		PD3		SW2			Switch: on: 9600 bps, off: 115200 bps
//
// Hyperterminal: 
//  	Data rate and protocol xxx-8-n-1-n, xxx = 9600/115200 bps
//
// Quartz:
//		Olimex 8 MHz changed to 7.3728 MHz for 115200 bps
//
// Program memory ~1000 Byte, data memory ~200 Byte
//
// heinz@gfai.de, 
//		11/2007 created 
//		11/2008 SW2 und 115200 Baud ergänzt
//		11/2012 pgmspace ergänzt, aufgeräumt
//  

/*

Funktion:

Der Spiegel empfängt ein ASCII-Zeichen über UART und sendet es über UART zurück. 
Das empfangene ASCII-Zeichen wird außerdem auf 8 LEDs angezeigt.
Bei Empfang eines CR (Return, 0x0d) wird eine Zeichenkette rückgesendet.
Bei ESC (Escape, 0x1b) wird das Rücksenden beendet.

Das Programm gehört zu einer Testumgebung für über USB-VCP (Virtual COM-Port) und RS485 
aufgebaute UART-Verbindungen. Es wurde auf dem Olimex AVR-P28 Board realisiert. 
Dazu werden acht LEDs an PORTC[0:5] und PORTD[6:7] bedient (über 360 Ohm gegen VDD). 
An PORTB[0] wird ein Read/Write Signal zur Steuerung der RS485-Schnittstelle ausgegeben.
Jedes über UART (RX/TX, TTL-Pegel, nichtinvertiert) ankommende Byte wird gespiegelt und 
über UART zurückgesendet. 

Die acht LEDs zeigen das jeweils letzte, empfangene Zeichen in Bitform an.
Um RS485 testen zu können, wird ein Read/Write-Signal (RSIO) auf PB0 
erzeugt, welches Tx/Rx des RS485-Bausteines steuert. Unmittelbar bevor ein Zeichen 
gesendet wird, geht RSIO auf high, nach Sendung sofort wieder auf low. 
RSIO wird am RS485-IC direkt an "data send enable" DE angeschlossen. 
Der Dateneingang UART-RXD liegt über einen pullup 10k an VDD, um Rückspiegelung durch 
den RS485 Baustein zu verhindern.


Betriebsarten
- Mode Spiegel (Start oder ESCAPE-Taste am Hyperterminal):
	Nach Reset wird die Baudrate an Schalter SW2 PD3 abgefragt. 
	Derzeit sind 115200 und 9600 Baud einstellbar.
	Dann werden alle LEDs im Abstand von "wait" kurz invertiert (Lebenszeichen).
	Kommt ein Zeichen über die UART, wird es zurückgesendet (gespiegelt) 
	und es wird auf den LEDs angezeigt. Im Abstand von "wait" wird das Zeichen 
	wiederum kurz invertiert auf den LEDs angezeigt. 

- Mode Zeichengenerator (RETURN-Taste am Hyperterminal):
	Nach RETURN wird ein Text (siehe sendTxt) periodisch wiederholt.

Oszillator
Mit internem Oszillator können etwa 13 bis 15 Zeichen fehlerfrei übertragen werden.
Mit 8 MHz Quarz sind bei 9600 Baud keine Übertragungsfehler zu erkennen. 
Besser allerdings ist ein 9600*n Quarz (n € N). Der Quartz im Olimex-Board ist steckbar,
so kann er ohne Probleme gegen einen 7,3726 MHz Quartz ausgetauscht werden (elpro HC49/U-S). 
Dieser ist vorteilhaft, sollen Baudraten höher 9600 Baud genutzt werden.

Hyperterminal einstellen
Unter WindowsXP findet man im Gerätemanager (Link erzeugen auf %windir%\system32\devmgmt.msc) 
unter "Anschlüsse (COM und LPT)" den "USB Serial Port (COMx)" des FTDI-Chips. 
Mit dem Windows-Hyperterminal (unter "C:\Programme\Windows NT\hypertrm.exe") ist dann 
eine Sitzung zu eröffnen mit den Einstellungen 

	- COMx 				x steht für die Portnummer
	- Bits pro Sekunde:	9600/115200 mit Switch SW2 (key) wählbar
	- Datenbits: 		8
	- Parität: 			keine
	- Stoppbits:		1
	- Flusssteuerung: 	keine

	Hyperterminal-Steuerzeichen:
		Special Character Escape Sequences:
		alert (beep) 	\a
		backslash 		\\
		backspace 		\b
		carriage return \r
		double quote 	\"
		formfeed 		\f
		horizontal tab 	\t
		newline 		\n
		null character 	\0
		single quote 	\'
		vertical tab 	\v
		question mark 	\?

		Xon	 (DC1)	CTL Q	0x11	okt. \021	dez.17
		Xoff (DC3)	CTL S	0x13	okt. \023	dez.19
		Esc			CTL [	0x1b	okt. \033	dez.27
		CR    		CTL M   0x0D	okt. \015	dez.13

Vorsicht!
Das Programm ist nur als Anregung für eigene Arbeiten zu verstehen. 
An einigen Stellen wurde ungenutzter oder Testcode als Option belassen, 
um abweichende Lösungs(un)möglichkeiten zu erproben. 

Grundlagen siehe
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial

*/


/********* INITS ***************************************************/


// globals

#define __AVR_ATmega8_		// Prozessor (ruft iom8.h über io.h auf)
#define F_CPU 7372800UL		// Quarzfrequenz in Hz: 7,3728 MHz
#define BAUDRATE1 9600
#define BAUDRATE2 115200

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>     // alte avr-libc: #include <avr/delay.h>
#include <stdint.h>			// uint8_t
#include <avr/pgmspace.h>	// prog_char
//#include <inttypes.h>
//#include <avr/sleep.h>

uint32_t baudrate;	// umschaltbar. Zur Änderung der Baudrate 
					// Schalter SW2 (PD3) umschalten, dann Reset-Button drücken
uint8_t 		ch;					// UART-Zeichen, global 8 bit
uint8_t 		prescal	= 0x04;		// Interrupt-Timer Prescaling Faktor
uint16_t		timer = 0xeeee;		// Interrupt-Timer Pausenzeit

// Flash-Text:
const prog_char *text = 
 "\023\f\a\n UART-Spiegel ATmega8-16PU auf Board Olimex AVR-P28 \r\n\
 heinz@gfai.de, 11/2007, UART 8-n-1-n, 9600/115200 Baud, \r\n\
 Sources http://www.gfai.de/~heinz/techdocs/index.htm \r\n\
 CR sendet diesen Text, ESC beendet die Ruecksendung. \r\n\n \021";

	/* C-Strings werden i.a. automatisch mit Null terminiert (\0)
	Xon/Xoff: Die Steuerzeichen sind zur Sicherheit eingebaut worden:
	Zu Beginn wird der Gegenseite mit Xoff (\oktal!) durch die Blume gesagt, 
	sie möge jetzt bitte schweigen. Zum Ende wird mit Xon angedeutet, 
	sie könne jetzt wieder plaudern. 
	Xon/Xoff kann, muß aber nicht an Hyperterminal oder Putty eingestellt sein.
	*/

	/*
	Datentypen bei Winavr siehe 
	http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial
	#include <stdint.h>

	typedef signed char        int8_t;
	typedef unsigned char      uint8_t;
	typedef short              int16_t;
	typedef unsigned short     uint16_t;
	typedef long               int32_t;
	typedef unsigned long      uint32_t;
	typedef long long          int64_t;
	typedef unsigned long long uint64_t;

	Win32-Typbezeichner (gehen auch):
	typedef unsigned char		BYTE;
	typedef unsigned short		WORD;
	typedef unsigned long		DWORD;
	typedef unsigned long long	QWORD;

	// Wertebereich unsigned
	// char 	(8 Bit, 	max. 2^8 -1 	= 255)
	// short 	(16 bit, 	max. 2^16 - 1 	= 65535)
	// long 	(32 bit, 	max. 2^32 - 1 	= 4.295e9)
	*/

/************ IO ****************/

// Setzen/Löschen von Port-Bits: // ATmega top idea: read PORTC is PINC ! dont forget !

#define bitset(PORT,BITNUM) ((PORT) |=  (1<<(BITNUM)))	// Set I/O bit; 	OR 		|=
#define bitclr(PORT,BITNUM) ((PORT) &=~ (1<<(BITNUM)))	// Clear I/O bit; 	AND 	&=
#define bittog(PORT,BITNUM) ((PORT) ^=  (1<<(BITNUM))) 	// Toggle I/O bit; 	XOR 	^=

// Merke: 	NEG ~ 		OR |  		AND & 		XOR ^

/************ UPs **************/

// Init data directions and ports
void initPorts(void) {				// siehe portpins.h
	// Datenrichtung einstellen (1: out, 0: in):
	DDRB = 0x01;	// PORTB0 for RSIO output 0000 0001 = hex 01
	DDRC = 0x3f;	// PORTC[0:5] as LED output 0011 1111 = hex 3f
	DDRD = 0xc0;	// PORTD[6:7] as LED output 1100 0000 = hex c0

	// Portbits setzen (bei in = 1: pullup):
	PORTB = 0x00;	// RSIO = 0 (receive = low, transmit = high)
	PORTC = 0x3f;	// PC[0-5] LEDs off
	PORTD = 0xc0;	// PD[6-7] LEDs off
}


// getkey (Pin oder einen Schalter abfragen -> Aufruf z.B. in askBaudrate() )
uint8_t getkey(volatile uint8_t *inputreg, uint8_t inputbit)	{
	// inp: Port-IN (PIN)		out: return-value 0/1
	// Funktionsaufruf:  z.B. getkey(&PIND,PD3)
	return(( *inputreg & (1<<inputbit))); 		// Wert zurückgeben
}


// Schalter SW2 an PB3 abfragen, Baudrate einstellen
void askBaudrate(void)	{
	// PD3 ~ SW2		Switch low: BAUDRATE1, high: BAUDRATE2
	uint8_t key = getkey(&PIND,PD3);	// Schalter SW2 an PORTD abfragen
	if (key == 0)	baudrate = BAUDRATE1;	// Schalter ein <-> PD3 = 0
	else 			baudrate = BAUDRATE2;	// aus: Geschwindigkeit auf BAUDRATE2 schalten
}


// Zeichenanzeige LEDs	inp: chr, out: PORTs; LEDs gehen gegen VDD -> negieren
void display(uint8_t chr)	{	// LEDs: PC[0:5]; PD[6:7], Negation, da LEDs gegen VDD
	PORTC = ~chr & DDRC; 	// ch negieren und mit DDRC maskieren
	PORTD = ~chr & DDRD;	// ch negieren und mit DDRD maskieren
}


/*
// LEDs invertieren lokal
void displayNeg(void)	{		// LEDs auf PC[0:5]; PD[6:7]
	PORTC = (~PINC & DDRC);	// PINC negieren und mit DDRC maskieren
	PORTD = (~PIND & DDRD);	// PIND negieren und mit DDRD maskieren
	// unbenutzte Bits und Pull-ups werden nicht mitgeschaltet
}	// ATmega top idea: read PORTC is PINC !!! dont forget !
*/


// UART RS-485 read/write-signal setzen
void RSIO(uint8_t state) {
	if (state==1)				// Set RSIO on PB0: Transmit data
		bitset(PORTB,PB0);		// oder PORTB |= (1<<PB0); 
	else if (state==0)			// Reset RSIO on PB0: Receive data
		bitclr(PORTB,PB0);		// oder PORTB &=~ (1<<PB0);
}


// UART Init (baudrate ist 9600 oder 115200)
void initUART(uint32_t baudrate)	{	// 32 Bit, da 115200 nicht in 16 Bit paßt
		// Baudrate berechnen
		// Optimum quartz frequency: 115200 bps * 64 = 7372800 Hz
		// damit: UBRR = fosc/(16*prescal*baud); (prescal = 4) 
		// aber Atmel:					// Tab.60, p.159, examples
		// UBRR = fosc/(16*baud) -1;	// Tab.52, p.136, ATmega8 user manual
		// UBRR(7372800, 115200) 	= 3 	= 0x03	(PORTD PIND_3 = 1 setzen)
		// UBRR(7372800, 9600) 		= 47 	= 0x2f	(PORTD PIND_3 = 0 default)

	uint16_t baud = F_CPU/(16*baudrate) - 1; // see p.136 ATmega8 user manual
		// F_CPU = 7372800 Hz:  UBRR = 3 bei 115200 Baud, UBRR = 47 für 9600 Baud

	// set URSEL = 0; URSEL is initially one and redirects UBRR to UCSRC
	UCSRC &=~ (1<<URSEL);	// reset to zero -> write to UBRR

	// set baud rate in UBRR 		// baudrate 32 Bit <-> Register 8 Bit
	UBRRH = (uint8_t) (baud>>8);	// highbyte um 8 Bit rechts schieben
	 	// Achtung: im UBBRH steht statt 0 eine 0x06: register-sharing mit UCSRC/URSEL
	UBRRL = (uint8_t) baud;			// lowbyte kopieren (0x03 oder 0x2f)

	// UCSRB: USART Control and Status Register B
		// Bit4 RXEN: Receiver Enable
		// Bit3 TXEN: Transmitter Enable
	// Enable Receiver and Transmitter
	UCSRB |= (1<<RXEN)|(1<<TXEN);	// Rx und Tx enable

	// UCSRC: USART Control and Status Register C
		// Bit7 URSEL: Register Select (UCSRC or UBRRH)
		// Bit3 USBS: Tx-Stop Bit Select: USBS=0: 1 Bit; USBS=1: 2 Bit
		// Bit2 Bit1 UCSZ0: Character Size 
		// UCSZ[2:0] Anzahl der Bit, 8 Bit = 011 (siehe S.157)

	// set URSEL = 1; URSEL redirects UBRR to UCSRC
	UCSRC |= (1<<URSEL);	// set one -> write to UCSRC

	// Set frame format: UCSZ[1:0] 8 databit, (USBS=0: 1 stop bit)
	UCSRC |= (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);	// erstmalig

	// Set frame format: 8 data, 2 stop bit
	// UCSRC = (1<<URSEL)|(1<<USBS)|(3<<UCSZ0); 
	// 3<<UCSZ0 bedeutet: UCSZ1=1, UCSZ0=1: 8 Bit 
	// ACHTUNG - Bug ATmega8: 	nach erstem Stopbit wird ausgegeben: 
	// 							Crash bei zwei Stoppbits!

	// Rx Complete Interrupt Freigabe:
	UCSRB |= (1<<RXCIE);		// enable receive complete interrupt
	// Achtung! RXCIE und TXCIE schließen sich aus!
	// UCSRB = (1<<TXCIE);		// enable transmit complete interrupt
	}

// UART Zeichen senden (lokales chc)
void uputc(char chc) { 		// UCSRA-Bit5 UDRE: USART Data Register Empty
	RSIO(1);				// RSIO auf transmit stellen (einschalten)
	UCSRB |= (1<<TXCIE);	// enable transmit complete interrupt
	while ( !( UCSRA & (1<<UDRE)) ); // Put data into buffer, send the data
		UDR = chc;			// UDR: USART I/O-data register
	UCSRB |= (1<<RXCIE);	// enable receive interrupt
}


// UART put string
void uputs(const prog_char *txt) {	// ab Zeiger *txt senden
	RSIO(1);			// RS485 auf Senden setzen
	while (*txt) {		// solange *strg != '\0' ungleich String-Ende
		uputc(*txt++);		
	}
}


// Entweder Overflow-Interrupt ODER Compare-Match Interrupt benutzen!
/*
// Init Timer1A Overflow-Interrupt
void initTimer_ovf(uint8_t presc) {
	// Init Timer1 Overflow Interrupt
	// Set prescaler TCCR1B 2:0 = CS1 2:0
	TCCR1B |= presc;		// set CS1 (3 bit)
	// set TOIE1 overflow-interrupt enable
	TIMSK |= 0x04;		// 0x04 = 0b0000 0100, set Bit#2
}
*/


// Init Timer1A Compare Match Interrupt
void initTimer(unsigned int delay, uint8_t presc) {
	// Kopiere delay auf Timer Output Compare Registers OCRxxx
		// delay ist 16 Bit breit, OCRxxx nur jeweils 8 Bit
	OCR1AH = (uint8_t) (delay>>8);	// high-Byte um 8 Stellen rechts schieben
	OCR1AL = (uint8_t) delay;		// low-Byte auf OCRxxL kopieren
	// Set prescaler TCCR1B 2:0 = CS1 2:0
	TCCR1B |= presc;		// set CS1 (3 bit)
		// 	/1: 	001 	0x01
		//	/8: 	010		0x02
		//	/64: 	011		0x03
		//	/256: 	100		0x04
		//	/1024: 	101		0x05
	// Entweder Overflow-Interrupt ODER Compare-Match Interrupt benutzen!
	// set OCIE1A timer1A-compare-match-interrupt enable
	TIMSK |= 0x10;		// = 0b0001 0000, set Bit#4
}


/*************************** Hauptprogramm ******************************/

int main(void) {

	initPorts();					// Ports initialisieren
	askBaudrate();					// Baudrate vom Schalter abfragen
	initUART(baudrate);				// UART initialisieren
	initTimer(timer,prescal);		// Pausen-Timer einstellen
	// initTimer_ovf(prescal);		// alternativ mit Overflow
	uputs(text);					// Eröffnung senden
	ch = 0x0d;						// weiter senden (0x0d=CR)
	sei();							// Interrupts freigeben


	while (1)
	{
		asm volatile ("nop");
		// hier passiert nichts mehr
		// den Rest machen die Interrupts 
		// ISR(USART_RXC_vect) bei Empfang oder
		// ISR(TIMER1_COMPA_vect) als Herzschlag
		// verweilen und auf Interrupts warten
	}

	return 0;

} 


/*************************** Interrupts ******************************/

/*  IRQ-Handler: 	Erlaubte vector-Namen siehe: iom8.h   
	Auszug: 	// USART, Rx Complete: 
	#define USART_RXC_vect			_VECTOR(11)
	http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html
	Namens-Definition siehe iom8.h _vector*/
/* 

// Interrupt Timer1 Overflow
ISR(TIMER1_OVF_vect) {	// wahlweise gegen ISR(TIMER1_COMPA_vect) austauschbar
.....}		
*/


// Interrupt bei Timer1A Compare Match (LEDs kurz invertieren als Herzschlag)
ISR(TIMER1_COMPA_vect) {// wahlweise gegen ISR(TIMER1_OVF_vect) austauschbar
	TCNT1 = 0;			// Timer-Zähler rücksetzen

	// show LEDs
	display(~ch);		// LEDs invertieren
	_delay_ms(100);		// warten für LEDs (in Millisekunden)
	display(ch);		// LEDs zurückschalten

	// Menuewahl:
	if (ch == 0x1b)		// wenn ESC darinnen steht
		return;			// dann nichts an das Hyperterminal zurücksenden
	if (ch == 0x0d)		// wenn CR angekommen war
		uputs(text);	// sende als Lebenszeichen Text an Hyperterminal
	else uputc(ch);		// sende das empfangene Zeichen zurück
}


// Transmit komplett Interrupt, RS485-IO rückstellen
ISR(USART_TXC_vect)	{	// Namens-Definition USART_TXC_vect siehe iom8.h
	RSIO(0);			// reset RSIO-pin AFTER transmission complete!
}		// RSIO(0) darf nicht innerhalb der uputc oder uputs erfolgen!


// Interrupt bei Empfang 
ISR(USART_RXC_vect)	{	// Namens-Definition USART_RXC_vect siehe iom8.h
	RSIO(0);		// TX wegnehmen zur Sicherheit
	ch = UDR;		// Zeichen aus UART-Register holen (char ch ist global)
	display(ch); 	// empf. Zeichen auf LEDs anzeigen
	uputc(ch);		// Zeichen an Hyperterminal zurücksenden
}

