【Linux】设备树

设备树简介

我们前面介绍过平台设备驱动,知道硬件资源信息可以放在设备中,然后在驱动的probe函数中从设备中获取资源信息。但是,Linux3.x以后的版本引入了设备树,设备树用于描述一个硬件平台的硬件资源,一般描述那些不能动态探测到的设备,可以被动态探测到的设备是不需要描述。设备树可以被bootloader(uboot)传递到内核,内核可以从设备树中获取硬件信息。

设备树描述硬件资源时有两个特点:

  • 以树状结构描述硬件资源。
  • 设备树源文件可以像头文件(.h文件)那样,一个设备树文件引用用一个设备树文件,这样可以实现代码的重用。例如多个硬件平台都使用rk系列处理器作为主控芯片, 那么我们可以将rk系列芯片的硬件资源写到一个单独的设备树文件里面一般使用.dtsi后缀, 其他板级设备树文件直接使用# include "xxx.dtsi"引用即可。

设备树框架

设备树由一系列被命名的节点(node)和属性(property)组成。

/dts-v1/; //需要的DTS 文件版本说明
#include "example.dtsi"; //包含头文件 可以是.dtsi .dts .h文件等
/ { //根节点 node1-name@unit-address { //节点1, 名称是"node1-name", 单元地址和reg属性的第一个地址一致 compatible = "xxx, xxxx";  a-string-property = "A string"; //节点属性和属性值, 是字符串 a-string-list-property = "first string", "second string";  a-byte-data-property = <0x00 0x13 0x24 0x36>;  label:child-node1 { //节点1的子节点名 "label" 是标签 first-child-property;  second-child-property = <1>;  a-string-property = "Hello, world";  };  child-node2 { //子节点2 };  };  node2-name { //节点2 an-empty-property;  a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */  node1 { //节点1的子节点 my-cousin = <&cousin>;  };  };
}; 
设备节点命名

设备节点的命名方式有三种。

  • node-name{};: node-name是结点名称。
  • node-name@unit-address{}; :其中的符号@可以理解为是一个分割符,unit-address用于指定“单元地址”, 它的值要和节点reg属性的第一个地址一致。如果节点没有reg属性值,可以直接省略@unit-address。同级别的子节点的node-name可以相同,但是要求unit-address不同。
  • label: node-name@unit-address{}; :label是节点标签,可以使用&label快捷地访问节点。

注意,根节点没有节点名,它直接使用“/”指代这是一个根节点。

特殊的设备节点
  • aliases节点:用于保存其他节点的别名。
  • chosen节点:该节点并不是一个真的设备,它的主要功能是帮助uboot向内核传递数据,最主要的参数是bootargs参数。
追加/修改节点内容
&cpu0 {cpu-supply = <&vdd_cpu>;
};

这些源码并不包含在根节点“/{…}”内,它们不是一个新的节点,而是向原有节点追加内容。 以上方源码为例,&cpu0表示向节点标签为cpu0的节点追加数据, 这个节点可能定义在本文件也可能定义在本文件所包含的设备树文件中。

节点路径

通过指定从根节点到所需节点的完整路径,可以唯一地标识设备树中的节点, 不同层次的设备树节点名字可以相同,同层次的设备树节点要唯一。 这有点类似于我们Windows上的文件,一个路径唯一标识一个文件或文件夹,不同目录下的文件文件名可以相同。例如前面节点的结构参考图中, 节点node1-name的子节点child-node1,节点路径就是 /node1-name/child-node1

设备树节点的标准属性

