STM32開發筆記11—SPI


SPI特色

SPI為一種同步的全雙工串列通信介面,常用於連接周邊晶片,例如:ADC、DAC、通訊IC等

  • 一部主機可以與多台從機通訊,但需要分時進行
  • 如果從機太多可使用GPIO來取代SSL線
  • 主機設定從機的SSL腳位為低準位作為選擇
  • 一般傳輸速率為5M/10M/20M bps
  • SPI是利用Rising edge, Falling edge決定資料的採樣
  • 4條主要訊號線
    • MOSI
      • Master Output, Slave Input
    • MISO
      • Master Input, Slave Output
    • SCK
      • 串列時脈,由master發出
      • 時鐘訊號線
      • 有時鐘訊號線,因此為同步通訊
      • SCK最高頻率被限制在$f_{pclk}$
    • SSI/NSS/CS
      • Slave Selected,master選擇要跟哪個slave進行通訊
      • 主機將從機SSI拉低成低電位,並將其他從機拉成高電位,藉此與特定從機溝通
      • 同理將SSL電位拉高,結束與從機之間的通訊
  • SPI為全雙工
    • 有兩條數據線,可以同時收發數據
  • NSS啟動通訊
  • SCK
    • SCK邊緣觸發
      • 上升/下降緣
      • 告知訊號可以發生變化了
    • SCK邊緣採樣
      • 上升/下降緣
      • 採樣對應的輸入輸出
  • CPOL
    • Clock Polarity
    • 選擇待機模式下的SCK要保持高準位還是低準位
      • CPOL=1 -> 待機高準位
      • CPOL=0 -> 待機低準位
  • CPHA
    • Clock Phase
    • 選擇資料再SCK的基數次變化或是偶數次變化
    • CPHA=0 -> 奇次讀取
    • CPHA=1 -> 偶次讀取
    • 例如選擇奇數次讀取,對於CPOL=0來說是上升緣採樣;對於CPOL=1來說就是下降緣採樣

  • 因此將CPOL與CPHA結合可以決定資料在哪種狀態下讀取。如果在Rising edge讀取數據,則發送端在Falling edge進行資料的轉態

其中CPOL=0,CPHA=0(奇數邊緣)以及CPOL=1,CPHA=1(偶數邊緣)使用最多。實踐使用時Master, Slaves之間要協調好要使用哪種模式進行溝通

STM32編寫SPI

  • 數據手冊查看相應PIN腳
  • 控制暫存器CR1的BR位控制$f_{pclk}$分頻因子
    • $f_{pclk}$指的是SPI所在總線上的總線頻率
    • APB1為$f_{pclk1}$
    • APB2為$f_{pclk2}$
  • 通過設置控制暫存器CR1的CPOL以及CPHA位,可以將SPI設定成前面提到的四種模式之一
  • 透過控制暫存器CR1的DEF位,可以配置成8, 16bits模式
  • 透過控制暫存器CR1的LSBFIRST位可以選擇MSB先行還是LSB先行
  • 控制暫存器CR2
    • TXEIE=1時,開啟TX中斷
    • RXNEIE=1時,開啟RX中斷
  • 狀態暫存器SR
    • RXNE=1時,接收暫存器非空
    • TXE=1時,發送暫存器為空
    • BSY=1時,表示SPI正在工作
    • FRE=1時,幀格式發生錯誤
  • 操作DR暫存器能夠控制SPI的讀寫
    • 對DR進行寫入,會將資料寫入TX buffer
    • 對DR進行讀取,會將RX buffer的資料讀取出來

閱讀datasheet重點

  • 看特色
    • Flash地址長度
    • 分塊、扇區擦除大小
    • 寫入數據大小範圍
    • 最高運行頻率
    • 電壓工作範圍
    • etc.
  • 大致了解各個PIN腳定義
  • flash中的SPI的功能框圖提及到工作模式選項(Mode 0, 3)
  • 狀態暫存器的介紹
    • 重要暫存器
      • BUSY位
      • WEL位
  • 關注指令集
    • 讀寫操作
    • 擦除操作
    • 讀狀態操作
    • ID與製造商消息等
  • 在寫code前要了解MCU與flash之間交互的機制
    • CS PIN拉低拉高代表的意義
    • 檢查flash狀態暫存器的bit位
    • 關注SPI的SR暫存器每個比特位的意義
    • dummy指令可以用來使flash回傳資料
    • 擦除、寫入等操作前需要先開啟Write Enable
    • 指令中的地址是MSB先行
    • 每個指令之間都存在些許差異,編寫前需要詳細閱讀指令介紹

