RGB LED PWM Control

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

Hello, I'm new with AVR's and I'm learning C programing for AVR. I just finished my first project which is is a led chaser with my own pattern, so i can say i learned a bit how avr's work. Now i would like to experiment with PWM control over RGB LED's. I got myself an RGB led with 4 pins, and an AVR butterfly. I did a bit of research on the web and all of the projects for AVR i found were written in ASM, so my questions are:
1: Can a code written in ASM be converted to C ?
2: Can you give me a link to a project about pwm control on RGB LED written in C ?

thank you, Florin

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

1. Yes, but in ASM tricks are possibly that have no direct equivalent in C. Timing critical stuff might be difficult it every single instruction counts; you never really know what the compiler generates.

2. No, I don't know any.

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

Can I suggest that you read through some of the Tutorials, maybe the 'Newbie's guide to AVR Timers' and getting just a single LED under PWM control before moving to 3-channels (e.g. Red, Green and Blue).

When you run out of PWM timers (which you will do at some point) for controlling your multiple LEDs, then you're going to have to consider controlling them more directly.

I use the following method which isn't actually 'PWM', but it works very well for LEDs...
Take an 8-bit byte that holds the required intensity of the LED and turn on/off the LED in a timed pattern according to the binary value of that intensity.
E.g.
For an intensity value of 187 (binary 10111011)...
switch the LED with the following pattern:
ON for 128 ticks
OFF for 64 ticks
ON for 32 ticks
ON for 16 ticks
ON for 8 ticks
OFF for 4 ticks
ON for 2 ticks
ON for 1 tick

Whilst this might seem a little complex for a single-LED (certainly more so than just setting up a PWM output), it can scale very well to multiple LEDs.

I simply pre-calculate the pattern needed on each port/pin for each of the 8 time-slices. All the timer interrupt then needs to do is set the port-value and set the next interrupt delay. This means that the interrupt is quick and simple, hence the 'tick' timebase can be nice and short.
It can even be used to charlieplex/multiplex LEDs if neccesary; I've had 2 RGB LEDs hanging off a tiny13.

(Hopefully someone might move this to a more appropriate forum!)

Nigel Batten
www.batsocks.co.uk

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

That looks like an interesting variation of regular old 'software pwm' where you turn every thing off, set the loop counter to 0, then every loop check if the counter is up to the R value, turn on the R, counter = G value, turn on G, etc. If you count up to 64 (6 bits of adjustment range) 70 times a sec, the program should loop ~4000 times a sec

Imagecraft compiler user

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

thx for your answers, and I'm waiting for more advices from you if possible :)

sorry, i thought this category is right for my thread

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

I've just done a software PWM for an RGB LED for a tiny15. I does not have memory, only registers and a hardware stack, so C is not practical :-(.

However, I set up a timer to fire with 25kHz. With an 8-Bit counter this gives a 100Hz PWM-interval. It decrements an 8-bit counter for each LED color and turns the LED off if the counter is zero. Every 256 interrupts it reloads the counters from the brightness setting and turns the LED's on.

The main routine is used to change the brightness settings to get interesting light changes.

Markus

Markus

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

Markus is your code written in C ? because i could use other AVR's

Florin

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

Quote:

Markus is your code written in C ?

I don't think so as he hinted with this
Quote:

C is not practical [for an AVR that has no RAM]

I am not sure all compilers have difficulties with RAM-less AVRs, so don't take this as a statement from me that I agree with MArkus, but as a hint to the fact that he actually didn't use C for this.

Anyhow, PWMing LEDs does not require high PWM periods (frequencies) as your brain will integrate the blinking. 100 Hz might well be enough. And then you'd have little problem with exact timings, so generally speaking thois would be more than doable with C.

Why not start out with a single (eg one-colour) LED and master the PWM technique there? Then move on to three colours. After all, a RGB LED is only three LEDs of different colours in a monolithic housing...

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

ok, I'll take the advice and start with single LED, but still i need a C example project.

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

State which AVR, what xtal speed, and which c compiler, maybe some kind soul will taior one of their existing projects for your specific example

Imagecraft compiler user

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

You can do the PWM either by using the PWM modes of timers (so read the timer tutorials in the tutorial forum) or you can "wiggle" a pin in PWM mode yourself but then you still need some basic timing tick (so read the timer tutorials in the tutorial forum).

