参考文章:【正点原子】I.MX6U嵌入式Linux驱动开发——Linux 音频驱动
STM32MP1 带有 SAI 接口,正点原子的 STM32MP1 开发板通过此接口外接了一个 CS42L51 音频 DAC 芯片,本节就学习一下如何使能 CS42L51 驱动,并且 CS42L51 通过芯片来完成音乐播放与录音。
处理器要想“听到”外界的声音,就必须把外界的声音转化为自己能够理解的“语言”。这里就涉及到一个模拟信号转换为数字信号的过程,通常使用ADC 芯片完成这个功能。同理,如果处理器要向外界传达自己的“心声”,也就是放音,就需要将数字信号转化为模拟信号,而完成这个功能的是 DAC 芯片。但是音频不单单是能出声、能听到就行。生活中往往需要听到的声音动听、录进去的语音贴近真实、可以调节音效、对声音能够进行一些处理(需要 DSP 单元)、拥有统一的标准接口,方便开发等等。所以需要一个专门用于音频的芯片,也就是音频编解码芯片,英文名字就是 Audio CODEC。
CS42L51 特性:
- 24 位转换器
- 支持 4kHz~96kHz 的采样率
- 多位 Delta Sigma 架构
- 低功耗,立体声播放的时候 1.8V 电压下功耗为 12.93mW。录音与播放一起工作时为 20.18mW
- 数字信号处理引擎
- 3:1 立体声输入选择器
- 带有溢漫的自动水平监控
I2S 总线用于主控制器和音频 CODEC 芯片之间传输音频数据,本节就是使用 I2S 协议来完成的。I2S 接口需要 3 根信号线(如果需要实现收和发,那么就要 4 根信号线,收和发分别使用一根信号线):
SCK:串行时钟信号,也叫做位时钟(BCLK),音频数据的每一位数据都对应一个 SCK,立体声都是双声道的,因此 SCK=2×采样率×采样位数。比如采样率为 44.1KHz、16 位的立体声音频,那么 SCK=2× 44100× 16=1411200Hz=1.4112MHz。
WS:字段(声道)选择信号,也叫做 LRCK,也叫做帧时钟,用于切换左右声道数据,WS 为 “1”表示正在传输左声道的数据,WS 为“0”表示正在传输右声道的数据。WS 的频率等于采样率,比如采样率为 44.1KHz 的音频,WS=44.1KHz。
SD:串行数据信号,也就是我们实际的音频数据,如果要同时实现放音和录音,那么就需要 2 根数据线,比如 CS42L51 的 SDOUT 和 SDIN,就是分别用于放音和录音。不管音频数据是多少位的,数据的最高位都是最先传输的。数据的最高位总是出现在一帧开始后(LRCK 变化)的第 2 个 SCK 脉冲处。
另外,有时候为了使音频 CODEC 芯片与主控制器之间能够更好的同步,会引入另外一个叫做 MCLK 的信号,也叫做主时钟或系统时钟,一般是采样率的 256 倍或 384 倍。
SAI 接口(串行音频接口)灵活性高、配置多样,可支持多种音频协议。该接口适用许多立体声或单声道应用。支持 I2S 标准、LSB 或 MSB 对齐、PCM/DSP、TDM 等等协议。全称为 Synchronous Audio Interface, SAI 的特性:
- 有两个独立的音频子模块,子模块可以用作接收和发送功能,并且带有自身的 FIFO
- 每个音频子模块集成多达 8 个字,每个字 32 位的 FIFO
- 两个音频子模块间可以是同步或异步模式
- 多个 SAI 之间看实现同步
- 两个音频子模块的主/从配置相互独立
- 数据大小可配置:8 位、10 位、16 位、20 位、24 位或 32 位
原理图:
①SAI 接口一共用到了 5 根数据线,这个 5 根数据线用于 STM32MP1 与 CS42L51 之间的音频数据收发。
②CS42L51 在使用的时候需要进行配置,配置接口为 I2C,连接到 STM32MP1 的 I2C4 上。
③CS42L51 是没有功放接口的,需要提供一个 HT6872 把 HP_R 耳机数据放大。
还有在开发板上,音频的耳机和 MIC 录音功能由 JP13 来控制。 1-2 连接就使用耳机进行录音,2-3 连接就使用板载 MIC 录音。
根据原理图,CS42L51 连接到了 STM32MP1 的 I2C4 接口。所以需要在设备树中的 i2c4 节点下添加 CS42L51 子节点。在 Documentation/devicetree/bindings/sound/cs42l51.txt 文档中介绍了如何设置节点信息,也可以拷贝 arch/arm/boot/dts/stm32mp15xx-dkx.dtsi 中的节点信息进行修改。最终结果如下:
&i2c4 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c4_pins_a>;
pinctrl-1 = <&i2c4_pins_sleep_a>;
status = "okay";
/delete-property/dmas;
/delete-property/dma-names;
cs42l51: cs42l51@4a {
compatible = "cirrus,cs42l51";
reg = <0x4a>;
#sound-dai-cells = <0>;
VL-supply = <&v3v3>;
VD-supply = <&v1v8_audio>;
VA-supply = <&v1v8_audio>;
VAHP-supply = <&v1v8_audio>;
reset-gpios = <&gpioz 7 GPIO_ACTIVE_LOW>;
clocks = <&sai2a>;
clock-names = "MCLK";
status = "okay";
cs42l51_port: port {
#address-cells = <1>;
#size-cells = <0>;
cs42l51_tx_endpoint: endpoint@0 {
reg = <0>;
remote-endpoint = <&sai2a_endpoint>;
frame-master;
bitclock-master;
};
cs42l51_rx_endpoint: endpoint@1 {
reg = <1>;
remote-endpoint = <&sai2b_endpoint>;
frame-master;
bitclock-master;
};
};
};
};
①设置 I2C4 引脚,与官方的 i2c4 引脚相同,可以直接使用。
②设置复位引脚为 PZ7,并设置供电为一个 1.8v 的节点(后续添加)。
③cs42l51_port 子节点用来设置 CS42L51 的数据输入/输出端口信息;cs42l51_tx_endpoint 端口用来接收 CPU 上哪个 SAI 的数据(即&sai2a_endpoint);cs42l51_rx_endpoint 端口用来将获取到的音频数据发送到 CPU 的哪个 SAI 接口上(即sai2b_endpoint)。
v1v8_audio: regulator-v1v8-audio {
compatible = "regulator-fixed";
regulator-name = "v1v8_audio";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
regulator-always-on;
regulator-boot-on;
};
SAI 音频接口相关文档介绍在devicetree/bindings/sound/st,stm32-sai.txt中。在 stm32mp151.dtsi 文件中有ST官方关于 SAI 相关接口的描述。直接将 stm32mp15xx-dkx.dtsi(对应的是ST 官方的 DK2 开发板) 中的相关描述复制进自己的设备树,并添加相关引脚定义即可。
&sai2 {
clocks = <&rcc SAI2>, <&rcc PLL3_Q>, <&rcc PLL3_R>;
clock-names = "pclk", "x8k", "x11k";
pinctrl-names = "default", "sleep";
pinctrl-0 = <&sai2a_pins_a>, <&sai2b_pins_b>;
pinctrl-1 = <&sai2a_sleep_pins_a>, <&sai2b_sleep_pins_b>;
status = "okay";
sai2a: audio-controller@4400b004 {
#clock-cells = <0>;
dma-names = "tx";
clocks = <&rcc SAI2_K>;
clock-names = "sai_ck";
status = "okay";
sai2a_port: port {
sai2a_endpoint: endpoint {
remote-endpoint = <&cs42l51_tx_endpoint>;
format = "i2s";
mclk-fs = <256>;
dai-tdm-slot-num = <2>;
dai-tdm-slot-width = <32>;
};
};
};
sai2b: audio-controller@4400b024 {
dma-names = "rx";
st,sync = <&sai2a 2>;
clocks = <&rcc SAI2_K>, <&sai2a>;
clock-names = "sai_ck", "MCLK";
status = "okay";
sai2b_port: port {
sai2b_endpoint: endpoint {
remote-endpoint = <&cs42l51_rx_endpoint>;
format = "i2s";
mclk-fs = <256>;
dai-tdm-slot-num = <2>;
dai-tdm-slot-width = <32>;
};
};
};
};
主要是对 sai2 节点做了四个方面描述: SAI2 接口引脚的 pinctrl 设置、时钟配置、修改 status 为“okay” 以及指定收发接口。
其中 pinctrl 的两组 IO 需要根据实际情况修改,IO 定义在 stm32mp15-pinctrl.dtsi 中,本节的 IO 与官方一致。
在设备树的根节点/
下创建一个 sound 子节点,用作控制音频数据的收发。参考文档: Documentation/devicetree/bindings/sound/audio-graph-card.txt。此文档有很多种配置方法,需要根据自己的音频选择对应的配置方法。
参考 DK2 开发板的设置,将 stm32mp15xx-dkx.dtsi 中的相关设置拷贝并修改。
sound: sound {
compatible = "audio-graph-card";
label = "STM32MP1-DK";
routing =
"Playback" , "MCLK",
"Capture" , "MCLK",
"MICL" , "Mic Bias";
dais = <&sai2a_port &sai2b_port>;
status = "okay";
};
①compatible:用于与驱动匹配,匹配的驱动为sound/soc/generic/audiograph-card.c。
②model:最终用户看到的此声卡名字,这里设置为“STM32MP1-DK”
③outing:音频器件一系列的连接设置,每个条目都是一对字符串,第一个字符串是连接的 sink,第二个是连接的 source(源)。
④dais:用来指定音频有多少个接收和发送,sai2a_port 和 sai2b_port 就是 CS42L51 发送和接收接口。
CS42L51 默认驱动的录音为单通道,所以需要修改驱动来实现双通道录音。在 sound/soc/codecs/cs42l51.c 文件中,找到 cs42l51_hw_params 函数,添加如下内容:
修改完后编译出新的内核镜像和设备树,用于启动开发板。
启动开发板时会打印以下信息:
启动以后会打印出 ALSA 设备列表,因为音频 CODEC 驱动基本都是 ALSA 架构的,在 ALSA 设备列表中可以找到“STM32MP1-DK”这个声卡。
在 /dev/snd 目录下,可以查找到以下文件:
controlC0:用于声卡控制,C0 表示声卡 0。
pcmC0D0p:用于播放的 pcm 设备,最后面的“p”是 playback 的缩写,表示放音。
pcmC0D1c:用于录音的 pcm 设备,最后面的“c”是 capture 的缩写,表示录音。
timer:定时器。
音频驱动使能以后还不能直接播放音乐或录音,还需要移植 alsa-lib 和 alsa-utils 这两个库。
alsa-lib 这个库在 buildroot 已经默认编译进去,所以可以不用再修改。
在 buildroot 源码目录下,进入 menuconfig,设置 alsa-utils,把 alsa-utils 下的软件全部选中:
然后编译出新的rootfs放入nfs中。
amixer 使用:
声卡相关选型默认都是关闭的,比如耳机和喇叭的左右声道输出等。因此在使用之前一定要先设置好声卡,alsa-utils 自带了 amixer 这个声卡设置工具。
使用amixer --help
命令查看帮助信息:
amixer 软件命令分为两组,scontrols、scontents、sset 和 sget 为一组。controls、contents、cset 和 cget 为另一组。这两组的基本功能都是一样的,只不过“s”开头的是 simple(简单)组,这一组命令是简化版。
使用 amixer scontrols
命令查看所有设置项:
再使用amixer controls
查看另一组设置项:
不同的设置项对应的设置值类型不同,使用amixer scontents
查看scontents对应的设置值:
可以看出“PCM”项目就是设置耳机音量的,音量范围为 0-127,当前音量为 103。有些设置项是 bool 类型,只有 on 和 off 两种状态。关于 controls 对应的设置值自行输入“amixer controls”命令查看即可。
使用amixer sset 设置项目 设置值
或者amixer cset 设置项目 设置值
来设置。
使用amixer sget 设置项目
或amixer cget 设置项目
命令。
首先使用 amixer 设置声卡,命令如下;
amixer cset name='PCM Playback Switch' 'on','on'
amixer cset name='PCM Playback Volume' '63','63'
amixer cset name='Analog Playback Volume' '204','204'
amixer cset name='PCM channel mixer' 'L R'
然后使用 aplay 播放 wav 格式音乐,buildroot 编译 alsa-utils 时已经在/usr/share/sounds/alsa/路径下存放了一些音乐,可以直接用来测试:
aplay /usr/share/sounds/alsa/Front_Right.wav //播放歌曲
注:由于 CS42L51 只有耳机播放的输出接口,没有喇叭接口,正点原子喇叭直接接到了耳机右声道上,所以播放时耳机和喇叭同时有声音。
将 JP13 跳接到 MIC 上,就可以使用录音功能。
首先还是设置声卡:
amixer cset name='PGA-ADC Mux Left' '3'
amixer cset name='PGA-ADC Mux Right' '3'
amixer cset name='Mic Boost Volume' '1','1'
然后使用 arecord 录制音频:
arecord -f S16_LE -d 10 -D hw:0,1 record.wav
-f 是用来设置录音的数据以 S16_LE 格式进行采样。-d 是指定录音时间,单位为 s,-D 是指定硬件声卡,0 是卡数,1 是在设备数量。这条指令就是录制一段以 S16_LE 格式 10s 的 wav 音频,音频名字为 record.wav。
录制完成以后使用 aplay 播放刚刚录制的音频:
aplay record.wav
phone 录音测试,也就是使用耳机上的 MIC 录音,前提是你的耳机支持录音。
录音方式与上述 MIC 相同。
首先使用 alsactl 保存声卡设置:
在上方配置完声卡信息后,使用如下命令保存:
alsactl -f /var/lib/alsa/asound.state store
-f 指定声卡配置文件,store 表示保存。关于 alsactl 的详细使用方法,输入“alsactl -h”即可查看。保存成功以后就会生成/var/lib/alsa/asound.state 文件,asound.state 里面就是关于声卡的各种设置信息。
如果要使用 asound.state 中的配置信息来配置声卡,执行以下命令:
alsactl -f /var/lib/alsa/asound.state restore
最后面的参数改为 restore 即可,也就是恢复的意思。
配置开机自启:
使用/etc/init.d/rcS 文件,添加以下内容:
if [ -f "/var/lib/alsa/asound.state" ]; then
echo "ALSA: Restoring mixer setting......"
/usr/sbin/alsactl -f /var/lib/alsa/asound.state restore &
fi
第 1 行判断/var/lib/alsa/asound.state 这个文件是否存在,存在的话就执行下面的。首先输出一行提示符:“ALSA: Restoring mixer setting…”,表示设置声卡,最后调用/usr/sbin/alsactl 来执行声卡设置工作。
alsamixer 是基于图形化的,直接输入“alsamixer”命令即可打开声卡配置界面:
F1 键:查看帮助信息
F2 键:查看系统信息
F3 键:播放设置
F4 键:录音设置
F6 键:选择声卡,多声卡情况下
Item: 设置项全名
最下面一行就是具体的设置项,比如“Bass”、“PCM”等等,通过键盘上左右键选择设置项。按下上下键来调整大小,比如设置耳机音量大小等。有些项目会显示“MM”,表示静音,按下“M”键修改为“OO”状态打开,“M”键用于修改打开或关闭某些项目。