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
461 stars 73 forks source link

Bug in ATtiny85 (ADC in free running mode) #136

Open makkiato83 opened 1 year ago

makkiato83 commented 1 year ago

I think there is a bug in the way the simulator handles ADC in free running mode.

This is a mode, documented in the datasheet, in which the ADC keeps sampling the chosen input. In is configured using the ADCSRA and ADCSRB registers.

To reproduce the error I have prepared this sketch: https://wokwi.com/projects/353310932474416129

In the Wokwi simulation, there is only one message reaching the console.

In a physical Attiny85, the console keeps receiving messages, as it should.

BUG(?): It appears like the simulator somewhere does not properly re-start the ADC conversion. This bug might (not checked) affect not just the free-running-mode (ADCSRB =0x00) but other interrupt-driven ADC-conversions as well (other values of ADCSRB).

urish commented 1 year ago

From a quick glance at the ADC peripheral code, it seems like this functionality was never implemented. If you want to try to implement it and send a pull-request, that might work.

Otherwise, I can open it for voting, but I'm pretty sure it won't get much attention - the ADC has been out for more than a year, and no one every asked about the free running mode (until now, at least).

makkiato83 commented 1 year ago

Thanks for the quick reply!

No need to open it for voting, I am exploring the Attiny features purely for educational purposes and I don't need this feature. I reported this bug just to let you know.

Keep up the great work!

drf5n commented 1 year ago

From a quick glance at the ADC peripheral code, it seems like this functionality was never implemented. If you want to try to implement it and send a pull-request, that might work.

Otherwise, I can open it for voting, but I'm pretty sure it won't get much attention - the ADC has been out for more than a year, and no one every asked about the free running mode (until now, at least).

I think to make it work, one would need to add a bit to this routine to schedule another completeADCRead dependent on ADSCRB & bit(ADATE):

https://github.com/wokwi/avr8js/blob/320007df8d652755ecbf424fd0128c2ef98862de/src/peripherals/adc.ts#L192-L205

But it gets complicated depending on the particular free running mode per ADSCRB & (0b111 << ADTS0)

The plain ADSCRB::ADTS[0:2]=0b000 free running mode trigger source might be a straightforward scheduling of a call to onADCRead(???), but the others trigger sources would seem to need something like interrupts.

 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); 
   if(this.cpu.data[ADSCRA & bit(ADATE)){
     switch(ADSCRB & (0b111 <<ADTS0)){
        case 0b000:
          this.cpu.addClockEvent(() => this.cpu.onADCRead(ADMUX???), this.sampleCycles)
          break;
          // 0b001-0b111 not handled
        default: ;
     }
   }
 } 

Datasheet ADSCRB reference: https://onlinedocs.microchip.com/pr/GUID-0EC909F9-8FB7-46B2-BF4B-05290662B5C3-en-US-12.1.1/index.html

ArminJo commented 1 month ago

Here is another bug of ADC freerunning mode in 328P https://wokwi.com/projects/393410516428554241. It works, but it swaps channnels, even during the loop. Only the first loop seems to be correct.

uint16_t sArray1[16];
uint16_t sArray2[16];
int sLoopCounter;
void setup() {
  Serial.begin(115200);
}
void loop() {
  Serial.print(F("sLoopCounter="));
  Serial.println(sLoopCounter++);

  // MVCE for https://github.com/wokwi/avr8js/issues/136
  ADMUX = 0xC0; // select A0
  ADCSRA = ((1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADIF) | 5);
  for (unsigned int i = 0; i < 16; i++) {
    loop_until_bit_is_set(ADCSRA, ADIF);
    ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished with "loop_until_bit_is_set(ADCSRA, ADIF)".
    uint16_t tValue = ADCL | (ADCH << 8);
    sArray1[i] = tValue; // store value at current counter index
  }
  ADCSRA = ((1 << ADEN) | (1 << ADIF) | 5); // Disable auto-triggering (free running mode), but the last conversion is still running
  delay(1);
  ADMUX = 0xC1;// select A1
  ADCSRA = ((1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADIF) | 5);
  for (unsigned int i = 0; i < 16; i++) {
    loop_until_bit_is_set(ADCSRA, ADIF);
    ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished with "loop_until_bit_is_set(ADCSRA, ADIF)".
    uint16_t tValue = ADCL | (ADCH << 8);
    sArray2[i] = tValue; // store value at current counter index
  }
  ADCSRA = ((1 << ADEN) | (1 << ADIF) | 5); // Disable auto-triggering (free running mode), but the last conversion is still running

  Serial.println(F("Print i=<ValueA0>|<ValueA1>"));
  /*
     We expect i=<ValueA0>|<ValueA1>, but this is only true the first time after boot,
     Next loops we see i=<ValueA1>|<ValueA0>
     and after 12 to 13 samples i=<ValueA0>|<ValueA0> or i=<ValueA1>|<ValueA1>
  */
  for (unsigned int i = 0; i < 16; i++) {
    Serial.print(i);
    Serial.print('=');
    Serial.print(sArray1[i]);
    Serial.print('|');
    Serial.print(sArray2[i]);
    Serial.print(F(", "));
  }
  Serial.println();

  delay(2000);
}