#include "driverlib.h"
#include "device.h"
#include <stdint.h>

/* ================= CONFIG ================= */
#define NUM_GPIO            32U
#define CPU_FREQ_MHZ        200UL

/*
 * Target pulse = 36 µs  ±4 µs window
 * At 200 MHz: 1 µs = 200 cycles
 */
#define TARGET_US           36UL
#define TOL_US              1UL

#define TARGET_CYCLES       (TARGET_US   * CPU_FREQ_MHZ)   /* 7200 */
#define TOL_CYCLES          (TOL_US      * CPU_FREQ_MHZ)   /* 800  */

#define MIN_CYCLES          (TARGET_CYCLES - TOL_CYCLES)   /* 6400 */
#define MAX_CYCLES          (TARGET_CYCLES + TOL_CYCLES)   /* 8000 */

#define TIMER_MAX           0xFFFFFFFFUL

/*
 * ePWM period for ISR sampling.
 * 200 ticks @ 200 MHz = 1 µs per ISR call.
 * This gives enough headroom to loop 32 pins safely.
 * Do NOT go below 150 ticks (0.75 µs) or ISR will overrun.
 */
#define EPWM_PERIOD         200U    /* 1 µs sampling interval */

/*
 * Minimum gap between two rising edges on the same pin.
 * Filters out glitches / switch bounce.
 * 4000 cycles = 20 µs
 */
#define MIN_GAP_CYCLES      4000U

/* ================= GPIO LIST ================= */
/*
 * Port B covers GPIO 32-63  → bit index = pin - 32
 * Port C covers GPIO 64-95  → bit index = pin - 64
 */
const uint16_t gpio_list[NUM_GPIO] = {

    40, 41 , 48, 49, 50, 51, 52, 53,
    60, 62, 66, 67, 68, 72, 73, 74,
 //   76, 77, 80, 81, 82, 83, 84, 85,
 //   86, 87, 88, 89, 90, 91, 92, 93
};

/* ================= PIN LOOKUP TABLE ================= */
/*
 * Pre-computed at init to avoid branch-heavy pin categorisation
 * inside the ISR.
 */
typedef struct {
    uint32_t  bit;       /* pre-shifted bitmask in the port register  */
    uint8_t   port;      /* 0 = Port B (GPIO 32-63), 1 = Port C (64+) */
} PinInfo;

static PinInfo pin_info[NUM_GPIO];

/* ================= STATE VARIABLES ================= */
/*
 * All volatile because they are written in ISR and read in main/CCS.
 */
volatile uint32_t rise_time[NUM_GPIO]      = {0};
volatile uint32_t last_fall_time[NUM_GPIO] = {0};
volatile uint8_t  state[NUM_GPIO]          = {0};   /* 1 = waiting for fall */

volatile uint32_t valid_count[NUM_GPIO]    = {0};   /* ← watch this in CCS */
volatile uint32_t rising_count[NUM_GPIO]   = {0};
volatile float    debug_us[NUM_GPIO]       = {0.0f};

/* Shadow registers: previous port snapshots */
static uint32_t last_b = 0;
static uint32_t last_c = 0;

/* Precomputed port masks */
static uint32_t mask_b = 0;
static uint32_t mask_c = 0;

/* ================= TIMER HELPER ================= */
/*
 * CPUTimer0 counts DOWN from TIMER_MAX.
 * Convert to an up-counting timestamp so subtraction works naturally.
 * Handles the 32-bit wraparound automatically via unsigned arithmetic.
 */
static inline uint32_t getTimestamp(void)
{
    return TIMER_MAX - CPUTimer_getTimerCount(CPUTIMER0_BASE);
}

/*
 * Safe elapsed time: handles 32-bit wrap of the up-counter.
 */
static inline uint32_t elapsed(uint32_t start, uint32_t end)
{
    /* Unsigned subtraction wraps correctly on overflow */
    return (end - start);
}

/* ================= INIT ================= */
void initGPIOs(void)
{
    uint16_t i;
    for (i = 0; i < NUM_GPIO; i++)
    {
        uint16_t pin = gpio_list[i];

        GPIO_setDirectionMode(pin, GPIO_DIR_MODE_IN);
        GPIO_setPadConfig(pin, GPIO_PIN_TYPE_PULLUP);
        /*
         * QUAL_ASYNC: no synchronisation filter.
         * If you have hardware debounce, keep ASYNC.
         * For noisy signals switch to GPIO_QUAL_6SAMPLE.
         */
        GPIO_setQualificationMode(pin, GPIO_QUAL_ASYNC);

        if (pin < 64)
        {
            pin_info[i].bit  = (1UL << (pin - 32));
            pin_info[i].port = 0; /* Port B */
            mask_b          |= pin_info[i].bit;
        }
        else
        {
            pin_info[i].bit  = (1UL << (pin - 64));
            pin_info[i].port = 1; /* Port C */
            mask_c          |= pin_info[i].bit;
        }
    }
}

