【STemWin】STM32H743VI单片机通过LTDC驱动4.3寸800×480高分辨率的40-pin FPC彩屏并裸机移植STemWin图形库

16 篇文章 7 订阅

本程序使用的单片机为STM32H743VI,晶振大小为25MHz。程序利用LTDC驱动如图所示的RGB888接口的4.3寸高分辨率屏。

  

  

彩屏的连接口是40-pin FPC接口,需要FPC座来连接。请注意FPC座的正反面。下面的绿色板子是一个FPC座转直插排针的板子。

插的时候注意黑色那一面是不导电的,要朝外插进去,里面金属那一面才是导电的。一定要根据FPC座的情况确定1脚的方向,不要搞反了。

  

笔者用的液晶型号是智晟鑫ZSX430-B4025,FPC座是40PIN翻盖式下接。从液晶屏背面看,1脚在最右边,所以上面的转接板下方的白色数字标错了。39其实是接的液晶的第2脚,1接的是液晶的第40脚。
此液晶屏的1脚和2脚连接21V的背光电源,只有连了背光,显示屏才会显示图像。在只连接了背光引脚的情况下,显示屏是黑的。背光正常时,可以从彩屏底部的小孔处看到白色的光亮。

电源只有3.3V的情况下,可以用KA2707升压芯片升压到21V给背光供电,电路如下图所示。背光是由几个LED灯串联而成,所以需要的电压较高。LCD_BL接单片机的普通I/O口,接到定时器的PWM输出通道上,可以用PWM来控制液晶屏的亮度。单片机上还可以接一个光敏电阻,根据外界环境光线强度自动调节彩屏的显示亮度。LED-和LED+分别接彩屏的1脚和2脚。
请注意L2一定要选择功率电感,不然亮度高了的话电感会冒烟。

关于智晟鑫ZSX430-B4025的详细资料,请参阅:百度网盘 请输入提取码(提取码:7wey)
资料里面包含了液晶手册,引脚说明,时序说明,还有KA2707背光芯片的示例电路。值得注意的是,此款液晶不带触摸功能,所以37~40脚是空引脚,XR、YD、XL、YU本来的意思分别是x right、y down、x left、y up,是预留给XPT2046触摸芯片用的。

以下是FPC排线和单片机LTDC的连接方式。STM32H743VI是100脚的芯片,LTDC只支持RGB565和RGB666,不支持RGB888,本程序采用的是RGB565,因此颜色线只接R3~7、G2~7和B3~7,剩余不用的R0~2、G0~1和B0~2可以悬空或者接地。除了颜色线以外,LTDC还有LTDC_CLK,LTDC_HSYNC,LTDC_VSYNC和LTDC_DE四根线。

【不含STemWin图形库的程序】

程序下载地址:百度网盘 请输入提取码(提取码:9crm)

下面的程序初始化完LTDC后,直接显示保存在单片机内部Flash中的一幅花的图片。

#include <stdio.h>
#include <stm32h7xx.h>
#include <string.h>
#include "common.h"
#include "images.h"

#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 480

#define SCREEN_HSW 1
#define SCREEN_VSW 1
#define SCREEN_HBP 46
#define SCREEN_VBP 23
#define SCREEN_HFP 210
#define SCREEN_VFP 22

LTDC_HandleTypeDef hltdc;

