【STM32N6570-DK】+ LCD绘图和字体显示
上期通过STM32CubeMX配置LTDC外设实现了lcd屏幕显示,本次继续深入实现lcd绘图和字体显示测试。由于STM32N6有很大的RAM空间且连续的,这次直接使用LCD整屏RAM buffer显存显示。
LCD整屏显存需要800X*480X*2=750K。AXISRAM3\4区域各自只有448K,合起来共896K。
具体地址使用AXISRAM3\4区域RAM。由于AXISRAM3\4区域默认是无效的,需要使能时钟和电源。
!(data/attachment/forum/202506/11/233244e7jb5gn4p7p8n77m.png "image.png")
首先配置一下RAMCFG:
!(data/attachment/forum/202506/11/233549uj5jt7q73n3lagi9.png "image.png")
添加额外代码使能代码如下:
!(data/attachment/forum/202506/11/233628ifnga2ksktcvwgx2.png "image.png")
```
/* SRAM3 and SRAM4 memories clock enable */
LL_MEM_EnableClock(LL_MEM_AXISRAM3);
LL_MEM_EnableClock(LL_MEM_AXISRAM4);
LL_MEM_EnableClock(LL_MEM_AXISRAM4);
LL_MEM_EnableClock(LL_MEM_AXISRAM4);
/* Power On AXSRAM3 and AXISRAM4 */
hramcfg_SRAM3.Instance = RAMCFG_SRAM3_AXI;
HAL_RAMCFG_EnableAXISRAM(&hramcfg_SRAM3);
hramcfg_SRAM4.Instance = RAMCFG_SRAM4_AXI;
HAL_RAMCFG_EnableAXISRAM(&hramcfg_SRAM4);
hramcfg_SRAM5.Instance = RAMCFG_SRAM5_AXI;
HAL_RAMCFG_EnableAXISRAM(&hramcfg_SRAM5);
hramcfg_SRAM6.Instance = RAMCFG_SRAM6_AXI;
HAL_RAMCFG_EnableAXISRAM(&hramcfg_SRAM6);
```
设置LCD显存起始地址在
#define LCD_LAYER_0_ADDRESS 0x34200000U /* SRAM3 */
可以在LTDC初始化中添加显存地址,或者在LTDC初始化之后添加调用如下函数设置显存地址。
HAL_LTDC_SetAddress(&hltdc,(uint32_t )LCD_LAYER_0_ADDRESS,0);
然后打开CACHE和MPU设置,这里要设置显存区域为非CACHE区。否则打开CACHE后显示会不正常。
!(data/attachment/forum/202506/11/234101vubw32s9iprw2yb9.png "image.png")
!(data/attachment/forum/202506/11/234117z0ldfi3x06z632pq.png "image.png")
生成代码之后,添加绘图函数。
```
/*
* Copyright (c) 2024, 流水源 Water Source (WoodData).
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-06 WoodData the first version
*/
#include "lcd_buffer.h"
/**
* @briefInitialize the st7789h2 LCD component.
* @retval Component status.
*/
uint32_t lcd_buffer_init(void *args)
{
return 0;
}
uint32_t lcd_buffer_display_on(uint8_t light)
{
if(light)
{
/* Enable backlight */
//HAL_GPIO_WritePin(LCD_BACKLIGHT_GPIO_PORT, LCD_BACKLIGHT_GPIO_PIN, GPIO_PIN_SET);
}else
{
/* Deactivate backlight */
//HAL_GPIO_WritePin(LCD_BACKLIGHT_GPIO_PORT, LCD_BACKLIGHT_GPIO_PIN, GPIO_PIN_RESET);
}
return 0;
}
uint32_t lcd_buffer_pixel (int16_t x,int16_t y,lcd_color_t color)
{
if((x >= LCD_WIDTH) || (y >= LCD_HEIGHT) || (x<0) || (y<0)) return 1;
/* Write data value to RAM memory */
*(__IO uint16_t*) (LCD_LAYER_0_ADDRESS + (2U*((y*LCD_WIDTH) + x))) = (uint16_t)color;
return 0;
}
uint32_t lcd_buffer_draw_bitmap (uint8_t mode, int16_t x,int16_t y,uint16_t w, uint16_t h, const uint8_t *bmp)
{
uint16_t x0,y0;
uint16_t *ptr;
ptr = (uint16_t *) bmp;
for(y0=0;y0<h;y0++)
{
for(x0=0;x0<w;x0++)
{
lcd_buffer_pixel(x+x0,y+y0,*ptr++);
}
}
return 0;
}
uint32_t lcd_buffer_fill (int16_t x,int16_t y,uint16_t w, uint16_t h, lcd_color_t color)
{
uint16_t x0,y0;
for(y0=0;y0<h;y0++)
{
for(x0=0;x0<w;x0++)
{
lcd_buffer_pixel(x+x0,y+y0,color);
}
}
return 0;
}
uint32_t lcd_buffer_draw_char_dot(uint8_t mode, int16_t x,int16_t y,uint16_t w,uint16_t h,const uint8_t *buff,lcd_color_t f_color,lcd_color_t b_color)
{
uint32_t i, j;
uint8_t *ptr= (uint8_t *)buff;
if((x >= LCD_WIDTH) || (y >= LCD_HEIGHT) || ((x+w)<=0) || ((y+h)<=0)) return 1;
switch(mode)
{
case 0:
for(i=0;i<h;i++)
{
for(j=0;j<w;)
{
if(*ptr & (0x80>>(j&0x07)))
{
lcd_buffer_pixel(x+j,y+i,f_color);
}else
{
lcd_buffer_pixel(x+j,y+i,b_color);
}
j++;
if(!(j&0x07))
{
ptr++;
}
}
if(w & 0x07) ptr++;
}
break;
case 1:
for(i=0;i<h;i++)
{
for(j=0;j<w;)
{
if(*ptr & (0x01<<(j&0x07)))
{
lcd_buffer_pixel(x+j,y+i,f_color);
}else
{
lcd_buffer_pixel(x+j,y+i,b_color);
}
j++;
if(!(j&0x07))
{
ptr++;
}
}
if(w & 0x07) ptr++;
}
break;
case 2:
break;
case 3:
break;
}
return 0;
}
lcd_adapter_t lcd_buffer_drv =
{
.info.sx = 0,
.info.sy = 0,
.info.width = LCD_WIDTH,
.info.height = LCD_HEIGHT,
.info.color_bit = 16,
.info.color_type = LCD_RGB565_16BIT,
.func.init = lcd_buffer_init,
.func.flush = NULL,
.func.get_backlight = NULL,
.func.set_backlight = lcd_buffer_display_on,
.draw.get_point = NULL,
.draw.set_point = lcd_buffer_pixel,
.draw.clear= NULL,
.draw.h_line = NULL,
.draw.v_line = NULL,
.draw.line = NULL,
.draw.fill = lcd_buffer_fill,
.draw.draw_bmp = lcd_buffer_draw_bitmap,
.draw.draw_char_bmp = lcd_buffer_draw_char_dot, //
};
```
主函数测试代码如下:
```
int main(void)
{
uint32_t i;
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* Enable the CPU Cache */
/* Enable I-Cache---------------------------------------------------------*/
SCB_EnableICache();
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
/* MCU Configuration--------------------------------------------------------*/
/* MPU Configuration--------------------------------------------------------*/
MPU_Config();
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* Configure the peripherals common clocks */
PeriphCommonClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_RAMCFG_Init();
MX_I2C1_Init();
MX_I2C2_Init();
MX_LTDC_Init();
MX_USART1_UART_Init();
MX_XSPI1_Init();
SystemIsolation_Config();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
HAL_LTDC_SetAddress(&hltdc,(uint32_t )LCD_LAYER_0_ADDRESS,0);
lcd_display_init(&lcd_buffer_drv);
lcd_clear(LCD_WHITE);
HAL_Delay(300);
lcd_clear(LCD_BLACK);
HAL_Delay(300);
lcd_clear(LCD_RED);
HAL_Delay(300);
lcd_clear(LCD_GREEN);
HAL_Delay(300);
lcd_clear(LCD_BLUE);
HAL_Delay(300);
for(i=0;i<10;i++){ lcd_draw_line(10+i*50,0,10+i*50,480,LCD_RED);HAL_Delay(200);}
for(i=0;i<10;i++){lcd_draw_line(0,10+i*40,800,10+i*40,LCD_GREEN);HAL_Delay(200);}
for(i=0;i<10;i++){lcd_draw_circle(400,240,10+i*20,LCD_YELLOW);HAL_Delay(200);}
for(i=0;i<10;i++){lcd_draw_rect(400-i*20,240-i*20,i*40,i*40,LCD_MAGENTA);HAL_Delay(200);}
lcd_fill_rect(80,30,80,80,LCD_RED);
lcd_draw_image(30,80,80,80,(uint8_t *)c_bmp_muzhu_RGB565);
lcd_set_font_window(0,0,lcd_get_width(),lcd_get_height());
lcd_set_font(&Font_ASCII16X8_default);
lcd_disp_str_at(200,200,"STM32N6-DK");
lcd_disp_str_at(250,250,"WoodData");
lcd_set_font(&Font_ASCII24x40);
lcd_disp_str_at(20,20,"Font_ASCII24x40");
HAL_Delay(5000);
while (1)
{
lcd_clear(LCD_RED);
HAL_Delay(1000);
lcd_clear(LCD_GREEN);
HAL_Delay(1000);
lcd_clear(LCD_BLUE);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
```
下面编译烧录查看显示效果:
!(data/attachment/forum/202506/11/235309sphndnnosojnhpb9.png "image.png")
!(data/attachment/forum/202506/11/235401nob35ao8j88pa330.png "1749657207725.png")
!(data/attachment/forum/202506/11/235411odedgg3wwjewtjji.png "1749657230352.png")
!(data/attachment/forum/202506/11/235422csbdj39ow4j4b4nw.png "1749657223803.png")
[!(/source/plugin/zhanmishu_markdown/template/editor/images/upload.svg) 附件:n6_lcd.zip](forum.php?mod=attachment&aid=2413212 "attachment")
利用LTDC和DMA2D提升显示效率,双缓冲避免闪烁。 RGB 接口数据线需等长布线,避免信号延迟差异 如果使用自定义字体库,需要将字体数据加载到显存中,并编写相应的显示函数。 针对支持DMA的LCD接口,使用DMA加速数据传输。 STM32芯片本身没有直接支持汉字显示的功能,因此需要借助外部字库。 绘图操作(如画圆、填充)需临时缓冲区,避免覆盖帧缓冲区,建议使用栈或堆 提前建立显示缓冲区,避免频繁操作LCD。 选择一个适合STM32的图形库,如u8g2、lvgl、TouchGFX等 如果需要显示复杂的图形或动画,可以考虑使用硬件加速(如DMA2D或GPU)来提高性能。 使用前缓冲(显示)和后缓冲(绘制),避免画面撕裂。切换缓冲时通过 LCD_WriteReg() 写入新的帧缓冲地址。 合理配置驱动参数,优化绘图与字体渲染逻辑,控制资源占用。 使用GB2312编码索引,可建立结构体数组。 使用合适的LCD驱动程序,根据LCD控制器的特性和数据手册进行配置。 优化绘图和字体显示算法,减少不必要的计算和内存访问。 启用双缓冲或调整刷新率 通过去耦电容、差分信号等措施抑制干扰 开启缓存(CACHE)和内存保护单元(MPU),并确保显存区域设置为非缓存区,以避免显示异常。 LCD的刷新率足够高,以避免闪烁和拖影。 可简化文本渲染和其他图形元素的创建。