当前位置: 首页 > 编程日记 > 正文

单片AT89C2051 + SD卡 + 3310LCD = 音乐播放器

http://www.amobbs.com/thread-4503884-1-1.html

这个小玩意,采用 ATMEL 的传统51MCU作主控制芯片,加上SD卡和显示屏,就可以作简单的音乐播放器了,虽然音质不怎么样,不过作为DIY还是蛮有乐趣,希望大家喜欢。
没有采用FAT文件系统,只是按扇区读取SD卡,由于2051资源有限,改为4051有望可以操作FAT,但目前程序还在不断完善中。
128byte怎样读取512byte的扇区数据?可以采用边读边播放的方式,就能解决。音乐文件是32KHz取样率的WAV文件,所以和HIFI就沾不上边了。
程序是用C来编写,以方便交流,资料整理中,完善后再上传。

这是未经整理的程序,有点乱,凑合着看,有时间再进一步改进。
SD部分是修改于本坛的一个贴子
----------------------------------------------------------
添加部分注释,提高可读性

#include <reg51.h>#include <INTRINS.H>#include <MATH.H>#include "LCD_3310.H"#define uchar unsigned char#define uint  unsigned int#define ulong unsigned long/************ 定义管脚 *************/
sbit DOUT = P3^0;  //SD卡数据输出
sbit CLK  = P3^1;  //SD卡时钟输入
sbit DIN  = P3^2;  //SD卡数据输入
sbit CS   = P3^3;  //SD卡片选使能/************ 全局变量 ************/                                                                                                                       
uchar pbuf[64]; //数据缓冲区
uchar p;        //播放缓冲区指针
uchar px;       //频谱显示的X坐标

