【Linux】Pinctrl子系统和GPIO子系统

Pinctrl子系统

在许多soc内部包含了多个pin控制器,通过pin控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性。Linux内核为了统一各soc厂商的pin脚管理,提供了pinctrl子系统。该系统的作用:

  • 在系统初始化的时候,枚举所有可以控制的pin,并标识这pin。
  • 设定引脚的功能复用,比如复用为GPIO还是SPI等。
  • 引脚的配置,比如上下拉,驱动强度和去抖等。

pinctrl子系统结构描述:

在这里插入图片描述

如上图所示,pinctrl核心层是内核抽象出来,向下为个SoC pin controler drvier提供底层通信接口的能力, 向上为其他驱动提供了控制pin的能力,比如pin复用、配置引脚的电气特性,同时也为GPIO子系统提供pin操作。而pin控制器驱动层,主要提供了操作pin的方法。

pinctrl子系统的源文件在内核源码/drivers/pinctrl目录下,主要包括核心文件,和其他内核驱动的接口头文件,底层的pin控制器驱动接口,还有其他厂商Pinctrl驱动文件。

pinctrl设备树节点介绍

首先,我们在arch\arm64\boot\dts\rockchip\rk3588s.dtsi文件中可以看到如下内容:

	pinctrl: pinctrl {compatible = "rockchip,rk3588-pinctrl";rockchip,grf = <&ioc>;#address-cells = <2>;#size-cells = <2>;ranges;gpio0: gpio@fd8a0000 {compatible = "rockchip,gpio-bank";reg = <0x0 0xfd8a0000 0x0 0x100>;interrupts = <GIC_SPI 277 IRQ_TYPE_LEVEL_HIGH>;clocks = <&cru PCLK_GPIO0>, <&cru DBCLK_GPIO0>;gpio-controller;#gpio-cells = <2>;gpio-ranges = <&pinctrl 0 0 32>;interrupt-controller;#interrupt-cells = <2>;};gpio1: gpio@fec20000 {compatible = "rockchip,gpio-bank";reg = <0x0 0xfec20000 0x0 0x100>;interrupts = <GIC_SPI 278 IRQ_TYPE_LEVEL_HIGH>;clocks = <&cru PCLK_GPIO1>, <&cru DBCLK_GPIO1>;gpio-controller;#gpio-cells = <2>;gpio-ranges = <&pinctrl 0 32 32>;interrupt-controller;#interrupt-cells = <2>;};gpio2: gpio@fec30000 {compatible = "rockchip,gpio-bank";reg = <0x0 0xfec30000 0x0 0x100>;interrupts = <GIC_SPI 279 IRQ_TYPE_LEVEL_HIGH>;clocks = <&cru PCLK_GPIO2>, <&cru DBCLK_GPIO2>;gpio-controller;#gpio-cells = <2>;gpio-ranges = <&pinctrl 0 64 32>;interrupt-controller;#interrupt-cells = <2>;};gpio3: gpio@fec40000 {compatible = "rockchip,gpio-bank";reg = <0x0 0xfec40000 0x0 0x100>;interrupts = <GIC_SPI 280 IRQ_TYPE_LEVEL_HIGH>;clocks = <&cru PCLK_GPIO3>, <&cru DBCLK_GPIO3>;gpio-controller;#gpio-cells = <2>;gpio-ranges = <&pinctrl 0 96 32>;interrupt-controller;#interrupt-cells = <2>;};gpio4: gpio@fec50000 {compatible = "rockchip,gpio-bank";reg = <0x0 0xfec50000 0x0 0x100>;interrupts = <GIC_SPI 281 IRQ_TYPE_LEVEL_HIGH>;clocks = <&cru PCLK_GPIO4>, <&cru DBCLK_GPIO4>;gpio-controller;#gpio-cells = <2>;gpio-ranges = <&pinctrl 0 128 32>;interrupt-controller;#interrupt-cells = <2>;};};
  • pinctrl节点就是在这里定义的,其他地方都是追加修改使用的&pinctrl
  • 这里还定义了5个GPIO bank,注意,这5各组后面还会使用。

我们再来看看别人在pinctrl节点中追加的内容:

&pinctrl {i2c6 {/omit-if-no-ref/i2c6m2_xfer: i2c6m2-xfer {rockchip,pins =/* i2c6_scl_m2 */<2 RK_PC3 9 &pcfg_pull_none_smt>,/* i2c6_sda_m2 */<2 RK_PC2 9 &pcfg_pull_none_smt>;};};
}

上述代码就是定义了一个i2c6子节点,子节点中又定义了i2c6m2_xfer节点。节点中定义了rockchip,pins属性,属性的值有两组。属性值中每个单元是啥意思呢?

  • 2:这个数字通常表示GPIO bank号(0-5),后续会具体讲解GPIO编号。
  • RK_PC3:这是rk平台上的一个具体的引脚编号,这是一个宏定义,在scripts\dtc\include-prefixes\dt-bindings\pinctrl\rockchip.h文件中,他其实也是一个数字,这个数字也涉及到GPIO编号,后续再介绍。
  • 9:这个数字表示引脚的功能,在rk平台上,每个引脚可以被配置为多种不同的功能,如GPIO、I2C或SPI等。当为0时(即RK_FUNC_GPIO)表示设置复用功能为GPIO。这里不是很明白为什么9就是i2c功能,没有看到在哪定义的。
  • &pcfg_pull_none_smt:这是一个引脚配置对象的引用,它定义了引脚的上拉/下拉配置。具体见arch\arm64\boot\dts\rockchip\rockchip-pinconf.dtsi文件。
	fiq_debugger: fiq-debugger {compatible = "rockchip,fiq-debugger";rockchip,serial-id = <2>;rockchip,wake-irq = <0>;/* If enable uart uses irq instead of fiq */rockchip,irq-mode-enable = <1>;rockchip,baudrate = <1500000>;  /* Only 115200 and 1500000 */interrupts = <GIC_SPI 423 IRQ_TYPE_LEVEL_LOW>;pinctrl-names = "default";pinctrl-0 = <&uart2m0_xfer>;status = "okay";};

GPIO子系统

在pinctrl子系统中把pin脚初始化成了普通GPIO后,就可以使用GPIO子系统的接口去操作IO口的电平、中断等。驱动开发者在设备树中添加gpio相关信息, 然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,极大的方便了驱动开发者使用GPIO。gpio子系统的代码在内核源码/drivers/gpio/目录下。

GPIO子系统结构简单描述如下图:

在这里插入图片描述

GPIO的核心是gpiolib框架,向上提供一些gpio接口给其他驱动调用,向下提供用于gpio资源注册函数。 上层的其他驱动,比如LED的驱动,可以通过函数向gpiolib申请gpio,然后设置和使用gpio。下层的控制器驱动(一般是SOC厂商编写),启动时会注册gpio资源到gpiolib,比如引脚数量,操作函数等等。

GPIO引脚计算

Rockchip Pin的ID按照:控制器(bank)+端口(port)+索引序号(pin)组成,例如GPIO3_B2。

  • 有5组GPIO,即5个控制器(bank),对应GPIO0~GPIO4,也就是前面在dts中定义的gpio0-gpio4。
  • 端口固定为A、B、C和D。
  • 索引序号固定为0、1、2、3、4、5、6、7。

有5个GPIO控制器,每个控制器都可以控制32个IO,所以GPIO2_B3的引脚编号为:3 * 32 + 1 * 8 + 2 = 106

示例(LED驱动)

背景:定义两个GPIO,并将这两个GPIO直接相连,其中一个GPIO设置为输出,另一个GPIO设置为中断。


&pinctrl {haptics_pins {reset_pins:reset_pins {rockchip,pins=<1 RK_PB3 RK_FUNC_GPIO &pcfg_pull_none>;};irq_pins:irq_pins{rockchip,pins=<2 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>;};};};/{si_haptics { status = "okay";compatible = "si,haptics"; reset-gpios = <&gpio1 RK_PB3 GPIO_ACTIVE_HIGH>; irq-gpios = <&gpio2 RK_PB2 GPIO_ACTIVE_HIGH>;/*与irq-gpios属性的作用相同,都是定义中断的引脚,但是在驱动中解析时使用的接口不一样*/interrupt-parent = <&gpio2>;interrupts = <RK_PB2 IRQ_TYPE_EDGE_RISING>;// pinctrl-names = "default";// pinctrl-0 =<&reset_pins>;};
};
/** @Date: 2024-10-17 16:08:08* @LastEditors: zdk* @LastEditTime: 2024-11-05 16:26:23* @FilePath: \kernel\drivers\haptics\haptic_drv.c*/#include <linux/init.h>  //包含宏定义的头文件
#include <linux/module.h>   //包含初始化加载模块的头文件
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include "haptic_ioctl.h"typedef struct
{struct miscdevice miscdev;//定义一个杂项设备结构体int reset_gpio;int irq_gpio;
}haptic_miscdev_t;ssize_t reset_show(struct device *dev, struct device_attribute *attr, char *buf)
{haptic_miscdev_t * haptic_dev= (haptic_miscdev_t *)dev->driver_data;return sprintf(buf, "reset_show reset=%d\n",gpio_get_value(haptic_dev->reset_gpio));
}ssize_t reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{int ret= 0;int val=0;haptic_miscdev_t * haptic_dev= (haptic_miscdev_t *)dev->driver_data;ret = kstrtoint(buf, 0 , &val);gpio_set_value(haptic_dev->reset_gpio,val);printk("reset_store reset=%d\n",val);return count;
}ssize_t irq_show(struct device *dev, struct device_attribute *attr, char *buf)
{return sprintf(buf, "hello i am irq_show\n");
}ssize_t irq_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{printk("%s:%s\n", __func__,buf);return count;
}static DEVICE_ATTR(reset, 0664, reset_show, reset_store);
static DEVICE_ATTR(irq, 0664, irq_show, irq_store);static struct attribute* haptic_attributes[] =
{&dev_attr_reset.attr,&dev_attr_irq.attr,NULL,
};static struct attribute_group haptic_attr_group = 
{.attrs = haptic_attributes,
};static irqreturn_t haptic_irq_hander(int irq, void *dev_id)
{haptic_miscdev_t * haptic_dev = (haptic_miscdev_t *)dev_id;printk("haptic_irq_hander:reset_value=%d  irq_value=%d\n",gpio_get_value(haptic_dev->reset_gpio),gpio_get_value(haptic_dev->irq_gpio));return IRQ_HANDLED;
}static void haptic_parse_dts(struct platform_device *pdev,haptic_miscdev_t * haptic_dev)
{int ret = 0;struct device_node* si_haptics_node=NULL;si_haptics_node = of_find_node_by_path("/si_haptics");if(si_haptics_node == NULL){printk(KERN_EMERG "get si_haptics_node failed!  \n");}haptic_dev->reset_gpio = of_get_named_gpio(si_haptics_node, "reset-gpios", 0);haptic_dev->irq_gpio = of_get_named_gpio(si_haptics_node,"irq-gpios",0);if(gpio_is_valid(haptic_dev->reset_gpio)){	ret = devm_gpio_request_one(&pdev->dev,haptic_dev->reset_gpio,GPIOF_OUT_INIT_LOW,"reset");printk("reset_gpio_number = %d init low\n",haptic_dev->reset_gpio);}if(gpio_is_valid(haptic_dev->irq_gpio)){printk("irq_gpio_number = %d \n",haptic_dev->irq_gpio);/*这种方式是使用interrupt属性解析中断号*///ret = devm_request_irq(&pdev->dev,irq_of_parse_and_map(si_haptics_node, 0),haptic_irq_hander,IRQF_TRIGGER_RISING,"haptic_interrupt",haptic_dev);ret = devm_request_irq(&pdev->dev,gpio_to_irq(haptic_dev->irq_gpio),haptic_irq_hander,IRQF_TRIGGER_RISING,"haptic_interrupt",haptic_dev);//这种方式是使用GPIO编号来映射中断号if(ret < 0){printk("devm_request_irq error %d \n",ret);}}
}static int haptic_drv_probe(struct platform_device *pdev)
{int ret = 0;haptic_miscdev_t *haptic_dev=NULL;haptic_dev = devm_kzalloc(&pdev->dev,sizeof(haptic_miscdev_t), GFP_KERNEL);haptic_dev->miscdev.name = pdev->name;haptic_dev->miscdev.minor = MISC_DYNAMIC_MINOR,ret = misc_register(&haptic_dev->miscdev);/* save as drvdata *///platform_set_drvdata函数,将设备数据信息存入在平台驱动结构体中pdev->dev->driver_data中platform_set_drvdata(pdev, haptic_dev);ret = sysfs_create_group(&pdev->dev.kobj,&haptic_attr_group);haptic_parse_dts(pdev,haptic_dev);return ret;
}static int haptic_drv_remove(struct platform_device *pdev)
{int ret = 0;haptic_miscdev_t *hap_miscdev = platform_get_drvdata(pdev);misc_deregister(&hap_miscdev->miscdev);return ret;
}static struct of_device_id haptic_dev_ids[] =
{{.compatible="si,haptics",},{.compatible="aw,haptics",},
};static struct platform_driver haptic_drv=
{.probe = haptic_drv_probe,.remove = haptic_drv_remove,.driver={.name="haptics_drv",.owner = THIS_MODULE,.of_match_table =of_match_ptr(haptic_dev_ids),},
};static int __init haptic_drv_init(void)
{int ret = 0;//内核层只能使用printk,不能使用printfprintk(KERN_EMERG "%s\n",__FUNCTION__); ret = platform_driver_register(&haptic_drv);return ret;
}static void __exit haptic_drv_exit(void)
{platform_driver_unregister(&haptic_drv);printk(KERN_EMERG "%s\n",__FUNCTION__); 
}module_init(haptic_drv_init);//模块入口
module_exit(haptic_drv_exit);//模块出口MODULE_AUTHOR("<daokuan.zhug@si-in.com>");//声明作者信息
MODULE_DESCRIPTION("Haptics Device V1.0.0"); //对这个模块作一个简单的描述
MODULE_LICENSE("GPL v2");//声明开源许可证// "GPL" 是指明 这是GNU General Public License的任意版本// “GPL v2” 是指明 这仅声明为GPL的第二版本

在这里插入图片描述

在这里插入图片描述

总结

  • of_get_named_gpio接口其实就是将设备树中定义的GPIO名称转换为GPIO编号,因为代码中都是使用编号来指定GPIO。

  • 上面的架构和平台设备驱动架构很像,但是好像没有看到设备的代码?个人理解DTS中的节点就是设备,并且在驱动和设备匹配的时候,我们也是使用到了设备树节点中的compatible属性,而且我们的设备名称就是节点名称。

  • 关于DTS中的interrupt-parentinterrupts属性即可定义中断的引脚,与irq-gpios属性好像是重复的,只定义一种即可,上述代码中用到了irq-gpios属性,然后将GPIO编号映射为中断编号。如果要使用interrupts属性则需要使用其他的接口,使用irq_of_parse_and_map(si_haptics_node, 0)函数即可获取中断号

    问题

    关于pinctrl节点中子节点定义,到底有啥作用呢?很多代码都会有pinctrl-names = "default"属性,但是将上述示例代码中的pinctrl节点定义删除好像也是可以的。其实pinctrl-namespinctrl-x是搭配着使用的,并且和其他属性没有直接的联系。因为这组属性就是定义GPIO的复用功能的,比如pinctrl-0定义为i2c功能,pinctrl-1定义为gpio功能,这样在驱动中,我们就可以快速切换GPIO的配置。

    &pinctrl {haptics_pins {reset_pins:reset_pins {rockchip,pins=<1 RK_PB3 RK_FUNC_GPIO &pcfg_pull_down>;};reset1_pins:reset1_pins{rockchip,pins = <1 RK_PB3 RK_FUNC_GPIO &pcfg_pull_up>;};irq_pins:irq_pins{rockchip,pins=<2 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>;};};};/{si_haptics { status = "okay";compatible = "si,haptics"; reset-gpios = <&gpio1 RK_PB3 GPIO_ACTIVE_HIGH>; irq-gpios = <&gpio2 RK_PB2 GPIO_ACTIVE_HIGH>;interrupt-parent = <&gpio2>;interrupts = <RK_PB2 IRQ_TYPE_EDGE_RISING>;pinctrl-names = "default","up";pinctrl-0 = <&reset_pins>;pinctrl-1 = <&reset1_pins>;};
    };
    

    上述pinctrl中,我们定义了reset_pins引脚的两种功能,但其实我们只是修改了它的上下拉状态,这样方便举例,一般的使用场景不会这样,而是会定义不同的功能,也就是rockchip,pins属性的第3个参数会不一致。然后在我们的设备节点中,我们定义了两个状态,pinctrl-0对应"default"pinctrl-1对应"up",这些names是可以自定义的。在驱动代码中,我们做了一些修改。

typedef struct
{struct miscdevice miscdev;//定义一个杂项设备结构体int reset_gpio;int irq_gpio;struct pinctrl* reset_pinctrl;
}haptic_miscdev_t;

我们自定义的设备结构体中增加了struct pinctrl*字段。

	haptic_dev->reset_pinctrl = devm_pinctrl_get(&pdev->dev);if(IS_ERR_OR_NULL(haptic_dev->reset_pinctrl)){printk("devm_pinctrl_get error\n");}

上述代码就是从DTS文件中解析struct pinctrl*字段。然后在设备属性节点中我们去切换这个配置,然后通过读取GPIO的状态来确实pinctrl的状态是否切换成功。

ssize_t reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{int ret= 0;int val=0;struct pinctrl_state* state=NULL;haptic_miscdev_t * haptic_dev= (haptic_miscdev_t *)dev->driver_data;ret = kstrtoint(buf, 0 , &val);	if(val==0){state = pinctrl_lookup_state(haptic_dev->reset_pinctrl,"default");}else{state = pinctrl_lookup_state(haptic_dev->reset_pinctrl,"up");}if(IS_ERR_OR_NULL(state)){printk("pinctrl_lookup_state error\n");return count;}ret = pinctrl_select_state(haptic_dev->reset_pinctrl,state);if(ret != 0){printk("pinctrl_select_state error\n");}printk("reset_store reset=%d reset_value=%d\n",val,gpio_get_value(haptic_dev->reset_gpio));return count;
}

在这里插入图片描述

补充

在解析DTS的时候,我们自定义了节点名称。但是如果使用设备树的方式匹配成功了,我们可以直接从struct platform_device *中获取设备树节点,即设备节点。

	struct device_node* si_haptics_node = pdev->dev.of_node;// si_haptics_node = of_find_node_by_path("/si_haptics");if(si_haptics_node == NULL){printk(KERN_EMERG "get si_haptics_node failed!  \n");}

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

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

相关文章

《Vue3 报错》Uncaught TypeError: s.finally is not a function

解决方案&#xff1a; 新建文件 my-polyfill.js // 当浏览器环境不支持Promise.prototype.finally if (!Promise.prototype[finally]) {Promise.prototype[finally] function(callback) {let P this.constructor;return this.then(value > P.resolve(callback()).then(…

RabbitMQ 七种工作模式介绍

目录 1.简单模式队列 2.WorkQueue(⼯作队列) 3 Publish/Subscribe(发布/订阅) 4 Routing(路由模式) 5.Topics(通配符模式) 6 RPC(RPC通信) 7 Publisher Confirms(发布确认) RabbitMQ 共提供了7种⼯作模式供我们进⾏消息传递,接下来一一介绍它的实现与目的 1.简单模式队列…

数组类算法【leetcode】

704. 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 二分查找 用于有序数组中&#xff0c;没有重复的数组。…

Pandas 数据分析工具详细教程

Pandas 数据分析工具详细教程 Pandas 是一个强大的 Python 数据分析库&#xff0c;广泛应用于数据科学、数据分析和机器学习等领域。它提供了高效的数据操作和分析功能&#xff0c;使得数据处理变得简单而高效。本文将详细介绍 Pandas 的基本概念、数据结构、常用操作及其在数…

基于 EventBridge + DashVector 打造 RAG 全链路动态语义检索能力

作者&#xff1a;肯梦 本文将演示如何使用事件总线&#xff08;EventBridge&#xff09;&#xff0c;向量检索服务&#xff08;DashVector&#xff09;&#xff0c;函数计算&#xff08;FunctionCompute&#xff09;结合灵积模型服务 [ 1] 上的 Embedding API [ 2] &#xff0…

GooglePlay: 应用和游戏的内容分级

对于后台私信的开发者们,希望能够携带详细过审记录和拒审邮件一同发来,方便我们尽快解决问题 应用与游戏 为您的应用或游戏选择类别和标签选择要添加的标签选择类别并添加标签类别示例与应用、游戏以及两者中所投放广告的内容分级相关的要求应用如何获得内容分级内容分级的用…

将Notepad++添加到右键菜单【一招实现】

一键添加注册表 复制以下代码保存为 Notepad.reg&#xff0c;将红框内路径修改为自己电脑的“Notepad.exe路径”后&#xff0c;再双击运行即可。 Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\*\shell\NotePad] "Notepad" "Icon""D:\\N…

[复健计划][紫书]Chapter 7 暴力求解法

7.1 简单枚举 例7-1 Division uva725 输入正整数n&#xff0c;按从小到大的顺序输出所有形如abcde/fghij n的表达式&#xff0c;其中a&#xff5e;j恰好为数字0&#xff5e;9的一个排列&#xff08;可以有前导0&#xff09;&#xff0c;2≤n≤79。枚举fghij&#xff0c;验证a…

【测试工具篇一】全网最强保姆级教程抓包工具Fiddler(2)

本文接上篇Fiddler介绍&#xff0c;开始讲fiddler如何使用之前&#xff0c;给大家讲讲http以及web方面的小知识&#xff0c;方便大家后面更好得理解fiddler使用。 目录 一、软件体系结构---B/S与C/S架构 B/S架构 C/S架构 二、HTTP基础知识 什么是http请求和响应? http协…

如何基于pdf2image实现pdf批量转换为图片

最近为了将pdf报告解析成为文本和图片&#xff0c;需要将大量多页的pdf文件拆分下单独的一页一页的图像&#xff0c;以便后续进行OCR和图像处理&#xff0c;因此就需要实现将pdf2image&#xff0c;本文主要结合开源的pdf2image和poppler&#xff0c;实现了pdf转换为png格式图片…

【Linux】Linux下查看cpu信息指令(top/mpstat/iostat/pidstat)说明

top命令 top(1) - Linux manual page (man7.org) top查看总的CPU利用率 us: 用户空间消耗的CPU资源占比&#xff0c;进程在用户态执行函数调用&#xff0c;编解码消耗的都是us sy: 内核空间消耗的CPU资源占比&#xff0c;进程调用系统调用达到内核后会增加sy的消耗 ni&…

Java学习者的福音:SpringBoot教学辅助平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理教学辅助平台的相关信息成为必然。开发合适…

csrf令牌

csrf get请求 路由 // index.php Route::get(/, function () {// return view(welcome);return view(login); });Route::get(d3,function(Request $request){echo "输入的内容是" . "<font color>".$request -> input(mytext)."</fon…

高校实验室安全巡检系统设计与实现(源码+定制+开发)高校实验室巡检系统、实验室安全管理平台、实验室安全监控系统、智能实验室巡查系统、高校实验室风险管理

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

容器内pip安装Apache Airflow的经历:如何重置初始密码

背景 Apache Airflow™https://github.com/apache/airflow 是一个开源平台&#xff0c;用于开发、调度和监控面向批处理的工作流程。Airflow 可扩展的 Python 框架使您能够构建几乎可以连接任何技术的工作流程。Web 界面有助于管理工作流程的状态。Airflow 可以通过多种方式部…

微服务透传日志traceId

问题 在微服务架构中&#xff0c;一次业务执行完可能需要跨多个服务&#xff0c;这个时候&#xff0c;我们想看到业务完整的日志信息&#xff0c;就要从各个服务中获取&#xff0c;即便是使用了ELK把日志收集到一起&#xff0c;但如果不做处理&#xff0c;也是无法完整把一次业…

精心整理教育研究专题数据资源大全-最新出炉_附下载链接

教育研究专题数据资源大全V1.0 下载链接-点它&#x1f449;&#x1f449;&#x1f449;&#xff1a;教育研究专题数据资源大全-最新出炉.zip 资源介绍 一、中国教育统计年鉴面板数据 简介&#xff1a;《中国教育统计年鉴》是由教育部发展规划司根据全国各省、自治区、直辖市…

汽修行业员工培训SOP的智能化搭建

汽修行业正经历着技术革新和服务模式的双重变革&#xff0c;员工的专业培训变得尤为重要。智能化的员工培训标准操作程序&#xff08;SOP&#xff09;在线知识库不仅能够提升培训效率&#xff0c;还能确保服务质量和作业安全。本文将探讨汽修行业如何智能化地搭建员工培训的SOP…

还在担心Mac卸载不干净?XApp帮你干净完成卸载

Mac的卸载机制非常独特&#xff0c;虽然将app拖拽到废纸篓也能够完成卸载&#xff0c;但是会有很多的文件残留&#xff0c;那么如何卸载干净非常重要 XApp&#xff0c;免费的Mac卸载工具&#xff0c;有着强大的垃圾检测机制&#xff0c;检测出更深层的垃圾&#xff0c;卸载更干…

游戏测试之浅谈测试思维

一、游戏测试与软件测试的区别 1、测试目标 软件测试&#xff1a;主要目标是确保软件在功能、性能、安全性等方面达到预期质量标准。关注点主要是软件的正确性、稳定性、安全性和效率。 游戏测试&#xff1a;不仅关注游戏的功能性和稳定性&#xff0c;还要关注游戏的体验性、平…