Changing ADC channel

Using the ATtiny85
Arduino IDE 1.8.9 Nightly Build from 2018
"attiny" core by David A. Mellis

I am trying to read a single ended input off ADC0 (PB5) then switch to a differential input after that conversion is complete between ADC2(PB4) and ADC3(PB3).

When I run the differential comparison on its own, I get the readings I am looking for ("mA: 0.12").
When I run the single ended conversion on its own, I get what I am looking for ("V: 4.92").
But when I try to run the single ended conversion, wait for it to complete, write the data to the screen frame buffer, then perform a differential conversion, wait for it to complete, then write the data to the screen buffer, I get the correct single ended data and "0" for my differential conversion. I have been unable to read the differential channel data successfully after a single ended conversion.

I have read the datasheet several times and cannot find what I am doing wrong.

Any ideas?

EDIT: I have been setting my RSTDISBL fuse bit using a high voltage programmer.

Full code:

#include <Tiny4kOLED.h>

volatile int value;
volatile bool voltageFlag=false;
volatile int voltage;
volatile int amperage;

void setup() {
  DDRB = DDRB & B11000111;   //PB3:PB5 as input
  ADCSRA |= (1<<ADEN);      //ADC enable
//  ADMUX=ADMUX | B00000011;  //use ADC3 (PB3)
//  ADMUX=ADMUX & B11110000;  //use ADC0 (PB5)
  ADMUX |= (1<<REFS1);  //use 1.1v as reference
  ADMUX = ADMUX & B10101111; //  use 1.1 as reference
  ADCSRA |= (1<<ADIE);      //ADC interrupt enable
  sei();                    //global interrupt enable
  ADCSRA=ADCSRA | B00000111;//set ADC prescaler to 128 (125kHz)
  oled.begin();
  oled.clear();
  oled.on();
  oled.switchRenderFrame();
}

ISR(ADC_vect){
  value=ADC;
  if(voltageFlag==true){
    voltage=value;
  }
  else{amperage=value;}
}

void readVoltage(){
  ADMUX = ADMUX & B11110000;  //ADC0 single ended
  voltageFlag=true;
  ADCSRA|=(1<<ADSC);          //start ADC conversion
}

void readAmperage(){
  ADMUX = ADMUX | B00000111;  //ADC2 - ADC3 X20
  voltageFlag=false;
  ADCSRA|=(1<<ADSC);       //start ADC conversion 
}

void loop() {
  readVoltage();
  while(ADCSRA & (1<<ADSC)){ /*do nothing*/ }
  float voltageFloat = (float)voltage/1023.0*6.0;
  oled.clear();
  oled.setCursor(0,0);
  oled.setFont(FONT6X8);
  oled.print(F("V: "));
  oled.setCursor(11,0);
  oled.print(voltageFloat);
  readAmperage();
  while(ADCSRA & (1<<ADSC)){ /*do nothing*/ }
  float amperageFloat = (float)amperage/1023.0/0.92;
  oled.setCursor(0,1);
  oled.print(F("mA: "));
  oled.setCursor(17,1);
  oled.print(amperageFloat);
  oled.setCursor(0,2);
  oled.print(F("mW: "));
  oled.switchFrame();  //displays frame buffer
  delay(10);
}

Your coding is awesome. I finally found what I need. :frowning:

joshuajayg:

void readVoltage(){

ADMUX = ADMUX & B11110000;  //ADC0 single ended
 voltageFlag=true;
 ADCSRA|=(1<<ADSC);          //start ADC conversion
}

void readAmperage(){
 ADMUX = ADMUX | B00000111;  //ADC2 - ADC3 X20
 voltageFlag=false;
 ADCSRA|=(1<<ADSC);       //start ADC conversion
}

The way the ADMUX is set just doesn't feel right.

It's clearer if you use the bit names here for starters: which bits you want set and which not?

wvmarle:
The way the ADMUX is set just doesn't feel right.

