Trouble with ADC Interrupts and PWM output.

Hi all this is my first post so please be gentle :slight_smile: . I have been playing with the ADC and PWM outputs. Attached is a schematic of my setup. I have a voltage divider with a light dependent resistor as R1 and the center node attached to pin PC1 and the center post of a potentiometer connected to pin PC0. I am varying the resistance of the LDR with an LED being driven by pwm from pin PD6.

My goal is to have the voltage of the voltage divider match the voltage coming out of the pot. So if I turn up the pot the duty cycle of the pwm driving the LED is increased making the LED brighter and bringing the voltage from the voltage divider go up to match the output from the pot. Here is the code that I am using.

volatile int pot;
volatile int ldr;
uint8_t channel;
int adcValue[2];
int error;
int pwmValue = 0;

void setup() {
  
  cli();
  
  ADMUX |= (1<<REFS0);                                    //set adc ref. to AVcc
  ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2);         //set prescaler to 128
  channel = 0;                                            //set channel to 0
  ADMUX = (0xF0 & ADMUX) | channel;                       //set multiplexer to ADC0
  ADCSRA |= (1<<ADEN);                                    //turn on ADC
  ADCSRA |= (1<<ADIE);                                    //enable ADC interrupts
  ADCSRA |= (1<<ADSC);                                    //start first conversion

  DDRD = 0b01000000;                                      //set OC0A to output
  TCCR0A |= (1<<WGM00) | (1<<WGM01);                      //set fast pwm mode
  TCCR0A |= (1<<COM0A1);                                  //set compare match mode for pwm
  TCCR0B |= (1<<CS00) | (1<<CS01);                        //set prescaler to 64

  Serial.begin(115200);
  
  sei();                                                  //enable global interrupts
}

void loop(){
  error = ldr - pot;
  if (error > 0)  pwmValue--;
  if (error < 0)  pwmValue++;

  OCR0A = pwmValue;

  Serial.print("  ");
  Serial.print(ldr);
  Serial.print("  ");
  Serial.print(pot);
  Serial.print("  ");
  Serial.print(error);
  Serial.print("  ");
  Serial.print(pwmValue);
  Serial.print("\n");

}


ISR (ADC_vect){
  adcValue[channel] = ADC;                            //store values from ADC
  pot = adcValue[0];                                  
  ldr = adcValue[1];
  channel++;                                          //increment channel
  if (channel >1) channel = 0;
  ADMUX = (0xF0 & ADMUX) | channel;                   //change multiplexer
  ADCSRA |= (1<<ADSC);                                //start next conversion
                
}

As it is right now it works, but if I comment out the Serial.Begin(115200) and all the Serial.print lines it stops working. The led just stays at one fixed brightness and doesn't vary no matter how much I turn the potentiometer. The odd thing is I wrote up the same sketch using software driven pwm and with that it works with the Serial.being and Serial.print lines commented out and doesn't work if I leave them in. Here is that code.

volatile int pot;
volatile int lsr;
uint8_t channel;
int adcValue[2];
int error;
int pwmValue = 0;
int pwmTickCount = 0;
int pwmTotal = 100;
bool pinState = true;


void setup() {
  
  cli();
  
  ADMUX |= (1<<REFS0);                                    //set adc ref. to AVcc
  ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2);         //set prescaler to 128
  channel = 0;                                            //set channel to 0
  ADMUX = (0xF0 & ADMUX) | channel;                       //set multiplexer to ADC0
  ADCSRA |= (1<<ADEN);                                    //turn on ADC
  ADCSRA |= (1<<ADIE);                                    //enable ADC interrupts
  ADCSRA |= (1<<ADSC);                                    //start first conversion

  DDRD = 0b01000000;
  TCCR1A = 0;                                             //set entire TCCR1A register to 0
  TCCR1B = 0;                                             //same for TCCR1B
  TCNT1  = 0;                                             //initialize counter value to 0
  OCR1A = 10;                                             //set time for each tick
  TCCR1B |= (1 << WGM12);                                 //turn on CTC mode
  TCCR1B |= (1 << CS11);                                  //set prescaler to 256
  TIMSK1 |= (1 << OCIE1A);                                //enable timer compare interrupt
  //Serial.begin(115200);
  
  sei();                                                  //enable global interrupts
}

void loop(){
 
  pwmHandle();
  
  error = lsr - pot;
  if (error > 0)  pwmValue--;
  if (error < 0)  pwmValue++;


  /*Serial.print("  ");
  Serial.print(lsr);
  Serial.print("  ");
  Serial.print(pot);
  Serial.print("  ");
  Serial.print(error);
  Serial.print("  ");
  Serial.print(pwmValue);
  Serial.print("\n");*/

}

