軟件模擬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; }