It's clearer if you use the bit names here for starters: which bits you want set and which not?

The four LSB of ADMUX are the ADC multiplexer bits. I set four at a time because it is shorter and I know where they are set from the last time. I've attached the register descriptions for the ADMUX register and MUX3:0 bits. Are you saying I ought to write it:

ADMUX |= (1<<MUX0) | (1<<MUX1) | (1<<MUX2);

rather than

ADMUX = ADMUX | B00000111;
//same as
ADMUX |= B00000111;

Well, that's a whole lot readable already. What also helps is adding some comments: what do the specific bits do (saves looking it up again and again in the spec sheet) and why you set/clear specific ones.

wvmarle:
Well, that's a whole lot readable already. What also helps is adding some comments: what do the specific bits do (saves looking it up again and again in the spec sheet) and why you set/clear specific ones.

Updated. Still same results. Voltage is displayed correctly, amperage is always 0. I suspect it is a timing issue. I have the while loop in there to wait until the conversion is finished and the bit is written low by hardware but maybe it is not working as planned.

#include <Tiny4kOLED.h>

volatile int value;
volatile bool voltageFlag=false;
volatile int voltage;
volatile int amperage;

void setup() {
  DDRB = DDRB & B11000111;   //PB3:PB5 as input
  ADCSRA |= (1<<ADEN);      //set ADEN to enable ADC
  ADMUX |= (1<<REFS1);  //set REFS1 for 1.1v ADC reference
  ADMUX &= (~(1<<REFS0)) & (~(1<<REFS2)); //clear REFS0 and REFS2 for 1.1v ADC reference
  ADCSRA |= (1<<ADIE);      //set ADIE for ADC interrupt enable
  sei();                    //global interrupt enable
  ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2);//set ADPS0:2 for ADC prescaler to 128 (125kHz)
  oled.begin();
  oled.clear();
  oled.on();
  oled.switchRenderFrame();
}

ISR(ADC_vect){
  value=ADC;
  if(voltageFlag==true){
    voltage=value;
  }
  else{amperage=value;}
}

void readVoltage(){
  ADMUX &= (~(1<<MUX0)) & (~(1<<MUX1)) & (~(1<<MUX2)) & (~(1<<MUX3));  //Clear MUX3:0 to use ADC0(PB5) single ended input
  voltageFlag=true;
  ADCSRA|=(1<<ADSC);          //set ADSC to start ADC conversion
}

void readAmperage(){
  ADMUX |= (1<<MUX0) | (1<<MUX1) | (1<<MUX2);  //Set MUX0:2 to use ADC2-ADC3 X20 gain differential mode
  voltageFlag=false;
  ADCSRA|=(1<<ADSC);       //set ADSC to start ADC conversion 
}

void loop() {
  readVoltage();
  while(ADCSRA & (1<<ADSC)){ /*do nothing*/ }  //check to see if conversion is finished using the start conversion bit (bit is cleared when conversion finishes).
  float voltageFloat = (float)voltage/1023.0*6.0;
  oled.clear();
  oled.setCursor(0,0);
  oled.setFont(FONT6X8);
  oled.print(F("V: "));
  oled.setCursor(11,0);
  oled.print(voltageFloat);
  readAmperage();
  while(ADCSRA & (1<<ADSC)){ /*do nothing*/ }  //check to see if conversion is finished using the start conversion bit (bit is cleared when conversion finishes).
  float amperageFloat = (float)amperage/1023.0/0.92;
  oled.setCursor(0,1);
  oled.print(F("mA: "));
  oled.setCursor(17,1);
  oled.print(amperageFloat);
  oled.setCursor(0,2);
  oled.print(F("mW: "));
  oled.switchFrame();  //displays frame buffer
  delay(10);
}

Much more readable this way, much easier to refer back to the datasheet. Order of instructions appears correct to me. There appears to be a problem with the switch between the two modes but I don't see anything in the datasheet related to this - other than when to update the ADMUX and related registers. That looks all correct.

