1. 程式人生 > >STM32 spi與FPGA的通訊

STM32 spi與FPGA的通訊

STM32 spi匯流排通訊


最近在研究SPI匯流排,至於協議和硬體描述就不多說了
四線包括時鐘、片選、接收、傳送


初始化SP
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //全雙工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                       //主模式
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;                  //16bit寬度
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;     //2--18MHz; 4--9MHz;  8--4.5MHz
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                    //高位在前
  SPI_InitStructure.SPI_CRCPolynomial = 7;
  SPI_Init(SPIx, &SPI_InitStructure);
  SPI_Cmd(SPIx, ENABLE);
  
SPI不能硬體控制CS,只能軟體來控,就是通過將NSS設為外部GPIO來控制。
像我所做的專案是使用STM32與FPGA通訊,而FPGA的SPI工作在這種一直狀態
作為主裝置的STM32,CS在傳輸資料的時候為低,傳輸完畢後必須拉高,這樣FPGA可以判斷出SPI的傳輸起止狀態。


FPGA的資料傳輸格式是16bit地址+16bit資料
對於讀16bit,實現如下


uint16_t spi_read(SPI_TypeDef* SPIx,uint32_t addr)
{  
  uint16_t value;
  uint16_t spi_nss;
  uint16_t add;
  uint32_t level;


  if(SPI1 == SPIx)
    spi_nss = SPI1_PIN_NSS;
  else if(SPI2 == SPIx)
    spi_nss = SPI2_PIN_NSS;


  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
  GPIO_ResetBits(GPIOA, spi_nss);  
  SPI_I2S_SendData(SPIx, addr); //0xf014 >> 2


  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
  SPI_I2S_SendData(SPIx, 0x0);


  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);
  SPI_I2S_ReceiveData(SPIx);


  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
  GPIO_SetBits(GPIOA, spi_nss);
     
  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);
  value = SPI_I2S_ReceiveData(SPIx);


  return value;
}
  
寫函式


void spi_write(SPI_TypeDef* SPIx,uint32_t addr, uint16_t value)
{


  uint16_t spi_nss;


  uint32_t level;


  if(SPI1 == SPIx)
    spi_nss = SPI1_PIN_NSS;
  else if(SPI2 == SPIx)
    spi_nss = SPI2_PIN_NSS;



  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
  GPIO_ResetBits(GPIOA, spi_nss);  
  SPI_I2S_SendData(SPIx, addr); 


  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
  SPI_I2S_SendData(SPIx, value); 


  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);
  SPI_I2S_ReceiveData(SPIx);


  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
  GPIO_SetBits(GPIOA, spi_nss);


  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);
  SPI_I2S_ReceiveData(SPIx);


}  
拿write函式舉例
只所以這麼設計是因為
如果是函式一開始就將NSS腳拉低,然後再去send,如下
  GPIO_ResetBits(GPIOA, spi_nss);  
  while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
  SPI_I2S_SendData(SPIx, addr); 
  
這樣在CS拉低一段時間後(時間大概有16個時鐘週期),才有CLK,這樣延時就會降低SPI的傳輸效率  
之前那種方式會在CS拉底後很快就有clk時鐘出來


之所以寫兩次再讀兩次而不是讀一次寫一次也是考慮到效率的問題


如果先寫一次再讀一次,看波形每個資料之間有比較大的空隙是沒有clk的,就是說在傳輸完一個數據後再
傳第二個會要等一段時間,這個對速度要求比較高的裝置是不允許的


還有值得注意的是:
如果SPI是主模式,那麼GPIO設定為
NSS是GPIO_Mode_Out_PP
CLK是GPIO_Mode_AF_PP
MOSI是GPIO_Mode_AF_PP
MISO是GPIO_Mode_IN_FLOATING


如果SPI是從模式,那麼GPIO設定為
NSS是GPIO_Mode_Out_PP
CLK是GPIO_Mode_IN_FLOATING
MOSI是GPIO_Mode_IN_FLOATING
MISO是GPIO_Mode_AF_PP