軟件模擬SPI接口程序代碼(4種模式)

軟件模擬SPI接口程序代碼(4種模式)

 

SPI協議簡介

    SPI的通訊原理很簡單,通常主從方式工做,這種模式一般有一個主設備和一個或多個從設備,一般採用的是4根線,它們是MISO(數據輸入,針對主機來講)、MOSI(數據輸出,針對主機來講)、SCLK(時鐘,主機產生)、CS/SS(片選,通常由主機發送或者直接使能,一般爲低電平有效)測試

●SPI接口介紹ui

SCK:時鐘信號,由主設備產生,因此主設備SCK信號爲輸出模式,從設備的SCK信號爲輸入模式。spa

CS:使能信號,由主設備控制從設備,,因此主設備CS信號爲輸出模式,從設備的CS信號爲輸入模式。3d

MOSI:主設備數據輸出,從設備數據輸入,因此主設備MOSI信號爲輸出模式,從設備的MOSI信號爲輸入模式。code

MISO:主設備數據輸入,從設備數據輸出,因此主設備MISO信號爲輸入模式,從設備的MISO信號爲輸出模式。blog

SPI接口鏈接圖接口

                                                                                 

注意:MOSI和MISO不能交叉鏈接(能夠把主從機理解爲一個總體系統,MOSI爲系統主機發送從機接收的數據線,MISO爲主機接收從機發送的數據線)ip

●SPI數據傳輸方向it

 SPI做爲全雙工的的串行通訊協議,數據傳輸時高位在前,低位在後。主機和從機公用由主機產生的SCK信號,因此在每一個時鐘週期內主機和從機有1bit的數據交換(由於MOSI和MISO數據線上的數據都是在時鐘的邊沿處被採樣)。以下圖:io

                         

SPI協議規定數據採樣是在SCK的上升沿或降低沿時刻(由SPI模式決定,下面會說到),觀察上圖,在SCK的邊沿處(上升沿或降低沿),主機會在MISO數據線上採樣(接收來從機的數據),從機會在MOSI數據線上採樣(接收來自主機的數據),因此每一個時鐘週期中會有一bit的數據交換。

                      

SPI傳輸模式

SPI總線傳輸一共有4種模式,這4種模式分別由時鐘極性(CPOL)和時鐘相位(CPHA)來定義。

                       

                        

CPOL:規定了SCK時鐘信號空閒狀態的電平 

CPHA:規定了數據是在SCK時鐘的上升沿仍是降低沿被採樣

----------- ------------------------------------

模式0:CPOL=0,CPHA =0  SCK空閒爲低電平,數據在SCK的上升沿被採樣(提取數據)

模式1:CPOL=0,CPHA =1  SCK空閒爲低電平,數據在SCK的降低沿被採樣(提取數據)

模式2:CPOL=1,CPHA =0  SCK空閒爲高電平,數據在SCK的降低沿被採樣(提取數據)

模式3:CPOL=1,CPHA =1  SCK空閒爲高電平,數據在SCK的上升沿被採樣(提取數據)

以模式0爲例:SCK空閒爲低電平,數據在SCK的上升沿被採樣(提取數據),在SCK的降低沿切換數據線的數據。

            

在時鐘的第1個上升沿(遊標1處)(採樣點)

  MOSI上數據爲1,則在此邊沿從機採樣(提取)數據爲1,採樣點在MOSI數據線的中間。

  MISO上數據爲0,則在此邊沿主機採樣(提取)數據爲0,採樣點在MISO數據線的中間。

在時鐘的第1個降低沿(遊標2處)(切換點)

  MOSI上數據由1切換爲0,,數據在時鐘降低沿時切換數據。

  MISO上數據由0切換爲1,,數據在時鐘降低沿時切換數據。

在時鐘的第2~8個上升沿(採樣點),主機在MISO上採樣數據,從機在MOSI上採樣數據。

在時鐘的第2~8個降低沿(切換點),主機在MISO上切換數據,從機在MOSI上切換數據

 

經過模擬SPI程序來加深理解

使用STM32L4R5ZI MCU進行的測試

初始化代碼

/**SPI1 GPIO Configuration    
    PA5     ------> SPI1_SCK
    PA6     ------> SPI1_MISO
    PA7     ------> SPI1_MOSI 
    */
#define SPI_SCK_PIN                     GPIO_PIN_5
#define SPI_SCK_GPIO_PORT               GPIOA
#define SPI_MOSI_PIN                    GPIO_PIN_7
#define SPI_MOSI_GPIO_PORT              GPIOA
#define SPI_MISO_PIN                    GPIO_PIN_6
#define SPI_MISO_GPIO_PORT              GPIOA
#define SPI_NSS_PIN                     GPIO_PIN_14
#define SPI_NSS_GPIO_PORT               GPIOD


