一、Logo显示应用程序

1. 应用程序头文件定义

/**
 * @file logo_display.c
 * @brief ST7789 Logo显示应用程序
 * 
 * 该应用程序在系统启动时显示Logo到SPI TFT显示器,
 * 支持自定义Logo图像、进度条显示和启动动画。
 * 
 * @copyright GPL v2
 */
​
#ifndef LOGO_DISPLAY_H
#define LOGO_DISPLAY_H
​
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <getopt.h>
​
/* 显示参数定义 */
#define DEFAULT_FB_DEVICE    "/dev/fb0"  /**< 默认FrameBuffer设备 */
#define DEFAULT_LOGO_FILE    "/etc/logo.bmp" /**< 默认Logo文件 */
#define DEFAULT_DELAY_MS     3000        /**< 默认显示延时(毫秒) */
#define MAX_LOGO_WIDTH       320         /**< Logo最大宽度 */
#define MAX_LOGO_HEIGHT      240         /**< Logo最大高度 */
​
/* 颜色定义(RGB565格式) */
#define COLOR_BLACK          0x0000      /**< 黑色 */
#define COLOR_WHITE          0xFFFF      /**< 白色 */
#define COLOR_RED            0xF800      /**< 红色 */
#define COLOR_GREEN          0x07E0      /**< 绿色 */
#define COLOR_BLUE           0x001F      /**< 蓝色 */
#define COLOR_YELLOW         0xFFE0      /**< 黄色 */
#define COLOR_CYAN           0x07FF      /**< 青色 */
#define COLOR_MAGENTA        0xF81F      /**< 洋红色 */
​
/* 错误代码定义 */
#define LOGO_SUCCESS         0           /**< 成功 */
#define LOGO_ERROR           -1          /**< 通用错误 */
#define LOGO_ERROR_OPEN_FB   -2          /**< 打开FrameBuffer失败 */
#define LOGO_ERROR_MMAP      -3          /**< 内存映射失败 */
#define LOGO_ERROR_FILE      -4          /**< 文件操作失败 */
#define LOGO_ERROR_FORMAT    -5          /**< 格式不支持 */
​
/**
 * @struct logo_image
 * @brief Logo图像数据结构
 */
struct logo_image {
    uint16_t width;                     /**< 图像宽度 */
    uint16_t height;                    /**< 图像高度 */
    uint16_t bpp;                       /**< 每像素位数 */
    uint8_t *data;                      /**< 图像数据指针 */
    size_t data_size;                   /**< 数据大小 */
    uint32_t *palette;                  /**< 调色板(对于索引颜色) */
    uint16_t palette_size;              /**< 调色板大小 */
};
​
/**
 * @struct app_config
 * @brief 应用程序配置结构
 */
struct app_config {
    char fb_device[256];               /**< FrameBuffer设备路径 */
    char logo_file[256];                /**< Logo文件路径 */
    int display_time;                   /**< 显示时间(毫秒) */
    int show_progress;                  /**< 是否显示进度条 */
    int show_animation;                 /**< 是否显示动画 */
    int center_logo;                    /**< 是否居中显示Logo */
    int clear_screen;                   /**< 是否清屏 */
    uint16_t bg_color;                  /**< 背景颜色 */
    uint16_t fg_color;                  /**< 前景颜色 */
    int verbose;                        /**< 详细输出 */
};
​
/**
 * @struct fb_context
 * @brief FrameBuffer上下文结构
 */
struct fb_context {
    int fd;                            /**< 文件描述符 */
    struct fb_var_screeninfo var_info; /**< 可变屏幕信息 */
    struct fb_fix_screeninfo fix_info; /**< 固定屏幕信息 */
    void *fb_mem;                      /**< 映射内存指针 */
    size_t fb_size;                    /**< 映射内存大小 */
    uint16_t screen_width;             /**< 屏幕宽度 */
    uint16_t screen_height;            /**< 屏幕高度 */
    uint16_t bpp;                      /**< 每像素位数 */
    size_t line_length;                /**< 每行字节数 */
};
​
#endif /* LOGO_DISPLAY_H */

2. FrameBuffer设备操作函数

/**
 * @brief 打开FrameBuffer设备
 * @param device FrameBuffer设备路径
 * @return 成功返回文件描述符,失败返回-1
 * 
 * 打开指定的FrameBuffer设备文件,准备进行显示操作。
 */
static int open_framebuffer(const char *device)
{
    int fd;
    
    fd = open(device, O_RDWR);
    if (fd < 0) {
        fprintf(stderr, "Error: Cannot open framebuffer device %s: %s\n",
                device, strerror(errno));
        return LOGO_ERROR_OPEN_FB;
    }
    
    return fd;
}
​
/**
 * @brief 关闭FrameBuffer设备
 * @param fd FrameBuffer文件描述符
 * 
 * 关闭打开的FrameBuffer设备,释放资源。
 */
static void close_framebuffer(int fd)
{
    if (fd >= 0) {
        close(fd);
    }
}
​
/**
 * @brief 获取FrameBuffer信息
 * @param fd FrameBuffer文件描述符
 * @param var_info 可变屏幕信息结构指针
 * @param fix_info 固定屏幕信息结构指针
 * @return 成功返回LOGO_SUCCESS,失败返回错误码
 * 
 * 获取FrameBuffer设备的屏幕信息,包括分辨率、颜色格式等。
 */
static int get_framebuffer_info(int fd,
                               struct fb_var_screeninfo *var_info,
                               struct fb_fix_screeninfo *fix_info)
{
    int ret;
    
    if (!var_info || !fix_info) {
        return LOGO_ERROR;
    }
    
    /* 获取可变屏幕信息 */
    ret = ioctl(fd, FBIOGET_VSCREENINFO, var_info);
    if (ret < 0) {
        fprintf(stderr, "Error: Cannot get variable screen info: %s\n",
                strerror(errno));
        return LOGO_ERROR;
    }
    
    /* 获取固定屏幕信息 */
    ret = ioctl(fd, FBIOGET_FSCREENINFO, fix_info);
    if (ret < 0) {
        fprintf(stderr, "Error: Cannot get fixed screen info: %s\n",
                strerror(errno));
        return LOGO_ERROR;
    }
    
    return LOGO_SUCCESS;
}
​
/**
 * @brief 初始化FrameBuffer上下文
 * @param ctx FrameBuffer上下文结构指针
 * @param config 应用程序配置指针
 * @return 成功返回LOGO_SUCCESS,失败返回错误码
 * 
 * 初始化FrameBuffer上下文,包括打开设备、获取信息和内存映射。
 */
static int init_framebuffer_context(struct fb_context *ctx,
                                   const struct app_config *config)
{
    int ret;
    
    if (!ctx || !config) {
        return LOGO_ERROR;
    }
    
    /* 打开FrameBuffer设备 */
    ctx->fd = open_framebuffer(config->fb_device);
    if (ctx->fd < 0) {
        return ctx->fd;
    }
    
    /* 获取FrameBuffer信息 */
    ret = get_framebuffer_info(ctx->fd, &ctx->var_info, &ctx->fix_info);
    if (ret != LOGO_SUCCESS) {
        close_framebuffer(ctx->fd);
        return ret;
    }
    
    /* 提取显示参数 */
    ctx->screen_width = ctx->var_info.xres;
    ctx->screen_height = ctx->var_info.yres;
    ctx->bpp = ctx->var_info.bits_per_pixel;
    ctx->line_length = ctx->fix_info.line_length;
    ctx->fb_size = ctx->fix_info.smem_len;
    
    if (config->verbose) {
        printf("Framebuffer info:\n");
        printf("  Resolution: %dx%d\n", ctx->screen_width, ctx->screen_height);
        printf("  Color depth: %d bpp\n", ctx->bpp);
        printf("  Line length: %zu bytes\n", ctx->line_length);
        printf("  Buffer size: %zu bytes\n", ctx->fb_size);
    }
    
    /* 检查颜色格式(必须支持RGB565) */
    if (ctx->bpp != 16) {
        fprintf(stderr, "Error: Unsupported color depth %d bpp. "
                "Only 16 bpp (RGB565) is supported.\n", ctx->bpp);
        close_framebuffer(ctx->fd);
        return LOGO_ERROR_FORMAT;
    }
    
    /* 内存映射FrameBuffer */
    ctx->fb_mem = mmap(NULL, ctx->fb_size, PROT_READ | PROT_WRITE,
                       MAP_SHARED, ctx->fd, 0);
    if (ctx->fb_mem == MAP_FAILED) {
        fprintf(stderr, "Error: Cannot map framebuffer memory: %s\n",
                strerror(errno));
        close_framebuffer(ctx->fd);
        return LOGO_ERROR_MMAP;
    }
    
    if (config->verbose) {
        printf("Framebuffer mapped at address %p\n", ctx->fb_mem);
    }
    
    return LOGO_SUCCESS;
}
​
/**
 * @brief 清理FrameBuffer上下文
 * @param ctx FrameBuffer上下文结构指针
 * 
 * 释放FrameBuffer上下文占用的资源。
 */
