Exynos4412的Linux5.4.174时钟驱动开发(四)——clk API的调用方法

news2024/11/18 3:26:05

系列文章目录

Exynos4412的Linux时钟驱动开发(一)——Exynos4412的时钟管理单元CMU

Exynos4412的Linux时钟驱动开发(二)——clock的初始化(CLK_OF_DECLARE的机制)

Exynos4412的Linux时钟驱动开发(三)——Common Clock Framework(CCF)简介

Exynos4412的Linux5.4.174时钟驱动开发(四)——clk API的调用方法

Exynos4412的Linux5.4.174时钟驱动开发(五)——时钟设备树的修改方法


Exynos4412的Linux5.4.174时钟驱动开发(四)——clk API的调用方法

  • 系列文章目录
  • 一、时钟使用者clock consumer与时钟提供者clock provider
  • 二、clock provider是如何构建的?
  • 三、clock consumer是如何构建的
  • 四、clk API是如何与底层时钟驱动相关联的
  • 五、时钟操作函数的基本原理
  • 六、clk API的调用方法


提示:对于刚刚想了解时钟驱动的初学者而言,不必去深究CCF的代码实现,例如clk_register()函数的代码。但是,一定要知道时钟使用者和时钟提供者这2种设备的设备树写法,以及如何调用clock API。当然,这需要了解一些CCF的基本知识。


一、时钟使用者clock consumer与时钟提供者clock provider

在上一篇博文《Exynos4412的Linux时钟驱动开发(三)——Common Clock Framework(CCF)简介》中提到:在CCF通用时钟框架下,用户编写设备驱动模块和设备树,可以调用clk_get(), clk_put(),clk_prepare(), clk_unprepare(),clk_enable(),clk_disable(),clk_get_rate()等clock API,就能够操作时钟。例如,Exynos4412驱动开发程序员,调用clk_set_rate()这个clk API设置PLL频率,实际上是调用samsung_pll35xx_set_rate函数。
Common Clock Framework(CCF)的概述框图,如下:
在这里插入图片描述

实际上,上图黄色部分的CCF接口由2部分组成:第1个部分是CCF core,充当consumer的角色,为Device driver提供clk API。CCF接口的第2个部分是特定时钟硬件驱动(底层时钟设备驱动),充当provider的角色,由厂商(manufacturer)编写底层时钟驱动函数。
在这里插入图片描述

  • 时钟提供者(clock provider)
    当时钟节点中有#clock-cells属性时,说明该节点是clock provider。那么,匹配compatible属性,调用时钟初始化函数,就能够构建clk_hw结构体,并把该节点添加到of_clk_provider的list链表中。
  • 时钟使用者(clock consumer)
    当设备节点中有clocks属性时,说明该节点是clock consumer。那么,就能够根据compatible匹配probe函数,调用devm_clk_get()函数根据clock-names来查找并获取对时钟生产者的引用,获取时钟生产者的clk结构体。

参考文献:
①Linux common clock framework(2)_clock provider——蜗窝科技 1

②[ARM]修改clock freq on imx8m2、 clock bindings 3

③Linux clock子系统【1】- 对clock时钟框架见解 4

二、clock provider是如何构建的?

主要过程是这样的:

  1. 设备树中建立时钟控制器的设备节点。示例代码如下:
		clock: clock-controller@10030000 {
			compatible = "samsung,exynos4412-clock";
			reg = <0x10030000 0x18000>;
			#clock-cells = <1>;
		};
  1. 根据compatible匹配初始化函数
    在drivers/clk/samsung/clk-exynos4.c中,有如下代码:
static void __init exynos4412_clk_init(struct device_node *np)
{
	exynos4_clk_init(np, EXYNOS4X12);
}
CLK_OF_DECLARE(exynos4412_clk, "samsung,exynos4412-clock", exynos4412_clk_init);

也就是说,会调用exynos4_clk_init()进行时钟初始化,注册时钟硬件。

  1. 构建struct clk_provider
    exynos4_clk_init()的部分代码如下:
