硬汉嵌入式论坛

 找回密码
 立即注册
查看: 2692|回复: 13
收起左侧

[UART] STM32H7基于STM32CubeMX的串口DMA+空闲中断接收不定长数据实现(开MPU和DCache)

  [复制链接]

5

主题

192

回帖

212

积分

高级会员

积分
212
发表于 2023-3-19 10:04:14 | 显示全部楼层 |阅读模式

V7_UART_MPU_CACHE.zip (1.13 MB, 下载次数: 144)



Hello,大家好,今天我准备和大家聊一聊STM32V7开发板串口接收不定长数据的实现。之前在论坛里发过一篇相关的帖子,

链接为:STM32H7基于STM32CubeMX的串口DMA+空闲中断接收不定长数据实现(HAL库1.9.0自带函数实现) - STM32H7 - 硬汉嵌入式论坛 - Powered by Discuz! (armbbs.cn)
由于当时对MPU和DCache未开始研究,该方法是关闭了MPU和DCache后实现的方式,适合应用于无cache的MCU。
接下来我们来看看开启MPU和DCache后,使用串口DMA+空闲中断接收不定长数据需要用到的知识点和注意的地方。



1、STM32CubeMX 看不清可以点击图片全屏显示。
首先我们打开STM32CubeMX,选择芯片STM32H743XIHx,选择 Trace and Debug -> Debug ->Serial Wire
image-20230316224643430.png


点击System Core -> CORTEX_M7 ,开启ICache和DCache,配置MPU内存保护,设置AXI SRAM的地址0x24000000。
相关设置项参考硬汉文档资料V3.5版本中关于MPU的知识。
image-20230316231942685.png


选择芯片管脚PA9、PA10配置成串口1,Configration中 DMA Settings 点击 Add 添加串口1的DMA通道,
选择NVIC Settings 点击USART1 global interrupt 使能 Enable 打勾。其他保持默认。
image-20230316230446570.png


时钟树配置,这里做实验,时钟配置使用默认设置。也可以根据自己实际需要配置。
image-20230316230651773.png


Project Manager 配置
image-20230316230751025.png
image-20230316230809166.png
image-20230316230834109.png


点击GENERATE CODE生成MDK代码
image-20230316230950910.png


2、H7 SRAM知识和MDK相关配置
点击魔法棒 勾选 Use MicroLIB,不勾选IRAM1,勾选IRAM2。
image-20230319091717380.png


《安富莱STM32-V7开发板用户手册,含BSP驱动包设计(V3.5)》第25章中可以看到
TCM 区TCM : Tightly-Coupled Memory 紧密耦合内存 。ITCM 用于运行指令,也就是程序代码,DTCM用于数据存取,特点是跟内核速度一样,而片上 RAM 的速度基本都达不到这个速度,所以有降频处理。速度:400MHz。DTCM 地址:0x2000 0000,大小 128KB。ITCM 地址:0x0000 0000,大小 64KB。
AXI SRAM 区位于 D1 域,数据带宽是 64bit,挂在 AXI 总线上。除了 D3 域中的 BDMB 主控不能访问,其它都可以访问此 RAM 区。速度:200MHz。地址:0x2400 0000,大小 512KB。用途:用途不限,可以用于用户应用数据存储或者 LCD 显存。
image-20230319092258346.png
从这张图可以看到TCM和内核连接,DMA控制器无法访问。因此如果开启DMA时,分配的SRAM区域需要注意。
0x2400 0000地址为D1域AXI SRAM 区,DMA可以通过 D2-to-D1 AHB bus访问AXI SRAM。
3、串口DMA空闲中断以及MPU Cache相关的函数
《安富莱STM32-V7开发板用户手册,含BSP驱动包设计(V3.5)》第24章中
[C] 纯文本查看 复制代码
/*
SCB_InvalidateDCache_by_Addr 此函数用于将数据 Cache 无效化,无效化的意思是将 Cache Line 标记为无效,等同于删除操作。这样
Cache 空间就都腾出来了,可以加载新的数据。
*/
static inline void SCB_InvalidateDCache_by_Addr(volatile void *addr, int32_t dsize);

/*
SCB_CleanDCache_by_Addr 此函数与本章 24.6.7 小节中讲解的函数作用一样,区别是这里可以指定地址和存储区大小。用于将数据
Cache 清除,清除的意思是将 Cache Line 中标记为 dirty 的数据写入到相应的存储区。
*/
static inline void SCB_CleanDCache_by_Addr(volatile void *addr, int32_t dsize);



