lzf713 发表于 2009-11-29 17:05:42

wav格式音乐播放器

我在http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=3614256&bbs_page_no=1&search_mode=3&search_text=lzf713&bbs_id=1000移植了http://elm-chan.org/works/mxb/report.html上面一个音乐程序。
我发现要手工输入乐谱,对于我没有音乐细胞的人来说是很痛苦的。经过马老师介绍,http://elm-chan.org/works/sd8p/report.html上面还有一个可以用ATtinyX5 series (25/45/85) 和SD卡就可以实现播放WAV格式音乐。那时候我觉得我无法实现,因为那时候我根本不认识FAT32文件系统。经过马老师的鼓励,我还是看了那个程序,结果发现并不是想像那么困难。我现在终于可以用M16或者M8实现了。
通过该程序,我初步认识了FAT32文件系统的一些基本知识,这是我最大的收获。
现在我对这个程序的描述:
可以播放PCM WAV格式音乐,可以是立体声或者单声道,但是对采样频率和采样位数有限制。如果单片机晶振频率是16MHz,则采样频率最高是22050Hz,采样位数8位(单片机可以处理16位,但是本质上是将第8位抛弃的,所以在转换音乐格式时候应该选择8位,这样可以节省CPU时间和SD卡存储空间),立体声。
如果单片机晶振频率是8MHz,则采样频率最高是11025Hz,采样位数8位,立体声。所以我建议采样频率最好是11025Hz。
如果要播放更加高采样频率的音乐是没有意义的,原因是单片机采样PWM方式进行DAC的,如果晶振频率是16MHz,那么PWM最高频率是16/256=0.0625MHz=62.5KHz,这是相当于载波,如果载波频率和信号频率很近时候,这样调整信号没有什么意义的。
转换音乐格式有很多软件,但是我使用豪杰音频通感觉到很好用。
http://cache.amobbs.com/bbs_upload782111/files_22/ourdev_508761.gif
(原文件名:image002.gif)

点击此处下载 ourdev_508762.rar(文件大小:36K) (原文件名:WAV_PLAY.rar)

Soul.art 发表于 2009-11-29 17:13:54

呵呵~~LZ的瞒喜欢搞音乐的嘛~~

用STM32可以做到16位音频~192Khz采样的~~

站长有个帖子介绍如何用双PWM输出16位音频

http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=3253953&bbs_page_no=1&search_mode=4&search_text=Soul.art&bbs_id=9999

ninjia 发表于 2009-11-29 18:43:18

顶!

laolu 发表于 2009-11-29 19:21:27

MARK

jsjjccc 发表于 2009-11-29 20:01:56

zbjzxc 发表于 2009-11-29 22:28:32

用ATtinyX5 series (25/45/85)是16位的pwm,楼主觉的有必要吗?哈哈,冒昧问下。

eduhf_123 发表于 2009-11-29 22:46:39

MARK 单片机音频解码

lzf713 发表于 2009-11-29 22:54:06

to:【5楼】 zbjzxc
我不知道你要表达什么意思,在下太愚钝了!http://elm-chan.org/works/sd8p/report.html上原来的作品的好像也是使用8位PWM,是将低8位数据抛弃的。

nbanba 发表于 2009-11-30 09:11:36

顶。。。。。。。。。。。。

zbjzxc 发表于 2009-11-30 09:21:37

楼主您好,那天意思没表达好~~加上自己的不懂~~
我以为ATtinyX5可以产生16位PWM!!!千万别介意!!!

The ATtinyX5 series (25/45/85) 8-pin AVR microcontroller has two fast PWM outputs in 250kHz carrier frequency.
这句话的意思是: ATtinyX5系列的AVR能 产生250kHz 的PWM载频。

1、那M8或M16能 产生250kHz 的PWM载频吗?
2、M8或M16采样频率最高能44.1kHz吗?

yunqian09 发表于 2009-11-30 09:42:11

标记下

lisn3188 发表于 2009-11-30 10:14:08

输出加个电容耦合,安全点。。

killin 发表于 2009-12-1 14:03:47

继续支持楼主.
另外【1楼】 Soul.art 潇洒的猪 老乡啊.希望能认识认识.

lzf713 发表于 2009-12-1 16:09:37

