Sharing Locks Between Spin and PASM
After working with Spin for a while, I recently decided to start delving into Propeller assembly, and am a bit confused on how locks (semaphores) can be implemented so Spin and PASM running in separate cogs can access global variables in a mutually exclusive way.
Here is a quick demo I wrote up to test my understanding:
The PASM is supposed to increment the values of three variables from zero up by steps of one, waiting for the Spin in another cog to display the values on the terminal once per second. As is, the code will now will display a set of values only once and stall, and they are 1428597 and not 1 as I would expect. If I remove the delay, it does increment and display more values, but in much larger jumps than one.
What am I getting wrong here, please?
Here is a quick demo I wrote up to test my understanding:
CON
_clkmode = xtal1 + pll16x ' System clock settings
_xinfreq = 5_000_000
HOME = 1 ' Parallax Serial Terminal constants
CRSRXY = 2
CR = 13
CLREOL = 11
CLS = 16
OBJ
debug : "FullDuplexSerial" ' Declare FullDuplexSerial object
VAR
'(4 contiguous longs)
long variable[3]
long SemID 'ID# of lock checked out by PASM
PUB Start
cognew(@entry, @variable[0]) 'Start cog and pass the address of variable[0] from Spin to PASM cog
SpinLoop
PUB SpinLoop
debug.start(31, 30, 0, 57600) ' Start serial connection for debug terminal
waitcnt(clkfreq + cnt) ' Wait 1 s before starting
repeat
IF lockset(SemID) <> true 'If this locks the resource by not getting back TRUE as the previous state
debug.dec(variable[0])
debug.str(string(CR))
debug.dec(variable[1])
debug.str(string(CR))
debug.dec(variable[2])
debug.str(string(CR, CR))
lockclr(SemID) 'unlock the variables to allow PASM to update them
waitcnt(clkfreq + cnt)
DAT 'Assembly language code
org
' Entry
entry mov l,par 'par value here is the variable[0] address passed from Spin in the Start method
mov v1, #0
mov v2, #0
mov v3, #0
locknew SemIDa 'check out a lock, store ID in register SemIDa
add l,#3*4 'Calculate address in main memory 3 four-byte longs from variable[0] (i.e. Spin variable SemID)
wrlong SemIDa,l 'copy lock number from PASM variable to Spin variable to give both access to the same lock
' Update values
update lockset SemIDa wc ' Set lock, set c flag to previous state of the lock
if_nc jmp #$+3 'if c indicates previous state of lock was clear, jump ahead three instructions
if_c lockset SemIDa wc 'if lock previously set, set lock and loop back three instructions to test previous state again
jmp #$-3
if_nc add v1, #1 'Increment variable values
add v2, #1
add v3, #1
mov p,par
wrlong v1,p
add p, #4
wrlong v2,p
add p, #4
wrlong v3,p
lockclr SemIDa
jmp update
'
' Data
'
p res 1
l res 1
v1 res 1
v2 res 1
v3 res 1
SemIDa res 1 'lock ID#
The PASM is supposed to increment the values of three variables from zero up by steps of one, waiting for the Spin in another cog to display the values on the terminal once per second. As is, the code will now will display a set of values only once and stall, and they are 1428597 and not 1 as I would expect. If I remove the delay, it does increment and display more values, but in much larger jumps than one.
What am I getting wrong here, please?

