前言
Github仓库地址:https://github.com/Amireux0013/RK3568_dmeo
随着嵌入式系统在各行各业的广泛应用,基于ARM架构的高性能开发板成为了嵌入式开发的主流平台。本文将详细介绍一个基于RK3568开发板的多功能触控应用系统的设计与实现过程,该系统集成了写字板、图片播放器和音乐播放器三大功能模块,通过触摸屏实现人机交互。通过本项目的开发实践,展示了在嵌入式Linux系统开发、设备驱动操作、多进程管理、图形界面设计等方面的专业技能。

一、系统架构设计
1.1 硬件平台
RK3568是瑞芯微推出的一款高性能ARM Cortex-A55四核处理器,主频高达2.0GHz,集成了Mali-G52 GPU,支持4K视频编解码,具备丰富的外设接口。本项目使用的开发板配置如下:
- 处理器:RK3568 (四核Cortex-A55,主频2.0GHz)
- 内存:2GB LPDDR4
- 存储:16GB eMMC + microSD卡扩展
- 显示:7英寸1024×600电容触摸屏
- 音频:内置音频编解码器,支持扬声器输出
- 接口:USB、HDMI、以太网、GPIO等
1.2 软件架构
系统采用分层设计思想,自底向上分为以下几层:
- 操作系统层:基于Linux内核定制,提供设备驱动和系统服务
- 设备驱动层:封装LCD显示屏(/dev/fb0)和触摸屏(/dev/input/event2)的底层操作
- 基础功能层:实现图形绘制、BMP图片解析、触摸事件处理等基础功能
- 应用功能层:实现写字板、图片播放器和音乐播放器三个功能模块
- 用户界面层:提供图形化菜单和交互界面
系统采用多进程架构,主进程负责菜单显示和功能切换,各功能模块在独立的子进程中运行,提高了系统的稳定性和模块化程度。
二、底层驱动开发
2.1 LCD驱动操作
在嵌入式Linux系统中,LCD显示通常通过帧缓冲设备来实现。本项目直接操作/dev/fb0设备文件,实现对显示屏的控制。以下是LCD初始化和操作的核心代码:
/**
* @brief 打开LCD屏幕驱动
* @return 成功返回文件描述符, 失败返回-1
*/
int lcd_open(void)
{
int lcd_fd = open("/dev/fb0", O_RDWR);
if (lcd_fd == -1)
{
perror("open /dev/fb0 failed");
return -1;
}
return lcd_fd;
}
通过对帧缓冲区的直接读写,实现了高效的图形显示。在RK3568平台上,LCD分辨率为1024×600,每个像素占用4字节(ARGB格式),因此帧缓冲区大小为1024×600×4字节。
2.2 触摸屏驱动操作
触摸屏通过Linux输入子系统提供的事件接口(/dev/input/event2)进行操作。系统通过读取输入事件,获取触摸坐标和触摸状态:
/**
* @brief 打开触摸屏驱动
* @return 成功返回文件描述符, 失败返回-1
*/
int ts_open(void)
{
int ts_fd = open("/dev/input/event2", O_RDWR);
if (ts_fd == -1)
{
perror("open /dev/input/event2 failed");
return -1;
}
return ts_fd;
}
/**
* @brief 获取触摸屏坐标
* @param ts_fd 触摸屏文件描述符
* @param x 用于存储x坐标的指针
* @param y 用于存储y坐标的指针
*/
void get_xy(int ts_fd, int *x, int *y)
{
struct input_event ts_buf;
int got_x = 0, got_y = 0;
while (1)
{
bzero(&ts_buf, sizeof(ts_buf));
read(ts_fd, &ts_buf, sizeof(ts_buf));
if (ts_buf.type == EV_ABS && ts_buf.code == ABS_X)
{
*x = ts_buf.value;
got_x = 1;
}
if (ts_buf.type == EV_ABS && ts_buf.code == ABS_Y)
{
*y = ts_buf.value;
got_y = 1;
}
if (ts_buf.type == EV_KEY && ts_buf.code == BTN_TOUCH && ts_buf.value == 0)
{
if (got_x && got_y) break;
}
}
}
这里实现了对Linux输入事件的精确解析,通过判断事件类型(type)、事件代码(code)和事件值(value),准确获取触摸坐标和触摸状态。
三、图形显示技术
3.1 BMP图片解析与显示
在嵌入式系统中,图片显示是一个常见需求。本项目实现了BMP格式图片的解析和显示功能,直接操作像素数据,无需依赖第三方图形库,提高了系统效率:
/**
* @brief 在指定位置显示BMP图片
* @param bmp_path 图片路径
* @param x0, y0 显示的起始坐标
* @param lcd_fd LCD文件描述符
* @return 成功返回0, 失败返回-1
*/
int show_bmp(const char *bmp_path, int x0, int y0, int lcd_fd)
{
int bmp_fd = open(bmp_path, O_RDONLY);
if (bmp_fd == -1)
{
printf("open bmp %s error\n", bmp_path);
return -1;
}
unsigned char head[54] = {0};
read(bmp_fd, head, 54);
int w = *((int *)&head[18]);
int h = *((int *)&head[22]);
// 计算每行需要补齐的字节数
int line_padding = (4 - (w * 3) % 4) % 4;
int line_size = w * 3 + line_padding;
// 读取BMP像素数据
unsigned char *bmp_data = malloc(line_size * h);
if (bmp_data == NULL) {
printf("malloc for bmp_data failed\n");
close(bmp_fd);
return -1;
}
lseek(bmp_fd, 54, SEEK_SET);
read(bmp_fd, bmp_data, line_size * h);
close(bmp_fd);
// 转换为LCD像素格式
int *lcd_buf = malloc(w * h * 4);
if(lcd_buf == NULL) {
printf("malloc for lcd_buf failed\n");
free(bmp_data);
return -1;
}
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
unsigned char b = bmp_data[y * line_size + x * 3];
unsigned char g = bmp_data[y * line_size + x * 3 + 1];
unsigned char r = bmp_data[y * line_size + x * 3 + 2];
lcd_buf[y * w + x] = (0x00 << 24) | (r << 16) | (g << 8) | b;
}
}
free(bmp_data);
// 写入LCD帧缓冲区
lseek(lcd_fd, 0, SEEK_SET);
for (int y = 0; y < h; y++)
{
if ((y0 + y) >= 600) continue;
for (int x = 0; x < w; x++)
{
if ((x0 + x) >= 1024) continue;
lseek(lcd_fd, (1024 * (y0 + y) + (x0 + x)) * 4, SEEK_SET);
write(lcd_fd, &lcd_buf[(h - 1 - y) * w + x], 4);
}
}
free(lcd_buf);
lseek(lcd_fd, 0, SEEK_SET);
return 0;
}
这段代码展示了对BMP文件格式的深入理解,包括文件头解析、像素数据读取、颜色格式转换和显示位置计算等。特别注意的是,BMP图片像素数据是从下到上、从左到右存储的,而LCD显示是从上到下、从左到右,因此在显示时需要进行坐标转换。
3.2 基本图形绘制
除了显示BMP图片外,系统还实现了基本图形绘制功能,如点和矩形:
/**
* @brief 绘制一个实心矩形
*/
void draw_rect(int lcd_fd, int x0, int y0, int w, int h, int color)
{
int *rect_buf = malloc(w * h * 4);
if (!rect_buf) return;
for(int i = 0; i < w * h; i++) {
rect_buf[i] = color;
}
for (int y = 0; y < h; y++) {
if (y0 + y >= 600) break;
lseek(lcd_fd, (1024 * (y0 + y) + x0) * 4, SEEK_SET);
write(lcd_fd, &rect_buf[y * w], w * 4);
}
free(rect_buf);
}
// 绘制点函数
void draw_point(int lcd_fd, int x, int y, int d)
{
int color = 0x00FF0000; // 红色
for (int m = y - d; m <= y + d; m++)
{
for (int n = x - d; n < x + d; n++)
{
if (m >= 0 && m < 600 && n >= 0 && n < 1024)
{
lseek(lcd_fd, (1024 * m + n) * 4, SEEK_SET);
write(lcd_fd, &color, 4);
}
}
}
}
这些函数通过直接操作帧缓冲区,实现了高效的图形绘制。特别是在绘制矩形时,采用了一次性生成像素缓冲区,然后批量写入的方式,提高了绘制效率。
四、功能模块实现
4.1 写字板模块
写字板模块实现了一个简单的触摸绘图功能,用户可以在屏幕上用手指绘制图形:
void run_drawing_pad(int lcd_fd, int ts_fd){
printf("启动 [写字板] 功能...\n");
// 初始化白色背景
int white_buf[1024 * 600];
for (int i = 0; i < 1024 * 600; i++)
{
white_buf[i] = 0X00FFFFFF;
}
lseek(lcd_fd, 0, SEEK_SET);
write(lcd_fd, white_buf, sizeof(white_buf));
// 在右上角画一个退出按钮
int gray_color = 0x00808080;
for (int y = 0; y < 40; y++) {
for (int x = 984; x < 1024; x++) {
white_buf[1024 * y + x] = gray_color;
}
}
lseek(lcd_fd, 0, SEEK_SET);
write(lcd_fd, white_buf, sizeof(white_buf));
flush_ts_events(ts_fd);
// 处理触摸事件
struct input_event ts_buf;
int x = -1, y = -1;
int is_touching = 0;
while (1)
{
read(ts_fd, &ts_buf, sizeof(ts_buf));
switch(ts_buf.type)
{
case EV_ABS:
if (ts_buf.code == ABS_X) {
x = ts_buf.value;
} else if (ts_buf.code == ABS_Y) {
y = ts_buf.value;
}
break;
case EV_KEY:
if (ts_buf.code == BTN_TOUCH) {
if (ts_buf.value == 1) {
is_touching = 1;
} else if (ts_buf.value == 0) {
is_touching = 0;
// 检查是否点击了退出区域
if (x > 984 && y < 40) {
return;
}
}
}
break;
case EV_SYN:
if (ts_buf.code == SYN_REPORT) {
if (is_touching) {
if (x >= 0 && y >= 0) {
draw_point(lcd_fd, x, y, 5);
}
}
}
break;
}
}
}
这段代码展示了对Linux输入事件的精确处理,通过区分不同类型的事件(EV_ABS、EV_KEY、EV_SYN),实现了触摸绘图功能。特别是对SYN_REPORT事件的处理,确保了绘图的实时性和流畅性。
4.2 图片播放器模块
图片播放器模块实现了自动循环播放BMP图片的功能:
void run_slideshow(int lcd_fd, int ts_fd)
{
printf("启动 [循环切换图片] 功能...\n");
char bmp_path[5][15] = {"1.bmp", "dog.bmp", "small.bmp", "school1.bmp", "school2.bmp"};
int i = 0;
fd_set rdfs;
struct timeval tv;
flush_ts_events(ts_fd);
while (1)
{
show_bmp(bmp_path[i], 0, 0, lcd_fd);
i = (i + 1) % 5;
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&rdfs);
FD_SET(ts_fd, &rdfs);
int ret = select(ts_fd + 1, &rdfs, NULL, NULL, &tv);
if (ret > 0 && FD_ISSET(ts_fd, &rdfs))
{
printf("检测到触摸,退出图片循环。\n");
break;
}
}
}
这段代码展示了对select系统调用的灵活应用,实现了定时切换图片和触摸检测的双重功能。通过设置超时时间为1秒,系统每秒自动切换一次图片,同时监听触摸事件,一旦检测到触摸,立即退出播放模式。
4.3 音乐播放器模块
音乐播放器模块是系统中最复杂的部分,它通过调用外部程序mplayer实现MP3音乐播放,并通过命名管道(FIFO)实现与mplayer的通信:
void run_music_player(int lcd_fd, int ts_fd)
{
printf("启动 [音乐播放器] 功能...\n");
show_bmp("./music_pic.bmp", 0, 0, lcd_fd);
unlink("pipe");
if (mkfifo("pipe", 0666) == -1) {
perror("mkfifo failed");
return;
}
int music_vol = 100;
int x, y;
pid_t mplayer_pid = -1;
flush_ts_events(ts_fd);
while (1)
{
mplayer_pid = fork();
if (mplayer_pid == 0)
{
printf("子进程(mplayer)启动中, PID: %d\n", getpid());
execlp("mplayer", "mplayer", "-slave", "-quiet", "-input", "file=./pipe", "-ao", "alsa:device=hw=0.0", music_name[music_num], NULL);
perror("execlp mplayer failed");
exit(1);
}
else if (mplayer_pid > 0)
{
printf("父进程(控制)运行中, PID: %d, mplayer PID: %d\n", getpid(), mplayer_pid);
usleep(500000);
int pipe_fd = open("./pipe", O_WRONLY);
if (pipe_fd == -1)
{
perror("open pipe failed");
kill(mplayer_pid, SIGKILL);
break;
}
int should_exit_player = 0;
while (!should_exit_player)
{
get_xy(ts_fd, &x, &y);
printf("触摸坐标: (%d, %d)\n", x, y);
// 处理各种触摸控制
// 暂停/播放
if (x > 297 && x < 402 && y > 194 && y < 317)
{
printf("指令: 暂停/播放\n");
send_pcm(pipe_fd, "pause\n");
}
// 其他控制代码...
}
close(pipe_fd);
if (should_exit_player) break;
}
}
unlink("pipe");
}
这段代码展示了对进程控制、进程间通信和外部程序调用的深入理解。通过fork()创建子进程运行mplayer,通过命名管道发送控制命令,实现了对音乐播放的精确控制。
五、多进程管理
系统采用多进程架构,主进程负责菜单显示和功能切换,各功能模块在独立的子进程中运行。这种设计提高了系统的稳定性和模块化程度:
int main()
{
int lcd_fd = lcd_open();
if (lcd_fd < 0) return -1;
int ts_fd = ts_open();
if (ts_fd < 0) {
close(lcd_fd);
return -1;
}
while (1)
{
draw_main_menu(lcd_fd);
int x, y;
get_xy(ts_fd, &x, &y);
printf("主菜单触摸坐标: (%d, %d)\n", x, y);
pid_t pid = -1;
// 写字板功能
if (x > 112 && x < 412 && y > 100 && y < 280) {
pid = fork();
if (pid == 0) {
run_drawing_pad(lcd_fd, ts_fd);
exit(0);
}
}
// 图片播放器功能
else if (x > 612 && x < 912 && y > 100 && y < 280) {
pid = fork();
if (pid == 0) {
run_slideshow(lcd_fd, ts_fd);
exit(0);
}
}
// 音乐播放器功能
else if (x > 112 && x < 412 && y > 320 && y < 500) {
pid = fork();
if (pid == 0) {
run_music_player(lcd_fd, ts_fd);
exit(0);
}
}
// 退出程序
else if (x > 612 && x < 912 && y > 320 && y < 500) {
printf("程序退出。\n");
break;
}
// 等待子进程退出
if (pid > 0) {
wait(NULL);
printf("子进程已退出, 返回主菜单。\n");
} else if (pid == -1 && (x > 112 && x < 912)) {
perror("fork failed");
}
}
// 清屏并关闭设备
draw_rect(lcd_fd, 0, 0, 1024, 600, 0x00000000);
close(lcd_fd);
close(ts_fd);
return 0;
}
这段代码展示了对多进程编程的深入理解,通过fork()创建子进程,通过wait()等待子进程退出,实现了功能模块的独立运行和资源回收。
六、技术难点与解决方案
6.1 帧缓冲区操作优化
在嵌入式系统中,显示性能是一个关键指标。本项目通过以下方式优化了帧缓冲区操作:
- 批量写入:在绘制矩形等大面积图形时,先在内存中生成像素缓冲区,然后一次性写入帧缓冲区,减少了I/O操作次数。
- 局部更新:在更新显示内容时,只更新变化的部分,而不是整个屏幕,减少了数据传输量。
- 内存映射:虽然本项目使用了read/write系统调用操作帧缓冲区,但在实际产品中,可以使用mmap将帧缓冲区映射到进程地址空间,进一步提高性能。
6.2 触摸事件处理
触摸事件处理是交互系统的核心。本项目实现了完整的触摸事件处理流程:
- 事件解析:准确解析Linux输入事件,区分不同类型的事件(EV_ABS、EV_KEY、EV_SYN)。
- 坐标转换:将触摸屏坐标系转换为LCD显示坐标系。
- 事件缓冲区清空:在功能切换时,清空触摸事件缓冲区,避免残留事件影响新功能的使用。
- 非阻塞模式:在需要的场景下,使用非阻塞模式读取触摸事件,提高系统响应性。
6.3 进程间通信
在音乐播放器模块中,需要与外部程序mplayer进行通信。本项目使用命名管道(FIFO)实现了进程间通信:
- 创建命名管道:使用mkfifo创建命名管道。
- 发送控制命令:通过向命名管道写入文本命令,控制mplayer的行为。
- 资源清理:在退出时,使用unlink删除命名管道文件,避免资源泄漏。
这种方式简单高效,适合嵌入式系统的资源限制。
七、项目亮点
7.1 无依赖设计
本项目直接操作底层设备驱动,不依赖第三方图形库和多媒体库(除了mplayer),具有以下优势:
- 系统资源占用低:适合资源受限的嵌入式系统。
- 启动速度快:无需加载大型库,启动时间短。
- 定制性强:可以根据具体需求定制功能,不受第三方库的限制。
7.2 模块化架构
系统采用模块化设计,各功能模块在独立的子进程中运行,具有以下优势:
- 稳定性高:一个模块的崩溃不会影响其他模块和主程序。
- 可扩展性强:可以方便地添加新功能模块,而不影响现有功能。
- 开发效率高:不同模块可以由不同开发者并行开发,提高团队效率。
7.3 资源管理
系统注重资源管理,避免内存泄漏和资源浪费:
- 内存分配与释放:每次malloc后都有对应的free,避免内存泄漏。
- 文件描述符管理:及时关闭不再使用的文件描述符,避免资源耗尽。
- 进程管理:使用wait等待子进程退出,避免僵尸进程。
- 信号处理:在需要的场景下,使用kill发送信号终止子进程。
八、总结
本项目展示了在嵌入式Linux系统上开发多功能触控应用的完整过程,涵盖了设备驱动操作、图形显示、触摸控制、多进程管理等多个技术领域。通过直接操作底层设备驱动,实现了高效的图形显示和触摸控制;通过多进程架构,实现了功能模块的独立运行和稳定性保障;通过命名管道等IPC机制,实现了与外部程序的通信和控制。
这些技术在嵌入式系统开发中具有广泛的应用价值,可以用于工业控制、智能家居、医疗设备等多个领域。通过本项目的开发实践,不仅掌握了嵌入式Linux系统开发的核心技能,还积累了丰富的实战经验,为未来更复杂的嵌入式系统开发奠定了坚实基础。
作者信息
- 作者:吕珂
- 邮箱:amireux.00@qq.com
- 版本:v2.1
- 日期:2025-06-12
- 项目集合地址:https://lyuke.top/mypoject
- 个人简历地址:https://jianli.lyuke.top/
- 个人主页地址:https://lyuke.top/