1. 引言

截图是很多应用场合都需要的一项功能,对于无外存等小内存场合,同时又无法读取像素点的情况,截图通常需要多个组件的配合。本文采用的STM32G4微控制器,使用的组件如下:

  • GUI图形库:LVGL;
  • 文件系统:FATFS。

其中,显示驱动采用ST7789,存储截图的介质为SD卡。另,为方便读写和查看,本文采用BMP编码。

2. 准备工作

  • ST7789驱动及LVGL移植:参见《ST7789驱动 》和《STM32移植LVGL驱动ST7789》;
  • SD驱动及FATFS移植:参见《SPI驱动SD卡及FATFS移植》;
  • BMP编码的相关代码实现:这里我们采用最适配的16位无颜色图RGB555格式的存储方式,注意!是RGB555格式,无像素掩码信息时Windows系统默认采用的时该格式,与GUI图形库常用的RGB565是不一样的,之前截图颜色总是有类似噪点的颜色偏差,后经“lvgl技术交流”群中@Def、大哥的指出才发现原来是因为颜色格式搞错了,详细信息可参阅微软的相关文档。为实现BMP编码,主要需要给出BMP文件头的定义和初始化方法,分ubmp.hubmp.c两个文件。

ubmp.h文件完整代码如下:

/**
 *******************************************************************************
 * @file    ubmp.h
 * @author  xixizhk
 *******************************************************************************
 * @version V2022 @ Nov 1, 2022 \n
 * Initial version, only 16bit BMP without compression supported.
 * @version V2022 @ Nov 29, 2022 \n
 * Specified for ONLY RGB555, with bugs fixed. @see:
 * https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/
 *******************************************************************************
 */


/* Define to prevent recursive inclusion **************************************/
#ifndef _UBMP_H
#define _UBMP_H

