SPI簡介
SPI為串行外設接口,全稱Serial Peripheral interface,是一種全雙工、同步的通信總線,廣泛用于不同設備之間的板級通訊。
MM32F0140的SPI支持接收和發送1 ~ 32位數據同時進行,主模式最大速率24Mbps,從模式最大速率12Mbps,支持一個主機與多個從機操作,支持DMA操作。
數據通信
在進行SPI數據通信時,通常由MOSI、MISO、SCK、NSS四個管腳與外部器件相連。如圖1所示,MOSI管腳將來自主設備的數據輸入到從設備,MISO管腳將從設備響應的數據傳入主設備,從設備通過SCK管腳獲得主設備提供的時鐘信號,使發送和接收部分使用相同的時鐘,保證數據傳輸的可靠性。NSS管腳進行從設備選擇,使主設備可以和某個從設備一對一單獨通信。
主設備數據輸入(MISO)
MISO為主設備輸入從設備輸出管腳,傳輸方向為從設備發送到主設備。
主設備數據輸出(MOSI)
MOSI為主設備輸出從設備輸入管腳,傳輸方向為主設備發送到從設備。
時鐘(SCK)
SCK為串口時鐘,控制數據交換的速率,由主設備產生,通過SCK引腳傳輸供從設備使用。
片選(NSS)
NSS為從設備選擇管腳,SPI通過控制片選管腳NSS來控制多個從設備。當NSS引腳功能被激活后,配置作為主設備的SPI進入主模式,將會拉低NSS引腳,其余連接到主設備NSS的SPI設備由于檢測到了NSS拉低的信號,會自動進入從設備模式。
圖1.SPI數據通信
數據傳輸時序
SPI可以通過配置時鐘極性(CPOL)與相位(CPHA)選擇四種不同的數據傳輸時序,即四種工作方式。
時鐘極性(CPOL)
時鐘極性是SCK在空閑時保持的電平狀態,當CPOL配置為0時,SCK在空閑狀態為低電平,即兩次傳輸之間為低電平;當CPOL配置為1時,SCK在空閑狀態為高電平,即兩次傳輸之間為高電平。
時鐘相位(CPHA)
時鐘相位用于決定采樣時刻,當CPHA配置為0時,第一個數據位采樣從第一個時鐘邊沿開始;當CPHA配置為1時,第一個數據位采樣從第二個時鐘邊沿開始;對數據進行邊沿采樣需要CPOL與CPHA的共同配置決定。
SCK低電平空閑,第一個沿采樣
當CPOL=0且CPHA=0,第一位數據位在SCK時鐘的第一個時鐘上升沿被采樣,如圖2所示。
圖2.SCK低電平空閑且第一個沿采樣
SCK高電平空閑,第一個沿采樣
當CPOL=1且CPHA=0,第一位數據位的SCK時鐘的第一個時鐘下降沿被采樣,如圖3所示。
圖3.SCK高電平空閑且第一個沿采樣
SCK低電平空閑,第二個沿采樣
當CPOL=0且CPHA=1,第一個數據位在SCK時鐘的第二個時鐘下降沿被采樣,如圖4所示。
圖4.SCK低電平空閑且第二個沿采樣
SCK高電平空閑,第二個沿采樣
當CPOL=1且CPHA=1,第一個數據位在SCK時鐘的第二個時鐘上升沿被采樣,如圖5所示。
圖5.SCK高電平空閑且第二個沿采樣
SPI配置
主模式
通過配置波特率發生器(SPI_I2S_SPBREG)設定串行時鐘波特率,公式為:波特率=fpclk/SPBRG (fpclk是APB時鐘頻率)。配置通用控制寄存器(SPI_I2S_CCTL)的SPI數據寬度位(SPILEN),決定數據幀的長度是7位還是8位;配置時鐘相位選擇位(CPHA)與時鐘極性標志位(CPOL),確定時序模式,為保證數據正常傳輸,主從設備的時序模式應保持配置一致;配置LSB在前使能位(LSBFE),決定數據位的輸出順序。操作全局控制寄存器(SPI_I2S_GCTL)的DW8_32位進行發送和接收數據寄存器有效數據選擇,可配置為只有低8位有效或32位數據都有效;操作主機模式位(MODE)為1,選擇主機模式;配置SPI/I2S選擇位(SPIEN)為1,使能SPI。
若只接收而不發送數據,則配置接收數據個數寄存器(SPI_I2S_RXDNR),定義下次接收過程中需要接收字節的個數。
從模式
配置通用控制寄存器(SPI_I2S_CCTL)的LSB在前使能位(LSBFE),決定數據位的輸出順序是從最低有效位到最高有效位或從最高有效位到最低有效位;配置SPI數據寬度位(SPILEN),決定數據幀的長度是7位還是8位;配置時鐘相位選擇位(CPHA)與時鐘極性標志位(CPOL),決定時序模式。配置全局控制寄存器(SPI_I2S_GCTL)的主機模式位(MODE)為0,選擇從機模式;配置SPI/I2S選擇位(SPIEN)為1,使能SPI。
數據發送
主模式
將需要發送的數據寫入發送數據寄存器(SPI_I2S_TXREG),該寄存器的有效位由全局控制器(SPI_I2S_GCTL)的DW8_32位控制。在發送第一個數據位時,整個數據被傳輸到移位寄存器,后續數據通過移位寄存器串行輸出到MOSI引腳。當中斷狀態寄存器(SPI_I2S_INTSTAT)的發送緩沖器有效中斷標志位(TX_INTF)被置1,數據已從發送緩沖器被傳輸到移位寄存器。
從模式
當從設備收到SCK傳來的時鐘信號,同時接收到MOSI引腳傳輸的第一個數據位,從設備開始發送,第一個位被發送到MISO引腳,其余bit位被傳輸到移位寄存器,通過移位寄存器將數據串行發送。當中斷狀態寄存器(SPI_I2S_INTSTAT)的發送緩沖器有效中斷標志位(TX_INTF)被置1,表示第一位已發送,其余位被傳輸到移位寄存器。
數據接收
主模式
從MISO引腳接收數據,數據通過移位寄存器,在最后一個采樣時鐘邊沿后,數據字節被傳輸到接收緩沖器中。當中斷狀態寄存器(SPI_I2S_INTSTAT)的接收端數據有效中斷標志位(RX_INTF)置1,數據接收完成,主模式下不再發送時鐘信號。
從模式
從MOSI引腳接收數據,數據通過移位寄存器,在最后一個采樣時鐘邊沿后,數據字節被傳輸到接收緩沖器中。當中斷狀態寄存器(SPI_I2S_INTSTAT)的接收端數據有效中斷標志位(RX_INTF)置1,數據接收完成。
實驗
本實驗為回環測試,通過使用杜邦線連接SPI的MISO與MOSI引腳,實現數據的發送與接收。配置SPI主機,SPI進行一次數據發送與接收并對發送與接收信息進行驗證,并通過串口打印傳輸情況,若有發送與接收數據不同的情況,串口打印出錯信息與出錯個數,若驗證成功則打印"spi loopback xfer done."。
啟用外設時鐘 enable_clock()
實驗使用SPI1,且需要通過串口打印實驗現象,因此需啟用SPI1與UART的外設時鐘。
{ /* Enable UART1 clock. */ RCC->APB2ENR |= RCC_APB2_PERIPH_UART1; /* Enable GPIOA clock. */ RCC->AHB1ENR |= RCC_AHB1_PERIPH_GPIOA; /* Enable SPI1 clock. */ RCC->APB2ENR |= RCC_APB2_PERIPH_SPI1; }
配置引腳 pin_init()
配置SPI的NSS(PA4)、MOSI(PA7)、MISO(PA6)、SCK(PA5)引腳,因為實驗現象通過串口顯示,所以配置UART的TX(PA9)與RX(PA10)引腳。
void pin_init() { /* Setup NSS(PA4). */ GPIOA->CHL = ~GPIO_CRL_MODE4_MASK; GPIOA->CHL |= (GPIO_PinMode_AF_PushPull << GPIO_CRL_MODE4_SHIFT); /* PA4 multiplexed push-pull output. */ GPIOA->AFRL = ~GPIO_AFRL_AFR_MASK; GPIOA->AFRL |= (GPIO_AF_0 << GPIO_CRL_MODE4_SHIFT); /* Use AF0. */ /* Setup MOSI(PA7). */ GPIOA->CHL = ~GPIO_CRL_MODE7_MASK; GPIOA->CHL |= (GPIO_PinMode_AF_PushPull << GPIO_CRL_MODE7_SHIFT); /* PA7 multiplexed push-pull output. */ GPIOA->AFRL |= (GPIO_AF_0 << GPIO_CRL_MODE7_SHIFT); /* Use AF0. */ /* Setup MISO(PA6). */ GPIOA->CHL = ~GPIO_CRL_MODE6_MASK; GPIOA->CHL |= (GPIO_PinMode_In_Floating << GPIO_CRL_MODE6_SHIFT); /* PA6 floating input. */ GPIOA->AFRL |= (GPIO_AF_0 << GPIO_CRL_MODE6_SHIFT); /* Use AF0. */ /* Setup SCK(PA5). */ GPIOA->CHL = ~GPIO_CRL_MODE5_MASK; GPIOA->CHL |= (GPIO_PinMode_AF_PushPull << GPIO_CRL_MODE5_SHIFT); /* PA5 floating input. */ GPIOA->AFRL |= (GPIO_AF_0 << GPIO_CRL_MODE5_SHIFT); /* Use AF0. */ /* Setup PA9, PA10. */ GPIOA->CRH = ~GPIO_CRH_MODE9_MASK; GPIOA->CRH |= (GPIO_PinMode_AF_PushPull << GPIO_CRH_MODE9_SHIFT); /* PA9 multiplexed push-pull output. */ GPIOA->AFRH = ~GPIO_AFRH_AFR_MASK; GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE9_SHIFT); /* Use AF1. */ GPIOA->CRH = ~GPIO_CRH_MODE10_MASK; GPIOA->CRH |= (GPIO_PinMode_In_Floating << GPIO_CRH_MODE10_SHIFT); /* PA10 floating input. */ GPIOA->AFRH |= (GPIO_AF_1 << GPIO_CRH_MODE10_SHIFT); /* Use AF1. */ }
UART初始化 uart_init()
初始化UART,配置時鐘頻率、波特率、數據長度、停止位、傳輸模式及是否使用校驗。
void uart_init() { /* Clear the corresponding bit to be used. */ UART1->CCR = ~( UART_CCR_PEN_MASK | UART_CCR_PSEL_MASK | UART_CCR_SPB0_MASK | UART_CCR_SPB1_MASK | UART_CCR_CHAR_MASK ); UART1->GCR = ~( UART_GCR_AUTOFLOWEN_MASK | UART_GCR_RXEN_MASK | UART_GCR_TXEN_MASK ); /* WordLength. */ UART1->CCR |= UART_CCR_CHAR_MASK; /* XferMode. */ UART1->GCR |= (UART_XferMode_RxTx << UART_GCR_RXEN_SHIFT); /* Setup baudrate, BOARD_DEBUG_UART_FREQ = 48000000u, BOARD_DEBUG_UART_BAUDRATE = 9600u. */ UART1->BRR = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) / 16u; UART1->FRA = (BOARD_DEBUG_UART_FREQ / BOARD_DEBUG_UART_BAUDRATE) % 16u; /* Enable UART1. */ UART1->GCR |= UART_GCR_UARTEN_MASK; }
SPI初始化 spi_init()
操作全局控制寄存器(SPI_I2S_GCTL)的MODE位,配置SPI為主模式,操作波特率發生器(SPI_I2S_SPBREG)配置波特率為400KHz,總線時鐘頻率為48MHz,操作通用控制寄存器(SPI_I2S_CCTL)的CPOL位與CPHA位配置通信模式,操作LSB在前使能位(LSBFE)令數據發送或接收最高位在前,操作全局控制寄存器對發送和接收數據寄存器有效數據進行選擇(DW8_32),配置為只有低8位有效,設置NSS位置1,使硬件控制主模式下的NSS輸出,配TXEN位與RXEN位置1,使能發送與接收;置SPI/I2S選擇位(SPIEN)為1,使能SPI。
void spi_init() { /* Master. */ SPI1->GCTL = SPI_I2S_GCTL_MODE_MASK; /* Master mode. */ /* XferMode. */ SPI1->GCTL |= (SPI_I2S_GCTL_RXEN_MASK | SPI_I2S_GCTL_TXEN_MASK); /* Enable TX and RX. */ /* AutoCS. */ SPI1->GCTL |= SPI_I2S_GCTL_NSS_MASK; /* NSS select signal that from hardware. */ /* BaudRate. */ SPI1->SPBRG = 120u; /* SPBRG = fpclk / baudrate = 48000000 / 400000 = 120. */ SPI1->CCTL = ~(SPI_I2S_CCTL_TXEDGE_MASK | SPI_I2S_CCTL_RXEDGE_MASK); /* Sampling data in the middle of transmission data bits. */ /* DataWidth. */ SPI1->GCTL = ~SPI_I2S_GCTL_DW832_MASK; /* Only the lower 8 bits are valid. */ /* CPOL CPHA. */ SPI1->CCTL = ~(SPI_I2S_CCTL_CPHA_MASK | SPI_I2S_CCTL_CPOL_MASK); /* CPOL = 0, CPHA = 0. */ /* LSB first enable bit. */ SPI1->CCTL = ~SPI_I2S_CCTL_LSBFE_MASK; /* The highest bit of data transmission or reception comes first. */ /* Enbale SPI. */ SPI1->GCTL |= SPI_I2S_GCTL_SPIEN_MASK; }
SPI發送數據 spi_putbyte()
當發送緩沖器未滿時,將數據傳入發送數據寄存器(SPI_I2S_TXRFG),根據初始化配置,數據低8位有效,通過MOSI引腳串行輸出。
void spi_putbyte(uint8_t c) { while (SPI_I2S_CSTAT_TXFULL_MASK SPI1->CSTAT) {} SPI1->TXREG = c; }
SPI接收數據 spi_getbyte()
當接收端緩沖器接收了一個完整字節時,讀接收數據寄存器(SPI_I2S_RXREG),返回接收數據。
uint8_t spi_getbyte() { while (0u == (SPI_I2S_CSTAT_RXAVL_MASK SPI1->CSTAT) ) {} return SPI1->RXREG; }
main()函數
main()函數結合上述操作,初始化SPI,定義發送數組spi_tx_buf[16]并賦值,將spi_tx_buf[16]數組中的數值進行發送,定義接收數組spi_rx_buf[16]對數據進行接收。本實驗使用杜邦線將MOSI引腳與MISO相連,因此,發送數據與接收數據應相同,將發送數組與接收數組的數值進行比較,定義變量spi_xfer_err_count用于計數發送與接收數值不同的數據個數。若發送與接收數值相同則串口輸出"spi loopback xfer done.",若不同則串口輸出傳輸錯誤與出錯個數。實驗現象如圖6所示。
int main() { enable_clock(); pin_init(); uart_init(); printf("spi_basic example.rn"); spi_init(); for (uint32_t i = 0u; i < 16u; i++) { spi_tx_buf[i] = i; } /* SPI xfer once. */ for (uint32_t i = 0u; i < 16u; i++) { spi_putbyte(spi_tx_buf[i]); spi_rx_buf[i] = spi_getbyte(); } /* validation. */ spi_xfer_err_count = 0u; for (uint32_t i = 0u; i < 16u; i++) { if (spi_rx_buf[i] != spi_tx_buf[i]) { spi_xfer_err_count++; } } if (spi_xfer_err_count == 0u) { printf("spi loopback xfer done.rn"); } else { printf("spi loopback xfer error. spi_xfer_err_count = %urn", (unsigned)spi_xfer_err_count); } while (1) {} }
圖6.實驗現象
來源: 靈動MM32MCU
-
mcu
+關注
關注
146文章
17019瀏覽量
350373 -
接口
+關注
關注
33文章
8526瀏覽量
150862 -
SPI
+關注
關注
17文章
1701瀏覽量
91345 -
通信總線
+關注
關注
0文章
44瀏覽量
9844
發布評論請先 登錄
相關推薦
評論