SPI通信協(xié)議深度解析:FPGA與STM32實(shí)戰(zhàn)
掃描二維碼
隨時(shí)隨地手機(jī)看文章
導(dǎo)言
SPI(Serial Peripheral Interface)是一種同步串行通信協(xié)議,被廣泛用于微控制器與傳感器、ADC、DAC、SRAM等外設(shè) IC 之間的連接。作為最常見的接口之一,SPI以其全雙工、點(diǎn)對(duì)點(diǎn)或多點(diǎn)通信的特性,允許多個(gè)設(shè)備共享同一總線,僅需少量引腳即可實(shí)現(xiàn)高效通信。在上一期,我們探討了SPI的工作原理和通信協(xié)議。為了更深入地理解SPI,本期我們將進(jìn)行SPI的項(xiàng)目實(shí)戰(zhàn)。
1項(xiàng)目介紹
本項(xiàng)目將使用STM32內(nèi)置的硬核SPI作為通信主機(jī),同時(shí)將FPGA作為通信從機(jī),以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的SPI通信系統(tǒng)。
系統(tǒng)的驗(yàn)證方法:
STM32向FPGA發(fā)送數(shù)據(jù),通過STM32的按鈕向不同的FPGA從機(jī)發(fā)送數(shù)據(jù)。從機(jī)接收到數(shù)據(jù)后,將數(shù)據(jù)發(fā)送到FPGA的串口模塊,然后上傳至上位機(jī)。
FPGA向STM32發(fā)送數(shù)據(jù),通過上位機(jī)將數(shù)據(jù)發(fā)送到FPGA串口。FPGA串口再將數(shù)據(jù)發(fā)送給STM32主機(jī),主機(jī)對(duì)所接收的數(shù)據(jù)進(jìn)行校驗(yàn)。如果校驗(yàn)正確,則STM32上的LED燈亮起。
通過以上步驟驗(yàn)證SPI系統(tǒng)的正確性。
本文主要描述FPGA和STM32關(guān)于SPI部分的核心代碼,相關(guān)的驗(yàn)證代碼將在文末提供。
2設(shè)計(jì)思路
STM32F4的SPI通信特點(diǎn)如下:
一旦STM32的SPI啟動(dòng),SPI時(shí)鐘SCK將一直處于工作狀態(tài)。與預(yù)期不同的是,SCK并非僅在STM32讀取或?qū)懭霐?shù)據(jù)時(shí)才從空閑狀態(tài)轉(zhuǎn)換為翻轉(zhuǎn)狀態(tài)。
由此帶來(lái)的問題是,從機(jī)FPGA會(huì)因SCK的翻轉(zhuǎn)而持續(xù)接收數(shù)據(jù),導(dǎo)致從機(jī)FPGA無(wú)法獲取所需數(shù)據(jù)。解決這個(gè)問題的關(guān)鍵在于在STM32的輸出口定義一個(gè)CS片選信號(hào)。只有在讀寫數(shù)據(jù)時(shí)激活片選信號(hào),以控制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片選信號(hào),而其不在同一個(gè)時(shí)鐘域,為防止亞穩(wěn)態(tài),因此需要進(jìn)行異步信號(hào)同步處理。在這里,我們?nèi)S_SYNC[1]作為FPGA內(nèi)部CS控制信號(hào)
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主機(jī)通信
以下是spi.c文件用于配置STM32內(nèi)置SPI的代碼。
//以下是SPI模塊的初始化代碼,配置成主機(jī)模式,模式3 //SPI口初始化//這里針是對(duì)SPI1的初始化void SPI1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitTypeDef GPIO_InitStructure1; SPI_InitTypeDef SPI_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB時(shí)鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//使能SPI1時(shí)鐘 //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 //這里只針對(duì)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; //串行同步時(shí)鐘的空閑狀態(tài)為高電平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步時(shí)鐘的第二個(gè)跳變沿(上升或下降)數(shù)據(jù)被采樣 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信號(hào)由硬件(NSS管腳)還是軟件(使用SSI位)管理:內(nèi)部NSS信號(hào)有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值計(jì)算的多項(xiàng)式 SPI_Init(SPI1, &SPI_InitStructure); //根據(jù)SPI_InitStruct中指定的參數(shù)初始化外設(shè)SPIx寄存器 SPI_Cmd(SPI1, ENABLE); //使能SPI外設(shè) SPI_CS = 0; SPI1_ReadWriteByte(0x0000);//啟動(dòng)傳輸 SPI_CS = 1;} //SPI1速度設(shè)置函數(shù)//SPI速度=fAPB2/分頻系數(shù)//@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256 //fAPB2時(shí)鐘一般為84Mhz:void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler){ assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判斷有效性 SPI1->CR1&=0XFFC7;//位3-5清零,用來(lái)設(shè)置波特率 SPI1->CR1|=SPI_BaudRatePrescaler; //設(shè)置SPI1速度 SPI_Cmd(SPI1,ENABLE); //使能SPI1} //SPI1 讀寫一個(gè)字節(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ā)送一個(gè)byte 數(shù)據(jù)}
4FPGA的SPI從機(jī)通信
以下是spi_slave.v文件,是FPGA的SPI從機(jī)模塊。
module spi_slave( clk , //50MHz時(shí)鐘 rst_n , //復(fù)位 data_in , //要發(fā)送的數(shù)據(jù) data_out , //接收到的數(shù)據(jù) spi_sck , //主機(jī)時(shí)鐘 spi_miso , //主收從發(fā)(從機(jī)) spi_mosi , //主發(fā)從收(從機(jī)) spi_cs , //主機(jī)片選,低有效(從機(jī)) tx_en , //發(fā)送使能 tx_done , //發(fā)送完成標(biāo)志位 rx_done //接收完成標(biāo)志位);parameter DATA_W = 16; parameter SYNC_W = 2; //計(jì)數(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; //計(jì)數(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異步信號(hào)同步化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邊沿檢測(cè)always @(posedge clk or negedge rst_n)begin if(!rst_n) //SCK時(shí)鐘空閑狀態(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; //因?yàn)楫惒叫盘?hào)同步化的原因,為了與延后的下降沿對(duì)齊,多打一拍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)志信號(hào),當(dāng)計(jì)數(shù)器計(jì)數(shù)完成,即使當(dāng)前CS還沒有失效,拉低標(biāo)志信號(hào),防止余的CS導(dǎo)致計(jì)數(shù)器又在計(jì)數(shù),當(dāng)cs出現(xiàn)下降沿時(shí),開始計(jì)數(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的項(xiàng)目實(shí)戰(zhàn),可以幫助我們更好的理解SPI的原理。





