1. 项目背景与开发板介绍
第一次拿到粤嵌GEC6818开发板时,我对着这块巴掌大的板子研究了半天。这块开发板虽然体积小,但功能相当强大,搭载了四核Cortex-A53处理器,主频能达到1.3GHz,完全能满足电子相册这种嵌入式应用的需求。开发板标配的7寸电容触摸屏分辨率是800×480,显示效果在这个尺寸下已经相当不错了。
选择这款开发板有几个原因:首先是性价比高,作为教学和入门开发都很合适;其次是资料齐全,粤嵌提供了完整的技术文档和示例代码;最重要的是它运行的是Linux系统,这对我们这些习惯Linux开发的程序员来说太友好了。我记得当时在淘宝上看到的价格是300多块钱,比树莓派便宜不少,但性能完全够用。
2. 开发环境搭建
在开始编码前,得先把开发环境搭好。我用的是一台Ubuntu 20.04的笔记本作为开发主机。首先安装交叉编译工具链:
sudo apt-get install gcc-arm-linux-gnueabihf这个工具链会把我们的代码编译成能在ARM架构上运行的程序。然后需要配置NFS共享,这样在开发板上就能直接访问主机上的文件,省去了反复烧录的麻烦。在/etc/exports文件里添加:
/home/yourname/workspace *(rw,sync,no_root_squash,no_subtree_check)接着重启NFS服务:
sudo service nfs-kernel-server restart开发板那边也需要配置,修改/etc/fstab添加挂载点:
192.168.1.100:/home/yourname/workspace /mnt nfs defaults 0 0这样每次开发板启动就会自动挂载我们的工作目录。我还建议安装minicom或者picocom作为串口终端工具,调试的时候特别有用。
3. 电子相册的核心功能实现
3.1 图片显示原理
电子相册最核心的功能当然是显示图片。GEC6818开发板的显示屏对应着/dev/fb0这个设备文件。显示图片的基本思路是:先把图片数据读入内存,然后通过内存映射的方式把数据写入framebuffer。
这里用到了mmap系统调用,它能把设备文件映射到进程地址空间。具体实现如下:
int lcd_fd = open("/dev/fb0", O_RDWR); char *fbp = (char *)mmap(NULL, 800*480*4, PROT_READ|PROT_WRITE, MAP_SHARED, lcd_fd, 0);800×480是屏幕分辨率,4表示每个像素用4字节表示(ARGB格式)。映射成功后,直接往fbp指向的内存写数据就能在屏幕上显示出来。
3.2 BMP图片处理
我选择支持BMP格式是因为它结构简单,不需要额外的库就能解析。BMP文件头包含了很多信息,最重要的是图片的宽度和高度。通过hexdump查看BMP文件结构:
hexdump -C test.bmp | head -n 5在代码中,我们这样读取图片信息:
lseek(bmp_fd, 18, SEEK_SET); read(bmp_fd, &width, 4); read(bmp_fd, &height, 4);需要注意的是BMP的像素数据是从下往上存储的,而framebuffer是从上往下,所以显示时需要做垂直翻转。另外BMP的像素顺序是BGR,而framebuffer通常是ARGB,需要进行转换。
3.3 触摸屏交互
开发板的触摸屏对应/dev/input/event0设备文件。触摸事件通过input子系统上报,我们需要读取struct input_event结构体:
struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value; };处理触摸事件的关键是识别点击和滑动。我设置了一个150像素的阈值来判断是否是有效滑动:
if((ts_x > old_x) && (ts_x - old_x > 150)) { // 从左向右滑动 show_next_image(); }对于点击事件,我划分了屏幕右侧200像素宽的区域作为控制区,分别对应上一张、下一张、自动播放和退出功能。
4. 多线程设计与优化
4.1 线程分工
为了让界面响应更流畅,我使用了三个线程:
- 主线程:负责初始化和触摸事件监听
- 显示线程:处理图片切换和显示
- 自动播放线程:控制幻灯片自动播放
使用pthread_create创建线程:
pthread_t display_thread; pthread_create(&display_thread, NULL, display_func, NULL);4.2 线程同步
多个线程访问共享资源时需要同步。我使用了互斥锁和条件变量:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER;当触摸事件发生时,主线程通过条件变量通知显示线程:
pthread_mutex_lock(&mutex); flag = 1; pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex);显示线程则等待条件变量:
pthread_mutex_lock(&mutex); while(flag == 0) { pthread_cond_wait(&cond, &mutex); } // 处理事件 pthread_mutex_unlock(&mutex);4.3 性能优化
在实际测试中,我发现频繁的内存分配和释放会影响性能。于是改用了内存池技术,预先分配好所需内存:
#define BUF_SIZE (800*480*4) static char display_buf[BUF_SIZE];图片加载也做了优化,提前把解码后的图片数据保存在内存中,切换时直接拷贝,避免了重复解码。
5. 功能扩展与改进
5.1 支持更多图片格式
虽然BMP简单易用,但文件体积太大。我后来增加了对JPEG的支持,使用开源的libjpeg库进行解码。解码流程大致如下:
struct jpeg_decompress_struct cinfo; jpeg_create_decompress(&cinfo); jpeg_stdio_src(&cinfo, infile); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo);5.2 添加过渡动画
简单的图片切换显得太生硬,我实现了淡入淡出效果。原理是通过alpha混合在两帧之间插入过渡帧:
for(int i=0; i<256; i+=16) { blend_images(old_img, new_img, i); usleep(10000); }5.3 增加图片信息显示
在照片底部添加了一个半透明的信息栏,显示文件名和拍摄时间(如果EXIF信息可用)。这里用到了FreeType库来渲染文字:
FT_Init_FreeType(&library); FT_New_Face(library, font_path, 0, &face); FT_Set_Char_Size(face, 0, 16*64, 300, 300);6. 常见问题与调试技巧
在开发过程中遇到了不少坑。比如刚开始触摸屏坐标不准确,后来发现需要做坐标转换:
ts_x = ts_x * 800 / 1024; ts_y = ts_y * 480 / 600;还有一次图片显示出现花屏,排查后发现是BMP文件头解析错误,忘记考虑字节序问题。解决方法是在读取时加上__attribute__((packed)):
struct __attribute__((packed)) bmp_header { // 结构体定义 };调试时我经常用printf输出关键变量值,后来改用syslog,这样在系统日志里也能看到调试信息:
openlog("photo_frame", LOG_PID, LOG_USER); syslog(LOG_DEBUG, "x=%d, y=%d", x, y);7. 项目部署与优化
最后一步是把程序部署到开发板上。我制作了一个简单的启动脚本:
#!/bin/sh cd /mnt ./photo_frame &然后修改/etc/rc.local在启动时自动运行。为了节省资源,我去掉了调试输出,并优化了内存使用。实测下来,程序运行时的内存占用不到20MB,非常轻量。