static void ltdc_init(void)
{
  GPIO_InitTypeDef gpio;
  LTDC_LayerCfgTypeDef layer;
  PLL3_ClocksTypeDef clock_result;
  RCC_PeriphCLKInitTypeDef clock = {0};
  
  __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();
  
  // PA3: LTDC_B5, PA4: LTDC_VSYNC, PA5: LTDC_R4 ,PA6: LTDC_G2
  gpio.Alternate = GPIO_AF14_LTDC;
  gpio.Mode = GPIO_MODE_AF_PP;
  gpio.Pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6;
  gpio.Pull = GPIO_NOPULL;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &gpio);
  
  // PB0: LTDC_R3, PB1: LTDC_R6
  gpio.Alternate = GPIO_AF9_LTDC;
  gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1;
  HAL_GPIO_Init(GPIOB, &gpio);
  
  // PB8~9: LTDC_B6~7, PB10~11: LTDC_G4~5
  gpio.Alternate = GPIO_AF14_LTDC;
  gpio.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11;
  HAL_GPIO_Init(GPIOB, &gpio);
  
  // PC0: LTDC_R5, PC6: LTDC_HSYNC, PC7: LTDC_G6
  gpio.Pin = GPIO_PIN_0 | GPIO_PIN_6 | GPIO_PIN_7;
  HAL_GPIO_Init(GPIOC, &gpio);
  
  // PD3: LTDC_G7, PD10: LTDC_B3
  gpio.Pin = GPIO_PIN_3 | GPIO_PIN_10;
  HAL_GPIO_Init(GPIOD, &gpio);
  
  // PE11: LTDC_G3, PE12: LTDC_B4, PE13: LTDC_DE, PE14: LTDC_CLK, PE15: LTDC_R7
  gpio.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
  HAL_GPIO_Init(GPIOE, &gpio);
  
  // LTDC时钟频率: 30MHz
  clock.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
  clock.PLL3.PLL3FRACN = 0;
  clock.PLL3.PLL3M = 25;
  clock.PLL3.PLL3N = 150;
  clock.PLL3.PLL3P = 2;
  clock.PLL3.PLL3Q = 2;
  clock.PLL3.PLL3R = 5;
  clock.PLL3.PLL3RGE = RCC_PLL3VCIRANGE_3;
  clock.PLL3.PLL3VCOSEL = RCC_PLL3VCOWIDE;
  HAL_RCCEx_PeriphCLKConfig(&clock);
  
  HAL_RCCEx_GetPLL3ClockFreq(&clock_result);
  printf("LTDC clock: %.1fMHz\n", clock_result.PLL3_R_Frequency / 1000000.0f);
  
  __HAL_RCC_LTDC_CLK_ENABLE();
  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 = SCREEN_HSW - 1;
  hltdc.Init.VerticalSync = SCREEN_VSW - 1;
  hltdc.Init.AccumulatedHBP = SCREEN_HSW + SCREEN_HBP - 1;
  hltdc.Init.AccumulatedVBP = SCREEN_VSW + SCREEN_VBP - 1;
  hltdc.Init.AccumulatedActiveW = SCREEN_HSW + SCREEN_HBP + SCREEN_WIDTH - 1;
  hltdc.Init.AccumulatedActiveH = SCREEN_VSW + SCREEN_VBP + SCREEN_HEIGHT - 1;
  hltdc.Init.TotalWidth = SCREEN_HSW + SCREEN_HBP + SCREEN_WIDTH + SCREEN_HFP - 1;
  hltdc.Init.TotalHeigh = SCREEN_VSW + SCREEN_VBP + SCREEN_HEIGHT + SCREEN_VFP - 1;
  hltdc.Init.Backcolor.Red = 0;
  hltdc.Init.Backcolor.Green = 0;
  hltdc.Init.Backcolor.Blue = 0;
  HAL_LTDC_Init(&hltdc);
  
  layer.WindowX0 = 0;
  layer.WindowX1 = IMAGE_FLOWER_WIDTH;
  layer.WindowY0 = 0;
  layer.WindowY1 = IMAGE_FLOWER_WIDTH;
  layer.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
  layer.Alpha = 255; // 整体(窗口区域+非窗口区域)半透明度(0为透明, 255为不透明), 窗口区域不会叠加上非窗口区域的颜色
  layer.Alpha0 = 0; // 非窗口区域半透明度
  layer.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
  layer.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
  layer.FBStartAdress = (uint32_t)image_flower; // 窗口区域显示内容
  layer.ImageWidth = IMAGE_FLOWER_WIDTH;
  layer.ImageHeight = IMAGE_FLOWER_WIDTH;
  layer.Backcolor.Red = 0; // 非窗口区域显示颜色
  layer.Backcolor.Green = 0;
  layer.Backcolor.Blue = 0;
  HAL_LTDC_ConfigLayer(&hltdc, &layer, LTDC_LAYER_1);
  
  // 为了消除开机时屏幕上随机黑线, 必须控制背光引脚
  // 默认情况下背光是关闭的, 当LTDC打开后延时几百毫秒再开背光
}

int main(void)
{
  uint8_t data;
  
  HAL_Init();
  
  clock_init();
  usart_init(115200);
  printf("STM32H743VI LTDC\n");
  printf("SystemCoreClock=%u\n", SystemCoreClock);
  
  ltdc_init();
  while (1)
  {
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
    {
      HAL_UART_Receive(&huart1, &data, 1, HAL_MAX_DELAY);
      printf("Data: 0x%02x\n", data);
    }
  }
}