/* register exynos4 clocks */
static void __init exynos4_clk_init(struct device_node *np,
				    enum exynos4_soc soc)
{
	struct samsung_clk_provider *ctx;
	exynos4_soc = soc;

	reg_base = of_iomap(np, 0);
[......]
	ctx = samsung_clk_init(np, reg_base, CLK_NR_CLKS);

	samsung_clk_of_register_fixed_ext(ctx, exynos4_fixed_rate_ext_clks,
			ARRAY_SIZE(exynos4_fixed_rate_ext_clks),
			ext_clk_match);

	exynos4_clk_register_finpll(ctx);

	if (exynos4_soc == EXYNOS4210) {
[......]
	} else {
		if (_get_rate("fin_pll") == 24000000) {
			exynos4x12_plls[apll].rate_table =
							exynos4x12_apll_rates;
			exynos4x12_plls[epll].rate_table =
							exynos4x12_epll_rates;
			exynos4x12_plls[vpll].rate_table =
							exynos4x12_vpll_rates;
		}

		samsung_clk_register_pll(ctx, exynos4x12_plls,
					ARRAY_SIZE(exynos4x12_plls), reg_base);
	}

	samsung_clk_register_fixed_rate(ctx, exynos4_fixed_rate_clks,
			ARRAY_SIZE(exynos4_fixed_rate_clks));
	samsung_clk_register_mux(ctx, exynos4_mux_clks,
			ARRAY_SIZE(exynos4_mux_clks));
	samsung_clk_register_div(ctx, exynos4_div_clks,
			ARRAY_SIZE(exynos4_div_clks));
	samsung_clk_register_gate(ctx, exynos4_gate_clks,
			ARRAY_SIZE(exynos4_gate_clks));
	samsung_clk_register_fixed_factor(ctx, exynos4_fixed_factor_clks,
			ARRAY_SIZE(exynos4_fixed_factor_clks));
[......]
	samsung_clk_of_add_provider(np, ctx);
[......]

可以看出,通过调用samsung_clk_init构建ctx(结构体类型为struct samsung_clk_provider *ctx)。
然后,通过调用samsung_clk_register_XXX等函数注册PLL、fix_rate、mux、div、gate、fixed_factor等时钟硬件,添加到ctx结构体中。同时,构建了clk_hw结构体。

  1. 后续代码还会调用samsung_clk_of_add_provider()将控制器节点(device node)加入到of_clk_provider的list链表中。

类似文献:
①clk子系统 - 代码分析 5

②Linux clock driver(2) clk_register 详解 6

三、clock consumer是如何构建的

来看看 struct clk *clk是怎么创建的?主要过程是这样的:

  1. 设备树中建立时钟使用者的设备节点。例如pwm的代码如下:
		pwm: pwm@139d0000 {
			compatible = "samsung,exynos4210-pwm";
			reg = <0x139D0000 0x1000>;
			interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&clock CLK_PWM>;
			clock-names = "timers";
			#pwm-cells = <3>;
		};
  1. 根据compatible匹配probe函数
    在driver/pwm/pwm-samsung.c中可以匹配关键字 “samsung,exynos4210-pwm”。
    也就是说,pwm-samsung.c有pwm的驱动程序。在pwm_samsung_probe()函数中,调用devm_clk_get(&pdev->dev, "timers")根据clock-names来查找并获取对时钟生产者的引用,获取时钟生产者的clk结构体。其中形参dev是时钟使用者设备(device for clock consumer),"timers"是时钟使用者的ID号。devm_clk_get的定义在drivers/clk/clk-devres.c。

至此,时钟使用者(consumer)与时钟生产者(provider)之间建立了联系,使用者已经获取了生产者的寄存器地址,可以对生产者时钟的配置寄存器进行操作。例如,在pwm_samsung_enable函数中,就调用了readl和writel函数对REG_TCON寄存器进行读写操作。

四、clk API是如何与底层时钟驱动相关联的

  • 问题来了:clock provider与clock consumer的操作是如何关联的呢?例如,
    clk_set_rate()这个clk API是如何与samsung_pll35xx_set_rate函数相关联的呢?

clk_set_rate()的定义在drivers/clk/clk.c中。clk_set_rate()调用clk_core_rate_protect函数,再调用clk_change_rate函数,最终调用回调函数指针set_rate