[C] 纯文本查看 复制代码
/*
HAL_UARTEx_ReceiveToIdle_DMA 此函数用于开启串口DMA+空闲中断
*/
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

/*
HAL_UARTEx_RxEventCallback 此函数用于串口接收触发空闲中断的回调
*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size);



4、代码实现
[C] 纯文本查看 复制代码
#define U1_RX_SIZE  (4*32)
#define U1_TX_SIZE  (4*32)

__ALIGNED(32) uint8_t g_U1RxBuffer[U1_RX_SIZE];
__ALIGNED(32) uint8_t g_U1TxBuffer[U1_TX_SIZE];

uint8_t g_U1RxCplt = 0; /* 未被DMA使用 */
uint8_t g_U1RxSize = 0; /* 未被DMA使用 */

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    /* HAL_UART_RECEPTION_TOIDLE 说明是DMA接收完成,或者半传输完成 */
    if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
    {
        HAL_UARTEx_ReceiveToIdle_DMA(huart, g_U1RxBuffer, U1_RX_SIZE);
    }
    /* HAL_UART_RECEPTION_STANDARD 缓冲区未满空闲中断回调函数处理 */
    if (huart->ReceptionType == HAL_UART_RECEPTION_STANDARD)
    {
        g_U1RxCplt = 1;
        g_U1RxSize = Size;
        HAL_UARTEx_ReceiveToIdle_DMA(huart, g_U1RxBuffer, U1_RX_SIZE);
    }
}

void AppUART_Init(void)
{
    HAL_UARTEx_ReceiveToIdle_DMA(&huart1, g_U1RxBuffer, U1_RX_SIZE);
}

void AppUART_Task(void)
{
    if (g_U1RxCplt == 1)
    {
        g_U1RxCplt = 0;
        /* 开启了cache 由于DMA更新了内存 cache不能同步,因此需要无效化从新加载 */
        SCB_InvalidateDCache_by_Addr((uint32_t *)g_U1RxBuffer, U1_RX_SIZE);
        //printf("%.*s\r\n", g_U1RxSize, g_U1RxBuffer);
        
        memcpy(g_U1TxBuffer, g_U1RxBuffer, g_U1RxSize);
        /* DMA发送时 cache的内容需要更新到SRAM中 */
        SCB_CleanDCache_by_Addr((uint32_t *)g_U1TxBuffer, U1_TX_SIZE);
        HAL_UART_Transmit_DMA(&huart1, g_U1TxBuffer, g_U1RxSize);
    }
    HAL_Delay(10);

}

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MPU Configuration--------------------------------------------------------*/
  MPU_Config();

  /* Enable I-Cache---------------------------------------------------------*/
  SCB_EnableICache();

  /* Enable D-Cache---------------------------------------------------------*/
  SCB_EnableDCache();

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  AppUART_Init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    AppUART_Task();
  }
  /* USER CODE END 3 */
}








评分

参与人数 1金币 +100 收起 理由
eric2013 + 100 很给力!

查看全部评分

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
发表于 2023-3-19 10:46:35 | 显示全部楼层
谢谢楼主分享
回复

使用道具 举报

4

主题

32

回帖

44

积分

新手上路

积分
44
发表于 2023-3-20 08:35:13 | 显示全部楼层
回复

使用道具 举报

0

主题

1

回帖

1

积分

新手上路

积分
1
发表于 2023-4-23 15:02:28 | 显示全部楼层
感谢楼主
回复

使用道具 举报

3

主题

1222

回帖

1231

积分

至尊会员

积分
1231
发表于 2023-4-24 17:14:26 | 显示全部楼层
回复

使用道具 举报

6

主题

8

回帖

26

积分

新手上路

积分
26
发表于 2024-1-16 11:09:58 | 显示全部楼层

硬汉哥不考虑下收录到HAL教程里面吗?
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
发表于 2024-1-16 11:17:52 | 显示全部楼层
Alo9611 发表于 2024-1-16 11:09
硬汉哥不考虑下收录到HAL教程里面吗?

收录了,视频教程里面收录了。
回复

使用道具 举报

6

主题

8

回帖

26

积分

新手上路

积分
26
发表于 2024-1-16 11:24:57 | 显示全部楼层
eric2013 发表于 2024-1-16 11:17
收录了,视频教程里面收录了。