#ifdef __cplusplus
extern "C" {
#endif

/**
 *******************************************************************************
 * @addtogroup Includes
 * @{
 */

#include "stdint.h"

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Definitions
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Types
 * @{
 */

/**
 * @brief File header, 14 bytes, packed.
 */
typedef struct
{
    /* File type, "BM" (0x424D) for .bmp file. */
    uint16_t bfType;
    /* File size in byte, including all the header, etc.. */
    uint32_t bfFileSize;
    /* Reserved, 0. */
    uint16_t bfReserved1;
    /* Reserved, 0. */
    uint16_t bfReserved2;
    /* Offset of pixel data from the beginning of the file header, in byte. */
    uint32_t bfOffsetBytes;
} __attribute__((__packed__)) ubmp_file_header_t;

/**
 * @brief Information header, 40 bytes, packed.
 */
typedef struct
{
    /* Size of the information header in byte, 40 */
    uint32_t biHeaderSize;
    /* Width of the picture, in pixel. */
    uint32_t biWidth;
    /* Height of the picture, in pixel. */
    uint32_t biHeight;
    /* Planes of bit plane, 1. */
    uint16_t biPlanes;
    /* Number of bits for one pixel. */
    uint16_t biBitCount;
    /* Compression options. */
    uint32_t biCompression;
    /* Size of the pixel data in byte, NOT equal to biWidth * biHeight. */
    uint32_t biDataSize;
    /* Pixels per meter (X). */
    uint32_t biXPelsPerMeter;
    /* Pixels per meter (Y). */
    uint32_t biYPelsPerMeter;
    /* Used color, 0. */
    uint32_t biColorUsed;
    /* Important color, 0.*/
    uint32_t biColorImportant;
} __attribute__((__packed__)) ubmp_info_header_t;

/**
 * @brief Type definition for BMP handler.
 */
typedef struct
{
    /* File header. */
    ubmp_file_header_t FileHeader;
    /* Information header. */
    ubmp_info_header_t InfoHeader;
    /* Header size in byte. */
    uint32_t HeaderSize;
    /* Line size in byte. */
    uint32_t LineSize;
} __attribute__((__packed__)) ubmp_rgb555_handler;


/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Constants
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Variables
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Macros
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Functions
 * @{
 */

void ubmp_init_rgb555(ubmp_rgb555_handler* _ubmp, uint32_t _w, uint32_t _h);

/**
 * @}
 */


#ifdef __cplusplus
}
#endif


#endif /* _UBMP_H */

/**************************** ALL RIGHTS RESERVED *****************************/

ubmp.c文件完整代码如下:

/**
 *******************************************************************************
 * @file    ubmp.c
 * @author  xixizhk
 *******************************************************************************
 * @version V2022 @ Nov 1, 2022 \n
 * Initial version, only 16bit BMP without compression supported.
 * @version V2022 @ Nov 29, 2022 \n
 * Specified for ONLY RGB555, with bugs fixed. @see:
 * https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/
 *******************************************************************************
 */


/**
 *******************************************************************************
 * @addtogroup Includes
 * @{
 */

#include "ubmp.h"

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Definitions
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Types
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Constants
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Variables
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Macros
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Functions
 * @{
 */

/**
 * @brief   Initialize a BMP handler for RGB555.
 * @param   _ubmp [Out]: point to the BMP handler.
 * @param   _w [In]: width of the picture in pixel. 
 * @param   _h [In]: height of the picture in pixel.
 * @retval  None.
 */
void ubmp_init_rgb555(ubmp_rgb555_handler* _ubmp, uint32_t _w, uint32_t _h)
{
    _ubmp->FileHeader.bfType = 0x4D42;
    _ubmp->FileHeader.bfReserved1 = 0;
    _ubmp->FileHeader.bfReserved2 = 0;
    _ubmp->FileHeader.bfOffsetBytes = 54;

    _ubmp->InfoHeader.biHeaderSize = 40;
    _ubmp->InfoHeader.biWidth = _w;
    _ubmp->InfoHeader.biHeight = _h;
    _ubmp->InfoHeader.biPlanes = 1;

    /* RGB555. */
    _ubmp->InfoHeader.biBitCount = 16;
    _ubmp->InfoHeader.biCompression = 0;
    
    _ubmp->InfoHeader.biXPelsPerMeter = 10000;
    _ubmp->InfoHeader.biYPelsPerMeter = 10000;
    _ubmp->InfoHeader.biColorUsed = 0;
    _ubmp->InfoHeader.biColorImportant = 0;

    _ubmp->LineSize = (((_w << 4) + 31) >> 5) << 2;
    _ubmp->InfoHeader.biDataSize = _ubmp->LineSize * _h;  /* Can also be 0. */
    _ubmp->FileHeader.bfFileSize = 54 + _ubmp->InfoHeader.biDataSize;

    _ubmp->HeaderSize = 54;
}

/**
 * @}
 */


/**************************** ALL RIGHTS RESERVED *****************************/

3. 实现方法

为保持截图数据的完整,建议采用类似“触发”的方式结合lvgl定时器实现截图。

  • Step 1:定义相关的变量:
static volatile uint8_t sstrigr = 0U;
static FATFS ssfs;
static FIL ssfile;
static FRESULT ssfres;
static UINT ssbn;
static char sspath[32];
static lv_res_t sslvres;
static uint8_t ssbuffer[1024];
static lv_obj_t* ssscreen;
static ubmp_rgb555_handler ss;
  • Step 2:实现截图函数:
/**
 * @brief   Callback function for snapshot.
 * @param   timer [In]: pointer to the timer.
 * @retval  None.
 */
static void ss_cb(lv_timer_t * timer)
{
  lv_img_dsc_t dsc;
  lv_coord_t y1, y2;
  uint16_t i, j, w, tmp;
  char diskletter[2] = "0";

  if (sstrigr != 1U)
  {
    return;
  }

  y1 = ssscreen->coords.y1;
  y2 = ssscreen->coords.y2;
  w = ssscreen->coords.x2 - ssscreen->coords.x1 + 1;
  ubmp_init_rgb555(&ss, w, y2 - y1 + 1);

  diskletter[0] = sspath[0];
  ssfres = f_mount(&ssfs, sspath, 1);
  ssfres = f_open(&ssfile, sspath, FA_CREATE_ALWAYS | FA_WRITE |FA_READ);
  ssfres = f_write(&ssfile, &ss, ss.HeaderSize, &ssbn);
  for (i = y2; i >= y1; i--)
  {
    ssscreen->coords.y1 = i;
    ssscreen->coords.y2 = i;
    sslvres = lv_snapshot_take_to_buf
    (
      ssscreen,
      LV_IMG_CF_TRUE_COLOR,
      &dsc,
      ssbuffer,
      sizeof(ssbuffer)
    );
    /* Convert to RGB555. */
    for (j = 0; j < w; j++)
    {
      tmp = *(((uint16_t* )ssbuffer) + j);
      *(((uint16_t* )ssbuffer) + j) = (tmp & 0x001F) + ((tmp & 0xFFC0) >> 1U);
    }
    for (j = 2 * w; j < ss.LineSize; j++)
    {
      ssbuffer[j] = 0;
    }
    ssbn = ss.LineSize;
    ssfres = f_write(&ssfile, ssbuffer, ssbn, &ssbn);
    if (i == 0U)
    {
      break;
    }
  }
  ssfres = f_close(&ssfile);
  ssfres = f_mount(0, diskletter, 1);

  ssscreen->coords.y1 = y1;
  ssscreen->coords.y2 = y2;
  
  sstrigr = 0U;
}
  • Step 3:新建LVGL定时器对象,并将上述ss_cb函数作为其回调:
lv_timer_create(ss_cb, 0, NULL);
  • Step 4:采用“触发”加信息传递的方式实现供外部调用的截图函数:
/**
 * @brief   Take the snapshot of specified screen to SD card.
 *          @note: screen with border NOT supported.
 * @param   _path [In]: path to save the snapshot.
 *          @note: format: diskletter + : + file name, eg: 0:ss.bmp.
 * @param   _src [In]: pointer to the screen object.
 * @retval  None.
 */
void snapshot(char* _path, lv_obj_t* _scr)
{
  uint8_t i;
  for (i = 0; i < sizeof(sspath) - 1; i++)
  {
    sspath[i] = _path[i];
  }
  sspath[sizeof(sspath) - 1] = '\0';
  ssscreen = _scr;
  sstrigr = 1U;
}

4. 截图效果

如下。

image

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· Apache Tomcat RCE漏洞复现(CVE-2025-24813)
点击右上角即可分享
微信分享提示