当前位置:   article > 正文

基于RK3399&ESP8285自动售货柜项目—③最终篇-RK3399端实现详解_rk3399 开源项目

rk3399 开源项目

基于RK3399&ESP8285自动售货柜项目—③最终篇-RK3399端实现详解

本系列文章将详细讲解该基于RK3399及ESP8285自动售货柜的完整实现方法,从硬件连接到网络通信再到软件实现,本产品所用开发板为RK3399以及MP08_2019/11/03 , 如有疑问与见解,可随时留言或评论
本项目所有资源文件:
https://pan.baidu.com/s/1L3gFmzxl-PafaeSkAFi58Q?pwd=srer

本系列以往文章列表:
基于RK3399&ESP8285自动售货柜项目—ESP8266(8285)程序编写与烧录
基于RK3399&ESP8285自动售货柜项目—MP08开发板端代码详解

前两篇文章中,我们学习了如何搭建ESP_SDK_V3.x开发环境、ESP代码框架以及本项目MP08开发板端的代码实现,那么本项目就剩最后一大块—售货机与用户的交互

我们知道,一个售货机,如果它没有手机端app或者小程序的交互,那么它必定有一个屏幕放在售货机上,用户直接可以通过这块屏幕来操作、支付,从而获得商品,那么我们现在也来做一块这样的交互屏幕

交互界面示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qUUNypTB-1670421204672)(D:\AiThinkerIDE_V1.5.2\文章\product3.jpg)]

一、RK3399开发板端需求分析

我们在做交互端前,要写出基本的需求,这样才能更好的、更加高效率的完成我们所需要的功能并且保证代码的稳定性

我这里列出几项基本需求,能保证大部分售货机都能够用上的基本需求:

  • 视频广告播放:在空闲时可以循环播放预先存入的mp4格式广告
  • 触摸屏获取触点坐标:用于用户触摸操作
  • 图片显示:能够显示jpg、bmp、png等常见格式图片
  • 摄像头捕获:可以用于人脸支付
  • TCP连接MP08:用于RK3399控制MP08开发板出货

那么有了这些基本需求,我们就可以分模块进行开发

二、开源库、软件源码包移植

在实现上述几个需求之前,我们需要事先移植几个开源库以及软件源码包,以支持rk3399开发板实现我们需要的功能

  • zlib-1.2.3.tar.gz—通用的内存空间的压缩库。
  • libpng-1.2.57.tar.gz—png格式图片的压缩或解压库
  • Jpegsrc.v9b.tar.gz—jpeg格式图片的压缩或解压库
  • alsa-lib-1.1.0.tar.bz2—alsa音频库
  • fftw-3.3.4.tar.gz—傅里叶变换库
  • MPlayer-1.3.0.tar.gz—Mplayer音视频播放源码包

2.1 移植前准备

1、创建一个工作目录
mkdir ~/work/rk3399  
2、创建源码目录:各个库的源码文件都存放在这个路径下
mkdir ~/work/rk3399/mplayer_source
3、创建编译目录:各个库编译的时候都在这个目录下进行
mkdir ~/work/rk3399/mplayer_build
4、创建编译安装的目录
mkdir ~/work/rk3399/mplayer_install
5、进入到工作目录
cd ~/work/rk3399
6、把相关的源码包复制到 mplayer_source 的文件夹中
cp /mnt/hgfs/mplayer源码包 ./mplayer_source/  
8、导出安装路径为全局路径:MPLAY_INSTALL是变量名,要记住,后面移植库需要使用到。
export MPLAY_INSTALL=$HOME/work/rk3399/mplayer_install
9、导出安装路径为全局路径:MPLAY_SRC是变量名,要记住,后面移植库需要使用到。
export MPLAY_SRC=$HOME/work/rk3399/mplayer_source
 10、导出安装路径为全局路径:MPLAY_BUILD是变量名,要记住,后面移植库需要使用到。
export MPLAY_BUILD=$HOME/work/rk3399/mplayer_build
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

注意:这一步导出的全局路径,只在当前命令终端有效,如果新开了一个终端必须重新导出这个路径

2.2 zlib-1.2.3.tar.gz源码包移植

