Linux 内核学习(1) --- 时钟子系统

news2024/12/28 21:02:05

标题

      • 时钟系统说明
      • 时钟树
      • Clock Provider
        • 时钟通用数据结构
        • clock_device 的注册
        • clock_provider DTS配置和注册
        • clock consumer
        • 时钟系统总结

时钟系统说明

时钟就是 SoC 中的脉搏,由它来控制各个部件按各自的节奏跳动。比如,CPU主频设置,串口的波特率设置,I2S的采样率设置,I2C的速率设置等等。这些不同的 clock 设置,都需要从某个或某几个时钟源头而来,最终开枝散叶,形成一棵时钟树。

Linux的时钟子系统由CCF(common clock framework) 框架管理,CCF向上给其他使用时钟的 IP 提供了通用的时钟接口,向下给驱动开发者提供硬件操作的接口。

这个也是一个consumer、framework、provider的模式。
Provider 是时钟的模块的具体实现者,系统开机时,需要通过驱动的时钟框架向系统注册不同的时钟。Provider,其他需要时钟的模块通过通用接口获取,使能,设置时钟。
Framework 是内核提供的一套通用时钟实现框架,包含了注册和使用的通用接口。
Consumer 是时钟模块的使用者,比如上面提到的 I2C 模块,Uart 模块的等等。

电源管理的两大主要方面就是时钟和电压

时钟树

在SoC上的模块很多,为了适应不同模块的时钟要求,会形成一课时钟树,如下所示:

根节点一般是 Oscillator(有源振荡器)或者 Crystal(无源振荡器),表示从芯片外部输入的基准时钟,
中间节点有很多种,包括 PLL(锁相环,用于提升频率的),Divider(分频器,用于降频的),Mux(从多个clock path中选择一个),Gate(只能被控制ON/OFF的)。Soc 上的时钟树
根据不同时钟的特点,clock framework 将 clock 分为 **Fixed rate、gate、Divider、Mux、Fixed factor、composite **六类,这六类的含义如下:

  • Fixed rate clock
    固定频率时钟,提供恒定的时钟信号,适用于需要固定频率的硬件,如PWM(脉宽调制)或定时器。
  • Gated clock
    门控时钟,只可以被开启或关闭,适用于可以被动态开启或者关闭的硬件,以节约电源
  • Divider clock
    分频时钟,可以将输入的时钟信号分频,产生一个较低频率的时钟信号,适用于需要降低时钟频率适应设备需要的设备
  • Muxed clock:
    复用时钟,时钟信号可以选择不同的时钟源
  • Fixed factor clock
    固定系数时钟,可以将输入时钟信号乘以一个固定的系数,产生一个更高或者更低的时钟信号
  • Composite clock
  • 组合时钟,可以由多个时钟实体组成,每个时钟实体都有特定的功能,适用于复杂时钟控制策略的硬件,比如复杂时钟路径和时钟管理

Linux 下 cat /sys/kernel/debug/clk/clk_summary 可以查看当前 Soc 的时钟树

Clock Provider

时钟通用数据结构

Linux 内核将上面六类设备特点抽象出来,用 struct clk_hw 表示

struct clk_hw {
  //指向CCF模块中对应 clock device 实例
 struct clk_core *core;
  //clk是访问clk_core的实例 每当consumer通过clk_get对CCF中的clock device(也就是clk_core)发起访问的时候都需要获取一个句柄,也就是clk
 struct clk *clk;
  //clock provider driver初始化时的数据,数据被用来初始化clk_hw对应的clk_core数据结构。
 const struct clk_init_data *init;
};

struct clk_init_data {
  //该clock设备的名字
 const char  *name;
  //clock provider driver 进行的具体的 HW 操作
 const struct clk_ops *ops;
  //描述该clk_hw的拓扑结构
 const char  * const *parent_names;
 const struct clk_parent_data *parent_data;
 const struct clk_hw  **parent_hws;
 u8   num_parents;
 unsigned long  flags;
};

Fixed rate clockgate clock 为例,它就包含一个 struct clk_hw 结构作为核心:

struct clk_fixed_rate {
	// 包含的 clk_hw 结构
	struct		clk_hw hw;
	unsigned long	fixed_rate;
	unsigned long	fixed_accuracy;
	u8		flags;
};

struct clk_gate {
	struct clk_hw hw;
	void __iomem	*reg;
	u8		bit_idx;
	u8		flags;
	spinlock_t	*lock;
};

