/*
 * knock_controller.c - ATtiny1616 knock conditioner firmware
 *
 * Hardware mapping (SOIC-20):
 *   PA4 ADC0 AIN4  ADC_CH1 conditioned waveform
 *   PA5 ADC0 AIN5  ADC_CH2 conditioned waveform
 *   PA6 ADC0 AIN6  CAL_CH1_ADC
 *   PA7 ADC0 AIN7  CAL_CH2_ADC
 *   PB5 ADC0 AIN8  CAL_RPM_ADC
 *   PB4             RPM_MCU rising-edge input
 *   PB3             SEL_CH1 active low
 *   PB2             SEL_CH2 active low
 *   PB0 TCA0 WO0    PWM_RAW
 *   PA0             UPDI through R37
 *
 * Toolchain: avr-gcc with ATtiny1616 device support (Microchip AVR 8-bit
 * GNU toolchain or recent avr-gcc/avr-libc). Clock assumption: 20 MHz
 * internal oscillator fuse, prescaler disabled at startup.
 */

#ifndef F_CPU
#define F_CPU 20000000UL
#endif

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/atomic.h>
#include <stdint.h>
#include <stdbool.h>

/* User-configurable engine/input constants. */
#define PULSES_PER_REV              2UL
#define RPM_MAX_VALID           12000UL
#define RPM_TIMEOUT_MS           1500UL
#define RPM_FULL_SCALE           8000UL

/* Signal-processing calibration. ADC is 10-bit, referenced to VDD. */
#define BASE_NOISE_COUNTS            6UL
#define RPM_NOISE_COUNTS_AT_MAX     80UL
#define FULL_SCALE_MAG_COUNTS       300UL
#define ADC_RAIL_LOW                  2U
#define ADC_RAIL_HIGH              1021U
#define ADC_RAIL_FAULT_SAMPLES     2048U

/* PWM: 20 MHz / (639 + 1) = 31.25 kHz, matching the analog filter notes. */
#define PWM_PER                    639U
#define TIMER_TICK_HZ         (F_CPU / 2UL)  /* TCB0 uses CLK_PER/2. */

/* ADC scheduler: waveform channels dominate; trims are sampled periodically. */
typedef enum {
    ADC_SLOT_CH1 = 0,
    ADC_SLOT_CH2,
    ADC_SLOT_CAL_CH1,
    ADC_SLOT_CAL_CH2,
    ADC_SLOT_CAL_RPM
} adc_slot_t;

typedef struct {
    int32_t baseline_q8;
    uint32_t envelope_q8;
    uint16_t rail_count;
} channel_state_t;

static volatile channel_state_t ch[2];
static volatile uint16_t cal_ch1 = 512;
static volatile uint16_t cal_ch2 = 512;
static volatile uint16_t cal_rpm = 0;
static volatile adc_slot_t adc_slot = ADC_SLOT_CH1;
static volatile uint16_t adc_pair_divider = 0;

static volatile uint16_t tcb_epoch = 0;
static volatile uint32_t rpm_last_edge_ticks = 0;
static volatile uint32_t rpm_period_ticks = 0;
static volatile bool rpm_period_valid = false;

static inline uint8_t mux_for_slot(adc_slot_t slot)
{
    switch (slot) {
    case ADC_SLOT_CH1:     return ADC_MUXPOS_AIN4_gc; /* PA4 */
    case ADC_SLOT_CH2:     return ADC_MUXPOS_AIN5_gc; /* PA5 */
    case ADC_SLOT_CAL_CH1: return ADC_MUXPOS_AIN6_gc; /* PA6 */
    case ADC_SLOT_CAL_CH2: return ADC_MUXPOS_AIN7_gc; /* PA7 */
    default:               return ADC_MUXPOS_AIN8_gc; /* PB5 */
    }
}

static inline uint16_t abs_i16(int16_t x)
{
    return (uint16_t)(x < 0 ? -x : x);
}

static inline void process_waveform_sample(uint8_t index, uint16_t sample)
{
    channel_state_t *s = (channel_state_t *)&ch[index];
    int32_t target_q8 = ((int32_t)sample << 8);

    /* Slow baseline tracker (~1024 samples) removes VREF and DC drift. */
    s->baseline_q8 += (target_q8 - s->baseline_q8) >> 10;

    int16_t centered = (int16_t)sample - (int16_t)(s->baseline_q8 >> 8);
    uint16_t magnitude = abs_i16(centered);

    /* Fast rectified-envelope IIR (~64 samples), deterministic integer math. */
    uint32_t mag_q8 = ((uint32_t)magnitude << 8);
    s->envelope_q8 += (mag_q8 - s->envelope_q8) >> 6;

    if (sample <= ADC_RAIL_LOW || sample >= ADC_RAIL_HIGH) {
        if (s->rail_count < 0xFFFFU) s->rail_count++;
    } else if (s->rail_count != 0U) {
        s->rail_count--;
    }
}