哦哦,好的,是这个吗?因为我好像没有在pdf文档中看到有相关的介绍
BSP视频教程第21期:轻松一键实现串口DMA不定长收发,支持裸机和RTOS,含MDK和IAR两种玩法,比STM32CubeMX还方便(2022-07-24)
https://www.armbbs.cn/forum.php? ... 1&fromuid=67150
(出处: 硬汉嵌入式论坛)
回复

使用道具 举报

6

主题

8

回帖

26

积分

新手上路

积分
26
发表于 2024-1-16 21:03:18 | 显示全部楼层
貌似,从理论上分析,接收开DMA循环接收比较好,用户就只需要负责处理数据,不需要再次开启
回复

使用道具 举报

5

主题

192

回帖

212

积分

高级会员

积分
212
 楼主| 发表于 2024-1-18 11:02:36 | 显示全部楼层
Alo9611 发表于 2024-1-16 21:03
貌似,从理论上分析,接收开DMA循环接收比较好,用户就只需要负责处理数据,不需要再次开启

空间中断接收方式不适用DMA循环接收,空闲中断只是触发了中断,如果循环接收下一个字节存放的位置不是缓冲区0的位置,而是上一次触发空闲中断的位置,重新开启接收是为了放DMA接收重新指向缓冲区首地址。
回复

使用道具 举报

38

主题

194

回帖

318

积分

高级会员

积分
318
发表于 2024-2-26 13:51:59 | 显示全部楼层
如果接收到BUF和U1_RX_SIZE大小一样,是不是不会触发空闲中断?而是会进入两次HAL_UART_RECEPTION_TOIDLE?
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
发表于 2024-2-26 15:55:39 | 显示全部楼层
会飞的猪_2020 发表于 2024-2-26 13:51
如果接收到BUF和U1_RX_SIZE大小一样,是不是不会触发空闲中断?而是会进入两次HAL_UART_RECEPTION_TOIDLE?

测试效果是两次HAL_UART_RECEPTION_TOIDLE

实际上空闲中断标志也会置位的,只是DMA传输完成中断回调里面将空闲中断关闭了,所以DMA传输完毕后,没有再进一步触发空闲。

[C] 纯文本查看 复制代码
/**
  * @brief DMA UART receive process complete callback.
  * @param hdma DMA handle.
  * @retval None
  */
static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
{
  UART_HandleTypeDef *huart = (UART_HandleTypeDef *)(hdma->Parent);

  /* DMA Normal mode */
  if (hdma->Init.Mode != DMA_CIRCULAR)
  {
    huart->RxXferCount = 0U;

    /* Disable PE and ERR (Frame error, noise error, overrun error) interrupts */
    CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

    /* Disable the DMA transfer for the receiver request by resetting the DMAR bit
       in the UART CR3 register */
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;

    /* If Reception till IDLE event has been selected, Disable IDLE Interrupt */
    if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
    {
      CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
    }
  }

  /* Check current reception Mode :
     If Reception till IDLE event has been selected : use Rx Event callback */
  if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
  {
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Rx Event callback*/
    huart->RxEventCallback(huart, huart->RxXferSize);
#else
    /*Call legacy weak Rx Event callback*/
    HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
  else
  {
    /* In other cases : use Rx Complete callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Rx complete callback*/
    huart->RxCpltCallback(huart);
#else
    /*Call legacy weak Rx complete callback*/
    HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
}

回复

使用道具 举报

38

主题

194

回帖

318

积分

高级会员

积分
318
发表于 2024-2-26 18:12:54 | 显示全部楼层
eric2013 发表于 2024-2-26 15:55
测试效果是两次HAL_UART_RECEPTION_TOIDLE

实际上空闲中断标志也会置位的,只是DMA传输完成中断回调里 ...

所以感觉原来的代码是不是在发送长度==size的时候会有bug, 我是在STM32F207发现进入两次的。

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
发表于 2024-2-27 07:53:04 | 显示全部楼层
会飞的猪_2020 发表于 2024-2-26 18:12
所以感觉原来的代码是不是在发送长度==size的时候会有bug, 我是在STM32F207发现进入两次的。

是的,实际上不是bug,传输两次后就完成了,不用再触发一次空闲中断了。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-4-28 17:51 , Processed in 0.409477 second(s), 29 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表