static void cleanup_framebuffer_context(struct fb_context *ctx)
{
    if (!ctx) {
        return;
    }
    
    /* 取消内存映射 */
    if (ctx->fb_mem && ctx->fb_mem != MAP_FAILED) {
        munmap(ctx->fb_mem, ctx->fb_size);
        ctx->fb_mem = NULL;
    }
    
    /* 关闭设备 */
    if (ctx->fd >= 0) {
        close_framebuffer(ctx->fd);
        ctx->fd = -1;
    }
}
​
/**
 * @brief 清空屏幕
 * @param ctx FrameBuffer上下文结构指针
 * @param color 填充颜色(RGB565格式)
 * 
 * 用指定颜色填充整个屏幕。
 */
static void clear_screen(struct fb_context *ctx, uint16_t color)
{
    uint16_t *fb_ptr = (uint16_t *)ctx->fb_mem;
    size_t total_pixels = ctx->screen_width * ctx->screen_height;
    size_t i;
    
    for (i = 0; i < total_pixels; i++) {
        fb_ptr[i] = color;
    }
}
​
/**
 * @brief 设置单个像素颜色
 * @param ctx FrameBuffer上下文结构指针
 * @param x X坐标
 * @param y Y坐标
 * @param color 颜色值(RGB565格式)
 * 
 * 在指定坐标设置像素颜色。
 */
static void set_pixel(struct fb_context *ctx, int x, int y, uint16_t color)
{
    uint16_t *fb_ptr;
    
    /* 边界检查 */
    if (x < 0 || x >= ctx->screen_width ||
        y < 0 || y >= ctx->screen_height) {
        return;
    }
    
    fb_ptr = (uint16_t *)ctx->fb_mem;
    fb_ptr[y * ctx->screen_width + x] = color;
}
​
/**
 * @brief 绘制矩形
 * @param ctx FrameBuffer上下文结构指针
 * @param x 起始X坐标
 * @param y 起始Y坐标
 * @param width 矩形宽度
 * @param height 矩形高度
 * @param color 填充颜色(RGB565格式)
 * @param filled 是否填充矩形(1为填充,0为边框)
 * 
 * 绘制指定位置和大小的矩形。
 */
static void draw_rectangle(struct fb_context *ctx,
                          int x, int y, int width, int height,
                          uint16_t color, int filled)
{
    int i, j;
    
    /* 边界检查 */
    if (x < 0 || y < 0 ||
        x + width > ctx->screen_width ||
        y + height > ctx->screen_height) {
        return;
    }
    
    if (filled) {
        /* 填充矩形 */
        for (j = y; j < y + height; j++) {
            uint16_t *line_start = (uint16_t *)ctx->fb_mem +
                                   j * ctx->screen_width + x;
            for (i = 0; i < width; i++) {
                line_start[i] = color;
            }
        }
    } else {
        /* 绘制矩形边框 */
        /* 上边框 */
        for (i = x; i < x + width; i++) {
            set_pixel(ctx, i, y, color);
        }
        /* 下边框 */
        for (i = x; i < x + width; i++) {
            set_pixel(ctx, i, y + height - 1, color);
        }
        /* 左边框 */
        for (j = y; j < y + height; j++) {
            set_pixel(ctx, x, j, color);
        }
        /* 右边框 */
        for (j = y; j < y + height; j++) {
            set_pixel(ctx, x + width - 1, j, color);
        }
    }
}

3. Logo图像处理函数

/**
 * @brief 加载BMP格式Logo图像
 * @param filename BMP文件路径
 * @param logo Logo图像结构指针
 * @return 成功返回LOGO_SUCCESS,失败返回错误码
 * 
 * 加载BMP格式的Logo图像文件,支持24位和32位颜色格式。
 */
static int load_bmp_logo(const char *filename, struct logo_image *logo)
{
    FILE *fp = NULL;
    uint8_t header[54];
    uint32_t data_offset;
    uint32_t image_size;
    int width, height;
    uint16_t bits_per_pixel;
    uint8_t *image_data = NULL;
    int row_padded;
    int i, j;
    
    /* 打开文件 */
    fp = fopen(filename, "rb");
    if (!fp) {
        fprintf(stderr, "Error: Cannot open logo file %s: %s\n",
                filename, strerror(errno));
        return LOGO_ERROR_FILE;
    }
    
    /* 读取BMP文件头 */
    if (fread(header, 1, 54, fp) != 54) {
        fprintf(stderr, "Error: Invalid BMP file header\n");
        fclose(fp);
        return LOGO_ERROR_FORMAT;
    }
    
    /* 检查BMP文件标志 */
    if (header[0] != 'B' || header[1] != 'M') {
        fprintf(stderr, "Error: Not a valid BMP file\n");
        fclose(fp);
        return LOGO_ERROR_FORMAT;
    }
    
    /* 提取图像信息 */
    data_offset = *(uint32_t*)&header[0x0A];
    width = *(int*)&header[0x12];
    height = *(int*)&header[0x16];
    bits_per_pixel = *(uint16_t*)&header[0x1C];
    
    if (width <= 0 || height <= 0) {
        fprintf(stderr, "Error: Invalid BMP image dimensions\n");
        fclose(fp);
        return LOGO_ERROR_FORMAT;
    }
    
    if (bits_per_pixel != 24 && bits_per_pixel != 32) {
        fprintf(stderr, "Error: Unsupported BMP format: %d bpp\n", bits_per_pixel);
        fclose(fp);
        return LOGO_ERROR_FORMAT;
    }
    
    /* 计算行填充字节 */
    row_padded = (width * (bits_per_pixel / 8) + 3) & (~3);
    
    /* 分配图像数据内存 */
    image_size = row_padded * abs(height);
    image_data = (uint8_t*)malloc(image_size);
    if (!image_data) {
        fprintf(stderr, "Error: Cannot allocate memory for image data\n");
        fclose(fp);
        return LOGO_ERROR;
    }
    
    /* 定位到图像数据 */
    fseek(fp, data_offset, SEEK_SET);
    
    /* 读取图像数据 */
    if (fread(image_data, 1, image_size, fp) != image_size) {
        fprintf(stderr, "Error: Cannot read image data\n");
        free(image_data);
        fclose(fp);
        return LOGO_ERROR_FILE;
    }
    
    fclose(fp);
    
    /* 转换为Logo图像结构 */
    logo->width = width;
    logo->height = abs(height);
    logo->bpp = bits_per_pixel;
    logo->data = image_data;
    logo->data_size = image_size;
    logo->palette = NULL;
    logo->palette_size = 0;
    
    return LOGO_SUCCESS;
}
​
/**
 * @brief 加载原始RGB565格式Logo图像
 * @param filename 原始文件路径
 * @param logo Logo图像结构指针
 * @param width 图像宽度
 * @param height 图像高度
 * @return 成功返回LOGO_SUCCESS,失败返回错误码
 * 
 * 加载原始RGB565格式的Logo图像文件。
 */
static int load_raw_logo(const char *filename, struct logo_image *logo,
                        int width, int height)
{
    FILE *fp = NULL;
    size_t file_size;
    size_t expected_size;
    uint8_t *image_data = NULL;
    
    /* 打开文件 */
    fp = fopen(filename, "rb");
    if (!fp) {
        fprintf(stderr, "Error: Cannot open logo file %s: %s\n",
                filename, strerror(errno));
        return LOGO_ERROR_FILE;
    }
    
    /* 获取文件大小 */
    fseek(fp, 0, SEEK_END);
    file_size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    
    /* 计算期望的文件大小(RGB565:每像素2字节) */
    expected_size = width * height * 2;
    
    if (file_size != expected_size) {
        fprintf(stderr, "Error: Invalid raw file size: got %zu, expected %zu\n",
                file_size, expected_size);
        fclose(fp);
        return LOGO_ERROR_FORMAT;
    }
    
    /* 分配内存 */
    image_data = (uint8_t*)malloc(file_size);
    if (!image_data) {
        fprintf(stderr, "Error: Cannot allocate memory for image data\n");
        fclose(fp);
        return LOGO_ERROR;
    }
    
    /* 读取数据 */
    if (fread(image_data, 1, file_size, fp) != file_size) {
        fprintf(stderr, "Error: Cannot read image data\n");
        free(image_data);
        fclose(fp);
        return LOGO_ERROR_FILE;
    }
    
    fclose(fp);
    
    /* 转换为Logo图像结构 */
    logo->width = width;
    logo->height = height;
    logo->bpp = 16;  /* RGB565 */
    logo->data = image_data;
    logo->data_size = file_size;
    logo->palette = NULL;
    logo->palette_size = 0;
    
    return LOGO_SUCCESS;
}
​
/**
 * @brief 加载Logo图像
 * @param filename Logo文件路径
 * @param logo Logo图像结构指针
 * @param config 应用程序配置指针
 * @return 成功返回LOGO_SUCCESS,失败返回错误码
 * 
 * 根据文件扩展名自动选择加载器,支持BMP和RAW格式。
 */
