于我而言,音频驱动是一个较为复杂的驱动子系统。前几年曾在嵌入式linux中开发过CS4344音频驱动,这款音频芯片跟AT73C213非常相似,所以驱动是“山寨”AT73C213驱动的,驱动开发异常顺利,几乎没有遇到什么问题。仅管如此,单理解这个驱动就费了不少劲,特别是理解linux中ALSA驱动子系统。
此外,音频驱动子系统中,还有很多概念需要理解,如PCM、音频采样频率、声道数目及其采样比特数等。
eCos中没有I2S驱动,也没有像USB、framebuf一样,提供一个音频IO子系统,但不代表我们无所作为。通过本次实现的eCos STM32 I2S驱动,可以了解到以下3个方面知识:
- 如何在eCos中添加一个新的驱动组件;
- 了解eCos中DMA + 中断 + 条件信号的应用;
- 了解eCos中如何实现一个设备驱动依赖于另一个设备驱动的实现;
我觉得以上3点是i2s驱动本身之外最重要的知识内容点。
STM32 I2S与SPI接口
STM32中有3个SPI接口和2个I2S接口,但是,2个I2S接口与2个SPI接口是复用的。这里我们使用的是I2S2接口。
音频硬件模块组成
首先了解下板子上音频系统由哪些硬件模块组成,确定要驱动哪些器件。如下图所示:
上图是来自安富莱开发板的音频系统,由3部组成:CPU、音频CODEC、音频输入输出。更具体的请参考安富莱开发板附带的原理图。
其中,wm8978是一个I2S接口立体声输出的音频codec,负责把I2S音频流转换输出到耳机或扬声器中,以及采集MIC或耳麦音频信号实现录音采集。AUX输入是FM收音机输入信号,通过它,可以实现FM收音机功能。
从上图可以看到,CPU通过I2C来控制wm8978,通过I2S把音频数据传送给WM8978。所以,我们要在eCos中实现两个驱动:
- STM32 I2S驱动:主要负责stm32 CPU与WM8978之间音频数据流的传输,以及STM32 I2S接口的控制,如I2S模式、音频协议、采样速率、DMA和中断等设置;
- WM8978驱动:通过I2C初始化和控制WM8978,如上电初始化、音量调节、声道选择、采样位数等;
eCos音频驱动框架
这里所说的音频驱动框架并不像USB、framebuf等那样完善,我们的目标是,让用户应用程序更好更方便地控制声卡驱动,从而实现音频播放。
从上面的描述,你是否发现:STM32 I2S驱动和WM8978驱动实际上有一定耦合关系。也就是:STM32 I2S驱动传输的音频数据流的对象是WM8978,而WM8978驱动则不负责音频数据流。此外,一些音频控制流数据如音频协议的设置需要同时提交给两者。
从这点出发,这两个驱动考虑有两种设计模式:分开设计模式和依赖设计模式。如下图所示。
之所以搞得这么“复杂”,是因为eCos中没有像USB、framebuf一样提供一个“适配”音频IO层,所以换作直接使用eCos提供的一般IO层API,即read、write、set_config等API。从这里也看到,有一个“适配”IO层是一件多么好的事情,当然这是题外话,如果你有兴趣,你可以尝试写一个音频IO层。
分开设计模式
分开设计指的是STM32 I2S驱动和WM8978驱动互为独立,互不依赖,具体的音频控制和音频数据由用户应用程序管理。这种设计方式有个缺点就是:需要用户应用程序知道更多的控制细节。比如,设置采样速率,则需要同时设置这两个驱动的相关参数;而设置音量,则仅需要设置WM8978驱动的相关参数。
当然,也可以把所有需要设置的参数在两个驱动中都实现,用户程序设置某个参数时,同时设置两个驱动的参数。如果某个驱动不需要这个设置参数,留空则可!
依赖设计模式
所谓依赖设计模式是指:对于用户应用程序而言,它仅能看到一个声卡设备文件:/dev/dsp。所有的音频数据流和音频控制流都是对这个声卡设备的。STM32 I2S驱动和WM8978驱动作为一个整体,其中,把WM8978驱动视为用户程序能看到的声卡设备驱动,而把STM32 I2S驱动作为这个声卡设备的数据传输通道,关系上,STM32 I2S驱动作为WM8978驱动的依赖设备,类似于eCos中的tty驱动与serial驱动的关系。
哪种驱动模式好?
分开设计模式和依赖设计模式哪种方式更好呢?我最后的选择是依赖设计模式。一方面,简化用户应用程序设计,甚至如果设计的好,可以兼容linux上的程序;另一方面,两种设计模式,对于驱动而言,复杂度相当,这得益于eCos良好的驱动架构体系!
STM32 I2S驱动机制实现
由于STM32 I2S接口与SPI接口是复用的,所以I2S驱动在设计上直接借鉴了eCos STM32 SPI驱动,代码实现是以SPI驱动为模板。
不同的是,I2S驱动中去掉了SPI驱动中使用的POLL模式,而直接使用DMA中断方式来传输数据。
为保证音频数据流连续的传输,设计使用DMA双缓存,也就是所谓的DMA乒乓模式。当一个缓存数据传输完成后,产生中断,然后继续传输另一个缓存数据。与此同时,驱动主线程把剩余音频流数据装载到刚刚传输完成的缓存中,供下一次DMA传输使用。
wm8978驱动机制实现
WM8978驱动大部分是直接拷贝安富莱开发板例程中的WM8978 BSP驱动源码。
驱动调试经验
这次eCos音频驱动调试花费不了精力,虽然驱动初步可用,但依然还有这样那样的问题,因此还需要后续继续完善。总结下调试经验,如下所述:
- 如果没有相关音频知识,建议先了解下相关音频知识,如音频采样速率、左右声道、I2S接口等;
- 先阅读下STM32参考手册SPI章节中关于I2S的描述和使用(应该是23章节);
- 了解下STM32参考手册DMA章节知识(应该是10章节);
- 看懂开发板中某个音频相关的例程,并且下到板子上体验下;
- WM8978的驱动直接拷贝自开发板例程,所以这个省事不少;
- 调试的重点应该放在I2S DMA中断和数据传输上。我在这里范了个错误导致调试了很久:配置DMA传输数量时,配置的是要传输数据的字节数。实际上DMA是以16bit为传输单位,所以配置DMA传输数量时应该是要传输数据的半字数,比如音频数据流1024 bytes,则配置DMA传输数量为512 bytes,否则则会多传输数据。
驱动源码下载
本人编写的驱动编码下载地址:http://52ecos.net/thread-558-1-1.html
下节将介绍下STM32 I2S驱动和WM8978驱动的具体实现细节。
推荐阅读相关文章:
- stm32移植ecos #33,ecos sd driver,SD卡驱动(3)
- stm32移植ecos #27,串行SPI flash驱动移植(下)
- stm32移植ecos #26,串行SPI flash驱动移植(上)
- stm32移植ecos #25,eCos i2c driver for stm32
- stm32移植ecos #34,ecos sd driver,SD卡驱动(4)
- stm32移植ecos #31,ecos sd driver,SD卡驱动(1)
- stm32移植ecos #30,ecos i2s driver,音频驱动(下)
- stm32移植ecos #29,ecos i2s driver,音频驱动(中)
依赖设计模式。一方面,简化用户应用程序设计,甚至如果设计的好,可以兼容linux上的程序;
郭总,不错哦~很正解
前面手抖了一下就发送了
哈哈,要蛋定啊……