/*
 * File:   main.c
 * Author: etrauschke
 *
 * Created on September 27, 2017, 8:16 AM
 */
#pragma config RSTOSC = 2, FEXTOSC = 7
//#pragma config RSTOSC = 0
#pragma config WDTE = OFF, XINST = 1, DEBUG = 1, WRTD = 1
//#pragma config BOREN = OFF, FCMEN = OFF, LVP=OFF

#define _XTAL_FREQ      12800000         // this is used by the __delay_ms(xx) and __delay_us(xx) functions

#define I2C_ADDRESS     0x30

#define I2C_REG_PERIOD  8
#define I2C_REG_WRITE   9
#define I2C_REG_READ    10

#define I2C_CFG_REG_CNT 9
#define I2C_REG_COUNT   11

#define TRIGGER0        PORTCbits.RC0
#define TRIGGER1        PORTCbits.RC1
#define TRIGGER2        PORTCbits.RC2
#define TRIGGER3        PORTCbits.RC3
#define TRIGGER4        PORTCbits.RC4


#include <xc.h>
#include <pic18f25k42.h>

short c = 0;
unsigned char buf[64];
unsigned char i2c_reg = 0xff;
unsigned short cfg_regs[I2C_CFG_REG_CNT];
unsigned short tick;
char trig_en[4] = {0,0,0,0};


inline void write_cfg_reg();
inline unsigned char read_cfg_reg(unsigned char byte);
void store_cfg_regs();
void load_cfg_regs();

void __interrupt(irq(IRQ_TMR1),high_priority) tmr1Int(void) {
    TRIGGER1 = 1;
    TMR1ON = 0;
    TMR1IF = 0;
}

void __interrupt(irq(IRQ_TMR3),high_priority) tmr3Int(void) {
    TRIGGER2 = 1;
    TMR3ON = 0;
    TMR3IF = 0;
}

void __interrupt(irq(IRQ_TMR5),high_priority) tmr5Int(void) {
    TRIGGER3 = 1;
    TMR5ON = 0;
    TMR5IF = 0;
}

void __interrupt(irq(IRQ_TMR0),high_priority) tmr0Int(void) {
    TRIGGER4 = 1;
    T0EN = 0;
    TMR0IF = 0;
}

void __interrupt(irq(IRQ_TMR2),low_priority) tmr2Int(void) {
   
    TMR2IF = 0;
    if (tick++ >= cfg_regs[I2C_REG_PERIOD]) {
        tick = 0;
        TMR1H = (cfg_regs[0] >> 8) & 0xff;
        TMR1L = cfg_regs[0] & 0xff;
        TMR3H = (cfg_regs[2] >> 8) & 0xff;
        TMR3L = cfg_regs[2] & 0xff;
        TMR5H = (cfg_regs[4] >> 8) & 0xff;
        TMR5L = cfg_regs[4] & 0xff;
        TMR0H = (cfg_regs[6] >> 8) & 0xff;
        TMR0L = cfg_regs[6] & 0xff;
        
        if (trig_en[3]) T0EN = 1;
        if (trig_en[0]) TMR1ON = 1;
        if (trig_en[1]) TMR3ON = 1;
        if (trig_en[2]) TMR5ON = 1;
        PORTCbits.RC0 = 1;
    } else {
        PORTCbits.RC0 = 0;
    }
    if (tick >= cfg_regs[1]) {
            TRIGGER1 = 0;
    }
    if (tick >= cfg_regs[3]) {
            TRIGGER2 = 0;
    }
    if (tick >= cfg_regs[5]) {
            TRIGGER3 = 0;
    }
    if (tick >= cfg_regs[7]) {
            TRIGGER4 = 0;
    }
    return;
}


/*
 * void __interrupt(irq(IRQ_I2C2),high_priority) i2c2Int(void)
 * 
 * Interrupt handler for I2C2 module.
 * 
 * Interrupt is set up to fire after an ACK is received or sent.
 * For writes, the first bytes determines which register should be accessed.
 * This value is kept in memory until a new write occurs. This allows the master
 * to read the register back after it has been written. To just read a register
 * without writing it first, the master can just send one byte with the register
 * number and read it after that.
 */
