STM32 HAL库实现SPI主从双机通信教程

一、简介

最近因为项目需求,需要在一块板子内实现一个主机和五个从机的通信;
主机平台选用的是STM32F407VGT6,从机平台选用的是STM32F103C8T6;通信总线选用的是SPI总线。在构想是觉得采用SPI进行主从通信会很简单,但在实际开发的过程中,各种坑,通信时而正常时而混乱。不过在不断探究中,也逐渐发现了,各种问题所在,借此记录下来,希望能帮助一些兄弟在开发中避免一些坑。
本次实现的平台如下:
通信主机:

  • 芯片:STM32F103RCT6
  • 硬件平台:野火mini开发板
  • 通信从机:

  • 芯片:STM32F103C8T6
  • 硬件平台:淘宝STM32F103C8T6最小系统开发板
  • 软件:

  • HAL库
  • MDK
  • STM32CubeMx
  • 二、开发过程中遇到的问题

    以下是我在开发中遇到问题:

    1. 相同的数据,每次发送,主从接收到都是不同的乱码
    开发板之间没共地,或者供地接触不够好;更换质量较好的杜邦线,线材很影响信号质量。
    2. 通信不正常,很没规律的不正常
    检查主从机的SPI外设配置,接线等。
    3. 数据出现移位
    SPI总线的时钟质量不好,出现不该出现的高低电平,让从机认为这是一bit数据,出现移位寄存器移位,例如原本是8bit数据,现在由于干扰从机接收到的可能是9bit或者10bit数据,而从机实际接收到的数据只是最先传入的8bit数据。
    要保证良好的时钟信号,同时也可以将SPI的数据采集触发改成时钟下降沿触发(好像是下降沿的信号质量要比上升沿的信号质量要更好),降低SPI通信速度。
    在保证时钟的稳定的情况下,可通过复位从机的SPI外设来解决偶然发生的数据移位问题。
    4.从机spi启动比主机慢
    在主机发出片选信号都需要加一段延时,以确保从机的SPI外设比主机先启动。

    三、硬件电路接线

    NSS片选我们使用软件控制方式:


    所以我们主从机的SPI通信接线就直接按照手册对接就行。
    片选信号根据自己需求设置GPIO口,通过软件控制,有效电平和标准SPI协议保持一样就好,空闲高,有效低。

    四、主从机SPI外设配置

    4.1、主机配置


    其中只有PA4、PA5、PA6、PA7是我们需要关注的,PA4是片选脚

    4.2、从机配置


    其中只有PA15、PB3、PB4、PB5是我们需要关注的,PA15是片选管脚。

    4.3、接线

    PA4 -> PA15(片选Nss)
    PA5 -> PB3(SCK)
    PA6 -> PB4(MISO)
    PA7 -> PB5(MOSI)

    五、如何清除移位寄存器

    通过RCC寄存器复位SPI1外设,在从新初始化SPI1外设完成移位寄存器清除

        if(SPI1->SR != 0x02)    //当BSY为1时,表示SPI正在忙于通信,但在通信还未开始的时候BSY为1就可以表示移位寄存器存在干扰数据
        {
            //只用通过RCC复位整个SPI外设后重新初始化,才能清除spi移位寄存器中的残留数据
            __HAL_RCC_SPI1_FORCE_RESET();
            __HAL_RCC_SPI1_RELEASE_RESET();
            MX_SPI1_Init();
            printf("SPI复位,清空移位寄存器残留数据\n");
            HAL_Delay(10);
        }
    

    六、代码

    6.1、主机代码

    uint8_t rx_buffer[22]={'1','2','3',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
    uint8_t tx_buffer[22]={0x00,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16};
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_SPI1_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
      printf("主从测试开始\n");
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        //SPI通信
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
        HAL_Delay(2);      //通过延时保证从机SPI外设比主机先启动
        HAL_SPI_TransmitReceive(&hspi1,tx_buffer,rx_buffer,8,100);
        HAL_Delay(10);
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
        printf("接收到的数据:");
        for(num = 0;num < 8;num++)
        {
            printf("%#x ",rx_buffer[num]);
        }
        memset(rx_buffer,0,8);
        printf("\n");
        HAL_Delay(5000);
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    //重映射printf函数
    int fputc(int ch,FILE*stream)
    {
    uint8_t c =ch;
    HAL_UART_Transmit(&huart1,&c,1,50);
    return ch;
    }
    

    6.2、从机代码

    uint8_t Tx_data[8]={0x87,0xa2,0x41,0x02,0x93,0x04,0x05,0x06};
    uint8_t Rx_data[8]={0};
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_DMA_Init();
      MX_ADC1_Init();
      MX_SPI1_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        bsp_spi_eeror(&hspi1);  //通信错误判断
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_15) == 0)    //识别NSS线(低电平有效)
        {
        HAL_SPI_TransmitReceive(&hspi1,Tx_data,Rx_data,8,100);
            
        while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_15) == 0){};    //等待 主机 释放 片选   
    
        printf("spi从机Rx_dete:");
        for(int i=0 ; i < 8;i++)
        {
            printf("%#x ",Rx_data[i]);
        }
        memset(Rx_data,0,8);
        printf("\n主机以释放nss线\n");
        }
    
        
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    

    七、效果展示

    左侧为主机串口显示,右侧为从机串口显示;与程序中的数据对比可发现收发数据传输均正确

    逻辑分析仪显示数据:由于中间片选出现了一次高电平干扰,导致后部分的数据分析异常,但实际传输的数据是正常的。

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 HAL库实现SPI主从双机通信教程

    发表评论