通过STM32CubeMx使用STM32F4系列芯片的DMA中断发送数据翻转GPIO

2025博客之星年度评选已开启 10w+人浏览 386人参与

在上节文章已经完成了对GPIO的配置, 以及对一些必要的DMA函数进行了讲解

(新手向详解)通过STM32CubeMx使用STM32F411的DMA发送数据翻转GPIO-CSDN博客

接下来将会完成对DMA中断设置的工作, 以及相关函数的理解

HAL_StatusTypeDef HAL_DMA_Start_IT

(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)

HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
  HAL_StatusTypeDef status = HAL_OK;

  /* calculate DMA base and stream number */
  DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;
  
  /* Check the parameters */
  assert_param(IS_DMA_BUFFER_SIZE(DataLength));
 
  /* Process locked */
  __HAL_LOCK(hdma);
  
  if(HAL_DMA_STATE_READY == hdma->State)
  {
    /* Change DMA peripheral state */
    hdma->State = HAL_DMA_STATE_BUSY;
    
    /* Initialize the error code */
    hdma->ErrorCode = HAL_DMA_ERROR_NONE;
    
    /* Configure the source, destination address and the data length */
    DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
    
    /* Clear all interrupt flags at correct offset within the register */
    regs->IFCR = 0x3FU << hdma->StreamIndex;
    
    /* Enable Common interrupts*/
    hdma->Instance->CR  |= DMA_IT_TC | DMA_IT_TE | DMA_IT_DME;
    
    if(hdma->XferHalfCpltCallback != NULL)
    {
      hdma->Instance->CR  |= DMA_IT_HT;
    }
    
    /* Enable the Peripheral */
    __HAL_DMA_ENABLE(hdma);
  }
  else
  {
    /* Process unlocked */
    __HAL_UNLOCK(hdma);          
    
    /* Return error status */
    status = HAL_BUSY;
  }
  
  return status;
}

此函数可以以中断的方式开启DMA传输

如果传输完成 , 中断就会触发 , 属于事件中断的一种

但是现在有一个问题

我们并没有给DMA配置中断

CubeMx配置

生成代码

void DMA2_Stream0_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream0_IRQn 0 */

  /* USER CODE END DMA2_Stream0_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_memtomem_dma2_stream0);
  /* USER CODE BEGIN DMA2_Stream0_IRQn 1 */

  /* USER CODE END DMA2_Stream0_IRQn 1 */
}

这是中断向量表直接映射过来的函数

发生DMA中断后 , CPU是先去中断向量表里面 , 找到了DMA2的

再从此处跳转

跳转到这里

回调函数:

HAL_StatusTypeDef HAL_DMA_RegisterCallback

(DMA_HandleTypeDef hdma, HAL_DMA_CallbackIDTypeDef CallbackID, void ( pCallback)(DMA_HandleTypeDef *_hdma))

HAL_StatusTypeDef HAL_DMA_RegisterCallback(DMA_HandleTypeDef *hdma, HAL_DMA_CallbackIDTypeDef CallbackID, void (* pCallback)(DMA_HandleTypeDef *_hdma))
{

  HAL_StatusTypeDef status = HAL_OK;

  /* Process locked */
  __HAL_LOCK(hdma);

  if(HAL_DMA_STATE_READY == hdma->State)
  {
    switch (CallbackID)
    {
    case  HAL_DMA_XFER_CPLT_CB_ID:
      hdma->XferCpltCallback = pCallback;
      break;

    case  HAL_DMA_XFER_HALFCPLT_CB_ID:
      hdma->XferHalfCpltCallback = pCallback;
      break;

    case  HAL_DMA_XFER_M1CPLT_CB_ID:
      hdma->XferM1CpltCallback = pCallback;
      break;

    case  HAL_DMA_XFER_M1HALFCPLT_CB_ID:
      hdma->XferM1HalfCpltCallback = pCallback;
      break;

    case  HAL_DMA_XFER_ERROR_CB_ID:
      hdma->XferErrorCallback = pCallback;
      break;

    case  HAL_DMA_XFER_ABORT_CB_ID:
      hdma->XferAbortCallback = pCallback;
      break;

    default:
      /* Return error status */
      status =  HAL_ERROR;
      break;
    }
  }
  else
  {
    /* Return error status */
    status =  HAL_ERROR;
  }

  /* Release Lock */
  __HAL_UNLOCK(hdma);
  
  return status;
}

感兴趣可以慢慢研究 , 粗略来讲

此函数的作用就是 , 先找到DMA的句柄 , 然后再通过你给的DMA句柄注册一个回调

这个回调的类型

typedef enum
{
  HAL_DMA_XFER_CPLT_CB_ID         = 0x00U,  /*!< Full transfer     */
  HAL_DMA_XFER_HALFCPLT_CB_ID     = 0x01U,  /*!< Half Transfer     */
  HAL_DMA_XFER_M1CPLT_CB_ID       = 0x02U,  /*!< M1 Full Transfer  */
  HAL_DMA_XFER_M1HALFCPLT_CB_ID   = 0x03U,  /*!< M1 Half Transfer  */
  HAL_DMA_XFER_ERROR_CB_ID        = 0x04U,  /*!< Error             */
  HAL_DMA_XFER_ABORT_CB_ID        = 0x05U,  /*!< Abort             */
  HAL_DMA_XFER_ALL_CB_ID          = 0x06U   /*!< All               */
}HAL_DMA_CallbackIDTypeDef;

