低成本物联网设备Linux使用ST7789-tft Logo显示应用程序
·
一、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, ¤t_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
-
编译应用程序
make
-
安装到系统
sudo make install
-
配置系统服务(可选)
-
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
-
准备Logo图像
-
格式:BMP(24位或32位)或RAW RGB565
-
建议尺寸:不超过320x240像素
-
颜色:支持真彩色
-
-
安装Logo文件
sudo cp mylogo.bmp /etc/logo.bmp
-
调整配置文件
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
调试
-
检查FrameBuffer设备
cat /proc/fb fbset -i
-
检查驱动程序
dmesg | grep st7789 lsmod | grep st7789
-
调试应用程序
logo_display -v -l logo.bmp 2>&1 | tee debug.log
故障排除
-
无法打开FrameBuffer设备
-
检查设备权限:
ls -l /dev/fb0 -
检查驱动是否加载:
lsmod | grep st7789
-
-
Logo显示不正常
-
检查Logo格式:确保是支持的格式
-
检查Logo尺寸:不要超过屏幕分辨率
-
检查颜色深度:FrameBuffer必须支持RGB565
-
-
动画效果不流畅
-
降低动画复杂度
-
减少显示时间
-
检查系统负载
-
### 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显示应用程序提供了:
-
完整的FrameBuffer操作接口
-
多种图像格式支持
-
动画效果和进度条显示
-
信号处理和优雅退出
-
系统服务集成
-
完整的构建和部署工具
应用程序可以轻松集成到嵌入式Linux系统中,在启动时显示自定义Logo和进度信息。
更多推荐
所有评论(0)