cancel
Showing results for 
Search instead for 
Did you mean: 

Are there any working examples using the CRC for MODBUS RTU? I cannot get a reasonable result out if the CRC device on the STM32L443

KiptonM
Lead

According to Wikipedia The MODBUS uses the CRC-16-ANSI also known as CRC-16-IBM.

The normal hexadecimal algebraic polynomial being 8005 and reversed A001

Initial value: 65,535.

Example of frame in hexadecimal: 

01 04 02 FF FF B8 80

(CRC-16-ANSI calculation for the 5 bytes from 01 to FF  gives 80B8 which is transmitted least significant byte first).

I found some code that does the CRC16 with lookup tables, and it provides the same answer. So I believe the Wiki example is correct.

Reading the RM0394 Reference manual it appears it is a pretty simple peripheral. Only a few registers to set up.

CRC->DR is the data register.You write to this register and it holds the result.

the CRC->IDR is not clear what it is used for. Looks like 8-bit memory.

The CRC->INIT has a reset value of FFFFFFFF which is the sme as what I need.

The Only register that is not clear is the CRC->CR register. Bit 0 loads the CRC->INIT register value into the CRC->DR register for a new calculation. The POLYSIZE bits should be 01 for a 16 bit polynomial. The REV_OUT and REV_IN bytes are not clear. I tried both with 0 or bit order not affected. But the answer was not correct, so I tried every combination (only 😎 none generated the right answer.

Here is my code:

void test_cr(uint32_t CR)
{
	char buf[100];
	CRC->POL = 0x8005;
	CRC->CR = CR;
 
	uint16_t i;
	uint8_t data[7] = { 1, 4, 2, 0xFF, 0xFF, 0, 0};
 
	for (i=0;i<5;i++)
	{
		CRC->DR = data[i];
		HAL_Delay(1);
	}
 
	sprintf(buf,"CRC_CR: 0x%08lX, Result: 0x%08lX -- should be 0x80B8\r\n", CRC->CR,CRC->DR);
	print_debug_str(buf);
}
 
void test_crc16(void)
{
	char buf[100];
 
	uint8_t data[7] = { 1, 4, 2, 0xFF, 0xFF, 0, 0};
 
	uint16_t  crc = Calculate_CRC16(data, 5);  // this was from a routine I found that did it by table lookup. 
 
	sprintf(buf,"Wikipedia CRC 0x%04X -- should be 0x80B8\r\n",crc);
	print_debug_str(buf);
 
 
	test_cr(0x19);
	test_cr(0x39);
	test_cr(0x59);
	test_cr(0x79);
	test_cr(0x99);
	test_cr(0xB9);
	test_cr(0xD9);
	test_cr(0xE9);
 
	__NOP();
}

I ran it and got:

Wikipedia CRC 0x80B8 -- should be 0x80B8

CRC_CR: 0x00000018, Result: 0x0000004E -- should be 0x80B8

CRC_CR: 0x00000038, Result: 0x00000009 -- should be 0x80B8

CRC_CR: 0x00000058, Result: 0x00000029 -- should be 0x80B8

CRC_CR: 0x00000078, Result: 0x00000036 -- should be 0x80B8

CRC_CR: 0x00000098, Result: 0x00000039 -- should be 0x80B8

CRC_CR: 0x000000B8, Result: 0x00000048 -- should be 0x80B8

CRC_CR: 0x000000D8, Result: 0x0000004A -- should be 0x80B8

CRC_CR: 0x000000E8, Result: 0x0000DE9D -- should be 0x80B8

None of the answers were right.

So then I wondered if the Polynomial had to be reversed or A001

Wikipedia CRC 0x80B8 -- should be 0x80B8

CRC_CR: 0x00000018, Result: 0x0000006F -- should be 0x80B8

CRC_CR: 0x00000038, Result: 0x00000063 -- should be 0x80B8

CRC_CR: 0x00000058, Result: 0x00000047 -- should be 0x80B8

CRC_CR: 0x00000078, Result: 0x0000001E -- should be 0x80B8

CRC_CR: 0x00000098, Result: 0x0000007B -- should be 0x80B8

CRC_CR: 0x000000B8, Result: 0x00000063 -- should be 0x80B8

CRC_CR: 0x000000D8, Result: 0x00000071 -- should be 0x80B8

CRC_CR: 0x000000E8, Result: 0x000090CD -- should be 0x80B8

With more reading I found something about a clock having to be started. I could not find anything about a CRC clock in RM0394 or nothing in the setup for CRC in the Device Configuration Tool, or in the RCC Device. It must be for another processor.

This should be pretty simple. What am I missing?

Thanks,

1 ACCEPTED SOLUTION

Accepted Solutions

I believe I've posted working examples before

CRC->DR = data[i]; // This writes a WORD

*((volatile uint8_t *)&CRC->DR) = data[i]; // Writes a BYTE

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

View solution in original post

7 REPLIES 7
KiptonM
Lead

In RM0394 I found a reference to crc_hclk AHB Clock, in paaragraph 14.3.2 but there was no mention of it anywhere else in the document using a text search.

I believe I've posted working examples before

CRC->DR = data[i]; // This writes a WORD

*((volatile uint8_t *)&CRC->DR) = data[i]; // Writes a BYTE

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
KiptonM
Lead

I saw that it worked for different sizes, but thought if you gave it a byte it would write a byte,

I will try that. It is always the little things.

That worked! It was the case where CRC->CR was 0xE9 to start!

I owe you one for that!

KiptonM
Lead

Even better. I found your code that did this:

uint32_t CRC_HardwareBlock(uint16_t Size, const uint8_t *Buffer)
{
	CRC->INIT = 0xFFFF;
	CRC->POL = 0x8005;
	CRC->CR = 0xE9;
 
	while(Size >= 4) // Do as much as we can at 32-bits
	{
		CRC->DR = *((uint32_t *)Buffer);
 
		Buffer += 4;
		Size -= 4;
	}
 
	while(Size >= 2) // Perhaps one of these
	{
		*((uint16_t *)&CRC->DR) = *((uint16_t *)Buffer);
 
		Buffer += 2;
		Size -= 2;
	}
 
	while(Size--) // Perhaps one of these
		*((uint8_t *)&CRC->DR) = *Buffer++;
 
	return(CRC->DR); // Return final CRC computation
}
 

First three lines are my mod. I do not have to be as generic as you are. 🙂

You are a clever person!!!

The peripheral is pretty ugly in it's implementation, the bus can deal with different widths at the interface, and this is easier to do in assembler. With C the structure defines the register as 32-bit wide so you have to explicitly cast it for the compiler to generate 8-bit or 16-bit access to it.

Volatile used here so the compiler/optimizer doesn't fold the operations as the peripheral logic is not a "memory" so order, and content are critical.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
GDC
Associate II

@Tesla DeLorean: " You are a clever person!!! " , I agree with @KiptonM

However I'm using STM32U535 and seems that the hardware peripheral used with the following code (the same suggested early) works only with a buffer size multiple of four bytes (or at least with the other "odd bytes" equal to zero). For sure I'm in error somewhere. Did you (or someone else) test the code with such uC ?

 

 

 

//	################################################################################################
uint32_t CRC_HardwareBlock(const uint8_t *Buffer, uint16_t Size ){
	//RCC->AHB1ENR	|=	RCC_AHB1ENR_CRCEN; __NOP();		// init()


	CRC->INIT = 0xFFFF;
	CRC->POL = 0x8005;
	CRC->CR = 0xE9;
	//CRC->CR	|=	CRC_CR_RESET;

	while(Size >= 4){ // Do as much as we can at 32-bits
		CRC->DR = *((uint32_t *)Buffer);

		Buffer += 4;
		Size -= 4;
	}

	while(Size >= 2){ // Perhaps one of these
		*((uint16_t *)&CRC->DR) = *((uint16_t *)Buffer);

		Buffer += 2;
		Size -= 2;
	}

	while(Size--) // Perhaps one of these
		*((uint8_t *)&CRC->DR) = *Buffer++;

	return(CRC->DR); // Return final CRC computation
}