Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

Multiple PWM for LED's, on a PIC

Status
Not open for further replies.

Buriedcode

Full Member level 6
Joined
May 6, 2004
Messages
357
Helped
43
Reputation
86
Reaction score
7
Trophy points
1,298
Location
London
Activity points
8,887
pwm+3ch+picbasic

Hi,

I suppose this is a fairly simple question, but...
I'm trying to write software for 3 PWM outputs on a PIC, with a frequency of roughly 100=200Hz. Its for controlling the brightness of LEDs, a red, green, and blue. (Don't have an RGB led, but I'm using tiny 0403 chips close together, its as good as).

I'm using the PIC16F628A which does have a hardware PWM, but alas, only one.
Could this somehow be 'multiplexed' at high speed for all three LED's?

My first code was simple. Set up a timer (Timer0) interupt, every time it 'pings'(rolls over) I decrement each of the PWM registers (containing 0-15, its only 4 bit so far) as well as a 'counter' register (filled with 15 at the start). When a PWM register reaches 0, its pin is pulled low. When the counter register reaches 0, all pins are made high, the counter is loaded with 15 again, and all PWM registers are reloaded.

Theres many prog's like this on the net, but honestly, I had this idea before I saw any of them :D

So far so good, it also means that adding up to 8 PWM outputs, won't drastically increase the time spent on PWM generation (just decrement another PWM register).

However, this is only 4-bit resolution. And despite the speed (4 Mhz osc, interrupt every 0.5ms or so) I can see a flicker when I turn my head quickly. I used an interupt because I really would like the PIC to do other things, with the PWM in the background, although, PWM generation is the main function of the software.

I suppose I could use Timer2 and set it to count to 127, instead of Timer0's 'set' 255. But I'm worried that my interrupt routine will eventually be too long, so the interrupt wants to go again while I'm in the middle of it (even if I turn interrupts off during the ISR). That would mean, any code in my 'main' code wouldn't get executed.

My second idea is a bit drastic. Setup a timer1, so it compares its value with a register and causes an interupt when they are the same. So, I only have to do 4 things: Switch of each PWM in turn (3) and then reload when the timer has reset.
Only 4 interupts fro every period of PWM, but putting the PWM values in order is tricky (two values might be the same).

I would like to have 8-bit resolution, 3-ch PWM running at a higher speed (100-150Hz?). Preferably with the ability to use the serial port. Does anyone have any 'fancy' methods for doing this? Or will I have to dedicate the entire PIC to generating these PWM's at a decent resolution/frequency? Ideally, I'm trying to make each LED randomly fade in and out to get various mixes of colours, with no set sequence.

All idea's welcome. And yes, I've scoured google for days and I've collected loads of info. Tomorrow I'll see just how quickly I can get an 8-bit version of my idea above to work.

Thanks.

BuriedCode
 

pic16f628a l.e.d. fading secrets

your second idea is best.
And it's not that tricky, really.

What you have to do is the following:

a)sort your values from lowest to highest (r for red, g for green, b for blue)
b)check if value r/g/b is >0 , if it is then turn on the r/g/b diode, else turn off
c)set the timer to call int for the first/next value change (you have got them sorted, remember?)
d)once int is called, check which values equal this time (so have current time saved somewhere in variable) and turn on all those leds
e)check if there's a led that turn's off later , if there is then goto point c. if there's none then set the timer to call interrupt at the end of period (100Hz PWM) - assuming we're not there yet... if we are goto point f)
f)once the last interrupt is called when time=PWM period time load new values into r,g,b time registers and goto a)

It shouldn't take more then few % of CPU time and RS232 connection will work fine in the background.
 

pic multiple pwm output

Hola B C,

After reading on time based interrupts I've experimented with good results with 8 LEDs in different conditions: flashing forever, flashing for a limited amount of time or just once. (Kind of a fixed PWM :?: for those flashing forever, simply ON / OFF)

The whole controlled with counters decreased at the pace imposed by two "time bases" generated through a TMR0 interrupt.

In the background, the main line code consists of nine tasks, eight for the LEds and one in charge of decrementing the counter of each LED.

Every (LED) task checks a flag telling: "go ahead and do your job" or "not the time yet, try again in the next round".

May be expanded to many more LEDs (my 16F877 has many pins still free but as a proof of concept it was enough).