程式碼範例

  • 通用標頭檔
#ifndef __BSP_SPI_H_
#define __BSP_SPI_H_

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stdint.h"

// RCC CLOCK
/*****************************************/
// SPI
#define SPI_RCC_CLK_CMD                 RCC_APB2PeriphClockCmd
#define SPI_RCC_CLK_PERIPH             RCC_APB2Periph_SPI5
#define SPI_RCC_CLK_OP                     ENABLE 

// GPIO
#define GPIO_RCC_CLK_CMD                 RCC_AHB1PeriphClockCmd
#define GPIO_RCC_CLK_PERIPH             RCC_AHB1Periph_GPIOF
#define GPIO_RCC_CLK_OP                     ENABLE

/*****************************************/

// pin define
/*****************************************/
// CSS
#define SPI_CSS_GPIO_PORT        GPIOF
#define SPI_CSS_GPIO_PIN         GPIO_Pin_6
#define SPI_CSS_GPIO_MODE               GPIO_Mode_OUT
#define SPI_CSS_GPIO_SPEED             GPIO_Fast_Speed
#define SPI_CSS_GPIO_PUPD                 GPIO_PuPd_NOPULL
#define SPI_CSS_GPIO_OTYPE             GPIO_OType_PP 

// SCLK
#define SPI_SCLK_GPIO_PORT       GPIOF
#define SPI_SCLK_GPIO_PIN        GPIO_Pin_7
#define SPI_SCLK_GPIO_MODE           GPIO_Mode_AF
#define SPI_SCLK_GPIO_PIN_SOURCE GPIO_PinSource7
#define SPI_SCLK_GPIO_AF                 GPIO_AF_SPI5


// MISO
#define SPI_MISO_GPIO_PORT       GPIOF
#define SPI_MISO_GPIO_PIN        GPIO_Pin_8
#define SPI_MISO_GPIO_MODE           GPIO_Mode_AF
#define SPI_MISO_GPIO_PIN_SOURCE GPIO_PinSource8
#define SPI_MSIO_GPIO_AF                 GPIO_AF_SPI5


// MOSI
#define SPI_MOSI_GPIO_PORT       GPIOF
#define SPI_MOSI_GPIO_PIN        GPIO_Pin_9
#define SPI_MOSI_GPIO_MODE           GPIO_Mode_AF
#define SPI_MOSI_GPIO_PIN_SOURCE GPIO_PinSource9
#define SPI_MOSI_GPIO_AF                 GPIO_AF_SPI5

// CS pin control
#define FLASH_SPI_CSS_LOW()             GPIO_ResetBits(SPI_CSS_GPIO_PORT, SPI_CSS_GPIO_PIN)
#define FLASH_SPI_CSS_HIGH()         GPIO_SetBits(SPI_CSS_GPIO_PORT, SPI_CSS_GPIO_PIN)

/*****************************************/

// SPI
/*****************************************/
#define FLASH_SPI                              SPI5
#define FLASH_SPI_OP                         ENABLE 
#define FLASH_SPI_BRP                         SPI_BaudRatePrescaler_2
#define FLASH_SPI_CPHA                     SPI_CPHA_2Edge 
#define FLASH_SPI_CPOL                     SPI_CPOL_High 
#define FLASH_SPI_CRC                         7           
#define FLASH_SPI_DATA_SIZE      SPI_DataSize_8b
#define FLASH_SPI_DIRECTION             SPI_Direction_2Lines_FullDuplex 
#define FLASH_SPI_FIRSTBIT             SPI_FirstBit_MSB
#define FLASH_SPI_MODE                     SPI_Mode_Master
#define FLASH_SPI_NSS                         SPI_NSS_Soft

/*****************************************/

// Flash Command
/*****************************************/
#define FLASH_DUMMY                             0xFF
#define FLASH_SPI_JEDEC                     0x9F
#define FLASH_WRITE_EN                     0x06
#define FLASH_READ_STATUS_REG         0x05
#define FLASH_POWER_DOWN                0xB9
#define FLASH_RELEASE_POWER_DOWN 0xAB
#define FLASH_ERASE_SECTOR             0x20
#define FLASH_READ_DATA                     0x03
#define FLASH_PAGE_PROGRAME             0x02

/*****************************************/

