硬汉嵌入式论坛

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

[LTDC] 【讨论】STM32H7的LTDC双缓冲不好用吗

[复制链接]

4

主题

9

回帖

21

积分

新手上路

积分
21
发表于 2025-5-29 11:02:12 | 显示全部楼层 |阅读模式


最近用STM32H743做一个音乐频谱,驱动一个1024x600的RGB屏,目前全屏刷新可以有30~50帧;


最近在加一些动画,想着在一个图层上用双缓存更新刷屏,但是开了行中断后,外面的代码就运行不了,
中断分组2,LTDC中断为(3,3)。

网上搜了一番,大多仅是介绍或者提及,目前还没看到实际在H7应用这种LTDC双缓存,是不好用吗?

以下是伪代码:

#include "main.h"
#include "stm32h7xx_hal.h"

/* 定义显示缓冲区大小 */
#define BUFFER_WIDTH  800
#define BUFFER_HEIGHT 480
#define PIXEL_FORMAT  LTDC_PIXEL_FORMAT_RGB565
#define PIXEL_SIZE    2 /* RGB565每像素2字节 */

/* 定义两个显示缓冲区 */
uint8_t displayBuffer1[BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE] __attribute__((aligned(32)));
uint8_t displayBuffer2[BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE] __attribute__((aligned(32)));

/* 当前活动缓冲区指针 */
uint8_t* activeBuffer = displayBuffer1;
uint8_t* drawingBuffer = displayBuffer2;

/* 缓冲区切换标志 */
volatile bool bufferReady = false;

/* 外设句柄声明 */
LTDC_HandleTypeDef hltdc;
DMA2D_HandleTypeDef hdma2d;
SDRAM_HandleTypeDef hsdram1;

/* 函数声明 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_LTDC_Init(void);
static void MX_DMA2D_Init(void);
static void MX_FMC_Init(void);
void HAL_LTDC_FrameEventCallback(LTDC_HandleTypeDef *hltdc);
void drawTestPattern(uint8_t* buffer);

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_FMC_Init();  /* 初始化SDRAM */
  MX_LTDC_Init(); /* 初始化LTDC */
  MX_DMA2D_Init(); /* 初始化DMA2D用于加速图形操作 */
  
  /* 初始化缓冲区内容 */
  drawTestPattern(displayBuffer1);
  drawTestPattern(displayBuffer2);
  
  /* 设置初始活动缓冲区 */
  hltdc.LayerCfg[0].FBStartAdress = (uint32_t)displayBuffer1;
  HAL_LTDC_ConfigLayer(&hltdc, &hltdc.LayerCfg[0], 0);
  
  /* 启用LTDC */
  HAL_LTDC_Enable(&hltdc);
  
  /* 设置LTDC帧更新中断优先级 */
  HAL_NVIC_SetPriority(LTDC_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(LTDC_IRQn);
  
  /* 启用LTDC帧更新中断 */
  __HAL_LTDC_ENABLE_IT(&hltdc, LTDC_IT_FU);
  
  while (1)
  {
    /* 绘制到后台缓冲区 */
    drawTestPattern(drawingBuffer);
   
    /* 标记缓冲区准备好切换 */
    bufferReady = true;
   
    /* 等待垂直同步事件处理完成 */
    while(bufferReady);
   
    /* 延时一段时间 */
    HAL_Delay(1000);
  }
}

void drawTestPattern(uint8_t* buffer)
{
  /* 使用DMA2D加速填充渐变图案 */
  hdma2d.Init.Mode = DMA2D_R2M;
  hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.OutputOffset = BUFFER_WIDTH - BUFFER_WIDTH;
  hdma2d.XferSize = BUFFER_WIDTH * BUFFER_HEIGHT;
  hdma2d.DestAddress = (uint32_t)buffer;
  
  if (HAL_DMA2D_Init(&hdma2d) != HAL_OK)
  {
    Error_Handler();
  }
  
  /* 设置前景色为蓝色 */
  __HAL_DMA2D_SET_FOREGROUND_COLOR(&hdma2d, 0x001F);
  
  if (HAL_DMA2D_Start(&hdma2d) != HAL_OK)
  {
    Error_Handler();
  }
  
  /* 等待DMA2D传输完成 */
  HAL_DMA2D_PollForTransfer(&hdma2d, 100);
}

