GPIO实验(2)——基于rk3308b-cc-plus开发板

本实验目的为用户可以通过应用层程序输入on/off来控制led灯的亮灭。

1.前置知识——简单字符设备的实现

1.1 设备号

1.1.1 主设备号与次设备号

对字符设备的访问是通过文件系统内的设备名称进行的。通常而言,主设备号标识设备对应的驱动程序。次设备号则由内核使用,用于正确确定设备文件所指的设备。你可以通过在/dev目录下利用ls -l查看设备的主次设备号。

dev_t类型用来保存设备编号。它是一个32位的数,其中12位标识主设备号,20位标识次设备号。

MAJOR(dev_t dev);//获取主设备号
MINOR(dev_t dec);//获取次设备号
MKDEV(int major, int minor);//由主次设备号得到设备号

1.1.2 分配和释放设备编号

在建立一个字符设备之前,我们的驱动程序首先要做的事情就是获得一个或者多个设备编号。

int register_chrdev_region(dev_t first, unsigned int count, char *name);//明确知道设备号选它
int alloc_chrdev_region(dev_t dev, unsigned int firstminor, unsigned count, char *name);//不知道就选择它,由内核自动分配主设备号
/*firstminor是要使用的被请求的第一个次设备号一般为0,count是所请求的连续设备编号的个数,name则是设备名称,你会在用户层的应用函数中使用到它*/

1.2 重要数据结构

1.2.1 file_operation

用于建立设备编号与驱动程序操作的连接。被初始化如下。

struct file_operations chr_ops = {
	.owner = THIS_MODULE,
	.open = chr_open,
	.read = chr_read
};//将其中对应的操作绑定到对应函数上

1.2.2 file

代表了一个打开的文件。它由内核在open时创建,并且传递给在该文件上进行操作的所有函数,直到close函数。

1.2.3 inode

内核用inode结构在内部表示文件。对于一个文件来说,可能会有多个表示打开的文件描述符的file结构,但是它们都会指向一个inode结构。该结构中对编写驱动程序代码有用的两个字段如下。

dev_t i_rdev; //该字段包含了真正的设备编号
struct cdev *i_cdev;//struct cdev表示字符设备的内核的内部结构。

1.3 字符设备的注册

当用户想要在运行时获取一个独立的cdev结构,应该如此编写代码

/*首先定义出struct cdev结构体,file_operation结构与设备编号*/
static struct cdev chr_dev;//内核内部使用cdev结构来表示字符设备,内核调用设备前,需要先注册该结构
struct file_operations chr_ops = {
	.owner = THIS_MODULE,
	.open = chr_open,
	.read = chr_read
};
static dev_t ndev;//用来保存设备编号,MAJOR获得主设备号,MINOR获得次设备号

/*然后利用cdev_init函数完成对cdev结构成员的初始化,建立cdev与file_operation的联系*/
cdev_init(&chr_dev, &chr_ops);

/*结构设置好后,利用下面的调用告诉内核该结构的信息*/
alloc_chrdev_region(&ndev, 0, 1, "chr_dev");//动态申请设备号
cdev_add(&chr_dev, ndev, 1);

cdev_add函数可能会失败,一定要对返回值进行检查。

1.4 简单字符设备实现(代码)

open和read函数没有实质内容,可以自己随便写

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/init.h>

//执行顺序:demo_init

static struct cdev chr_dev;
static dev_t ndev;//用来保存设备编号,MAJOR获得主设备号,MINOR获得次设备号

static int chr_open(struct inode *nd, struct file *filp)
{
	int major;
	int minor;

	major = MAJOR(nd->i_rdev); //inode中的i_rdev包含了真正的设备编号
	minor = MINOR(nd->i_rdev);

	printk("chr_open, major = %d, minor = %d\n", major, minor);
	return 0;
}

static ssize_t chr_read(struct file *filp, char __user *u, size_t sz, loff_t *of)
{
	printk("chr_read process!\n");
	return 0;
}