由此可以知道:

  1. 每次注册进入内核的 clock device 设备,都会包含一个 struct clk_hw 结构
  2. strutc clk_hw 包含一个重要的结构体成员 const struct clk_init_data *init,里面包含了注册进入内核的时钟的具体操作方法

struct clk_init_data 包含一个重要成员 clk_ops,里面就是时钟设备的具体操作方法函数:

struct clk_ops {
	int		(*prepare)(struct clk_hw *hw);
	void		(*unprepare)(struct clk_hw *hw);
	int		(*is_prepared)(struct clk_hw *hw);
	void		(*unprepare_unused)(struct clk_hw *hw);
	int		(*enable)(struct clk_hw *hw);
	void		(*disable)(struct clk_hw *hw);
	int		(*is_enabled)(struct clk_hw *hw);
	void		(*disable_unused)(struct clk_hw *hw);
	unsigned long	(*recalc_rate)(struct clk_hw *hw,
					unsigned long parent_rate);
	long		(*round_rate)(struct clk_hw *hw, unsigned long rate,
					unsigned long *parent_rate);
	int		(*determine_rate)(struct clk_hw *hw,
					  struct clk_rate_request *req);
	int		(*set_parent)(struct clk_hw *hw, u8 index);
	u8		(*get_parent)(struct clk_hw *hw);
	int		(*set_rate)(struct clk_hw *hw, unsigned long rate,
				    unsigned long parent_rate);
	int		(*set_rate_and_parent)(struct clk_hw *hw,
				    unsigned long rate,
				    unsigned long parent_rate, u8 index);
	unsigned long	(*recalc_accuracy)(struct clk_hw *hw,
					   unsigned long parent_accuracy);
	int		(*get_phase)(struct clk_hw *hw);
	int		(*set_phase)(struct clk_hw *hw, int degrees);
	void		(*init)(struct clk_hw *hw);
	int		(*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};


struct clk_init_data {
	const char		*name;
	const struct clk_ops	*ops;
	const char		* const *parent_names;
	u8			num_parents;
	unsigned long		flags;
};
clock_device 的注册

clock_provider 注册的流程如下图:
clock_provider_framework
这里的注册是指将不同的 clock_device 注册到内核的 CCF 框架中,具体时钟驱动的匹配和注册在下面讲介绍:

