|
正如马老师所说的,WAV播放器的电路是简单的,但其中涉及到的读取SD卡数据的部分却是很复杂的,花了一周时间研究了 “实用公交车语音报站器-WAVE播放器”程序,尤其对其中移植的 那个日本开发者的Petit FatFs - FAT file system module作了一番研究,在网上搜集了很多FAT方面的资料以及SD卡相关物理层规范,在研读马老师程序的时候一一作了相关备注,现将备注后的程序贴出来,供朋友们学习,有不当之处请不吝指教。
主要备注了main.c和mmc.c这两部分。我是在马老师的备注的基础上备注的,注意打开原程序看备注的相关内容即可知道我备注的是哪些内容。
main.c部分:
/*****************************************************
This program was produced by the CodeWizardAVR V2.04.4a Advanced
http://www.hpinfotech.com
Chip type : ATmega16
Program type : Application
Clock frequency : 11.2896 MHz
Memory model : Small
External SRAM size : 0
Data Stack size : 256
*****************************************************/
#include "globle.h"
/*---------------------------------------------------------*/
/* Work Area */
/*---------------------------------------------------------*/
#define work_state_no_disk 0
#define work_state_mount_disk 1
#define work_state_scan_disk 2
#define work_state_stand_by 3
#define work_state_play_w 4
#define work_state_play_z 5
#define work_state_play_m 6
#define work_state_opendir 10
#define work_state_play_1 11
#define work_state_play_2 12
#define work_state_err 15
#define Work_err_mount '0'
#define Work_err_opendir '1'
#define Work_err_readdir '2'
#define Work_err_play '3'
volatile BYTE FifoRi, FifoWi, FifoCt; /* FIFO controls */
BYTE Buff[BSIZE]; /* Wave output FIFO */
FATFS Fs; /* File system object */
DIR Dir; /* Directory object */
FILINFO Fno; /* File information */
WORD rb; /* Return value. Put this here to avoid bugs of avr-gcc */
bit led2_blik;
BYTE play_w_name[13];
BYTE play_m_name[13] = {"M.WAV"};
void main(void)
{
UCHAR key_value,led1_ct=0,max_z,k;
UCHAR work_state = work_state_no_disk, pre_key = No_key;
BYTE * name_ptr;
BYTE res,i;
bit play_m_ok,play_ex=FALSE ;
PORTA=0xFF;
DDRA=0xF0; //PA7/PA6输出控制LED;PA0-PA3输入按键口,内部上拉有效
PORTB=0xF1; //
DDRB=0xBE; // PB0/PB6输入,上拉有效;PB4/PB5/PB7输出
PORTD=0x03;
DDRD=0x82; // PD7(PWM口)/PD1输出;PD0输入,上拉有效
#if _DEBUG == 1
// USART initialization
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: On
// USART Transmitter: On
// USART Mode: Asynchronous
// USART Baud Rate: 9600
UCSRA=0x00;
UCSRB=0x18;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x47;
#endif
// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 11.025 kHz
// Mode: CTC top=OCR0
// OC0 output: Disconnected
TCCR0=0x0D; //CTC模式,1024分频率(11289.6/1024=11.025khz) ,OC0 输出脚用作普通I/O脚;
TCNT0=0x00; //赋初值0;
OCR0=0xDC; //fclkio/(N(1+ocr0))=11289.6khz/(1024*(1+220))~=49.886hz; 即对应~=20.045ms中断周期
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 11289.600 kHz
// Mode: CTC top=OCR1A
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: On
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0x09; //分频率为1;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0xFE; //由此算出CTC中断频率为22.13647KHZ;应该在后面使用中动态调整其输出频率;
OCR1BH=0x00;
OCR1BL=0x00;
// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: 11289.600 kHz
// Mode: Fast PWM top=FFh
// OC2 output: Non-Inverted PWM
ASSR=0x00;
TCCR2=0x69;//快速PWM模式,升序匹配时清零OC0;降序匹配时置位OC0;分频率为1;中断频率为44.1khz;
TCNT2=0x00;
OCR2=0x80;//此初值对应的占空比无意义,后面使用时根据需要更改此值;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x12;//T/C1输出比较A匹配中断使能,T/C0输出比较匹配中断使能
// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;
// Global enable interrupts
#asm("sei")
while (1)
{
if(key_scan)
{
key_scan = FALSE;
key_value = key_read();
if(key_value != No_key)
{
play_ex = TRUE;
pre_key = key_value;
}
if (!sd_in_ok)//如果没有插入SD卡
{
if (++led1_ct > 25)//大于(25*20ms=500ms);
{
led1_ct = 0;
LED_1 = !LED_1;//LED1 1秒亮灭1次;
}
led2_blik = FALSE;
LED_2 = 1;//灭LED2
k = 0;//没卡插入的情况下无论是否按键也让计数变量K复位;
pre_key = No_key; //没卡插入的情况下无论是否按键也让键值为 无按键;
work_state = 0; //没卡插入的情况下无论是否按键也让工作状态复位到0;
}
else if(led2_blik)//如果已经插入卡,且需要LED2指示错误状态时;
{
if (++led1_ct > 25)
{
led1_ct = 0;
LED_2 = !LED_2;//LED2 1秒亮灭1次;
}
}
}
switch(work_state)
{
case work_state_no_disk:
if(sd_in_ok) //若已插卡
{
LED_1 = 0; //点亮LED1;
max_z = 0;
k=0;
work_state = work_state_mount_disk;
}
break;
case work_state_mount_disk:
if (pf_mount(&Fs))//加载SD卡卷,若返回值不为0代表出错;
{
work_state = work_state_err;
}
else//加载SD卡卷 后的返回值为0代表没有出错;
{
init_spi_fast();//初始化SPI为5.6448MHZ的主模式;
work_state = work_state_scan_disk;
}
break;
case work_state_scan_disk:
if (pf_opendir(&Dir, "") == FR_OK)//打开根目录若返回0代表 成功;
{
for(;;)
{
res = pf_readdir(&Dir, &Fno);//反复 读刚创打开的文件目录 类 中国的信息(包括其中的子文件夹也包括其中的文件);读到此根目录最后会给Fno.fname[0]赋值0(即NULL);
if (res || !Fno.fname[0]) // 读刚创建的文件目录 类 的信息 出错; 读完此目录下最后一个项目后会让Fno.fname[0]=0从而进入此条件语句;
{
if (max_z) //已经读到有效的文件个数大于1
{
LED_2 = 0; //点亮LED2;
work_state = work_state_stand_by;
}
else
{
work_state = work_state_err;
}
break;
}
else //读刚创建的文件目录 类 的信息 没有出错;
{
if (Fno.fattrib & (AM_DIR|AM_HID) || !strstr(Fno.fname, "Z.WAV"))//若读到的不是目录也不是隐藏性质的文件,且不是文件名为"Z.WAV"的文件(即Z前面没有XX位数字);
{
#if _DEBUG == 1
for (i=0; i<13; i++){putchar(Fno.fname);}
putchar(0x0d); putchar(0x0a);
#endif
}
else //读到的文件正确(即属于 xxM.wav或xxZ.wav或w.wav)
{
max_z++; //对遍历的有效文件个数进行计数;
#if _DEBUG == 1
for (i=0; i<13; i++){putchar(Fno.fname);} //显示读出的根目录下的有效文件 的文件名;
putchar(0x0d); putchar(0x0a);
putchar(0x30+max_z);
putchar(0x0d); putchar(0x0a);
#endif
}
}
}
}
else//打开根目录 失败;
{
work_state = work_state_err;
}
break;
case work_state_stand_by: //此步骤的目的是定位要播放的文件的文件名,将其由字符指针name_ptr指向;
if (play_ex)
{
play_ex = FALSE;
play_m_ok = FALSE;
for(i = 0; i < 13; i++) play_w_name = '';
if(pre_key == play_w)
{
play_w_name[0] = 'W'; play_w_name[1] = '.';
play_w_name[2] = 'W'; play_w_name[3] = 'A'; play_w_name[4] = 'V';
name_ptr = play_w_name;
work_state = work_state_opendir;
}
else if(pre_key == play_next)
{
if(k < max_z - 1) k++;
play_w_name[0] = 0x30+k; play_w_name[1] = 'Z'; play_w_name[2] = '.'; //由这里看播放的站台文件只能是1Z.WAV~9Z.WAVA而不是马朝老师书上说的0Z.WAV~xxZ.WAV范围;
play_w_name[3] = 'W'; play_w_name[4] = 'A'; play_w_name[5] = 'V';
name_ptr = play_w_name;
work_state = work_state_opendir;
}
else if(pre_key == play_back)
{
if (k) k--;
play_w_name[0] = 0x30+k; play_w_name[1] = 'Z'; play_w_name[2] = '.'; //由这里看播放的站台文件只能是0Z.WAV~9Z.WAVA而不是马朝老师书上说的xxZ.WAV范围;
play_w_name[3] = 'W'; play_w_name[4] = 'A'; play_w_name[5] = 'V';
name_ptr = play_w_name;
work_state = work_state_opendir;
}
else if(pre_key == play_m)
{
name_ptr = play_m_name;
play_m_ok = TRUE;
work_state = work_state_opendir;
}
}
break;
case work_state_opendir:
if (pf_opendir(&Dir, "") == FR_OK)//再次打开根目录若返回0代表 成功;
{
work_state = work_state_play_w;
}
else //再次打开根目录若返回非0代表 失败;
{
work_state = work_state_err;
}
break;
case work_state_play_w: //找到要打开的文件(的位置);
res = pf_readdir(&Dir, &Fno);//第2次循环回来重头开始读取根目录里面的内容;
if (res || !Fno.fname[0])//若读取出错或此轮已读完;
{
if (play_m_ok)
work_state = work_state_opendir;
else
{
work_state = work_state_err;
}
}
else //读取成功且没到目录末尾;
{
if (Fno.fattrib & (AM_DIR|AM_HID) || !strstr(Fno.fname, name_ptr)) //读出的内容为隐藏性质或者为子目录,或者刚打开的文件名不是要找的文件名;//extern char *strstr(char *str1, char *str2)函数的含义为:找出str2字符串在str1字符串中第一次出现的位置(不包括str2的串结束符);返回值:返回该位置的指针,如找不到,返回空指针。
{
#if _DEBUG == 1
for (i=0; i<13; i++){putchar(Fno.fname);}
putchar(0x0d); putchar(0x0a);
#endif
}
else //读出的内容不为隐藏性质且不为子目录,且刚打开的文件名正是要找的文件名;
{
#if _DEBUG == 1
for (i = 0; i<13; i++){putchar(Fno.fname);}
putchar(0x0d); putchar(0x0a);
#endif
if (play_m_ok)
{
for(i=0; i<13; i++) play_m_name = Fno.fname;
}
play_state = 0;
work_state = work_state_play_1;
}
}
break;
case work_state_play_1:
if (play(Fno.fname))//进行目标文件的读操作; 动态放入 到前面定义的FIFO中; 不正常的话返回值为1;
{
work_state = work_state_err;
}
else//正常的话返回值为0
{
if (play_ex || play_state == 0) //正常的话不会进入到此条件,因为上面 状态”work_state_stand_by“中就已经让play_ex为0了;
{
work_state = work_state_stand_by;
if (play_m_ok)
{
if (pre_key == play_m || play_state == 0)
{
for(i = 0; i < 13; i++) {play_m_name = '';}
play_m_name[0] = 'M'; play_m_name[1] = '.';
play_m_name[2] = 'W'; play_m_name[3] = 'A'; play_m_name[4] = 'V';
play_ex = FALSE;
work_state = work_state_play_w;
}
}
}
}
break;
case work_state_err:
led2_blik = TRUE;
#if _DEBUG == 1
// putchar(work_err);
// putchar(0x0d); putchar(0x0a);
#endif
break;
}
}
}
mmc.c部分:
/*-----------------------------------------------------------------------*/
/* PFF - Low level disk control module for ATmega16 ECNU CMA 2010 */
/*-----------------------------------------------------------------------*/
#include "globle.h"
// SPI initialization -12M
void init_spi_low(void)
{
SPCR=0x5E; // SPI Type: Master,Clock Rate: 2*187.5kHz ,Clock Phase: Cycle Start //SPI主模式,频率11.2896M/=352.8KHZ; 模式3
SPSR=0x01; // SPI Clock Polarity: High, Data Order: MSB First
}
// SPI initialization -12M
void init_spi_fast(void)
{
SPCR=0x5C; // SPI Type: Master,Clock Rate: 2*3000kHz ,Clock Phase: Cycle Start //SPI主模式,频率11.2896M/2=5.6448MHZ;模式3;
SPSR=0x01; // SPI Clock Polarity: High, Data Order: MSB First
}
BYTE rcv_spi (void)//SPI接收一个字节;
{
SPDR = 0xff;
while(!(SPSR&(1<<SPIF))){};
return SPDR;
}
void xmit_spi (BYTE data)//SPI发送一个字节;
{
SPDR = data;
while(!(SPSR&(1<<SPIF))){};
}
void fmit_spi (void)//SPI发送一个内容为0XFF的字节;
{
SPDR = 0xff;
while(!(SPSR&(1<<SPIF))){};
}
BYTE read_step;
bit read_step_bit;
INT pre_data;
BYTE m_data_0;
void fwd_blk_part( //读出一个扇区中的特定区域中的数据,注意对读出的音频数据进行的声道与量化位数的转换操作也在这里;
void *dest, /* Pointer to the destination object to put data */
WORD ofs, /* Byte offset in the sector (0..511) */
WORD cnt /* Byte count (1..512), b15:destination flag */
)
{
WORD rb;
BYTE* p;
WORD i;
rb = 514 - ofs - cnt; /* 一个扇区中,减掉偏移字节数,减掉要读取的字节数,就剩下要丢弃的字节数(包括两个字节的CRC校验) */
while(ofs --)rcv_spi(); /* 丢弃需要偏移的字节。*/
if(dest == 0) /* 如果指针被赋值为0, 直接处理音频数据 */
{
if (channel_flag == 0x12) // 16bit 2ch
{
while(cnt) /* 接收指定的字节数 */
{
m_data_0 = rcv_spi();
--cnt;
if (read_step == 1) // 左声道高8位 //左声道低8bit抛弃;
{
if(m_data_0 <= 0x80) //为0或正数的INT;
pre_data = m_data_0 + 0x80; //m_data_0本身为无符号字符类变量,对应16BIT的信号本身用的有符号的INT类型表示的,对于0~+127的正的INT数加上0X80(有符号字符类型变量对应的+0值,结果仍旧为改值本身(因符号位不参与运算)
else //为负数的INT;
pre_data = m_data_0 + 0x81;//这里为何要对负的字符类型变量加1个-1,原因不明;
}
else if (read_step == 3) // 右声道高8位 //右声道低8bit抛弃;
{
if (m_data_0 <= 0x80)
m_data_0 = (m_data_0 + 0x80);
else
m_data_0 = (m_data_0 + 0x81);
m_data_0 = (pre_data + m_data_0) >> 1; // (左+右)/2
while(FifoCt == FULL_bsize); /* 如果缓冲区满,则等待 */
Buff[FifoWi] = m_data_0; /* 一旦缓冲区有空位,则接收音频数据填入 */
#asm("cli") /* 修改音频数据的个数之前首先关闭中断 */
FifoCt ++; /* 修改音频数据 */
#asm("sei") /* 修改完成后打开中断 */
++FifoWi;
}
if (++read_step > 3) read_step = 0;
}
}
else if(channel_flag == 0x0A) // 8bit 2ch
{
while(cnt) /* 接收指定的字节数 */
{
m_data_0 = rcv_spi();
--cnt;
if (read_step_bit) // 右声道高8位
{
if (m_data_0 <= 0x80)
m_data_0 = (m_data_0 + 0x80);
else
m_data_0 = (m_data_0 + 0x81);
m_data_0 = (pre_data + m_data_0) >> 1; // (左+右)/2
while(FifoCt == FULL_bsize); /* 如果缓冲区满,则等待 */
Buff[FifoWi] = m_data_0; /* 一旦缓冲区有空位,则接收音频数据填入 */
#asm("cli") /* 修改音频数据的个数之前首先关闭中断 */
FifoCt ++; /* 修改音频数据 */
#asm("sei") /* 修改完成后打开中断 */
++FifoWi;
}
else // 左声道高8位
{
if(m_data_0 <= 0x80)
pre_data = m_data_0 + 0x80;
else
pre_data = m_data_0 + 0x81;
}
read_step_bit = !read_step_bit;
}
}
else if (channel_flag == 0x11) // 16 bit 1ch
{
while(cnt) /* 接收指定的字节数 */
{
m_data_0 = rcv_spi();
--cnt;
if (read_step_bit) // 只读取高8位
{
if (m_data_0 <= 0x80)
m_data_0 = m_data_0 + 0x80;
else
m_data_0 = m_data_0 + 0x81;
while(FifoCt == FULL_bsize); /* 如果缓冲区满,则等待 */
Buff[FifoWi] = m_data_0; /* 一旦缓冲区有空位,则接收音频数据填入 */
#asm("cli") /* 修改音频数据的个数之前首先关闭中断 */
FifoCt ++; /* 修改音频数据 */
#asm("sei") /* 修改完成后打开中断 */
++FifoWi;
}
read_step_bit = !read_step_bit;
}
}
else // 8bit 1ch
{
for(i = 0;i < cnt;i++) /* 接收指定的字节数 */
{
m_data_0 = rcv_spi();
while(FifoCt == FULL_bsize);/* 如果缓冲区满,则等待 */
Buff[FifoWi] = m_data_0; /* 一旦缓冲区有空位,则接收音频数据填入 */
#asm("cli") /* 修改音频数据的个数之前首先关闭中断 */
FifoCt ++; /* 修改音频数据 */
#asm("sei") /* 修改完成后打开中断 */
++FifoWi;
}
}
}
else
{
p = dest; /* 若指针被赋值为某个缓冲区的地址 */
do
{
*p++ = rcv_spi(); /* 在指定的缓冲区中放入数据 */
}while (--cnt); /* 接收完指定的字节数 */
}
do
{
rcv_spi(); /* 抛弃剩下的字节数 */
} while (--rb);
}
/*--------------------------------------------------------------------------
Module Private Functions
---------------------------------------------------------------------------*/
BYTE CardType;
/*-----------------------------------------------------------------------*/
/* Deselect the card and release SPI bus */
/*-----------------------------------------------------------------------*/
void release_spi(void)
{
DESELECT();
fmit_spi();//我估计是通过调用一个发送0XFF字节内容达到延时的目的;
}
/*-----------------------------------------------------------------------*/
/* Send a command packet to MMC */
/*-----------------------------------------------------------------------*/
BYTE send_cmd(BYTE cmd, DWORD arg) /* Command byte, Argument */
{
BYTE n, res;
if (cmd & 0x80) /* ACMD<n> is the command sequense of CMD55-CMD<n> */ //这里是PFF原作者使用的一个技巧,对需要先发起CMD55的命令在其宏定义时在其BIT7置位了1作为了此处识别的标志,如ACMD41;
{
cmd &= 0x7F;
res = send_cmd(CMD55, 0);
if (res > 1) return res;
}
/* Select the card and wait for ready */
DESELECT();
fmit_spi();
SELECT();
fmit_spi();
/* Send command packet */
xmit_spi(cmd); /* Start + Command index */
xmit_spi((BYTE)(arg >> 24)); /* Argument[31..24] */
xmit_spi((BYTE)(arg >> 16)); /* Argument[23..16] */
xmit_spi((BYTE)(arg >> 8)); /* Argument[15..8] */
xmit_spi((BYTE)arg); /* Argument[7..0] */
n = 0x01; /* Dummy CRC + Stop */
if (cmd == CMD0) n = 0x95; /* Valid CRC for CMD0(0) */
if (cmd == CMD8) n = 0x87; /* Valid CRC for CMD8(0x1AA) */
xmit_spi(n);
/* Receive command response */
n = 10; /* Wait for a valid response in timeout of 10 attempts */
do
{
res = rcv_spi();
}while ((res & 0x80) && --n);//对于R1响应0X01的BIT7为0,故这里只是对0X01值的部分判断;在SD对主机的数据没发出响应之前总线反馈给主机的数据一直保持的是0XFF;
return res; /* Return with the response value */
}
/*--------------------------------------------------------------------------
Public Functions
---------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (void)
{
BYTE n, cmd, ty, ocr[4];
WORD tmr;
init_spi_low(); /* Initialize USI */
for (tmr = 100; tmr; tmr--) fmit_spi(); /* Dummy clocks */ //在选中SD卡前发送大于72个小于400KHZ的时钟;
SELECT(); //选中SD卡;
for (tmr = 1000; tmr; tmr--) fmit_spi(); /* Dummy clocks */ // 延时一段时间,此时主机到SD的数据总线一直为0XFF;
ty = 0;
if (send_cmd(CMD0, 0) == 1) /* Enter Idle state */
{
if (send_cmd(CMD8, 0x1AA) == 1) /* SDv2 */ //符合SD2.0标准的卡(即大于4GD卡)会对CMD8返回1;
{
for (n = 0; n < 4; n++) ocr[n] = rcv_spi(); /* Get trailing return value of R7 resp */
if (ocr[2] == 0x01 && ocr[3] == 0xAA) /* The card can work at vdd range of 2.7-3.6V */
{
for (tmr = 25000; tmr && send_cmd(ACMD41, 1UL << 30); tmr--) ; /* Wait for leaving idle state (ACMD41 with HCS bit) */ //从网上查到 UL是unsigned long的缩写;这里发送ACMD41命令中的32bit参数的bit30给出的1即HCS位代表主机支持高容量卡,正常的话FOR循环中用tmr是不会累计减到零的,SD卡对主机发的ACMD41命令的响应为“在初始化完成前返回0X01的R1响应,在初始化完成后返回0x00的R1响应”
//故主机一直需要发送ACMD41命令通过返回值进行查询SD是否已经完成了初始化;
if (tmr && send_cmd(CMD58, 0) == 0) /* Check CCS bit in the OCR */ //正常的话此处的tmr不应为0;使用此处的发送CMD58命令来获得卡是否为高容量SD卡,还是标准容量的SD卡,这会导致后面读扇区的地址参数的不一样; 正常的话“send_cmd(CMD58, 0) == 0”应该为真,也即是CMD58返回的R3(由R1响应+32BIT的OCR寄存器内容)响应中的R1响应值应该为0X00,马朝老师书上说的R1响应正常的话一般为0X01;
//为何此处就变成了0X00,看了SD卡V2版本规范中对R1响应的解释就明白了,R1响应最后一 bit 的解释是“In idle state: The card is in idle state and running the initializing process”,也就是R1的值为0x01则代表SD卡处于初始化完成前的停止状态,而上面已经用ACMD41命令对卡进行了初始化,且当时对ACMD41返回的R1的值已经为0X00,故凡从(初始化完成)此后的R1响应的值都为0X00;
{
for (n = 0; n < 4; n++) ocr[n] = rcv_spi();
ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; /* SDv2 */ //CT_SD2 | CT_BLOCK代表高容量SD卡,CT_SD2代表标准容量SD卡;
}
}
}
else /* SDv1 or MMC */ //不符合SD2.0标准的卡不会对CMD8有反应,返回值一直保持为0XFF;
{
if (send_cmd(ACMD41, 0) <= 1)//对SD卡的初始化命令有反应;
{
ty = CT_SD1; cmd = ACMD41; /* SDv1 */ //为SD1.0标准的SD卡,用ACMD41命令来初始化;
}
else //对SD卡的初始化命令无反应;
{
ty = CT_MMC; cmd = CMD1; /* MMCv3 */ //为MMC卡,用CMD1命令来初始化;
}
for (tmr = 25000; tmr && send_cmd(cmd, 0); tmr--) ; /* Wait for leaving idle state */ //正常的话此处的CMD1或ACMD41执行后会在 tmr还未减到零时就得到0X00返回值;
if (!tmr || send_cmd(CMD16, 512) != 0) /* Set R/W block length to 512 */ //这里是运算顺序为先执行逻辑或两端的运算最后执行逻辑或运算; 这里的CMD16的返回值正常的话应该为0X00,因为在已经完成初始化后使用返回R1响应的命令时其返回值不是0X01而是0X00;
{
ty = 0;
}
}
}
CardType = ty;
release_spi();
return ty ? 0 : STA_NOINIT; //初始化正常的话返回值为0,否则为代表1的STA_NOINIT;
}
/*-----------------------------------------------------------------------*/
/* Read partial sector */
/*-----------------------------------------------------------------------*/
DRESULT disk_readp(
void *dest, /* Pointer to the destination object to put data */
DWORD lba, /* Start sector number (LBA) */
WORD ofs, /* Byte offset in the sector (0..511) */
WORD cnt /* Byte count (1..512), b15:destination flag */
)
{
DRESULT res;
BYTE rc;
WORD tmr;
if (!(CardType & CT_BLOCK)) lba *= 512; /* Convert LBA to BA if needed */ //对应标准容量的卡由逻辑块地址(扇区号)转换为逻辑字节地址;
res = RES_ERROR;
if (send_cmd(CMD17, lba) == 0) /* READ_SINGLE_BLOCK */
{
tmr = 30000;
do /* Wait for data packet in timeout of 100ms */
{
rc = rcv_spi();
} while (rc == 0xFF && --tmr); //循环直到得到值为0XFE的前导标识响应;
if (rc == 0xFE) //在SD v2.0规范中的P111~P112的“For Single Block Read, Single Block Write and Multiple Block Read”部分有SD卡给主机发出的数据的格式的详细描述,首先发出的是0XFE前导码(因数据总线空闲时为0XFF,否则无法区别何时是真正的数据);
{
fwd_blk_part(dest, ofs, cnt);
res = RES_OK;
}
}
release_spi();
return res;
}
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
你熬了10碗粥,别人一桶水倒进去,淘走90碗,剩下10碗给你,你看似没亏,其实你那10碗已经没有之前的裹腹了,人家的一桶水换90碗,继续卖。说白了,通货膨胀就是,你的钱是挣来的,他的钱是印来的,掺和在一起,你的钱就贬值了。
|