新唐NUC980使用记录:在驱动程序中使用GPIO

目的

GPIO是最基础的外设,使用频率也非常高,有很多外部模块在使用时需要用到GPIO功能,这篇文章将简单体验在NUC980 Liunx驱动程序中使用GPIO功能。

这篇文章中内容均在下面的开发板上进行测试:
《新唐NUC980使用记录:自制开发板(基于NUC980DK61YC)》

开发板中提供了两组共四个直连到GPIO口上的轻触按钮和发光二极管,可以方便地进行GPIO功能测试:
在这里插入图片描述
需要注意的是默认情况下PE10和PE12是被设置成USB相关功能的,可能需要修改内核进行调整:
在这里插入图片描述

这篇文章主要是在下面文章基础上进行的:
《新唐NUC980使用记录:访问以太网(LAN8720A) & 启用SSH》

基础说明

在驱动程序中使用GPIO可以直接操作寄存器,当然新唐官方也对此进行了一定的封装( arch\arm\mach-nuc980\inlcude\mach\gpio.h ),以方便使用。

在程序中加入下面头文件后就可以使用封装好的函数直接操作GPIO口了:

#include <linux/gpio.h>
#include <mach/gpio.h>

可以使用下面函数来操作GPIO口:

gpio_request(NUC980_PC7, "NUC980_PC7"); // 检查GPIO是否正被使用,未使用返回0

gpio_direction_input(NUC980_PC7); // 将GPIO口设置为输入模式(NUC980上电复位后默认均为输入模式)
gpio_direction_output(NUC980_PC7, 1); // 将GPIO口设置为输出模式,并输出高电平

gpio_set_value(NUC980_PC7, 1); // 设置GPIO口输出高电平
gpio_set_value(NUC980_PC7, 0); // 设置GPIO口输出低电平

gpio_get_value(NUC980_PC7); // 读取GPIO口端口电平值

gpio_to_irq(NUC980_PC7); // 获取GPIO中断号

使用示例

基础准备

首先建立相关目录和文件:

cd ~/nuc980-sdk/
mkdir -p drivers/gpio
cd drivers/gpio/

# gpio_dev.c文件为驱动代码、Makefile为驱动编译脚本、gpio_dev_test.c为驱动测试代码
touch gpio_dev.c
touch Makefile
touch gpio_dev_test.c

上面建立的Makefile文件中填入下面内容(编译驱动模块需要用到内核源码,下面需要指定正确的路径):

# 配置内核源码路径
KERNEL_DIR := /home/nx/nuc980-sdk/NUC980-linux-4.4.y

MODULE_DIR := $(shell pwd)

obj-m += gpio_dev.o 

# 以模块方式编译驱动
all:
	$(MAKE) -C $(KERNEL_DIR) M=$(MODULE_DIR) modules 

clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(MODULE_DIR) modules clean 

驱动程序和驱动测试代码写完后可以分别使用下面方式编译拷贝到开发板中进行测试:

# 配置编译工具链
export PATH=$PATH:/home/nx/nuc980-sdk/arm_linux_4.8/bin

# 编译生成驱动模块
make
# 开发板启用了SSH的话可以使用SCP命令将程序通过网络拷贝到开发板中
scp gpio_dev.ko root@192.168.31.142:/root/

# 编译生成开发板的可执行文件并拷贝开发板中
arm-linux-gcc -o gpio_dev_test gpio_dev_test.c
scp gpio_dev_test root@192.168.31.142:/root/

在开发板中使用下面方式进行测试:

# 安装驱动模块
insmod gpio_dev.ko

# 使用测试程序
# ./gpio_dev_test xx xx xx 

# 卸载驱动模块
rmmod gpio_dev

输出与控制

下面驱动程序中控制了PB13,其接了一个输出低电平点亮的LED。
gpio_dev.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#include <linux/gpio.h>
#include <mach/gpio.h>

static int gpio_dev_open(struct inode *node, struct file *file)
{
	return 0;
}

static int gpio_dev_close(struct inode *node, struct file *file)
{
	return 0;
}

static ssize_t gpio_dev_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	return 0;
}

static ssize_t gpio_dev_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	if (*buf == '0')
	{
		gpio_set_value(NUC980_PB13, 0); // 输出高电平

	}
	else if (*buf == '1')
	{
		gpio_set_value(NUC980_PB13, 1); // 输出低电平

	}
	else
	{
		return 1;
	}
	
	return 0;
}

static const struct file_operations gpio_dev_fops = {
	.owner = THIS_MODULE,
	.open = gpio_dev_open,
	.release = gpio_dev_close,
	.read = gpio_dev_read,
	.write = gpio_dev_write,
};

