This blog is about implementing an 8 bit CRC on an STM32G071xx micro. Several other ST micros have the same CRC hardware system so it will apply directly to them. There is also some discussion of CRC in general. Pretty much all this stuff is on the we but I have gathered some sources which may be helpful. The novel parts of the blog are the tentative suggestion of a general methodology for implementing CRC support and a C coding suggestion specific to the ST hardware which seems to have eluded several players.
CRC for a definition I can’t do better than Wikipedia:
A cyclic redundancy check is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data. Blocks of data entering these systems get a short check value attached, based on the remainder of a polynomial division of their contents.
I needed a way of implementing error detection in a medium integrity system which can use either UART or I2C comms for device to controller communications. The messages are short - typically a few bytes and the controller is expected to be another small micro running code written in C, maybe under a simple RTOS but not a full blown OS. This is important because it implies that the controller (running code written by other people and about which I have no knowledge and little influence) must implement the CRC calculations as well.
When you start delving into CRCs you very quickly find that there is not just one way of calculating an 8 bit CRC but a huge number of ways (at least 67 million ways). So we need not only to calculate the CRC but calculate the right CRC. In this context is the right 8 bit CRC is the one we specify in an un-ambiguous way so that the device designed (me) and the controller designer end up with the same thing.
The following reference will help you get to this point:
https://reveng.sourceforge.io/crc-catalogue/all.htm
But it lists 19 different 8 bit CRCs.
I found this helpful to pick one:
https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_CRCLibrary.pdf
Section 7.2.1.1 defines 8-bit SAE J1850 CRC, and importantly gives a set of test vectors for it.
To sum up so far, we now have a definition for a traceable definition for an 8-bit CRC cross referenced from two reputable sources and a set of test vectors so we can tell if we have got a good implementation.
To give my controller design a reasonable level of support I need to provide standard C code to implement the CRC, and while I’m at it I shall compare the performance of a pure code solution with using the hardware on the chip I use in the device.
When you start Googling about CRCs and ST processors one of the things you find out quite quickly is that many people have a lot of trouble getting the hardware to do what they want. I have a guess as to why this is and a code suggestion that may be helpful.
I found some useful stuff here:
https://electronics.stackexchange.com/questions/33821/calculating-a-simple-crc
And a useful CRC calculator here:
http://www.sunshine2k.de/coding/javascript/crc/crc_js.html
From the title of the blog, you’ll guess that you need the ST reference document:
This is a nice processor, available in tiny packages as well as more normal sizes. I has up to 128k of flash, 36k of ram, 64MHz core and lots of other good features. And it’s cheap.
One of the features is the hardware CRC engine.
I promised a methodology suggestion for choosing and implementing a CRC:
- Decide if CRC is the right solution
- If not already specified for your application, then pick a suitable one
- Locate a third party set of test vectors
- Decide if you need code for pure software, hardware or both
- Write the code, including a test function
My example code may look a bit weird, especially the proliferation of u postfixes and the number of casts.
The original code was written to be MISRA 2012 compliant (Google it !).
MISRA is a code standard for embedded C and attempts to stop you from falling into C’s many traps for the unwary.
It’s a real pain to use, not because of MISRA 2012 itself, but because checking tools (essential) are either very expensive or not very good. (The cheap tool I have (PC Lint 9) isn’t very nice to use, expensive tools may be better - I don’t know).
I’m also trying to use the “Embedded C Coding Standard” (www.barrgroup.com). I’m not sticking exactly to the Barr document as written – it’s a style guide and I don’t agree with all of it.
I’ve edited some of the really odd things MISRA 2012 and PC Lint require because they do not help casual readers to understand the code.
(Comments re. coding standards and style are welcome.)
So at last we get to the code:
// // FILE: crc.c // COPYRIGHT: // // AUTHOR: MK // DATE: 01/04/2021 // ORIGIN: New // // PROJECT: // HARDWARE: STM32G071Gxx // TOOL CHAIN: KEIL // // INCLUDES #include "main.h" #include "crc.h" // USEFUL REFERENCE DATA // http://www.sunshine2k.de/coding/javascript/crc/crc_js.html // https://electronics.stackexchange.com/questions/33821/calculating-a-simple-crc // https://reveng.sourceforge.io/crc-catalogue/all.htm // https://users.ece.cmu.edu/~koopman/crc/ // https://www.autosar.org/fileadmin/user_upload/standards/classic/4-3/AUTOSAR_SWS_CRCLibrary.pdf // // CRC is SAE J1850 CRC8, test spec from Autosar document page 22 // // definition according to crc-catalogue: // width=8 poly=0x1d init=0xff refin=false refout=false xorout=0xff check=0x4b residue=0xc4 name="CRC-8/SAE-J1850" // // DEFINITIONS #define CRC_POLY (uint8_t)0x1d //#define CRC_HARDWARE static uint8_t crc8_table[256]; // 8-bit table static bool b_table_made = false; #ifndef CRC_HARDWARE void crc_init(void) { uint16_t i; uint16_t j; uint8_t crc; if (!b_table_made) { for (i = 0u; i < 256u; i++) { crc = (uint8_t) i; for (j = 0u; j < 8u; j++) { crc = (crc << 1u) ^ (((crc & 0x80u) != 0u) ? CRC_POLY : 0u); } crc8_table[i] = crc & 0xffu; } b_table_made = true; } } #else void crc_init(void) { SET_BIT(RCC->AHBENR, RCC_AHBENR_CRCEN); delay_us(0); CRC->CR = 0x10u; // set bit operation CRC->INIT = 0xffu; CRC->POL = CRC_POLY; } #endif #ifndef CRC_HARDWARE uint8_t crc8(uint8_t *bytes, uint8_t n_bytes) { uint8_t crc = 0xffu; // initial value while(0u < n_bytes--) { crc = crc8_table[(crc) ^ (*bytes++)]; } return(crc ^ 0xffu); } #else uint8_t crc8(uint8_t *bytes, uint8_t n_bytes) { uint8_t *p_dr8 = CRC; // magic sauce - force 8 bit hardware access to CRC-DR register // illegal cast according to MISRA 12, no MISRA legal way to do it CRC->CR |= 1u; // reset CRC while (0u < n_bytes--) { *p_dr8 = *bytes++; // use the magic sauce } return(((uint8_t)(CRC->DR)) ^ 0xffu); } #endif // test passes if return value is 1, anything else is a fail uint8_t crc8_test(void) { uint8_t testcases[] = {0x04,0x59,0x00,0x00,0x00,0x00, 0x03,0x37,0xf2,0x01,0x83, 0x04,0x79,0x0f,0xaa,0x00,0x55, 0x04,0xb8,0x00,0xff,0x55,0x11, 0x09,0xcb,0x33,0x22,0x55,0xaa,0xbb,0xcc,0xdd,0xee,0xff, 0x03,0x8c,0x92,0x6b,0x55, 0x04,0x74,0xff,0xff,0xff,0xff, 0x09,0xcb,0x33,0x22,0x55,0xaa,0xbb,0xc3,0xdd,0xee,0xff}; // error in this line // each test line, number of test bytes, crc result expected, test bytes uint8_t n_cases = 8u; uint8_t *p_test_data = testcases; uint8_t n_test_bytes; uint8_t ref; uint8_t n_fails = 0u; uint8_t crc; while(n_cases-- > 0u) { n_test_bytes = *p_test_data++; ref = *p_test_data++; crc = crc8(p_test_data, n_test_bytes); if (crc != ref) { n_fails++; } p_test_data += n_test_bytes; } return(n_fails); }
The include files reference the ST library definitions and give external refernces for the crc functions.
The software implementation is based on the code at the electronics.stackexchange.com link.
The hardware and test code are mine, the test using the vectors from the Autosar document.
I shan’t go through the code line by line, just draw your attention to lines 89 and 95.
The ST ref manual says this (section 13.3.3):
The CRC_DR register can be accessed by word, right-aligned half-word and right-aligned byte. For the other registers only 32-bit access is allowed.
You can set the polynomial size in the control register but the paragraph above is the only clue about how to control the data size.
The ARM core (in this chip) can write to peripheral memory in 8 bit, 16 bit and 32 bit widths. This is a hardware thing.
The Keil C compiler will use an 8 bit hardware write if it is writing 8 bit data – this behaviour may not be the case with all C ompilers.
The standard peripheral definitions provided by ST for the processor define CRC as a struct and indirectly CRC->DR as a pointer to 32 bit data at the same location (DR is the first register in the struct).
So we need to force the data to be written as 8 bits, in line 89 *p_dr8 is defined as a pointer to 8 bit data and in line 95 it is used to force an 8 bit write to the CRC data register (CRC->DR).
If you don’t do this but just use CRC->DR = (uint32_t)*bytes++ the compiler does a 32 bit write and the CRC engine does not do what you need.
Finally, how quick is the hardware:
All timing done with 16MHz processor core clock.
The software implementation takes 2.096ms to initialise and 66us to run the test.
The hardware implementation takes 3.55us to initialise and 64.8us to run the test.
The software implementation needs 256 bytes of RAM or FLASH for the table.
If you need to change the CRC (because the application needs more than one type) the hardware is much more efficient in both memory and time.
It’s probably faster with longer data streams and polynomials with more bits but I didn’t test that.
I'm happy to explain anything which isn't clear and engage in discussions about style, MISRA, c coding and almost anything else except politics.
MK
Top Comments