前言
首先非常感谢大家来阅读我的文章,在这里特别感谢朱老师的嵌入式Linux课程让我学到了很多东西,我博客的许多文章也是由朱老师的课程笔记加上自己的一些理解总结而来的。本篇博客将和大家分享一个小项目——图片编解码播放器,其中会用到驱动、IO编程、kernel配置操作的一些知识等等,算是对前边所学知识的综合运用。如果大家之前浏览过我的文章,那么这篇包括之后的文章希望能让大家有所收获!
一、项目展示与整体规划
1、关于做项目不得不说的事儿
(1)真实项目(真正公司根据客户需求,某个特定方案进行制作,有硬件的配合)与实训项目(学完课程进行训练的项目)
(2)如何选择项目课题:噱头还是实质
(3)如何完美学习一个项目过程:正统流程(顺序学习)or解剖流程
2、本系列文章将如何演绎一个项目
(1)目的:从程序员角度出发,经历一个项目从无到有的过程,同时锻炼编程功底和发现并解决问题的能力,提升实战功力。
(2)做法:实训项目、选题常见、重实质、解剖流程办事
3、项目展示
1、该项目的最终形态
1.1、平台方面:本项目中会考虑平台可移植性,即可通过简单配置方便移植到各种开发板上(指不同大小尺寸的LCD,不同类型的触摸屏)
1.2、图片支持方面,会考虑支持以下图片格式:
* bmp 直接读取图片文件显示,如果图片大于LCD尺寸则使用插值缩放
* jpg libjpeg解码jpg->rgb888->rgb565
* png libpng解码
* gif libgif解码
1.3、操作支持方面,考虑做如下操作效果:
* 设定时间自动播放(通过串口操作,实现顺序播放、随机播放等)
* 点击屏幕两侧实现播放换页(上翻下翻)
* 划屏实现图片翻页(上翻下翻)
* 实现简单屏幕按钮控件,点击“上一页”、“下一页”等button进行换页显示
扩展功能:
* 在多点电容触摸屏上实现两指触摸放大缩小图片
* 图片播放同时增加背景音乐播放
2、项目执行计划
2.1、原则:项目采用分期逐步推进的方式执行,分数个版本来逐渐实现所有功能。
2.2、各版本初步规划
(1)版本1:
* 实现bmp、jpg、png、gif等图片的显示,移植各种解码库
* 实现播放列表组织,自动识别并显示各种格式的混杂图片库文件
* 实现自动定时切换顺序播放
(2)版本2:
* 添加触摸屏支持,实现侧边点击上下翻
3.通过读取文件内容来判断。
所有的图片文件都包括:文件识别头和图象数据两部分,其中文件识别头用来让计算机判断是哪种文件 格式。
JPEG
所有的JPEG文件以字符串“0xFFD8”开头,并以字符串“0xFFD9”结束。依此便可判别是否是JPEG文件。
BMP
BMP文件以字符串“0x4D42”开头
GIF
gif头六个是 GIF89a或 GIF87a
4、应该如何学做项目
(1)要动手练而不是只看
(2)学写程序的关键:聚沙成塔集腋成裘
(3)做项目的核心关注点:1、用代码实现自己的构想;2、解决过程中遇到的问题
二、环境搭建和基础确认
1、开发环境
(1)硬件:PC(主机Win10,虚拟机ubuntu16.04) + 开发板(Study210)
(2)软件:linux(直接基于linux API)
2、需要用到的基础环境
(1)开发板uboot(uboot可以在iNand中,也可以在外部SD卡中)
(2)移植好的内核(zImage在tftp服务器中,或者zImage直接fastboot方式烧录到iNand中)建议使用开发板厂商提供的移植好的源码包来自己修改、编译后得到zImage。
(3)自己制做的根文件系统rootfs
(4)主机ubuntu中配置好的tftp服务器
(5)主机ubuntu中配置好的nfs服务器
3、其他小细节
(1)代码编辑器:SourceInsight
(2)代码管理:Makefile,此外也可尝试使用git进行代码版本管理,若要学习,可自行百度 廖雪峰git
(3)调试流程:Windows共享文件夹编辑、虚拟机ubuntu中编译、make cp到nfs方式的rootfs中在开发板上运行
(4)开发板标准:以朱老师个人店的Study210(1024*600)为准
三、开始动手写代码
1、Makefile介绍
(1)这是一个通用的项目管理的Makefile体系,自己写的含有子文件夹组织的项目可以直接套用这套Makefile体系
(2)包含三类:顶层Makefile、Makefile.build、子文件夹下面的Makefile
#顶层的Makefile
#----------------------------------------------指定编译工具链-----------------------------------------------
CROSS_COMPILE = arm-linux-#指定编译器种类,若未将交叉编译链路径导出环境变量,则需使用绝对路径
AS = $(CROSS_COMPILE)as#
LD = $(CROSS_COMPILE)ld#链接工具
CC = $(CROSS_COMPILE)gcc#编译工具
CPP = $(CC) -E#
AR = $(CROSS_COMPILE)ar#打包工具
NM = $(CROSS_COMPILE)nm#
STRIP = $(CROSS_COMPILE)strip#优化工具
OBJCOPY = $(CROSS_COMPILE)objcopy#
OBJDUMP = $(CROSS_COMPILE)objdump#
export AS LD CC CPP AR NM#将定义的变量导出,方便其他目录的子Makefile使用
export STRIP OBJCOPY OBJDUMP#将定义的变量导出,方便其他目录的子Makefile使用
CFLAGS := -Wall -O2 -g#编译器参数
CFLAGS += -I $(shell pwd)/include#指定编译器头文件所在目录,使用<>进行包含(根据实际项目手动修改)
LDFLAGS := -lm -lfreetype -lvga#指定编译器链接库(根据实际项目手动修改)
export CFLAGS LDFLAGS#将定义的变量导出,方便其他makefile使用
TOPDIR := $(shell pwd)#获得当前程序的顶层目录
export TOPDIR #输出顶层目录
TARGET := image_player#编译后生成的可执行程序名(根据实际项目手动修改)
#-------------------------顶层要生成的.o文件以及顶层文件夹中要生成的.o文件(根据实际项目手动修改)------------------
obj-y += main.o#对应顶层目录下的main.c
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/
#--------------------------------------------顶层的第一个规则(默认规则)------------------------------------
all:
make -C ./ -f $(TOPDIR)/MAkefile.build
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
cp:
cp ../pictures_player_project /root/rootfs/ #复制到nfs方式启动的根文件系统所在的文件
#--------------------------顶层的清除规则-------------------
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
.PHONY:all clean
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
#Makefile.build文件
PHONY := __build
__build:
obj-y :=
subdir-y :=
include Makefile
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y)) : c/ d/
# __subdir-y : c d
# subdir-y : c d
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
# c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)
include $(dep_files)
endif
PHONY += $(subdir-y)
__build : $(subdir-y) built-in.o
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
dep_file = .$@.d
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY : $(PHONY)
#子Makefile文件示例
obj-y=fb.o
(3)可以参考学习:http://www.cnblogs.com/lidabo/p/4521123.html
Makefile.build是一个纯Makefile规则文件,不需要去修改,其可以放在Makefile中,也可以独立放出来。
obj-y表示编译进内核
obj-m表示编译成模块
2、SourceInsight建立工程开始代码的编写工作
四、framebuffer基本操作代码
1、使用之前framebuffer驱动文章中所写的应用层代码作为参考。
void draw_back(unsigned int width, unsigned int height, unsigned int color)
{
unsigned int x, y;
for (y=0; y<height; y++)
{
for (x=0; x<width; x++)
{
*(pfb + y * WIDTH + x) = color;
}
}
}
void draw_line(unsigned int color)
{
unsigned int x, y;
for (x=50; x<600; x++)
{
*(pfb + 200 * WIDTH + x) = color;
}
}
//不可变信息FSCREENINFO,使用ioctl的FBIOGET_FSCREENINFO名
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* Start of frame buffer mem */
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem */
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes */
unsigned long mmio_start; /* Start of Memory Mapped I/O */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 reserved[3]; /* Reserved for future compatibility */
};
smem_len = xres*yres*bpp/8
(2)可变信息VSCREENINFO,使用ioctl的FBIOGET_VSCREENINFO名
struct fb_var_screeninfo {
__u32 xres; /* visible resolution */
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible,偏移量 */
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what */
__u32 grayscale; /* != 0 Graylevels instead of colors,灰度等级 */
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */
__u32 nonstd; /* != 0 Non standard pixel format */
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* height of picture in mm */
__u32 width; /* width of picture in mm */
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 reserved[5]; /* Reserved for future compatibility */
};
基本操作代码:
//创建一个新文件夹:display,将fb.c放入该文件夹
//在子文件夹中添加子Makefile
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#define FBDEVICE "dev/fb0"
#define WIDTH 1024
#define HEIGHT 600
#define WHITE 0xffffffff
#define BLACK 0x00000000
//定义全局变量
unsigned int *pfb = NULL;//用于保存申请到的内存空间的首地址
void lcd_draw_background(unsigned int width, unsigned int height, unsigned int color);
void lcd_draw_line(unsigned int color);
int main(void)
{
int ret = -1, fd = -1;
struct fb_fix_screeninfo finfo = {0};//用于获取framebuffer不可变信息
struct fb_var_screeninfo vinfo = {0};//用于获取framebuffer可变信息
//第一步:打开设备
fd = open(FBDEVICE, O_RDWR);
if(fd < 0)
{
perror("open /dev/fb0");
return -1;
}
printf("open successsfully.\n");
//第二步:获取硬件的设备信息
ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);//获取不可变信息
if (ret < 0)
{
perror("ioctl");
return -1;
}
printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);
ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);//获取可变信息
if (ret < 0)
{
perror("ioctl");
return -1;
}
printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("bpp = %u.\n", vinfo.bits_per_pixel);
//第三步:使用mmap进行内存映射
//根据第二步获取到的设备信息,计算需要申请的内存大小
unsigned int len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel /8;
printf("len = %d.\n", len);
pfb= mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (NULL == pfb)
{
perror("mmap fail");
return -1;
}
printf("peb = %p.\n", pfb);
//第四步:填充framebuffer,使LCD显示画面
lcd_draw_background(WIDTH, HEIGHT, WHITE);
lcd_draw_line(BLACK);
//第五步:关闭设备文件
close(fd);
return 0;
}
void lcd_draw_background(unsigned int width, unsigned int height, unsigned int color)
{
unsigned int x, y;
for (y=0; y<height; y++)
{
for (x=0; x<width; x++)
{
*(pfb + y * WIDTH + x) = color;
}
}
}
void lcd_draw_line(unsigned int color)
{
int x, y;
for(y = 60; y < 260; y++)
{
for(x = 60; x < 100; x++)
{
*(pfb + y * WIDTH + x) = color;
}
}
}
上述程序实际执行效果:
五、图片显示原理和实践
1、图片显示原理
(1)概念1:像素
可以将像素视为整个图像中不可分割的单位或者是元素。不可分割的意思是它不能够再切割成更小单位抑或是元素,它是以一个单一颜色的小格存在 。每一个点阵图像包含了一定量的像素,这些像素决定图像在屏幕上所呈现的大小。
(2)概念2:点阵
由像素构成的矩阵,有时说点阵就是代指显示屏。
(3)分辨率(物理分辨率、显示分辨率)
物理分辨率:物理设备真实的分辨率。
显示分辨率:电脑软件可设置的分辨率,其小于等于物理分辨率,通过某些点不显示或者连续的点显示成相同颜色,即变粗的效果来实现。
常见的屏幕一般是4:3或16:9(长宽之比),7寸屏是指对角线的长度是七英寸。
(4)清晰度(分辨率和像素间距有关)像素间距(两个像素点之间的距离)相同时,分辨率越大越清晰;分辨率相同时,像素间距越小越清晰。
(4)bpp(RGB565:16位色、RGB888:24位色):即像素深度,每个像素用 多少bit数据表示,16位色使用16bpp,24位色使用32bpp(因为涉及到内存对齐,多余出来的那八位用ff或者00来表示)
(5)颜色序(RGB、BGR):描述颜色顺序,那几位是那种颜色,那几位是另一种颜色。
2、图片点阵数据获取方式
一幅图片的数据就是一个数组,数组中的元素排布顺序就是像素点的排布,若为24位色,0、1、2字节是一个像素点,3、4、5是另一个像素点
(1)Image2LCD软件提取
(2)通过图片/视频文件直接代码方式提取
注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。