static int major = 0;
static const char *gpio_dev_name = "gpio_dev";
static struct class *gpio_dev_class;
static struct device *gpio_dev_device;

static int __init gpio_dev_init(void)
{
	/* GPIO口设置 */
	if (gpio_direction_output(NUC980_PB13, 1)) // 设置为输出模式,并输出高电平
	{
		return 1; // 设置失败
	}

	major = register_chrdev(0, gpio_dev_name, &gpio_dev_fops); // 注册字符设备,第一个参数0表示让内核自动分配主设备号

	gpio_dev_class = class_create(THIS_MODULE, "gpio_dev_class"); //
	if (IS_ERR(gpio_dev_class))
	{
		unregister_chrdev(major, gpio_dev_name);
		return -1;
	}
	gpio_dev_device = device_create(gpio_dev_class, NULL, MKDEV(major, 0), NULL, gpio_dev_name); // 创建设备节点创建设备节点,成功后就会出现/dev/gpio_dev_name的设备文件
	if (IS_ERR(gpio_dev_device))
	{
		device_destroy(gpio_dev_class, MKDEV(major, 0));
		unregister_chrdev(major, gpio_dev_name);
		return -1;
	}

	return 0;
}

static void __exit gpio_dev_exit(void)
{
	device_destroy(gpio_dev_class, MKDEV(major, 0)); // 销毁设备节点,销毁后/dev/下设备节点文件就会删除
	class_destroy(gpio_dev_class);

	unregister_chrdev(major, gpio_dev_name); // 注销字符设备

	/* GPIO口复位 */
	gpio_direction_input(NUC980_PB13); // 复原为输入模式
}

module_init(gpio_dev_init); // 模块入口
module_exit(gpio_dev_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

gpio_dev_test.c

#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int fd;
	
	/* 判断参数 */
	if (argc != 4)
	{
		printf("Usage: gpio_dev_test <devpath> -w <value>\n"); // 写数据 value = 0 or 1
		return -1;
	}

	/* 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("applog: can not open file %s\n", argv[1]);
		return -1;
	}

	/* 写数据 */
	if (0 == strcmp(argv[2], "-w"))
	{
		write(fd, argv[3], 1);
	}
	else
	{
		close(fd);
		return -1;
	}

	close(fd);

	return 0;
}

在这里插入图片描述
上面改变引脚电平值时,LED会对应亮灭。

输入与读取

下面驱动程序中控制了PE10,其接了一个轻触开关。
gpio_dev.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#include <linux/gpio.h>
#include <mach/gpio.h>

static int gpio_dev_open(struct inode *node, struct file *file)
{
	return 0;
}

static int gpio_dev_close(struct inode *node, struct file *file)
{
	return 0;
}

static ssize_t gpio_dev_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int value, ret;
	value = gpio_get_value(NUC980_PE10); // 读数据
	ret = copy_to_user(buf, &value, 1); // 从内核空间拷贝数据到用户空间
	return ret;
}

static ssize_t gpio_dev_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	return 0;
}

static const struct file_operations gpio_dev_fops = {
	.owner = THIS_MODULE,
	.open = gpio_dev_open,
	.release = gpio_dev_close,
	.read = gpio_dev_read,
	.write = gpio_dev_write,
};

static int major = 0;
static const char *gpio_dev_name = "gpio_dev";
static struct class *gpio_dev_class;
static struct device *gpio_dev_device;

static int __init gpio_dev_init(void)
{
	/* GPIO口设置 */
	if (gpio_direction_input(NUC980_PE10)) // 设置为输入模式
	{
		return 1; // 设置失败
	}

	major = register_chrdev(0, gpio_dev_name, &gpio_dev_fops); // 注册字符设备,第一个参数0表示让内核自动分配主设备号

	gpio_dev_class = class_create(THIS_MODULE, "gpio_dev_class"); //
	if (IS_ERR(gpio_dev_class))
	{
		unregister_chrdev(major, gpio_dev_name);
		return -1;
	}
	gpio_dev_device = device_create(gpio_dev_class, NULL, MKDEV(major, 0), NULL, gpio_dev_name); // 创建设备节点创建设备节点,成功后就会出现/dev/gpio_dev_name的设备文件
	if (IS_ERR(gpio_dev_device))
	{
		device_destroy(gpio_dev_class, MKDEV(major, 0));
		unregister_chrdev(major, gpio_dev_name);
		return -1;
	}

	return 0;
}

static void __exit gpio_dev_exit(void)
{
	device_destroy(gpio_dev_class, MKDEV(major, 0)); // 销毁设备节点,销毁后/dev/下设备节点文件就会删除
	class_destroy(gpio_dev_class);

	unregister_chrdev(major, gpio_dev_name); // 注销字符设备

	/* GPIO口复位 */
	gpio_direction_input(NUC980_PE10); // 复原为输入模式
}

