RT-Thread论坛
直播中

淡淡的爱

13年用户 975经验值
私信 关注
[问答]

STM32H7使用Serial V2框架配合DMA,可以修改DMA的buffer内存位置吗?

因为主内存设置在了dtcm,不能被dma访问,然而serial v2框架开启dma,缓存使用的是动态分配的内存,要如何修改buffer位置呢

回帖(1)

马占云

2025-10-13 18:14:42

在STM32H7系列中,一些内存区域(如DTCM)不能被DMA访问。Serial V2框架(通常指的是HAL库中的串口驱动)在使用DMA时,会动态分配内存作为DMA缓冲区。如果默认情况下,动态分配的内存位于DTCM(紧耦合内存)中,而DMA无法访问DTCM,那么我们需要将DMA缓冲区分配到其他可以被DMA访问的内存区域,比如AXI SRAM(地址0x24000000)或者SRAM1(地址0x30000000)等。

解决这个问题的方法通常有两种:

1. **修改动态分配内存的位置**:通过修改堆栈的位置,或者使用特定的内存分配函数(如使用`__attribute__((section("name")))`)将缓冲区分配到特定的内存段。

2. **自定义串口驱动**:覆盖Serial V2框架中关于DMA缓冲区的分配方式。

由于我们通常希望保持框架的完整性,第一种方法可能更可取,即通过链接器脚本和自定义分配函数来控制内存分配位置。

以下是具体的步骤:

### 步骤1:修改链接器脚本
在链接器脚本中,定义一个新的内存区域(例如,`AXI_RAM`)并创建一个新的段(例如,`.dma_buffer`)。然后,确保这个内存区域可以被DMA访问。

例如,在STM32H750的链接器脚本中(如`STM32H750VBTx_FLASH.ld`),添加或修改内存区域:

```ld
MEMORY
{
  DTCMRAM (xrw)   : ORIGIN = 0x20000000, LENGTH = 128K
  AXI_RAM (xrw)   : ORIGIN = 0x24000000, LENGTH = 512K   /* 可根据实际芯片调整 */
  ...
}

/* 在SECTIONS部分添加一个段用于DMA缓冲区 */
.dma_buffer (NOLOAD) :
{
  . = ALIGN(4);
  *(.dma_buffer)
  . = ALIGN(4);
} >AXI_RAM
```

### 步骤2:创建自定义的内存分配函数
我们可以创建一个函数,用于在特定的段中分配内存。例如:

```c
void *malloc_dma(size_t size) {
    // 使用__attribute__((section(".dma_buffer")))来指定分配的内存位于.dma_buffer段
    // 但是注意,我们不能直接这样使用,因为标准malloc并不支持指定段。
    // 所以我们需要自己实现一个简单的分配器或者使用链接器脚本预留空间。
}
```

然而,更简单的方法是创建一个大的数组,并放置到该段中,然后自己管理这个数组。但是,如果框架是动态分配内存,我们需要重写分配函数。

### 步骤3:重写串口驱动中DMA缓冲区的分配
在Serial V2框架中,DMA缓冲区的分配通常发生在HAL_UART_Receive_DMA和HAL_UART_Transmit_DMA等函数中。但是这些函数内部调用的是标准的内存分配(如malloc),所以我们需要替换内存分配函数。

但是,修改HAL库并不是一个好主意。另一种方法是,在调用HAL_UART_Receive_DMA之前,我们自己分配内存(使用我们自己的分配函数,将内存分配到DMA可访问的区域),然后将这个缓冲区传递给HAL库。

然而,查看HAL库的代码,我们发现HAL_UART_Receive_DMA函数有一个参数是用户提供的缓冲区:

```c
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
```

所以,我们可以自己管理接收和发送缓冲区。也就是说,我们不要使用HAL库内部动态分配的缓冲区,而是自己提供缓冲区,并且这个缓冲区位于DMA可访问的区域。

因此,我们可以这样做:

1. 在DMA可访问的区域(如AXI RAM)中定义缓冲区数组。

2. 使用`HAL_UART_Receive_DMA`时,传递这个缓冲区的地址。

例如:

```c
// 在特定的段中定义缓冲区
__attribute__((section(".dma_buffer"))) uint8_t uart_rx_buffer[128];
__attribute__((section(".dma_buffer"))) uint8_t uart_tx_buffer[128];

// 初始化后,启动DMA接收
HAL_UART_Receive_DMA(&huart1, uart_rx_buffer, 128);
```

这样,DMA就会使用我们指定的缓冲区,这个缓冲区位于AXI RAM中,可以被DMA访问。

### 步骤4:如果框架内部使用了动态分配怎么办?
然而,问题中提到“serial v2框架开启dma,缓存使用的是动态分配的内存”。这里需要明确:在HAL库中,当我们调用`HAL_UART_Receive_DMA`时,我们传递的缓冲区是用户提供的,HAL库内部不会重新分配接收缓冲区(它只是使用我们提供的缓冲区)。但是对于发送,同样也是由用户提供缓冲区。所以,我们完全可以通过自己管理缓冲区来解决。