【含有STemWin图形库的程序】

程序下载地址:百度网盘 请输入提取码(提取码:s3b4)

STM32H743VI单片机的内部SRAM分为4个区域:IRAM1、IRAM2、RAM1和RAM2,如下图所示。

若想要将显存放在内部SRAM中,则只能放在IRAM2中,因为LTDC只支持从0x24000000这个地址块上读取图像数据。从H7的手册中可以看到,LTDC只连接了AXI SRAM(也就是0x24000000那块SRAM),和SRAM1~4和Backup RAM都没有连接上。

因此,在Keil中定义screen_buffer数组的时候,需要使用__attribute__((at(0x24000000)))语句:

uint16_t screen_buffer[DISPLAY_HEIGHT][DISPLAY_WIDTH] __attribute__((at(0x24000000))); // 必须在项目属性中取消IRAM2 default的勾选才能编译成功

而且还要注意项目属性中IRAM2的default复选框不能勾选,否则编译不通过。

由于这块SRAM的大小有限,只有512KB,所以无法保存完整个屏幕800×480的区域,我们只选择保存并显示546×480大小的图像,其余区域显示为黑色。

#ifndef _SCREEN_H
#define _SCREEN_H

#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 480
#define DISPLAY_WIDTH 546
#define DISPLAY_HEIGHT SCREEN_HEIGHT

#define SCREEN_HSW 1
#define SCREEN_VSW 1
#define SCREEN_HBP 46
#define SCREEN_VBP 23
#define SCREEN_HFP 210
#define SCREEN_VFP 22

extern uint16_t screen_buffer[DISPLAY_HEIGHT][DISPLAY_WIDTH];

typedef void (*Screen_Function)(void);

void Screen_CopyBuffer(int layer, int src, int dest);
void Screen_CopyRect(int layer, int x0, int y0, int x1, int y1, int xsize, int ysize);
void Screen_CopyRectFromMemdev(void *dest, const void *src, int xsize, int ysize, int dest_linebytes, int src_linebytes);
void Screen_FillRect(int layer, int x0, int y0, int x1, int y1, uint32_t color);
void Screen_Init(void);

#endif

修改后的程序:

#include <stdio.h>
#include <stm32h7xx.h>
#include <string.h>
#include "Screen.h"

DMA2D_HandleTypeDef hdma2d;
LTDC_HandleTypeDef hltdc;
uint16_t screen_buffer[DISPLAY_HEIGHT][DISPLAY_WIDTH] __attribute__((at(0x24000000))); // 必须在项目属性中取消IRAM2 default的勾选才能编译成功

void Screen_CopyBuffer(int layer, int src, int dest)
{
  memcpy(screen_buffer[dest], screen_buffer[src], DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t));
}

void Screen_CopyRect(int layer, int x0, int y0, int x1, int y1, int xsize, int ysize)
{
  hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.Mode = DMA2D_M2M;
  hdma2d.Init.OutputOffset = DISPLAY_WIDTH - xsize;
  HAL_DMA2D_Init(&hdma2d);
  
  hdma2d.LayerCfg[DMA2D_FOREGROUND_LAYER].InputColorMode = DMA2D_INPUT_RGB565;
  hdma2d.LayerCfg[DMA2D_FOREGROUND_LAYER].InputOffset = DISPLAY_WIDTH - xsize;
  HAL_DMA2D_ConfigLayer(&hdma2d, DMA2D_FOREGROUND_LAYER);
  
  HAL_DMA2D_Start(&hdma2d, (uint32_t)&screen_buffer[y0][x0], (uint32_t)&screen_buffer[y1][x1], xsize, ysize);
  HAL_DMA2D_PollForTransfer(&hdma2d, HAL_MAX_DELAY);
}

