Lamp fader
I made myself a dimmer that has has 239 steps (the filament responds best in this range) from dividing the clock by the numbers 121 to 360. I want more fine control than this over the lamp (more of a resolution). Since the propeller will not accept numbers like 139.41 what should I divide the clock by for a greater range? I tried numbers like 13941 but they did not work (lamp was at full brightness).
Here is the code:
I commented out the lines that turn my dimmer into a "light bulb fade off over time" program to test the dimmer at fixed brightnesses. The end result of the program will dim the lamp over a period of X minutes. The program, if the lines were not commented out, would lower the lamps brightness by one step per second. The problem with this is at the lower brightnesses you can see the filament stepping dimmer. I want a finer resolution so the fading would appear more analog.
Here is the code:
CON
_xinfreq = 5_000_000
_clkmode = xtal1 + pll16x
var
word brightness
long stack[noparse][[/noparse]20]
PUB Main
cognew(Subtract, @stack[noparse][[/noparse]0])
cognew(Dimmer, @stack[noparse][[/noparse]10])
PUB Subtract
brightness := 360
'repeat while brightness > 121
' brightness := brightness - 1
'waitcnt(clkfreq + cnt)
PUB Dimmer
dira[noparse][[/noparse]16] := 0
dira[noparse][[/noparse]8] := 1
repeat
outa[noparse][[/noparse]8] := 0
repeat while ina[noparse][[/noparse]16] == 0 'Wait until zero cross fires
waitcnt(clkfreq / brightness + cnt)
outa[noparse][[/noparse]8] := 1
I commented out the lines that turn my dimmer into a "light bulb fade off over time" program to test the dimmer at fixed brightnesses. The end result of the program will dim the lamp over a period of X minutes. The program, if the lines were not commented out, would lower the lamps brightness by one step per second. The problem with this is at the lower brightnesses you can see the filament stepping dimmer. I want a finer resolution so the fading would appear more analog.
Comments
If the zero cross signal is narrow enough, you should use waitpeq instead of the repeat loop. waitpeq will be faster in continuing execution.
Don't use a division in the waitcnt. I'd rather give it a fixed variable. The subtract can take care of the value of this variable. If you divide 80_000_000 by 121 you get 661_157, if you divide by 360 you get 250_000. So, simply put any value in between in the variable and you have 411_157 steps.
To give you better advice it would be interesting to see what the cirquit to switch on the light looks like.
PS: As perception of light is not linear - plus the alternating currency will produce non linear brightness (in sense that it's not proportional to the switch on time), it makes sense to create a table of values which give the impression of a linear fade.
dat
fade long 250_000, 251_000, 252_000 ........ and so on (·~128 values would propably give a soft fade )
In your subtract you'd increase an index inside of that array and provide that value to dimmer.
Post Edited (MagIO2) : 11/23/2009 10:54:30 PM GMT
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Style and grace : Nil point
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Style and grace : Nil point
http://mouser.com/Search/ProductDetail.aspx?qs=sGAEpiMZZMt82OzCyDsLFGDf%2b07/%2bYgHW//tOw8Iy2o=
It contains two LED's wired in inverse parallel that couple to an NPN transistor. I have the two LEDs hooked up to the 110 line with two 10k ohm resistors in series.
FWIW, I'll be coding my dimmer to accept an input range of 0 (off) to 255 (full on) to accommodate DMX512. The user may do linear corrections in the high-level code (keeps the assembly simpler, and I've done this with an LED dimmer [noparse][[/noparse]see my BAM object in ObEx]).
[noparse][[/noparse]Edit] You might consider the H11AA1 opto-isolator as it gives you an output for each half cycle of the AC input. The attached circuit will give you high-going pulse at 120 Hz.
Post Edited (JonnyMac) : 11/23/2009 11:37:22 PM GMT
Is your code intended to pulse the power to the lamp as it makes P8 0 almost instantly after making it 1?
Have you considered:
Reverse phase control:
http://www.epanorama.net/documents/lights/lightdimmer.html
Fire the triac at the zero crossing, wait for a time and then turn off (a time less than a half cycle). You should be able to get plenty of resolution.
The other thing is that there is no reason to do a division just do:
waitcnt(brightness + cnt) but change your values of brightness accordingly, your resolution is then the clock time period (1/80000000) which I suspect is good enough.
Cheers,
Graham
Still not sure of the need for assembly.
Graham
Graham
Speed. PASM is on the order of 50 to 75x faster than Spin; by running at a faster speed you can get better resolution of control. If you're running a dimmer in a separate cog, anyway, it might as well be a PASM cog for the best performance. A program that simple is not difficult to code in Assembly and would be worth the effort.
No, it's based on the lamp you're controlling -- this is why my dimmer projects do not attempt to linearize at the low level; it's easy to create a translation table to take a straight linear input and apply a curve to it to get the best visual output. Again, see my BAM object for an example of this.
-- http://obex.parallax.com/objects/517/
PUB Dimmer dira[noparse][[/noparse]16] := 0 dira[noparse][[/noparse]8] := 1 repeat outa[noparse][[/noparse]8] := 0 repeat while ina[noparse][[/noparse]16] == 0 'Wait until zero cross fires waitcnt(waitduration + cnt) outa[noparse][[/noparse]8] := 1
So far I have this
org 0 'Set the program origin at mem position 0
mov dira, 8 'Set pin 8 to output
Now how do you specify a jnp command to wait for the ina <pin> register to go high?
CON _xinfreq = 5_000_000 _clkmode = xtal1 + pll16x var long waitduration long stack[noparse][[/noparse]20] PUB Main cognew(Subtract, @stack[noparse][[/noparse]0]) cognew(Dimmer, @stack[noparse][[/noparse]10]) PUB Subtract waitduration := clkfreq / 180 'repeat while waitduration < 660000 ' waitduration := waitduration + 1600 ' waitcnt(clkfreq + cnt) PUB Dimmer dira[noparse][[/noparse]16] := 0 dira[noparse][[/noparse]8] := 1 repeat outa[noparse][[/noparse]8] := 0 'repeat while ina[noparse][[/noparse]16] == 0 'Wait until zero cross fires waitpeq(0, 16, 0) waitcnt(waitduration + cnt) outa[noparse][[/noparse]8] := 1
pub dimmer | zcmask dira[noparse][[/noparse]16]~ ' p16 is zero-cross input dira[noparse][[/noparse]8]~~ ' p8 is triac gate output zcmask := |< 16 ' pin mask for p16 repeat outa[noparse][[/noparse]8]~ waitpne(zcmask, zcmask, 0) ' wait for zc to be clear waitpeq(zcmask, zcmask, 0) ' wait for zc to be active waitcnt(holdoff + cnt) outa[noparse][[/noparse]8]~~
As the perception of light is more sensitive to little changes in the dark, the SPIN code should be feasible as it switches on the TRIAC at the beginning of the half-periode which will not add much brightness. If you switch on at the end of the periode the light will be darker. That area is covered by the waitcnt with high resolution.
Exactly as MagIO2 says the resolution even in spin using waitcnt will be 12.5ns the only thing Spin will limit is the minimum time and it will mean you are a little late catching the crossing perhaps.
If you want to learn assembly here are some resources:
http://forums.parallax.com/showthread.php?p=668559
Which contains two wonderful PDFs by deSilva and Potatohead
http://forums.parallax.com/showthread.php?p=647408
Which is my own "assembly step by step" which contains 4 programs containing the basics to get you started.
It will become an object that can be started with start and have the brightness changed by setbrightness. Notice there is a pulse_time variable this is because in assembly I'm not sure the pin will pulse for long enough to turn on the triac, you may be able to remove the delay, I just guessed a value for that so you may need to change it.
Reading the comments may teach you plenty about assembly too.
Hope it works [noparse]:)[/noparse]
VAR long bright ' Stored in hub ram PUB start cognew(@entry,@bright) PUB setbrightness(offt) off_time := offt DAT org entry mov cross_mask,#1 'Put 1 in mask shl cross_mask,cross_pin 'Create mask mov triac_mask,#1 shl triac_mask,triac_pin 'Create mask or dira,triac_mask 'Set triac pin as an output mov p,par 'par is second argument of cognew and the address of the brightness variable in hub ram :loop andn outa,triac_mask ' Make triac pin zero rdlong off_time,p ' Read "brightness" from hub ram (par is second argument of cognew) waitpeq cross_mask,cross_mask ' Wait until ina&cross_mask == cross_mask mov time,cnt ' Store current time add time,off_time ' Add the delay to current time waitcnt time,0 ' Wait until cnt == cnt+off_time or outa,triac_mask ' Make triac pin high mov time,cnt ' add time,pulse_time ' waitcnt time,0 ' A delay because your triac will probably need ' a pulse longer than 12.5ns? jmp #:loop cross_pin long 16 triac_pin long 8 cross_mask long 0 triac_mask long 0 time long 0 off_time long 0 pulse_time long 100 p long 0
1.
The PASM code is very small ... so you have lots of place to put the array there that holds the waittimes to give the impression of a linear fade. So, the setbrightness would not set the time itself, but an index of that table.
PASM part then would not use that value directly, but do a lookup in that table and use what it finds there.
2.
rdlong could be done after the waitpeq. As a half periode is 1/120 second the value might have been changed between rdlong and end of wait by setbrightness. Ok ... wrong value would only be used for a half-periode, but you started with this 'more accurate'-thing ;o)
3.
Instead of adding pulse_time like you did it, you could use
waitcnt time, pulse_time
instead of
waitcnt time, 0
for the first waitcnt.
Then you don't need the second mov time, cnt and add time, pulse_time.
... just wanted to mention this for those who learn PASM.
2. Yes good idea (but it wasn't me who started the accuracy thing)
3. Nice optimisation, this thing will probably work for Mhz dimmer circuits [noparse]:)[/noparse]
Graham
VAR long index ' Stored in hub ram PUB start cognew(@entry,@index) PUB setbrightness(bright) index := bright ' Select the brightness (from a table) DAT org entry mov cross_mask,#1 'Put 1 in mask shl cross_mask,cross_pin 'Create mask mov triac_mask,#1 shl triac_mask,triac_pin 'Create mask or dira,triac_mask 'Set triac pin as an output mov p,par 'par is second argument of cognew and the address of the brightness variable in hub ram :loop andn outa,triac_mask ' Make triac pin zero waitpeq cross_mask,cross_mask ' Wait until ina&cross_mask == cross_mask rdlong index_,p ' Read "brightness" index from hub ram (par is second argument of cognew) add index_,#lookup_ ' Add the address of the lookup table to the index this provides the address of the element wanted movs :inline,index_ ' Move the address into the place holder (0-0) of add command at label :inline mov time,cnt ' Store current time :inline add time,0-0 ' Add the delay to current time waitcnt time,pulse_time ' Wait until cnt == time and add pulse_time to time automatically or outa,triac_mask ' Make triac pin high waitcnt time,0 ' A delay because your triac will probably need ' a pulse longer than 12.5ns? jmp #:loop cross_pin long 16 triac_pin long 8 cross_mask long 0 triac_mask long 0 time long 0 index_ long 0 pulse_time long 100 p long 0 lookup_ long 100,1000,2000,3000,4000,5000,6000,7000,8000,9000,10000 ' A few random values
Please check, I don't even have a prop with me.
Of course the look up table could be huge and should have proper values in it.
Graham
entry mov cross_mask,#1 'Put 1 in mask shl cross_mask,cross_pin 'Create mask mov triac_mask,#1 shl triac_mask,triac_pin 'Create mask or dira,triac_mask 'Set triac pin as an output mov p,par 'par is second argument of cognew and the address of the brightness variable in hub ram ... jmp #:loop cross_pin long 16 triac_pin long 8 cross_mask long 0 triac_mask long 0
If cross_pin and trisac_pin are hardcoded anyway, it makes no sense to calculate the masks during runtime. And even if the values are not hardcoded you can get rid of mov cross_mask, #1 when initializing cross_mask with 1. Saves 2 longs of our valuable COG-RAM in this case.
In case you initialize all values, you save 4 longs.
;o)
[noparse];)[/noparse]
VAR long index ' Stored in hub ram PUB start cognew(@entry,@index) PUB setbrightness(bright) index := bright ' Select the brightness (from a table) DAT org entry or dira,triac_mask 'Set triac pin as an output mov p,par 'par is second argument of cognew and the address of the brightness variable in hub ram :loop andn outa,triac_mask ' Make triac pin zero waitpeq cross_mask,cross_mask ' Wait until ina&cross_mask == cross_mask rdlong index_,p ' Read "brightness" index from hub ram (par is second argument of cognew) add index_,#lookup_ ' Add the address of the lookup table to the index this provides the address of the element wanted movs :inline,index_ ' Move the address into the place holder (0-0) of add command at label :inline mov time,cnt ' Store current time :inline add time,0-0 ' Add the delay to current time waitcnt time,pulse_time ' Wait until cnt == time and add pulse_time to time automatically or outa,triac_mask ' Make triac pin high waitcnt time,0 ' A delay because your triac will probably need ' a pulse longer than 12.5ns? jmp #:loop cross_mask long 1<<16 triac_mask long 1<<8 time long 0 index_ long 0 pulse_time long 100 p long 0 lookup_ long 1100,1000,2000,3000,4000,5000,6000,7000,8000,9000,10000 ' A few random values
Post Edited (Graham Stabler) : 11/25/2009 8:49:02 AM GMT
Maybe someone else can now find a nice set of values for the lookup table and we have a nice driver to put into the Object Exchange.