小玩意,单片AT89C2051 + SD卡 + 3310LCD = 音乐播放器
这个小玩意,采用 ATMEL 的传统51MCU作主控制芯片,加上SD卡和显示屏,就可以作简单的音乐播放器了,虽然音质不怎么样,不过作为DIY还是蛮有乐趣,希望大家喜欢。没有采用FAT文件系统,只是按扇区读取SD卡,由于2051资源有限,改为4051有望可以操作FAT,但目前程序还在不断完善中。
128byte怎样读取512byte的扇区数据?可以采用边读边播放的方式,就能解决。音乐文件是32KHz取样率的WAV文件,所以和HIFI就沾不上边了。
程序是用C来编写,以方便交流,资料整理中,完善后再上传。
http://cache.amobbs.com/bbs_upload782111/files_35/ourdev_609975S2WX0Z.jpg
(原文件名:0001.jpg)
http://cache.amobbs.com/bbs_upload782111/files_35/ourdev_609976VRQG71.jpg
(原文件名:0002.jpg)
http://cache.amobbs.com/bbs_upload782111/files_35/ourdev_609977KCHVWV.jpg
(原文件名:0003.jpg)
http://cache.amobbs.com/bbs_upload782111/files_35/ourdev_609978IFDBHI.jpg
(原文件名:0004.jpg)
http://cache.amobbs.com/bbs_upload782111/files_35/ourdev_609979TL1D5P.PNG
(原文件名:SD_player.PNG)
下面是测试用的HEX文件,由于部分显示数据在SD卡中,所以SD卡需要存入一个特别的文件,然后把格式为32KHZ,8bit,单声的WAV文件存入即可。
点击此处下载 ourdev_609987T97WV4.rar(文件大小:2K) (原文件名:sdcard.rar)
还是有就视频文件,顺便听听音质。
点击此处下载 ourdev_609986DT65P1.avi视频文件大小:5.82M) (原文件名:SD_player.wmv.avi)
程序已更新于80楼 牛人。 厉害 顶一下 mark mark ding! 我还以为是MP3......原来是WAV 马克 make 终于逮着楼主了,我想问下,你那个2个IO驱动1602的例子,我改动到了ATtiny13上,GCC编译,但是用Protues无论如何也没有显示,用示波器观察电容充放电发现τ值过小,特求助于你,望能解答,谢谢 新年强帖,膜拜 真是狠,卵上都能雕出花来,不错.做个记号 不愧是牛仔 总有新的东东 回复【9楼】renpeng009 大鹏集成
终于逮着楼主了,我想问下,你那个2个io驱动1602的例子,我改动到了attiny13上,gcc编译,但是用protues无论如何也没有显示,用示波器观察电容充放电发现τ值过小,特求助于你,望能解答,谢谢
-----------------------------------------------------------------------
那个两个口驱动LCD1602的电路
我在PIC16F917上用三口做个
(只是在PROTEUS上做的)
没有问题
估计是你的延时有问题
个人感觉延时要把握好
刚开始我也是现实不出来
不过我从新计算延时后
就显示出来了
http://cache.amobbs.com/bbs_upload782111/files_35/ourdev_610016LLHYLM.png
(原文件名:PIC16F917-I/O驱动LCD1602.png) 楼主又用2051做出了意想不到的作品,牛!很期待开源 佩服! mark mk 牛人再现!!!!!!!!!!!!!!!!!!! 哇...牛
我想问下,用个快一点的mcu能不能解码mp3呢?比如stc的1t系列加上32M的晶振? Cowboy出品,必属精品。mark一下 ding 抬头看精品 这是今天我见过的最好贴,思路新奇!
这么一个没什么资源的IC能作出MY3,并没用DAC,真的了不起! 开源,期待 牛人,佩服啊 我用C8051F330也做过一个,带FAT32文件系统,裁剪的文件系统,SD卡中放入WAV文件就可以按83的文件名播放。512的XDATA我就用来读SD卡,也只能读一个播放一个,要是有1024就好了,一个读SD卡,一个作播放缓冲,两个缓冲轮着换。
20K采样以上的WAV不用低通滤波也能用PWM播放出音质不错的歌曲,我试过44.1KHz,8bit采样,音质很好,但20K以下的就有很大的电流音,应该是PWM的高低电平变化引起的,20K以上人耳听不到 挺不错的 回复【楼主位】cowboy
-----------------------------------------------------------------------
我也有一块3310,做的真不错,期待上传源程序! 等待zl的程序ing.. cowboy历来就是小成本大制作的成功大导演!!!!!!!!! 连洞洞板都省了果然是牛! 回复【31楼】freedestiny 小梁
连洞洞板都省了果然是牛!
----------------------------------------------------------------------- 太厉害了... 厉害啊 mark 高手 强 mark 牛人 太牛了。顶一个 牛… 这电池像是DV上用的 真简洁,强! 牛人 楼主能说一下怎么边读边播嘛?期待楼主开源。 超级牛人, 顶一个 牛人,膜拜中 厉害,学习。。。 总是搞那么嗨的东西 - -
顶! 精品 牛气!Mark! 牛,怎么做到的? 强烈要求楼主开源啊!!顶 刷新了上百遍还是不见ZL开源... 记号~~ 在单片机脚上焊这么多贴片电阻 也是水平啊 而且焊点还不错啊 回复【19楼】shenrongze寻寻觅觅
哇...牛
我想问下,用个快一点的mcu能不能解码mp3呢?比如的1t系列加上32m的晶振?
-----------------------------------------------------------------------
某人说 STM32硬解MP3都还要看它的程序效率。。。。 这个也快置顶了,哈哈,那个vga还没弄懂呢 牛仔 语出必惊人啊。 顶啊,厉害,真想知道是怎么实现的。 Cowboy出品,厉害 回复【57楼】elecfun 熊
回复【19楼】shenrongze寻寻觅觅
哇...牛
我想问下,用个快一点的mcu能不能解码mp3呢?比如的1t系列加上32m的晶振?
-----------------------------------------------------------------------
某人说 stm32硬解mp3都还要看它的程序效率。。。。
-----------------------------------------------------------------------
软解吧?硬解不是直接操控vs1053之类的解码芯片就好了吗? 强啊,期待! 回复【62楼】shenrongze 寻寻觅觅
回复【57楼】elecfun 熊
回复【19楼】shenrongze寻寻觅觅
哇...牛
我想问下,用个快一点的mcu能不能解码mp3呢?比如的1t系列加上32m的晶振?
-----------------------------------------------------------------------
某人说 stm32硬解mp3都还要看它的程序效率。。。。
-----------------------------------------------------------------------
软解吧?硬解不是直接操控vs1053之类的解码芯片就好了吗?
-----------------------------------------------------------------------
敢问各位大侠,何谓“硬解”何谓“软解”?请赐教 mark! 靠,牛人一个,特别是看了楼主那几个手指头输入1602的控制指令显示内容的视频,一绝吧!! 牛人再次出現 mark 回复【64楼】believe0815
敢问各位大侠,何谓“硬解”何谓“软解”?请赐教
-----------------------------------------------------------------------
硬解,用专用的硬件解码,再通过mcu控制它
软件,直接用mcu写程序解 做个记号,上班后研究一下 lz非常牛,一定境界了,能去钻研小东西,把小东西做到绝了,顶~~~~ 乖乖 牛 精品 真不错
佩服
膜拜下
学习去 牛!期待中! 偶滴神啊! 昨天外出,没有回贴,看来大家对这个也蛮有兴趣。 80楼在哪.. 这是未经整理的程序,有点乱,凑合着看,有时间再进一步改进。
SD部分是修改于本坛的一个贴子
----------------------------------------------------------
添加部分注释,提高可读性
#include <reg51.h>
#include <INTRINS.H>
#include <MATH.H>
#include "LCD_3310.H"
#define uchar unsigned char
#define uintunsigned 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; //数据缓冲区
uchar p; //播放缓冲区指针
uchar px; //频谱显示的X坐标
code ulong Track =
{ //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_ERROR0X03
#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 CLK
RI = 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={0x40,0x00,0x00,0x00,0x00,0x95};
CS = 1;
for(i = 0; i < 0x0f; i++) //复位时,至少要72个时钟周期,
{ //现在是,15*8=120个clk
spi_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个clk
return 0; //返回0,写入成功
}
/************ SD卡初始化 ************/
uchar sd_init(void)
{
uchar time, temp;
uchar pcmd = {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;
uint j = 0;
pcmd = 0x51;
pcmd = *ad;
pcmd = *(++ad);
pcmd = *(++ad);
pcmd = 0;
pcmd = 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字节CRC
spi_write(0xff);
CS = 1;
return 0;
}
/******* 读取一扇区的声音数据 *********/
uchar sd_read_sector(uchar data *ad)
{
uchar temp, time, pcmd;
uint j = 0;
pcmd = 0x51;
pcmd = *ad;
pcmd = *(++ad);
pcmd = *(++ad);
pcmd = 0;
pcmd = 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; //随便挑一个数据显示
LCD3310_set_XY(px,5); //设定显示位置
px += 6;
if (px >= 39) px = 0;
if (temp & 0x80) temp ^= 0x80; //求得声音振幅
else temp = 0x80 - temp;
temp = Level; //不同幅度对应不同的谱线图案
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 = SBUF; //为求快速,不用函数调用
RI = 0;_nop_(); pbuf = SBUF; //直接启动串口移入
RI = 0;_nop_(); pbuf = SBUF; //连续读四字节
RI = 0;_nop_(); pbuf = SBUF;
if(j >= 512) break;
while((((uchar)j - p) & 63) > 55); //检测播放进度,
} //如果缓冲区接近溢出,先暂停等待
spi_read();//略过 crc
spi_read();//略过 crc
spi_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; addr < Track;)//播放第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^2
sbit SCLK = P3^4;
sbit D_C= P3^5;
sbit SCE= P3^7;
code unsigned char Font =
{
0x3E, 0x51, 0x49, 0x45, 0x3E ,// 0
0x00, 0x42, 0x7F, 0x40, 0x00 ,// 1
0x42, 0x61, 0x51, 0x49, 0x46 ,// 2
0x21, 0x41, 0x45, 0x4B, 0x31 ,// 3
0x18, 0x14, 0x12, 0x7F, 0x10 ,// 4
0x27, 0x45, 0x45, 0x45, 0x39 ,// 5
0x3C, 0x4A, 0x49, 0x49, 0x30 ,// 6
0x01, 0x71, 0x09, 0x05, 0x03 ,// 7
0x36, 0x49, 0x49, 0x49, 0x36 ,// 8
0x06, 0x49, 0x49, 0x29, 0x1E ,// 9
0x00, 0x00, 0x36, 0x36, 0x00 ,// :
0x7f, 0x3e, 0x1c, 0x08, 0x00 ,// >
0x3e, 0x3e, 0x00, 0x3e, 0x3e ,// ||
0x00, 0x00, 0x00, 0x00, 0x00 ,//" "
};
code unsigned char Level = {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);
LCD3310_write_dat(Font[++n]);
LCD3310_write_dat(Font[++n]);
LCD3310_write_dat(Font[++n]);
LCD3310_write_dat(Font[++n]);
} ... 收藏 果然利害,好好 膜拜!顶一下~ 再增加一个DA输出,能否做成立体声? 用AT89C52芯片 牛人!膜拜! 厉害。 牛人,厉害。 强贴。膜拜 兄台真乃神人!能否讲解一下是怎么实现的呢 果然好作品! 3310显示那些频谱是假的吧?没节奏啊... 又见楼主,牛人啊。。。 mark lz的内存卡貌似悲剧了,以后杂用啊? 靠,你太厉害了。
要是让我设计一个这个东东,会很复杂的。 很厉害,很崇拜!老大为何不直接把工程整个打包传上来呢???? 请问,老大,你这个屏多少钱买的啊??? 厉害!