S3C2416裸机开发系列十八_音频驱动实现(1)

S3C2416裸机开发系列十八

音频驱动实现(1)

象棋小子    1048272975

在消费电子产品中,往往都会用到音频系统来播放音乐、进行通话等多媒体应用,此外,对于一些需语音提示的产品,音频部分都是不可或缺的功能。笔者此处就s3c2416的音频驱动实现作一个简单的介绍。

1. IIS音频总线

s3c2416支持IIS、PCM、AC97这三种音频接口,此处只分析IIS音频接口。IIS接口(Inter-IC Sound)在20世纪80年代首先被飞利浦公司用于消费音频,为数字音频设备之间的音频数据传输而制定的一种总线标准。IIS有以下三个主要的信号:

1) 串行时钟SCLK,也叫做位时钟(BCLK)。数字音频的每一位均需对应一个SCLK脉冲,因此位时钟频率应大于等于2*采样频率*采样位数。乘以2表示每个采样会产生左声道和右声道的数据。

2) 帧时钟LRCK,也叫做左右声道切换时钟(WS)。LRCK为1时表示传输的是右声道的数据,为0时表示传输的是左声道的数据,因此IIS是非常适合于立体声系统的。LRCK是一个占空比约50%的方波,这个频率是需要尽可能与采样频率一致的,不然无法体现原来的音频本质。

3) 串行数据SDATA,用二进制补码表示音频数据。在串行时钟SCLK脉冲下,数据一位一位出现在SDATA线上。对于具体的IIS主机或设备,为支持全双工(例如通话时需同时支持放音与录音),串行数据线分串行输入SDI和串行输出SDO这两根。SDI用来传输采样设备数字化后的录音数据,SDO用来传输需播放的音频数据。

有时为了IIS主控制与IIS设备能够更好的同步,还需要传输一路时钟信号MCLK,也叫做主时钟。主要用于IIS设备A/D、D/A采样时的采样时钟,一般是采样频率的256倍、384倍、512倍、768倍。在满足要求的条件下,应尽可能选用较低的主时钟。

2. WM8960音频编解码器

WM8960是欧胜微电子推出的一款低功耗、高质量的立体编码解码器。该芯片内置有麦克风接口、立体声耳机驱动器以及D类立体声扬声器驱动,24比特模数转换器(ADC)和数模转换器(DAC)。

WM8960具有三对左右声道的模拟输入,其中INPUT1专用于Mic输入,支持单端或差分的Mic信号接入。这个输入具有一个程控放大器(PGA),并且可用自动电平控制(ALC)对Mic信号进行增益放大。其它的INPUT2、3可做为Mic差分接入的同相输入或线输入。

WM8960具有一对左右声道的耳机输出,16欧负载时,输出40mW。一对D类左右声道扬声器输出,每声道8欧负载,在1W输出功率时,具有87%的效率。一路左右声道混合输出。

3. WM8960驱动编写

声音是模拟信号,cpu是不能处理模拟信号的,并且认为模拟信号也是不具有传输性的。因此音频编解码器至少具有三个主要功能部分:模数转换器(ADC)、数模转换器(DAC)、程控放大器(PGA)。ADC用来采样外部的模拟声音信号(如Mic录音),进行离散化后,转换成数字音频,通过音频总线(如IIS)传输给cpu,cpu再对数字音频进行处理,如调频、混合、存储等。DAC用来把从cpu过来的数字音频信号还原成原来的模拟声音信号,DAC转换后的离散化PCM调制信号再通过滤波器真实还原出原来的模拟声音。PGA可在各个阶段对音频信号进行可编程的增益放大,例如音量的控制(可参考WM8960_HeadphoneVolume()函数),Mic灵敏度的调节(可参考WM8960_RecorderVolume()函数)等。

WM8960在使用前必须进行初始化,即需配置音频接口IIS的参数(可参考WM8960_Init()的实现),若进行录音,需配置录音路径的上电、接通,并进行增益的设定(具体见WM8960_RecorderStart()函数的实现)。若进行放音,需配置是耳机、扬声器等的话音路径,进行增益设定(可参考WM8960_HeadphoneStart()函数的实现)。IIS是音频接口,只能传输音频信号,因此WM8960还需另外的IIC接口,通过IIC总线写寄存器对这些配置进行设定。IIC驱动编写在前面的章节有详细的介绍,此处不再细说,WM8960模块驱动WM8960.c如下:

#include "IIC.h"

#include "WM8960.h"

#define VolumeLevel 7

static int RecorderVolume;

static int HeadphoneVolume;