1、解压源码
tar -xf zlib-1.2.3.tar.gz -C ./mplayer_build/
3、进入到解压后目录,
cd ./mplayer_build/zlib-1.2.3/ 
4、配置:注意以下不是单行命令,需要一次性复制贴到命令终端上执行
CC=aarch64-linux-gnu-gcc  \
./configure --prefix=/home/huzhiyuan/work/xiangmu/mplayer_install  \
 --libdir=/home/huzhiyuan/work/xiangmu/mplayer_install/lib --includedir=$MPLAY_INSTALL/include \
 --shared
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WBRaiW2W-1670421204673)(D:\AiThinkerIDE_V1.5.2\文章\image-20221203212122926.png)]

5、编译源码包,安装到指定目录下。
make -j8  &&  make  install -j8 
  • 1
  • 2

2.3 libpng源码包移植

1、解压源码
$ tar -xf $MPLAY_SRC/libpng-1.2.57.tar.gz -C $MPLAY_BUILD
2、进入到解压后目录,
$ cd  $MPLAY_BUILD/libpng-1.2.57/ 
3、配置:注意以下不是单行命令,需要一次性复制贴到命令终端上执行
$ ./configure --prefix=$MPLAY_INSTALL  \
CC=aarch64-linux-gnu-gcc  --host=aarch64-linux-gnu  \
--enable-shared  --enable-static  \
CPPFLAGS=-I/$MPLAY_INSTALL/install/include/  \
LDFLAGS=-L/$MPLAY_INSTALL/lib/   \
LIBS=-lz
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trEo8Vdo-1670421204675)(D:\AiThinkerIDE_V1.5.2\文章\image-20221203212411615.png)]

4、编译源码包,安装到指定目录下。
$ make -j8  &&  make  install -j8
  • 1
  • 2

如果提示没有zlib.h文件,解决办法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dJsk2Qmp-1670421204675)(D:\AiThinkerIDE_V1.5.2\文章\image-20221203212617261.png)]

然后再编译源码包即可

2.4 libjpeg源码包移植

1、解压源码
$ tar -xf $MPLAY_SRC/jpegsrc.v9b.tar.gz -C $MPLAY_BUILD
2、进入到解压后目录,
$ cd  $MPLAY_BUILD/jpeg-9b  
3、配置:注意以下不是单行命令,需要一次性复制贴到命令终端上执行
$ ./configure --prefix=$MPLAY_INSTALL  \
CC=aarch64-linux-gnu-gcc  --host=aarch64-linux-gnu  \
--enable-shared  --enable-static
4、编译源码包,安装到指定目录下。
$ make -j8  &&  make  install -j8 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.5 fftw源码包移植

1、解压源码
$ tar -xf $MPLAY_SRC/fftw-3.3.4.tar.gz -C $MPLAY_BUILD
2、进入到解压后目录,
$ cd  $MPLAY_BUILD/fftw-3.3.4/
3、配置:注意以下不是单行命令,需要一次性复制贴到命令终端上执行
$ ./configure --prefix=$MPLAY_INSTALL  \
CC=aarch64-linux-gnu-gcc  --host=aarch64-linux-gnu  \
--enable-shared  --enable-static
4、编译源码包,安装到指定目录下。
$ make -j8  &&  make  install -j8 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.6 alsa-lib源码包移植

1、解压源码
$ tar -xf $MPLAY_SRC/alsa-lib-1.1.0.tar.bz2 -C $MPLAY_BUILD
2、进入到解压后目录,
$ cd  $MPLAY_BUILD/alsa-lib-1.1.0
3、配置:注意以下不是单行命令,需要一次性复制贴到命令终端上执行
$ ./configure --prefix=$MPLAY_INSTALL  \
CC=aarch64-linux-gnu-gcc  --host=aarch64-linux-gnu  \
--disable-python
4、编译源码包,安装到指定目录下。
$ make -j8  &&  make  install -j8 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.7 mplayer源码包移植

1、解压源码
$ tar -xf $MPLAY_SRC/MPlayer-1.3.0.tar.gz -C $MPLAY_BUILD
2、进入到解压后目录,
$ cd  $MPLAY_BUILD/MPlayer-1.3.0/
3、配置:注意以下不是单行命令,需要一次性复制贴到命令终端上执行
$ ./configure --prefix=/home/huzhiyuan/work/xiangmu/mplayer_install  \
 --cc=aarch64-linux-gnu-gcc  --host-cc=gcc \
 --target=aarch64-linux-gnu \
 --enable-cross-compile --enable-fbdev \
 --enable-png --enable-jpeg --enable-alsa --enable-ossaudio \
 --disable-x264-lavc --disable-freetype --disable-fontconfig \
 --extra-cflags="-I/home/huzhiyuan/work/include/  -DHAVE_ARMV8=0" \
 --extra-ldflags="-L/home/huzhiyuan/work/lib/" \
 --extra-libs="-lasound -ljpeg -lpng"  2>&1 |tee logfile