void __interrupt(irq(IRQ_I2C2),high_priority) i2c2Int(void) {

    I2C2IF = 0;    
    if (!I2C2PIRbits.ACKTIF) {
        return;
    }

    I2C2PIRbits.ACKTIF = 0;
    // byte is data
    if (I2C2STAT0bits.D) {
        // write to PIC
        if (!I2C2STAT0bits.R) {
            // this is the first data byte => register number
            if (i2c_reg == 0xff) {
                i2c_reg = I2C2RXB;
                if (i2c_reg == I2C_REG_WRITE) {
                    store_cfg_regs();
                }
                if (i2c_reg == I2C_REG_READ) {
                    load_cfg_regs();
                }
            // register data
            } else {
                buf[c++] = I2C2RXB;
                if (c == 2 && i2c_reg < I2C_CFG_REG_CNT) {
                    write_cfg_reg();
                }
            }
        // read from PIC
        } else {
            // read value from cfg_regs and send to I2C master,
            // but at most 2 bytes
            if (i2c_reg == 0xff || c > 1) {
                I2C2TXB = 0;
            } else {
                I2C2TXB = read_cfg_reg(c++);
            }
        }
    // byte is address
    } else {
        // address data indicates new connection, clean out old status
        c = 0;
        I2C2STAT1bits.CLRBF = 1;

        if (!I2C2STAT0bits.R){
            // for writes we expect a new register being sent as next byte
            i2c_reg = 0xff;
        } else {
            // for reads we return the current register number
            I2C2TXB = i2c_reg;
        }
    }
    
    // release SCL to free I2C bus
    I2C2CON0bits.CSTR = 0;
    return;
}

void loop() {
    int i;
    while(1);
}

/*
 * write_cfg_reg()
 * 
 * Store cfg values received from I2C into cfg_regs.
 * 
 * To keep the math for calculating the timer values and the ticks when a
 * trigger gets turned off out of the interrupt routines we just save the
 * actual value to be written into the TMRxH and TMRxL into the values in
 * i2c_regs. We also calculate the offtick value on which the TMR2 period 
 * timer disables the output.
 * 
 * The values which go into the TMRx registers are the TMRs max value minus
 * the requested delay so it rolls over after the delay. Note that 0xffff as
 * a TMRx value doesn't create reliable interrupts so the smallest delay is
 * 10 us. Since all timers are delayed by that amount the relative delay
 * between the timers is still 0.
 * 
 * The offtick is calculated as follows:
 * 
 * offtick = ontick/10 + 1 + len
 * 
 * ontick is the delay of TMRx which has a 10x time resolution (10us vs.
 * 100us for the period timer). The len is the duration of the pulse in
 * 100us ticks. The +1 is necessary since we want to make sure that we have
 * always at least one full tick before we turn the output off.
 */
inline void write_cfg_reg() {
    unsigned short val, len;
    unsigned char base_reg;
    
    // No special magic required for writing period register, just subtract 1 to
    // account for the way we reset the period timer.
    if (i2c_reg == I2C_REG_PERIOD) {
        cfg_regs[I2C_REG_PERIOD] = ((buf[0] << 8) | buf[1]) - 1;
        return;
    }
    
    val = (buf[0] << 8) | buf[1];
    base_reg = i2c_reg & 0xfe;
    
    if (base_reg == i2c_reg) {
        // update delay, calculate len from previous delay and current offtick
        len = cfg_regs[base_reg + 1] - 1 - (0xfffe - cfg_regs[base_reg]) / 10;
        cfg_regs[i2c_reg] = 0xfffe - val;
    } else {
        if (!val) {
            cfg_regs[base_reg + 1] = 0;
            trig_en[base_reg>>1] = 0;
            return;
        }
        trig_en[base_reg>>1] = 1;
        len = val;
        //trig_en[base_reg>>1] = en;
    }
    // offtick always has to be changed, either because len or delay has changed
    cfg_regs[base_reg + 1] = (0xfffe - cfg_regs[base_reg]) / 10 + 1 + len;
}

/*
 * unsigned char read_cfg_reg(unsigned char byte)
 * 
 * Read byte from value in cfg_regs to send it out to I2C.
 * 
 * The value of 'byte' refers to which byte of the value is requested and must
 * be 0 or 1 (the values in cfg_regs are 2 bytes long).
 */
inline unsigned char read_cfg_reg(unsigned char byte) {
    unsigned char base_reg;
    unsigned short val;
    
    if (i2c_reg == I2C_REG_PERIOD) {
        val = cfg_regs[I2C_REG_PERIOD] + 1;
    } else {  
        base_reg = i2c_reg & 0xfe;
        if (base_reg == i2c_reg) {
            // return delay value
            val =  0xfffe - cfg_regs[i2c_reg];
        } else {
            val = cfg_regs[base_reg + 1] - 1 - (0xfffe - cfg_regs[base_reg]) / 10;
        }
    }
    return *((unsigned char *)&val + 1 - byte);    
}


/*
 * void store_cfg_regs()
 * 
 * Store the values in cfg_regs into EEPROM.
 *
 * Storage to EEPROM requires an unlock sequence which has to be
 * followed without interruption or useless compiler junk instructions.
 * So we're turning off interrupts and use assembly.
 */