// WM8960寄存器不能通过IIC读,开辟缓存记录寄存器的变化

static unsigned short WM8960_Reg[56] = {

0x0097, 0x0097, 0x0000, 0x0000,

0x0000, 0x0008, 0x0000, 0x000a,

0x01c0, 0x0000, 0x00ff, 0x00ff,

0x0000, 0x0000, 0x0000, 0x0000,

0x0000, 0x007b, 0x0100, 0x0032,

0x0000, 0x00c3, 0x00c3, 0x01c0,

0x0000, 0x0000, 0x0000, 0x0000,

0x0000, 0x0000, 0x0000, 0x0000,

0x0100, 0x0100, 0x0050, 0x0050,

0x0050, 0x0050, 0x0000, 0x0000,

0x0000, 0x0000, 0x0040, 0x0000,

0x0000, 0x0050, 0x0050, 0x0000,

0x0002, 0x0037, 0x004d, 0x0080,

0x0008, 0x0031, 0x0026, 0x00e9,

};

static void WM8960_WriteReg(unsigned char RegAddr, unsigned short Value)

{

unsigned char Data;

unsigned char Addr;

// WM8960只有7位的寄存器地址,外加寄存器值第8位,构成8位数据

Addr = (RegAddr<<1) |((Value>>8) & 0x1);

// WM8960有9位的寄存器值,最高位与寄存器地址一齐发送

Data = (unsigned char)Value; // 低8位寄存器值

IIC_WriteBytes(WM8960_SlaveAddr,Addr, &Data, 1);

WM8960_Reg[RegAddr] = Value; // 写成功后更新寄存器的值

}

static unsigned short WM8960_ReadReg(unsigned char RegAddr)

{

return WM8960_Reg[RegAddr]; // 返换缓存的WM8960寄存器的值(9位)

}

unsigned char WM8960_HeadphoneVolume(unsigned char Control)

{

// -10db ~ 6db (0x6f ~ 0x7f)

unsigned char Level;

if (Control == VolumeDown) { // 耳机音量减

if ((0x7f-0x6f)/VolumeLevel ==0) {

HeadphoneVolume--;

} else {

HeadphoneVolume -=(0x7f-0x6f)/VolumeLevel;

}

if (HeadphoneVolume < 0) {

HeadphoneVolume = 0;

}

} else {// 耳机音量加

if ((0x7f-0x6f)/VolumeLevel ==0) {

HeadphoneVolume++;

} else {

HeadphoneVolume +=(0x7f-0x6f)/VolumeLevel;

}

if (HeadphoneVolume >VolumeLevel) {

HeadphoneVolume =VolumeLevel;

}

}

if (HeadphoneVolume == 0) {

Level = 0; // 静音

} else {

Level =((0x7f-0x6f)*HeadphoneVolume)/VolumeLevel + 0x6f;

}

// Headphone Volume Updata

WM8960_WriteReg(0x02,(1<<8)|Level);

WM8960_WriteReg(0x03,(1<<8)|Level);

return ((HeadphoneVolume*100)/VolumeLevel);// 返回音量百分比

}

void WM8960_HeadphoneStop()

{

unsigned short RegValue;

RegValue = WM8960_ReadReg(0x1a);

RegValue =~((1<<8)|(1<<7)|(1<<6)|(1<<5));

WM8960_WriteReg(0x1a, RegValue);

}

void WM8960_HeadphoneStart()

{

unsigned short RegValue;

// DAC Left/Right,LOUT1/ROUT1Output Buffer Power up

WM8960_WriteReg(0x1a, 0x01e0);

// Left DAC Digital Volume -28db

WM8960_WriteReg(0x0a, 0x01c5);

// Right DAC Digital Volume -28db

WM8960_WriteReg(0x0b, 0x01c5);

// DAC Digital No mute

WM8960_WriteReg(0x05, 0x0000);

// Left DAC to Left Output Mixer

WM8960_WriteReg(0x22, 0x0100);

// Right DAC to Left Output Mixer

WM8960_WriteReg(0x25, 0x0100);

// Left/Right Output Mixer Enable

RegValue = WM8960_ReadReg(0x2f);

RegValue |= (1<<2) |(1<<3);

WM8960_WriteReg(0x2f, RegValue);

}

unsigned char WM8960_RecorderVolume(unsigned char Control)

