DSP F28335与STM32 HAL SPI通信详解

目录

SPI通信简介

一、DSP F28335主机代码

二、STM32代码

 


SPI通信简介

PCB芯片间的数据通信方式有很多种,其中串行(IO引脚较少)低速总线最常用的是SPI和I2C。
I2C(即IIC)是一种半双工总线(即同一时刻只能收或只能发),每个芯片只需提供两个两个引脚(SDA和SCL)即可实现多块芯片间的通信。
SPI是一种全双工同步通讯协议,由一个主设备和一个或多个从设备组成,主设备启动一个与从设备的同步通讯,从而完成数据的交换。SPI 接口一般由4根线组成,CS片选信号(有的单片机上也称为NSS),SCLK时钟信号线,MISO数据线(主机输入从机输出),MOSI数据线(主机输出从机输入),CS 决定了唯一的与主设备通信的从设备,如没有CS 信号,则只能存在一个从设备,主设备通过产生移位时钟信号来发起通讯。通讯时主机的数据由MISO输入,由MOSI 输出,输入的数据在时钟的上升或下降沿被采样,输出数据在紧接着的下降或上升沿被发出(具体由SPI的时钟相位和极性的设置而决定)。如果有多个从机,主机就要为每个从机提供一个CS片选引脚。

DSP F28335与STM32 HAL SPI通信详解


在全双工的SPI通信模式下,主机每发送一个字节(或半字),从机就必须同步返回一个字节(或半字)。在这种通信机制下,SPI的通信效率非常高。同时,由于SCLK的时钟信号可以设置得比较高(MBit级),SPI的通信速度也非常快。
项目中DSP和STM32之间需要进行通信,由于SPI的通信协议更加简单,波特率更高,我选用了SPI作为芯片间的通信方式。其中DSP作为主机,STM32作为从机。


 

一、DSP F28335主机代码

DSP在通信中作为主机,所有通信都由DSP主动发起,DSP发起前一定要让stm32做一个spi的准备完毕的信号(在下面代码中是GPIO49),另外,如果发现数据有错位、乱序的话要做好复位。spi通信对时钟信号的依赖非常强,只要一开始错位,那么后面的数据全部是错的。

Uint16 sdata[17];
Uint16 rdata_SPI[17];
Uint16 rdata[17];
void Bsp_SpiInit(void)
{
    //初始化管脚
	EALLOW;
	GpioCtrlRegs.GPBPUD.bit.GPIO54 = 0;   // Enable pull-up on GPIO54 (SPISIMOA)
	GpioCtrlRegs.GPBPUD.bit.GPIO55 = 0;   // Enable pull-up on GPIO55 (SPISOMIA)
	GpioCtrlRegs.GPBPUD.bit.GPIO56 = 0;   // Enable pull-up on GPIO56 (SPICLKA)
	GpioCtrlRegs.GPBPUD.bit.GPIO57 = 0;   // Enable pull-up on GPIO57 (SPISTEA)
	GpioCtrlRegs.GPBQSEL2.bit.GPIO54 = 3; // Asynch input GPIO16 (SPISIMOA)
	GpioCtrlRegs.GPBQSEL2.bit.GPIO55 = 3; // Asynch input GPIO17 (SPISOMIA)
	GpioCtrlRegs.GPBQSEL2.bit.GPIO56 = 3; // Asynch input GPIO18 (SPICLKA)
	GpioCtrlRegs.GPBQSEL2.bit.GPIO57 = 3; // Asynch input GPIO19 (SPISTEA)
	GpioCtrlRegs.GPBMUX2.bit.GPIO54 = 1; // Configure GPIO54 as SPISIMOA
	GpioCtrlRegs.GPBMUX2.bit.GPIO55 = 1; // Configure GPIO55 as SPISOMIA
	GpioCtrlRegs.GPBMUX2.bit.GPIO56 = 1; // Configure GPIO56 as SPICLKA
	GpioCtrlRegs.GPBMUX2.bit.GPIO57 = 1; // Configure GPIO57 as SPISTEA
	EDIS;
    SpiaRegs.SPIFFTX.all=0xE040;//使能SPIFIFO功能;TXFIFO复位;清除 TXFF INT 中断位;TXFIFO 中 断不使能;
	SpiaRegs.SPIFFRX.all=0x204f;//重新使能接收 FIFO 操作;清RXFIFO中断标志位;中断不使能。
	SpiaRegs.SPIFFCT.all=0x0;//这个寄存器是设置FIFO延时的,不需要用到。

	SpiaRegs.SPICCR.all =0x004F; // 复位,下降沿发送,上升沿接收(即时钟极性是:空闲时为高电平),  字长16位。关闭SPI内部LOOP BACK  禁止回送
	SpiaRegs.SPICTL.all =0x0006; // 主机模式, 时钟相位为正常相位, 
    SpiaRegs.SPICTL.bit.CLK_PHASE=0;//TALK=1使能主机发送, SPI中断不使能.  时钟相位为:数据在第2个时钟边沿被选择
	SpiaRegs.SPIBRR =0x0003; //波特率由主机决定=9.375MHz 。波特率=LSPCLK/(SPIBRR+1)=150MHz/4/4=9.375MHz
	SpiaRegs.SPICCR.all =0x00CF;//下降沿发送,上升沿接收(即时钟极性是:空闲时为高电平), 字长16位。准备发送或接收  禁止回送模式SPILBK=0
	SpiaRegs.SPIPRI.bit.FREE = 1; // Set so breakpoints don't disturb xmission //仿真用的
}

