Absolutely you can. Chip's graphics_demo does exactly that, for example. The parameters needed to get the TV and Graphics COG running are passed with PAR, using offsets to reference a table of variables. The display buffer is passed as an absolute address in the HUB. Both methods are used successfully. And a quick read through the beginners clobbering their larger programs, or seeing them as pixels on the display highlights the problems hard coded addresses can bring to the table.
That bit of code is actually quite instructive, IMHO. I learned a LOT of PASM by working through it.
Hard coded addresses are bad form in general, though sometimes necessary.
PASM COGs written with either a PAR offset, or that utilize the mailbox methods are atomic, and can be reused in many different memory / language scenarios. Hard-coding memory addresses doesn't work the same way, though again, sometimes necessary.
**This thread has been very good Harpit. I find my own effort considerably improved from this discussion.
I will want to show the beginners both methods. Now that I read Stephan more closely, that is what he said too.
I just did not get it the first time around. Thanks Stephan.
As to Potatohead's comment. My own experience has been that the best instructors I had were those who showed me the basics clear and bright, not the high flying astro physicists that knew oh so much and did not have the time to discuss what I needed to know with me. Teaching/showing/demonstrating the basics is a funny business.
I'm trying to keep it as simple as I can but the book will of necessity have much more than we are discussing here.
Question. If I have a HUBOP in my code, why does that make things indeterminate. Why does the HUBOP not act the same way through each cycle. And, could a synchronizing technique be used to improve duplication. ie make it more exact
Let me repeat it again. For a given value D(elay) your frequency generator behaves deterministic. The relationship between D and generated frequency is where you may have issues. Let's have a look:
I call that relationship between delay and generated frequency (loop runtime in this case) simple. It's just your basic linear equation. Now let's insert a hubop.
Hub operations have to wait for their turn to access the hub. So it's not just a matter of adding 8 to the loop execution time of the first example. hubop-infected loops end up as multiple of 16 (time for a hub window) wrt their runtime. Also, as you can see, the equation is not that simple anymore ...
Note, the way the above loops are constructed means that a change in D by 1 adds 4 cycles to the runtime. IIRC, your frequency loop adds 16 cycles for each increment which neatly sidesteps the hub aliasing issue (because you're adding always a full hub window which leaves the remainder untouched).
Some how I cant see how to get the variable read from one COG into another COG
First Cog is the console
Second cog generates a signal but we are interested only in the delay created
Third cog read the pot and works fine.
The read signal is not getting into the second cog properly
Here is the code
con
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
VAR
long pot, pot_read, countup
OBJ
fds : "FullDuplexSerial"
PUB wavelengths
fds.start(31,30,0,115200) 'start console at 115200 for debug output
dira := %00000000_10000000_00000000_00000000
cognew(@toggle, @pot_read)
cognew(@get_pot, @pot)
waitcnt(clkfreq/4+cnt)
long[@pot_data][0]:=pot*10
repeat 'loop
fds.tx($1) 'home to 0,0
fds.dec (countup)
fds.tx(" ") 'space
fds.tx($0d) 'new line
fds.dec(pot) 'print value as decimal
fds.tx(" ")
fds.dec(pot_read) 'print value as decimal
fds.tx(" ")
countup := countup + 1
waitcnt(clkfreq/60+cnt) 'flicker free wait
dat
pot_data 'this is for storing the data that is swapped
'++++++++++++++++++++++++++++++++++++Cog0 to toggle signal ++++++DOES NOT WORK++++++++++++++++++++
org 0 'start of the program storage locations
toggle mov dira, #%11 'pin now sets lines 0 and 1 as outputs
mov outa, #%01 'sets the two bits
loop
call #clkdelay 'call the delay subroutine
shl outa, #%1 'shift left one bit
call #clkdelay 'call the delay subroutine
shr outa, #%1 'shift back right one bit
jmp #loop
clkdelay rdlong @pot_data, time 'the delay subroutine, load pot into time
re_do wrlong time, mem0 'write inot temp variable
mov mem0, par 'srite temp to PAR
sub time, #1 wz 'sub 1 from time and set flag if 0
if_nz jmp #re_do 'if flag not 0 go back to take4
clkdelay_ret ret 'return for delay subroutine
time res 1 'location for time
mem0 res 1 'temp storage
'+++++++++++++++++++++++++Cog 1 reads the potentiometer into pot++++++WORKS+++++++++++++++++++++++++++++
DAT org 0 'sets the starting point in Cog
get_pot mov dira, set_dira2 'sets direction of the prop pins
or outa , chs_Bit 'Makes Chip select high
andn outa, chs_Bit 'makes chip select low
andn outa , clk_bit 'ANDN it with the Clock Bi to make low
or outa , din_Bit 'Makes the Din high
call #Tog_clk 'clk hi-lo to read data
or outa , din_Bit 'Makes the Din high
call #Tog_Clk 'toggle clock line hi then low to read in the data
andn outa , din_Bit 'makes Din low 000 for line 0
call #Tog_Clk 'toggle clock line hi then low to read in the data
andn outa , din_Bit 'makes Din low 000 for line 0
call #Tog_Clk 'toggle clock line hi then low to read in the data
andn outa , din_Bit 'makes Din low 000 for line 0
call #Tog_Clk 'toggle clock line hi then low to read in the data
andn outa , din_Bit 'makes Din low
call #Tog_Clk 'toggle clock line hi then low to read in the data
call #Tog_Clk 'toggle clock line hi then low to read in the data
mov dat_red, #0 'Clear register we will read data into
mov count, #12 'Counter for number of bits we will read
read_bit mov temp2, ina 'read in what is in all the input lines
andn temp2, mask26 wz 'mask off everything except Dout line. Set Z flag
shl Dat_red, #1 'shift register left 1 bit to get ready for next bit
if_nz add Dat_red, #1 'if value is still positive add 1 to data register
call #Tog_Clk 'toggle clock to get next bit ready in Dout
sub count, #1 wz 'decrement the "bits read" counter. Set Z flag
if_nz jmp #read_bit 'go up and do it again if counter not yet 0
wrlong dat_red, mem2 'write it in PAR to share it as P.Val
mov mem2, par 'get address of mem
or outa , chs_Bit 'Makes Chip select high
jmp #get_pot 'go back to do it all again
Tog_Clk or outa, clk_bit 'make clock bit high
andn outa, clk_bit 'make clock bit low
Tog_Clk_ret ret 'return from this subroutine
Set_dira2 long %00001011_00000000_00000000_00000000 'Set dira register
Chs_Bit long %00000001_00000000_00000000_00000000 'Chip select bit 24
Din_Bit long %00000010_00000000_00000000_00000000 'Data in bit 25
Dout_Bit long %00000100_00000000_00000000_00000000 'Data out bit 26
Clk_Bit long %00001000_00000000_00000000_00000000 'Clock bit 27
mask26 long %11111011_11111111_11111111_11111111 'Mask for reading the Dout bit only
temp2 res 1 'temporary storage variable, misc
count res 1 'temporary storage variable, read bit counter
Dat_Red res 1 'temporary storage variable, data being read
mem2 res 1 'memory
could you please add a verbal description of the logic of your code.
I mean which variables are involved to exchange the data from cog to cog?
a description like:
the cognew-command uses variable ... as ....
the ...PASM-cog does ..... and stores the value ....
a program that should explain cog to cog datatransfer should do just that.
Your code above has almost hidden the central thing of reading and writing from/to HUB-RAM somewhere in the code.
If you would like to keep the MCP3208-reading pack it aside with one subroutine call and all other subroutine calls are "nested" under this first subcall.
Add an explanation that "variable ... will contain the value that will be transferred to HUB-RAM"
then do the WRLONG
first PASM-cog does setup a value. Second PASM-cog does a simple manipulation of that value and writes back the new value.
Nothing more.
I would like to see another thing. Please use variablenames and labels that are selfexplaining consequently
labels like "get_pot" "read_bit" are selfexplaining. But variable names like "temp2" "mem2" are obfuscating.
Reading this labels needs extra mental capacity to translate temp2 means .... etc.
If you use a variable for more than one purpose it gets even worse to understand the code.
The brain of the beginner has to think of
in this line mem2 means....
in this line mem2 means....
use a new variable for each purpose with a self-explaining name.
keep the drafted code coming but improve it along the comments.
best regards
...
clkdelay [COLOR="blue"]rdlong @pot_data, time[/COLOR] 'the delay subroutine, load pot into time
[COLOR="red"]re_do wrlong time, mem0 'write inot temp variable
mov mem0, par 'srite temp to PAR
[/COLOR] sub time, #1 wz 'sub 1 from time and set flag if 0
if_nz jmp #re_do 'if flag not 0 go back to take4
clkdelay_ret ret 'return for delay subroutine
[COLOR="blue"]time res 1 'location for time[/COLOR]
[COLOR="red"]mem0 res 1 'temp storage[/COLOR]
'+++++++++++++++++++++++++Cog 1 reads the potentiometer into pot++++++WORKS+++++++++++++++++++++++++++++
...
[COLOR="red"] wrlong dat_red, mem2 'write it in PAR to share it as P.Val
mov mem2, par 'get address of mem
[/COLOR] or outa , chs_Bit 'Makes Chip select high
jmp #get_pot 'go back to do it all again
Tog_Clk or outa, clk_bit 'make clock bit high
andn outa, clk_bit 'make clock bit low
Tog_Clk_ret ret 'return from this subroutine
...
temp2 res 1 'temporary storage variable, misc
count res 1 'temporary storage variable, read bit counter
Dat_Red res 1 'temporary storage variable, data being read
[COLOR="red"]mem2 res 1 'memory [/COLOR]
@Harprit: You did it right before, why this regression all of a sudden? Meaning the first time you run this code you'll write to more or less random hub locations. Besides, keep it simple. Basically one cog reads from location L, another updates/writes to location L. So give them the same address as a cognew parameter. How are they supposed to find each other otherwise (unless the main loop does the transfer which I can't see it doing right now)?
Maybe it helps maybe not:
{
direct link | 3rd party link
|
+-----------+
+- rdlong -->| |<-- rdlong --- par_N -<-+
| | cog N | |
^ | | +-----------+
| +-----------+ | |
par_shared | | cog X |
| +-----------+ | |
^ | | +-----------+
| | cog M | |
+- wrlong ---| |--- wrlong --> par_M ->-+
+-----------+
|
}
The left side shows a use case where both cogs communicate through a single hub location (they are directly linked). cog M continuously writes to par_shared and cog N will read that value and act on it. On the right side, both cogs have their own parameter area. Again, cog M writes to it and cog N reads from it. Without 3rd party help, cog N will never see the value written by cog M. This is where cog X comes in which simply reads the value produced by cog M, optionally processes it (e.g. limit min/max value) and then writes it to par_N where cog N can pick it up.
As usual, there are lots of other ways of dealing with inter-cog communication.
A simple demonstration of moving a value between two cogs.
Taking advise from kuroneko, here is how we write to high memory in one Cog and read the same in another Cog.
The number 2000 is written to location 500 in main memory, the HUB memory.
The number is then read in Cog 2 and 3 is added so it is not the same as the 2000 we wrote, when displayed
The numbers are displayed on the console so they are being shared with all SPIN objects also because they are declared global in VAR.
CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
VAR
long writ_val, read_val
OBJ
fds : "FullDuplexSerial"
PUB displays_values
fds.start(31,30,0,115200) 'start console at 115200 for debug output
cognew(@writer, @writ_val) 'start new cog at "writer"
cognew(@reader, @read_val) 'start new cog at "reader"
repeat 'loop
fds.tx($1) 'home to 0,0
fds.dec(writ_val) 'print written value
fds.tx(" ") 'spaces
fds.dec(read_val) 'print read value
fds.tx(" ") 'spaces
waitcnt(clkfreq/60+cnt) 'flicker free wait
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dat
org 0
writer wrlong value, #500 'write 2000 inot location 500
wrlong value, par 'move to PAR. Will be in writ_val
jmp #writer 'loop back
value long 2000
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dat
org 0
reader rdlong read, #500 'read from location 500
add read, #3 'add the number 3 to it to change it
wrlong read, mem1 'write it ot memory
mov mem1, par 'move it to PAR. Will be in read_val
jmp #reader
mem1 res 1
read res 1
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
In my experiments I have found that it is not possible to write to all high memory locations and then read that same location effectively. Some locations work and some right next to them do not. The program itself comes no where near filling up the memory of the hub.
What is the reason for this and how can the right resisters be identified.
(You can play with this in the program in #311 above)
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dat
org 0
writer wrlong value, #500 'write 2000 inot location 500
wrlong value, par 'move to PAR. Will be in writ_val
jmp #writer 'loop back
value long 2000
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dat
org 0
reader rdlong read, #500 'read from location 500
add read, #3 'add the number 3 to it to change it
[COLOR="red"] wrlong read, mem1 'write it ot memory
mov mem1, par 'move it to PAR. Will be in read_val
[/COLOR] jmp #reader
mem1 res 1
read res 1
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
As for using an arbitrary address for data exchange, DON'T. Your program occupies 200 longs when compiled with the prop tool. Meaning (at least) addresses 0..799 in hub memory are in use. So writing to 500 will definitely bury something (e.g. the active SPIN code).
Not sure if you misunderstood my schematic (it contains 2 use cases together, I added some explanation to that post) but if you need e.g. 3 hub locations then I'd suggest an array of 3 longs. Pass @array{0} to both cog's and use par itself as the shared location (currently #500). Let the writer use par+4 (array[1]) as its private location and dedicate par+8 (array[2]) to the reader. This was mentioned already in post [post=1026960]#300[/post]. Anyway, 2 locations will be sufficient in this case (shared and reader private).
CON
_clkmode = XTAL1|PLL16X
_xinfreq = 5_000_000
VAR
long array[2]
OBJ
fds: "FullDuplexSerial"
PUB displays_values
fds.start(31,30,0,115200) 'start console at 115200 for debug output
cognew(@writer, @array{0}) 'start new cog at "writer"
cognew(@reader, @array{0}) 'start new cog at "reader"
repeat 'loop
fds.tx($1) 'home to 0,0
fds.dec(array[0]) 'print written value
fds.tx(" ") 'spaces
fds.dec(array[1]) 'print read value
fds.tx(" ") 'spaces
waitcnt(clkfreq/2+cnt) 'flicker free wait
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DAT org 0
writer mov :time, cnt ' now
rdlong :delay, #0 ' get clkfreq
add :time, :delay ' now + 1 sec
' setup phase complete
:loop [COLOR="orange"]wrlong :value, par[/COLOR] ' write value to shared location
[COLOR="orange"]add :value, #1[/COLOR] ' increment value
waitcnt :time, :delay ' wait for a sec
[COLOR="orange"]jmp #:loop[/COLOR] ' repeat
:value long 2000 ' initial value to be written
:delay res 1 ' |
:time res 1 ' loop delay handling
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DAT org 0
reader add :private, par ' resolve our private address
' setup phase complete
:loop [COLOR="blue"]rdlong :value, par[/COLOR] ' read from shared location
[COLOR="blue"]add :value, #3[/COLOR] ' add the number 3 to it to change it
[COLOR="blue"]wrlong :value, :private[/COLOR] ' write modified value to private location
[COLOR="blue"]jmp #:loop[/COLOR] ' repeat
:private long 4 ' @array[1]
:value res 1 ' temporary
'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dat
org 0
prog_gen mov dira, set_dira
out1 mov outa, on
nop
mov outa, off
jmp #out1
set_dira long %00000001_00000000_0000000_00000000
on long %00000001_00000000_0000000_00000000
off long %00000000_00000000_0000000_00000000
Forgive me please but I've seen 10 examples of this MOV technique to toggle pins. I'm a PASM beginer so I may be completely wrong but doesn't this MOV also zero out all the other pins?
IE, if I'm doing stuff with multiple pins from the same cog arn't these techniques going to stomp all over each other?
Forgive me please but I've seen 10 examples of this MOV technique to toggle pins. I'm a PASM beginer so I may be completely wrong but doesn't this MOV also zero out all the other pins?
IE, if I'm doing stuff with multiple pins from the same cog arn't these techniques going to stomp all over each other?
You are correct. The preferred alternative is to say "or dira, set_dira" ....
Looks like he's still on chapter 1 so keeping the number of instructions to remember all at once lower may be better.
The first instruction in a program can use MOV to set DIRA and OUTA because everything starts out with zeros. After that if you want to modify either OUTA or DIRA without changing other pins use OR and ANDN.
Note: In this case only one pin is being addressed so this technique can be used.
The choice is also affected by what other Cogs may be doing to OUTA and DIRA. Not a problem in this case.
Jazzed is right, I am trying to keep it as simple as possible right now. (only 1 pin)
But you make a very valid point here. Specially so for beginners.
The thing is that people new to a language tend to be cargo-cult programmers.
IE, we copy some program code from the book into our own code with little or no understanding of how the code works and then try and work it out from there.
So, if I'm grepping through your book looking for (for example) how to toggle a pin, I may copy what is there and have no concept as to why it is breaking everything else.
Oh - here is a Real Life example of this when I was learning SPIN from the SPIN book.
In the SPIN book they gave a listing which illustrated how to do something (I think it was copying data from cog to cog) incorrectly. Then, later they show how it should be done.
When I was writing my keyboard code guess which I copied and pasted? Yup, you guessed it. Guess who took two days to find his error? This guy.
Especially in low level languages people who are learning are still thinking in blocks of code. The reason we do that is because the difficulty in learning a low level language is NOT the language but understanding how to break down our problem into the low-level pieces.
"I'm going to take this example of how to do a loop, cut + paste, replace the innards".
This is why the learning curve is shallow for some and they can just pick up the datasheet. If you've programmed in ASM before then you have the hardest part done. At that point it's simply syntax.
@_red_, I have used snippets also just to bootstrap. I tend to dig in and make things "my own" with no intent to give it back That usually meant writing from scratch very often for years and now. As a beginner long ago I relied entirely on the code listing from one book and was really frustrated when the book had syntax or other errors. ....
The choice is also affected by what other Cogs may be doing to OUTA and DIRA. Not a problem in this case.
This is very true. Still, one must remember that the registers are entirely separate for different cogs. The key is that the cogs are connected "wire-or" like. I.E. if one cog sets dira for a bit and sets that bit high, it doesn't matter what other cogs do, the bit will remain high as long as any one cog sets it high.
copy & paste without understanding is bad programming style. It is quick (and dirty - very dirty)
Now how can an author support such lazy people in an optimal way?
Right at the beginning a small chapter called
"The secret of fast finished projects"
Writing a concrete example burt still short example of how too quick reading and guessing around drove a coder nuts for days or weeks.
And an example of how systematic step by step coding and step by step testing turned out to be three times faster in the end.
Plus intensive commenting of the code and explaining the limits of the code.
The most important to keep in mind at this time is that I am still very much a beginner. Be patient. Very stupid as far as PASM goes. No assembly experience. No CS education.
So Heater's words are well to keep in mind. Probably the best comments to date.
Stefan is an accomplished and experienced teacher in the best German tradition. Valued postings. There is no doubt in my mind about that.
Kuroneko knows his stuff, and how. There is no doubt in my mind about that either. I have learned much from him and hope to learn much more. He still has not figured out how much of a beginner I am!
Jazzed is very good but I find his stuff a bit harder to understand. Not for beginners. He writes not for them.
Potatohead is very good and has been very helpful to me. Still way ahead of where I am headed.
Same goes for JohnyMac and many many others.
My problem is to listen to all of them VERY CAREFULLY and write a beginners book. Sort the pepper out from the fly specs. I think my coding is still rather primitive but I do try to avoid sophistications that might trip up a beginner. It has its problems By the time I get the book done, hopefully, this will have improved enough to be acceptable to most knowledgeable forum readers. Comments like those made by _red_ are more than useful. They are the essence of what I need to know and respond to. Unfortunately there are too few of them.
Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post.
Here is the code to vary a frequency from a potentiometer.
It uses the PAR register to transfer data between cogs.
Since only one variable is transferred, an array is not needed
{{
This program uses a potentiometer to control the frequency of a signal.
It works but you have to have an MCP 3208 reading a pot on line 1 to see
what is happening.
Program 010PASM WavLenFromPot.spin
Aug 17 2011
Harprit Singh Sandhu
Terms: MIT License
}}
CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
VAR
long pot, pot_read, countup, new
OBJ
fds : "FullDuplexSerial"
PUB wavelengths
fds.start(31,30,0,115200) 'start console at 115200 for debug output
dira := %00000000_10000000_00000000_00000000
cognew(@display, @pot_read)
cognew(@get_pot, @pot_read)
waitcnt(clkfreq/4+cnt)
repeat 'loop
fds.tx($1) 'home to 0,0
fds.dec(countup) 'counter displayed
fds.tx(" ") 'space
fds.tx(" ") 'space
fds.tx($0d) 'new line
fds.dec(pot_read) 'print value as decimal
fds.tx(" ") 'space
fds.tx(" ") 'space
countup := countup + 1 'increment counter
waitcnt(clkfreq/60+cnt) 'flicker free wait
'++++++++++++++++++++++++++++++++++++Cog0 to toggle signal ++++++WORKS++++++++++++++++++++
'This routine takes the potentiometer reading and uses it the control the delay between
'the on and off states of one pin (23). The first half of the routine turns the pin on
'calls the delay, turns the pin off, calls the delay and loops forever.
'
dat
org 0
display mov dira, set_dira 'to set directions
re_do1 or outa, pin_ID 'turn on pin
call #clkdelay 'delay call
andn outa, pin_ID 'turn off pin
call #clkdelay 'delay call
jmp #re_do1 'do it again
'
'The routine adds 20 to the register to keep having the CNT have to go
'all the way around 32 bits. The timed delay is created by subrtracting 1 from the
'delay value till it gets to 0 and then returning to the calling routine
clkdelay rdlong delay, par 'read delay from PAR
add delay, #20 'add 20 to overcome CNT undercount
re_do2 sub delay, #1 wz 'sub 1 from time and set flag if 0
if_nz jmp #re_do2 'if flag not 0 go back to take4
clkdelay_ret ret 'return for delay subroutine
set_dira long %00000000_10000000_00000000_00000000
pin_ID long %00000000_10000000_00000000_00000000
delay res 1
'+++++++++++++++++++++++++Cog 1 reads the potentiometer into pot++++++WORKS+++++++++++++++++++++++++++++
'This routine reads a potentiometer and puts its value in PAR
'the value varies from 0 to 4095, 12 bit resolution from an MCP 3208 or
'other A2D chip. This routine is optimized and can be used in other programs
'
dat
org 0
get_pot mov dira, set_dira2 'sets direction of the prop pins
or outa , chs_Bit 'Makes Chip select high
andn outa, chs_Bit 'makes chip select low
andn outa , clk_bit 'ANDN it with the Clock Bi to make low
or outa , din_Bit 'Makes the Din high
call #Tog_clk 'clk hi-lo to read data
or outa , din_Bit 'Makes the Din high
call #Tog_Clk 'toggle clock line hi then low to read in the data
andn outa , din_Bit 'makes Din low 000 for line 0
call #Tog_Clk 'toggle clock line hi then low to read in the data
andn outa , din_Bit 'makes Din low 000 for line 0
call #Tog_Clk 'toggle clock line hi then low to read in the data
andn outa , din_Bit 'makes Din low 000 for line 0
call #Tog_Clk 'toggle clock line hi then low to read in the data
andn outa , din_Bit 'makes Din low
call #Tog_Clk 'toggle clock line hi then low to read in the data
call #Tog_Clk 'toggle clock line hi then low to read in the data
mov dat_red, #0 'Clear register we will read data into
mov count, #12 'Counter for number of bits we will read
read_bit mov temp, ina 'read in what is in all the input lines
andn temp, mask26 wz 'mask off everything except Dout line. Set Z flag
shl Dat_red, #1 'shift register left 1 bit to get ready for next bit
if_nz add Dat_red, #1 'if value is still positive add 1 to data register
call #Tog_Clk 'toggle clock to get next bit ready in Dout
sub count, #1 wz 'decrement the "bits read" counter. Set Z flag
if_nz jmp #read_bit 'go up and do it again if counter not yet 0.
wrlong dat_red, par 'write to PAR
or outa , chs_Bit 'Makes Chip select high
jmp #get_pot 'go back to do it all again
Tog_Clk or outa, clk_bit 'make clock bit high
andn outa, clk_bit 'make clock bit low
Tog_Clk_ret ret 'return from this subroutine
Set_dira2 long %00001011_00000000_00000000_00000000 'Set dira register
Chs_Bit long %00000001_00000000_00000000_00000000 'Chip select bit 24
Din_Bit long %00000010_00000000_00000000_00000000 'Data in bit 25
Dout_Bit long %00000100_00000000_00000000_00000000 'Data out bit 26
Clk_Bit long %00001000_00000000_00000000_00000000 'Clock bit 27
mask26 long %11111011_11111111_11111111_11111111 'Mask for reading the Dout bit only
temp res 1 'temporary storage variable, misc
count res 1 'temporary storage variable, read bit counter
Dat_Red res 1 'temporary storage variable, data being read
value res 1 'memory
Please do not do this. The chip has a PAR register, and it's there for a well engineered workflow that lies at the core of darn near every PASM code body they will encounter. This is a basic idea that really needs to be communicated properly.
I think cut 'n paste samples for loops, comparisons, setups for video, math, etc... can be useful, if they are documented, and more importantly, explained well. Having starting points, or templates can be extremely valuable for learning as one can get a program running, then work on areas of interest in that controlled, known environment.
They cannot serve as the basis though. The core ideas have to be put out there and built upon so the beginner gets the 'mindset' of how said building works.
What if, for example, the same PASM program is to run on two COGs? (My early work does not show this case, but it will on next release) What do we do with that hard coded address? Duplicate a entire program?
What if that particular bit of memory is in use? There are lots of memory cases, and given we have 32K, conflicts are going to happen. Passing addresses means code that can be re-used, repurposed, and or can adapt to other code added. Say something is written to serial, and a video display is desired. Those buffers are large, and the addition of a video display isn't normally a big deal, unless the hard coded address happens to be right in the buffer! User sees flickering pixels or characters or colors, wonders about it, then wonders again when the buffer is cleared, causing their PASM to break...
There are lots of other cases, in fact nearly all cases where this is what I like to call, "a written problem", something the user authored that is a problem, in addition to the problem they are trying to solve.
So here is a newbie question... Is it possible to call / use an object (eg: FullDuplexSerial) directly from PASM or do you always have to forward everything through a SPIN object via shm?
So here is a newbie question... Is it possible to call / use an object (eg: FullDuplexSerial) directly from PASM or do you always have to forward everything through a SPIN object via shm?
It depends. If the object has a PASM driver, then it should be possible to invoke it via it's shared memory interface. However, you lose the functionality provided by the SPIN PUB functions.
For example, FullDuplexSerial has a PASM driver which has a shared memory interface (rx_head . . . ). So if your PASM code knows that address it can update those variables. However your PASM code will need to do the same things as the tx function.
Question
This is tangential to _red_'s question.
We have 8 cogs
It takes a certain amount of time for the HUB to come back to a Cog
Is there a benefit to writing a routine in PASM if a SPIN equivalent can get done by the time the next HUB access occurs?
Meaning: When is it beneficial to write a PASM routine and what types of routines, in general, are suitable for PASM.
I can see that there is constantly changing and rapidly changing information that another cog needs immediately that may well be generated in PASM to good effect.
Interfaces to other chips and encoders etc are well suited, but what else.
H
When is it beneficial to write a PASM routine and what types of routines, in general, are suitable for PASM.
PASM is necessary when the performance of SPIN is inadequate, e.g. timing precision, reaction latency, instruction throughput. Also, due to the speed of PASM, it is sometimes possible to combine multiple functions / interfaces into a single PASM driver.
Because there is a significant latency between the cogstart and when execution starts, in most cases the PASM drivers are loaded at startup rather than on-demand.
In addition to the interface drivers, PASM is used for compute-intensive functions, i.e. floating point, FFT, AES. PASM also gets used for processor and language interpreters / emulators. (Although I did a small Infocom Z3 interpreter in SPIN.)
Harprit,
I'm a little bit shocked that you have written a book on Spin and are well into PASM book and you can still ask such a question.
A cursory glance at how the Prop, Spin and PASM work will have you guessing that functionality written in Spin will be 50 to 100 times slower than the same functionity written in PASM. As soon as you find out that Spin is executed by a byte code intererpreter with the byte codes in HUB that is what your gut should be telling you.
Now I don't know what the performance ratio is exactly and it probably varies a lot depending on what you are doing. As an example, if I remember correctly, my FFT was 80 times faster in PASM than Spin.
Its a philosophical question at this stage.
Its not necessary that I don't know what the answer is, I need to know what the beginner community thinks so that I can respond to their needs in the best way possible and not make any serious mistakes. Please notice that my use of a HUB memory register to exchange information between COGS in my last program posting was disapproved of with some emphasis. (Though how to do just that has just been discussed). So these questions may be more useful than we think.
Comments
That bit of code is actually quite instructive, IMHO. I learned a LOT of PASM by working through it.
Hard coded addresses are bad form in general, though sometimes necessary.
PASM COGs written with either a PAR offset, or that utilize the mailbox methods are atomic, and can be reused in many different memory / language scenarios. Hard-coding memory addresses doesn't work the same way, though again, sometimes necessary.
**This thread has been very good Harpit. I find my own effort considerably improved from this discussion.
I just did not get it the first time around. Thanks Stephan.
As to Potatohead's comment. My own experience has been that the best instructors I had were those who showed me the basics clear and bright, not the high flying astro physicists that knew oh so much and did not have the time to discuss what I needed to know with me. Teaching/showing/demonstrating the basics is a funny business.
I'm trying to keep it as simple as I can but the book will of necessity have much more than we are discussing here.
HSS
Note, the way the above loops are constructed means that a change in D by 1 adds 4 cycles to the runtime. IIRC, your frequency loop adds 16 cycles for each increment which neatly sidesteps the hub aliasing issue (because you're adding always a full hub window which leaves the remainder untouched).
First Cog is the console
Second cog generates a signal but we are interested only in the delay created
Third cog read the pot and works fine.
The read signal is not getting into the second cog properly
Here is the code
could you please add a verbal description of the logic of your code.
I mean which variables are involved to exchange the data from cog to cog?
a description like:
the cognew-command uses variable ... as ....
the ...PASM-cog does ..... and stores the value ....
a program that should explain cog to cog datatransfer should do just that.
Your code above has almost hidden the central thing of reading and writing from/to HUB-RAM somewhere in the code.
If you would like to keep the MCP3208-reading pack it aside with one subroutine call and all other subroutine calls are "nested" under this first subcall.
Add an explanation that "variable ... will contain the value that will be transferred to HUB-RAM"
then do the WRLONG
first PASM-cog does setup a value. Second PASM-cog does a simple manipulation of that value and writes back the new value.
Nothing more.
I would like to see another thing. Please use variablenames and labels that are selfexplaining consequently
labels like "get_pot" "read_bit" are selfexplaining. But variable names like "temp2" "mem2" are obfuscating.
Reading this labels needs extra mental capacity to translate temp2 means .... etc.
If you use a variable for more than one purpose it gets even worse to understand the code.
The brain of the beginner has to think of
in this line mem2 means....
in this line mem2 means....
use a new variable for each purpose with a self-explaining name.
keep the drafted code coming but improve it along the comments.
best regards
Stefan
I will do it and re-post but it will take some time. Hang on.
HSS
Maybe it helps maybe not: The left side shows a use case where both cogs communicate through a single hub location (they are directly linked). cog M continuously writes to par_shared and cog N will read that value and act on it. On the right side, both cogs have their own parameter area. Again, cog M writes to it and cog N reads from it. Without 3rd party help, cog N will never see the value written by cog M. This is where cog X comes in which simply reads the value produced by cog M, optionally processes it (e.g. limit min/max value) and then writes it to par_N where cog N can pick it up.
As usual, there are lots of other ways of dealing with inter-cog communication.
Taking advise from kuroneko, here is how we write to high memory in one Cog and read the same in another Cog.
The number 2000 is written to location 500 in main memory, the HUB memory.
The number is then read in Cog 2 and 3 is added so it is not the same as the 2000 we wrote, when displayed
The numbers are displayed on the console so they are being shared with all SPIN objects also because they are declared global in VAR.
HSS
What is the reason for this and how can the right resisters be identified.
(You can play with this in the program in #311 above)
HSS
Not sure if you misunderstood my schematic (it contains 2 use cases together, I added some explanation to that post) but if you need e.g. 3 hub locations then I'd suggest an array of 3 longs. Pass @array{0} to both cog's and use par itself as the shared location (currently #500). Let the writer use par+4 (array[1]) as its private location and dedicate par+8 (array[2]) to the reader. This was mentioned already in post [post=1026960]#300[/post]. Anyway, 2 locations will be sufficient in this case (shared and reader private).
Forgive me please but I've seen 10 examples of this MOV technique to toggle pins. I'm a PASM beginer so I may be completely wrong but doesn't this MOV also zero out all the other pins?
IE, if I'm doing stuff with multiple pins from the same cog arn't these techniques going to stomp all over each other?
Looks like he's still on chapter 1 so keeping the number of instructions to remember all at once lower may be better.
The first instruction in a program can use MOV to set DIRA and OUTA because everything starts out with zeros. After that if you want to modify either OUTA or DIRA without changing other pins use OR and ANDN.
Note: In this case only one pin is being addressed so this technique can be used.
The choice is also affected by what other Cogs may be doing to OUTA and DIRA. Not a problem in this case.
Jazzed is right, I am trying to keep it as simple as possible right now. (only 1 pin)
But you make a very valid point here. Specially so for beginners.
Please keep posting.
HSS
IE, we copy some program code from the book into our own code with little or no understanding of how the code works and then try and work it out from there.
So, if I'm grepping through your book looking for (for example) how to toggle a pin, I may copy what is there and have no concept as to why it is breaking everything else.
Oh - here is a Real Life example of this when I was learning SPIN from the SPIN book.
In the SPIN book they gave a listing which illustrated how to do something (I think it was copying data from cog to cog) incorrectly. Then, later they show how it should be done.
When I was writing my keyboard code guess which I copied and pasted? Yup, you guessed it. Guess who took two days to find his error? This guy.
Especially in low level languages people who are learning are still thinking in blocks of code. The reason we do that is because the difficulty in learning a low level language is NOT the language but understanding how to break down our problem into the low-level pieces.
"I'm going to take this example of how to do a loop, cut + paste, replace the innards".
This is why the learning curve is shallow for some and they can just pick up the datasheet. If you've programmed in ASM before then you have the hardest part done. At that point it's simply syntax.
Red
This is very true. Still, one must remember that the registers are entirely separate for different cogs. The key is that the cogs are connected "wire-or" like. I.E. if one cog sets dira for a bit and sets that bit high, it doesn't matter what other cogs do, the bit will remain high as long as any one cog sets it high.
Now how can an author support such lazy people in an optimal way?
Right at the beginning a small chapter called
"The secret of fast finished projects"
Writing a concrete example burt still short example of how too quick reading and guessing around drove a coder nuts for days or weeks.
And an example of how systematic step by step coding and step by step testing turned out to be three times faster in the end.
Plus intensive commenting of the code and explaining the limits of the code.
keep the questions coming
best regards
Stefan
So Heater's words are well to keep in mind. Probably the best comments to date.
Stefan is an accomplished and experienced teacher in the best German tradition. Valued postings. There is no doubt in my mind about that.
Kuroneko knows his stuff, and how. There is no doubt in my mind about that either. I have learned much from him and hope to learn much more. He still has not figured out how much of a beginner I am!
Jazzed is very good but I find his stuff a bit harder to understand. Not for beginners. He writes not for them.
Potatohead is very good and has been very helpful to me. Still way ahead of where I am headed.
Same goes for JohnyMac and many many others.
My problem is to listen to all of them VERY CAREFULLY and write a beginners book. Sort the pepper out from the fly specs. I think my coding is still rather primitive but I do try to avoid sophistications that might trip up a beginner. It has its problems By the time I get the book done, hopefully, this will have improved enough to be acceptable to most knowledgeable forum readers. Comments like those made by _red_ are more than useful. They are the essence of what I need to know and respond to. Unfortunately there are too few of them.
Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post. Beginners please post.
Harprit
It uses the PAR register to transfer data between cogs.
Since only one variable is transferred, an array is not needed
Harprit
-Tor
Please do not do this. The chip has a PAR register, and it's there for a well engineered workflow that lies at the core of darn near every PASM code body they will encounter. This is a basic idea that really needs to be communicated properly.
I think cut 'n paste samples for loops, comparisons, setups for video, math, etc... can be useful, if they are documented, and more importantly, explained well. Having starting points, or templates can be extremely valuable for learning as one can get a program running, then work on areas of interest in that controlled, known environment.
They cannot serve as the basis though. The core ideas have to be put out there and built upon so the beginner gets the 'mindset' of how said building works.
What if, for example, the same PASM program is to run on two COGs? (My early work does not show this case, but it will on next release) What do we do with that hard coded address? Duplicate a entire program?
What if that particular bit of memory is in use? There are lots of memory cases, and given we have 32K, conflicts are going to happen. Passing addresses means code that can be re-used, repurposed, and or can adapt to other code added. Say something is written to serial, and a video display is desired. Those buffers are large, and the addition of a video display isn't normally a big deal, unless the hard coded address happens to be right in the buffer! User sees flickering pixels or characters or colors, wonders about it, then wonders again when the buffer is cleared, causing their PASM to break...
There are lots of other cases, in fact nearly all cases where this is what I like to call, "a written problem", something the user authored that is a problem, in addition to the problem they are trying to solve.
No, scratch that. It's easy enough to show the right way the first time:
No, scratch that. It's easy enough to show the right way the first time:
Now done with the PAR variable.
H
It depends. If the object has a PASM driver, then it should be possible to invoke it via it's shared memory interface. However, you lose the functionality provided by the SPIN PUB functions.
For example, FullDuplexSerial has a PASM driver which has a shared memory interface (rx_head . . . ). So if your PASM code knows that address it can update those variables. However your PASM code will need to do the same things as the tx function.
This is tangential to _red_'s question.
We have 8 cogs
It takes a certain amount of time for the HUB to come back to a Cog
Is there a benefit to writing a routine in PASM if a SPIN equivalent can get done by the time the next HUB access occurs?
Meaning: When is it beneficial to write a PASM routine and what types of routines, in general, are suitable for PASM.
I can see that there is constantly changing and rapidly changing information that another cog needs immediately that may well be generated in PASM to good effect.
Interfaces to other chips and encoders etc are well suited, but what else.
H
PASM is necessary when the performance of SPIN is inadequate, e.g. timing precision, reaction latency, instruction throughput. Also, due to the speed of PASM, it is sometimes possible to combine multiple functions / interfaces into a single PASM driver.
Because there is a significant latency between the cogstart and when execution starts, in most cases the PASM drivers are loaded at startup rather than on-demand.
In addition to the interface drivers, PASM is used for compute-intensive functions, i.e. floating point, FFT, AES. PASM also gets used for processor and language interpreters / emulators. (Although I did a small Infocom Z3 interpreter in SPIN.)
I'm a little bit shocked that you have written a book on Spin and are well into PASM book and you can still ask such a question.
A cursory glance at how the Prop, Spin and PASM work will have you guessing that functionality written in Spin will be 50 to 100 times slower than the same functionity written in PASM. As soon as you find out that Spin is executed by a byte code intererpreter with the byte codes in HUB that is what your gut should be telling you.
Now I don't know what the performance ratio is exactly and it probably varies a lot depending on what you are doing. As an example, if I remember correctly, my FFT was 80 times faster in PASM than Spin.
Its a philosophical question at this stage.
Its not necessary that I don't know what the answer is, I need to know what the beginner community thinks so that I can respond to their needs in the best way possible and not make any serious mistakes. Please notice that my use of a HUB memory register to exchange information between COGS in my last program posting was disapproved of with some emphasis. (Though how to do just that has just been discussed). So these questions may be more useful than we think.
H