  • clk_set_rate() ——>clk_core_rate_protect()——>clk_change_rate()—>set_rate

回调函数set_rate是结构体clk_ops的成员函数指针,声明在include/linux/clk-provider.h中,由时钟提供者(provider)实现(也就是底层时钟硬件驱动)。

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);
	int		(*save_context)(struct clk_hw *hw);
	void	(*restore_context)(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);
[...]

在调用exynos4_clk_init对时钟初始化时,会调用_samsung_clk_register_pll注册PLL时钟硬件,这个函数定义在drivers/clk/samsung/clk-pll.c中。这个函数就会把samsung_pll35xx_set_rate的指针填充到set_rate中。
这样,samsung_pll35xx_set_rate就与set_rate相互关联了,也就回答了刚才提出的问题。

  • exynos4_clk_init()—>_samsung_clk_register_pll()—>samsung_pll35xx_set_rate()

代码如下:

static const struct clk_ops samsung_pll36xx_clk_ops = {
	.recalc_rate = samsung_pll36xx_recalc_rate,
	.set_rate = samsung_pll36xx_set_rate,
	.round_rate = samsung_pll_round_rate,
	.enable = samsung_pll3xxx_enable,
	.disable = samsung_pll3xxx_disable,
};

可以看出,对于Exynos4412的PLL时钟硬件的操作,只有recalc_rate、set_rate、round_rate、enable、disable这5种操作。

五、时钟操作函数的基本原理

操作时钟,本质上是配置clock management unit(CMU)相应的寄存器。以修改Exynos4412的PLL频率为例。

修改Exynos4412的PLL频率是调用底层时钟驱动函数samsung_pll35xx_set_rate。该函数的定义在drviers/clk/samsung/clk-pll.c中。代码如下:

static int samsung_pll35xx_set_rate(struct clk_hw *hw, unsigned long drate,
					unsigned long prate)
{
	struct samsung_clk_pll *pll = to_clk_pll(hw);
	const struct samsung_pll_rate_table *rate;
	u32 tmp;

	/* Get required rate settings from table */
	rate = samsung_get_pll_settings(pll, drate);
	if (!rate) {
		pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
			drate, clk_hw_get_name(hw));
		return -EINVAL;
	}

	tmp = readl_relaxed(pll->con_reg);

	if (!(samsung_pll35xx_mp_change(rate, tmp))) {
		/* If only s change, change just s value only*/
		tmp &= ~(PLL35XX_SDIV_MASK << PLL35XX_SDIV_SHIFT);
		tmp |= rate->sdiv << PLL35XX_SDIV_SHIFT;
		writel_relaxed(tmp, pll->con_reg);

		return 0;
	}

	/* Set PLL lock time. */
	writel_relaxed(rate->pdiv * PLL35XX_LOCK_FACTOR,
			pll->lock_reg);

	/* Change PLL PMS values */
	tmp &= ~((PLL35XX_MDIV_MASK << PLL35XX_MDIV_SHIFT) |
			(PLL35XX_PDIV_MASK << PLL35XX_PDIV_SHIFT) |
			(PLL35XX_SDIV_MASK << PLL35XX_SDIV_SHIFT));
	tmp |= (rate->mdiv << PLL35XX_MDIV_SHIFT) |
			(rate->pdiv << PLL35XX_PDIV_SHIFT) |
			(rate->sdiv << PLL35XX_SDIV_SHIFT);
	writel_relaxed(tmp, pll->con_reg);

	/* Wait until the PLL is locked if it is enabled. */
	if (tmp & BIT(pll->enable_offs)) {
		do {
			cpu_relax();
			tmp = readl_relaxed(pll->con_reg);
		} while (!(tmp & BIT(pll->lock_offs)));
	}
	return 0;
}

可以看出,修改PLL时钟频率的主要流程是:

  1. 获取PMS数值。 调用samsung_get_pll_settings函数获取与需要设置频率所对应的PMS数值(就是samsung_pll_rate_table结构体)
  2. 设置PLL锁定时长。调用writel_relaxed函数,向PLL_LOCK寄存器写入参数
  3. 设置PLL的PMS数值。调用writel_relaxed函数,向PLL_CON0寄存器写入参数