static inline void adc_start_slot(adc_slot_t slot)
{
    adc_slot = slot;
    ADC0.MUXPOS = mux_for_slot(slot);
    ADC0.COMMAND = ADC_STCONV_bm;
}

ISR(ADC0_RESRDY_vect)
{
    uint16_t sample = ADC0.RES; /* Reading RES clears RESRDY. */

    switch (adc_slot) {
    case ADC_SLOT_CH1:
        process_waveform_sample(0, sample);
        adc_start_slot(ADC_SLOT_CH2);
        break;

    case ADC_SLOT_CH2:
        process_waveform_sample(1, sample);
        if (++adc_pair_divider >= 256U) {
            adc_pair_divider = 0;
            adc_start_slot(ADC_SLOT_CAL_CH1);
        } else {
            adc_start_slot(ADC_SLOT_CH1);
        }
        break;

    case ADC_SLOT_CAL_CH1:
        cal_ch1 = sample;
        adc_start_slot(ADC_SLOT_CAL_CH2);
        break;

    case ADC_SLOT_CAL_CH2:
        cal_ch2 = sample;
        adc_start_slot(ADC_SLOT_CAL_RPM);
        break;

    default:
        cal_rpm = sample;
        adc_start_slot(ADC_SLOT_CH1);
        break;
    }
}

/* TCB0 periodic interrupt at 0xFFFF extends the 16-bit counter to 32 bits. */
ISR(TCB0_INT_vect)
{
    tcb_epoch++;
    TCB0.INTFLAGS = TCB_CAPT_bm;
}

static uint32_t timer_ticks_now_isr(void)
{
    uint16_t hi1 = tcb_epoch;
    uint16_t lo = TCB0.CNT;
    uint8_t pending = TCB0.INTFLAGS & TCB_CAPT_bm;
    uint16_t hi2 = tcb_epoch;

    if (hi2 != hi1) {
        hi1 = hi2;
        lo = TCB0.CNT;
    } else if (pending && lo < 0x8000U) {
        hi1++;
    }
    return ((uint32_t)hi1 << 16) | lo;
}

static uint32_t timer_ticks_now(void)
{
    uint32_t value;
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        value = timer_ticks_now_isr();
    }
    return value;
}

ISR(PORTB_PORT_vect)
{
    uint8_t flags = PORTB.INTFLAGS;
    PORTB.INTFLAGS = flags;

    if (flags & PIN4_bm) {
        uint32_t now = timer_ticks_now_isr();
        uint32_t dt = now - rpm_last_edge_ticks;
        uint32_t min_ticks = (TIMER_TICK_HZ * 60UL) /
                             (RPM_MAX_VALID * PULSES_PER_REV);
        rpm_last_edge_ticks = now;

        /* Reject implausibly short spikes; low-speed timeout is handled in main. */
        if (dt >= min_ticks) {
            if (!rpm_period_valid) {
                rpm_period_ticks = dt;
                rpm_period_valid = true;
            } else {
                /* 1/4 IIR period filter gives useful jitter rejection. */
                int32_t error = (int32_t)(dt - rpm_period_ticks);
                rpm_period_ticks = (uint32_t)((int32_t)rpm_period_ticks + (error >> 2));
            }
        }
    }
}

static void clock_init(void)
{
    /* Fuse must select the 20 MHz internal oscillator. Disable main prescaler. */
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0);
}

static void gpio_init(void)
{
    /* Safe startup: force PWM pin low before enabling TCA0 ownership. */
    PORTB.OUTCLR = PIN0_bm;
    PORTB.DIRSET = PIN0_bm;

    /* External 10 k pull-ups are fitted on active-low selects. */
    PORTB.DIRCLR = PIN2_bm | PIN3_bm | PIN4_bm;
    PORTB.PIN2CTRL = PORT_ISC_INTDISABLE_gc;
    PORTB.PIN3CTRL = PORT_ISC_INTDISABLE_gc;
    PORTB.PIN4CTRL = PORT_ISC_RISING_gc;
    PORTB.INTFLAGS = PIN4_bm;

    /* Analog inputs: input buffers disabled for lower noise/power. */
    PORTA.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc;
    PORTA.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc;
    PORTA.PIN6CTRL = PORT_ISC_INPUT_DISABLE_gc;
    PORTA.PIN7CTRL = PORT_ISC_INPUT_DISABLE_gc;
    PORTB.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc;
}

static void pwm_init(void)
{
    /* TCA00=0 selects the default WO0 location, PB0 on this package. */
    PORTMUX.CTRLC &= (uint8_t)~PORTMUX_TCA00_bm;

    TCA0.SINGLE.CTRLA = 0;
    TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm |
                        TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
    TCA0.SINGLE.PER = PWM_PER;
    TCA0.SINGLE.CMP0 = 0;
    TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc |
                        TCA_SINGLE_ENABLE_bm;
}

