STM32 CRC for data validation
Suppose you
are sending data packets to a critical device, the packets contain information
instructing the device to continue operating, or to blow up in your face. Now imagine
the data gets corrupted, by way of EMI or any form of interference. Now it is
possible that the corruption has modified the command so that the device blows up
in your face. It would be convenient if there was a way to prevent data
corruption all together. I am not sure how one would prevent data corruption,
but I know there are methods to verify whether the data has been corrupted or
not, one such method is Cyclic Redundancy Check (CRC).
Implementing
a CRC in software is not that hard. Using the images as reference below it is
possible to implement the algorithm in plain C code. Though I will not do that
here because this post is about the fact that STM32 microcontrollers (F0, F1,
F2, F3, F4, L1) provide a hardware CRC engine that accomplishes the same task
thousands of times faster then doing it in code. The snippet below from the linked application note shows an impressive hardware advantage versus doing a CRC algorithm in code.
CRC flow chart and algorithm
So, how does
it work and how do we use it? It turns out to be one of the easiest peripherals
on the microcontroller with the fewest registers. The idea behind the CRC
algorithm is this:
- Start with an initial CRC value, in the image above it is 0xFF
- Then XOR it with your data
- If the result of step 2 has a most significant bit of 0 then you simply left shift that number by 1
- If the result from step 2 has a 1 in the MSB then you also left shift by 1 but also XOR it with a chosen polynomial
- While doing these steps you must also keep track of your “bind index” which is nothing more than a counter to keep track of how many times you have shifted. This counter must be equal in magnitude to the number of bits in your data. The example above goes from 0 to 7, in other words 8 iterations for an 8 bit number/data. The Point is to shift through every bit
- The polynomial you chose cannot change during the process. As you can see above the poly is 0xCB and any time we XOR with a poly its always 0xCB
Ultimately
you will end up with a final value and this value is your check sum or CRC
value. Notice that if you keep all parameters the same (initial_crc,
input_data, poly) you will always get the same return value. This is fine, because this is not encryption we are doing, it is error checking. One more thing to note is that CRC is also used to verify data written to memory. For example, if you are saving to an SD card or other device and you want to verify the data written, run a CRC on the original and the copy to see if they match up. So that is
the basic idea of how it works but how do you use it to your benefit?
Usage Example:
The scenario
is this: You have two devices one is a receiver we will call RX and one is a
transmitter we will call TX. The TX wants to send data to the RX but also wants
to make sure that the data sent is ignored if it is corrupted. So the TX will
run the data it wants to send through a CRC algorithm or engine. It will then
have a CRC code that will be unique to that data. So TX sends its data along
with the CRC code to the RX.
Now RX
receives the data and CRC code. RX will run the data through the same CRC
algorithm/engine. RX will now have calculated its own CRC code. RX now will
compare the CRC it received with the one it calculated and if they match then
the data is not corrupted. If the CRC codes do not match then something is
corrupted and thus we know that the packet received is no good and must be ignored
or handled in some way, perhaps asking for a resend from TX. For this to work,
TX and RX must both use the exact same algorithm for error checking, so all the
values must match (initial_crc, input_data, poly). Note that if the CRC codes
do not match this could mean the data was corrupt or maybe the CRC code sent
was corrupt, we really don’t know but it does not change the fact that you have corrupt data.
STM32F1 and
STM32L0 are the only ones I will cover because those are the only ones I have
on hand and thus can generate working code for. I encourage you to read your reference
manual or even this application note by STM from which I stole the above
images.
STM32L0 CRC
The CRC peripheral
in the L0 series contains a total of 5
registers described below.
- CRC_DR : The CRC data register. When writing to the data register you are giving the CRC engine the input data on which it will calculate the CRC. When you read from the data register you will get the result from that calculation
- CRC_IDR : The CRC idr stores data it really has absolutely no purpose in the CRC calculation, the manual says you can perhaps use it to store a byte of data, but again it has nothing to do with CRC…crazy.
- CRC_CR : The CRC control register lets us configure the CRC to reverse the input data or the output data. Allows you to select the polynomial size. And it allows you to reset the entire calculation
- CRC_INIT : The CRC init register allows you to set the initial CRC value, by default it is set to 0xFFFF FFFF
- CRC_POL : The CRC poly register allows you to chose the polynomial coefficient used in the calculations. The default value is 0x04C1 1DB7
STM32F1 CRC
The
CRC peripheral in the F1 series is even simpler to use, it has 3 register, one
of which is useless the IDR, and another register only allows us to use 1 BIT.
Then there is a data register. So basically you have your main data register
and 1 single bit to worry about
- CRC_DR : The CRC data register. When writing to the data register you are giving the CRC engine the input data on which it will calculate the CRC. When you read from the data register you will get the result from that calculation
- CRC_IDR : Same as STM32L0
- CRC_CR : The CRC control register lets us reset the entire calculation
NOTE:
Since the data register is used to write our input data to generate a CRC code
and also used to read that generated CRC you would think there would be an
issue if you do a write and then immediately read. But there is no issue
because according to the data sheet the microcontroller will pause the next
read or write until it is complete doing the current one
Code:
STM32L0
Below are two separate functions that initialize the CRC to use an 8 bit polynomial and a 32 bit polynomial. The actual polynomial chosen does not matter so long as its not all zeroes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void crc_init_8(void) { RCC->AHBENR |= RCC_AHBENR_CRCEN; // enable clock for CRC CRC->CR |= CRC_CR_RESET; // Reset calculation CRC->POL = 0xCB; // pick a random poly CRC->CR = 2 << CRC_CR_POLYSIZE_Pos; // set poly to 8 bit CRC->INIT = 0xFF; //init value also 8 bit } void crc_init_32(void) { RCC->AHBENR |= RCC_AHBENR_CRCEN; // enable clock for CRC CRC->CR |= CRC_CR_RESET; // Reset calculation CRC->POL = 0x04C11DB7; // 32 bit poly CRC->CR = 0x00000000; // sets poly size to 32 bit CRC->INIT = 0xFFFFFFFF; //32 bit init value } |
Now here is my main code. What I have done here is pretty self explanatory if you read the comments. What I am trying to illustrate is that if you are sending multiple bytes you do not need to get a CRC code for every single byte, just run all of your data through the CRC and get the final CRC code and use that as your check sum.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | int main(void) { //***************************| NON CRC CODE |*************************** setClockTo32Mhz(); SystemCoreClockUpdate(); SysTick_Config(SystemCoreClock / 1000); NVIC_EnableIRQ(USART2_IRQn); init_uart2(); //debug led PB3 RCC->IOPENR |= RCC_IOPENR_GPIOBEN; GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODE3_1)) | GPIO_MODER_MODE3_0; //********************************************************************* // just dummy data for testing uint8_t dummy_byte = 0xC1; uint8_t dummy_array[4] = { 0x41, 0x20, 0xff, 0x10 }; while (1) { //EX 1:Put 1 byte through CRC, print the CRC code , reset calculation crc_init_8(); CRC->DR = dummy_byte; uart2_send(CRC->DR); //EX 2:Put 4 bytes through CRC one by one, get the CRC code at end crc_init_8(); uint8_t i; for (i = 0; i < 4; i++) { CRC->DR = dummy_array[i]; } uart2_send((uint8_t) (CRC->DR & 0xFF)); delayMs(2000); //EX 3:Put 4 bytes in the 32bit Data register all at once, get CRC code crc_init_32(); uint32_t all_bytes = 0x00; for (i = 3; i >= 1; i--) { all_bytes |= dummy_array[i] << (8 * i); } all_bytes |= dummy_array[0]; CRC->DR = all_bytes; uint32_t crc_code = CRC->DR; uart2_send(((crc_code) & 0xFF)); uart2_send(((crc_code >> 8) & 0xFF)); uart2_send(((crc_code >> 16) & 0xFF)); uart2_send(((crc_code >> 24) & 0xFF)); delayMs(2000); } return 0; } |
The output from the above code onto a port monitor generated :
EX 1: 0x80
EX 2: 0xDB
EX 3: 0x43B2D897
Note that you are not limited to use an 8 bit poly and 8 bit init value for your 8 bit data, you can set it up for 32 bit mode and run your 8 bit data through it. You will then get a 32 bit CRC code unique to your 8 bit data if you consider that a safer route go ahead and do it.
Now here is the all the code including helper functions and no line numbers for nice copy and pasting.
/* Includes */ #include <stddef.h> #include "stm32l0xx.h" #include "stdlib.h" #include "stdint.h" #include "stdbool.h" /* Private macro */ /* Private variables */ uint32_t msTICKS = 0; bool SENDCOMPLETE = false; /* Private function prototypes */ /* Private function prototypes */ void SysTick_Handler(void); void delayMs(uint32_t ms); void setClockTo32Mhz(void); void init_uart2(void); void USART2_IRQHandler(void); void uart2_send(uint8_t data); void crc_init_8(void); void crc_init_32(void); int main(void) { //***************************| NON CRC CODE |*************************** setClockTo32Mhz(); SystemCoreClockUpdate(); SysTick_Config(SystemCoreClock / 1000); NVIC_EnableIRQ(USART2_IRQn); init_uart2(); //debug led PB3 RCC->IOPENR |= RCC_IOPENR_GPIOBEN; GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODE3_1)) | GPIO_MODER_MODE3_0; //********************************************************************* uint8_t dummy_byte = 0xC1; uint8_t dummy_array[4] = { 0x41, 0x20, 0xff, 0x10 }; while (1) { // Put 1 byte through CRC, print the CRC code , reset calculation crc_init_8(); CRC->DR = dummy_byte; uart2_send(CRC->DR); //Put 4 bytes through CRC one by one, get the CRC code at end crc_init_8(); uint8_t i; for (i = 0; i < 4; i++) { CRC->DR = dummy_array[i]; } uart2_send((uint8_t) (CRC->DR & 0xFF)); delayMs(2000); //Put 4 bytes in the 32bit Data register all at once, get CRC code crc_init_32(); uint32_t all_bytes = 0x00; for (i = 3; i >= 1; i--) { all_bytes |= dummy_array[i] << (8 * i); } all_bytes |= dummy_array[0]; CRC->DR = all_bytes; uint32_t crc_code = CRC->DR; uart2_send(((crc_code) & 0xFF)); uart2_send(((crc_code >> 8) & 0xFF)); uart2_send(((crc_code >> 16) & 0xFF)); uart2_send(((crc_code >> 24) & 0xFF)); delayMs(2000); } return 0; } void crc_init_8(void) { RCC->AHBENR |= RCC_AHBENR_CRCEN; // enable clock for CRC CRC->CR |= CRC_CR_RESET; // Reset calculation CRC->POL = 0xCB; // pick a random poly CRC->CR = 2 << CRC_CR_POLYSIZE_Pos; // set poly to 8 bit CRC->INIT = 0xFF; //init value also 8 bit } void crc_init_32(void) { RCC->AHBENR |= RCC_AHBENR_CRCEN; // enable clock for CRC CRC->CR |= CRC_CR_RESET; // Reset calculation CRC->POL = 0x04C11DB7; // 32 bit poly CRC->CR = 0x00000000; // sets poly size to 32 bit CRC->INIT = 0xFFFFFFFF; //32 bit init value } //***************************| ALL NON CRC CODE BELOW|*************************** void uart2_send(uint8_t data) { SENDCOMPLETE = false; USART2->TDR = data; while (SENDCOMPLETE == false) ; } void USART2_IRQHandler(void) { // get status register and check what generated the interrupt volatile const uint32_t STATUS = USART2->ISR; if ((STATUS & USART_ISR_TC)) // transmit complete { USART2->ICR |= USART_ICR_TCCF; SENDCOMPLETE = true; } if ((STATUS & USART_ISR_RXNE)) //send received data to buffer { } } void init_uart2(void) { RCC->IOPENR |= RCC_IOPENR_GPIOAEN; RCC->APB1ENR |= RCC_APB1ENR_USART2EN; // PA2 and PA3 to Alternate Function Mode GPIOA->MODER = ( GPIOA->MODER & ~(GPIO_MODER_MODE2_0)) | (GPIO_MODER_MODE2_1); GPIOA->MODER = ( GPIOA->MODER & ~(GPIO_MODER_MODE15_0)) | (GPIO_MODER_MODE15_1); //Select the specific Alternate function GPIOA->AFR[0] |= 4 << GPIO_AFRL_AFSEL2_Pos; GPIOA->AFR[1] |= 4 << GPIO_AFRH_AFSEL15_Pos; // Baudrate = clk_Frq / BRR ===> 32Mhz / 9600 = 0xD05 USART2->BRR = 0xD05; //160000 / 96; // Enable RX_NE interrupt and TXE interrupt, enable UART, RECEIVE , TRANSMIT COMPLETE USART2->CR1 = USART_CR1_TE | USART_CR1_UE | USART_CR1_RXNEIE | USART_CR1_RE | USART_CR1_TCIE; } void delayMs(uint32_t ms) { msTICKS = 0; while (msTICKS < ms) ; } void SysTick_Handler(void) { msTICKS++; } void setClockTo32Mhz(void) { //adjust flash latency FLASH->ACR |= FLASH_ACR_LATENCY; while ((FLASH->ACR & FLASH_ACR_LATENCY) == 0) ; //wait for latency set flag //set voltage scaling to range 1 PWR->CR |= PWR_CR_VOS_0; PWR->CR &= ~(PWR_CR_VOS_1); while (((PWR->CSR) & (PWR_CSR_VOSF)) == 1) ; //wait for voltage to settle //turn on HSE external, HSE bypass and security RCC->CR |= RCC_CR_CSSHSEON | RCC_CR_HSEBYP | RCC_CR_HSEON; while (((RCC->CR) & RCC_CR_HSERDY) == 0) ; //wait for the HSE to be ready //reset and configure pll mull and div settings, and PLL source RCC->CFGR = ((RCC->CFGR & ~(RCC_CFGR_PLLDIV | RCC_CFGR_PLLMUL)) | RCC_CFGR_PLLDIV2 | RCC_CFGR_PLLMUL8 | RCC_CFGR_PLLSRC_HSE); while ((RCC->CR & RCC_CR_PLLRDY) == 1) ; //turn on PLL , wait for ready RCC->CR |= RCC_CR_PLLON; while (((RCC->CR) & RCC_CR_PLLRDY) == 0) ; // wait for pll to ready //set PLL as system clock RCC->CFGR |= RCC_CFGR_SW_PLL; while (((RCC->CFGR) & (RCC_CFGR_SWS_PLL)) != RCC_CFGR_SWS_PLL) ; }
Code:
STM32F1
Here is the code for the STM32F103C8. In this example the same examples are done. Some minor differences are the way I print out the code because I have implemented a printing function for the F1
but the CRC register programming is even simpler, there is no poly setting or init value setting either. The F1 does CRC only in 32 bit but that does not mean your data can only be 32 bit , it just means your CRC code will be 32 bit.
int main (void) { //****************************| NON CRC CODE |********************* SysTick_Config(SystemCoreClock / 1000); printMsg_config_Type printer; printer.TX_pinNumber = 9; printer.Uart_instance = USART1; printer.tx_port = GPIOA; printMsg_init(printer); //***************************************************************** crc_init(); uint8_t dummy_byte = 0xC1; uint8_t dummy_array[4] = { 0x41, 0x20, 0xff, 0x10 }; while(1) { // Put 1 byte through CRC, print the CRC code , reset calculation CRC->CR = 1; CRC->DR = dummy_byte; printMsg("%x \n", CRC->DR); delayMs(500); //Put 4 bytes through CRC one by one, get the CRC code at end uint8_t i; CRC->CR = 1; for (i = 0; i < 4; i++) { CRC->DR = dummy_array[i]; } printMsg("%x \n", CRC->DR); delayMs(500); //Put 4 bytes in the 32bit Data register all at once, get CRC code uint32_t all_bytes = 0x00000000; for (i = 3; i >= 1; i--) { all_bytes |= dummy_array[i] << (8 * i); } all_bytes |= dummy_array[0]; CRC->DR = all_bytes; printMsg("%x \n", CRC->DR); delayMs(500); } } void crc_init(void) { RCC->AHBENR |= RCC_AHBENR_CRCEN; // enable clock for CRC CRC->CR |= CRC_CR_RESET; // Reset calculation }
I calculated crc for 32-bit polynomial but value is not match with crc-32 in online crc calulator
ReplyDeleteit match with CRC-32/MPEG-2 .If I want to match with crc-32 is any thing to change
Note on the online CRC calculator site itself, the differences between the definition of CRC-32 and CRC/MPEG2:
DeletePolynomial Init RevIn RevOut XorOut
CRC-32 0x04C11DB7 0xFFFFFFFF true true 0xFFFFFFFF
CRC-32/MPEG-2 0x04C11DB7 0xFFFFFFFF false false 0x00000000
So, if you are getting a result that matches the latter and wanted to match the former, the things you need to change are laid out in this table; i.e., change the settings for hcrc.Init.InputDataInversionMode (or looking at the registers directly, REV_IN in CRC_CR), hcrc.Init.OutputDataInversionMode (REV_OUT in CRC_CR), and when your loop is done, XOR the result with 0xFFFFFFFF (there is no way to get the CRC device to do this for you, you just have to do it yourself upon completion). Note that ST (annoyingly) refers to the bit-reversal on input and output "Inversion", but this is not inverting (e.g., x = ~x;) but mirroring the bits so b31 becomes b0, b30 becomes b1, etc., so it refers to the same settings as what the online CRC calculator calls (because of a typo) "RefIn" and "RefOut"; they both refer to "Reversing the bit order".
How to decode the CRC value at another STM32 ?
ReplyDeleteThis is a perfect tutorial, thank you
ReplyDeleteIf for STM32F1 I don't have any poly to configure, how do I know witch poly is being used in this calculation?
ReplyDeleteyou have to look at your Reference Manual and it will tell you what poly it uses. A quick glance at the STM32F1 manual shows : 0x4C11DB7
DeleteThis comment has been removed by the author.
ReplyDeleteI noticed that in the example above when you used CRC-8 with 0xCB as poly and 0xC1 as byte you get the result 0x80. However if you look at the CRC flow chart and algorithm, the example posted uses the same values but the returned CRC value is 0x4C. The settings appeared all same to me. Why are the values different with everything being same?
ReplyDelete