For the latter approach:
You need to produce a PWM signal with a period of, say, 100 Hz (or more). Let's also assume that you want a PWM resolution of 100.

This means that you need to get some basic timer tick working at 100x100 Hz = 10 KHz. Use a timer for that. (Read the tutorials...).

Now every time the timer generates a tick you set a flag in the ISR.

Main loop in the program looks at the flag and holds a count of how many it has seen. You code so that this counter wraps to 0 at 100. Every time it wraps it sets the pin you are using for your PWM signal high. You also have a duty-cycle value you want to generate, and every time the count variable passes this value you set the PWM pin low.

Sketchy:

volatile int timerTick;

ISR(...) {
   // This ISR will fire with a frequency of 10KHz
   // (value not critical, could be a higher frequency)
   timerTick = 1;
}


int main()
{
   int tickCount;
   int dutyCycle = 42;

   //  Set up the timer here (as you have learned from the timer tutorials) to generate interrupts with 10 KHz or so

   while (1) {
      if (timerTick == 1) {
         tickCount++;
         timerTick = 0;

         if (tickCount >= 100) {
            // Wrap and set the pin high
            tickCount = 0;
            PORTx |= 1<<PWM_PIN;
         }
         else if (tickCount == dutyCycle) {
            // Set the pin low
            PORTx &= ~(1<<PWM_PIN);
         }
      }
   }
}

When this works you can move on to create code that generalizes this for n pins with a shared PWM frequency, but woith individual duty cycles.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

jayjay1974 wrote:
1. Yes, but in ASM tricks are possibly that have no direct equivalent in C. Timing critical stuff might be difficult it every single instruction counts; you never really know what the compiler generates.
I don't want to hijack the thread, but I will ask, what effect can you do with PWM for an LED that would be visible to a human and can't be done in C? I agree that if you push a particular AVR to its maximum that you can do things with asm that you cant do with C, but since we can't perceive lights flickering at greater than about 60 times per second, I can't imagine any visible effect that you couldn't do in C and have the AVR twiddling its thumbs most of the time.

Smiley

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

I think nothing ;) More a general comment not specifically related to LED PWM.

I'd put all the PWM stuff inside the ISR, and do other modulation effects in the main loop.

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

Smiley, do you remember who posted the holiday light project for Christmas 2006 IIRC? That was a fully-worked example for RGB driving, and in addition made a string of lights. IIRC it was in Off-Topic Forum.

Quote:
what effect can you do with PWM for an LED that would be visible to a human and can't be done in C?

That is always the interesting challenge. :evil: IF the algorithm makes great use of Carry or Rotate (C has no concept of those), then I'll concede. Similarly, special arithmetic widths such as 24 bits--doesn't jesper's miniDDS use that? But in general one can almost always coerce C to get the job done, especially in a small contained app like this.

If there is a great advantage to using carry (say), then one can just drop into inline ASM for the critical sequence.

Lee

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

Quote:
Smiley, do you remember who posted the holiday light project for Christmas 2006 IIRC?

That was miamicanes. [No posts from him since the beginning of this year.] Forum search for "rgb christmas" leads to
http://www.avrfreaks.net/index.p...
http://www.avrfreaks.net/index.p...

Hmmm--I thought the firmware was posted, but I can't seem to find it.

Lee

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

not sure if miamicanes ever posted his code. but gmprops code is on that other thread thread (an early version of it) That code is not for the AVR's PWM though, it's for working with TLC5940 external LED pwm chips.

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

thx for all your help, I'll study a bit more and I'll come back if i have questions.

Florin

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

if the part that is stumping you is the timer interrupt, this program is simple enough that the RG and B will get bright and dim just by running in a tight loop. You dont care if it runs at 70 times a sec or 700 or 7000 as long as it works.

Imagecraft compiler user

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

youritronics wrote:
Markus is your code written in C ? because i could use other AVR's

No, my code is in assembler. In fact, for the Software-PWM part, assembler does very nicely. But for the part where I'm trying to find nice algorithms to change the colors around C would be much better.

While some C compiler do work for the tiny15, gcc/winavr does not and I thought a tour with assembler only sharpens my mind :-).

I you want I'll post the source.

Markus

Markus

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

JohanEkdahl wrote:
You can do the PWM either by using the PWM modes of timers (so read the timer tutorials in the tutorial forum) or you can "wiggle" a pin in PWM mode yourself but then you still need some basic timing tick (so read the timer tutorials in the tutorial forum).