module_init(gpio_dev_init); // 模块入口
module_exit(gpio_dev_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

gpio_dev_test.c

#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int fd, value;

	/* 判断参数 */
	if (argc != 3)
	{
		printf("Usage: gpio_dev_test <devpath> -r\n"); // 读数据
		return -1;
	}

	/* 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("applog: can not open file %s\n", argv[1]);
		return -1;
	}

	/* 读数据 */
	if (0 == strcmp(argv[2], "-r"))
	{
		read(fd, &value, 1);
		printf("applog: read - %d\n", value);
	}
	else
	{
		close(fd);
		return -1;
	}

	close(fd);

	return 0;
}

在这里插入图片描述
上面演示中当按钮处于按下和松开状态时读取到的数据不同。

输入中断

下面驱动程序中控制了PE10,其接了一个轻触开关。
gpio_dev.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>

// #include <linux/interrupt.h>
// #include <linux/irq.h>
#include <linux/gpio.h>
#include <mach/gpio.h>

static int gpio_dev_open(struct inode *node, struct file *file)
{
	return 0;
}

static int gpio_dev_close(struct inode *node, struct file *file)
{
	return 0;
}

static ssize_t gpio_dev_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int value, ret;
	value = gpio_get_value(NUC980_PE10); // 读数据
	ret = copy_to_user(buf, &value, 1);	 // 从内核空间拷贝数据到用户空间
	return ret;
}

static ssize_t gpio_dev_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	return 0;
}

static const struct file_operations gpio_dev_fops = {
	.owner = THIS_MODULE,
	.open = gpio_dev_open,
	.release = gpio_dev_close,
	.read = gpio_dev_read,
	.write = gpio_dev_write,
};

static int major = 0;
static const char *gpio_dev_name = "gpio_dev";
static struct class *gpio_dev_class;
static struct device *gpio_dev_device;

static int irqno;
static int dev_id = 138;

// 中断回调函数
static irqreturn_t IntHandler(int irq, void *dev_id)
{	
	int value;
	value = gpio_get_value(NUC980_PE10); // 读数据
	printk("modlog: irq = %d, dev_id = %d, value = %d\n", irq, *(int *)dev_id, value);
	return IRQ_HANDLED;
}

static int __init gpio_dev_init(void)
{
	/* GPIO口设置 */
	gpio_direction_input(NUC980_PE10); // 设置为输入模式
	irqno = gpio_to_irq(NUC980_PE10); // 获取中断号
	request_irq(irqno, IntHandler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "NUC980_PE10", (void *)&dev_id); // 设置上升沿和下降沿中断

	major = register_chrdev(0, gpio_dev_name, &gpio_dev_fops); // 注册字符设备,第一个参数0表示让内核自动分配主设备号

	gpio_dev_class = class_create(THIS_MODULE, "gpio_dev_class"); //
	if (IS_ERR(gpio_dev_class))
	{
		unregister_chrdev(major, gpio_dev_name);
		return -1;
	}
	gpio_dev_device = device_create(gpio_dev_class, NULL, MKDEV(major, 0), NULL, gpio_dev_name); // 创建设备节点创建设备节点,成功后就会出现/dev/gpio_dev_name的设备文件
	if (IS_ERR(gpio_dev_device))
	{
		device_destroy(gpio_dev_class, MKDEV(major, 0));
		unregister_chrdev(major, gpio_dev_name);
		return -1;
	}

	return 0;
}

static void __exit gpio_dev_exit(void)
{
	device_destroy(gpio_dev_class, MKDEV(major, 0)); // 销毁设备节点,销毁后/dev/下设备节点文件就会删除
	class_destroy(gpio_dev_class);

	unregister_chrdev(major, gpio_dev_name); // 注销字符设备

	/* GPIO口复位 */
	free_irq(irqno, (void *)&dev_id); // 释放中断
	gpio_direction_input(NUC980_PE10); // 复原为输入模式
}

module_init(gpio_dev_init); // 模块入口
module_exit(gpio_dev_exit); // 模块出口

MODULE_LICENSE("GPL"); // 模块许可

在这里插入图片描述
上面只演示了输入中断功能,按钮按下和松开时会触发中断打印内核日志,实际使用中还需要考虑按键消抖以及用户应用程序使用等。

总结

NUC980在驱动程序中使用GPIO也不复杂,新唐官方也做了一部分的工作了,当然实际工作中需要根据业务需求结合linux内核逻辑来编写相关代码。对于简单的工作而言其实直接在用户应用程序中处理也可以。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Naisu Xu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值