嵌入式系统驱动初级【8】——设备树

一、起源

减少垃圾代码

减轻驱动开发工作量

驱动代码和设备信息分离

参考Open Fireware设计

用来记录硬件平台中各种硬件设备的属性信息

二、基本组成

两种源文件:

1. xxxxx.dts dts是device tree source的缩写

2. xxxxx.dtsi  dtsi是device tree source include的缩写,意味着这样源文件用于被dts文件包含用

实际使用时,需要把dts文件编译成对应的二进制文件(.dtb文件,dtb是device tree binary的缩写 )便于运行时存放在内存加快读取信息的速度

三、基本语法

dts文件主体内容由多个节点组成

每个节点可以包含0或多个子节点,形成树状关系

每个dts文件都有一个根节点,其它节点都是它的子孙

根节点一般来描述整个开发板硬件平台,其它节点用来表示具体设备、总线的属性信息

各个节点可以有多个属性,每个属性用key-value键值对来表示

节点语法:


 

[label:] node-name[@unit-address] {    

    [properties definitions];    

    [child nodes];

};



label: 可选项,节点别名,为了缩短节点访问路径,后续节点中可以使用  &label 来表示引用指定节点

node-name: 节点名

unit-address: 设备地址,一般填写该设备寄存器组或内存块的首地址

properties definitions:属性定义

child nodes:子节点

属性语法:

[label:] property-name = value;

[label:] property-name;

属性可以无值

有值的属性,可以有三种取值:

1. arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示,空格分隔),用尖括号表示(< >)

2. string(字符串), 用双引号表示(" ")

3. bytestring(1个或多个字节,空格分隔),用方括号表示([])

4. 用,分隔的多值

   

四、特殊节点

## 4.1 根节点

根节点表示整块开发板的信息

```c

#address-cells   // 在子节点的reg属性中, 使用多少个u32整数来描述地址(address)

#size-cells      // 在子节点的reg属性中, 使用多少个u32整数来描述大小(size)

compatible       // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备,即描述其兼容哪些平台                        

model            // 比如有2款板子配置基本一致, 它们的compatible是一样的,那么就通过model来分辨这2款板子

```

## 4.2 /memory

所有设备树文件的必需节点,它定义了系统物理内存的 layout

```

device_type = "memory";

reg             //用来指定内存的地址、大小

```

## 4.3  /chosen

传递内核启动时使用的参数parameter

```

bootargs  //字符串,内核启动参数, 跟u-boot中设置的bootargs作用一样

```

## 4.4 /cpus  多核CPU支持

/cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu

所以 /cpus 中有以下2个属性:

```

#address-cells   // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)

#size-cells      // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) 必须设置为0

```

五、常用属性

## 5.1 phandle

​   数字形式的节点标识,在后续节点中属性值性质表示某节点时,可以引用对应节点

​    如:

```

pic@10000000 {    

    phandle = <1>;    

    interrupt-controller;

};

another-device-node {    

    interrupt-parent = <1>;   // 使用phandle值为1来引用上述节点

};

```

## 5.2 地址   ---------------  重要

reg属性:表示内存区域region,语法:

```

reg = <address1 length1 [address2 length2] [address3 length3]>;

```

#address-cells:reg属性中, 使用多少个u32整数来描述地址(address),语法:

```

#address-cells = <数字>;

```

#size-cells:reg属性中, 使用多少个u32整数来描述大小(size),语法:

```

#size-cells = <数字>;

```

## 5.3 compatible ---------------  重要

驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求,语法:

```

compatible = "字符串1","字符串2",...;

```

## 5.4 中断 ---------------  重要

a. 中断控制器节点用的属性:

interrupt-controller 一个无值空属性用来声明这个node接收中断信号,表示该节点是一个中断控制器

#interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符

b. 中断源设备节点用的属性:

interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的,语法:

```

interrupt-parent = <引用某中断控制器节点>

```

interrupts 一个中断标识符列表,表示每一个中断输出信号,语法:

```

interrupts = <中断号 触发方式>

1 low-to-high 上升沿触发

2 high-to-low 下降沿触发

4 high level  高电平触发

8 low level   低电平触发

```

## 5.5 gpio ---------------  重要