For the latter approach:
You need to produce a PWM signal with a period of, say, 100 Hz (or more). Let's also assume that you want a PWM resolution of 100.

This means that you need to get some basic timer tick working at 100x100 Hz = 10 KHz. Use a timer for that. (Read the tutorials...).

Now every time the timer generates a tick you set a flag in the ISR.

Main loop in the program looks at the flag and holds a count of how many it has seen. You code so that this counter wraps to 0 at 100. Every time it wraps it sets the pin you are using for your PWM signal high. You also have a duty-cycle value you want to generate, and every time the count variable passes this value you set the PWM pin low.

This trick doesn't work quite as elegantly for a 100-count-resolution PWM, but it's pretty cute for an even-power-of-two resolution like 256:

Every sampling interval, you add the "duty cycle" setting to an accumulating total. Use the carry from this addition as your PWM signal.

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

Quote:

This trick doesn't work quite as elegantly for a 100-count-resolution PWM, but it's pretty cute for an even-power-of-two resolution like 256:

Agree with the aestethical remarks ("elegant","cute"). Still, with the problem at hand the frequency of the PWM need not be so high that we are pressed for time. How many cycles do you actually gain? 10 or so?

Never obfuscate things more than necessary. When not in need to optimize for code performance - optimize for maintenance.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

condemned wrote:

I use the following method which isn't actually 'PWM', but it works very well for LEDs...
Take an 8-bit byte that holds the required intensity of the LED and turn on/off the LED in a timed pattern according to the binary value of that intensity.
E.g.
For an intensity value of 187 (binary 10111011)...
switch the LED with the following pattern:
ON for 128 ticks
OFF for 64 ticks
ON for 32 ticks
ON for 16 ticks
ON for 8 ticks
OFF for 4 ticks
ON for 2 ticks
ON for 1 tick

Whilst this might seem a little complex for a single-LED (certainly more so than just setting up a PWM output), it can scale very well to multiple LEDs.

I simply pre-calculate the pattern needed on each port/pin for each of the 8 time-slices. All the timer interrupt then needs to do is set the port-value and set the next interrupt delay. This means that the interrupt is quick and simple, hence the 'tick' timebase can be nice and short.
It can even be used to charlieplex/multiplex LEDs if neccesary; I've had 2 RGB LEDs hanging off a tiny13.

Can you publish your C code for this?
It is similar, maybe the same idea, with the so-called "bit angle modulation" proposed by Artistic Licence.

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

smileymicros wrote:
jayjay1974 wrote:
1. Yes, but in ASM tricks are possibly that have no direct equivalent in C. Timing critical stuff might be difficult it every single instruction counts; you never really know what the compiler generates.
I don't want to hijack the thread, but I will ask, what effect can you do with PWM for an LED that would be visible to a human and can't be done in C? I agree that if you push a particular AVR to its maximum that you can do things with asm that you cant do with C, but since we can't perceive lights flickering at greater than about 60 times per second, I can't imagine any visible effect that you couldn't do in C and have the AVR twiddling its thumbs most of the time.

Smiley


Although you won't see 60Hz flicker looking straight at a LED that's diffused over a moderately large area, you certainly will in peripheral vision, or when your eye moves, more so if it isn't diffused as the source size is smaller.
You need to be at around 200Hz to avoid this in most situations.
Couple that to the fact that if you want a apparently linear range of brightnesses and no obvious stepping between levels, you need to apply a nonlinear gamma correction function to compensate for the eye's nonlinear perception of brightness. In practice if you want 256 brightness levels (which is about the minimum for step-free fades), you need about 11-12 bits of PWM resolution to do it well. This gets somewhat complicated to do in software for more than a few channels.

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

In my case (tiny16@1.6Mhz internal clock) the CPU budget looks as follows:
- PWM interval: 100Hz
- Resolution: 8bit - 256 steps
-> Interrupt frequency is 25kHz
-> 80 CPU cycles between interrupts

My assembler interrupt routine has 28 instruction in its longest path, consuming about 36 cycles in worst case. This gives some margin for using C (consumes probably some more instructions) or faster PWM (200Hz is possible).

If I'd use a more modern CPU (tiny25@8Mhz) I'd gain a factor 5 in speed and could run 10bit resolution at 200Hz. For Mikeharrioson's 'good' PWM (12bit@200Hz) you'd need to use hardware PWM (819kHz interrupt rate...).

