Fade LED using timers only

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

Hello,

I'm a newbie in this area.
I have question what is or how can I set fastest timer?
I have only basic knowledge I know how to use FCPU, but they aren't fast enough, I can see how LED's are flashing.
And I don't want to use PWM. I want to replicate it using timers only.

So here is my code:

#include 
#include 
#include 

volatile uint8_t count;
volatile uint8_t cik;
volatile uint8_t virz=0;

void main()
{
   // Prescaler = FCPU
   TCCR0|=(1<<CS00);

   //Enable Overflow Interrupt Enable
   TIMSK|=(1<<TOIE0);

   //Initialize Counter
   TCNT0=254;

   //Initialize our varriable
   count=0;
   cik=0;

   //Port C[3,2,1,0] as out put
   DDRB|=0b11111111;
   DDRD = 0b00000000;

   //Enable Global Interrupts
   sei();

   //Infinite loop
   while(1)
   {
   		if(bit_is_set(PIND, 7))
		{
			if(cik < 255) cik++;
			_delay_ms(10);
		}
		
   		if(bit_is_set(PIND, 6))
		{
			if(cik > 0) cik--;
			_delay_ms(10);
		}
	}
}

ISR(TIMER0_OVF_vect)
{
	TCNT0=254;
   //Increment our variable
   if(virz == 0) count++;
   else count--;

	if(count >= 255) virz=1;
	if(count <= 0) virz=0;
   if(count==cik)
   {
      PORTB=~PORTB; //Invert the Value of PORTC
   }
}

Later on the same principal I want to run LED's matrix

Best regards

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

your code is running slow, because you are constantly going into the ISR. You need to read up on the PWM functionality of the timer. This will allow you to interrupt once per PWM cycle, instead of once per PWM count.

Let's say your ISR takes 50 cycles to execute, yet you are only allowing for 1 CPU cycle before the next interrupt. As such you are missing 49 interrupts. As a result your PWM is running ~50x slower than what you want it to be.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

Just FYI, I have run 4 RGB LEDs (that's 12 individual LEDs) at about 60Hz refresh, 256 software-controlled intensity steps, on an AVR with an 18.432 MHz clock. The interrupt rate was 60*265 or about 65 us. My timer ISR to handle 12 LED software PWM channels took less than 5 us of that 65. So a software-driven multiple-LED project is no problem for the AVR.

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

From your description at the start, I sense a lot of confusion. For example:

Quote:
And I don't want to use PWM. I want to replicate it using timers only.

You HAVE to use some sort of PWM in order to control the brightness of the LEDs. You basically have two choices. One is the PWM system using one or more timers and their compare registers. The other is software, driven by some kind of timer element (whether or not one of the hardware timers is up to you).

After all, PWM stands for "P"ulse "W"idth "M"odulatioin. It is PWM which-ever way you make it.

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

Thank you all, I'll try to learn more about timers.

And if I think about:

Quote:
After all, PWM stands for "P"ulse "W"idth "M"odulatioin. It is PWM which-ever way you make it.

I'm just trying to make PWM without using ORC, but with interrupts. Am I correct?

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

Yes you are. You're implementing software PWM and avoiding hardware PWM.

What glitch said, allow more time for the interrupt routine.

By the way,

   if(count >= 255) virz=1;
   if(count <= 0) virz=0; 

Your count is an uint8_t, it can't be > 255, it can only be == 255. It can't be < 0 too, only == 0.

The Dark Boxes are coming.

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

Thank you all! I managed to get my code working! You all made me realize that I can use fast PWM mode as well, my code now is working.

Here it is:

#include 
#include 

volatile uint8_t cik;


void main()
{
   // Prescaler = FCPU
   TCCR0|=(0<<COM1A0)|(1<<COM1A1)|(0<<COM1B0)|(0<<COM1B1)|

(0<<FOC1A)|(0<<FOC1B)|(1<<WGM11)|(0<<WGM10); //fast PWM mode

   //Initialize our varriable
   cik=0;

   //Port C as out put
   DDRB|=0b11111111;
   DDRD = 0b00000000;

   PORTB = 0b11111111;


   //Infinite loop
   while(1)
   {
   		if(bit_is_set(PIND, 7))
		{
			if(cik < 255) cik++;
			_delay_ms(10);
		}
		
   		if(bit_is_set(PIND, 6))
		{
			if(cik > 0) cik--;
			_delay_ms(10);
		}
		if(TCNT0 > cik) PORTB = 0b00000000;
		else PORTB = 0b11111111;

	}
}

But I have question, this time I really don't know how to do it. How can I control them without interrupting main while loop? Because I want to use LCD, but LCD requires a delay.

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

You need to setup a timer compare match interrupt and use it instead of "if (TCNT0 > cik)...".

The Dark Boxes are coming.

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

Hello again,

I tried to do this "timer compare match interrupt"

For me it's not working, and probably I don't understand timers that well to develop this kind of code.

So here is my code again, interrupt is at the end.

#include 
#include 
#include  



void main()
{
   // Prescaler = FCPU
   TCCR1B|=(0<<COM1A0)|(1<<COM1A1)|(0<<COM1B0)|(0<<COM1B1)|

(0<<FOC1A)|(0<<FOC1B)|(1<<WGM11)|(0<<WGM10);


   //Initialize our varriable
   count=0;
   cik=0;
   OCR1B=0;

   //Port B as out put
   DDRB|=0b11111111;
   DDRD = 0b00000000;

   PORTB = 0b11111111;

 
	

   //Infinite loop
   while(1)
   {
   		if(bit_is_set(PIND, 7))
		{
			if(OCR1B < 255) OCR1B++;
			_delay_ms(10);
		}
		
   		if(bit_is_set(PIND, 6))
		{
			if(OCR1B > 0) OCR1B--;
			_delay_ms(10);
		}
	/*	if(TCNT0 > cik) PORTB = 0b00000000;
		else PORTB = 0b11111111;*/

	}
}

ISR (TIMER1_COMPB_vect)
{
	PORTB = ~PORTB;
}


Is there an interrupt which is called, when value of TCNT0 changes?

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

Unless you want me to read the datasheet for you, here's a hint: find the table with BOTTOM and TOP values in each mode, check what is used as BOTTOM and TOP in the mode you chose to use and see. Timer is probably the most confusing part of AVR and datahseet writers made sure the quest of configuring one's timer is not an easy game. But I already can see that you enabled Output Comparer A (not sure which mode), while using only Comparer B interrupt. Also, you seem to have none of the timer1-related interrupts enabled and global interrupts are not enabled either. Patience.

The Dark Boxes are coming.

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

Yea, couldn't agree more, for a beginner like me, timers are very confusing!

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

How fast is your uC running? Using no prescaler on the timer seems insane. Even when running at 1MHz, using a prescaler of 32 is perfectly acceptable. You can even go to 64 if you want to but i wouldn't. I personally run my LEDs at 244Hz normally.

Your code can definately be improved on so there is lots more for you to learn :)

