1、为什么使用设备树?
使用设备树开发可以提高我们的代码通用性,我们可以设备树中添加硬件的信息,当我们驱动硬件的时候,我们可以从设备树中获取硬件的信息进行开发,如果我们需要修改硬件信息的话,直接去修改设备树,而驱动硬件的代码不用修改,这样即使换个芯片,代码依旧可用。
2、设备树的概念
设备树本质上是一个文本文件,包含了芯片的各种信息。
设备树里面所编写的设备的信息是节点,节点里面的内容才是设备树关键。这里面的内容我们称之为属性!
属性的内容一共就分为两大类:数字属性、字符串属性节点的构成:
节点名 {
属性名 = 属性值;
};
其中属性值是 数字需要用 <> 括起来
属性值是 字符串 需要 "" 引起来
3、设备树常见的节点和属性
节点:
- 根节点:所有节点必须写入到根节点
- modle节点:提示当前板子信息
- alises节点:启动的顺序
- chosenj节点:设备树给内核的传参
属性:
- status: 节点信息的使能 (disabled/okay)
- compatible:内核的驱动来匹配设备树信息的关键点
- reg:传递寄存器的信息
4、常用函数
- 通过名字获取设备树的节点结构体
函数头文件: <linux/of.h>
函数的原型:
struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
函数的参数:
from:从哪里寻找节点,一般这个地方填 NULL->代表从 /节点出发寻找!
name:查找节点名字
函数返回值:就是设备树节点 在内核抽象的结构体- 通过路径获取节点
函数头文件: <linux/of.h>
函数的原型:
struct device_node *of_find_node_by_path(const char *path)
函数的参数:
path:你要寻找的节点的路径!从 /路径出发
函数返回值:就是设备树节点 在内核抽象的结构体- 获取节点属性的字符串属性值
函数头文件: <linux/of.h>
函数的原型: int of_property_read_string(
const struct device_node *np,
const char *propname,
const char **out_string
);
函数的参数:
np:你要提供你想获取属性的 节点结构体
propname:你提供你想获取属性名
out_string:会把获取得到的属性值写入到此处!
函数返回值:获取属性成功返回 0,获取属性失败返回 非 0- 读取一个属性值是整形 u32 类型的属性
函数头文件:<linux/of.h>
函数的原型:
int of_property_read_u32(
const struct device_node *np,
const char *propname,
u32 *out_value
)
函数的参数:
np:设备节点结构体
propname :你要获取属性的属性名
out_value:把获取得到的属性返回于此
函数返回值:获取属性成功返回 0,获取属性失败返回 非 0- 通过接口来获取设备树的 GPIO 的属性
函数头文件: <linux/of_gpio.h>
函数的原型:
int of_get_named_gpio_flags(
struct device_node *np,
const char *list_name,
int index,
enum of_gpio_flags *flags
);
函数的参数:
np:设备树的节点
list_name:你要获取 GPIO 属性值的 属性名
index:索引号 多个引脚的时候,索引号代表你去获取不同的引脚!
flags:获取完毕引脚后 会把引脚有效电平填入其中
函数返回值:就是我们的 GPIO 的编号
5、使用设备树实现流水灯
设备树代码
led_all {
status = "okay";
led_gpio = <&gpio0 RK_PC5 GPIO_ACTIVE_LOW>,
<&gpio0 RK_PC6 GPIO_ACTIVE_LOW>,
<&gpio0 RK_PD0 GPIO_ACTIVE_LOW>,
<&gpio1 RK_PD5 GPIO_ACTIVE_LOW>;
};
设备代码
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/miscdevice.h"
#include "linux/fs.h"
#include "linux/gpio.h"
#include "linux/cdev.h"
#include "linux/of.h"
#include "linux/of_gpio.h"
struct device_node *mynode;
int gpio_num[10] = {0};
int count = 0;
enum of_gpio_flags active_flag[10] = {0};
dev_t mydevnum;
struct cdev mycdev;
struct file_operations myoper;
struct class *my_class;
//功能函数
int led_open(struct inode *i, struct file *f)
{
for (int i = 0; i < count; i++)
{
gpio_set_value(gpio_num[i],!active_flag[i]);
}
return 0;
}
int led_close(struct inode *i, struct file *f)
{
for (int i = 0; i < count; i++)
{
gpio_set_value(gpio_num[i],active_flag[i]);
}
return 0;
}
//加載函數
static int __init mybeep_init(void)
{
//获取设备数节点
mynode = of_find_node_by_path("/led_all");
if (mynode == NULL)
{
/* code */
printk("没有此节点\r\n");
return -EINVAL;
}
//判断此节点是否使能
const char * retstr;
of_property_read_string(mynode,"status",&retstr);
if (strcmp(retstr,"okay") != 0)
{
/* code */
printk("此节点不能使用\r\n");
return -EINVAL;
}
//获取GPIO信息
while (1)
{
gpio_num[count] = of_get_named_gpio_flags(mynode,"led_gpio",count,&active_flag[count]);
if (gpio_num[count] < 0)
{
/* code */
break;
}
count++;
}
//初始化GPIO
for (int i = 0; i < count; i++)
{
gpio_request(gpio_num[i],"RK_led");
gpio_direction_output(gpio_num[i],active_flag[i]);
}
//申请设备号
alloc_chrdev_region(&mydevnum,10,1,"myleddevice");
//初始化Linux2.6核心结构体
myoper.owner = THIS_MODULE;
myoper.open = led_open;
myoper.release = led_close;
cdev_init(&mycdev,&myoper);
//添加设备
cdev_add(&mycdev,mydevnum,1);
//添加设备文件
my_class = class_create(THIS_MODULE,"myledall");
//生成设备文件
device_create(my_class,NULL,mydevnum,NULL,"myledall");
return 0;
}
static void __exit mybeep_exit(void)
{
device_destroy(my_class,mydevnum);
class_destroy(my_class);
cdev_del(&mycdev);
unregister_chrdev_region(mydevnum,1);
}
module_init(mybeep_init);
module_exit(mybeep_exit);
MODULE_LICENSE("GPL");
主函数代码
#include "stdio.h"
#include "unistd.h"
#include "fcntl.h"
#include "sys/types.h"
#include <sys/stat.h>
int main()
{
int fd = 0;
while (1)
{
/* code */
fd = open("/dev/myledall", O_RDWR);
sleep(1);
close(fd);
sleep(1);
}
return 0;
}
6、出现的错误
内核编译出错。原因是因为节点{}后没加;需要注意的是,一个使用多个属性时要用,隔开最后再加;