void Screen_CopyRectFromMemdev(void *dest, const void *src, int xsize, int ysize, int dest_linebytes, int src_linebytes)
{
  hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.Mode = DMA2D_M2M;
  hdma2d.Init.OutputOffset = dest_linebytes / 2 - xsize;
  HAL_DMA2D_Init(&hdma2d);
  
  hdma2d.LayerCfg[DMA2D_FOREGROUND_LAYER].InputColorMode = DMA2D_INPUT_RGB565;
  hdma2d.LayerCfg[DMA2D_FOREGROUND_LAYER].InputOffset = src_linebytes / 2 - xsize;
  HAL_DMA2D_ConfigLayer(&hdma2d, DMA2D_FOREGROUND_LAYER);
  
  HAL_DMA2D_Start(&hdma2d, (uint32_t)src, (uint32_t)dest, xsize, ysize);
  HAL_DMA2D_PollForTransfer(&hdma2d, HAL_MAX_DELAY);
}

void Screen_FillRect(int layer, int x0, int y0, int x1, int y1, uint32_t color)
{
  int height = y1 - y0 + 1;
  int width = x1 - x0 + 1;
  
  hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.Mode = DMA2D_R2M;
  hdma2d.Init.OutputOffset = DISPLAY_WIDTH - width;
  HAL_DMA2D_Init(&hdma2d);
  
  color = ((color & 0xf800) << 8) | ((color & 0x7e0) << 5) | ((color & 0x1f) << 3); // RGB565转RGB888
  HAL_DMA2D_Start(&hdma2d, color, (uint32_t)&screen_buffer[y0][x0], width, height);
  HAL_DMA2D_PollForTransfer(&hdma2d, HAL_MAX_DELAY);
}

void Screen_Init(void)
{
  GPIO_InitTypeDef gpio;
  LTDC_LayerCfgTypeDef layer;
  PLL3_ClocksTypeDef clock_result;
  RCC_PeriphCLKInitTypeDef clock = {0};
  
  __HAL_RCC_CRC_CLK_ENABLE(); // STemWin GUI_Init前必须打开CRC
  __HAL_RCC_DMA2D_CLK_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();
  
  // PA3: LTDC_B5, PA4: LTDC_VSYNC, PA5: LTDC_R4 ,PA6: LTDC_G2
  gpio.Alternate = GPIO_AF14_LTDC;
  gpio.Mode = GPIO_MODE_AF_PP;
  gpio.Pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6;
  gpio.Pull = GPIO_NOPULL;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &gpio);
  
  // PB0: LTDC_R3, PB1: LTDC_R6
  gpio.Alternate = GPIO_AF9_LTDC;
  gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1;
  HAL_GPIO_Init(GPIOB, &gpio);
  
  // PB8~9: LTDC_B6~7, PB10~11: LTDC_G4~5
  gpio.Alternate = GPIO_AF14_LTDC;
  gpio.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11;
  HAL_GPIO_Init(GPIOB, &gpio);
  
  // PC0: LTDC_R5, PC6: LTDC_HSYNC, PC7: LTDC_G6
  gpio.Pin = GPIO_PIN_0 | GPIO_PIN_6 | GPIO_PIN_7;
  HAL_GPIO_Init(GPIOC, &gpio);
  
  // PD3: LTDC_G7, PD10: LTDC_B3
  gpio.Pin = GPIO_PIN_3 | GPIO_PIN_10;
  HAL_GPIO_Init(GPIOD, &gpio);
  
  // PE11: LTDC_G3, PE12: LTDC_B4, PE13: LTDC_DE, PE14: LTDC_CLK, PE15: LTDC_R7
  gpio.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
  HAL_GPIO_Init(GPIOE, &gpio);
  
  // LTDC时钟频率: 30MHz
  clock.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
  clock.PLL3.PLL3FRACN = 0;
  clock.PLL3.PLL3M = 25;
  clock.PLL3.PLL3N = 150;
  clock.PLL3.PLL3P = 2;
  clock.PLL3.PLL3Q = 2;
  clock.PLL3.PLL3R = 5;
  clock.PLL3.PLL3RGE = RCC_PLL3VCIRANGE_3;
  clock.PLL3.PLL3VCOSEL = RCC_PLL3VCOWIDE;
  HAL_RCCEx_PeriphCLKConfig(&clock);
  
  HAL_RCCEx_GetPLL3ClockFreq(&clock_result);
  printf("LTDC clock: %.1fMHz\n", clock_result.PLL3_R_Frequency / 1000000.0f);
  
  __HAL_RCC_LTDC_CLK_ENABLE();
  __HAL_RCC_DMA2D_CLK_ENABLE();
  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 = SCREEN_HSW - 1;
  hltdc.Init.VerticalSync = SCREEN_VSW - 1;
  hltdc.Init.AccumulatedHBP = SCREEN_HSW + SCREEN_HBP - 1;
  hltdc.Init.AccumulatedVBP = SCREEN_VSW + SCREEN_VBP - 1;
  hltdc.Init.AccumulatedActiveW = SCREEN_HSW + SCREEN_HBP + SCREEN_WIDTH - 1;
  hltdc.Init.AccumulatedActiveH = SCREEN_VSW + SCREEN_VBP + SCREEN_HEIGHT - 1;
  hltdc.Init.TotalWidth = SCREEN_HSW + SCREEN_HBP + SCREEN_WIDTH + SCREEN_HFP - 1;
  hltdc.Init.TotalHeigh = SCREEN_VSW + SCREEN_VBP + SCREEN_HEIGHT + SCREEN_VFP - 1;
  hltdc.Init.Backcolor.Red = 0;
  hltdc.Init.Backcolor.Green = 0;
  hltdc.Init.Backcolor.Blue = 0;
  HAL_LTDC_Init(&hltdc);
  
  layer.WindowX0 = (SCREEN_WIDTH - DISPLAY_WIDTH) / 2;
  layer.WindowX1 = layer.WindowX0 + DISPLAY_WIDTH;
  layer.WindowY0 = (SCREEN_HEIGHT - DISPLAY_HEIGHT) / 2;
  layer.WindowY1 = layer.WindowY0 + DISPLAY_HEIGHT;
  layer.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
  layer.Alpha = 255; // 整体(窗口区域+非窗口区域)半透明度(0为透明, 255为不透明), 窗口区域不会叠加上非窗口区域的颜色
  layer.Alpha0 = 0; // 非窗口区域半透明度
  layer.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
  layer.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
  layer.FBStartAdress = (uint32_t)screen_buffer; // 窗口区域显示内容
  layer.ImageWidth = DISPLAY_WIDTH;
  layer.ImageHeight = DISPLAY_HEIGHT;
  layer.Backcolor.Red = 0; // 非窗口区域显示颜色
  layer.Backcolor.Green = 0;
  layer.Backcolor.Blue = 0;
  HAL_LTDC_ConfigLayer(&hltdc, &layer, LTDC_LAYER_1);
  // 为了消除开机时屏幕上随机黑线, 必须控制背光引脚
  // 默认情况下背光是关闭的, 当LTDC打开后延时几百毫秒再开背光
  
  hdma2d.Instance = DMA2D;
}

