Adventures in Spin
In our last episode we (and I mean plural there!) got the buttons working
on my QuickStart board. Thank you kindly, neighbors.
This thing begs to be my first adventure in parallelism.
Load that puppy into another cog and let it buck!
Then anybody that wants to see if buttons are happening need only asks nicely.
But no.
I'm not having much getting this to work.
I've studied several of the objects from the exchange and read the book.
Tried a bunch of wrong ways.
But I haven't stumbled blindly on a right way yet.
Both of these have been debugged in terms of single cog methodology,
and work correctly.
But what do they need to succeed in parallel land?
[ugly code removed to save room]
on my QuickStart board. Thank you kindly, neighbors.
This thing begs to be my first adventure in parallelism.
Load that puppy into another cog and let it buck!
Then anybody that wants to see if buttons are happening need only asks nicely.
But no.
I'm not having much getting this to work.
I've studied several of the objects from the exchange and read the book.
Tried a bunch of wrong ways.
But I haven't stumbled blindly on a right way yet.
Both of these have been debugged in terms of single cog methodology,
and work correctly.
But what do they need to succeed in parallel land?
[ugly code removed to save room]

Comments
CON { TPad_Demo.spin } _CLKMODE=XTAL2 [color=red]' you really want it to run in slowmo?[/color] _xinfreq = 5_000_000 VAR LONG MS001 [color=red]' ok, it worked with an array - now it's time to learn bit operations ;o)[/color] BYTE TPad[s][ 8 ][/s], Demo [color=red]' the main program per default has the whole rest of the HUB-RAM as stack [s]LONG Stack1[16][/s] ' the other COG should define it's stack by itself [s]LONG Stack2[16][/s][/color] OBJ scan: "TPad_Scan" PUB TPdemo | Pad, P1 ' local variables Demo := 0 If Demo [color=green]TPad := %10101010[/color] [color=red][s]TPad[1]:=1[/s][/color] [color=red][s]TPad[3]:=1[/s][/color] [color=red][s]TPad[5]:=1[/s][/color] [color=red][s]TPad[7]:=1[/s][/color] MS001 := CLKFREQ / 1_000 ' define 1 millisec [color=red]' you want to do parallel processing, so a COG should only use the PINs it's really responsible for ! [s]dira [0..7] := %11111111 ' all pins outputs[/s] [s]outa [0..7] := %11111111 ' all pins high[/s] [s]WaitMS(1000) ' one second[/s] [s]outa [0..7] := %000000000 ' all pins low[/s][/color] [color=green]' What this COG is responsible for are the LED-PINs outa[ 16..23 ] := %00000000 ' this is optional, as OUTA is guaranteed to be 0 on startup of a COG dira[ 16..23 ] := %11111111 ' you created an object out of TPad_Scan, but you also have to "use" is somewhere ' the code there will not run automatically! ' 2 things here - cognew does not work across several *.spin-files (objects), so you need to implement a ' start-function which is doing the cognew there. And then you have the choice where to store the status ' of the buttons - either inside of the object or in the program using the object. I think it's more flexible ' to let the caller decide where the status should be stored in general. In this special case it really ' does not matter. scan.start( @TPad ) [/color] ' start the main loop ' show what the scanner brought me Repeat Repeat pad from 0 to 7 ' button 0 to 7 if [color=green]TPad & |<pad[/color] ' 1 is on [color=red][s]dira[pad+16] := 1 ' so blinkenlighten[/s][/color] outa[pad+16] := 1 ' LED start at pin 16 WaitMS(10) ' 10 nice flicker outa[pad+16] := 0 ' PUB WaitMS(W)| C 'wait W milliseconds C := W*MS001 WaitCNT (C+cnt) )There is also some potential for optimizing this code. You can easily get rid of the whole inner repeat-loop because you have 8 bit input and 8 bit output. Do you want to try yourself?
Let me know if you have any problems running/compiling the code - what I posted is not tested, as I don't have my Prop Tool at hand.
[s][color=red]CON { TPad_scan.spin } _CLKMODE=XTAL2 _xinfreq = 5_000_000 ' not needed in an object file which does not contain a standalone program[/color][/s] VAR LONG MS001 [s][color=red]BYTE TPad[8] ' global array returns results[/s] [s]BYTE Demo[/color][/s] [color=green]LONG statusStorageAdr LONG cogID LONG Stack[ 16 ] PUB start( sAdr ) statusStorageAdr := sAdr if cogID cogstop( cogID-1 ) cogID = cognew( TPScan, @Stack ) + 1 [/color] PUB TPScan | Pad, P1 ' local variables MS001 := CLKFREQ / 1_000 ' define count for 1 millisec dira [0..7] := %11111111 ' set all pad pind as outputs outa [0..7] := %11111111 ' preset all pad pins high [s][color=red]Demo := 0[/color][/s] Repeat ' loop forever Repeat pad from 0 to 7 ' 0-7 for QuickStart board dira [pad] := 1 ' make pin an output dira [pad] := 0 ' make it an input WaitMS(2) ' short delay for dome decay P1 := ina[pad] ' read the pad if P1 == 0 ' 0 here means pressed [color=green]long[ statusStorageAdr ] |= |<pad[/color] else ' [color=green]long[ statusStorageAdr ] &= !|<pad[/color] [color=red][s] if demo if TPad[pad] ==1 ' 1 is on dira[pad+16] := 1 ' so blinkenlighten outa[pad+16] := 1 ' ' LED start at pin 16 WaitMS(10) ' 10 nice flicker outa[pad+16] := 0 ' [/s][/color] PUB WaitMS(W)| C 'wait for W milliseconds C := W*MS001 ' WaitCNT (C+cnt)Without separating the compiler will really create 2 copies of this function in your executable. When you put it in a SPIN-file of it's own and use this SPIN-file in each place where it's needed, there will only be one copy in the executable.
But I think you should try this by yourself as an excerzise as well ;o)
You set me on the straight and narrow path.
I appreciate that. Maybe it will help someone get started else as well?
I still had a few minor issues to work out.
One was that for some fool reason I was trying to use CogID as a variable.
That didn't work very well (well duh!)
After it was up and running I went though it and did a bit of housekeeping,
so there are a lot of name changes to make it read better (at least to my eye).
TP for touchpads? In 6 months will I remember what the heck that's about?
So it's Buttons now. Keep it simple...
The bit manipulation suggestion was timely.
That was going to be my very next concern.
And, since you brought it up...
There are only 8 buttons, so why take up a whole long?
Sure, it's an improvement over an 8 byte array, but longs are rare and precious...
So I tried this:
BYTE[Buttons] |= |< B ' bit set Bth bit = 1
which compiled and ran fine.
But was that because of the BYTE specifier? over ride?
Or just because I was only dinking with 8 bits?
Would it have happily written the higher bits if B said to?
I'll start trying to puzzle out how to break out the common code.
save the longs!
Anyway, sharing the current state of the art...
the Demo:
CON { Button_Demo.spin } {this COG is responsible for the LEDs } _CLKMODE=XTAL2 _xinfreq = 5_000_000 VAR LONG MS001 WORD Buttons ' there are only 8 buttons OBJ button: "Button_Scanner" PUB ButtonDemo | B ' local variables MS001 := CLKFREQ / 1_000 ' define 1 millisec button.start( @Buttons ) ' send address of Buttons outa[ 16..23 ] := 000000 ' dira[ 16..23 ] := 111111 ' ' main loop - show what the scanner sees Repeat Repeat B from 0 to 7 ' bit 0 to 7 if Buttons & |< B ' bit AND of the Bth bit outa[B+16] := 1 ' LEDs start at pin 16 WaitMS(10) ' 10 nice flicker outa[B+16] := 0 ' PUB WaitMS(W)| C 'wait W milliseconds C := W*MS001 WaitCNT (C+cnt)and the scanner:
'CON { Button_Scanner.spin } ' { responsible for the touch pad buttons } VAR LONG MS001 WORD Buttons LONG ButtonCogID LONG Stack[ 16 ] ' define my stack PUB start( sAdr ) ' start this in a new cog Buttons := sAdr ' save status if ButtonCogID cogstop(ButtonCogID-1) ' no cog available ButtonCogID := cognew(ButtonScan, @Stack) + 1 PUB ButtonScan | B, B1 ' local variables MS001 := CLKFREQ / 1_000 ' define count for 1 millisec dira [0..7] := 111111 ' all pad pins outputs outa [0..7] := 111111 ' all pad pins high 'main loop - scan the buttons Repeat ' loop forever Repeat B from 0 to 7 ' 0-7 for QuickStart board dira [B] := 1 ' make pin an output dira [B] := 0 ' make pin an input WaitMS(2) ' short delay for some decay B1 := ina[B] ' read the pad if B1 == 0 ' 0 here means pressed BYTE[Buttons] |= |< B ' bit OR Bth bit else BYTE[Buttons] &= !|< B ' invert bit AND Bth bit PUB WaitMS(W)| C 'wait for W milliseconds C := W*MS001 ' WaitCNT (C+cnt)[edit] corrected Buttons to WORD for proper address alignment (Per Mr. K)
I did get this part working.
It is in a file named QS_Util.spin
In the Demo.spin file I added:
OBJ util:"QS_Utilities"
So I can call that method with util.WaitMS(milliseconds).
The burr under my saddle is that I haven't figured out how to initialize MS001 in the utility file.
Even including the CONstant section and adding MS001 := ClkFreq/1000 to a start.util method didn't help.
It does work if hard coded... but that's not exactly elegant, is it...
Anybody have a page reference in the manual where this kind of stuff is discussed?
.
Corrected that...
Thank you.
Same problem you have in Button_Scanner.spin!
If you want to make the variable names speak more for themselves, you should consider to rename Buttons in Button_Scanner.spin to ButtonsAdr. Oh ... and Buttons in Button_Demo.spin can live with just one byte. It's only ButtonsAdr which NEEDs to be 16bit.
What I meant with "You can easily get rid of the whole inner repeat-loop because you have 8 bit input and 8 bit output" is:
PUB ButtonDemo | B ' local variables MS001 := CLKFREQ / 1_000 ' define 1 millisec button.start( @Buttons ) ' send address of Buttons outa[ 16..23 ] := 000000 ' dira[ 16..23 ] := 111111 ' ' main loop - show what the scanner sees Repeat [color=green]outa[ 16..23 ] := Buttons WaitMS(10) outa[ 16..23 ] := 0[/color] [s][color=red] Repeat B from 0 to 7 ' bit 0 to 7 if Buttons & |< B ' bit AND of the Bth bit outa[B+16] := 1 ' LEDs start at pin 16 WaitMS(10) ' 10 nice flicker outa[B+16] := 0[/color][/s]I'd also change the comments for
BYTE[Buttons] |= |< B
BYTE[Buttons] &= !|< B
it's set a single bit for the first and clear single bit for the second
About LONG MS001 ... you should move it into the QS_Utilities.spin file and I'd simply introduce an init-function which calculates the right amount like you currently do it. But here is also something to think about! You could add it in a VAR section which means that each instance of the QS_Utilites will have it's own copy. For this variable I'd say it makes no sense to have an individual copy per instance, as the clockfreq is a global setting anyway! So, add it to a DAT section. This on the other hand means that you only have to call init once! (Having an init which needs to initialize a VAR variable would have to be called per instance).
Well ... it does not hurt to call it twice ..
But starting a Utilities.spin-file propably leads to a collection of different functions and you need to be aware of these little differences in VAR vs. DAT at latest when you add functions that also need initialization.
About your question:
SPIN makes sure that values assigned to BYTE[] or a byte-size-variable only effect exactly these 8 bits meant. So, if you do a BYTE[ x ] := |<10 a zero would actually be assigned as the 11th bit is out of sight for this operation.
CogId of course can also be a byte variable. Using long is just dreaming of 2^32-2 COGs ;o)
I still need to work on my key driver, but in the mean time I'm using a cut down version from the
object exchange so I could get one with a different project.