  • 首先 Linux 内核会读取设备树中定义的不同的 clock_device 节点,匹配到不同的 clock_init 函数
  • clock_init 函数中,根据设备树的配置,注册不同类型的 clock_device到 CCF 中

不同的 clock_device 都会提供 register 函数用于注册,举例如下:

// include/linux/clock-provider.h
struct clk *clk_register_gate(struct device *dev, const char *name,
		const char *parent_name, unsigned long flags,
		void __iomem *reg, u8 bit_idx,
		u8 clk_gate_flags, spinlock_t *lock);

struct clk *clk_register_divider(struct device *dev, const char *name,
		const char *parent_name, unsigned long flags,
		void __iomem *reg, u8 shift, u8 width,
		u8 clk_divider_flags, spinlock_t *lock);

struct clk *clk_register_divider_table(struct device *dev, const char *name,
		const char *parent_name, unsigned long flags,
		void __iomem *reg, u8 shift, u8 width,
		u8 clk_divider_flags, const struct clk_div_table *table,
		spinlock_t *lock);		
...
clk_register_mux(...);
clk_register_mux_table(...);
clk_register_fixed_factor(...);
clk_register_composite(...);

这些注册函数最终都会通过函数 clk_register 注册到 Common Clock Framework 中,返回为 struct clk 指针。如下所示:
clock_device_regsiter
在这里插入图片描述
在内核的drivers/clk目录下,可以看到各个芯片厂商对各自芯片 clock 驱动的实现

clock_provider DTS配置和注册

在设备数中,需要首先定义 clock_provider 的特性节点,以 rk3399 为例,下面的 cru(clock reset unit) 的配置如下:

cru: clock-controller@ff760000 {
	compatible = "rockchip,rk3399-cru";
	reg = <0x0 0xff760000 0x0 0x1000>;
	rockchip,grf = <&grf>;
	#clock-cells = <1>;
	#reset-cells = <1>;
	assigned-clocks =
		<&cru PLL_GPLL>, <&cru PLL_CPLL>,
		<&cru PLL_NPLL>,
		<&cru ACLK_PERIHP>, <&cru HCLK_PERIHP>,
		<&cru PCLK_PERIHP>,
		<&cru ACLK_PERILP0>, <&cru HCLK_PERILP0>,
		<&cru PCLK_PERILP0>, <&cru ACLK_CCI>,
		<&cru HCLK_PERILP1>, <&cru PCLK_PERILP1>;
	assigned-clock-rates =
		 <594000000>,  <800000000>,
		<1000000000>,
		 <150000000>,   <75000000>,
		  <37500000>,
		 <100000000>,  <100000000>,
		  <50000000>, <600000000>,
		 <100000000>,   <50000000>;
};

不同属性的的含义如下:

属性含义
compatible驱动的匹配名称,内核通过字段匹配到不同的初始化函数
clock-output-names输出时钟的名字,当consumer 使用此时钟时,使用该属性的值
clock-frequency输出时钟的频率
clock-cells输出的时钟的路数,当#clock-cells为0时,代表仅输出1路时钟,若大于等于1,则代表输出多路时钟,Clock consumers通过编号索引使用。
assigned-clocks表示该设备需要使用时钟信号,这个属性的值是一个整数数组,每一个元素对应一个时钟信号
assigned-clock-rates和 assigned-clocks 成对使用,表述输入时钟的频率

在 Linux 内核代码中,还需要声明相匹配的时钟驱动,这样在初始化阶段,内核就可以自动匹配 DTS 中的 compatible 字段,向系统注册时钟设备
CLK_OF_DECLARE 宏用于声明与设备树(DeviceTree)绑定的时钟控制器驱动
CLK_OF_DECLARE 宏的定义如下:

#define CLK_OF_DECLARE(name, compat, fn) \
        OF_DECLARE_1(clk_of_match, name, compat, fn)

//name:时钟提供者的名称,通常是一个结构体的实例。
//compat:与设备树中兼容性字段匹配的字符串,用于识别时钟控制器。
//fn:一个函数指针,指向用于初始化时钟控制器的函数。
//当内核解析设备树时,它会查找与compat参数匹配的节点,并调用fn参数指定的函数来初始化时钟控制器。这样,内核就可以通过设备树来配置和管理设备的时钟

继续跟踪 CLK_OF_DECLARE 的代码可以看到

#define CLK_OF_DECLARE(name, compat, fn) OF_DECLARE_1(clk, name, compat, fn)

#define OF_DECLARE_1(table, name, compat, fn) \
		_OF_DECLARE(table, name, compat, fn, of_init_fn_1)
		
#define _OF_DECLARE(table, name, compat, fn, fn_type)			\
	static const struct of_device_id __of_table_##name		\
		__used __section(__##table##_of_table)			\
		 = { .compatible = compat,				\
		     .data = (fn == (fn_type)NULL) ? fn : fn  }
			 
// 如果将 fn 传入参数 hi6220_clk_media,那么展开后可以看到下面的一个实例:
static const struct of_device_id __of_table_hi6220_clk_media
			__used __section(__clk_of_table)	
			= {
				.compatible = compat;
				.data = fn; // check fn type __init 函数
			 }		

本质上定义了 struct of_device_id 结构,使用 compatible 字段进行匹配

所以 clock 驱动编写的一般步骤是:

  1. 实现 struct clk_ops 相关成员函数
  2. 定义分配 struct clk_onecell_data 结构体,初始化相关数据
  3. 定义分配 struct clk_init_data 结构体,初始化相关数据
  4. 调用 clk_register 将时钟注册进框架
  5. 调用 clk_register_clkdev 注册时钟设备
  6. 调用 of_clk_add_provider,将 clk provider 存放到 of_clk_provider 链表中管理
  7. 调用 CLK_OF_DECLARE 声明驱动

其中的第二步,struct clk_onecell_data 是在 clk-provider.h 中定义,结构如下:

struct clk_onecell_data {
	struct clk **clks;
	unsigned int clk_num;
};

一般用于保存 clk_register 函数的返回值 struct clk 结构,并将其作为私有数据参数,通过 of_clk_add_provider 注册到 of_clk_provider 链表中

	struct clk_onecell_data *clk_data;
	clk_data = kzalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
	......
	
	clk_data->clks = kzalloc(qty * sizeof(struct clk *), GFP_KERNEL);
	.....
	
	clk_data->clks[i] = clk_register_gate(NULL, clk_name,
				      clk_parent, clkflags,
				      reg_idx, reg_bit,
				      flags,
				      &clk_lock);
	clk_data->clk_num = qty;
	of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
clock consumer

Clock consumers意为时钟使用者,通常是CPU核心部件或者其他外设。
下面是一个 clock consumer 的配置:

	i2c8: i2c@ff3e0000 {
		compatible = "rockchip,rk3399-i2c";
		reg = <0x0 0xff3e0000 0x0 0x1000>;
		assigned-clocks = <&pmucru SCLK_I2C8_PMU>;
		assigned-clock-rates = <200000000>;
		clocks = <&pmucru SCLK_I2C8_PMU>, <&pmucru PCLK_I2C8_PMU>;
		clock-names = "i2c", "pclk";
		interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH 0>;
		pinctrl-names = "default";
		pinctrl-0 = <&i2c8_xfer>;
		#address-cells = <1>;
		#size-cells = <0>;
		status = "disabled";
	};

clocks 属性:它代表了设备的时钟源,通常以 phandle + specifier 组合进行引用,比如在本例中使用的时钟是pmcru 中的 SCLK_I2C8_PMU 和 PCLK_I2C8_PMU 作为时钟源

clock-names:这代表了Clock consumers中使用的时钟名字,方便设备驱动代码进行相应的时钟解析
比如使用下面的代码进行解析:

	mdev->aclk = devm_clk_get(dev, "aclk");
	if (IS_ERR(mdev->aclk)) {
		DRM_ERROR("Get engine clk failed.\n");
		err = PTR_ERR(mdev->aclk);
		mdev->aclk = NULL;
		goto err_cleanup;
	}
	
	clk_prepare_enable(mdev->aclk);

clock comsumer 的架构如下所示:
clock_consumer
主要就是获取和操作 clk,即通过 clock 名称获取 struct clk 指针的过程,由 clk_get、devm_clk_get、clk_get_sys、of_clk_get、of_clk_get_by_name、of_clk_get_from_provider 等接口负责实现,

//启动clock前的准备工作/停止clock后的善后工作。可能会睡眠。
int clk_prepare(struct clk *clk)
void clk_unprepare(struct clk *clk)
 
//启动/停止clock。不会睡眠。
static inline int clk_enable(struct clk *clk)
static inline void clk_disable(struct clk *clk)

//clock频率的获取和设置
static inline unsigned long clk_get_rate(struct clk *clk)
static inline int clk_set_rate(struct clk *clk, unsigned long rate)
static inline long clk_round_rate(struct clk *clk, unsigned long rate)

//获取/选择clock的parent clock
static inline int clk_set_parent(struct clk *clk, struct clk *parent)
static inline struct clk *clk_get_parent(struct clk *clk)
 
//将clk_prepare和clk_enable组合起来,一起调用。将clk_disable和clk_unprepare组合起来,一起调用
static inline int clk_prepare_enable(struct clk *clk)
static inline void clk_disable_unprepare(struct clk *clk)

注意 clk_prepare()clk_enable() 是两个不同的函数

  1. clk_prepare() 函数的作用是准备时钟,它执行时钟的初始化操作,但并不立即启用时钟。这个函数的主要目的是为了确保时钟在启用之前所有的准备工作都已经完成,比如分配必要的资源、设置初始参数等。调用clk_prepare后,时钟处于“已准备”状态,但还没有开始运行。

  2. clk_enable函数的作用是启用时钟,它使得时钟开始运行,并提供时钟信号给相关的硬件。在调用clk_enable之前,必须先调用clk_prepare来确保时钟已经准备好。一旦时钟被启用,它就可以被硬件设备使用。

这里分析一下 clk_get 函数的实现过程:

struct clk *clk_get(struct device *dev, const char *con_id)
{
 const char *dev_id = dev ? dev_name(dev) : NULL;
 struct clk *clk;

 if (dev) {
  // 通过扫描所有 "clock-names" 中的值,和传入的 name 比较,如果相同,获得它的 index(即 clock-names 中的第几个),调用 of_clk_get,取得 clock指针。
  clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);
  if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)
   return clk;
 }

