Working With CRC (Inline PASM2)
JonnyMac            
            
                Posts: 9,506            
            It was cold in LA today (okay, colder that it should have been), so I stayed indoors and went down a rabbit hole (I will finish those PCB layouts tomorrow). While trying the new getcrc() function in Spin2 I found that it doesn't match my (and others) CRC-8 for 1-Wire, and I often use this in other places. For history, here's where I started:
pub crc8x(p_src, n) : crc | b
  repeat n
    b := byte[p_src++]
    repeat 8
      if (crc ^ b) & 1
        crc := (crc >> 1) ^ $8C
      else
        crc >>= 1
      b >>= 1
This implementation was by Micah Dowty. It's slow in Spin, so I use it in the P1 when speed is not an issue. Thankfully, Cam Thompson (RIP) provided a PASM version of it that is in my P1 1-Wire library, and I extracted once for a project that needed faster CRC calculations than Spin1 could provide (luckily, I had a spare cog).
For the, P2, that method can be run inline using the crcnib instruction. Over time it's become:
pub crc8_maxim(p_src, count) : crc | b
  org
                        rdfast    #0, p_src
.loop                   rfbyte    b
                        rev       b
                        setq      b
                        crcnib    crc, #$8C
                        crcnib    crc, #$8C
                        djnz      count, #.loop
  end
The other CRC that I use is 16-bit for MODBUS. This is the inline version:
pub crc16_modbus(p_src, count) : crc | b
  org
                        mov       crc, ##$FFFF
                        rdfast    #0, p_src
.loop                   rfbyte    b
                        rev       b
                        setq      b
                        crcnib    crc, ##$A001
                        crcnib    crc, ##$A001
                        djnz      count, #.loop
  end
The difference here is initial value and polynomial. This is the rub with Spin2's getcrc(): it guesses your CRC size based on the polynomial and always fills the initial result with 1s. Sadly, it doesn't work with MODBUS either.
This is the an inline version of getcrc() -- it matches the interpreter
pub spin2_getcrc(p_buf, poly, count) : crc | b
  org
                        encod     crc, poly                     ' preset result
                        bmask     crc
                        tjz       count, #.done                 ' exit if count = 0
                        rdfast    #0, p_buf
.loop                   rfbyte    b
                        shl       b, #24
                        setq      b
                        crcnib    crc, poly
                        crcnib    crc, poly
                        djnz      count, #.loop
.done
  end
There's a shl where I think there should be a rev; if you replace that and reflect the polynomial, it will work for MODBUS (but that's not what's in the interpreter).
In the end, I made this generic method that is a bit of a monster and works with all but three of the test cases I pulled down from https://crccalc.com/
pub get_crc(size, poly, startcrc, ri, rr, xr, p_src, count) : crc | b
'' Returns CRC of buffer at p_scr
'' -- size....... CRC type, e.g., 8, 16, 32
'' -- poly....... polynomial for crc calculation
'' -- startcrc... initial crc
'' -- ri......... !0 if input is reflected
'' -- rr......... !0 if result is reflected
'' -- xr......... xor with result
'' -- p_src...... pointer to buffer
'' -- count...... bytes in buffer
  org
                        rev       poly                          ' reflect poly for crcnib
                        rol       poly, size
                        mov       crc, startcrc                 ' get starting value
                        tjnz      rr, #$+3                      ' match result reflection
                        rev       crc
                        rol       crc, size
                        rdfast    #0, p_src                     ' setup buffer read
.loop                   rfbyte    b
                        tjnz      ri, #$+3                      ' input reflection
                        rev       b
                        rol       b, #8
                        rev       b                             ' crc work here
                        setq      b
                        crcnib    crc, poly
                        crcnib    crc, poly
                        djnz      count, #.loop                 ' done?
                        tjnz      rr, #$+3                      ' result reflection
                        rev       crc
                        rol       crc, size
                        xor       crc, xr
  end
The test program is attached. It works in Propeller Tool, FlexProp, and Spin Tools.
PS: I'm sure one of the clever PASM2 gurus will find ways to improve this. Please be kind and share.
Edit: Code as been updated per excellent suggestion from TonyB; new code attached to post #3.


                            
Comments
In this case the following code
rev b rol b, #8 rev bhas the same result as
I've never used P2 CRC's but I think you need
shl b,#24for non-reflected bits orrev bfor reflected bits. Ifriandrrwere combined into a single 2-bit value sayrthenrczr r wczwould set c = r[1] & z = r[0] and you could use conditional instructions instead oftjnzx2.EDIT:
added
wczThanks for the cool tip, Tony!
pub get_crc(size, poly, startcrc, reflect, xr, p_src, count) : crc | b '' Returns CRC of buffer at p_scr '' -- size....... CRC type, e.g., 8, 16, 32 '' -- poly....... polynomial for crc calculation '' -- startcrc... initial crc '' -- reflect.... bit1 for input reflection, bit0 for output reflection '' -- xr......... xor with result '' -- p_src...... pointer to buffer '' -- count...... bytes in buffer org mov crc, startcrc ' get starting value tjz count, #.done rczr reflect wcz ' get reflection bits if_nz rev crc ' match output reflection if_nz rol crc, size rev poly ' reflect poly for crcnib rol poly, size rdfast #0, p_src ' setup buffer read rep @.crcend, count rfbyte b if_nc shl b, #24 if_c rev b setq b crcnib crc, poly crcnib crc, poly .crcend if_nz rev crc if_nz rol crc, size xor crc, xr .done endEDIT: Updated -- again! -- per great tip from TonyB
Really nice work Jon. I'm sure a universal CRC routine like this will come in very handy for people.
EDIT: also same to Chip who I just learned may have created the original...I've not been following the forums a lot lately.
Truth be told, I got a lot of great feedback and help in this thread
-- https://forums.parallax.com/discussion/171293/crc8
When Chip releases a new version of PNut, the updated interpreter source is included, so I was able to learn from it (e.g., I had never used rdfast. While chatting with Chip about a week ago he mentioned wanting to create a general-purpose CRC function so I spent a chilly Saturday indoors experimenting. And then TonyB helped me improve on that. I appreciate the really great programmers in the forums willing to share small -- but often very powerful -- tips with the rest of us.
Jon,
Of course you are one of them!
Jon, if code is in cog/LUT RAM then
repis faster alternative todjnzand could cut CRC loop time by 25%,from this
.loop rfbyte b if_nc shl b, #24 if_c rev b setq b crcnib crc, poly crcnib crc, poly djnz count, #.loop ' done?to this
rep @.crcend, count rfbyte b if_nc shl b, #24 if_c rev b setq b crcnib crc, poly crcnib crc, poly .crcendInterrupts ignored during REP blocks and
countnot changed.Thanks, again, Tony! Updated post #3.