可见,操作时钟,就是配置时钟寄存器。

六、clk API的调用方法


调用clk API需要与时钟设备树相一致。设备树的修改方法在下篇博文Exynos4412的Linux5.4.174时钟驱动开发(五)——时钟设备树的修改方法中介绍。


  1. 在include/linux/clk.h中,声明了clk API,其定义在drivers/clk/clk.c和drivers/clk/clkdev.c中。
    在clk.h中,可以看到有很多的条件编译,例如
#ifdef CONFIG_COMMON_CLK
#ifdef CONFIG_HAVE_CLK
[......]

所以,在编译Linux内核之前make menucongfig时,要注意需要在defconfig中使能以上的几个CONFIG来配置内核。

  1. 调用clk API重点是传递正确的实参。我们可以查阅clk.h,看看API的注释。例如,clk_set_rate的声明如下:
/**
 * clk_set_rate - set the clock rate for a clock source
 * @clk: clock source
 * @rate: desired clock rate in Hz
 *
 * Returns success (0) or negative errno.
 */
int clk_set_rate(struct clk *clk, unsigned long rate);

可以看出,clk_set_rate的2个形参分别是想要配置时钟的struct clk和频率(rate Hz)。

  • 那么,问题来了:怎样获得struct clk *clk
  • 答案:
    首先通过clk_get函数,根据clk节点的名字,获取clk节点。然后,使用clk_set_rate()函数设置clk节点的时钟。clk_set_rate() 函数最终将会调用clk_ops->set_rate() 设置时钟。
    还有其他一些获取clk结构体的函数,如下:
/*从一个时钟list链表中以dev或者字符id名称查找一个时钟clk结构体*/
struct clk *clk_get(struct device *dev, const char *id);

struct clk *devm_clk_get(struct device *dev, const char *id);

/*该函数与clk_get函数对应,释放对应时钟结构体,即对结构体的引用计数减1*/
void clk_put(struct clk *clk);

void devm_clk_put(struct device *dev, struct clk *clk);

struct clk *clk_get_sys(const char *dev_id, const char *con_id);

struct clk *of_clk_get(struct device_node *np, int index);

struct clk *of_clk_get_by_name(struct device_node *np, const char *name);

struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec);

例1:
根据clk节点的name,通过clk_get 获取时钟节点。

clk1 = clk_get(&dev, " snr_clk");
clk2 = clk_get(&dev, " dpi_pixel_clk ");
clk3 = clk_get(&dev, " cvbs_pixel_clk ");
clk4= clk_get(&dev, " vdac_pixel_clk ");

然后,就可以调用clk API来设置各个时钟节点。

clk_disable(clk1);
clk_set_rate(clk2, 24*1000000);
clk_enable(clk3);

例2:

	// 确定时钟个数
	int nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks",
						"#clock-cells");
	// 获得时钟
	for (i = 0; i < nr_pclks; i++) {
		struct clk *clk = of_clk_get(dev->of_node, i);
	}
	// 使能时钟
	clk_prepare_enable(clk);
	// 禁止时钟
	clk_disable_unprepare(clk);

参考文献:
①设备树中时钟的使用 7

②设备树学习之(三)——Clock8