struct file_operations chr_ops = {
	.owner = THIS_MODULE,
	.open = chr_open,
	.read = chr_read
};//将其中对应的操作绑定到对应函数上

static int demo_init(void)
{
	int ret;

	cdev_init(&chr_dev, &chr_ops);//静态内存定义初始化
	ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev");//由内核自动分配主设备号
	if (ret < 0)
		return ret;
	printk("demo_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev));
	ret = cdev_add(&chr_dev, ndev, 1);
	if (ret < 0)
		return ret;
	return 0;
}

static void demo_exit(void)
{
	printk("demo_exit process!\n");
	cdev_del(&chr_dev);
	unregister_chrdev_region(ndev, 1);
}


module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

完成编写后,将编译(一定要用开发板的内核来编译)得到的ko文件传入开发板,使用insmod xx.ko,将该模块加载入内核。

/*应用层程序,打开设备文件,并且测试定义的read和close文件,可以通过dmesg命令查看是否成功执行*/
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define CHAR_DEV_NAME "/dev/chr_dev"

int main(void)
{
	int ret;
	int fd;
	char buf[32];

	fd = open(CHAR_DEV_NAME, O_RDONLY | O_NDELAY);
	if (fd < 0) {
		printf("open failed!\n");
		return -1;
	}

	read(fd, buf, 32);
	close(fd);

	return 0;
}

编译时应当使用开发板的编译环境,否则无法运行。

2. 将gpio申请和字符设备注册结合

2.1 补充知识-read与write的实现核心

unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);

read使用copy_to_user,从设备拷贝数据到用户空间。
write使用copy_from_user,从用户空间拷贝数据到设备。

2.2 补充知识-建立设备(个人理解,不一定正确。。)

1.手动:mknod /dev/dev_name(你的设备名) c(字符设备) major(主设备号) minor(次设备号)

2.自动: 现在/sys/class下创建目录 class_create()
然后在/dev下创建设备 device_create()

2.3 dts文件描述与代码

2.3.1 dts文件

leds {
        	status = "okay";
        	compatible = "gpio-myled";
			gpio-myled = <&gpio0 RK_PB2 GPIO_ACTIVE_HIGH>;
    };

2.3.2 驱动程序

#include <linux/io.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/cdev.h>

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>


static int GPIO_PIN;
static int majorNumber;
static const char *CLASS_NAME = "led_control_class";/*Class 名称,对应/sys/class*/
static const char *DEVICE_NAME = "led_control_demo";/*Device 名称,对应/dev*/

static struct cdev gpio_dev;
static dev_t ndev;
static int gpio_status;
static char recv_msg[20];

static struct class *gpio_control_class;
static struct device *gpio_control_device;

static int gpio_control_open(struct inode *node, struct file *file);
static ssize_t gpio_control_read(struct file *file, char *buf, size_t len, loff_t *offset);
static ssize_t gpio_control_write(struct file *file, const char *buf, size_t len, loff_t *offset);
static int gpio_control_release(struct inode *node, struct file *file);

/*File opertion 结构体,我们通过这个结构体建立应用程序到内核之间操作的映射*/
static struct file_operations file_oprts = {
	.open = gpio_control_open,
	.read = gpio_control_read,
	.write = gpio_control_write,
	.release = gpio_control_release,
};

struct gpio_myled_info
{
	int myled_gpio;
	int gpio_enable_value;
};