void HAL_LTDC_FrameEventCallback(LTDC_HandleTypeDef *hltdc)
{
  /* 帧更新事件回调(替代垂直同步) */
  if(bufferReady)
  {
    /* 交换前后台缓冲区 */
    uint8_t* temp = activeBuffer;
    activeBuffer = drawingBuffer;
    drawingBuffer = temp;
   
    /* 更新LTDC指向新的活动缓冲区 */
    hltdc->LayerCfg[0].FBStartAdress = (uint32_t)activeBuffer;
    HAL_LTDC_ConfigLayer(hltdc, &hltdc->LayerCfg[0], 0);
   
    /* 清除缓冲区准备标志 */
    bufferReady = false;
  }
}

/* 系统时钟配置函数 */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** 初始化RCC振荡器
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 5;
  RCC_OscInitStruct.PLL.PLLN = 160;
  RCC_OscInitStruct.PLL.PLLP = 2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  RCC_OscInitStruct.PLL.PLLR = 2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  
  /** 初始化RCC时钟
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
                              |RCC_CLOCKTYPE_D1PCLK1|RCC_CLOCKTYPE_D3PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB4CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
  
  /** 初始化外设时钟
  */
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC|RCC_PERIPHCLK_FMC;
  PeriphClkInitStruct.PLL2.PLL2M = 2;
  PeriphClkInitStruct.PLL2.PLL2N = 25;
  PeriphClkInitStruct.PLL2.PLL2P = 2;
  PeriphClkInitStruct.PLL2.PLL2Q = 2;
  PeriphClkInitStruct.PLL2.PLL2R = 2;
  PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_3;
  PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
  PeriphClkInitStruct.FmcClockSelection = RCC_FMCCLKSOURCE_D1HCLK;
  PeriphClkInitStruct.LtdcClockSelection = RCC_LTDCCLKSOURCE_PLL3;
  PeriphClkInitStruct.PLL3.PLL3M = 4;
  PeriphClkInitStruct.PLL3.PLL3N = 24;
  PeriphClkInitStruct.PLL3.PLL3P = 2;
  PeriphClkInitStruct.PLL3.PLL3Q = 2;
  PeriphClkInitStruct.PLL3.PLL3R = 2;
  PeriphClkInitStruct.PLL3.PLL3RGE = RCC_PLL3VCIRANGE_3;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/* LTDC初始化函数 */
static void MX_LTDC_Init(void)
{
  LTDC_LayerCfgTypeDef pLayerCfg = {0};

  hltdc.Instance = LTDC;
  hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL;
  hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL;
  hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL;
  hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
  hltdc.Init.HorizontalSync = 40;
  hltdc.Init.VerticalSync = 9;
  hltdc.Init.AccumulatedHBP = 88;
  hltdc.Init.AccumulatedVBP = 32;
  hltdc.Init.AccumulatedActiveW = 888;
  hltdc.Init.AccumulatedActiveH = 512;
  hltdc.Init.TotalWidth = 910;
  hltdc.Init.TotalHeigh = 525;
  hltdc.Init.Backcolor.Blue = 0;
  hltdc.Init.Backcolor.Green = 0;
  hltdc.Init.Backcolor.Red = 0;
  if (HAL_LTDC_Init(&hltdc) != HAL_OK)
  {
    Error_Handler();
  }

  pLayerCfg.WindowX0 = 0;
  pLayerCfg.WindowX1 = BUFFER_WIDTH;
  pLayerCfg.WindowY0 = 0;
  pLayerCfg.WindowY1 = BUFFER_HEIGHT;
  pLayerCfg.PixelFormat = PIXEL_FORMAT;
  pLayerCfg.Alpha = 255;
  pLayerCfg.Alpha0 = 0;
  pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA;
  pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA;
  pLayerCfg.FBStartAdress = (uint32_t)displayBuffer1;
  pLayerCfg.ImageWidth = BUFFER_WIDTH;
  pLayerCfg.ImageHeight = BUFFER_HEIGHT;
  pLayerCfg.Backcolor.Blue = 0;
  pLayerCfg.Backcolor.Green = 0;
  pLayerCfg.Backcolor.Red = 0;
  if (HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg, 0) != HAL_OK)
  {
    Error_Handler();
  }
}