主程序:

#include <stdio.h>
#include <stm32h7xx.h>
#include <GUI.h>
#include "common.h"
#include "Screen.h"

int main(void)
{
  char str[30];
  GUI_MEMDEV_Handle memdev;
  GUI_RECT rect;
  
  HAL_Init();
  
  clock_init();
  usart_init(115200);
  printf("STM32H743VI LTDC\n");
  printf("SystemCoreClock=%u\n", SystemCoreClock);
  
  Screen_Init();
  GUI_Init();
  GUI_SetBkColor(GUI_LIGHTBLUE);
  GUI_Clear();
  
  GUI_SetFont(GUI_FONT_32B_ASCII);
  GUI_DispString("Hello World");
  GUI_SetBkColor(GUI_LIGHTRED);
  GUI_ClearRect(50, 50, 100, 100);
  GUI_InvertRect(80, 80, 120, 120);
  
  GUI_CopyRect(0, 0, 300, 300, 200, 40);
  memdev = GUI_MEMDEV_Create(300, 300, 200, 40);
  GUI_MEMDEV_CopyFromLCD(memdev);
  GUI_MEMDEV_Select(memdev);
  GUI_InvertRect(300, 300, 500, 340);
  GUI_MEMDEV_CopyToLCD(memdev);
  GUI_SelectLCD();
  GUI_MEMDEV_Delete(memdev);
  
  rect.x0 = 30;
  rect.y0 = 140;
  rect.x1 = 500;
  rect.y1 = 190;
  memdev = GUI_MEMDEV_Create(rect.x0, rect.y0, rect.x1 - rect.x0 + 1, rect.y1 - rect.y0 + 1);
  GUI_MEMDEV_Select(memdev);
  GUI_SetBkColor(GUI_LIGHTYELLOW);
  GUI_SetColor(GUI_DARKRED);
  while (1)
  {
    snprintf(str, sizeof(str), "Ticks: %u", HAL_GetTick() / 1000);
    GUI_ClearRectEx(&rect);
    GUI_DispStringInRect(str, &rect, GUI_TA_HCENTER | GUI_TA_VCENTER);
    GUI_MEMDEV_CopyToLCD(memdev);
    GUI_Delay(1000);
  }
}