So from your code, i gather that you want to PWM all of PORTB at a changable duty cylce?

Start by setting up a timer in normal mode and enable the OCR interrupt. Inside the interrupt do PORTB = ~PORTB. Set the OCR value to 128 and the output will come on 1/2 the time. Now change it to 64 and see it change and then change it to 192 and see it change again.

Now in the main write something like

while (1) {
    while (!tempByte) {
        OCR = tempByte++;
        delay_ms(2);
    }
    while (tempByte) {
        OCR = tempByte--;
        delay_ms(2);
    }
}

This will fade on and off all of portD about once a second

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

In the tutorials section there is a fantastic TUT from abcminiuser (Dean) that really helps you understand timers. It is very much worth the effort to work through.

https://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=50106

There is also a TUT on PWM that is good. Check them out.

-fab

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

Thank you for the tutorial.
Now I understand timers much better, but still not perfectly!

My code is working... But I can see a tiny flickering in LEDs!
How can I get in interrupt faster, or count faster?

#include 
#include 
#include 
#include  

volatile uint8_t cik; //how much in Latvian
volatile uint8_t count;


int main()
{
	lcd_init();
	
 // Prescaler = FCPU and FAST PWM I guess	

     TCCR1B |= (1 << CS00)|(1<<WGM11);



	/* enable Timer/Counter 1 interrupt */
   TIMSK |= (1<<TOIE1);
	TCNT1 = 65535; 

	sei(); //global interrupts
   //Port B as out put
   DDRB|=0b11111111;
   //PortD as input
   DDRD = 0b00000000;

   PORTB = 0b11111111; //Light all LEDs up

   cik=64; //set LEDs at 25percent brightness
   count=0;

	while(1)
	{
  		if(bit_is_set(PIND, 7))
		{
			if(cik< 255) { cik++; }
			_delay_ms(5);
		}
		
   		if(bit_is_set(PIND, 6))
		{
			if(cik> 0) { cik--; }
			_delay_ms(5);
		}
		//lcd_clrscr();
		lcd_home();	
	//	lcd_clrscr();
		lcd_string2("PWM testesana! \nCik = d",cik);
	
		//for (int i=0;i<10;i++) //some delay
			_auxDelay(1000000);
	

	}
	return 0;

}


ISR (TIMER1_OVF_vect)
{
	count++;
	if(count >= cik) 
	{
		PORTB = 0b00000000;		
	}
	else
	{
		PORTB = 0b11111111;
	}
	if(count >=255) count=0;
	TCNT1 = 65535;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AVR timers count up and you only leave one count before interrupt. Same error as earlier. Also, you don't need WGM bits if you only use overflow.

The Dark Boxes are coming.

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

this is not how you use the timer to create a software PWM port.

What you do is setup the timer to run anywhere between 60 and 500 Hz (slower will be seen by the human eye, faster will be pointless) i normally go for 244, or go for 122 if the clock frequency and prescalers allow for it.

Now, enable one compare AND one overflow interrupt. In the overflow interrupt, turn all the LEDs on. In the compare, turn all the LEDs off.

You can cange when the compare happens by changing the OCR value. the higher the OCR value, the brighter the LED. So set the OCR value to that cik variable you have and all should work VERY well.

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

Quote:

Now, enable one compare AND one overflow interrupt. In the overflow interrupt, turn all the LEDs on. In the compare, turn all the LEDs off.

Something a bit like this I guess:

// The following two ISRs are doing "poor man's PWM" 
//  but this allows it to be on a pin of my choice
ISR(TIMER0_COMP_vect) {
	PORTC &= ~(1<<PC0);
}
ISR(TIMER0_OVF_vect) {
	PORTC |= (1<<PC0);
}

int main(void) {
	// going to use PORTC.0 to PWM the contrast voltage
	DDRC = (1<<DDC0);
	TIMSK |= ((1<<OCIE0) | (1<<TOIE0)); // use both interrupts
	OCR0 = 10; // 10 out of 256 means very short on period (low voltage)
	TCCR0 = (1<<CS00); // timer on - nice high PWM frequency

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

Just like that :)