code ulong Track[17] = {   //0x15000,0x58000   SD卡中各声音文件的首址,以后打算把这些数据放在SD卡的特定配置文件中再读入。0xd7800-0x8a00,0x76b800-0x8a00,0xedc000-0x8a00,0x1752800-0x8a00,0x1F08000-0x8a00,0x2569800-0x8a00,0x2EDB800-0x8a00,0x3480000-0x8a00,0x3BFA800-0x8a00,0x41EB000-0x8a00,0x48EF000-0x8a00,0x508A000-0x8a00,0x59AE800-0x8a00,0x60AF000-0x8a00,0x6878000-0x8a00,0x6DBE000-0x8a00,0x7525800-0x8a00,};/******* SD访问错误码的定义 *******/#define INIT_CMD0_ERROR   0X01#define INIT_CMD1_ERROR   0X02#define READ_BLOCK_ERROR  0X03#define WRITE_BLOCK_ERROR 0X04/********* 通用延时函数 ***********/
void delay(uint i){while(i--);}/******** SD写入一个字节 **********/
void spi_write(uchar x){   //不采用循环结构是为了提高处理速度DIN = x & 0x80;CLK = 0;CLK = 1;DIN = x & 0x40;CLK = 0;CLK = 1;DIN = x & 0x20;CLK = 0;CLK = 1;DIN = x & 0x10;CLK = 0;CLK = 1;DIN = x & 0x08;CLK = 0;CLK = 1;DIN = x & 0x04;CLK = 0;CLK = 1;DIN = x & 0x02;CLK = 0;CLK = 1;DIN = x & 0x01;CLK = 0;CLK = 1;}/******* SD慢速写入一个字节 ********/
void spi_write_low_speed(uchar x){uchar i;for(i = 8; i; --i){DIN = x & 0x80;x <<= 1;CLK = 0;delay(1);CLK = 1;delay(1);}}/*********** SD读入一字节 ***********/
uchar spi_read(void){   //利用51串口的同步移位功能,以达了最高的读度2MHz CLKRI = 0;while(RI == 0);return SBUF;}/******** SD慢速读入一字节 **********/
uchar spi_read_low_speed(void){uchar temp,i;for(i = 8; i; --i){CLK = 0;delay(1);temp <<= 1;if(DOUT) temp++;CLK = 1;delay(1);}return temp;}/******** 发送一组SD命令 ************/
uchar write_cmd(uchar data *pcmd){uchar temp,time=0,i;for(i = 0; i<6; i++) //一条命令都是6个字节,形参用指针,{                    //指向6个字节命令,
        spi_write(pcmd);}do                   //看看写进去没有,通过so管脚
    {temp = spi_read();time++;}                    //一直到读到的不是0xff或超时,退出去while(temp==0xff && time<100);return temp;}/****** 慢速发送一组SD命令 **********/
uchar write_cmd_low_speed(uchar *pcmd){uchar temp,time=0,i;for(i=0;i<6;i++)    //一条命令都是6个字节,形参用指针,{                   //指向6个字节命令,
        spi_write_low_speed(pcmd);}do                  //看看写进去没有,通过so管脚
    {temp = spi_read_low_speed();time++;}                   //一直到读到的不是0xff或超时,退出去while(temp==0xff && time<100);return temp;}/********* SD卡 激活,复位 *********/
uchar sd_reset(void){uchar time,temp,i;uchar pcmd[6]={0x40,0x00,0x00,0x00,0x00,0x95};CS = 1;for(i = 0; i < 0x0f; i++) //复位时,至少要72个时钟周期,{                         //现在是,15*8=120个clkspi_write_low_speed(0xff);}CS = 0;time=0;do{temp = write_cmd_low_speed(pcmd);time++;if(time > 100) return INIT_CMD0_ERROR;}while(temp != 0x01);      //校验码是0x01,表示写入成功CS = 1;spi_write_low_speed(0xff);//时序上要求补8个clkreturn 0;                 //返回0,写入成功
}/************ SD卡初始化 ************/
uchar sd_init(void){uchar time, temp;uchar pcmd[6] = {0x41,0x00,0x00,0x00,0x00,0xff};CS = 0;time = 0;do{temp = write_cmd_low_speed(pcmd);time++;if(time > 100) return INIT_CMD1_ERROR;}             while(temp != 0x00);CS = 1;spi_write_low_speed(0xff);return 0;}/******* 读取一扇区的点阵图像 *********/
uchar sd_read_bmp(uchar data *ad){uchar temp, time, x, pcmd[6];uint j = 0;pcmd[0] = 0x51;pcmd[1] = *ad;pcmd[2] = *(++ad);pcmd[3] = *(++ad);pcmd[4] = 0;pcmd[5] = 0xff;CS = 0;time = 0;do{temp = write_cmd(pcmd);if(++time > 100){CS = 1;return READ_BLOCK_ERROR;}}while(temp != 0);//等待SD卡回应while(spi_read() != 0x7f); //0xfe,51的串口移位是LSB优先,所以结果高低位倒置for (j = 0; j < 504; j++)  //3310的分辨率为 84 * 48,总计用504字节
    {LCD3310_write_dat(spi_read());}for (x = 0; x < 10; x++) spi_read(); //略过8字节数据和2字节CRCspi_write(0xff);CS = 1;return 0;}/******* 读取一扇区的声音数据 *********/
uchar sd_read_sector(uchar data *ad){uchar temp, time, pcmd[6];uint j = 0;pcmd[0] = 0x51;pcmd[1] = *ad;pcmd[2] = *(++ad);pcmd[3] = *(++ad);pcmd[4] = 0;pcmd[5] = 0xff;CS = 0;time = 0;do{temp = write_cmd(pcmd);if(++time > 100){CS = 1;return READ_BLOCK_ERROR;}}while(temp != 0);//等待SD回应的时间有点长,所以在这里插入显示模拟的频谱图temp = pbuf[16];         //随便挑一个数据显示LCD3310_set_XY(px,5);    //设定显示位置px += 6;if (px >= 39) px = 0;if (temp & 0x80) temp ^= 0x80; //求得声音振幅else temp = 0x80 - temp;temp = Level[temp>>4];         //不同幅度对应不同的谱线图案
    LCD3310_write_dat(temp);LCD3310_write_dat(temp);LCD3310_write_dat(temp);while(spi_read() != 0x7f);//0xfe,51的串口移位是LSB优先,所以结果高低位倒置while(1)     //读取512字节数据
    {RI = 0;_nop_(); pbuf[j++ & 63] = SBUF; //为求快速,不用函数调用RI = 0;_nop_(); pbuf[j++ & 63] = SBUF; //直接启动串口移入RI = 0;_nop_(); pbuf[j++ & 63] = SBUF; //连续读四字节RI = 0;_nop_(); pbuf[j++ & 63] = SBUF;if(j >= 512) break;while((((uchar)j - p) & 63) > 55);     //检测播放进度,}                                          //如果缓冲区接近溢出,先暂停等待spi_read();//略过 crcspi_read();//略过 crcspi_write(0xff);//SD 时序要求补8个脉冲CS = 1;return 0;}/**************************** 主程序 *******************************/
int main(void){uchar key,n,Count,Min,Sec;ulong addr;  // SD 的扇区地址
P1   = 0x80; // DAC 输出中点电压RI   = 1;REN  = 1;TMOD = 0x02;TH0  = 256 - 62.5;  //定时器设定约为 32KHz,和WAV文件取样率对应ET0  = 1;EA   = 1;px   = 0;n    = 64;do pbuf[--n] = 0x80; while(n); //填充播放缓冲区delay(65535);LCD3310_init();LCD3310_set_XY(0,0);LCD3310_write_cmd(0x22);       //设定LCD扫描顺序
    sd_reset();sd_init();addr = 0x4f400;sd_read_bmp((uchar) &addr);    //显示欢迎画面while (D_C == 1) ;while (D_C == 0) ;             //等待按键delay(65535);//==============  main loop ==================while(1)  //循环播放所有曲目
    {TR0 = 0;LCD3310_write_cmd(0x22);LCD3310_set_XY(0,0);addr = 0x4f600 + ((uint)n<<9);sd_read_bmp((uchar) &addr); //显示歌名、歌手LCD3310_write_cmd(0x20);TR0  = 1;p    = 0xd0;Min  = 0;Sec  = 0;Count= 0;for (addr = Track[n]; addr < Track[n+1];)//播放第n曲
        {//============ 按键处理 ===============key = (key >> 2) | (P3 & 0x30); //仅一句的扫键函数,包括扫描和消抖if (key == 0x03)                //键码为03是播放/暂停键
            {LCD3310_set_XY(78,5);TCON ^= 0x10;               //TR0 取反if (TR0) LCD3310_print(11); //显示播放符号else     LCD3310_print(12); //显示暂停符号
            }       else if (key == 0x2b)           //键码为2b是前一曲
            {if ((Min || (Sec & 0xf0))) n--;//10秒后跳本曲开始else n -= 2;                   //10秒内跳前一曲break;}else if (key == 0x17)           //键码为17是后一曲break;//======== 读一扇区数据或暂停 =========if (TR0 == 0) {delay(2000); continue;}sd_read_sector((uchar) &addr);addr += 512;//=========== 播放时间计数 ============Count += 2;if (Count >= 125){Count -= 125;Sec++;if ((Sec & 0x0f) > 9){Sec += 6;if (Sec >= 0x60){Sec = 0;Min++;if ((Min & 0x0f) > 9){Min += 6;if (Min > 0x60) Min = 0;}}}}//======= 分时间片显示时间/标志 ========switch (Count & 14){case 2: LCD3310_set_XY(44,5);LCD3310_print(Min>>4);//分钟十位break;case 4: LCD3310_set_XY(50,5);LCD3310_print(Min&15);//分钟个位break;case 6: LCD3310_set_XY(56,5);LCD3310_print(10);    //分隔符break;case 8: LCD3310_set_XY(62,5);LCD3310_print(Sec>>4);//秒十位break;case 10: LCD3310_set_XY(68,5);LCD3310_print(Sec&15);//秒个位break;case 12:LCD3310_set_XY(78,5);if (Count & 0x40) LCD3310_print(13); //闪动播放符号else LCD3310_print(11); }}n++;       //下一曲n &= 15;   //这个SD卡只有16首歌}//while(1);}//main()void timer0 (void) interrupt 1 using 1{if (TL0 & 1) _nop_(); //消除中断响应时间不一致,造成的频率抖动P1  = pbuf[++p & 63]; //输出一个声音数据
}

这是3310 LCD 部分

#include <reg51.h>#include <INTRINS.H>sbit SDIN = P3^2; //P3^2sbit SCLK = P3^4;sbit D_C  = P3^5;sbit SCE  = P3^7;code unsigned char Font[70] = {0x3E, 0x51, 0x49, 0x45, 0x3E ,  // 00x00, 0x42, 0x7F, 0x40, 0x00 ,  // 10x42, 0x61, 0x51, 0x49, 0x46 ,  // 20x21, 0x41, 0x45, 0x4B, 0x31 ,  // 30x18, 0x14, 0x12, 0x7F, 0x10 ,  // 40x27, 0x45, 0x45, 0x45, 0x39 ,  // 50x3C, 0x4A, 0x49, 0x49, 0x30 ,  // 60x01, 0x71, 0x09, 0x05, 0x03 ,  // 70x36, 0x49, 0x49, 0x49, 0x36 ,  // 80x06, 0x49, 0x49, 0x29, 0x1E ,  // 90x00, 0x00, 0x36, 0x36, 0x00 ,  // :0x7f, 0x3e, 0x1c, 0x08, 0x00 ,  // >0x3e, 0x3e, 0x00, 0x3e, 0x3e ,  // ||0x00, 0x00, 0x00, 0x00, 0x00 ,  //" "
 };    code unsigned char Level[8] = {0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff,};extern void delay(unsigned int i);void LCD3310_write_cmd(unsigned char cmd){D_C  = 0;SCLK = 0;SCE  = 0;delay(3);SDIN = cmd & 0x80;SCLK = 1;SDIN = cmd & 0x40;SCLK = 0;SCLK = 1;SDIN = cmd & 0x20;SCLK = 0;SCLK = 1;SDIN = cmd & 0x10;SCLK = 0;SCLK = 1;SDIN = cmd & 0x08;SCLK = 0;SCLK = 1;SDIN = cmd & 0x04;SCLK = 0;SCLK = 1;SDIN = cmd & 0x02;SCLK = 0;SCLK = 1;SDIN = cmd & 0x01;SCLK = 0;SCLK = 1;D_C  = 1;SDIN = 1;SCE  = 1;}void LCD3310_write_dat(unsigned char dat){//    D_C  = 1;SCLK = 0;SCE  = 0;delay(3);SDIN = dat & 0x80;SCLK = 1;SDIN = dat & 0x40;SCLK = 0;SCLK = 1;SDIN = dat & 0x20;SCLK = 0;SCLK = 1;SDIN = dat & 0x10;SCLK = 0;SCLK = 1;SDIN = dat & 0x08;SCLK = 0;SCLK = 1;SDIN = dat & 0x04;SCLK = 0;SCLK = 1;SDIN = dat & 0x02;SCLK = 0;SCLK = 1;SDIN = dat & 0x01;SCLK = 0;SCLK = 1;D_C  = 1;SDIN = 1;SCE  = 1;}void LCD3310_init(void){LCD3310_write_cmd(0x21);LCD3310_write_cmd(0xd7);LCD3310_write_cmd(0x06);LCD3310_write_cmd(0x20);LCD3310_write_cmd(0x0c);}void LCD3310_set_XY(unsigned char x,unsigned char y){if (x >= 84) return;if (y >= 6)  return;LCD3310_write_cmd(0x80 | x);LCD3310_write_cmd(0x40 | y);}void LCD3310_print(unsigned char n){n = (n << 2) + n;LCD3310_write_dat(Font[n]);LCD3310_write_dat(Font[++n]);LCD3310_write_dat(Font[++n]);LCD3310_write_dat(Font[++n]);LCD3310_write_dat(Font[++n]);}

回复【104楼】maxims
呵呵,希望能解释一下电路
-----------------------------------------------------------------------

R1、C1 组成LCD的上电复位电路。
R2~R4是上拉电阻,虽然2051的IO有内部的弱上拉,但这三个IO是LCD接口与键盘接口复用,需要上拉强一点。
R5、R6、Q1组成OC输出的反相器,当SCE为高电平时,三极管导通,键盘使能,LCD通信中止;当SCE为低电平时,LCD通信使能,键盘断开。需要注意的是图中左右两键没加隔离二极管,不要同时按下,否则引起显示错乱。
R2~R6的参数不要大幅度改动,这些参数是经过计算得到一个比较合适的值。
晶振、C3、C5没什么好说,这些都单片机系统必需的。
R7、R8是P1.0和P1.1的上拉电阻,因为这两个口是开漏输出。
R9~R24组成R2R型DAC,选取50K/100K是因为2051输出高电平的带载能力差,电阻太小将导致DAC线性变差。这里的电阻最好用1%的金属膜电阻,以改善DAC的线性度。50K电阻是非标电阻,这里用51K和2.7M电阻并联代替。
C6是高频滤波电容,以减少DAC输出的高频噪声。
C7是输出耦合电容,连接LINE OUT输出端子,输出阻抗较高,50K,只能接功放机或有源音箱。如果接耳机,需加一级放大,可以用运放做跟随器,或用TDA2822功放IC,能带个小嗽叭。

回复【105楼】jeep
//由于部分显示数据在sd卡中,所以sd卡需要存入一个特别的文件//
那个是特别文件还不明白你的意思
-----------------------------------------------------------------------

回复【108楼】wsm80828
很想知道这个
//由于部分显示数据在sd卡中,所以sd卡需要存入一个特别的文件//  

-----------------------------------------------------------------------

是一个存放歌曲名称、歌手名称、歌曲首址和长度的文件,2051只有2K ROM,不可能把整个中文字库存进ROM内,只能存在SD卡中,以图片形式存贮,需要时读入。哪位能用VB或VC做一个转换工具自动生成一个playlist.dat就方便了。

U32 GetRootDir(void)
{

        U32 root, fat1, fat2;
        MMCRdBolckOne(0x00,Buffer);      // 读取SD卡中的数据
       
        fat1 = ((Buffer[0x0f]<<8) | Buffer[0x0e]) * 512;
    fat2 = ((Buffer[0x25]<<8) | Buffer[0x24]) * 512 + fat1;
        root = ((Buffer[0x25]<<8) | Buffer[0x24]) * 512 + fat2;
       
        return root;
}
这里是使用512的缓存,可以修改成楼主的64缓存,只要读取出第 0x0e,0x0f, 0x25,0x24地址的数据就可以计算了

用于查找根目录的函数,那样就可以查找歌曲了
根目录 每32个数据表示一个文件,分辨出WAV格式的文件 找到相应的簇地址,然后进行播放

马老师你好,对于你所提及的问题,在我转换过的声音文件中也有同样体现。究其原因,主要是8BIT取样深度不够,声音电平在接近零点时,由于随机噪声的影响,导致取样值在0x80,0x79,0x81这间变化,以产生噪声。我认为这种噪声一直都存在,只是其它声音较大时掩蔽了而矣。
    解决方法,可参考类似杜比动态降噪技术,作这样的处理:检测当前声音幅度,如果在持续的一段时间内(比如0.2秒)声音幅度小于一定值(比如0x80 正负1),那么都过滤为0x80,即可解决此问题。
    我记得有些音频处理软件可以进行这种变换,我回去找找。即使没有,编个小程序转一下也不难。

听过【149楼】所提供的8BIT声音样本,发现其噪声很大,估计所用的商业软件在转换算法上有问题。我用WINDOWS XP附件自带的录音机,打开原始16BIT声音文件,然后另存为44KHz 8BIT,效果也比【149楼】的好得多。

回复【151楼】cowboy
听过【149楼】所提供的8bit声音样本,发现其噪声很大,估计所用的商业软件在转换算法上有问题。我用windows xp附件自带的录音机,打开原始16bit声音文件,然后另存为44khz 8bit,效果也比【149楼】的好得多。
-----------------------------------------------------------------------

谢谢!

我使用过几个商业软件,如上图中的AUDITION、天天静听等,都是如此。自己写了一个转换程序,就是直接简单的采用除256的方法,直接把16位降成8位,这样处理后,静音部分可以完全转换为静音,而且总的噪声比这些商业软件小了许多(-3db左右),但是还有,还是可以比较清晰的听到,尤其是当调节音量输出比较大的时候。

我会试一下WINDOWNS的附件,听听效果。

现在手头的项目,需要语音提示。考虑到存储容量,使用8K、8位的WAV数据,应该可以达到电话的语音质量,对于一般应用够了。其它都可以,成本也不高,就是转换数据本声的噪声。想找一种简单的处理办法。

后面我还会继续提供一些我使用过的处理办法。

另外是否其它的朋友有这方面的经验,软件或算法,只要提供一个思路就可以了,先表示感谢。

/**********************************************************************************************/

点击此处下载 ourdev_611680M0SOZB.rar(文件大小:2.32M) (原文件名:goldwave v5.23 汉化版.rar)
用这个转换成8Bit单声道PCM文件,效果很好。16Bit转8Bit不会改变采样频率,故需先转成32KHz的其它格式再转成PCM

回复【154楼】machao
回复【151楼】cowboy  
听过【149楼】所提供的8bit声音样本,发现其噪声很大,估计所用的商业软件在转换算法上有问题。我用windows xp附件自带的录音机,打开原始16bit声音文件,然后另存为44khz 8bit,效果也比【149楼】的好得多。
-----------------------------------------------------------------------
谢谢!
我使用过几个商业软件,如上图中的audition、天天静听等,都是如此。自己写了一个转换程序,就是直接简单的采用除256的方法,直接把16位降成8位,这样处理后,静音部分可以完全转换为静音,而且总的噪声比这些商业软件小了许多(-3db左右),但是还有,还是可以比较清晰的听到,尤其是当调节音量输出比较大的时候。
我会试一下windowns的附件,听听效果。
现在手头的项目,......
-----------------------------------------------------------------------

马老师我提个建议吧。我做过类似的项目,是用PWM直接接一功放驱动喇叭。没有加低通滤波,当使用20K以下的采样频率WAV文件时有啸声,使用20K以上时人就听不到了。这里应该是由PWM的高低电平跳动引起的,使用DA应该没有类似问题。
所以我就没有采用8K的采样,而是使用24K采样,8:2的ADPCM编码方式。对比下文件大小:8KHz 8Bit的PCM格式64Kbps, 24KHz 8Bit ADPCM为 24*8/4 = 48Kbps,只有8K的3/4大小。而音质上压缩的肯定比降低采样频率更好一些。
这里也有一个小问题,我使用这个软件编8:4 ADPCM再解码时有很大噪音,这里是因为其码表可能与我用的不同。网上8bit的ADPCM基本上没有,我是将16位的改成8位的,所以码表是可能不同。我的解决办法是自己编码再自己解码,8:2也能达到较好的效果,听歌尚可,语音更不用说,用DA的话效果肯定更好,而加滤波的话有些音色会变。

播放BUF和读取BUF我是分开的,开辟了两个数据区A,B,这样就不用读一个播放一个。播放完BUF_A再播放BUF_B,同时BUF_A从FLASH中读取相应数据,依此循环。

回复【159楼】amazing030

谢谢您了,我会用你建议的goldwave v5.23.rar试一下的。

压缩编码的方式我知道,做过图象的压缩,JPEG,H26x等。在这个项目上,不想使用这么复杂的东西。我设计是提供一个DS卡,和规定的文件名,然后给的PC程序给用户。用户自己需要什么语音自己在PC上做,然后转换成8位的,考入SD卡就可以了。

另外,系统使用8位MCU,时钟也就10M左右,还要做其它的事情,ADPCM解码,还是不做的好。

另外,你的解释是不对的。我提供的两个文件与系统播放无关,就是在PC上转换,然后在PC上播放,采样率为44.1k。仅用PC转换,在PC播放,沙沙的噪声非常明显。与什么PWM没有关系。

解决数字音频信号传输中劣化方式(http://www.av010.com/jswz_html/jswz2280_1.html)

专业的解释,看来解决比较困难了。

/**********************************************************************************************/

回复【80楼】cowboy
-----------------------------------------------------------------------
小弟不才,
请问楼主
void timer0 (void) interrupt 1 using 1
{
    if (TL0 & 1) _nop_(); //消除中断响应时间不一致,造成的频率抖动
    P1  = pbuf[++p & 63]; //输出一个声音数据
}

这其中的" & 63"的作用是什么呢?

回复【167楼】hongfadg
-----------------------------------------------------------------------

p & 63 是取8位“p”中的低6位,舍弃高2位,由于缓冲区只有64字节,“++p & 63”正好循环指向pbuf[0]至pbuf[63]。

/**********************************************************************************************/

播放器升级预告,增加FAT32文件系统,也就是可以随意增减音乐文件,不必按连续的储存空间存放文件,允许有文件碎片,同时也不再需要在SD卡内存放一个经特殊制作系统文件。
硬件没改变,只是软件升级,虽然增加了FAT32部分代码,但总代码量仍在2K以内,89C2051能装得下。
测试基本通过,整理好后再上传。

上传升级版的整套工程文件,以及SD内的示范文件。

点击此处下载 ourdev_615881TB11F5.rar(文件大小:429K) (原文件名:SD_player.rar)

由于水平有限,程序可能还有很多不完善的地方,希望大家多提意见。特别是SD卡驱动和FAT32文件系统,本人理解并不深入,程序对各种SD卡的兼容性未作详细测试,有可能出现某些SD卡不能播放的情况。对SD卡的基本要求是 文件系统为FAT32格式,暂不支持FAT16;SDHC高速卡也不支持。

/**********************************************************************************************/

转载于:https://www.cnblogs.com/LittleTiger/p/3976672.html

相关文章:

不带头节点的链表有哪些缺点_23张图!万字详解「链表」,从小白到大佬!

链表和数组是数据类型中两个重要又常用的基础数据类型。数组是连续存储在内存中的数据结构&#xff0c;因此它的优势是可以通过下标迅速的找到元素的位置&#xff0c;而它的缺点则是在插入和删除元素时会导致大量元素的被迫移动&#xff0c;为了解决和平衡此问题于是就有了链表…

劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(一)

《写给大家看的C语言书(第2版)》是邮电社图灵公司引进翻译的一本C语言入门书&#xff0c;这是一本垃圾书。搞不清图灵为什么引进了这样一本垃圾书。该书作者基本不懂得C编程技术&#xff0c;书中误导、错谬比比皆是。  该书的附录B给出了一个21点游戏的代码&#xff0c;这是一…

【数学 技巧】2.14计数

有趣的组合数学题&#xff1b;考试时候打满确实挺不容易的…… 题目描述 对于一个 $n$ 阶排列 $p$&#xff0c;我们建立一张无向简单图 $G(p)$&#xff0c;有 $n$ 个节点&#xff0c;标号从 $1$ 到 $n$&#xff0c;每个点向左右两侧最近的比它大的点以及比它小的点连边。 形式化…

冒泡排序 算法

算法思路&#xff1a; 从第一个元素开始遍历&#xff0c;比较当前元素和下一个元素的大小不符合&#xff0c;则交换结束最后一个元素&#xff0c;则重新遍历 实现&#xff1a; void bubble_sort(vector<int> &arr) {for (int i 0;i < arr.size() - 1; i) {//交…

5单个编译总会编译全部_5分钟读懂JavaScript预编译

大家都知道JavaScript是解释型语言&#xff0c;既然是解释型语言&#xff0c;就是编译一行&#xff0c;执行一行&#xff0c;那又何来预编译一说呢?脚本执行js引擎都做了什么呢&#xff1f;今天我们就来看看吧。1-JavaScript运行三部曲语法分析预编译解释执行语法分析很简单&a…

2014百度面试题目---“求比指定整数大且最小的不重复数”解答

题目&#xff1a;给定任意一个正整数&#xff0c;求比这个数大且最小的“不重复数”&#xff0c;“不重复数”的含义是相邻两位不相同&#xff0c;例如1101是重复数&#xff0c;而1201是不重复数。 代码&#xff1a; #include <iostream> using namespace std;bool istha…

3DMAX 批量 场景 对象 导出 .X格式 脚本

一、首先你需要下载一个 Total Commader文件管理软件。利用这个软件你可以收集文件夹下包含子文件夹下的max文件&#xff08;或完整路径&#xff09;打开TotalCMD后使用查找文件&#xff1a;&#xff08;如图红框中的操作&#xff09;1.2.3. 复制文件名和完整路径后粘贴到文本文…

C++复数类面向对象的参考

#include <bits/stdc.h> #include <future> #include <thread>using namespace std;class Complex { public:Complex (double r 0, double i 0): re (r), im (i) {} ///冒号后面是初始化的过程&#xff0c;注意分清初始化和赋值的区别Comp…

快速排序 算法

算法思路 序列终任意选择一个数&#xff0c;把序列分为比这个数大和比这个数小的两个子序列不断重复以上步骤(递归) 代码实现 int partition1(vector<int> &arr, int begin , int end) {int ret arr[begin];int index begin 1;for (int i index;i < end; i…

利用Spring AOP与JAVA注解为系统增加日志功能

Spring AOP一直是Spring的一个比较有特色的功能&#xff0c;利用它可以在现有的代码的任何地方&#xff0c;嵌入我们所想的逻辑功能&#xff0c;并且不需要改变我们现有的代码结构。 鉴于此&#xff0c;现在的系统已经完成了所有的功能的开发&#xff0c;我们需要把系统的操作日…

maven引入hadoop_如何添加Hadoop依赖通过Maven

匿名用户1级2017-09-09 回答Hadoop开发中需要用到至少不下10个的依赖包&#xff0c;它们相互间的依赖关系比较复杂&#xff0c;不同版本的依赖关系也有所不同&#xff0c;而间接依赖导致的程序错误并不会在运行之前报错&#xff0c;因此确定适合一个版本的依赖包&#xff0c;会…

js 闭包作用

2019独角兽企业重金招聘Python工程师标准>>> 一、变量的作用域 要理解闭包&#xff0c;首先必须理解Javascript特殊的变量作用域。 变量的作用域无非就是两种&#xff1a;全局变量和局部变量。 Javascript语言的特殊之处&#xff0c;就在于函数内部可以直接读取全…

vue实用组件——页面公共头部

可伸缩自适应的页面头部&#xff0c;屏幕适应范围更广泛 效果如下&#xff1a; 代码如下&#xff1a; <template> <div class"site-header"> <div class"logo"><img src"/assets/icons/logo.png" alt"">&…

插入排序 算法

算法思路 维护一段有序数列同时遍历待排序数列&#xff0c;在有序数列中找到合适的位置插入元素 基本代码 实现如下: void insertion(vector<int>& arr){for(int i1;i<arr.size();i){int tempi;for(int ji-1;j>0;j--){//有序序列不断得增加if(arr[temp]<…

线段树入门【转】

文章来自 &#xff1a; http://blog.csdn.net/x314542916/article/details/7837276 学习算法&#xff0c;自己收藏着。 线段树的入门级 总结 线段树是一种二叉搜索树&#xff0c;与区间树相似&#xff0c;它将一个区间划分成一些单元区间&#xff0c;每个单元区间对应线段树中的…

python自动化框架pytest pdf_pytest+python下的UI自动化基础框架

整体设计模式&#xff1a;config目录&#xff1a;存放一些公共的静态文件&#xff0c;如项目名称&#xff0c;配置文件等这些环境变量(可以用其他组件替换&#xff0c;如sql&#xff0c;主要能把配置文件的内容被程序识别)。httptrquest目录&#xff1a;存放接口代码&#xff0…

ny520 最大素因子 筛选法求素数

最大素因子时间限制&#xff1a;1000 ms | 内存限制&#xff1a;65535 KB难度&#xff1a;2 描述 GreyAnts最近正在学习数论中的素数&#xff0c;但是现在他遇到了一个难题&#xff1a;给定一个整数n,要求我们求出n的最大素因子的序数,例如&#xff1a;2的序数是1,3的序数是2…

JAVA_SE之内部类

内部类分类&#xff1a; 1. 成员内部类 静态内部类 非静态内部类 2. 局部内部类 3. 匿名内部类 1. 成员内部类&#xff1a; package com.atguigu.java; /** 类的第5个成员&#xff1a;内部类* 1.相当于说&#xff0c;我们可以在类的内部再定义类。外面的类&#xff1a;外部类。…

希尔排序 算法

算法思路 插入排序的改进版&#xff0c;选择插入距离远的元素选择一个间距&#xff0c;将序列分成很多子序列并行插入排序降低间距&#xff0c;并重复插入元素&#xff0c;直到间距将为1&#xff0c;完成排序。 算法实现 void shell_sort(vector<int> &arr, int b…

解决Apache CXF 不支持传递java.sql.Timestamp和java.util.HashMap类型问题

在项目中使用Apache开源的Services Framework CXF来发布WebService&#xff0c;CXF能够很简洁与Spring Framework 集成在一起&#xff0c;在发布WebService的过程中&#xff0c;发布的接口的入参有些类型支持不是很好&#xff0c;比如Timestamp和Map。这个时候我们就需要编写一…

python教学上机实验报告怎么写_Python基础(下)

不要忘了冒号啊&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;对于基本数据类型的变量&#xff0c;变量传递给函数后&#xff0c;函数会在内存中复制一个新的变量&#xff0c;从而不影响原来的变量。(我们称此为值传递)但是对于表来说&#xff0c;表传递给函数…

比较有用的样式

背景图水平垂直居中 background:#ebebeb url(/Images/BlogHTImg/bkht_jia.jpg) center center no-repeat; 背景图居左垂直居中 background:#ebebeb url(/Images/BlogHTImg/bkht_jia.jpg) left center no-repeat; background:#ebebeb url(/Images/BlogHTImg/bkht_jia.jpg) 5px…

Python:线程之定位与销毁

背景 开工前我就觉得有什么不太对劲&#xff0c;感觉要背锅。这可不&#xff0c;上班第三天就捅锅了。 我们有个了不起的后台程序&#xff0c;可以动态加载模块&#xff0c;并以线程方式运行&#xff0c;通过这种形式实现插件的功能。而模块更新时候&#xff0c;后台程序自身不…

选择排序 算法

算法思路 维护一段有序数列&#xff0c;同时遍历待排序数列&#xff0c;找到最小的元素插入有序数列中重复&#xff0c;直到待排序数列没有剩余元素 代码实现 void select_sort(vector<int> &arr) {for (int i 0;i < arr.size(); i) {int temp arr[i];int in…

hdu2236 无题II 最大匹配 + 二分搜索

中文题目&#xff0c;题意大家都明白。 看到“不同的行和列”就觉得要用二分匹配来做。要求最大值与最小值的差值最小&#xff0c;是通过枚举边的下限和上限来完成。 枚举过程是这样的&#xff0c;在输入的过程可以记录下边权的最大值MAX和最小值MIN。那么他们的边权的差值的最…

python十大标准_python对标准类型的分类

python的标准类型可以按照三种方式分类。一、按存储模型分类按存储模型分可以分为原子(标量)类型和容器类型。原子(标量)类型指对象(这里的对象不是对象数据类型&#xff0c;而是任何可能的值)的值只能含有一种数据类型&#xff0c;比如数值和字符串。容器类型指它们的值可以含…

mysql慢查询开启及分析方法

最近服务维护的公司的DB服务器&#xff0c;总是会出现问题&#xff0c;感觉需要优化一下了&#xff0c;登陆上去&#xff0c;发现慢查询日志都没有开&#xff0c;真是惭愧&#xff0c; 故果断加上慢查询日志&#xff0c;经过分析sql记录&#xff0c;发现问题很多&#xff0c;开…

如何在调试页面的时候清除页面的缓存?

1.按F12,弹出下图 2.点击右上角的三个点: 3.点击settings 4.找到Network,下面的Disable cache(while DevTools is open) 转载于:https://www.cnblogs.com/studybrother/p/10396990.html

JAVA图片处理--缩放,切割,类型转换

import java.io.*; import java.awt.*; import java.awt.image.*; import java.awt.Graphics; import java.awt.color.ColorSpace; import javax.imageio.ImageIO;public class ChangeImageSize {/** *//*** 缩放图像* param srcImageFile 源图像文件地址* param result …

文本框自动提示_Excel办公小技巧,使用艺术字与文本框,就是那么的简单

Excel中的艺术字同时拥有文字和图形两种对象的属性&#xff0c;不仅可以修改其中的内容&#xff0c;还可以调整形状的大小、设置边框以及内部填充等效果&#xff0c;常在编辑表格标题或者输入一些比较有提示性的文本时使用&#xff0c;在突出关键内容的同时美化表格效果添加艺术…