 return clk_get_sys(dev_id, con_id);
}
时钟系统总结

Linux 的时钟框架的简要关系可以用下图来说明:
Linux common clock framweork

  • clock provider 使用 clk_hw 结构向 CCF 框架注册一个时钟设备,返回一个 struct clk 结构,其中 clk_hw 结构中 包含了 struct clk_inid_data 结构, struct clk_init_data 结构包含对时钟操作的具体函数集合 struct clk_ops
  • 为了简化操作,CCF 将 clock 设备抽象为六中不同的类型,并且做了相应的接口封装,比如,对于 gate 类型的设备,直接使用 clk_register_gate 等类型的函数就可以进行注册
  • CCF 框架会为每个注册的 clock_device 都分配一个 struct clk_core 结构,其中包含了操作函数的结构体
    struct clk_ops 结构,本质就是关联到 init_data 中的 clk_ops
  • consumer 通过 clk_get 等函数 获取的 struct clk 结构,其中包含了重要结构就是 struct clk_core,通过操作里面的 struct clk_ops 就可以关联到注册进入 CCF 框架的 clk_ops 结构,实现对时钟硬件设备操作的目的

文件中的关键代码路径

include/linux/clk.h
include/linux/clk.c
include/linux/clk-provider.h

driver/clk/clk-gate.c
driver/clk/clk-mux.c
driver/clk/clk-divider.c