{

// -10db ~ 10db (0xaf ~ 0xd7)

unsigned char Level;

if (Control == VolumeDown) { //Mic灵敏度调低

if ((0xd7-0xaf)/VolumeLevel ==0) {

RecorderVolume--;

} else {

RecorderVolume -=(0xd7-0xaf)/VolumeLevel;

}

if (RecorderVolume < 0) {

RecorderVolume = 0;

}

} else {// Mic灵敏度调高

if ((0xd7-0xaf)/VolumeLevel ==0) {

RecorderVolume++;

} else {

RecorderVolume += (0xd7-0xaf)/VolumeLevel;

}

if (RecorderVolume >VolumeLevel) {

RecorderVolume =VolumeLevel;

}

}

Level =((0xd7-0xaf)*RecorderVolume)/VolumeLevel + 0xaf;

// Left ADC Digital Volume Control

WM8960_WriteReg(0x15, (1<<8)| Level);

return ((RecorderVolume*100)/VolumeLevel);// 返回音量百分比

}

void WM8960_RecorderStop()

{

unsigned short RegValue;

RegValue = WM8960_ReadReg(0x19);

RegValue &=~((1<<5)|(1<<3)|(1<<1));

WM8960_WriteReg(0x19, RegValue);

}

void WM8960_RecorderStart()

{

unsigned short RegValue;

// Mic单端接入到LINPUT1

// VREF,Vmid Divider,PGA Left,ADCLeft,MICBIAS,Master Clock上电或使能

WM8960_WriteReg(0x19, 0x00ea);

// Left Channel Input PGA Enable

RegValue = WM8960_ReadReg(0x2f);

RegValue |= (1<<5);

WM8960_WriteReg(0x2f, RegValue);

// Connect Left Input PGA to LeftInput Boost mixer

// Left Channel Input PGA BoostGain 0db

WM8960_WriteReg(0x20, 0x0108);

// PGA放大0db,Mic到ADC增益30db,ADC full scale level is 1.0Vrms

WM8960_WriteReg(0x00, 0x13f);//Left Input PGA Volume 30db

}

void WM8960_Init(void)

{

unsigned short RegValue;

WM8960_WriteReg(0xf, 0); // 复位WM8960寄存器

// VMID 2x50k,VREF上电

WM8960_WriteReg(0x19, 0x00c0); //WM8960_POWER1

WM8960_WriteReg(0x1a, 0x0000); //WM8960_POWER2

WM8960_WriteReg(0x2f, 0x0000); //WM8960_POWER3

// Slow Clock Enable,left/rightdata = left ADC

RegValue = WM8960_ReadReg(0x17);

WM8960_WriteReg(0x17, RegValue |(1<<2) | (1<<0));

// SYSCLK 使用MCLK时钟,ADC/DAC采样率SYSCLK/256/1.5,即384fs

WM8960_WriteReg(0x04, 0x0048); //WM8960_CLOCK1

// DAC静音,不使用去加重

WM8960_WriteReg(0x05, 0x0008); //WM8960_DACCTL1

// 音频接口配置为I2S Format, 16位字长,slave mode

WM8960_WriteReg(0x07, 0x0002); //WM8960_IFACE1

// ADCLRC/GPIO1作为Jackdetect input,Mic基准电压0.9 * AVDD

WM8960_WriteReg(0x30, 0x0002);

// ADCLRC/GPIO1 Pin FunctionSelect GPIO pin

WM8960_WriteReg(0x09, 0x0040);

// Headphone Volume Update, 0db

WM8960_WriteReg(0x02, 0x0179);

WM8960_WriteReg(0x03, 0x0179);

// Left ADC Digital Volume Control0db

WM8960_WriteReg(0x15, 0x01c3);

RecorderVolume = VolumeLevel/2;

HeadphoneVolume = VolumeLevel/2;

}

WM8960模块驱动头文件WM8960.h如下:

#ifndef __WM8960_H__

#define __WM8960_H__

#ifdef __cplusplus

extern "C" {

#endif

// WM8960 7位IIC设备地址

#define WM8960_SlaveAddr        0x1a

#define VolumeDown          0x0

#define VolumeUp            0x1

void WM8960_Init(void);

void WM8960_RecorderStart(void);

void WM8960_RecorderStop(void);

unsigned char WM8960_RecorderVolume(unsigned char Control);

void WM8960_HeadphoneStart(void);

void WM8960_HeadphoneStop(void);

unsigned char WM8960_HeadphoneVolume(unsigned char Control);

#ifdef __cplusplus

}

#endif

#endif /*__WM8960_H__*/

4. IIS接口驱动

