参考文章:
(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 来初始化、测试。
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
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");
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;
}
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
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);
}
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
成功执行