I didn't think of it as PWM, :idea: but... :?:
-----------------------------------------------------------------------------------------
EDIT: After re reading you post for the fourth time: the secret is what I did, keeping the clerical duties out of the foreground (ISR) and bringing it to the main line code, as described. In such a way you keep your ISR quite short :D !

Even the smallest period of time that any of your LEDs is visible allows for many many ISRs.
-----------------------------------------------------------------------------------------

What do you think?

Agustín Tomás
 

multiple pwm outputs pic

Hi again,

MirekCz, atferrari, thanks for your replies!

Its always interesting to hear peoples opinions on the matter, we all have different ways of doing things.

MirekCz, the second method is my favourite idea too. The ISR will be tiny (hopefully) since it just turns off the appropriate LED(s) and reloads the counter compare register with the next PWM value. And because its called a maximum of 4 times, my PWM frequency can be higher while still having loads of room for other 'goodies'. Also, I was thinking, even if the code to sort the PWM's in order of size is huge, it will only load in the new values once its finished. Which means there will a slight delay between changing PWM values, and the colours changing, but the delay will be stupid, like 1-2ms :) Not at all noticable.

The only 'issue' I have with that is the way in which I sort out the values in order. I can find out the relationship between two values easily using 'subwf' and checking the 'Carry flag' to see if X > Y, and the Z flag to if X = Y. But doing it for three becomes very tricky :( Obviously, if I've compared 'temp1' and 'temp2', I then compare the highest of these two to 'temp3'. But in some instances, 'temp1' could be equal to 'temp3'. And considering I'm doing this in assembly, its very tricky. I have some pseudo-code scribbled down somewhere... Any ideas welcome :)

The ISR will require two registers. One has the bit mask for turning off the right LED (10000000,01000000 etc.) and the other contains the PWM value for the NEXT interupt. Maybe I'm over-complicating it, I'm sure theres a nice little 'assembly trick' I'm not aware of.

atferrari, I like your idea of 'shrinking' the ISR! So the ISR just turns on/off flags depending on what its meant to do, and the 'main; code does the functions according to these flags? Nice. In fact, it doesn't really matter which method I use in my PWM generation, I could use the flag idea for all of them. Thats providing my 'main' code isn't massive, because the interrupt could be called/returned at any point in the main code, so I want to make sure the main code checks the 'flags' before the interrupt is called again. But I don't think that will be a problem.

Thanks guys, I'm copied/pasted your idea's into notepad for future reference, I'll let you know of my progress..

Thanks again,

BuriedCode.
 

To be honest I couldn't grasp the concept of your 2nd option but I presume that if you free yourself of any order, other than the succesive call in the main loop, using my idea, will simplify your life and whatever complication comes from adding other "goodies" (?).

In fact, dealing with timed tasks keeping (whatever) counters business in the main line code, out of the ISR is conceptually simple and powerful.

It all started from an enlightening thread here (you will skip the first posts):

**broken link removed**

Look my ISR. It only flags another TMR0 period and increments a tenfold counter for a slower time base. Neat & simple. The eight counters are dealt with in the background.

Code:
	;ISR.ASM

	;saving current environment
	
	BSF DEBUG_BIT0			;DEBUG to detect activity
	MOVWF W_TEMP			;copy W to temp register
	
	SWAPF STATUS,W			;swap STATUS keeping copy in W
	MOVWF STATUS_TEMP		;copy W to temp register (retaining
							;BANK selection at interrupt time)
	CLRF STATUS			;makes BANK0 regardless of current bank	
	 
	MOVF PCLATH,W			;copy PCLATH in W
	MOVWF PCLATH_TEMP		;copy W to temp register (retaining
							;PAGE selection at interrupt time)
							
	CLRF PCLATH			;making PAGE0 regardless of current page
	
	;current environment saved
	;----------------------------------------------------------------

   ;**********************************************

	;actual interrupt service routine starts here
	
	BSF FAST_TB_OK			;show "fast time base period elapsed"
	DECF SLOW_TB_COUNTER	;decrements slow time base counter
	BTFSS STATUS,Z			;if counter =0, then...
	
	GOTO SET_NEXT_PERIOD		;not =0 - period not elapsed - leave!
	LOADFILE SLOW_TB_COUNTER,10	;...prepare next count and
	BSF SLOW_TB_OK			;show "slow time base period elapsed"