void initTimer0(void)
{
    CPUTimer_stopTimer(CPUTIMER0_BASE);
    CPUTimer_setPeriod(CPUTIMER0_BASE, TIMER_MAX);
    CPUTimer_setPreScaler(CPUTIMER0_BASE, 0U);   /* No prescaler – full 200 MHz */
    CPUTimer_reloadTimerCounter(CPUTIMER0_BASE);
    CPUTimer_startTimer(CPUTIMER0_BASE);
}

void initEPWM(void)
{
    SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_EPWM1);
    SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_TBCLKSYNC);

    EPWM_setTimeBasePeriod(EPWM1_BASE, EPWM_PERIOD - 1U);
    EPWM_setTimeBaseCounterMode(EPWM1_BASE, EPWM_COUNTER_MODE_UP);
    EPWM_setClockPrescaler(EPWM1_BASE, EPWM_CLOCK_DIVIDER_1,
                           EPWM_HSCLOCK_DIVIDER_1);

    EPWM_setInterruptSource(EPWM1_BASE, EPWM_INT_TBCTR_PERIOD);
    EPWM_setInterruptEventCount(EPWM1_BASE, 1U);
    EPWM_enableInterrupt(EPWM1_BASE);

    SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_TBCLKSYNC);
}

/* ================= ISR ================= */
__interrupt void epwm1ISR(void)
{
    /*
     * 1. Snapshot the timestamp FIRST – before any other work –
     *    to minimise jitter in the captured edge time.
     */
    uint32_t now = getTimestamp();

    /*
     * 2. Read both ports once and mask to only our pins.
     *    A single 32-bit read is atomic on C28x.
     */
    uint32_t cur_b = GPIO_readPortData(GPIO_PORT_B) & mask_b;
    uint32_t cur_c = GPIO_readPortData(GPIO_PORT_C) & mask_c;

    /*
     * 3. XOR with previous values to find changed bits.
     */
    uint32_t diff_b = cur_b ^ last_b;
    uint32_t diff_c = cur_c ^ last_c;

    /*
     * 4. Only iterate if something actually changed –
     *    saves CPU when all pins are idle.
     */
    if (diff_b | diff_c)
    {
        uint16_t i;
        for (i = 0; i < NUM_GPIO; i++)
        {
            uint32_t bit  = pin_info[i].bit;
            uint32_t diff = (pin_info[i].port == 0) ? diff_b : diff_c;
            uint32_t cur  = (pin_info[i].port == 0) ? cur_b  : cur_c;

            /* Skip pins that did not change */
            if (!(diff & bit)) continue;

            /* ===== RISING EDGE ===== */
            if (cur & bit)
            {
                /*
                 * Glitch / bounce filter:
                 * Ignore if the last falling edge was too recent.
                 */
                uint32_t gap = elapsed(last_fall_time[i], now);
                if (gap >= MIN_GAP_CYCLES)
                {
                    rise_time[i]    = now;
                    state[i]        = 1U;
                    rising_count[i]++;
                }
            }
            /* ===== FALLING EDGE (only if we saw a valid rising edge) ===== */
            else if (state[i] == 1U)
            {
                uint32_t width = elapsed(rise_time[i], now);
                float    us    = (float)width / (float)CPU_FREQ_MHZ;

                /* Store for CCS watch window debugging */
                debug_us[i] = us;

                /*
                 * Accept only pulses in [MIN_CYCLES, MAX_CYCLES].
                 * Equivalent to 32 µs … 40 µs at ±4 µs tolerance.
                 */
                if (width >= MIN_CYCLES && width <= MAX_CYCLES)
                {
                    valid_count[i]++;
                }

                last_fall_time[i] = now;
                state[i]          = 0U;
            }
        }
    }

    /* 5. Update shadow registers */
    last_b = cur_b;
    last_c = cur_c;

    /* 6. Clear interrupt flags */
    EPWM_clearEventTriggerInterruptFlag(EPWM1_BASE);
    Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP3);
}

/* ================= MAIN ================= */
void main(void)
{
    Device_init();
    Device_initGPIO();
    Interrupt_initModule();
    Interrupt_initVectorTable();

    initGPIOs();
    initTimer0();

    Interrupt_register(INT_EPWM1, &epwm1ISR);
    initEPWM();

    Interrupt_enable(INT_EPWM1);

    EINT;
    ERTM;

    /*
     * ── CCS Expression Window ──────────────────────────────────────
     *  Add these expressions to watch per-pin behaviour:
     *
     *    valid_count[0]   ... valid_count[31]   ← increments ONLY on 36 µs pulse
     *    rising_count[0]  ... rising_count[31]  ← every rising edge (debug)
     *    debug_us[0]      ... debug_us[31]      ← last measured pulse width (µs)
     *
     *  If valid_count[i] increments but debug_us[i] is outside 32-40 µs,
     *  re-check your pulse generator or wiring.
     * ──────────────────────────────────────────────────────────────
     */
    while (1)
    {
        /* Nothing needed here – all logic is in the ISR */
    }
}