The interrupt seems redundant to me. Can just as well read the ADC value directly in loop(), after all you're waiting for the conversion to complete already.

Two things to try:

  • read amperage first, then voltage. If the problem is the switch between the two modes I expect you get to see amps, no voltage.
  • read amperage twice - ignoring the result of the first conversion. Maybe it needs to go through a conversion to settle?

The interrupt was originally there for reading a single channel during the learning process. It is redundant now and I have removed it.

Switching the order of ADC samples produced the same results. However...

Sampling the amperage twice did produce a correct reading. So then I tried sampling once with a delay introduced after changing to the differential channel. A delay of 25 microseconds gave me inconsistent readings between 0 and the anticipated reading of about 0.1. Once I upped the delay to 50 microseconds, I got consistent readings. With my frequency of 16mHz and an ADC prescaler of 128, that would be over 3 ADC clock cycles. Why do I need 3 ADC clock cycles to get a channel change? I know if you change voltage reference you should wait 1 millisecond before sampling but I don't know why and don't remember reading that in the datasheet. Any idea as to why?

Thank you for your help, wvmarle. This is the first time I have ever asked for programming help.

What I now have:

#include <Tiny4kOLED.h>

void setup() {
  DDRB = DDRB & B11000111;   //PB3:PB5 as input
  ADCSRA |= (1<<ADEN);      //set ADEN to enable ADC
  ADMUX |= (1<<REFS1);  //set REFS1 for 1.1v ADC reference
  ADMUX &= (~(1<<REFS0)) & (~(1<<REFS2)); //clear REFS0 and REFS2 for 1.1v ADC reference
  ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2);//set ADPS0:2 for ADC prescaler to 128 (125kHz)
  oled.begin();
  oled.clear();
  oled.on();
  oled.switchRenderFrame();
}


void readVoltage(){
  ADMUX &= (~(1<<MUX0)) & (~(1<<MUX1)) & (~(1<<MUX2)) & (~(1<<MUX3));  //Clear MUX3:0 to use ADC0(PB5) single ended input
  ADCSRA|=(1<<ADSC);          //set ADSC to start ADC conversion
}

void readAmperage(){
  ADMUX |= (1<<MUX0) | (1<<MUX1) | (1<<MUX2);  //Set MUX0:2 to use ADC2-ADC3 X20 gain differential mode
  delayMicroseconds(50);
  ADCSRA|=(1<<ADSC);       //set ADSC to start ADC conversion 
}

void loop() {
  readAmperage();
  while(ADCSRA & (1<<ADSC)){ /*do nothing*/ }  //check to see if conversion is finished using the start conversion bit (bit is cleared when conversion finishes).
  float amperageFloat = (float)ADC/1023.0/0.92;
  oled.clear();
  oled.setFont(FONT6X8);
  oled.setCursor(0,1);
  oled.print(F("mA: "));
  oled.setCursor(17,1);
  oled.print(amperageFloat);
  oled.setCursor(0,2);
  oled.print(F("mW: "));
  readVoltage();
  while(ADCSRA & (1<<ADSC)){ /*do nothing*/ }  //check to see if conversion is finished using the start conversion bit (bit is cleared when conversion finishes).
  float voltageFloat = (float)ADC/1023.0*6.0;
  oled.setCursor(0,0);
  oled.print(F("V: "));
  oled.setCursor(11,0);
  oled.print(voltageFloat);
  oled.switchFrame();  //displays frame buffer
  delay(10);
}

No mention of either in the data sheet.

The reference voltage probably needs time to settle - the datasheet mentions the ADC does use sample-and-hold circuit circuits at least for the inputs so I suppose also for the reference voltage, and it mentions the sample hold is done three ADC clock cycles into the conversion. Changes to the ADC registers come in force the moment a new conversion starts, no mention about the need for a wait after changing the settings.