// Functions
/*****************************************/
void                                                          Flash_Init(void);
uint32_t                                               Flash_Read_WRITE_JEDEC_ID(void);
uint32_t                                               SPI_FLASH_ReadID(void);
void                                                       Flash_Write_Enable(void);
void                                                          Flash_Wait_For_Ready(void);
void                                                          Flash_Power_Down(void);
uint32_t                                               Flash_Release_Power_Down(void);
void                                                          Flash_Erase_Sector(uint32_t addr);
void                                                          Flash_Read_Data(uint32_t addr, uint8_t* buffer, uint32_t count);
void                                                          Flash_Write_Data(uint32_t addr, uint8_t* data, int count);

/*****************************************/

#endif /* __BSP_SPI_H_ */
  • 幾個常見操作
    • 讀寫
    • 製造商信息
    • 暫存器讀寫
    • power down/release操作等
#include "bsp_spi.h"

/*
 * GPIO Init function
 */
void Flash_GPIO_Init(){
    GPIO_InitTypeDef gpio_init;

    /* RCC Enable */
    GPIO_RCC_CLK_CMD(GPIO_RCC_CLK_PERIPH, GPIO_RCC_CLK_OP);

    /* Config AF Pin */
    GPIO_PinAFConfig(SPI_SCLK_GPIO_PORT, SPI_SCLK_GPIO_PIN_SOURCE, SPI_SCLK_GPIO_AF);
    GPIO_PinAFConfig(SPI_MISO_GPIO_PORT, SPI_MISO_GPIO_PIN_SOURCE, SPI_MSIO_GPIO_AF);
    GPIO_PinAFConfig(SPI_MOSI_GPIO_PORT, SPI_MOSI_GPIO_PIN_SOURCE, SPI_MOSI_GPIO_AF);   

    /* configure css pin */
    gpio_init.GPIO_Pin = SPI_CSS_GPIO_PIN;
    gpio_init.GPIO_Mode = SPI_CSS_GPIO_MODE;
    gpio_init.GPIO_Speed = SPI_CSS_GPIO_SPEED;
    gpio_init.GPIO_PuPd = SPI_CSS_GPIO_PUPD;
    gpio_init.GPIO_OType = SPI_CSS_GPIO_OTYPE;
    GPIO_Init(SPI_CSS_GPIO_PORT, &gpio_init);

    /* configure sclk pin */
    gpio_init.GPIO_Pin = SPI_SCLK_GPIO_PIN;
    gpio_init.GPIO_Mode = SPI_SCLK_GPIO_MODE;
    GPIO_Init(SPI_SCLK_GPIO_PORT, &gpio_init);

    /* configure miso pin */
    gpio_init.GPIO_Pin = SPI_MISO_GPIO_PIN;
    GPIO_Init(SPI_MISO_GPIO_PORT, &gpio_init);

  /* configure mosi pin */
    gpio_init.GPIO_Pin = SPI_MOSI_GPIO_PIN;
    GPIO_Init(SPI_MOSI_GPIO_PORT, &gpio_init);
}


/*
 * SPI Init function
 */
void Flash_SPI_Init(){
    SPI_InitTypeDef spi_init;

    /* RCC Enable */
    SPI_RCC_CLK_CMD(SPI_RCC_CLK_PERIPH, SPI_RCC_CLK_OP);

    /* Stop Flash Signal */
    FLASH_SPI_CSS_HIGH();

    /* configure SPI */
    spi_init.SPI_BaudRatePrescaler = FLASH_SPI_BRP;
    spi_init.SPI_CPHA = FLASH_SPI_CPHA;
    spi_init.SPI_CPOL = FLASH_SPI_CPOL;
    spi_init.SPI_CRCPolynomial = FLASH_SPI_CRC;
    spi_init.SPI_DataSize = FLASH_SPI_DATA_SIZE;
    spi_init.SPI_Direction = FLASH_SPI_DIRECTION;
    spi_init.SPI_FirstBit = FLASH_SPI_FIRSTBIT;
    spi_init.SPI_Mode = FLASH_SPI_MODE;
    spi_init.SPI_NSS = FLASH_SPI_NSS;
    SPI_Init(FLASH_SPI, &spi_init);

    /* Enable SPI Flash */
    SPI_Cmd(FLASH_SPI, FLASH_SPI_OP);
}


/*
 * API interface
 */
void Flash_Init(){
    Flash_GPIO_Init();
    Flash_SPI_Init();
}


/*
 * Flash Send & Read operation
 */
static uint8_t SPI_FLASH_SendByte(uint8_t byte){

  while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET){}

  SPI_I2S_SendData(FLASH_SPI, byte);

  while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET){}

  return SPI_I2S_ReceiveData(FLASH_SPI);
}


/**
 * Read Flash data
 */
