例子說明及框圖
本例子基於STM32F103ZET6芯片,實現SPI1與SPI2的主從通信。其中SPI1配置為主機,SPI2配置為從機,均配置為全雙工模式。硬件連接圖:
其中,我們需要注意的是,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的框圖:
從框圖可看出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配置為從模式,全雙工,使能接收中斷。
驗證情況
可見,與我們前面分析的一致,ucSPI2_RxBuf為從機接收自主機的數據;ucSPI1_RxBuf為主機接收自從機的數據。這裡發現ucSPI1_RxBuf的所有數組元素都往後移了一個單位,那是因為主機第一次發送數據給從機的時候,從機並沒有數據返還給主機,即此時還沒有數據存儲在ucSPI1_RxBuf[0]中。
閱讀更多 嵌入式大雜燴 的文章