基于 STC12 单片机的 WAV 播放器
物电学院电信二班
小组成员:张景宝、李镇宇、周方嫒
一、 设计特点
1、 采用 PWM 调制方式,使硬件简单,实现容易。
2、 由于没有专用解码芯片,所以只能播放 WAV 音轨。
3、 通过软件自动适应音乐采样率。
4、 设计电池接口,可便携。
5、 声音大小可通过硬件调整。
二、 基本原理
将 PCM 文件结合采样率调制到 PWM,采取适当滤波,驱动耳机即可还
原音乐文件。
三、 硬件设计
1、 电源部分
2、 SD 卡部分
3、 音频接口部分
4、 CPU 部分
四、 软件设计
1、 编译平台:KEIL V9.0
2、 使用资源情况
9 级优化
3、 SD 卡低层驱动
/*************************************************************
- 功能描述:向 SD 卡写命令
- 隶属模块:SD 卡模块
- 函数属性:内部
- 参数说明:SD 卡的命令是 6 个字节,pcmd 是指向命令字节序列的指针
- 返 回 说 明 : 命 令 写 入 后 , SD 卡 的 回 应 值 , 调 用 不 成 功 , 将 返 回 0xff
*************************************************************/
u8 SD_SendCommand(u8 cmd, u32 arg, u8 crc)
{
unsigned char r1;
unsigned char Retry = 0;
// SPI_ReadWriteByte(0xff);
//片选端置低,选中 SD 卡
SD_CS=0; //打开片选
//发送
SPI_ReadWriteByte(cmd | 0x40); //分别写入命令
SPI_ReadWriteByte(arg >> 24);
SPI_ReadWriteByte(arg >> 16);
SPI_ReadWriteByte(arg >> 8);
SPI_ReadWriteByte(arg);
SPI_ReadWriteByte(crc);
//等待响应,或超时退出
while((r1 = SPI_ReadWriteByte(0xFF))==0xFF)
{
Retry++;
if(Retry > 200)
{
break;
}
}
//关闭片选
//在总线上额外增加 8 个时钟,让 SD 卡完成剩下的工作
SPI_ReadWriteByte(0xFF);
SD_CS=1; //打开片选
//返回状态值
return r1;
}
/*************************************************************
- 功能描述:初始化 SD 卡,使用 CMD1
- 隶属模块:SD 卡模块
- 函数属性:内部
- 参数说明:无
- 返回说明:调用成功,返回 0x00,否则返回 INIT_CMD1_ERROR (sd.h 中有定义)
*************************************************************/
u8 SD_Init()
{
unsigned char time,temp,i;
SPI_Init();
SPI_SetSpeed(3); //设置到低速模式
SD_CS=1; //打开片选
for(i=0;i<0x0A;i++) //初始时,首先要发送最少 74 个时钟信号,这是必须的!!!
{
SPI_ReadWriteByte(0xff); //120 个时钟
}
SD_CS=0; //打开片选
time=0;
do
{
temp=SD_SendCommand(CMD0, 0 ,0x95);//写入 CMD0 复位 SD 卡
time++;
if(time==200)
{
SD_CS=1; //打开片选
}
}while(temp!=0x01);
time=0;
do
{
temp=SD_SendCommand(CMD1, 0 , 0xff); //写入 CMD1 激活 SD 卡
time++;
if(time==200)
{
SD_CS=1; //打开片选
}
}while(temp!=0);
SPI_SetSpeed(0);
temp = SD_SendCommand(CMD59, 0, 0x01);
if(temp != 0x00)
{
return temp; //命令错误,返回 r1
}
temp=SD_SendCommand(CMD16,512,0xff);
if(temp!=0x00)
{
return temp ;
//命令错误,返回 r1
}
SD_CS=1; //打开片选
SPI_ReadWriteByte(0xff); //按照 SD 卡的操作时序在这里补 8 个时钟
return 0;
}
/*----------------------------------------------------------*/
/* Initialize Disk Drive
/*----------------------------------------------------------*/
DSTATUS disk_initialize (void)
{
DSTATUS stat;
stat = SD_Init();
return stat;
}
/*----------------------------------------------------------*/
/* Read Partial Sector
/*----------------------------------------------------------*/
DRESULT disk_readp (
BYTE* buff,
DWORD sector,
WORD offset,
WORD count
/* Pointer to the destination object */
/* Sector number (LBA) */
/* Offset in the sector */
/* Byte count (bit15:destination) */
)
{
*/
*/
DRESULT res;
uchar response=0xff;
uint byteLeft,retry=0;
byteLeft=512-offset-count;
//接收数据后,跳过的字节数
if((offset+count)>512)
return RES_PARERR; // 读 取 字 节 超 出 扇
区
if(SD_SendCommand(CMD17, sector<<9,0))
return RES_ERROR; //读命令发送失败
SD_CS_ASSERT;
do
{
//等待起始数据块起始标志 0xfe (0x11111110)
response=SPI_ReadWriteByte(0xff);
if(retry++ > 0xfffe) //超时错误
{
SD_CS_DEASSERT;
return RES_ERROR;
}
}while(response != 0xfe);
//等待回应
//跳过 offset(偏移字节数)个数据
if (offset)
{
do
{
SPI_ReadWriteByte(0xff);
}while (--offset);
}
if(buff)
{
do
{
//接收到的数据块数据存入缓冲区
*buff++=SPI_ReadWriteByte(0xff);
}while (--count);
}
else
{
}
//dummy
if (byteLeft)
{
//跳过 byteLeft 个数据
do
{
SPI_ReadWriteByte(0xff);
}while (--byteLeft);
}
SPI_ReadWriteByte(0xff);
SPI_ReadWriteByte(0xff);
res= RES_OK;
SD_CS_DEASSERT;
SPI_ReadWriteByte(0xff);
return res;
}
4、 数据缓冲区设计
BYTE xdata Cache0[SIZE];
BYTE xdata Cache1[SIZE];
LPBYTE data Cache;
LPBYTE data AudioCache = Cache0;
//忽略 CRC 校验(两个字节)
//等待 8 个 clk
if(Cache == Cache0) Cache = Cache1; else Cache = Cache0;
5、 音乐还原过程
void Timer0(void) interrupt 1
{
CCAP0L = CCAP0H = AudioCache[AudioPos];
AudioPos++;
if(AudioPos>=SIZE)
{
play_f=1;
AudioPos = 0;
if(AudioCache == Cache0) AudioCache = Cache1;
else AudioCache = Cache0;
}
}
6、 WAV 文件格式与自适应采样率
static
DWORD load_header (void)
{
/* 0:Invalid format, 1:I/O error, >=1024:Number of samples */
DWORD sz, f;
BYTE b, al = 0;
if (pf_read(Cache, 12, &rb)) return 1;/* Load file header (12 bytes) */
if (rb != 12 || LD_DWORD(Cache+8) != FCC('W','A','V','E')) return 0;
for (;;) {
pf_read(Cache, 8, &rb);
if (rb != 8) return 0;
sz = LD_DWORD(&Cache[4]);
/* Get Chunk ID and size */
/* Chunk size */
/* FCC */
switch (LD_DWORD(&Cache[0])) {
/* 'fmt ' chunk */
case FCC('f','m','t',' ') :
if (sz & 1) sz++;
/* Align chunk size */
if (sz > 100 || sz < 16) return 0; /* Check chunk size */
pf_read(Cache, sz, &rb);
if (rb != sz) return 0;
if (Cache[0] != 1) return 0;/* Check coding type (LPCM) */
b = Cache[2];
if (b != 1 && b != 2) return 0;
b = Cache[14];
if (b != 8 && b != 16) return 0;
/* Check channels (1/2) */
/* Get content */
/* Check resolution (8/16 bit) */
if (b & 16) al <<= 1;
f = LD_DWORD(&Cache[4]);/* Check sampling freqency (8k-48k) */
if (f < 8000 || f > 8000) return 4;
TH0 = -(FOSC / 12 / f);
break;
case FCC('d','a','t','a') :
/* 'data' chunk */
if (!al) return 0; /* Check if format valid */
if (sz < 1024 || (sz & (al - 1))) return 0;/* Check size */
if (Fs.fptr & (al - 1)) return 0;
/* Check offset */
return sz;
/* Start to play */
case FCC('D','I','S','P') :
/* 'DISP' chunk */
SendString("disp\n");
case FCC('L','I','S','T') :
case FCC('f','a','c','t') :
if (sz & 1) sz++;
/* 'LIST' chunk */
/* 'fact' chunk */
/* Align chunk size */
pf_lseek(Fs.fptr + sz);
break;
/* Skip this chunk */
default :
/* Unknown chunk */