IIS最主要配置模式寄存器(IISMOD)和IIS时钟预分频寄存器(IISPSR)。IISPSR用来产生IIS基时钟,即用作编解码时钟(codec clock),与音频编解码器约定好这个时钟后,例如384fs,是可以得到传输的音频信号的采样频率fs的,由采样率可以得到IIS总线上左右声道切换时钟频率(与采样率一致)。在约定好采样位数后,通过采样频率fs可以得到IIS总线的位时钟(BCLK),这个时钟通常为2*fs*采样位数。这些信号同步之后,cpu与音频编解码器即能正确地一一对应传输音频数据。IISMOD主要用来设置采样位数、编解码时钟、位时钟、数据格式等信息。

音频是实时播放/录音的,任何cpu的IIS控制器均只有非常有限的FIFO,不能根据FIFO的状态采用查询或中断的方式来读写音频FIFO,因为cpu根本无法及时加载或保存音频数据,必然造成播放或录音的断续。例如,cpu需要从固态存储器加载音频数据或保存音频数据到固态存储器中,这都是需要较长的时间的。因此对于音频驱动,实用的只能是采用DMA传输方式,在IIS初始化中,除了IIS引脚配置外,还需配置DMA,并开启相应通道的DMA传输中断,具体实现可参考IIS_Init()函数。

对于实际的应用系统中,音频的播放/录音,均是边播放(录音),边加载(保存)音频数据的,这样只需较小的音频缓存空间,加载/保存时间也被分化在较短的时间内。为了避免播放/录音的断续,通常是要采用双缓存的,即一个缓存空间在播放/录音时,另一个缓存空间就在加载/保存音频数据,通过DMA中断可快速切换使用的缓存空间。具体可参考DMA_IRQ()中断处理的实现。

在需播放音频时,必须先配置IIS Tx的相关参数,包括音频数据的采样率,采样位数,声道数。不同的采样率,IIS接口的时钟配置不一样,不同采样位数、声道数,音频数据在FIFO中的格式并不一样,因此需要告诉接口函数这些参数,具体实现可参考IIS_TxInit()函数。

解码出的PCM音频数据需要写入播放缓存中,音频数据在缓存中的格式有特定的要求,这部分的实现可参考IIS_WriteBuffer()函数。音频缓存写好后,即可通过IIS_TxStart()函数启用IIS接口传输,DMA此后会根据FIFO的状态自动传输缓存中的音频数据到FIFO中,不断解码出的PCM音频数据需不断调用IIS_WriteBuffer()函数写入缓存中,即可实现连续播放音频。

在需录音时,必须先配置IIS Rx的相关参数,包括录音的采样率,采样位数。通常对于电话级应用,采样率可设置成8k,采样位数8位,对于cd音乐级应用,采样率可设置成44.1k、16位,这已经是天籁之音了,再高的采样频率,人耳 也无法辨别。这部分的实现可参考IIS_RxInit()函数,IIS接收通道准备好后,即可通过IIS_RxStart()函数接收录制的数字音频。

录制的PCM音频数据在缓存中对不同采样位数有不同的格式,音频数据需要从缓存中读出,通过处理后(如有损无损压缩、混频等音频处理),再保存进固态存储器中。从缓存读出录制的音频可参考IIS_ReadBuffer()函数的实现,录制时音频数据源源不断地存放进缓存中,因此需不断地调用IIS_ReadBuffer()函数读出录音数据,进行处理后保存。

S3C2416裸机开发系列十八_音频驱动实现(1)

时间: 2024-10-07 05:07:18

S3C2416裸机开发系列十八_音频驱动实现(1)的相关文章

S3C2416裸机开发系列十四_GCC下UCGUI的移植(1)

S3C2416裸机开发系列十四 GCC下UCGUI的移植(1) 象棋小子    1048272975 GUI(图形用户界面)极大地方便了非专业用户的使用,用户无需记忆大量的命令,取而代之的是可以通过窗口.菜单.按键等方式进行操作.在某些场合,设计一款人机界面丰富友好的嵌入式产品能赢得更多的用户.笔者此处就s3c2416基于UCGUI图形用户界面的使用作一个简单的介绍. 1. 代码准备 UCGUI 3.98源码,这个版本的UCGUI是开放源码的最高版本,之后版本只提供库文件,不再开源.笔者以UCG

S3C2416裸机开发系列十五_GCC下uCOS的移植(1)

