/*
    Eine 8-kanalige PWM mit intelligentem Lösungsansatz
 
    ATmega32 @ 8 MHz
 
*/
 
// Defines an den Controller und die Anwendung anpassen
 
#define F_CPU         8000000L          // Systemtakt in Hz
#define F_PWM         100L               // PWM-Frequenz in Hz
#define PWM_PRESCALER 8                  // Vorteiler für den Timer
#define PWM_STEPS     256                // PWM-Schritte pro Zyklus(1..256)
#define PWM_PORT      PORTB              // Port für PWM
#define PWM_DDR       DDRB               // Datenrichtungsregister für PWM
#define PWM_CHANNELS  8                  // Anzahl der PWM-Kanäle
 
// ab hier nichts ändern, wird alles berechnet
 
#define T_PWM (F_CPU/(PWM_PRESCALER*F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt
//#define T_PWM 1   //TEST
 
#if ((T_PWM*PWM_PRESCALER)<(111+5))
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden
#endif
 
#if ((T_PWM*PWM_STEPS)>65535)
    #error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER erhöhen.   
#endif
// includes
 
#include <stdint.h>
#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdbool.h>
#include <stdint.h>

// globale Variablen
 
uint16_t pwm_timing[PWM_CHANNELS+1];          // Zeitdifferenzen der PWM Werte
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];      
uint8_t  pwm_mask[PWM_CHANNELS+1];            // Bitmaske für PWM Bits, welche gelöscht werden sollen
uint8_t  pwm_mask_tmp[PWM_CHANNELS+1];        
uint8_t  pwm_setting[PWM_CHANNELS];           // Einstellungen für die einzelnen PWM-Kanäle
uint8_t  pwm_setting_tmp[PWM_CHANNELS+1];     // Einstellungen der PWM Werte, sortiert
 
volatile uint8_t pwm_cnt_max=1;               // Zählergrenze, Initialisierung mit 1 ist wichtig!
volatile uint8_t pwm_sync;                    // Update jetzt möglich
 
// Pointer für wechselseitigen Datenzugriff
 
uint16_t * isr_ptr_time  = pwm_timing;
uint16_t * main_ptr_time = pwm_timing_tmp;
uint8_t *  isr_ptr_mask  = pwm_mask;
uint8_t *  main_ptr_mask = pwm_mask_tmp;
 
// Multiplexroutine

inline void multiplex(bool multi0, bool multi1, bool multi2, bool multi3)
{
	for (int i=0; i<5; i++)
	{
		switch (i)
		{
			//
			case (0):
			{
				PORTD |= (1<<i);
				for (int j=4; j<8; j++)
				{
					if (multi0[1] == true)
					{
						PORTD |= (1<<j);
					}
					else 
					{
						PORTD &= ~(1<<j);
					}
				}
				
				PORTD &= ~(1<<i);
			}break; 
			//
			case (1):
			{
				PORTD |= (1<<i);
				for (uint8_t j=4; j<8; j++)
				{
					if (multi1[(j-4)] == true)
					{
						PORTD |= (1<<j);
					}
					else 
					{
						PORTD &= ~(1<<j);
					}
				}
				
				PORTD &= ~(1<<i);
			}break; 
			//
			case (2):
			{
				PORTD |= (1<<i);
				for (uint8_t j=4; j<8; j++)
				{
					if (multi2[(j-4)] == true)
					{
						PORTD |= (1<<j);
					}
					else 
					{
						PORTD &= ~(1<<j);
					}
				}
				
				PORTD &= ~(1<<i);
			}break; 
			//
			case (3):
			{
				PORTD |= (1<<i);
				for (uint8_t j=4; j<8; j++)
				{
					if (multi3[(j-4)] == true)
					{
						PORTD |= (1<<j);
					}
					else 
					{
						PORTD &= ~(1<<j);
					}
				}
				
				PORTD &= ~(1<<i);
			}break; 
			
		}
	}
}

// Zeiger austauschen
// das muss in einem Unterprogramm erfolgen,
// um eine Zwischenspeicherung durch den Compiler zu verhindern
 
void tausche_zeiger(void) {
    uint16_t * tmp_ptr16;
    uint8_t * tmp_ptr8;
 
    tmp_ptr16 = isr_ptr_time;
    isr_ptr_time = main_ptr_time;
    main_ptr_time = tmp_ptr16;
    tmp_ptr8 = isr_ptr_mask;
    isr_ptr_mask = main_ptr_mask;
    main_ptr_mask = tmp_ptr8;
}
 
// PWM Update, berechnet aus den PWM Einstellungen
// die neuen Werte für die Interruptroutine
 
