最重要收获:了解到同一个寄存器按字节,半字和字访问的区别。同一个内存寄存器地址,强转为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。

STM32CubeMX中RCC感叹号_数据

STM32CubeMX中RCC感叹号_stm32_02

STM32CubeMX中RCC感叹号_STM32CubeMX中RCC感叹号_03

STM32硬件CRC支持对输入数据按字节,半字和字反转,支持对输出结果进行反转。

3.重点在于STM32硬件CRC的DR寄存器理解,手册上指出该寄存器可以按字访问,右对齐的按半字和右对齐按字节访问,需理解该访问含义。

手册上的给出DR寄存器如下描述,最后红线部分描述数据写入时的操作,可动态调整数据大小,从而减少写入的访问次数。此处的动态调整数据大小指动态调整写入DR寄存器数据的大小,DR寄存器可以按字节、半字和字访问,动态调整即为可以在写入时采用按字节,半字或字的访问方式把数据写入DR寄存器,写入访问大小在计算不同多项式因子的结果时尤为重要。如用该硬件CRC计算CRC8结果时,使用CRC8/ROHC,多项式因子为0x07,初始值0XFF,结果异或值0x00,输入数据反转,输出数据反转。

STM32CubeMX中RCC感叹号_crc_04

STM32CubeMX中RCC感叹号_stm32_05

用于初始化和计算的数据代码如下

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 *类型.

STM32CubeMX中RCC感叹号_stm32_06