驱动(RK3588S)第六课时:linux2.6的使用与GPIO子系统的使用

一、Linux2.6 字符设备驱动编写框架

Linux2.6 他是咱们字符设备驱动编写的第二种方法,这种方法要比杂项的复杂,过程和函数是比较多的,他最大的优点就在于他的资源很多 — 设备号,而杂项设备号是非常有限的,也就是 0 — 255 因为主设备号是固定是 10 。但是Linux2.6 他使用一个 32 位的数字表示设备号,这个设备号里包含了主设备号和次设备号,32 位他做一个划分,前 12 位是主设备号,后 20 位是次设备
号,因此他的设备号的取值范围是比较大的。主设备号的范围:2^12 次设备号的范围:2^20, 他使用主设备号和次设备号合成了一个完整的设备号。

1、合成一个完整的设备号函数

函数头文件:#include <linux/cdev.h>
函数参数:
ma: — 主设备号
mi: ---- 次设备号
函数返回值:合成之后的完整的设备号
函数功能:合成一个完整的设备号
函数原型:MKDEV(ma,mi)

2、从完整的设备号里提取主设备号

函数头文件:#include <linux/cdev.h>
函数参数:dev :设备号
函数返回值:提取的主设备,提取的次设备
函数功能:从完整的设备号里提取主设备号
函数原型:MAJOR(dev)
MINOR(dev)

3、动态申请设备号

**函数头文件:#include <linux/fs.h>
函数参数:dev:保存申请成功的设备号
baseminor:次设备号起始值
count:连续申请次设备号的数量
name:设备的名字 — 无所谓
函数返回值:成功返回 0 失败负数
函数功能:动态申请设备号
函数原型:int alloc_chrdev_region(
dev_t dev,
unsigned baseminor,
unsigned count,
const char name)

4、静态申请设备号

*函数功能:静态申请设备号
函数原型:int register_chrdev_region(
dev_t from,
unsigned count,
const char name)
函数头文件:同上
函数参数:from:是你自己提前使用 MKDEV 函数合成一个完整的设备号
count:申请的次设备号的数量
name:设备的名字 — 无所谓
函数返回值:成功返回 0 失败负数

5、释放申请的设备号

函数功能:释放申请的设备号
函数原型:void unregister_chrdev_region(
dev_t from,
unsigned count)
函数头文件:同上
函数参数:from:设备号
count:申请的次设备号的数量
函数返回值:无

6、Linux2.6 字符设备驱动的核心结构体

**这个结构体一般只需要咱们定义一个结构体变量即可。
struct cdev {
struct kobject kobj;
struct module owner;//代表这个模块 THIS_MODULE
const struct file_operations ops;//操作设备集合的方法
struct list_head list;
dev_t dev;//设备号
unsigned int count;//次设备号的数量
} __randomize_layout;

7、初始化核心结构体

**函数功能:初始化核心结构体
函数原型:void cdev_init(
struct cdev cdev,
const struct file_operations fops)
函数头文件:#include<linux/cdev.h>
函数参数:cdev:定义的核心结构体
fops:定义操作设备方法集合的结构体变量
函数返回值:无

8、向内核去申请 linux2.6 字符设备

*函数功能:向内核去申请 linux2.6 字符设备
函数原型:int cdev_add(
struct cdev p,
dev_t dev,
unsigned count)
函数头文件:#include<linux/cdev.h>
函数参数:p:定义的核心结构体
dev:设备号
count:次设备号的数量
函数返回值:成功返回 0 失败负数

9、释放申请的设备

*函数功能:释放申请的设备
函数原型:void cdev_del(struct cdev p)
函数头文件:#include<linux/cdev.h>
函数参数:p:定义的核心结构体
函数返回值:无

10、创建一个类,去管理你注册的设备 /sys/class/name

**函数功能:创建一个类,去管理你注册的设备
函数原型:struct class * class_create(
struct module owner,
const char name)
函数头文件:#include<linux/device.h>
函数参数:owner:他是一个固定的值 THIS_MODULE
name:创建类的名字
函数返回值:成功返回指向 struct class 失败 NULL

11、自动创建设备节点

**函数功能:自动创建设备节点
函数原型:struct device *device_create(
struct class *class,
struct device *parent,
dev_t devt,
void drvdata,
const char fmt,…)
函数头文件:#include<linux/device.h>
函数参数:class:创建的类
parent:父设备 — 写 NULL
devt:设备号
drvdata:内核的私有数据 — 写 NULL
fmt:他一般就是你创建的设备节点的名字
函数返回值:成功返回一个指向 struct device 失败 NULL

12、销毁类

*函数功能:销毁类
函数原型:void class_destroy(struct class cls)
函数头文件:#include<linux/device.h>
函数参数:cls:就是定义的类的变量名
函数返回值:无

13、销毁设备节点,节点位于 /dev/name

*函数功能:销毁设备节点 /dev/name
函数原型:void device_destroy(struct class class,dev_t devt)
函数头文件:#include<linux/device.h>
函数参数:class:定义的类名
devt:设备号

二、GPIO子系统的使用