static int gpio_myled_probe(struct platform_device *pdev)
{
	enum of_gpio_flags flag;
	struct gpio_myled_info *gpio_info;
	struct device_node *gpio_myled_node = pdev->dev.of_node;

	printk("Myled GPIO Test Program Probe\n");

	gpio_info = devm_kzalloc(&pdev->dev, sizeof(struct gpio_myled_info *),GFP_KERNEL);
	if (!gpio_info)
	{
		dev_err(&pdev->dev, "devm_kzalloc failed!\n");
		return -ENOMEM;
	}

	GPIO_PIN = of_get_named_gpio_flags(gpio_myled_node, "gpio-myled", 0, &flag);
	if (!gpio_is_valid(GPIO_PIN))
	{
		dev_err(&pdev->dev, "gpio-myled: %d is invalid!\n", GPIO_PIN);
		return -ENODEV;
	}
	if (gpio_request(GPIO_PIN, "gpio-myled"))
	{
		dev_err(&pdev->dev, "gpio-myled: %d request failed!\n", GPIO_PIN);
		gpio_free(GPIO_PIN);
		return -ENODEV;
	}

	gpio_info->myled_gpio = GPIO_PIN;
	gpio_info->gpio_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0:1;
	gpio_direction_output(gpio_info->myled_gpio, gpio_info->gpio_enable_value);
	printk("Myled gpio putout\n");

	return 0;
}

static struct of_device_id myled_match_table[] = {
	{ .compatible = "gpio-myled",},
	{},
};

static struct platform_driver gpio_myled_driver = {
	.driver = {
		.name = "gpio-myled",
		.owner = THIS_MODULE,
		.of_match_table = myled_match_table,
	},
	.probe = gpio_myled_probe,
};

/*当用户打开这个设备文件时,调用这个函数*/
static int gpio_control_open(struct inode *node, struct file *file)
{
	printk(KERN_ALERT "GPIO init\n");
	return 0;
}

/*当用户试图从设备空间读取数据时,调用这个函数*/
static ssize_t gpio_control_read(struct file *file, char *buf, size_t len, loff_t *offset)
{
	int cnt = 0;/*将内核空间的数据copy到用户空间*/
	cnt = copy_to_user(buf, &gpio_status, 1);
	if (cnt == 0) {
		return 0;
	} else {
		printk(KERN_ALERT "ERROR occur when reading!!\n");
		return -EFAULT;
	}
	return 1;
}

/*当用户往设备文件写数据时,调用这个函数*/
static ssize_t gpio_control_write(struct file *file, const char *buf, size_t len, loff_t *offset)
{
	/*将用户空间的数据copy到内核空间*/
	int cnt = copy_from_user(recv_msg, buf, len);
	if (cnt == 0) {
		if (memcmp(recv_msg, "on", 2) == 0) {
			printk(KERN_INFO "gpio ON!\n");
			gpio_set_value(GPIO_PIN, 1);
			gpio_status = 1;
		} else if (memcmp(recv_msg, "off", 3) == 0) {
			printk(KERN_INFO "gpio OFF!\n");
			gpio_set_value(GPIO_PIN, 0);
			gpio_status = 0;
		}
	} else {
		printk(KERN_ALERT "ERROR occur when writing!!\n");
		return -EFAULT;
	}
	return len;
}

/*当用户打开设备文件时,调用这个函数*/
static int gpio_control_release(struct inode *node, struct file *file)
{
	printk(KERN_INFO "Release!!\n");
	return 0;
}