SET_NEXT_PERIOD

	LOADFILE TMR0,217		;have to decrement 255-217 =38 times to o'flow
	BCF INTCON,T0IF			;reset TMR0 interrupt flag


   ;**********************************************

	;actual interrupt service routine ends here

	;----------------------------------------------------------------
	;retrieving main line code environment

	MOVF PCLATH_TEMP,W		;retrieve PCLATH from temp register
	MOVWF PCLATH			;recovering PAGE selection at interrupt time
	
	SWAPF STATUS_TEMP,W		;retrieve STATUS from temp register
	MOVWF STATUS			;recovering BANK selection at interrupt time
	 
	SWAPF W_TEMP,F			;Swap W_TEMP 
	SWAPF W_TEMP,W			;Swap W_TEMP into W - STATUS remains unaffected
	
	;main line code environment	retrieved
	
	BCF DEBUG_BIT0			;DEBUG to detect activity
	RETFIE                 ;back to main line code with interrupts globally enabled again

Genuinely interested in your outcome. Please post:!:

Buena suerte

Agustín Tomás
 

you can use PIC16F777 , this device have 3 PWM mod
 

hi

i've worked on this subject before, generating pwms using software. i ve made it some but believe me this is "time wasting". pic has the principle of doing the processes line by line, that is, it is not capable of processing doing things at the same time.
after a few investigations i ve found that i should use pwm generator ic's. i recommend you to look through

and i have a brief writing there..
 
Last edited by a moderator:

Hi all, long time no post.

Well, I've been working on it, only a few hours, but I've got the 'ordering' code done, and its quite clever.....

It simply does a conditional swap are the first two, then the second two, then the first two again. After that, all 3 are arrange in order of the size of their contents. And heres the clever bit, if the swap in one of the above ways occurs, a flag is raised. After arranging the registers, we now need to know what LED(s) to turn off, in what order. The order must be the same as the order of PWM values we've already arranged. So I load in the 'pin' values (one register for each LED pattern, 01000000 for green for example, on RB6) in to temp registers, and using the flags, perform the same 'swaps' as I did for the PWM values.

I am now left with 6 registers, ordered: LowPWM, lowpin, MedPWM, medpin and HighPWM, highpin. These are in that exact order in the register memory, so that before we call the ISR for the first time (to turn off the LED with the lowest PWM time/ or 'mark') we load in 'LowPWM' into Timer2. When the ISR is called (Timer2 rolls over to 0) one can simply increment the FSR, and the bit-pattern for the correct LED is in 'INDF'. It then increments the FSR again to get the next PWMvalue.

Believe me, it would take hours for me to explain exactly what I've done, and it really would be far easier to get a microcontroller with 3 hardware PWM's, but I like he challenge. Plus, its nice to have a PWM 'software module' handy, that operates on interrupts and takes very little resources. For an old 16F84A I have, I've got it doing 3 8-bit PWM's running at about 244Hz, a keyboard, and an LCD module all on 4MHz, with loads of program memory to spare.

It needs 'tweaking' so I can use Timer2 OR Timer0 (the only timer some basic PICs have) and at the moment, it can't cope with 0 PWM values, it converts them to '1'. But I can fix that so a '0' PWM value would mean 'off' and 255 would mean 'on'. Thanks for all your help, when I have a fully tested version, I might put up the code, although I'm sure someone else has done it, it blows microchips' 'software PWM' app note out of the water. With an ISR of 15 cycles, and a 'setup' routine, done once every time new PWM values are given of about 200 cycles, its relatively small. Its really only designed for apps where no HW PWM is available, other code needs to be executed at the same time, and when the PWN values do not have to be changed very quickly (fading all 3 LED's randomly cannot be done very quickly, especially with other things going on, like UART/keyboard etc..).

Thanks again.

A very proud, BuriedCode. :D
 

Hi,

I didnt read the whole thread so this may have been suggested. If you get a copy of PICBasic Pro, there is a very simple command that will turn any i/o pin into a PWM.
Very simple, 1 line per pin......

PWM 1,127,100 will give you a 50% duty cycle at 100Hz on pin 1. It will run until you change the value or turn it off.
You can also asign a varible.....

loop:
duty VAR WORD
duty = duty + 1
if duty = 255
then duty = 0
PWM 1,duty,100
goto loop
 

JMG,

Don't worry, all idea's are welcome :D

