/*

DMX512 Receiver controls 24 open collector switches,

100 Hz Soft-PWM used to contol LEDs, relays etc.

ATmega16  @16MHz, 8 Bit PWM
ATmega164 @20MHz, 10 Bit PWM, non linear mapping

*/

// includes

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <string.h>
#include <stdlib.h>
#include "dmx-pwm24.h"
#include "lcd.h"
#include <util/delay.h>

// eeprom variables

uint8_t ee_enc_delta EEMEM = 1;

// global variables

volatile uint8_t flag_dmx;                  // new data available
volatile uint8_t flag_100ms;                // itself
volatile int8_t  enc_last;                  // encoder state
volatile int8_t  enc_delta;                 // encoder position 
volatile uint8_t pwm_cnt_max=1;             // count limit
volatile uint8_t pwm_sync;                  // update now possible
volatile uint16_t dmx_base;
                    
uint8_t  dmx_data[DMX_SIZE];
PWM_t    pwm_data[PWM_CHANNELS];
PWM_t    pwm_setting_tmp[PWM_CHANNELS+1];   // PWM data, sorted

pwm_data_t isr_data;
pwm_data_t main_data;                       // PWM data

pwm_data_t* volatile isr_ptr = &isr_data;
pwm_data_t* volatile main_ptr = &main_data;

// channel assignment, 32 bit, PORT AABBCCDD, 
// PMW channel -> port pin, defined via macros

uint32_t pwm_mask_init[32] PROGMEM = {
    PAT(&PORTB, 2), PAT(&PORTB, 1), PAT(&PORTB, 0), PAT(&PORTA, 1), // 0-3
    PAT(&PORTA, 0), PAT(&PORTB, 3), PAT(&PORTA, 4), PAT(&PORTA, 3), // 4-7
    PAT(&PORTA, 2), PAT(&PORTA, 7), PAT(&PORTA, 6), PAT(&PORTA, 5), // 8-11
    PAT(&PORTC, 5), PAT(&PORTC, 6), PAT(&PORTC, 7), PAT(&PORTC, 2), // 12-15
    PAT(&PORTC, 3), PAT(&PORTC, 4), PAT(&PORTD, 7), PAT(&PORTC, 0), // 16-19
    PAT(&PORTC, 1), PAT(&PORTD, 6), PAT(&PORTD, 5), PAT(&PORTD, 4), // 20-23
    0, 0, 0, 0, 0, 0, 0, 0};   // virtual channels

// table for nonlinear PWM conversion
// used to achieve visual linear dimming

// PWM[Index] = PWM[1]*1,027^index

uint16_t pwmtable[256] PROGMEM = {
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4,
    4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6,
    6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 9,
    9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13,
    14, 14, 14, 15, 15, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20,
    21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 28, 28, 29, 30, 31, 32,
    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 49,
    50, 51, 53, 54, 56, 57, 59, 61, 62, 64, 66, 68, 69, 71, 73, 75,
    77, 79, 82, 84, 86, 89, 91, 94, 96, 99, 102, 104, 107, 110, 113, 116,
    120, 123, 126, 130, 133, 137, 141, 145, 149, 153, 157, 161, 166, 170, 175, 180,
    185, 190, 195, 200, 206, 211, 217, 223, 229, 236, 242, 249, 256, 263, 270, 278,
    285, 293, 301, 309, 318, 327, 336, 345, 354, 364, 374, 385, 395, 406, 417, 429,
    441, 453, 465, 478, 491, 505, 519, 533, 548, 563, 578, 594, 610, 627, 644, 662,
    680, 699, 719, 738, 759, 780, 801, 823, 846, 869, 893, 918, 943, 969, 996, 1023};


//   functions 

// swap pointers

inline void swap_pointers(void) {
    pwm_data_t* tmp_ptr;

    tmp_ptr = isr_ptr;
    isr_ptr = main_ptr;
    main_ptr = tmp_ptr;
}

