【STM32F429开发板用户手册】第31章 STM32F429的SPI总线基础知识和HAL库API
2020-12-26 12:28
标签:back size 二分 gac color ror ini enable 返回 最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255 本章节为大家讲解SPI(Serial peripheral interface)总线的基础知识和对应的HAL库API。 31.1 初学者重要提示 31.2 SPI总线基础知识 31.3 SPI总线的HAL库用法 31.4 源文件stm32f4xx_hal_spi.c 31.5 总结 认识一个外设,最好的方式就是看它的框图,方便我们快速的了解SPI的基本功能,然后再看手册了解细节。 通过这个框图,我们可以得到如下信息: 此引脚在主机模式下用于时钟输出,从机模式下用于时钟输入。 此引脚在从机模式下用于发送数据,主机模式下接收数据。 此引脚在从机模式下用于数据接收,主机模式下发送数据。 根据SPI和SS设置,此引脚可用于: (1) 选择从器件进行通信。 (2) 允许多主模式(可以禁止NSS引脚输出)。 这个知识点在初学的时候容易忽视,所以我们这里整理下。 SPI1,SPI4,SPI5,SPI6在APB2总线,SPI2,SPI3在APB1总线。SPI的最高时钟由这些总线决定的。 STM32F429主频在168MHz下,SPI1,SPI4,SPI5,SPI6的最高时钟是84MHz,而SPI2和SPI3是42MHz。这里特别注意一点,SPI工作时最少选择二分频,也就是说SPI1,4,5,6实际通信时钟是42MHz,而SPI2,3是21MHz。 片选信号SS在单一的主从器件配置下是可选的,一般情况下可以不使用。 全双工通信(F4只有一个移位寄存器) 全双工就是主从器件之间同时互传数据,SPI总线的全双工模式接线方式如下: 关于这个接线图要认识到以下几点: 半双工通信 半双工就是同一个时刻只能为一个方向传输数据,SPI总线的半工模式接线方式如下: 关于这个接线图要认识到以下几点: 单工模式 单工就是只有一种通信方向,即发送或者接收,SPI总线的单工模式接线方式如下: 关于这个接线图要认识到以下几点: SPI总线星型拓扑用到的地方比较多,V6开发板就是用的星型拓扑外接多种SPI器件: 关于这个接线图,有以下几点需要大家了解: SPI总线主要有四种通信格式,由CPOL时钟极性和CPHA时钟相位控制: 四种通信格式如下: SCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。 SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。 SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。 SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。 SPI总线相关的寄存器是通过HAL库中的结构体SPI_TypeDef定义的,在stm32f429xx.h中可以找到这个类型定义: 这个结构体的成员名称和排列次序和CPU的寄存器是一 一对应的。 __IO表示volatile, 这是标准C语言中的一个修饰字,表示这个变量是非易失性的,编译器不要将其优化掉。core_m4.h 文件定义了这个宏: 下面我们看下SPI的定义,在stm32f429xx.h文件。 我们访问SPI的CR1寄存器可以采用这种形式:SPI->CR1 = 0。 下面是SPI总线的初始化结构体,用到的地方比较多: 下面将结构体成员逐一做个说明: 用于设置工作在主机模式还是从机模式。 用于设置SPI工作在全双工,单工,还是半双工模式。 用于设置SPI总线数据收发的位宽,支持8bit或者16bit。 用于设置空闲状态时,CLK是高电平还是低电平。 用于设置NSS信号由硬件NSS引脚管理或者软件SSI位管理。 用于设置SPI时钟分频,仅SPI工作在主控模式下起作用,对SPI从机模式不起作用。 用于设置数据传输从最高bit开始还是从最低bit开始。 用于设置是否使能SPI总线的TI模式。 用于设置是否使能CRC计算。 用于设置CRC计算使用的多项式,必须是奇数,范围0到65535。 下面是SPI总线的初始化结构体,用到的地方比较多: 注意事项: 条件编译USE_HAL_SPI_REGISTER_CALLBACKS用来设置使用自定义回调还是使用默认回调,此定义一般放在stm32f4xx_hal_conf.h文件里面设置: #define USE_HAL_SPI_REGISTER_CALLBACKS 1 通过函数HAL_SPI_RegisterCallback注册回调,取消注册使用函数HAL_SPI_UnRegisterCallback。 这里重点介绍下面几个参数,其它参数主要是HAL库内部使用和自定义回调函数。 这个参数是寄存器的例化,方便操作寄存器,比如使能SPI1。 SET_BIT(SPI1 ->CR1, SPI_CR1_SPE)。 这个参数是用户接触最多的,在本章节3.2小节已经进行了详细说明。 用于SPI句柄关联DMA句柄,方便操作调用。 此文件涉及到的函数较多,这里把几个常用的函数做个说明: 函数原型: 函数描述: 此函数用于初始化SPI。 函数参数: 注意事项: 对于局部变量来说,这个参数就是一个随机值,如果是全局变量还好,一般MDK和IAR都会将全部变量初始化为0,而恰好这个 HAL_SPI_STATE_RESET = 0x00U。 解决办法有三 方法1:用户自己初始串口和涉及到的GPIO等。 方法2:定义SPI_HandleTypeDef SpiHandle为全局变量。 方法3:下面的方法 使用举例: 函数原型: 函数描述: 用于复位SPI总线初始化。 函数参数: 函数原型: 函数描述: 此函数主要用于SPI数据收发,全双工查询方式。 函数参数: 使用举例: 函数原型: 函数描述: 此函数主要用于SPI数据收发,全双工中断方式。 函数参数: 使用举例: 函数原型: 函数描述: 此函数主要用于SPI数据收发,全双工DMA方式。 函数参数: 使用举例: 本章节就为大家讲解这么多,要熟练掌握SPI总线的查询,中断和DMA方式的实现,因为基于SPI接口的外设芯片很多,熟练后,可以方便的驱动各种SPI接口芯片,以便选择合适的驱动方式。 【STM32F429开发板用户手册】第31章 STM32F429的SPI总线基础知识和HAL库API 标签:back size 二分 gac color ror ini enable 返回 原文地址:https://www.cnblogs.com/armfly/p/13376167.html第31章 STM32F429的SPI总线基础知识和HAL库API
31.1 初学者重要提示
31.2 SPI总线基础知识
31.2.1 SPI总线的硬件框图
31.2.2 SPI接口的区别和时钟源(SPI1到SPI6)
31.2.3 SPI总线全双工,单工和半双工通信
31.2.4 SPI总线星型拓扑
31.2.5 SPI总线通信格式
31.3 SPI总线的HAL库用法
31.3.1 SPI总线结构体SPI_TypeDef
typedef struct
{
__IO uint32_t CR1; /*!*/
__IO uint32_t CR2; /*!*/
__IO uint32_t SR; /*!*/
__IO uint32_t DR; /*!*/
__IO uint32_t CRCPR; /*!*/
__IO uint32_t RXCRCR; /*!*/
__IO uint32_t TXCRCR; /*!*/
__IO uint32_t I2SCFGR; /*!*/
__IO uint32_t I2SPR; /*!*/
} SPI_TypeDef;
#define __O volatile /*!#define __IO volatile /*!
#define PERIPH_BASE 0x40000000UL
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL)
#define SPI1_BASE (APB2PERIPH_BASE + 0x3000UL)
#define SPI2_BASE (APB1PERIPH_BASE + 0x3800UL)
#define SPI3_BASE (APB1PERIPH_BASE + 0x3C00UL)
#define SPI4_BASE (APB2PERIPH_BASE + 0x3400UL)
#define SPI5_BASE (APB2PERIPH_BASE + 0x5000UL)
#define SPI6_BASE (APB2PERIPH_BASE + 0x5400UL)
#define SPI1 ((SPI_TypeDef *) SPI1_BASE)
#define SPI2 ((SPI_TypeDef *) SPI2_BASE)
#define SPI3 ((SPI_TypeDef *) SPI3_BASE)
#define SPI4 ((SPI_TypeDef *) SPI4_BASE)
#define SPI5 ((SPI_TypeDef *) SPI5_BASE)
#define SPI6 ((SPI_TypeDef *) SPI6_BASE)
31.3.2 SPI总线初始化结构体SPI_InitTypeDef
typedef struct
{
uint32_t Mode;
uint32_t Direction;
uint32_t DataSize;
uint32_t CLKPolarity;
uint32_t CLKPhase;
uint32_t NSS;
uint32_t BaudRatePrescaler;
uint32_t FirstBit;
uint32_t TIMode;
uint32_t CRCCalculation;
uint32_t CRCPolynomial;
} SPI_InitTypeDef;
#define SPI_MODE_SLAVE (0x00000000U)
#define SPI_MODE_MASTER (SPI_CR1_MSTR | SPI_CR1_SSI)
#define SPI_DIRECTION_2LINES (0x00000000U)
#define SPI_DIRECTION_2LINES_RXONLY SPI_CR1_RXONLY
#define SPI_DIRECTION_1LINE SPI_CR1_BIDIMODE
#define SPI_DATASIZE_8BIT (0x00000000U)
#define SPI_DATASIZE_16BIT SPI_CR1_DFF
#define SPI_POLARITY_LOW (0x00000000U)
#define SPI_POLARITY_HIGH SPI_CR1_CPOL
#define SPI_NSS_SOFT SPI_CR1_SSM
#define SPI_NSS_HARD_INPUT (0x00000000U)
#define SPI_NSS_HARD_OUTPUT (SPI_CR2_SSOE
#define SPI_BAUDRATEPRESCALER_2 (0x00000000U)
#define SPI_BAUDRATEPRESCALER_4 (SPI_CR1_BR_0)
#define SPI_BAUDRATEPRESCALER_8 (SPI_CR1_BR_1)
#define SPI_BAUDRATEPRESCALER_16 (SPI_CR1_BR_1 | SPI_CR1_BR_0)
#define SPI_BAUDRATEPRESCALER_32 (SPI_CR1_BR_2)
#define SPI_BAUDRATEPRESCALER_64 (SPI_CR1_BR_2 | SPI_CR1_BR_0)
#define SPI_BAUDRATEPRESCALER_128 (SPI_CR1_BR_2 | SPI_CR1_BR_1)
#define SPI_BAUDRATEPRESCALER_256 (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
#define SPI_FIRSTBIT_MSB (0x00000000U)
#define SPI_FIRSTBIT_LSB SPI_CR1_LSBFIRST
#define SPI_TIMODE_DISABLE (0x00000000U)
#define SPI_TIMODE_ENABLE SPI_CR2_FRF
#define SPI_CRCCALCULATION_DISABLE (0x00000000U)
#define SPI_CRCCALCULATION_ENABLE SPI_CR1_CRCEN
31.3.3 SPI总线句柄结构体SPI_HandleTypeDef
typedef struct __SPI_HandleTypeDef
{
SPI_TypeDef *Instance;
SPI_InitTypeDef Init;
uint8_t *pTxBuffPtr;
uint16_t TxXferSize;
__IO uint16_t TxXferCount;
uint8_t *pRxBuffPtr;
uint16_t RxXferSize;
__IO uint16_t RxXferCount;
void (*RxISR)(struct __SPI_HandleTypeDef *hspi);
void (*TxISR)(struct __SPI_HandleTypeDef *hspi);
DMA_HandleTypeDef *hdmatx;
DMA_HandleTypeDef *hdmarx;
HAL_LockTypeDef Lock;
__IO HAL_SPI_StateTypeDef State;
__IO uint32_t ErrorCode;
#if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U)
void (* TxCpltCallback)(struct __SPI_HandleTypeDef *hspi);
void (* RxCpltCallback)(struct __SPI_HandleTypeDef *hspi);
void (* TxRxCpltCallback)(struct __SPI_HandleTypeDef *hspi);
void (* TxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi);
void (* RxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi);
void (* TxRxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi);
void (* ErrorCallback)(struct __SPI_HandleTypeDef *hspi);
void (* AbortCpltCallback)(struct __SPI_HandleTypeDef *hspi);
void (* MspInitCallback)(struct __SPI_HandleTypeDef *hspi);
void (* MspDeInitCallback)(struct __SPI_HandleTypeDef *hspi);
#endif
} SPI_HandleTypeDef;
31.4 SPI总线源文件stm32f4xx_hal_spi.c
31.4.1 函数HAL_SPI_Init
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi)
{
/* 检测句柄是否有效 */
if (hspi == NULL)
{
return HAL_ERROR;
}
/* 检查参数 */
assert_param(IS_SPI_ALL_INSTANCE(hspi->Instance));
assert_param(IS_SPI_MODE(hspi->Init.Mode));
assert_param(IS_SPI_DIRECTION(hspi->Init.Direction));
assert_param(IS_SPI_DATASIZE(hspi->Init.DataSize));
assert_param(IS_SPI_NSS(hspi->Init.NSS));
assert_param(IS_SPI_BAUDRATE_PRESCALER(hspi->Init.BaudRatePrescaler));
assert_param(IS_SPI_FIRST_BIT(hspi->Init.FirstBit));
assert_param(IS_SPI_TIMODE(hspi->Init.TIMode));
if (hspi->Init.TIMode == SPI_TIMODE_DISABLE)
{
assert_param(IS_SPI_CPOL(hspi->Init.CLKPolarity));
assert_param(IS_SPI_CPHA(hspi->Init.CLKPhase));
}
#if (USE_SPI_CRC != 0U)
assert_param(IS_SPI_CRC_CALCULATION(hspi->Init.CRCCalculation));
if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
{
assert_param(IS_SPI_CRC_POLYNOMIAL(hspi->Init.CRCPolynomial));
}
#else
hspi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
#endif
if (hspi->State == HAL_SPI_STATE_RESET)
{
/* 解锁 */
hspi->Lock = HAL_UNLOCKED;
#if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U)
/* 自定义回调函数 */
hspi->TxCpltCallback = HAL_SPI_TxCpltCallback; /* Legacy weak TxCpltCallback */
hspi->RxCpltCallback = HAL_SPI_RxCpltCallback; /* Legacy weak RxCpltCallback */
hspi->TxRxCpltCallback = HAL_SPI_TxRxCpltCallback; /* Legacy weak TxRxCpltCallback */
hspi->TxHalfCpltCallback = HAL_SPI_TxHalfCpltCallback; /* Legacy weak TxHalfCpltCallback */
hspi->RxHalfCpltCallback = HAL_SPI_RxHalfCpltCallback; /* Legacy weak RxHalfCpltCallback */
hspi->TxRxHalfCpltCallback = HAL_SPI_TxRxHalfCpltCallback; /* Legacy weak TxRxHalfCpltCallback */
hspi->ErrorCallback = HAL_SPI_ErrorCallback; /* Legacy weak ErrorCallback */
hspi->AbortCpltCallback = HAL_SPI_AbortCpltCallback; /* Legacy weak AbortCpltCallback */
if (hspi->MspInitCallback == NULL)
{
hspi->MspInitCallback = HAL_SPI_MspInit; /* Legacy weak MspInit */
}
/* 初始化底层硬件: GPIO, CLOCK, NVIC... */
hspi->MspInitCallback(hspi);
#else
/* 初始化底层硬件: GPIO, CLOCK, NVIC... */
HAL_SPI_MspInit(hspi);
#endif
}
hspi->State = HAL_SPI_STATE_BUSY;
/* 关闭SPI外设 */
__HAL_SPI_DISABLE(hspi);
/*----------------------- SPIx CR1 & CR2 配置 ---------------------*/
/* 配置的各种SPI参数 */
WRITE_REG(hspi->Instance->CR1, (hspi->Init.Mode | hspi->Init.Direction | hspi->Init.DataSize |
hspi->Init.CLKPolarity | hspi->Init.CLKPhase | (hspi->Init.NSS & SPI_CR1_SSM) |hspi->Init.BaudRatePrescaler | hspi->Init.FirstBit | hspi->Init.CRCCalculation));
/* 配置NSS和TI模式位 */
WRITE_REG(hspi->Instance->CR2, (((hspi->Init.NSS >> 16U) & SPI_CR2_SSOE) | hspi->Init.TIMode));
#if (USE_SPI_CRC != 0U)
/*---------------------------- SPIx CRCPOLY 配置 ------------------*/
/* 配置 : CRC 多项式 */
if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
{
WRITE_REG(hspi->Instance->CRCPR, hspi->Init.CRCPolynomial);
}
#endif
#if defined(SPI_I2SCFGR_I2SMOD)
CLEAR_BIT(hspi->Instance->I2SCFGR, SPI_I2SCFGR_I2SMOD);
#endif
hspi->ErrorCode = HAL_SPI_ERROR_NONE;
hspi->State = HAL_SPI_STATE_READY;
return HAL_OK;
}
if(HAL_SPI_DeInit(&SpiHandle) != HAL_OK)
{
Error_Handler();
}
if(HAL_SPI_Init(&SpiHandle) != HAL_OK)
{
Error_Handler();
}
SPI_HandleTypeDef hspi = {0};
/* 设置SPI参数 */
hspi.Instance = SPIx; /* 例化SPI */
hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 设置波特率 */
hspi.Init.Direction = SPI_DIRECTION_2LINES; /* 全双工 */
hspi.Init.CLKPhase = _CLKPhase; /* 配置时钟相位 */
hspi.Init.CLKPolarity = _CLKPolarity; /* 配置时钟极性 */
hspi.Init.DataSize = SPI_DATASIZE_8BIT; /* 设置数据宽度 */
hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 数据传输先传高位 */
hspi.Init.TIMode = SPI_TIMODE_DISABLE; /* 禁止TI模式 */
hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
hspi.Init.CRCPolynomial = 7; /* 禁止CRC后,此位无效 */
hspi.Init.NSS = SPI_NSS_SOFT; /* 使用软件方式管理片选引脚 */
hspi.Init.Mode = SPI_MODE_MASTER; /* SPI工作在主控模式 */
/* 初始化SPI */
if (HAL_SPI_Init(&hspi) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
31.4.2 函数HAL_SPI_DeInit
HAL_StatusTypeDef HAL_SPI_DeInit(SPI_HandleTypeDef *hspi)
{
/* 判断SPI句柄 */
if (hspi == NULL)
{
return HAL_ERROR;
}
/* 检查参数 */
assert_param(IS_SPI_ALL_INSTANCE(hspi->Instance));
hspi->State = HAL_SPI_STATE_BUSY;
/* 关闭SPI外设时钟 */
__HAL_SPI_DISABLE(hspi);
#if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U)
if (hspi->MspDeInitCallback == NULL)
{
hspi->MspDeInitCallback = HAL_SPI_MspDeInit; /* Legacy weak MspDeInit */
}
/* 复位底层硬件: GPIO, CLOCK, NVIC... */
hspi->MspDeInitCallback(hspi);
#else
/* 复位底层硬件: GPIO, CLOCK, NVIC... */
HAL_SPI_MspDeInit(hspi);
#endif
hspi->ErrorCode = HAL_SPI_ERROR_NONE;
hspi->State = HAL_SPI_STATE_RESET;
/* 解锁 */
__HAL_UNLOCK(hspi);
return HAL_OK;
}
31.4.3 函数HAL_SPI_TransmitReceive
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
uint16_t Size,uint32_t Timeout)
{
/* 省略未写 */
/* 16bit数据传输 */
if (hspi->Init.DataSize == SPI_DATASIZE_16BIT)
{
/* 省略未写 */
}
/* 8bit数据传输 */
else
{
/* 省略未写 */
}
/* 省略未写 */
}
SPI_HandleTypeDef hspi = {0};
if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
31.4.4 函数HAL_SPI_TransmitReceive_IT
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size)
{
/* 省略未写 */
/* 设置传输参数 */
hspi->ErrorCode = HAL_SPI_ERROR_NONE;
hspi->pTxBuffPtr = (uint8_t *)pTxData;
hspi->TxXferSize = Size;
hspi->TxXferCount = Size;
hspi->pRxBuffPtr = (uint8_t *)pRxData;
hspi->RxXferSize = Size;
hspi->RxXferCount = Size;
/* 设置中断处理 */
if (hspi->Init.DataSize > SPI_DATASIZE_8BIT)
{
hspi->RxISR = SPI_2linesRxISR_16BIT;
hspi->TxISR = SPI_2linesTxISR_16BIT;
}
else
{
hspi->RxISR = SPI_2linesRxISR_8BIT;
hspi->TxISR = SPI_2linesTxISR_8BIT;
}
#if (USE_SPI_CRC != 0U)
/* 复位CRC计算 */
if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
{
SPI_RESET_CRC(hspi);
}
#endif
/* 使能TXE, RXNE 和 ERR 中断 */
__HAL_SPI_ENABLE_IT(hspi, (SPI_IT_TXE | SPI_IT_RXNE | SPI_IT_ERR));
/* 检测SPI是否已经使能 */
if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
{
/* 使能SPI外设 */
__HAL_SPI_ENABLE(hspi);
}
error :
/* 解锁 */
__HAL_UNLOCK(hspi);
return errorcode;
}
SPI_HandleTypeDef hspi = {0};
if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
31.4.5 函数HAL_SPI_TransmitReceive_DMA
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
uint16_t Size)
{
/* 省略未写 */
/* 使能RX DMA */
if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->DR, (uint32_t)hspi->pRxBuffPtr,
hspi->RxXferCount))
{
}
/* 使能RX DMA */
if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)hspi->pTxBuffPtr, (uint32_t)&hspi->Instance->DR,
hspi->TxXferCount))
{
}
/* 省略未写 */
}
SPI_HandleTypeDef hspi = {0};
if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
31.5 总结
上一篇:gitlab的api操作
下一篇:C语言宽字符处理函数对照表
文章标题:【STM32F429开发板用户手册】第31章 STM32F429的SPI总线基础知识和HAL库API
文章链接:http://soscw.com/essay/38353.html