static int load_logo_image(const char *filename,
                          struct logo_image *logo,
                          const struct app_config *config)
{
    const char *ext;
    int ret;
    
    if (!filename || !logo) {
        return LOGO_ERROR;
    }
    
    /* 获取文件扩展名 */
    ext = strrchr(filename, '.');
    if (!ext) {
        fprintf(stderr, "Error: Cannot determine file type\n");
        return LOGO_ERROR_FORMAT;
    }
    
    if (config->verbose) {
        printf("Loading logo image: %s\n", filename);
    }
    
    /* 根据扩展名选择加载器 */
    if (strcasecmp(ext, ".bmp") == 0) {
        ret = load_bmp_logo(filename, logo);
    } else if (strcasecmp(ext, ".raw") == 0 ||
               strcasecmp(ext, ".rgb565") == 0) {
        /* 对于RAW格式,需要指定尺寸或从配置中获取 */
        ret = load_raw_logo(filename, logo, 320, 240);
    } else {
        fprintf(stderr, "Error: Unsupported file format: %s\n", ext);
        return LOGO_ERROR_FORMAT;
    }
    
    if (ret == LOGO_SUCCESS && config->verbose) {
        printf("Logo loaded: %dx%d, %d bpp, %zu bytes\n",
               logo->width, logo->height, logo->bpp, logo->data_size);
    }
    
    return ret;
}
​
/**
 * @brief 释放Logo图像资源
 * @param logo Logo图像结构指针
 * 
 * 释放Logo图像占用的内存资源。
 */
static void free_logo_image(struct logo_image *logo)
{
    if (!logo) {
        return;
    }
    
    if (logo->data) {
        free(logo->data);
        logo->data = NULL;
    }
    
    if (logo->palette) {
        free(logo->palette);
        logo->palette = NULL;
    }
    
    logo->width = 0;
    logo->height = 0;
    logo->bpp = 0;
    logo->data_size = 0;
    logo->palette_size = 0;
}

4. 颜色转换和图像渲染函数

/**
 * @brief 将24位RGB颜色转换为16位RGB565格式
 * @param r 红色分量(0-255)
 * @param g 绿色分量(0-255)
 * @param b 蓝色分量(0-255)
 * @return RGB565格式颜色值
 * 
 * 将24位RGB颜色转换为ST7789支持的16位RGB565格式。
 */