4、修改config.mak文件,删除第33行的 ‘ -s#解决安装时候的错误:修改config.mak ,删除 config.mak 中的  -s
$ sed -i 's/INSTALLSTRIP = -s/INSTALLSTRIP = /g' config.mak
5、编译源码包,安装到指定目录下。
$ make -j8  &&  make  install -j8 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2.8 打包文件发送到rk3399开发板

1、创建打包文件目录
mkdir mplayer-count/lib  mplayer-count/bin 
2、复制生成动态库文件和mplayer可执行文件到打包目录中
cp  ./mplayer_install/lib/ lib* so*  ./mplayer-count/lib
cp  ./mplayer_install/bin/        ./mplayer-count/bin
3、打包生成文件
tar cjf  mplayer-count.bz2  mplayer-count
4、查看压缩包的大小
du -hs mplayer-count.bz2
5、 把打包好的文件mplayer-count.bz2 复制开发板中 sftp
6、 在开发板上,进入工作目录并解压对应压缩包
tar -xf mplayer-count.bz2
7、 将解压后的文件进行处理 
cd mplayer-count
cp ./mplayer-count/lib/* /lib/ -rfap
cp ./mplayer-count/bin/mplayer  /bin  
这样的开发板的环境就搭建好了。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

三、视频广告播放模块

在Linux系统上,有许多好用的开源视频播放软件,这里我们使用mplayer多媒体播放器,它可以用于各个主流操作系统如:Windows、Linux、MAC OS等

我们将资源包中的video_file.tar.gz解压放到rk3399开发板中,终端输入指令:

mplayer -vo fbdev2 -ao alsa -zoom -x 800 -y 1280  xm.mpg
  • 1

如果可以正常播放视频就表示源码包移植成功

mplayer具体其他用法可以自行查找相关文档,本篇主要讲解自动售货机项目rk3399端实现,故不深入讨论mplayer使用方法

3.1 准备工作

在开始代码编写之前,需要保证开发板中有以下红框圈起来的文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6SuEQvAU-1670421204677)(D:\AiThinkerIDE_V1.5.2\文章\image-20221203220845862.png)]

接着,我们新建一个user.c文件,用来编写我们的项目代码

进入user.c加入所有需要的头文件

#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <setjmp.h>
#include <jpeglib.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <sys/wait.h>

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

代码刚开始运行时,需要搜索给定目录下的所有多媒体文件名并存放到结构体中

#define MAX_COUNT 50 //音频/视频/图片最大数量 
//全局
//多媒体文件数量 
typedef struct media_count{
	int video_count;	//视频数量 
	int pic_count;		//图片数量 
	int audio_count;	//音频数量 
}MEDIA_COUNT; 
MEDIA_COUNT m_cnt;

//多媒体文件名 
typedef struct media_name{
	char *video_name[MAX_COUNT];	//视频名 
	char *pic_name[MAX_COUNT];		//图片名 
	char *audio_name[MAX_COUNT];	//音频名 
}MEDIA_NAME;
MEDIA_NAME m_name;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.2 搜索给定目录下多媒体文件名并存储

//获取路径下的多媒体(音/视频/图片文件)
void get_media(const char *path)
{
	struct dirent *dir_info;//里面存储了路径下的文件名
	int count = 0;//数量
	
	DIR *dp = opendir(path);//打开路径
	//读取路径下的文件名,一次只能读取一个,指针会自动偏移
	while((dir_info = readdir(dp)) != NULL)
	{
		if(strstr(dir_info->d_name,".mp4") != NULL)//mp4文件 
		{
			m_name.video_name[m_cnt.video_count++] = dir_info->d_name;
		}
		else if(strstr(dir_info->d_name,".avi") != NULL)//avi文件
		{
			m_name.video_name[m_cnt.video_count++] = dir_info->d_name;
		}
		else if(strstr(dir_info->d_name,".mpg") != NULL)//mpg文件
		{
			m_name.video_name[m_cnt.video_count++] = dir_info->d_name;
		}
		else if(strstr(dir_info->d_name,".mp3") != NULL)//mp3文件
		{
			m_name.audio_name[m_cnt.audio_count++] = dir_info->d_name;
		}
		else if(strstr(dir_info->d_name,".jpg") != NULL)//jpg文件
		{
			m_name.pic_name[m_cnt.pic_count++] = dir_info->d_name;
		}
		else if(strstr(dir_info->d_name,".png") != NULL)//png文件
		{
			m_name.pic_name[m_cnt.pic_count++] = dir_info->d_name;
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

3.3 视频播放代码实现

由于播放视频时,还需要时刻捕获触摸屏触点,故视频、音频、图片等都需要新建一个线程代码去实现,防止独自占用整个进程

//全局
pthread_mutex_t lock;//线程互斥锁
void video_play(void)//播放视频函数
{
	int i;
	pthread_t pid[MAX_COUNT];//存储视频播放的线程id
	
	create_pipe();//创建管道
	//创建互斥锁 
	if(pthread_mutex_init(&lock,NULL) != 0)
	{
		perror("creat mutex failed\r\n");
		exit(EXIT_FAILURE);
	}

	for(i = 0;i < m_cnt.video_count;i++)
	{
		//创建播放视频的线程 
		if(pthread_create(&pid[i],NULL,pthread_media_play,m_name.video_name[i]) < 0)
		{
			perror("pthread_create error\r\n");
			exit(EXIT_FAILURE);
		}
	}
	
	//主线程中不断获取触摸屏触点 
	while(1)
	{
		if(1 == get_ts_coordinate())//获取触摸屏坐标
		{
			pthread_mutex_destroy(&lock);//销毁线程锁 
			for(i = 0;i < m_cnt.video_count;i++)
			{
				pthread_cancel(pid[i]);//终止线程
			}
			clean_fb(0);//清屏 
			system("killall -9 mplayer");//终止mplayer进程 
			break;
		}
	}
}

void *pthread_media_play(void *arg)//媒体播放线程函数(音频和视频) 
{
	char *mediaName = (char *)arg;//强转
	char temp[150] = {"mplayer -slave -quiet -input file=./my_pipe "};//mplayer播放命令前缀 
	while(1)
	{
		pthread_mutex_lock(&lock);//上锁 
		if(strstr(mediaName,"mp3")!=NULL)//如果为音频 
		{
			system(strcat(temp,mediaName));
		}
		else//视频 
		{
			strcat(temp,"-geometry 0:0 -zoom -x 1000 -y 800 -vf rotate=1 -vo fbdev2 ");//视频播放命令 
			system(strcat(temp,mediaName));
		}
		pthread_mutex_unlock(&lock);//解锁 
		sleep(1); 
		printf("mediaName:%s\r\n",mediaName);
	}
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

3.4 管道创建代码

mplayer用代码控制播放需要管道,故还需要写一个管道创建的函数

int create_pipe(void)//创建管道 
{
	system("unlink ./my_pipe");//删除管道
	system("mkfifo ./my_pipe");//创建管道
	int Pipe_fd = open("./my_pipe", O_RDWR);
	if (Pipe_fd < 0)
	{
		perror("open pipe file ERROR!\n");
		return Pipe_fd;
	}
    return Pipe_fd;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

至此,视频播放就已经完成

3.5 音频播放代码

如果我们想要单独播放音频文件(mp3),那么可以加入以下函数

/*
功能:播放音频 
形参:int num:待播放音频在m_name.audio_name数组中的编号
返回:void 
*/
void audio_play(int num)//播放音频函数
{
	int i;
	pthread_t pid;//音频播放线程id 
	
	create_pipe();//创建管道
	//创建互斥锁 
	if(pthread_mutex_init(&lock,NULL) != 0)
	{
		perror("creat mutex failed\r\n");
		exit(EXIT_FAILURE);
	}

	if(pthread_create(&pid,NULL,pthread_media_play,m_name.audio_name[num]) < 0)
	{
		perror("pthread_create error\r\n");
		exit(EXIT_FAILURE);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

四、触摸屏获取触摸点坐标

触摸屏设备节点文件存放在/dev/input/路径下,在使用之前我们需要先打开触摸屏设备节点文件,通常文件名为event0-4,本项目rk3399触摸屏设备节点文件名为event1

4.1 打开/关闭触摸屏设备节点文件

//全局
#define OPEN	1
#define CLOSE	0
#define LCD_LENGTH 1280 //LCD屏幕长度(像素)
#define LCD_WIDTH 800 //LCD屏幕宽度(像素) 
#define LCD_SIZE (1280*800) //LCD屏幕尺寸 

//触摸屏数据结构 
typedef struct touch_screen_value{
	int fd_ts;//触摸屏文件描述符
	int x;//x轴绝对位置坐标
	int y;//y轴绝对位置坐标
}TS_VALUE;
TS_VALUE ts_value;

/*
功能:打开/关闭触摸屏 
形参:int flag:1 打开 0关闭 
返回:成功返回0 失败返回非零 
*/
int open_or_close_ts(int flag)
{
	switch(flag)
	{
		case OPEN:
			ts_value.fd_ts = open("/dev/input/event1",O_RDWR);
			if(ts_value.fd_ts < 0)
			{
				perror("open fd_ts error\r\n");
				exit(EXIT_FAILURE);
			}
			break;
		case CLOSE:
			return close(ts_value.fd_ts);
			break;
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

4.2 获取触摸屏坐标

我们需要知道,触摸屏为input子系统下的标准节点文件,它上报的是ABS(绝对坐标事件),下面为获取触摸坐标点函数

/*
功能:获取触摸屏坐标 
形参:void
返回:int ret:获取到x,y坐标返回1 否则返回0 
备注:EV_ABS:绝对坐标 
多点触控(多点触控的电容屏)
#define ABS_MT_POSITION_X	0x35
#define ABS_MT_POSITION_Y	0x36
#define ABS_MT_PRESSURE		0x3a
*/
int get_ts_coordinate(void)
{
	struct input_event temp;//输入事件结构体
	int ret = 0;
	ts_value.x = ts_value.y = 0;//每次进来都清零 
	
	while(1)
	{
		read(ts_value.fd_ts,&temp,sizeof(struct input_event));//获取输入事件
		//获取x轴绝对位置坐标
		//type为事件类型-绝对坐标
		//code为数据项
		//value为数据值 
		if(temp.type == EV_ABS && temp.code == ABS_MT_POSITION_X && temp.value < LCD_WIDTH && temp.value > 0)
		{
			ts_value.x = temp.value;
		}
		//获取y轴绝对位置坐标 
		else if(temp.type == EV_ABS && temp.code == ABS_MT_POSITION_Y && temp.value < LCD_LENGTH && temp.value > 0)
		{
			ts_value.y = temp.value;
		}else
		{
			//如果两个都大于0,就表示x和y均获取到 
			if(ts_value.x > 0 && ts_value.y > 0)
			{
				ret = 1;
			}
			break;
		}
		printf("x:%d	y:%d\r\n",ts_value.x,ts_value.y);
	} 
	
	return ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

五、图片显示

图片显示这一模块,要考虑到我们既有bmp格式图片,也有其他类型格式图片比如jpg,与音视频播放相同,我们在显示图片的时候也需要获取触摸点,故也采用线程的方式来实现

5.1 图片显示及支付接口调用(摄像头)

在图片显示函数中,我还加入了摄像头调用接口(用于模拟人脸支付),如若你想加入真正的支付接口,也可以直接在本函数中修改代码即可

//全局
int choice = -1; //商品选择标志

/*
功能:显示图片(bmp/jpg)
形参:void
返回:void
购物窗口像素点分部:
选择: (0,1010)	-	(399,1280)
付款: (400,1010)-	(799,1280)
图片: (0,163)   -   (799-1009) 
货道: (0,0)     -   (569,162)
退出: (570,0)   -   (799,162) 
*/
void pic_play(void)
{
	pthread_t pid;//图片显示线程id 
	int i = 0,flag = 1;
	int ret;
	
	while(flag)
	{
		//创建显示图片的线程
	 	if(pthread_create(&pid,NULL,pthread_pic_play,m_name.pic_name[i]) < 0)
		{
			perror("pthread_create error\r\n");
			exit(EXIT_FAILURE);
		}
		printf("%s has been display\r\n",m_name.pic_name[i]);
		pthread_join(pid,NULL);//等待线程结束,回收资源 
		while(1)
		{
			if(1 == get_ts_coordinate())//获取触摸点 
			{
				if(ts_value.x >= 0 && ts_value.x <=399 && ts_value.y >= 163 && ts_value.y <= 1009) //上一张图片 
				{
					i = (i==0) ? (m_cnt.pic_count-1) : (i-1);
				}
				else if(ts_value.x >= 400 && ts_value.x <=799 && ts_value.y >= 163 && ts_value.y <= 1009) //下一张图片 
				{
					i = (i == m_cnt.pic_count-1) ? 0 : (i+1);
				}
				else if(ts_value.x >= 570 && ts_value.x <=799 && ts_value.y > 0 && ts_value.y <= 162) //退出 
				{
					flag = 0;
				}
				else if(ts_value.x >= 0 && ts_value.x <=399 && ts_value.y > 1010 && ts_value.y <= 1280) //选择 
				{
					choice = i;
				}
				else if(ts_value.x >= 400 && ts_value.x <=799 && ts_value.y > 1010 && ts_value.y <= 1280) //付款 
				{
					if(choice < 0)//如果没有点击选择按钮 
					{
						audio_play(0);//播放提示音频,提示:请选择商品 
						continue;
					}
					ret = fork();//创建一个子进程来调用摄像头 
					if(ret == 0)
					{
						system("./v4l2_sample");//运行摄像头 
						exit(EXIT_SUCCESS);
					}
					else
					{
						char buf[10] = {0};
						
						sleep(5);//等待5s 
						printf("******************************\r\n");
						system("killall -9 v4l2_sample");//关闭摄像头进程 
						wait(NULL);//等待进程结束,回收资源 
						
						sprintf(buf,"%d",*(m_name.pic_name[choice]+7)-'0');//取出货道号并放在buf中 
						printf("buf = %s\r\n",buf);
						send(clifd,buf,strlen(buf),0);//发送货道号到TCP_Client端---也就是MP08_ESP8285 
				        recv(clifd,buf,sizeof(buf),0);//如果MP08接收到数据,则会返回一个'0' 
				        printf("recv:%s\r\n",buf);//打印查看是否正确发送 
				        choice = -1;//切换状态为未选择 
					}
				}
				break;
			}
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

5.2 清屏代码

在这里,我们可以用显存映射的方法,编写一个清屏函数

/*
功能:清屏函数 
形参:int color:清屏颜色 
返回:void 
*/
void clean_fb(int color)
{
	struct fb_fix_screeninfo fix_info;//固定数据,如显存等
	struct fb_var_screeninfo var_info;//可变数据,如像素等
	int *pfile = NULL;//指向映射后的显存地址 
	int fd_fb;//显示屏设备节点文件描述符 
	int x,y,ret;
	
	//打开LCD显示屏
	fd_fb = open("/dev/fb0",O_RDWR);
	if(fd_fb < 0)
	{
		perror("open fd_fb error\r\n");
		exit(EXIT_FAILURE);
	} 
	//获取LCD设备信息
	ret = ioctl(fd_fb,FBIOGET_FSCREENINFO,&fix_info);
    if(ret < 0)
    {
        perror("ioctl fix_info error\r\n");
        exit(EXIT_FAILURE);
    }
    ret = ioctl(fd_fb,FBIOGET_VSCREENINFO,&var_info);
    if(ret < 0)
    {
        perror("ioctl var_info error\r\n");
        exit(EXIT_FAILURE);
    }
    //打印信息检查参数 
	printf("fix_info.smem_len = %d, fix_info.line_length = %d, "
	       "var_info.xres = %d, var_info.yres = %d, var_info.bits_per_pixel = %d\n",
           fix_info.smem_len, fix_info.line_length, var_info.xres, 
		   var_info.yres, var_info.bits_per_pixel);
	//映射显存地址
    pfile = (int *)mmap(NULL,fix_info.smem_len,PROT_READ |PROT_WRITE,MAP_SHARED,fd_fb,0);
    if(pfile == MAP_FAILED)
    {
        perror("mmap error\r\n");
        exit(EXIT_FAILURE);
    }	
		
	for(y = 0;y < var_info.yres;y++)
    {
        for(x = 0;x < var_info.xres;x++)
        {
            *(pfile + x + y * var_info.xres) = color;
        }
    }
	
	//关闭LCD屏幕,释放资源。
	close(fd_fb);
	//取消映射
 	munmap(pfile,fix_info.smem_len);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

六、摄像头接口配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VMUCybd6-1670421204678)(D:\AiThinkerIDE_V1.5.2\文章\image-20221203235118945.png)]

我们在本地目录下,创建一个临时文件夹aaa

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tzz9BJaU-1670421204679)(D:\AiThinkerIDE_V1.5.2\文章\image-20221203235536544.png)]

将资源包中的以下文件放到aaa中

  • ascii.h ascii.c
  • color.h color.c
  • lcd.h lcd.c
  • Makefile
  • v4l2.c

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oSWpZdo0-1670421204680)(D:\AiThinkerIDE_V1.5.2\文章\image-20221203235818132.png)]

修改lcd.c的114行,将该行注释掉,防止调用摄像头时清屏(如果想要清屏也可以不注释掉)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6P6JpMc-1670421204681)(D:\AiThinkerIDE_V1.5.2\文章\image-20221204000647464.png)]

接着,运行Makefile文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jwpvc2ea-1670421204682)(D:\AiThinkerIDE_V1.5.2\文章\image-20221204000019896.png)]