节点属性分为标准属性和自定义属性,也就是说我们在设备树中可以根据自己的实际需要定义、添加设备属性。 标准属性的属性名是固定的,自定义属性名可按照要求自行定义。

  • 根节点的compatible属性(字符串):用于标识设备树能否与Linux内核匹配,该属性值的一半格式为"厂商,板子名称"

  • 普通节点的compatible属性(字符串):指兼容性。该属性值的一般格式为"厂商,设备驱动名"。如果Linux内核中的匹配表中有与compatible属性中的值相同的值,则该Linux内核可以使用该设备驱动。当驱动的兼容性信息与设备树的compatible属性匹配后,会运行驱动代码里的probe函数。

  • status属性(字符串):标识设备可用(“okay”)还是不可用(“disabled”)。

    状态值描述
    okay使能设备
    disabled禁用设备
    fail表示设备不可运行,目前驱动不支持,待修复。
    fail-sss表示设备不可运行,目前驱动不支持,待修复。“sss”的值与具体的设备相关。
  • #address-cells#size-cells属性(u32):#address-cells和 #size-cells属性同时存在,用于标明该如何编写reg属性值。#address-cells用于标明reg属性中address所占字长数,size-cells用于标明length所占的字长数。

  • reg属性:该属性的格式一般为reg = <address,length, address,length,…>address表示其实地址,length表示地址长度,一般用于内存中(也可以用于其他设备)。

#address-cells = <2>;	//起始地址占两个字长
#size-cells = <1>;	//地址长度占一个字长
reg = <0x400080,0x600040,0x4000>;	//表示0x400080和0x600040是起始地址,地址长度为0x4000

获取设备树节点信息

内核提供了一组函数用于从设备节点获取资源(设备节点中定义的属性)的函数,这些函数以of_开头,称为OF操作函数。

查找节点函数
根据节点路径寻找节点
of_find_node_by_path函数 (内核源码/include/linux/of.h)
参数:path: 指定节点在设备树中的路径。
返回值:device_node: 结构体指针,如果查找失败则返回NULL,否则返回device_node类型的结构体指针,它保存着设备节点的信息。struct device_node *of_find_node_by_path(const char *path)
根据节点名字寻找节点
of_find_node_by_name函数 (内核源码/include/linux/of.h)
参数:from: 指定从哪个节点开始查找,它本身并不在查找行列中,只查找它后面的节点,如果设置为NULL表示从根节点开始查找。name: 要寻找的节点名。
返回值:device_node: 结构体指针,如果查找失败则返回NULL,否则返回device_node类型的结构体指针,它保存着设备节点的信息。struct device_node *of_find_node_by_name(struct device_node *from,const char *name);

提取属性值的of函数

上一小节我们讲解了查找节点的函数,它们有一个共同特点,找到一个设备节点就会返回这个设备节点对应的结构体指针(device_node*)。这个过程可以理解为把设备树中的设备节点“获取”到驱动中。“获取”成功后我们再通过一组of函数从设备节点结构体(device_node)中获取我们想要的设备节点属性信息。

查找节点属性函数
of_find_property函数 (内核源码/include/linux/of.h)
参数:np: 指定要获取那个设备节点的属性信息。name: 属性名。lenp: 获取得到的属性值的大小,这个指针作为输出参数,这个参数“带回”的值是实际获取得到的属性大小。
返回值:property: 获取得到的属性。property结构体,我们把它称为节点属性结构体,如下所示。失败返回NULL。从这个结构体中我们就可以得到想要的属性值了。struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
读取整型属性函数
of_property_read_uX_array函数组 (内核源码/include/linux/of.h)
参数:np: 指定要读取那个设备节点结构体,也就是说读取那个设备节点的数据。	propname: 指定要获取设备节点的哪个属性。out_values: 这是一个输出参数,是函数的“返回值”,保存读取得到的数据。sz: 这是一个输入参数,它用于设置读取的长度。	
返回值:返回值,成功返回0,错误返回错误状态码(非零值)-EINVAL(属性不存在)-ENODATA(没有要读取的数据)-EOVERFLOW(属性值列表太小)//8位整数读取函数
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)//16位整数读取函数
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)//32位整数读取函数
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)//64位整数读取函数
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
简化后的读取整型属性函数

这里的函数是对读取整型属性函数的简单封装,将读取长度设置为1。用法与读取属性函数完全一致,这里不再赘述。

