Working With CRC (Inline PASM2)
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
has the same result as
I've never used P2 CRC's but I think you need
shl b,#24
for non-reflected bits orrev b
for reflected bits. Ifri
andrr
were combined into a single 2-bit value sayr
thenrczr r wcz
would set c = r[1] & z = r[0] and you could use conditional instructions instead oftjnz
x2.EDIT:
added
wcz
Thanks for the cool tip, Tony!
EDIT: 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
rep
is faster alternative todjnz
and could cut CRC loop time by 25%,from this
to this
Interrupts ignored during REP blocks and
count
not changed.Thanks, again, Tony! Updated post #3.