aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals/adc.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/peripherals/adc.ts')
-rw-r--r--src/peripherals/adc.ts258
1 files changed, 258 insertions, 0 deletions
diff --git a/src/peripherals/adc.ts b/src/peripherals/adc.ts
new file mode 100644
index 0000000..1e0e2d1
--- /dev/null
+++ b/src/peripherals/adc.ts
@@ -0,0 +1,258 @@
+/**
+ * AVR-8 ADC
+ * Part of AVR8js
+ * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf
+ *
+ * Copyright (C) 2019, 2020, 2021 Uri Shaked
+ */
+
+import { AVRInterruptConfig, CPU } from '../cpu/cpu';
+import { u8 } from '../types';
+
+export enum ADCReference {
+ AVCC,
+ AREF,
+ Internal1V1,
+ Internal2V56,
+ Reserved,
+}
+
+export enum ADCMuxInputType {
+ SingleEnded,
+ Differential,
+ Constant,
+ Temperature,
+}
+
+export type ADCMuxInput =
+ | { type: ADCMuxInputType.Temperature }
+ | { type: ADCMuxInputType.Constant; voltage: number }
+ | { type: ADCMuxInputType.SingleEnded; channel: number }
+ | {
+ type: ADCMuxInputType.Differential;
+ positiveChannel: number;
+ negativeChannel: number;
+ gain: number;
+ };
+
+export type ADCMuxConfiguration = { [key: number]: ADCMuxInput };
+
+export interface ADCConfig {
+ ADMUX: u8;
+ ADCSRA: u8;
+ ADCSRB: u8;
+ ADCL: u8;
+ ADCH: u8;
+ DIDR0: u8;
+ adcInterrupt: u8;
+ numChannels: u8;
+ muxInputMask: u8;
+ muxChannels: ADCMuxConfiguration;
+ adcReferences: ADCReference[];
+}
+
+export const atmega328Channels: ADCMuxConfiguration = {
+ 0: { type: ADCMuxInputType.SingleEnded, channel: 0 },
+ 1: { type: ADCMuxInputType.SingleEnded, channel: 1 },
+ 2: { type: ADCMuxInputType.SingleEnded, channel: 2 },
+ 3: { type: ADCMuxInputType.SingleEnded, channel: 3 },
+ 4: { type: ADCMuxInputType.SingleEnded, channel: 4 },
+ 5: { type: ADCMuxInputType.SingleEnded, channel: 5 },
+ 6: { type: ADCMuxInputType.SingleEnded, channel: 6 },
+ 7: { type: ADCMuxInputType.SingleEnded, channel: 7 },
+ 8: { type: ADCMuxInputType.Temperature },
+ 14: { type: ADCMuxInputType.Constant, voltage: 1.1 },
+ 15: { type: ADCMuxInputType.Constant, voltage: 0 },
+};
+
+const fallbackMuxInput = {
+ type: ADCMuxInputType.Constant,
+ voltage: 0,
+};
+
+export const adcConfig: ADCConfig = {
+ ADMUX: 0x7c,
+ ADCSRA: 0x7a,
+ ADCSRB: 0x7b,
+ ADCL: 0x78,
+ ADCH: 0x79,
+ DIDR0: 0x7e,
+ adcInterrupt: 0x2a,
+ numChannels: 8,
+ muxInputMask: 0xf,
+ muxChannels: atmega328Channels,
+ adcReferences: [
+ ADCReference.AREF,
+ ADCReference.AVCC,
+ ADCReference.Reserved,
+ ADCReference.Internal1V1,
+ ],
+};
+
+// Register bits:
+const ADPS_MASK = 0x7;
+const ADIE = 0x8;
+const ADIF = 0x10;
+const ADSC = 0x40;
+const ADEN = 0x80;
+
+const MUX_MASK = 0x1f;
+const ADLAR = 0x20;
+const MUX5 = 0x8;
+const REFS2 = 0x8;
+const REFS_MASK = 0x3;
+const REFS_SHIFT = 6;
+
+export class AVRADC {
+ /**
+ * ADC Channel values, in voltage (0..5). The number of channels depends on the chip.
+ *
+ * Changing the values here will change the ADC reading, unless you override onADCRead() with a custom implementation.
+ */
+ readonly channelValues = new Array(this.config.numChannels);
+
+ /** AVCC Reference voltage */
+ avcc = 5;
+
+ /** AREF Reference voltage */
+ aref = 5;
+
+ /**
+ * Invoked whenever the code performs an ADC read.
+ *
+ * The default implementation reads the result from the `channelValues` array, and then calls
+ * `completeADCRead()` after `sampleCycles` CPU cycles.
+ *
+ * If you override the default implementation, make sure to call `completeADCRead()` after
+ * `sampleCycles` cycles (or else the ADC read will never complete).
+ */
+ onADCRead: (input: ADCMuxInput) => void = (input) => {
+ // Default implementation
+ let voltage = 0;
+ switch (input.type) {
+ case ADCMuxInputType.Constant:
+ voltage = input.voltage;
+ break;
+ case ADCMuxInputType.SingleEnded:
+ voltage = this.channelValues[input.channel] ?? 0;
+ break;
+ case ADCMuxInputType.Differential:
+ voltage =
+ input.gain *
+ ((this.channelValues[input.positiveChannel] || 0) -
+ (this.channelValues[input.negativeChannel] || 0));
+ break;
+ case ADCMuxInputType.Temperature:
+ voltage = 0.378125; // 25 celcius
+ break;
+ }
+ const rawValue = (voltage / this.referenceVoltage) * 1024;
+ const result = Math.min(Math.max(Math.floor(rawValue), 0), 1023);
+ this.cpu.addClockEvent(() => this.completeADCRead(result), this.sampleCycles);
+ };
+
+ private converting = false;
+ private conversionCycles = 25;
+
+ // Interrupts
+ private ADC: AVRInterruptConfig = {
+ address: this.config.adcInterrupt,
+ flagRegister: this.config.ADCSRA,
+ flagMask: ADIF,
+ enableRegister: this.config.ADCSRA,
+ enableMask: ADIE,
+ };
+
+ constructor(private cpu: CPU, private config: ADCConfig) {
+ cpu.writeHooks[config.ADCSRA] = (value, oldValue) => {
+ if (value & ADEN && !(oldValue && ADEN)) {
+ this.conversionCycles = 25;
+ }
+ cpu.data[config.ADCSRA] = value;
+ cpu.updateInterruptEnable(this.ADC, value);
+ if (!this.converting && value & ADSC) {
+ if (!(value & ADEN)) {
+ // Special case: reading while the ADC is not enabled should return 0
+ this.cpu.addClockEvent(() => this.completeADCRead(0), this.sampleCycles);
+ return true;
+ }
+ let channel = this.cpu.data[this.config.ADMUX] & MUX_MASK;
+ if (cpu.data[config.ADCSRB] & MUX5) {
+ channel |= 0x20;
+ }
+ channel &= config.muxInputMask;
+ const muxInput = config.muxChannels[channel] ?? fallbackMuxInput;
+ this.converting = true;
+ this.onADCRead(muxInput);
+ return true; // don't update
+ }
+ };
+ }
+
+ completeADCRead(value: number) {
+ const { ADCL, ADCH, ADMUX, ADCSRA } = this.config;
+ this.converting = false;
+ this.conversionCycles = 13;
+ if (this.cpu.data[ADMUX] & ADLAR) {
+ this.cpu.data[ADCL] = (value << 6) & 0xff;
+ this.cpu.data[ADCH] = value >> 2;
+ } else {
+ this.cpu.data[ADCL] = value & 0xff;
+ this.cpu.data[ADCH] = (value >> 8) & 0x3;
+ }
+ this.cpu.data[ADCSRA] &= ~ADSC;
+ this.cpu.setInterruptFlag(this.ADC);
+ }
+
+ get prescaler() {
+ const { ADCSRA } = this.config;
+ const adcsra = this.cpu.data[ADCSRA];
+ const adps = adcsra & ADPS_MASK;
+ switch (adps) {
+ case 0:
+ case 1:
+ return 2;
+ case 2:
+ return 4;
+ case 3:
+ return 8;
+ case 4:
+ return 16;
+ case 5:
+ return 32;
+ case 6:
+ return 64;
+ case 7:
+ default:
+ return 128;
+ }
+ }
+
+ get referenceVoltageType() {
+ const { ADMUX, adcReferences } = this.config;
+ let refs = (this.cpu.data[ADMUX] >> REFS_SHIFT) & REFS_MASK;
+ if (adcReferences.length > 4 && this.cpu.data[ADMUX] & REFS2) {
+ refs |= 0x4;
+ }
+ return adcReferences[refs] ?? ADCReference.Reserved;
+ }
+
+ get referenceVoltage() {
+ switch (this.referenceVoltageType) {
+ case ADCReference.AVCC:
+ return this.avcc;
+ case ADCReference.AREF:
+ return this.aref;
+ case ADCReference.Internal1V1:
+ return 1.1;
+ case ADCReference.Internal2V56:
+ return 2.56;
+ default:
+ return this.avcc;
+ }
+ }
+
+ get sampleCycles() {
+ return this.conversionCycles * this.prescaler;
+ }
+}