to:【11楼】 lisn3188 龙南
确实要加电容。因为有直流成分存在,所以应该加电容。如果是单通道输出,在中断里面进行一些处理,如下面:
//T2匹配中断服务程序
ISR(TIMER2_COMP_vect)
{
register unsigned int temp;
register unsigned char dt1,dt2;
if(FifoCt>=Channel)
{
    FifoRi++;            //获取第一个数据(由于是公用,所以提前获取)
    if(FifoRi==MAXCOUNT)
          FifoRi=0;
        dt1=Buff;
        FifoCt--;
    if(PlayMode==1)    //单通道播放形式
       {
           if(Channel==1)//如果音源只有一个通道
          {
                  OCR1A=dt1;
                  OCR1B=~dt1;//增加输出音量,但是接线需要喇叭接在OCR1A和OCR1B之间才有效果
      }   
           else            //如果音源有两个通道,那么一个喇叭的信号是这两个通道的平均值
          {
                  FifoRi++;    //获取下一个数据
                  if(FifoRi==MAXCOUNT)
                   FifoRi=0;
                  dt2=Buff;
                  FifoCt--;       
                  temp=(dt1+dt2 )>>1;       
                  OCR1A=(unsigned char)temp;
                  OCR1B=(unsigned char)(~temp); //增加输出音量,但是接线需要喇叭接在OCR1A和OCR1B之间才有效果
                }
       }
        else         //双通道播放形式
       {
           if(Channel==1) //如果音源只有一个通道,那么两个喇叭的信号是相同的
          {
                  OCR1A=dt1;        
                  OCR1B=dt1;
                }
       else       //如果音源有两个通道,那么两个喇叭的信号是不相同的
          {
                  FifoRi++;
                  if(FifoRi==MAXCOUNT)
                   FifoRi=0;
                  OCR1A=dt1;
                  OCR1B=Buff;
                  FifoCt--;                                                                              
                }
       }
}
}
那么喇叭两个引脚直接连在OCR1A和OCR1B上,那么音量可以加大一倍,也不需要外接电容。当然和原来的接法,还是要加电容的。

lzf713 发表于 2009-12-1 16:15:50

我郁闷死了,上次买的1G金士顿的SD卡,还没有到一周,就出现不能读写了,格式化,结果能格式化4GB出来,然后读写速度奇慢,3分多钟才能写24MB。那回购买处,他们说能格式化,也能写,说明卡没有坏,只是你不小心中毒而已,并且还说已经是特价物品(那时候买30快,也没有讨价还价,也没有说什么特价的)不保修的。

lookavr 发表于 2009-12-3 13:45:22

好东西,哈哈,如果能录就实用了

jeoge 发表于 2009-12-3 16:52:18

不错,学习中.

machao 发表于 2009-12-4 13:38:05

支持。

我已经推荐给一个学生继续深入玩下去。

建议采用AT90PWM2B,它也是AVR内核,但有一个10位的DAC,可以替代掉高速PWM(它也有高速PWM,但与85稍微不同),音质应该还可以提高。并且AT90PWM2B引脚多,这样就有实际应用的价值了。

AT90PWM2B可以向双龙购买,我已经拿到样片。

lisn3188 发表于 2009-12-4 16:57:52

请问楼主,看了你的程序,我理解的是这样:

1 使用T1快速PWM进行DA,16M/256=62.5K 远大于采样率11.025K
2 用T2 的比较匹配中断来更新数据,8分频,2M/256=7.8125K 和音频采样率11.025K 有差别,这样可以WAV出正常的声音吗?

StephenCui 发表于 2009-12-4 16:59:54

Mark学习了

lisn3188 发表于 2009-12-4 17:47:25

自己看出来了,原来在读取WAVE头文件的时候更改了OCR2的值,算出与WAVE采样率对应的中断频率

qzzz 发表于 2009-12-4 23:38:39

厉害 ,支持你

yangyi 发表于 2009-12-5 08:56:20

不错,支持!

trueboy 发表于 2009-12-11 20:23:59

请问lz,tf卡能用吗

lzf713 发表于 2009-12-11 20:57:47

to【23楼】 trueboy :
tf卡?是什么卡?没有见过,我也不知道行不行。在这里,我买了一张SD卡,用了一周就玩完了,没有信心买其他卡了。
你就买一个试试吧,也许引脚不同,驱动时序不同,只要是基于FAT32文件系统的,其他都相同的。

anning 发表于 2009-12-13 19:41:46

mark!

wjy6264 发表于 2009-12-14 22:46:11

学习

trueboy 发表于 2009-12-24 09:39:53

tf卡就是比sd卡还要小的卡,最小的卡。
我这里买2G的金士顿TF卡,30元钱,送个卡托,模样跟SD卡一模一样。
理论上,mmc、sd、tf卡都是兼容的。不过我没试过。

lz在另一个帖子里面写:

今天,我将音乐盒程序(看楼主提供的)修改了,将里面的音色改了,改为钢琴的音色。效果有点钢琴味道(感觉比原来的好一些),但是M8的存储器装不下程序,我改为M16,占用空间80%左右。引脚也需要修改,喇叭要接PIND.4和PIND.5引脚(参考芯片资料)。
现在提供芯片烧写代码,有兴趣的可以试试。(由于源程序还要进一步修改,暂时不上传,以免浪费空间,就像上面提供的曲谱输入程序一样,不断修改,不断上传,现在也没有办法修改,浪费太多空间了)
将后缀名txt改为hex
点击此处下载 ourdev_498205.txt(文件大小:35K) (原文件名:yinyue.txt)

