Shop OBEX P1 Docs P2 Docs Learn Events
Synth object less than 1Hz — Parallax Forums

Synth object less than 1Hz

electricsmithelectricsmith Posts: 20
edited 2010-01-02 06:59 in Propeller 1
Just wondering, I need to generate a broad range of frequencies with some good resolution below 1Hz up to 1KHz.· Has anyone modified the Synth object to work for values less than 1Hz?

Comments

  • Ahle2Ahle2 Posts: 1,179
    edited 2009-12-31 11:05
    What kind of signals are we talking about?
    What's the purpose?
  • electricsmithelectricsmith Posts: 20
    edited 2009-12-31 14:51
    Hey Ahle2, the signals are pulses from a hall effect sensor reading indentations in a motor shaft in order to resolve the RPM of the motor.· I finally got some code working that takes those pulses and converts them to a new frequency.· I had used the synth object to output the new frequency.· what I'm finding is that the RPM and frequency output is perfect at high speeds where the pulses are very small and fast, but they become very inaccurate the slower the RPM's and frequency get.· Reason is due to using integers with the synth object and rounding.· For these slow frequencies, it's almost as if I need a synth object that you can pass 0.654Hz or 1.345Hz to or something like that in order for it to operate correctly.· I guess I need milliHz accuracy if there is such a thing from zero to about 1KHz.
  • LeonLeon Posts: 7,620
    edited 2009-12-31 16:03
    I've used a software DDS with an AVR that goes from 0.07 Hz to about 200 kHz.

    Leon

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Amateur radio callsign: G1HSM
  • JonnyMacJonnyMac Posts: 9,208
    edited 2009-12-31 20:05
    Since AVR code is useless on the Propeller... I wrote this simple demo that may do what you need. Of course, you can't pass a fractional value to a routine so you're forced to pass two values that define that fraction -- see the code.
  • electricsmithelectricsmith Posts: 20
    edited 2009-12-31 20:21
    The code below comes from the Synth spin object in the OBEX. If I send it the Freq value in terms of (cycles per 1000 seconds) or milliHz and just substitute
    frq := fraction(Freq, CLKFREQ*1000, s) 'Compute FRQA/FRQB value
    for
    frq := fraction(Freq, CLKFREQ, s) 'Compute FRQA/FRQB value
    in the Synth code below, wouldn't that give me Hz output that is of the resolution I need? (e.g. I need to generate 1.234Hz on pin 1,
    so I send the command synth("A",1,1234))





    PUB Synth(CTR_AB, Pin, Freq) | s, d, ctr, frq

    Freq := Freq #> 0 <# 128_000_000 'limit frequency range

    if Freq < 500_000 'if 0 to 499_999 Hz,
    ctr := constant(%00100 << 26) '..set NCO mode
    s := 1 '..shift = 1
    else 'if 500_000 to 128_000_000 Hz,
    ctr := constant(%00010 << 26) '..set PLL mode
    d := >|((Freq - 1) / 1_000_000) 'determine PLLDIV
    s := 4 - d 'determine shift
    ctr |= d << 23 'set PLLDIV

    frq := fraction(Freq, CLKFREQ, s) 'Compute FRQA/FRQB value
    ctr |= Pin 'set PINA to complete CTRA/CTRB value

    if CTR_AB == "A"
    CTRA := ctr 'set CTRA
    FRQA := frq 'set FRQA
    DIRA[noparse][[/noparse]Pin]~~ 'make pin output

    if CTR_AB == "B"
    CTRB := ctr 'set CTRB
    FRQB := frq 'set FRQB
    DIRA[noparse][[/noparse]Pin]~~ 'make pin output

    PRI fraction(a, b, shift) : f

    if shift > 0 'if shift, pre-shift a or b left
    a <<= shift 'to maintain significant bits while
    if shift < 0 'insuring proper result
    b <<= -shift

    repeat 32 'perform long division of a/b
    f <<= 1
    if a => b
    a -= b
    f++
    a <<= 1
  • Toby SeckshundToby Seckshund Posts: 2,027
    edited 2009-12-31 20:22
    To read such low frequencies you will have to count over a long period or have a phase locked loop to multipy it up and read that. Then there is the fine art of integrating the multiplied frequency enough, but not too much that you get back to the original frequency. I beleive that this is how heartrate readouts work, etc.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Style and grace : Nil point
  • LeonLeon Posts: 7,620
    edited 2009-12-31 20:46
    JonnyMac said...
    Since AVR code is useless on the Propeller... I wrote this simple demo that may do what you need. Of course, you can't pass a fractional value to a routine so you're forced to pass two values that define that fraction -- see the code.

    The same technique could be used on the Propeller.

    Leon

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Amateur radio callsign: G1HSM
  • JonnyMacJonnyMac Posts: 9,208
    edited 2009-12-31 21:13
    @electricsmith: I think CLKFREQ*1000 is going to be problematic in that the value will exceed 32 bits.

    @Leon: Talk is cheap -- post code; Propeller code, that is, nobody here gives a rat's behind about the AVR or the XMOS. tongue.gif When I want AVR code, I log in to an AVR forum.
  • LeonLeon Posts: 7,620
    edited 2009-12-31 21:22
    The algorithm is very simple:

    ; main loop
    ;
    ;    r28,r29,r30 is the phase accumulator
    ;      r24,r25,r26 is the adder value determining frequency
    ;
    ;     add value to accumulator
    ;    load byte from current table in flash memory
    ;    output byte to port
    ;    repeat 
    ;
    LOOP1:
            add        r28,r24            ; 1
            adc        r29,r25            ; 1
            adc        r30,r26            ; 1
            lpm                    ; 3
            out        PORTB,r0        ; 1
            rjmp    LOOP1            ; 2 => 9 cycles
    
    
    



    Converting it to PASM should be quite straightforward, it'll run a lot faster with 32-bit arithmetic. If I can find one of my AVR or ARM boards with a DAC chip on it I'll try it on the Propeller.

    Leon

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Amateur radio callsign: G1HSM

    Post Edited (Leon) : 12/31/2009 9:39:26 PM GMT
  • StefanL38StefanL38 Posts: 2,292
    edited 2009-12-31 21:41
    Ok simple algorithm but how do you feed it with the frequency?

    let's say 0.007943 Hz. What values do r24..r30 have in this case ?

    best regards

    Stefan
  • LeonLeon Posts: 7,620
    edited 2009-12-31 22:16
    Here is the formula:

    ;
    ; Output frequency (using 24 bit accumulator) :
    ;
    ;    f = deltaPhase * fClock/2^24
    ;
    ;   fClock is in this case the CPU clock divided by the
    ;    number of cycles to output the data ( 9 cycles )
    ;
    ;    f = r24/r25/r26 * (11059200/9)/16777216
    ;
    ;    f = r24/r25/r26 * 0.073242188
    ;
    ;    fMax (theoretical) = 0.5 * fClock
    ;
    
    



    Leon

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Amateur radio callsign: G1HSM
  • electricsmithelectricsmith Posts: 20
    edited 2009-12-31 23:54
    okay, tried the clkfreq*1000, total garbage out just like you said.· I'm so lost with what you guys are talking about with the assembly.· I assume you are just creating a formula to loop different numbers of clock cycles at 1 and then at zero, but i am still a little lost.
  • JonnyMacJonnyMac Posts: 9,208
    edited 2010-01-01 01:26
    Tragically, there's been a lot of assembly posted that has nothing to do with the Propeller, and I'm sure you (and others) find that very confusing. My little demo is actually quite simple (PASM is pretty easy, really) and if you spend a few minutes with it -- ignoring the non-Propeller code in this forum -- you'll probably get the gist of it. In Spin my program would look like this:

    pub slowsqw(pin) | t
    
      dira[noparse][[/noparse] pin ]~~
    
      t := cnt
      repeat
        waitcnt(t += hctics)
        !outa[noparse][[/noparse] pin ]
    



    The reason for using PASM instead of Spin is to get rid of the instruction overhead necessary to get good accuracy at fractional Hz values.


    tongue.gif Just discovered the "Ignore User" button and no longer have to put up with [noparse][[/noparse]near-useless] AVR assembly in the Propeller forum

    Post Edited (JonnyMac) : 1/1/2010 1:34:09 AM GMT
  • DroneDrone Posts: 433
    edited 2010-01-01 13:22
    JonnyMac said...
    Just discovered the "Ignore User" button and no longer have to put up with [noparse][[/noparse]near-useless] AVR assembly in the Propeller forum

    That's just plain mean. I found the AVR code useful in this thread - it's easy understand as explained, and easy to port to PASM. Thank you Leon!

    Regards, David
  • StefanL38StefanL38 Posts: 2,292
    edited 2010-01-01 13:47
    Happy new year !

    In some cases I love to rersearch across the internet to find information (even if it is just for somebody else and not for me)

    In this case I don't. Leon or Drone can you post a DETAILED example
    how to calculate every single value and how the code works

    To me as I'm NOT familiar with AVR-assembler I just see
    add values, do some kind of output and jump
    but I can't see any relation between the
    add the out and the lpm (whatever it is)

    I would be very happy if you would post an example that shows ALL details of how to calculate it
    starting from a low frequency

    r24 = ........

    r25 = .......
    etc.

    best regards

    Stefan
  • LeonLeon Posts: 7,620
    edited 2010-01-01 14:14
    Try Googling Direct Digital Synthesis, it's a big subject.

    LPM is Load Program Memory. It loads the memory addressed by R29 and R30 (from the waveform look-up table) into the R0 register. It is then output to the DAC.

    I based my hardware and software on this work by Jesper Hansen:

    www.myplace.nu/avr/minidds/

    The calculation you are after is trivial for a Propeller, as you only have one 32-bit register.

    Leon

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Amateur radio callsign: G1HSM

    Post Edited (Leon) : 1/1/2010 2:21:36 PM GMT
  • JonnyMacJonnyMac Posts: 9,208
    edited 2010-01-01 20:02
    Sorry, Drone, I wasn't trying to be mean -- I even used a smiley to indicate I was being a *little* jokey -- but just a little. Posting code from other processors in this forum, unless those listings are really generic and spelled out very well (assuming no knowledge of the other processor), is just plain silly (i.e., not helpful). That was the point I was trying to make. So far in this thread I seem to be the only person who has posted working Propeller code. And I'm the mean one?.... tongue.gif

    Happy New Year!
  • LeonLeon Posts: 7,620
    edited 2010-01-01 20:31
    Here is the algorithm in C for an LPC2138 ARM chip:

        // phase increment = accumulator size / sample rate * frequency
        accum += freq_mult * freq;
        sample = sine_table[noparse][[/noparse]accum>>24]; // 8 bit sine pointer, use for 256 table 
        // send the sample to the DAC, shift and mask appropriate bits
        DACR = ((sample) << 6) & 0xFFC0;
    
    



    It is run from a timer ISR.

    Leon

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Amateur radio callsign: G1HSM
  • JonnyMacJonnyMac Posts: 9,208
    edited 2010-01-01 21:00
    Since the Propeller has a built-in sine table, why not show us how to do it in PASM?
  • Tracy AllenTracy Allen Posts: 6,666
    edited 2010-01-01 21:35
    @electricsmith, could you use an external divider chip, to say divide the synth output by 1024? Then the synth could operate in a range that gives better resolution.

    Do you understand why the counter module has the poor resolution? With an 80 mhz clkfreq, the frq parameter is 53. That means that every 12.5 nanoseconds, 53 is added to the phase accumulator, and after 1.013 second the sum passes 2^32 and rolls over back to zero. And repeats at a base frequency of 0.9872 Hz. Not exactly one second. Nearby values bracket the 1 second value:
    52 --> 0.9686 Hz
    53 --> 0.9872 Hz
    54 --> 1.005 Hz
    55 --> 1.024 Hz
    That is in the nature of direct digital synthesis, which is exactly what the cog counters do. If you can use an external divider chip, the resolution would be much better and also set it and forget it, involving only the cog counter.
    53687 --> 999.998 Hz for 1000 Hz entry
    53740 --> 1000.986 Hz for 1001 Hz entry
    In the digital synthesis of 1000 Hz, the frq value 53687 is added to the phase accumulator 80000 times before it overflows and rolls back to zero, and that is one compete cycle of the most significant bit, high and low. (Actually, it sometimes takes 80001 times for it to roll over, a slightly longer period that makes it 999.998 Hz instead of 1000 Hz, but that bobble and subharmonic content is intrinsic to DDS).

    I think the Spin routine that Jon posted about 5 back might well be quite accurate enough for your application at the milliHertz level. There will be a deterministic delay from the waitcnt to the actual pin toggle, which will not affect the frequency. I sense there is some confusion about how to calculate the fractional frequency. To repeat the core snippet,
    t := cnt
      repeat
        waitcnt(t += hctics)
        !outa[noparse][[/noparse] pin ]
    



    The constant hctics needs to expressed in number of clock cycles. So if you specify your desired frequency FmHz in milliHertz, then formally,
    hctics = clkfreq *1000 / (FmHz * 2) where 1000 is for the milli factor and the 2 is for 1/2 cycle low and 1/2 cycle high.
    For example, with clkfreq=80 MHz, and a desired of 1001 milliHertz, then hctics=39_960_039. Check: 12.5 ns period * 39_960_039 = 499.5 ms, half a period at 1.001 Hz.

    But as you already know, you can't muliply clkfreq by 1000 within the 2^32 long. One way to manage it is,
    hctics := (clkfreq * 50 / FmHz) * 10 ' assuming clkfreq<=80mHz, stays within bounds of 2^32
    hctics := (clkfreq * 25 / FmHz) * 20 ' assuming clkfreq<=80mHz, stays within bounds of 2^31

    or with a two-step division (slightly more accurate) as
    hctics := (clkfreq / FmHz * 500) + (((clkfreq // FmHz) * 500) / FmHz) ' it would be *500 to account for the factor of 2

    edit: need to stay within bound of 2^31 to avoid probems with twos complement

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com

    Post Edited (Tracy Allen) : 1/2/2010 3:45:13 AM GMT
  • electricsmithelectricsmith Posts: 20
    edited 2010-01-01 23:35
    Okay, I think I'm tracking.· Based on what you guys have written up and explained to me, the following should work right?:
    My intent here is to start a cog that continually checks some variables modified by the main cog and turns on or off on a specific pin
    a squarewave that is accurate to the mHz resolution as discussed above.· I understand that the code below takes some time to run so there
    will be some inaccuracy.· I was thinking if I could determine what that tick count for those instructions were it could probably be subtracted
    from the hctics value to 'calibrate' it.· I may be way off base though.·

    ************************************************************************************
    'Pass this routine the address of the frequency in (cycles/1kSeconds or mHz) (e.g. 1.234Hz would be 1234Hz)
    'Pin is the address of the output pin
    'Repeat_ptr is the address to a flag that is set when the synth should generate and when it should not


    <<<<code to be inserted here to start this routine in its own cog>>>>

    PUB Synth_mHz(Pin_ptr, mHz_ptr, Repeat_ptr) | A,B,C,hctics

    · A:=LONG[noparse][[/noparse]Pin_ptr][noparse][[/noparse]0]
    ··DIRA[noparse][[/noparse]A]:=1
    ·
    · REPEAT
    ··· B:=LONG[noparse][[/noparse]mHz_ptr][noparse][[/noparse]0]
    ··· C:=LONG[noparse][[/noparse]Repeat_ptr][noparse][[/noparse]0]

    ··· IF C==0
    ······NEXT

    ··· hctics := (clkfreq /·B * 500) + (((clkfreq / B) * 500) / B)··'<<minus some adj for·repeat loop time
    ·················································································'I changed the clkfreq//B to clkfreq/B, I assumed this was a typo

    ··· REPEAT
    · ··· C:=LONG[noparse][[/noparse]Repeat_ptr][noparse][[/noparse]0]
    ····· IF C==0
    ······· EXIT
    ····· ELSE
    ······· WAITCNT(hctics + CNT)
    ······· !OUTA[noparse][[/noparse]A]

    DAT
    · PIN············· LONG 1
    · mHz············ LONG 1234
    · SYNTH_ON··· LONG 1
  • JonnyMacJonnyMac Posts: 9,208
    edited 2010-01-02 00:12
    You should grab cnt before dropping into the repeat loop (use a local variable to hold it, then update that in the waitcnt instruction), otherwise you add the instruction overhead for retrieving cnt to every loop iteration (this is discussed in the Propeller manual).

    Something like this -- pass the pin (#), the address of the enable flag, and the address of the half-cycle ticks value.

    pub slowsqw(p, enflag, hcpntr)| t, enabled, hctix 
    
      dira[noparse][[/noparse]p]~~                                                      ' make pin an output
      t := cnt                                                       ' sync with system counter
      
      repeat
        enabled := long[noparse][[/noparse]enflag]                                      ' check enable flag
        if enabled
          hctix := long[noparse][[/noparse]hcpntr]                                      ' get half-cycle tix
          waitcnt(t += hctix)                                        ' wait
          !outa[noparse][[/noparse]p]                                                   ' toggle pin
        else
          outa[noparse][[/noparse]p]~                                                   ' pin low when disabled
    



    I updated (and attached) the PASM version I wrote yesterday to add an enable flag (your code indcates a desire for that) and uses Tracy's hctics calculation in favor of the fp routines I originally tried.

    Post Edited (JonnyMac) : 1/2/2010 1:05:52 AM GMT
  • electricsmithelectricsmith Posts: 20
    edited 2010-01-02 01:41
    omg, that's awesome man, thanks. I had a cold chill, I actually followed the assembly code. I'm tracking now. Toggling the enable flag is a method for generating IR as well I assume. I am a long way from a fully assembled and optimized brain however. This is good. I will post the whole application when I get it done and get your input on optimizing. My output will use this synth routine you've helped me with. Thank you guys for all your help. This stuff is very addictive.
  • JonnyMacJonnyMac Posts: 9,208
    edited 2010-01-02 01:57
    Cool! I find PASM pretty friendly and have become quite comfortable with using it in my projects (which are never terribly complicated). My N&V column this month has to do with receiving and transmitting Sony IR codes. Parallax has a digital reprint online in case the IR stuff you mention has to do with IR remotes.

    Here's a cool tip on controlling an IR LED. You can use a background cog or even a foreground counter module to do the modulation -- just connect the modulation output to the cathode side. That way, you can simply write a "1" to the cathode pin (from any cog) to disable the IR output. Pretty cool.

    Post Edited (JonnyMac) : 1/2/2010 2:05:14 AM GMT
  • Tracy AllenTracy Allen Posts: 6,666
    edited 2010-01-02 03:33
    @Jon,
    Oops, I got caught in the twos complement trap. The result, 80_000_000 * 50 = 4_000_000_000 is indeed less than 2^32. However, it is greater than 2^31, so the subsequent division evaluates it as a twos complement number, -294_967_296. That gives the wrong answer. So the following is safe and is a positive number when clkfreq=80MHz:
    hctics := (clkfreq * 25 / FmHz) * 20 ' assuming clkfreq<=80mHz, stays within bounds of 2^31

    The other formula I listed is more accurate

    @electricsmith,
    The // in the formula is not a typo. The first expression is a straight division by B, then the second expression works on the remainder from the first division.

    Simple example: 8000/113 = 70.79646017699
    ' long way around...
    x := 8000 / 113   ' integer result x=70
    r := 8000 // 113   ' integer remainder = 90, meaning,  exact answer is (70 + (90/113))
    y := x * 1000 + (r * 1000 / 113)    ' improved result, 70000 + (90000/113) = 70000 + 796 = 70796
    ' to three decimal places
    


    The original integer result has been shifted left three decimal places to make room for the contribution from the remainder.

    This roundabout method allows making calcs with big numbers so that they don't overflow 2^32, or I should say 2^31 so as not to get into trouble with twos complement. Take this, with clkfreq 80 MHz and B=1234 milliHertz.
    hctics := ((clkfreq / B * 1000) + (((clkfreq // B) * 1000) / B)) / 2
    ' first expression = (80_000_000 / 1234 * 1000) = 64829000 which is less than 2^31 so long as B is greater than about 40 milliHertz
    ' second expression = (80_000_000 // 1234 * 1000 / 1234) = (1014000 / 1234) = 821
    ' overall result hctics = 64_829_821 / 2 = 32_414_910

    That compares with an "exact" calculated value of (clkfreq * 1000) / (1234 * 2) = 32,414,910.8589951

    Working that with the simpler formula,
    hctics := (80_000_000 * 25 / 1234) * 20 ' result 32,414,900 pretty close
    and better than simply
    hctics := 80_000_000 / 1234 * 500 ' result 32,414,500

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
  • StefanL38StefanL38 Posts: 2,292
    edited 2010-01-02 06:40
    @Tracy: thank you very much Tracy ! Very good description.


    @Leon:
    If you explain your NON-propeller-code like Tracy Allen did explain things, you might win people to take a look at other microcontrollers and even win them to use other microcontrollers too.
    With your short and snippy style like above I guess you are just annoying

    best regards

    Stefan

    Post Edited (StefanL38) : 1/2/2010 6:46:47 AM GMT
  • Tracy AllenTracy Allen Posts: 6,666
    edited 2010-01-02 06:59
    Um, breaker, breaker. To avoid the hot glow of invidious comparison, I can sincerely say that I often find Leon's contributions very insightful. Separate from that, I do appreciate the complement, thank you. The key I think is to go for the fundamentals no matter what processor you are talking about. The Prop is well endowed with counter power.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Tracy Allen
    www.emesystems.com
Sign In or Register to comment.