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:
  1. Start with an initial CRC value, in the image above it is 0xFF
  2. Then XOR it with your data
  3. If the result of step 2 has a most significant bit of 0 then you simply left shift that number by 1
  4. 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
  5. 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
  6. 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.
  1. 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
  2. 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. 
  3. 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
  4. CRC_INIT : The CRC init register allows you to set the initial CRC value, by default it is set to 0xFFFF FFFF
  5. 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
  1. 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
  2. CRC_IDR : Same as STM32L0
  3. 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
 
}

Comments

  1. I calculated crc for 32-bit polynomial but value is not match with crc-32 in online crc calulator
    it match with CRC-32/MPEG-2 .If I want to match with crc-32 is any thing to change

    ReplyDelete
    Replies
    1. Note on the online CRC calculator site itself, the differences between the definition of CRC-32 and CRC/MPEG2:
      Polynomial 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".

      Delete
  2. How to decode the CRC value at another STM32 ?

    ReplyDelete
  3. This is a perfect tutorial, thank you

    ReplyDelete
  4. If for STM32F1 I don't have any poly to configure, how do I know witch poly is being used in this calculation?

    ReplyDelete
    Replies
    1. you 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

      Delete
  5. This comment has been removed by the author.

    ReplyDelete
  6. I 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
Share your comments with me

Archive

Contact Form

Send