S3C2416裸机开发系列十五 GCC下uCOS的移植(1) 象棋小子    1048272975 操作系统是用来管理系统硬件.软件及数据资源,控制程序运行,并为其它应用软件提供支持的一种系统软件.根据不同的种类,又可分为实时操作系统.桌面操作系统.服务器操作系统等.对于一些小型的应用,对系统实时性要求高,硬件资源有限等的情况下,应尽量避免使用复杂庞大的操作系统(如Linux),使用小型的实时操作系统(如uCOS)更能满足应用的需求.笔者此处就uCOS-II的移植作一个简单的介绍. 1. 代码准

S3C2416裸机开发系列十五_GCC下uCOS的移植(2)

S3C2416裸机开发系列十五 GCC下uCOS的移植(2) 象棋小子    1048272975 4. uCOS配置 uCOS是可裁减实时操作系统,可以根据实际的应用对内核未使用到的功能进行裁减,以进一步节省系统宝贵的硬件资源,通常可用的uCOS-II内核代码在6K~26K,这在uCOS-II配置文件os_cfg.h中进行配置,这个配置文件在源码目录为os_cfg_r.h,从目录中拷贝添加到uCOS/uCOS-II/Cfg目录中,并重命名为os_cfg.h. #ifndef OS_CFG_H

S3C2416裸机开发系列十四_GCC下UCGUI的移植(2)

S3C2416裸机开发系列十四 GCC下UCGUI的移植(2) 象棋小子    1048272975 现在主要讲解一下在GCC移植UCGUI,Makefile工程如何加入目录,加入源码,c标准库,编译选项的设置. 笔者的Makefile模板提取自uboot,工程中加入目录,加入源码都是很简单的,详细的介绍请参考前面章节" GCC启动代码工程应用实例".下面主要介绍UCGUI目录下很多的源码文件Makefile的编写,一种可行的方式就是把GUI目录上所有的c文件,不管有无用到,均加入工程

S3C2416裸机开发系列十七_GCC下Fatfs的移植

S3C2416裸机开发系列十七 GCC下Fatfs的移植 象棋小子    1048272975 对于固态存储器,其存储容量可以很大,往往需要一款文件系统对存储器用户数据进行组织文件的管理.它对文件存储器空间进行组织和分配,负责文件的存储并对存入的文件进行保护和检索.在嵌入式系统中,往往需要采用windows兼容的文件系统,像相机的照片.视频监控.语音产品等,很多都需要从windows计算机上提取资源或在windows计算机上进一步处理.Fatfs由于其开源免费,支持fat32,受到了广泛的应用,

BizTalk开发系列(十八) 使用信封拆分数据库消息

之前写了一篇的<BizTalk开发系列(十七) 信封架构(Envelop)> 是关于信封架构及其拆装原理的,都是理论性的内容.信封在BizTalk开发过程中最常用的应该是在读取SQL Server 数据库的时候.一次性读取多条消息,提高了SQL Adapter对SQL Server的读取性能.今天就来做一下这个实例,增强对信封消息的理解. 数据库采用的是SQL Server 2005示例数据库:AdventureWorks 通过select * from Production.Culture选

嵌入式Linux裸机开发(十六)——shell实现

嵌入式Linux裸机开发(十六)--shell实现 一.shell简介 Shell是用户与操作系统之间的接口,为用户提供了使用操作系统的接口. 1.图形界面shell 图形界面shell(Graphical User Interface shell 即 GUI shell) 应用最为广泛图形界面shell是Windows Explorer(微软的windows系列操作系统)和Linux shell,其中linux shell 包括 X window manager (BlackBox和FluxB

嵌入式Linux裸机开发(十二)——iNand简介

嵌入式Linux裸机开发(十二)--iNand简介 一.iNand简介 iNand是SanDisk公司研发的存储芯片,可以看成SD卡或MMC卡芯片化. iNand是SanDisk公司符合eMMC协议的芯片系列名称,内部采用MLC存储颗粒.iNand接口电路设计复杂,功能完善,提供eMMC接口协议,与SoC的eMMC控制器配对通信. 相对MLC NandFlash,iNAND有以下优点: 1.提高性能 A.减少SOC的工作量,节约SOC资源. 如果使用MLC做存储,SOC要参与FLASH的坏块管理

Go语言开发(十八)、Go语言MySQL数据库操作

Go语言开发(十八).Go语言MySQL数据库操作 一.MySQL数据库驱动 1.MySQL数据库驱动简介 Go语言官方没有实现MySQL数据库驱动,常用的开源MySQL数据库驱动实现如下:(1)Go MySQL DriverGo MySQL Driver支持database/sql接口,全部采用Go语言实现.官方网站:https://github.com/go-sql-driver/mysql/(2)MyMySQLMyMySQL支持database/sql接口,也支持自定义的接口,全部采用Go