Shop OBEX P1 Docs P2 Docs Learn Events
Share Your [Working] Spin2 Snippets, Please :) - Page 3 — Parallax Forums

Share Your [Working] Spin2 Snippets, Please :)

13

Comments

  • No worries. I can hardly remember a post I didn't go back and edit -- seems like clicking the "Post Comment" button causes me see spelling errors or think of a better way to express an idea.
  • cgraceycgracey Posts: 14,133
    You can always reset the smart pin to clear the base count in progress.
  • Cluso99Cluso99 Posts: 18,066
    JonnyMac wrote: »
    No worries. I can hardly remember a post I didn't go back and edit -- seems like clicking the "Post Comment" button causes me see spelling errors or think of a better way to express an idea.

    Haha! Me too and often a few times. It’s amazing how many times you have to read something to see all the typos etc, and of course all the incorrect autocorrections.
  • kwinnkwinn Posts: 8,697
    Cluso99 wrote: »
    JonnyMac wrote: »
    No worries. I can hardly remember a post I didn't go back and edit -- seems like clicking the "Post Comment" button causes me see spelling errors or think of a better way to express an idea.

    Haha! Me too and often a few times. It’s amazing how many times you have to read something to see all the typos etc, and of course all the incorrect autocorrections.

    I think that is a universal thing.
  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-03-21 16:59
    This comment in the pulses/cycle documentation:
    If X[31:16] is set to 0, the output will be high for the duration of Y > 0.
    ...allows us to create a non-blocking, BS2-style pulseout method.
    pub pulseout(pin, us) | m, x
    
    '' Output pulse on pin that is us microseconds long
    
      m := P_OE | P_PULSE                                           ' set pulse mode
      if (pinread(pin))                                             ' if pin is high
        m |= P_INVERT_OUTPUT                                        '  invert pulse
    
      x := 1 #> (clkfreq / 1_000_000) <# $FFFF                      ' set base timing
      pinstart(pin, m, x, 0)                                        ' setup smart pin
      wypin(pin, us)                                                ' output the pulse
    

    Edit: Added comments and detection of pin start to set inversion when necessary.
  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-03-21 17:56
    Here's an updated version of pulses() that also works with the pin's present state.
    pub pulses(pin, count, khz) | m, x
    
    '' Output count pulses on pin
    '' -- khz is output frequency
    ''    * duty cycle is 50%
    
      m := P_OE | P_PULSE                                           ' set pulse/cycles mode
      if (pinread(pin))                                             ' if pin is high
        m |= P_INVERT_OUTPUT                                        '  invert pulses
     
      x := 2 #> (clkfreq / (kHz * 1000)) <# $FFFF                   ' ticks in period
      x |= ((x >> 1) << 16)                                         ' pulse ticks
      pinstart(pin, m, x, 0)                                        ' setup smart pin
      wypin(pin, count)                                             ' output the pulses
    
  • Well, darn, something is amiss. My sleep schedule has been off for a couple weeks so I'm running tired.

    Was writing a bit of test code for a magazine article using the pulses() method. I connected my 'scope to a couple pins and ran this code.
      pinlow(32)
      pinhigh(33)
      waitms(10)
      
      repeat n from 1 to 8
        pulses(32, n, 10)
        pulses(33, n, 10)
        waitms(200)
    
    What I expected was high-going pulses on Ch1 (pin 32) and low-going pulses on Ch2 (pin 33) -- but both go low. Again, I'm tired, so please be kind when pointing out my silly error. :blush:
    800 x 480 - 22K
  • evanhevanh Posts: 15,126
    edited 2020-03-21 22:02
    For some reason the pinlow()/pinhigh() aren't working. Do they need a DIR control maybe? I've not used spin2.

    EDIT: If those already have smartpin mode set then OUT is overridden by the smartpin. Presumably there is a pinstop() method in spin2.

  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-03-21 22:52
    The pinlow() and pinhigh() methods translate directly to drvl and drvh in the interpreter. If I just run those lines once (not in a loop), they work; it is on the second and subsequent loops that things go off the rails. This version of pulses() fixes the problem, but is not as elegant as I'd like.
    pub pulses(pin, count, khz, level) | m, x
    
    '' Output count pulses on pin
    '' -- khz is output frequency
    ''    * duty cycle is 50%
    '' -- level defines pulse high (1) or low (0)
    ''    * output left in opposite state
    
      m := P_OE | P_PULSE                                           ' set cycles mode
      if (level == 0)                                               ' if low-going pulse
        m |= P_INVERT_OUTPUT                                        '  invert the output
     
      x := 2 #> (clkfreq / (kHz * 1000)) <# $FFFF                   ' ticks in period
      x |= ((x >> 1) << 16)                                         ' pulse ticks
      pinstart(pin, m, x, 0)                                        ' setup smart pin
      wypin(pin, count)                                             ' output the pulses
    
  • AribaAriba Posts: 2,682
    edited 2020-03-22 00:07
    If a pin is in smart-mode you can't read the input state of the pin with pinread(). You only get the 'done-flag' of the smart pin function, which is high after the first pulses are done.

    Maybe it works if you disable the smartpin mode before you read the current state. You anyway start the mode again with pinstart().
    pub pulses(pin, count, khz) | m, x
    
    '' Output count pulses on pin
    '' -- khz is output frequency
    ''    * duty cycle is 50%
    
      pinsetup(pin, 0, 0, 0)                                        ' disable smart pin mode <---
      m := P_OE | P_PULSE                                           ' set pulse/cycles mode
      if (pinread(pin))                                             ' if pin is high
        m |= P_INVERT_OUTPUT                                        '  invert pulses
     
      x := 2 #> (clkfreq / (kHz * 1000)) <# $FFFF                   ' ticks in period
      x |= ((x >> 1) << 16)                                         ' pulse ticks
      pinstart(pin, m, x, 0)                                        ' setup smart pin
      wypin(pin, count)                                             ' output the pulses
    

    Maybe pinfloat(pin) is enough to disable the smart mode...

    Andy
  • evanhevanh Posts: 15,126
    edited 2020-03-22 00:19
    JonnyMac wrote: »
    ... it is on the second and subsequent loops that things go off the rails.
    And there's the why. The smartpin mode is still set from prior iteration and therefore overriding the OUT.

    EDIT: Found the spin2 method for turning off the smartpin - pinclear().

  • If a pin is in smart-mode you can't read the input state of the pin with pinread(). You only get the 'done-flag' of the smart pin function, which is high after the first pulses are done.
    Thanks for the reminder, Andy, and the result I was getting makes perfect sense in that context.

    I think for the auto level mode, the pin would require an external pull-up or pull-down, so I'm going to leave the pulse level parameter in.
  • evanhevanh Posts: 15,126
    Note, smartpin modes are unaffected by a coginit. They are each a little processor themselves. Only a full chip hard reset will clear them all at once.

  • cgraceycgracey Posts: 14,133
    To completely unconfigure a smart pin in PASM:

    FLTL #pin
    WRPIN #0,#pin

    In Spin2:

    PINCLEAR(pin)
  • evanh wrote: »
    Rayman wrote: »
    And another oddity is DIR must be raised for WXPIN to work. Presumably to action the Z change.

    Or maybe because DIR=0 is the reset operation on the pin?

    I would like to see a guide for using the smart pins, at least some basic examples for those of us new to them. I finally got repository mode working between spin2 and pasm2 code.
  • evanhevanh Posts: 15,126
    edited 2020-03-23 05:32
    wmosscrop wrote: »
    evanh wrote: »
    And another oddity is DIR must be raised for WXPIN to work. Presumably to action the Z change.
    Or maybe because DIR=0 is the reset operation on the pin?
    Z is retained indefinitely for repository mode. Neither DIR low nor clearing the mode erases Z. That said, some modes do hold Z in reset while DIR is low.

    I would like to see a guide for using the smart pins, at least some basic examples for those of us new to them. I finally got repository mode working between spin2 and pasm2 code.
    Not quite a guide but there is this - https://forums.parallax.com/discussion/169542/p2-links-for-where-to-obtain-tools-sample-test-code-reference-only/p1
    and this - https://forums.parallax.com/discussion/169069/p2-tricks-traps-differences-between-p1-reference-material-only/p1
  • AribaAriba Posts: 2,682
    While Spin on the P1 was way too slow for this, I think that there should be no problem doing this directly in Spin2, rather than assembly since the speed will be limited by the LED timing anyway. I demonstrated this at the Tachyon code level on the P1 (even though in practice I used a cog code module), so Spin2 should work fine. I basically broke up each data bit into 3 periods, the "from low" to high "clock" period, the data period, and then the clock idle low period. Let's see it done in pure Spin2!

    Here is a Spin2 only code that uses the Async TX smart mode to drive WS2812B LEDs. It works down to 20 MHz sysclock! The Start and Stop bits of an async transmitter have just the right states, if you invert the output and use 3 TX-bits per WS2812 bit.
    CON
      _clkfreq  = 160_000_000
      WS = 47   'pin
    
    VAR
      long  luma[256]
    
    PUB main() | i,b
      repeat i from 0 to 255                           'generate 24bit patterns for 1 color byte
        luma[i] := %110_110_110_110_110_110_110_11'0
        repeat b from 0 to 7
          luma[i] ^= i.[b] << (21-b*3)
    
      b := (clkfreq/2_400_000 * $10000) & !255         '800kHz * 3
      pinstart(WS, P_ASYNC_TX + P_INVERT_OUTPUT + P_OE , b+21, 0)   '22 bits + start + stop = 24
    
      repeat
        repeat i from 0 to 255     'fade 3 LEDs demo
          wsrgb(i,0,0)
          wsrgb(0,i,0)
          wsrgb(0,0,i)
          waitms(25)
    
    PUB wsrgb(r,g,b)               'send patterns for a single LED
      wypin(WS, luma[g])           'green
      repeat until pinread(WS)
      wypin(WS, luma[r])           'red
      repeat until pinread(WS)
      wypin(WS, luma[b])           'blue
      repeat until pinread(WS)
    
    Andy

  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-03-24 07:04
    Finally wrapped my head around analog input using smart pins and made a simple object to turn any pin into an analog input that returns a user-specified range. This is really intended for simple inputs like potentiometers.

    Here's an example of setup:
      pot.start(JOY_X, -500, 500)                                   ' setup joystick input
    
    This will configure the JOY_X pin to analog input and read back -500 (pot at ground) to 500 (pot at 3.3v).

    The read() method in the object returns the scaled value. Here it is in the demo program:
      repeat
        waitms(100)
        term.fstr1(string("Joy X = %4d\r"), pot.read())
    
    While setup and calibration can be done in Spin2, I chose inline P2ASM so that the calibration code would work at any speed (see attached).

  • JonnyMacJonnyMac Posts: 8,912
    edited 2020-04-11 15:13
    Finally got around to playing with NCO frequency mode. The formula for the Y register value is: frequency * 2^32 / clkfreq -- this is where muldiv64() saves the day. As we cannot use a value greater than $FFFF_FFFF, this method uses (frequency * 2) * 2^31 / clkfreq. I found that the X register acts like a frequency divisor, so I input frequency by a factor of 10 (0.1Hz units -- helpful for musical tones), and fix that by setting X to 10. It seems to work nicely.

    Edit: Big thanks to @Ariba for pointing out the use of frac versus muldiv(). Using frac as shown below is the equivalent to (fr01 << 32) +/ clkfreq. It's faster and cleaner.
    pub freqout(pin, duration, fr01) | y
    
    '' Output frequency on pin for duration milliseconds
    '' -- fr01 is in 0.1Hz units
    '' -- duration limited by waitms() (~10s @ 200MHz)
    
      if (fr01 > 0)
        pinstart(pin, P_NCO_FREQ | P_OE, 10, fr01 frac clkfreq)
        waitms(duration)
        
      pinf(pin)
      pinclear(pin)
      
      
    pub nco_freq(pin, fr01)
    
    '' Output frequency on pin
    '' -- fr01 is in 0.1Hz units
    ''    * set to 0 to stop output
    
      if (fr01 > 0)
        pinstart(pin, P_NCO_FREQ | P_OE, 10, fr01 frac clkfreq)
      else
        pinf(pin)
        pinclear(pin)
    
  • evanhevanh Posts: 15,126
    muldiv64() is sliced bread.
  • cgraceycgracey Posts: 14,133
    Use QEXP to convert musical notes to frequencies. That's a very nice use of QEXP.
  • AribaAriba Posts: 2,682
    I think yo can use FRAC instead of muldiv, it's shorter and needs no shifts:
      y := muldiv64((fr01 << 1), $8000_0000, clkfreq)
      becomes:
      y := fr01 FRAC clkfreq
    
    Because FRAC works with unsigned numbers, it may also be the solution for your DUTY calculation (in another thread)

    Andy
  • Thanks, Andy, you are absolutely right.
  • The P2 has two NCO modes: FREQ and DUTY. The first, of course, allows the programmer to set the frequency output of a pin, but maintains a fixed duty cycle (50%). The second allows the duty cycle to be specified, but uses a variable frequency output.

    For NCO_DUTY mode, the Y register calculation is DC% * 2^32. As Andy pointed out above, we can use frac to do the math. For duty cycle as a percentage, the command looks like this:
      pinstart(pin, P_NCO_DUTY | P_OE, x, dc frac 100)
    
    ...where dc is 0..100. If we're using this to control LED output (don't forget the gamma adjustment!), and want to use DMX-compatible 0..255, we do it like this:
      pinstart(pin, P_NCO_DUTY | P_OE, x, level frac 255)
    
    Great. What about X? I stumbled around on this, but after input from @Ariba, @Cluso99, @evanh, and @Rayman, I did an experiment and it made sense. As with NCO_FREQ, X acts like a clock divider which controls the output frequency. The highest output frequency from NCO_DUTY is caused by the duty cycle of 50%; This will cause the internal counter to carry every other cycle. If we set X to 1 and are running a 200MHz clkfreq, the output will be 100MHz.

    To find X for a desired maximum output frequency (50% duty), we can use this formula:
      X = clkfeq / (2 * fr)
    
    ... where fr is the desired maximum output frequency. If I am running a 200MHz system and want NCO_DUTY to have a max output frequency of 50kHz, X becomes 2000. The minimum output frequency can calculated with
      fr = clkfreq / X * duty_cycle
    
    ... where duty_cycle is 0.01 for a 0%..100% setup, or 0.00392 for a 0..255 (DMX compatible) setup.
  • evanhevanh Posts: 15,126
    Good stuff there Jon.

    With the PWM modes alongside, I had pretty much avoided getting my head around that mode. Probably what is most interesting to me at this moment is I note its behaviour is similar to the modulated bitstream of sigma delta ADC/DACs. What's called Pulse Density Modulation (PDM).

  • evanh wrote: »
    Good stuff there Jon.

    With the PWM modes alongside, I had pretty much avoided getting my head around that mode. Probably what is most interesting to me at this moment is I note its behaviour is similar to the modulated bitstream of sigma delta ADC/DACs. What's called Pulse Density Modulation (PDM).

    Isn't DUTY mode not just similar, but actually the same as a dedicated digital/1bit DAC?
  • evanhevanh Posts: 15,126
    edited 2020-04-11 23:28
    I guess it's same as a first-order modulator. It would be interesting handling of Y register updates for PCM samples. It looks like it might act as a buffer, which is good.
  • evanhevanh Posts: 15,126
    Oh, because "high time" is always a single base period, DUTY mode can't cross 50%. PDM is balanced in that respect. So that's a difference.

  • ElectrodudeElectrodude Posts: 1,614
    edited 2020-04-13 15:09
    evanh wrote: »
    Oh, because "high time" is always a single base period, DUTY mode can't cross 50%. PDM is balanced in that respect. So that's a difference.

    I don't know if I've ever tried this on my P2, but from what the P2 documentation says about the output being Z overflow, if you tell DUTY mode to do more than 50%, it will switch from single-period high times to single-period low times. So, it's the same as the P1's counters' NCO DUTY mode, and the same as PDM.
  • cgraceycgracey Posts: 14,133
    edited 2020-04-13 15:23
    Duty can go to 99.999999%.
Sign In or Register to comment.