Is there a compiler that takes a SPIN program and generates the ASM for it? To clarify further, I mean a small SPIN program where the resulting ASM can fit into 1 cog.
Not really. You might look at PropBasic which is a compiler that takes a variant of Basic and translates it into ASM and/or LMM (Large Memory Model - a semi-interpreted ASM that executes out of HUB memory at about 1/4 COG speed). The output of PropBasic is Spin source code (mostly DAT blocks) that gets compiled / assembled.
On a number of occasions I've written a spin program knowing it would be changed into asm at some stage. I tend to write the spin code in an "asm" sort of way. For instance - if there are multiple and/or/bitshifts in a line of spin, split it up into individual lines. And if you have a variable multiplied by 2 or 4 etc, use a bitshift instead. There is some fascinating maths involved in multiplying any number by a constant using bitshifts only - and this sort of coding works out faster.
The thing is that using tricks like reducing "n x 15" to "n bitshift left 4 places and subract n from the answer" are not the sort of things an automatic translation program can do very easily.
I believe the very clever C programmers have got code that you can tell the compiler whether to put it in hub or cog.
If you like, maybe post some of your spin code and we can think about how to convert it to asm?
The thing is that using tricks like reducing "n x 15" to "n bitshift left 4 places and subract n from the answer" are not the sort of things an automatic translation program can do very easily.
I'm often surprised by the optimizations gcc performs :-). Here's a log of converting a simple .spin function to PASM, automatically:
This is under Linux, so the "$" is a command prompt and "cat" is the program to type a file out. The weird name "_ZN8testSpin5Mul15Ei" is due to the way C++ encodes type information in function names. Also note that I did give -O3 to propeller-elf-gcc to tell it to optimize for speed as much as it can; if it's optimizing for space (the normal case in the Propeller world) it'll generate a call to a multiply routine instead because that takes less space than the shift and sub.
Of course the .pasm isn't a complete program, you'd need the register declarations and some interface code. Giving the "-mspin" argument to propeller-elf-gcc will make it output a lot of that for you. Also, it's using the normal C calling convention (where return addresses are stored in the "lr" register) instead of the PASM calling convention (where you return with "ret"); there are ways to force gcc to use that, but they require editing the .cpp file that spin2cpp creates.
Ok, below is the source code. What I want to convert to ASM is contained in the MafControlSpinCog method. The gist of the logic is I have 2 inputs, MafInPin and MafClampInPin, and 1 output, MafOutSelPin. MafInPin is a 5V squarewave signal that varies from 20 to 200 Hz. While MafClampInPin == 1, MafOutSelPin is continuously set to the value !MafInPin and I measure the clock counts for the high and low periods of MafInPin. When MafClampInPin transistions to 0, MafOutSelPin is held at the last frequency measured for the high and low period clock counts of MafInPin (i.e. it is clamped). With SPIN code, I get about 40 to 80 uSecs of delay from sampling MafInPin to output on MafOutSelPin.
{{
File.......... MafClamp.spin
Purpose....... Control and test routines for the Maf Clamp board.
Author........ Jim Edwards, Asm code for MafControlAsmCog created by Kuroneko
E-mail........ jim.edwards4@comcast.net
History....... v1.0 - Initial release
Copyright..... Copyright (c) 2012 Jim Edwards
Terms......... See end of file for terms of use.
}}
' TODO:
' 1. Look at what happens if clamp/mirror control loses sync with mafin inputs. Might happen when cycling car power. What happens if there is no pulsing when power is turned on, but before car is turned over.
OBJ
pst : "Parallax Serial Terminal"
CON
' General constants
_clkmode = XTAL1 | PLL16X
_xinfreq = 5_000_000
' Debug and test constants
_PST_ENABLED = FALSE
_TEST_ENABLED = TRUE
_USE_CONTROL_SPIN_COG = FALSE
_ERR_CHECKING_ENABLED = FALSE
' Control spin cog constants
_STACK_SIZE = 64
_STATE_INIT = 0
_STATE_MIRROR = 1
_STATE_CLAMP = 2
' Waveform test cog constants
_FREQ1 = 50
_NCYCLES1 = 4
_FREQ2 = 100
_NCYCLES2 = 4
_DUTY_25 = 0
_DUTY_50 = 1
_DUTY_75 = 2
_CLAMP = 0
_MIRROR = 1
' Operational I/O pin constants
_MAFIN1_PIN = 5
_MAFIN2_PIN = 11
_MAFCLAMPIN_PIN = 20
_MAFOUTSEL1_PIN = 27
_MAFOUTSEL2_PIN = 18
_CLAMPONLED_PIN = 15
' Test output pin constants
_MAFIN1RAW_TESTOUTPIN = 24
_MAFIN2RAW_TESTOUTPIN = 22
_MAFCLAMPINRAW_TESTOUTPIN = 23
VAR
long stack1[_STACK_SIZE]
long stack2[_STACK_SIZE]
long stack3[_STACK_SIZE]
PUB MafClampMain
dira[_CLAMPONLED_PIN]~~
if (_PST_ENABLED)
pst.Start(115200)
waitcnt(clkfreq * 2 + cnt)
pst.Clear
pst.Str(string("MafClampMain", pst#NL))
if (_TEST_ENABLED)
MafTestCogStart(_MAFIN1RAW_TESTOUTPIN, _MAFIN2RAW_TESTOUTPIN, _MAFCLAMPINRAW_TESTOUTPIN, @stack1)
if (_USE_CONTROL_SPIN_COG)
MafControlSpinCogStart(_MAFIN1_PIN, _MAFOUTSEL1_PIN, _MAFCLAMPIN_PIN, @stack2)
MafControlSpinCogStart(_MAFIN2_PIN, _MAFOUTSEL2_PIN, _MAFCLAMPIN_PIN, @stack3)
else
MafControlAsmCogStart(_MAFIN1_PIN, _MAFOUTSEL1_PIN, _MAFCLAMPIN_PIN)
MafControlAsmCogStart(_MAFIN2_PIN, _MAFOUTSEL2_PIN, _MAFCLAMPIN_PIN)
repeat
outa[_CLAMPONLED_PIN] := !ina[_MAFCLAMPIN_PIN]
if (_TEST_ENABLED)
waitcnt(clkfreq / 1000 + cnt)
else
waitcnt(clkfreq / 10 + cnt)
PUB MafTestCogStart(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, stack_ptr) : cog
' Starts test cog process for testing operation of Maf Clamp board.
cog := cognew(MafTestCog(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin), stack_ptr) + 1
PUB MafTestCog(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin) | index, duty_cycle
dira[mafin1raw_testoutpin]~~
dira[mafin2raw_testoutpin]~~
dira[mafclampinraw_testoutpin]~~
outa[mafin1raw_testoutpin]~
outa[mafin2raw_testoutpin]~
outa[mafclampinraw_testoutpin]~~
repeat
repeat index from 0 to 2
if (index == 0)
duty_cycle := _DUTY_25
elseif (index == 1)
duty_cycle := _DUTY_50
else
duty_cycle := _DUTY_75
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ1, _NCYCLES1, duty_cycle, _MIRROR, _MIRROR, 0)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ2, _NCYCLES2, duty_cycle, _MIRROR, _MIRROR, 0)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ1, _NCYCLES1, duty_cycle, _MIRROR, _CLAMP, 1)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ2, _NCYCLES2, duty_cycle, _CLAMP, _MIRROR, 1)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ1, _NCYCLES1, duty_cycle, _MIRROR, _CLAMP, 2)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ2, _NCYCLES2, duty_cycle, _CLAMP, _MIRROR, 2)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ1, _NCYCLES1, duty_cycle, _MIRROR, _CLAMP, 3)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ2, _NCYCLES2, duty_cycle, _CLAMP, _MIRROR, 3)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ1, _NCYCLES1, duty_cycle, _MIRROR, _MIRROR, 0)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ2, _NCYCLES2, duty_cycle, _MIRROR, _CLAMP, 1)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ1, _NCYCLES1, duty_cycle, _CLAMP, _MIRROR, 1)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ2, _NCYCLES2, duty_cycle, _MIRROR, _CLAMP, 2)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ1, _NCYCLES1, duty_cycle, _CLAMP, _MIRROR, 2)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ2, _NCYCLES2, duty_cycle, _MIRROR, _CLAMP, 3)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ1, _NCYCLES1, duty_cycle, _CLAMP, _MIRROR, 3)
TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, _FREQ2, _NCYCLES2, duty_cycle, _MIRROR, _MIRROR, 0)
PUB ValidDutyCycle(duty_cycle)
return((duty_cycle == _DUTY_25) OR (duty_cycle == _DUTY_50) OR (duty_cycle == _DUTY_75))
PUB ValidStartStopState(start_state, stop_state)
return(((start_state == _CLAMP) OR (start_state == _MIRROR)) AND ((stop_state == _CLAMP) OR (stop_state == _MIRROR)))
PUB TestWaveForm(mafin1raw_testoutpin, mafin2raw_testoutpin, mafclampinraw_testoutpin, freq, ncycles, duty_cycle, start_state, stop_state, stop_nquarter_cycles) | nquarter_cycles, quarter_period
nquarter_cycles := ncycles * 4
if ((NOT _ERR_CHECKING_ENABLED) OR ((freq > 0) AND (ncycles => 1) AND (ValidDutyCycle(duty_cycle)) AND (ValidStartStopState(start_state, stop_state)) AND (stop_nquarter_cycles < nquarter_cycles)))
outa[mafin1raw_testoutpin]~~
outa[mafin2raw_testoutpin]~~
outa[mafclampinraw_testoutpin] := start_state
quarter_period := clkfreq / (freq * 4)
repeat while (nquarter_cycles > 0)
waitcnt(quarter_period + cnt)
nquarter_cycles--
if (((nquarter_cycles // 4) == 0) OR ((duty_cycle == _DUTY_25) AND ((nquarter_cycles // 4) == 3)) OR ((duty_cycle == _DUTY_50) AND ((nquarter_cycles // 4) == 2)) OR ((duty_cycle == _DUTY_75) AND ((nquarter_cycles // 4) == 1)))
!outa[mafin1raw_testoutpin]
!outa[mafin2raw_testoutpin]
if (nquarter_cycles == stop_nquarter_cycles)
outa[mafclampinraw_testoutpin] := stop_state
PUB MafControlSpinCogStart(mafin_pin, mafoutsel_pin, mafclampin_pin, stack_ptr) : cog
' Starts spin-based Maf control cog process.
cog := cognew(MafControlSpinCog(mafin_pin, mafoutsel_pin, mafclampin_pin), stack_ptr) + 1
PUB MafControlSpinCog(mafin_pin, mafoutsel_pin, mafclampin_pin) | maf_cur_val, maf_last_val, c1, c2, c3, high_cnt, low_cnt, state, full_cycle
dira[mafin_pin]~
dira[mafoutsel_pin]~~
dira[mafclampin_pin]~
state := _STATE_INIT
repeat
case state
_STATE_INIT:
' Startup state, just mirror inverted value of MafInPin to mafoutsel_pin and wait to acquire first measurement
' of high and low counts of one cycle of mafin_pin.
maf_last_val := c1 := -1
high_cnt := low_cnt := 0
full_cycle := FALSE
repeat until (full_cycle)
maf_cur_val := ina[mafin_pin]
outa[mafoutsel_pin] := !maf_cur_val
if ((maf_cur_val == 1) AND (maf_last_val == 0)) ' Rising edge detected on mafin_pin.
if (c1 == -1)
c1 := cnt
else
c3 := cnt
high_cnt := c2 - c1
low_cnt := c3 - c2
c1 := c3
full_cycle := TRUE
elseif ((maf_cur_val == 0) AND (maf_last_val == 1)) ' Falling edge detected on mafin_pin.
c2 := cnt
maf_last_val := maf_cur_val
state := _STATE_MIRROR
_STATE_MIRROR:
' mafoutsel_pin will follow the inverted value of mafin_pin during this state. When the active low signal mafclampin_pin
' turns to 0, wait until mafin_pin reaches it's next rising edge transition.
full_cycle := FALSE
repeat until ((ina[mafclampin_pin] == 0) AND full_cycle)
full_cycle := FALSE
maf_cur_val := ina[mafin_pin]
outa[mafoutsel_pin] := !maf_cur_val
if ((maf_cur_val == 1) AND (maf_last_val == 0)) ' Rising edge detected on mafin_pin.
c3 := cnt
high_cnt := c2 - c1
low_cnt := c3 - c2
c1 := c3
full_cycle := TRUE
elseif ((maf_cur_val == 0) AND (maf_last_val == 1)) ' Falling edge detected on mafin_pin.
c2 := cnt
maf_last_val := maf_cur_val
state := _STATE_CLAMP
_STATE_CLAMP:
' mafoutsel_pin will be clamped at a frequency given by the last high and low counts measured while in the _STATE_MIRROR
' state. When the active low signal mafclampin_pin returns to 1, hold mafoutsel_pin at 1 until mafin_pin reaches it's
' next rising edge transition.
repeat until (ina[mafclampin_pin] == 1)
c1 := cnt
repeat until ((cnt - c1) => high_cnt)
outa[mafoutsel_pin] := 0
c1 := cnt
repeat until ((cnt - c1) => low_cnt)
outa[mafoutsel_pin] := 1
maf_last_val := -1
repeat
maf_cur_val := ina[mafin_pin]
if ((maf_cur_val == 1) AND (maf_last_val == 0)) ' Rising edge detected on mafin_pin.
c1 := cnt ' Ensure that _STATE_MIRROR state starts counting process properly for high/low period.
outa[mafoutsel_pin] := !maf_cur_val
state := _STATE_MIRROR
maf_last_val := maf_cur_val
quit
maf_last_val := maf_cur_val
PUB MafControlAsmCogStart(mafin_pin, mafoutsel_pin, mafclampin_pin) : cog | pins
pins := NEGX | mafclampin_pin << 18 | mafoutsel_pin << 9 | mafin_pin
if cog := cognew(@MafControlAsmCog, @pins) + 1
repeat while pins
DAT org 0
MafControlAsmCog
movi ctra, #%0_01000_000 ' POS detector
rdlong temp, par ' [!Z]:p2:p1:p0 = 5:9:9:9
movs ctra, temp ' input pin
shl pin_0, temp ' pin -> mask
wrlong zero, par ' release caller
shr temp, #9 ' [!Z]:p2:p1
movd ctra, temp ' output pin (inverted)
shl pin_1, temp ' pin -> mask
shr temp, #9 ' [!Z]:p2
shl pin_2, temp ' pin -> mask
mov dira, pin_1 ' drive output
movs ctrb, ctra ' grab input pin for NEG detector
movi ctrb, #%0_01100_000 ' NEG detector
' At this point we have both counters setup for level detection. Counter A will be using
' feedback mode later to support the pass-through (out := !in).
mov frqa, #1 ' |
mov frqb, #1 ' activate counters
:revert waitpne pin_0, pin_0 ' |
mov phsa, #0 ' |
waitpeq pin_0, pin_0 ' |
mov phsb, #0 ' sync accumulators
andn outa, pin_1 ' release output
movi ctra, #%0_01001_000 ' enable feedback
:mirror waitpne pin_0, pin_0 ' high/low transition
mov period_h, phsa ' |
mov phsa, #0 ' record and reset
waitpeq pin_0, pin_0 ' low/high transition
mov period_l, phsb ' |
mov phsb, #0 ' record and reset
mov cnt, cnt ' sync point
test pin_2, ina wz,wc ' sample clamp pin
if_z jmp #:clamp ' active (carry clear) (%%)
jmp #:mirror ' keep measuring
:loop waitcnt cnt, period_l ' |
nop ' | counter test at end of
nop ' | high cycle
or outa, pin_1 ' manual: high (input low)
waitcnt cnt, period_h ' |
test pin_2, ina wz ' | sample clamp pin
if_nz jmp #:revert ' | inactive
:clamp andn outa, pin_1 ' manual: low (input high)
if_nc movi ctra, #%0_01000_000 ' disable feedback
if_nc add cnt, period_h ' |
if_nc sub cnt, #26 + 1 ' adjust initial low period
jmpret zero, #:loop wc,nr ' set carry (%%)
' initialised data and/or presets
pin_0 long 1 ' mafin_pin
pin_1 long 1 ' mafoutsel_pin
pin_2 long 1 ' mafclampin_pin
' uninitialised data and/or temporaries
period_h res 1
period_l res 1
temp res 1
fit
CON
zero = $1F0 ' par
DAT
{{
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ TERMS OF USE: MIT License │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
│is furnished to do so, subject to the following conditions: │
│ │
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
│ │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}
Ok, when I get it done I will post it. The part I want to do in ASM shouldn't be that hard I hope.
If there are any issues let me know. For testing I used 1Hz/8Hz signals to have visual feedback. The active code is below and including the init method.
CON
_clkmode = XTAL1|PLL16X
_xinfreq = 5_000_000
CON
_MafInPin = 16
_MafOutSelPin = 17
_MafClampInPin = 23
PUB selftest
outa[_MafClampInPin] := 1
dira[16..23]~~
ctra := constant(%0_00100_000 << 23 | _MafInPin)
frqa := 53 ' 1Hz
init(_MafInPin, _MafOutSelPin, _MafClampInPin)
waitcnt(clkfreq*2 + cnt)
frqa := 429 ' 8Hz
waitcnt(clkfreq*2 + cnt)
frqa := 53 ' 1Hz
waitcnt(clkfreq*2 + cnt)
outa[_MafClampInPin]~ ' clamp 1Hz
waitcnt(clkfreq*2 + cnt)
frqa := 429 ' 8Hz
waitcnt(clkfreq*2 + cnt)
outa[_MafClampInPin]~~ ' mirror
waitcnt(clkfreq*2 + cnt)
outa[_MafClampInPin]~ ' clamp 8Hz
waitcnt(clkfreq*2 + cnt)
frqa := 53 ' 1Hz
waitcnt(clkfreq*2 + cnt)
outa[_MafClampInPin]~~ ' mirror
waitpne(0, 0, 0)
PUB init(MafInPin, MafOutSelPin, MafClampInPin) : cog | pins
pins := NEGX | MafClampInPin << 18 | MafOutSelPin << 9 | MafInPin
if cog := cognew(@entry, @pins) + 1
repeat while pins
DAT org 0
entry movi ctra, #%0_01000_000 ' POS detector
rdlong temp, par ' [!Z]:p2:p1:p0 = 5:9:9:9
movs ctra, temp ' input pin
shl pin_0, temp ' pin -> mask
wrlong zero, par ' release caller
shr temp, #9 ' [!Z]:p2:p1
movd ctra, temp ' output pin (inverted)
shl pin_1, temp ' pin -> mask
shr temp, #9 ' [!Z]:p2
shl pin_2, temp ' pin -> mask
mov dira, pin_1 ' drive output
movs ctrb, ctra ' grab input pin for NEG detector
movi ctrb, #%0_01100_000 ' NEG detector
' At this point we have both counters setup for level detection. Counter A will be using
' feedback mode later to support the pass-through (out := !in).
mov frqa, #1 ' |
mov frqb, #1 ' activate counters
:revert waitpne pin_0, pin_0 ' |
mov phsa, #0 ' |
waitpeq pin_0, pin_0 ' |
mov phsb, #0 ' sync accumulators
movi ctra, #%0_01001_000 ' enable feedback
:mirror waitpne pin_0, pin_0 ' high/low transition
mov period_h, phsa ' |
mov phsa, #0 ' record and reset
waitpeq pin_0, pin_0 ' low/high transition
mov period_l, phsb ' |
mov phsb, #0 ' record and reset
mov cnt, cnt ' sync point
test pin_2, ina wz,wc ' sample clamp pin
if_z jmp #:clamp ' active (carry clear) (%%)
jmp #:mirror ' keep measuring
:loop test pin_2, ina wz ' sample clamp pin
if_nz jmp #:revert ' inactive
waitcnt cnt, [COLOR="orange"]period_l[/COLOR] ' |
or outa, pin_1 ' manual: high (input low)
waitcnt cnt, [COLOR="orange"]period_h[/COLOR] ' |
:clamp andn outa, pin_1 ' manual: low (input high)
if_nc movi ctra, #%0_01000_000 ' disable feedback
if_nc add cnt, [COLOR="orange"]period_h[/COLOR] ' |
if_nc sub cnt, #19 ' adjust initial low period
jmpret zero, #:loop wc,nr ' set carry (%%)
' initialised data and/or presets
pin_0 long 1 ' MafInPin
pin_1 long 1 ' MafOutSelPin
pin_2 long 1 ' MafClampInPin
' uninitialised data and/or temporaries
period_h res 1
period_l res 1
temp res 1
fit
CON
zero = $1F0 ' par
DAT
That was fast! I won't have time until the weekend to try this out, but were you able to scope out the MafInPin and MafOutSelPin to see how much delay there is between the two signals? With SPIN I see around 40 to 80 uSecs of delay that varies across that range. Also, for transitions back and forth between the clamp and mirror states, my SPIN code ensures there are graceful transitions on the MafOutSelPin to sync back up with MafInPin particularly when going from the clamp state back to the mirror state. I noticed you used counters in the ASM code, does it still transition gracefully like the original SPIN code (your eye won't see this, but a scope will).
Delay in mirror mode is one clock cycle (12.5ns @80MHz). As for transitions, unless I messed it up it should behave like the SPIN version, clamp input is acted on after low/high transition (in mirror mode) and the output is kept low (in clamp mode) until the input transitions from low to high (output high to low).
Super! That is awesome and I can't wait to try it out this weekend. Just one last question, I intend to run 2 separate cogs for this mirror/clamp code each cog with different input/output pins. I assume that I can call init() twice with the proper pins and it will just work. Thanks!
Just one last question, I intend to run 2 separate cogs for this mirror/clamp code each cog with different input/output pins. I assume that I can call init() twice with the proper pins and it will just work.
Yes, that's the idea. Anyway, just play around with it, if it needs fixing re: certain behaviour just drop me a line (it's not a 100% copy of the SPIN code).
Yes, that's the idea. Anyway, just play around with it, if it needs fixing re: certain behaviour just drop me a line (it's not a 100% copy of the SPIN code).
Ok, just another question because I'm not familiar with how ASM works. But, I notice at end you have a data section for the pins. If you have multiple cogs, is this pin data created separately for each cog or is it shared (latter would be a problem I think). Thanks!
The DAT section contains only ones (ready for 1 << n). Those are finalised once the cog is up and running (i.e. in cog memory). The original data (hub memory) remains unchanged and can therefore be re-used for another cog. So yes, each cog gets its own (separate) setup.
The DAT section contains only ones (ready for 1 << n). Those are finalised once the cog is up and running (i.e. in cog memory). The original data (hub memory) remains unchanged and can therefore be re-used for another cog. So yes, each cog gets its own (separate) setup.
That is quite a clever technique kuroneko. Better than what I have used, which is to hard code the pins into variables.
So - you declare a variable as a 00000000_00000000_00000000_00000001
Then in the setup you pass the pin number. Maybe it is pin 12.
Then you bitshift this by 12 places.
Now you have 00000000_00000000_00010000_00000000 which can be used as a mask.
@Jim: Just realised that the clamp loop uses the measured periods the wrong way round, the high period length needs to be applied to the low period (inverted output). This may not be an issue assuming 50% DUTY. I fixed the code in post #10. Apologies.
@Jim: Just realised that the clamp loop uses the measured periods the wrong way round, the high period length needs to be applied to the low period (inverted output). This may not be an issue assuming 50% DUTY. I fixed the code in post #10. Apologies.
I haven't copied the code over yet to test out so that was ok. I will be doing this on the weekend. Will get back to you on results. Thanks.
I just did a quick load of the code. It seems to work but with a 2 channel scope and ext trigger on the clamp signal, it's a bit hard to determine exactly whether it operates correctly in all aspects; I need to see the 3 signals all at the same time. So, I've got a friend with a 4 channel tektronix scope that I'm going to see if I can borrow to really scrutinize this. I needed to do this anyway as my own code hasn't been fully checked out either.
Just realized that my 2 channel scope actually has a 16 channel logic analyer capability so that should suffice for this as all the signals look good from the analog side anyway. Will give that a try on saturday.
@kuroneko: I found some bugs in my original code and have fixed them. I wrote a pretty extensive test waveform generator to vary the clamp signal timing relative to frequency changes. I want to modify that next weekend to vary the input duty cycle a bit to ensure the code can handle it. I will post my code changes to the state machine at that time. If you could fix up your asm code, I would appreciate it. It works very well to cut down on jitter relative to spin code! It seems to be about 10x faster.
Ok, got busy this morning and wrote the final code to vary the duty cycle of my test waveform generator. I am running my spin code version of the state machine to clamp/mirror the inputs. Looked everything over real good in the logic analyzer and the code now functions exactly as I wanted. Here is the slightly modified source for the state machine that needs to converted to ASM. If you diff this version with the last version there are probably a half dozen minor changes or so. It is very important to follow the state machine as the STATE_INIT state gets the counter variables going correctly for the rest of the states.
PUB MafControlSpinCog(mafin_pin, mafoutsel_pin, mafclampin_pin) | maf_cur_val, maf_last_val, c1, c2, c3, high_cnt, low_cnt, state, full_cycle
dira[mafin_pin]~
dira[mafoutsel_pin]~~
dira[mafclampin_pin]~
state := _STATE_INIT
repeat
case state
_STATE_INIT:
' Startup state, just mirror inverted value of MafInPin to mafoutsel_pin and wait to acquire first measurement
' of high and low counts of one cycle of mafin_pin.
maf_last_val := c1 := -1
high_cnt := low_cnt := 0
repeat until (full_cycle)
full_cycle := FALSE
maf_cur_val := ina[mafin_pin]
outa[mafoutsel_pin] := !maf_cur_val
if ((maf_cur_val == 1) AND (maf_last_val == 0)) ' Rising edge detected on mafin_pin.
if (c1 == -1)
c1 := cnt
else
c3 := cnt
high_cnt := c2 - c1
low_cnt := c3 - c2
c1 := c3
full_cycle := TRUE
elseif ((maf_cur_val == 0) AND (maf_last_val == 1)) ' Falling edge detected on mafin_pin.
c2 := cnt
maf_last_val := maf_cur_val
state := _STATE_MIRROR
_STATE_MIRROR:
' mafoutsel_pin will follow the inverted value of mafin_pin during this state. When the active low signal mafclampin_pin
' turns to 0, wait until mafin_pin reaches it's next rising edge transition.
repeat until ((ina[mafclampin_pin] == 0) AND full_cycle)
full_cycle := FALSE
maf_cur_val := ina[mafin_pin]
outa[mafoutsel_pin] := !maf_cur_val
if ((maf_cur_val == 1) AND (maf_last_val == 0)) ' Rising edge detected on mafin_pin.
c3 := cnt
high_cnt := c2 - c1
low_cnt := c3 - c2
c1 := c3
full_cycle := TRUE
elseif ((maf_cur_val == 0) AND (maf_last_val == 1)) ' Falling edge detected on mafin_pin.
c2 := cnt
maf_last_val := maf_cur_val
state := _STATE_CLAMP
_STATE_CLAMP:
' mafoutsel_pin will be clamped at a frequency given by the last high and low counts measured while in the _STATE_MIRROR
' state. When the active low signal mafclampin_pin returns to 1, hold mafoutsel_pin at 1 until mafin_pin reaches it's
' next rising edge transition.
repeat until (ina[mafclampin_pin] == 1)
c1 := cnt
repeat until ((cnt - c1) => high_cnt)
outa[mafoutsel_pin] := 0
c1 := cnt
repeat until ((cnt - c1) => low_cnt)
outa[mafoutsel_pin] := 1
maf_last_val := -1
repeat
maf_cur_val := ina[mafin_pin]
if ((maf_cur_val == 1) AND (maf_last_val == 0)) ' Rising edge detected on mafin_pin.
c1 := cnt ' Ensure that _STATE_MIRROR state starts counting process properly for high/low period.
outa[mafoutsel_pin] := !maf_cur_val
state := _STATE_MIRROR
maf_last_val := maf_cur_val
quit
maf_last_val := maf_cur_val
full_cycle is a local variable so should be initialised to FALSE
cnt can be -1 (minor, you'd just spend one more cycle in the init state)
when switching back to mirror mode you should make sure to at least sample one more cycle, full_cycle is still TRUE and the clamp input may already be 0 again
Anyway, I had to make a minor adjustment in that the clamp release test is now done during output being high (previously low). Also when switching to clamp mode I output at least one clamped cycle whereas the SPIN version could skip that (and wait for the rising edge immediately before returning to mirror mode). Do you expect clamp pin changes within the same cycle?
To summarise the differences:
mirror-to-clamp: at least one clamped cycle is issued before the clamp pin is (re)sampled
clamp-to-mirror: at least one input cycle is measured to get updated high/low values
full_cycle is a local variable so should be initialised to FALSE
cnt can be -1 (minor, you'd just spend one more cycle in the init state)
when switching back to mirror mode you should make sure to at least sample one more cycle, full_cycle is still TRUE and the clamp input may already be 0 again
Anyway, I had to make a minor adjustment in that the clamp release test is now done during output being high (previously low). Also when switching to clamp mode I output at least one clamped cycle whereas the SPIN version could skip that (and wait for the rising edge immediately before returning to mirror mode). Do you expect clamp pin changes within the same cycle?
To summarise the differences:
mirror-to-clamp: at least one clamped cycle is issued before the clamp pin is (re)sampled
clamp-to-mirror: at least one input cycle is measured to get updated high/low values
@kuroneka: Good catch on not initializing full_cycle. It needs to be set false at entry to both the _STATE_INIT and _STATE_MIRROR states. I've corrected my original code (also source shows the full source of the project including my test waveform generator). I guess full_cycle was set by the interpreter to 0 to begin with before entry to _STATE_INIT and it didn't matter that it was TRUE on entering _STATE_MIRROR because the clamp pin was always high at that point for my test waveforms. Now it is fixed. The reason I changed the code is that c1 needs to be initialized to the clock count before entering _STATE_MIRROR, either from _STATE_INIT (where c1 gets initially set up) or _STATE_CLAMP. For both of these states the code will have detected the first rising edge and so c1 needs to be set to start the measurement cycle of high and low counts within _STATE_MIRROR. I do not expect the clamp pin to ever change within one cycle. This project is for a car, it reads a Mass Airflow Signal that varies between 20 to 200 Hz and normally the clamp pin signal will change around the same frequency every time, say F, between 20 and 200. So, as the frequency rises from 20 Hz, it hits the clamp frequency F and the mafout pins stay at the last recorded frequency. Then as frequency falls from 200 Hz, it again hits the clamp frequency F but this time reverts to mirror mode. The clamped maf out pin will drive the stock ECU of the car and a piggyback ECU will read the raw maf in pin and take over the car control when the clamp point is hit but we need to fool the stock ECU that everything is ok by clamping it's maf input with the maf out pin of this circuit.
Anyway, I tested the code again using both my spin based control cog and your asm based control cog. The logic analyzer shows they work exactly the same, only the delay of the spin cog from input edge to output edge is something like 60-80 uSecs and for your asm cog it's only about 10 uSecs with little variation from that.
Thanks for the help! The circuit and code work exactly as I had hoped.
Anyway, I tested the code again using both my spin based control cog and your asm based control cog. The logic analyzer shows they work exactly the same, only the delay of the spin cog from input edge to output edge is something like 60-80 uSecs and for your asm cog it's only about 10 uSecs with little variation from that.
Cool. One last thing, I'm puzzled about the 10 uSec. The way inversion is done in mirror mode is by using counter feedback, i.e. the input is fed through a FF and output one clock cycle later (inverted). So I would at least expect a value in the ns range (12.5ns @80MHz). Can you clarify?
Cool. One last thing, I'm puzzled about the 10 uSec. The way inversion is done in mirror mode is by using counter feedback, i.e. the input is fed through a FF and output one clock cycle later (inverted). So I would at least expect a value in the ns range (12.5ns @80MHz). Can you clarify?
I thought this might just be due to the sample rate I had my logic analyzer set on initially, which was 100 KHz, i.e. 10 uSec resolution. However, I just tried setting my LA to acquire at 100 MHz (10 nSec resolution) using deep memory mode and it is showing that the delay from input to output is 7.5 to 8 uSec. I can't explain why the real world results differ from your expectations.
I will look at this tomorrow. My inputs are conditioned through an LM324 op amp circuit and outputs go through a 2N3904 NPN transister. The circuits for the maf input and output on one cog are shown below. I don't think they are adding uSecs to the equation.
Comments
There are just too many things that would be Spin or PASM specific to make a compilier worth the bother.
Spin only uses variables in hub RAM while PASM uses both hub and cog RAM.
This would make any kind of conversion from one to the other problematic. IMO.
The thing is that using tricks like reducing "n x 15" to "n bitshift left 4 places and subract n from the answer" are not the sort of things an automatic translation program can do very easily.
I believe the very clever C programmers have got code that you can tell the compiler whether to put it in hub or cog.
If you like, maybe post some of your spin code and we can think about how to convert it to asm?
I'm often surprised by the optimizations gcc performs :-). Here's a log of converting a simple .spin function to PASM, automatically:
This is under Linux, so the "$" is a command prompt and "cat" is the program to type a file out. The weird name "_ZN8testSpin5Mul15Ei" is due to the way C++ encodes type information in function names. Also note that I did give -O3 to propeller-elf-gcc to tell it to optimize for speed as much as it can; if it's optimizing for space (the normal case in the Propeller world) it'll generate a call to a multiply routine instead because that takes less space than the shift and sub.
Of course the .pasm isn't a complete program, you'd need the register declarations and some interface code. Giving the "-mspin" argument to propeller-elf-gcc will make it output a lot of that for you. Also, it's using the normal C calling convention (where return addresses are stored in the "lr" register) instead of the PASM calling convention (where you return with "ret"); there are ways to force gcc to use that, but they require editing the .cpp file that spin2cpp creates.
Eric
Ok, just another question because I'm not familiar with how ASM works. But, I notice at end you have a data section for the pins. If you have multiple cogs, is this pin data created separately for each cog or is it shared (latter would be a problem I think). Thanks!
That is quite a clever technique kuroneko. Better than what I have used, which is to hard code the pins into variables.
So - you declare a variable as a 00000000_00000000_00000000_00000001
Then in the setup you pass the pin number. Maybe it is pin 12.
Then you bitshift this by 12 places.
Now you have 00000000_00000000_00010000_00000000 which can be used as a mask.
That is very clever.
I haven't copied the code over yet to test out so that was ok. I will be doing this on the weekend. Will get back to you on results. Thanks.
- full_cycle is a local variable so should be initialised to FALSE
- cnt can be -1 (minor, you'd just spend one more cycle in the init state)
- when switching back to mirror mode you should make sure to at least sample one more cycle, full_cycle is still TRUE and the clamp input may already be 0 again
Anyway, I had to make a minor adjustment in that the clamp release test is now done during output being high (previously low). Also when switching to clamp mode I output at least one clamped cycle whereas the SPIN version could skip that (and wait for the rising edge immediately before returning to mirror mode). Do you expect clamp pin changes within the same cycle?To summarise the differences:
@kuroneka: Good catch on not initializing full_cycle. It needs to be set false at entry to both the _STATE_INIT and _STATE_MIRROR states. I've corrected my original code (also source shows the full source of the project including my test waveform generator). I guess full_cycle was set by the interpreter to 0 to begin with before entry to _STATE_INIT and it didn't matter that it was TRUE on entering _STATE_MIRROR because the clamp pin was always high at that point for my test waveforms. Now it is fixed. The reason I changed the code is that c1 needs to be initialized to the clock count before entering _STATE_MIRROR, either from _STATE_INIT (where c1 gets initially set up) or _STATE_CLAMP. For both of these states the code will have detected the first rising edge and so c1 needs to be set to start the measurement cycle of high and low counts within _STATE_MIRROR. I do not expect the clamp pin to ever change within one cycle. This project is for a car, it reads a Mass Airflow Signal that varies between 20 to 200 Hz and normally the clamp pin signal will change around the same frequency every time, say F, between 20 and 200. So, as the frequency rises from 20 Hz, it hits the clamp frequency F and the mafout pins stay at the last recorded frequency. Then as frequency falls from 200 Hz, it again hits the clamp frequency F but this time reverts to mirror mode. The clamped maf out pin will drive the stock ECU of the car and a piggyback ECU will read the raw maf in pin and take over the car control when the clamp point is hit but we need to fool the stock ECU that everything is ok by clamping it's maf input with the maf out pin of this circuit.
Anyway, I tested the code again using both my spin based control cog and your asm based control cog. The logic analyzer shows they work exactly the same, only the delay of the spin cog from input edge to output edge is something like 60-80 uSecs and for your asm cog it's only about 10 uSecs with little variation from that.
Thanks for the help! The circuit and code work exactly as I had hoped.
I thought this might just be due to the sample rate I had my logic analyzer set on initially, which was 100 KHz, i.e. 10 uSec resolution. However, I just tried setting my LA to acquire at 100 MHz (10 nSec resolution) using deep memory mode and it is showing that the delay from input to output is 7.5 to 8 uSec. I can't explain why the real world results differ from your expectations.
When you measure this I'd be interested in the delay seen directly on the propeller pins (taking any external h/w out of the equation).
PCB artist, a free schematic and board layout software package from Advanced Circuits. See http://www.4pcb.com/free-pcb-layout-software/