static uint16_t rgb888_to_rgb565(uint8_t r, uint8_t g, uint8_t b)
{
    return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
​
/**
 * @brief 将RGB565颜色转换为24位RGB颜色
 * @param rgb565 RGB565格式颜色值
 * @param r 返回的红色分量指针
 * @param g 返回的绿色分量指针
 * @param b 返回的蓝色分量指针
 * 
 * 将RGB565格式颜色转换回24位RGB颜色。
 */
static void rgb565_to_rgb888(uint16_t rgb565,
                            uint8_t *r, uint8_t *g, uint8_t *b)
{
    if (r) *r = (rgb565 >> 8) & 0xF8;
    if (g) *g = (rgb565 >> 3) & 0xFC;
    if (b) *b = (rgb565 << 3) & 0xF8;
}
​
/**
 * @brief 转换BMP图像到RGB565格式
 * @param logo Logo图像结构指针
 * @param ctx FrameBuffer上下文指针
 * @return 成功返回LOGO_SUCCESS,失败返回错误码
 * 
 * 将BMP格式图像转换为FrameBuffer可用的RGB565格式。
 */
static int convert_bmp_to_rgb565(struct logo_image *logo,
                                const struct fb_context *ctx)
{
    uint8_t *bmp_data = logo->data;
    uint8_t *rgb565_data = NULL;
    int width = logo->width;
    int height = logo->height;
    int bpp = logo->bpp;
    int src_row_padded;
    int dst_row_size;
    int i, j;
    uint8_t r, g, b;
    
    /* 计算源图像行大小(BMP有4字节对齐) */
    src_row_padded = (width * (bpp / 8) + 3) & (~3);
    
    /* 计算目标图像行大小(RGB565:每像素2字节) */
    dst_row_size = width * 2;
    
    /* 分配目标图像内存 */
    rgb565_data = (uint8_t*)malloc(height * dst_row_size);
    if (!rgb565_data) {
        fprintf(stderr, "Error: Cannot allocate memory for RGB565 conversion\n");
        return LOGO_ERROR;
    }
    
    /* 转换图像数据 */
    for (j = 0; j < height; j++) {
        uint8_t *src_row = bmp_data + (height - 1 - j) * src_row_padded; /* BMP是倒序存储 */
        uint16_t *dst_row = (uint16_t*)(rgb565_data + j * dst_row_size);
        
        for (i = 0; i < width; i++) {
            if (bpp == 24) {
                b = src_row[i * 3];
                g = src_row[i * 3 + 1];
                r = src_row[i * 3 + 2];
            } else if (bpp == 32) {
                b = src_row[i * 4];
                g = src_row[i * 4 + 1];
                r = src_row[i * 4 + 2];
                /* 忽略Alpha通道 */
            } else {
                free(rgb565_data);
                return LOGO_ERROR_FORMAT;
            }
            
            dst_row[i] = rgb888_to_rgb565(r, g, b);
        }
    }
    
    /* 释放原始数据,使用转换后的数据 */
    free(logo->data);
    logo->data = rgb565_data;
    logo->data_size = height * dst_row_size;
    logo->bpp = 16;  /* 现在已经是RGB565格式 */
    
    return LOGO_SUCCESS;
}
​
/**
 * @brief 居中显示Logo图像
 * @param ctx FrameBuffer上下文结构指针
 * @param logo Logo图像结构指针
 * 
 * 在屏幕中心显示Logo图像,支持自动缩放(如果需要)。
 */
static void display_logo_centered(struct fb_context *ctx,
                                 const struct logo_image *logo)
{
    uint16_t *fb_ptr = (uint16_t *)ctx->fb_mem;
    uint16_t *logo_data = (uint16_t *)logo->data;
    int screen_width = ctx->screen_width;
    int screen_height = ctx->screen_height;
    int logo_width = logo->width;
    int logo_height = logo->height;
    int start_x, start_y;
    int i, j;
    
    /* 计算居中位置 */
    start_x = (screen_width - logo_width) / 2;
    start_y = (screen_height - logo_height) / 2;
    
    /* 边界检查 */
    if (start_x < 0) start_x = 0;
    if (start_y < 0) start_y = 0;
    
    /* 复制Logo数据到FrameBuffer */
    for (j = 0; j < logo_height && j + start_y < screen_height; j++) {
        uint16_t *fb_row = fb_ptr + (j + start_y) * screen_width + start_x;
        uint16_t *logo_row = logo_data + j * logo_width;
        
        for (i = 0; i < logo_width && i + start_x < screen_width; i++) {
            fb_row[i] = logo_row[i];
        }
    }
}
​
/**
 * @brief 显示带进度条的Logo
 * @param ctx FrameBuffer上下文结构指针
 * @param logo Logo图像结构指针
 * @param progress 进度百分比(0-100)
 * @param config 应用程序配置指针
 * 
 * 在屏幕底部显示进度条,Logo显示在进度条上方。
 */
static void display_logo_with_progress(struct fb_context *ctx,
                                      const struct logo_image *logo,
                                      int progress,
                                      const struct app_config *config)
{
    int screen_width = ctx->screen_width;
    int screen_height = ctx->screen_height;
    int logo_width = logo->width;
    int logo_height = logo->height;
    int progress_height = 20;  /* 进度条高度 */
    int padding = 10;          /* 边距 */
    int start_x, start_y;
    
    /* 计算Logo位置(在进度条上方居中) */
    start_x = (screen_width - logo_width) / 2;
    start_y = (screen_height - logo_height - progress_height - padding) / 2;
    
    if (start_y < 0) start_y = 0;
    
    /* 清屏(使用配置的背景色) */
    if (config->clear_screen) {
        clear_screen(ctx, config->bg_color);
    }
    
    /* 显示Logo */
    display_logo_centered(ctx, logo);
    
    /* 绘制进度条背景 */
    int bar_x = screen_width / 4;
    int bar_y = screen_height - progress_height - padding;
    int bar_width = screen_width / 2;
    
    draw_rectangle(ctx, bar_x, bar_y, bar_width, progress_height,
                   config->bg_color, 1);
    
    /* 绘制进度条边框 */
    draw_rectangle(ctx, bar_x, bar_y, bar_width, progress_height,
                   config->fg_color, 0);
    
    /* 绘制进度条填充 */
    if (progress > 0) {
        int fill_width = (bar_width * progress) / 100;
        if (fill_width > 0) {
            draw_rectangle(ctx, bar_x + 2, bar_y + 2,
                          fill_width - 4, progress_height - 4,
                          config->fg_color, 1);
        }
    }
}
​
/**
 * @brief 创建渐变色背景
 * @param ctx FrameBuffer上下文结构指针
 * @param start_color 起始颜色
 * @param end_color 结束颜色
 * @param direction 渐变方向(0=垂直,1=水平)
 * 
 * 创建平滑的渐变色背景。
 */
static void create_gradient_background(struct fb_context *ctx,
                                      uint16_t start_color,
                                      uint16_t end_color,
                                      int direction)
{
    uint16_t *fb_ptr = (uint16_t *)ctx->fb_mem;
    int width = ctx->screen_width;
    int height = ctx->screen_height;
    int i, j;
    
    /* 提取起始和结束颜色的RGB分量 */
    uint8_t start_r, start_g, start_b;
    uint8_t end_r, end_g, end_b;
    
    rgb565_to_rgb888(start_color, &start_r, &start_g, &start_b);
    rgb565_to_rgb888(end_color, &end_r, &end_g, &end_b);
    
    if (direction == 0) {
        /* 垂直渐变 */
        for (j = 0; j < height; j++) {
            uint16_t *row = fb_ptr + j * width;
            float ratio = (float)j / height;
            
            uint8_t r = start_r + (uint8_t)((end_r - start_r) * ratio);
            uint8_t g = start_g + (uint8_t)((end_g - start_g) * ratio);
            uint8_t b = start_b + (uint8_t)((end_b - start_b) * ratio);
            
            uint16_t color = rgb888_to_rgb565(r, g, b);
            
            for (i = 0; i < width; i++) {
                row[i] = color;
            }
        }
    } else {
        /* 水平渐变 */
        for (j = 0; j < height; j++) {
            uint16_t *row = fb_ptr + j * width;
            
            for (i = 0; i < width; i++) {
                float ratio = (float)i / width;
                
                uint8_t r = start_r + (uint8_t)((end_r - start_r) * ratio);
                uint8_t g = start_g + (uint8_t)((end_g - start_g) * ratio);
                uint8_t b = start_b + (uint8_t)((end_b - start_b) * ratio);
                
                row[i] = rgb888_to_rgb565(r, g, b);
            }
        }
    }
}

5. 启动动画效果函数

/**
 * @brief 显示淡入动画效果
 * @param ctx FrameBuffer上下文结构指针
 * @param logo Logo图像结构指针
 * @param duration_ms 动画持续时间(毫秒)
 * 
 * 实现Logo的淡入动画效果。
 */
static void show_fade_in_animation(struct fb_context *ctx,
                                  const struct logo_image *logo,
                                  int duration_ms)
{
    const int steps = 20;  /* 动画步数 */
    const int delay_ms = duration_ms / steps;
    int step;
    
    /* 清屏为黑色 */
    clear_screen(ctx, COLOR_BLACK);
    
    /* 淡入动画 */
    for (step = 0; step <= steps; step++) {
        float alpha = (float)step / steps;
        
        /* 计算混合颜色 */
        uint16_t *fb_ptr = (uint16_t *)ctx->fb_mem;
        uint16_t *logo_data = (uint16_t *)logo->data;
        int width = ctx->screen_width;
        int height = ctx->screen_height;
        int logo_width = logo->width;
        int logo_height = logo->height;
        int start_x = (width - logo_width) / 2;
        int start_y = (height - logo_height) / 2;
        int i, j;
        
        /* 清屏 */
        clear_screen(ctx, COLOR_BLACK);
        
        /* 绘制带透明度的Logo */
        for (j = 0; j < logo_height && j + start_y < height; j++) {
            uint16_t *fb_row = fb_ptr + (j + start_y) * width + start_x;
            uint16_t *logo_row = logo_data + j * logo_width;
            
            for (i = 0; i < logo_width && i + start_x < width; i++) {
                uint16_t logo_pixel = logo_row[i];
                
                if (logo_pixel != 0) {  /* 如果不是透明像素 */
                    /* 简单混合:黑色背景 + Logo颜色 × alpha */
                    uint8_t r, g, b;
                    rgb565_to_rgb888(logo_pixel, &r, &g, &b);
                    
                    r = (uint8_t)(r * alpha);
                    g = (uint8_t)(g * alpha);
                    b = (uint8_t)(b * alpha);
                    
                    fb_row[i] = rgb888_to_rgb565(r, g, b);
                }
            }
        }
        
        /* 延时 */
        usleep(delay_ms * 1000);
    }
}
​
/**
 * @brief 显示滑动动画效果
 * @param ctx FrameBuffer上下文结构指针
 * @param logo Logo图像结构指针
 * @param direction 滑动方向(0=从左到右,1=从右到左,2=从上到下,3=从下到上)
 * @param duration_ms 动画持续时间(毫秒)
 * 
 * 实现Logo的滑动进入动画效果。
 */
static void show_slide_animation(struct fb_context *ctx,
                                const struct logo_image *logo,
                                int direction,
                                int duration_ms)
{
    const int steps = 30;  /* 动画步数 */
    const int delay_ms = duration_ms / steps;
    int step;
    int width = ctx->screen_width;
    int height = ctx->screen_height;
    int logo_width = logo->width;
    int logo_height = logo->height;
    
    /* 计算起始位置 */
    int start_x, start_y;
    int target_x = (width - logo_width) / 2;
    int target_y = (height - logo_height) / 2;
    
    switch (direction) {
    case 0:  /* 从左到右 */
        start_x = -logo_width;
        start_y = target_y;
        break;
    case 1:  /* 从右到左 */
        start_x = width;
        start_y = target_y;
        break;
    case 2:  /* 从上到下 */
        start_x = target_x;
        start_y = -logo_height;
        break;
    case 3:  /* 从下到上 */
        start_x = target_x;
        start_y = height;
        break;
    default:
        start_x = target_x;
        start_y = target_y;
        break;
    }
    
    /* 滑动动画 */
    for (step = 0; step <= steps; step++) {
        float ratio = (float)step / steps;
        int current_x, current_y;
        
        /* 计算当前位置 */
        current_x = start_x + (int)((target_x - start_x) * ratio);
        current_y = start_y + (int)((target_y - start_y) * ratio);
        
        /* 清屏 */
        clear_screen(ctx, COLOR_BLACK);
        
        /* 在当前位置绘制Logo */
        uint16_t *fb_ptr = (uint16_t *)ctx->fb_mem;
        uint16_t *logo_data = (uint16_t *)logo->data;
        int i, j;
        
        for (j = 0; j < logo_height; j++) {
            int fb_y = current_y + j;
            
            if (fb_y >= 0 && fb_y < height) {
                uint16_t *fb_row = fb_ptr + fb_y * width;
                uint16_t *logo_row = logo_data + j * logo_width;
                
                for (i = 0; i < logo_width; i++) {
                    int fb_x = current_x + i;
                    
                    if (fb_x >= 0 && fb_x < width) {
                        fb_row[fb_x] = logo_row[i];
                    }
                }
            }
        }
        
        /* 延时 */
        usleep(delay_ms * 1000);
    }
}
​
/**
 * @brief 显示缩放动画效果
 * @param ctx FrameBuffer上下文结构指针
 * @param logo Logo图像结构指针
 * @param duration_ms 动画持续时间(毫秒)
 * 
 * 实现Logo的缩放动画效果。
 */
static void show_zoom_animation(struct fb_context *ctx,
                               const struct logo_image *logo,
                               int duration_ms)
{
    const int steps = 25;  /* 动画步数 */
    const int delay_ms = duration_ms / steps;
    int step;
    
    /* 缩放动画 */
    for (step = 0; step <= steps; step++) {
        float scale;
        
        if (step < steps / 2) {
            /* 放大阶段 */
            scale = 0.1f + (float)step / (steps / 2) * 0.9f;
        } else {
            /* 保持阶段 */
            scale = 1.0f;
        }
        
        /* 计算缩放后的尺寸 */
        int scaled_width = (int)(logo->width * scale);
        int scaled_height = (int)(logo->height * scale);
        int start_x = (ctx->screen_width - scaled_width) / 2;
        int start_y = (ctx->screen_height - scaled_height) / 2;
        
        /* 清屏 */
        clear_screen(ctx, COLOR_BLACK);
        
        /* 绘制缩放后的Logo(简单最近邻插值) */
        uint16_t *fb_ptr = (uint16_t *)ctx->fb_mem;
        uint16_t *logo_data = (uint16_t *)logo->data;
        int i, j;
        
        for (j = 0; j < scaled_height; j++) {
            int src_y = (int)(j / scale);
            
            if (src_y >= logo->height) src_y = logo->height - 1;
            
            uint16_t *src_row = logo_data + src_y * logo->width;
            int fb_y = start_y + j;
            
            if (fb_y >= 0 && fb_y < ctx->screen_height) {
                uint16_t *fb_row = fb_ptr + fb_y * ctx->screen_width;
                
                for (i = 0; i < scaled_width; i++) {
                    int src_x = (int)(i / scale);
                    
                    if (src_x >= logo->width) src_x = logo->width - 1;
                    
                    int fb_x = start_x + i;
                    
                    if (fb_x >= 0 && fb_x < ctx->screen_width) {
                        fb_row[fb_x] = src_row[src_x];
                    }
                }
            }
        }
        
        /* 延时 */
        usleep(delay_ms * 1000);
    }
}
​
/**
 * @brief 显示启动动画
 * @param ctx FrameBuffer上下文结构指针
 * @param logo Logo图像结构指针
 * @param config 应用程序配置指针
 * 
 * 根据配置显示不同的启动动画效果。
 */
static void show_boot_animation(struct fb_context *ctx,
                               const struct logo_image *logo,
                               const struct app_config *config)
{
    if (!config->show_animation) {
        return;
    }
    
    if (config->verbose) {
        printf("Showing boot animation\n");
    }
    
    /* 随机选择一种动画效果 */
    srand(time(NULL));
    int animation_type = rand() % 3;
    
    switch (animation_type) {
    case 0:
        show_fade_in_animation(ctx, logo, 1000);
        break;
    case 1:
        show_slide_animation(ctx, logo, rand() % 4, 800);
        break;
    case 2:
        show_zoom_animation(ctx, logo, 1200);
        break;
    }
    
    /* 动画结束后,确保Logo正确显示 */
    clear_screen(ctx, config->bg_color);
    display_logo_centered(ctx, logo);
}

二、信号处理和应用程序主函数

6. 信号处理函数

/**
 * @brief 信号处理器
 * @param sig 信号编号
 * 
 * 处理应用程序接收到的信号,实现优雅退出。
 */
static volatile int keep_running = 1;
​
static void signal_handler(int sig)
{
    switch (sig) {
    case SIGINT:
        printf("\nReceived SIGINT, shutting down gracefully...\n");
        keep_running = 0;
        break;
    case SIGTERM:
        printf("\nReceived SIGTERM, shutting down gracefully...\n");
        keep_running = 0;
        break;
    case SIGUSR1:
        printf("\nReceived SIGUSR1, refreshing display...\n");
        break;
    default:
        break;
    }
}
​
/**
 * @brief 设置信号处理器
 * 
 * 为应用程序设置必要的信号处理器。
 */
static void setup_signal_handlers(void)
{
    struct sigaction sa;
    
    /* 设置SIGINT处理器 */
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);
    
    /* 设置SIGTERM处理器 */
    sigaction(SIGTERM, &sa, NULL);
    
    /* 设置SIGUSR1处理器 */
    sigaction(SIGUSR1, &sa, NULL);
    
    /* 忽略SIGPIPE信号 */
    sa.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &sa, NULL);
}
​
/**
 * @brief 显示使用帮助信息
 * @param program_name 程序名称
 * 
 * 打印命令行参数的使用说明。
 */
