这一切的一切都是因为这六行代码引起的惨案

void Spi_transferU16Data(STR_SpiStruct *pxSpi, STR_GpioStruct *pCs, uint16_t *pDataOut, uint16_t len, uint16_t *pDataIn)
{
    while(pxSpi->pxDriver->GetStatus().busy == 1) {}

    pxSpi->pxDriver->Transfer(pDataOut, pDataIn, len);

    while(pxSpi->pxDriver->GetStatus().busy == 1) {}
}

这是一个SPI U16数据收发的函数,pDataOut是数据发送缓存区,pDataIn是数据接收缓存区。看似非常简单,但我想GO进去看Transfer底层的代码是如何写的,于是就有了今天的文章。

首先,我查了F407系列的SPI部分的中文手册,查到了比较有用的SPI通信框图以及收发数据的框图,如下。

SPI通信框图:

全双工发送和接收过程:(SPE位负责使能和关闭SPI

然后,又在工程文档中,FIND ALL 了很长时间,找到了Transfer的原型函数。但是发现,数据处理似乎并不在这个函数当中,而是一个以 RXNEIE 接收中断函数中去处理的数据信息。于是乎,我紧接着又FIND ALL 出来了SPI的中断服务函数,都贴到下面了。

官方"收发"代码例程:

static int32_t SPI_Transfer (const void *data_out, void *data_in, uint32_t num, const SPI_RESOURCES *spi) {

  if ((data_out == NULL) || (data_in == NULL) || (num == 0U)) { return ARM_DRIVER_ERROR_PARAMETER; }
  if ((spi->info->state & SPI_CONFIGURED) == 0U)              { return ARM_DRIVER_ERROR; }
  if ( spi->info->status.busy)                                { return ARM_DRIVER_ERROR_BUSY; }

  // Check if receive and transmit pins available
  if ((spi->io.miso == NULL) || (spi->io.mosi == NULL)) {
    return ARM_DRIVER_ERROR;
  }

  // Update SPI statuses
  spi->info->status.busy       = 1U;
  spi->info->status.data_lost  = 0U;
  spi->info->status.mode_fault = 0U;

  // Save transfer info
  spi->xfer->rx_buf = (uint8_t *)((uint32_t)data_in);
  spi->xfer->tx_buf = (uint8_t *)((uint32_t)data_out);
  spi->xfer->num    = num;
  spi->xfer->rx_cnt = 0U;
  spi->xfer->tx_cnt = 0U;

#ifdef __SPI_DMA  //启用DMA的话走下面这部分逻辑
  if ((spi->rx_dma != NULL) || (spi->tx_dma != NULL)) {
    // DMA mode

    if (spi->rx_dma != NULL) {
      // Prepare DMA to receive RX data
      spi->rx_dma->hdma->Init.PeriphInc             = DMA_PINC_DISABLE;
      spi->rx_dma->hdma->Init.MemInc                = DMA_MINC_ENABLE;
      if (spi->reg->CR1 & SPI_CR1_DFF) {
        // 16 - bit data frame
        spi->rx_dma->hdma->Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
        spi->rx_dma->hdma->Init.MemDataAlignment    = DMA_MDATAALIGN_HALFWORD;
      } else {
        //  8 - bit data frame
        spi->rx_dma->hdma->Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        spi->rx_dma->hdma->Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
      }
      // Initialize and start SPI RX DMA Stream
      if (HAL_DMA_Init     (spi->rx_dma->hdma) != HAL_OK) { return ARM_DRIVER_ERROR; }
      if (HAL_DMA_Start_IT (spi->rx_dma->hdma, (uint32_t)(&spi->reg->DR), (uint32_t)spi->xfer->rx_buf, num) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }

      // Disable Rx Not Empty interrupt, in DMA mode it is used only to allow data lost detection
      // before reception with DMA is started
      spi->reg->CR2 &= ~SPI_CR2_RXNEIE;

      // RX Buffer DMA enable
      spi->reg->CR2 |= SPI_CR2_RXDMAEN;
    }

    if (spi->tx_dma != NULL) {
      // Prepare DMA to send TX data
      spi->tx_dma->hdma->Init.PeriphInc             = DMA_PINC_DISABLE;
      spi->tx_dma->hdma->Init.MemInc                = DMA_MINC_ENABLE;
      if (spi->reg->CR1 & SPI_CR1_DFF) {
        // 16 - bit data frame
        spi->tx_dma->hdma->Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
        spi->tx_dma->hdma->Init.MemDataAlignment    = DMA_MDATAALIGN_HALFWORD;
      } else {
        //  8 - bit data frame
        spi->tx_dma->hdma->Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        spi->tx_dma->hdma->Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
      }

      // Initialize and start SPI TX DMA Stream
      if (HAL_DMA_Init     (spi->tx_dma->hdma) != HAL_OK) { return ARM_DRIVER_ERROR; }
      if (HAL_DMA_Start_IT (spi->tx_dma->hdma, (uint32_t)spi->xfer->tx_buf, (uint32_t)(&spi->reg->DR), num) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }

      // TX Buffer DMA enable
      spi->reg->CR2 |= SPI_CR2_TXDMAEN;
    }
  } else
#endif
  {
    // Interrupt mode
    spi->reg->CR2 |= SPI_CR2_RXNEIE;//使能接收中断
    SPI_TxData (spi->reg, spi->xfer);//输出第一个发送字节,启动SPI时钟移位
  }

  return ARM_DRIVER_OK;
}

这里我们没有启用DMA传输数据的话,比较核心的代码在最后几行:

    spi->reg->CR2 |= SPI_CR2_RXNEIE;//使能接收中断
    SPI_TxData (spi->reg, spi->xfer);//输出第一个发送字节

第一个发送数据写入到SPI_DR寄存器后,RXNE置1,此时满足RXNE = 1 且 RXNEIE = 1 ,所以就进入SPI的中断服务函数。

SPI中断服务函数:

void SPI_IRQHandler (const SPI_RESOURCES *spi) 
{
  uint8_t  data_8bit;
  uint16_t data_16bit;
  uint32_t event, sr;

  event = 0U;

  // Save status register
  sr = spi->reg->SR;

  if ((sr & (SPI_SR_OVR | SPI_SR_UDR | SPI_SR_MODF)) != 0U) {
    if ((sr & SPI_SR_OVR) != 0U) {
      // Clear Overrun flag by reading data and status register
      if ((spi->reg->CR1 & SPI_CR1_DFF) == 0U) {
        // 8-bit data frame
        data_8bit = *(volatile uint8_t *)(&spi->reg->DR);
        if (spi->xfer->rx_cnt < spi->xfer->num) {
          if (spi->xfer->rx_buf != NULL) {
            *(spi->xfer->rx_buf++) = data_8bit;
          }
        }
      } else {
        // 16-bit data frame
        data_16bit = *(volatile uint16_t *)(&spi->reg->DR);
        if (spi->xfer->rx_cnt < spi->xfer->num) {
          if (spi->xfer->rx_buf != NULL) {
            *(spi->xfer->rx_buf++) = (uint8_t) data_16bit;
            *(spi->xfer->rx_buf++) = (uint8_t)(data_16bit >> 8U);
          }
        }
      }
      spi->xfer->rx_cnt++;
      sr = spi->reg->SR;

      spi->info->status.data_lost = 1U;
      event |= ARM_SPI_EVENT_DATA_LOST;
    }

    if ((sr & SPI_SR_UDR) != 0U) {
      // Underrun flag is set
      spi->info->status.data_lost = 1U;
      event |= ARM_SPI_EVENT_DATA_LOST;
    }

    if ((sr & SPI_SR_MODF) != 0U) {
      // Mode fault flag is set
      spi->info->status.mode_fault = 1U;

      // Write CR1 register to clear MODF flag
      spi->reg->CR1 = spi->reg->CR1;
      event |= ARM_SPI_EVENT_MODE_FAULT;
    }
  }
  else {//正常收发分支
    if (((sr & SPI_SR_RXNE) != 0U) && ((spi->reg->CR2 & SPI_CR2_RXNEIE) != 0U)) {
      // RX buffer Not Empty
      event = SPI_RxData (spi->reg, spi->xfer);

      if (event == 0U) {
        event = SPI_TxData (spi->reg, spi->xfer);
      }
      else {
        if (event == ARM_SPI_EVENT_TRANSFER_COMPLETE) {
          spi->info->status.busy = 0U;
        }
      }

      if ((event & ARM_SPI_EVENT_DATA_LOST) != 0U) {
        spi->info->status.data_lost = 1U;
      }
    }
  }

  // Send event
  if ((event != 0U) && ((spi->info->cb_event != NULL))) {
    spi->info->cb_event(event);
  }
}

#ifdef __SPI_DMA_TX
static void SPI_TX_DMA_Complete(const SPI_RESOURCES *spi) {

  if ((__HAL_DMA_GET_COUNTER(spi->tx_dma->hdma) != 0) && (spi->xfer->num != 0)) {
    // TX DMA Complete caused by transfer abort
    return;
  }

  spi->xfer->tx_cnt = spi->xfer->num;
}
#endif

中断服务函数正常收发分支如下:

else {
  if (((sr & SPI_SR_RXNE) != 0U) && ((spi->reg->CR2 & SPI_CR2_RXNEIE) != 0U))
  {
    // RXNE=1 且接收中断使能,收到完整1字节
    event = SPI_RxData (spi->reg, spi->xfer);

    if (event == 0U) {
      // 接收未完成,继续填充下一笔发送数据
      event = SPI_TxData (spi->reg, spi->xfer);
    }
    else {
      // SPI_RxData 返回传输完成事件:所有字节收发完毕
      if (event == ARM_SPI_EVENT_TRANSFER_COMPLETE) {
        spi->info->status.busy = 0U; // 核心:清除忙标志
      }
    }

    if ((event & ARM_SPI_EVENT_DATA_LOST) != 0U) {
      spi->info->status.data_lost = 1U;
    }
  }
}

核心执行顺序(中断触发一次完整流程)

  1. SPI_RxData():读取 DR,存入 rx_buf,rx_cnt 自增;
    • rx_cnt == num:返回 ARM_SPI_EVENT_TRANSFER_COMPLETE
    • 若没收完:返回 event=0
  2. 没收完数据 → 调用 SPI_TxData() 写下一个发送字节;
    • 当下一次 TXE 硬件置 1,会再次进入中断、再次走这套逻辑。
  3. 收完全部数据:
    • event 为传输完成,执行 spi->info->status.busy = 0U
    • 上层 while(GetStatus().busy) 循环退出

总结:整套驱动触发逻辑以 RXNE 接收中断为驱动主线。

附上 TX和RX函数

static uint32_t SPI_TxData (SPI_TypeDef *reg, SPI_TRANSFER_INFO *xfer) {
  uint32_t event;
  uint16_t data_16bit;
  uint8_t  data_8bit;

  if (xfer->tx_cnt < xfer->num) {
    if ((reg->CR1 & SPI_CR1_DFF) == 0U) {
      if (xfer->tx_buf != NULL) {
        data_8bit = *(xfer->tx_buf++);
      } else {
        data_8bit = (uint8_t)xfer->def_val;
      }

      // Write data to data register
      *(volatile uint8_t *)(&reg->DR) = data_8bit;
    } else {
      if (xfer->tx_buf != NULL) {
        data_16bit  = *(xfer->tx_buf++);
        data_16bit |= *(xfer->tx_buf++) << 8U;
      } else {
        data_16bit  = (uint16_t)xfer->def_val;
      }

      // Write data to data register
      *(volatile uint16_t *)(&reg->DR) = data_16bit;
    }

    xfer->tx_cnt++;

    event = 0U;
  }
  else {
    // Unexpected transfer, data lost
    event = ARM_SPI_EVENT_DATA_LOST;
  }

  return (event);
}
static uint32_t SPI_RxData (SPI_TypeDef *reg, SPI_TRANSFER_INFO *xfer) {
  uint32_t event;
  uint16_t data_16bit;
  uint8_t  data_8bit;

  if (xfer->rx_cnt < xfer->num) {
    if ((reg->CR1 & SPI_CR1_DFF) == 0U) {
      // 8-bit data frame
      data_8bit = *(volatile uint8_t *)(&reg->DR);
      if (xfer->rx_buf != NULL) {
        *(xfer->rx_buf++) = data_8bit;
      }
    } else {
      // 16-bit data frame
      data_16bit = *(volatile uint16_t *)(&reg->DR);
      if (xfer->rx_buf != NULL) {
        *(xfer->rx_buf++) = (uint8_t) data_16bit;
        *(xfer->rx_buf++) = (uint8_t)(data_16bit >> 8U);
      }
    }

    xfer->rx_cnt++;

    if (xfer->rx_cnt == xfer->num) {
      // Transfer completed
      event = ARM_SPI_EVENT_TRANSFER_COMPLETE;
    }
    else {
      event = 0U;
    }
  }
  else {
    // Unexpected transfer, data lost

    // Read DR to flush it
    *(volatile uint8_t *)(&reg->DR);

    event = ARM_SPI_EVENT_DATA_LOST;
  }

  return (event);
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