//8位整数读取函数
int of_property_read_u8 (const struct device_node *np, const char *propname,u8 *out_values)//16位整数读取函数
int of_property_read_u16 (const struct device_node *np, const char *propname,u16 *out_values)//32位整数读取函数
int of_property_read_u32 (const struct device_node *np, const char *propname,u32 *out_values)//64位整数读取函数
int of_property_read_u64 (const struct device_node *np, const char *propname,u64 *out_values)
读取字符串属性函数
参数:np: 指定要获取那个设备节点的属性信息。propname: 属性名。	out_string: 获取得到字符串指针,这是一个“输出”参数,带回一个字符串指针。也就是字符串属性值的首地址。这个地址是“属性值”在内存中的真实位置,也就是说我们可以通过对地址操作获取整个字符串属性(一个字符串属性可能包含多个字符串,这些字符串在内存中连续存储,使用’0’分隔)。
返回值:返回值:成功返回0,失败返回错误状态码。int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string)int of_property_read_string_index(const struct device_node *np,const char *propname, int index,const char **out_string)

相比前面的函数增加了参数index,它用于指定读取属性值中第几个字符串,index从零开始计数。 第一个函数只能得到属性值所在地址,也就是第一个字符串的地址,其他字符串需要我们手动修改移动地址,非常麻烦,推荐使用第二个函数。

读取布尔型属性函数
static inline bool of_property_read_bool(const struct device_node *np, const char *propname);

参数:

  • np: 指定要获取那个设备节点的属性信息。
  • propname: 属性名。

返回值:

这个函数不按套路出牌,它不是读取某个布尔型属性的值,仅仅是读取这个属性存在或者不存在。如果想要或取值,可以使用之前讲解的“全能”函数查找节点属性函数of_find_property。

内存映射相关of函数

在设备树的设备节点中大多会包含一些内存相关的属性,比如常用的reg属性。通常情况下,得到寄存器地址之后我们还要通过ioremap函数将物理地址转化为虚拟地址。现在内核提供了of函数,自动完成物理地址到虚拟地址的转换。

of_iomap函数 (内核源码/drivers/of/address.c)参数:np: 指定要获取那个设备节点的属性信息。index: 通常情况下reg属性包含多段,index 用于指定映射那一段,标号从0开始。
返回值:成功,得到转换得到的地址。失败返回NULLvoid __iomem *of_iomap(struct device_node *np, int index)

内核也提供了常规获取地址的of函数,这些函数得到的值就是我们在设备树中设置的地址值。介绍如下

参数:np: 指定要获取那个设备节点的属性信息。index: 通常情况下reg属性包含多段,index 用于指定映射那一段,标号从0开始。r: 这是一个resource结构体,是“输出参数”用于返回得到的地址信息。
返回值:成功返回0,失败返回错误状态码。int of_address_to_resource(struct device_node *np, int index, struct resource *r)

示例

我们使用的开发板,有配套的SDK,在编译脚本配置中,我们可以知道使用的DTS为哪个。

在这里插入图片描述

其中.dtb文件为.dts文件编译成功后生成的二进制文件,类似于.c文件编译生成.bin文件。如果我们要定义自己的节点和属性,有两种方式

  • 在已有的dts文件中直接添加我们自己的节点。
  • 新建我们自己的dts文件,自己定义节点,然后在平台dts中include我们自己创建的dts文件。

显然,第二种方式更有利于我们的维护。我们新建一个文件haptics.dts,文件内容如下:

/{node1-name@6B { /*节点1, 名称是"node1-name", 单元地址和reg属性的第一个地址一致 */compatible = "xxx, xxxx";  string-property = "a string";string-list-property = "first string", "second string";  byte-data-property = <0x00 0x13 0x24 0x36>;label:child-node1 {  second-child-property = <1>;  a-string-property = "Hello, world";  };};child-node2 {  second-child-property = <1>;  a-string-property = "Hello, world";  };  
};
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/** Copyright (c) 2021 Rockchip Electronics Co., Ltd.**//dts-v1/;#include "rk3588-9tripod-i3588.dtsi"
//#include "rk3588-9tripod-i3588-ov13850-dcphy0.dtsi"
#include "rk3588-9tripod-i3588-ov13855-dcphy0.dtsi"
//#include "rk3588-9tripod-i3588-ov13855-dcphy1.dtsi"
#include "rk3588-9tripod-i3588-imx415-dphy0.dtsi"
//#include "rk3588-9tripod-i3588-imx415-dphy1.dtsi"
#include "rk3588-9tripod-i3588-linux.dtsi"#include "haptics.dts"/ {model = "9Tripod I3588 Board";compatible = "9tripod,rk3588-9tripod-i3588", "rockchip,rk3588";
};

