wokwi / avr8js

Arduino (8-bit AVR) simulator, written in JavaScript and runs in the browser / Node.js
https://blog.wokwi.com/avr8js-simulate-arduino-in-javascript/
MIT License
481 stars 78 forks source link

Implement ADC (Analog to Digital Conversion) #13

Closed urish closed 3 years ago

urish commented 4 years ago

Register description can be found in page 257 / section 24.9 of the datasheet.

Some very quick-and-dirty PoC can be found here (basically, it always returns some random value when reading from the ADC):

AVR8JS Simon Game Demo

(look at the constructor of the AVRRunner class, in execute.ts).

MarkMCode commented 4 years ago

I found that the analog pin specified by analogRead() can be found by reading the ADMUX register (0x7c). The following code should find the pin number of the analog pin being read on port C (based on execute.ts from your Simon Says example):

this.cpu.writeHooks[0x7a] = value => {
      if (value & (1 << 6)) {
        this.cpu.data[0x7a] = value & ~(1 << 6); // clear bit - conversion done
        const ADMUXval = this.cpu.data[0x7c];   //Value held in ADMUX selection register
    const analogPin = ADMUXval & 15;    //Apply mask to clear first 4 bits as only latter half is important for selection
        return true; // don't update
      }
    };

If every pin on port C had an "analogValue" variable, the above code could then check the necessary pin for its analog value and write that to the ADCL and ADCH registers as you have done in the Simon Says example. This could look something like:

this.cpu.writeHooks[0x7a] = value => {
      if (value & (1 << 6)) {
        this.cpu.data[0x7a] = value & ~(1 << 6); // clear bit - conversion done
        const ADMUXval = this.cpu.data[0x7c];   //Value held in ADMUX selection register
    const analogPin = ADMUXval & 15;    //Apply mask to clear first 4 bits as only latter half is important for selection
        const analogValue = eval("runner.portC.analogValue("+analogPin +");");
        this.cpu.data[0x78] = analogValue & 0xff;
        this.cpu.data[0x79] = (analogValue >> 8) & 0x3;
        return true; // don't update
      }
    };

(Excuse the use of eval(), I'm not sure of a better way of implementing that line right now) The above code is just an idea I haven't had a chance to test yet, but I hope it helps!

MarkMCode commented 4 years ago

My current implementation is as follows:

this.cpu.writeHooks[0x7a] = value => {
      if (value & (1 << 6)) {
                this.cpu.data[0x7a] = value & ~(1 << 6); // clear bit - conversion done
        const ADMUXval = this.cpu.data[0x7c];   //Value held in ADMUX selection register
        const analogPin = ADMUXval & 15;    //Apply mask to clear first 4 bits as only latter half is important for selection
        globalThis.Runner.portC.setAnalogValue(globalThis.analogArray[analogPin]);
                return true; // don't update
      }
    };

In which globalThis.analogArray contains the ADC values for each pin, which are calculated in a circuit simulation running in the same window. Runner.portC.setAnalogValue is defined in gpio.ts as:

setAnalogValue(analogValue: number){
     //Write analogValue to ADCH and ADCL
        this.cpu.data[0x78] = analogValue & 0xff;
        this.cpu.data[0x79] = (analogValue >> 8) & 0x3;
  }

This maybe isn't optimal as it uses a lot of global variables, but I'll sort that at some point!

urish commented 3 years ago

The ADC peripheral as now included in AVR8js 0.18.0