Shop OBEX P1 Docs P2 Docs Learn Events
Using P2 Spin to create an array of text strings — Parallax Forums

Using P2 Spin to create an array of text strings

Using P2 Spin, I have a buffer defined in a var as a long, outbuf[400] that contains a long string. I would like to store up to 6 of outbuf[400] values referenced via another array, movebuf[6]. I haven’t run across any obvious examples in my search for code. My initial test of just storing the strings into movebuf didn’t work out although my code could be at fault (it was getting late and I was tired!), thought I’d ask the coding experts for ideas before I hit it again today.

Bob

Comments

  • Duane DegnDuane Degn Posts: 10,588
    edited 2022-02-15 20:37

    I'm sure there are better ways of doing this but this is one way I'm using in my current project.

    I have a method which will let me find a specific text string in a list.

    DAT negativeNumberText  byte "NEGATIVE INDEX", 0
    PUB FindString(firstStr, stringIndex) : result
    '' Finds start address of one string in a list
    '' of string. "firstStr" is the address of
    '' string #0 in the list. "stringIndex"
    '' indicates which of the strings in the list
    '' the method is to find.
    
      if stringIndex < 0
        result := @negativeNumberText
        return
      result := firstStr
      repeat while stringIndex
        repeat while byte[result++]
        stringIndex--
    

    I use the above method to fill an array with the start of each of the strings.

    PUB FillStringPointers(indexLocation, firstString, numberOfStrings) | localIndex, maxIndex
    
      maxIndex := numberOfStrings - 1
    
      repeat localIndex from 0 to maxIndex
        long[indexLocation][localIndex] := FindString(firstString, localIndex)
    

    Here's an example call to FillStringPointers().

      FillStringPointers(@zMenuIndex, @zMenuLine, Z_MENU_SIZE_9) 
    

    Here's the data used in the above.

    DAT
    
    zMenuIndex              long 0-0[Z_MENU_SIZE_9]
    
    zMenuLine               byte "User Flow", 0
                            byte "Raw Flow", 0
                            byte "Set Units", 0
                            byte "Time/Date", 0
                            byte "Serial #", 0
                            byte "*Sub Flow", 0
                            byte "Circles", 0
                            byte "Rectangles", 0
                            byte "*Other", 0
    

    After FillStringPointers() is called, the array zMenuIndex will contain the address of each string listed under zMenuLine.

    The address of each string could have been entered into the zMenuIndex but this would add an extra element to the program I didn't want to when writing the above code.

    A text string could be accessed using:

      Serial Str(zMenuIndex[2])
    

    The above command would send "Set Units" to the UART.

    Edit: In case it's not obvious, here's the constant defined.

    CON
    
      Z_MENU_SIZE_9 = 9
    
  • An alternative technique of storing text arrays is to use the same number of bytes for each string. This will end up having wasted space since there will be a lot of empty bytes.

    Here's an example of this technique.

    DAT 'Flow Units Text
    
    fUnitsT1264B0           byte "CUSTOM", 0[CHAR_PER_UNIT_12 - 6]
    {1270}                  byte "GR/", 0[CHAR_PER_UNIT_12 - 3]
    {1276}                  byte "KG/", 0[CHAR_PER_UNIT_12 - 3]
    {1282}                  byte "LB/", 0[CHAR_PER_UNIT_12 - 3]
    {1288}                  byte "MMSCF", 0[CHAR_PER_UNIT_12 - 5]
    {1294}                  byte "mSCF", 0[CHAR_PER_UNIT_12 - 4]                  '5
    DAT nccUnits1300        byte "NCC", 0[CHAR_PER_UNIT_12 - 3]
    {1306}                  byte "NLP", 0[CHAR_PER_UNIT_12 - 3]
    {1312}                  byte "NM3/", 0[CHAR_PER_UNIT_12 - 4]
    {1318}                  byte "NMLP", 0[CHAR_PER_UNIT_12 - 4]
    {1324}                  byte "SCC", 0[CHAR_PER_UNIT_12 - 3]                   '10
    {1330}                  byte "SCF", 0[CHAR_PER_UNIT_12 - 3]
    {1336}                  byte "SCI", 0[CHAR_PER_UNIT_12 - 3]
    {1342}                  byte "SCM", 0[CHAR_PER_UNIT_12 - 3]
    {1348}                  byte "SFP", 0[CHAR_PER_UNIT_12 - 3]
    {1354}                  byte "SLP", 0[CHAR_PER_UNIT_12 - 3]                   '15
    {1360}                  byte "SM3/", 0[CHAR_PER_UNIT_12 - 4]
    {1366}                  byte "SMLP", 0[CHAR_PER_UNIT_12 - 4]
    {1372}                  byte "SMP", 0[CHAR_PER_UNIT_12 - 3]
    {1378}                  byte "TP", 0[CHAR_PER_UNIT_12 - 2]
    extraUnitText1384B0     byte "EXTRA0", 0[CHAR_PER_UNIT_12 - 6]
    {1390}                  byte "EXTRA1", 0[CHAR_PER_UNIT_12 - 6]
    {1396}                  byte "EXTRA2", 0[CHAR_PER_UNIT_12 - 10] '6 + 4 = 10
    DAT reg1400             byte 0[4]  'Part of group above
    {1402}                  byte "EXTRA3", 0[CHAR_PER_UNIT_12 - 6]
    {1408}                  byte "EXTRA4", 0[CHAR_PER_UNIT_12 - 6]
    {1414}                  byte "EXTRA5", 0[CHAR_PER_UNIT_12 - 6]
    {1420}                  byte "EXTRA6", 0[CHAR_PER_UNIT_12 - 6]
    {1426}                  byte "EXTRA7", 0[CHAR_PER_UNIT_12 - 6]
    {1432}                  byte "EXTRA8", 0[CHAR_PER_UNIT_12 - 6]
    {1438}                  byte "EXTRA9", 0[CHAR_PER_UNIT_12 - 6]
    {1444}                  byte "EXTRAa", 0[CHAR_PER_UNIT_12 - 6]
    {1450}                  byte "EXTRAb", 0[CHAR_PER_UNIT_12 - 6]
    

    Here the address of a specific string can be found by multiplying by the largest string allowed.

    For example:

      Serial.Str(@fUnitsT1264B0 + (2 * CHAR_PER_UNIT_12))
    

    The above command would send the text "KG/" to the UART.

    Using the fixed length technique require more RAM but has the benefit of allowing the text to be altered by the program. The altered text just as to fix in the limited area. This is 11 bytes in the example above. You need to leave a terminating zero.

  • Thats an interesting approach to the problem, I need to walk through your code and make sure I understand what you are doing.
    My case is a bit different as it appears you are using pre-defined strings in the DAT, my output strings are constantly changing and created on the fly. This has given me a couple of ideas to try out too!
    This is for my hexapod where I create a string of number’s separated by commas on the fly that is then used by the corresponding hexapod leg for movement. Right now I create one movement string and the string sequence is sent off to its leg for action. Then it starts creating the movement string for the next leg. As each string is created via a very math intensive process, and I need to create 6 separate strings, I thought about doing all the math for each leg upfront, creating an array to temporarily contain the movement strings. After all 6 strings are created I could quickly index through the array sending the strings out more quickly rather than having the lull between string creation due to the math.

  • JonnyMacJonnyMac Posts: 9,159
    edited 2022-02-16 05:21

    Maybe I'm looking at things too simplistically, but I would start here.

    con
    
      BUF_SIZE = 400
    
    
    var
    
      byte  move0[BUF_SIZE]
      byte  move1[BUF_SIZE]
      byte  move2[BUF_SIZE]
      byte  move3[BUF_SIZE]
      byte  move4[BUF_SIZE]
      byte  move5[BUF_SIZE]
    
      byte  bufidx
    

    Now you can create a few help routines that simplify your coding interface.

    pub buf_pntr(idx) : p_buf
    
      p_buf := @move0 + (idx * BUF_SIZE)
    
    
    pub clear_move(idx)
    
      bytefill(buf_pntr(idx), 0, BUF_SIZE)
    
    
    pub build_move(idx) | p_buf
    
      p_buf := buf_pntr(idx)
    
      ' build string starting at p_buf
    
    
    pub send_move(idx) : nextidx
    
      serial.str(buf_pntr(idx))
    
      if (++idx == 6)
        nextidx := 0
      else
        nextidx := idx 
    

    Am I out of whack?

  • @DiverBob said:
    This is for my hexapod where I create a string of number’s separated by commas on the fly that is then used by the corresponding hexapod leg for movement. Right now I create one movement string and the string sequence is sent off to its leg for action. Then it starts creating the movement string for the next leg. As each string is created via a very math intensive process, and I need to create 6 separate strings, I thought about doing all the math for each leg upfront, creating an array to temporarily contain the movement strings. After all 6 strings are created I could quickly index through the array sending the strings out more quickly rather than having the lull between string creation due to the math.

    Are the values being sent integers? If so, saving the values to send as strings seems like an extra (and unnecessary) complication.
    I'd save the values you want to send as integers and then create the strings on the fly with "Serial.Dec()" type methods.

    Jon's strategy is pretty much the same as my second strategy but Jon's is a lot easier to read. I just copied a section from a project I'm currently working on. Jon was extra nice and wrote easy to read code.

  • @"Duane Degn" said:
    Are the values being sent integers? If so, saving the values to send as strings seems like an extra (and unnecessary) complication.
    I'd save the values you want to send as integers and then create the strings on the fly with "Serial.Dec()" type methods.

    Jon's strategy is pretty much the same as my second strategy but Jon's is a lot easier to read. I just copied a section from a project I'm currently working on. Jon was extra nice and wrote easy to read code.

    Jon's code is always easy to read! The string consists of a series of integer like this: "$2,900,0,900,1" where the "$" is the start character followed by the robot leg the string affects, the commas separate integers representing angle data to the robot leg controllers. The string is built on the fly based on the desired movement. Duane, I like the code example you sent as it gave me other ideas to try out also.

  • JonnyMacJonnyMac Posts: 9,159
    edited 2022-02-18 22:03

    Thanks, guys, for the kind words about my coding style. To me, writing code appeals to my technical and artistic sides.

    Seeing the construction of Bob's string reminded me about a project I did for the P1 several years ago called jm_hfcp.spin (human friendly control protocol). Like Bob's strings, it uses plain text to send commands and values from point A to point B (and back if needed). Over coffee I thought I'd explore porting that to the P2, and see if Bob would share how he's building his strings. Keeping it simple, I made these to methods.

    pub putc(p_str, c) : p_next
    
      byte[p_str++] := c
    
      return p_str
    
    
    pub putdec(p_str, value) : p_next | zflag, d, c
    
    '' Put value as decimal into string at p_str
    '' -- returns pointer to next character in p_str
    '' -- working range is NEGX+1..POSX
    
      if (value == 0)
        byte[p_str++] := "0"
        return p_str                                                ' we can exit early
    
      elseif (value < 0)
        byte[p_str++] := "-"                                        ' add sign to string
        abs= value                                                  ' remove sign from value
    
      zflag, d := false, 1_000_000_000                              ' no zeros, initial divisor
    
      repeat 10
        if (value >= d)                                             ' digit for this position?
          c := value / d                                            ' extract
          byte[p_str++] := c + "0"                                  ' convert to ASCII and write
          value //= d                                               ' remove digit from value
          zflag := true                                             ' we can print 0s now
        elseif (zflag || (d == 1))                                  ' if 0s okay or last digit
          byte[p_str++] := "0"                                      '  write the zero
        d /= 10                                                     ' update divisor
    
      p_next := p_str                                               ' return next 
    

    I tested with this:

      p_cmd := @cmdbuf
    
      p_cmd := putc(p_cmd, "$")
      p_cmd := putc(p_cmd, ",")
      p_cmd := putdec(p_cmd, 2)
      p_cmd := putc(p_cmd, ",")
      p_cmd := putdec(p_cmd, 900)
      p_cmd := putc(p_cmd, ",")
      p_cmd := putdec(p_cmd, 0)
      p_cmd := putc(p_cmd, ",")
      p_cmd := putdec(p_cmd, 900)
      p_cmd := putc(p_cmd, ",")
      p_cmd := putdec(p_cmd, 1)
    

    ...which produced the string Bob described in his comment. Since the P2 makes inline assembly possible, I thought I'd convert putdec() to see if there's a benefit.

    pub putdec2(p_str, value) : p_next
    
    '' Put value as decimal into string at p_str
    '' -- returns pointer to next character in p_str
    '' -- working range is NEGX+1..POSX
    
      org
    .check0         tjnz      value, #.checkneg
                    wrbyte    #"0", p_str                           ' value is 0
                    add       p_str, #1                      
                    jmp       #.done                                ' early exit
    
    .checkneg       cmps      value, #0                     wcz      
        if_a        jmp       #.dodec                                
                    wrbyte    #"-", p_str                           ' write sign to string
                    add       p_str, #1                              
                    abs       value                                 ' remove sign from value
    
    .dodec          mov       pr0, #0                               ' zflag is false
                    mov       pr1, ##1_000_000_000                  ' set divisor
                    mov       pr2, #10                              ' up to 10 digits
    
    .dloop          cmp       value, pr1                    wcz
        if_b        jmp       #.dzlast
    
                    qdiv      value, pr1                            ' divide
                    getqx     pr3                                   ' get digit
                    getqy     value                                 ' save remainder
                    add       pr3, #"0"                             ' convert to ASCII
                    wrbyte    pr3, p_str                             
                    add       p_str, #1                              
                    mov       pr0, #1                               ' can print 0s now
                    jmp       #.dnext                                
    
    .dzlast         tjnz      pr0, #.is0                            ' is zflag set?
                    cmp       pr1, #1                       wcz     ' or last digit?
      if_ne         jmp       #.dnext
    
    .is0            wrbyte    #"0", p_str
                    add       p_str, #1 
    
    .dnext          qdiv      pr1, #10
                    getqx     pr1
                    djnz      pr2, #.dloop
    
    .done           mov       p_next, p_str
      end               
    

    The inline assembly version will trim 30%-50% of the time required to convert a non-zero value, so it might be worth using. I did it an an exercise. Note, though, the it won't work with NEGX -- to me this is an unlikely value in a control program, so I didn't burden the conversion code with handling it.

  • JonnyMacJonnyMac Posts: 9,159
    edited 2022-02-18 22:57

    In acting, take 1 is usually a warm-up, and things get better with take 2. After a few hours of day-job work, I took a lunch break and went back to my jm_hfcp code. The dec() method in that object does correctly deal with negx so I ported it to the P2, adding early exit for 0.

    pub dec(p_buf, value) : p_next | x, d, zf 
    
    '' Put [signed] decimal representation of value into p_buf
    
      if (value == 0)
        byte[p_buf++] := "0"
        return p_buf    
    
      x := value == negx                           
      if (value < 0)                                
        value := abs(value+x)                       
        byte[p_buf++] := "-"                                    
    
      d, zf := 1_000_000_000, false                           
    
      repeat 10                                    
        if (value >= d)                              
          byte[p_buf++] := value / d + "0" + x*(d == 1)         
          value //= d                              
          zf := true                              
        elseif (zf || (d == 1))                  
          byte[p_buf++] := "0"                                  
        d /= 10
    
      p_next := p_buf
    

    It's a bit slower than the code above for having to deal with the negx fix-it flag. I ported it t inline PASM and it's a big speed boost over the Spin2 version. This is what I'll have in my P2 version of HFCP. This early exits on 0, and handles negx correctly.

    pub dec2(p_buf, value) : p_next | x, n, d, zf, ch
    
    '' Put [signed] decimal representation of value into p_buf
    
      org
    .check0         tjnz      value, #.checkneg
                    wrbyte    #"0", p_buf                           ' value is 0
                    add       p_buf, #1                         
                    jmp       #.done  
    
    .checkneg       mov       x, #0
                    cmps      value, ##negx                 wcz     ' if negx
        if_e        mov       x, #1                                 '  set flag
    
                    cmps      value, #0                     wcz     ' if negative
        if_ae       jmp       #.dodec
    
                    adds      value, x                              ' adjust if negx
                    abs       value                                 ' make positive
                    wrbyte    #"-", p_buf                           ' write sign
                    add       p_buf, #1
    
    .dodec          mov       n, #10                                ' up to 10 digits in long
                    mov       d, ##1_000_000_000                    ' set divisor
                    mov       zf, #0                                ' clear print 0 flag          
    
    .loop           cmp       value, d                      wcz     ' >0 digit at this position?
        if_b        jmp       #.dzlast
                    qdiv      value, d                              ' extract digit
                    getqx     ch
                    getqy     value                                 ' save remainder
                    add       ch, #"0"                              ' convert to ASCII
                    cmp       d, #1                         wcz     ' if last digit
        if_e        add       ch, #1                                ' fix negx
                    wrbyte    ch, p_buf                             ' write sign
                    add       p_buf, #1
                    mov       zf, #1
                    jmp       #.next
    
    .dzlast         tjnz      zf, #.is0                             ' is zflag set?
                    cmp       d, #1                         wcz     ' or last digit?
      if_ne         jmp       #.next
    
    .is0            wrbyte    #"0", p_buf                           ' add "0" to buffer
                    add       p_buf, #1            
    
    .next           qdiv      d, #10                                ' update divisor
                    getqx     d
                    djnz      n, #.loop                             ' update digit count
    
    .done           mov       p_next, p_buf
      end
    
  • @JonnyMac said:
    Thanks, guys, for the kind words about my coding style. To me, writing code appeals to my technical and artistic sides.

    Seeing the construction of Bob's string reminded me about a project I did for the P1 several years ago called jm_hfcp.spin (human friendly control protocol). Like Bob's strings, it uses plain text to send commands and values from point A to point B (and back if needed). Over coffee I thought I'd explore porting that to the P2, and see if Bob would share how he's building his strings. Keeping it simple, I made these to methods.

    Here is the method I'm using for creating my strings. Based on the notes, you made the suggestions that went into creating this on the P1 a while back!

    pub buildstring(legnum, femur1, tibia1, coxa1, LDown)
    ' output buffer for leg controller movement commands - based on JonnyMac code suggestions/example
      bytefill(@outbuf, 0, 128)
      strAppend(@outbuf, string("$,"))
      strAppend(@outbuf, dec(legnum))
      strAppend(@outbuf, string(","))
      strAppend(@outbuf, dec(femur1))
      strAppend(@outbuf, string(","))
      strAppend(@outbuf, dec(tibia1))
      strAppend(@outbuf, string(","))
      strAppend(@outbuf, dec(coxa1))
      strAppend(@outbuf, string(","))
      strAppend(@outbuf, dec(lDown))
      strAppend(@outbuf, string(13))
    
    
    pub strAppend(buf, s)| n1, n2
      n1 := strsize(buf)
      n2 := strsize(s) + 1    'trailing 0
      buf += n1
      bytemove(buf, s, n2)
    

    Thanks for the additional suggestions, I like your newest string builder, especially the PASM version. I can use the 2 versions to help me in learning PASM which is a task I've been putting off for a while. I especially need to convert the math heavy Spin code over to in-line PASM as it could really use the speed increase.
    This gives me something to study on the plane tomorrow as my wife and I head out of this land of ice and snow to much warmer climates for a break!
    Thank you Jon for all your input, I have quite a bit of code inspired or written by you in my robot!

    Bob

  • JonnyMac, your example code for putting strings into an array got me thinking and searching the Spin commands, so I came up with this as another possible solution. The debug output shows the expected values at the correct locations.

    pub testStringArray() | n
    ' test ability to store and address multiple strings in an array
      repeat n from 1 to 6
        'create test string
        strAppend(@outbuf, string("$,"))
        strAppend(@outbuf, dec(n))                                                  'dec() comes from simple_numbers.spin by Chip Gracey as converted to Spin2
        strAppend(@outbuf, string(",900 "))
    
        'copy test string into movebuf1 at location based on n and buffer size
        bytemove(@movebuf1 + (n * OUTBUF_SIZE), @outbuf, OUTBUF_SIZE)
        'clear the test string from outbuf
        bytefill(@outbuf, 0, OUTBUF_SIZE)
    
        'verify test strings are being saved correctly
        debug(zstr(@movebuf1 + (n * OUTBUF_SIZE)))
      ' final debug used to verify the program hasn't hung up!
      debug("Done!")
    
  • JonnyMacJonnyMac Posts: 9,159
    edited 2022-03-05 13:28

    You indicated speed was so I was thinking that having each of the routines return pointer to the next available character in the buffer might help versus always having to use strsize() to find the length of the buffer. The code you showed might be changed to this:

      bytefill(@outbuf, 0, BUF_SIZE)
      p_buf := @outbuf
      p_buf := puts(p_buf, string("$,"))
      p_buf := putd(p_buf, lenNum)
      p_buf := putc(p_buf, COMMA)
      p_buf := putd(p_buf, femur1)
      p_buf := putc(p_buf, COMMA)
      p_buf := putd(p_buf, tibia1)
      p_buf := putc(p_buf, COMMA)
      p_buf := putd(p_buf, coxa1)
      p_buf := putc(p_buf, COMMA)
      p_buf := putd(p_buf, lDown)
      p_buf := putc(p_buf, CR)
    

    ...using these methods

    pub putc(p_buf, c) : p_next
    
    '' Put character c into p_buf
    '' -- returns pointer to next character in p_buf 
    
      byte[p_buf++] := c
    
      return p_buf
    
    
    pub puts(p_buf, p_str) : p_next | len
    
    '' Put string p_str into p_buf
    '' -- returns pointer to next character in p_buf 
    
      len := strsize(p_str)
      bytemove(p_buf, p_str, len)
    
      return p_buf + len
    
    
    pub putd(p_buf, value) : p_next | x, n, d, zf, ch
    
    '' Put value as decimal string into p_buf
    '' -- returns pointer to next character in p_buf
    
      org
    .check0         tjnz      value, #.checkneg
                    wrbyte    #"0", p_buf                           ' value is 0
                    add       p_buf, #1                         
                    jmp       #.done  
    
    .checkneg       mov       x, #0
                    cmps      value, ##negx                 wcz     ' if negx
        if_e        mov       x, #1                                 '  set flag
    
                    cmps      value, #0                     wcz     ' if negative
        if_ae       jmp       #.dodec
    
                    adds      value, x                              ' adjust if negx
                    abs       value                                 ' make positive
                    wrbyte    #"-", p_buf                           ' write sign
                    add       p_buf, #1
    
    .dodec          mov       n, #10                                ' up to 10 digits in long
                    mov       d, ##1_000_000_000                    ' set divisor
                    mov       zf, #0                                ' clear print 0 flag          
    
    .loop           cmp       value, d                      wcz     ' >0 digit at this position?
        if_b        jmp       #.dzlast
                    qdiv      value, d                              ' extract digit
                    getqx     ch
                    getqy     value                                 ' save remainder
                    add       ch, #"0"                              ' convert to ASCII
                    cmp       d, #1                         wcz     ' if last digit
        if_e        add       ch, #1                                ' fix negx
                    wrbyte    ch, p_buf                             ' write sign
                    add       p_buf, #1
                    mov       zf, #1
                    jmp       #.next
    
    .dzlast         tjnz      zf, #.is0                             ' is zflag set?
                    cmp       d, #1                         wcz     ' or last digit?
      if_ne         jmp       #.next
    
    .is0            wrbyte    #"0", p_buf                           ' add "0" to buffer
                    add       p_buf, #1            
    
    .next           qdiv      d, #10                                ' update divisor
                    getqx     d
                    djnz      n, #.loop                             ' update digit count
    
    .done           mov       p_next, p_buf
      end
    

    I can use the 2 versions to help me in learning PASM which is a task I've been putting off for a while. I especially need to convert the math heavy Spin code over to in-line PASM as it could really use the speed increase.

    I love having inline PASM to experiment with, and to add speed updates in many places. That said, there are elements of the Spin2 interpreter that are amazingly efficient, so it's always best to do an emperical test. I do it with this simple code.

      t := getct()
      { test code here }
      t := getct() - t - 40
      term.dec(t)
    

    Sometimes this test will reveal that the manual PASM isn't worth the effort, but the only way to know is to write the code and test.

  • JonnyMacJonnyMac Posts: 9,159
    edited 2022-02-21 04:07

    Decided to try a couple more number-to-string conversions in PASM2 while watching the Daytona 500. These produce fixed-width strings.

    pub ibin(value, digits, p_dest) : p_next
    
    '' Put [indicated] binary representation of value into buffer @p_dest
    
      org
                    cmps      digits, #1                    wcz     ' must be >0
        if_b        jmp       #.done
    
                    fle       digits, #32                           ' limit to 32
    
                    wrbyte    #"%", p_dest                          ' put indicator into buffer
                    add       p_dest, #1
    
                    ror       value, digits                         ' move bits to output to msb end
    
    .binout         rol       value, #1                             ' get a digit (bit)
                    mov       pr0, value                            ' copy
                    and       pr0, #1                               ' mask off other digits
                    add       pr0, #"0"                             ' convert to ASCII
                    wrbyte    pr0, p_dest                           ' write to destination
                    add       p_dest, #1    
                    djnz      digits, #.binout                      ' finished?
    
    .done           mov       p_next, p_dest                        ' return next char in target
      end
    
    
    pub ihex(value, digits, p_dest) : p_next
    
    '' Put [indicated] hex representation of value into buffer @p_dest
    
      org
                    cmps      digits, #1                    wcz     ' must be >0  
        if_b        jmp       #.done
    
                    fle       digits, #8                            ' limit to 8
    
                    wrbyte    #"$", p_dest                          ' put indicator into buffer
                    add       p_dest, #1
    
                    mov       pr0, digits                           ' copy digits
                    shl       pr0, #2                               ' convert digits to bits
                    ror       value, pr0                            ' align bits to output with msb
    
    .hexout         rol       value, #4                             ' get a digit (nibble)
                    mov       pr0, value                            ' copy
                    and       pr0, #$F                              ' mask off other digits
                    cmp       pr0, #10                      wcz     ' check for 0..9
        if_b        add       pr0, #"0"                             ' make "0".."9"
        if_ae       add       pr0, #("A"-10)                        ' make "A".."F"
                    wrbyte    pr0, p_dest                           ' write to destination     
                    add       p_dest, #1    
                    djnz      digits, #.hexout                      ' finished?   
    
    .done           mov       p_next, p_dest                        ' return next char in target 
      end
    
  • Thanks Jon for all the input. Unfortunately I’m well away from my stuff in snowy Michigan and enjoying the warm sands of Maui for a bit before I can get back to my computer and try these out. I appreciate all you have done to help me and others out over the years.

Sign In or Register to comment.