然后,我们还需要在我们SDK中使用的dts文件中include我们自己新建的这个文件。最后编译kernel,DTS文件是和kernel一起编译的,然后烧录,如果编译过程中出现DTS的问题,可自行解决。

设备树中的设备节点在文件系统中有与之对应的文件,位于/proc/device-tree目录。

在这里插入图片描述

可以找到我们自己创建的节点,这里展现的应该都是根节点下的子节点或熟悉,我们可以使用cat命令获取一些属性的信息,或者进行节点目录中获取更多的信息。

在这里插入图片描述

那我们怎么在驱动中获取DTS信息呢,就需要我们上述介绍的接口。我们一般是在probe函数中进行DTS解析。

static void haptic_parse_dts(struct platform_device *pdev)
{int ret = 0;struct device_node* node1=NULL;struct device_node* child_node1=NULL;const char *out_string=NULL;uint32_t out_bytes[4]={0};node1 = of_find_node_by_path("/node1-name@6B");if(node1 == NULL){printk("/node1-name@6B is NULL\n");return;}printk("node1->name = %s\n",node1->name);ret = of_property_read_string(node1,"compatible",&out_string);if(0 != ret){printk("property compatible is NULL\n");return;}printk("compatible = %s\n",out_string);ret = of_property_read_string_index(node1,"string-list-property",0,&out_string);if(0 != ret){printk("string-list-property[0] is NULL\n");return;}printk("string-list-property[0] = %s\n",out_string);ret = of_property_read_string_index(node1,"string-list-property",1,&out_string);if(0 != ret){printk("string-list-property[1] is NULL\n");return;}printk("string-list-property[1] = %s\n",out_string);ret = of_property_read_u32_array(node1,"byte-data-property",out_bytes,ARRAY_SIZE(out_bytes));if(0 != ret){printk("byte-data-property is NULL\n");return;}printk("byte-data-property = 0x%02x 0x%02x 0x%02x 0x%02x\n",out_bytes[0],out_bytes[1],out_bytes[2],out_bytes[3]);child_node1 = of_find_node_by_name(node1,"child-node1");if(child_node1 == NULL){printk("/node1-name@6B/child-node1 is NULL\n");return;}printk("child_node1->name = %s\n",child_node1->name);ret = of_property_read_u32(child_node1,"second-child-property",out_bytes);if(0 != ret){printk("second-child-property is NULL\n");return;}printk("second-child-property= %d\n",out_bytes[0]);}static int haptic_drv_probe(struct platform_device *pdev)
{int ret = 0;int* status=NULL;struct resource* res0=NULL;struct resource* res1=NULL;haptic_miscdev_t *hap_miscdev=NULL;struct file_operations *haptics_fops=NULL;res0 = platform_get_resource(pdev,IORESOURCE_MEM,0);res1 = platform_get_resource(pdev,IORESOURCE_MEM,1);printk("res0 start=%d size=%d\n",(int)res0->start,(int)(res0->end-res0->start+1));printk("res1 start=%d size=%d\n",(int)res1->start,(int)(res1->end-res1->start+1));status = dev_get_platdata(&pdev->dev);printk("status=%d\n",*status);haptics_fops = devm_kzalloc(&pdev->dev,sizeof(struct file_operations), GFP_KERNEL);haptics_fops->open = haptics_open;haptics_fops->release = haptics_release;haptics_fops->unlocked_ioctl = haptics_ioctl;hap_miscdev = devm_kzalloc(&pdev->dev,sizeof(haptic_miscdev_t), GFP_KERNEL);hap_miscdev->res = res0;hap_miscdev->status = *status;hap_miscdev->miscdev.name = pdev->name;hap_miscdev->miscdev.fops = haptics_fops;hap_miscdev->miscdev.minor = MISC_DYNAMIC_MINOR,ret = misc_register(&hap_miscdev->miscdev);/* save as drvdata *///platform_set_drvdata函数,将设备数据信息存入在平台驱动结构体中pdev->dev->driver_data中platform_set_drvdata(pdev, hap_miscdev);ret = sysfs_create_group(&pdev->dev.kobj,&haptic_param_attr_group);haptic_parse_dts(pdev);return ret;
}

