ALSA架构学习2(驱动MAX98357A)

news2025/4/21 20:49:46

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/cards
 0 [Headphones     ]: bcm2835_headpho - bcm2835 Headphones
                      bcm2835 Headphones
 1 [MAX98357A      ]: simple-card - MAX98357A
                      MAX98357A
 2 [vc4hdmi        ]: vc4-hdmi - vc4-hdmi
                      vc4-hdmi

此时在系统中也已经可以看到。

2.4 硬件连接

根据引脚说明连接即可。 

详细连接如下:

MAX98357A PinRaspberry Pi 3B Pin描述
DINGPIO21 / Pin 40I²S 数据输出
BCLKGPIO18 / Pin 12I²S 时钟
LRCLKGPIO19 / Pin 35I²S 帧时钟
SD_MODEGPIO4 / Pin 7(可选)控制 DAC 启动(如果驱动中有)
GNDPin 6 或任意 GND
VDDPin 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);
#endif

static 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 max98
         mplayer-2146    [003] d..1.  3792.358634: max98357a_daiops_trigger <-snd_soc_pcm_dai_trigger
         mplayer-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的这部分再单独看看吧。

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

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

相关文章

数据结构*集合框架顺序表-ArrayList

集合框架 常见的集合框架 什么是顺序表 顺序表是一种线性表数据结构&#xff0c;它借助一组连续的存储单元来依次存储线性表中的数据元素。一般情况下采用数组存储。 在数组上完成数据的增删查改。 自定义简易版的顺序表 代码展示&#xff1a; public interface IArray…

VMware Workstation 保姆级 Linux(CentOS) 创建教程(附 iso)

文章目录 一、下载二、创建 一、下载 CentOS-7.9-x86_64-DVD-2009.iso 二、创建 VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包)

软考-信息系统项目管理师-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实现轮播图效果

这段代码实现了一个具有自动轮播、手动切换功能的图片轮播图&#xff0c;并且配有指示器&#xff08;小圆点&#xff09;来显示当前图片位置。轮播图可通过左右箭头按钮进行手动切换&#xff0c;也能自动定时切换&#xff0c;当鼠标悬停在轮播图上时&#xff0c;自动轮播会暂停…

嵌入式学习——opencv图像库编程

环境配置 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和图像处理库&#xff0c;广泛用于各种计算机视觉任务&#xff0c;如图像处理、视频分析、人脸识别、物体检测、机器学习等。它提供了丰富的函数和工具&#xff0c;用于处理…

【每日八股】复习 MySQL Day1:事务

文章目录 复习 MySQL Day1&#xff1a;事务MySQL 事务的四大特性&#xff1f;并发事务会出现什么问题&#xff1f;MySQL 事务的隔离级别&#xff1f;不同事务隔离级别下会发生什么问题&#xff1f;MVCC 的实现原理&#xff1f;核心数据结构版本链构建示例可见性判断算法MVCC 可…

外接键盘与笔记本命令键键位不同解决方案(MacOS)

文章目录 修改键位第一步&#xff1a;打开设置第二步&#xff1a;进入键盘快捷键第三步&#xff1a;修改修饰键设置第四步&#xff1a;调整键位第五步&#xff1a;保存设置tips ikbc c87键盘win键盘没反应的解决亲测的方法这是百度的答案标题常规组合键尝试‌&#xff1a;型号差…

kotlin知识体系(五) :Android 协程全解析,从作用域到异常处理的全面指南

1. 什么是协程 协程(Coroutine)是轻量级的线程&#xff0c;支持挂起和恢复&#xff0c;从而避免阻塞线程。 2. 协程的优势 协程通过结构化并发和简洁的语法&#xff0c;显著提升了异步编程的效率与代码质量。 2.1 资源占用低&#xff08;一个线程可运行多个协程&#xff09;…

vscode stm32 variable uint32_t is not a type name 问题修复

