/* ----------------------------------------------------------------------------
 
 Experiment 10b:   I2C Kommunikation - Software I2C für slave
 =============    ===============================
 
 Dateiname: softTWI_slave.c
  
 Autoren    : Tim Fischer       (Hochschule Heilbronn, Fakultaet T1)
               
 Datum      : 20.06.2021
  
 Version    : 0.1
  
 Hardware:  Simulide 0.5.16-RC5
 
 Software:  Entwicklungsumgebung: AtmelStudio 7.0
            C-Compiler: AVR/GNU C Compiler 5.4.0
 
 Funktion:	Dient als Emulation eines TWI Slaves.

 Features:	
		- Nachbildung des Daten-, Adress- und Adressmaskierungsregisters
		- Im Kontrollregister sind nur TWEA, TWEN und TWINT umgesetzt
		- ACK wird bei Wunsch gesendet
		- bis 40kHz bei 8MHz Takt in Simulide getestet (jedoch ohne weitere Funktionen)

 To Do:
		- bit counting über "bitPos" besser umsetzen
		- Variable IO-Pins umsetzen
		- TWSR-umsetzen
		- header-file erstellen
		- für längere Datenübertragung überprüfen
 
 Header-Files:  lcd_lib_de.h (Library zur Ansteuerung LCD-Display Ver.1.3)
 
  
// ----------------------------------------------------------------------------*/
 
// Deklarationen ==============================================================
 
// Festlegung der Quarzfrequenz
#define F_CPU 16000000UL
#define F_SCL 10000L //100 kHz

// Include von Header-Dateien
//#include <stdio.h>
#include <avr/interrupt.h>
//#include <math.h>	
//#include <util/delay.h>

// Konstanten
#define	SET_BIT(BYTE, BIT)	((BYTE) |=  (1 << (BIT))) // Bit Zustand in Byte setzen
#define CLR_BIT(BYTE, BIT)	((BYTE) &= ~(1 << (BIT))) // Bit Zustand in Byte loeschen
#define TGL_BIT(BYTE, BIT) 	((BYTE) ^=  (1 << (BIT))) // Bit Zustand in Byte wechseln (toggle)

/// Port for the I2C
#define I2C_DDR DDRD
#define I2C_PIN PIND
#define I2C_PORT PORTD

// Pins to be used in the bit banging
#define I2C_SCL 0
#define I2C_SDA 1

#define RISING_EDGE		1
#define FALLING_EDGE	0
#define TRUE			1
#define FALSE			0
#define BITS_IN_BYTE	8

// Used PC interrupts
#define I2C_PCINT_SCL PCINT16
#define I2C_PCINT_SDA PCINT17

//Funktionsprototypen
void I2C_Init();
void I2C_setAddress(char Address);
char I2C_readData();

uint8_t Address			=  0b0001010;
uint8_t AddressMask		= 0b11111110;

// Interne Variablen für Soft I2C
uint8_t bitPos			= BITS_IN_BYTE + 1; // Startbit wird auch als bit erkannt, deswegen +1
uint8_t receivedData	= 0;				// mit leerer TWDR-Variable beginnen 
uint8_t receivedSda		= TRUE;				// SDA wird aunfangs auf high sein
uint8_t receivedSda_old = FALSE;			// Vorgängerwert zu SDA
uint8_t receivedScl		= TRUE;				// SCL wird anfangsauf high sein
uint8_t receivedScl_old = TRUE;				// Vorgängerwert zu SCL
uint8_t isAddressByte	= TRUE;				// erstes gelesenes Byte ist Adressbyte
uint8_t isCorrectAddress= FALSE;			// anfangs ist nicht kla, ob die an den Slave mit Adresse TWAR gesendet wurde
uint8_t isAcknowleging	= FALSE;			// SDA zunächst nicht auf Low ziehen

uint8_t TWCR_soft		= 0;				// Variable als Kontrollregister	 Ersatz
uint8_t TWDR_soft		= 0;				// Variable als Datenregister		 Ersatz
uint8_t TWAR_soft		= 0;				// Variable als Adressregister		 Ersatz
uint8_t TWAMR_soft		= 0;				// Variable als Adressmaskenregister Ersatz


int main(void)
{
	DDRC= 0xFF;								// Auf DDRC die Daten ausgeben
	I2C_Init();								// soft TWI initialisieren

	I2C_setAddress(Address);				// eigene Adresse setzen
	
    while (1) 
    {
		PORTC = I2C_readData();				// Daten an PortC ausgeben
    }
}

