Shop OBEX P1 Docs P2 Docs Learn Events
Lamp fader — Parallax Forums

Lamp fader

kt88seampkt88seamp Posts: 112
edited 2009-11-25 18:53 in Propeller 1
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:

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.
«1

Comments

  • MagIO2MagIO2 Posts: 2,243
    edited 2009-11-23 22:39
    I suppose you have some cirquit, which tells your prop on pin 16 that power line currently crossed the 0V. So, 1/120 second means that you already missed half of the periode. You should get faster with your dimmer.

    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
  • Toby SeckshundToby Seckshund Posts: 2,027
    edited 2009-11-23 22:49
    Is there a need for a form of gamma corection. whereas the video signal is shaped to exagerate the lower portions and compress the upper ones, so that the non-linear phosphors react correctly, could you shape the oposite so that there appears to be more resolution at the bottm end ?

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Style and grace : Nil point
  • MagIO2MagIO2 Posts: 2,243
    edited 2009-11-23 22:54
    Video? Phosphors?
  • Toby SeckshundToby Seckshund Posts: 2,027
    edited 2009-11-23 23:03
    Only a metophor. If the lamp brightness change is too large at the bottom end, and the number of brighness levels are fixed, then by using nonlinear steps perhaps the desired control could happen. I am sure that the eye is not linear.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Style and grace : Nil point
  • kt88seampkt88seamp Posts: 112
    edited 2009-11-23 23:05
    I suppose you have some cirquit, which tells your prop on pin 16 that power line currently crossed the 0V. So, 1/120 second means that you already missed half of the periode. You should get faster with your dimmer. said...


    Really, you have zero cross every 1/120th of a second (one half 60 cycles).
    The zero cross circuit uses this optoisololator:

    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.
  • JonnyMacJonnyMac Posts: 9,225
    edited 2009-11-23 23:20
    You'd get better resolution by writing this code in assembly -- something I'll be doing shortly to port the EFX-TEK RC-4 (presently SX-based) to the Propeller. The circuit we use on the RC-4 gives a ZC signal at 120 Hz and with no interrupt to force any specific timing unit, you could easily use 1us timing and get 8_333 steps (microseconds per half cycle).

    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
    426 x 307 - 37K
  • kt88seampkt88seamp Posts: 112
    edited 2009-11-23 23:32
    My optoisolator does the same thing.
  • JonnyMacJonnyMac Posts: 9,225
    edited 2009-11-23 23:37
    I didn't see that you were using the xxx410 -- that's good. So... converting to PASM is going to be your next step to improve the timing resolution.
  • kt88seampkt88seamp Posts: 112
    edited 2009-11-23 23:51
    What equation do you use for the "gamma correction" (compressing the bright and expanding the low)?
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-23 23:56
    Forgive my ignorance:

    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
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-24 00:03
    Ah of course, so the fact he makes p8 0 doesn't make any difference got it. It has been a while since I ever looked at this.

    Still not sure of the need for assembly.

    Graham
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-24 00:05
    The post I replied to was deleted. A triac stays on until crossing, I forgot that.
  • kt88seampkt88seamp Posts: 112
    edited 2009-11-24 00:09
    It would work to have a greater resolution for the lower brightness and a lower for the upper, ega gamma correction. So how much where? Is there an equation?
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-24 00:22
    An equation based on the eye's response still would not take into account the non linear effect of switching the AC signal so I think you would be best to experiment and create a look up table as MagIO2 suggested. By doing away with the division you should be able to fine tune it nicely.

    Graham
  • JonnyMacJonnyMac Posts: 9,225
    edited 2009-11-24 00:32
    Graham Stabler said...
    Still not sure of the need for assembly.

    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.
  • JonnyMacJonnyMac Posts: 9,225
    edited 2009-11-24 00:36
    kt88seamp said...
    Is there an equation?

    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/
  • kt88seampkt88seamp Posts: 112
    edited 2009-11-24 05:17
    OK, I have been reading the manual and am still new to assembler so I have trouble understanding much of it. My goal is to rewrite the dimmer routine in assembler.



    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?
  • JonnyMacJonnyMac Posts: 9,225
    edited 2009-11-24 05:22
    Have a look at the WAITPEQ instruction in the manual -- it will certainly be more effective than your REPEAT loop waiting for the pin change, and it translates nearly directly to PASM.
  • kt88seampkt88seamp Posts: 112
    edited 2009-11-24 05:38
    I replaced that repeat loop with a WAITPEQ instruction. The lamp now flickers. Based on the manual I am trying to compare pin 16 to low. I must have misentered it some how or else something else is up.

    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 
    
    
  • JonnyMacJonnyMac Posts: 9,225
    edited 2009-11-24 06:20
    Okay, you seemed to skip past the part in the manual that explains how waitpeq and waitpne work with pin masks, not pin numbers -- this allows you to monitor for complex state changes. In your case it's simple -- it might look something like this:

    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]~~
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2009-11-24 07:20
    I'm not sure whether transforming the code into PASM will add so much value. The SPIN code does not react as fast as the PASM, but you should have in mind where the reaction takes place. It's close to the 0V. So, in this area switching on the light bulb will be close to invisible. The main section that will effect the brightness much more is well covered by the waitcnt. And as you know waitcnt has a resolution of clockticks - even in SPIN.
    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.
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-24 09:01
    I've nothing against assembly in fact I am coding it right now however I don't think it is required to get this working as you want it, get it working and then translate to assembly would be best.

    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.
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-24 09:35
    And because I need the practise here is some code that may work [noparse]:)[/noparse]

    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
    
    
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2009-11-24 14:28
    Following suggestions:

    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.
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-24 14:50
    1. Good idea In that case indeed brightness would be the index to a table, not too difficult to do but needs a little self modifying code
    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
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-24 15:07
    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
  • MagIO2MagIO2 Posts: 2,243
    edited 2009-11-24 19:11
    Looks good.·· ... but ...
    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)
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-24 19:19
    True, but :P

    [noparse];)[/noparse]
  • Graham StablerGraham Stabler Posts: 2,510
    edited 2009-11-24 23:48
    Oh what the hell:

    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
  • MagIO2MagIO2 Posts: 2,243
    edited 2009-11-25 07:03
    What happened to :P ??? ;o)

    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.
Sign In or Register to comment.