
简介基于ARM6818开发板的电子相册项目源码包面向嵌入式学习者与物联网开发入门者可帮助快速上手多媒体应用开发。项目覆盖LCD图片显示、文件检索与删除、滑动切图、触摸屏交互及背景音乐播放等典型功能适合用来巩固驱动移植、文件系统操作与GUI界面设计技能。压缩包共37个文件其中24个bmp图片素材、6个C源文件与5个头文件构成核心代码与显示资源另有1个mp3音频文件用于伴奏演示整体大小约23.59MB结构清晰便于对照学习。资源已吸引3400余人浏览学习。通过学习可获得一个可直接移植调试的完整项目框架包含Search_pic、show_bmp、get_slide、get_xy等模块化代码代码中涉及BMP位图加载、触摸屏中断映射及音频播放等底层机制对理解ARM平台下多媒体开发与嵌入式综合实战均有直接帮助。1. 为什么 ARM6818 会成为电子相册项目的主角一块 ARM6818 开发板默认烧好 Linux你把它点亮屏幕上只有星空。要做电子相册最直接的想法是把一张照片映射到 Framebuffer但滑动浏览、触摸翻页、显示中文文件名哪一步都不像看起来那么轻松。ARM6818 是三星 S5P6818 方案八核 Cortex-A53 和 Mali-400 GPU跑 QT 比想象中从容。这个项目真正练习的不是 C 语言的指针而是“Linux 图形栈、交叉编译、触摸校准”这一整条链路。我给自己的目标是拿到一块新板子两个小时把能触摸翻页的电子相册跑起来。2. 先从 ARM6818 的显示链路谈电子相册的选型如果直接把图片写到 /dev/fb0 的显存里这确实是显示照片但意味着你要手工处理 JPEG 解码、图片缩放、窗口遮挡。ARM6818 上跑的是 Linux有完整的图形栈做电子相册其实是在“用哪个图形方案”之间做选择。2.1 从处理器到屏幕ARM6818 的显示通路S5P6818 的 Display Controller 可以输出 RGB、LVDS 和 HDMI 信号开发板厂家一般已经在内核里把 LCD 驱动配好用户态能看到的就是/dev/fb0。你可以通过 fbset 查看当前屏幕参数这是电子相册项目里最常用来“定位到底显示没显示”的命令。ls -l /dev/fb* cat /sys/class/graphics/fb0/name fbset -fb /dev/fb0fbset 输出里的geometry行包含分辨率、位深和虚拟分辨率例如geometry 1280 800 1280 1600 32表示屏幕分辨率是 1280x800但虚拟显存高度有 1600说明驱动里可能开启了双缓冲。如果你的屏幕是 1920x1080而 fbset 显示的是 800x480那通常不是屏幕坏了而是 kernel cmdline 中video参数或设备树里 timing 没有配对。这一步不解决后面烧进任意程序都显示不全。常见开发板的屏幕接口和显示参数可以参考下表接口引脚数常见分辨率内核设备备注RGB88824行场同步800x480 / 1024x768/dev/fb0学习板最常见的裸屏LVDS4 或 8 对差分线1280x800 / 1920x1080/dev/fb0或 DRM需要 LVDS 转接板HDMI19 脚1080pDRM/KMS有热插拔适合演示2.2 为什么选 QT 而不是直接写 Framebuffer裸 FB 方案适合验证“怎么点亮液晶屏”但一旦需求变成相册就绕不开 JPEG/PNG 解码。A53 内核虽然带硬件浮点可你不会想在裸机上维护一个图片解码库还要自己处理触摸手势和字体渲染。Atmel 的 DirectFB 是个中间选择但资料太少调试成本不低。QT5 在 ARM6818 上一般有三种运行平台linuxfb、eglfs、xcb。xcb 在开发板上很少用eglfs 需要 Mali GPU 的 DRM 驱动非常完整很多厂家的 BSP 做不到这一点。所以最靠谱的反而是看起来“不够高级”的 linuxfb 插件它直接往 /dev/fb0 里画图还能用 tslib 接收触摸事件。我常和同事说电子相册这种固定界面LinuxFB 的性能完全够把 GPU 留给动画效果反而容易踩驱动 bug。选 QT5 的另一个理由是内存管理。一个空白窗体大约占用 80~120MB对板上 512MB 或 1GB 的内存来说不紧张。如果换成裸 FB 自己写缩略图缓存、字体位图、滑动缓存全要自己管理最后代码量远大于 QT 工程。2.3 电子相册的功能边界与文件系统规划电子相册做到什么程度我认为最小闭环是开机直接进相册、自动播放、触摸停住翻页、返回列表。功能边界不用一开始就加“图片批量导入”或“EXIF 信息”先把交互链路跑通后面都是加法。开发阶段的文件系统布局一般是rootfs 放在 Ubuntu 上开发板通过 NFS 挂载这个 rootfs这样交叉编译后的二进制直接可见不用反复烧 SD 卡。这是“开发板挂载 ubuntu”最常见的用法。mount -t nfs -o nolock,tcp 192.168.1.100:/opt/arm6818/rootfs /opt/arm6818/rootfs这里的/opt/arm6818/rootfs是 Ubuntu 上存放根文件系统的目录192.168.1.100换成开发板能访问到的主机 IP。nolock是为了避免 NFS 锁协商失败tcp比 UDP 更不容易出现文件截断。挂载成功后照片目录建议放在/usr/share/photoQT 运行库放/opt/qt5触摸校准文件放/etc/pointercal。3. 用交叉编译在 Ubuntu 上跑通第一个 ARM6818 电子相册程序现在进入实现。目标是生成一个显示单张图片的 QT 可执行文件并让它运行在 ARM6818 上。整个过程在 Ubuntu 上完成交叉编译器以 arm-linux-gnueabihf 为例QT 版本用开发板 SDK 里自带的 Qt5 即可。3.1 准备交叉工具链与 sysroot交叉编译工具链可以直接从 Ubuntu 安装sudo apt install gcc-arm-linux-gnueabihf g-arm-linux-gnueabihf export CROSSarm-linux-gnueabihf- export SYSROOT/opt/arm6818/rootfs export QT_ROOT$SYSROOT/opt/qt5sysroot 是一个独立目录里面放着从 ARM6818 根文件系统复制出来的/lib和/usr/lib。这样编译时连接器只去 sysroot 里找libQt5Core.so不会误用 Ubuntu 桌面版的 Qt 库。如果厂商 SDK 已经提供 sysroot就直接指向它如果没有就把开发板上的/usr/lib、/opt/qt5拷贝出来。3.2 最小 QT 工程从源码到 ARM 可执行文件工程用 CMake 组织交叉编译工具链文件如下set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g) set(CMAKE_SYSROOT /opt/arm6818/rootfs) set(CMAKE_PREFIX_PATH /opt/arm6818/rootfs/opt/qt5) set(CMAKE_FIND_ROOT_PATH /opt/arm6818/rootfs) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)说明CMAKE_SYSROOT告诉编译器头文件和库的根目录CMAKE_PREFIX_PATH指向 Qt5 安装位置否则find_package(Qt5)会到 Ubuntu 宿主路径里找CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER允许 cmake 使用宿主的工具程序而查找库和头文件只允许在 sysroot 内。工程文件CMakeLists.txtcmake_minimum_required(VERSION 3.5) project(photo_frame) set(CMAKE_AUTOMOC ON) find_package(Qt5 REQUIRED COMPONENTS Widgets) add_executable(photo_frame main.cpp) target_link_libraries(photo_frame Qt5::Widgets)main.cpp#include QApplication #include QLabel #include QPixmap int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label; label.setAlignment(Qt::AlignCenter); QString photoPath /usr/share/photo/sample.jpg; QPixmap pix(photoPath); if (pix.isNull()) label.setText(cant load image); else label.setPixmap(pix.scaled(label.size(), Qt::KeepAspectRatio)); label.showFullScreen(); return app.exec(); }编译命令mkdir build cd build cmake -DCMAKE_TOOLCHAIN_FILE../arm-linux-gnueabihf.cmake .. make file photo_framefile输出应该显示ELF 32-bit LSB executable, ARM。如果你的输出是x86-64说明 CMake 没有使用交叉工具链多半是 toolchain 文件路径写错或编译器名不对。QApplication 和 QLabel 在这里负责创建事件循环和窗口label.scaled(...)会自动把图片按比例缩放适应开发板屏幕。3.3 部署到开发板并验证显示开发板通过 NFS 挂载同一套 rootfs 时直接拷贝到 NFS 目录就能看到sudo cp photo_frame $SYSROOT/root/ sudo cp -r /opt/qt5lib/* $SYSROOT/opt/qt5/lib/ # 如果 rootfs 里还没有 Qt 库进入开发板串口终端后设置 Qt 运行环境export QTDIR/opt/qt5 export LD_LIBRARY_PATH$QTDIR/lib export QT_QPA_PLATFORMlinuxfb:fb/dev/fb0:rotation0 export QT_QPA_FB_TSLIB1 cd /root ./photo_frameQT_QPA_PLATFORM是 Qt5 最关键的变量linuxfb:fb/dev/fb0告诉 Qt 往哪个帧缓冲设备上绘制。如果屏幕方向不对把rotation改成 90/180/270如果触摸没反应QT_QPA_FB_TSLIB1会让 Qt 读取 tslib 校准数据。很多第一次运行崩溃的原因就是没指定这个变量Qt 默认尝试 EGLFS而eglfs在没有 Mali 驱动时直接报Failed to open /dev/dri/card0。提示运行时若输出 “Using device hook: libts”说明 tslib 已经在工作如果报找不到/dev/input/touchscreen先检查开发板的触摸节点名称。4. 把电子相册补全图片加载、触摸翻页与中文显示只有 sample.jpg 的程序严格来说不叫相册。实际需求是开机进入缩略图列表点开图片后可以左右滑动切换再回到列表。这一章补全这三个功能顺便解决中文文件名显示。4.1 图片资源放在哪SD 卡还是 rootfs存储位置优点缺点推荐场景rootfs ext4开机即用路径固定换照片要 ssh 或重烧开发阶段 / 固定演示SD 卡挂载拔卡换照片方便设备节点不固定要动态挂载给最终用户使用开发时我优先用 SD 卡因为从电脑拷照片方便。程序启动时同时扫描/mnt/sd和/usr/share/photo哪个存在用哪个。挂载命令mkdir -p /mnt/sd mount /dev/mmcblk0p1 /mnt/sdSD 卡有时会被识别为/dev/mmcblk1p1所以先用ls /dev/mmcblk*确认节点不要把节点写死。4.2 用 QListWidget 创建缩略图浏览器缩略图列表用 QListWidget 的 IconMode 实现代码很短QListWidget *list new QListWidget; list-setViewMode(QListView::IconMode); list-setIconSize(QSize(160, 120)); list-setResizeMode(QListView::Adjust); QDir dir(/mnt/sd); QStringList filters; filters *.jpg *.jpeg *.png; QFileInfoList files dir.entryInfoList(filters, QDir::Files, QDir::Name); for (const QFileInfo fi : files) { QIcon icon(fi.absoluteFilePath()); QListWidgetItem *item new QListWidgetItem(icon, fi.fileName(), list); item-setData(Qt::UserRole, fi.absoluteFilePath()); }这里的QDir::Name排序有一个小坑10.jpg会排在1.jpg前面。如果照片很多建议自己写一个自然排序比较器。Qt::UserRole用来存图片绝对路径后面点击事件里直接取回来不用再拼接字符串。4.3 全屏看图与触摸翻页手势处理点击缩略图后进入一个全屏 QWidget并在界面上重写鼠标事件来感应左右滑动。ARM6818 的触摸输入经过 tslib 后会被 Qt 转换成 QMouseEvent所以不需要处理 QTouchEvent。class FullScreenView : public QWidget { Q_OBJECT int m_startX 0; int m_index 0; QStringList m_paths; public: void showPhoto(int idx) { if (idx 0 || idx m_paths.size()) return; m_index idx; QString key QString::number(idx); QPixmap pix; if (!QPixmapCache::find(key, pix)) { pix QPixmap::fromImage(QImageReader(m_paths.at(idx)).read()); QPixmapCache::insert(key, pix); } // 显示到控件 } protected: void mousePressEvent(QMouseEvent *e) override { m_startX e-pos().x(); } void mouseReleaseEvent(QMouseEvent *e) override { int dx e-pos().x() - m_startX; if (dx -80) showPhoto(m_index 1); else if (dx 80) showPhoto(m_index - 1); } };滑动阈值 80px 是经验值。太小会出现“轻轻一碰就翻页”太大用户要费力滑动。电阻屏的采样点有时会跳变配合 tslib 校准之后这里的事件坐标才准确。4.4 中文文件名和中文乱码根治屏幕上出现方块或问号通常有两个原因字体缺失或文件系统编码不匹配。先解决字体mkdir -p /usr/share/fonts/tt cp wqy-microhei.ttc /usr/share/fonts/tt/在 Qt 启动时加载QFontDatabase::addApplicationFont(/usr/share/fonts/tt/wqy-microhei.ttc); QFont font(WenQuanYi Micro Hei, 12); app.setFont(font);然后用fc-list :langzh检查板子上有没有可用中文字体。如果文件系统是 ext4文件名默认 UTF-8Qt 也能正确解码。如果照片放在 SD 卡且卡被格式化成 vfat挂载时要带参数mount -t vfat -o iocharsetutf8,codepage936 /dev/mmcblk0p1 /mnt/sdiocharsetutf8表示把 vfat 里的文件名转成 UTF-8codepage936处理 FAT 的短文件名。这里的 936 是简体中文代码页和开发板 locale 无关只影响挂载层。5. 用 tslib 校准和预加载让 ARM6818 电子相册真正能摆上台面5.1 触摸不准先校准很多 ARM6818 开发板的触摸屏出厂没有校准点屏幕左上角鼠标位置偏到右上。这不是内核驱动坏了而是触摸屏的 ADC 坐标和屏幕空间的线性映射参数没生成。tslib 的ts_calibrate会生成/etc/pointercal文件Qt 的 linuxfb 插件会在QT_QPA_FB_TSLIB1时读取它。export TSLIB_TSDEVICE/dev/input/touchscreen export TSLIB_CALIBFILE/etc/pointercal export TSLIB_CONFFILE/etc/ts.conf ts_calibrate校准完成后可以先用ts_test画十字看偏移再启动 QT 程序。如果触摸方向反了需要在/etc/ts.conf里改x_swap、y_swap等参数而不是改 Qt 代码。校准文件生成后不用每次开机都跑把它留在 rootfs 里即可。5.2 翻页卡顿预加载比十种优化技巧都有效电子相册最影响体验的是翻页瞬间“白屏”因为 JPEG 解码在 ARM6818 上约需要 100~200ms。解决办法是用QtConcurrent::run在后台预解码前后两张图片并存入 QPixmapCacheQPixmapCache::setCacheLimit(30 * 1024); // 30MB对 512MB 内存安全 int next m_index 1; if (next m_paths.size()) { auto *fw new QFutureWatcherQImage(); fw-setFuture(QtConcurrent::run([]() { return QImageReader(m_paths.at(next)).read(); })); connect(fw, QFutureWatcherQImage::finished, this, []() { QPixmapCache::insert(next, QPixmap::fromImage(fw-result())); fw-deleteLater(); }); }QPixmapCache::setCacheLimit默认只有 10MB播放 1280x800 的 JPEG 大约只能存三四张调到 30MB 可以多缓存几张又不会把开发板内存吃满。QtConcurrent在 Qt5 里依赖Qt Concurrent模块记得在 CMake 中加入它。验证启动速度可以用time ./photo_frame如果启动耗时超过 1.5s先确认是否因为 NFS 网络延迟导致加载库变慢。把程序放到本机 eMMC 后再试通常能明显缩短。还想做得更顺滑可以用 QPropertyAnimation 平移当前图片而不是直接替换但这需要把两个 QPixmap 同时叠在同一控件上最终效果会更接近手机相册的翻页体验。本文还有配套的精品资源点击获取