Trying to get 10 bit value from two 8 bit registers (solved)

Hi all

I am trying to program the ATtiny 85 in the Arduino ide, and practice manually doing things through the registers.

The purpose of the code is to read the adc value, convert it to voltage and then if it is below a threshold, turn on an LED. The code works fine if I just read the lower 8 bits of left shifted adc data. When I change it to read both registers and try to combine the data, the LED just stays lit.

I can't see anything that would be wrong with the code so i'm asking to see if there is something i'm missing.

//Red LED on PB0
//Blue LED on PB1
//Pushbutton PB2
//Battery voltage divider PB3
//Mosfet control PB4


#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>

int mode = 0;

void setup() {

  delay(10000);

  DDRB = 0b0010011; //set PB0,PB1,PB4 as output, PB2,PB3 as input
  PORTB = 0b00000011; //set PB0,PB1 HIGH so LEDs are off, other pins LOW

  //ADMUX |= 0b00100011; //configure the ADC to be on PB3, and left shift for easy read at 8 bit resolution (this config works fine)
  ADMUX |= 0b00000011; //configure the ADC to be on PB3
  ADCSRA |= 0b00000110; //set clock division to 64
}

void loop() {
  
  /*Lipo battery voltage measurement                
  batt voltage = (voltage divider voltage * (R1 + R2)) / R2
  for 0v to 5v range, 10 bit resolution, gives 4.89mV/bit
  R1 = 3.270k R2 = 1.975k*/
  
  ADCSRA |= 0b11000000; //Enable the ADC and start measurement
  delay(20);  //wait for completion
  uint8_t analogVL = ADCL; //Read lower portion of ADC result (ADCL must be read first)
  uint8_t analogVH =  ADCH; //Read upper portion of ADC result 
  uint16_t analogV = analogVL | (analogVH << 8);  //combine values together. Shift upper portion left by 8, then add to lower portion
  float battVoltage = (((analogVL * 0.00489) * 5245) / 1975); //calc lipo battery voltage, scaled for 10 bit value
  
  //float battVoltage = (((analogVH * 0.0196) * 5245) / 1975); //calc lipo battery voltage, scaled for 8 bit value (this config works fine)
  
  if (battVoltage < 10.2){
    digitalWrite(0,LOW);
  }
  else{
    digitalWrite(0,HIGH);  
  }

  delay(1000);
  1. Why not just use analogRead()?

  2. The problem is where you have the two values stored in a uint8_t, and then combine them. By the rules of C type promotion, the intermediate calculations are carried out as uint8's (ie, there is no type promotion, as all operands are the same type), so the two high bits are truncated. Store the value in ADCH in a uint16_t, or cast AnalogVH to uint16_t before leftshifting it.

Just after I posted this, I discovered I had used accidentally used analogVH instead of analogV for the final battery voltage calculation.
It was:

float battVoltage = (((analogVH * 0.00489) * 5245) / 1975);

instead of:

float battVoltage = (((analogV * 0.00489) * 5245) / 1975);
  1. Why not just use analogRead()?

Just want to practice more advanced programming. I also have some projects that use the ATtiny 10, which must be programmed this way, so it is good practice.

  1. The problem is where you have the two values stored in a uint8_t, and then combine them. By the rules of C type promotion, the intermediate calculations are carried out as uint8's (ie, there is no type promotion, as all operands are the same type), so the two high bits are truncated. Store the value in ADCH in a uint16_t, or cast AnalogVH to uint16_t before leftshifting it.

I fixed the error and the program works without changing ADCH to 16 bit, but I do see your point. Im not sure why it does work now.
Thanks for pointing that out though!

The compiler will give you the correct 16 bit value if you read ADC. uint16_t Ain = ADC;

EmanEric:
Just after I posted this, I discovered I had used accidentally used analogVH instead of analogV for the final battery voltage calculation.
It was:

float battVoltage = (((analogVH * 0.00489) * 5245) / 1975);

instead of:

float battVoltage = (((analogV * 0.00489) * 5245) / 1975);

Just want to practice more advanced programming. I also have some projects that use the ATtiny 10, which must be programmed this way, so it is good practice.

I fixed the error and the program works without changing ADCH to 16 bit, but I do see your point. Im not sure why it does work now.
Thanks for pointing that out though!

Oh, duh, because 8 is an int16 (the default integer type), so AnalogVH is promoted to an int16 when you leftshift it.