void pwm_update(void) {
    uint8_t i, j, k, min;
    uint8_t tmp;
 
    // PWM Maske für Start berechnen
    // gleichzeitig die Bitmasken generieren und PWM Werte kopieren
 
    tmp=0;
    j = 1;
    for(i=1; i<=(PWM_CHANNELS); i++) {
        main_ptr_mask[i]=~j;                        // Maske zum Löschen der PWM Ausgänge
        pwm_setting_tmp[i] = pwm_setting[i-1];
        if (pwm_setting_tmp[i]!=0) tmp |= j;        // Maske zum setzen der IOs am PWM Start
        j <<= 1;
    }
    main_ptr_mask[0]=tmp;                           // PWM Start Daten 
 
    // PWM settings sortieren; Einfügesortieren
 
    for(i=1; i<=PWM_CHANNELS; i++) {
        min=255;
        k=i;
        for(j=i; j<=PWM_CHANNELS; j++) {
            if (pwm_setting_tmp[j]<min) {
                k=j;                                // Index und PWM-setting merken
                min = pwm_setting_tmp[j];
            }
        }
        if (k!=i) {
            // ermitteltes Minimum mit aktueller Sortiertstelle tauschen
            tmp = pwm_setting_tmp[k];
            pwm_setting_tmp[k] = pwm_setting_tmp[i];
            pwm_setting_tmp[i] = tmp;
            tmp = main_ptr_mask[k];
            main_ptr_mask[k] = main_ptr_mask[i];
            main_ptr_mask[i] = tmp;
        }
    }
 
    // Gleiche PWM-Werte vereinigen, ebenso den PWM-Wert 0 löschen falls vorhanden
 
    k=PWM_CHANNELS;             // PWM_CHANNELS Datensätze
    i=1;                        // Startindex
 
    while(k>i) {
        while ( ((pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i]==0))  && (k>i) ) {
 
            // aufeinanderfolgende Werte sind gleich und können vereinigt werden
            // oder PWM Wert ist Null
            if (pwm_setting_tmp[i]!=0)
                main_ptr_mask[i+1] &= main_ptr_mask[i];        // Masken vereinigen
 
            // Datensatz entfernen,
            // Nachfolger alle eine Stufe hochschieben
            for(j=i; j<k; j++) {
                pwm_setting_tmp[j] = pwm_setting_tmp[j+1];
                main_ptr_mask[j] = main_ptr_mask[j+1];
            }
            k--;
        }
        i++;
    }
    
    // letzten Datensatz extra behandeln
    // Vergleich mit dem Nachfolger nicht möglich, nur löschen
    // gilt nur im Sonderfall, wenn alle Kanäle 0 sind
    if (pwm_setting_tmp[i]==0) k--;
 
    // Zeitdifferenzen berechnen
    
    if (k==0) { // Sonderfall, wenn alle Kanäle 0 sind
        main_ptr_time[0]=(uint16_t)T_PWM*PWM_STEPS/2;
        main_ptr_time[1]=(uint16_t)T_PWM*PWM_STEPS/2;
        k=1;
    }
    else {
        i=k;
        main_ptr_time[i]=(uint16_t)T_PWM*(PWM_STEPS-pwm_setting_tmp[i]);
        j=pwm_setting_tmp[i];
        i--;
        for (; i>0; i--) {
            main_ptr_time[i]=(uint16_t)T_PWM*(j-pwm_setting_tmp[i]);
            j=pwm_setting_tmp[i];
        }
        main_ptr_time[0]=(uint16_t)T_PWM*j;
    }
 
    // auf Sync warten
 
    pwm_sync=0;             // Sync wird im Interrupt gesetzt
    while(pwm_sync==0);
 
    // Zeiger tauschen
    cli();
    tausche_zeiger();
    pwm_cnt_max = k;
    sei();
}
 
// Timer 1 Output COMPARE A Interrupt
 
ISR(TIMER1_COMPA_vect) {
    static uint8_t pwm_cnt;
    uint8_t tmp;
 
    OCR1A += isr_ptr_time[pwm_cnt];
    tmp    = isr_ptr_mask[pwm_cnt];
    
    if (pwm_cnt == 0) {
        PWM_PORT = tmp;                         // Ports setzen zu Begin der PWM
        pwm_cnt++;
    }
    else {
        PWM_PORT &= tmp;                        // Ports löschen
        if (pwm_cnt == pwm_cnt_max) {
            pwm_sync = 1;                       // Update jetzt möglich
            pwm_cnt  = 0;
        }
        else pwm_cnt++;
    }
}
 
int main(void) {
 
    // PWM Port einstellen
    
    PWM_DDR = 0xFF;         // Port als Ausgang
    
    // Timer 1 OCRA1, als variablem Timer nutzen
 
    TCCR1B = 2;             // Timer läuft mit Prescaler 8
    TIMSK |= (1<<OCIE1A);   // Interrupt freischalten
 
    sei();                  // Interrupts gloabl einschalten
 
bool multi0[] = {false, true, false, true};
bool multi1[] = {false, true, false, true};
bool multi2[] = {false, true, false, true};
bool multi3[] = {false, true, false, true};

multiplex(multi0, multi1, multi2, multi3);
 	
 	
/******************************************************************/
// nur zum testen, in der Anwendung entfernen
/*
// Test values
volatile uint8_t tmp;
const uint8_t t1[8]={255, 40, 3, 17, 150, 99, 5, 9};
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};
const uint8_t t5[8]={9, 1, 1, 1, 1, 1, 1, 1};
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};
const uint8_t t7[8]={0, 0, 0, 0, 0, 0, 0, 88};
 
 
// Messung der Interruptdauer
    tmp =1;
    tmp =2;
    tmp =3;
 
// Debug 
 
    memcpy(pwm_setting, t1, 8);
    pwm_update();
 
    memcpy(pwm_setting, t2, 8);
    pwm_update();
 
    memcpy(pwm_setting, t3, 8);
    pwm_update();
 
    memcpy(pwm_setting, t4, 8);
    pwm_update();
 
    memcpy(pwm_setting, t5, 8);
    pwm_update();
    
    memcpy(pwm_setting, t6, 8);
    pwm_update();
    
    memcpy(pwm_setting, t7, 8);
    pwm_update();
*/
/******************************************************************/
 
    while(1);
    return 0;
}