问题 在使用vscodekeil开发stm32程序时&#xff0c;发现有时候vscode的自动补全功能失效&#xff0c;且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 …

【java+Mysql】学生信息管理系统

学生信息管理系统是一种用于管理学生信息的软件系统&#xff0c;旨在提高学校管理效率和服务质量。本课程设计报告旨在介绍设计和实现学生信息管理系统的过程。报告首先分析了系统的需求&#xff0c;包括学生基本信息管理、成绩管理等功能。接着介绍了系统的设计方案&#xff0…

小白从0学习网站搭建的关键事项和避坑指南(2)

以下是针对小白从零学习网站搭建的 进阶注意事项和避坑指南&#xff08;第二期&#xff09;&#xff0c;覆盖开发中的高阶技巧、常见陷阱及解决方案&#xff0c;帮助你在实战中提升效率和质量&#xff1a; 一、进阶技术选型避坑 1. 前端框架选择 误区&#xff1a;盲目追求最新…

Windows 10 上安装 Spring Boot CLI详细步骤

在 Windows 10 上安装 Spring Boot CLI 可以通过以下几种方式完成。以下是详细的步骤说明&#xff1a; 1. 手动安装&#xff08;推荐&#xff09; 步骤 1&#xff1a;下载 Spring Boot CLI 访问 Spring Boot CLI 官方发布页面。下载最新版本的 .zip 文件&#xff08;例如 sp…

vue2技术练习-开发了一个宠物相关的前端静态商城网站-宠物商城网站

为了尽快学习掌握相关的前端技术&#xff0c;最近又实用 vue2做了一个宠物行业的前端静态网站商城。还是先给大家看一下相关的网站效果&#xff1a; 所以大家如果想快速的学习或者掌握一门编程语言&#xff0c;最好的方案就是通过学习了基础编程知识后&#xff0c;就开始利用…

嵌入式学习——远程终端登录和桌面访问

目录 通过桥接模式连接虚拟机和Windows系统 1、桥接模式 2、虚拟机和Windows连接&#xff08;1&#xff09; 3、虚拟机和Windows连接&#xff08;2&#xff09; 在Linux虚拟机中创建新用户 Windows系统环境下对Linux系统虚拟机操作 远程登录虚拟机&#xff08;1&#xff…

如何新建一个空分支(不继承 master 或任何提交)

一、需求分析&#xff1a; 在 Git 中&#xff0c;我们通常通过 git branch 来新建分支&#xff0c;这些分支默认都会继承当前所在分支的提交记录。但有时候我们希望新建一个“完全干净”的分支 —— 没有任何提交&#xff0c;不继承 master 或任何已有内容&#xff0c;这该怎么…

Qt编写推流程序/支持webrtc265/从此不用再转码/打开新世界的大门

一、前言 在推流领域&#xff0c;尤其是监控行业&#xff0c;现在主流设备基本上都是265格式的视频流&#xff0c;想要在网页上直接显示监控流&#xff0c;之前的方案是&#xff0c;要么转成hls&#xff0c;要么魔改支持265格式的flv&#xff0c;要么265转成264&#xff0c;如…

[第十六届蓝桥杯 JavaB 组] 真题 + 经验分享

A&#xff1a;逃离高塔(AC) 这题就是简单的签到题&#xff0c;按照题意枚举即可。需要注意的是不要忘记用long&#xff0c;用int的话会爆。 &#x1f4d6; 代码示例&#xff1a; import java.io.*; import java.util.*; public class Main {public static PrintWriter pr ne…

深⼊理解 JVM 执⾏引擎

深⼊理解 JVM 执⾏引擎 其中前端编译是在 JVM 虚拟机之外执⾏&#xff0c;所以与 JVM 虚拟机没有太⼤的关系。任何编程语⾔&#xff0c;只要能够编译出 满⾜ JVM 规范的 Class ⽂件&#xff0c;就可以提交到 JVM 虚拟机执⾏。⾄于编译的过程&#xff0c;如果你不是想要专⻔去研…