Markus

Markus

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

ok, Markus what is the difference from software pwm and hardware pwm ?

Florin

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

Hardware PWM: You set up a timer and it's PWM support and connect your LED/whatever to the timer's output compare output pin. Once set the PWM the PWM output runs on its own using hardware counters and requires no CPU intervention. The entire PWN operation is done by the timer hardware. The PWM speed is entirely limited by the clock source/options of the timer you are using. Some AVR's have special clocks for fast PWM, for example the AT90PWM can feed 64Mhz to the timer for PWM.

Software PWM: You set up a timer to run an interrupt at a certain frequency. Your interrupt routine will do the counting and set pins high or low. You have a lot more flexibility (number of channels, pins used, etc.), but the speed is limited to the run-time of your interrupt routine.

Markus

Markus

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

One more remark: Usually you have one or two PWM channels/output compare pins per timer. On an average AVR it is three (OC0a, OC1a and OC1b), my tiny15 had only one. One pin/channel is not enough for three colors, so I choose software PWM, where I'm not tied to specific hardware pins/functions.

Markus

Markus

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

Mikeharrison wrote:
smileymicros wrote:
jayjay1974 wrote:
1. Yes, but in ASM tricks are possibly that have no direct equivalent in C. Timing critical stuff might be difficult it every single instruction counts; you never really know what the compiler generates.
I don't want to hijack the thread, but I will ask, what effect can you do with PWM for an LED that would be visible to a human and can't be done in C? I agree that if you push a particular AVR to its maximum that you can do things with asm that you cant do with C, but since we can't perceive lights flickering at greater than about 60 times per second, I can't imagine any visible effect that you couldn't do in C and have the AVR twiddling its thumbs most of the time.

Smiley


Although you won't see 60Hz flicker looking straight at a LED that's diffused over a moderately large area, you certainly will in peripheral vision, or when your eye moves, more so if it isn't diffused as the source size is smaller.
You need to be at around 200Hz to avoid this in most situations.
Couple that to the fact that if you want a apparently linear range of brightnesses and no obvious stepping between levels, you need to apply a nonlinear gamma correction function to compensate for the eye's nonlinear perception of brightness. In practice if you want 256 brightness levels (which is about the minimum for step-free fades), you need about 11-12 bits of PWM resolution to do it well. This gets somewhat complicated to do in software for more than a few channels.

I guess then that the solution would be to use:
he AVR’s High-speed PWM
http://www.avrfreaks.net/index.p...

OR one of the AT90PWMxx devices that IIRC do a PLL for a 64 MHz PWM clock.

Smiley

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

tiny25/45/85 have a PLL too. But only for one timer with 2 PWM outputs.

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

Mikeharrison wrote:
In practice if you want 256 brightness levels (which is about the minimum for step-free fades), you need about 11-12 bits of PWM resolution to do it well. This gets somewhat complicated to do in software for more than a few channels.

Complicated, yes perhaps. But it is perfectly doable. I did it back in 2003 with a 16 MHz AVR. It could control up to 20 channels in a Mega8/16. I still have the old webpage from back then (16 channel version):
http://www.ejberg.dk/ledfade2/index.html

Sorry, but this one is also in asm, not C.

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

The Tiny26 also has the PLL for 64MHz...
I just got a few yesterday and am playing with the PWM function...
I couldn't find the AT90PWM here... I may contact the local Atmel dealer and see if I can get some...

Anyhoo...
I found some broken code online an got it working to fade a 2 color red/green led...


// attiny26 
//outputs PB2 and PB3
//input PB6
 
#define F_CPU 1000000UL  


#include 
#include 
#include 
#include 



enum { UP, DOWN };

volatile unsigned int i =0;
volatile unsigned int delay = 5;
volatile unsigned int pwm;
volatile unsigned int direction;


