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

Posted Naisu Xu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了新唐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);

以上是关于新唐NUC980使用记录:在驱动程序中使用GPIO的主要内容,如果未能解决你的问题,请参考以下文章

新唐NUC980使用记录:向内核添加USB无线网卡驱动(基于RTL8188EUS)

新唐NUC980使用记录:开发环境准备与编译配置基础说明

新唐NUC980使用记录:使用wpa_supplicant访问无线网络

新唐NUC980使用记录:自制开发板(基于NUC980DK61YC)

新唐NUC980使用记录:访问以太网(LAN8720A) & 启用SSH

新唐NUC980使用记录:基础说明与资料索引