Sine wave PWM LED dimming

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

Hi all,

I've started working on my 1st atmel studio project - christmas lights on on Attiny85. It has 4 led outputs on PB0, 1, 3 and 4 and a button interrupt on PB2 to change mode. Timer0 is used ad a 'timebase' and just incrementss the timebase variabl on overflow to manage timing. Timer1 is in PWM mode. Modes 1, 2 and 4 work fine and it cycles through all modes just fine; I left them empty here for clarity. For mode 3, for now I'm just trying to get the LED on OC1A (PB1) to dim and brighten with PWM in a sinusodial fashion, by setting OCR1A compare register to the next value in the pulse[] array every 10 or so milliseconds (using the for loop as a sort of delay). Instead, it 'blockily' goes from bright to dim, and has glitches at the bottom of tthe cycle. I can't really describe it but it basically looks crap. Are there glaring errors in my algoritthm I'm missing? Any help much appreciated!

Ion

 

#include <avr/interrupt.h>
#include <avr/io.h>
int timebase = 0;
char counter = 0;
char mode = 0;

char pulse[180] = {0,4,8,13,17,22,26,31,35,39,44,48,53,57,
    61,65,70,74,78,83,87,91,95,99,103,107,111,115,119,123,127,131,135,138,142,
    146,149,153,156,160,163,167,170,173,177,180,183,186,189,192,195,198,200,
    203,206,208,211,213,216,218,220,223,225,227,229,231,232,234,236,238,239,
    241,242,243,245,246,247,248,249,250,251,251,252,253,253,254,254,254,254,
    254,254,254,255,254,254,254,253,253,252,251,251,250,249,248,247,246,245,
    243,242,241,239,238,236,234,232,231,229,227,225,223,220,218,216,213,211,
    208,206,203,200,198,195,192,189,186,183,180,177,173,170,167,163,160,156,
    153,149,146,142,138,135,131,127,123,119,115,111,107,103,99,95,
    91,87,83,78,74,70,65,61,57,53,48,44,39,35,31,26,22,17,13,8,4};
    
int main(void)
{
    DDRB = 0b00011011;      //set port 3 to input and others to output
    PORTB = 0b00000100;        //set port 3 to pullup input
    GIMSK = 0b01000000;        //enable INT0 pin
    MCUCR = 0b00000010;     //falling edge of INT0 generates interrupt
    TCCR1 = 0b000000110;    //setup timer 1 with no prescaler
    TCCR0A = 0b00000000;    //setup timer 0 with x1024 prescaler
    TCCR0B = 0b00000101;    //and 'normal' operation mode
    TIMSK = 0b00000010;      //set interrupt on timer 0 overflow (255 counts)
    OCR1C = 0b11111111;        //Max PWM cycle period
    sei();                   //enable interrupts
    
    while(1)
    { 
        while(mode == 0)
        {
            PORTB = 0b00011111;     //All LEDs on
        }
        
        while(mode == 1)
        {
            //mode 2
        }
        
        while(mode == 2)    //mode 3
        {
            TCCR1 = 0b01100110;  //enable PWM mode and OC1A output for timer1
            for(timebase = 0; timebase < 5;)
            {
                DDRB = 0b00011011; //something to do while waiting; has no effect

            }
            counter++;
            if (counter == 180)
            {
                counter = 0;
            }
        }
        
        while(mode == 3)
        {
            //mode 4        
        }
    }
}

ISR(TIMER0_OVF_vect) //interrupt triggered with 'timer 0 overflow' vector
{
 timebase++;
}