所谓的 gpio 子系统值的就是使用内核封装好的函数对设备进行操作,这里一般操
作设备都是通过 gpio 口,因此咱们需要掌握操作 gpio 口函数。在使用 GPIO 口区操作硬件的时候,你需要先申请注册才能使用当前的 gpio 口的资源。我以下检测是灯,高电平亮,低电平不亮。
下面演示 GPIO1_D0 pin 脚计算方法:
bank = 1; //GPIO1_D0 => 1, bank ∈ [0,4]
group = 3; //GPIO1_D0 => 3, group ∈ {(A=0), (B=1), (C=2), (D=3)}
X = 0; //GPIO1_D0 => 0, X ∈ [0,7]
number = group * 8 + X = 3 * 8 + 0 = 24
pin = bank*32 + number= 1 * 32 + 24 = 56;
最后要得到的是pin

1、申请你要使用 gpio 口的资源

*函数功能:申请你要使用 gpio 口的资源
函数原型: int gpio_request(unsigned gpio, const char label)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
label:标签 — 一般没有太大作用,就是标识
函数返回值:成功返回 0 失败负数

2、释放gpio口资源

函数功能: 释放 gpio 口资源
函数原型: void gpio_free(unsigned gpio)
函数头文件: #include <linux/gpio.h>
函数参数:gpio:gpio:这里就是你要申请注册的 gpio 口的编号
函数返回值:无

3、配置 gpio 口的工作模式为输出

函数功能:配置 gpio 口的工作模式为输出
函数原型: int gpio_direction_output(unsigned gpio, int value)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
value:默认给的值 — 一般就是 0 或者是 1 代表高低电平
函数返回值:成功返回 0 失败负数

4、配置 gpio 口的工作模式为输入

函数功能: 配置 gpio 口的工作模式为输入
函数原型: int gpio_direction_input(unsigned gpio)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
函数返回值:成功返回 0 失败负数
这里配置的 gpio 口模式为输入和输出那么这里的输入和输出是针对于 CPU 来说的。

5、配置 gpio 口的工作模式为输入

函数功能:获取这个 gpio 口引脚上的电平的状态
函数原型: int gpio_get_value(unsigned gpio)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
函数返回值:返回获取的电平的状态 — 高电平或者是低电平 1/0

6、 设置 gpio 的电平状态

函数功能: 设置 gpio 的电平状态
函数原型: void gpio_set_value(unsigned gpio, int value)
函数头文件:#include <linux/gpio.h>
函数参数:gpio:这里就是你要申请注册的 gpio 口的编号
value:你要设置的电平的状态 — 高电平 1 低电平 0
函数返回值:无

三、代码实现

linux26.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
dev_t dev;
struct cdev mydev;
struct class *myclass=NULL;
int gpio_value = 0;
int myled_open (struct inode *inode, struct file *fp)
{
	gpio_set_value(21,1);
	printk("myled open ok\n");
	printk("myled open 正确打开\n");
	return 0;
}

int myled_close (struct inode *inode, struct file *fp)
{
	gpio_set_value(21,0);
	printk("myled close ok\n");
	printk("myled close 关闭正确\n");
	return 0;
}
struct file_operations myfops={
	.open = myled_open,
	.release = myled_close,

};
static int __init myled_init(void)
{	
	int all;
	gpio_value=gpio_request(21, "led5");
	printk("gpio_value:%d\n",gpio_value);
	if(gpio_value==0)
	{
		printk("申请成功\n");
	}
	gpio_direction_output(21, 1);
	all=alloc_chrdev_region(&dev,0, 1,"led");
	if(all<0)
	{
		printk("alloc_chrdev_region error\n");
		printk("动态创建失败\n");
		return -1;
	}
	printk("主设备号:%d\n",MAJOR(dev));
	printk("次设备号:%d\n",MINOR(dev));
	cdev_init(&mydev,&myfops);
	cdev_add(&mydev,dev,1);
	myclass=class_create(THIS_MODULE,"class_led");
	if(myclass == NULL)
	{
		printk("class_create error\n");
		printk("class_create 类创建失败\n");
		return -1;
	}
	device_create(myclass,NULL,dev,NULL,"myled");
	return 0;
}
static void __exit myled_exit(void)
{
	device_destroy(myclass,dev);
	class_destroy(myclass);
	cdev_del(&mydev);
	unregister_chrdev_region(dev,1);
	gpio_free(21);
}

module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

app.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	int fd = 0;
	if(argc < 2)
	{
		printf("请输入正确的参数\n");
		return -1;
	}
	while(1)
		{
			fd = open(argv[1],O_RDWR); // --- 底层的open函数
			sleep(5);
			close(fd);//底层的close
			sleep(5);
		}
	return 0;
}

Makefile

obj-m += linux26.o #最终生成模块的名字就是 led.ko      
    
KDIR:=/home/stephen/RK3588S/kernel  #他就是你现在rk3588s里内核的路径 
    
CROSS_COMPILE_FLAG=/home/stephen/RK3588S/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
    #这是你的交叉编译器路径 --- 这里你也要替换成你自己的交叉编译工具的路径
all:
	make -C $(KDIR) M=$(PWD) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE_FLAG)
	aarch64-none-linux-gnu-gcc app.c -o app
    #调用内核层 Makefile 编译目标为 modules->模块 文件在当前路径
    # 架构  ARCH=arm64 
clean:
	rm -f  *.o *.mod.o *.mod.c *.symvers *.markers *.order app  *.mod

编译之后将app和linux.ko推送金开发板中。
在这里插入图片描述
开发板中的现象:
在这里插入图片描述
灯现象:
在这里插入图片描述
在这里插入图片描述

  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值