static void show_usage(const char *program_name)
{
    printf("Usage: %s [OPTIONS]\n\n", program_name);
    printf("Display logo on ST7789 SPI TFT display during system boot.\n\n");
    printf("Options:\n");
    printf("  -d, --device DEVICE    Framebuffer device (default: /dev/fb0)\n");
    printf("  -l, --logo FILE        Logo image file (BMP or RAW RGB565)\n");
    printf("  -t, --time MSEC        Display time in milliseconds (default: 3000)\n");
    printf("  -b, --bgcolor COLOR    Background color in hex (default: 0000)\n");
    printf("  -f, --fgcolor COLOR    Foreground color in hex (default: FFFF)\n");
    printf("  -p, --progress         Show progress bar\n");
    printf("  -a, --animation        Show animation effects\n");
    printf("  -c, --center           Center the logo\n");
    printf("  -s, --clear            Clear screen before display\n");
    printf("  -v, --verbose          Verbose output\n");
    printf("  -h, --help             Show this help message\n");
    printf("\nColor format: RRGGBB (hex), will be converted to RGB565\n");
    printf("Example: --bgcolor 0000FF for blue background\n");
    printf("\nExamples:\n");
    printf("  %s -l /etc/logo.bmp -t 5000 -p\n", program_name);
    printf("  %s --device /dev/fb1 --logo logo.raw --animation\n", program_name);
}
​
/**
 * @brief 将十六进制字符串转换为颜色值
 * @param hex_str 十六进制颜色字符串(格式:RRGGBB)
 * @return RGB565格式的颜色值,失败返回0x0000
 * 
 * 将HTML格式的颜色字符串转换为RGB565颜色值。
 */
static uint16_t hex_string_to_color(const char *hex_str)
{
    unsigned int r, g, b;
    
    if (!hex_str || strlen(hex_str) != 6) {
        fprintf(stderr, "Error: Invalid color format. Use RRGGBB hex format.\n");
        return COLOR_BLACK;
    }
    
    if (sscanf(hex_str, "%02x%02x%02x", &r, &g, &b) != 3) {
        fprintf(stderr, "Error: Invalid color format. Use RRGGBB hex format.\n");
        return COLOR_BLACK;
    }
    
    return rgb888_to_rgb565((uint8_t)r, (uint8_t)g, (uint8_t)b);
}

7. 命令行参数解析函数

/**
 * @brief 解析命令行参数
 * @param argc 参数个数
 * @param argv 参数数组
 * @param config 应用程序配置结构指针
 * @return 成功返回0,失败返回-1
 * 
 * 解析命令行参数并填充应用程序配置结构。
 */
