Shop OBEX P1 Docs P2 Docs Learn Events
Adventures in Spin — Parallax Forums

Adventures in Spin

cavelambcavelamb Posts: 720
edited 2012-03-09 22:29 in Propeller 1
In our last episode we (and I mean plural there!) got the buttons working
on my QuickStart board. Thank you kindly, neighbors.

This thing begs to be my first adventure in parallelism.
Load that puppy into another cog and let it buck!
Then anybody that wants to see if buttons are happening need only asks nicely.

But no.
I'm not having much getting this to work.
I've studied several of the objects from the exchange and read the book.
Tried a bunch of wrong ways.
But I haven't stumbled blindly on a right way yet.

Both of these have been debugged in terms of single cog methodology,
and work correctly.

But what do they need to succeed in parallel land?

[ugly code removed to save room]

Comments

  • MagIO2MagIO2 Posts: 2,243
    edited 2012-03-08 02:17
    Ok ... let me start with part 1 as it takes a while to do all the changes ;o)
    CON { TPad_Demo.spin }
      _CLKMODE=XTAL2            [color=red]' you really want it to run in slowmo?[/color]
      _xinfreq = 5_000_000                                          
    
    VAR
      LONG MS001
      [color=red]' ok, it worked with an array - now it's time to learn bit operations ;o)[/color]
      BYTE TPad[s][ 8 ][/s], Demo
      [color=red]' the main program per default has the whole rest of the HUB-RAM as stack
      [s]LONG Stack1[16][/s]
      ' the other COG should define it's stack by itself
      [s]LONG Stack2[16][/s][/color]
    
    OBJ scan: "TPad_Scan"
      
    PUB TPdemo | Pad, P1 ' local variables
      Demo := 0
      If Demo
        [color=green]TPad := %10101010[/color]
        [color=red][s]TPad[1]:=1[/s][/color]
        [color=red][s]TPad[3]:=1[/s][/color]
        [color=red][s]TPad[5]:=1[/s][/color]
        [color=red][s]TPad[7]:=1[/s][/color]
                       
      MS001 := CLKFREQ / 1_000      ' define 1 millisec
    
      [color=red]' you want to do parallel processing, so a COG should only use the PINs it's really responsible for !
      [s]dira [0..7] := %11111111      ' all pins outputs[/s]
      [s]outa [0..7] := %11111111      ' all pins high[/s]
      [s]WaitMS(1000)                  ' one second[/s]
      [s]outa [0..7] := %000000000     ' all pins low[/s][/color] 
    
      [color=green]' What this COG is responsible for are the LED-PINs
      outa[ 16..23 ] := %00000000      ' this is optional, as OUTA is guaranteed to be 0 on startup of a COG
      dira[ 16..23 ] := %11111111
    
      ' you created an object out of TPad_Scan, but you also have to "use" is somewhere
      ' the code there will not run automatically!
      ' 2 things here - cognew does not work across several *.spin-files (objects), so you need to implement a
      ' start-function which is doing the cognew there. And then you have the choice where to store the status
      ' of the buttons - either inside of the object or in the program using the object. I think it's more flexible
      ' to let the caller decide where the status should be stored in general. In this special case it really
      ' does not matter.
      scan.start( @TPad )
      [/color]
    
    ' start the main loop
    ' show what the scanner brought me
     Repeat                      
        Repeat pad from 0 to 7      ' button 0 to 7
          if [color=green]TPad & |<pad[/color]           ' 1 is on
              [color=red][s]dira[pad+16] := 1     ' so blinkenlighten[/s][/color]
              outa[pad+16] := 1     ' LED start at pin 16
              WaitMS(10)            ' 10 nice flicker
              outa[pad+16] := 0 '     
          
    PUB WaitMS(W)|  C               'wait W milliseconds
      C := W*MS001                 
      WaitCNT (C+cnt)   
    )
    

    There is also some potential for optimizing this code. You can easily get rid of the whole inner repeat-loop because you have 8 bit input and 8 bit output. Do you want to try yourself?

    Let me know if you have any problems running/compiling the code - what I posted is not tested, as I don't have my Prop Tool at hand.
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-03-08 02:36
    And here is the TPad_scan.spin.
    [s][color=red]CON { TPad_scan.spin }
     _CLKMODE=XTAL2
     _xinfreq = 5_000_000                                          
    ' not needed in an object file which does not contain a standalone program[/color][/s]
    VAR
      LONG MS001 
      [s][color=red]BYTE TPad[8]                ' global array returns results[/s]
      [s]BYTE Demo[/color][/s]
      [color=green]LONG statusStorageAdr
      LONG cogID
      LONG Stack[ 16 ]
    
    PUB start( sAdr )
      statusStorageAdr := sAdr
    
      if cogID
        cogstop( cogID-1 )
    
      cogID = cognew( TPScan, @Stack ) + 1
    [/color]
    
    PUB TPScan | Pad, P1 ' local variables
      MS001 := CLKFREQ / 1_000     ' define count for 1 millisec
      dira [0..7] := %11111111     ' set all pad pind as outputs    
      outa [0..7] := %11111111     ' preset all pad pins high
      [s][color=red]Demo := 0[/color][/s]
       
      Repeat                      ' loop forever
         Repeat pad from 0 to 7     ' 0-7 for QuickStart board
           dira [pad] := 1         ' make pin an output 
           dira [pad] := 0         ' make it an input
           WaitMS(2)               ' short delay for dome decay
           P1 := ina[pad]          ' read the pad
           if P1 == 0              ' 0 here means pressed
              [color=green]long[ statusStorageAdr ] |= |<pad[/color]
           else                    '
              [color=green]long[ statusStorageAdr ] &= !|<pad[/color]
    
    [color=red][s]
          if demo 
            if TPad[pad] ==1      ' 1 is on 
              dira[pad+16] := 1   ' so blinkenlighten
              outa[pad+16] := 1 ' ' LED start at pin 16
              WaitMS(10)          ' 10 nice flicker
              outa[pad+16] := 0 '
    [/s][/color]
                        
    PUB WaitMS(W)|  C             'wait for W milliseconds
      C := W*MS001                ' 
      WaitCNT (C+cnt)
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-03-08 02:47
    If you now have a look at both SPIN-files you see that you use one function in both: WaitMS, so it makes sense to split this and put it in it's own SPIN-file - let's say tools.spin or time.spin ...
    Without separating the compiler will really create 2 copies of this function in your executable. When you put it in a SPIN-file of it's own and use this SPIN-file in each place where it's needed, there will only be one copy in the executable.

    But I think you should try this by yourself as an excerzise as well ;o)
  • cavelambcavelamb Posts: 720
    edited 2012-03-08 14:48
    A few minor problems, but nothing difficult.
    You set me on the straight and narrow path.
    I appreciate that. Maybe it will help someone get started else as well?

    I still had a few minor issues to work out.
    One was that for some fool reason I was trying to use CogID as a variable.
    That didn't work very well (well duh!)

    After it was up and running I went though it and did a bit of housekeeping,
    so there are a lot of name changes to make it read better (at least to my eye).
    TP for touchpads? In 6 months will I remember what the heck that's about?
    So it's Buttons now. Keep it simple...

    The bit manipulation suggestion was timely.
    That was going to be my very next concern.
    And, since you brought it up... :smile:
    There are only 8 buttons, so why take up a whole long?
    Sure, it's an improvement over an 8 byte array, but longs are rare and precious...
    So I tried this:
    BYTE[Buttons] |= |< B ' bit set Bth bit = 1

    which compiled and ran fine.
    But was that because of the BYTE specifier? over ride?
    Or just because I was only dinking with 8 bits?
    Would it have happily written the higher bits if B said to?


    I'll start trying to puzzle out how to break out the common code.
    save the longs!


    Anyway, sharing the current state of the art...

    the Demo:
    CON { Button_Demo.spin }
        {this COG is responsible for the LEDs }
      _CLKMODE=XTAL2 
      _xinfreq = 5_000_000
                                            
    VAR
      LONG MS001
      WORD Buttons                               ' there are only 8 buttons
      
    OBJ button: "Button_Scanner"
    
    PUB ButtonDemo | B                       ' local variables
      MS001 := CLKFREQ / 1_000         ' define 1 millisec 
      button.start( @Buttons )                 ' send address of Buttons
      outa[ 16..23 ] := 000000          '
      dira[ 16..23 ] := 111111            ' 
      ' main loop - show what the scanner sees
      Repeat                      
        Repeat B from 0 to 7                    ' bit 0 to 7
          if Buttons & |< B                        ' bit AND of the Bth bit  
             outa[B+16] := 1                       ' LEDs start at pin 16
             WaitMS(10)                            ' 10 nice flicker
             outa[B+16] := 0
                      '      
    PUB WaitMS(W)|  C                        'wait W milliseconds
      C := W*MS001                 
      WaitCNT (C+cnt)
    




    and the scanner:
    'CON { Button_Scanner.spin }
    '    { responsible for the touch pad buttons }
    VAR
      LONG MS001                   
      WORD Buttons
      LONG ButtonCogID
      LONG Stack[ 16 ]                 ' define my stack
    
    PUB start( sAdr )                  ' start this in a new cog
      Buttons := sAdr             ' save status
      if ButtonCogID
        cogstop(ButtonCogID-1)         ' no cog available
      ButtonCogID := cognew(ButtonScan, @Stack) + 1
      
    PUB ButtonScan | B, B1             ' local variables
      MS001 := CLKFREQ / 1_000         ' define count for 1 millisec
      dira [0..7] := 111111         ' all pad pins outputs    
      outa [0..7] := 111111         ' all pad pins high   
      'main loop - scan the buttons   
      Repeat                           ' loop forever
         Repeat B from 0 to 7          ' 0-7 for QuickStart board
           dira [B] := 1               ' make pin an output 
           dira [B] := 0               ' make pin an input
           WaitMS(2)                   ' short delay for some decay
           B1 := ina[B]                ' read the pad
           if B1 == 0                  ' 0 here means pressed
              BYTE[Buttons] |= |< B    ' bit OR Bth bit   
           else                    
              BYTE[Buttons] &= !|< B   ' invert bit AND Bth bit
                        
    PUB WaitMS(W)|  C             'wait for W milliseconds
      C := W*MS001                ' 
      WaitCNT (C+cnt)
    

    [edit] corrected Buttons to WORD for proper address alignment (Per Mr. K)
  • cavelambcavelamb Posts: 720
    edited 2012-03-08 16:27
    It has been interesting, to say the least...
    I did get this part working.
    It is in a file named QS_Util.spin

    In the Demo.spin file I added:
    OBJ util:"QS_Utilities"

    So I can call that method with util.WaitMS(milliseconds).

    The burr under my saddle is that I haven't figured out how to initialize MS001 in the utility file.
    Even including the CONstant section and adding MS001 := ClkFreq/1000 to a start.util method didn't help.
    It does work if hard coded... but that's not exactly elegant, is it...

    Anybody have a page reference in the manual where this kind of stuff is discussed?

    .
    PUB WaitMS(W)                      'wait for W milliseconds
      W := W * 1000                       '_MS001 - but only for 5 megahertz clock rate???
      WaitCNT (W+cnt)
    
  • kuronekokuroneko Posts: 3,623
    edited 2012-03-08 17:00
    @cavelamb: You communicate the address of Buttons to the scanner object. Addresses need at least a word to be stored safely.
  • cavelambcavelamb Posts: 720
    edited 2012-03-08 17:31
    kuroneko wrote: »
    @cavelamb: You communicate the address of Buttons to the scanner object. Addresses need at least a word to be stored safely.

    Corrected that...
    Thank you.
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-03-08 23:31
    Ok ... let's start the next roundtrip ;o)
      outa[ 16..23 ] := [b][color=green]%[/color][/b]000000
      dira[ 16..23 ] := [b][color=green]%[/color][/b]111111
    

    Same problem you have in Button_Scanner.spin!

    If you want to make the variable names speak more for themselves, you should consider to rename Buttons in Button_Scanner.spin to ButtonsAdr. Oh ... and Buttons in Button_Demo.spin can live with just one byte. It's only ButtonsAdr which NEEDs to be 16bit.

    What I meant with "You can easily get rid of the whole inner repeat-loop because you have 8 bit input and 8 bit output" is:
    PUB ButtonDemo | B                       ' local variables
      MS001 := CLKFREQ / 1_000         ' define 1 millisec 
      button.start( @Buttons )                 ' send address of Buttons
      outa[ 16..23 ] := 000000          '
      dira[ 16..23 ] := 111111            ' 
      ' main loop - show what the scanner sees
      Repeat
        [color=green]outa[ 16..23 ] := Buttons
        WaitMS(10)
        outa[ 16..23 ] := 0[/color]
        [s][color=red]
        Repeat B from 0 to 7                    ' bit 0 to 7
          if Buttons & |< B                        ' bit AND of the Bth bit  
             outa[B+16] := 1                       ' LEDs start at pin 16
             WaitMS(10)                            ' 10 nice flicker
             outa[B+16] := 0[/color][/s]
    

    I'd also change the comments for
    BYTE[Buttons] |= |< B
    BYTE[Buttons] &= !|< B
    it's set a single bit for the first and clear single bit for the second

    About LONG MS001 ... you should move it into the QS_Utilities.spin file and I'd simply introduce an init-function which calculates the right amount like you currently do it. But here is also something to think about! You could add it in a VAR section which means that each instance of the QS_Utilites will have it's own copy. For this variable I'd say it makes no sense to have an individual copy per instance, as the clockfreq is a global setting anyway! So, add it to a DAT section. This on the other hand means that you only have to call init once! (Having an init which needs to initialize a VAR variable would have to be called per instance).
    Well ... it does not hurt to call it twice ..
    But starting a Utilities.spin-file propably leads to a collection of different functions and you need to be aware of these little differences in VAR vs. DAT at latest when you add functions that also need initialization.

    About your question:
    SPIN makes sure that values assigned to BYTE[] or a byte-size-variable only effect exactly these 8 bits meant. So, if you do a BYTE[ x ] := |<10 a zero would actually be assigned as the 11th bit is out of sight for this operation.

    CogId of course can also be a byte variable. Using long is just dreaming of 2^32-2 COGs ;o)
  • cavelambcavelamb Posts: 720
    edited 2012-03-09 22:29
    I'll be back with you in just a minute, Magl.

    I still need to work on my key driver, but in the mean time I'm using a cut down version from the
    object exchange so I could get one with a different project.

    :)
Sign In or Register to comment.