void store_cfg_regs() {
    
    unsigned char i;
    unsigned char *ptr = (unsigned char*)cfg_regs;
    GIEH = 0;
    GIEL = 0;
    NVMCON1 = 0x00;
    NVMCON1bits.WREN = 1;
    for (i = 0; i < I2C_CFG_REG_CNT * 2; i++) {
        NVMADRL = i;
        NVMDAT = *(ptr++);
        asm("MOVLW 0x55;");
        asm("MOVWF NVMCON2;");
        asm("MOVLW 0xAA;");
        asm("MOVWF NVMCON2;");
        asm("BSF NVMCON1, 1;");
        while (NVMCON1bits.WR);
        NVMIF = 0;
    }
    NVMCON1bits.WREN = 0;
    GIEH = 1;
    GIEL = 1;
}

/*
 * Load trigger values from EEPROM storage
 * 
 * Trigger delays are at even indexes of cfg_regs, lengths at odd indexes.
 * If pulse length of trigger is 0, disable trigger channel.
 */
void load_cfg_regs() {
    unsigned char i;
    unsigned char *ptr = (unsigned char *)cfg_regs;
    for (i = 0; i < I2C_CFG_REG_CNT * 2; i++) {
        NVMADRL = i;
        NVMCON1 = 0;
        NVMCON1bits.RD = 1;
        *(ptr++) = NVMDAT;
    }
    // by default, no delay
    for (i = 0; i < 8; i+=2) {
        if (cfg_regs[i] == 0xffff) {
            cfg_regs[i] = 0xfffe; 
        }
    }
    // by default, pulse off
    for (i = 1; i < 8; i+=2) {
        if (cfg_regs[i] == 0xffff) {
            cfg_regs[i] = 0x0; 
        }
        if (cfg_regs[i] != 0) {
            trig_en[(i-1)>>1] = 1; 
        }
    } 
    // default period 10ms
    if (cfg_regs[I2C_REG_PERIOD] == 0xffff) {
            cfg_regs[I2C_REG_PERIOD] = 99;
    }
}

void main(void) {
    int i;
    
    TRISB = 0x00;         //I2C on PORTB as output
    ODCONB = 0xff;
    TRISC = 0x00;
    ANSELB = 0x00;
    ANSELC = 0x00;

    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0x00;

    RB1PPS = 0x23;
    RB2PPS = 0x24;
    I2C2SDAPPS = 0x0a;
    I2C2SCLPPS = 0x09;

    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0x01;
 
    /* set up NCO 
     * NCO divider: 256 
     * counter toggles output -> additional divider by 2
     * 51.2MHz / 512 = 100 kHz -> 10us clock 
     */
    NCO1CLK = 0x00;
    NCO1INCU = 0x00;
    NCO1INCH = 0x10;
    NCO1INCL = 0x00;
    NCO1EN = 1;
    
    /* set up REFCLOCK to provide input for TMR1,3,5
     * source: NCO
     * divider: 1:1
     */
    CLKRCON = 0x90;
    CLKRCLK = 0x06;
    CLKREN = 1;

    /* TMR0 trigger timer
     * 16 bit mode
     * Fosc/4 1:128 -> 10us tick
     */
    T0CON0 = 0x10;
    T0CON1 = 0x57;
    TMR0IF = 0;
    TMR0IE = 1;
    T0EN = 1;

    /* TMR1 trigger timer
     * source CLKREF
     * 10us tick
     */
    T1CLK = 0x08;
    T1CON = 0x02;
    TMR1IF = 0;
    TMR1IE = 1;
    TMR1ON = 1;

    /* TMR3 trigger timer
     * source CLKREF
     * 10us tick
     */
    T3CLK = 0x08;
    T3CON = 0x02;
    TMR3IF = 0;
    TMR3IE = 1;
    TMR3ON = 1;

    /* TMR5 trigger timer
     * source CLKREF
     * 10us tick
     */
    T5CLK = 0x08;
    T5CON = 0x02;
    TMR5IF = 0;
    TMR5IE = 1;
    TMR5ON = 1;
    
    /* TMR2 period timer
     * source: CLKREF
     * divider 1:1
     * period 9+1 -> 100us tick
     */
    T2CLK = 0x08;
    T2PR = 9;
    T2CON = 0x00;
    TMR2IE = 1;
    T2CONbits.ON = 1;

    /* setup I2C
     */
    I2C2ADR0 = I2C_ADDRESS;
    I2C2CON0 = 0x00;
    I2C2CON1 = 0x00;
    I2C2CON2 = 0x28;
    I2C2CON0bits.EN = 1;
    RB1I2C = 0x21;
    RB2I2C = 0x21;
    PIR6bits.I2C2IF = 0;
    I2C2PIR = 0x00;
    PIE6bits.I2C2IE = 1;
    I2C2PIEbits.ACKT2IE = 1;
    I2C2STAT1 = 0;
    
    //initialize timer offsets for trigger delay 
    load_cfg_regs();
     
    GIEH = 1;
    GIEL = 1;
    I2C2CON0bits.EN = 1;
  
    loop();
    
    return;
}


