粤嵌gec6818实训项目

实训项目讲解

将开发板摄像头采集到的图像数据通过UDP上传
编程思想:
1.mmap显示图片
2.触摸屏点到某区域打开摄像头
3.V4L2编程
4.UDP编程

具体流程以及代码讲解

  • 1.mmap显示图片
    A:mmap的作用是映射文件描述符和指定文件的(off_t off)区域至调用进程的(addr,addr *len)的内存区域,如下图所示:

B:函数原型:void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
C:参数讲解:
(1) start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。
(2) length:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
(3) flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
MAP_DENYWRITE //这个标志被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
MAP_FILE //兼容标志,被忽略。
MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
(4) fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。
offset:被映射对象内容的起点。
D:使用

#define LCD_BUF_SIZE 800*480*4
unsigned int *lcd_fd_addr = mmap(NULL, 
				LCD_BUF_SIZE, 
				PROT_READ | PROT_WRITE, 
				MAP_SHARED, 
				lcd_fd, 
				0);
  • 2.输入子系统之触摸屏触发
    A:linux输入子系统

    B:linux输入子系统结构体
    一、time:输入事件发生的时间戳,精确到微秒。时间结构体定义如下: struct timeval { __time_t tv_sec; // 秒 long int tv_usec; // 微秒(1 微秒 = 10-3毫秒 = 10-6秒) };
    二、type:输入事件的类型。比如: EV_SYN:事件间的分割标志,有些事件可能会在时间和空间上产生延续,比如持 续按住一个按键。为了更好地管理这些持续的事件,EV_SYN 用以将他们分割成一个 个的小的数据包。 EV_KEY:用以描述键盘,按键或者类似键盘的设备的状态变化。 EV_REL:相对位移,比如鼠标的移动,滚轮的转动等。 EV_ABS:绝对位移,比如触摸屏上的坐标值。 EV_MSC:不能匹配现有的类型,这相当于当前暂不识别的事件。比如在 Linux 系统中按下键盘中针对 Windows 系统的“一键杀毒”按键,将会产生该事件。 EV_LED:用于控制设备上的 LED 灯的开关,比如按下键盘的大写锁定键,会同 时产生 ”EV_KEY” 和 ”EV_LED” 两个事件。
    三、code:这个“事件的代码”用于对事件的类型作进一步的描述。比如:当发生 EV_KEY 事件时,则可能是键盘被按下了,那么究竟是哪个按键被按下了呢?此时查看 code 就知道 了。当发生 EV_REL 事件时,也许是鼠标动了,也许是滚轮动了。这时可以用 code 的值 来加以区分。
    四、value:当 code 都不足以区分事件的性质的时候,可以用 value 来确认。比如由 EV_REL 和 REL_WHEEL 确认发生了鼠标滚轮的动作,但是究竟是向上滚还是向下滚呢? 再比如由由 EV_KEY 和 KEY_F 确认了发生键盘上 F 键的动作,但究竟是按下呢还是弹起呢?这时都可以用 value 值来进一步判断。
    C: 使用:
int tsfd = open("/dev/input/event0", O_RDWR);
	while(1)
	{
		//读取触摸屏数据
		struct input_event xy;
		read(tsfd, &xy , sizeof(xy));

		//分析触摸屏数据
		//事件为触摸屏的X轴
		if(xy.type == EV_ABS && xy.code == ABS_X) 
		{
			printf("x=%d\n", xy.value);
			x = xy.value;
		}
  • 3.V4L2编程
    A:V4L2工作原理
    V4L2是Video4linux2的简称,为linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写,摄像头驱动设备一般在:/dev/video0 或 /dev/video7下。
    同时还需要其中的动态库。

    B:使用
        //初始化摄像头设备
	linux_v4l2_yuyv_init("/dev/video7");
	
	//开启摄像头采集功能
	linux_v4l2_start_yuyv_capturing();

        //获取一针图像	
	linux_v4l2_get_yuyv_data(&video_buf);				    

        //摄像头停止
	linux_v4l2_yuyv_quit();
  • 4.UDP编程
    A:套接字
    在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据
    socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。
    即socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。Socket()函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
    B:UDP通信框架

C:UDP通信

数据发送接口
ssize_t sendto(int socket, //UDP通信对象
void *message,//消息
size_t length,//消息的长度
int flags,//默认为 0
struct sockaddr *dest_addr, //接收方的IP地址
socklen_t dest_len);//接收方的地址长度

数据接收接口
ssize_t recvfrom(int socket,//UDP通信对象
void *buffer, //消息
size_t length,//消息的长度
int flags,//默认为 0
struct sockaddr *address,//发送方的地址
socklen_t *address_len);//发送方的地址长度

项目汇总

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <pthread.h>
#include <strings.h>
#include <linux/input.h>
#include "lcd.h"
#include "api_v4l2.h"
#include <linux/input.h>
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/mman.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "jpeglib.h"
#include "yuyv.h"

#define LCD_BUF_SIZE 800*480*4
struct jpg_data video_buf;
int ts_x, ts_y;
int x = 0, y = 0;

//内存映射的方法 显示图片
void show_24_bmp(char *picpath, int x, int y, int w, int h)
{
	//打开LCD文件
	int lcd_fd = open("/dev/fb0", O_RDWR);
	//判断lcd 是否成功打开
	if(lcd_fd == -1)
		perror("lcd open failed!\n");

	//打开bmp图片
	int bmp_fd = open(picpath, O_RDONLY);
	//判断bmp图片是否打开
	if(bmp_fd == -1)
		perror("bmp open failed!\n");

	//内存映射函数
	unsigned int *lcd_fd_addr = mmap(NULL, 
								LCD_BUF_SIZE, 
								PROT_READ | PROT_WRITE, 
								MAP_SHARED, 
								lcd_fd, 
								0);

	if(lcd_fd_addr == MAP_FAILED)
		perror("mmap failed!\n");

	int *p = lcd_fd_addr + (800*y+x);

	char buf24[w*h*3];//char为1字节 *3字节 普通图片文件为R-G-B
	int buf32[w*h]; // int 为4个字节 需要将24位图转换为32位图 开发板的为R-G-B-透明格式

	//将bmp图片的头54字节无用信息内存去掉
	lseek(bmp_fd, 54, SEEK_SET);
	//读取bmp图片
	read(bmp_fd, buf24, w*h*3);

	//将位图转换
	for(int n = 0; n < w*h; n++)
		buf32[n] = buf24[3*n] << 0 | buf24[(3*n)+1] << 8 | buf24[(3*n)+2] << 16;

	//将图片翻转在任意位置显示图片,宽度长度等
	for(int y = 0; y < h; y++)
	{
		for(int x = 0; x < w; x++)
		{
			*(p + 800*((h-1)-y)+x) = buf32[w*y+x];
		}
	}

	//取消映射
	int ret = munmap(lcd_fd_addr, LCD_BUF_SIZE);
	if(ret == -1)
		perror("munmap is failed\n");

	//关闭lcd 和 图片文件标识符
	close(lcd_fd);
	close(bmp_fd);

}

void touch()
{
	//打开触摸屏的设备文件
	int tsfd = open("/dev/input/event0", O_RDWR);
	while(1)
	{
		//读取触摸屏数据
		struct input_event xy;
		read(tsfd, &xy , sizeof(xy));

		//分析触摸屏数据
		//事件为触摸屏的X轴
		if(xy.type == EV_ABS && xy.code == ABS_X) 
		{
			printf("x=%d\n", xy.value);
			x = xy.value;
		}

		//事件为触摸屏的Y轴
		if(xy.type == EV_ABS && xy.code == ABS_Y)
		{
			printf("y=%d\n", xy.value);
			y = xy.value;
		}

		//事件为触摸屏的的按下和松开的事件
		if(xy.type == EV_KEY && xy.code == BTN_TOUCH)
		{
			printf("touch=%d\n", xy.value);

			if(xy.value == 0)
				break;
		}
	}
	
	//关闭触摸屏
	close(tsfd);
}


int main(int argc,char**argv)
{
	if(argc != 2)
	{
			printf("input ip\n");
			return 1;
	}
	
	//打开LCD 设备
	lcd_open();
	mmap_lcd();
	
	//初始化摄像头设备
	linux_v4l2_yuyv_init("/dev/video7");
	
	//开启摄像头采集功能
	linux_v4l2_start_yuyv_capturing();
	
	//添加主界面代码
	show_24_bmp("main.bmp", 0, 0, 800, 480);

	//添加触摸屏
	touch();

	//判断是否开启网络视频功能 
	if( x > 90 &&  y > 250  && x < 270  && y < 270)
	{
		printf("star!!\n");
		
		int udp=socket(AF_INET,SOCK_DGRAM,0);

		struct sockaddr_in  dest; 
		dest.sin_family  = AF_INET;  
		dest.sin_port     =  htons(6666);
		dest.sin_addr.s_addr =  inet_addr(argv[1]);  
		while(1)
		{
			//获取一针图像	
			linux_v4l2_get_yuyv_data(&video_buf);				
			//显示当前一针图像	
			show_video_data(0, 0, video_buf.jpg_data , video_buf.jpg_size);

			//采集后的数据上传到网络
			int size = sendto(udp,video_buf.jpg_data,video_buf.jpg_size,0,(struct sockaddr *)&dest,sizeof(dest));
				//printf("size=%d\n",size);
				printf("camera is ok\n");
			if(x )
		}
	}

	linux_v4l2_yuyv_quit();

	lcd_close();
}

将写好的代码放到Ubuntu中交叉编译生成可执行文件,这里我写了一份Makefile进行make,但是存在不足,每次都要先进行make clean 之后才能进行make

Makefile使用

#定义变量保存arm-linux-gcc

CC=arm-linux-gcc

TARGET=main

CONFIG=-I./jpeg -L./jpeg -ljpeg -lapi_v4l2_arm1 -lpthread

#定义变量SRCS 当前目录下所有.c结尾的源文件
SRCS=$(wildcard *.c)
#变量OBSJ 将SRCS下所有的.c文件编译出.o目标文件
OBJS=$(patsubst %.c, %.o, $(SRCS))

$(TARGET):$(OBJS)
	$(CC) -o $@ $^ -lpthread $(CONFIG)

%.o:%.c
	$(CC) -c $< -o $@ $(CONFIG)

clean:
	rm -f *.o 

#clean:
#	rm -f *.o $(TARGET) 删除可执行文件

# $@表示目标文件
# $^表示所有的依赖项(动态库or静态库)
# $< 重输入 表示第一个文件

结果

(1)在开发板执行可执行文件+ip地址

(2)在开发板上触摸相应的地方开启摄像头数据传输

(3)运行结果:可以看到摄像头采集到的数据不仅显示到LCD屏上还传输到电脑上

posted @ 2021-01-22 16:26  Kεvin  阅读(2194)  评论(0编辑  收藏  举报