会生成/bin 和 /debug两个文件夹,查看/bin中的文件,生成了一个可执行文件v4l2_sample

将生成的v4l2_sample复制到跟user.c同一个路径下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Jbr6UVA-1670421204683)(D:\AiThinkerIDE_V1.5.2\文章\image-20221204000321932.png)]

运行示例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TxkJiD9V-1670421204684)(D:\AiThinkerIDE_V1.5.2\文章\image-20221204001328945.png)]

现象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N6kM1mB3-1670421204685)(D:\AiThinkerIDE_V1.5.2\文章\image-20221204001449722.png)]

七、TCP通信

我们在MP08-ESP8285中已经编写好了TCP客户端的代码,那么我们就需要在RK3399上编写TCP服务端的代码来与MP08进行通信

#define SERVER_IP "192.168.31.116" //存放rk3399开发板ip地址 
#define PORT_NUM 6000 //TCP-port 
typedef struct sockaddr SA;
typedef struct sockaddr_in SIN;
int serfd,clifd;
SIN seraddr,cliaddr;
socklen_t addrlen;

void connect_tcp(void)//连接tcp
{
	 int ret;

    //创建通信套接字
    serfd = socket(AF_INET,SOCK_STREAM,0);
    if(serfd == -1)
    {
             perror("socket failed\r\n");
             exit(0);
    }

    //编写服务器地址信息
    bzero(&seraddr,sizeof(SIN));  //功能类似memset
    seraddr.sin_family=AF_INET;
    seraddr.sin_port = htons(PORT_NUM);
    seraddr.sin_addr.s_addr = inet_addr(SERVER_IP);  //对IP地址进行转化

    //绑定函数
    int reuse=1;
    ret=setsockopt(serfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));


    ret = bind(serfd,(SA*)&seraddr,sizeof(SA));
    if(ret == -1)
    {
            perror("failed\r\n");
            return -1;
    }

    //创建监听队列
    ret = listen(serfd,5);
    if(ret == -1)
    {
            perror("listen failed\r\n");
            return -1;
    }

    //等待被连接
    clifd = accept(serfd,(SA*)&cliaddr,&addrlen);
    if(clifd == -1)
    {
            perror("accept failed\r\n");
            return -1;
    }

    printf("accept sucess\r\n");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