static int parse_arguments(int argc, char *argv[], struct app_config *config)
{
    int opt;
    int option_index = 0;
    
    /* 长选项定义 */
    static struct option long_options[] = {
        {"device",    required_argument, 0, 'd'},
        {"logo",      required_argument, 0, 'l'},
        {"time",      required_argument, 0, 't'},
        {"bgcolor",   required_argument, 0, 'b'},
        {"fgcolor",   required_argument, 0, 'f'},
        {"progress",  no_argument,       0, 'p'},
        {"animation", no_argument,       0, 'a'},
        {"center",    no_argument,       0, 'c'},
        {"clear",     no_argument,       0, 's'},
        {"verbose",   no_argument,       0, 'v'},
        {"help",      no_argument,       0, 'h'},
        {0, 0, 0, 0}
    };
    
    /* 设置默认配置 */
    strncpy(config->fb_device, DEFAULT_FB_DEVICE, sizeof(config->fb_device) - 1);
    strncpy(config->logo_file, DEFAULT_LOGO_FILE, sizeof(config->logo_file) - 1);
    config->display_time = DEFAULT_DELAY_MS;
    config->show_progress = 0;
    config->show_animation = 0;
    config->center_logo = 1;
    config->clear_screen = 1;
    config->bg_color = COLOR_BLACK;
    config->fg_color = COLOR_WHITE;
    config->verbose = 0;
    
    /* 解析命令行选项 */
    while ((opt = getopt_long(argc, argv, "d:l:t:b:f:pacsvh", 
                              long_options, &option_index)) != -1) {
        switch (opt) {
        case 'd':
            strncpy(config->fb_device, optarg, sizeof(config->fb_device) - 1);
            break;
            
        case 'l':
            strncpy(config->logo_file, optarg, sizeof(config->logo_file) - 1);
            break;
            
        case 't':
            config->display_time = atoi(optarg);
            if (config->display_time < 0) {
                fprintf(stderr, "Error: Display time must be positive\n");
                return -1;
            }
            break;
            
        case 'b':
            config->bg_color = hex_string_to_color(optarg);
            break;
            
        case 'f':
            config->fg_color = hex_string_to_color(optarg);
            break;
            
        case 'p':
            config->show_progress = 1;
            break;
            
        case 'a':
            config->show_animation = 1;
            break;
            
        case 'c':
            config->center_logo = 1;
            break;
            
        case 's':
            config->clear_screen = 1;
            break;
            
        case 'v':
            config->verbose = 1;
            break;
            
        case 'h':
            show_usage(argv[0]);
            exit(EXIT_SUCCESS);
            
        case '?':
            /* 未知选项 */
            return -1;
            
        default:
            fprintf(stderr, "Error: Unknown option encountered\n");
            return -1;
        }
    }
    
    /* 检查非选项参数 */
    if (optind < argc) {
        fprintf(stderr, "Error: Unexpected argument: %s\n", argv[optind]);
        return -1;
    }
    
    return 0;
}
​
/**
 * @brief 验证应用程序配置
 * @param config 应用程序配置结构指针
 * @return 成功返回0,失败返回-1
 * 
 * 验证配置参数的有效性。
 */
static int validate_config(const struct app_config *config)
{
    /* 检查FrameBuffer设备路径 */
    if (strlen(config->fb_device) == 0) {
        fprintf(stderr, "Error: Framebuffer device path is empty\n");
        return -1;
    }
    
    /* 检查Logo文件路径 */
    if (strlen(config->logo_file) == 0) {
        fprintf(stderr, "Error: Logo file path is empty\n");
        return -1;
    }
    
    /* 检查显示时间 */
    if (config->display_time < 0) {
        fprintf(stderr, "Error: Display time must be positive\n");
        return -1;
    }
    
    /* 检查Logo文件是否存在 */
    if (access(config->logo_file, F_OK) != 0) {
        fprintf(stderr, "Warning: Logo file not found: %s\n", config->logo_file);
        /* 这不是致命错误,程序可以继续运行 */
    }
    
    return 0;
}

8. 进度显示和系统集成函数

/**
 * @brief 显示启动进度
 * @param ctx FrameBuffer上下文结构指针
 * @param logo Logo图像结构指针
 * @param config 应用程序配置指针
 * 
 * 显示启动进度动画,模拟系统启动过程。
 */
static void show_boot_progress(struct fb_context *ctx,
                              struct logo_image *logo,
                              const struct app_config *config)
{
    const int total_steps = 10;
    int step;
    
    if (!config->show_progress) {
        return;
    }
    
    if (config->verbose) {
        printf("Showing boot progress\n");
    }
    
    /* 初始显示 */
    clear_screen(ctx, config->bg_color);
    display_logo_centered(ctx, logo);
    
    /* 模拟启动进度 */
    for (step = 1; step <= total_steps; step++) {
        int progress = (step * 100) / total_steps;
        
        /* 更新进度条 */
        display_logo_with_progress(ctx, logo, progress, config);
        
        /* 模拟启动任务 */
        switch (step) {
        case 1:
            if (config->verbose) printf("  Starting kernel...\n");
            break;
        case 2:
            if (config->verbose) printf("  Mounting filesystems...\n");
            break;
        case 3:
            if (config->verbose) printf("  Starting network...\n");
            break;
        case 4:
            if (config->verbose) printf("  Loading drivers...\n");
            break;
        case 5:
            if (config->verbose) printf("  Starting services...\n");
            break;
        case 6:
            if (config->verbose) printf("  Configuring system...\n");
            break;
        case 7:
            if (config->verbose) printf("  Starting applications...\n");
            break;
        case 8:
            if (config->verbose) printf("  Finalizing startup...\n");
            break;
        case 9:
            if (config->verbose) printf("  System ready...\n");
            break;
        case 10:
            if (config->verbose) printf("  Startup complete\n");
            break;
        }
        
        /* 延时模拟任务执行 */
        usleep(config->display_time * 1000 / total_steps);
    }
}
​
/**
 * @brief 检查FrameBuffer是否支持所需功能
 * @param ctx FrameBuffer上下文结构指针
 * @return 成功返回0,失败返回错误码
 * 
 * 验证FrameBuffer设备是否支持所需的分辨率和颜色格式。
 */
static int check_framebuffer_capabilities(const struct fb_context *ctx)
{
    /* 检查分辨率 */
    if (ctx->screen_width < 320 || ctx->screen_height < 240) {
        fprintf(stderr, "Error: Framebuffer resolution %dx%d is too small\n",
                ctx->screen_width, ctx->screen_height);
        fprintf(stderr, "Minimum required: 320x240\n");
        return LOGO_ERROR_FORMAT;
    }
    
    /* 检查颜色深度 */
    if (ctx->bpp != 16) {
        fprintf(stderr, "Error: Framebuffer color depth is %d bpp\n", ctx->bpp);
        fprintf(stderr, "Required: 16 bpp (RGB565)\n");
        return LOGO_ERROR_FORMAT;
    }
    
    /* 检查内存映射 */
    if (!ctx->fb_mem || ctx->fb_mem == MAP_FAILED) {
        fprintf(stderr, "Error: Framebuffer memory mapping failed\n");
        return LOGO_ERROR_MMAP;
    }
    
    /* 检查显存大小 */
    size_t required_size = ctx->screen_width * ctx->screen_height * 2;
    if (ctx->fb_size < required_size) {
        fprintf(stderr, "Error: Framebuffer memory too small\n");
        fprintf(stderr, "Available: %zu bytes, Required: %zu bytes\n",
                ctx->fb_size, required_size);
        return LOGO_ERROR;
    }
    
    return LOGO_SUCCESS;
}
​
/**
 * @brief 创建默认Logo(当找不到Logo文件时)
 * @param logo Logo图像结构指针
 * @param width Logo宽度
 * @param height Logo高度
 * 
 * 创建简单的默认Logo作为备用。
 */
static void create_default_logo(struct logo_image *logo, int width, int height)
{
    size_t data_size = width * height * 2;  /* RGB565: 2 bytes per pixel */
    uint16_t *logo_data = (uint16_t *)malloc(data_size);
    int i, j;
    
    if (!logo_data) {
        fprintf(stderr, "Warning: Cannot allocate memory for default logo\n");
        return;
    }
    
    /* 创建简单的棋盘格Logo */
    for (j = 0; j < height; j++) {
        for (i = 0; i < width; i++) {
            uint16_t color;
            
            if ((i / 20 + j / 20) % 2 == 0) {
                color = rgb888_to_rgb565(0, 120, 200);  /* 蓝色 */
            } else {
                color = rgb888_to_rgb565(200, 200, 200);  /* 灰色 */
            }
            
            /* 添加边框 */
            if (i < 2 || i >= width - 2 || j < 2 || j >= height - 2) {
                color = rgb888_to_rgb565(255, 255, 255);  /* 白色边框 */
            }
            
            /* 添加文字区域 */
            if (j >= height / 2 - 10 && j < height / 2 + 10 &&
                i >= width / 2 - 40 && i < width / 2 + 40) {
                color = rgb888_to_rgb565(255, 255, 255);  /* 白色背景 */
                
                /* 简单的"LOGO"文字 */
                if (j == height / 2 && i >= width / 2 - 30 && i < width / 2 + 30) {
                    color = rgb888_to_rgb565(0, 0, 0);  /* 黑色文字 */
                }
            }
            
            logo_data[j * width + i] = color;
        }
    }
    
    logo->width = width;
    logo->height = height;
    logo->bpp = 16;
    logo->data = (uint8_t *)logo_data;
    logo->data_size = data_size;
    logo->palette = NULL;
    logo->palette_size = 0;
}
​
/**
 * @brief 初始化应用程序日志系统
 * @param config 应用程序配置指针
 * 
 * 根据配置初始化日志输出级别。
 */
static void init_logging(const struct app_config *config)
{
    if (config->verbose) {
        printf("=== ST7789 Logo Display Application ===\n");
        printf("Version: 1.0\n");
        printf("Build date: %s %s\n", __DATE__, __TIME__);
        printf("\n");
    }
}
​
/**
 * @brief 显示应用程序启动信息
 * @param config 应用程序配置指针
 * 
 * 显示应用程序的启动配置信息。
 */
