三篇初级学习有关Nucleo的板的文章,以下预留跳转其他文章的位置:
NUCLEO-G0B1RE STM32G0B1RET6的学习(1)------STM32CubeIDE的安装、新建工程和配置硬件SPI-CSDN博客
NUCLEO-G0B1RE STM32G0B1RET6的学习(2)------与W25Q32的SPI通信:读取ID、擦除、写和读/解决SPI通信使用模式3的首帧异常的问题-CSDN博客
NUCLEO-G0B1RE STM32G0B1RET6的学习(3)------SPI从DMA HAL库到应用层回调函数CallBack的定义-CSDN博客
本篇介绍SPI从DMA HAL库到应用层回调函数的定义,Los geht's~
目录
分析HAL库的回调函数调用
本文所述SPI是有使用DMA的情况下,具体设置IOC可看前两篇博文
在传输完成后,会触发DMA中断,会调用就是以下函数:
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
回调的是hdma->XferCpltCallback(hdma);


通过查询功能,搜索XferCpltCallback,可以看到在以下函数定义中进行注册,
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size)

总结来说,我们调用HAL_SPI_Transmit_DMA函数,或调用HAL_SPI_Receive_DMA函数,或调用HAL_SPI_TransmitReceive_DMA函数来传输数据,就会把相应的回调函数SPI_DMATransmitCplt、SPI_DMAReceiveCplt、SPI_DMATransmitReceiveCplt的函数指针赋值给XferCpltCallback函数指针,然后在传输完成调用HAL_DMA_IRQHandler中断函数时,调用XferCpltCallback,通过函数指针,实际执行的是相应的回调函数SPI_DMATransmitCplt、SPI_DMAReceiveCplt、SPI_DMATransmitReceiveCplt(具体调用哪个回调函数,看一开始使用哪个传输数据的HAL库函数)
就以我在上两篇博文为例,我使用的是HAL_SPI_TransmitReceive_DMA来进行数据传输

那么对应的是SPI_DMATransmitReceiveCplt函数,在通过搜索该函数,查看其定义:


最终调用的是HAL_SPI_TxRxCallback(hspi);
关系如下:
|-----------------------------------------------------------------|-----------------------------|
| 调用关系(定义中调用) | 注册点(函数指针赋值) |
| HAL_DMA_IRQHandler | |
| ↓ | |
| hdma->XferCpltCallback(只是函数指针) 实际调用 SPI_DMATransmitReceiveCplt | HAL_SPI_TransmitReceive_DMA |
| ↓ | |
| HAL_SPI_TxRxCpltCallback | |
在传输完成后,通过DMA中断的调用,最终运行HAL_SPI_TxRxCallback回调函数定义的程序
所以,通过对使用哪个传输数据的HAL库函数对应的回调函数进行重写就可以做到在传输完成后,运行某一段想要运行的代码,比如SPI用户自定义的状态变化,就可以做超时判断等等,完善代码,不必在while中死等
就以我在上两篇博文为例,我使用的是HAL_SPI_TransmitReceive_DMA来进行数据传输,最终在spi.c中,重写HAL_SPI_TxRxCallback来达到回调的效果,在传输完成后,就会运行HAL_SPI_TxRxCallback函数中定义的程序

应用层回调函数CallBack的定义
spi.c可以说是属于驱动层,在实际使用中,spi调通后,不会动spi.c这一层的程序(这一层一般是初始化、基础的发送接收程序编写等等),会偏向在main.c或其他.c,这一应用层来编写用户自定义的程序,不直接调用HAL库的函数,比如用户的回调函数、发送和接收具体的温度、湿度、压力等等数据,所以会希望main.c中可以定义用户的回调函数。
那么就可以类似HAL库定义和调用回调函数的方法,来写应用层定义和调用回调函数。
关系如下:
|--------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
| 调用关系(定义中调用) | 注册点(函数指针赋值) |
| HAL_SPI_TxRxCpltCallback(spi.c中定义的函数) | |
| ↓ | |
| SPI_TxRxCallBack (spi.c中定义的函数指针变量) 实际调用 TxRxCallBack(main.c中定义的函数) | MX_SPI1_Init(spi.c中定义的函数) 实际调用 SPI_TxRxCb(spi.c中定义的函数) (如果简化的话,可以直接MX_SPI1_Init中注册,不必定义SPI_TxRxCb这一函数) |
具体代码(只体现回调函数部分的内容,注意程序要写在提示的USER CODE BEGIN和USER CODE END之间):
spi.c:
cpp
static void (*SPI_TxRxCallBack)(SPI_HandleTypeDef* hspi) = NULL;
void SPI_TxRxCb(void(*txrxCallBack)(SPI_HandleTypeDef* hspi));
void MX_SPI1_Init(void)
{
...
/* USER CODE BEGIN SPI1_Init 2 */
SPI_TxRxCb(TxRxCallBack);
/* USER CODE END SPI1_Init 2 */
}
/* USER CODE BEGIN 1 */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(SPI_TxRxCallBack != NULL)
{
SPI_TxRxCallBack(&hspi1);
}
}
void SPI_TxRxCb(void(*txrxCallBack)(SPI_HandleTypeDef *hspi))
{
SPI_TxRxCallBack = txrxCallBack;
}
/* USER CODE END 1 */
main.h
cpp
void TxRxCallBack(SPI_HandleTypeDef *hspi);
main.c
cpp
void TxRxCallBack(SPI_HandleTypeDef *hspi)
{
//在完成发送生接收后的回调函数,可放应用层调用
uint8_t a = 0;
a = 1;
}
最后,在TxRxCallBack函数中增加断点,可以结合逻辑分析仪看,逻辑分析仪只扫描到了一帧数据,断点停在了TxRxCallBack函数中

工程文件(不过还是建议从第一篇博文开始自己搭工程):