搜索
bottom↓
回复: 127

wav格式音乐播放器

[复制链接]

出0入0汤圆

发表于 2009-11-29 17:05:42 | 显示全部楼层 |阅读模式
我在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,这是相当于载波,如果载波频率和信号频率很近时候,这样调整信号没有什么意义的。
转换音乐格式有很多软件,但是我使用豪杰音频通感觉到很好用。

(原文件名:image002.gif)

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

出0入0汤圆

发表于 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

出0入0汤圆

发表于 2009-11-29 18:43:18 | 显示全部楼层
顶!

出0入0汤圆

发表于 2009-11-29 19:21:27 | 显示全部楼层
MARK

出0入0汤圆

发表于 2009-11-29 20:01:56 | 显示全部楼层

出0入0汤圆

发表于 2009-11-29 22:28:32 | 显示全部楼层
用ATtinyX5 series (25/45/85)是16位的pwm,楼主觉的有必要吗?哈哈,冒昧问下。

出0入0汤圆

发表于 2009-11-29 22:46:39 | 显示全部楼层
MARK 单片机音频解码

出0入0汤圆

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

出0入0汤圆

发表于 2009-11-30 09:11:36 | 显示全部楼层
顶。。。。。。。。。。。。

出0入0汤圆

发表于 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吗?

出0入0汤圆

发表于 2009-11-30 09:42:11 | 显示全部楼层
标记下

出0入0汤圆

发表于 2009-11-30 10:14:08 | 显示全部楼层
输出加个电容耦合,安全点。。

出0入0汤圆

发表于 2009-12-1 14:03:47 | 显示全部楼层
继续支持楼主.
另外【1楼】 Soul.art 潇洒的猪 老乡啊.希望能认识认识.

出0入0汤圆

 楼主| 发表于 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[FifoRi];
        FifoCt--;
    if(PlayMode==1)    //单通道播放形式
         {
           if(Channel==1)  //如果音源只有一个通道
            {
                  OCR1A=dt1;
                  OCR1B=~dt1;  //增加输出音量,但是接线需要喇叭接在OCR1A和OCR1B之间才有效果
        }   
           else            //如果音源有两个通道,那么一个喇叭的信号是这两个通道的平均值
            {
                  FifoRi++;    //获取下一个数据
                  if(FifoRi==MAXCOUNT)
                   FifoRi=0;
                  dt2=Buff[FifoRi];
                  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[FifoRi];
                  FifoCt--;                                                                              
                }
         }
  }
}
那么喇叭两个引脚直接连在OCR1A和OCR1B上,那么音量可以加大一倍,也不需要外接电容。当然和原来的接法,还是要加电容的。

出0入0汤圆

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

出0入0汤圆

发表于 2009-12-3 13:45:22 | 显示全部楼层
好东西,哈哈,如果能录就实用了

出0入0汤圆

发表于 2009-12-3 16:52:18 | 显示全部楼层
不错,学习中.

出0入0汤圆

发表于 2009-12-4 13:38:05 | 显示全部楼层
支持。

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

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

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

出0入0汤圆

发表于 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出正常的声音吗?

出0入0汤圆

发表于 2009-12-4 16:59:54 | 显示全部楼层
Mark学习了

出0入0汤圆

发表于 2009-12-4 17:47:25 | 显示全部楼层
自己看出来了,原来在读取WAVE头文件的时候更改了OCR2的值,算出与WAVE采样率对应的中断频率

出0入0汤圆

发表于 2009-12-4 23:38:39 | 显示全部楼层
厉害 ,支持你

出10入10汤圆

发表于 2009-12-5 08:56:20 | 显示全部楼层
不错,支持!

出0入0汤圆

发表于 2009-12-11 20:23:59 | 显示全部楼层
请问lz,tf卡能用吗

出0入0汤圆

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

出5入8汤圆

发表于 2009-12-13 19:41:46 | 显示全部楼层
mark!

出0入0汤圆