在这里插入图片描述

上述示例中,我们是将驱动代码编译进内核中,所以在内核启动过程中进行了probe。

总结

  • #address-cells属性指定了子节点的reg属性中地址信息所占用的单元格(cell)数量,每个单元格通常是32位(4字节)。#size-cells属性指定了子节点的reg属性中长度信息所占用的单元格数量。这两个属性通常一起使用,因为它们共同定义了子节点的reg属性的格式。注意,每个单元格是32位。但是好像不是reg属性的也是这种格式,例如上例中的byte-data-property属性,每个单元格是32位的。

  • /{...};表示根节点,每个设备都有一个根节点,即使include的文件中也有根节点,最终会合并为一个。个人感觉最好还是将上述示例改为.dtsi文件,然后不需要版本声明。

  • 上层的配置会覆盖底层的配置。被include的文件为底层文件,如果里面有节点与上层相同,追加修改后,还是以上层的为准。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/57396.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

node和npm版本冲突

问题描述&#xff1a; 解决办法&#xff1a; 一、 查看自己当前的node和npm版本 node -v npm -v 二、 登录node官网地址 node官网地址 https://nodejs.org/zh-cn/about/previous-releases 查看与自己node版本兼容的是哪一版本的npm,相对应进行更新即可。 三 升级node 下载最…

笑死人不偿命的联想:大象是什么?

element&#xff08;元素&#xff09;一词&#xff0c;起源不明。但是它长得很像elephant&#xff08;大象&#xff09;一词&#xff0c;其同通部分为ele-这一结构&#xff0c;因此我们很容易将两个单词进行拆分出来&#xff1a; element n.元素 // ele ment名缀elephant n.大…

书生-第四期闯关:完成SSH连接与端口映射并运行hello_world.py

端口映射完成后&#xff0c;访问127.0.0.1&#xff1a;7860成功展示如下界面&#xff1a; 书生浦语大模型实战营 项目地址&#xff1a;https://github.com/InternLM/Tutorial/

DBT踩坑第三弹

1. dbt在获取元数据信息的时候&#xff0c;底层使用pyHive的时候database信息没有传进去&#xff0c;pyHive默认又是会设置databasedefault&#xff0c;如果没有default库权限的&#xff0c;这个时候就会抛出Access异常。所以此时最好修改下 dbt-spark 的源码&#xff0c;把dat…

Codeforces Round 966 (Div. 3)

D. Right Left Wrong 题意 思路 我们可以先预处理前缀和&#xff0c;然后贪心每次找最左边的L和最右边的R&#xff0c;计算区间和&#xff0c;然后缩小区间重复操作即可 时间复杂度 O(N) void solve() {int n;cin >> n;vector<int> arr(n 1);vector<int>…

Qt 实战(10)模型视图 | 10.5、代理

文章目录 一、代理1、简介2、自定义代理 前言&#xff1a; 在Qt的模型/视图&#xff08;Model/View&#xff09;框架中&#xff0c;代理&#xff08;Delegate&#xff09;是一个非常重要的概念。它充当了模型和视图之间的桥梁&#xff0c;负责数据的显示和编辑。代理可以自定义…

NSSCTF-WEB-nizhuansiwei

前言 就直接上题目吧 这题有些意思 正文 <?php $text $_GET["text"]; $file $_GET["file"]; $password $_GET["password"];//定义三个变量 if(isset($text)&&(file_get_contents($text,r)"welcome to the zjctf"))…

无迹卡尔曼滤波器(UKF)

正如我们在前一章中所看到的&#xff0c;当状态转移模型f (x)和观测模型h (x)接近于线性时&#xff0c;EKF的性能是令人满意的。然而&#xff0c;当f (x)或h (x)模型是高度非线性的时&#xff0c;线性化误差会导致与状态的真实值显著不同的估计&#xff0c;以及不能捕获状态中的…

金蝶云星空与管易云的数据集成实战案例