static void show_startup_info(const struct app_config *config)
{
    if (!config->verbose) {
        return;
    }
    
    printf("Configuration:\n");
    printf("  Framebuffer device: %s\n", config->fb_device);
    printf("  Logo file: %s\n", config->logo_file);
    printf("  Display time: %d ms\n", config->display_time);
    printf("  Background color: 0x%04X\n", config->bg_color);
    printf("  Foreground color: 0x%04X\n", config->fg_color);
    printf("  Show progress bar: %s\n", config->show_progress ? "Yes" : "No");
    printf("  Show animation: %s\n", config->show_animation ? "Yes" : "No");
    printf("  Center logo: %s\n", config->center_logo ? "Yes" : "No");
    printf("  Clear screen: %s\n", config->clear_screen ? "Yes" : "No");
    printf("\n");
}

9. 应用程序主函数

/**
 * @brief 应用程序主函数
 * @param argc 命令行参数个数
 * @param argv 命令行参数数组
 * @return 成功返回0,失败返回错误码
 * 
 * Logo显示应用程序的入口点,协调所有功能的执行。
 */
int main(int argc, char *argv[])
{
    struct app_config config;
    struct fb_context fb_ctx;
    struct logo_image logo;
    int ret = EXIT_SUCCESS;
    int logo_loaded = 0;
    
    /* 初始化Logo结构 */
    memset(&logo, 0, sizeof(logo));
    memset(&fb_ctx, 0, sizeof(fb_ctx));
    fb_ctx.fd = -1;
    
    /* 解析命令行参数 */
    if (parse_arguments(argc, argv, &config) != 0) {
        fprintf(stderr, "Error: Failed to parse command line arguments\n");
        show_usage(argv[0]);
        return EXIT_FAILURE;
    }
    
    /* 验证配置 */
    if (validate_config(&config) != 0) {
        return EXIT_FAILURE;
    }
    
    /* 初始化日志系统 */
    init_logging(&config);
    
    /* 显示启动信息 */
    show_startup_info(&config);
    
    /* 设置信号处理器 */
    setup_signal_handlers();
    
    /* 初始化FrameBuffer上下文 */
    if (init_framebuffer_context(&fb_ctx, &config) != LOGO_SUCCESS) {
        fprintf(stderr, "Error: Failed to initialize framebuffer\n");
        ret = EXIT_FAILURE;
        goto cleanup;
    }
    
    /* 检查FrameBuffer功能 */
    if (check_framebuffer_capabilities(&fb_ctx) != LOGO_SUCCESS) {
        ret = EXIT_FAILURE;
        goto cleanup;
    }
    
    /* 加载Logo图像 */
    if (load_logo_image(config.logo_file, &logo, &config) == LOGO_SUCCESS) {
        logo_loaded = 1;
        
        /* 如果需要,转换图像格式 */
        if (logo.bpp == 24 || logo.bpp == 32) {
            if (convert_bmp_to_rgb565(&logo, &fb_ctx) != LOGO_SUCCESS) {
                fprintf(stderr, "Error: Failed to convert logo to RGB565 format\n");
                free_logo_image(&logo);
                logo_loaded = 0;
            }
        }
    } else {
        fprintf(stderr, "Warning: Failed to load logo from %s\n", config.logo_file);
        fprintf(stderr, "Creating default logo\n");
    }
    
    /* 如果没有成功加载Logo,创建默认Logo */
    if (!logo_loaded) {
        create_default_logo(&logo, 200, 100);
        if (logo.data) {
            logo_loaded = 1;
        }
    }
    
    if (!logo_loaded) {
        fprintf(stderr, "Error: No logo available for display\n");
        ret = EXIT_FAILURE;
        goto cleanup;
    }
    
    /* 检查Logo尺寸 */
    if (logo.width > fb_ctx.screen_width || logo.height > fb_ctx.screen_height) {
        fprintf(stderr, "Warning: Logo size %dx%d is larger than screen %dx%d\n",
                logo.width, logo.height,
                fb_ctx.screen_width, fb_ctx.screen_height);
        fprintf(stderr, "Logo will be clipped\n");
    }
    
    /* 显示启动动画 */
    show_boot_animation(&fb_ctx, &logo, &config);
    
    /* 显示启动进度 */
    show_boot_progress(&fb_ctx, &logo, &config);
    
    /* 如果没有显示进度条,直接显示Logo */
    if (!config.show_progress) {
        if (config.clear_screen) {
            clear_screen(&fb_ctx, config.bg_color);
        }
        
        if (config.center_logo) {
            display_logo_centered(&fb_ctx, &logo);
        } else {
            /* 在左上角显示Logo */
            uint16_t *fb_ptr = (uint16_t *)fb_ctx.fb_mem;
            uint16_t *logo_data = (uint16_t *)logo.data;
            int i, j;
            
            for (j = 0; j < logo.height && j < fb_ctx.screen_height; j++) {
                uint16_t *fb_row = fb_ptr + j * fb_ctx.screen_width;
                uint16_t *logo_row = logo_data + j * logo.width;
                
                for (i = 0; i < logo.width && i < fb_ctx.screen_width; i++) {
                    fb_row[i] = logo_row[i];
                }
            }
        }
    }
    
    if (config.verbose) {
        printf("Logo displayed successfully\n");
        printf("Waiting for %d ms...\n", config.display_time);
    }
    
    /* 等待指定时间,同时检查信号 */
    if (config.display_time > 0) {
        struct timespec start_time, current_time;
        long elapsed_ms = 0;
        
        clock_gettime(CLOCK_MONOTONIC, &start_time);
        
        while (keep_running && elapsed_ms < config.display_time) {
            /* 休眠一段时间并检查是否应该退出 */
            usleep(100000);  /* 100ms */
            
            clock_gettime(CLOCK_MONOTONIC, &current_time);
            elapsed_ms = (current_time.tv_sec - start_time.tv_sec) * 1000 +
                        (current_time.tv_nsec - start_time.tv_nsec) / 1000000;
        }
    }
    
    if (config.verbose) {
        printf("Display time elapsed\n");
    }
    
cleanup:
    /* 清理FrameBuffer上下文 */
    cleanup_framebuffer_context(&fb_ctx);
    
    /* 释放Logo资源 */
    free_logo_image(&logo);
    
    if (config.verbose) {
        if (ret == EXIT_SUCCESS) {
            printf("Application completed successfully\n");
        } else {
            printf("Application exited with error\n");
        }
    }
    
    return ret;
}

四、Makefile和系统集成

10. Makefile文件

# Makefile for ST7789 Logo Display Application
​
# Compiler and flags
CC = $(CROSS_COMPILE)gcc
CFLAGS = -Wall -Wextra -O2 -g
LDFLAGS = 
​
# Source files
SRCS = logo_display.c
OBJS = $(SRCS:.c=.o)
TARGET = logo_display
​
# Installation paths
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
SYSTEMD_DIR = /lib/systemd/system
INIT_D_DIR = /etc/init.d
​
# Default target
all: $(TARGET)
​
# Build the application
$(TARGET): $(OBJS)
    $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
​
# Compile source files
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@
​
# Clean build files
clean:
    rm -f $(OBJS) $(TARGET)
​
# Install the application
install: all
    install -d $(DESTDIR)$(BINDIR)
    install -m 755 $(TARGET) $(DESTDIR)$(BINDIR)
    install -d $(DESTDIR)/etc
    install -m 644 logo.bmp $(DESTDIR)/etc/logo.bmp 2>/dev/null || true
    install -d $(DESTDIR)$(SYSTEMD_DIR)
    install -m 644 logo-display.service $(DESTDIR)$(SYSTEMD_DIR)/ 2>/dev/null || true
    install -d $(DESTDIR)$(INIT_D_DIR)
    install -m 755 logo-display.init $(DESTDIR)$(INIT_D_DIR)/logo-display 2>/dev/null || true
​
# Uninstall the application
uninstall:
    rm -f $(DESTDIR)$(BINDIR)/$(TARGET)
    rm -f $(DESTDIR)$(SYSTEMD_DIR)/logo-display.service 2>/dev/null || true
    rm -f $(DESTDIR)$(INIT_D_DIR)/logo-display 2>/dev/null || true
​
# Create distribution tarball
dist: clean
    mkdir -p dist/$(TARGET)-1.0
    cp $(SRCS) Makefile README.md LICENSE logo.bmp \
       logo-display.service logo-display.init dist/$(TARGET)-1.0/
    tar -czf $(TARGET)-1.0.tar.gz -C dist $(TARGET)-1.0
    rm -rf dist