static void pwm_set_u16(uint16_t duty)
{
    uint32_t compare = ((uint32_t)duty * (PWM_PER + 1UL)) >> 16;
    if (compare > PWM_PER) compare = PWM_PER;
    TCA0.SINGLE.CMP0BUF = (uint16_t)compare;
}

static void timebase_init(void)
{
    TCB0.CTRLA = 0;
    TCB0.CTRLB = TCB_CNTMODE_INT_gc;
    TCB0.CCMP = 0xFFFFU;
    TCB0.INTFLAGS = TCB_CAPT_bm;
    TCB0.INTCTRL = TCB_CAPT_bm;
    TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc | TCB_ENABLE_bm;
}

static void adc_init(void)
{
    ADC0.CTRLA = 0;
    ADC0.CTRLC = ADC_PRESC_DIV16_gc | ADC_REFSEL_VDDREF_gc;
    ADC0.SAMPCTRL = 1; /* Adequate with the schematic's 4.7k/1nF ADC networks. */
    ADC0.INTFLAGS = ADC_RESRDY_bm;
    ADC0.INTCTRL = ADC_RESRDY_bm;
    ADC0.CTRLA = ADC_ENABLE_bm;

    /* Initialize baselines to midscale so startup does not create a false hit. */
    ch[0].baseline_q8 = (512L << 8);
    ch[1].baseline_q8 = (512L << 8);
    adc_start_slot(ADC_SLOT_CH1);
}

static uint32_t rpm_read(void)
{
    uint32_t period;
    uint32_t last;
    bool valid;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        period = rpm_period_ticks;
        last = rpm_last_edge_ticks;
        valid = rpm_period_valid;
    }

    if (!valid || period == 0U) return 0U;

    uint32_t age = timer_ticks_now() - last;
    uint32_t timeout_ticks = (TIMER_TICK_HZ / 1000UL) * RPM_TIMEOUT_MS;
    if (age > timeout_ticks) {
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { rpm_period_valid = false; }
        return 0U;
    }

    return (TIMER_TICK_HZ * 60UL) / (period * PULSES_PER_REV);
}

static uint16_t severity_to_pwm(void)
{
    uint32_t env1, env2;
    uint16_t trim1, trim2, trim_rpm, rail1, rail2;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        env1 = ch[0].envelope_q8 >> 8;
        env2 = ch[1].envelope_q8 >> 8;
        trim1 = cal_ch1;
        trim2 = cal_ch2;
        trim_rpm = cal_rpm;
        rail1 = ch[0].rail_count;
        rail2 = ch[1].rail_count;
    }

    bool enable1 = (PORTB.IN & PIN3_bm) == 0; /* PB3 SEL_CH1 */
    bool enable2 = (PORTB.IN & PIN2_bm) == 0; /* PB2 SEL_CH2 */

    if (!enable1 && !enable2) return 0U; /* No channel selected: quiet output. */
    if ((enable1 && rail1 >= ADC_RAIL_FAULT_SAMPLES) ||
        (enable2 && rail2 >= ADC_RAIL_FAULT_SAMPLES)) {
        return 0U; /* Persistent rail fault: fail to the safe 0.5016 V output. */
    }

    /* Each channel trim gives 0.5x..1.5x digital sensitivity. */
    uint32_t scaled1 = (env1 * (512UL + trim1)) >> 10;
    uint32_t scaled2 = (env2 * (512UL + trim2)) >> 10;
    uint32_t magnitude;

    if (enable1 && enable2) magnitude = (scaled1 > scaled2) ? scaled1 : scaled2;
    else magnitude = enable1 ? scaled1 : scaled2;

    uint32_t rpm = rpm_read();
    if (rpm > RPM_FULL_SCALE) rpm = RPM_FULL_SCALE;

    /* RV3 controls how strongly the noise floor rises with RPM (0..100%). */
    uint32_t rpm_floor = (rpm * RPM_NOISE_COUNTS_AT_MAX * trim_rpm) /
                         (RPM_FULL_SCALE * 1023UL);
    uint32_t floor = BASE_NOISE_COUNTS + rpm_floor;

    if (magnitude <= floor) return 0U;
    magnitude -= floor;

    uint32_t span = (FULL_SCALE_MAG_COUNTS > floor) ?
                    (FULL_SCALE_MAG_COUNTS - floor) : 1UL;
    if (magnitude >= span) return 65535U;
    return (uint16_t)((magnitude * 65535UL) / span);
}

int main(void)
{
    cli();
    clock_init();
    gpio_init();
    pwm_init();
    timebase_init();
    adc_init();

    /* 1 s watchdog: any main-loop lockup resets the MCU; R32 holds PWM low. */
    wdt_enable(WDTO_1S);
    sei();

    for (;;) {
        uint16_t duty = severity_to_pwm();
        pwm_set_u16(duty);
        wdt_reset();
    }
}