lz能不能把钢琴音色数据传给我,我试验了提取音色库,可是失败了。

lzf713 发表于 2009-12-24 22:58:31

回复【27楼】trueboy
-----------------------------------------------------------------------
   是的,我改了钢琴的音色,效果确实比音乐盒好听一些,但是钢琴音色占用ROM空间是音乐盒的10倍左右,用M8是无法装下来的。后来,我尝试用钢琴音色的包络去代替,目的是缩减空间,但是试验结果发现音效很差(可能我获取包络数据不是很准确)。到了后来,我给可以播放WAV格式的音乐播放器吸引了,而我也实现了,我发现这个效果是最理想的,也是最省功夫的,不需要手工输入乐谱,因此对于我这种音乐盲来说是最好的选择。所以放弃手工输入乐谱那种音乐发生器了,再也没有继续研究如何改善音质效果。
   我现在将源文件上传到那个帖子上了了,也许你能继续研究!

lzf713 发表于 2010-1-30 17:57:36

由买了2G的金士顿SD卡,又可以开工了。上面所提供的程序,在换歌曲时候会有“嗒”一声响,是程序有一点错误造成的
具体是播放程序。
原来是:
unsigned char play(void)
{
unsigned long int size;
unsigned char key;
size=load_head();
if(size<10)
   return 1;
FifoCt=0; //复位FIFO队列
FifoRi=0;
FifoWi=0;       

ReadFile(BeginReadOffset,BytePerSector-BeginReadOffset);
size=size-BeginReadOffset;
只要改为:
unsigned char play(void)
{
unsigned long int size;
unsigned char key;
size=load_head();
if(size<10)
   return 1;
FifoCt=0; //复位FIFO队列
FifoRi=0;
FifoWi=0;       

ReadFile(BeginReadOffset,BytePerSector-BeginReadOffset);
size=size-(BytePerSector-BeginReadOffset);
就行了。
现在搞能够录音了,原来以为很困难的,但是使用现成的FAT32文件系统(如ZLG的),不过如此而已。不过现在没有什么激_情了。

cooler 发表于 2010-1-30 20:05:33

MARK

tearsman520 发表于 2010-2-6 14:07:48

MARK

a13736925316 发表于 2010-2-6 14:30:07

顶起来~

moen 发表于 2010-2-6 16:29:58

楼主强得一米

fy024 发表于 2010-2-6 23:27:09

太厉害了

i387DX 发表于 2010-2-7 04:14:04

MARK一下……

by886 发表于 2010-2-9 19:44:08

mark....

shiriusu 发表于 2010-2-9 20:06:43

mark

tearsman520 发表于 2010-2-11 13:33:40

楼主能留个联系方式么~

我最近也在做相似的东西,希望能得到指点~~:)

leo2010@msn.com

tearsman520 发表于 2010-2-11 20:08:47

void SD_Read_Sector(unsigned long int address)//从指定地址读取扇区
{
unsigned char ret;
unsigned int i;
union
   {
    unsigned long int cluster;
        unsigned char dt;
   }temp;                              //定义temp联合体
address <<=1;                  //地址左移1   位
temp.cluster=address;       //设置簇的地址
//------------------------------------
CLR_SPI_SS();                  //选中SD   卡;
SPDR=0x51;                      //发送CMD17
while(!(SPSR & 0x80));      //等待发送完成
SPDR=temp.dt;            //发送dt中的前三个字符,也就是簇号的前三位
while(!(SPSR & 0x80));
SPDR=temp.dt;
while(!(SPSR & 0x80));
SPDR=temp.dt;
while(!(SPSR & 0x80));
SPDR=0;
while(!(SPSR & 0x80));
SPDR=0xff;                     //发送字节0xFF,等待响应
while(!(SPSR & 0x80));
do{
      SPDR=0xff;
      while(!(SPSR & 0x80));
      ret=SPDR;      
    }while(ret!=0x0);         //若没有收到正确的R1响应,则继续该循环
//-----------------------------------
do{
      SPDR=0xff;                  //发送字节0xFF等待响应
      while(!(SPSR & 0x80));
      ret=SPDR;                     //收到返回的响应
    }while(ret!=0xfe);         //判断响应值是否为0xfe(有效数据块的标志)
//-----------------------------------
for(i=0;i<512;i++)            //开始接受数据
   {
    SPDR=0xff;
    while(!(SPSR & 0x80));//发送字节0xFF接收数据
    Buff=SPDR;                //读取SPDR数据缓冲器中内容,共512个字节。
   }
SPDR=0xff;
while(!(SPSR & 0x80));
SPDR=0xff;
while(!(SPSR & 0x80));
//-----------------------------------
SET_SPI_SS();                //取消选择SD卡
SPDR=0xff;                     //发送0xff等待响应
while(!(SPSR & 0x80));
ret=SPDR;                        //读取SPDR数据缓冲器中内容
}

这段代码中,函数的参数address到底是什么?是扇区号吗?那为什么要把扇区号左移1位呢?

address <<= 1;

这个代码是什么意思呢?

lzf713 发表于 2010-2-18 16:30:56

address到底是什么?是扇区号吗?
不错,是扇区号。

那为什么要把扇区号左移1位呢?
因为传入参数是扇区号,但是读取SD卡需要的具体地址编号,由于读取SD卡具体地址编号与扇区号是512倍关系(一扇区有512字节)
所以需要将扇区号乘以512就可以获得地址编号了,那么扇区号往左边移动9位也可以实现相同效果。
你可以下面代码中可以看出来,本质是相当于往左边移动9位的,而不是1位。因为unsigned long int address是4个字节的,将address往左边移动义务之后再将其赋予给temp.cluster,由于temp是一个联合体,也就是说temp.cluster和unsigned char dt;共用内存的。
在GCC编译器中
联合体中,数组下标大的装数据大端,如:temp.cluster=0xaabbccdd,那么temp.dt=0xaa,temp.dt=0xbb
temp.dt=0xcc,temp.dt=0xdd

我现在假设address=00000000 00000000 10101010 01010101
那么address<<=1;则address=00000000 00000001 01010100 10101010
然后执行temp.cluster=address,那么temp.dt=00000000,temp.dt=00000001,temp.dt=01010100,temp.dt=10101010
在下面程序中将temp.dt,temp.dt,temp.dt,0发送出去,这就是扇区号所对应SD卡存储单元开始地址。
这样做的目的,无非想提高程序执行速度而已。
如果我这样说,你还不能明白,你还需要阅读SD卡读写协议。

void SD_Read_Sector(unsigned long int address)//从指定地址读取扇区
{
unsigned char ret;
unsigned int i;
union
   {
    unsigned long int cluster;
unsigned char dt;
   }temp;                              //定义temp联合体
address <<=1;                  //地址左移1   位
temp.cluster=address;       //设置簇的地址
//------------------------------------
CLR_SPI_SS();                  //选中SD   卡;
SPDR=0x51;                      //发送CMD17
while(!(SPSR & 0x80));      //等待发送完成
SPDR=temp.dt;            //发送开始读取地址,高位字节在前,低位字节在后 ,注意不是temp.dt; 相当于往左边移动8位
while(!(SPSR & 0x80));
SPDR=temp.dt;
while(!(SPSR & 0x80));
SPDR=temp.dt;
while(!(SPSR & 0x80));
SPDR=0;               //你想想,为什么是0
while(!(SPSR & 0x80));

tearsman520 发表于 2010-2-18 20:22:55

回复【40楼】lzf713
-----------------------------------------------------------------------

呵呵,我没注意到是union——联合体~~

已经搞懂了,谢谢你悉心地解释~~

http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=3882010&bbs_page_no=1&search_mode=4&search_text=tearsman520&bbs_id=9999

void SD_Read_Sector(unsigned long int address)//从指定地址读取扇区
{
unsigned char ret;
unsigned int i;
union   
   {
    unsigned long int cluster;
unsigned char dt;
   }temp;                              //定义temp联合体
address <<=1;                  //地址左移1   位
temp.cluster=address;       //设置簇的地址
//------------------------------------
CLR_SPI_SS();                  //选中SD   卡;
SPDR=0x51;                      //发送CMD17
while(!(SPSR & 0x80));      //等待发送完成
SPDR=temp.dt;            //发送开始读取地址,高位字节在前,低位字节在后 ,注意不是temp.dt; 相当于往左边移动8位
while(!(SPSR & 0x80));   
SPDR=temp.dt;
while(!(SPSR & 0x80));
SPDR=temp.dt;
while(!(SPSR & 0x80));
SPDR=0;               //你想想,为什么是0
while(!(SPSR & 0x80));

这一段代码等同于address <<= 9,然后从dt开始发送~这样理解对么?

对了,还有,:)可否留个联系方式?有一些问题还想请教您~

lzf713 发表于 2010-2-18 21:40:51

你理解正确,但是程序运行速度会慢一些,如下程序:
address<<=9;
SPDR=(address && 0xff000000)>>24;
while(!(SPSR & 0x80));      //等待发送完成   
SPDR=(address && 0x00ff0000)>>16;
while(!(SPSR & 0x80));      //等待发送完成
SPDR=(address && 0x0000ff00)>>8;
while(!(SPSR & 0x80));      //等待发送完成
SPDR=(address && 0x000000ff);
while(!(SPSR & 0x80));      //等待发送完成

tearsman520 发表于 2010-2-19 12:34:55

回复【42楼】lzf713
-----------------------------------------------------------------------

怎么会慢呢?因为union是联合体,变量共享同一个内存空间,如果执行以下语句~

address <<= 9;

那么从dt<<24 + dt<<16 + dt<<8 + dt就是address,对吧?然后开始发送命令,一次一个字节,所以程序如下:

address <<= 9;
temp.cluster = address;

CLR_SPI_SS();                  //选中SD卡;   
SPDR=0x51;                      //发送CMD17   
while(!(SPSR & 0x80));      //等待发送完成   
SPDR=temp.dt;            //发送开始读取地址,高位字节在前,低位字节在后
while(!(SPSR & 0x80));   
SPDR=temp.dt;   
while(!(SPSR & 0x80));   
SPDR=temp.dt;   
while(!(SPSR & 0x80));   
SPDR=temp.dt;               
while(!(SPSR & 0x80));

这样做会慢?因为address开始就左移了9位?

tearsman520 发表于 2010-2-19 12:39:47

回复【42楼】lzf713
-----------------------------------------------------------------------

还有一个问题就是~ATmega16的RAM有1Kbytes,您在程序中开的buffer是712字节。

如果buffer只有256个字节,程序该怎么改动呢?我现在想在一款小容量单片机上实现这个WAV播放器;

内部RAM只有512bytes。所以我只开了256字节的buffer。改动了一些程序~~想向您请教~~

wcm_e 发表于 2010-2-19 15:03:20

谢谢, mark

lzf713 发表于 2010-2-19 18:16:11

执行address <<= 9将会比执行address <<= 1慢
如果你不信,则可以调试一下。

至于那个程序中开的buffer是712字节,这个问题不大。这个数据缓冲区是先进先出队列缓冲区。进:从SD卡读取数据,出:播放音乐时候。
由于SD卡每次一个完整读取过程,都是512字节个数据,也就是说,一般来说,你这个数据缓冲区至少需要512字节那么大,而我设置它为712,只是想充分使用M16内部RAM,当然725也可以,1024就不行了(因为程序中还有其他变量需要RAM存放的)

而你提出256个字节是否可以?这个绝对可以(我没有测试个,但是原来那个程序是使用FatFs FAT文件系统模块,任意个数据缓冲区都可以实现的),原理是在读取SD卡时候需要进行一些修改,比如判断先进先出数据缓存区剩下数据个数如果少于256个则可以读取下一个数据,否则等待,如此循环,一直到读取SD卡512字节个数据完为止,而在播放时候则没有任何影响(当然需要你读取数据速度能跟上来,否则有停顿感觉)

lzf713 发表于 2010-2-19 18:24:06

回复【46楼】lzf713
执行address &lt;&lt;= 9将会比执行address &lt;&lt;= 1慢
如果你不信,则可以调试一下。
至于那个程序中开的buffer是712字节,这个问题不大。这个数据缓冲区是先进先出队列缓冲区。进:从SD卡读取数据,出:播放音乐时候。
由于SD卡每次一个完整读取过程,都是512字节个数据,也就是说,一般来说,你这个数据缓冲区至少需要512字节那么大,而我设置它为712,只是想充分使用M16内部RAM,当然725也可以,1024就不行了(因为程序中还有其他变量需要RAM存放的)
而你提出256个字节是否可以?这个绝对可以(我没有测试个,但是原来那个程序是使用FatFs FAT文件系统模块,任意个数据缓冲区都可以实现的),原理是在读取SD卡时候需要进行一些修改,比如判断先进先出数据缓存区剩下数据个数如果少于256个则可以读取下一个数据,否则等待,如此循环,一直到......
-----------------------------------------------------------------------

至于256字节缓存区,我还必须说明的是,由于FAT32文件系统,一般一个扇区容量是512个字节数据,那么在获取MBR,DBR相关信息时候,一般先读取完512字节数据后再进行相关运算从而获取,比如FAT表位置。由于你缓冲区是256,所以需要进行灵活处理。我在程序中也有相关例子,就是获取下一簇的簇号(进行判断簇号是否连续),那里是没有使用缓冲区的。

yaya001 发表于 2010-2-19 18:49:00

mark

zhames 发表于 2010-3-16 23:40:29

mark

damao0668 发表于 2010-3-19 18:07:00

好强大

在此也想问个小问题
关于SD:对文本写入文字
首先是写入:我先在文本所在起始扇区:写入54个字符
然后就是读取这54个字符:但读取的是所有扇区512个字符,所以读出来的数据里面:除了有自己写入的54个外还有512字节以外其它的没用乱码出现
请问这应该怎样解决呢?

bcfai123 发表于 2010-3-19 19:37:45

mark

lzf713 发表于 2010-3-21 21:08:24

回复【50楼】damao0668
好强大
在此也想问个小问题
关于SD:对文本写入文字
首先是写入:我先在文本所在起始扇区:写入54个字符
然后就是读取这54个字符:但读取的是所有扇区512个字符,所以读出来的数据里面:除了有自己写入的54个外还有512字节以外其它的没用乱码出现
请问这应该怎样解决呢?
-----------------------------------------------------------------------

你54个字符在扇区512个字符的最前面,如果你将扇区512个字符存放在一个数组buff中,那么buff~buff就是你所需要的数据。

swustlx86 发表于 2010-3-22 09:24:27

mark

damao0668 发表于 2010-3-23 17:09:09

回复【52楼】lzf713
-----------------------------------------------------------------------

我已经解决了,把后面的字符都以空字符补完就行了

bcfai123 发表于 2010-3-26 21:19:11

mark

thinki 发表于 2010-4-16 00:23:23

顶一下

yueyeleo 发表于 2010-4-16 22:30:51

下载来学习学习,呵呵

KANGYD 发表于 2010-5-12 22:24:20

好贴,没敢想啊!呵呵

wmsky 发表于 2010-5-13 00:02:06

学习学习,呵呵

yusufu 发表于 2010-5-13 00:55:41

mark,学习学习

niba 发表于 2010-5-27 22:41:23

程序中T2匹配中断服务程序中 if(FifoCt>=Channel) 这啥意思呀,
怎么和通道号做比较呢

lzf713 发表于 2010-5-28 00:22:38

你想想,FifoCt表示先进先出队列里面有用(还没有播放)的数据,先假设是音源是单通道格式,不管你播放是单通道还是双通道,如果出现FifoCt<Channel这种情况,说明什么问题?是不是读取数据速度跟不上?队列里面有数据吗?
如果音源格式是双通道的,也不管播放是单声道还双通道,每次播放都要两个通道数据(如果双通道播放,则分别送到不同的DAC,这样才能实现双声道同步,如果是单通道播放则是他们数据和平均值),如果出现FifoCt<Channel这种情况,能进行上面的操作吗?
//=============================================
我在这里还提出一个问题,你知道T2匹配中断服务程序执行需要多少时间吗?如果要播放更高采样频率的音乐能否?

xinyou 发表于 2010-5-28 00:35:21

mark

cxj869 发表于 2010-5-28 01:46:38

不错,mark

span42 发表于 2010-7-7 11:22:53

按楼主的方案做出来了,mega16L晶振7.3728只能播放采样频率11.025khz以下的,高了就没意义了,低采样率的话,效果差了点

ysu533 发表于 2010-9-5 18:02:51

mark

ysu533 发表于 2010-9-7 10:41:04

我也用mega8搭了个试了试, 用来播放8bit, 8KHz的提示音. 就播放一些提示语而言, 我觉得效果已经足够好了.

就是里面的参数设置,不太明白

T2的值 为什么是 FCPU/采样频率/8, 这个8应该是8bit的分辨率吧?

最难理解的是T1的值,怎么和采样频率没有关系, 多少采样频率值都是一样的?

ysu533 发表于 2010-9-7 15:56:38

终于明白了其中的原理的, 谢谢楼主

zhames 发表于 2010-9-7 16:15:48

mark

5irmb 发表于 2010-10-3 01:31:42

马克。

exploer 发表于 2010-10-3 01:41:53

mark

siway2006 发表于 2010-10-3 11:41:29

程序我大概看了一下,楼主是这样处理的,先读512数据,等播放完再读下一个512数据,
这里我想问一下,这样出来的效果会不会感觉中间有断续?因为你读512数据时,肯定是停顿了一会,
这个一会,会不会感觉出来?

siway2006 发表于 2010-10-3 12:07:53

我刚才转换了一个22K的WAV文件,播放率看了一下是352K,我现在有个产品,方案是m8+vs1003+SD卡,能播放320K的mp3文件,感觉播放率再高就不行了,不知道我有没有记错,我试过用它播放22K的WAV,好像有卡,那怕22K能播放,比22K高的WAV文件肯定卡,我已经是用16M的晶振,所以我对楼主说的那个16M能播放22K WAV文件,有点不信,不知道有没有实际试过?

lzf713 发表于 2010-10-3 12:35:38

回复【72楼】siway2006
程序我大概看了一下,楼主是这样处理的,先读512数据,等播放完再读下一个512数据,
这里我想问一下,这样出来的效果会不会感觉中间有断续?因为你读512数据时,肯定是停顿了一会,
这个一会,会不会感觉出来?
-----------------------------------------------------------------------

看来你的阅读能力还需加强,你大概看了一下就得到这样的结论,难免太过于武断了。

而你在73楼发表的言论充分表明你没有尝试过就怀疑别人。

siway2006 发表于 2010-10-3 12:58:38

呵呵,,尝试要时间的,我只是把我看到的问题提出来,免得太多人走弯路

thinki 发表于 2010-10-23 21:15:41

Mark

hekun559 发表于 2010-10-24 22:43:16

MARK 解码
!!

huangrui 发表于 2010-10-29 01:04:15

Mark...

xiaojiadu 发表于 2010-11-1 21:08:08

好的

wang_ww 发表于 2011-1-7 21:32:20

谢谢分享

kdjnok 发表于 2011-1-24 21:36:49

void fread(unsigned long int address,unsigned int offset,unsigned int size)
{
unsigned char ret;
unsigned int i,s;
union
   {
    unsigned long int cluster;
        unsigned char dt;
   }temp;
address <<=1;
temp.cluster=address;
//------------------------------------
CLR_SPI_SS();
SPDR=0x51;
while(!(SPSR & 0x80));
SPDR=temp.dt;
while(!(SPSR & 0x80));
SPDR=temp.dt;
while(!(SPSR & 0x80));
SPDR=temp.dt;
while(!(SPSR & 0x80));
SPDR=0;
while(!(SPSR & 0x80));
SPDR=0xff;
while(!(SPSR & 0x80));
do{
      SPDR=0xff;
      while(!(SPSR & 0x80));
      ret=SPDR;   
    }while(ret!=0x0);
//-----------------------------------
do{
      SPDR=0xff;
      while(!(SPSR & 0x80));
      ret=SPDR;         
    }while(ret!=0xfe);
//=====================================
for(i=0;i<offset;i++)                 //抛弃不需要数据
   {
    SPDR=0xff;                  
    while(!(SPSR & 0x80));
   }
if(SampleBit==16)                //如果采样是16位,则抛弃低8位
   s=size/2;
else
   s=size;
for(i=offset;i<s+offset;i++)
   {
    while(FifoCt==MAXCOUNT);       //如果队列满则等待
    SPDR=0xff;      
        FifoWi++;                      //利用等待获取数据这段时间进行调整写入队列位置
        if(FifoWi==MAXCOUNT)
       FifoWi=0;            
    while(!(SPSR & 0x80));
    ret=SPDR;
        if(SampleBit==16)
       {
       SPDR=0xff;                  
       while(!(SPSR & 0x80));       
           ret=SPDR;
           ret=ret-0x80; //有符号数转无符号数(归一化)
       }
    Buff=ret;
    cli();
        FifoCt++;
        sei();       
   }
for(i=size+offset;i<512;i++)                 //抛弃不需要数据
   {
    SPDR=0xff;                  
    while(!(SPSR & 0x80));        
   }
//=====================================
SPDR=0xff;
while(!(SPSR & 0x80));
SPDR=0xff;
while(!(SPSR & 0x80));
SET_SPI_SS();
SPDR=0xff;
while(!(SPSR & 0x80));
ret=SPDR;
}
中这段:
//-----------------------------------
do{
      SPDR=0xff;
      while(!(SPSR & 0x80));
      ret=SPDR;         
    }while(ret!=0xfe);
如果由于干扰或其它原因使ret!=0xfe,程序就会陷入死循环。

bigworms 发表于 2011-1-25 12:29:28

mark

ybx520 发表于 2011-2-5 16:57:37

有一点哒哒声怎么回事啊?

chrysler 发表于 2011-3-16 08:51:44

记号

mcu2007 发表于 2011-3-17 21:53:22

mark

thinki 发表于 2011-3-19 21:40:56

回复【84楼】ybx520
-----------------------------------------------------------------------

同84楼的情况,我用PWM音频做输出的时候也是有哒哒的声音,不知道怎样处理掉

IGO_AVR 发表于 2011-3-26 21:58:00

楼主你好,看了你的程序,想来你是不是用用T2的CTC模式产生一个22050HZ的采样频率,然后用OCCR1A产生一个PWM信号,频率为62.5KHZ,然后T2中断里面改变OCCR1A以产生不同脉宽的PWM来模拟DAC?我觉得这样模拟的前提是采样频率的快速PWM的基频相差很大,否则不可以这样来模拟,是不是啊?
但是我对这句不理解:   TCCR2=TCCR2 |(1<<WGM21) | (1<<CS22)| (1<<CS21)| (1<<CS20);//T2:匹配清0,1024分频
OCR2=0xff;//匹配频率大概61Hz
61HZ是用来干什么啊?
我现在的想法是这样的,我用定时器一产生一个基频可变的快速PWM,这样的话我就可以只用一个定时器来做音频播放了,但是在实际波形输出的时候有点混,不知道您有没有用过类似方法,还请指教。

IGO_AVR 发表于 2011-3-26 22:18:26

所有的模块部分都调得差不多了,明天准备组装了,今天还有最后一个问题:PCM编码有正负之分,不知道楼主是怎么处理的?0X80设置为静音吗?那0XFF呢?

cu_ice 发表于 2011-3-28 08:47:36

mark

ngei 发表于 2011-3-28 09:12:17

mark

fwluck 发表于 2011-3-28 17:58:23

收藏

honey655729 发表于 2011-3-28 18:35:07

多谢楼主分享

chishangpiao 发表于 2011-3-28 19:29:22

好好品尝,准备动手。

cc1989summer 发表于 2011-3-28 19:30:03

Mark

zzuyelei 发表于 2011-3-28 20:50:41

如此强帖 此时不顶 更待何时

lzf713 发表于 2011-3-28 21:08:49

回复【88楼】IGO_AVR
楼主你好,看了你的程序,想来你是不是用用t2的ctc模式产生一个22050hz的采样频率,然后用occr1a产生一个pwm信号,频率为62.5khz,然后t2中断里面改变occr1a以产生不同脉宽的pwm来模拟dac?我觉得这样模拟的前提是采样频率的快速pwm的基频相差很大,否则不可以这样来模拟,是不是啊?
但是我对这句不理解:   tccr2=tccr2 |(1&lt;&lt;wgm21) | (1&lt;&lt;cs22)| (1&lt;&lt;cs21)| (1&lt;&lt;cs20);//t2:匹配清0,1024分频
ocr2=0xff;//匹配频率大概61hz
61hz是用来干什么啊?
我现在的想法是这样的,我用定时器一产生一个基频可变的快速pwm,这样的话我就可以只用一个定时器来做音频播放了,但是在实际波形输出的时候有点混,不知道您有没有用过类似方法,还......
-----------------------------------------------------------------------

//------------------------------------------------------------------
void KeyServer(void)
{
unsigned char key;
unsigned int time;
key=GKey;
//如果是停止按键按下,为了节省电源,将PWM停止,T2继续工作,然后处于睡眠状态,由T2匹配中断唤醒
//然后查询RUN按键是否按下,如果是则恢复原来状态,同时退出KeyServer(void)
if(!(key & (1<<STOP)))
   {
   TCCR2=0;
   TCCR2=TCCR2 |(1<<WGM21) | (1<<CS22)| (1<<CS21)| (1<<CS20);//T2:匹配清0,1024分频
   OCR2=0xff;//匹配频率大概61Hz

   TCCR1A=0;   //停止PWM
   TCCR1B=0;
//如果CPU是M8,这里要修改
   DDRD=DDRD & (~((1<<DDC4) | (1<<DDC5))); //喇叭端口输入,M16的OC1A,OC1B为输入引脚
   PORTD=PORTD & (~((1<<PD4) | (1<<PD5))); //喇叭端口,没有上拉电阻
       cli();
   do{
               ACSR=ACSR & (~(1<<ACIE)); //脱裤子放屁(原来一直没有使用模拟比较器,其默认是0)
               ACSR=ACSR & (~(1<<ACD));
         set_sleep_mode(SLEEP_MODE_IDLE); //睡眠模式是:空闲,如果RUN按键接到外部中断,掉电模式可以更加省电
         sleep_enable(); //MCUCR=MCUCR | (1<<SE)
               sei();          //允许T2匹配中断唤醒CPU
               sleep_cpu();    //执行sleep指令
         sleep_disable();//MCUCR=MCUCR & (~(1<<SE));
               key=KEY_PIN & KEYMASK2; //读取按键状态
           }while(key & (1<<RUN));   //如果RUN按键没有按下,继续睡眠
   cli();
   DeviceIni(); //恢复T2盒PWM设置,为播放音乐准备,睡眠前已经改变了
   }
if(!(key & (1<<LAST))) //选择上一首歌
   {
   searchLast();
   }
//if(!(key & (1<<NEXT)))    //选择下一首歌,不需要处理
time=0;
do{                        //等待松键
   key=KEY_PIN & KEYMASK2;
       time++;
       if(time==20000)
          {
          
          }
   }while(key!=KEYMASK2);
}
//------------------------------------------------------------------
不知道你能否看明白?

lzf713 发表于 2011-3-28 21:13:46

回复【89楼】IGO_AVR
所有的模块部分都调得差不多了,明天准备组装了,今天还有最后一个问题:pcm编码有正负之分,不知道楼主是怎么处理的?0x80设置为静音吗?那0xff呢?
-----------------------------------------------------------------------

0x80设置为静音,不错确实是这样,那么0xff,0x00可以理解最大值,至于0XFF是正最大值或者负最大值,你可以自己定义,一般来说是正最大值

IGO_AVR 发表于 2011-3-29 09:53:01

楼主,我现在能播放音乐,但是有明显的突突突的声音,关于问题的具体描述和我自己的观点都在一下帖子的130楼处了,能帮忙分析下原因吗?
http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=3981990&bbs_id=9999
谢谢!

lzf713 发表于 2011-3-29 11:12:10

但是有明显的突突突的声音?也许你先读完512字节数据然后再播放这种模式不好,肯定会有断续感觉,因为播放完了,需要等待你读取512字节数据,这是需要时间的。一边读取一边播放才是正道,除非你的RAM足够大,将一首歌全部读取到RAM中然后再播放。
还有,也许你的熔丝设置不对!
页: [1] 2
查看完整版本: wav格式音乐播放器