Shop OBEX P1 Docs P2 Docs Learn Events
Propeller Assembly for beginners - Page 11 — Parallax Forums

Propeller Assembly for beginners

18911131429

Comments

  • potatoheadpotatohead Posts: 10,261
    edited 2011-08-13 12:27
    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.
  • HarpritHarprit Posts: 539
    edited 2011-08-13 13:19
    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.

    HSS
  • kuronekokuroneko Posts: 3,623
    edited 2011-08-13 16:42
    Harprit wrote: »
    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:
    :loop   mov     [COLOR="blue"]D[/COLOR], base
            djnz    [COLOR="blue"]D[/COLOR], $
            jmp     #:loop
    
    '      D:  1  2  3  4  ...    cycles := D*4 + 12
    ' cycles: 16 20 24 28  ...
    
    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.
    :loop   rdlong  [COLOR="blue"]D[/COLOR], par
            djnz    [COLOR="blue"]D[/COLOR], $
            jmp     #:loop
    
    '      D:  1  2  3  4  5  6  7  8  9  ...    cycles := ((D - 1) >> 2) * 16 + 32
    ' cycles: 32 32 32 32 48 48 48 48 64  ...
    
    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).
  • HarpritHarprit Posts: 539
    edited 2011-08-15 09:26
    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 
    
  • StefanL38StefanL38 Posts: 2,292
    edited 2011-08-15 10:05
    Hi Harprit,

    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
  • HarpritHarprit Posts: 539
    edited 2011-08-15 15:55
    Stefan

    I will do it and re-post but it will take some time. Hang on.

    HSS
  • kuronekokuroneko Posts: 3,623
    edited 2011-08-15 17:32
    Harprit wrote: »
    ...
    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.
  • HarpritHarprit Posts: 539
    edited 2011-08-16 11:49
    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   
    '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    HSS
  • HarpritHarprit Posts: 539
    edited 2011-08-16 15:02
    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)

    HSS
  • kuronekokuroneko Posts: 3,623
    edited 2011-08-16 17:03
    Harprit wrote: »
    '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    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
    '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
  • __red____red__ Posts: 470
    edited 2011-08-17 07:59
    Harprit wrote: »
    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?
  • jazzedjazzed Posts: 11,803
    edited 2011-08-17 08:11
    __red__ wrote: »
    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.
  • HarpritHarprit Posts: 539
    edited 2011-08-17 08:38
    Red:

    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
  • __red____red__ Posts: 470
    edited 2011-08-17 09:06
    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
  • jazzedjazzed Posts: 11,803
    edited 2011-08-17 09:34
    @_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. ....
    Harprit wrote: »
    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.
  • StefanL38StefanL38 Posts: 2,292
    edited 2011-08-17 10:36
    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.

    keep the questions coming
    best regards

    Stefan
  • HarpritHarprit Posts: 539
    edited 2011-08-17 11:05
    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.

    Harprit
  • HarpritHarprit Posts: 539
    edited 2011-08-17 11:49
    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
    

    Harprit
  • TorTor Posts: 2,010
    edited 2011-08-17 13:22
    __red__ wrote: »
    The thing is that people new to a language tend to be cargo-cult programmers.
    I've never seen this fact stated in such a succinct manner.. Yes, this is so true. Cargo-cult programmers.. I love it. Well said! :)

    -Tor
  • jazzedjazzed Posts: 11,803
    edited 2011-08-17 21:44
    Harprit wrote: »
    Here is the code to vary a frequency from a potentiometer.
    It uses a HUB register to transfer data between cogs
    ...
                  wrlong    dat_red,  register1     'write it to register read/written to.
    ...
    register1      long      2000                                   'register written to in HUB  
    
    Using hard coded addresses for sharing variables is wrong. We have shown you how to do it.
  • potatoheadpotatohead Posts: 10,261
    edited 2011-08-17 22:39
    Seconded.

    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.
  • ericballericball Posts: 774
    edited 2011-08-18 04:28
    The only reasons to hardcode a HUB address is to simplify the explanation of HUB memory addressing, followed quickly by why it should never be done.

    No, scratch that. It's easy enough to show the right way the first time:
    VAR
      LONG sharedlong
    PUB start
      COGINIT( @cogstart, @sharedlong )
    DAT
                ORG      0
    cogstart    RDLONG   clockfreq, #0
                WRLONG   clockfreq, PAR
                             
    clockfreq   RES      1
    
  • ericballericball Posts: 774
    edited 2011-08-18 04:28
    The only reasons to hardcode a HUB address is to simplify the explanation of HUB memory addressing, followed quickly by why it should never be done.

    No, scratch that. It's easy enough to show the right way the first time:
    VAR
      LONG sharedlong
    PUB start
      COGINIT( @cogstart, @sharedlong )
    DAT
                ORG      0
    cogstart    RDLONG   clockfreq, #0
                WRLONG   clockfreq, PAR
                             
    clockfreq   RES      1
    
  • __red____red__ Posts: 470
    edited 2011-08-18 09:28
    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?
  • HarpritHarprit Posts: 539
    edited 2011-08-18 09:44
    I have revised the program to remove the use of the HUB register to move information between hubs.
    Now done with the PAR variable.

    H
  • ericballericball Posts: 774
    edited 2011-08-18 10:07
    __red__ wrote: »
    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.
  • HarpritHarprit Posts: 539
    edited 2011-08-18 10:25
    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
  • ericballericball Posts: 774
    edited 2011-08-18 12:00
    Harprit wrote: »
    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.)
  • Heater.Heater. Posts: 21,230
    edited 2011-08-18 12:24
    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.
  • HarpritHarprit Posts: 539
    edited 2011-08-18 13:07
    Heater:

    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
Sign In or Register to comment.