目录
前言
说到色盲,大家肯定会想到各种奇奇怪怪的图片,比如下图:
可能你看来看去,也看不出来图片中有啥;可能你第一眼看过去就知道图片中有个图形,但它是三角形呢?还是圆形呢?还是方形呢?「可先在评论区留下你的答案」,等会在文末附上参考答案。
既然是嵌入式,当然少不了虚拟机了。我用的是VMare Workstation 16,使用的ubuntu 也是16。至于为什么都是16呢,当然与它的各项性能有关。下面正式进入正题。
设计目标
- 搭建基本的开发环境
- 实现基础的图片显示、切换
- 模拟现实触摸屏效果
- 计算测试结果,得出报告
搭建环境
测试测试,当然需要看到才能测试,所以先把测试环境给搭建起来。本次设计使用的屏幕是虚拟屏幕。至于为什么是虚拟屏幕,原因【¥】你懂我懂大家都懂。
1、虚拟机与 windows 挂载共享文件目录
最后可别忘了在底下点个「确定」。
2、搭建虚拟测试环境(屏幕)
进入共享文件夹:
进入触摸屏模拟器文件夹:
分别对 event_drv 和 mmap_drv 文件中的object文件 进行清除【对上次编译】和编译 ,以及安装驱动:
清楚指令:make clean
编译指令:make
驱动安装指令:sudo insmod xxx.ko
3、启动虚拟测试环境(屏幕)
就会看到如下图的lcd模拟器:
到此环境搭建、启动完成。接下来就交给程序猿了!
Code
有图才有测试,来先上图:
上图也没那么简单,不过也没有那么容易。
1、打开(bmp)图片(open函数)
- bmp图片是没有经过压缩的图格式
- bmp格式(24位图):文件信息头+图片信息头+像素点颜色码
- 注意:bmp图片需要严格使用宽度是4的倍数(字节对齐)
- 如果不是4的倍数,bmp图片储存时宽度会自动补齐为4的倍数
- 如果不处理这个问题,图片会倾斜
<linux 提供了两个open函数,选其一即可>
int open(const char *path, int oflag, ...);
int openat(int fd, const char *path, int oflag, ...);//选用前者
参数1:const char *path —— 所需要打开的文件的路径以及文件名
比如:/test/a.bmp (根目录下的a.bmp文件)
参数2: oflag -—— 文件的权限 —— 分为:可读、可写、可读可写等
返回值:
返回 -1 则说明文件不可被创建 or 修改。
代码如下:
int bmp_fd = open(bmp_name , O_RDWR);
if(bmp_fd == -1){
printf("open error %s\n ", bmp_name);
return -1;
}
2、读取(bmp)图片(read函数)
<与open函数一样,read函数同样也有两个>
ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset);
ssize_t read(int fildes, void *buf, size_t nbyte);//选用后者
参数1:fildes —— 所需要读取的文件描述符
参数2:void *buf —— 储存读取到的数据(地方)
参数3: size_t nbyte —— 储存多大字节的数据
代码如下:
//创建能储存bmp颜色数据的buf
unsigned char bmp_buf[800*480*3];
//每次使用前,先对buf的内容进行清空处理
bzero(bmp_buf ,sizeof(bmp_buf));
//读取数据到buf里面
read(bmp_fd , bmp_buf ,sizeof(bmp_buf));
3、显示(bmp)图片(mmap函数)
俗话说:“眼见为实,耳听为虚。”所以我们必须来点实际的,将图片在虚拟屏幕上展示出来。
1)打开 lcd 驱动
int lcd_fd = open("/dev/ubuntu_lcd" , O_RDWR); //驱动一般都在文件夹/dev/下
if(lcd_fd == -1)
{
printf("open lcd error!\n");
return -1;
}
2)内存映射mmap【*重点】
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
参数1:void *addr —— 映射内存的起始地址,一般写NULL,由内核自动分配
参数2:size_t length —— 映射内存的大小
参数3:int prot —— 从以下参数选择一个或者多个,多个使用 位或 | 操作
- PROT_EXEC 可执行权限.
- PROT_READ 可读权限.
- PROT_WRITE 可写权限.
- PROT_NONE 内存不可以操作.
参数4:int flags —— 使用MAP_SHARED,用于共享映射内存
参数5:int fd —— open函数打开的文件描述符
参数6:off_t offset —— 操作共享内存的偏移量,用于映射屏幕写0,不需要偏移返回值:成功 —— 返回映射内存的起始地址
失败 —— 返回MAP_FAILED (MAP_FAILED是(void *) -1地址),并写入出错码
unsigned int *mmap_fd = (unsigned int *)mmap(NULL , 800*480*4 , PROT_READ | PROT_WRITE , MAP_SHARED , lcd_fd , 0);
for(y=0 ;y<480 ;y++)
{
for(x=0 ; x<800 ;x++)
{
*(mmap_fd+x+y*800) = bmp_buf[x*3+(479-y)*800*3]<< 0 | bmp_buf[3*x+1+(479-y)*800*3]<<8 | bmp_buf[3*x+2+(479-y)*800*3]<<16 ; //分别对应着R B G 的位置
}
}
3)释放映射内存
int munmap(void *addr, size_t length);
参数1:void *addr —— 需要释放的内存映射地址(mmap返回的地址)
参数2:size_t length —— 需要释放内存的大小返回值:成功 —— 返回0
失败 —— 返回-1,并写入出错码
//原码如下:
munmap(mmap_fd , 800*480*4);
4)关闭文件描述符
//原码如下:
close(bmp_fd);
close(lcd_fd);
4、触摸屏
同样,触摸屏也是要打开的。
int ts_fd = open("/dev/ubuntu_event" , O_RDWR);
if(ts_fd == -1)
{
printf("open ubuntu_event error !\n");
return -1;
}
但这里的触摸屏是根据触摸事件来判断的。但由于没有真正实感,只能通过鼠标点击。即通过捕抓x,y坐标进行判断当前事件。
#include <linux/input.h>
struct input_event {
struct timeval time; // 事件发生的事件
__u16 type; // 事件的类型 用来区分不同的硬件设备 比如键盘/鼠标/触摸屏...
__u16 code; // 事件编码 比如键盘的某个按键
__s32 value; // 事件的值 比如某个按键时按下还是松开 / 状态
};
/*
* Event types
*/
#define EV_SYN 0x00 //事件的分割标志
#define EV_KEY 0x01 //按键事件
#define EV_REL 0x02 //相对位移事件 鼠标
#define EV_ABS 0x03 //绝对位移事件 触摸屏
/*
* Absolute axes
*/#define ABS_X 0x00 // x轴
#define ABS_Y 0x01 // y轴
注意:上面定义,都是linux的自带文件,路径: ../usr/include/linux/ input.h 或者 input-event-codes.h
我们不仅仅捕抓一次的(x,y),所以需要加入一个循环,将每一次点击的(x,y)都存起来。然后根据xy的范围值进行判断,判断当前用户的操作。
struct input_event data;
//将触摸屏的信息读取到 data
printf("the touch screen was activited!\n");
while(1)
{
read( ts_fd , &data , sizeof(struct input_event));
//判断是不是触摸屏事件
if(data.type == 3 && data.code == 0)
{
//x轴的值
*x = data.value;
printf("x = %d\n" , *x);
}else if(data.type == 3 && data.code == 1)
{
//y轴的值
*y = data.value;
printf("y = %d\n" , *y);
if(x>0){
break;
}
}
}
//关闭触摸屏
close(ts_fd);
5、主函数
//部分原码:
int main()
{
//初始化测试界面
show_color();//白背景
show_bmp("../picture/1.bmp" , 800 ,400 ,0 ,0);//题目一
show_bmp("../picture/20.bmp" , 140 ,30 , 330 ,415);//下一题
char auth_ans[3] = {'3', '3', '1'};//官方参考答案
char usr_ans[3] = {0};//用户测试答案
/*
i //用来指示答案图片
k //用来指示下一道题
j //用来指示选择后跳转到下一道题
b //用来指示用户测试得到的答案
a //用来指示用户的测试结果比较
*/
while(1){
new_ts(&x,&y);
if((280 < x && x < 330 ) && (360 < y && y <390)) //选择答案是第一个
{
show_bmp(picture_an[i + 0] , 800 ,400 ,0 ,0);
show_bmp(picture_next[j], 140 ,30 , 330 ,415);
usr_ans[b] = '1';
printf("==== %c\n",usr_ans[b]);
}
if((370 < x && x < 420 ) && (360 < y && y <390) )//选择答案是第二个
{
show_bmp(picture_an[i + 1] , 800 ,400 ,0 ,0);
show_bmp(picture_next[j], 140 ,30 , 330 ,415);
usr_ans[b] = '2';
printf("==== %c\n",usr_ans[b]);
}
if((460 < x && x < 500 ) && (360 < y && y <390)) //选择答案是第三个
{
show_bmp(picture_an[i + 2], 800 ,400 ,0 ,0);
show_bmp(picture_next[j], 140 ,30 , 330 ,415);
usr_ans[b] = '3';
printf("==== %c\n",usr_ans[b]);
}
if((330 < x && x < 470 ) && (415 < y && y < 445 ))
{
//切换到下一张图片测试
if(k < 3)
{
show_bmp( picture_topic[k], 800 ,400 ,0 ,0);
j++;
show_bmp(picture_next[j], 140 ,30 , 330 ,415);
}
k++;
i = i+3;
j++;
b++;
}
printf("k = %d\n",k);
if(k > 3 ){ //测试完成,打印结果
show_bmp("../picture/done.bmp" , 800 ,480 ,0 ,0);
printf("the test is done!!\n");
break;
}
}
return 0;
}
效果演示
结果演示
正常者
色弱者
色盲者
设计总结
本设计为很基础的嵌入式开发设计,所用的素材基本都是就地取材,如read、open等函数。设计中唯一较难理解的应该就是内存映射的那一部分了。因为一个像素点包含4个字节,分别对应A、R、B、G。一个字节对应者8位比特。所以需要将图片提取出来的像素值给它安排到对应的位置上比如G是最后提取的,即左移0位即可;而R是首先提取的,所以要将其左移16位。同时数据的先后问题,所以会出现显示图片倒置的现象,只需将其纵坐标的值对称交换即可。实现同样的效果的方法还有很多,比如HTML5,因为HTML5设计出来的色盲检测系统可能会更贴切实际感官。同时设计效果还可以多样化,比如给用户提示哪一道错误、增加返回上一道题目的功能等。enmm,大脑继续开发!
注意:本设计所有资源(lcd+源码)已上传,需要的自取
本设计中所用的资源,部分来源于网络,若侵权,联系删除。
写在最后,本文不代表任何人观点,若有不足之处,欢迎指正~