旮旯旭 发表于 2023-3-19 10:04:14

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






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



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



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



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



Project Manager 配置





点击GENERATE CODE生成MDK代码



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



《安富莱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 显存。从这张图可以看到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章中
/*
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);


/*
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、代码实现
#define U1_RX_SIZE(4*32)
#define U1_TX_SIZE(4*32)

__ALIGNED(32) uint8_t g_U1RxBuffer;
__ALIGNED(32) uint8_t g_U1TxBuffer;

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 */
}







eric2013 发表于 2023-3-19 10:46:35

谢谢楼主分享

YYY13941 发表于 2023-3-20 08:35:13

{:8:}{:8:}

054236 发表于 2023-4-23 15:02:28

感谢楼主{:32:}

morning_enr6U 发表于 2023-4-24 17:14:26

{:8:}{:8:}{:8:}{:8:}{:8:}

Alo9611 发表于 2024-1-16 11:09:58

eric2013 发表于 2023-3-19 10:46
谢谢楼主分享

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

eric2013 发表于 2024-1-16 11:17:52

Alo9611 发表于 2024-1-16 11:09
硬汉哥不考虑下收录到HAL教程里面吗?

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

Alo9611 发表于 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?mod=viewthread&tid=114241&fromuid=67150
(出处: 硬汉嵌入式论坛)

Alo9611 发表于 2024-1-16 21:03:18

貌似,从理论上分析,接收开DMA循环接收比较好,用户就只需要负责处理数据,不需要再次开启

旮旯旭 发表于 2024-1-18 11:02:36

Alo9611 发表于 2024-1-16 21:03
貌似,从理论上分析,接收开DMA循环接收比较好,用户就只需要负责处理数据,不需要再次开启

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

会飞的猪_2020 发表于 2024-2-26 13:51:59

如果接收到BUF和U1_RX_SIZE大小一样,是不是不会触发空闲中断?而是会进入两次HAL_UART_RECEPTION_TOIDLE?

eric2013 发表于 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传输完毕后,没有再进一步触发空闲。

/**
* @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 */
}
}

会飞的猪_2020 发表于 2024-2-26 18:12:54

eric2013 发表于 2024-2-26 15:55
测试效果是两次HAL_UART_RECEPTION_TOIDLE

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

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

eric2013 发表于 2024-2-27 07:53:04

会飞的猪_2020 发表于 2024-2-26 18:12
所以感觉原来的代码是不是在发送长度==size的时候会有bug, 我是在STM32F207发现进入两次的。

是的,实际上不是bug,传输两次后就完成了,不用再触发一次空闲中断了。
页: [1]
查看完整版本: STM32H7基于STM32CubeMX的串口DMA+空闲中断接收不定长数据实现(开MPU和DCache)