summaryrefslogtreecommitdiff
path: root/src/peripherals/avrdx-adc.ts
blob: dc95d86a36efa233a93b7a2f01333b6dcf49d9ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// AVR-Dx ADC0 peripheral
// 12-bit ADC with accumulation, free-running mode, and multiple input sources.

import type { CPU, 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();
    }
  }
}