Comments
DAT 'Assembly language code org ' Entry entry mov l,par ' par value here is the variable[0] address passed from Spin in the Start method mov v1, #0 mov v2, #0 mov v3, #0 locknew SemIDa ' check out a lock, store ID in register SemIDa add l,#3*4 ' Calculate address in main memory 3 four-byte longs from variable[0] (i.e. Spin variable SemID) wrlong SemIDa,l ' copy lock number from PASM variable to Spin variable to give both access to the same lock ' Update values update lockset SemIDa wc ' Set lock, set c flag to previous state of the lock if_c jmp #$-1 add v1, #1 ' Increment variable values add v2, #1 add v3, #1 mov p,par wrlong v1,p add p, #4 wrlong v2,p add p, #4 wrlong v3,p lockclr SemIDa [COLOR="#FF8C00"]rdlong cnt, #0 add cnt, cnt waitcnt cnt, #0[/COLOR] jmp [COLOR="#FF0000"]#[/COLOR]update ' ' Data ' p res 1 l res 1 v1 res 1 v2 res 1 v3 res 1 SemIDa res 1 'lock ID#Could you please explain how your pause works?
rdlong cnt, #0
add cnt, cnt
waitcnt cnt, #0
I thought "cnt" was a read-only register containing the current system counter value, but it appears the rdlong instruction writes to it?
Basically, one cog sets "the" lock to indicate that it is reading about 8 bytes of data via I2C from a touchscreen controller (to hub RAM); the lock is cleared after it's finished. In the event that the lock is already checked out, it simply skips reading the touchscreen controller; the main loop will come back around before long.
'From here onwards, we "while" away. First, scan the FT5206 touchscreen 'controller, and dump all of the touch registers into Propeller RAM. ' 'Update touch registers ONLY if the "lock" is clear mlCheckTouch lockset lock_touch wc if_c jmp #TouchEnd 'PASM is using the lock, so don't overwrite the registers mov temp1, #5 '#of registers to read - 1 mov touchPtr, par sub touchPtr, #4 'reach before the RA8875 "mailbox" and get the FT5206's memory pointer rdlong touchPtr, touchPtr 'reset the pointer address 'Initiate an I2C Read from the FT5206. call #startfunc ' start(); mov i2cTransfer, #FT5206_Address call #writebytefunc mov i2cTransfer, #1 'address #1 = "Gesture ID" call #writebytefunc '#stopfunc call NOT in example code. Should be a Repeated Start. call #stopfunc call #startfunc ' start(); mov i2cTransfer, #FT5206_Address | 1 'READ call #writebytefunc 'will return Z = 0 (nonzero) from the '5206's ACK mlTouchLoop mov i2cAck, #0 'ACK call #readbytefunc wrbyte i2cTransfer, touchPtr 'write the returned value 'Keep track of where we are add touchPtr, #1 'writing bytes, not words or longs djnz temp1, #mlTouchLoop 'Done. Read the last register with NACK, and release the bus. mov i2cAck, #1 'NACK call #readbytefunc wrbyte i2cTransfer, touchPtr 'write the returned value call #stopfunc 'Done writing HUB RAM, so release the lock... lockret lock_touch 'PASM can now grab it... 'If the screen is touched, reseet the "display timer" and/or turn the display back on (if it's off) sub touchPtr, #4 'from $06 to $02 - TD_STATUS rdbyte temp1, touchPtr wz if_z jmp #mlTouchEnd 'It's NOT touched. 'Display touched...reset the "off" timer mov tmrDisplay, #0 'DISPLAY TOUCHED...reset timeout timer 'Screen off? test bits, #1 wz if_z jmp #DisplayOn 'Yes, the display is off. Turn it back on. mlTouchEnd '===============================================================Meanwhile, the Spin code that reads the values from hub RAM, waits to check out the lock, reads several registers, and then releases the lock.
PUB CheckTouch repeat until not lockset(lock_touch) px := (800- (((FT5206Regs[2] & $F) << 8) | FT5206Regs[3])) py := (480- (((FT5206Regs[4] & $F) << 8) | FT5206Regs[5])) 'DEBUG...SCREEN FLIPPED tch := FT5206Regs[1] lockclr(lock_touch)If I use the code as written above, the Spin side immediately locks up (pardon the pun), and as the lock is checked out, the PASM side doesn't read the touch controller, either.
I use locks elsewhere, but only in Spin code, so multiple subroutines accessing the same shared resource have to wait on each other; they work just fine. What is going on here when introducing PASM to the mix?
-Phil