gpio也是最常见的IO口,常用的属性有:

a. 对于GPIO控制器:

gpio-controller,无值空属性,用来说明该节点描述的是一个gpio控制器

#gpio-cells,用来表示要用几个cell描述一个 GPIO引脚

b. 对于GPIO使用者节点:

gpio使用节点的属性

```

xxx-gpio = <&引用GPIO控制器 GPIO标号 工作模式>

工作模式:

1 低电平有效 GPIO_ACTIVE_HIGH

0 高电平有效 GPIO_ACTIVE_LOW

```

## 5.6 属性设置套路

一般来说,每一种设备的节点属性设置都会有一些套路,比如可以设置哪些属性?属性值怎么设置?那怎么知道这些套路呢,有两种思路:

1. 抄类似的dts,比如我们自己项目的平台是4412,那么就可以抄exynos4412-tiny4412.dts、exynos4412-smdk4412.dts这类相近的dts

2. 查询内核中的文档,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平台的i2c属性设置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds这类属性设置方法

六、常用接口

struct device_node  对应设备树中的一个节点

struct property 对应节点中一个属性

## 6.1 of_find_node_by_path

```c

/**

include/of.h

of_find_node_by_path - 通过路径查找指定节点

@path - 带全路径的节点名,也可以是节点的别名

成功:得到节点的首地址;失败:NULL

*/

struct device_node * of_find_node_by_path(const char *path);

```

## 6.2 of_find_property

```c

/*

include/of.h

of_find_property - 提取指定属性的值

@np - 设备节点指针

@name - 属性名称

@lenp - 属性值的字节数

成功:属性值的首地址;失败:NULL

*/

struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);

```

## 6.3 of_get_named_gpio

```c

/**

 * include/of_gpio.h

 * of_get_named_gpio - 从设备树中提取gpio口

 * @np - 设备节点指针

 * @propname - 属性名

 * @index - gpio口引脚标号 

 * 成功:得到GPIO口编号;失败:负数,绝对值是错误码

 */

int of_get_named_gpio(struct device_node *np, const char *propname, int index);

```

## 6.4 irq_of_parse_and_map

```c

/*

    功能:获得设备树中的中断号并进行映射

    参数:node:设备节点

         index:序号

    返回值:成功:中断号  失败:错误码

*/

unsigned int irq_of_parse_and_map(struct device_node *node, int index);

```

## 6.5 读属性值

of_property_read_string

```c

/*

of_property_read_string - 提取字符串(属性值)

@np - 设备节点指针

@propname - 属性名称

@out_string - 输出参数,指向字符串(属性值)

成功:0;失败:负数,绝对值是错误码

*/

int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);

```

读数值

```c

int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)

int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)

int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)

```

判断属性是否存在

```c

int of_property_read_bool(const struct device_node *np,const char *propname)

```

读数组

```c

int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_value,size_t sz)

```

七、GPIO接口

## 7.1 向内核申请GPIO

int gpio_request(unsigned gpio,const char *label)

功能:其实就是让内核检查一下该GPIO引脚是否被其它设备占用,如果没有占用则返回0并用label做一下标记,表示被本设备占用,否则返回负数



 

void gpio_free(unsigned gpio)

功能:去除本设备对该GPIO的占用标记,表示本设备向内核归还对该GPIO引脚的使用权,此后其它设备可占用该GPIO引脚



 

## 7.2 设置GPIO方向

int gpio_direction_input(unsigned gpio)

int gpio_direction_output(unsigned gpio,int value)

## 7.3 读写GPIO数据

int gpio_get_value(unsigned gpio)

int gpio_set_value(unsigned gpio,int value)



 

# 八、led驱动设备树版



 

1. 在设备树源文件的根节点下添加本设备的节点(该节点中包含本设备用到的资源信息)

   ..../linux3.14/arch/arm/boot/dts/exynos4412-fs4412.dts