程序使用STemWin图形库绘图。接下来说明一下STemWin是怎么移植的。

在ST官网下载的STM32H7 CubeMX库的压缩包(en.stm32cubeh7.zip)中,Middlewares的ST文件夹下有一个STemWin文件夹,这就是STemWin图形库。我们需要将其中的Lib/STemWin_CM7_wc16.a、OS/GUI_X.c、Config/GUIConf.c和h、Config/LCDConf_Lin_Template.c和h以及inc文件夹下的所有文件复制到工程中,将LCDConf_Lin_Template.c/h重命名为LCDConf_Lin.c/h。

然后将STemWin头文件所在的文件夹路径添加到项目属性中:

将GUI_X.c、STemWin_CM7_wc16.a、GUIConf.c和LCDConf_Lin.c添加到工程中

STemWin_CM7_wc16.a的文件类型需要修改为Library file:

现在需要修改GUIConf.c、GUI_X.c和LCDConf_Lin.c这三个文件。

GUIConf.c中主要要修改STemWin使用的内存大小和地址:

#define GUI_NUMBYTES  0x48000
static U32 aMemory[GUI_NUMBYTES / 4] __attribute__((at(0x30000000)));

我们将整个RAM1(0x30000000)都分配给STemWin用作图形处理。

GUI_X.c中主要是要实现GUI_Delay延时函数,我们将该函数和HAL库的HAL_Delay绑定就行:

GUI_TIMER_TIME GUI_X_GetTime(void) { 
  return HAL_GetTick(); 
}

void GUI_X_Delay(int ms) { 
  HAL_Delay(ms);
}

在LCDConf_Lin.c中,我们需要指定绘图区域的大小(546×480),以及颜色格式(RGB565):

//
// Physical display size
//
#define XSIZE_PHYS DISPLAY_WIDTH
#define YSIZE_PHYS DISPLAY_HEIGHT

//
// Color conversion
//
#define COLOR_CONVERSION GUICC_M565

//
// Display driver
//
#define DISPLAY_DRIVER GUIDRV_LIN_16

还有显存地址:

#ifndef   VRAM_ADDR
  #define VRAM_ADDR screen_buffer // TBD by customer: This has to be the frame buffer start address
#endif

在LCD_X_Config中,可以将一些绘图函数和DMA2D图形加速器绑定:

  //
  // Set custom functions for several operations to optimize native processes
  //
  LCD_SetDevFunc(0, LCD_DEVFUNC_COPYBUFFER, (Screen_Function)Screen_CopyBuffer);
  LCD_SetDevFunc(0, LCD_DEVFUNC_COPYRECT, (Screen_Function)Screen_CopyRect);
  //LCD_SetDevFunc(0, LCD_DEVFUNC_FILLRECT, (Screen_Function)Screen_FillRect);
  //LCD_SetDevFunc(0, LCD_DEVFUNC_DRAWBMP_8BPP, (void(*)(void))CUSTOM_LCD_DrawBitmap8bpp); 
  //LCD_SetDevFunc(0, LCD_DEVFUNC_DRAWBMP_16BPP, (void(*)(void))CUSTOM_LCD_DrawBitmap16bpp);
  
  GUI_MEMDEV_SetDrawMemdev16bppFunc(Screen_CopyRectFromMemdev);

笔者发现绑定LCD_DEVFUNC_FILLRECT后,GUI_InvertRect函数无法正常工作,于是就注释掉了那句话,取消绑定了。

到此,STemWin就移植完成了,可以运行程序查看效果:

【关于触控】

这个彩屏不带触控功能,上面的37~40脚只是4个焊点:YU XL YD XR,分别代表Y up、X left、Y down、X right,并没有连接任何触控器件。如果需要触控功能可以加装一个如下图所示的四线电阻屏或电容屏膜,接到焊点上之后通过FPC的37~40脚引出,再接一个XPT2046触控检测芯片,芯片通过SPI接口和单片机连接,就可以检测触控了。

淘宝上一般的液晶屏都是用XPT2046芯片检测触控的。

