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.
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.
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.
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.
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.
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
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...
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.
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.
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.
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.
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)
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.
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)
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:
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.
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).
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?
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.
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.
Comments
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.
Edit: Added comments and detection of pin start to set inversion when necessary.
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. 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.
EDIT: If those already have smartpin mode set then OUT is overridden by the smartpin. Presumably there is a pinstop() method in spin2.
Maybe it works if you disable the smartpin mode before you read the current state. You anyway start the mode again with pinstart().
Maybe pinfloat(pin) is enough to disable the smart mode...
Andy
EDIT: Found the spin2 method for turning off the smartpin - pinclear().
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.
FLTL #pin
WRPIN #0,#pin
In Spin2:
PINCLEAR(pin)
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
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.
Andy
Here's an example of setup: 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: 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).
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.
Andy
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: ...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: 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: ... 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 ... where duty_cycle is 0.01 for a 0%..100% setup, or 0.00392 for a 0..255 (DMX compatible) setup.
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?
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.