```

fs4412-leds {

    compatible = "fs4412,led2-5";

    led2-gpio = <&gpx2 7 0>;

    led3-gpio = <&gpx1 0 0>;

    led4-gpio = <&gpf3 4 0>;

    led5-gpio = <&gpf3 5 0>;

};

```

 2. 在linux内核源码的顶层目录下执行:make dtbs  (生成对应的dtb文件)

 3. cp   ?????.dtb   /tftpboot

 4. 编写驱动代码:

    a. 通过本设备在设备树中的路径找到对应节点(struct device_node类型的地址值)

    b. 调用 of_get_named_gpio 函数得到某个GPIO的编号

    c. struct leddev结构体中记录所有用到的GPIO编号

    d. 使用某个GPIO引脚前需先通过gpio_request函数向内核申请占用该引脚,不用该引脚时可通过gpio_free归还给内核

    e. 通过gpio_direction_input和gpio_direction_output函数来设置某个GPIO的作用

    f. 通过gpio_get_value函数可以获取某个GPIO引脚的当前电平

    g.  通过gpio_set_value函数可以改变某个GPIO引脚的电平

dtb_led_dev.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/of.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>

#include "led_dev.h"

int major = 11, minor = 0, num = 1;

struct myled_dev
{
	struct cdev mydev;

	unsigned int led2gpio;
	unsigned int led3gpio;
	unsigned int led4gpio;
	unsigned int led5gpio;
};

struct myled_dev *pgmydev = NULL;

int myopen(struct inode *pnode, struct file * pfile)
{
	pfile->private_data = (void *) (container_of(pnode->i_cdev, struct myled_dev, mydev));

	return 0;
}

int myclose(struct inode *pnode, struct file * pfile)
{

	return 0;
}

void led_on(struct myled_dev *pmydev,int ledno)
{
	switch(ledno)
	{
	case 2:
		gpio_set_value(pmydev->led2gpio, 1);
		break;
	case 3:
		gpio_set_value(pmydev->led3gpio, 1);
		break;
	case 4:
		gpio_set_value(pmydev->led4gpio, 1);
		break;
	case 5:
		gpio_set_value(pmydev->led5gpio, 1);
		break;
	}
}

void led_off(struct myled_dev *pmydev,int ledno)
{
	switch(ledno)
	{
	case 2:
		gpio_set_value(pmydev->led2gpio, 0);
		break;
	case 3:
		gpio_set_value(pmydev->led3gpio, 0);
		break;
	case 4:
		gpio_set_value(pmydev->led4gpio, 0);
		break;
	case 5:
		gpio_set_value(pmydev->led5gpio, 0);
		break;
	}
}

long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
	struct myled_dev *pmydev = (struct myled_dev *) pfile->private_data;

	if(arg < 2 || arg > 5)
	{
		return -1;
	}
	switch (cmd)
	{
		case MY_LED_ON:
			led_on(pmydev,arg);
			break;
		case MY_LED_OFF:
	 		led_off(pmydev,arg);
	 		break;
		default:
	 		return -1;
	}
	return 0;
}



void request_leds_gpio(struct myled_dev *pmydev, struct device_node *pnode)
{
	pmydev->led2gpio = of_get_named_gpio(pnode, "led2-gpio", 0);
	gpio_request(pmydev->led2gpio, "led2");

	pmydev->led3gpio = of_get_named_gpio(pnode, "led3-gpio", 0);
	gpio_request(pmydev->led3gpio, "led3");

	pmydev->led4gpio = of_get_named_gpio(pnode, "led4-gpio", 0);
	gpio_request(pmydev->led4gpio, "led4");

	pmydev->led5gpio = of_get_named_gpio(pnode, "led5-gpio", 0);
	gpio_request(pmydev->led5gpio, "led5");
}

void free_leds_gpio(struct myled_dev *pmydev)
{
	gpio_free(pmydev->led2gpio);
	gpio_free(pmydev->led3gpio);
	gpio_free(pmydev->led4gpio);
	gpio_free(pmydev->led5gpio);
}

void set_leds_gpio_output(struct myled_dev *pmydev)
{
	gpio_direction_output(pmydev->led2gpio, 0);
	gpio_direction_output(pmydev->led3gpio, 0);
	gpio_direction_output(pmydev->led4gpio, 0);
	gpio_direction_output(pmydev->led5gpio, 0);
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = myopen,
	.release = myclose,
	.unlocked_ioctl = myled_ioctl,
};