static int gpio_myled_init(void)
{
	platform_driver_register(&gpio_myled_driver);
	printk(KERN_ALERT "Driver init\r\n");/*注册一个新的字符设备,返回主设备号*/
	cdev_init(&gpio_dev, &file_oprts);
	if (alloc_chrdev_region(&ndev, 0, 1, DEVICE_NAME) < 0) {
		printk(KERN_ALERT "Register failed!!\r\n");
	return 0;
	}
	if (cdev_add(&gpio_dev, ndev, 1) < 0) {
		printk("add wrong!\n");
		return 0;
	}
	majorNumber = MAJOR(ndev);
	printk(KERN_ALERT "Registe success,major number is %d\r\n", majorNumber);

	/*以CLASS_NAME创建一个class结构,这个动作将会在/sys/class目录创建一个名为CLASS_NAME的目录*/
	gpio_control_class = class_create(THIS_MODULE, CLASS_NAME);
	if (IS_ERR(gpio_control_class)) {
		unregister_chrdev_region(ndev, 1);
		return PTR_ERR(gpio_control_class);
	}

	/*以DEVICE_NAME为名,参考/sys/class/CLASS_NAME在/dev目录下创led建一个设备:/dev/DEVICE_NAME*/
	gpio_control_device = device_create(gpio_control_class, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
	if (IS_ERR(gpio_control_device)) {
		class_destroy(gpio_control_class);
		unregister_chrdev_region(ndev, 1);
		return PTR_ERR(gpio_control_device);
	}
	printk(KERN_ALERT "gpio_control device init success!!\r\n");
	return 0;
}
module_init(gpio_myled_init);

static void gpio_myled_exit(void)
{

	device_destroy(gpio_control_class, MKDEV(majorNumber, 0));
	class_unregister(gpio_control_class);
	class_destroy(gpio_control_class);
	unregister_chrdev_region(ndev, 1);
	platform_driver_unregister(&gpio_myled_driver);
}
module_exit(gpio_myled_exit);

MODULE_AUTHOR("Pato");
MODULE_LICENSE("GPL");

2.3.3 测试代码

#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
static char buf[256] = {1};
int main(int argc,char *argv[])
{
    int fd = open("/dev/led_control_demo",O_RDWR);
    if(fd < 0)
    {
        perror("Open file failed!!!\r\n");
        return -1;
    }
    printf("Please input <on> or <off>:\n");
    scanf("%s",buf);
    if(strlen(buf) > 3){
    	printf("Ivalid input!\n");
    }
    else
    {
        int ret = write(fd,buf,strlen(buf));
        if(ret < 0){
        	perror("Failed to write!!");
        }
    }
     close(fd);
    return 0;
    
 }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: DLT-RK3288B开发板是一款基于RK3288芯片的高性能、低功耗开发板,支持4K视频解码和编码,采用6层PCB设计,具备强大的图形处理能力,适用于人工智能、虚拟现实等领域的开发。 该开发板配备了2GB DDR3 RAM和16GB eMMC内存,在存储和运行性能上表现出色。它支持多种外接设备,如HDMI、Ethernet、USB2.0、USB3.0、RS232、LVDS等接口,使其广泛适用于智能家居、智能电子设备、数字标牌等场景。 除此之外,该开发板还支持Android、Linux、Ubuntu等多种操作系统,具备完善的驱动和软件支持,便于用户进行应用开发。 此外,该开发板还提供了开发板的原理图、数据手册、示例代码和固件等资料,使开发者能够快速上手操作和进行产品开发。 综合来看,DLT-RK3288B开发板结构坚实,性能稳定,功能丰富,适合中小型企业、个人开发者进行软硬件开发,是一款非常优秀的开发板。 ### 回答2: dlt-rk3288b开发板是一款基于Rockchip RK3288芯片的高性能开发板。该板具有强大的性能和极高的功能扩展性,可用于各种应用场景。 该开发板的资料包括硬件资料和软件资料。硬件资料包括原理图、PCB设计、BOM清单、3D模型等,可供用户用于二次开发和定制化生产。软件资料包括系统镜像、驱动程序、SDK等,可供用户快速上手开发和应用。 该开发板支持多种操作系统,如Android、Linux等。用户可根据自己的需求选择适合的操作系统来运行。开发板的性能强大,支持高清视频播放、图像处理、人工智能等多种应用场景。 此外,dlt-rk3288b开发板还具有丰富的接口,包括HDMI、USB、以太网、GPIO、SPI、UART等,可支持各种外围设备的连接和交互。 总之,dlt-rk3288b开发板资料完备,功能强大,可实现多种应用场景。该开发板是开发人员和工程师进行开发和应用的理想选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值