#define SPI_SCK_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_MISO_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_MOSI_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_NSS_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOD_CLK_ENABLE()

#define MOSI_H  HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_SET)  
#define MOSI_L  HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_RESET)  
#define SCK_H   HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_SET)  
#define SCK_L   HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_RESET)  
#define MISO    HAL_GPIO_ReadPin(SPI_MISO_GPIO_PORT, SPI_MISO_PIN) 
#define NSS_H   HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET)  
#define NSS_L   HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET) 
	·

void SPI_Init(void)
{  
  /*##-1- Enable peripherals and GPIO Clocks #########################*/
  /* Enable GPIO TX/RX clock */
  SPI_SCK_GPIO_CLK_ENABLE();
  SPI_MISO_GPIO_CLK_ENABLE();
  SPI_MOSI_GPIO_CLK_ENABLE();
  SPI_NSS_GPIO_CLK_ENABLE();


  /*##-2- Configure peripheral GPIO #######################*/
  /* SPI SCK GPIO pin configuration  */
  GPIO_InitTypeDef GPIO_InitStruct;
  
  GPIO_InitStruct.Pin       = SPI_SCK_PIN;
  GPIO_InitStruct.Mode      = GPIO_MODE_OUTPUT_PP;
  //GPIO_InitStruct.Pull      = GPIO_PULLDOWN;
  GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
  HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_SET);

  /* SPI MISO GPIO pin configuration  */
  GPIO_InitStruct.Pin = SPI_MISO_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  HAL_GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);

  /* SPI MOSI GPIO pin configuration  */
  GPIO_InitStruct.Pin = SPI_MOSI_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  HAL_GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
  HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_SET);
  
  GPIO_InitStruct.Pin = SPI_NSS_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  HAL_GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
  HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
  
}

 模擬SPI4種工做模式

/* CPOL = 0, CPHA = 0, MSB first */
uint8_t SOFT_SPI_RW_MODE0( uint8_t write_dat )
{
    uint8_t i, read_dat;
    for( i = 0; i < 8; i++ )
    {
        if( write_dat & 0x80 )
            MOSI_H;  
        else                    
            MOSI_L;  
        write_dat <<= 1;
        delay_us(1);	
        SCK_H; 
        read_dat <<= 1;  
        if( MISO ) 
            read_dat++; 
		delay_us(1);
        SCK_L; 
        __nop();
    }
	
    return read_dat;
}


/* CPOL=0,CPHA=1, MSB first */
uint8_t SOFT_SPI_RW_MODE1(uint8_t byte) 
{
    uint8_t i,Temp=0;

	for(i=0;i<8;i++)     // 循環8次
	{
		SCK_H;     //拉高時鐘
		if(byte&0x80)
        {
			MOSI_H;  //若最到位爲高,則輸出高
        }
		else      
		{
			MOSI_L;   //若最到位爲低,則輸出低
		}
		byte <<= 1;     // 低一位移位到最高位
		delay_us(1);
		SCK_L;     //拉低時鐘
		Temp <<= 1;     //數據左移

		if(MISO)
			Temp++;     //若從從機接收到高電平,數據自加一
		delay_us(1);

	}
	return (Temp);     //返回數據
}

/* CPOL=1,CPHA=0, MSB first */
uint8_t SOFT_SPI_RW_MODE2(uint8_t byte) 
{
    uint8_t i,Temp=0;

	for(i=0;i<8;i++)     // 循環8次
	{
		if(byte&0x80)
        {
			MOSI_H;  //若最到位爲高,則輸出高
        }
		else      
		{
			MOSI_L;   //若最到位爲低,則輸出低
		}
		byte <<= 1;     // 低一位移位到最高位
		delay_us(1);
		SCK_L;     //拉低時鐘
		Temp <<= 1;     //數據左移

		if(MISO)
			Temp++;     //若從從機接收到高電平,數據自加一
		delay_us(1);
		SCK_H;     //拉高時鐘
		
	}
	return (Temp);     //返回數據
}


/* CPOL = 1, CPHA = 1, MSB first */
uint8_t SOFT_SPI_RW_MODE3( uint8_t write_dat )
{
    uint8_t i, read_dat;
    for( i = 0; i < 8; i++ )
    {
		SCK_L; 
        if( write_dat & 0x80 )
            MOSI_H;  
        else                    
            MOSI_L;  
        write_dat <<= 1;
        delay_us(1);	
        SCK_H; 
        read_dat <<= 1;  
        if( MISO ) 
            read_dat++; 
		delay_us(1);
        __nop();
    }
    return read_dat;
}
相關文章
相關標籤/搜索