int __init myled_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major,minor);
	
	struct device_node *pnode = NULL;

	pnode = of_find_node_by_path("/fs4412-leds");
	if(pnode == NULL)
	{
		printk("of_find_node_by_path failed.\n");
		return -1;
	}

	ret = register_chrdev_region(devno, num, "myled");
	if(ret)
	{
		ret = alloc_chrdev_region(&devno, minor, num, "myled");
		if(ret)
		{
			printk("devno failed.\n");
			return -1;
		}
		major = MAJOR(devno);
		minor = MINOR(devno);
	}

	pgmydev = (struct myled_dev *) kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
	if(pgmydev == NULL)
	{
		unregister_chrdev_region(devno, num);
		printk("kmalloc failed.\n");
		return -1;
	}
	memset(pgmydev,0,sizeof(struct myled_dev));

	cdev_init(&pgmydev->mydev,&myops);
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev,devno,num);

	/*ioremap*/
	request_leds_gpio(pgmydev,pnode);

	/*set gpio output*/
	set_leds_gpio_output(pgmydev);

	return 0;
}

void __exit myled_exit(void)
{
	dev_t devno = MKDEV(major,minor);

	free_leds_gpio(pgmydev);

	cdev_del(&pgmydev->mydev);

	unregister_chrdev_region(devno, num);

	kfree(pgmydev);
	pgmydev = NULL;

}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("imysy_22");

module_init(myled_init);
module_exit(myled_exit);



  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【目  录】: 第1篇系统篇 第1章嵌入系统概述3 1.1嵌入系统的定义和特点3 1.1.1嵌入系统的定义3 1.1.2嵌入系统和通用计算机比较4 1.1.3嵌入系统的特点5 1.2嵌入系统的硬件6 1.2.1嵌入处理器7 1.2.2嵌入存储器15 1.2.3嵌入I/O设备18 1.2.4嵌入I/O接口18 1.3嵌入系统的软件21 1.3.1无操作系统嵌入软件22 1.3.2带操作系统嵌入软件24 1.4嵌入系统的分类27 1.4.1按硬件(嵌入处理器)划分27 1.4.2按软件复杂度划分27 1.4.3按实时性划分28 1.4.4按使用对象划分28 1.5嵌入系统的应用28 1.5.1国防军事28 1.5.2工业控制29 1.5.3消费电子30 1.5.4办公自动化产品30 1.5.5网络和通信设备30 1.5.6汽车电子31◆嵌入系统原理及应用目录1.5.7金融商业31 1.5.8生物医学32 1.5.9信息家电32 1.6本章小结34 习题134 第2章嵌入系统开发35 2.1嵌入系统的开发环境、开发工具和调试方35 2.1.1嵌入系统的开发环境35 2.1.2嵌入系统的开发工具37 2.1.3嵌入系统的调试方43 2.2嵌入系统的开发语言50 2.2.1嵌入硬件开发语言50 2.2.2嵌入软件开发语言51 2.3嵌入系统的开发过程53 2.3.1需求分析54 2.3.2系统设计55 2.3.3系统实现61 2.3.4系统测试70 2.3.5系统发布73 2.4嵌入开发工程师之路74 2.4.1嵌入行业和人才的现状分析74 2.4.2嵌入开发工程师的能力要求74 2.4.3嵌入开发工程师的进阶之路75 2.5本章小结77 习题278 第2篇内核篇 第3章ARM CortexM3处理器81 3.1ARM CortexM3组成结构81 3.1.1CortexM3内核82 3.1.2调试系统84 3.2ARM CortexM3总线接口86 3.2.1CortexM3总线接口类型87 3.2.2CortexM3总线连接方案88 3.3ARM CortexM3编程模型89 3.3.1工作状态89 3.3.2数据类型89 3.3.3寄存器89 3.3.4指令系统93 3.3.5操作模和特权分级96 3.3.6异常和中断98 3.3.7双堆栈机制105 3.4ARM CortexM3存储器系统107 3.4.1存储器映射107 3.4.2位带操作110 3.4.3存储格112 3.5ARM CortexM3的低功耗模113 3.6本章小结114 习题3115 第4章基于ARM CortexM3的STM32微控制器117 4.1从CortexM3到基于CortexM3的MCU117 4.2基于CortexM3的STM32系列微控制器概述118 4.2.1产品线118 4.2.2命名规则124 4.2.3生态系统125 4.2.4开发方法131 4.2.5学习之路134 4.3STM32F103微控制器基础136 4.3.1概述136 4.3.2主系统结构137 4.3.3功能模块139 4.3.4引脚定义140 4.3.5存储器组织141 4.4STM32F103微控制器的最小系统145 4.4.1电源电路145 4.4.2时钟电路148 4.4.3复位电路149 4.4.4调试和下载电路150 4.4.5其他151 4.5STM32F103微控制器的时钟系统153 4.5.1输入时钟153 4.5.2系统时钟155 4.5.3由系统时钟分频得到的其他时钟155 4.5.4STM32F10x时钟系统相关库函数157 4.6STM32F103微控制器的低功耗模162