/* DMA2D初始化函数 */
static void MX_DMA2D_Init(void)
{
  hdma2d.Instance = DMA2D;
  hdma2d.Init.Mode = DMA2D_M2M;
  hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.OutputOffset = 0;
  if (HAL_DMA2D_Init(&hdma2d) != HAL_OK)
  {
    Error_Handler();
  }
}

/* FMC初始化函数 */
static void MX_FMC_Init(void)
{
  FMC_SDRAM_TimingTypeDef SdramTiming = {0};

  hsdram1.Instance = FMC_SDRAM_DEVICE;
  hsdram1.Init.SDBank = FMC_SDRAM_BANK2;
  hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8;
  hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12;
  hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;
  hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
  hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;
  hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
  hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
  hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
  hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0;
  
  SdramTiming.LoadToActiveDelay = 2;
  SdramTiming.ExitSelfRefreshDelay = 7;
  SdramTiming.SelfRefreshTime = 4;
  SdramTiming.RowCycleDelay = 7;
  SdramTiming.WriteRecoveryTime = 3;
  SdramTiming.RPDelay = 2;
  SdramTiming.RCDDelay = 2;

  if (HAL_SDRAM_Init(&hsdram1, &SdramTiming) != HAL_OK)
  {
    Error_Handler();
  }
  
  /* 配置SDRAM时序参数 */
  __IO uint32_t tmpmrd = 0;
  
  /* 发送SDRAM初始化序列 */
  tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1          |
                   SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL       |
                   SDRAM_MODEREG_CAS_LATENCY_3               |
                   SDRAM_MODEREG_OPERATING_MODE_STANDARD     |
                   SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
  
  /* 发送SDRAM模式寄存器配置命令 */
  HAL_SDRAM_SendCommand(&hsdram1, &Command, 0x1000);
}

/* GPIO初始化函数 */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOI_CLK_ENABLE();
  __HAL_RCC_GPIOJ_CLK_ENABLE();
  __HAL_RCC_GPIOK_CLK_ENABLE();

  /* 配置所有LTDC和FMC相关的GPIO引脚 */
  /* 此处省略具体GPIO配置,实际应用中需要根据硬件连接配置 */
}   

回复

使用道具 举报

14

主题

260

回帖

302

积分

高级会员

积分
302
发表于 2025-5-29 14:15:02 | 显示全部楼层
这个双缓存跟LTDC关系不太密切吧,主要就是更换LTDC指向缓存的地址吧。
反正我们使用的是TouchGFX带的双缓存,具体实现代码是在中断中完成的
void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef *hltdc)
    {
        if (LTDC->LIPCR == lcd_int_active_line)
        {
            //entering active area
            HAL_LTDC_ProgramLineEvent(hltdc, lcd_int_porch_line);
            HAL::getInstance()->vSync();
            OSWrappers::signalVSync();
            // Swap frame buffers immediately instead of waiting for the task to be scheduled in.
            // Note: task will also swap when it wakes up, but that operation is guarded and will not have
            // any effect if already swapped.
            HAL::getInstance()->swapFrameBuffers();
            GPIO::set(GPIO::VSYNC_FREQ);
        }
        else
        {
            //exiting active area
            HAL_LTDC_ProgramLineEvent(hltdc, lcd_int_active_line);
            GPIO::clear(GPIO::VSYNC_FREQ);
            HAL::getInstance()->frontPorchEntered();
        }
    }

HAL::getInstance()->swapFrameBuffers();就是切了一下缓存
回复

使用道具 举报

1万

主题

7万

回帖

11万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
117512
QQ
发表于 2025-5-30 09:13:59 | 显示全部楼层
好用,比如使用emwin的三缓冲做音乐频普和各种动画,效果很好
回复

使用道具 举报

1

主题

5

回帖

8

积分

新手上路

积分
8
发表于 2025-6-22 12:06:47 | 显示全部楼层
/* 定义两个显示缓冲区 */
uint8_t displayBuffer1[BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE] __attribute__((aligned(32)));
uint8_t displayBuffer2[BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE] __attribute__((aligned(32)));

楼主 你用的是全帧缓存吗?存放在SDRAM上还是存放在哪里?SDRAM是多大位宽的?有总线带宽竞争吗?
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-8-12 04:21 , Processed in 0.041407 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

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