花了两天时间做了这个小项目,原因是平时很少用到数据结构,写这个小项目的目的是用一下“双向链表”,可能由于粤嵌培训的原因大家的这个项目都是基于6818开发板的,但是两个开发板还是有很大的区别,所以今天我用6ULL的开发板去做一下这个项目,代码量也到达了500多行的代码, 我自己在写过程中也移植了大量的代码,所以没必要反复去敲代码,会用就行,当然这些移植的代码是我以前写过的,移植拿来用了一下。
项目用到的技术栈: 数据结构“双向链表”,多进程并发,linuxC编程,交叉编译,文件I/O,触摸屏移植等
项目链接:(开发板一样的话可以直接用)
https://pan.baidu.com/s/10tNmc4xT7IIXy5ncC8i9Pw?pwd=43yx提取码:43yx --来自百度网盘超级会员V1的分享
一.项目演示:
(放不了视频只能放个图了,可以滑动切换显示图片的,双向滑动进行双向的显示。)
二:项目流程:
第一步:lcd显示图片,并且获取图像文件信息
第二步:获取触摸屏坐标
第三步:双向链表移植运用
其实就这么简单的几步而已,但是里面还是有一些其他问题在里面的,接下来一步一步的叙述下去。
第一步:lcd显示图片,并且获取图像文件信息:
①、首先打开/dev/fb0 设备文件
②、使用 ioctl()函数获取到当前显示设备的参数信息,譬如屏幕的分辨率大小、像素格式,根据屏幕参数计算显示缓冲区的大小
获取到 LCD 屏幕的参数信息,譬如 LCD 的 X 轴分辨率、Y 轴分辨率以及像素格式等信息,通过这些参数计算出 LCD 显示缓冲区的大小
用法:
FBIOGET_VSCREENINFO:表示获取 FrameBuffer 设备的可变参数信息,可变参数信息使用 structfb_var_screeninfo 结构体来描述:
struct fb_var_screeninfo fb_var; ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
FBIOPUT_VSCREENINFO :表示设置 FrameBuffer 设备的可变参数信息
struct fb_var_screeninfo fb_var = {0}; /* 对 fb_var 进行数据填充 */ ...... ...... /* 设置可变参数信息 */ ioctl(fd, FBIOPUT_VSCREENINFO, &fb_var);
FBIOGET_FSCREENINFO :表示获取 FrameBuffer 设备的固定参数信息,既然是固定参数,那就意味着应用程序不可修改。
struct fb_fix_screeninfo fb_fix; ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
注意:
上 面 所 提 到 的 三 个 宏 定 义 FBIOGET_VSCREENINFO 、 FBIOPUT_VSCREENINFO 、FBIOGET_FSCREENINFO 以及 2 个数据结构 struct fb_var_screeninfo 和 struct fb_fix_screeninfo 都定义在 <linux/fd.h>头文件中
③、通过存储映射 I/O 方式将屏幕的显示缓冲区映射到用户空间(mmap)过 mmap()将显示器的显示缓冲区(显存)映射到进程的地址空间中,这样应用程序便可直接对显示缓冲区进行读写操作
为什么这里需要使用存储映射 I/O 这种方式呢?
其实使用普通的 I/O 方式(譬如直接 read、write)也是可以的,只是,当数据量比较大时,普通 I/O 方式效率较低,在这种情况下,数据量是比较庞大的,使用普通 I/O 方式必然导致效率低下,所以才会采用存储映射I/O 方式。
④、映射成功后就可以直接读写屏幕的显示缓冲区,进行绘图或图片显示等操作了
⑤、完成显示后,调用 munmap()取消映射、并调用 close()关闭设备文件
这就是LCD显示一张图片的全部原理,这里需要注意:
显示图片格式这里非常有讲究的:需要显示像素800*480,位置深度是16位, RGB565 格式的 BMP 图像格式,这里错一点显示出来都不对!!!!
如何设置这样的图片修改格式:
在 Windows 下我们转换得到的 BMP 位图通常是 24 位色的 RGB888 格式图像,所以我们找一张图片,图片格式无所谓,只要Photoshop软件能打开即可;确定图片之后,我们启动Photoshop软件,并且使用 Photoshop 软件打开这张图片,打开之后点击菜单栏中的文件--->存储为,然后选择自己需要的格式图片(像素800*480,位置深度是16位, RGB565 格式的 BMP 图像格式)
接下来我们来看显示一张图片,并获取图像信息的代码:
char str[100]={0};
/**** 静态全局变量 ****/
int width=0; //LCD X 分辨率
int height=0; //LCD Y 分辨率
unsigned short *screen_base = NULL; //映射后的显存基地址
unsigned long line_length=0; //LCD 一行的长度(字节为单位)
unsigned short *screen_base_flag = NULL; //映射后的显存基地址Flag
void print_bothway_list(struct List *list);
//功能描述: 在LCD上显示指定的BMP图片
static int show_bmp_image(char * str)
{
bmp_file_header file_h;
bmp_info_header info_h;
unsigned short *line_buf = NULL; //行缓冲区
unsigned long line_bytes; //BMP图像一行的字节的大小
unsigned int min_h, min_bytes;
int fd = -1;
int j;
/* 打开文件 */
if (0 > (fd = open(str, O_RDONLY))) {
perror("open error");
return -1;
}
/* 读取BMP文件头 */
if (sizeof(bmp_file_header) !=
read(fd, &file_h, sizeof(bmp_file_header))) {
perror("read error");
close(fd);
return -1;
}
if (0 != memcmp(file_h.type, "BM", 2)) {
fprintf(stderr, "it's not a BMP file\n");
close(fd);
return -1;
}
/* 读取位图信息头 */
if (sizeof(bmp_info_header) !=
read(fd, &info_h, sizeof(bmp_info_header))) {
perror("read error2");
close(fd);
return -1;
}
/* 打印信息 */
printf("文件大小: %d\n"
"位图数据的偏移量: %d\n"
"位图信息头大小: %d\n"
"图像分辨率: %d*%d\n"
"像素深度: %d\n", file_h.size, file_h.offset,
info_h.size, info_h.width, info_h.height,
info_h.bpp);
/* 将文件读写位置移动到图像数据开始处 */
if (-1 == lseek(fd,file_h.offset, SEEK_SET))
{
perror("lseek error");
close(fd);
return -1;
}
;
/* 申请一个buf、暂存bmp图像的一行数据 */
line_bytes = info_h.width * info_h.bpp / 8;
line_buf = malloc(line_bytes);
if (NULL == line_buf)
{
fprintf(stderr, "malloc error\n");
close(fd);
return -1;
}
if (line_length > line_bytes)
min_bytes = line_bytes;
else
min_bytes = line_length;
/**** 读取图像数据显示到LCD ****/
if (0 < info_h.height)
{//倒向位图
if (info_h.height > height)
{
min_h = height;
lseek(fd, (info_h.height - height) * line_bytes, SEEK_CUR);
screen_base += width * (height - 1); //定位到屏幕左下角位置
}
else
{
min_h = info_h.height;
screen_base += width * (info_h.height - 1); //定位到....不知怎么描述 懂的人自然懂!
}
for (j = min_h; j > 0; screen_base -= width, j--)
{
read(fd, line_buf, line_bytes); //读取出图像数据
memcpy(screen_base, line_buf, min_bytes);//刷入LCD显存
}
}
else
{ //正向位图
int temp = 0 - info_h.height; //负数转成正数
if (temp > height)
min_h = height;
else
min_h = temp;
for (j = 0; j < min_h; j++, screen_base += width)
{
read(fd, line_buf, line_bytes);
memcpy(screen_base, line_buf, min_bytes);
}
}
// width=0; //LCD X 分辨率
// height=0; //LCD Y 分辨率
// short *screen_base = NULL; //映射后的显存基地址
// long line_length=0; //LCD 一行的长度(字节为单位)
/* 关闭文件、函数返回 */
screen_base = NULL;
close(fd);
free(line_buf);
return 0;
}
void bmp_init(int m_signal )
{
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
unsigned int screen_size;
int fd;
/* 打开 framebuffer 设备 */
if (0 > (fd = open("/dev/fb0", O_RDWR))) {
perror("open erro1");
exit(EXIT_FAILURE);
}
/* 获取参数信息 */
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
screen_size = fb_fix.line_length * fb_var.yres;
line_length = fb_fix.line_length;
width = fb_var.xres;
height = fb_var.yres;
/* 将显示缓冲区映射到进程地址空间 */
screen_base = mmap(NULL, 4*screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
if (MAP_FAILED == (void *)screen_base)
{
perror("mmap error");
close(fd);
exit(EXIT_FAILURE);
}
//serve
screen_base_flag=screen_base;
/* 显示 BMP 图片 */
memset(screen_base, 0xFF, 4*screen_size);
struct List *List_Photo=create_bothway_list();//创建链表
print_bothway_list(List_Photo);
// show_bmp_image(m_signal);
/* 退出 */
munmap(screen_base, 4*screen_size); //取消映射
close(fd); //关闭文件
exit(EXIT_SUCCESS); //退出进程
}
第二步:获取触摸屏坐标
tslib 提供的 API 接口来编写触摸屏应用程序,使用 tslib 库函数需要在我们的应用
程序中包含 tslib 的头文件 tslib.h,使用 tslib 编程其实非常简单,基本步骤如下所示:第一步打开触摸屏设备:
#include "tslib.h" struct tsdev *ts_open(const char *dev_name, int nonblock);
参数 dev_name 指定了触摸屏的设备节点;参数 nonblock 表示是否以非阻塞方式打开触摸屏设备,如果nonblock 等于 0 表示阻塞方式,如果为非 0 值则表示以非阻塞方式打开。
第二步配置触摸屏设备:
#include "tslib.h" int ts_config(struct tsdev *ts)
调用 ts_config()函数进行配置
第三步读取触摸屏数据:
读取触摸屏数据使用 ts_read()或 ts_read_mt()函数,区别在于 ts_read 用于读取单点触摸数据,而
ts_read_mt 则用于读取多点触摸数据:#include "tslib.h" int ts_read(struct tsdev *ts, struct ts_sample *samp, int nr) int ts_read_mt(struct tsdev *ts, struct ts_sample_mt **samp, int max_slots, int nr)
参数 ts 指向一个触摸屏设备句柄,参数 nr 表示对一个触摸点的采样数,设置为 1 即可!
ts_read_mt()函数有一个 max_slots 参数,表示触摸屏支持的最大触摸点数,应用程序可以通过调用 ioctl()函数来获取触摸屏支持的最大触摸点数以及触摸屏坐标的最大分辨率等信息
ts_read()函数的 samp 参数是一个 struct ts_sample *类型的指针,指向一个 struct ts_sample 对象,structts_sample 数据结构描述了触摸点的信息;调用 ts_read()函数获取到的数据会存放在 samp 指针所指向的内存中。struct ts_sample 结构体内容如下所示:struct ts_sample { int x; //X 坐标 int y; //Y 坐标 unsigned int pressure; //按压力大小 struct timeval tv; //时间 };
ts_read_mt()函数的 samp 参数是一个 struct ts_sample_mt **类型的指针,多点触摸应用程序,每一个触摸点的信息使用 struct ts_sample_mt 数据结构来描述;
struct ts_sample_mt { int x; //X 坐标 int y; //Y 坐标 unsigned int pressure; //按压力大小 int slot; //触摸点 slot int tracking_id; //ID int tool_type; int tool_x; int tool_y; unsigned int touch_major; unsigned int width_major; unsigned int touch_minor; unsigned int width_minor; int orientation; int distance; int blob_id; struct timeval tv; //时间 /* BTN_TOUCH state */ short pen_down; //BTN_TOUCH 的状态 short valid; //此次样本是否有效标志 触摸点数据是否发生更新 };
提示:我的代码用的是多点触摸的运用,你们也可以单点触摸,这里还是看你自己的选择的
代码演示:(多点触摸配置:)
#include "ts_touch.h" struct input_absinfo slot; struct ts_mt *mt = NULL; int max_slots; int fd; int i; int Press_x=0; int Press_Y=0; static int ts_read(const int fd, const int max_slots, struct ts_mt *mt) { struct input_event in_ev; static int slot = 0;//用于保存上一个slot static struct tp_xy xy[12] = {0};//用于保存上一次的x和y坐标值,假设触摸屏支持的最大触摸点数不会超过12 int i; /* 对缓冲区初始化操作 */ memset(mt, 0x0, max_slots * sizeof(struct ts_mt)); //清零 for (i = 0; i < max_slots; i++) mt[i].id = -2;//将id初始化为-2, id=-1表示触摸点删除, id>=0表示创建 for ( ; ; ) { if (sizeof(struct input_event) != read(fd, &in_ev, sizeof(struct input_event))) { perror("read error"); return -1; } switch (in_ev.type) { case EV_ABS: switch (in_ev.code) { case ABS_MT_SLOT: slot = in_ev.value; break; case ABS_MT_POSITION_X: xy[slot].x = in_ev.value; mt[slot].valid = 1; break; case ABS_MT_POSITION_Y: xy[slot].y = in_ev.value; mt[slot].valid = 1; break; case ABS_MT_TRACKING_ID: mt[slot].id = in_ev.value; mt[slot].valid = 1; break; } break; //case EV_KEY://按键事件对单点触摸应用比较有用 // break; case EV_SYN: if (SYN_REPORT == in_ev.code) { for (i = 0; i < max_slots; i++) { mt[i].x = xy[i].x; mt[i].y = xy[i].y; } } return 0; } } } int touch_get() { /* 打开文件 */ fd = open("/dev/input/event1", O_RDONLY); if (0 > fd) { perror("open error"); exit(EXIT_FAILURE); } /* 获取触摸屏支持的最大触摸点数 */ if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &slot)) { perror("ioctl error"); close(fd); exit(EXIT_FAILURE); } max_slots = slot.maximum + 1 - slot.minimum; /* 申请内存空间并清零 */ mt = calloc(max_slots, sizeof(struct ts_mt)); /* 读数据 */ for ( ; ; ) { if (0 > ts_read(fd, max_slots, mt)) break; for (i = 0; i < max_slots; i++) { if (mt[i].valid) {//判断每一个触摸点信息是否发生更新(关注的信息发生更新) if (0 <= mt[i].id) { printf("slot<%d>, 按下(%d, %d)\n", i, mt[i].x, mt[i].y); Press_x=mt[i].x; Press_Y=mt[i].y; } else if (-1 == mt[i].id) { printf("slot<%d>, 松开(%d, %d)\n", i, mt[i].x, mt[i].y); if(mt[i].x>Press_x) { return 1; } if(mt[i].x<Press_x) { return 0; } } else printf("slot<%d>, 移动(%d, %d)\n", i, mt[i].x, mt[i].y); } } } /* 关闭设备、退出 */ close(fd); free(mt); exit(EXIT_FAILURE); }
第三步:双向链表移植运用
这里最主要的因为我平时也没用过什么数据结构,这次用了一下,本来的这个双向链表是用来保存数据的,现在我把用来保存字符串了,因为一张图对应一个路径,就用来保存路径了。
#include "BothwayLinkedListWithHead.h"
#include <stdio.h>
#include <stdlib.h>
char Photo_Path[4][100]={"./photo/1.bmp","./photo/2.bmp","./photo/3.bmp","./photo/4.bmp"};
/*
根据用户输入数据的顺序,创建一个带头节点的双向链表,
将新链表返回,并打印输出
*/
struct List * create_bothway_list()
{
//1.创建一个头节点,并初始化
struct List *list = malloc(sizeof(struct List));
list->first = NULL;
list->last = NULL;
list->num = 0;
int i=0;
while(1)
{
//3.创建数据节点 并初始化
struct node *pnew = malloc(sizeof(struct node));
pnew->path = Photo_Path[i];
pnew->next = NULL;
pnew->prev = NULL;
//4.把新节点 加入到链表中
if( list->first == NULL ) //从无到有
{
list->first = pnew;
list->last = pnew;
}
else //从少到多
{
//尾插法
list->last->next = pnew;
pnew->prev = list->last;
list->last = pnew;
//头插法
//pnew->next = list->first;
//list->first->prev = pnew;
//list->first = pnew;
}
list->num ++ ;
i++;
if(i==4)
{
break;//over
}
}
//5.返回新创建的链表的头节点
return list;
}
注意这里我有四个图片所以判断到了4就停止了。
最终主程序代码:
这里就简简单单的用了一下父子进程吧,本来两个进程可以用管道,还有信号量进行通讯的,我用了是可以实现的,但是多此一举了,所以就没去用了,你们可以去用一下,这里我就不想去把代码复杂化了
int main(int argc,char **argv)
{
pid_t pid= fork();
if(pid<0)
{
perror("fork()");
}
else if(pid==0)//child
{
bmp_init(m_signal);
}
else //partent
{
touch_get();//touch
}
exit(0);
}
总结:
项目一共花了我两天的时间,项目可以继续的升华下去,但是这个项目对于我来说结果达到了,数据结构使用了就行,网上还是有人去把项目神升华的,加入了音乐,播放器啥的,这里我就不去演示了,也是一些文件的调用罢了,想要把这个项目写在简历里的话我建议还是要去多做点东西的,进程间的通信都可以加在吗里面,交给你们了,祝大家在嵌入式的路越走越顺!