【全志T113-S3_100ask】按键控制USB摄像头采集图像

参考文章:

(2条消息) 【全志T113-S3_100ask】5-编写按键驱动(input子系统+内核按键驱动)_全志 key gpio-keys_第四维度4的博客-CSDN博客

(2条消息) 【全志T113-S3_100ask】USB摄像头通过v4l2采集图像_crabxd的博客-CSDN博客

一、材料准备

全志T113-100ask-pro、USB摄像头

二、环境配置

1、使能内核驱动

在内核目录:~/buildroot-100ask_t113-pro/buildroot/output/build/linux-origin_master/下执行,以下命令进入菜单

make menuconfig
-> Device Drivers 
    -> Input device support 
        -> Generic input layer (needed for keyboard, mouse, ...) (按y选中) 
            -> Keyboards (INPUT_KEYBOARD [按y选中]) 
                ->GPIO Buttons

保存退出菜单。

2、修改设备树

~/buildroot-100ask_t113-pro/buildroot/output/build/linux-origin_master/arch/arm/boot/dts目录下找到名为 sun8iw20p1-t113-100ask-t113-pro.dts 的文件,使用vim打开它。

在根节点下添加以下代码:

gpio-keys {
        compatible = "gpio-keys";
        autorepeat;

        pinctrl-names = "default";
        user_key {
            label = "USER KEY";
            linux,code = <103>;
            gpios = <&pio PB 4 GPIO_ACTIVE_LOW>;
        };
};

完成之后,按esc退出编辑模式,输入:wq保存退出

3、编译内核

~/buildroot-100ask_t113-pro/buildroot/ 目录下执行以下代码,编译内核

make linux-rebuild V=1

4、编译最小系统镜像

~/buildroot-100ask_t113-pro/buildroot/ 目录下执行以下两个代码,编译最小系统镜像(如果是初次编译的话,时间可能会比较长,这是正常的,你可以先看看下一部分的内容,编写代码,等编译完成再继续下一步。)

make  BR2_EXTERNAL="../br2t113pro ../br2lvgl "  100ask_t113-pro_sdcard_core_defconfig
make  V=1

编译完成之后,会在~/buildroot-100ask_t113-pro/buildroot/output/images 目录下生成一个镜像文件

将文件拷贝到windows下使用wind32diskimage烧写,等待烧写完成之后,将SD卡插到开发板上启动即可。

5、启动成功后,输入root进入

6、设备联网

由于我们后续需要将编写的应用程序下载到开发板上,所以我们需要让开发板联网

用手机或者电脑开个热点,在开发板上执行以下命令:

/etc/wlan-connect.sh wifi名称 wifi密码 1

联网成功之后,可以使用ifconfig命令查看分配到的IP

7、在开发板当前目录(/root)下,创建一个目录JPG用于存放照片

三、编写代码

在编写代码之前,让我们先捋一下:

首先我们需要驱动按键、使用按键,所以我们需要三个文件:key.c、key_drv.c、key.h;

然后,我们需要使用USB摄像头,使用还需要两个文件:camera.c、camera.h

最后,我们需要用一个 main.c 来初始化、测试。

  1. key.h

#ifndef __KEY_H
#define __KEY_H

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int key(void);

#endif
  1. key_drv.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define KEY_CNT            1        /* 设备号个数     */
#define KEY_NAME        "key"    /* 名字         */

/* 定义按键值 */
#define KEY0VALUE        0XF0    /* 按键值         */
#define INVAKEY            0X00    /* 无效的按键值  */

/* key设备结构体 */
struct key_dev{
    dev_t devid;            /* 设备号      */
    struct cdev cdev;        /* cdev     */
    struct class *class;    /* 类         */
    struct device *device;    /* 设备      */
    int major;                /* 主设备号      */
    int minor;                /* 次设备号   */
    struct device_node    *nd; /* 设备节点 */
    int key_gpio;            /* key所使用的GPIO编号        */
    atomic_t keyvalue;        /* 按键值         */    
};

struct key_dev keydev;        /* key设备 */

/*
 * @description    : 初始化按键IO,open函数打开驱动的时候
 *                   初始化按键所使用的GPIO引脚。
 * @param         : 无
 * @return         : 无
 */