但是,如果框架指的是更高级的框架(如中间件、RTOS的串口驱动等),它们可能会在内部动态分配内存。在这种情况下,我们需要修改该框架的代码,使其使用我们分配在DMA可访问区域的缓冲区。

例如,在FreeRTOS中,如果我们使用流缓冲区或消息缓冲区,我们可以自定义这些缓冲区的内存位置。或者,我们可以修改框架的分配方式。

### 总结
- 如果直接使用HAL库,那么自己定义位于DMA可访问区域的缓冲区数组,并将该数组传递给`HAL_UART_Receive_DMA`或`HAL_UART_Transmit_DMA`函数即可。
- 如果使用了更高级的框架,需要查看该框架的源代码,看是否允许自定义缓冲区的位置,或者修改框架使其允许使用外部指定的缓冲区。

所以,关键点在于:避免使用框架内部的动态分配,而是自己管理缓冲区,并将缓冲区放在DMA可访问的内存区域。

### 示例代码
下面是一个简单的示例,展示如何将缓冲区分配到AXI RAM,并用于串口DMA接收。

```c
// 在链接器脚本中定义的.dma_buffer段中分配空间
__attribute__((section(".dma_buffer"))) static uint8_t s_uart1_rx_buffer[128];

int main(void) {
  // ...初始化HAL和UART...

  // 启动DMA接收
  HAL_UART_Receive_DMA(&huart1, s_uart1_rx_buffer, sizeof(s_uart1_rx_buffer));

  while (1) {
    // 处理数据...
  }
}
```

这样,DMA就会使用我们定义的位于AXI RAM中的缓冲区。

在STM32H7中使用Serial V2框架(如STM32Cube HAL库)配合DMA时,若主内存(如DTCM)无法被DMA访问,需将DMA缓冲区分配到DMA可访问的内存区域(如AXI SRAM、SRAM1/2/3等)。以下是具体解决方案:


1. 自定义DMA缓冲区的内存位置


方法1:全局数组显式指定区域(推荐)


在代码中定义全局数组,并通过链接器脚本将其定位到DMA可访问的RAM区域(如AXI SRAM):


// 在DMA可访问区域定义缓冲区(如AXI SRAM地址0x24000000)
__attribute__((section(".axi_ram"))) uint8_t uartRxBuffer[128];
__attribute__((section(".axi_ram"))) uint8_t uartTxBuffer[128];

// 初始化UART DMA接收
HAL_UART_Receive_DMA(&huart1, uartRxBuffer, sizeof(uartRxBuffer));

方法2:动态分配在特定内存区域


重写内存分配函数,使用__attribute__或自定义分配器:


// 自定义DMA内存分配函数
void* DMAMalloc(size_t size) {
    return malloc(size); // 实际需结合MPU配置或自定义堆管理
}

// 使用示例
uint8_t *dmaBuffer = DMAMalloc(128);
HAL_UART_Receive_DMA(&huart1, dmaBuffer, 128);



2. 修改链接器脚本


在链接器脚本(如STM32H7xxxxx_FLASH.ld)中添加DMA可访问内存区域(如AXI SRAM):


MEMORY {
  DTCM  (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
  AXI_RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512K  /* DMA可访问区域 */
}

/* 定义.axi_ram段 */
.axi_ram (NOLOAD) : {
  . = ALIGN(4);
  *(.axi_ram)   /* 将标记为.axi_ram的变量放入此区域 */
} >AXI_RAM



3. 配置MPU(可选)


确保MPU允许DMA访问目标内存区域(STM32CubeMX默认配置通常已包含):


void MPU_Config(void) {
  MPU_Region_InitTypeDef MPU_InitStruct = {0};
  HAL_MPU_Disable();

  // 配置AXI SRAM为可被DMA访问(Shareable, Cacheable)
  MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  MPU_InitStruct.BaseAddress = 0x24000000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE; // DMA必须共享
  MPU_InitStruct.Number = MPU_REGION_NUMBER0;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  MPU_InitStruct.SubRegionDisable = 0x00;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
  HAL_MPU_ConfigRegion(&MPU_InitStruct);

  HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}



4. 在STM32CubeMX中配置(辅助)



  1. 分配内存区域

    System Core > SCTR中确认AXI SRAM已启用。

  2. 生成代码后手动修改

    通过上述方法显式指定缓冲区位置。




关键点总结



  • DMA可访问区域:使用AXI SRAM(0x24000000)或SRAM1/2/3(0x30000000等),避开DTCM。

  • 链接器脚本:定义自定义段(如.axi_ram)确保变量定位正确。

  • API调用:使用HAL_UART_Receive_DMA()时传入自定义缓冲区的地址。

  • 缓存一致性:若启用缓存(D-Cache),需调用SCB_CleanDCache_by_Addr()确保数据同步。


通过以上步骤,Serial V2框架的DMA缓冲区可被安全分配到DMA可访问的内存区域,避免因内存权限导致的DMA传输错误。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分