• 实训项目


    实训项目讲解

    将开发板摄像头采集到的图像数据通过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
    ", 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!
    ");
    
    	//打开bmp图片
    	int bmp_fd = open(picpath, O_RDONLY);
    	//判断bmp图片是否打开
    	if(bmp_fd == -1)
    		perror("bmp open failed!
    ");
    
    	//内存映射函数
    	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!
    ");
    
    	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
    ");
    
    	//关闭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
    ", xy.value);
    			x = xy.value;
    		}
    
    		//事件为触摸屏的Y轴
    		if(xy.type == EV_ABS && xy.code == ABS_Y)
    		{
    			printf("y=%d
    ", xy.value);
    			y = xy.value;
    		}
    
    		//事件为触摸屏的的按下和松开的事件
    		if(xy.type == EV_KEY && xy.code == BTN_TOUCH)
    		{
    			printf("touch=%d
    ", xy.value);
    
    			if(xy.value == 0)
    				break;
    		}
    	}
    	
    	//关闭触摸屏
    	close(tsfd);
    }
    
    
    int main(int argc,char**argv)
    {
    	if(argc != 2)
    	{
    			printf("input ip
    ");
    			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!!
    ");
    		
    		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
    ",size);
    				printf("camera is ok
    ");
    			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屏上还传输到电脑上

    每一天的分享时刻
  • 相关阅读:
    android发送/接收json数据
    Android 图文数据JSON解析
    Android中View绘制流程以及invalidate()等相关方法分析
    Android WebView常见问题及解决方案汇总
    非递归方法的堆排序实现
    快速排序算法
    从导数谈起
    Linux网络编程8——对TCP与UDP的简易封装
    Linux网络编程7——使用TCP实现双方聊天
    Linux网络编程6——使用TCP实现文件服务器
  • 原文地址:https://www.cnblogs.com/kevinkala/p/14314009.html
Copyright © 2020-2023  润新知