static int keyio_init(void)
{
    keydev.nd = of_find_node_by_path("/key");
    if (keydev.nd== NULL) {
        return -EINVAL;
    }

    keydev.key_gpio = of_get_named_gpio(keydev.nd ,"key-gpio", 0);
    if (keydev.key_gpio < 0) {
        printk("can't get key0\r\n");
        return -EINVAL;
    }
    printk("key_gpio=%d\r\n", keydev.key_gpio);
    
    /* 初始化key所使用的IO */
    gpio_request(keydev.key_gpio, "key0");    /* 请求IO */
    gpio_direction_input(keydev.key_gpio);    /* 设置为输入 */
    return 0;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int key_open(struct inode *inode, struct file *filp)
{
    int ret = 0;
    filp->private_data = &keydev;     /* 设置私有数据 */

    ret = keyio_init();                /* 初始化按键IO */
    if (ret < 0) {
        return ret;
    }

    return 0;
}

/*
 * @description        : 从设备读取数据 
 * @param - filp     : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    int value;
    struct key_dev *dev = filp->private_data;

    if (gpio_get_value(dev->key_gpio) == 0) {         /* key0按下 */
        while(!gpio_get_value(dev->key_gpio));        /* 等待按键释放 */
        atomic_set(&dev->keyvalue, KEY0VALUE);    
    } else {    
        atomic_set(&dev->keyvalue, INVAKEY);        /* 无效的按键值 */
    }

    value = atomic_read(&dev->keyvalue);
    ret = copy_to_user(buf, &value, sizeof(value));
    return ret;
}

/*
 * @description        : 向设备写数据 
 * @param - filp     : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int key_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open = key_open,
    .read = key_read,
    .write = key_write,
    .release =     key_release,
};

/*
 * @description    : 驱动入口函数
 * @param         : 无
 * @return         : 无
 */
static int __init mykey_init(void)
{
    /* 初始化原子变量 */
    atomic_set(&keydev.keyvalue, INVAKEY);

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (keydev.major) {        /*  定义了设备号 */
        keydev.devid = MKDEV(keydev.major, 0);
        register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
    } else {                        /* 没有定义设备号 */
        alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);    /* 申请设备号 */
        keydev.major = MAJOR(keydev.devid);    /* 获取分配号的主设备号 */
        keydev.minor = MINOR(keydev.devid);    /* 获取分配号的次设备号 */
    }
    
    /* 2、初始化cdev */
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);
    
    /* 3、添加一个cdev */
    cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);

    /* 4、创建类 */
    keydev.class = class_create(THIS_MODULE, KEY_NAME);
    if (IS_ERR(keydev.class)) {
        return PTR_ERR(keydev.class);
    }

    /* 5、创建设备 */
    keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, KEY_NAME);
    if (IS_ERR(keydev.device)) {
        return PTR_ERR(keydev.device);
    }
    
    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit mykey_exit(void)
{
    /* 注销字符设备驱动 */
    gpio_free(keydev.key_gpio);
    cdev_del(&keydev.cdev);/*  删除cdev */
    unregister_chrdev_region(keydev.devid, KEY_CNT); /* 注销设备号 */

    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("z");
  1. key.c

#include "key.h"

int key(void)
{
    struct input_event in_ev = {0};
    int fd = -1;
    int value = -1;

    /* 打开文件 */
    if (0 > (fd = open("/dev/input/event5", O_RDONLY))) {
        perror("open error");
        exit(-1);
    }

    /* 循环读取数据 */
    if (sizeof(struct input_event) !=
        read(fd, &in_ev, sizeof(struct input_event))) {
        perror("read error");
        exit(-1);
        }

    if (EV_KEY == in_ev.type) { //按键事件
        switch (in_ev.value) {
        case 0:
            printf("code<%d>: 检测到按键松开\n", in_ev.code);
            return 0;
            break;
        case 1:
            printf("code<%d>: 检测到按键按下\n", in_ev.code);
            return 1;
            break;
        case 2:
            //printf("code<%d>: 长按\n", in_ev.code);
            return 2;
            break;
        }
    }
    return 0;
}
  1. camera.h

#ifndef __CAMERA_H
#define __CAMERA_H

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>

int take_Photo(char *name);
int camera_Init(void);
void close_camera(void);

#endif
  1. camera.c


    struct v4l2_format vfmt;
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集
    
    //设置宽高(宽高有要求,不是说可以随便设置,宽和高的倍数固定)
    vfmt.fmt.pix.width = 1280;
    vfmt.fmt.pix.height = 720;
    //设置采集格式
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;

    ret = ioctl(fd,VIDIOC_S_FMT,&vfmt);
    if(ret < 0)
    {
        perror("格式设置错误!");
    }

    if(vfmt.fmt.pix.width == 1280 && vfmt.fmt.pix.height == 720 && vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG)
    {
        printf("采集格式设置成功!\n\n");
    }else 
    {
        printf("设置失败!\n");
        return -1;
    }
    return 0;
}

