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