I'm slowly being converted to PICbasic, its becoming more and more popular, for ease of programming and functions (like the 'pot' command, pure genius). But I've read up on the PWM generation in PICbasic and it does have its limitations, because its really a 'higher-level code' I don't know exactly how it does it. At least with assembly I have complete control over exactly what the PIC does every instruction even if it does take a very long time.

I've since changed my algorithm for, what I believe to be the most efficient. Each PWM period, an interrupt is called a maximum of 4 times, depending on whether any of the PWM values are 0, 255 or the same value. Although its probably not the best form of PWM generation it gives the best comprimise of speed, efficiency, memory for my app, contrllling 3 LED's.

When I've completed the code for this, and tested it in an embedded prog (woth UART, keyboard, LCD SPI etc..) I'll get a copy of PICbasic and compare the two. From what I've read in forums and the net, it seems that for all apps that don't require *exact* timing (accuracy to one instruction period) PICbasic is the best to use.

Thankyou.

BuriedCode.
 

I'm very much a beginner with PICBasic and still struggle with many of its functions. Have you seen www.picbasic.co.uk ? There is a moderator on the forums called Melanie, and if she can't do it with PICBasic, it aint worth doing. Very much a resource to tap.

You are right in that there is no cycle specific timing with PBP, but PBP generates ASM when you compile it and you could probably understand what the program is doing by looking at the ASM. You could then modify to work for your own needs, and PBP has done the grunt work for you.

Just a thought.
 

buriedcode,

I've been reading this topic, but when I read your problem with "fully on" (255) or "fully off" (0), I thought of a suggestion:

If you write your interrupt code to take a pin mask and xor it with a copy of your port, it may help. Here's an example:

Time | Mask
------+------
40 | B'00000001'
60 | B'00000010'
80 | B'00000100'
255 | B'00000111'

would PWM pin 0 40/256 cycles and pin 1 at 60/256 cycles and pin 2 at 80/256 cycles. If you wanted pin 0 to be fully on and pin 2 to be fully off, but pin 1 to be left alone you would set the values to:

Time | Mask
------+------
40 | B'00000000'
60 | B'00000010'
80 | B'00000000'
255 | B'00000010'

and make sure pin 0 was set and pin 2 was clear. You would still interrupt at 40 and 80, but you wouldn't do anything.

I'm very interested to see your finished code. Thanks.
 

Hi Guys !
I was designing the RBG LED controller many years ago. But i m using 89C52 microcontroller that time and i developed the the 6 channel PWM output using 1 timer. :!:
The trick is using 1 timer to generate interrupt, 1 register as Timer Counter Value, whenever the Timer interrupt it will increment the Timer Counter in the ISR . I use another 3 register as Red, Green and Blue counter. After increment the Timer counter, the ISR will check the RGB counter one by one. SUB it with Timer Counter, If Timer Counter value greater than the respective R / G / B Counter, set the IO output to High. Lastly when the Timer Counter = 0 or overflow reset all RGB IO to Low. By doing this way in asm, u can calculate the required machine cycle needed for whole ISR and this is the maximum freq for the PWM :D This is to avoid the next interrupt happen again while u still in the same ISR.

About 100Hz freq, i think by doing this way it is very hard to make sure the freq always stay at 100Hz but slightly different is OK as long as the different is keep below 10% of the PWM freq. Pls stay above then 50Hz so our human eyes cannot see the different or flashing when come to low dimming level. Pls check on your LED spec on the minimum freq needed for dimming purpose.

At the later stage, if u need to adjust the PWM freq what u need to do is change the Timer value but it would not affect all other software features.

At the main routine, we just need to put in the so called "Dimming Level" into the R/G/B Counter and the ISR will dim accordingly. Another good thing is if we do it in this way , we can add in many other features like FADE and LED Curve. Some LED cannot perform linearly according to the PWM pulse width, thus we need a secondary rountine to sit into main rountine and adjust the dimming level b4 we actually output to the Counter Value for RGB Counter, the LED Curve for RGB can generate from a look up table which need some testing.

The Fade function is some nice feature as well, changing the RGB counter will cause the Output to change immediately, if we create a another secondary sub routine in main routine, we can increase the RGB counter slowly by second timer until it reaches the user required level, this would cause the LED to light up slowly until it reaches the required dimming level. when we change 3 RGB counter at the same time it would create a very nice color change. How fast to fade in new value can be determine by second timer value. Make it modular so easy to maintain.

