Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

My software PWM challenge...

Status
Not open for further replies.

Hippogriff

Member
Hi all,

I've hit a hurdle with my RGB LED experiments. I have a PIC 16F684 controlling 3 RGB LED strips... so that's 9 output pins I'm using. Everything is absolutely fine when I'm setting individual pins off and on, I can get reds, greens, blues, yellows, purples, cyans and whites on each individual RGB LED strip - it all looks very nice indeed.

The issues come when I start to do software PWM on each of the pins. If I'm doing the same colours as above, i.e. ones that are - effectively - set at either 255 or 0 PWM levels, then everything is fine. The problem is when I set them to something a bit more interesting, like "Dark Orange" RGB = 255,140,0 or "Deep Pink" RGB = 255,20,147 - then I get flickering.

I think my PWM routine, setting 9 IO lines, might be too resource intensive to stop the flickering going on, because - if I reduce the routine so that it only does a single set of RGB then I can set "Dark Orange" or "Deep Pink" and the single RGB LED strip does not flicker.

If I do two RGB LED strips, then I see some flicker and, when doing all three, back to full-on flickering.

I was wondering how I might get around this and, so far, the only thing I've thought of is to change direction a bit and use three PIC 12F683s in concert, each one controlling a single RGB LED strip - which I know, from experience, can do the job perfectly well. Is that a mad idea or should I be able to finesse the current 'solution' so that I can set three RGB LED strips from a single PIC without experiencing flickering?

My simple software PWM routine (JAL) for 9 pins...

Code:
procedure OutRGB3 ( byte in pRTop,
                    byte in pGTop,
                    byte in pBTop,
                    byte in pRMid,
                    byte in pGMid,
                    byte in pBMid,
                    byte in pRBot,
                    byte in pGBot,
                    byte in pBBot
                  ) is
  var byte BrightnessTest

  for 255 using BrightnessTest loop
    if (BrightnessTest >= pRTop) then RTop = off else RTop = on end if
    if (BrightnessTest >= pGTop) then GTop = off else GTop = on end if
    if (BrightnessTest >= pBTop) then BTop = off else BTop = on end if
    if (BrightnessTest >= pRMid) then RMid = off else RMid = on end if
    if (BrightnessTest >= pGMid) then GMid = off else GMid = on end if
    if (BrightnessTest >= pBMid) then BMid = off else BMid = on end if
    if (BrightnessTest >= pRBot) then RBot = off else RBot = on end if
    if (BrightnessTest >= pGBot) then GBot = off else GBot = on end if
    if (BrightnessTest >= pBBot) then BBot = off else BBot = on end if
  end loop
end procedure
 
What frequency are you running the chip at?

Have you tried increasing the frequency?

Alternativly, you could decrease the resolution of you PWM signal.

A
 
I'm going to check the .asm and .hex when I change the code to be this... see if it is any different (more efficient) or not... really might not be, but I'm not sure how good the JAL compiler is at this kind of stuff and I'm willing to try anything...

Code:
procedure OutRGB3 ( byte in pRTop,
                    byte in pGTop,
                    byte in pBTop,
                    byte in pRMid,
                    byte in pGMid,
                    byte in pBMid,
                    byte in pRBot,
                    byte in pGBot,
                    byte in pBBot
                  ) is
  var byte BrightnessTest

  for 255 using BrightnessTest loop
    RTop = (BrightnessTest < pRTop)
    GTop = (BrightnessTest < pGTop)
    BTop = (BrightnessTest < pBTop)
    RMid = (BrightnessTest < pRMid)
    GMid = (BrightnessTest < pGMid)
    BMid = (BrightnessTest < pBMid)
    RBot = (BrightnessTest < pRBot)
    GBot = (BrightnessTest < pGBot)
    BBot = (BrightnessTest < pBBot)
  end loop
end procedure

...might be clutching at straws.

UPDATE:

Tried it, it seems that...

Code:
RTop = (BrightnessTest < pRTop)

...generates a lot less .asm (12 statements) than...

Code:
if (BrightnessTest >= pRTop) then RTop = off else RTop = on end if