// PWM update, convert PWM settings into control data for ISR

void pwm_update(PWM_t *data) {
    uint8_t i, j, k;
    PWM_t min;
    uint32_t tmp, tmp1;

    // copy pwm settings
    // fill in masks
    // generate pulse mask for PWM start
    tmp=0;
    for(i=1; i<=PWM_CHANNELS; i++) {
        pwm_setting_tmp[i]= data[i-1];
        tmp1=pgm_read_dword(&pwm_mask_init[i-1]);
        if (pwm_setting_tmp[i]) tmp |= tmp1;
        main_ptr->mask[i] = ~tmp1;
    }
    main_ptr->mask[0]=tmp;

    // sort PWM settings, insert sort
    
    for(i=1; i<PWM_CHANNELS; i++) {
        min=PWM_STEPS;
        k=i;
        for(j=i; j<=PWM_CHANNELS; j++) {
            if (pwm_setting_tmp[j]<min) {
                k=j;                        // store index and PWM value
                min = pwm_setting_tmp[j];
            }
        }
        if (k!=i) {
            // swap calculated minimum with actual sorting position
            min = pwm_setting_tmp[k];
            pwm_setting_tmp[k] = pwm_setting_tmp[i];
            pwm_setting_tmp[i] = min;
            tmp = main_ptr->mask[k];
            main_ptr->mask[k] = main_ptr->mask[i];
            main_ptr->mask[i] = tmp;
        }
    }

    // unite equal PWM values, delete PWM-value 0 and MAX

    k=PWM_CHANNELS;             // PWM_CHANNELS data entries

    for (i=1; i<PWM_CHANNELS; i++) {
        if (pwm_setting_tmp[i]==0 || pwm_setting_tmp[i]==PWM_STEPS) {
            k--;
            pwm_setting_tmp[i]=0;                       // delete entry
        }
        else if (pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) {
            k--;
            pwm_setting_tmp[i]=0;                       // delete entry
            main_ptr->mask[i+1] &= main_ptr->mask[i];   // unite masks
        } 
    }

    // special case, all channels zero

    if (pwm_setting_tmp[i]==0 || pwm_setting_tmp[i]==PWM_STEPS) {
        k--;
        pwm_setting_tmp[i]=0;                       // delete entry
    }

    // fill gaps, move data to array start

    for (i=1, j=1; j<=PWM_CHANNELS; j++) {  // read all channels
        if (pwm_setting_tmp[j]!=0) {        //
            if (i!=j) {                 // move entry to actual position
                pwm_setting_tmp[i] = pwm_setting_tmp[j];
                main_ptr->mask[i] = main_ptr->mask[j];
            }
            i++;
        }
    }

    // calculate time differences

    if (k==0) {        // Special case, all channels zero
        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 {
        for (i=k, min=PWM_STEPS; i>0; i--) {
            main_ptr->time[i]=(uint16_t)T_PWM*(min-pwm_setting_tmp[i]);
            min=pwm_setting_tmp[i];
        }
        main_ptr->time[0]=(uint16_t)T_PWM*min;
    }

    // wait for sync

    pwm_sync=0;             // Sync will be set in interrupt
    while(pwm_sync==0);

    // swap pointers
    cli();
    swap_pointers();
    pwm_cnt_max = k;
    sei();
}

// convert dmx data into nonlinear PWM data

void convert(PWM_t *output, uint8_t *input) {
    uint8_t i;

    for (i=0; i<DMX_SIZE; i++) {
#ifdef PWM10BIT
        output[i] = pgm_read_word(&pwmtable[input[i]]);   // non linear PWM, 10 bit
        //output[i] = input[i]<<2;        // transform 8 Bit into 10 bit 
#else
        output[i] = input[i];           // 1:1 copy, just 8 Bit PWM
#endif
    }
}
 
// main

int main(void) {

    char tmp_string[4];
    uint8_t key_old, key_new;
    uint16_t dmx_adr, i, j;

    // variables init

    // load encoder position == DMX address from EEPROM
    enc_delta = eeprom_read_byte(&ee_enc_delta);
    if (enc_delta < 0)  enc_delta = 0;
    if (enc_delta > 84) enc_delta = 84;

    // IO init

    DDRA  = 0x0;        // first inputs for DMX adress select, then outputs, channel 0..7
    PORTA = 0xFF;
    PORTB = 0;          // all LOW
    DDRB  = 0xFF;  		// PB0-PB3 channel 20-23, rest misc stuff
    DDRC  = 0x0;        // first inputs for DMX adress select, then outputs, channel 8..15
    PORTC = 0xFF;
    PORTD = 0x0F;       // pull-ups ON for PD0-PD3
                        // PD4-PD7 channel 16-20
    DDRD  = 0xF0;       // PD0-PD3 input, rest outputs

    // UART config
 
    UBRRH = UBRR_VAL >> 8;
    UBRRL = UBRR_VAL & 0xFF;

    // use Timer 1 OCRA1

    TCCR1B = 2;             // timer runs at 1/8 clock
    TIMSK |= (1<<OCIE1A);   // enable interrupt

    //  PWM init, trick for generating a minimum timer frequency ~ 500 Hz
    //  4 virtual PWM channels with constant settings

    pwm_data[24] = 50;
    pwm_data[25] = 100;
    pwm_data[26] = 150;
    pwm_data[27] = 200;

    //  rotary encoder init
 
    enc_last = 0;
    if( PHASE_A ) enc_last = 3;
    if( PHASE_B ) enc_last ^= 1;          // convert gray to binary

    // read DIP switches

    _delay_ms(1);
    dmx_base = (~PINA & 0xFF);
    if (~PINC & (1<<PC7)) dmx_base += 256;
    PORTA = 0;
    DDRA = 0xFF;
    PORTC = 0;
    DDRC = 0xFF;
    PORTB |= (1<<PB6);                  // DIP switch OFF

    // SPI init

    SPCR = (1<<SPE) | (1<<MSTR);
    SPSR = (1<<SPI2X);

    // LCD init
    lcd_init();
    lcd_setcursor(0,0);
    lcd_string_P(PSTR("DMX LED Module"));
    lcd_setcursor(0,1);
    lcd_string_P(PSTR("BUILD "));
    itoa(BUILD_NO, tmp_string, 10);
    lcd_string(tmp_string);

#ifndef DEBUG
    _delay_ms(3000);
#endif
    lcd_setcursor(0,1);
    lcd_string_P(PSTR("DMX Address:    "));

    key_old = KEY_PRESSED;
 
    sei();

    // power on self test

    // fast fade R
    for(j=0; j<256; j+= 2) {
        for (i=0; i<8; i++) {
            dmx_data[3*i]   =j;
            dmx_data[3*i+1] =0;
            dmx_data[3*i+2] =0;
        }
        convert(pwm_data, dmx_data);
        pwm_update(pwm_data);
        _delay_ms(10);
    }   
 
    // fast fade G
    for(j=0; j<256; j+= 2) {
        for (i=0; i<8; i++) {
            dmx_data[3*i]   =0;
            dmx_data[3*i+1] =j;
            dmx_data[3*i+2] =0;
        }
        convert(pwm_data, dmx_data);
        pwm_update(pwm_data);
        _delay_ms(10);
    }   
 
    // fast fade B
    for(j=0; j<256; j+= 2) {
        for (i=0; i<8; i++) {
            dmx_data[3*i]   =0;
            dmx_data[3*i+1] =0;
            dmx_data[3*i+2] =j;
        }
        convert(pwm_data, dmx_data);
        pwm_update(pwm_data);
        _delay_ms(10);
    }   

    // all off
    for (i=0; i<24; i++) {
            dmx_data[i] =0;
    }
    convert(pwm_data, dmx_data);
    pwm_update(pwm_data);
 
    // activate UART fpr DMX reception
    UCSRB = (1<<RXCIE) | (1<<RXEN);

// endless main loop

    while (1) {
        if (flag_dmx) {
            flag_dmx=0;
            convert(pwm_data, dmx_data);
            pwm_update(pwm_data);
        }
        if (flag_100ms) {
            flag_100ms=0;

            // refresh LCD

            dmx_adr = DMX_ADR_CALC;   
            itoa(dmx_adr, tmp_string, 10);
            lcd_setcursor(12,1);
            if (dmx_adr<10) lcd_data(' ');
            if (dmx_adr<100) lcd_data(' ');
            lcd_string(tmp_string);

            // store DMX adress on key release
            key_new = KEY_PRESSED;
            if (!key_new && key_old) {  // key release
                // save encoder postion == DMX base address
                // clear lower 2 MSBs and set LSB to avoid flickering DMX address
                eeprom_write_byte(&ee_enc_delta, (uint8_t) ((enc_delta & ~0x03) | 1));
            }
            key_old = key_new;
        }
    }
}

// UART RX complete interrupt

ISR(USART_RXC_vect) {
    uint8_t status, data;
    static int16_t index=512;
 
    // read UART data, also clears interrupt flag            
    status = UCSRA;
    data = UDR;

    // allow nested interupt for PWM to minimize jitter
    sei();

    if (status & (1<<FE)) {                 // frame error
        if (data==0) {                      // break -> DMX Reset
            //index = -1-DMX_ADR_CALC;      // LCD/jog dial mode
            index = -1-dmx_base;            // DIP switch mode
        }
    }
    else {                                  // normal reception
        if ((index >= 0) && (index < DMX_SIZE)) {
            dmx_data[index]=data;           // store data
        }
        if (index == (DMX_SIZE-1)) {
            flag_dmx=1;                     // trigger update
        }
    }
    index++;
}

// Timer 1 Output COMPARE A Interrupt

ISR(TIMER1_COMPA_vect) {

    static uint8_t pwm_cnt, slow_cnt;
    ut_t tmp;
    int8_t enc_new, enc_diff;           // encoder stuff

    OCR1A   += isr_ptr->time[pwm_cnt];
    tmp.d32  = isr_ptr->mask[pwm_cnt];

    if (pwm_cnt == 0) {                 // set ports at begin of PWM
        PORTD = tmp.ary[0] | 0x0F;      // PD0-3 no change
        PORTC = tmp.ary[1];
        PORTB = tmp.ary[2] | 0xF0;      // PB4-7 no change
        PORTA = tmp.ary[3];
        pwm_cnt++;
        slow_cnt++;
        if (slow_cnt == 10) {           // 100ms timer
            slow_cnt=0;
            flag_100ms=1;               // trigger lcd refresh
        }
    }
    else {                              // clear ports
        PORTD &= (tmp.ary[0] | 0x0F);       
        PORTC &= tmp.ary[1];
        PORTB &= tmp.ary[2] | 0xF0;
        PORTA &= tmp.ary[3];
                                
        if (pwm_cnt == pwm_cnt_max) {
            pwm_sync = 1;               // update now possible
            pwm_cnt=0;
        }
        else pwm_cnt++;
    }
    
    // decode rotary encoder, setup for DMX address
    // only active, when jogdail is pressed
    // DMX address stored in eeprom on release in main loop
    if (KEY_PRESSED) {
        enc_new = 0;
        if( PHASE_A ) enc_new = 3;
        if( PHASE_B ) enc_new ^= 1;             // convert gray to binary
        enc_diff = enc_last - enc_new;          // difference last - new
        if( enc_diff & 1 ){                     // bit 0 = value (1)
            enc_last   = enc_new;               // store new as next last
            enc_delta += (enc_diff & 2) - 1;    // bit 1 = direction (+/-)
            if (enc_delta < 0)  enc_delta = 0;
            if (enc_delta > 84) enc_delta = 84;
        }     
    }
}