int take_Photo(char *name)
{
    //申请内核缓冲区队列
    struct v4l2_requestbuffers reqbuffer;
    reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置采集格式
    //申请4个缓冲区
    reqbuffer.count = 4;
    //指定映射方式
    reqbuffer.memory = V4L2_MEMORY_MMAP; 

    ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffer);
    if(ret < 0)
    {
        perror("申请队列空间失败!");
    }

    
    //将内核的缓冲区队列映射到用户空间
    struct v4l2_buffer mapbuffer;
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//指定type
    for(int i = 0; i < 4; i++)//查询出缓冲区
    {
        mapbuffer.index = i;
        ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer);//从内核空间中查询一个空间做映射
        if(ret < 0)
        {
            perror("查询内核空间失败");
        }

        mptr[i] =(unsigned char *)mmap(NULL,mapbuffer.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
        size[i]=mapbuffer.length;

        //使用完毕,“放回去”
        ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer);
        if(ret < 0)
        {
            perror("放回失败!");
        }
    }

    //开始采集
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if(ret < 0)
    {
        perror("开启失败");
    }

    //从队列中提取一帧数据
    struct v4l2_buffer  readbuffer;
    readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
    if(ret < 0)
    {
        perror("提取数据失败");
    }

    FILE *file=fopen(name, "w+");
    fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);//写数据
    fclose(file);//关闭文件
    
    //通知内核已经使用完毕
    ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
    if(ret < 0)
    {
        perror("放回队列失败");
    }
    //停止采集
    ret = ioctl(fd, VIDIOC_STREAMOFF, &type);

    //释放映射
    for(int i=0; i<4; i++)
    {
        munmap(mptr[i], size[i]);
    }

    return 0;
}

void close_camera(void)
{
    //关闭设备
    close(fd);
}
  1. main.c

#include "camera.h"
#include "key.h"

int main(int argc, char* argv[])
{
    int key_val,num;                    // key_val:用于存放按键状态;num:用于存放用户输入的图像采集数量
    int i = 0;                          // 用于记录当前拍照次数
    char name[20] = {0};                // 用于存放照片文件名

    printf("\n/***********图像采集************/\r\n\n");
    printf("/*******按下按键采集图像********/\r\n\n");
    printf("请输入要采集图像的数量:");
    scanf("%d",&num);

    camera_Init();                      // 初始化摄像头
    printf("/*******摄像头初始化成功********/\r\n\n");

    while(1)
    {
        
        key_val = key();                // 获取按键状态
        if(key_val == 1)                // 如果按键被按下,则执行拍照
        {
            i++;
            sprintf(name,"./JPG/%d.jpg",i);     // 照片的位置:./JPG/   照片名字:i.jpg
            printf("进行第%d次拍摄\r\n\n",i);
            take_Photo(name);                   // 拍照
            printf("/***********拍照成功************/\r\n");
        }
        if(i>=num)                      // 如果到达采集的数量,跳出循环
            break;

    }
    printf("/********正在关闭摄像头*********/\r\n\n");
    close_camera();                     // 关闭摄像头
}

7.编写Makefile文件

因为这里涉及的源文件比较多,使用我们是Makefile来进行编译链接源文件。

KERN_DIR = /home/usr/buildroot-100ask_t113-pro/buildroot/output/build/linux-origin_master    
#这里的路径需要根据自己的情况进行修改


all:
    make -C $(KERN_DIR) M=`pwd` modules 
    $(CROSS_COMPILE)gcc -o camera_App main.c camera.c key.c

clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
    rm -f camera_App

obj-m    += key_drv.o

到这里我们的代码就基本编写完成了。

四、编译测试

在存放代码的目录下,执行make命令,即可编译;

编译成功后会生成一个mainApp的可执行文件,我们将该文件通过TFTP发送下载到我们的开发板上

打开MobaXterm(没有的话,下载一个),选择 servers

打开TFTP server,点击TFTP后的设置,选择你存放可执行文件(camera_App)的文件夹

开启TFTP服务

在开发板上使用以下命令下载

tftp -gr camera_App 主机的IP

下载成功,可以在开发板上看到camera_App,执行以下命令:

chmod 777 camera_App./camera_App

成功执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DING_WEI_GUANG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值