Timer delayed ADC conversions

Go To Last Post
6 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

I'm currently working on a small project to monitor the voltage of a car battery using a tiny45.
The battery voltage scaled down and connected to a ADC. I can use the ADC in free running mode (generating interrupts when a conversion is ready), but then it samples the voltage way too often. All I need is a few times per second, and I need the rest of the time to correctly produce three different software-generated pwm signals - the indicator is a RGB LED (way cool!). When the ADC samples too often, the pwm is interrupted and the color of the LED flickers noticeably.
So, I'm trying to use a timer instead to trigger the ADC conversions. I'm using timer owerflow as a trigger source (I figured that was the easiest thing to do). The timer fires just fine and generates its interrupt, but the ADC doesn't seem to start converting once this happens.
I've set the ADTS bits in ADCSRB to "timer/counter overflow". Shouldn't the ADC start a conversion automatically once the timer overflows, or am I missing something important here? The tiny has 2 timers, but there is no such distinction in the trigger source - does I have to use one of the timers? I'm using timer1 now (less to read in the datasheet, since it has no pwm).

The code I'm using follows (with unimportant parts edited out):

#include 
#include 
#include 
#include "main.h"
#include "out.h"

ISR(ADC_vect) {
	uint16_t vin = ADC;
[...] // Do some conversions and set registers controlling the led
}
ISR(TIM1_OVF_vect) {
// Do nothing
}

int main () {
[...] // Initialize led and ports
	cli(); // No interrupts during startup
	

// *** Initialize ADC ***
	uint8_t ad_vref = (1 << REFS2) | (1 << REFS1); // 2.56 V internal source, uncoupled.
	uint8_t ad_select = meas_in; // Select ADC to use

	ADMUX |= ad_vref | ad_select;

	uint8_t ad_prescale = 0x07; // Divide by 128
	uint8_t ad_interrupt_enable = (1 << ADIE);
	uint8_t ad_enable = (1 << ADEN);
	uint8_t ad_autotrig = (1 << ADATE);

	ADCSRA |= ad_prescale | ad_interrupt_enable | ad_enable | ad_autotrig;

	uint8_t ad_autotrig_source = (1 << ADTS2); // Use Timer/Counter Overflow for interrupt

	ADCSRB |= ad_autotrig_source;

// *** ADC initialized ***

// *** Initialize timer ***
	
	uint8_t timer_overflow_interrupt_enable = (1 << TOIE1);

	TIMSK |= timer_overflow_interrupt_enable;

// *** Timer initialized ***

	sei(); // Startup ready, allow interrupts

	timer_start();

while (1) {
[...] // Run led pwm
}

void timer_start() {
	uint8_t timer_prescale = 0x0F; // CK/16384

	TCCR1 |= timer_prescale;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
The tiny has 2 timers, but there is no such distinction in the trigger source - does I have to use one of the timers?

The OC triggers apply to timer 0, so I expect the TOV trigger applies to timer 0 also.

I think this is a mistake in the manual -- it really should say what timer it refers to.
(I recall puzzling over it, but I don't recall how I figured it out, maybe I just guessed
correctly.)

You don't need the (empty) TOV ISR, by the way; just remember to clear the TOV0 flag in
your ADC ISR.

Quote:
I'm using timer1 now (less to read in the datasheet, since it has no pwm).

Timer 1 has two PWM channels plus inverses. Maybe useful to drive your LED?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
The OC triggers apply to timer 0, so I expect the TOV trigger applies to timer 0 also.

Ah, I should have known... of course I would have to pay for trying to escape reading all those pages. :)

Quote:
You don't need the (empty) TOV ISR, by the way; just remember to clear the TOV0 flag in
your ADC ISR.

Ah, quite. It's just there right now to flash the LED so I know the timer generated an interrupt.

Quote:
Timer 1 has two PWM channels plus inverses. Maybe useful to drive your LED?

Indeed, there are. I didn't even notice! And they claim that Timer0 is the one with PWM... :) Good to know for the future, but I think the software version will be enough, for this project.

Thanks for your answers!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why do you even need interrupts for the ADC? Why not just read the ADC when you want to without interrupts. If it's battery voltage - you really don't need to sample it on accurate time intervals.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ah, but that would be the easy approach, and no fun! :D

Two reasons, actually (now that I think about it):

-I want to learn to use interrupts in a good way, and this project seemed to be good for that. I've already used the timer and ADC a few times, with polling.
-To poll the ADC would force me to do more loops in the main program (or at least some if-statements and incrementing), so that it'd only poll every 1000 PWM periods, or whatever number. Why not use hardware that already exists on board to do this?

Your suggestion is very valid, though. :) I am a hardware guy, and don't think about the easy software solutions. Thanks for pointing it out.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Let the interrupt only set a flag, so NO jump to a subroutine.
This is probably just one or two clock cycles.
If all main tasks are done ( these you do not want to be interrupted) LED is set, PWM is done, than check if the flag is set increment a soft counter and reset the flag.
Test the counter, if counter reached a the predefined value > ADC and reset counter

Than again the main routine LED, PWM