ISR (TIMER1_OVF1_vect){


	for (i=0;i 4 Mhz
//  TCCR1B |= _BV(CS11) | _BV(CS13) | _BV(CS13) | _BV(CS13); // all for hi prescaler
  TCCR1B |= _BV(CS10);//CS13=0, CS12=0, CS11=0, CS10=1 => 64 Mhz?

// Enable Timer1 OVF interrupt
 TIMSK |= _BV(TOIE1);
//   timer_enable_int (_BV (TOIE1));

    // enable interrupts
    sei ();
}

int main (void){

    ioinit ();

    // loop forever, the interrupts are doing the rest

    for (;;){;}

    return 0;
}

Sorry, it's a bit of a mess...

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

Another addition...

I was searching for a way to increase the resolution of PWM generated sine....

And I found this very interesting paper about using adders instead of counters...

http://www.itee.uq.edu.au/~aupec/aupec04/papers/PaperID230.pdf

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

pop48m wrote:
Can you publish your C code for this?

OK, Here's some code for an ATMega8...
The usual 'not suitable for any purpose' caveats apply.

/*
a demonstration of bit-width LED dimming.
Nigel Batten
May 2008

Written for a 'factory setting' Mega8
highbyte: 0xd9
lowbyte:  0xe1
e.g. running on 1Mhz internal RC oscillator.

requires the following:
An 8-bit timer with a CTC mode.
10 bytes of ram free.

Can be extended to handle more ports.
Can be modified to mask off pins used for other purposes.

*/

#include 
#include 

// define the processor speed if it's not been defined at the compilers command line.
#ifndef F_CPU
#define F_CPU 1000000
#endif

volatile uint8_t g_timeslice[8] ; // one byte for each bit-position being displayed on a port.
volatile uint8_t g_tick = 0;
volatile uint8_t g_bitpos = 0; // which bit position is currently being shown

void led_init( void ) ;
void led_encode_timeslices( uint8_t a[] );



int main (void)
{
uint8_t brightness[8]; // brightness for each LED on port D.

    led_init();
    led_encode_timeslices( brightness ) ;
    sei();


// now a (simple) demonstration...
// In the real-world, you'd probably want to decouple the
// animation speed from the LED flicker-rate.
uint8_t slowtick = 50;
uint8_t position = 0 ;
    while(1)
    {
        while(g_tick==0){ /*wait for g_tick to be non-zero*/ }
        g_tick = 0 ; //consume the tick
        // make each of the LEDs slightly dimmer...
        for ( uint8_t index = 0 ; index < 8 ; index++ )
        {
            if (brightness[ index ] > 0) brightness[ index ]-- ;
        }
        // once every 50 ticks, advance the head of the sweep...
        slowtick-- ;
        if (slowtick==0)
        {
            slowtick = 50;
            position++ ;
            position &= 7 ;
            brightness[ position ] = 255 ;
        }
        // and now re-encode all the timeslices...
        led_encode_timeslices( brightness ) ;
    }
    return(0);
}


void led_init( void )
{
    PORTD = 0x00 ; // All outputs to 0.
    DDRD = 0xff ; // All outputs.
    
    TCCR2 |= (1<<WGM21) ; // set the timer to CTC mode.
    TCCR2 |= ((1<<CS21)|(1<<CS20)) ; // use clock/32 tickrate
    g_bitpos = 0 ;
    OCR2 = 1 ; // initial delay.
    TIMSK |= (1 << OCIE2) ; // Enable the Compare Match interrupt
}


// encode an array of 8 LED brightness bytes into the pattern
// to be shown on the port for each of the 8 timeslices.
void led_encode_timeslices( uint8_t intensity[] )
{
    uint8_t portbits = 0;
    uint8_t bitvalue ;
    
    for ( uint8_t bitpos = 0 ; bitpos < 8 ; bitpos++ )
    {
        portbits = 0;
        bitvalue = 1 ;
        for ( uint8_t ledpos = 0 ; ledpos < 8 ; ledpos++ )
        {
            if (intensity[ ledpos ] & (1 << bitpos)) portbits |= bitvalue ;
            bitvalue = bitvalue << 1 ;
        }
        g_timeslice[ bitpos ] = portbits ;
    }
}

ISR( TIMER2_COMP_vect )
{
    g_bitpos ++ ;
    g_bitpos &= 7;
    PORTD = g_timeslice[ g_bitpos ] ;
    // now set the delay...
    TCNT2 = 0;
    OCR2 <<= 1 ;
    if (g_bitpos == 0) OCR2 = 1 ; // reset the compare match value.
    if (g_bitpos == 7) g_tick = 1 ; // give the main loop a kick.
}

I think that the interrupt handler could be tightened up, but it serves it's purpose as a demo.

pop48m wrote:
It is similar, maybe the same idea, with the so-called "bit angle modulation" proposed by Artistic Licence.
I've not seen that before - Yes - it's the same thing. There's a nice summary of different lighting control methods in their appnote011 - worth a google.

Nigel Batten
www.batsocks.co.uk

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

Just one word about nonlinearity and gamma correction. This is only needed for linear brightness change perception and not for color mixing resp. color changing applications. In fact it's absolutely contraproductive to use nonlinear correction curves (exponential or whatever) for the three color values in RGB-color-mixing applications. Color perception is dependent on the brightness ratios of the three different colors (R,G and B) and not on their absolute values.
So for color changing applications you get far better and smoother visual effects, if you change the color brightness ratio linearly.
This in fact shows, why the HSB (or HSV) color scheme is preferred over the RGB color scheme in color changing applications. One can then simply change Hue and Saturation linearly, and if linear brightness change of the resulting color is also needed, one can implement a gamma correction curve for the Brightness value only. In the end of course one would transform the HSB- to a RGB-value to drive a RGB-LED accordingly.

Regards
Neni

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

Forgive me for bringing back an older thread but I was wondering if a relatively low overhead eight output soft' pwm method that I came up with last year might be worth a look?

Basically I use a large RAM array with one element for each PWM update interval (or duty cycle 'step') and I update the output port from that array during each interrupt interval;

/********************************************************
 *  8 channel Soft' PWM Method      Mike McLaren, K8LH  *
 ********************************************************/

unsigned char step[256];    // pwm output step array
unsigned char interval = 0; // value 0..255

void interrupt()
{ portx ^= step[interval];  // update port from array
  step[interval++] = 0;     // clear array for next cycle
.....

I refresh or rebuild the array at the end of each period by inserting one bit for each output (8 bits total) into the array at the correct index or interval, which happens to be the pwm value for each output;

/********************************************************
 *  8 channel Soft' PWM Method      Mike McLaren, K8LH  *
 ********************************************************/
unsigned char led[] = { 0, 128, 100, 50, 220, 44, 50, 50 };
unsigned char step[256];    // pwm output step array
unsigned char interval = 0; // value 0..255

void interrupt()
{ portx ^= step[interval];  // update port from array
  step[interval++] = 0;     // clear array for next cycle
  if(interval == 0)         // if end-of-period
  { step[led[0]] |= 1;      // insert b0 output toggle bit
    step[led[1]] |= 2;      // insert b1 output toggle bit
    step[led[2]] |= 4;      // insert b2 output toggle bit
    step[led[3]] |= 8;      // insert b3 output toggle bit
    step[led[4]] |= 16;     // insert b4 output toggle bit
    step[led[5]] |= 32;     // insert b5 output toggle bit
    step[led[6]] |= 64;     // insert b6 output toggle bit
    step[led[7]] |= 128;    // insert b7 output toggle bit
    interval = 0;           // reset for new period
    step[0] = ~step[0];     // initialize 1st element
  }
}

You'll notice it takes the same amount of time to insert 8 bits into a 64 element (64 pwm step) array as it does to insert 8 bits into a 256 element (256 pwm step) array.

Given the led[] array values shown above, the step[] array will contain values like this after being refreshed or rebuilt at the end of each pwm period (until led[] array values are changed in 'main');

step[interval = 000] -> '11111110', b0 output off, all others on
step[interval = 001] -> '00000000'
step[interval = 002] -> '00000000'
~~~
step[interval = 044] -> '00100000', b5 output off
step[interval = 045] -> '00000000'
step[interval = 046] -> '00000000'
step[interval = 047] -> '00000000'
step[interval = 048] -> '00000000'
step[interval = 049] -> '00000000'
step[interval = 050] -> '11001000', b3, b6, b7 outputs off
step[interval = 051] -> '00000000'
~~~

Granted, this is not the most efficient use of RAM but it does provide a relatively low overhead soft' pwm solution.

Have fun. Regards, Mike

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

...[code]

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

MikeMac wrote:
Forgive me for bringing back an older thread but I was wondering if a relatively low overhead eight output soft' pwm method that I came up with last year might be worth a look? Basically I use a large RAM array with one element for each PWM update interval (or duty cycle 'step') and I update the output port from that array during each interrupt interval;
/******************************************************** * 8 channel Soft' PWM Method Mike McLaren, K8LH * ********************************************************/ unsigned char step[256]; // pwm output step array unsigned char interval = 0; // value 0..255 void interrupt() { portx ^= step[interval]; // update port from array step[interval++] = 0; // clear array for next cycle ..... 

I refresh or rebuild the array at the end of each period by inserting one bit for each output (8 bits total) into the array at the correct index or interval, which happens to be the pwm value for each output;

/******************************************************** * 8 channel Soft' PWM Method Mike McLaren, K8LH * ********************************************************/ unsigned char led[] = { 0, 128, 100, 50, 220, 44, 50, 50 }; unsigned char step[256]; // pwm output step array unsigned char interval = 0; // value 0..255 void interrupt() { portx ^= step[interval]; // update port from array step[interval++] = 0; // clear array for next cycle if(interval == 0) // if end-of-period { step[led[0]] |= 1; // insert b0 output toggle bit step[led[1]] |= 2; // insert b1 output toggle bit step[led[2]] |= 4; // insert b2 output toggle bit step[led[3]] |= 8; // insert b3 output toggle bit step[led[4]] |= 16; // insert b4 output toggle bit step[led[5]] |= 32; // insert b5 output toggle bit step[led[6]] |= 64; // insert b6 output toggle bit step[led[7]] |= 128; // insert b7 output toggle bit interval = 0; // reset for new period step[0] = ~step[0]; // initialize 1st element } } 

You'll notice it takes the same amount of time to insert 8 bits into a 64 element (64 pwm step) array as it does to insert 8 bits into a 256 element (256 pwm step) array. Given the led[] array values shown above, the step[] array will contain values like this after being refreshed or rebuilt at the end of each pwm period (until led[] array values are changed in 'main');

 step[interval = 000] -> '11111110', b0 output off, all others on step[interval = 001] -> '00000000' step[interval = 002] -> '00000000' ~~~ step[interval = 044] -> '00100000', b5 output off step[interval = 045] -> '00000000' step[interval = 046] -> '00000000' step[interval = 047] -> '00000000' step[interval = 048] -> '00000000' step[interval = 049] -> '00000000' step[interval = 050] -> '11001000', b3, b6, b7 outputs off step[interval = 051] -> '00000000' ~~~ 

Granted, this is not the most efficient use of RAM but it does provide a relatively low overhead soft' pwm solution. Have fun. Regards, Mike

Lookup table will be WAY faster. I don't know what CPU it's on but unrolling the loop and using assembly language can slash the number of cycles for both creating and using the draw list. I don't know of an official term used for LED but for vector graphics of all kinds, draw list SEEMS to be the approved term. It's a classic trade-off. Double-buffer the draw-lists so that the frame-rate is a division of the refresh-rate. Generally, the draw-list is created as a task and used as an interrupt.

-i-

Last Edited: Sun. Dec 17, 2017 - 07:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I'm sure the op is happy to get a faster way of doing things after 9 years of waiting. wink

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

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

The fact that Adafruit has JUST launched a product within the link to this address is included is so all of the NEW people looking at it can save themselves a whole heap of pain. I must point out that they have not provided a good software example. https://github.com/adafruit/RGB-... A system that requires a 700MHz CPU (with inline ASM no less) is not so much lazy but thoughtless. The idea is to diffuse error, not to introduce it!

-i-

Last Edited: Sun. Dec 17, 2017 - 09:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've also done software PWM (C, not ASM) of RGB LEDs.  I was running 6 LEDs (18, 6x3rgb).  Clock rate 18.432 MHz.  I think the ISR took maybe 5% of available cycles.  I set up a timer interrupt that would process 255 times in under 1/60 sec (longer than that and you get flicker).  I think the ISR ran about every 60us.  Basic sequence
 

1) At beginning of 255-count sequence, turn all LEDs ON (using a temp variable - see below) and zero a counter
2) Get pointer to list of RGB values (3 per RGB)
3) For each value, if counter is greater or equal to the value, turn off that LED (in the temp variable)
4) After all LEDs have been processed, write out the new LED values from the temp variable
5) Increment counter.  If equal to 255, set to 0 and repeat sequence
 

Two notes: (a) Steps 1 and 3 assure that an LED value of 0 won't have an ON glitch for that LED.  (b) Step 5 assures that an LED value of 255 won't have an OFF glitch.
 

If you want a little more precise timing, actually write out the temp variable values to the LED at the beginning of the ISR, using values that were set the last time the ISR ran. I think that's what I did, more from an overblown perfectionism than from any necessity.

Last Edited: Sun. Dec 17, 2017 - 11:21 PM