In order to create more advance features, the processor speed is very important, I think by using 24MHz crystal with PIC, it is enough to create all those features and maybe up to 10 - 12 channel if you able to write a good compact code for the ISR.
:D:D:D:D:D:D:D
 

Hi guys!

Becoming a big 'ol topic this...:D

jonw0224, good to hear you are interested. About the 'bit mask' thing, you've hit the nail right on the head. Its exactly what I've done in all 3 idea's I've had. The 'start bit pattern' determines which LED's start the period as 'off' and is used to keep track of which ones we have 'on' every time the ISR is called. I've tried to think of a better way, but I think this is easy and efficient.

ECSD, good to hear your input. I think I mentioned in my original post, that this was my first idea. For a 4-bit PWM, the 'ISR' has a 4-bit counter, along with the PWM values for each LED (also 4-bits). Every interrupt form a timer, decrements all of these counters, when a 'PWM' counter reaches 0, its corresponding pin is turned off. When the 'counter' reaches 0, we simply reload everything and start again, a new PWM period. Although, the reason I abandonned this at the time was the amount of processor time it takes up. For 100Hz this would only need :100*16*(ISR length, say, 8 instructions) = 12800 instructions per second.

Example:

8-bit PWM, so an 8-bit counter is used, 0-255. The ISR must be called 255 times per PWM period. So for a frequency of 100Hz, thats 25500 interrupts per second. Now, this leaves us plenty of time to do 'other things', with a 4Mhz PIC, we have a million instructions per second to play with. BUT.....how big is our interrupt routine? say 8 instructions max. Thats 8*255*100 instructions per second = 204000. Just over 20% of the total time the PIC is running. When I did this, I noticed a slight flicker, so I had to up the frequency to 180-200Hz, plus I think my ISR was about 17 instructions. This left my PIC with about 10% left to spare. So, its almost like having a PIC running at 4Mhz/10 = 400Khz, still useful, but I'm obsessed with efficiency :D

That said......I am indeed going to do this, but without it being in an interrupt routine. I've decided to just dedicate a small 8-pin PIC to do all the PWM work in its 'main code' with a UART in an interrupt to change PWM values. That way, I don't need the PIC to do anything else, it can run at 4Khz, and here in the UK I can get a 8-pin PIC for 90p. (0.9GBP). Its just easier, and cheaper.

Anyway, I will explain my code fully once its finished. It has to be said, it really is complicated. The code is straight forward, but the idea behind it took me a week to think up. I've got it down to 400 instructions, including the ISR, and 'without' any optimisations (I usually write a rough code, then add subroutines and little tricks to shrink it down to about 40%). It has 4 interupts per PWM period. With the ISR being 8 instrucitons. At 100Hz, thats 100*4*8 = 3200. Which, on a 4MHz PIC is about 0.32% of the total time used for PWM generation. It uses a single 'timer interrupt' which can be either Timer0, Timer1, or Timer2 depending on which routine you use. The real drawback with it is 'set up'.

For most PWM algorithms you people have described, you simply load in your PWM values, and start the interrupts. Making changing colours and fading very easy. But my code pushes all the complexity into the 'set up' routine, so every time you change a PWM value, you must re-do this, which is 350(ish) instructions. However, if you want a single colour most of the time, that changes every-so-often, like a Temperature indicator for your PC, its great, your PIC can do loads of other things whilst running this in the background. Personally, I'm planning on using it for an LCD backlight, that changes colour depending on user settings and other factors (an MSN message makes it flash green, CPU temp high makes it RED, and I've got a little fading routine that just makes my PC look cool :D).

Anyway, I'm ranting, as I said, I've deciding to buckle, and spend about 1USD on a micro dedicated to this task. Allowing it to run fast, change colours quickly, and fade, as well as store patterns and control the current for each LED (PWM, ADC feedback to obtain a 'pure' white compensating for temperature variations and resistor tolerences). This will use everything ECSD, jonw0224, atferrari, MirekCz have said. And 'hopefully' when I get time, I'll set up a website, with all the algorithms on there (I've got 3 main algorithms for PWM generation, only 2 are any good though).

Thanks again guys.

BuriedCode.
 

Hey BuriedCode !
I am interested in what u going to design, hope can visit your website as soon as possible. Btw, i wish to know about ur idea of backlight, where you going to implement it ? and how u get the status from ...such as MSN Messanger and CPU heat up ?

Good Idea BurriedCode Good Luck !
 

Status
Not open for further replies.

Similar threads

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top