最重要收获:了解到同一个寄存器按字节,半字和字访问的区别。同一个内存寄存器地址,强转为volitale uint8_t *类型,volitale uint16_t *类型和volitale uint32_t *类型时,若其支持按字节,半字和字访问时,这三个类型写入的结果对CPU来说是不一致的。感觉支持多类型访问的寄存器,写入低字节时,CPU记录低字节有数据更新,然后只进行低字节数据运算。低2字节有数据更新,CPU记录低2字节有变化,然后只进行低2字节数据计算。当写入4字节数据时,cpu记录4个字节均有变化,然后进行4字节数据运算。感觉一个寄存器,在CPU测有多个写入记录。
1.STM32L071单片机的硬件CRC使用,单片机手册见CRC章节介绍;
2.需注意的点:CRC输入数据按字节,半字和字反转,反转的含义即数据bit位置互换,即原数据从高位开始计算bit,则反转后从低位开始计算bit。如0x51的bit如图,其二级制为0b0101 0001,则反转即原来的0-7bit位置,依次用7-0bit位置替换,反转后结果为0x8A=0b1000 1010。如图:原来的bit0反转后变为bit7,bit1反转后为bit6,依次bit7反转后为bit0。图示的是按字节反转的结果。按半字反转即原始数据bit15变为反转后bit0,原始数据bit14变为反转后bit1,....,原始数据bit1变为反转后bit14,0原始数据bit1变为反转后bit15;按字反转即31原始数据bit变为反转后bit0,原始数据bit30变为反转后bit1,....,原始数据bit1变为反转后bit30,0原始数据bit1变为反转后bit31。如0x5128,按半字反转后结果为0x148A;数据0x51286983按字反转后结果为0xC196148A。
STM32硬件CRC支持对输入数据按字节,半字和字反转,支持对输出结果进行反转。
3.重点在于STM32硬件CRC的DR寄存器理解,手册上指出该寄存器可以按字访问,右对齐的按半字和右对齐按字节访问,需理解该访问含义。
手册上的给出DR寄存器如下描述,最后红线部分描述数据写入时的操作,可动态调整数据大小,从而减少写入的访问次数。此处的动态调整数据大小指动态调整写入DR寄存器数据的大小,DR寄存器可以按字节、半字和字访问,动态调整即为可以在写入时采用按字节,半字或字的访问方式把数据写入DR寄存器,写入访问大小在计算不同多项式因子的结果时尤为重要。如用该硬件CRC计算CRC8结果时,使用CRC8/ROHC,多项式因子为0x07,初始值0XFF,结果异或值0x00,输入数据反转,输出数据反转。
用于初始化和计算的数据代码如下
void MX_CRC_Init(void)
{
hcrc.Instance = CRC;
hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_DISABLE; //不使用默认的多项式因子
hcrc.Init.GeneratingPolynomial = 0x07; //自定义多项式因子0x07
hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_DISABLE; //不使用默认初始化值
hcrc.Init.InitValue = 0xFF; //自定义初始值0xFF
hcrc.Init.CRCLength = CRC_POLYLENGTH_8B; //CRC多项式因子宽度为8
hcrc.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_BYTE; //使能输入数据反转
hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_ENABLE; //使能输出数据反转
hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_BYTES; //输入数据格式为字节序
if (HAL_CRC_Init(&hcrc) != HAL_OK)
{
Error_Handler();
}
}
uint8_t crc_buf[] = {0x55,0xaa,0x99,0x88,0x66,0x88,0x77,0x44,0x55,0x33}; //CRC结果为0xFC,正确
uint32_t crc_buf[] = {0x55,0xaa,0x99,0x88,0x66,0x88,0x77,0x44,0x55,0x33}; //CRC结果为0x8B,相当于如下数组数据,结果与每个字节后都增加了3个0x00数据
//{0x55,0x00,0x00,0x00,0xaa,0x00,0x00,0x00,0x99,0x00,0x00,0x00,0x88,0x00,0x00,0x00,0x66,0x00,0x00,0x00,0x88,0x00,0x00,0x00,0x77,0x00,0x00,0x00,0x44,0x00,0x00,0x00,0x55,0x00,0x00,0x00,0x33,0x00,0x00,0x00}
//是由于函数在写入时,数组的每个数据都会左移24位后写到DR寄存器中,此时写入DR寄存器中的数据宽度为32bit,进行CRC计算时会把DR寄存器中的所有数据均进行计算,故得出的结果与实际想要的结果不同
uint32_t crc_result = 0;
int main(void)
{
MX_CRC_Init(); //初始化CRC寄存器,见上述第1行函数定义
/* USER CODE BEGIN 2 */
crc_result = HAL_CRC_Calculate(&hcrc,(uint32_t *)crc_buf,sizeof(crc_buf)); //CRC计算,见第32行
}
uint32_t HAL_CRC_Calculate(CRC_HandleTypeDef *hcrc, uint32_t pBuffer[], uint32_t BufferLength)
{
uint32_t index;
uint32_t temp = 0U;
hcrc->State = HAL_CRC_STATE_BUSY;
__HAL_CRC_DR_RESET(hcrc); //重新复位CRC寄存器,不会累积上次的CRC计算结果
switch (hcrc->InputDataFormat)
{
case CRC_INPUTDATA_FORMAT_WORDS:
/* Enter 32-bit input data to the CRC calculator */
for (index = 0U; index < BufferLength; index++)
{
hcrc->Instance->DR = pBuffer[index];
}
temp = hcrc->Instance->DR;
break;
case CRC_INPUTDATA_FORMAT_BYTES: //使用该逻辑部分计算CRC结果,按字节序格式写入数据
/* Specific 8-bit input data handling */
temp = CRC_Handle_8(hcrc, (uint8_t *)pBuffer, BufferLength); //见第63行原始库代码和100行自己修改后代码
break;
case CRC_INPUTDATA_FORMAT_HALFWORDS:
/* Specific 16-bit input data handling */
temp = CRC_Handle_16(hcrc, (uint16_t *)(void *)pBuffer, BufferLength); /* Derogation MisraC2012 R.11.5 */
break;
default:
break;
}
hcrc->State = HAL_CRC_STATE_READY;
return temp;
}
static uint32_t CRC_Handle_8(CRC_HandleTypeDef *hcrc, uint8_t pBuffer[], uint32_t BufferLength)//原始库代码
{
uint32_t i; /* input data buffer index */
uint16_t data;
__IO uint16_t *pReg;
uint32_t temp = 0;
for (i = 0U; i < (BufferLength / 4U); i++)
{
temp = ((uint32_t)pBuffer[4U * i] << 24U) | \
((uint32_t)pBuffer[(4U * i) + 1U] << 16U) | \
((uint32_t)pBuffer[(4U * i) + 2U] << 8U) | \
(uint32_t)pBuffer[(4U * i) + 3U];
hcrc->Instance->DR = temp; //首先按字写入数据,所有写入DR寄存器的数据均进行CRC计算
}
if ((BufferLength % 4U) != 0U) //数据长度不是4字节对齐
{
if ((BufferLength % 4U) == 1U) //余1个字节数据
{
*(__IO uint8_t *)(__IO void *)(&hcrc->Instance->DR) = pBuffer[4U * i]; //按字节写入,此时仅对单字节进行CRC运算,高3字节不参与CRC运算
}
if ((BufferLength % 4U) == 2U)
{
data = ((uint16_t)(pBuffer[4U * i]) << 8U) | (uint16_t)pBuffer[(4U * i) + 1U];
pReg = (__IO uint16_t *)(__IO void *)(&hcrc->Instance->DR); //按半字写入,此时仅对低半字进行CRC运算,高2字节不参与CRC运算
*pReg = data;
}
if ((BufferLength % 4U) == 3U)
{
data = ((uint16_t)(pBuffer[4U * i]) << 8U) | (uint16_t)pBuffer[(4U * i) + 1U];
pReg = (__IO uint16_t *)(__IO void *)(&hcrc->Instance->DR); /* Derogation MisraC2012 R.11.5 */
*pReg = data;
*(__IO uint8_t *)(__IO void *)(&hcrc->Instance->DR) = pBuffer[(4U * i) + 2U]; /* Derogation MisraC2012 R.11.5 */
}
}
return hcrc->Instance->DR;
}
static uint32_t CRC_Handle_8(CRC_HandleTypeDef *hcrc, uint8_t pBuffer[], uint32_t BufferLength)//自己修改的代码
{
uint32_t i; /* input data buffer index */
for (i = 0U; i < BufferLength; i++)
{
*(__IO uint8_t *)(__IO void *)(&hcrc->Instance->DR) = pBuffer[i]; //按字节写入,此时仅对单字节进行CRC运算,高3字节不参与CRC运算
}
return hcrc->Instance->DR;
}
最初把crc_buf类型设置为uint32_t类型,最后计算得出的CRC结果不正确结果为0x8B,实际与如下图结果一致,把类型设置为uint8_t类型后结果正确结果为0xFC。
使用CRC_Handle_8原始库代码和自己修改的代码,计算结果都是0xFC。从中可以清楚的看到DR寄存器按字节,按半字和按字访问的区别。按字访问时,写入DR寄存器中的所有数据均参与运算,即32bit数据均参与运算。按半字访问时,DR寄存器的低2字节参与CRC运算;按字访问时,DR寄存器的低字节参与CRC运算。按字节访问即把hcrc->Instance->DR地址强转为volitale uint8_t *类型,按半字访问即把hcrc->Instance->DR地址强转为volitale uint16_t *类型.