ISR(INT0_vect)  //interrupt triggered with 'INT0' vector
{
    if(mode == 3)
    {
        mode = 0;    
    else
    {
        mode++;
    }    
    main(); //I put this here so it instantly changes mode
            //without having to finish the while loop
}

 

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

Ionforbes wrote:

  main(); //I put this here so it instantly changes mode
            //without having to finish the while loop

Whoa.  You had better think long and hard about recursive calls to main().  No matter how good an idea you might think it is, eventually you'd run out of stack, right?  Whoa -- from inside an ISR, too -- are interrupts going to be reenabled?  Perhaps. 

 

Timebase must be volatile.  Se the FAQ for your favourite toolchain.

 

If you want to get to the end of your main loop, then don't sit in one place.  Start thinking about a state machine for your "mode".  Take action when needed to change modes.  One of the actions can be to check for a change of mode.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

Ionforbes wrote:
I've started working on ... christmas lights

Nothing like getting ahead of the game!

 

wink

 

Ionforbes wrote:
it 'blockily' goes from bright to dim

Remember that out eyes don't perceive "brightness" linearly:

 

https://ledshield.wordpress.com/2012/11/13/led-brightness-to-your-eye-gamma-correction-no/

 

Some tables there that you can use to get a "linear"-looking fade.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Worth noting that LEDs don't dim nicely, no matter how you control them, at the lower end.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

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

May want to put your 

char pulse[180]

in PROGMEM to save RAM space.

"If you find yourself in an even battle, you didn't plan very well."
https://www.gameactive.org
https://github.com/CmdrZin

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

dim and brighten with PWM in a sinusodial fashion,

Why?  How would that appear to your eyes ?  Doubt someone would take a look at the pulsations & say hey that's a sinewave!   Maybe a ramp would be just as good---what effect do you want? Doubt you need to  use a sinewave at all.

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

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

avrcandies wrote:

Doubt someone would take a look at the pulsations & say hey that's a sinewave!   Maybe a ramp would be just as good---what effect do you want? Doubt you need to  use a sinewave at all.

 

A sinewave-ish dimming cycle looks very differently to a ramp (although being pedantic you'd want a triangle). The transition from going-up to going-down when using a sine feels a lot smoother.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

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

Ok so I had another look and decided to put the LED routine in its own function 'lightshow(char mode)' (for now I'm just turning different LEDs on and off in different modes as a placeholder). I then put this function in an infinite while loop in main() and passed the mode through the function statement. The external interrupt just incrementss the state variable. But the interrupt doesn't change the mode. My (limited!) understanding was that the interrupt would go back to the earlier line of code it was just on when finished, in this case it would redo the 'lightshow()' function with the new state. However I can only get it to work by calling the lightshow function in the ISR, but I now know that's a terrible practice. Any help greatly appreciated.

#include <avr/interrupt.h>
#include <avr/io.h>
int timebase = 0;
char counter = 0;
char state = 0;

void lightshow(char mode)
{
	while(mode == 0)
	{
		PORTB = 0b00011111;
	}
	
	while(mode == 1)
	{
		PORTB = 0b00000101;
	}
	
	while(mode == 2)
	{
		PORTB = 0b00000110;
	}
	while(mode == 3)
	{
		PORTB = 0b00010100;
	}
}
	
int main(void)
{
	DDRB = 0b00011011;      //set port 3 to input and others to output
	PORTB = 0b00000100;		//set port 3 to pullup input
	GIMSK = 0b01000000;	    //enable INT0 pin
	MCUCR = 0b00000010;     //falling edge of INT0 generates interrupt
	TCCR1 = 0b000000110;	//setup timer 1 with no prescaler
	TCCR0A = 0b00000000;	//setup timer 0 with x1024 prescaler
	TCCR0B = 0b00000101;	//and 'normal' operation mode
	TIMSK = 0b00000010;      //set interrupt on timer 0 overflow (255 counts)
	OCR1C = 0b11111111;		
	sei();                   //enable interrupts
	while(1)
	{ 
		lightshow(state);
	}
}


ISR(TIMER0_OVF_vect) //interrupt triggered with 'timer 1 overflow' vector
{
 timebase++;
}

ISR(INT0_vect)  //interrupt triggered with 'INT0' vector
{
	if(state == 3)
	{
		state = 0;
	}

	else
	{
		state++;
	}
}

Thanks, Ion

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

and still those things that change in ISR and used somewhere else (and the other way around) need to be volatile!!! (and state isen't)

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

OK so I did a bit of research on volatiles, makes sense now, but sadly it wasn't the causee of the issue here :(

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

I not 100% sure but think that you have to clear (on AVR's it done with a set) the interrupt bit.

 

(I would not do it this way because  denounce often are needed, I would do a pull in the timer interrupt)

 

 

 

Add:

Ok I just looked in the datasheet and it clear the bit when interrupt is taken, but INT0 make a interrupt on all io's even if output, so a write to the port will trigger an ISR

 

Add : forget most of this, you don't write to PCMSK to tell which IO you use

Last Edited: Sat. Jan 9, 2021 - 03:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry I don't quite get what you mean. Could you expand a bit? Would disabling and reenabling interrupts clear the stack from 'return to post-interupt' locations or somthing?

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

ok forget it the main thing is that you don't write to PCMSK to tell which IO that are enabled for INT0

 

forget my nonsense so #14 is correct i'm wrong

Last Edited: Sat. Jan 9, 2021 - 06:59 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:
ok forget it the main thing is that you don't write to PCMSK to tell which IO that are enabled for INT0

INT0 and PCMSK are not related, are they?  INT0's friend is GIMSK, and after counting the zeroes twice in the binary constant it seems that the correct bit is set.

 

But we don't see a complete but "non-working" program with proper use of volatile.

 

Once it "kind of works, but..." then it will be time to talk about bouncing and noise and debounce.  That can be after >>one<< dimming sequence is good enough.  Heck, let's just have one interrupt-driven long-blink that can be observed and timed...

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Sat. Jan 9, 2021 - 04:24 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK so I made timebase and state volatile, but I still can't get the thing to change modes. I'd have thought that when the ISR is over, and the 'state' variable was incremented, it would go back to the while loop for that mode and break out of it when it sees the state no longer matches; then it would do the while loop that does match the state variable. There's probably a glaring flaw in my process somehwere, but I suppose it's hard to spot when it's your own code!

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

You can poll a switch, and do not have to use an interrupt. A switch is a relatively slow event so you can check and debounce manually if nothing else is going on or interrupts are doing all the work in the background. May not always be able to use polling, but when you can it is a quick/easy way to do it.

https://godbolt.org/z/KEYc9W

This compiles, but I do not have a tiny85 so possibly is flawed but there may be ideas in there to use.

 

Your cannot make use of all the 255 levels of pwm due to the way the eye perceives brightness. The example above narrows it down to 32 levels which translate to the full range of the pwm values in a way that your perception of brightness is linear from 0-31, and you will most likely be able to discern a change in brightness for each level. Once you get the ability to use more pwm values (like a 16bit timer where you can use a few more bits), then you can do the lower end of brightness much better which can be difficult to do well when pwm resolution is low.

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

Here's what I did for a pair of glowing eyes for a cosplay costume. Gives you another approach to the problem.    

https://github.com/CmdrZin/chips...

Have fun.

"If you find yourself in an even battle, you didn't plan very well."
https://www.gameactive.org
https://github.com/CmdrZin

Last Edited: Sat. Jan 9, 2021 - 06:45 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A sinewave-ish dimming cycle looks very differently to a ramp

My point is, you are not really worried about creating a sinewave ...no one is going to walk up & look at the led glowing and say hey that's an awful (or great) looking sinewave...they'd have only a rather vague idea of the "shape" ...so maybe you don't even need a table of values at all, maybe an accelerating count would do the trick.

 

The best way to experiment is to hook up a pot to control the pwm...then you can turn it up & down to get the visual effects you want...you roughly note how you are turning the pot (or log the pot voltage on a scope)....that will allow a table to be made to match your desired pattern.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Sat. Jan 9, 2021 - 06:59 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well thanks everyone for your help, I did manage to get it working by using a switch case; I think my first approach was gettting trapped in while loops somewhere. I'm not bothered about the array's RAM usage since the rest of the program only uses 3 or 4 bytes anyway.

#include <avr/interrupt.h>
#include <avr/io.h>
volatile int timebase = 0;
unsigned char counter = 0;
volatile char state = 0;

unsigned int pulse[180] = {0,4,8,13,17,22,26,31,35,39,44,48,53,57,
    61,65,70,74,78,83,87,91,95,99,103,107,111,115,119,123,127,131,135,138,142,
    146,149,153,156,160,163,167,170,173,177,180,183,186,189,192,195,198,200,
    203,206,208,211,213,216,218,220,223,225,227,229,231,232,234,236,238,239,
    241,242,243,245,246,247,248,249,250,251,251,252,253,253,254,254,254,254,
    254,254,254,254,254,254,254,253,253,252,251,251,250,249,248,247,246,245,
    243,242,241,239,238,236,234,232,231,229,227,225,223,220,218,216,213,211,
    208,206,203,200,198,195,192,189,186,183,180,177,173,170,167,163,160,156,
    153,149,146,142,138,135,131,127,123,119,115,111,107,103,99,95,
    91,87,83,78,74,70,65,61,57,53,48,44,39,35,31,26,22,17,13,8,4};
    

int main(void)
{
    DDRB = 0b00011011;      //set port 3 to input and others to output
    PORTB = 0b00000100;        //set port 3 to pullup input
    GIMSK = 0b01000000;        //enable INT0 pin
    MCUCR = 0b00000010;     //falling edge of INT0 generates interrupt
    TCCR1 = 0b000000110;    //setup timer 1 with x32 prescaler
    TCCR0A = 0b00000000;    //setup timer 0 with x1024 prescaler
    TCCR0B = 0b00000101;    //and 'normal' operation mode
    TIMSK = 0b00000010;      //set interrupt on timer 0 overflow (255 counts)
    OCR1C = 0b11111111;        
    sei();                   //enable interrupts
    
    while(1)
    { 
        switch(state)
        {
            case 0 :     //solid mode
                PORTB = 0b00011111;
                break;

            case 1 :    //blinking
                for (counter = 0; counter<4; counter++)
                {
                    for(timebase = 0; timebase<5;)
                    {
                        PORTB = 0b00011111;
                    }
                    for(timebase = 0; timebase<5;)
                    {
                        PORTB = 0b00000100;
                    }
                }
                break;
                
            case 2 :
                TCCR1 = 0b01100110;
                for (timebase = 0; timebase < 1;)
                {
                    OCR1A = pulse[counter];
                }
                    if (counter == 180)
                {
                counter = 0;
                }
                counter++;
                break;

            case 3 :
                PORTB = 0b00010100;
                break;    
        }
    }
}


ISR(TIMER0_OVF_vect) //interrupt triggered with 'timer 1 overflow' vector
{
 timebase++;
}

ISR(INT0_vect)  //interrupt triggered with 'INT0' vector
{
    if(state == 3)
    {
        state = 0;
    }

    else
    {
        state++;
    }
    TCCR1 = 0b000000110;
}

 

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

Why does your case 2 for, look more like a flag ?  Also , why set the counter index to zero , then always add 1?  It can never get back to used as zero.  add first, then perhaps reset (or use an else to reset or add)

 

note you could make it much simpler:   mycow = (++mycow) % 6;    gives  0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2.... in an easy line of code

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Sat. Jan 9, 2021 - 10:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ionforbes wrote:
I think my first approach was gettting trapped in while loops somewhere.

Yes, you were calling your lightshow function, passing in a value for the mode parameter.

Within the fuction, nothing ever changes the value of mode.

So if you passed in a value of 0,1,2 or 3 it would be endlessly stuck in the corresponding while loop.

 

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

I've managed to get to cyle through all four LEDs now using both PWM channels and changing the DDR of either the inverted or non inverted output, thus effectively getting 4 independent outputs. However when changing from the non-inverted to the inverted output of the same PWM channel (that is, using the sme output compare register), the inverted output will output a sort of 'glitch' pulse before doing the ramp up/ramp down routine, ruining the gentle glow :(. I've attatched a scope shot to illustrate. What I don't understand is why it only does it when changing LED but still on the same PWM comparator, but doesn't do it when switching to an LED on a different compare-output. Even more confusing is the fact it takes exactly 1ms, or 8000 clock cycles, so I'd have thought it would be an obvious reason. Anyway I'm completely stumped, so I'd be grateful for any ideas.

 

#include <avr/interrupt.h>
#include <avr/io.h>
volatile unsigned int timebase = 0;
unsigned char counter = 0;
volatile unsigned char state = 2;
unsigned char breakflag = 0;
unsigned char leds = 0;

unsigned char pulse[180] = {0,0,0,0,1,1,2,3,4,6,7,9,11,12,14,17,19,21,24,27,
	29,32,35,38,42,45,49,52,56,59,63,67,71,75,79,83,88,92,96,100,105,
	109,114,118,123,127,131,136,140,145,149,154,158,162,166,171,175,
	179,183,187,191,195,198,202,205,209,212,216,219,222,225,227,230,
	233,235,237,240,242,243,245,247,248,250,251,252,253,253,254,254,
	254,255,254,254,254,253,253,252,251,250,248,247,245,243,242,240,
	237,235,233,230,227,225,222,219,216,212,209,205,202,198,195,191,
	187,183,179,175,171,166,162,158,154,149,145,140,136,131,127,123,
	118,114,109,105,100,96,92,88,83,79,75,71,67,63,59,56,52,49,45,42,
	38,35,32,29,27,24,21,19,17,14,12,11,9,7,6,4,3,2,1,1,0,0,0};
	
unsigned char invertedpulse[180] = 
	{255,254,254,254,253,253,252,251,250,248,247,245,243,242,240,
	237,235,233,230,227,225,222,219,216,212,209,205,202,198,195,
	191,187,183,179,175,171,166,162,158,154,149,145,140,136,131,
	127,123,118,114,109,105,100,96,92,88,83,79,75,71,67,
	63,59,56,52,49,45,42,38,35,32,29,27,24,21,19,
	17,14,12,11,9,7,6,4,3,2,1,1,0,0,0,
	0,0,0,0,1,1,2,3,4,6,7,9,11,12,14,
	17,19,21,24,27,29,32,35,38,42,45,49,52,56,59,
	63,67,71,75,79,83,88,92,96,100,105,109,114,118,123,
	127,131,136,140,145,149,154,158,162,166,171,175,179,183,187,
	191,195,198,202,205,209,212,216,219,222,225,227,230,233,235,
	237,240,242,243,245,247,248,250,251,252,253,253,254,254,255};

int main(void)
{
	DDRB = 0b00011011;      //set port 3 to input and others to output
	PORTB = 0b00000100;		//set port 3 to pullup input
	GIMSK = 0b01000000;	    //enable INT0 pin
	MCUCR = 0b00000010;     //falling edge of INT0 generates interrupt
	TCCR1 = 0b000000110;	//setup timer 1 with x32 prescaler
	TCCR0A = 0b00000000;	//setup timer 0 with x1024 prescaler
	TCCR0B = 0b00000101;	//and 'normal' operation mode
	TIMSK = 0b00000010;      //set interrupt on timer 0 overflow (255 counts)
	OCR1C = 0b11111111;		
	sei();                   //enable interrupts
	
	while(1)
	{ 
		switch(state)
		{
			case 0 :     //solid mode
				PORTB = 0b00011111;
				break;

			case 1 :
				PORTB = 0b00001100;
				break;
				
			case 2 :		//Gentle alternation mode
				PORTB = 0b00000100;   //Turn off all LEDs
				if (leds == 0)
				{
					OCR1A = 0b00000000;  //Initialise PWM A at zero
					GTCCR = 0b00000000;  //Disable PWM B
					TCCR1 = 0b01100110;  //Enable PWM A; Only non-inverted output connected
					for (timebase = 0; timebase < 1;) //Keep PWM value for 1024 clock cycles (~10ms)
					{
						
						OCR1A = pulse[counter];  //Update PWM value
					}
					if (counter == 179)  //If end of array reached
					{
						counter = 0; //Reset array index counter
						leds++;		 //Next LED will light
						if (leds == 4)
						{
							leds = 0;	//Go back to 1st LED
						}
					}
				}
				/*******************************
				PULSE HAPPENS BETWEEN THESE TWO 
				*******************************/
				if (leds == 1)
				{
					OCR1A = 0b11111111;	//Initialise PWM A at 255 (max)
					GTCCR = 0b00000000;	//Disable PWM B
					TCCR1 = 0b01010110; //Enable PWM A with inverted & normal output
					DDRB = 0b00011001;	//Disable non-inverted output A; only inverted is on
					for (timebase = 0; timebase < 1;) //Keep PWM value for 1024 clock cycles (~10ms)
					{
						OCR1A = invertedpulse[counter]; //Inverted PWM values as output is inverted
					}
					if (counter == 179)		//
					{						//
						counter = 0;        //This lot same as before
						leds++;				//
						if (leds == 4)		//
						{					//
							leds = 0;		//
						}
					}
				}
				if (leds == 2)
				{
					OCR1B = 0b00000000; //Initialise PWM B at zero
					TCCR1 = 0b000000110; //Disable PWM A & disconnect outputs
					GTCCR = 0b01100000;	//Enable PWM B; only non-inverted output
					for (timebase = 0; timebase < 1;)
					{
						OCR1B = pulse[counter];			
					}
					if (counter == 179)			//
					{							//
						counter = 0;			//
						leds++;					//Same as before
						if (leds == 4)			//
						{						//
							leds = 0;			//
						}
					}
				}
				/*******************************
				AND THESE TWO 
				*******************************/
				if (leds == 3)
				{
					DDRB = 0b00001011;	//Disable non-inverted output B; only inverted is on
					OCR1B = 0b11111111;	//Initialise PWM B at 255 (max)
					GTCCR = 0b01010000;	//Enable PWM B with inverted & normal output
					TCCR1 = 0b00000110;	//Disable PWM A
					for (timebase = 0; timebase < 1;)
					{
						OCR1B = invertedpulse[counter];
					}
					if (counter == 179)
					{
						counter = 0;
						leds++;
						if (leds == 4)
						{
							leds = 0;
						}
					}
				}					
				counter++;
				break;

			case 3 :
				PORTB = 0b00010100;
				break;	
		}
	}
}


ISR(TIMER0_OVF_vect) //interrupt triggered with 'timer 1 overflow' vector
{
 timebase++;
}

ISR(INT0_vect)  //interrupt triggered with 'INT0' vector
{
	if(state == 3)
	{
		state = 0;
	}

	else
	{
		state++;
	}
	TCCR1 = 0b000000110;
	GTCCR = 0b00000000;
	DDRB = 0b00011011;
}

Thanks, Ion

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

Why repeat (copy) this all over the place...put it in one common place that all get to....shorter code is easier to trace what is going on or going wrong.

 

					if (counter == 179)
					{
						counter = 0;
						leds++;
						if (leds == 4)
						{
							leds = 0;
						}

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

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

Just wanted to include the whole picture incase the issue was outside that loop. I get the feeling it's a harware thing related to changing the timer control or DDR regsiter. I'll reread the datasheet.

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

 

CmdrZin wrote:

May want to put your 

char pulse[180]

in PROGMEM to save RAM space.

Unless it is C++ the modern alternative to PROGMEM is "__flash" and is preferable in almost every way (as it does not need pgm_read*() dereference).

 

Also note that for sine it's unlikely that you need [180] samples - that suggests a half wave. In fact in #22 you appear to have both halves - so 360 in total. You only need a quarter wave (90 degrees) to have all the values.

 

In this:

 

1, 2, 3 and 4 are really the same set of numbers. Suppose the 90 samples from 0 to 90 (section "1") are something like:

 

0,0,0,0,1,1,2,3,4,6,7,9,11,12,14,17,19,21,24,27,29,32 ... 235,237,240,242,243,245,247,248,250,251,252,253,253,254,254,255

then "2" is just the same numbers counting backwards through the sample array.

 

Section "3" is the same as "1" with a minus sign.

 

Section "4" is the same as "2" with a minus sign.

 

So given 90 samples to make the "1","2","3","4" sequence take the 90 samples for 1/4 of the entire wave and for each lot of 90:

 

"1" count up through the array

"2" count down through the array

"3" apply - and count up through the array

"4" apply - and count down through the array

 

(it's actually simpler to do than to describe!)

 

PS Oh and forgot to say it doesn't have to be 90 for each quadrant. At present you are using 360 bytes for the two halves. Consider maybe using 256 bytes for 1/4 of the array so your entire signal will be 1024 samples but using just 256 bytes (less) storage. It will have a much more accurate definition as it is 1024 rather than 360. The one downside is that if you use more samples then you may limit your upper frequency at playback time - this won't matter for an LED.

 

Oh and note that LED brightness is not linear so you may want to account for that too.

Last Edited: Mon. Jan 11, 2021 - 11:02 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

comment to #25

care should be take when you go from one 1/4 to an other.

The table should NOT go from 0 to 90 deg (both included), the easy way is to have the values  shifted 1/2 a number!

 

so if we say you want a sine wave of 180 steps (2 deg resolution) the table is 45 numbers the first value in the table should the value for 1 deg and the last for 89 deg. 

(for ease the table size should be a power of 2)

   

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
for (timebase = 0; timebase < 1;)

timebase is an int, and is modified in the interrupt so you will have to protect those reads whether its a problem now or not (eventually it will be a problem). Or if the var only needs to be 8bits, then make it 8bits and will no longer need to be protected.

 

The example in #15 makes use of functions, and you will be better off using more functions so you can see the logic of your code more easily. You can also see in that example 32 levels of brightness were chosen as you cannot see the 256 levels anyway. A formula is out there to produce a brightness table (0-31 -> 0-255), either a CIE 1931 formula, or gamma formula (happens to be close to the CIE 1931 formula), or make up your own by trial and error. You can work your way up and down the brightness table and do not need to duplicate both slopes.

Last Edited: Mon. Jan 11, 2021 - 12:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

BTW I was just looking at some of the numbers in your data tables:

0,0,0,0,1,1,2,3,4,6,7,9,11,12,14,17,19,21,24,27,29,32 ... 235,237,240,242,243,245,247,248,250,251,252,253,253,254,254,255

There is something very wrong there. Just look at the shape of sine():

 

start from 0 that grows rapidly then begins to tail off as it reaches 255. No way does it go 0, 0, 0, 0, 1, 1, etc

 

In fact in Excel this is the very start of the sequence - degrees in col A, sin() in col B:

 

 

So I'd start by looking at where your sample values came from.

 

(or have they already been adjusted for the non-linearity of LED light perhaps?)

 

For the record those Excel values are:

0, 4, 8, 13, 17, 22, 26, 31, 35, 40, 44, 48, 53, 57, 61, 66, 70, 74, 79, 83, 87, 91, 
95, 100, 104, 108, 112, 116, 120, 124, 128, 131, 135, 139, 143, 146, 150, 154, 157, 
161, 164, 167, 171, 174, 177, 181, 184, 187, 190, 193, 196, 198, 201, 204, 207, 209, 
212, 214, 217, 219, 221, 223, 226, 228, 230, 232, 233, 235, 237, 238, 240, 242, 243, 
244, 246, 247, 248, 249, 250, 251, 252, 252, 253, 254, 254, 255, 255, 255, 255, 255

 

Last Edited: Mon. Jan 11, 2021 - 01:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I generated the values using a little python script; it starts at 90 or 180 degrees (for inverted or no inverted), goes one full cycle, and is halved and offset to be above 0.

 

import math

i=90
counter=0
while i<450:
    print(str(int(255*(0.5+0.5*math.sin(math.radians(i))))), end=",")
    counter=counter+1
    if counter==15:
        print("")
        counter=0
    i = i+2

It gives a smooth start and peak, and I doubt it's the reason for the 1ms glitch. I think it's a result of changing DDR bits. I'll write a simple program to flip some DDR bits, hook it up to scope and observe.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
 print(str(int(255*(0.5+0.5*math.sin(math.radians(i))))), end=",")

The offset by 0.5 is curious. What is the intention there? In Python if I simplistically do the same as I did in Excel:

>>> for ang in range(0,89):
...     print(int(256 * math.sin(math.radians(ang))))
...
0
4
8
13
17
22
26
31
35
40
44
48
53
57
61
66
70
74
79
83
87
etc....

then I get pretty much the same result as Excel. I must be missing why it is that creating sin() samples from 0..89 degrees is not this simple?

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

clawson wrote:

Unless it is C++ the modern alternative to PROGMEM is "__flash" and is preferable in almost every way (as it does not need pgm_read*() dereference).

 

"For each named address space supported by avr-gcc there is an equally named but uppercase built-in macro defined. The purpose is to facilitate testing if respective address space support is available or not"

 

I'll continue to use PROGMEM thank you very much.

"If you find yourself in an even battle, you didn't plan very well."
https://www.gameactive.org
https://github.com/CmdrZin

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

CmdrZin wrote:
I'll continue to use PROGMEM thank you very much.
I'm talking about this:

const __flash char str[] = "Hello World";

int main(void) {
    int i = 0;
    while (str[i] != '\0') {
        UART_sendchar(str[i++]);
    }
}

which generates:

0000008c <main>:
}

const __flash char str[] = "Hello World";

int main(void) {
  8c:	cf 93       	push	r28
  8e:	df 93       	push	r29
  90:	c9 e6       	ldi	r28, 0x69	; 105
  92:	d0 e0       	ldi	r29, 0x00	; 0
  94:	88 e4       	ldi	r24, 0x48	; 72
    int i = 0;
    while (str[i] != '\0') {
        UART_sendchar(str[i++]);
  96:	f7 df       	rcall	.-18     	; 0x86 <UART_sendchar>

const __flash char str[] = "Hello World";

int main(void) {
    int i = 0;
    while (str[i] != '\0') {
  98:	fe 01       	movw	r30, r28
  9a:	21 96       	adiw	r28, 0x01	; 1
  9c:	85 91       	lpm	r24, Z+
  9e:	81 11       	cpse	r24, r1
  a0:	fa cf       	rjmp	.-12     	; 0x96 <main+0xa>
        UART_sendchar(str[i++]);
    }
}

versus:

#include <avr/pgmspace.h>
const PROGMEM char str[] = "Hello World";

int main(void) {
    int i = 0;
    while (pgm_read_byte(&str[i]) != '\0') {
        UART_sendchar(pgm_read_byte(&str[i++]));
    }
}

which generates:

0000008c <main>:
}

#include <avr/pgmspace.h>
const PROGMEM char str[] = "Hello World";

int main(void) {
  8c:	cf 93       	push	r28
  8e:	df 93       	push	r29
    int i = 0;
    while (pgm_read_byte(&str[i]) != '\0') {
  90:	c8 e6       	ldi	r28, 0x68	; 104
  92:	d0 e0       	ldi	r29, 0x00	; 0
  94:	fe 01       	movw	r30, r28
  96:	84 91       	lpm	r24, Z
  98:	88 23       	and	r24, r24
  9a:	41 f0       	breq	.+16     	; 0xac <main+0x20>
        UART_sendchar(pgm_read_byte(&str[i++]));
  9c:	fe 01       	movw	r30, r28
  9e:	84 91       	lpm	r24, Z
  a0:	f2 df       	rcall	.-28     	; 0x86 <UART_sendchar>
  a2:	21 96       	adiw	r28, 0x01	; 1
#include <avr/pgmspace.h>
const PROGMEM char str[] = "Hello World";

int main(void) {
    int i = 0;
    while (pgm_read_byte(&str[i]) != '\0') {
  a4:	fe 01       	movw	r30, r28
  a6:	84 91       	lpm	r24, Z
  a8:	81 11       	cpse	r24, r1
  aa:	f8 cf       	rjmp	.-16     	; 0x9c <main+0x10>
        UART_sendchar(pgm_read_byte(&str[i++]));
    }
}

so why on earth would you choose to involve all the pgmspace.h / pgm_read_byte() nonsense simply to use "PROGMEM" when avr-gcc has now had native support for "__flash" added for some time. You will notice that when __flash is used there's no need for .h inclusion to access macros like PROGMEM and pgm_read_byte(). __flash is a modifier recognised by the compiler itself - it does not need header support to be used. What it does is mark the bytes in str[] as being located in flash (not RAM) and therefore needing LPM not LD access. So when you do something like:

  UART_sendchar(str[i++]);

it knows to generate an LPM access. It does not need a cumbersome pgm_read_byte() macro added to force the LPM.

 

In the same major change that Georg Johan-Lay did to add __flash he also added things like __memx which are "super pointers". These are native 24 bit pointers in which 23 bits are used for addressing and the upper bit is a flag to say whether the data is flash or RAM based so that at the point of dereference the compiler knows to use LPM not LD.

 

While you could remain in the legacy world of the PROGMEM/pgm_read_byte world (which was all basically a "hack" to some how force LPMs) you could recognize that the compiler has developed and got seriously better in the last few years (I think __flash entered at 4.6 so subsequent versions have the support).

 

Unfortunately while those who control the GCC C compiler development were happy to allow the addition of __flash to support Harvard in avr-gcc the C++ dev team are more stringent about "standards compiance" and would not admit the change. So C++ programmers are stuck with PROGMEM and the cumbersome pgm_read*() stuff as the only way to locate and access data in flash.

 

The user manual for the compiler:

 

https://gcc.gnu.org/onlinedocs/g...