知之者不如好之,好之者不如乐之
分类: 其他平台
2014-12-29 09:40:03
引用
314forever 的 用SPI连接I2S接口的音频DAC
前些阵子研究过这个,最后得出不可能的结论,可是还不太死心,近日终于搞定。
到NXP的网站上可以找到I2S接口的说明书,分析一下I2S总线特点:1、数据不能断流;2、时序上有严格的要求。这两条决定了如果我打算用没有FIFO的SPI接口模拟I2S总线,SPI就不可能工作在主机方式,事实上两面两个成功的例子当中SPI也都是工作在从机方式的。I2S总线上的数据位可以从16bit-24bit不等,为了使SPI就口有足够的时间装填数据,因此选择24bit的数据格式。剩下的就是如何产生I2S总线的2个信号BCLK、LRCK,音频DAC的工作信号MCLK和SPI接口的CS信号的问题了。起初打算用LPC2148自己产生这些信号,结果发现不行,因为没有足够的PWM通道。最后想了一个不太经济的方法,就是用单片机产生这些信号。起初用的是tiny2331 — 两路独立的PWM(BCLK、LRCK) + 系统时钟输出(MCLK),但是CS的时序时钟调不好;后来打算换成mega8 — 三路独立的PWM(BCLK、LRCK、CS) + 74HC04(MCLK);在查找手册的时候发现mega48与tiny2313一样有系统时钟输出功能,重要的是mega48时可以跑20MHz的,正好手头有一片mega48,就用它了。
整理一下方案:
I2S数据输出使用LPC2148的SPI接口,16-bit从机方式,MISO输出I2S数据,在中断中加载下一输出数据;
mega48工作在16.9344.MHz,分别由PB0(设置熔丝位)、PD6、PB1、PB3输出MCLK、LRCK、BCLK和CS,程序比较简单如下#include
#include#define SYS_LED PD1
#define SET_LED PORTD &= ~_BV(SYS_LED)
#define CLR_LED PORTD |= _BV(SYS_LED)#define NOP __asm__ __volatile__("nop")
int main(void){
CLR_LED;
DDRB = _BV(PB0) | _BV(PB1) | _BV(PB3);
DDRD = _BV(PD6);
_delay_ms(10);
/* use Timer0 to generate LRCK : CTC mode, TOP = OCR0A, OCR0A toggle */
OCR0A = 192 - 1;
TCCR0A = 0x42;
/* use Timer1 to generate SSEL : fast PWM mode, TOP = ICR1, OCR1A set on match and clear on TOP */
ICR1H = 0;
ICR1L = 192 - 1;
OCR1AH = 0;
OCR1AL = 128 - 1 - 2;
TCCR1A = 0xC2;
/* use Timer2 to generate BCLK : CTC mode, TOP = OCR2A, OCR1A toggle */
OCR2A = 4 - 1;
TCCR2A = 0x42;
TCCR2B = 0x01; // BCLK start
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
TCCR0B = 0x01; // LRCK start
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
TCCR1B = 0x19; // SSEL start
SET_LED;
while(1);
return 0;
}用上述方法连接CS4334成功,两个声道均能正常发生;LPC2148中开两个1K(小了没试过)的Buffer可以流畅播放44.1KHz 16bit的WAV文件(经过FAT系统,用fatgetc读取数据)。不过用此法连接PCM1742的时候,输出有极大的噪音,不知道是DAC输出部分没搞好还是芯片有问题。