具体配置详情在上个笔记,此为上次笔记的代码化
(还有一部分未完成)
//wm8978.c
#include "stm32f4xx.h"
#include "usart.h"
#include "delay.h"
#include "sys.h"
#include "i2c.h"
#include "wm8978.h"
//WM8978寄存器值缓冲区,共58个寄存器,写WM8978的相应寄存器时同步保存到本地,又因为WM8978寄存器值是9位的因此用u16保存
static u16 WM8978_REGVAL_TBL[58]=
{
0X0000,0X0000,0X0000,0X0000,0X0050,0X0000,0X0140,0X0000,
0X0000,0X0000,0X0000,0X00FF,0X00FF,0X0000,0X0100,0X00FF,
0X00FF,0X0000,0X012C,0X002C,0X002C,0X002C,0X002C,0X0000,
0X0032,0X0000,0X0000,0X0000,0X0000,0X0000,0X0000,0X0000,
0X0038,0X000B,0X0032,0X0000,0X0008,0X000C,0X0093,0X00E9,
0X0000,0X0000,0X0000,0X0000,0X0003,0X0010,0X0010,0X0100,
0X0100,0X0002,0X0001,0X0001,0X0039,0X0039,0X0039,0X0039,
0X0001,0X0001
};
u8 WM8978_RegConfig(u8 regadd,u16 val){
IIC_Start();//启用IIC准备发送WM8978地址
IIC_Send_Byte(WM8978_ADDR);
if(IIC_Wait_Ack()){
printf("WM8978_RegConfig err :\n");
return 1;
}
IIC_Send_Byte((regadd<<1)|((val>>8)&0x01));//发送寄存器地址(高七位)以及配置数据的第一位
if(IIC_Wait_Ack()){
printf("WM8978_RegConfig err :\n");
return 2;
}
IIC_Send_Byte(val);//发送数据,此处与源程序不同,源程序为IIC_Send_Byte(val&0xFF)
if(IIC_Wait_Ack()){
printf("WM8978_RegConfig err :\n");
return 3;
}
delay_ms(5);
IIC_Stop();
WM8978_REGVAL_TBL[regadd]=val;
return 0;
}
u8 WM8978_Init(){
u8 res;
GPIO_InitTypeDef GPIO_InitStruct;
//因为I2S的线连接情况,需要使能相应的时钟和配置相应引脚
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC, ENABLE); //使能外设GPIOB,GPIOC时钟
//PB12/13 复用功能输出,对应I2S的LRCK和SCLK
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStruct);//初始化
//PC2/PC3/PC6复用功能输出,对应SDOUT、SDIN和MCLK
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3|GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOC, &GPIO_InitStruct);//初始化
//因为I2S2和SPI2是共用一个复用功能的,因此配置相应GPIO的相应引脚如下:
GPIO_PinAFConfig(GPIOB,GPIO_PinSource12,GPIO_AF_SPI2); //PB12,AF5 I2S_LRCK
GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_SPI2); //PB13,AF5 I2S_SCLK
GPIO_PinAFConfig(GPIOC,GPIO_PinSource3,GPIO_AF_SPI2); //PC3 ,AF5 I2S_DACDATA
GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_SPI2); //PC6 ,AF5 I2S_MCK
GPIO_PinAFConfig(GPIOC,GPIO_PinSource2,GPIO_AF6_SPI2); //PC2 ,AF6 I2S_ADCDATA I2S2ext_SD是AF6,影响录音机
IIC_Init();//初始化IIC接口
WM8978_RegConfig(0,0);//写任意值到R0对WM8978进行软复位
WM8978_RegConfig(1,0x1B);//00011011 MICEN设1使能,BIASEN设1开启模拟,VMIDSEL设置为11——5k欧姆
//WM8978的每一个模拟输出都可以单独的使能或者不使能,联合到模拟混合器的每一个输出可以单独的使能,
//所有输出都是默认不使能。为了节省电能,WM8978不用的部分应该保留不使能。输出可以在任何时间被使能,
//但当配置为推动模式时如果BUFIO被禁用或者BUFDCOP被停用不推荐这样做,因为这可能会导致弹出式噪音。
WM8978_RegConfig(2,0x1B0);//0001 1011 0000 不先使能adc
WM8978_RegConfig(3,0x6C);//设置LOUT2EN(bit6),ROUT2EN(bit5),RMIXER(bit3),LMIXER(bit2),DACENR(bit1)和DACENL(bit0)等6个位。
//LOUT2EN和ROUT2EN,设置为1,使能喇叭输出:LMIXER和RMIXER设置为1,使能左右声道混合器: DACENL和DACENR是使能左右声道的DAC,必须设置为1。
//此处不先设置为1的原因是,因为均设置为1使能了dac,而只有在输入的时候使能dac,可以在后面需要的时候再配置
WM8978_RegConfig(4,0x06);
WM8978_RegConfig(6,0x00);
WM8978_RegConfig(10,0x008);
WM8978_RegConfig(49,0x60);
return 0;
}
u16 WM8978_ReadReg(u8 regadd){
return WM8978_REGVAL_TBL[regadd];
}
//使能adc为输出、使能dac为输入
void WM8978_ADDR_CFG(u8 adcen,u8 dacen){
u16 regval=0;
//设置与dac相关的R3
regval=WM8978_REGVAL_TBL[3];
if(dacen==1)regval=regval|3;
else regval=regval & ~(3);
WM8978_RegConfig(3,regval);
//设置与adc相关的R2
regval=WM8978_REGVAL_TBL[2];
if(adcen==1)regval=regval|3;
else regval=regval & ~(3);
WM8978_RegConfig(2,regval);
}
void WM8978_OUTCfg(u8 dacen,u8 byp){
u16 regval=0;
regval=WM8978_REGVAL_TBL[50];
if(dacen==1)regval=regval|1;//将左右声道的dac输出接入左右声道混合器里
if(byp==1){
regval=regval|2;//使能byp
regval=regval|20;//设置byp的增益为0db
}
//R50控制左声道,R51控制右声道,寄存器相同,配置相同
WM8978_RegConfig(50,regval);
WM8978_RegConfig(51,regval);
}
//fmt=1,MSB;fmt=0,LSB;fmt=2,I2S飞利浦标准;fmt=3,DSP/PCM
//wl=00,16bits;wl=01,20bits;wl=10,24bits;wl=11,32bits;
void WM8978_I2SCfg(u8 fmt,u8 wl){
if(wl>3)return ;
if(fmt>3)return ;
fmt=fmt<<3;//配置01到bits3、bits4
wl=wl<<5;//配置10到bits5,bits6
WM8978_RegConfig(4,fmt|wl);//进行寄存器更新配置
}
//可添加耳机函数,此处用不到不添加
void WM8978_TrumpetVol(u8 val){
if(val==0)val=val|1<<6;
WM8978_RegConfig(54,val);
WM8978_RegConfig(55,val|(1<<8));//bits8配置为1,进行配置更新
return ;
}
//还可添加EQ的配置,暂时还不确定需不需要添加
//由于需要通过IIC来配置寄存器,因此需要配置IIC
//I2C.c
#include "stm32f4xx.h"
#include "usart.h"
#include "delay.h"
#include "sys.h"
#include "i2c.h"
void IIC_Init(void){
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;//普通输出模式
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;//高电平有效,上拉
GPIO_Init(GPIOB,&GPIO_InitStruct);
IIC_SCL=1;
IIC_SDA=1;
}
void IIC_Start(void){
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
delay_ms(5);//延时10ms
IIC_SDA=0;
delay_ms(5);
IIC_SCL=0;
return ;
}
void IIC_Stop(void){
SDA_OUT();
IIC_SCL=0;
IIC_SDA=0;
delay_ms(5);
IIC_SCL=1;
delay_ms(2);
IIC_SDA=1;
delay_ms(5);
}
void IIC_Send_Byte(u8 sendb){
u8 wt;//等待次数,8位
SDA_OUT();
IIC_SCL=0;
delay_ms(5);
IIC_SDA=1;//注意此处与源程序不一样
for(wt=0;wt<8;wt++){
IIC_SDA=sendb>>7;
sendb<<=1;
delay_ms(2);
IIC_SCL=1;
delay_ms(2);
IIC_SDA=0;
}
return ;
}
u8 IIC_Read_Byte(unsigned char ack){
unsigned char re=0;
int i;
SDA_IN();
for(i=0;i<8;i++){
IIC_SCL=0;
delay_ms(2);
IIC_SCL=1;
re<<=1;
re=re|SDA_TS;//if(SDA_TS)re++;注意此处与源程序不同
}
if(!ack)IIC_NAck();
else IIC_Ack();
return re;
}
u8 IIC_Wait_Ack(void){
u16 waittime=0;
SDA_IN();//应答信号是主机等待从机应答,所以应该设置为输入
IIC_SDA=1;
delay_ms(2);
IIC_SCL=1;
delay_ms(2);
while(SDA_TS){
waittime++;
if(waittime>200){
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//返回默认低电平状态
return 0;
}
void IIC_Ack(void){
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
return ;
}
void IIC_NAck(void){
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
return ;
}
//I2S.c部分配置,大量参考正点的课程
//I2S初始化
//参数I2S_Standard: @ref SPI_I2S_Standard I2S标准,
//I2S_Standard_Phillips,飞利浦标准;
//I2S_Standard_MSB,MSB对齐标准(右对齐);
//I2S_Standard_LSB,LSB对齐标准(左对齐);
//I2S_Standard_PCMShort,I2S_Standard_PCMLong:PCM标准
//参数I2S_Mode: @ref SPI_I2S_Mode I2S_Mode_SlaveTx:从机发送;I2S_Mode_SlaveRx:从机接收;I2S_Mode_MasterTx:主机发送;I2S_Mode_MasterRx:主机接收;
//参数I2S_Clock_Polarity @ref SPI_I2S_Clock_Polarity: I2S_CPOL_Low,时钟低电平有效;I2S_CPOL_High,时钟高电平有效
//参数I2S_DataFormat: @ref SPI_I2S_Data_Format :数据长度,I2S_DataFormat_16b,16位标准;I2S_DataFormat_16bextended,
//16位扩展(frame=32bit);I2S_DataFormat_24b,24位;I2S_DataFormat_32b,32位.
void I2S2_Init(u16 I2S_Std,u16 I2S_Mode,u16 I2S_Clock_Polarity,u16 I2S_DataFormat){
I2S_InitTypeDef I2S_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//使能SPI2时钟
RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,ENABLE); //复位SPI2
RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,DISABLE);//结束复位
I2S_InitStruct.I2S_Mode=I2S_Mode;//IIS模式
I2S_InitStruct.I2S_Standard=I2S_Std;//IIS标准
I2S_InitStruct.I2S_DataFormat=I2S_DataFormat;//IIS数据长度
I2S_InitStruct.I2S_MCLKOutput=I2S_MCLKOutput_Disable;//主时钟输出禁止(先禁止再通过后续进行配置)
I2S_InitStruct.I2S_AudioFreq=I2S_AudioFreq_Default;//IIS频率设置
I2S_InitStruct.I2S_CPOL=I2S_Clock_Polarity;//空闲状态时钟电平
I2S_Init(SPI2,&I2S_InitStruct);//初始化IIS,实际对I2SPR和I2SCFGR两个寄存器配置
SPI_I2S_DMACmd(SPI2,SPI_I2S_DMAReq_Tx,ENABLE);//操作SPI_CR2寄存器,进行SPI2 TX DMA请求使能.
I2S_Cmd(SPI2,ENABLE);//SPI2 I2S EN使能. 用的是I2S2,因此使能I2S2
}
//采样率计算公式:Fs=I2SxCLK/[256*(2*I2SDIV+ODD)]
//I2SxCLK=(HSE/pllm)*PLLI2SN/PLLI2SR
//一般HSE=8Mhz
//pllm:在Sys_Clock_Set设置的时候确定,一般是8
//PLLI2SN:一般是192~432
//PLLI2SR:2~7
//I2SDIV:2~255
//ODD:0/1
//I2S分频系数表@pllm=8,HSE=8Mhz,即vco输入频率为1Mhz
//表格式:采样率/10,PLLI2SN,PLLI2SR,I2SDIV,ODD
const u16 I2S_PSC_TBL[][5]=
{
{800 ,256,5,12,1}, //8Khz采样率
{1102,429,4,19,0}, //11.025Khz采样率
{1600,213,2,13,0}, //16Khz采样率
{2205,429,4, 9,1}, //22.05Khz采样率
{3200,213,2, 6,1}, //32Khz采样率
{4410,271,2, 6,0}, //44.1Khz采样率
{4800,258,3, 3,1}, //48Khz采样率
{8820,316,2, 3,1}, //88.2Khz采样率
{9600,344,2, 3,1}, //96Khz采样率
{17640,361,2,2,0}, //176.4Khz采样率
{19200,393,2,2,0}, //192Khz采样率
};
//根据公式计算出对应的采样率放入一个数组中,通过查表法直接查询,免去计算
u8 I2S_SampleRateSet(u32 samplerate){
u8 i=0;
u32 temp=0;
samplerate=samplerate/10;
for(i=0;i<(sizeof(I2S_PSC_TBL)/10);i++)
//看看采样率是否存在表中
{
if(samplerate==I2S_PSC_TBL[i][0])break;
}
RCC_PLLI2SCmd(DISABLE);//先关闭PLLI2S
if(i==(sizeof(I2S_PSC_TBL)/10))return 1;//不存在的采样率
RCC_PLLI2SConfig((u32)I2S_PSC_TBL[i][1],(u32)I2S_PSC_TBL[i][2]);//设置I2SxCLK的频率(x=2) 设置PLLI2SN PLLI2SR
RCC->CR|=1<<26; //开启I2S时钟
while((RCC->CR&1<<27)==0); //等待I2S时钟开启成功.
temp=I2S_PSC_TBL[i][3]<<0; //设置I2SDIV
temp|=I2S_PSC_TBL[i][4]<<8; //设置ODD位
temp|=1<<9; //使能MCKOE位,输出MCK
SPI2->I2SPR=temp; //设置I2SPR寄存器
return 0;
}