对链表的理解:
浅析linux设备驱动的clock(时钟)的注册


  1. Linux common clock framework(2)_clock provider——蜗窝科技 ↩︎

  2. [ARM]修改clock freq on imx8m ↩︎

  3. clock bindings ↩︎

  4. Linux clock子系统【1】- 对clock时钟框架见解 ↩︎

  5. clk子系统 - 代码分析 ↩︎

  6. [Linux clock driver(2) clk_register 详解](http ** s://blog.csdn.net/yinjian1013/article/details/78552586) ↩︎

  7. 设备树中时钟的使用 ↩︎

  8. 设备树学习之(三)——Clock ↩︎

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

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

相关文章

新蜂商城 -- 代码学习研读

新蜂商城 -- 代码学习研读1.Big Data -- Postgres1.1 Big Data -- Postgres2.Big Data -- Postgres3.AwakeningGit Website: https://github.com/newbee-ltd/newbee-mall. 新蜂商城线上预览地址: http://mall.newbee.ltd. &#x1f449;&#x1f449;学习的朋友给个小星星.感…

蓝牙标签操作流程

电脑网页端后台系统 下载蓝牙标签APP注册账号&#xff0c;登录电脑网页端即可制作模板 蓝牙标签管理系统 安卓手机系统 1. 使用手机浏览器扫码下载 2. 拷贝链接到手机浏览器下载 http://a.picksmart.cn:8088/picksmart/app/new-app-release-v3.0.31.apk 苹果手机系统&#…

公众号运营要做什么?公众号运营规划方案分享

你真的理解什么是公众号运营吗&#xff1f; 公众号运营遵循的是创作-分发-增长-变现的路径&#xff0c;从内容创作到内容分发到用户增长&#xff0c;到最终的转化变现&#xff0c;这才是完整的一个运营闭环&#xff0c;在这条路径中&#xff0c;缺少了哪一环都将影响整体运营效…

学习笔记之范海鹰微表情识别

微表情识别1. 微表情由来1.1 基本情绪和次级情绪1.2 保罗埃克罗的贡献2. 微表情意义2.1 微表情产生原理2.2 微表情的形态意义2.3 微表情的读心内涵2.4 微表情的应用价值3.微表情面部表情3.1 基线反应3.2 惊讶3.3 厌恶、轻蔑3.4 愤怒3.5 恐惧3.6 悲伤3.7 愉悦作为网上冲浪12级选…

redis实现session管理以及缓存穿透与雪崩

小伙正在评博客之星&#xff0c;欢迎大家来互相助力 我的链接 redis实现session管理以及缓存穿透与雪崩一、Redis实现分布式Session管理1 管理机制2 开发Session管理1. 引入依赖2. 开发Session管理配置类3.打包测试即可二、缓存穿透与雪崩缓存穿透缓存击穿&#xff08;量太大&a…

APSIM练习 2:残留物覆盖对休耕期间土壤储水的影响

跟踪覆盖度随着残留物分解而下降。 APSIM 模拟了作物残茬对休耕期间水分捕获和保留效率的影响。随着残留物分解&#xff0c;残留物覆盖率下降。在 APSIM 中模拟残留物分解以响应天气以及残留物的化学成分。通过进行此模拟&#xff0c;您将加强在先前练习中学到的技能&#xff0…

如何给藏品赋能?元宇宙电商NFG系统助力跨境电商新机遇

当前营销市场已陷入沉闷、难以创新的困境&#xff0c;大环境不容乐观&#xff0c;品牌方迫切需要新的创意与内容来吸引消费者。数字藏品是 Z世代和千禧一代在自我表达和社交方面的新兴媒介&#xff0c;在展示形式和互动方式上突破了传统营销方式的局限&#xff0c;帮助品牌在营…

github上传代码记录

文章目录新建仓库执行命令新建仓库 首先先创建一个仓库&#xff08;在主页面中点击New即可创建&#xff09; 填写仓库名称&#xff08;剩下的可以不用点击选择&#xff09; 创建完成之后&#xff0c;github上就会出现提交的提示代码。我们选择第一个进行代码的提交。 执行命…

数据结构中【迷宫问题】的两个OJ题

前言 今天是美好的一天&#xff0c;现在是体育课时间&#xff0c;我神奇的体育老师让我们男生需要做40个俯卧撑作为期末作业&#xff0c;可惜啊可惜&#xff0c;我差了一丝丝&#xff0c;这个东西对于我这种高瘦子还是有很大的挑战的&#xff0c;我现在能充分的感觉到码字的手…

Docker- 7.3、跨主机网络-flannel

flannel是CoreOS开发的容器网络解决方案。flannel为每个host分配一个subnet&#xff0c;容器从此subnet中分配IP&#xff0c;这些IP可以在host间路由&#xff0c;容器间无需NAT和port mapping就可以跨主机通信。每个subnet都是从一个更大的IP池中划分的&#xff0c;flannel会在…

【自学Java】Java语言HelloWorld

Java语言HelloWorld详解 Java语言HelloWorld详解教程 我们使用 java 编辑器&#xff0c;新建一个 Helloworld.java 文件,输入如下内容&#xff1a; package com.haicoder;public class HelloWorld {public static void main(String[] args) {System.out.println("嗨客网…

KubeSphere两种安装方式

目录 &#x1f9e1;KubeSphere简介 &#x1f9e1;KubeSphere安装 &#x1f9e1;前置环境 &#x1f9e1;基于K8S &#x1f9e1;KubeKey一键安装 &#x1f49f;这里是CS大白话专场&#xff0c;让枯燥的学习变得有趣&#xff01; &#x1f49f;没有对象不要怕&#xff0c;我们…

CSS——过渡与动画

1. 缓动效果 给过渡和动画加上缓动效果&#xff08;比如具有回弹效果的过渡过程&#xff09; 回弹效果是指当一个过渡达到最终值时&#xff0c;往回倒一点&#xff0c;然后再次回到最终值&#xff0c;如此往复一次或多次&#xff0c;并逐渐收敛&#xff0c;最终稳定在最终值。…

报表开发工具FastReport.NET的十大常见问题及解决方法

Fastreport是目前世界上主流的图表控件&#xff0c;具有超高性价比&#xff0c;以更具成本优势的价格&#xff0c;便能提供功能齐全的报表解决方案&#xff0c;连续三年蝉联全球文档创建组件和库的“ Top 50 Publishers”奖。 FastReport.NET官方版下载&#xff08;qun&#x…

Redis基础篇——SQL和NoSQL区别

文章目录认识 NoSQLSQL 和 NoSQL 的区别认识 NoSQL NoSQL&#xff0c;泛指非关系型的数据库。随着互联网web2.0网站的兴起&#xff0c;传统的关系数据库在处理web2.0网站&#xff0c;特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心&#xff0c;出现了很多…

熵及其相关概念

文章目录一、什么是熵&#xff1f;二、相对熵&#xff08;KL散度&#xff09;三、交叉熵四、条件熵&#xff0c;联合熵&#xff0c;互信息一、什么是熵&#xff1f; 熵&#xff0c;entropy&#xff0c;一个简简单单的字却撑起了机器学习的半壁江山&#xff0c;熵起源于热力学&a…

怎么做3D可视化?NebulaGraph Explorer 的图数据库可视化实践告诉你答案

前言 图数据可视化是现代 Web 可视化技术中比较常见的一种展示方式&#xff0c;NebulaGraph Explorer 作为基于 NebulaGraph 的可视化产品&#xff0c;在可视化图数据领域积累了许多经验&#xff0c;尤其是在图形渲染性能等领域。本文将系统性分享下 NebulaGraph Explorer 在 …

串口通信协议

同步通信和异步通信 同步通信:需要时钟信号的约束&#xff0c;在时钟信号的驱动下两方进行数据交换&#xff0c;一般会选择在上升沿或者下降沿进行数据的采样&#xff0c;以及时钟极性和时钟相位【eg.SPI,IIC】。 异步通信:不需要时钟信号的同步&#xff0c;通过&#xff08;…

只根据\r、\n、\r\n三种分隔符分割字符串splitlines()方法

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 只根据\r、\n、\r\n三种 分隔符分割字符串 splitlines()方法 选择题 对于以下python代码表述错误的一项是? s字符串1\n字符串2\r字符串3\r\n字符串4\n\r字符串5 print("【执行】s字符串…

移植FreeRTOS到STM32

移植FreeRTOS到STM32单片机上引言介绍什么是 RTOS&#xff1f;为什么嵌入式设备往往使用RTOS&#xff1f;FreeRTOS具体步骤总结引言 本文详细介绍如何移植FreeRTOS到STM32单片机上。移植操作系统是嵌入式开发的入门基础&#xff0c;单片机和嵌入式在物理上其实是一摸一样的&am…