ISR (TIMER1_COMPA_vect)
{
  pwmTickCount++;
}

void pwmHandle() {
  
  if(pinState == true) {
    if(pwmTickCount >= pwmValue)
      pinState = false;
    } else {
        if (pwmTickCount >= pwmTotal){
          pinState = true;
          pwmTickCount = 0;
        }
      }
  if (pinState == true) {
    PORTD = (0b01000000);
  }
  if (pinState == false) {
    PORTD = (0b00000000);
  }
}


ISR (ADC_vect){
  adcValue[channel] = ADC;
  pot = adcValue[0];
  lsr = adcValue[1];
  channel++;
  if (channel >1) channel = 0;
  ADMUX = (0xF0 & ADMUX) | channel;
  ADCSRA |= (1<<ADSC);
  
}

I thought it might be related to what timer I was using so I rewrote both of them using different timers with the same results. I am stumped by this and would appreciate any help.

schemeit-project(1).pdf (19.9 KB)

Such low level code is hard to analyze. Why don't you use the standard functions? Why interrupts? Why T0?
Serial.begin() may initialize further registers, which you don't handle in your setup.

Even if a LDR is not very fast, you may get out of it kind of a square wave, according to the PWM signal of the LED.

Thanks for the reply.

DrDiettrich:
Why don't you use the standard functions?

By the standard functions do you mean analogRead and analogWrite? I was trying to stay away from those because of their overhead. I am just practicing using the using the adc and the pwm outputs for use in a project, so I was trying to save on cpu time.

DrDiettrich:
Why interrupts? Why T0?

I wrote other sketches doing the same thing without the interrupts. I was using the macro loop_until_bit_is_clear but I didn't know what the cpu was doing while it was waiting for the bit to be clear, this made me wonder if interrupts would be more efficient. I picked T0 because its output pin was one of the only open pwm pins for my project.

f I comment out the Serial.Begin(115200) and all the Serial.print lines it stops working

Post the non-working code.

uint8_t channel;
int adcValue[2];

These may not work properly in an interrupt routine, because they are not declared volatile.

I agree with DrDiettrich: it is much easier to use analogRead to read the pot and the LDR (with a capacitor to filter the ripple) and analogWrite to light the LED. Five or so lines of code total.

As for "overhead" are you concerned about a few extra microseconds/loop?

Thanks for the reply.

jremington:
Post the non-working code.

Here is the non-working code.

volatile int pot;
volatile int ldr;
uint8_t channel;
int adcValue[2];
int error;
int pwmValue = 0;

void setup() {
  
  cli();
  
  ADMUX |= (1<<REFS0);                                    //set adc ref. to AVcc
  ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2);         //set prescaler to 128
  channel = 0;                                            //set channel to 0
  ADMUX = (0xF0 & ADMUX) | channel;                       //set multiplexer to ADC0
  ADCSRA |= (1<<ADEN);                                    //turn on ADC
  ADCSRA |= (1<<ADIE);                                    //enable ADC interrupts
  ADCSRA |= (1<<ADSC);                                    //start first conversion

  DDRD = 0b01000000;                                      //set OC0A to output
  TCCR0A |= (1<<WGM00) | (1<<WGM01);                      //set fast pwm mode
  TCCR0A |= (1<<COM0A1);                                  //set compare match mode for pwm
  TCCR0B |= (1<<CS00) | (1<<CS01);                        //set prescaler to 64


  
  sei();                                                  //enable global interrupts
}

void loop(){
  error = ldr - pot;
  if (error > 0)  pwmValue--;
  if (error < 0)  pwmValue++;

  OCR0A = pwmValue;

 

}


ISR (ADC_vect){
  adcValue[channel] = ADC;                            //store values from ADC
  pot = adcValue[0];                                  
  ldr = adcValue[1];
  channel++;                                          //increment channel
  if (channel >1) channel = 0;
  ADMUX = (0xF0 & ADMUX) | channel;                   //change multiplexer
  ADCSRA |= (1<<ADSC);                                //start next conversion
    
}

It is the same code just without the Serial stuff.

I tried changing the variables to volatile with no change.

jremington:
As for "overhead" are you concerned about a few extra microseconds/loop?

I will be using the adc and pwm in a project that is very time sensitive, so those few extra microseconds might make a difference.

I don't see anything obviously wrong and it is certainly not obvious what the failure might have to do with serial.print().

On the other hand, Timer0 is normally in use by the Arduino for other purposes, and interfering with that may cause problems.

As already pointed out, writing low level code is difficult and the result is hard to debug. Not only does every bit have to be set correctly, it is easy to forget setting or clearing other bits that are required for proper function. You need to read the processor data sheet with a magnifying glass, usually several times, to avoid those sorts of mistakes.

