STM32硬件SPI主從通信實例

例子說明及框圖

本例子基於STM32F103ZET6芯片,實現SPI1與SPI2的主從通信。其中SPI1配置為主機,SPI2配置為從機,均配置為全雙工模式。硬件連接圖:

STM32硬件SPI主從通信實例

其中,我們需要注意的是,SPI的從機不能主動發送數據,只能應答數據。本例子的數據交互過程:

1、主機使用查詢方式發送數據給從機。

2、從機使用中斷接收方式接收數據,把接收到的數據加上0x05再發送給主機。

從機總是在收到主機的數據時,才會發送數據給從機。即從機被動發送數據,也即主機主動申請數據。


代碼細節

主函數:

<code>int main(void)
{  
uint8_t i = 0;

//-----------------------------------------------------------------------------------------------
// 上電初始化函數
SysInit();

//-----------------------------------------------------------------------------------------------
// 主程序
while (1)
{
/* 主機發、收數據 */
for (i = 0; i < SPI_BUF_LEN; i++)
{

ucSPI1_RxBuf[i] = SPI1_ReadWriteByte(ucSPI1_TxBuf[i]);
}
}

return 0;
}/<code>

其中,ucSPI1_RxBuf與ucSPI1_TxBuf的定義為:

<code>uint8_t ucSPI1_RxBuf[SPI_BUF_LEN] = {0};
uint8_t ucSPI1_TxBuf[SPI_BUF_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05};/<code>

SPI1_ReadWriteByte函數為SPI1的讀寫函數,其作用是往SPI1發送緩衝區寫入數據的同時可以讀取SPI1接收緩衝區中的數據,其內部實現為:

<code>uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // 等待發送區空  
SPI_I2S_SendData(SPI1, TxData); // 通過外設SPI1發送一個byte數據
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);// 等待接收完一個byte  
return SPI_I2S_ReceiveData(SPI1); // 返回通過SPIx最近接收的數據    
}/<code>

為什麼可以這麼寫呢?看一下SPI的框圖:

STM32硬件SPI主從通信實例

從框圖可看出SPI有 2 個緩衝區,一個用於寫入(發送緩衝區),一個用於讀取(接收緩衝區)。對數據寄存器執行寫操作時,數據將寫入發送緩衝區,從數據寄存器執行讀取時,將返回接收緩衝區中的值。這樣寫並不會出現讀到的數據等於發送的數據。


SPI2中斷函數:

<code>void SPI2_IRQHandler(void)
{
/* 判斷接收緩衝區是否為非空 */
if (SET == SPI_I2S_GetITStatus(SPI2, SPI_I2S_IT_RXNE))
{
ucSPI2_RxBuf[ucSPI2_RxCount] = SPI2->DR; /* 讀取接收緩衝區數據 */

while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);  /* 等待發送區空 */
SPI2->DR = ucSPI2_RxBuf[ucSPI2_RxCount] + 0x05; /* 往發送緩衝區填數據 */

/* 計數器處理 */
ucSPI2_RxCount++;
if (ucSPI2_RxCount > SPI_BUF_LEN - 1)
{
ucSPI2_RxCount = 0;
}

/* 清中斷標誌 */
SPI_I2S_ClearITPendingBit(SPI2, SPI_I2S_IT_RXNE);
}
}/<code>

從機接收到主機數據後,會加上0x05,再返還給主機。


SPI1初始化函數:

<code>void bsp_SPI1_Init(void)
{
/* 定義SPI結構體變量 */
GPIO_InitTypeDef  GPIO_InitStructure;
SPI_InitTypeDef   SPI_InitStructure;

/* SPI的IO口和SPI外設打開時鐘 */
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

/* SPI的IO口設置 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 複用輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* SPI的基本配置 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;  // 設置SPI工作模式:設置為主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 設置SPI的數據大小:SPI發送接收8位幀結構
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;  // 串行同步時鐘的空閒狀態為高電平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;  // 串行同步時鐘的第二個跳變沿(上升或下降)數據被採樣
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;  // NSS信號由硬件(NSS管腳)還是軟件(使用SSI位)管理:內部NSS信號有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;// 定義波特率預分頻的值:波特率預分頻值為256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;// 指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始

SPI_InitStructure.SPI_CRCPolynomial = 7;  // CRC值計算的多項式
SPI_Init(SPI1, &SPI_InitStructure);  // 根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器

SPI_Cmd(SPI1, ENABLE);  // 使能SPI外設
}/<code>

SPI1配置為主模式,全雙工。


SPI2初始化函數:

<code>void bsp_SPI2_Init(void)
{
/* 定義SPI結構體變量 */
GPIO_InitTypeDef  GPIO_InitStructure;
SPI_InitTypeDef  SPI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

/* SPI的IO口和SPI外設打開時鐘 */
   RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

/* SPI的IO口設置 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PB13/14/15複用推輓輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

/* SPI的基本配置 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;  // 設置SPI工作模式:設置為從SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 設置SPI的數據大小:SPI發送接收8位幀結構
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;  // 串行同步時鐘的空閒狀態為高電平

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;  // 串行同步時鐘的第二個跳變沿(上升或下降)數據被採樣
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;  // NSS信號由硬件(NSS管腳)還是軟件(使用SSI位)管理:內部NSS信號有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;// 定義波特率預分頻的值:波特率預分頻值為256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;// 指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始
SPI_InitStructure.SPI_CRCPolynomial = 7;  // CRC值計算的多項式
SPI_Init(SPI2, &SPI_InitStructure);  // 根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器

SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_RXNE, ENABLE);  // 使能接收中斷

SPI_Cmd(SPI2, ENABLE);  // 使能SPI2外設

/* NVIC中斷控制器配置 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 中斷優先級分組2

NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn; // SPI2中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; // 搶佔優先級3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; // 子優先級3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根據指定的參數初始化VIC寄存器
}/<code>

SPI2配置為從模式,全雙工,使能接收中斷。


驗證情況

STM32硬件SPI主從通信實例

可見,與我們前面分析的一致,ucSPI2_RxBuf為從機接收自主機的數據;ucSPI1_RxBuf為主機接收自從機的數據。這裡發現ucSPI1_RxBuf的所有數組元素都往後移了一個單位,那是因為主機第一次發送數據給從機的時候,從機並沒有數據返還給主機,即此時還沒有數據存儲在ucSPI1_RxBuf[0]中。


分享到:


相關文章: