Early success followed by confusion...

As a retired electronic (hardware) design engineer who has experience of code and HDL abstraction and bloat I was delighted to discover the Propellor! I was very pleased that my first simple veroboard P8X32A-D40 ciruit worked and ran code from the Propeller Tool, with very low current consumption. Then I tried a serial LCD display and have become quite confused, mainly about how to creat strings or byte arrays in Spin that are a mixture of hex and text. I have a long-winded routine that sets every byte in an array as a number, and passing this to an assembly serial transmit routine works, but when I attempt something more elegant it seems to upset the Tool or do something strange (I can monitor the serial line on an oscilloscope). Does Spin natively support strings, or do I have to include an additional library? Are there any subtleties about how an assembly cog works its way through a string?

Comments

  • 17 Comments sorted by Date Added Votes
  • Hello and welcome. You will probably get some very quick resolution to your problem if you post your code here in the forum as an attachment.
  • Spin just support strings very minimalistic.

    in DAT sections you can use

    mystringname1 byte 1,2,3,"A","B","C",$0D,$0A,0
    mystringname2 byte "Hello World",0

    in code you can use the string function

    myaddress:=string("Hello",$0D,$0A,0)

    to do the same.

    Note: You can NOT use variables to build a string, this is compile time only. So myAddress:=string("Hello", myNumber,0) will not work.

    In both cases you basically have a byte-array terminated by a zero, also known as zero-terminated string.

    myaddress would already be the address of the array in memory, to access the string at mystringname1 you need to get the address of mystringname1 by using the @ operator giving you the hub-address of the label.

    myaddress1 := @mystringname1 to get the address of the byte-array.

    Most serial drivers have a str(address) method to output a string. That method takes a address as starting point and then sends out byte by byte over the serial line stopping at the first 0.

    You can access (and change) the content of bytes in the byte-arrays thru indexing.

    value := byte[myaddress][4] gives you the 5th byte so the "o" from "Hello" in the example string.
    byte[myaddress][0] :="J" makes "Jello" out of "Hello"

    for more complex manipulations you can use a library, there are a lot of them to use, but provided with the library folder in Proptool you have "SimpleStrings" to start with.

    Enjoy!

    Mike


    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • Spin has very limited support for strings, but it's enough for relatively simple things. There are library routines in the Propeller Object Exchange for more complex stuff. Look at the sources for the library routines for examples. Particularly useful are BYTEMOVE and STRING which you should look up in the Propeller Manual if you haven't already. STRING takes a string literal, possibly with embedded characters written as hex values, and returns the address of the string with a zero byte terminator as it's stored in the program. BYTEMOVE can be used to copy a string to a buffer. For example:

    BYTEMOVE(@buffer[5],STRING("label xxx",CR,LF,"more yyy"),20)

    The xxx and yyy would be overlaid later with something else.
  • Ahh! Thank you both - that explains a lot. I'm working with the .pdf manual which means I tend to dip in and out rather than seeing the complete picture.

    BTW possible typo:

    myaddress:=string("Hello",$0D,$0A,0)

    gives an error message for the final zero - "only values 1 - 255 are allowed". I think the string function adds it own zero at the end.

    Kind regards,

    David
  • kwinnkwinn Posts: 7,495
    dspread wrote: »
    Ahh! Thank you both - that explains a lot. I'm working with the .pdf manual which means I tend to dip in and out rather than seeing the complete picture.

    BTW possible typo:

    myaddress:=string("Hello",$0D,$0A,0)

    gives an error message for the final zero - "only values 1 - 255 are allowed". I think the string function adds it own zero at the end.

    Kind regards,

    David

    The zero at the end is not needed as the string() function puts it there automatically.
    In science there is no authority. There is only experiment.
    Life is unpredictable. Eat dessert first.
  • tonyp12tonyp12 Posts: 1,819
    edited May 19 Vote Up0Vote Down
    Have you done any C programming?, it have hardly no string support, unless you add str.lib and it's pretty much just a few byte-by-byte functions.

    If you have a function that expect a pointer to a location in memory that for example is a block of ascII text.
    If you instead of a pointer just insert "MY TEXT" the compiler will put that text in flash and insert the pointer.
    " " adds a zero, ' ' doesn't

    if your function expect raw data and length, you could create a macro
    #define TXSTRING(pnt) (TXdata((pnt), sizeof(pnt)-1)) // macro to get string and string len to function TXdata()

    example of use:
    TXSTRING("AT&K=0\r"); // hardware flow off
  • Getting there, but...

    My application uses pipelining, i.e. one COG performs a fixed function, then another COG processes that result and outputs its own result, etc. Of course they all run together, and I am arranging them all to have exactly the same loop time so that they stay in synchronism (phase is irrelevant).
    So far, so good.
    What I am stuck on now is how to write a spin file containing independent assembly code for several cogs, which have to read and write from the correct main RAM locations. I haven't come across an example or tutorial that gives an explicit example of multicog ASM.
    Helpful comments or examples welcome!
  • tomcrawfordtomcrawford Posts: 670
    edited July 1 Vote Up0Vote Down
    Multicog PASM is just like a single PASM cog, except more than once.

    Each PASM cog is in its own DAT section, with its own local constants and variables.
    Each PASM cog is launched with its own beginning address and PARM values, one a time.
    Each PASM cog is passed pointers to the variables in hub memory it needs to know about.

    If I recall correctly, there are examples in this text.

    Have fun! It all makes perfect sense one you understand it.
    Re-inventing the wheel is not a waste of time if, when you are done, you understand why it is round.
    Cool, CA, USA 95614
  • AribaAriba Posts: 2,077
    Separate DAT sections are not needed, but help to recognize the code sections, becaus they get a slightly different background color.

    Important is an ORG 0 before every code section, and that the register variables are defined in the same section. Don't use RES to reserve variables (exept maybe in the last section). And don't use register variables with the same name in different sections.
    The Assembler does not warn you if you use a variable name from another section, so be careful.

    Here is the structure:
    VAR
      long  sharedVar
    
    PUB Main
      ...
      cognew(@asm1, @sharedVar)
      cognew(@asm2, @sharedVar)
      ...
    
    DAT
    	org	0
    asm1	mov	var1,par
    	...
    	wrlong  var2,var1
    	...
    
    var1	long	0      'don't use RES here
    var2	long	0
    
    
    
    	org	0
    asm2	mov	varA,par
    	...
    	rdlong  varB,varA
    	...
    
    varA	long	0
    varB	long	0
    
    Andy
  • Thank you both,
    That confirms what I was thinking, including that each PASM section has to have its own independent copy of any shared variables.
    How about passing multiple shared variables from the SPIN code? I tried passing the first through PAR and then adding offsets of 4 in the PASM code to find subsequent ones, which compiled OK but didn't seem to work (a WRLONG followed by a RDLONG seems not to return the data that I start with, and which I can monitor on my oscilloscope through MOV OUTA and an R/2R network). I also tried assigning the SPIN location (@) of each variable to a declared PASM variable, with the same result.
    What I find particularly difficult is that there seems to be no way of seeing in the Propeller tool what locations the compiler has assigned to the variables etc., which would be a good sanity check (the hex file display doesn't look easy to navigate), and no steppable simulator.
    On the bright side, I have made real progress with other areas. One simple point that dawned on me yesterday is that for optimal timing there must be 2 + 4N instructions between hub operations, where N≥0.
  • Each PASM section has to independently know the address of any shared variable.

    Here is how I pass multiple addresses: May not be the best but it works every time.
    I have an array of longs. I put the addresses of each shared variable in an entry in the array, ordered in a manner known to both the spin and the specific PASM section. Then I pass the beginning address of the array as the PARM. The PASM section fetches the addresses, incrementing the PARM by four and saves them in registers for subsequent use in WRwhatever and RDwhatevers.
    Don't forget that the syntax for WRwhatevers has the source first, unlike all other instructions.
    Re-inventing the wheel is not a waste of time if, when you are done, you understand why it is round.
    Cool, CA, USA 95614
  • dspred,
    You can get a listing by compiling with bst or homespun.
    Some parts of the pasm code can show an address after you compile with PropTool and then point the mouse to the line/label. It shows the address (in cog) in yellow.
    While these may not be quite what you are looking for, but they may help at some point intime.
    My Prop boards: P8XBlade2, RamBlade, CpuBlade, TriBlade
    Prop OS (also see Sphinx, PropDos, PropCmd, Spinix)
    Website: www.clusos.com
    Prop Tools (Index) , Emulators (Index) , ZiCog (Z80)
  • Thanks for that - I'll go back and try again. I'm semi-retired, the "semi" meaning that I still don't get into my workshop as often as I'd like, so it might be a few days.
    Cluso99: You mentioned some tantalising tools, but I can't find any active links to BST or Homespun.
  • Follow the Tools link in my signature. There you should find links to both bst and homespun, plus other goodies
    My Prop boards: P8XBlade2, RamBlade, CpuBlade, TriBlade
    Prop OS (also see Sphinx, PropDos, PropCmd, Spinix)
    Website: www.clusos.com
    Prop Tools (Index) , Emulators (Index) , ZiCog (Z80)
  • Here is an example of how I pass multiple address to multiple PASM cogs. May not be the best, but it is clear and it works:

    I have an array of longs into which I stick the addresses of the variables the first PASM cog needs to know about. I pass the address of the list of addresses in PARM.
    CON
            _clkmode = xtal1 + pll16x    'Standard clock mode 
            _xinfreq = 5_000_000         '* crystal frequency = 80 MHz
            RedOff = 8, GreenOff = 6, BlueOff = 4, YellOff = 2   'LED pin Offsets
      
    VAR                                       
      long  MyParm[10]                'passing addresses, etc to pasm cogs
      byte  Cog                       'used to display cog numbers
      long  CommoFlag                 'commands to PASMs, Flags from PASM
    'Bits 0..7 are PASMA, 8..15 are PASMB, 16..23 PASMC, 24..31 PASMD
    'for each cog, <>0 says start, =0 says done
      long  CogCPri                   'variable known to spin and PASM C
      long  CogDPri                   'variable known to spin and PASM D  
      long  SharedVar1, SharedVar2     'variables known to all cogs
      long  CogAPri                   'variable known to spin and PASM A
      long  CogBPri                   'variable known to spin and PASM B
      long  Lites                     'which leds to turn on
      long  CTemp                     'build command long here
      long  t2                        'just a temp
    OBJ
      pst      : "parallax serial terminal"
      
    PUB main                                     
      pst.Start (115_200)                        'start up ser terminal
      pst.str(String("hello, world   "))         'runs in cog 1
      pst.NewLine
      waitcnt(clkfreq/10+cnt)
    
      dira[1..0]~~                          'enable two leds
    
      MyParm[0] := @CommoFlag                'pass command/flag address to each cog
      MyParm[1] := @SharedVar1               'address of shared variable
      MyParm[2] := @SharedVar2               'second shared variable
      MyParm[3] := @CogAPri                  'PASM A's unique variable
      MyParm[4] := YellOff                   'Note this is not an address                 
      cog := cognew(@ACog, @MyParm)           'start up the A cog
      pst.str(string("Cog A: "))
      pst.dec(cog)
      pst.newline
      waitcnt(clkfreq/100 + cnt)            'wait for cog to fetch parameters
    

    Now the cog can get those addresses and stash them locally:
    DAT   '***********A PASM cog adds variables******************
    ACog    org   0                  'always, always              '
            mov A_AdPar,Par          'get the address of the parameter list
            rdlong A_AdCFlag, A_AdPar   'command flag byte address
            add A_AdPar, #4             'to next entry in parameter list
            rdlong A_AdSV1, A_AdPar     'address of shared variable 1
            add A_AdPar, #4
            rdlong A_AdSV2, A_AdPar      'address of shared variable 2
            add A_AdPar, #4
            rdlong A_AdLong, A_AdPar     'address of special long
            add A_AdPar, #4
            rdlong A_PinOff, A_AdPar     'pin offset
            mov A_Work, #%11             'will control two adjacent pins
            shl A_Work, A_PinOff         'align mask to my pins
            mov dira, A_Work             'enable my outs               
    A_loop  rdbyte A_work, A_AdCFlag wz   'see if a command active
            if_Z jmp #A_loop              'none
            and A_Work, #$03              'keep just led bits
            shl A_Work, A_PinOff          'align        
            mov outa, A_Work               'lite them up
            rdlong A_Work, A_AdSV1        'fetch operand 1
            rdlong A_Work2, A_AdSV2       'operand 2
            add A_Work, A_Work2           'perform add
            wrlong A_Work, A_AdLong       'store result
            mov A_Work, A_Delay           'look busy          
            add A_Work, cnt
            waitcnt A_Work, 0
            wrbyte A_Zero, A_AdCFlag       'indicate I am done             
            jmp #A_loop        
                
    A_zero       long  0
    A_Delay      long  8_000_000    '1/10 second:  always the longest pole
    A_AdPar      res                 'parameter list address
    A_ADCFlag    res                 'address of this guy's command/flag byte
    A_AdSV1      res                 'address of shared variable 1
    A_AdSV2      res                 'address of shared variable 2                                                     
    A_AdLong     res                 'address of long known only to this pasm (and spin)
    A_Work       res                 'generally useful
    A_Work2      res                 '
    A_PinOff     res                 'pins offset
    A_PinMask    res                 'mask corresponding to pins
    

    Attached is code that starts up 4 PASM cogs, each with variables known to all, and variable(s) known only to itself and the spin program.
    Re-inventing the wheel is not a waste of time if, when you are done, you understand why it is round.
    Cool, CA, USA 95614
  • I've sorted out the problem:

    I have been using PASM declarations of the form <label> long <value> (i.e. one label and one line for each variable), and fell into thinking that each was declaring a named variable. It seemed seemed natural then to leave out the value when no initialisation was necessary - the compiler didn't complain. Yesterday a careful study of the label COG locations revealed that if no value was specified, no space was allocated! Having gone back to the manual and seen the more general <label> long value1, value2, value3, etc. it make sense that a value is required to identify the presence of each variable: the label just points to the start of the allocated space.

    Putting in a value for every variable restored my expected allocation, and the program works as intended.

    Thanks everyone.
  • dspread wrote: »
    I've sorted out the problem:

    I have been using PASM declarations of the form <label> long <value> (i.e. one label and one line for each variable), and fell into thinking that each was declaring a named variable. It seemed seemed natural then to leave out the value when no initialisation was necessary - the compiler didn't complain. Yesterday a careful study of the label COG locations revealed that if no value was specified, no space was allocated! Having gone back to the manual and seen the more general <label> long value1, value2, value3, etc. it make sense that a value is required to identify the presence of each variable: the label just points to the start of the allocated space.

    Putting in a value for every variable restored my expected allocation, and the program works as intended.

    Thanks everyone.

    This is the way you can name (label) the same location more than once. It maybe a block of longs where the block has a name, and the individual long/word/byte/(s) are individually named as well.
    My Prop boards: P8XBlade2, RamBlade, CpuBlade, TriBlade
    Prop OS (also see Sphinx, PropDos, PropCmd, Spinix)
    Website: www.clusos.com
    Prop Tools (Index) , Emulators (Index) , ZiCog (Z80)
Sign In or Register to comment.