SPI通信協(xié)議深度解析:FPGA與STM32實戰(zhàn)
掃描二維碼
隨時隨地手機看文章
導(dǎo)言
SPI(Serial Peripheral Interface)是一種同步串行通信協(xié)議,被廣泛用于微控制器與傳感器、ADC、DAC、SRAM等外設(shè) IC 之間的連接。作為最常見的接口之一,SPI以其全雙工、點對點或多點通信的特性,允許多個設(shè)備共享同一總線,僅需少量引腳即可實現(xiàn)高效通信。在上一期,我們探討了SPI的工作原理和通信協(xié)議。為了更深入地理解SPI,本期我們將進行SPI的項目實戰(zhàn)。
1項目介紹
本項目將使用STM32內(nèi)置的硬核SPI作為通信主機,同時將FPGA作為通信從機,以實現(xiàn)一個簡單的SPI通信系統(tǒng)。
系統(tǒng)的驗證方法:
STM32向FPGA發(fā)送數(shù)據(jù),通過STM32的按鈕向不同的FPGA從機發(fā)送數(shù)據(jù)。從機接收到數(shù)據(jù)后,將數(shù)據(jù)發(fā)送到FPGA的串口模塊,然后上傳至上位機。
FPGA向STM32發(fā)送數(shù)據(jù),通過上位機將數(shù)據(jù)發(fā)送到FPGA串口。FPGA串口再將數(shù)據(jù)發(fā)送給STM32主機,主機對所接收的數(shù)據(jù)進行校驗。如果校驗正確,則STM32上的LED燈亮起。
通過以上步驟驗證SPI系統(tǒng)的正確性。
本文主要描述FPGA和STM32關(guān)于SPI部分的核心代碼,相關(guān)的驗證代碼將在文末提供。
2設(shè)計思路
STM32F4的SPI通信特點如下:
一旦STM32的SPI啟動,SPI時鐘SCK將一直處于工作狀態(tài)。與預(yù)期不同的是,SCK并非僅在STM32讀取或?qū)懭霐?shù)據(jù)時才從空閑狀態(tài)轉(zhuǎn)換為翻轉(zhuǎn)狀態(tài)。
由此帶來的問題是,從機FPGA會因SCK的翻轉(zhuǎn)而持續(xù)接收數(shù)據(jù),導(dǎo)致從機FPGA無法獲取所需數(shù)據(jù)。解決這個問題的關(guān)鍵在于在STM32的輸出口定義一個CS片選信號。只有在讀寫數(shù)據(jù)時激活片選信號,以控制STM32的讀寫過程。
#define SPI_CS PBout(7)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(GPIOB, &GPIO_InitStructure); SPI_CS = 0;SPI1_ReadWriteByte(0xAB12);SPI_CS = 1;
FPGA亞穩(wěn)態(tài)問題:
由于需要接收CS片選信號,而其不在同一個時鐘域,為防止亞穩(wěn)態(tài),因此需要進行異步信號同步處理。在這里,我們?nèi)S_SYNC[1]作為FPGA內(nèi)部CS控制信號
always @(posedge clk or negedge rst_n)begin if(!rst_n) cs_sync <= 2'b11; else cs_sync <= {cs_sync[0],spi_cs};end
3STM32的SPI主機通信
以下是spi.c文件用于配置STM32內(nèi)置SPI的代碼。
//以下是SPI模塊的初始化代碼,配置成主機模式,模式3 //SPI口初始化//這里針是對SPI1的初始化void SPI1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitTypeDef GPIO_InitStructure1; SPI_InitTypeDef SPI_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB時鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能SPI1時鐘 //GPIOFB3,4,5初始化設(shè)置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;//PB3~5復(fù)用功能輸出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//復(fù)用功能 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;//100MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_7; GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure1.GPIO_OType = GPIO_OType_PP;//推挽輸出 GPIO_InitStructure1.GPIO_Speed = GPIO_High_Speed;//100MHz GPIO_InitStructure1.GPIO_PuPd = GPIO_PuPd_UP;//上拉 GPIO_Init(GPIOB, &GPIO_InitStructure1);//初始化 GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3復(fù)用為 SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1); //PB4復(fù)用為 SPI1 GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1); //PB5復(fù)用為 SPI1 //這里只針對SPI口初始化 RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE); //復(fù)位SPI1 RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE); //停止復(fù)位SPI1 SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; //設(shè)置SPI單向或者雙向的數(shù)據(jù)模式:SPI設(shè)置為單向 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //設(shè)置SPI工作模式:設(shè)置為主SPI SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b; //設(shè)置SPI的數(shù)據(jù)大小:SPI發(fā)送接收16位幀結(jié)構(gòu) SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步時鐘的空閑狀態(tài)為高電平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步時鐘的第二個跳變沿(上升或下降)數(shù)據(jù)被采樣 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信號由硬件(NSS管腳)還是軟件(使用SSI位)管理:內(nèi)部NSS信號有SSI位控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定義波特率預(yù)分頻的值:波特率預(yù)分頻值為256 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定數(shù)據(jù)傳輸從MSB位還是LSB位開始:數(shù)據(jù)傳輸從MSB位開始 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值計算的多項式 SPI_Init(SPI1, &SPI_InitStructure); //根據(jù)SPI_InitStruct中指定的參數(shù)初始化外設(shè)SPIx寄存器 SPI_Cmd(SPI1, ENABLE); //使能SPI外設(shè) SPI_CS = 0; SPI1_ReadWriteByte(0x0000);//啟動傳輸 SPI_CS = 1;} //SPI1速度設(shè)置函數(shù)//SPI速度=fAPB2/分頻系數(shù)//@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256 //fAPB2時鐘一般為84Mhz:void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler){ assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判斷有效性 SPI1->CR1&=0XFFC7;//位3-5清零,用來設(shè)置波特率 SPI1->CR1|=SPI_BaudRatePrescaler; //設(shè)置SPI1速度 SPI_Cmd(SPI1,ENABLE); //使能SPI1} //SPI1 讀寫一個字節(jié)//TxData:要寫入的字節(jié)//返回值:讀取到的字節(jié)void SPI1_ReadWriteByte(u16 TxData){ while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET){}//等待發(fā)送區(qū)空 SPI_I2S_SendData(SPI1, TxData); //通過外設(shè)SPIx發(fā)送一個byte 數(shù)據(jù)}
4FPGA的SPI從機通信
以下是spi_slave.v文件,是FPGA的SPI從機模塊。
module spi_slave( clk , //50MHz時鐘 rst_n , //復(fù)位 data_in , //要發(fā)送的數(shù)據(jù) data_out , //接收到的數(shù)據(jù) spi_sck , //主機時鐘 spi_miso , //主收從發(fā)(從機) spi_mosi , //主發(fā)從收(從機) spi_cs , //主機片選,低有效(從機) tx_en , //發(fā)送使能 tx_done , //發(fā)送完成標(biāo)志位 rx_done //接收完成標(biāo)志位);parameter DATA_W = 16; parameter SYNC_W = 2; //計數(shù)器參數(shù)parameter CNT_W = 4;parameter CNT_N = DATA_W; input clk;input rst_n;input [DATA_W-1:0] data_in;input spi_sck;input spi_mosi;input spi_cs;input tx_en; output [DATA_W-1:0] data_out;output spi_miso;output tx_done;output rx_done; reg [DATA_W-1:0] data_out;reg spi_miso;reg tx_done;reg rx_done; //中間變量reg [SYNC_W-1:0] sck_sync;wire sck_nedge;wire sck_pedge;reg spi_mosi_reg; reg [SYNC_W-1:0] cs_sync;wire cs_nedge;wire cs_pedge; reg cs_low; //計數(shù)器變量reg [CNT_W-1:0] cnt_rxbit;wire add_cnt_rxbit;wire end_cnt_rxbit; reg [CNT_W-1:0] cnt_txbit;wire add_cnt_txbit;wire end_cnt_txbit;reg tx_flag; //CS異步信號同步化always @(posedge clk or negedge rst_n)begin if(!rst_n) cs_sync <= 2'b11; else cs_sync <= {cs_sync[0],spi_cs};endassign cs_nedge = cs_sync[1:0] == 2'b10;assign cs_pedge = cs_sync[1:0] == 2'b01; //SCK邊沿檢測always @(posedge clk or negedge rst_n)begin if(!rst_n) //SCK時鐘空閑狀態(tài)位高電平,工作模式3 sck_sync <= 2'b11; else sck_sync <= {sck_sync[0],spi_sck};endassign sck_nedge = sck_sync[1:0] == 2'b10;assign sck_pedge = sck_sync[1:0] == 2'b01; //上升沿接收,工作模式三always @(posedge clk or negedge rst_n)begin if(!rst_n) cnt_rxbit <= 0; else if(add_cnt_rxbit)begin if(end_cnt_rxbit) cnt_rxbit <= 0; else cnt_rxbit <= cnt_rxbit + 1'b1; endendassign add_cnt_rxbit = sck_pedge && cs_low;assign end_cnt_rxbit = add_cnt_rxbit && cnt_rxbit == CNT_N - 1; //下降沿發(fā)送,工作模式三always @(posedge clk or negedge rst_n)begin if(!rst_n) cnt_txbit <= 0; else if(add_cnt_txbit)begin if(end_cnt_txbit) cnt_txbit <= 0; else cnt_txbit <= cnt_txbit + 1'b1; endendassign add_cnt_txbit = sck_nedge && tx_flag && cs_low;assign end_cnt_txbit = add_cnt_txbit && cnt_txbit == CNT_N - 1; //因為異步信號同步化的原因,為了與延后的下降沿對齊,多打一拍always @(posedge clk or negedge rst_n)begin if(!rst_n) spi_mosi_reg <= 0; else spi_mosi_reg <= spi_mosi;end always @(posedge clk or negedge rst_n)begin data_out <= 0; else if(cs_low) - 1 - cnt_rxbit ] <= spi_mosi_reg;end always @(posedge clk or negedge rst_n)begin if(!rst_n) spi_miso <= 0; else if(cs_low && tx_flag) spi_miso <= data_in[CNT_N - 1 - cnt_txbit]; else spi_miso <= 0;end always @(posedge clk or negedge rst_n)begin if(!rst_n) rx_done <= 0; else if(end_cnt_rxbit) rx_done <= 1; else rx_done <= 0;end always @(posedge clk or negedge rst_n)begin if(!rst_n) tx_done <= 0; else if(end_cnt_txbit) tx_done <= 1; else tx_done <= 0;end //cs為低電平標(biāo)志信號,當(dāng)計數(shù)器計數(shù)完成,即使當(dāng)前CS還沒有失效,拉低標(biāo)志信號,防止余的CS導(dǎo)致計數(shù)器又在計數(shù),當(dāng)cs出現(xiàn)下降沿時,開始計數(shù)always @(posedge clk or negedge rst_n)begin if(!rst_n) cs_low <= 0; else if(end_cnt_rxbit) cs_low <= 0; else if(cs_nedge) cs_low <= 1;end always @(posedge clk or negedge rst_n)begin if(!rst_n) tx_flag <= 0; else if(tx_en) tx_flag <= 1; else if(end_cnt_txbit) tx_flag <= 0;end endmodule
5總結(jié)
本章主要是SPI的項目實戰(zhàn),可以幫助我們更好的理解SPI的原理。