【自制PCB板】

用FPC转接座和杜邦线完成程序验证后,笔者做了一个STM32H743ZI的PCB板,如下图所示。

这个FPC座的针脚看起来和STM32差不多,但是用刀头烙铁焊接极为困难,很难甩掉中间短路的连锡。搞了一天时间都没焊接成功,最后笔者是找别人焊的。别人用钢网,锡浆,再用热风枪一下子就焊好了,还不残留任何助焊剂。

笔者一开始不知道L2要选功率电感,画板子的时候稀里糊涂地选了一个0603的封装,结果插上板子电感就冒烟。。。还好冒的小。
补救方法:背光引脚输出100Hz 10%占空比的PWM波,降低屏幕亮度,这样电感就不冒烟了,也不烫了。

#include <stdio.h>
#include <stm32h7xx.h>
#include <string.h>
#include "Screen.h"

......
TIM_HandleTypeDef htim2;

void Screen_Init(void)
{
......
  TIM_OC_InitTypeDef tim_oc = {0};
  
......
  __HAL_RCC_TIM2_CLK_ENABLE();
  
  // PA0: LCD_BL
  gpio.Alternate = GPIO_AF1_TIM2;
  gpio.Mode = GPIO_MODE_AF_PP;
  gpio.Pin = GPIO_PIN_0;
  gpio.Pull = GPIO_NOPULL;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &gpio);

......
  HAL_LTDC_ConfigLayer(&hltdc, &layer, LTDC_LAYER_1);
  
  // 为了消除开机时屏幕上随机黑线, 必须控制背光引脚
  // 默认情况下背光是关闭的, 当LTDC打开后延时几百毫秒再开背光
  htim2.Instance = TIM2;
  htim2.Init.Period = 99;
  htim2.Init.Prescaler = SystemCoreClock / 20000 - 1;
  HAL_TIM_PWM_Init(&htim2);
  tim_oc.OCMode = TIM_OCMODE_PWM1;
  tim_oc.Pulse = 0;
  HAL_TIM_PWM_ConfigChannel(&htim2, &tim_oc, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  memset(screen_buffer, 0, sizeof(screen_buffer)); // 清屏
  HAL_Delay(500);
  __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 10); // 设置PWM占空比为10%, 打开背光
  HAL_Delay(500); // 打开背光后保持屏幕内容为纯黑一段时间, 避免随机黑块
  
  hdma2d.Instance = DMA2D;
}

htim2.Init.Period = 99;
htim2.Init.Prescaler = SystemCoreClock / 20000 - 1;
其中SystemCoreClock=480000000,因此htim2.Init.Prescaler=23999。
分频系数为23999+1=24000,计数范围0~99。
TIM2所在的APB1总线的频率是120MHz,因此最终背光PWM的频率为(2*120MHz)/(23999+1)/(99+1)=100Hz

为了使tim_oc.Pulse的取值范围为0~100,htim2.Init.Period要设成99。
若tim_oc.Pulse=99,当定时器计数计到99时,由于CNT不小于CCR,输出低电平,其余时间为高电平,为99%占空比。
若tim_oc.Pulse=100,则CNT永远不小于CCR,永远输出高电平,为100%占空比。

初始化完成后,可用__HAL_TIM_SET_COMPARE宏改变占空比,取值范围为0~100。0是关背光,100是开最高亮度。
如果发现图像显示偏了,没有对准屏幕边框,那就调整Screen.h里面的SCREEN_HBP、SCREEN_VBP、SCREEN_HFP和SCREEN_VFP的值。

在屏幕左上角和右下角各显示一个像素点,然后右上角到左下角画一条直线:

GUI_SetColor(GUI_GREEN);
GUI_DrawPoint(0, 0);
GUI_DrawPoint(SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);
GUI_DrawLine(SCREEN_WIDTH - 1, 0, 0, SCREEN_HEIGHT - 1);
GUI_MEMDEV_CopyToLCD(memdev);

经过确定,当HBP=VBP=7时,屏幕上能同时显示左上角和右下角的点,调正了。HFP和VFP这两个值可以随便填,对屏幕显示无影响。

#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 480

#define SCREEN_HSW 1
#define SCREEN_VSW 1
#define SCREEN_HBP 7
#define SCREEN_VBP 7
#define SCREEN_HFP 20
#define SCREEN_VFP 20

  • 4
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值