金蝶云星空与管易云的数据集成案例分享 在企业信息化系统中&#xff0c;实现不同平台之间的数据无缝对接是提升业务效率的关键。本文将聚焦于一个具体的系统对接集成案例&#xff1a;如何将金蝶云星空中的调拨申请单数据集成到管易云的采购订单新增模块&#xff0c;特别是针对…

成本累计曲线:项目预算的秘密武器

在项目管理的过程中&#xff0c;成本控制是影响项目成败的关键因素之一&#xff0c;而其中“成本累计曲线”就像是一位财务导航员&#xff0c;为项目的成本控制和进度监控提供了极大的帮助。那么&#xff0c;什么是成本累计曲线&#xff1f;它包含哪些步骤&#xff1f;如何应用…

idea连接数据库出现错误的解决方式

在使用idea连接数据库时&#xff0c;出现错误&#xff1a; The server has terminated the handshake. The protocol list option (enabledTLSProtocols) is set, this option might cause connection issues with some versions of MySQL. Consider removing the protocol li…

C++朝花夕拾

目录 目录 函数分文件编写 野指针 const与指针 const修饰指针——常量指针 const修饰常量——指针常量 const既修饰指针,又修饰常量 const阻止函数修改 delete和delete[]的区别 内存四区&#xff08;面试会问&#xff1f;&#xff09; 程序运行前 代码区 全局区 程…

WPF中如何解决DataGrid的Header没有多余的一行

将最后一行设置DataGridTemplateColumn Width"*" 使其自适应

网站制作公司哪家比较靠谱?分享5家2024年口碑好的网站制作公司

想要分辨一家网站制作公司靠不靠谱并不简单&#xff0c;可能它流程透明&#xff0c;设计优秀。但这就一定是适合自己的吗&#xff1f;所以口碑这东西很重要。适合自己也很重要&#xff0c;要多方面去了解。 以下是五家在2024年口碑不错的网站制作公司&#xff0c;分享一下设计…

51单片机STC8G串口Uart配置

测试环境 单片机型号&#xff1a;STC8G1K08-38I-TSSOP20&#xff0c;其他型号请自行测试&#xff1b; IDE&#xff1a;KEIL C51&#xff1b; 寄存器配置及主要代码 STC8G系列单片机具有4个全双工异步串行通信接口&#xff1b;本文以串口1为例&#xff0c;串口1有4种工作方式…

像素、分辨率、PPI(像素密度)、帧率的概念

文章目录 前言一、像素1、定义2、像素点也不是越多越好 二、分辨率1、定义 三、PPI(像素密度)1、定义2、计算公式3、视网膜屏幕 四、帧率1、帧 (Frame)2、帧数 (Frames)3、帧率 (Frame Rate)4、FPS (Frames Per Second)5、赫兹 五、其他1、英寸2、为何显示器尺寸以英寸命名 总结…

Linux初阶——信号

一、预备 1、信号的处理方式 1.1. 默认动作 当收到一个信号时&#xff0c;就执行这个信号的默认动作。 1.2. 忽略 当收到一个信号时&#xff0c;就忽略执行这个信号的默认动作。 1.3. 自定义动作 当收到一个信号时&#xff0c;就执行信号的自定义动作。 2、硬件中断 你…

跨设备使用的便签软件哪款好?

在快节奏的现代生活中&#xff0c;便签软件已成为我们不可或缺的数字助手&#xff0c;它们帮助我们记录灵感、安排日程、设置提醒&#xff0c;极大地提升了我们的工作与生活效率。然而&#xff0c;面对市场上琳琅满目的便签应用&#xff0c;选择一款既实用又适合手机使用的便签…

【万兴科技-注册_登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

51单片机应用开发(进阶)---外部中断(按键+数码管显示0-F)

实现目标 1、巩固数码管、外部中断知识 2、具体实现&#xff1a;按键K4&#xff08;INT1&#xff09;每按一次&#xff0c;数码管从0依次递增显示至F&#xff0c;再按则循环显示。 一、共阳数码管 1.1 共阳数码管结构 1.2 共阳数码管码表 共阳不带小数点0-F段码为&#xff…