### 回答1: 嵌入系统设计与应用是指在特定应用领域中,使用嵌入处理器和相关硬件资源,结合嵌入操作系统和软件开发工具,设计和开发满足特定需求的嵌入系统。 ARM Cortex-A8是一款高性能的嵌入处理器,广泛应用于手机、平板电脑、智能电视等嵌入设备中。它具有强大的计算能力和低功耗特性,能够提供快速、高效的数据处理和多任务处理能力。 Linux是一种开源的嵌入操作系统,提供了丰富的软件资源和开发工具,可以满足各种嵌入应用的需求。在使用ARM Cortex-A8和Linux进行嵌入系统设计和应用时,可以借助Linux的强大功能和丰富的软件生态系统,快速开发出满足特定需求的嵌入应用。 设计和开发嵌入系统时,首先需要选择合适的硬件平台和操作系统。选择ARM Cortex-A8作为处理器可以得到高性能和低功耗的优势,而选择Linux作为操作系统可以借助其丰富的软件资源和开发工具。 然后,根据具体的嵌入应用需求,对系统进行架构设计和软件模块划分。在嵌入系统设计中,需要考虑系统的实时性、功耗控制、硬件接口与外设的驱动、应用程序的开发等方面。 在应用开发阶段,可以使用C/C++等编程语言,结合相应的开发工具,编写应用程序和驱动程序。同时,可以借助Linux的丰富资源,如网络协议栈、文件系统、数据库等,快速实现系统的功能。 最后,在系统调试和测试阶段,可以使用调试工具和仿真平台进行系统性能测试和调试,以确保系统的稳定性和可靠性。 综上所述,嵌入系统设计与应用基于ARM Cortex-A8和Linux可以提供高性能、低功耗和丰富软件资源的优势,能够快速开发出满足特定需求的嵌入应用。 ### 回答2: 嵌入系统设计与应用是指将计算机系统嵌入到特定的电子设备中,以完成特定的功能。基于ARM Cortex-A8和Linux的嵌入系统设计与应用是指利用ARM Cortex-A8处理器和Linux操作系统来设计和开发嵌入系统。 ARM Cortex-A8是一种高性能、低功耗的32位RISC处理器。它采用精简指令集架构,具有较高的运算能力和较低的能耗。Cortex-A8处理器广泛应用于嵌入领域,可用于智能手机、平板电脑、汽车导航系统等各种嵌入设备。 Linux是一种开源的操作系统内核,具有广泛的硬件支持和强大的软件生态系统。在嵌入系统设计中,Linux提供了丰富的功能和驱动支持,能够提供稳定可靠的操作环境。同时,Linux还可以方便地进行定制和扩展,以满足各种应用需求。 在基于ARM Cortex-A8和Linux的嵌入系统设计中,我们可以利用Linux提供的运行时库、工具链以及开发环境来进行系统开发。可以利用C/C++编程语言来进行应用程序的开发,使用Linux提供的设备驱动程序来进行硬件的控制和交互。同时,我们还可以利用Linux的网络支持和文件系统功能来实现网络连接和数据存储。 综上所述,基于ARM Cortex-A8和Linux的嵌入系统设计与应用具有高性能、低功耗、可定制和可扩展等优势,可以适用于各种嵌入设备的开发和应用。它在智能手机、平板电脑、汽车导航系统等领域具有广泛的应用前景。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值