Shop OBEX P1 Docs P2 Docs Learn Events
Learning to use FIELD — Parallax Forums

Learning to use FIELD

JonnyMacJonnyMac Posts: 9,306
edited 2025-05-27 20:17 in PASM2/Spin2 (P2)

When Chip added field to Spin2 I got an earful from people using my jm_fullduplexserial.spin2 object because I was using field as a local variable. After that got sorted I never paid attention to what field was about. @"Stephen Moraco" told me it was pretty neat, but until I asked Chip about it a few weeks ago at the Parallax day I didn't actually give it a try.

For those like me who ignored it, here's the skinny: field lets us create and n-bit array within a block of RAM. For example, S.BUS packets arrive 25 bytes at a time, and bytes 1-22 represent a packed array of sixteen 11-bit values. I struggled at first because I couldn't get my 25-byte array to translate properly. Then I read the details in the docs and found this comment.

Given the 11-bit size of S.BUS values I was forced to copy my byte array into an array of longs. Then it all worked. Here's the original method with a stacked loop for unpacking the values

pub decode_sbus(p_sbus, p_chans) | ch, work, bc, flags          ' ~107us @ 200MHz

'' Convert S.BUS array (of bytes) to channel values (words)
'' -- p_sbus is [byte] pointer to S.BUS data
'' -- p_chans is [word] pointer to channel values 

  if (byte[p_sbus][0] <> $0F)                                   ' check header
    return

  if (byte[p_sbus][24] <> $00)                                  ' check footer
    return

  p_sbus += 1                                                   ' point to data byte #1

  longfill(@ch, 0, 3)                                           ' clear locals

  repeat while (ch <= 15)                                       ' Ch 1-16 decoding
    repeat while (bc < 11)                                      ' collect enough bits for channel
      work |= (byte[p_sbus++] << bc)                            ' get byte, position, add to work
      bc += 8                                                   ' bump bit count
    word[p_chans][ch++] := work & $07FF                         ' extract and save channel
    work >>= 11                                                 ' remove channel, re-align lsb
    bc -= 11                                                    ' fix bit count

  flags := byte[p_sbus]                                         ' flags byte follows channels

  word[p_chans][16] := (flags & %0001) ? $07FF : 0              ' convert ch17 bit
  word[p_chans][17] := (flags & %0010) ? $07FF : 0              ' convert ch18 bit

And here's the update using field which runs about twice as fast as the original.

pub decode_sbus_v2(p_sbus, p_chans) | sb[6], pf, ch, flags      ' ~49us @ 200MHz

'' Convert S.BUS array (of bytes) to channel values (words)
'' -- p_sbus is [byte] pointer to S.BUS data
'' -- p_chans is [word] pointer to channel values

  if (byte[p_sbus][0] <> $0F)                                   ' check header
    return

  if (byte[p_sbus][24] <> $00)                                  ' check footer
    return

  bytemove(@sb, p_sbus+1, 22)                                   ' leave out header, flags, footer

  pf := ^@sb.[10..0]                                            ' set base field pointer

  repeat ch from 0 to 15                                        ' Ch 1-16 decoding  
    word[p_chans][ch] := field[pf][ch]

  flags := byte[p_sbus][23]

  word[p_chans][16] := (flags & %0001) ? $07FF : 0              ' convert ch17 bit
  word[p_chans][17] := (flags & %0010) ? $07FF : 0              ' convert ch18 bit

As you can see, the second version is a bit tidier and runs faster.

With P2 smart pins I could write a cogless version of my S.BUS interface and will do that later that would be fine for projects that don't have a screaming fast process loop. Having started with the BASIC Stamp 1 I am very stingy about resources, so I've come up with cogless drivers where I have more time in my process loop than I have cogs that I can spare.

Comments

  • nice insights, thanks for sharing.

  • RaymanRayman Posts: 15,224

    Field looks powerful, but the documentation is pretty spartan....
    Maybe someone can use AI one day to make better documentation?

    Don't fully understand it. Can see how it can work within a single long/word/byte.
    But appears @JonnyMac is able to span many bytes somehow...
    Don't think this is documented?

    I tried variation of the example in docs and it doesn't work. No idea why not...

    CON _clkfreq = 10_000_000
    
    var
    word k2[2]
    
    PUB go() | p, k, i
    
      p := ^@k2.[2..0]        'get a pointer to the three lowest bits of k
    
      repeat 10
        field[p][i++]~~      'set three bits at a time, progressing upwards
        debug(ubin_long(k2))
    
  • JonnyMacJonnyMac Posts: 9,306
    edited 2025-05-29 02:07

    I think the problem has to do with debug; you're pointing to a word (k2) but telling it to print as 32 bits. Internally, the upper 32 bits will be set to 0.

    I poked around the debug docs and found a mechanism that will show you what you wanted to see -- and it proves the field feature is working.

  • RaymanRayman Posts: 15,224
    edited 2025-05-29 12:29

    Interesting... Was wondering if it work with bits offset from LSB, seems it does:

    CON _clkfreq = 10_000_000

    var
    word k2[2]
    
    PUB go() | p, k, i
    
      p := ^@k2.[3..1]        'get a pointer to the three nearly lowest bits of k, starting at bit #1
    
      repeat 10
        field[p][i++]~~      'set three bits at a time, progressing upwards
        debug(ubin_long_array(@k2,1))
    
Sign In or Register to comment.