////////////////////////////////////////////////// 
// I2C Initialisierung 
//////////////////////////////////////////////////
void I2C_Init()												
{
	SET_BIT(PCICR , PCIE2);					// Interrupt, wenn PC an Port D wie über PCMSK2 gegeben
	SET_BIT(PCMSK2, I2C_PCINT_SCL);			// Interrupt, wenn PC an SCL Pin
	SET_BIT(PCMSK2, I2C_PCINT_SDA);			// Interrupt, wenn PC an SDA Pin
	sei();
}

//////////////////////////////////////////////////
// Pin Change Interupt Handler
//////////////////////////////////////////////////
ISR(PCINT2_vect)
{
	if(!(TWCR_soft && TWEN)) {return;};							// Falls nicht Enabled, dann abbrechen
	
	receivedScl = (!(PIND & (1<<PIND0))==0);					// SCL und SDA als Bitwert einlesen
	receivedSda = (!(PIND & (1<<PIND1))==0);

	if (receivedScl == receivedScl_old)							// wenn eine Änderung in SDA auftritt
	{	if((receivedScl==TRUE))									// und SCL auf High liegt, dann ist an dieser Position ein Start oder Stoppbit 
		{
			receivedData  = 0;									// Daten zurücksetzen
			isAddressByte = TRUE;								// nächstes übertragenes Byte ist Addressbyte
			TGL_BIT(PORTB, PORTB3);								// DEBUG
			bitPos = BITS_IN_BYTE+1;							// Stopp -> Start nicht als Bit zählen (Workaround, die Anzahl der Bits bis zum ACK korrekt zu ermitteln)
		};

	} else if(receivedSda == receivedSda_old)	{				// wenn SDA sich über SCL=high nicht geändert hat
		if (receivedScl == FALLING_EDGE)	{					// und eine Steigende Flanke auf SLC anliegt
			if (isAcknowleging){								// wenn noch ACK anliegt, dann 
				CLR_BIT(I2C_DDR , I2C_SDA);							// ACK extern wieder aufheben (war einen Takt angelegen)
				isAcknowleging =  FALSE;							// ACK auc intern aufheben
				bitPos = BITS_IN_BYTE+1;							// Workaround, die Anzahl der Bits bis zum nächsten ACK korrekt zu ermitteln
			} 
			receivedData   = (receivedData <<1) + receivedSda_old;// neues Datenbit ans Datenbyte anreihen
			bitPos--;											
		};
	};
	if (bitPos ==0)												// wenn 8bits eingelesen
	{
		bitPos = BITS_IN_BYTE;									// Anzahl zurücksetzen
		TWDR_soft = receivedData;								// empfangene Daten ins Pseudoregister

		if (TWCR_soft & (1<<TWEA))								// wenn Acknowlege Enable
		{
			SET_BIT(I2C_DDR, I2C_SDA);								// für ACK SDA auf low ziehen
			isAcknowleging =  TRUE;									// internes Flag setzen
		};
					
		if (isAddressByte)										// falls es sich um das Adressbyte gehandelt hat
		{
			isCorrectAddress = ((TWDR_soft & TWAMR_soft)>>1) == ((TWAR_soft & TWAMR_soft) >>1); // Flag für Übereinstimmung von ermittelten und gegebener Adresse in den maskierten Bits berechnen
			isAddressByte	 = FALSE;								// Flag für Adressbyte zurücksetzen
		}
		else if (isCorrectAddress)								// falls es sich um die korrekte Adresse handelt
		{
			isCorrectAddress= FALSE;								// Flag für korrekte Adresse zurücksetzen
			TWCR_soft		= TWCR_soft |(1<<TWINT);				// Interuptbit in Pseudoregister setzen
		};
	};
		
	receivedSda_old = receivedSda;							// alte Werte für das nächste mal merken
	receivedScl_old = receivedScl;
}

//////////////////////////////////////////////////
// Setzen der I2C Adresse auf die der Slave hört
//////////////////////////////////////////////////
void I2C_setAddress(char Address)						
{
	TWAR_soft = (Address<<1);								// Adresse in das Pseudoregister schreiben
	TWAMR_soft= AddressMask;								// Adressmaske in das Pseudoregister schreiben
	TWCR_soft = (1<<TWEA)|(1<<TWEN)|(1<<TWIE);				// Enable Ack, Enable Interupt und Enable TWI


//	sei();
}

//////////////////////////////////////////////////
// Auslesen der übermittelten Daten 
//////////////////////////////////////////////////
char I2C_readData()						
{
	while (!(TWCR_soft & (1<<TWINT)));						// warte solange bis TWINT gesetzt ist
	CLR_BIT(TWCR_soft, TWINT);								// lösche TWINT (nur im Soft TWI notwendig)
	return TWDR_soft;										// übermittle Daten
}