【STM32】使用DMA方式实现串口数据转发

前言

其实之前做RM比赛的时候就要有做转发器的想法,但是当时是因为云台和底盘上下分开,通过滑环相连,为了减少通讯线路,才萌生做转发器的想法,虽然最后方案讨论不够完善,所以就搁置了。现在是因为某模块以及焊死在板子上,没办法直接使用串口进行通讯,所以不得不使用串口转发的方式,来进行模块的连接。

实现串口转发的方式有很多,各有优劣。本文主要利用DMA方式实现串口转发功能。

环境

  • 芯片:STM32F103RCT6(芯片仅做示例,更换32其他型号实现原理相同)
  • HAL库版本:1.8.0
  • STM32CubeMX版本:5.6.1

总体思路

方案一:使能两个串口的DMA接收和发送通道,使能串口空闲中断判断数据帧结束,初始化缓存数组变量。

Created with Raphaël 2.2.0 开始 串口1 DMA接收数据 触发串口 空闲中断? 串口1中断处理函数 清除中断标志位 启动串口2 DMA发送 结束 yes no

优点:低CPU占用率和中断资源占用率,几乎不出现死机情况
缺点:高延时,内存占用率高,且一旦数据量超过缓存大小就会被覆盖。由于启用4条DMA通道,所以总线占用率也变高,进一步增加延时。

方案二:仅使能两个串口的DMA接收通道,使能串口接收中断判断一字节数据到达,初始化缓存数组变量。

Created with Raphaël 2.2.0 开始 触发串口 接收中断? 串口1中断处理函数 清除中断标志位 将串口DR寄存器数据 赋值给缓存数组 启动串口2 DMA发送一字节数据 结束 yes no

优点:超低延时,内存占用率低
缺点:占用大量中断资源,有概率遇到DMA忙导致数据丢失(大约每1160字节丢1字节),多次频繁请求DMA会出现死机情况(连续发送约16k字节数据后,出现死机情况)

实现

以方案二为例,使用CubeMX新建工程
1.编辑串口参数编辑串口参数

2.启用串口发送中断
启用串口发送中断
3.勾选启用串口全局中断
启用串口全局中断
4.取消选择生成HAL中断处理函数,这部分需要我们自己写
取消选择生成HAL中断处理函数
使用CubeMX生成我们的工程文件,并打开main.c。在main函数中添加如下代码

  /* USER CODE BEGIN 2 */
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); //使能串口1接收中断
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE); //使能串口2接收中断
  /* USER CODE END 2 */

打开stm32f1xx_it.c,找到USARTx_IRQHandler(x为你使用的串口号,x=1,2…),编写中断处理函数。

/* 初始化缓存数组 */
uint8_t USART1RxBuff[1];
uint8_t USART2RxBuff[1];

void USART1_IRQHandler(void)
{
  /* 判断中断类型为串口接收中断 */
  if ((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET))
  {
    /* 清除中断标志位 */
    __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
    
    /* 将串口接收到的数组移入缓存 */
    USART1RxBuff[0] = (uint8_t)(huart1.Instance->DR & (uint8_t)0x00FF);

    /* 启用DMA发送一字节数据 */
    HAL_UART_Transmit_DMA(&huart2, USART1RxBuff, USART1RxFIFO, 1);
  }
  
  /* 判断中断类型为串口发送中断 */
  if ((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) != RESET))
  {
    __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TC);
  }
  /* USER CODE END USART1_IRQn 0 */
}

编写DMA中断处理函数

/**
  * @brief This function handles DMA1 channel4 global interrupt.
  */
void DMA1_Channel4_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel4_IRQn 0 */
  huart1.gState = HAL_UART_STATE_READY;                //重置串口状态为就绪态
  hdma_usart1_tx.State = HAL_DMA_STATE_READY;          //重置DMA状态为就绪态
  __HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TC4); //清除DMA传输完成标志位
  __HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_HT4); //清除DMA半传输完成标志位
  __HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TE4); //清除DMA传输出错标志位
  __HAL_UNLOCK(&hdma_usart1_tx);                       //解锁DMA
  /* USER CODE END DMA1_Channel4_IRQn 0 */
}

同样的方法编写另一个串口的中断

然后编译+下载即可实现串口转发功能

后记

实际上这一版程序如文中所说并不完善,有很多正在尝试但是还是未解决的问题

  1. 丢字节的情况:尝试使用FIFO进行发送和接收,结果还是丢字节。DMA一次性发送512字节,结果发送2048,依然丢一字节,遂怀疑是硬件问题。
  2. 长时间多次请求DMA会导致死机,也就是方案二中收发到13-16k数据时会出现死机

其实这也算不上一个合格的转发,对于数据完整性没有任何保证,但是实际上要做到这些,需要上位机和下位机进行配合,如使用分包传输的方式。但是这种方式解决一些燃眉之急还是不错的。

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32中,使用DMA传输UART数据主要分为三个步骤: 1. 配置DMA控制器 首先需要配置DMA控制器,使其能够正确地管理UART数据的传输。这包括配置DMA传输方向、传输数据长度、传输模式等。 例如,使用STM32CubeMX生成的代码中,可以通过以下函数进行DMA控制器的配置: ```c /* Configure DMA controller */ static void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Channel5_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn); /* DMA1_Channel5 UART_RX Init */ hdma_usart2_rx.Instance = DMA1_Channel5; hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_rx.Init.Mode = DMA_CIRCULAR; hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx); /* DMA1_Channel4 UART_TX Init */ hdma_usart2_tx.Instance = DMA1_Channel4; hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_tx.Init.Mode = DMA_NORMAL; hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(&huart2, hdmatx, hdma_usart2_tx); } ``` 这个函数主要是配置了USART2的DMA传输通道,设置了接收和发送DMA模式,以及DMA传输的优先级等参数。 2. 启动DMA传输 接下来,需要启动DMA传输。在STM32中,可以使用HAL库提供的函数`HAL_UART_Receive_DMA()`和`HAL_UART_Transmit_DMA()`来启动DMA传输。 例如,使用`HAL_UART_Receive_DMA()`函数启动DMA接收: ```c /* Start DMA reception */ HAL_UART_Receive_DMA(&huart2, (uint8_t *)rx_buf, RX_BUFFER_SIZE); ``` 在这个例子中,我们使用了`rx_buf`作为接收缓冲区,`RX_BUFFER_SIZE`定义了缓冲区的大小。 3. 处理DMA传输完成的中断 最后,在DMA传输完成之后,需要处理DMA传输完成的中断。在中断处理函数中,可以读取接收缓冲区中的数据,或者更新发送缓冲区中的数据。 例如,使用以下代码处理DMA接收完成中断: ```c void DMA1_Channel5_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_usart2_rx); } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart2) { /* Process received data */ process_data(rx_buf); } } ``` 在这个例子中,我们使用了`process_data()`函数来处理接收到的数据。`HAL_UART_RxCpltCallback()`函数是HAL库提供的回调函数,当DMA接收完成时会自动调用该函数。 类似地,可以使用`HAL_UART_TxCpltCallback()`函数处理DMA发送完成中断。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值