发表于 2009-12-14 22:46:11 | 显示全部楼层
学习

出0入0汤圆

发表于 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能不能把钢琴音色数据传给我,我试验了提取音色库,可是失败了。

出0入0汤圆

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

出0入0汤圆

 楼主| 发表于 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的),不过如此而已。不过现在没有什么激_情了。

出0入0汤圆

发表于 2010-1-30 20:05:33 | 显示全部楼层
MARK

出0入0汤圆

发表于 2010-2-6 14:07:48 | 显示全部楼层
MARK

出0入0汤圆

发表于 2010-2-6 14:30:07 | 显示全部楼层
顶起来~

出0入0汤圆

发表于 2010-2-6 16:29:58 | 显示全部楼层
楼主强得一米

出0入0汤圆

发表于 2010-2-6 23:27:09 | 显示全部楼层
太厉害了

出0入0汤圆

发表于 2010-2-7 04:14:04 | 显示全部楼层
MARK一下……

出0入0汤圆

发表于 2010-2-9 19:44:08 | 显示全部楼层
mark....

出0入0汤圆

发表于 2010-2-9 20:06:43 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-2-11 13:33:40 | 显示全部楼层
楼主能留个联系方式么~

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

leo2010@msn.com

出0入0汤圆

发表于 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[4];
   }temp;                              //定义temp  联合体
  address <<=1;                  //地址左移1   位
  temp.cluster=address;       //设置簇的地址
//------------------------------------
  CLR_SPI_SS();                  //选中SD   卡;
  SPDR=0x51;                      //发送CMD17
  while(!(SPSR & 0x80));      //等待发送完成
  SPDR=temp.dt[2];              //发送dt[4]  中的前三个字符,也就是簇号的前三位
  while(!(SPSR & 0x80));
  SPDR=temp.dt[1];
  while(!(SPSR & 0x80));
  SPDR=temp.dt[0];
  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;

这个代码是什么意思呢?

出0入0汤圆

 楼主| 发表于 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[4];共用内存的。
在GCC编译器中
联合体中,数组下标大的装数据大端,如:temp.cluster=0xaabbccdd,那么temp.dt[3]=0xaa,temp.dt[2]=0xbb
temp.dt[1]=0xcc,temp.dt[0]=0xdd

我现在假设address=00000000 00000000 10101010 01010101
那么address<<=1;则address=00000000 00000001 01010100 10101010
然后执行temp.cluster=address,那么temp.dt[3]=00000000,temp.dt[2]=00000001,temp.dt[1]=01010100,temp.dt[0]=10101010
在下面程序中将temp.dt[2],temp.dt[1],temp.dt[0],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[4];
   }temp;                              //定义temp  联合体
  address <<=1;                  //地址左移1   位
  temp.cluster=address;       //设置簇的地址
//------------------------------------
  CLR_SPI_SS();                  //选中SD   卡;
  SPDR=0x51;                      //发送CMD17
  while(!(SPSR & 0x80));      //等待发送完成
  SPDR=temp.dt[2];              //发送开始读取地址,高位字节在前,低位字节在后 ,注意不是temp.dt[3]; 相当于往左边移动8位
  while(!(SPSR & 0x80));  
  SPDR=temp.dt[1];
  while(!(SPSR & 0x80));
  SPDR=temp.dt[0];
  while(!(SPSR & 0x80));
  SPDR=0;                 //你想想,为什么是0
  while(!(SPSR & 0x80));

出0入0汤圆

发表于 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[4];  
   }temp;                              //定义temp  联合体  
  address <<=1;                  //地址左移1   位  
  temp.cluster=address;       //设置簇的地址  
