ALSA架构学习2(驱动MAX98357A)
1 前言和环境
之前其实写过两篇,一篇是讲ALSA,一篇是I2S。
ALSA架构学习1(框架)_alsa框架学习-CSDN博客
总线学习5--I2S_max98357接喇叭教程-CSDN博客
在ALSA那篇的结尾,也提了几个小练习。比如:
### 4. **定制音频驱动程序**
- **目标**: 开发一个简单的 ALSA 驱动程序,用于控制一个虚拟或简单的音频硬件设备。
- **技术点**: 学习如何编写一个基本的 PCM 驱动程序,理解 ALSA 内核 API(如 `snd_pcm_new`、`snd_pcm_ops` 等),处理音频数据的 DMA 传输。
- **扩展**: 支持更复杂的硬件设备,如具有多个 PCM 子设备的音频控制器,实现高级功能,如音频格式转换、硬件加速等。### 5. **基于 I2S 的嵌入式音频项目**
- **目标**: 在嵌入式设备(如 Raspberry Pi 或 BeagleBone)上,使用 I2S 接口与外部 DAC(数模转换器)或 ADC(模数转换器)通讯,实现音频输入和输出功能。
- **技术点**: 配置设备树中的 I2S 接口,编写或修改 ALSA 驱动以支持 I2S,处理音频数据的采集与播放。
- **扩展**: 开发一个简单的 DSP(数字信号处理)功能,如均衡器或混响效果。
正好这次就使用I2S的MAX98357A,试一试在树莓派3B上面驱动起来。

2 操作流程
2.1 alsa驱动
首先看一下kernel的make menuconfig


在这里可以看到,MAX98357A已经作为module集成在kernel了。
直接就可以加载
sudo modprobe snd-soc-max98357a
此时可以看到,已经加载成功了。

2.2 设备树
哦,天啊,dtbo居然也是现成的。
![]()
2.3 系统修改
看来只用改系统配置就可以了。
在/boot/fireware/config.txt中增加配置
dtparam=i2s=on
dtoverlay=max98357a
重启之后就可以看到已经加载上了。是card1

查看声卡:
tom@raspberrypi:~ $ cat /proc/asound/cards0 [Headphones ]: bcm2835_headpho - bcm2835 Headphonesbcm2835 Headphones1 [MAX98357A ]: simple-card - MAX98357AMAX98357A2 [vc4hdmi ]: vc4-hdmi - vc4-hdmivc4-hdmi
此时在系统中也已经可以看到。

2.4 硬件连接
根据引脚说明连接即可。

详细连接如下:
| MAX98357A Pin | Raspberry Pi 3B Pin | 描述 |
|---|---|---|
| DIN | GPIO21 / Pin 40 | I²S 数据输出 |
| BCLK | GPIO18 / Pin 12 | I²S 时钟 |
| LRCLK | GPIO19 / Pin 35 | I²S 帧时钟 |
| SD_MODE | GPIO4 / Pin 7(可选) | 控制 DAC 启动(如果驱动中有) |
| GND | Pin 6 或任意 GND | 地 |
| VDD | Pin 1 (3.3V) 或 Pin 2 (5V) | 推荐 5V 供电,声音更大 |
| GAIN0/1 | 接地或浮空(按需要) | 控制输出增益 |
| OUTP/OUTN | 连接喇叭 | 差分输出(无须耳放) |
直接播放card1就可以听到声音了。(card0依然是3.5mm耳机口)

也可以安装mplayer播放mp3,此时要指定是card1。
这里还有一个问题,MAX98357是一个 “数字音频功放”(DAC+AMP),本身没有可控音量寄存器。所以没法在系统中控制音量,比如用alsamixer这些工具。只能在播放的时候增加参数-volume 30来控制。