​
# Run the application
run: all
    ./$(TARGET) --verbose
​
# Debug build
debug: CFLAGS += -DDEBUG -g3
debug: clean all
​
# Static analysis
analyze:
    $(CC) $(CFLAGS) --analyze $(SRCS)
​
# Format source code
format:
    indent -linux -l120 -nut $(SRCS)
​
.PHONY: all clean install uninstall dist run debug analyze format

11. Systemd服务文件

# logo-display.service - ST7789 Logo Display Service
​
[Unit]
Description=ST7789 SPI TFT Logo Display Service
After=local-fs.target
Before=graphical.target
Wants=local-fs.target
ConditionPathExists=/dev/fb0
​
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/logo_display -l /etc/logo.bmp -t 3000 -p -a -v
StandardOutput=journal
StandardError=journal
TimeoutSec=30
​
[Install]
WantedBy=multi-user.target

12. Init.d启动脚本

#!/bin/sh
### BEGIN INIT INFO
# Provides:          logo-display
# Required-Start:    $local_fs
# Required-Stop:     $local_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: ST7789 Logo Display
# Description:       Display logo on ST7789 SPI TFT during boot
### END INIT INFO
​
# Application path
APP_PATH="/usr/local/bin/logo_display"
LOGO_FILE="/etc/logo.bmp"
​
# Default options
DISPLAY_TIME=3000
SHOW_PROGRESS=1
SHOW_ANIMATION=1
VERBOSE=0
​
# Get configuration from /etc/default/logo-display
if [ -f /etc/default/logo-display ]; then
    . /etc/default/logo-display
fi
​
# Build command line
CMD="$APP_PATH -l $LOGO_FILE -t $DISPLAY_TIME"
​
if [ "$SHOW_PROGRESS" = "1" ]; then
    CMD="$CMD -p"
fi
​
if [ "$SHOW_ANIMATION" = "1" ]; then
    CMD="$CMD -a"
fi
​
if [ "$VERBOSE" = "1" ]; then
    CMD="$CMD -v"
fi
​
case "$1" in
    start)
        echo "Starting logo display..."
        $CMD &
        ;;
    stop)
        echo "Stopping logo display..."
        pkill -f "logo_display"
        ;;
    restart)
        $0 stop
        sleep 1
        $0 start
        ;;
    status)
        if pgrep -f "logo_display" >/dev/null; then
            echo "logo-display is running"
        else
            echo "logo-display is not running"
        fi
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac
​
exit 0

13. 配置文件示例

# /etc/default/logo-display
# Configuration for ST7789 Logo Display Service
​
# Display time in milliseconds
DISPLAY_TIME=5000
​
# Show progress bar (1=yes, 0=no)
SHOW_PROGRESS=1
​
# Show animation effects (1=yes, 0=no)
SHOW_ANIMATION=1
​
# Verbose output (1=yes, 0=no)
VERBOSE=0
​
# Framebuffer device
FB_DEVICE="/dev/fb0"
​
# Logo file path
LOGO_FILE="/etc/logo.bmp"
​
# Background color (hex RRGGBB)
BG_COLOR="000000"
​
# Foreground color (hex RRGGBB)
FG_COLOR="FFFFFF"

五、构建和使用说明

14. 构建和安装说明

# ST7789 Logo显示应用程序构建指南
​
## 依赖项
- Linux系统(支持FrameBuffer)
- GCC编译器
- 系统开发头文件
​
## 构建步骤
1. 克隆或下载源代码
   ```bash
   git clone <repository-url>
   cd st7789-logo-display
  1. 编译应用程序

    make
  2. 安装到系统

    sudo make install
  3. 配置系统服务(可选)

    • Systemd系统:

      sudo systemctl enable logo-display.service
      sudo systemctl start logo-display.service
    • Init.d系统:

      sudo update-rc.d logo-display defaults
      sudo service logo-display start

自定义Logo

  1. 准备Logo图像

    • 格式:BMP(24位或32位)或RAW RGB565

    • 建议尺寸:不超过320x240像素

    • 颜色:支持真彩色

  2. 安装Logo文件

    sudo cp mylogo.bmp /etc/logo.bmp
  3. 调整配置文件

    sudo nano /etc/default/logo-display

命令行使用

# 基本使用
logo_display -l /path/to/logo.bmp
​
# 显示进度条和动画
logo_display -l logo.bmp -t 5000 -p -a
​
# 自定义颜色
logo_display -l logo.bmp -b 0000FF -f FFFFFF
​
# 详细输出
logo_display -l logo.bmp -v
​
# 指定FrameBuffer设备
logo_display -d /dev/fb1 -l logo.bmp
​
# 显示帮助
logo_display -h

调试

  1. 检查FrameBuffer设备

    cat /proc/fb
    fbset -i
  2. 检查驱动程序

    dmesg | grep st7789
    lsmod | grep st7789
  3. 调试应用程序

    logo_display -v -l logo.bmp 2>&1 | tee debug.log

故障排除

  1. 无法打开FrameBuffer设备

    • 检查设备权限:ls -l /dev/fb0

    • 检查驱动是否加载:lsmod | grep st7789

  2. Logo显示不正常

    • 检查Logo格式:确保是支持的格式

    • 检查Logo尺寸:不要超过屏幕分辨率

    • 检查颜色深度:FrameBuffer必须支持RGB565

  3. 动画效果不流畅

    • 降低动画复杂度

    • 减少显示时间

    • 检查系统负载

​
### 15. 测试脚本
```bash
#!/bin/bash
# test_logo_display.sh - ST7789 Logo Display Test Script
​
echo "=== ST7789 Logo Display Test ==="
echo
​
# Test 1: Check framebuffer device
echo "Test 1: Checking framebuffer device..."
if [ -c /dev/fb0 ]; then
    echo "✓ Framebuffer device /dev/fb0 exists"
else
    echo "✗ Framebuffer device /dev/fb0 not found"
    exit 1
fi
​
# Test 2: Check framebuffer info
echo
echo "Test 2: Checking framebuffer information..."
if command -v fbset &> /dev/null; then
    fbset -i | grep -E "mode|geometry|depth"
else
    echo "Warning: fbset command not found"
fi
​
# Test 3: Test basic display
echo
echo "Test 3: Testing basic display..."
if [ -f /etc/logo.bmp ]; then
    echo "✓ Found default logo file"
    timeout 5 logo_display -l /etc/logo.bmp -t 2000 -v
    if [ $? -eq 0 ]; then
        echo "✓ Basic display test passed"
    else
        echo "✗ Basic display test failed"
    fi
else
    echo "✗ Default logo file not found"
    echo "Creating test logo..."
    # Create a simple test pattern
    convert -size 320x240 xc:blue -fill red -draw 'circle 160,120 160,20' test.bmp
    timeout 5 logo_display -l test.bmp -t 2000
    rm -f test.bmp
fi
​
# Test 4: Test with progress bar
echo
echo "Test 4: Testing progress bar display..."
timeout 10 logo_display -l /etc/logo.bmp -t 5000 -p -v
if [ $? -eq 0 ]; then
    echo "✓ Progress bar test passed"
else
    echo "✗ Progress bar test failed"
fi
​
# Test 5: Test animation effects
echo
echo "Test 5: Testing animation effects..."
timeout 8 logo_display -l /etc/logo.bmp -t 3000 -a -v
if [ $? -eq 0 ]; then
    echo "✓ Animation test passed"
else
    echo "✗ Animation test failed"
fi
​
# Test 6: Performance test
echo
echo "Test 6: Performance test..."
start_time=$(date +%s%N)
logo_display -l /etc/logo.bmp -t 1000 --clear 0 > /dev/null 2>&1
end_time=$(date +%s%N)
elapsed=$(( (end_time - start_time) / 1000000 ))
echo "Display time: ${elapsed}ms"
​
if [ $elapsed -lt 2000 ]; then
    echo "✓ Performance test passed"
else
    echo "✗ Performance test failed - too slow"
fi
​
echo
echo "=== Test Complete ==="

这个完整的Logo显示应用程序提供了:

  1. 完整的FrameBuffer操作接口

  2. 多种图像格式支持

  3. 动画效果和进度条显示

  4. 信号处理和优雅退出

  5. 系统服务集成

  6. 完整的构建和部署工具

应用程序可以轻松集成到嵌入式Linux系统中,在启动时显示自定义Logo和进度信息。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