STM32 HAL库 SPI从机不定长数据帧接收响应

2019-07-20 01:50发布

  需要使用STM32的SPI从模式接收不定长数据,参考了很多论坛的资料都是进行简答的数据收发,SPI只作为从模式接收定长帧,更多的不涉及发送。不定长帧的数据若是串口方式可使用DMA加空闲中断,特意去看了下SPI不支持空闲中断,然后设计上也就没有采用DMA方式接收数据,单纯的使用中断方式维护。
  从机不定长帧的区分使用了定时器超时机制,确定当前主机的通信速率,计算两次数据之间的最小时间间隔,以此为最小超时时间。

​
  SPI从机中断响应回在时钟结束后响应中断,因此最小数据间隔应该大于15us。
主机SPI配置:
[mw_shl_code=c,true]static void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
  hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
//                hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_LSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}[/mw_shl_code]

主机收发数据:
[mw_shl_code=c,true]void master_tx_frame(uint16_t* rx_data)
{
        unsigned short tx_data[4] = {0x0055,0x1FFF,0x0100,0x8C00};

        HAL_SPI_TransmitReceive(&hspi1,(uint8_t*)tx_data,(uint8_t*)rx_data,4,0xFFFF);       
}[/mw_shl_code]
HAL_SPI_TransmitReceive默认数据类型uint8_t*,发送十六位数据时需保证SPI数据宽带为16位,发送字节为发送的十六位字节的字节数。使用该函数SPI传送为非连续模式,而HAL_SPI_Tramsmit函数使用的是连续模式。即两次数据时钟间隔是否存在。
​​​
  从机SPI配置:[mw_shl_code=c,true]static void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_SLAVE;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
  hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_LSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */
}[/mw_shl_code]

定时器配置:
[mw_shl_code=c,true]static void MX_TIM3_Init(void)
{
  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 44;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 19;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */

  /* USER CODE END TIM3_Init 2 */
}[/mw_shl_code]
从机接收中断:[mw_shl_code=c,true]void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
        HAL_TIM_Base_Stop_IT(&htim3);        //关闭定时器
        rx_buf[rx_index++] = rx_data ;  //写入缓存
        if(tx_flag==1) //发送标志 置换缓冲区内容
        {
//                hspi1.Instance->DR = tx_dat[tx_index++] ;       
                hspi1.Instance->DR = tx_dat[++tx_index];        //送第一个数据 避免数据丢失       
//                hspi1.Instance->DR = rx_data ;
        }
        HAL_SPI_Receive_IT(&hspi1,(uint8_t*)&rx_data,1); //接收一个字节进入一次中断
        htim3.Instance->CNT = 0 ;                                //设置计数值为0
        __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);        //清标志
        HAL_TIM_Base_Start_IT(&htim3);  //开启定时器
}[/mw_shl_code]定时器更新中断:
[mw_shl_code=c,true]void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
        if(htim->Instance == htim3.Instance)
        {                                                                                                                                                                                                //关SPI接收中断
//                frame_cnt++;
                GPIOB->ODR ^= GPIO_PIN_0;        //led取反
                tx_flag = 0 ;      //清发送标志  超时时发送完一帧
//                tx_index = 0 ;                //超时时发送完一帧
                check_flag = 1 ;  //置检验标志
//                __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);        //清定时器更新标志
                HAL_TIM_Base_Stop_IT(&htim3);                                                   //关闭定时器
        }
}[/mw_shl_code]
当SPI接收中断响应时,关闭定时器,获取数据写入缓存。因为SPI是串行传输,每次都是交互彼此数据缓冲区的内容,所以在中断中使用hspi1.Instance->DR = tx_dat[++tx_index]填充数据。很多论坛的朋友都是在中断中采用HAL_SPI_Transmit或者HAL_SPI_Transmit_DMA方式发送数据。其实HAL库在接收数据的时候已经选择不会发送当前数据了,而HAL_SPI_Transmit是一个带有超时性质的函数,会立即有结果。从机的数据传输需要依靠主机提供时钟,在当前中断中数据HAL库会传输失败,但是数据会到缓冲区,下一次时钟到来会将数据传输出去。因此在中断中直接更新其寄存器是很好的方式。先将需要发送的数据放到缓冲区里,下一次主机发起传输的时候双方缓冲区互换类容,这样就将想要发送的数据发送出去了。
  注意SPI中断里的代码的话应该尽量精简,因为SPI都是高速信号,频繁的中断会加重芯片的负荷,更不要想在中断中加入串口之类的。建议在线调试,这样可以直观的看到接收的数据。
  定时器超时中断证明一帧数据接收完毕,之后可以拿去做数据校验。从机发送是依靠主机时钟进行的,因此超时需要将发送标志置零,表示当前帧数据同样发送完毕。
主函数处理:
[mw_shl_code=c,true]if(check_flag==1)//数据帧判断标志
                        {
                                if(rx_buf[0]==0x0055&&rx_buf[1]==0x1FFF)//数据处理 装载新的数据
                                {
                                                get_cmd_cnt++;        //调试用
                                                tx_dat = tx_buf_first ;        //更换缓冲区
                                                tx_index=0;                                                        //清发送下标
                                                hspi1.Instance->DR = tx_dat[tx_index];        //送第一个数据 避免数据丢失
                                                tx_flag = 1 ;  //置发送标志
                                }
                                else if(rx_buf[0]==0x0155&&rx_buf[1]==0x1001)
                                {
                                                get_cmd_cnt++;        //调试用
                                                tx_dat = tx_buf_second ; //更换缓冲区
                                                tx_index=0;                                                        //清发送下标
                                                hspi1.Instance->DR = tx_dat[tx_index];        //送第一个数据 避免数据丢失
                                                tx_flag = 1 ;  //置发送标志
                                }       
                                else
                                        hspi1.Instance->DR = 0;  //确认不是命令字时正常返回0
                                rx_index = 0 ;                                                 //清接收下标
                                timeout_cnt++;                                                 //调试用
                                check_flag = 0 ;                                         //清帧头判断标志
                        }[/mw_shl_code]
这样的方式可成功的在主机的下一帧数据来临时返回上一帧的应答数据。测试效果:
​​​




友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。