【新手笔记】"伪"智能管家的设计
本次智能管家的设计使用到S5PV210开发板(属于MPU芯片),基于linux内核(采用GPL协议)的操作系统,使用C语言编写。
智能管家的主要的流程图:
设计代码流程主要思路:通过设计一个Flag和Mod传递参数控制开发板进行动作,用到多线程去开启一个控制摄像头操作的线程和一个控制触摸屏的线程。
其中触摸屏的函数是控制开发板进行其他动作的主要函数。
关键部分:
while(1){
if(Flag == 1) //音乐
{
bmp_show("./BMPphoto/yinyue.bmp");
printf("Music!\n");
music_play();
}
else if(Flag == 2) //相册
{
bmp_show("./BMPphoto/xiangce.bmp");
printf("Photo_album!\n");
Photo_album();
}
else if(Flag == 3) //视频
{
bmp_show("./BMPphoto/shipin.bmp");
printf("Video!\n");
video();
}
else if(Flag == 4) //摄像头
{
bmp_show("./BMPphoto/shexiangtou.bmp");
printf("Camera!\n");
linux_v4l2();
}
else if(Flag == 5) //语音
{
bmp_show("./BMPphoto/yuyin.bmp");
printf("Welconme To Use Voice!\n");
sleep(1);
voice();
}
else if(Flag == 6) //游戏
{
bmp_show("./BMPphoto/game.bmp");
printf("Game!\n");
Game_Play();
}
}
主函数主要是实现判断触摸屏传递过来的Flag参数进行对应动作的判断,以及根据Flag进行开发板的动作控制。此外主函数还实现线程的创建(触摸屏线程和摄像头线程)。
关键部分:
char * cmd = "madplay";
char music_name[100][50];
bzero( music_name, 5000);
music_cont = dir("./Music", ".mp3", music_name);
char music_cmd_name[music_cont][50];
int fd_mixer = open("/dev/mixer", O_RDWR);
if(fd_mixer == -1)
{
perror("open mixer error!\n");
return -1;
}
ret = ioctl( fd_mixer, MIXER_READ(SOUND_MIXER_OGAIN), &vol);
if(ret == -1)
{
perror("read SOUND_MIXER_OGAIN ERROR!\n");
return -1;
}
//拆分左右音道
left = vol & 0xFF;
right = (vol & 0xFF00) >>8;
while(1)
{
printf("Now Playing Music!\n");
sleep(2);
if(Flag == 11) //返回
{
system("killall madplay");
bmp_show("./BMPphoto/main.bmp");
Mod = 0;
Flag = 0;
break;
}
else if(Flag == 12) //开始播放
{
sprintf( music_cmd_name[0], "%s %s &", cmd, music_name[0]);
system(music_cmd_name[0]);
printf("Flag : %d\n",Flag);
Flag = 0;
}
else if(Flag == 18) //暂停播放
{
system("killall -SIGSTOP madplay");
Flag = 0;
}
else if(Flag == 19) //继续播放
{
system("killall -SIGCONT madplay");
Flag = 0;
}
else if(Flag == 13) //下一首
{
system("killall madplay");
if(x >= music_cont) x = 0;
sprintf( music_cmd_name[x], "%s %s &", cmd, music_name[x]);
system(music_cmd_name[x]);
x++;
Flag = 0;
}
else if(Flag == 14) //上一首
{
system("killall madplay");
if(x < 0) x = music_cont - 1;
sprintf( music_cmd_name[x], "%s %s &", cmd, music_name[x]);
system(music_cmd_name[x]);
x--;
Flag = 0;
}
else if(Flag == 15) //加大音量
{
left += 3;
right += 3;
if(left > 100 && right > 100)
{
left = right =100;
printf("Sound can't add more\n");
}
vol = left | (right<<8);
ioctl( fd_mixer, MIXER_WRITE(SOUND_MIXER_OGAIN), &vol);
Flag = 0;
}
else if(Flag == 16) //减小音量
{
left -= 3;
right -= 3;
if(left < 70 && right <70)
{
left = right =70;
printf("Sound can't reduce more\n");
}
vol = left | (right<<8);
ioctl( fd_mixer, MIXER_WRITE(SOUND_MIXER_OGAIN), &vol);
Flag = 0;
}
}
在音乐函数里,主要是实现歌曲的上下首切换和音量调节功能,其中在开发板中,需要移植madplay(用于播放音乐的执行文件),通过system命令调用madplay实现播放音乐的效果。
在播放过程中还调用了列举该文件下所有音乐的命令,通过sprintf函数拼接命令和歌曲名字来实现切换歌曲的效果。
对于音量的调控,是基于调节开发板下的SOUND_MIXER_OGAIN声音增益,控制左右声道合成出完整声道输出,实现音量控制的效果,由于小于70左右几乎听不到声音,故音量最小值控制在70左右。
关键部分:
while(1)
{
if(Flag == 21)
{
bmp_show("./BMPphoto/main.bmp");
Mod = 0;
Flag = 0;
break;
}
else if(Flag == 22)
{
tmp ++;
if(tmp >= bmp_cont)
{
tmp = 0;
}
lcd_draw_jpg( 0, 0, file_name[tmp]);
Flag = 0;
}
else if(Flag == 23)
{
tmp --;
if(tmp < 0)
{
tmp = bmp_cont - 1;
}
lcd_draw_jpg( 0, 0, file_name[tmp]);
Flag = 0;
}
else if(Flag == 24)
{
while(Flag == 24)
{
tmp = photonum;
lcd_draw_jpg( 0, 0, file_name[tmp]);
sleep(2);
if(photonum >= bmp_cont)
photonum = 0;
else
photonum ++;
}
}
else if(Flag == 25)
{
lcd_draw_jpg( 0, 0, file_name[tmp]);
Flag = 0;
}
}
相册主要是显示JPG格式的图片,而关于JPG格式图片的显示,本萌新暂时没有能力解析,借鉴于老师的功能函数。
//创建管道文件
if( -1 == access("/tmp/CuiHua" , F_OK) )
{
ret = mkfifo("/tmp/CuiHua", 0777);
if (-1 == ret )
{
perror("mkfifo error ");
return -1 ;
}
printf("mkfifo succeed !\n");
}
//打开管道文件
fd_fifo = open("/tmp/CuiHua", O_RDWR);
if ( -1 == fd_fifo)
{
perror("open CuiHua error ");
return -1 ;
}
视频的实现功能与音乐的功能相似,主要在于视频是采用管道,系统调用mplayer函数。
游戏主要是关于移植GBA模拟器的执行文件至开发板之中运行,通过系统调用VisualBoyAdvance去运行后缀的游戏,此外不加参数下GBA显示在LCD(800*480)的左上角一小块,加上参数“-3”后会扩展至全屏显示,通过插入键盘至开发板去游玩。
对于游戏,疑惑点在于返回至主界面时,系统调用killall VisualBoyAdvance不能在游戏这个函数体中实现,而是在触摸屏中在反馈退出游戏指令前时调用上述命令才能结束GBA模拟器。
关键代码:
int touch_xy(int *x, int *y)
{
int count = 0;
int ts_fd = open("/dev/event0", O_RDONLY);
if(ts_fd == -1)
{
perror("open ts error\n");
return -1;
}
struct input_event ts_event;
while(1)
{
read( ts_fd, &ts_event, sizeof(struct input_event));
if(EV_ABS == ts_event.type)
if(ABS_X == ts_event.code)
{
*x = ts_event.value;
count++;
}
else if(ABS_Y == ts_event.code)
{
*y = ts_event.value;
count++;
}
if(count == 2)
{
printf("%s: ( %d, %d)\n", __FUNCTION__ ,*x, *y );
count = 0;
break;
}
}
return 0;
}
先打开开发板中的触摸屏设备(event0)并判断是否打开成功,而输入设备的数据存储在input_event的结构体之中,读取结构的信息,对比定义中触摸屏的定义符EV_ABS,确定为触摸屏事件后,判断松开和按下的情况,ts_event.code的值先是先是x轴的,其次是显示y轴的,由此我们可以判断ABS_X和ABS_Y的值记录LCD触摸屏的输入值,从而实现触摸屏的xy坐标输出,为后续的各功能切换做准备。
关键代码:
lcd_init();
/* 初始化摄像头设备*/
linux_v4l2_device_init("/dev/video3");
/* 启动摄像头*/
linux_v4l2_start_capturing();
char * jpg_name = malloc(20) ;
char * Video = malloc(50);
char * tmp = "./Video/%d.jpg";
while(1)
{
//获取摄像头捕捉的画面
linux_v4l2_get_fream(&freambuf);
if(Flag == 40)
show_video_data(80, 0, freambuf.buf , freambuf.length);
else if(Flag == 42)
{
show_video_data(80, 0, freambuf.buf , freambuf.length);
photo_num ++ ;
bzero(jpg_name , 20);
sprintf( jpg_name,"./Video/%d.jpg", photo_num);
//打开一个新的文件
fd_pic_file = open(jpg_name, O_RDWR | O_CREAT | O_TRUNC , 0666);
if ( -1 == fd_pic_file)
{
perror("open pic error ");
}
//写入摄像头数据
ret = write ( fd_pic_file , freambuf.buf , freambuf.length);
printf(" Write:%d\n", ret );
//关闭文件(如果没关闭相当没保存)
close(fd_pic_file);
if ( photo_num >= 150 )
{
video_num ++ ;
bzero(Video_name , 20);
sprintf( Video_name,"./Video/%d.avi", video_num);
//printf("%d\n\n\n",video_num);
sprintf(Video,"ffmpeg %s%s -f image2 -i %s", " ",Video_name,tmp);
printf("%s\n\n\n",Video);
system(Video);
//system("ffmpeg -f image2 -i ./Video/%d.jpg ./Video/123.avi");
system("rm ./Video/*.jpg");
Flag = 0;
}
}
}
linux_v4l2_yuyv_quit();
由于在设计中考虑到摄像头的显示问题,在此设计多一个线程去控制摄像头的显示以及录像等功能。对于视频的原理在于拍摄多张照片后,通过移植ffmeg实现JPG照片转换为avi视频的功能。
关键代码:
//打开LCD设备
lcd_fd = open("/dev/fb0", O_RDWR);
if(lcd_fd == -1)
{
perror("open lcd device error!\n");
return -1;
}
//内存映射
unsigned int *lcd_fb_addr = mmap( NULL, lcd_size, PROT_READ|PROT_WRITE, MAP_SHARED, lcd_fd, 0);
if(*(lcd_fb_addr) == -1)
{
perror("mmap error!\n");
goto err;
}
//打开图片
bmp_fd = open( pathName, O_RDONLY);
if(bmp_fd == -1)
{
perror("open bmp error!\n");
return -1;
}
//读写图片位置偏移
ret = lseek( bmp_fd, 54, SEEK_SET);
if(ret == -1)
{
perror("lseek error!\n");
return -1;
}
//读取图片信息
char bmp_buf[bmp_size];
ret = read( bmp_fd, bmp_buf, bmp_size);
//printf("read : %d\n", ret);
//图片数据处理
int lcd_buf[480][800];
for(y=0; y<H; y++)
for(x=0; x<W; x++)
{
lcd_buf[479-y][x] = bmp_buf[ (y*800 + x)*3 + 0] << 0 |
bmp_buf[ (y*800 + x)*3 + 1] << 8 |
bmp_buf[ (y*800 + x)*3 + 2] << 16 ;
}
//写入图片到LCD
for(y=0; y<H; y++)
for(x=0; x<W; x++)
{
*(lcd_fb_addr + x + y*800) = lcd_buf [y][x] ;
}
//关闭图片
retval = munmap( lcd_fb_addr, lcd_size);
if(retval == -1)
{
perror("unmap fb error\n");
return -1;
}
retval = close(lcd_fd);
if(retval == -1)
{
perror("open lcd device error\n");
return -1;
}
return 0;
err:
close(lcd_fd);
return -1;
BMP格式的照片信息头54个字节是记录照片的各种信息,在本次设计中没有用到,可以用lseek跳过图片的头信息。而对于LCD屏幕(开发板中的fb0文件),实际上是ARGB四个参数,分别对应灰度和RGB三原色。而位深是24位的BMP格式只有RGB三原色,此外实际BMP照片存储的不是规则顺序RGB,而是BGR,故在LCD屏幕上显示BMP格式的照片,需要分别对RGB三原色进行位移对应,才能显示正确的照片颜色。
而采用内存映射mmap的关系,显存,一旦映射关系建立之后,LCD控制器就会自动从显存中读取像素数据传输 给LCD驱动器。这个显示的过程不需要CPU的参与,就可以减少CPU的工作负担。
1、摄像头在拍摄过程中容易出现卡顿,掉帧的现象,使得录像出来的功能画质不佳,猜测有a、线程开设过多,占用CPU过多内存,摄像头工作无法保持流畅,主要原因在于设计代码的不完善(主要原因);b、开发板的芯片型号有点旧。
2、语音功能极度不完善,能运用alsa库等进行录音和上传录音至Linux端,也能实现简单开发板控制动作,主要问题在于自我开发能力不够,不会将语音的返回的ID号整合于自己的代码之中,也就导致无法通过语音控制开发板实现相册,音乐等功能。
本次设计,对我这个萌新来说还是有很大收获的,遗憾是有些挺多功能不完善的,想实现的却技术能力不足,希望将来能继续努力吧,留下这篇文章去记录自己做过的“智能管家”。