static uint8_t SPI_FLASH_ReadByte(){
    return SPI_FLASH_SendByte(FLASH_DUMMY);
}    


/*
 * FLASH W25Q256JV read/write JEDEC ID command
 */
uint32_t Flash_Read_WRITE_JEDEC_ID(){
    uint32_t ManufacturerID, MemoryID, CapacityID;

    /* CS LOW */
    FLASH_SPI_CSS_LOW();

    /* send command to flash */ 
    SPI_FLASH_SendByte(FLASH_SPI_JEDEC);

    /* read data */
    ManufacturerID = SPI_FLASH_ReadByte();
    MemoryID = SPI_FLASH_ReadByte();
    CapacityID = SPI_FLASH_ReadByte();

    /* CS HIGH */   
    FLASH_SPI_CSS_HIGH();

    return (ManufacturerID << 16) | (MemoryID << 8) | CapacityID;
}


/*
 * Flash Write Enable
 */
void Flash_Write_Enable(){
    FLASH_SPI_CSS_LOW();
    SPI_FLASH_SendByte(FLASH_WRITE_EN);
    FLASH_SPI_CSS_HIGH();
}


/*
 * Wait until BUSY & WEL become 0
 */
void Flash_Wait_For_Ready(){
    uint8_t status=0;
    FLASH_SPI_CSS_LOW();
    SPI_FLASH_SendByte(FLASH_READ_STATUS_REG);

    do{
        status = SPI_FLASH_SendByte(FLASH_DUMMY);
    }while((status & 0x01) == SET);

    FLASH_SPI_CSS_HIGH();
}


/*
 * Flash Power Down
 */
void Flash_Power_Down(){
    FLASH_SPI_CSS_LOW();
    SPI_FLASH_SendByte(FLASH_POWER_DOWN);
    FLASH_SPI_CSS_HIGH();
}


/*
 * Flash Release Power Down
 */
uint32_t Flash_Release_Power_Down(){
    uint32_t rev;
    FLASH_SPI_CSS_LOW();
    SPI_FLASH_SendByte(FLASH_RELEASE_POWER_DOWN);

    SPI_FLASH_SendByte(FLASH_DUMMY);
    SPI_FLASH_SendByte(FLASH_DUMMY);
    SPI_FLASH_SendByte(FLASH_DUMMY);    
    rev = SPI_FLASH_SendByte(FLASH_DUMMY);
    FLASH_SPI_CSS_HIGH();

    return rev;
}


/*
 * Flash Erase Sector
 */
void Flash_Erase_Sector(uint32_t addr){
    Flash_Write_Enable();

    FLASH_SPI_CSS_LOW();
    SPI_FLASH_SendByte(FLASH_ERASE_SECTOR);
    SPI_FLASH_SendByte((addr >> 24) & 0xff);
    SPI_FLASH_SendByte((addr >> 16) & 0xff);
    SPI_FLASH_SendByte((addr >> 8) & 0xff);
    SPI_FLASH_SendByte(addr & 0xff);
    FLASH_SPI_CSS_HIGH();

    Flash_Wait_For_Ready();
}


/*
 * Flash Read Data
 */
void Flash_Read_Data(uint32_t addr, uint8_t* buffer, uint32_t count){
    Flash_Wait_For_Ready();
    FLASH_SPI_CSS_LOW();
    SPI_FLASH_SendByte(FLASH_READ_DATA);
    SPI_FLASH_SendByte((addr >> 24) & 0xff);
    SPI_FLASH_SendByte((addr >> 16) & 0xff);
    SPI_FLASH_SendByte((addr >> 8) & 0xff);
    SPI_FLASH_SendByte(addr & 0xff);

  while (count--){
    *(buffer++) = SPI_FLASH_SendByte(FLASH_DUMMY);
  }

    FLASH_SPI_CSS_HIGH();
}


/*
 * Flash Page Write
 */
void Flash_Write_Data(uint32_t addr, uint8_t* data, int count){    
    Flash_Write_Enable();   
    FLASH_SPI_CSS_LOW();
    SPI_FLASH_SendByte(FLASH_PAGE_PROGRAME);
    SPI_FLASH_SendByte((addr >> 24) & 0xff);
    SPI_FLASH_SendByte((addr >> 16) & 0xff);
    SPI_FLASH_SendByte((addr >> 8) & 0xff);
    SPI_FLASH_SendByte(addr & 0xff);

    while(count--){
        SPI_FLASH_SendByte(*data);
        data++;
    }

    FLASH_SPI_CSS_HIGH();
    Flash_Wait_For_Ready();
}

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

%d 位部落客按了讚: