From 1b194ac4578dea8e71b0d61d1cb4d875f435ba71 Mon Sep 17 00:00:00 2001 From: Apexo Date: Sat, 28 Mar 2026 23:40:53 +0100 Subject: D3AA simulator --- src/peripherals/avrdx-adc.ts | 188 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 src/peripherals/avrdx-adc.ts (limited to 'src/peripherals/avrdx-adc.ts') diff --git a/src/peripherals/avrdx-adc.ts b/src/peripherals/avrdx-adc.ts new file mode 100644 index 0000000..34a6f24 --- /dev/null +++ b/src/peripherals/avrdx-adc.ts @@ -0,0 +1,188 @@ +// AVR-Dx ADC0 peripheral +// 12-bit ADC with accumulation, free-running mode, and multiple input sources. + +import { type CPU, type AVRInterruptConfig } from 'avr8js/cpu/cpu'; +import { type AVRDxVREF } from './avrdx-vref'; + +const CTRLA = 0x0000; +const CTRLB = 0x0001; +const CTRLC = 0x0002; +// const CTRLD = 0x0003; +// const CTRLE = 0x0004; +// const SAMPCTRL = 0x0006; +const MUXPOS = 0x0008; +// const MUXNEG = 0x0009; +const COMMAND = 0x000A; +// const EVCTRL = 0x000B; +const INTCTRL = 0x000C; +const INTFLAGS = 0x000D; +// const DBGCTRL = 0x000E; +// const TEMP = 0x000F; +const RESL = 0x0010; +const RESH = 0x0011; + +// CTRLA bits +const ADC_ENABLE_bm = 0x01; +const ADC_FREERUN_bm = 0x02; +// const ADC_RESSEL_12BIT_gc = 0x00; +// const ADC_RESSEL_10BIT_gc = 0x04; +// const ADC_LEFTADJ_bm = 0x10; +// const ADC_CONVMODE_SINGLEENDED_gc = 0x00; +// const ADC_RUNSTBY_bm = 0x80; + +// CTRLB accumulation +// const ADC_SAMPNUM_NONE_gc = 0x00; +// const ADC_SAMPNUM_ACC2_gc = 0x01; +// const ADC_SAMPNUM_ACC4_gc = 0x02; +// const ADC_SAMPNUM_ACC8_gc = 0x03; +// const ADC_SAMPNUM_ACC16_gc = 0x04; +// const ADC_SAMPNUM_ACC32_gc = 0x05; +// const ADC_SAMPNUM_ACC64_gc = 0x06; + +// ADC0.COMMAND +const ADC_STCONV_bm = 0x01; + +// ADC0.INTCTRL / INTFLAGS +const ADC_RESRDY_bm = 0x01; + +// ADC MUXPOS special values +// const ADC_MUXPOS_AIN25_gc = 0x19; // PA5 battery voltage divider +// const ADC_MUXPOS_GND_gc = 0x40; // Ground +const ADC_MUXPOS_TEMPSENSE_gc = 0x42; // internal temperature sensor +// const ADC_MUXPOS_VDDDIV10_gc = 0x44; // VDD/10 +// const ADC_MUXPOS_VDDIO2DIV10_gc = 0x45; // VDDIO2/10 + +// ADC prescaler (CTRLC) +// const ADC_PRESC_DIV2_gc = 0x00; +// const ADC_PRESC_DIV4_gc = 0x01; +// const ADC_PRESC_DIV8_gc = 0x02; +// const ADC_PRESC_DIV16_gc = 0x03; +// const ADC_PRESC_DIV32_gc = 0x04; +// const ADC_PRESC_DIV64_gc = 0x05; +// const ADC_PRESC_DIV128_gc = 0x06; +// const ADC_PRESC_DIV256_gc = 0x07; + + +export class AVRDxADC { + /** Voltage at the ADC pin in volts (after external voltage divider) */ + private voltagePinV = 0; + /** Temperature: pre-computed raw accumulated ADC result (from SIGROW calibration) */ + private temperatureInput = 0; + private conversionCallback: (() => void) | null = null; + + private readonly resrdyIrq: AVRInterruptConfig; + + constructor(private cpu: CPU, private base: number, resrdyIrqNo: number, private vref: AVRDxVREF) { + this.resrdyIrq = { + address: resrdyIrqNo * 2, // vector 26, word addr 0x34 + flagRegister: base + INTFLAGS, + flagMask: ADC_RESRDY_bm, + enableRegister: base + INTCTRL, + enableMask: ADC_RESRDY_bm, + } as const; + + // COMMAND register - writing STCONV starts a conversion + cpu.writeHooks[base + COMMAND] = (value) => { + cpu.data[base + COMMAND] = value; + if (value & ADC_STCONV_bm) { + this.startConversion(); + } + return true; + }; + + // INTCTRL + cpu.writeHooks[base + INTCTRL] = (value) => { + cpu.data[base + INTCTRL] = value; + if (value & ADC_RESRDY_bm) { + cpu.updateInterruptEnable(this.resrdyIrq, value); + } + return true; + }; + + // INTFLAGS - write 1 to clear + cpu.writeHooks[base + INTFLAGS] = (value) => { + cpu.data[base + INTFLAGS] &= ~value; + if (value & ADC_RESRDY_bm) { + cpu.clearInterrupt(this.resrdyIrq); + } + return true; + }; + + // RES registers - read only (but firmware can read them) + cpu.writeHooks[base + RESL] = () => true; // ignore writes + cpu.writeHooks[base + RESH] = () => true; + } + + /** Set the voltage at the ADC pin (volts, after external divider). + * The ADC result is computed at conversion time from this voltage, + * the current VREF selection, and the accumulation count. */ + setVoltagePinV(volts: number) { + this.voltagePinV = volts; + } + + /** Set the raw ADC result for temperature (computed by runner with SIGROW values) */ + setRawTemperatureResult(raw16: number) { + this.temperatureInput = raw16; + } + + private startConversion() { + const ctrla = this.cpu.data[this.base + CTRLA]; + if (!(ctrla & ADC_ENABLE_bm)) return; + + // Compute approximate conversion time + // Prescaler from CTRLC + const prescDiv = [2, 4, 8, 16, 32, 64, 128, 256][this.cpu.data[this.base + CTRLC] & 0x07]; + // Number of accumulated samples + const sampNum = this.cpu.data[this.base + CTRLB] & 0x07; + const numSamples = sampNum === 0 ? 1 : (1 << sampNum); // 1, 2, 4, 8, 16, 32, 64 + // Each conversion ~13 ADC clock cycles (plus init delay for first) + const adcCycles = 15 * numSamples; + const cpuCycles = adcCycles * prescDiv; + + // Schedule completion + if (this.conversionCallback) { + this.cpu.clearClockEvent(this.conversionCallback); + } + // TODO: do ADC CPU cycles depend on clock scaling? + this.conversionCallback = this.cpu.addClockEvent(() => this.completeConversion(), cpuCycles); + } + + private completeConversion() { + this.conversionCallback = null; + + const muxpos = this.cpu.data[this.base + MUXPOS]; + let result: number; + + if (muxpos === ADC_MUXPOS_TEMPSENSE_gc) { + // Temperature: use pre-computed accumulated result from SIGROW calibration + result = this.temperatureInput; + } else { + // External pin (voltage divider on AIN25, etc.): + // Compute ADC result from physical pin voltage, current VREF, and accumulation + const vref = this.vref.adcRefVolts; + const sampNum = this.cpu.data[this.base + CTRLB] & 0x07; + const numSamples = sampNum === 0 ? 1 : (1 << sampNum); + const single = Math.min(4095, Math.max(0, Math.round(this.voltagePinV / vref * 4096))); + result = single * numSamples; + } + + // Clamp to 16-bit + result = Math.max(0, Math.min(0xFFFF, Math.round(result))); + + // Write result + this.cpu.data[this.base + RESL] = result & 0xFF; + this.cpu.data[this.base + RESH] = (result >> 8) & 0xFF; + + // Clear STCONV + this.cpu.data[this.base + COMMAND] &= ~ADC_STCONV_bm; + + // Set RESRDY flag and fire interrupt + this.cpu.setInterruptFlag(this.resrdyIrq); + + // Free-running: start another conversion + const ctrla = this.cpu.data[this.base + CTRLA]; + if (ctrla & ADC_FREERUN_bm) { + this.startConversion(); + } + } +} -- cgit v1.2.3