参考网址:
Linux kernel中的CPU时钟管理

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

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

相关文章

解锁智能未来:用Ollama开启你的本地AI之旅

Ollama是一个用于在本地运行大型语言模型&#xff08;LLM&#xff09;的开源框架。它旨在简化在Docker容器中部署LLM的过程&#xff0c;使得管理和运行这些模型变得更加容易。Ollama提供了类似OpenAI的API接口和聊天界面&#xff0c;可以非常方便地部署最新版本的GPT模型并通过…

Linux 硬链接和软链接怎么区分使用?

一、什么是硬链接和软链接 硬链接 在Linux操作系统中&#xff0c;硬链接相当于存储在硬盘驱动器中的文件&#xff0c;它实际上引用或指向硬盘驱动器上的某个点。硬链接是原始文件的镜像副本。 硬链接与软链接的区别在于&#xff0c;删除原始文件不会影响硬链接&#xff0c;但…

研发岗-面临统信UOS系统配置总结

第一步 获取root权限 配置环境等都需要用到root权限&#xff0c;所以我们先获取到root权限&#xff0c;方便下面的操作 下载软件 在UOS应用商店下载的所需应用 版本都比较低 安装node 官网下载了【arm64】的包&#xff0c;解压到指定文件夹&#xff0c;设置链接&#xff0…

在Windows下面的vscode配置cmake使用vcpkg包管理器

安装 vscode下载地址 cmake下载地址 vcpkg下载地址 创建CMake项目 // main.cpp #include <fmt/core.h>int main() {fmt::print("Hello World!\n");return 0; }// CMakeLists.txtcmake_minimum_required(VERSION 3.10)project(HelloWorld)find_package(fmt…

数据结构基础 ——栈和队列(三)

一、物理结构和逻辑结构 物理结构就是看得见&#xff0c;摸得着。而数组和链表&#xff0c;就是内存中实实在在的存储结构。逻辑结构就是看不见、摸不着。 二、 栈(stack&#xff09; 栈(stack&#xff09;是一种线性数据结构&#xff0c;栈中的元素只能先进后出 (First In La…

GitHub repository - Watch - Star - Fork - Follow

GitHub repository - Watch - Star - Fork - Follow References 眼睛图标旁边写着 Watch 字样。点击这个按钮就可以 Watch 该仓库&#xff0c;今后该仓库的更新信息会显示在用户的公开活动中。Star 旁边的数字表示给这个仓库添加 Star 的人数。这个数越高&#xff0c;代表该仓库…

【Java】第十五届蓝桥杯JavaB组第一道填空题

&#xff03;【Java】第十五届蓝桥杯JavaB组第一道填空题 大家好 我是寸铁&#x1f44a; 总结了一篇【Java】第十五届蓝桥杯JavaB组第一道填空题文章 喜欢的小伙伴可以点点关注 &#x1f49d; Java B组 第一道填空题题解如下:

Excel从零基础到高手【办公】

第1课 - 快速制作目录【上篇】第1课 - 快速制作目录【下篇】第2课 - 快速定位到工作表的天涯海角第3课 - 如何最大化显示工作表的界面第4课 - 给你的表格做个瘦身第5课 - 快速定位目标区域所在位置第6课 - 快速批量填充序号第7课 - 按自定义的序列排序第8课 - 快速删除空白行第…

计算机视觉异常检测——PatchCore面向全召回率的工业异常检测

1. 概述 异常检测问题在工业图像数据分析中扮演着至关重要的角色&#xff0c;其目的是从大量正常数据中识别出异常行为或模式。这一任务的挑战在于&#xff0c;正常数据的样本相对容易获取&#xff0c;而异常情况却因其稀有性和多样性而难以收集。为了解决这一问题&#xff0c…