...which is, seemingly, 22 statements. I reckon they're logically the same. The resulting .hex file is the same size, though. I guess I'd better try this and see if it makes any difference whatsoever. Probably won't.
 
Last edited:
I prefer to do multi-PWM in a TMR0 or TMR2 interrupt. That gives you known PWM timing. Atomsoft recently posted a good thread on his method which is basically the same as the way I do it (well it's a standard way), something like this;

(in interrupt)
pwm++;
if(pwm == 0) turn all LEDs ON

if(pwm == red) turn red LED OFF
if(pwm == green) turn green LED OFF
if(pwm == blue) turn blue LED OFF

As this turns all the LEDs ON at the one time, only once per PWM cycle you save lots of overhead. Also the == evaluations only take 2 asm instructions (MOVFW, SUBWF) to test for a match and 2 more (SKPNZ, BCF LED) to turn off the LED. The whole thing is extremely fast with 4 asm instructions per PWM channel and so you can do many PWM channels.

With your 8MHz xtal (2MHz TMR0 ticks) interrupt occurs every 128uS, so a 8bit PWM takes 128*256uS or 32mS so PWM freq is 1/0.032 = 31 Hz.

You can speed that up by using 7bit PWM, or a faster xtal, or by making the interrupt (TMR2) occur in a shorter period.

For instance if you go 20MHz xtal and TMR2 int of 128 ticks, gives an 8bit PWM at 152Hz.
 
Last edited:
I liked the idea of trying an interrupt, so thought I would give it a go. I reckon I must be missing something, though, because I get pretty obvious flicker even with a single LED set at brightness 200.

With the prescaler assigned to the Timer0 module and Prescaler Rate Select bits set to 000 (1:2) I actually get a frequency of around 7Hz (that's what my logic analyser tells me anyway).

Very visible flickering.

If I set the prescaler to be assigned to the WDT I get a frequency of 13.9Hz. I guess that's double as the rate is now 1:1.

Still visible flickering.

My interrupt routine is about as straightforward as I think it can get...

Code:
procedure TimerInterrupt is
  pragma interrupt
  
  if (PWM == 0) then
    LED1 = on
  end if
  
  if (PWM == Brightness) then
    LED1 = off
  end if

  PWM = PWM + 1
  
  -- Reset for next interrupt.
  TMR0 = 0
  INTCON_TMR0IF = 0
end procedure

If I just simply toggle the LED's state in that ISR, then I get a 'no flicker LED' - which I suppose is 50% duty cycle or, effectively, half brightness - as opposed to the 200 (~80% brightness) I'm trying to set. In that case, my logic analyser tells me that I have a frequency of 932Hz!!!

By the way, doing it the 'old fashioned way', by using a simple routine like this to do the PWM, and just call it all the time in my main program loop...

Code:
procedure OutPWM is
  var byte BrightnessTest
  
  for 255 using BrightnessTest loop
    LED1 = (BrightnessTest < 200)
  end loop
end procedure

I get a steady LED at ~80% brightness and my logic analyser tells me that I've got a frequency of 217Hz.

So, what gives? The ISR is obviously running frequently enough - a simple toggle of the LED's state and you can't tell there's any flickering. If you add a bit more code into the ISR, though, then things appear to slow right down.

I tried setting TMR0 to 127 (half way to overflow and interrupt) instead of 0 in the ISR, but that had no effect... frequency stayed at 13.9Hz, so it seems (to me) that it's the time spent in the interrupt, thus not allowing another interrupt to occur, as opposed to the frequency of the actual interrupt based on the timer.

Then... I left TMR0 being set to 127 instead of 0 and just did the toggle LED state, ending up getting a frequency of 1.76KHz.

If I'm going to get this to work for 9 LEDs, I'll need to get it to work for 1 LED first. :eek:

Bearing in mind this is the first time I've enabled a PIC timer, am I missing something very obvious? Appreciate any further pointers on this... especially if I'm starting down the wrong track with my code above.
 
I have done what, I think, are some interesting experiments with 2 PIC 16F684s today.

After my failure with the interrupt, I wanted to see if either method of doing PWM was more efficient, that - effectively - amounts to the difference in the two JAL lines:

Code:
if (BrightnessTest < Brightness) then LED1 = on else LED1 = off end if

...and...

Code:
LED1 = (BrightnessTest < Brightness)

...because both are inside a loop going from 0 to 255.

I thought the second one would be more efficient as it results in less lines of .asm, but observation seems to have proven me wrong.

Basically, I put the program on separate PICs - then just repeatedly called the PWM procedure in my main program and then measured them side-by-side with my logic analyser. As I'm constantly setting the LED to be 200 brightness, ~80%, I expected to see highs and lows from my logic analyser amounting to about 80% and 20% respectively.

I did see that, but there are slight differences.

With the first method, the total time taken in a PWM burst (the rising edge to the next rising edge) is 4.289250ms. With the second method, the total time taken is 4.595750ms... that's over 5% longer to do a PWM burst using what I thought would be the more efficient code.

I burned the first program onto the 2 PICs and measured again - they were both the same. I burned the second program onto the 2 PICs and measured again - they were both the same. That ruled out any PIC differences that might've caught me out.

It seems that the entire procedure containing...

Code:
if (BrightnessTest < Brightness) then LED1 = on else LED1 = off end if

...gets turned into...

Code:
l_pwmout
                               datalo_clr v_brightnesstest
                               clrf     v_brightnesstest
l__l100
                               movlw    200
                               subwf    v_brightnesstest,w
                               btfsc    v__status, v__z
                               goto     l__l104
                               btfsc    v__status, v__c
                               goto     l__l104
                               bsf      v__porta_shadow, 0 ; x44
                               movf     v__porta_shadow,w
                               movwf    v_porta
                               goto     l__l103
l__l104
                               bcf      v__porta_shadow, 0 ; x45
                               movf     v__porta_shadow,w
                               movwf    v_porta
l__l103
                               incf     v_brightnesstest,f
                               movlw    255
                               subwf    v_brightnesstest,w
                               btfss    v__status, v__z
                               goto     l__l100
                               return

...and the entire procedure containing...

Code:
LED1 = (BrightnessTest < Brightness)

...gets turned into...

Code:
l_pwmout
                               datalo_clr v_brightnesstest
                               clrf     v_brightnesstest
l__l100
                               movlw    200
                               subwf    v_brightnesstest,w
                               bcf      v____bitbucket_1, 0 ; _btemp1
                               btfsc    v__status, v__z
                               goto     l__l114
                               btfss    v__status, v__c
                               bsf      v____bitbucket_1, 0 ; _btemp1
l__l114
                               bcf      v__porta_shadow, 0 ; x44
                               btfsc    v____bitbucket_1, 0 ; _btemp1
                               bsf      v__porta_shadow, 0 ; x44
                               movf     v__porta_shadow,w
                               movwf    v_porta
                               incf     v_brightnesstest,f
                               movlw    255
                               subwf    v_brightnesstest,w
                               btfss    v__status, v__z
                               goto     l__l100
                               return

Which seems shorter (fewer statements) but does seem to use this 'bitbucket' and a few temp variables (if I'm reading correctly). All-in-all, I'm rather nonplussed as to why the first code seems more efficient, i.e. a PWM burst - rising edge to next rising edge - takes a shorter amount of time.

I might ask the JALlist folks about this as well... see if they can educate me too.

I would still like to get this interrupt thing working at 4MHz if that's going to be possible.
 
OK,

As per the original advice, I moved the "LED1 = on" outside the 0 to 255 loop so that I now just check if I need to turn it off inside the loop. I thought this should save some of the processing time and I can now time a rising edge to the next rising edge at 4.198500ms.

That's an improvement over 4.289250ms... close to 2%... probably not enough to stop flickering when doing this with 9 LEDs, but I guess the savings will actually become more noticeable with the more LEDs that I am doing. The only way I will know is to try...
 
...
I would still like to get this interrupt thing working at 4MHz if that's going to be possible.

4MHz osc is the difficult end of the scale but not impossible. I guess you wanted to use the internal osc which is limited to 4MHz and you must use TMR0 too? So it's for a bottom end super-cheap PIC?

Set TMR0 to 1:1 prescaler by assigning the prescaler to the WDT.
From memory that means; OPTION_REG = 0b00001000;

Now TMR0 is 1uS per tick.
Then in each interrupt you can load TMR0 to make the interrupt happen faster than the standard time of 256uS;

(somewhere in TMR0 interrupt put this) TMR0 += (256-(50-2));
thsi preloads the TMR0 register to it will make an interrupt ever 50uS. The -2 is needed as there is a 2 inst delay from writing to the TMR0 register. I am going from memory here so please check with your frequency meter. :)

Now you have an interrupt every 50uS (every 50 PIC instructions). This gives 20000 ints/second so if you use 8bit PWM the PWM freq is 20000/256 = 78Hz.

Now your int needs to be less than 50 asm instructions total for 9 PWM channels.
int context save; = 4 inst
pwm++ = 1 inst
all LEDs on = MOVLW, MOVWF = 2 inst
each PWM test to turn a LED off; MOVFW,SUBWF,SKPNZ,BCF LED = 4 inst x9 = 36 inst
TMR0 += (256-(50-2)) = MOVLW, ADDWF = 2 inst
clear TMR0 roll flag = 1 inst
restore context = 4 inst
retfie = 2 inst

That equals 52 insts so it's very close... Of course if you go to 7bit PWM that gives you twice as much time so it gets easy. Or you can make your interrupt 78 inst long which gives you 50Hz PWM and plenty of time again.

If you are worried about LED flicker it's pretty trivial to put a cap across each of the 9 LEDs, they have to be driven through a resistor anyway.

Why did you need 9 LEDs with individual brightness control from the cheapest possible PIC anyway??
 
Last edited:
4MHz osc is the difficult end of the scale but not impossible. I guess you wanted to use the internal osc which is limited to 4MHz and you must use TMR0 too? So it's for a bottom end super-cheap PIC?

4MHz or 8MHz, but with the internal oscillator, yes... I'm all about reducing component count. As for cheapness of PIC, I couldn't care less - if there's a PIC that'll do this a lot easier, please do give me ideas, I have successfully used the 16F628a, 16F676 and 16F684 (as well as 12F863) previously to do PWM across 3 LEDs (3 colours of a single RGB LED, I mean). I looked at these PICs 'cos they have the IO pins I need to control 9 output pins, no other reason. So I would be happy to look elsewhere.

Set TMR0 to 1:1 prescaler by assigning the prescaler to the WDT. From memory that means; OPTION_REG = 0b00001000;

Now TMR0 is 1uS per tick.

I'd already done this.

Then in each interrupt you can load TMR0 to make the interrupt happen faster than the standard time of 256uS;

(somewhere in TMR0 interrupt put this) TMR0 += (256-(50-2));
thsi preloads the TMR0 register to it will make an interrupt ever 50uS. The -2 is needed as there is a 2 inst delay from writing to the TMR0 register. I am going from memory here so please check with your frequency meter. :)

I believed that I'd already done this, too. I set TMR0 to 127 in the interrupt, hoping the interrupt would happen in half as short a time. No effect, as described above.

Now your int needs to be less than 50 asm instructions total for 9 PWM channels.

That looks like the challenge here.

If you are worried about LED flicker it's pretty trivial to put a cap across each of the 9 LEDs, they have to be driven through a resistor anyway.

No. These are 3 RGB LED strips.

Why did you need 9 LEDs with individual brightness control from the cheapest possible PIC anyway??

I want it for art... ;) ... as I say, I don't care about cheapest possible PIC - show me a PIC at £10 that will handle this (as long as it's not as big as an Apple iPad) and I'll take a gander.