八、主函数

主函数就非常的简单,因为我们已经对各功能进行了模块化处理,只需要对各个功能进行调用就行了

int main(int argc,const char *argv[])
{
	if(argc != 2)//需要两个参数,第一个是可执行文件名,第二个是存放媒体文件的路径 
	{
		fprintf(stderr,"Usage:%s [media path]\r\n",argv[0]);
		exit(EXIT_FAILURE);
	}
	connect_tcp();//连接tcp 
	get_media(argv[1]);//获取路径下的媒体文件 
	open_or_close_ts(OPEN);//打开触摸屏 
	while(1)
	{
		clean_fb(0);//清屏
		video_play(); //播放广告视频 
		pic_play();//显示货道图片 
	}
	//通信结束
    close(clifd);
    close(serfd);
	
}	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

那么,至此整个项目就已经完成

九、总结

整个项目由于时间较为紧迫,代码采用了大量的全局变量以保证程序尽量一遍就能写出来且没问题,我本人其实非常不喜欢用全局变量,故希望后来者能够利用更好的数据结构和算法来修改或者重构本项目代码,如有人写出来,可以随时联系我,与我一起交流

同时在本文最后也感谢胡工对我学习上、生活上的教导,给我提供大量丰富的资源,有机会接触到如此棒的项目,在今后工作上我也将继续努力,保持学习的积极主动性,随时为大家分享我工作上、生活中学习到的技术或其他知识

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号