如果你想要一个传输完成后的回调

那么就把这个参数传到 CallbackID的形参位置

 HAL_DMA_XFER_CPLT_CB_ID

最后一个形参是你要传入的回调函数 , 就是前面设置的, 当DMA传输完成后 , 你要进入哪个回调函数

代码实战:

 void my_dma_TC_callback(DMA_HandleTypeDef *_hdma)
 {
        
 }

/* USER CODE BEGIN 2 */
       HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream0,
                                    HAL_DMA_XFER_CPLT_CB_ID,
                                        my_dma_TC_callback);                                                                                          my_dma_TC_callback);
  /* USER CODE END 2 */
 while (1)
  {
                HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, (uint32_t)data_gpio[counter % 2] , (uint32_t)&GPIOA->ODR , 1);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

那么想要在中断里持续反转

就只用定义一个变量

然后对2取余

uint32_t counter = 0 ;

 void my_dma_TC_callback(DMA_HandleTypeDef *_hdma)
 {
                counter ++ ;
                HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, (uint32_t)data_gpio[counter % 2] , (uint32_t)&GPIOA->ODR , 1);

 }

这样偶数时 取余是高电平 , 奇数时取余是低电平 , 然后循环调用, 就实现了高低电平转换

那么这样反转有什么用呢?

那么不妨假设数组变大

这样GPIO就可以按照你想要的任何时序去变化 , 这只是一个基础 , 输出的波形变化可以利用在各种领域各种地方

比如说ADC上电的时候要去写寄存器 , 就可以交给DMA去同步并发

<think>嗯,用户需要了解STM32CubeMX配置串口中断收发的方法和代码示例。从引用内容来看,用户已经查阅过几篇相关技术文档,主要集中在STM32F4和G0系列的串口中断实现,包括DMA+空闲中断等高级用法。 用户可能正在开发一个需要高效串口通信的项目,比如工业控制或物联网设备。ta的提问非常具体,说明有一定嵌入式基础,但可能对HAL中断机制还不够熟悉。值得注意的是,用户特别强调“使用中断”,说明ta可能遇到了轮询方式性能不足的问题。 从技术角度看,这个问题需要拆解为三个层面:CubeMX配置(硬件抽象层)、中断服务函数实现(驱动层)、用户回调函数(应用层)。引用中提到的DMA+空闲中断组合是高效方案,但用户初次接触可能更适合从基础中断模式开始讲解。 安全提示很重要,stm32f4xx_it.c这个文件经常被新手忽略,导致中断无法触发。代码示例中特意加入了LED翻转作为调试手段,因为实际开发中串口调试经常和指示灯配合使用。回调函数里的HAL_UART_Receive_IT重复调用是经典写法,这点必须强调。 相关问题的设计意图是覆盖用户后续可能遇到的痛点:为什么接收不完整(引出DMA方案),如何发送大量数据(解决阻塞问题),以及调试技巧。其中空闲中断的扩展问题特别关键,很多实际项目都会遇到可变长度数据帧的处理需求。 </think>以下基于STM32CubeMX配置串口中断收发的方法及代码示例,结合HAL实现流程清晰说明: --- ### **一、CubeMX配置步骤** 1. **启用串口** - 在 `Connectivity` 中选择目标串口(如USART1) - 模式选择 `Asynchronous`(异步模式) - 配置波特率、数据位、停止位等参数(常用115200-8-N-1) 2. **开启中断** - `NVIC Settings` 中勾选 `USARTx global interrupt` - 设置中断优先级(默认优先级即可) 3. **生成代码** - 点击 `GENERATE CODE` 生成MDK/IAR工程 --- ### **二、关键代码实现 #### 1. **初始化后启动接收中断** ```c /* main.c 用户代码区 */ int main(void) { HAL_Init(); SystemClock_Config(); MX_USART1_UART_Init(); HAL_UART_Receive_IT(&huart1, &rx_buffer, 1); // 启动单字节接收中断 while (1) { // 主循环其他任务 } } ``` #### 2. **中断回调函数(接收完成处理)** ```c /* 重写回调函数(放在main.c或自定义文件) */ uint8_t rx_buffer; // 接收缓存 uint8_t tx_buffer[] = "ACK\n"; // 应答数据 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 1. 回发接收到的数据(示例) HAL_UART_Transmit_IT(&huart1, &rx_buffer, 1); // 2. 重新使能接收中断(重要!) HAL_UART_Receive_IT(&huart1, &rx_buffer, 1); } } ``` #### 3. **发送中断使用示例** ```c // 在需要发送的位置调用 HAL_UART_Transmit_IT(&huart1, (uint8_t*)"Hello\n", 6); // 发送完成回调(可选) void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 发送完成翻转LED } } ``` --- ### **三、注意事项** 1. **中断开关** - 启用接收中断后,每次接收完成需**重新调用** `HAL_UART_Receive_IT()` 否则后续数据无法触发中断[^1][^3] 2. **回调函数位置** - 需在 `stm32f4xx_it.c` 的 `USART1_IRQHandler()` 中调用 `HAL_UART_IRQHandler(&huart1)` 3. **缓存管理** - 大数据量时建议用 **DMA+空闲中断**(参考引用[2]),避免频繁中断影响性能 --- ### **
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值