//------------------------------------  
  CLR_SPI_SS();                  //选中SD   卡;  
  SPDR=0x51;                      //发送CMD17  
  while(!(SPSR & 0x80));      //等待发送完成  
  SPDR=temp.dt[2];              //发送开始读取地址,高位字节在前,低位字节在后 ,注意不是temp.dt[3]; 相当于往左边移动8位
  while(!(SPSR & 0x80));   
  SPDR=temp.dt[1];  
  while(!(SPSR & 0x80));  
  SPDR=temp.dt[0];  
  while(!(SPSR & 0x80));  
  SPDR=0;                 //你想想,为什么是0
  while(!(SPSR & 0x80));  

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

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

出0入0汤圆

 楼主| 发表于 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));      //等待发送完成

出0入0汤圆

发表于 2010-2-19 12:34:55 | 显示全部楼层
回复【42楼】lzf713
-----------------------------------------------------------------------

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

address <<= 9;

那么从dt[3]<<24 + dt[2]<<16 + dt[1]<<8 + dt[0]就是address,对吧?然后开始发送命令,一次一个字节,所以程序如下:
  
  address <<= 9;
  temp.cluster = address;

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

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

出0入0汤圆

发表于 2010-2-19 12:39:47 | 显示全部楼层
回复【42楼】lzf713
-----------------------------------------------------------------------

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

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

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

出0入0汤圆

发表于 2010-2-19 15:03:20 | 显示全部楼层
谢谢, mark

出0入0汤圆

 楼主| 发表于 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字节个数据完为止,而在播放时候则没有任何影响(当然需要你读取数据速度能跟上来,否则有停顿感觉)

出0入0汤圆

 楼主| 发表于 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,所以需要进行灵活处理。我在程序中也有相关例子,就是获取下一簇的簇号(进行判断簇号是否连续),那里是没有使用缓冲区的。

出0入0汤圆

发表于 2010-2-19 18:49:00 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-3-16 23:40:29 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-3-19 18:07:00 | 显示全部楼层
好强大

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

出0入0汤圆

发表于 2010-3-19 19:37:45 | 显示全部楼层
mark

出0入0汤圆

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

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

出0入0汤圆

发表于 2010-3-22 09:24:27 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-3-23 17:09:09 | 显示全部楼层
回复【52楼】lzf713
-----------------------------------------------------------------------

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

出0入0汤圆

发表于 2010-3-26 21:19:11 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-16 00:23:23 | 显示全部楼层
顶一下

出0入0汤圆

发表于 2010-4-16 22:30:51 | 显示全部楼层
下载来学习学习,呵呵

出0入0汤圆

发表于 2010-5-12 22:24:20 | 显示全部楼层
好贴,没敢想啊!呵呵

出0入0汤圆

发表于 2010-5-13 00:02:06 | 显示全部楼层
学习学习,呵呵

出0入0汤圆

发表于 2010-5-13 00:55:41 | 显示全部楼层
mark,学习学习

出0入0汤圆

发表于 2010-5-27 22:41:23 | 显示全部楼层
程序中T2匹配中断服务程序中 if(FifoCt>=Channel) 这啥意思呀,
怎么和通道号做比较呢

出0入0汤圆

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

出0入0汤圆

发表于 2010-5-28 00:35:21 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-5-28 01:46:38 | 显示全部楼层
不错,mark

出0入50汤圆

发表于 2010-7-7 11:22:53 | 显示全部楼层
按楼主的方案做出来了,mega16L晶振7.3728只能播放采样频率11.025khz以下的,高了就没意义了,低采样率的话,效果差了点

出0入0汤圆

发表于 2010-9-5 18:02:51 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-9-7 10:41:04 | 显示全部楼层
我也用mega8搭了个试了试, 用来播放8bit, 8KHz的提示音. 就播放一些提示语而言, 我觉得效果已经足够好了.

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

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

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

出0入0汤圆

发表于 2010-9-7 15:56:38 | 显示全部楼层
终于明白了其中的原理的, 谢谢楼主

出0入0汤圆

发表于 2010-9-7 16:15:48 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-10-3 01:31:42 | 显示全部楼层
马克。

出0入0汤圆