Here's what I'm doing... the three individual sections have independent colour control.

**broken link removed**
 
Last edited:
Thanks. I can also see 16F1823 and 16F1824, which both have internal 32MHz oscillators and 14 pins, the same as my 16F684. I'm guessing that they would be just as appropriate? Not sure where to purchase them from yet, eBay drew a blank on them.

At first I thought there were no JAL device files for these, but it seems that there is.
 
Results of experiments to report...

I set another pin (A5) high just before the commencement of my PWM procedure for 9 LEDs, then set it low immediately after completion. This way I could measure different ways of coding, how long it spends in the PWM routine has a direct bearing on flicker or not.

Method 1 - my standard approach for 1 RGB LED strip, using 8-bit PWM with a 255 loop that, each iteration, sets each of the 9 LEDs on or off depending on whether it is lower than the specified threshold (for each LED) or not. The time spent in this PWM routine was 25ms, according to my logic analyser.

Method 2 - taking the suggestion in this thread of setting each of the 9 LEDs on at the start of the PWM routine, then keeping the 255 loop and checking whether each LED has reached its threshold and, only then, turning it off. The time spent in this PWM routine was 20.9ms. A not insignificant saving.

Method 3 - doing the same as method 2, but only having a loop of 127, and setting all colours that were 255 to be 127, all colours that were 200 to 100 etc., so I have 7-bit PWM. The time spent in this routine was (what you'd expect) 9.8ms - or roughly half... or 40% of the time spent in the original PWM routine.

I haven't checked this with 3 RGB LED strips connected yet, to see if the flicker is banished or not. I have just done this on a breadboard with the code running blindly, i.e. none of the 9 output pins was connected to any LEDs.

I'll do that later, but I am hopeful about this now. I just hope 7-bit PWM looks OK too. I've tried it with a single orange LED (fading from off to fully on) and it looked OK I think.

P.S. - have also ordered some 16F1823s, just in case...
 
Last edited:
Since cost in not really an issue, just get a 20MHz xtal and two 22pF caps, or even a 3-pin 20MHz ceramic resonator which does not need the caps. These will work with your current PIC.

That gives you all the time you need to do 8bit PWM in the interrupt and be well above the flicker rate. Changing to a different PIC is unneccessary. :)
 