void Bsp_SpiSendRecv(Uint16* Data,int Len)
{
     Uint16 i,j,tmp;
     sdata[0]=0xaa;                 //数据帖头
     static Uint16 ErrorCount=0;
     //采样值发去ARM显示
     if (Bsp_GpioRead(49)!=1)//stm32已经拉低
     {
		 for ( i=0;i<Len;i++ )
		 {
			 sdata[i+1]=Data[i];
		 }
		 SpiaRegs.SPIFFTX.all=0xE040;//使能SPIFIFO功能;TXFIFO复位;清除 TXFF INT 中断位;TXFIFO 中 断不使能;
		 SpiaRegs.SPIFFRX.all=0x204f;//重新使能接收 FIFO 操作;清RXFIFO中断标志位;中断不使能。
		 for ( i=0;i<(Len+1);i++ )
		 {

			SpiaRegs.SPITXBUF=sdata[i];        //发送数据
			 while(SpiaRegs.SPIFFRX.bit.RXFFST !=1) { }  //接收FIFO为空时,等待!
			 // 检查返回数据
			 rdata_SPI[i] = SpiaRegs.SPIRXBUF;
			 if(rdata_SPI[0]==0xbb)   //收到正确的帖头
			 {
				 if(i>0)
				 {
					rdata[i-1]=rdata_SPI[i];
				 }
			 }
			 else
			 {
				 ErrorCount++;
				 if (ErrorCount>10)
				 {
					 ErrorCount=0;
					 SpiaRegs.SPISTS.all=0x0000;   //所有状态位清零
					 SpiaRegs.SPICCR.bit.SPISWRESET=1;//软复位
					 SpiaRegs.SPIFFTX.all=0xE040;//使能SPIFIFO功能;TXFIFO复位;清除 TXFF INT 中断位;TXFIFO 中 断不使能;
					 SpiaRegs.SPIFFRX.all=0x204f;//重新使能接收 FIFO 操作;清RXFIFO中断标志位;中断不使能。

				 }
			 }
		 }
		 while(SpiaRegs.SPIFFRX.bit.RXFFST !=0)
		 {
			 rdata_SPI[++i] = SpiaRegs.SPIRXBUF;
		 }
     }

}

二、STM32代码

我的STM32使用HAL库,SPI的通信方式使用DMA通道,如果程序对控制时间不敏感,使用查询方式也是可以的。但不要使用中断方式,我曾测试过中断方式,SPI通信的错位、乱序机率大增,而且我还找不到方法消除。

SPI_HandleTypeDef hspi2;
DMA_HandleTypeDef hdma_spi2_tx;
DMA_HandleTypeDef hdma_spi2_rx;

/* SPI2 init function */
void MX_SPI2_Init(void)
{

  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_SLAVE;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_HIGH;
  hspi2.Init.CLKPhase = SPI_PHASE_2EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspInit 0 */

  /* USER CODE END SPI2_MspInit 0 */
    /* SPI2 clock enable */
    __HAL_RCC_SPI2_CLK_ENABLE();

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**SPI2 GPIO Configuration
    PB13     ------> SPI2_SCK
    PB14     ------> SPI2_MISO
    PB15     ------> SPI2_MOSI
    */
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_14;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* SPI2 DMA Init */
    /* SPI2_TX Init */
    hdma_spi2_tx.Instance = DMA1_Channel5;
    hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_spi2_tx.Init.Mode = DMA_NORMAL;
    hdma_spi2_tx.Init.Priority = DMA_PRIORITY_HIGH;
    if (HAL_DMA_Init(&hdma_spi2_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi2_tx);

    /* SPI2_RX Init */
    hdma_spi2_rx.Instance = DMA1_Channel4;
    hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_spi2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi2_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_spi2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_spi2_rx.Init.Mode = DMA_NORMAL;
    hdma_spi2_rx.Init.Priority = DMA_PRIORITY_HIGH;
    if (HAL_DMA_Init(&hdma_spi2_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi2_rx);

  /* USER CODE BEGIN SPI2_MspInit 1 */

  /* USER CODE END SPI2_MspInit 1 */
  }
}

void SpiReset(void)//SPI重启
{
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_10,GPIO_PIN_SET);
	HAL_SPI_DeInit(&hspi2);
	MX_SPI2_Init();	
	Stat=HAL_SPI_TransmitReceive_DMA(&hspi2, (uint8_t*)y,(uint8_t*)SpiRecv, 9);
	if (Stat!=HAL_OK)
		HAL_NVIC_SystemReset();
	HAL_Delay(500);
	LastSpiRecvTick=SystemTimeTick;
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_10,GPIO_PIN_RESET);
}


void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
	int i;
	static int ErrorCount=0;
	
	y[0]=0xbb;
	for (i=1;i<9;i++)
		y[i]++;
	
	//数据传输错误判断
	if (SpiRecv[0]!=0x00AA)
		ErrorCount++;
	else
	{
		ErrorCount=0;
		LastSpiRecvTick=SystemTimeTick;
	}
	if (ErrorCount>10)//重启spi
	{
		ErrorCount=0;
		SpiReset();
	}	

	HAL_SPI_TransmitReceive_DMA(&hspi2, (uint8_t*)y,(uint8_t*)SpiRecv, 9);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_10,GPIO_PIN_RESET);
}

void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{	
	SpiReset();
}

STM32端同样要注意SPI通信的数据错位、乱序的问题,一定要做好超时、错误后的复位工作。

 

上一篇:第三章 寻址方式 第四章 程序编写和调试


下一篇:2020-11-23