The canned analogWrite and analogRead spare you this effort, but do cost a few microseconds extra.

Thanks for all the replies. I guess I am going to have to spend more time reading the datasheet.

Not sure what you're trying to do here.
You talk about getting the voltage on the LDR divider the same as on the pot.
Remember that you're using PWM. LED fully on, fully off.
Only your brain thinks it's dimming.
You're relying on the LDR being slow?
Leo..

The question isn't whether or not the setup with the LDR works. I got that to work it just stops working when I remove Serial.begin and Serial.print from the sketch.

What do you mean by "the setup with the LDR"?

Please post the minimal but complete code that demonstrates the problem, both working and non-working versions.

jremington:
What do you mean by "the setup with the LDR"?

I was responding to WaWa's question. I meant that the LDR sensing the brightness of a PWM driven LED works.

jremington:
Please post the minimal but complete code that demonstrates the problem, both working and non-working versions.

Here is the working code.

volatile int pot;
volatile int ldr;
volatile uint8_t channel;
volatile int adcValue[2];
int error;
int pwmValue = 0;

void setup() {
  
  cli();
  
  ADMUX |= (1<<REFS0);                                    //set adc ref. to AVcc
  ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2);         //set prescaler to 128
  channel = 0;                                            //set channel to 0
  ADMUX = (0xF0 & ADMUX) | channel;                       //set multiplexer to ADC0
  ADCSRA |= (1<<ADEN);                                    //turn on ADC
  ADCSRA |= (1<<ADIE);                                    //enable ADC interrupts
  ADCSRA |= (1<<ADSC);                                    //start first conversion

  DDRD = 0b01000000;                                      //set OC0A to output
  TCCR0A |= (1<<WGM00) | (1<<WGM01);                      //set fast pwm mode
  TCCR0A |= (1<<COM0A1);                                  //set compare match mode for pwm
  TCCR0B |= (1<<CS00) | (1<<CS01);                        //set prescaler to 64

  Serial.begin(115200);
  
  sei();                                                  //enable global interrupts
}

void loop(){
  error = ldr - pot;
  if (error > 0)  pwmValue--;
  if (error < 0)  pwmValue++;

  OCR0A = pwmValue;

  Serial.print("  ");
  Serial.print(ldr);
  Serial.print("  ");
  Serial.print(pot);
  Serial.print("  ");
  Serial.print(error);
  Serial.print("  ");
  Serial.print(pwmValue);
  Serial.print("\n");

}


ISR (ADC_vect){
  adcValue[channel] = ADC;                            //store values from ADC
  pot = adcValue[0];                                  
  ldr = adcValue[1];
  channel++;                                          //increment channel
  if (channel >1) channel = 0;
  ADMUX = (0xF0 & ADMUX) | channel;                   //change multiplexer
  ADCSRA |= (1<<ADSC);                                //start next conversion
                
}

And here is the non working code.

volatile int pot;
volatile int ldr;
volatile uint8_t channel;
volatile int adcValue[2];
int error;
int pwmValue = 0;

void setup() {
  
  cli();
  
  ADMUX |= (1<<REFS0);                                    //set adc ref. to AVcc
  ADCSRA |= (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2);         //set prescaler to 128
  channel = 0;                                            //set channel to 0
  ADMUX = (0xF0 & ADMUX) | channel;                       //set multiplexer to ADC0
  ADCSRA |= (1<<ADEN);                                    //turn on ADC
  ADCSRA |= (1<<ADIE);                                    //enable ADC interrupts
  ADCSRA |= (1<<ADSC);                                    //start first conversion

  DDRD = 0b01000000;                                      //set OC0A to output
  TCCR0A |= (1<<WGM00) | (1<<WGM01);                      //set fast pwm mode
  TCCR0A |= (1<<COM0A1);                                  //set compare match mode for pwm
  TCCR0B |= (1<<CS00) | (1<<CS01);                        //set prescaler to 64

  
  
  sei();                                                  //enable global interrupts
}

void loop(){
  error = ldr - pot;
  if (error > 0)  pwmValue--;
  if (error < 0)  pwmValue++;

  OCR0A = pwmValue;

  
}


ISR (ADC_vect){
  adcValue[channel] = ADC;                            //store values from ADC
  pot = adcValue[0];                                  
  ldr = adcValue[1];
  channel++;                                          //increment channel
  if (channel >1) channel = 0;
  ADMUX = (0xF0 & ADMUX) | channel;                   //change multiplexer
  ADCSRA |= (1<<ADSC);                                //start next conversion
                
}

It is pretty much the same as the working code but the Serial.begin and Serial.print lines are removed.

Then Serial.begin() most probably does something, that is missing from your setup.