Yeah, but if I did that, then I'd not need the interrupt at all. I only really tried the interrupt idea to resolve the issue I'd reported in the first place.
 
Maybe you're missing the point of the interrupt?

It gives exact PWM timing so the brightness levels won't fluctuate based on program operation, and it frees your program up to do whatever it needs and just loads the 9 PWM variables, then all the PWM activity is done automatically in the interrupt so it gives you a similar level of function as a 9-channel "hardware" PWM module.

It looks to me like people keep suggesting good sensible options and you keep rejecting them and trying to do something the hard way for some reason.
 
Maybe you're missing the point of the interrupt?

Incorrect. I get it, you're fond of interrupts. ;) Maybe I am not so fond. ;)

It gives exact PWM timing so the brightness levels won't fluctuate based on program operation, and it frees your program up to do whatever it needs and just loads the 9 PWM variables, then all the PWM activity is done automatically in the interrupt so it gives you a similar level of function as a 9-channel "hardware" PWM module.

My loop also gives me a defined, repeatable, routine. I just need to do it fast enough.

It looks to me like people keep suggesting good sensible options and you keep rejecting them and trying to do something the hard way for some reason.

Whoa. Just because I don't like your suggestion is no need to get protective of it. I have tried it, after all (with just 1 LED, let alone 9), and it didn't work - I got flickering. I didn't just reject it out-of-hand, did I?

I have put some significant effort into giving it a go. As I say, it didn't work - you then started to recommend things I had reported I'd already tried in a previous post - 1:1 WDT, setting TMR0 value to > 0 etc.. It doesn't mean your suggestion was bad... just that it didn't work, for me, in my situation. Therefore I've moved on from it as an idea.

My ideas now rest with either a) 7-bit PWM for the 9 LEDs, or b) 32MHz PIC16F1823... or, who knows, maybe both.

I'll keep asking questions if you'll keep offering possible solutions.
 
Hippogriff
It doesn't mean your suggestion was bad... just that it didn't work, for me, in my situation. Therefore I've moved on from it as an idea.

If you want an event to happen on a regular interval then timer based interrupts are the bee's knees.

Without seeing your code how is anyone supposed to known what went wrong ?
 
Er... I showed the timer interrupt code in post #7. Simple as it gets... the LED flickered, hence it didn't work... for me.
 
Status
Not open for further replies.

Latest threads

Back
Top