发表于 2010-10-3 01:41:53 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-10-3 11:41:29 | 显示全部楼层
程序我大概看了一下,楼主是这样处理的,先读512数据,等播放完再读下一个512数据,
这里我想问一下,这样出来的效果会不会感觉中间有断续?因为你读512数据时,肯定是停顿了一会,
这个一会,会不会感觉出来?

出0入0汤圆

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

出0入0汤圆

 楼主| 发表于 2010-10-3 12:35:38 | 显示全部楼层
回复【72楼】siway2006
程序我大概看了一下,楼主是这样处理的,先读512数据,等播放完再读下一个512数据,
这里我想问一下,这样出来的效果会不会感觉中间有断续?因为你读512数据时,肯定是停顿了一会,
这个一会,会不会感觉出来?
-----------------------------------------------------------------------

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

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

出0入0汤圆

发表于 2010-10-3 12:58:38 | 显示全部楼层
呵呵,,尝试要时间的,我只是把我看到的问题提出来,免得太多人走弯路

出0入0汤圆

发表于 2010-10-23 21:15:41 | 显示全部楼层
Mark

出0入0汤圆

发表于 2010-10-24 22:43:16 | 显示全部楼层
MARK 解码
!!

出0入0汤圆

发表于 2010-10-29 01:04:15 | 显示全部楼层
Mark...

出0入0汤圆

发表于 2010-11-1 21:08:08 | 显示全部楼层
好的

出0入0汤圆

发表于 2011-1-7 21:32:20 | 显示全部楼层
谢谢分享

出0入0汤圆

发表于 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[4];
   }temp;
  address <<=1;
  temp.cluster=address;
//------------------------------------
  CLR_SPI_SS();
  SPDR=0x51;
  while(!(SPSR & 0x80));
  SPDR=temp.dt[2];
  while(!(SPSR & 0x80));
  SPDR=temp.dt[1];
  while(!(SPSR & 0x80));
  SPDR=temp.dt[0];
  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[FifoWi]=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,程序就会陷入死循环。

出0入0汤圆

发表于 2011-1-25 12:29:28 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-2-5 16:57:37 | 显示全部楼层
有一点哒哒声怎么回事啊?

出0入0汤圆

发表于 2011-3-16 08:51:44 | 显示全部楼层
记号

出0入0汤圆

发表于 2011-3-17 21:53:22 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-3-19 21:40:56 | 显示全部楼层
回复【84楼】ybx520  
-----------------------------------------------------------------------

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

出0入0汤圆

发表于 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,这样的话我就可以只用一个定时器来做音频播放了,但是在实际波形输出的时候有点混,不知道您有没有用过类似方法,还请指教。

出0入0汤圆

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

出0入0汤圆

发表于 2011-3-28 08:47:36 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-3-28 09:12:17 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-3-28 17:58:23 | 显示全部楼层
收藏

出0入0汤圆

发表于 2011-3-28 18:35:07 | 显示全部楼层
多谢楼主分享

出0入0汤圆

发表于 2011-3-28 19:29:22 | 显示全部楼层
好好品尝,准备动手。

出0入0汤圆

发表于 2011-3-28 19:30:03 | 显示全部楼层
Mark

出0入0汤圆

发表于 2011-3-28 20:50:41 | 显示全部楼层
如此强帖 此时不顶 更待何时

出0入0汤圆

 楼主| 发表于 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);
}
//------------------------------------------------------------------
不知道你能否看明白?

出0入0汤圆

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

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

出0入0汤圆

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

出0入0汤圆

 楼主| 发表于 2011-3-29 11:12:10 | 显示全部楼层
但是有明显的突突突的声音?也许你先读完512字节数据然后再播放这种模式不好,肯定会有断续感觉,因为播放完了,需要等待你读取512字节数据,这是需要时间的。一边读取一边播放才是正道,除非你的RAM足够大,将一首歌全部读取到RAM中然后再播放。
还有,也许你的熔丝设置不对!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-2 05:46

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表