3 代码学习
3.1 设备树
在./arch/arm/boot/dts/overlays/max98357a-overlay.dts
tom@raspberrypi:~/linux $ cat ./arch/arm/boot/dts/overlays/max98357a-overlay.dts
// Overlay for Maxim MAX98357A audio DAC// dtparams:
// no-sdmode - SD_MODE pin not managed by driver.
// sdmode-pin - Specify GPIO pin to which SD_MODE is connected (default 4)./dts-v1/;
/plugin/;/ {compatible = "brcm,bcm2835";/* Enable I2S */fragment@0 {target = <&i2s_clk_producer>;__overlay__ {status = "okay";};};/* DAC whose SD_MODE pin is managed by driver (via GPIO pin) */fragment@1 {target-path = "/";__overlay__ {max98357a_dac: max98357a {compatible = "maxim,max98357a";#sound-dai-cells = <0>;sdmode-gpios = <&gpio 4 0>; /* 2nd word overwritten by sdmode-pin parameter */status = "okay";};};};/* DAC whose SD_MODE pin is not managed by driver */fragment@2 {target-path = "/";__dormant__ {max98357a_nsd: max98357a {compatible = "maxim,max98357a";#sound-dai-cells = <0>;status = "okay";};};};/* Soundcard connecting I2S to DAC with SD_MODE */fragment@3 {target = <&sound>;__overlay__ {compatible = "simple-audio-card";simple-audio-card,format = "i2s";simple-audio-card,name = "MAX98357A";status = "okay";simple-audio-card,cpu {sound-dai = <&i2s_clk_producer>;};simple-audio-card,codec {sound-dai = <&max98357a_dac>;};};};/* Soundcard connecting I2S to DAC without SD_MODE */fragment@4 {target = <&sound>;__dormant__ {compatible = "simple-audio-card";simple-audio-card,format = "i2s";simple-audio-card,name = "MAX98357A";status = "okay";simple-audio-card,cpu {sound-dai = <&i2s_clk_producer>;};simple-audio-card,codec {sound-dai = <&max98357a_nsd>;};};};__overrides__ {no-sdmode = <0>,"-1+2-3+4";sdmode-pin = <&max98357a_dac>,"sdmode-gpios:4";};
};
首先是打开I2S接口。i2s_clk_producer
fragment@0 {target = <&i2s_clk_producer>;__overlay__ {status = "okay";};
};
里面有4个fragment,通过参数来确定使用哪个。没有参数就是第一个。这个时候带了SD_MODE。多用了一个GPIO去控制器件开关,感觉主要是用于低功耗。
3.2 驱动代码
驱动代码是max98357a.c
可以看出,这个代码还是比较祖传,15年的,差不多10年了。
tom@raspberrypi:~/linux $ cat ./sound/soc/codecs/max98357a.c
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.** max98357a.c -- MAX98357A ALSA SoC Codec driver*/#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <sound/soc-dapm.h>struct max98357a_priv {struct gpio_desc *sdmode;unsigned int sdmode_delay;int sdmode_switch;
};static int max98357a_daiops_trigger(struct snd_pcm_substream *substream,int cmd, struct snd_soc_dai *dai)
{struct snd_soc_component *component = dai->component;struct max98357a_priv *max98357a =snd_soc_component_get_drvdata(component);if (!max98357a->sdmode)return 0;switch (cmd) {case SNDRV_PCM_TRIGGER_START:case SNDRV_PCM_TRIGGER_RESUME:case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:mdelay(max98357a->sdmode_delay);if (max98357a->sdmode_switch) {gpiod_set_value(max98357a->sdmode, 1);dev_dbg(component->dev, "set sdmode to 1");}break;case SNDRV_PCM_TRIGGER_STOP:case SNDRV_PCM_TRIGGER_SUSPEND:case SNDRV_PCM_TRIGGER_PAUSE_PUSH:gpiod_set_value(max98357a->sdmode, 0);dev_dbg(component->dev, "set sdmode to 0");break;}return 0;
}static int max98357a_sdmode_event(struct snd_soc_dapm_widget *w,struct snd_kcontrol *kcontrol, int event)
{struct snd_soc_component *component =snd_soc_dapm_to_component(w->dapm);struct max98357a_priv *max98357a =snd_soc_component_get_drvdata(component);if (event & SND_SOC_DAPM_POST_PMU)max98357a->sdmode_switch = 1;else if (event & SND_SOC_DAPM_POST_PMD)max98357a->sdmode_switch = 0;return 0;
}static const struct snd_soc_dapm_widget max98357a_dapm_widgets[] = {SND_SOC_DAPM_OUTPUT("Speaker"),SND_SOC_DAPM_OUT_DRV_E("SD_MODE", SND_SOC_NOPM, 0, 0, NULL, 0,max98357a_sdmode_event,SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
};static const struct snd_soc_dapm_route max98357a_dapm_routes[] = {{"SD_MODE", NULL, "HiFi Playback"},{"Speaker", NULL, "SD_MODE"},
};static const struct snd_soc_component_driver max98357a_component_driver = {.dapm_widgets = max98357a_dapm_widgets,.num_dapm_widgets = ARRAY_SIZE(max98357a_dapm_widgets),.dapm_routes = max98357a_dapm_routes,.num_dapm_routes = ARRAY_SIZE(max98357a_dapm_routes),.idle_bias_on = 1,.use_pmdown_time = 1,.endianness = 1,
};static const struct snd_soc_dai_ops max98357a_dai_ops = {.trigger = max98357a_daiops_trigger,
};static struct snd_soc_dai_driver max98357a_dai_driver = {.name = "HiFi",.playback = {.stream_name = "HiFi Playback",.formats = SNDRV_PCM_FMTBIT_S16 |SNDRV_PCM_FMTBIT_S24 |SNDRV_PCM_FMTBIT_S32,.rates = SNDRV_PCM_RATE_8000 |SNDRV_PCM_RATE_16000 |SNDRV_PCM_RATE_32000 |SNDRV_PCM_RATE_44100 |SNDRV_PCM_RATE_48000 |SNDRV_PCM_RATE_88200 |SNDRV_PCM_RATE_96000,.rate_min = 8000,.rate_max = 96000,.channels_min = 1,.channels_max = 2,},.ops = &max98357a_dai_ops,
};static int max98357a_platform_probe(struct platform_device *pdev)
{struct max98357a_priv *max98357a;int ret;max98357a = devm_kzalloc(&pdev->dev, sizeof(*max98357a), GFP_KERNEL);if (!max98357a)return -ENOMEM;max98357a->sdmode = devm_gpiod_get_optional(&pdev->dev,"sdmode", GPIOD_OUT_LOW);if (IS_ERR(max98357a->sdmode))return PTR_ERR(max98357a->sdmode);ret = device_property_read_u32(&pdev->dev, "sdmode-delay",&max98357a->sdmode_delay);if (ret) {max98357a->sdmode_delay = 0;dev_dbg(&pdev->dev,"no optional property 'sdmode-delay' found, ""default: no delay\n");}dev_set_drvdata(&pdev->dev, max98357a);return devm_snd_soc_register_component(&pdev->dev,&max98357a_component_driver,&max98357a_dai_driver, 1);
}#ifdef CONFIG_OF
static const struct of_device_id max98357a_device_id[] = {{ .compatible = "maxim,max98357a" },{ .compatible = "maxim,max98360a" },{}
};
MODULE_DEVICE_TABLE(of, max98357a_device_id);
#endif#ifdef CONFIG_ACPI
static const struct acpi_device_id max98357a_acpi_match[] = {{ "MX98357A", 0 },{ "MX98360A", 0 },{},
};
MODULE_DEVICE_TABLE(acpi, max98357a_acpi_match);
#endifstatic struct platform_driver max98357a_platform_driver = {.driver = {.name = "max98357a",.of_match_table = of_match_ptr(max98357a_device_id),.acpi_match_table = ACPI_PTR(max98357a_acpi_match),},.probe = max98357a_platform_probe,
};
module_platform_driver(max98357a_platform_driver);MODULE_DESCRIPTION("Maxim MAX98357A Codec Driver");
MODULE_LICENSE("GPL v2");
这里首先设置了codec的参数
.playback = {.formats = S16/S24/S32, // 支持的位深.rates = 8k ~ 96kHz, // 采样率范围.channels_min/max = 1~2 // 单声道或双声道
}
然后主要还是围绕着SD_MODE来操作的。播放时才给芯片上电,一旦停止播放,则直接断电。
tom@raspberrypi:~/alsa $ echo function | sudo tee /sys/kernel/debug/tracing/current_tracer
tom@raspberrypi:~/alsa $ echo max98357a_daiops_trigger | sudo tee /sys/kernel/debug/tracing/set_ftrace_filter
tom@raspberrypi:~/alsa $ sudo bash -c 'echo 1 > /sys/kernel/debug/tracing/tracing_on'
tom@raspberrypi:~/alsa $ sudo cat /sys/kernel/debug/tracing/trace | grep max98mplayer-2146 [003] d..1. 3792.358634: max98357a_daiops_trigger <-snd_soc_pcm_dai_triggermplayer-2146 [001] d..1. 3815.900668: max98357a_daiops_trigger <-snd_soc_pcm_dai_trigger
看来就是在播放时拉高SD_MODE也就是GPIO4,停止时拉低。
switch (cmd) {case SNDRV_PCM_TRIGGER_START:case SNDRV_PCM_TRIGGER_RESUME:case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:mdelay(max98357a->sdmode_delay);if (max98357a->sdmode_switch) {gpiod_set_value(max98357a->sdmode, 1);dev_dbg(component->dev, "set sdmode to 1");}break;case SNDRV_PCM_TRIGGER_STOP:case SNDRV_PCM_TRIGGER_SUSPEND:case SNDRV_PCM_TRIGGER_PAUSE_PUSH:gpiod_set_value(max98357a->sdmode, 0);dev_dbg(component->dev, "set sdmode to 0");break;}
4 后记
好了,总算写完了。其实可以对比一下之前esp32使用i2s(总线学习5--I2S_max98357接喇叭教程-CSDN博客)。 之前使用python播放wav,连同读取解析wav,转码,相当于干了很多播放器的活,代码也没有超过40行。在linux上使用ALSA真的是复杂了非常非常多。有时候真的怀疑这些是不是过度封装。很多时候代码的存在,到底是一个技术问题,还是一个管理问题,真的要打个问号。。。
不过linux的好处就是有大量的现存代码,如果对linux的机制熟悉,可以简单配置一下就可以发声。这也算勉强方便的地方吧。
看了一下驱动,其实干的事真不多,核心的I2S也没有涉及,后面可能针对ALSA的这部分再单独看看吧。
相关文章:
ALSA架构学习2(驱动MAX98357A)
1 前言和环境 之前其实写过两篇,一篇是讲ALSA,一篇是I2S。 ALSA架构学习1(框架)_alsa框架学习-CSDN博客 总线学习5--I2S_max98357接喇叭教程-CSDN博客 在ALSA那篇的结尾,也提了几个小练习。比如: ### 4…...
数据结构*集合框架顺序表-ArrayList
集合框架 常见的集合框架 什么是顺序表 顺序表是一种线性表数据结构,它借助一组连续的存储单元来依次存储线性表中的数据元素。一般情况下采用数组存储。 在数组上完成数据的增删查改。 自定义简易版的顺序表 代码展示: public interface IArray…...
VMware Workstation 保姆级 Linux(CentOS) 创建教程(附 iso)
文章目录 一、下载二、创建 一、下载 CentOS-7.9-x86_64-DVD-2009.iso 二、创建 VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包)...
51、项⽬中的权限管理怎么实现的
答:权限管理有三个很重要的模块; (1)⽤⼾模块:可以给⽤⼾分配不同的⻆⾊ (2)⻆⾊模块:可以授于⽤⼾不同的⻆⾊,不同的⻆⾊有不同权限 (3)权限模块:⽤于管理系统中的权限接⼝,为⻆⾊提供对…...
软考-信息系统项目管理师-2 信息技术发展
总结思维导图 云计算(掌握) (3)多租户和访问控制管理访问控制管理是云计算应用的核心问题之一云计算访问控制的研究主要集中在云计算访问控制模型、基于ABE密码体制的云计算访问控制、云中多租户及虚拟化访问控制研究云中多租户及虚拟化访问控制是云计算的典型特征。 大数据(…...
Spring Boot JPA 开发之Not an entity血案
项目状况介绍 项目环境 JDK 21Spring Boot 3.4.3Hibernate: 6.6.13.Final项目描述 因为是微服务架构,项目层级如下 project-parent project-com project-A … project-X 其中: project-parent定义依赖库的版本project-com 定义了一些公用的方法和配置,包括持久层的配置。…...
HTMLCSS实现轮播图效果
这段代码实现了一个具有自动轮播、手动切换功能的图片轮播图,并且配有指示器(小圆点)来显示当前图片位置。轮播图可通过左右箭头按钮进行手动切换,也能自动定时切换,当鼠标悬停在轮播图上时,自动轮播会暂停…...
嵌入式学习——opencv图像库编程
环境配置 OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和图像处理库,广泛用于各种计算机视觉任务,如图像处理、视频分析、人脸识别、物体检测、机器学习等。它提供了丰富的函数和工具,用于处理…...
【每日八股】复习 MySQL Day1:事务
文章目录 复习 MySQL Day1:事务MySQL 事务的四大特性?并发事务会出现什么问题?MySQL 事务的隔离级别?不同事务隔离级别下会发生什么问题?MVCC 的实现原理?核心数据结构版本链构建示例可见性判断算法MVCC 可…...
java 设计模式之代理模式
简介 代理模式:使用代理类来增强目标类的功能。在代码结构上,代理对象持有目标对象,通过代理对象访问目标对象,这样可以在不改变目标对象的前提下增加额外的功能,如权限校验、缓存等 代理模式内部的角色:…...
外接键盘与笔记本命令键键位不同解决方案(MacOS)
文章目录 修改键位第一步:打开设置第二步:进入键盘快捷键第三步:修改修饰键设置第四步:调整键位第五步:保存设置tips ikbc c87键盘win键盘没反应的解决亲测的方法这是百度的答案标题常规组合键尝试:型号差…...
python爬虫复习
requests模块 爬虫的分类 通用爬虫:将一整张页面进行数据采集聚焦爬虫:可以将页面中局部或指定的数据进行采集 聚焦爬虫是需要建立在通用的基础上来实现 功能爬虫:基于selenium实现的浏览器自动化的操作分布式爬虫:使用分布式机群…...
kotlin知识体系(五) :Android 协程全解析,从作用域到异常处理的全面指南
1. 什么是协程 协程(Coroutine)是轻量级的线程,支持挂起和恢复,从而避免阻塞线程。 2. 协程的优势 协程通过结构化并发和简洁的语法,显著提升了异步编程的效率与代码质量。 2.1 资源占用低(一个线程可运行多个协程)…...
vscode stm32 variable uint32_t is not a type name 问题修复
问题 在使用vscodekeil开发stm32程序时,发现有时候vscode的自动补全功能失效,且problem窗口一直在报错。variable “uint32_t” is not a type name uint32_t 定义位置 uint32_t 实际是在D:/Keil_v5/ARM/ARMCC/include/stdint.h中定义的。将D:/Keil_v5…...
Formality:Bug记录
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 本文记录博主在使用Synopsys的形式验证工具Formality中遇到的一个Bug。 Bug复现 情况一 // 例1 module dff (input clk, input d_in, output d_out …...
在ubuntu20.04+系统部署VUE及Django项目的过程记录——以腾讯云为例
目录 1. 需求2. 项目准备3. VUE CLI项目部署3.1 部署前的准备3.1.1 后端通信路由修改3.1.2 导航修改 3.2 构建项目3.3 配置nginx代理 4. 后端配置4.1 其他依赖项4.2 单次执行测试4.3 创建Systemd 服务文件4.4 配置 Nginx 作为反向代理 5. 其他注意事项 1. 需求 近期做一些简单…...
回归,git 分支开发操作命令
核心分支说明 主分支(master/production)存放随时可部署到生产环境的稳定代码,仅接受通过测试的合并请求。 开发分支(develop)集成所有功能开发的稳定版本,日常开发的基础分支,从该分支创建特性…...
【java+Mysql】学生信息管理系统
学生信息管理系统是一种用于管理学生信息的软件系统,旨在提高学校管理效率和服务质量。本课程设计报告旨在介绍设计和实现学生信息管理系统的过程。报告首先分析了系统的需求,包括学生基本信息管理、成绩管理等功能。接着介绍了系统的设计方案࿰…...
小白从0学习网站搭建的关键事项和避坑指南(2)
以下是针对小白从零学习网站搭建的 进阶注意事项和避坑指南(第二期),覆盖开发中的高阶技巧、常见陷阱及解决方案,帮助你在实战中提升效率和质量: 一、进阶技术选型避坑 1. 前端框架选择 误区:盲目追求最新…...
Windows 10 上安装 Spring Boot CLI详细步骤
在 Windows 10 上安装 Spring Boot CLI 可以通过以下几种方式完成。以下是详细的步骤说明: 1. 手动安装(推荐) 步骤 1:下载 Spring Boot CLI 访问 Spring Boot CLI 官方发布页面。下载最新版本的 .zip 文件(例如 sp…...
spring boot -- 配置文件application.properties 换成 application.yml
在Spring Boot项目中,application.properties和application.yml是两种常用的配置文件格式,它们各自具有不同的特点和适用场景2。以下是它们之间的主要差异2: 性能差异 4: 加载机制 2: application.properties文件会被加载到内存中,并且只加载一次,之后直接从内存中读取…...
Spring Boot实战:基于策略模式+代理模式手写幂等性注解组件
一、为什么需要幂等性? 核心定义:在分布式系统中,一个操作无论执行一次还是多次,最终结果都保持一致。 典型场景: 用户重复点击提交按钮网络抖动导致的请求重试消息队列的重复消费支付系统的回调通知 不处理幂等的风…...
【Rust 精进之路之第14篇-结构体 Struct】定义、实例化与方法:封装数据与行为
系列: Rust 精进之路:构建可靠、高效软件的底层逻辑 作者: 码觉客 发布日期: 2025-04-20 引言:超越元组,给数据赋予意义 在之前的学习中,我们了解了 Rust 的基本数据类型(标量)以及两种基础的复合类型:元组 (Tuple) 和数组 (Array)。元组允许我们将不同类型的值组合…...
postgres 数据库信息解读 与 sqlshell常用指令介绍
数据库信息: sqlshell Server [localhost]: 192.168.30.101 Database [postgres]: Port [5432]: 5432 Username [postgres]: 用户 postgres 的口令: psql (15.12, 服务器 16.8 (Debian 16.8-1.pgdg120+1)) 警告:psql 主版本15,服务器主版本为16.一些psql功能可能无法正常使…...
论文阅读:2024 arxiv DeepInception: Hypnotize Large Language Model to Be Jailbreaker
总目录 大模型安全相关研究:https://blog.csdn.net/WhiffeYF/article/details/142132328 DeepInception: Hypnotize Large Language Model to Be Jailbreaker DeepInception:催眠大型语言模型,助你成为越狱者 https://arxiv.org/pdf/2311.0…...
vue2技术练习-开发了一个宠物相关的前端静态商城网站-宠物商城网站
为了尽快学习掌握相关的前端技术,最近又实用 vue2做了一个宠物行业的前端静态网站商城。还是先给大家看一下相关的网站效果: 所以大家如果想快速的学习或者掌握一门编程语言,最好的方案就是通过学习了基础编程知识后,就开始利用…...
嵌入式学习——远程终端登录和桌面访问
目录 通过桥接模式连接虚拟机和Windows系统 1、桥接模式 2、虚拟机和Windows连接(1) 3、虚拟机和Windows连接(2) 在Linux虚拟机中创建新用户 Windows系统环境下对Linux系统虚拟机操作 远程登录虚拟机(1ÿ…...
wpf stylet框架 关于View与viewmodel自动关联绑定的问题
1.1 命名规则 Aview 对应 AVIewModel, 文件夹 views 和 viewmodels 1.2 需要注册服务 //RootViewModel是主窗口 public class Bootstrapper : Bootstrapper<RootViewModel>{/// <summary>/// 配置IoC容器。为数据共享创建服务/// </summary…...
如何新建一个空分支(不继承 master 或任何提交)
一、需求分析: 在 Git 中,我们通常通过 git branch 来新建分支,这些分支默认都会继承当前所在分支的提交记录。但有时候我们希望新建一个“完全干净”的分支 —— 没有任何提交,不继承 master 或任何已有内容,这该怎么…...
HarmonyOS-ArkUI-动画分类简介
本文的目的是,了解一下HarmonyOS动画体系中的分类。有个大致的了解即可。 动效与动画简介 动画,是客户端提升界面交互用户体验的一个重要的方式。可以使应用程序更加生动灵越,提高用户体验。 HarmonyOS对于界面的交互方面,围绕回归本源的设计理念,打造自然,流畅品质一提…...