【C++类和对象】上篇

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

MDK平台 - Code, RO-data , RW-data, ZI-data详解

文章目录 1 . 前言2 . Code, RO-data , RW-data, ZI-data解析3 . RAM上电复位4 . 细节扩展5 . 总结 【全文大纲】 : https://blog.csdn.net/Engineer_LU/article/details/135149485 1 . 前言 MDK编译后&#xff0c;会列出Code, RO-data , RW-data, ZI-data&#xff0c;以下解析…

YOLO-World: Real-Time Open-Vocabulary Object Detection 简介+安装+运行+训练(持续更新)

前言 YOLO_WORLD太牛了&#xff01;&#xff01;众所周知&#xff0c;传统是视觉目标检测一旦训练好后&#xff0c;如果我们需要增加新的识别目标的话&#xff0c;必须得重新训练模型。在生产中如果经常要新增检测目标&#xff0c;对时效性影响很大&#xff0c;而且随着数据量…

【前端面试3+1】12 toktn验证过程、面向对象特性、webpack和vite的区别、【字符串中的第一个唯一字符】

一、token验证过程 用户登录&#xff1a;用户提供用户名和密码进行登录。服务器验证&#xff1a;服务器接收到用户提供的用户名和密码&#xff0c;进行验证。生成token&#xff1a;如果用户名和密码验证通过&#xff0c;服务器会生成一个token&#xff0c;通常包含一些加密的信…

代码随想录阅读笔记-回溯【复原IP地址】

题目 给定一个只包含数字的字符串&#xff0c;复原它并返回所有可能的 IP 地址格式。 有效的 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 . 分隔。 例如&#xff1a;"0.1.2.201…

最新AI模型与Python技术处理和分析气候数据:ChatGPT在大气科学领域建模、数据分析、可视化与资源评估中的高效应用及论文写作

本文深度探讨人工智能在大气科学中的应用&#xff0c;特别是如何结合最新AI模型与Python技术处理和分析气候数据。课程介绍包括GPT-4等先进AI工具&#xff0c;旨在帮助大家掌握这些工具的功能及应用范围。课程内容覆盖使用GPT处理数据、生成论文摘要、文献综述、技术方法分析等…

力扣2923、2924.找到冠军I、II---(简单题、中等题、Java、拓扑排序)

目录 一、找到冠军I 思路描述&#xff1a; 代码&#xff1a; 二、找到冠军II 思路描述&#xff1a; 代码&#xff1a; 一、找到冠军I 一场比赛中共有 n 支队伍&#xff0c;按从 0 到 n - 1 编号。 给你一个下标从 0 开始、大小为 n * n 的二维布尔矩阵 grid 。对于满足…

DNF手游攻略:2024新手攻略大全

在《DNF手游》的世界中&#xff0c;前期阶段对于新手玩家来说至关重要。以下是一份综合整理的新手攻略&#xff0c;帮助玩家快速适应游戏并取得进展。 1. 角色建立策略&#xff1a; 在前期&#xff0c;建议玩家建立3个角色&#xff0c;包括1个大号和2个小号。大号可以根据个人喜…

从电子病历(EMRs)构建医学知识图谱

从电子病历 EMRs 构建医学知识图谱 提出背景传统的三元组结构本研究采用的四元组结构第四元作用第四元类型以往的方法本研究的方法 大威天龙八 论文&#xff1a;Real-world data medical knowledge graph: construction and applications 提出背景 本研究在中国一家三甲医院的…

【GD32】MQ-5液化气检测传感器

2.33 MQ-5液化气检测传感器 MQ-5气体传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡(Sno2)。当传感器所处环境中存在可燃气体时&#xff0c;传感器的电导率随空气中可燃气体浓度的增加而增大。使用简单的电路即可将电导率的变化转换为该气体浓度相对应的输出信号。…

ExpressLRS硬件实测性能分析

ExpressLRS硬件实测性能分析 1. 源由2. 远航测试3. 实验室测试3.1 芯片RSSI与实测功率差异3.2 SNR信噪比稳定3.3 140db衰减器衰减&#xff0c;40个频点信号稳定 4. 外场测试4.1 无屏蔽样品4.2 有屏蔽样品4.3 有屏蔽vs无屏蔽样品 5. 估算6. 总结7. 补充说明 -- 50mW视频 1. 源由…