提交 b263f70c 编写于 作者: L liaofei

增加GPIO输入捕获中断实验

上级 84db60ff
ifeq ($(KERNELRELEASE),)
#第一次执行时 KERNELRELEASE 为空,执行此分支
#内核源码目录,需要先编译内核源码,在编译源码外的内核模块
KERNELDIR ?= /home/lf/workspace/source/my_source/linux/linux-5.4.31
#NFS跟文件系统目录
ROOTFS ?= /home/lf/workspace/rootfs
#当前目录
PWD := $(shell pwd)
#$(MAKE) 相当于 make
#-C $(KERNELDIR) 执行 KERNELDIR 目录的Makefile
#M=$(PWD) 内核源码树之外的一个目录
#modules 只编译模块
all:
$(MAKE) -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- M=$(PWD) modules
arm-none-linux-gnueabihf-gcc -oapp.out app.c -std=gnu99 -D_GNU_SOURCE
module:
$(MAKE) -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- M=$(PWD) modules
app:
arm-none-linux-gnueabihf-gcc -oapp.out app.c -std=gnu99 -D_GNU_SOURCE
copy:
# 因为采用insmod加载模块,所以拷贝到NFS跟文件系统的root目录即可
cp *.ko $(ROOTFS)/root/
cp *.out $(ROOTFS)/root/
install:
# 若采用modprobe加载模块,则需要进行安装操作
$(MAKE) -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS) modules_install
cp *.out $(ROOTFS)/root/
clean:
rm -rf *.o .*.cmd *.mod.* *.mod modules.order Module.symvers .tmp_versions
else
#当从源码目录的 Makefile 再次进入时 KERNELRELEASE 已经赋值,执行此分支
#obj-m 表示编译成模块
obj-m := gpio_capture.o
endif
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : ap3216cApp.c
作者 : 正点原子Linux团队
版本 : V1.0
描述 : ap3216c设备测试APP。
其他 : 无
使用方法 :./ap3216cApp /dev/ap3216c
论坛 : www.openedv.com
日志 : 初版V1.0 2021/03/19 正点原子Linux团队创建
***************************************************************/
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <signal.h>
struct pulse_par {
//周期,在上升沿时刻结合上一个上升沿的时间一起计算周期
long long period;
//脉宽,在下降沿时刻结合上一个上升沿的时间一起计算脉宽
long long width;
};
static int dev_fd = -1;
void sig_handler(int signum, siginfo_t *siginfo, void *p)
{
int ret;
struct pulse_par pulse_par;
if(signum == SIGIO)
{
if(siginfo->si_band & POLLIN)
{
//设备有数据可读
ret = read(dev_fd, &pulse_par, sizeof(pulse_par));
if(ret == sizeof(pulse_par))
printf("period: %lld, width: %lld\r\n", pulse_par.period, pulse_par.width);
else
printf("read failed\r\n");
}
}
}
void noblock_test(char *file)
{
int flag;
struct sigaction act;
printf("noblock mode\r\n");
/* 1、注册信号处理函数 */
if(sigaction(SIGIO, NULL, &act) < 0) //获取之前的信号处理方式
{
perror("error");
return ;
}
sigemptyset(&act.sa_mask); //清空信号集
sigaddset(&act.sa_mask, SIGIO); //添加SIGIO到信号集,处理过程中要屏蔽SIGIO
act.sa_sigaction = sig_handler; //信号处理函数
act.sa_flags = SA_SIGINFO; //使用sa_sigaction的信号处理函数
if(sigaction(SIGIO, &act, NULL) < 0) //设置信号处理方式
{
perror("error");
return ;
}
/* 2、打开设备文件 */
dev_fd = open(file, O_RDWR|O_NONBLOCK);
if(dev_fd < 0)
{
perror("error");
return ;
}
/* 3、设置在此文件描述符上接收信号的进程标识 */
if(fcntl(dev_fd, F_SETOWN, getpid()) < 0)
{
perror("error");
close(dev_fd);
return ;
}
/* 4、设置标识输入输出可进行的信号 */
if(fcntl(dev_fd, F_SETSIG, SIGIO) < 0)
{
perror("error");
close(dev_fd);
return ;
}
/* 5、设置文件的FASYNC标志 */
flag = fcntl(dev_fd, F_GETFL);
if(flag < 0)
{
perror("error");
close(dev_fd);
return ;
}
if(fcntl(dev_fd, F_SETFL, flag|FASYNC) < 0)
{
perror("error");
close(dev_fd);
return ;
}
//等待信号被触发
while(1)
{
sleep(100);
}
}
void block_test(char *file)
{
int ret;
struct pulse_par pulse_par;
printf("block mode\r\n");
dev_fd = open(file, O_RDWR);
if(dev_fd < 0)
{
printf("can't open file %s\r\n", file);
return ;
}
while (1)
{
ret = read(dev_fd, &pulse_par, sizeof(pulse_par));
if(ret == sizeof(pulse_par))
printf("period: %lld, width: %lld\r\n", pulse_par.period, pulse_par.width);
else
printf("read failed,erron %d\r\n", ret);
}
}
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf("Error Usage!\r\n");
return -1;
}
if((argc >= 3) && !strncmp(argv[2], "NOBLOCK", 7))
noblock_test(argv[1]);
else
block_test(argv[1]);
return 0;
}
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#define CAPTURE_NUMBER 5
struct pulse_par {
//周期,在上升沿时刻结合上一个上升沿的时间一起计算周期
long long period;
//脉宽,在下降沿时刻结合上一个上升沿的时间一起计算脉宽
long long width;
};
struct capture_handle{
//确保设备只被打开一次
atomic_t flag;
//输入信号所使用的GPIO
struct gpio_desc *gpio;
//GPIO的中断号
int irq;
//工作队列,用于中断后半部
struct work_struct work;
//等待队列,在未采集到一个新的脉冲周期时用于挂起线程
wait_queue_head_t wait_queue;
//采集完成标志
int update;
//用于向应用层发送信号
struct fasync_struct *fasync;
//记录上一个上升沿时间,
long long last_rising;
//上一次的按键状态
int last_state;
//脉冲参数
struct pulse_par pulse_par;
//设备的ID号,这里作为设备的次设备号
uint32_t id;
//链表节点
struct list_head node;
};
//设备号,即第一个脉冲捕获设备的设备号
static dev_t capture_num;
//cdev对象
static struct cdev capture_cdev;
//class对象
static struct class *capture_class;
//capture句柄列表
static struct list_head capture_list = LIST_HEAD_INIT(capture_list);
//根据ID查找设备句柄
static struct capture_handle *find_capture_handle(uint32_t id)
{
struct capture_handle *pos;
struct capture_handle *n;
struct capture_handle *capture_handle;
capture_handle = NULL;
list_for_each_entry_safe(pos, n, &capture_list, node)
{
if(pos->id == id)
{
capture_handle = pos;
break;
}
}
return capture_handle;
}
//分配一个ID
static int32_t alloc_id(void)
{
int32_t id;
//按从小到大顺序生成ID
for(id = 0; find_capture_handle(id) && (id < CAPTURE_NUMBER); id++)
{
;
}
//ID必须小于注册的最大ID号
if(id >= CAPTURE_NUMBER)
return -EINVAL;
return id;
}
//将设备添加到链表
static void add_capture(struct capture_handle *capture_handle)
{
list_add(&capture_handle->node, &capture_list);
}
//将设备从链表中移除
static void remove_capture(struct capture_handle *capture_handle)
{
list_del(&capture_handle->node);
}
//打开设备,应用层执行open时调用
static int capture_open(struct inode *inode, struct file *file)
{
uint32_t id;
struct capture_handle *capture_handle;
//提取次设备号,次设备号即ID
id = MINOR(inode->i_rdev);
//以次设备号作为ID去查找设备句柄
capture_handle = find_capture_handle(id);
if(!capture_handle)
{
printk("find capture handle failed\r\n");
return -EINVAL;
}
if(atomic_dec_and_test(&capture_handle->flag))
{
//设置文件私有数据
file->private_data = (void*)capture_handle;
return 0;
}
else
{
//设备处于打开状态
printk("device busy\r\n");
atomic_inc(&capture_handle->flag);
return -EBUSY;
}
}
static int capture_release(struct inode *inode, struct file *file)
{
struct capture_handle *capture_handle = file->private_data;
//复位原子变量
atomic_set(&capture_handle->flag, 1);
return 0;
}
//初始化 fasync_struct 结构体指针变量,在应用层设置FASYNC标志时会执行驱动的 capture_fasync 函数
static int capture_fasync(int fd, struct file *file, int on)
{
struct capture_handle *capture_handle = file->private_data;
return fasync_helper(fd, file, on, &capture_handle->fasync);
}
//读数据函数,应用层执行read时调用
static ssize_t capture_read(struct file *file, char __user *buf, size_t len, loff_t *pos)
{
struct capture_handle *capture_handle = file->private_data;
//以阻塞式打开,且脉冲参数没有更新,则加入等待队列并休眠
if((!(file->f_flags & O_NONBLOCK)) && (!capture_handle->update))
{
if(wait_event_interruptible(capture_handle->wait_queue, capture_handle->update))
return -ERESTARTSYS;
}
//将脉冲参数拷贝到应用层
if(copy_to_user(buf, &capture_handle->pulse_par, sizeof(struct pulse_par)))
return -EFAULT;
capture_handle->update = 0;
return sizeof(capture_handle->pulse_par);
}
//工作队列函数
static void capture_work(struct work_struct *w)
{
int input_state;
long long cur_rising;
struct capture_handle *capture = container_of(w, struct capture_handle, work);
//读取GPIO电平,电平为有效值返回1,有效值在设备树中指定
input_state = gpiod_get_value(capture->gpio);
//检查GPIO状态是否改变,若未改变则认为是误触发
if(capture->last_state == input_state)
return;
if(input_state == 1)
{
//上升沿捕获周期
//记录上升沿时间
cur_rising = ktime_get_boottime_ns();
if(capture->last_rising != 0)
{
//计算周期
capture->pulse_par.period = cur_rising - capture->last_rising;
//发送信号通知相应进程
kill_fasync(&capture->fasync, SIGIO, POLL_IN);
//唤醒正在等待脉冲捕获事件的进程
capture->update = 1;
wake_up_interruptible(&capture->wait_queue);
}
//更新last_rising
capture->last_rising = cur_rising;
}
else
{
//下降沿捕获脉宽
//计算脉宽
if(capture->last_rising != 0)
capture->pulse_par.width = ktime_get_boottime_ns() - capture->last_rising;
}
//记录按键状态
capture->last_state = input_state;
}
//中断处理函数
static irqreturn_t capture_handler(int irq, void *dev)
{
struct capture_handle *capture = (struct capture_handle*)dev;
//调度工作队列
schedule_work(&capture->work);
//返回IRQ_HANDLED,表示中断被成功处理
return IRQ_HANDLED;
}
//设备和驱动匹配成功执行
static int capture_probe(struct platform_device *pdev)
{
int result;
int32_t id;
uint32_t irq_flags;
const char *label;
struct device *device;
struct capture_handle *capture;
printk("%s\r\n", __FUNCTION__);
//分配一个ID
id = alloc_id();
if(id < 0)
return id;
//读取标签,确定设备文件名
result = of_property_read_string(pdev->dev.of_node, "label", &label);
if(result != 0)
{
printk("get config info failed\r\n");
return result;
}
//设置平台设备ID
pdev->id = id;
//分配设备句柄,devm表示模块卸载时自动释放
capture = devm_kzalloc(&pdev->dev, sizeof(struct capture_handle), GFP_KERNEL);
if(!capture)
{
printk("alloc memory failed\r\n");
return -ENOMEM;
}
//复位capture设备句柄
memset(capture, 0, sizeof(struct capture_handle));
capture->last_state = -1;
//绑定ID
capture->id = id;
//初始化等待队列
init_waitqueue_head(&capture->wait_queue);
//初始化工作队列
INIT_WORK(&capture->work, capture_work);
//获取GPIO,并设置为输入(devm表示模块卸载时自动释放)
capture->gpio = devm_gpiod_get_index(&pdev->dev, "input", 0, GPIOD_IN);
if(IS_ERR(capture->gpio))
{
printk("get gpio failed\r\n");
return PTR_ERR(capture->gpio);
}
//获取中断号
capture->irq = of_irq_get(pdev->dev.of_node, 0);
if(capture->irq <= 0)
{
printk("irq get failed");
return capture->irq;
}
//获取中断触发方式
irq_flags = irq_get_trigger_type(capture->irq);
if(irq_flags == IRQF_TRIGGER_NONE)
irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
//注册中断
result = devm_request_irq(&pdev->dev, capture->irq, capture_handler, irq_flags, "capture", (void*)capture);
if(result != 0)
{
printk("request irq failed\r\n");
return result;
}
//设置原子变量为1,表示只能被一个进程打开一次
atomic_set(&capture->flag, 1);
//添加capture到链表
add_capture(capture);
//设置平台设备的驱动私有数据
pdev->dev.driver_data = (void*)capture;
//创建设备文件,将ID作为此设备的次设备号
printk("device major %d, device minor %d, device file name = %s\r\n",
MAJOR(capture_num+capture->id), MINOR(capture_num+capture->id), label);
device = device_create(capture_class, NULL, capture_num+capture->id, NULL, label);
if(IS_ERR(device))
{
remove_capture(capture);
printk("device create failed");
return PTR_ERR(device);
}
return 0;
}
//设备或驱动卸载时执行
static int capture_remove(struct platform_device *pdev)
{
struct capture_handle *capture;
printk("%s\r\n", __FUNCTION__);
//提取平台设备的驱动私有数据
capture = (struct capture_handle*)pdev->dev.driver_data;
//删除设备文件
device_destroy(capture_class, capture_num+capture->id);
//从设备句柄链表中删除capture设备
remove_capture(capture);
return 0;
}
/* 匹配列表,用于设备树和平台驱动匹配 */
static const struct of_device_id capture_of_match[] = {
{.compatible = "atk,gpio_capture"},
{ /* Sentinel */ }
};
//平台驱动
struct platform_driver capture_drv = {
.driver = {
.name = "gpio_capture",
.owner = THIS_MODULE,
.pm = NULL,
.of_match_table = capture_of_match,
},
.probe = capture_probe,
.remove = capture_remove,
};
static struct file_operations capture_ops = {
.owner = THIS_MODULE,
.open = capture_open,
.release = capture_release,
.read = capture_read,
.fasync = capture_fasync,
};
static int __init plt_drv_init(void)
{
int result;
printk("%s\r\n", __FUNCTION__);
//根据次设备号起始值动态分配并注册字符设备号
result = alloc_chrdev_region(&capture_num, 0, CAPTURE_NUMBER, "csdn,capture");
if(result < 0)
{
printk("alloc chrdev failed\r\n");
return result;
}
printk("first device major %d, first device minor %d\r\n", MAJOR(capture_num), MINOR(capture_num));
//初始化CDEV对象
cdev_init(&capture_cdev, &capture_ops);
//向系统添加CDEV对象
result = cdev_add(&capture_cdev, capture_num, CAPTURE_NUMBER);
if(result < 0)
{
unregister_chrdev_region(capture_num, CAPTURE_NUMBER);
printk("add cdev failed\r\n");
return result;
}
//创建class对象
capture_class = class_create(THIS_MODULE, "capture,class");
if(IS_ERR(capture_class))
{
cdev_del(&capture_cdev);
unregister_chrdev_region(capture_num, CAPTURE_NUMBER);
printk("class create failed");
return PTR_ERR(capture_class);
}
//注册平台驱动
result = platform_driver_register(&capture_drv);
if(result < 0)
{
class_destroy(capture_class);
cdev_del(&capture_cdev);
unregister_chrdev_region(capture_num, CAPTURE_NUMBER);
printk("add cdev failed\r\n");
return result;
}
return 0;
}
static void __exit plt_drv_exit(void)
{
printk("%s\r\n", __FUNCTION__);
//注销平台驱动
platform_driver_unregister(&capture_drv);
//销毁class对象
class_destroy(capture_class);
//从系统删除CDEV对象
cdev_del(&capture_cdev);
//注销字符设备号
unregister_chrdev_region(capture_num, CAPTURE_NUMBER);
}
module_init(plt_drv_init);
module_exit(plt_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("pled_dev");
//在顶层设备树根节点中加入如下节点
gpio_capture@0 {
compatible = "atk,gpio_capture"; /* 用于设备树与驱动进行匹配 */
status = "okay"; /* 状态 */
label = "gpio_capture0"; /* 标签,对应设备文件名 */
input-gpios = <&gpiog 3 GPIO_ACTIVE_LOW>; /* 中断所使用的引脚 */
interrupt-parent = <&gpiog>; /* 节点的父中断控制器 */
interrupts = <3 IRQ_TYPE_EDGE_BOTH>; /* 中断源 */
};
//用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树
//用新的.dtb文件启动系统
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册