SD卡初始化以及命令详解
SD卡是嵌入式设备中很常用的一种存储设备,体积小,容量大,通讯简单,电路简单所以受到很多设备厂商的欢迎,主要用来记录设备运行过程中的各种信息,以及程序的各种配置信息,很是方便,有这样几点是需要知道的
SD 卡是基于 flash
的存储卡。
SD 卡和 MMC
卡的区别在于初始化过程不同。SD卡并不是我们通常意义上的手机扩展卡,那种卡叫做TF卡,但是通讯以及驱动模式是类似的.
SD 卡的通信协议包括 SD
和 SPI
两类,SD卡上电之后默认处于SD状态。
SD 卡使用卡内智能控制模块进行 FLASH
操作控制,包括协议、安全算法、
数据存取、ECC
算法、缺陷处理和分析、电源管理、时钟管理。这些都不需要用户关系,这是SD卡厂商做的事情
驱动SD卡主要要实现读扇区,写扇区,初始化,获取SD卡相关配置信息这几个就可以了,
另外.SD卡本身只是一种数据介质,它不含有文件系统,文件系统是一种文件的组织格式,是独立于存储介质的一种规范
标准SD卡引脚序列
SD卡引脚功能表
TF卡引脚排序
TF卡引脚功能表
由此可见,TF卡比SD卡少了一个VSS引脚,也就是少了一个供电引脚
另外电路设计时若SD卡使用SPI模式,那么不用的几根数据线应加上上拉电阻,否者会因为这几根数据线的电流震荡引起电流损耗,造成电路上的不稳定
SD卡电路SPI驱动模式
SD卡内部有五个我们可以读取的寄存器,分别如下
要读取这些信息就需要与卡通讯,SD通讯是用命令+数据的形式进行的,命令格式如下
也就是说,一次SD卡命令发送一共要发送6个字节,对于SPI通讯而言,就是SPI总线上传送六个字节
字节 1
的最高 2
位固定为 01,低 6
位为命令号(比如 CMD16,
为 10000
即 16
进制的 0X10,完整的 CMD16,第一个字节为 01010000,即
0X10+0X40)。
字节 2~5
为命令参数,有些命令是没有参数的。对于没有参数的命令默认发送0即可
字节 6
的高七位为 CRC
值,最低位恒定为 1,crc计算遵循以下规律
GX为生成多项式,具体计算方法请查看CRC计算相关,不过有一点好处就是,在SPI驱动模式下,不需要CRC校验(默认SD卡在SPI模式下不开启CRC校验,在SD模式下默认开始CRC校验),所以我们只需要对CMD0进行CRC就可以了,后面的CRC都可以不管(因为在CMD0之前是SD模式,所以第一个命令需要,切换之后就不用了),而CMD0的CRC为0x95(加上了之后的一位1)
注:SPI模式下打开crc校验需要用到CMD59的保留命令,请查阅相关资料
SD卡的命令表如下所示(以下仅写出SPI模式的CMD)
CMD0 复位SD卡,
重置所有卡到 Idle状态,参数为0
CMD1 设置SD卡到ACTIVATE模式,也就是推出IDLE模式
CMD8 发送接口状态命令
CMD9 读取CSD寄存器
CMD10 读取CID寄存器
CMD12 在多块读取的时候请求停止读取
CMD13读取SD卡状态寄存器
CMD16 设置单个扇区的大小一般都设置为512字节一个扇区
CMD17 读取扇区命令
CMD18 读取多个扇区知道发送停止命令
CMD24 写扇区命令
CMD25 写多个扇区命令
CMD27 编辑CSD位
CMD28设置地址组保护位。写保护由卡配置数据的WP_GRP_SIZE
指定
CMD29清除保护位
CMD30 要求卡发送写保护状态,参数中有要查询的地址
CMD32 设置要擦除的第一个写数据块地址
CMD33 设置要擦除的最后一个写数据块地址
CMD38 擦除所有选中的块
CMD42 设置SD卡的解锁或者上锁
CMD55 告诉SD卡下一个命令式卡应用命令,不是标准命令
CMD56 应用相关的数据块读写命令
CMD58 读取OCR信息
CMD59 设置crc校验的使能与关闭(前面说到过)
ACMD13 发送SD卡状态
ACMD18保留作为 SD
安全应用(也就是这命令没用)
ACMD22发送写数据块的数目。响应为 32
位+CRC
ACMD23设置写前预擦除的数据块数目(用来加速多数据块写操作)。“1”=默认(一个块)(1)
不管是否使用 ACMD23,在多数据块写操作中都需要 STOP_TRAN(CMD12)命令
ACMD25 26 38 保留作为安全应用
ACMD41要求访问的卡发送它的操作条件寄存器(OCR)内容
ACMD42连接[1]/断开[0]卡上CD/DAT3(pin
1]的 50K
欧姆上拉电阻。上拉电阻可用来检测卡
ACMD43-49保留作为安全应用
ACMD51读取 SD
配置寄存器 SCR
ACMD命令,全称应该是application CMD,所以使用ACMD都需要在发送CMD55之后
发出命令后会收到相应的响应,
所有响应通过 CMD
线传输,响应以 MSB
开始,不同类型的响应长度根据类型不同而不同。
响应以起始位开始(通常为“0”),接着这是传输方向的位(卡为
0)。除了 R3
外其他
响应都有 CRC。每个响应都以结束位(通常为“1”)结束。,SD卡响应格式有多种
1. R1响应
2. R1b响应
多了一个忙数据
3. R2响应
4. R3响应(针对于read ocr的响应
CMD58)
5. 响应R4和R5都是正对于SD mode的响应
6. 针对CMD8命令的响应R7
SD卡的初始化以及识别过程(为了方便起见,我们只检测响应的R1状态)
1. 初始化与 SD
卡连接的硬件条件(MCU
的 SPI
配置,IO
口配置);
2. 上电延时(>74
个 CLK)(为了让卡正常启动)
3. 复位卡(CMD0),进入 IDLE
状态,检测R1的最低位,是否为闲置状态
4. 发送 CMD8,检查是否支持 2.0
协议,因为这个命令是在2.0的协议里面才添加的
5. 根据不同协议检查 SD
卡(命令包括:CMD55、CMD41、CMD58
和 CMD1
等);
6. 取消片选,发多 8
个 CLK,结束初始化
具体请查看下图
以下是网络上找到的一份经我修改之后的SD卡驱动,不完全符合SD卡标准驱动,但是我用着一直还蛮正常,大家有兴趣可以看看改改
Spisd.c
#include "spisd.h" //预定义SD卡类型
u8 SD_Type=0;//SD卡的类型 //这部分应根据具体的连线来修改!
#define SD_CS PAout(4) //SD卡片选引脚 //data:要写入的数据
//返回值:读到的数据
static u8 SdSpiReadWriteByte(u8 data)
{
return Spi1ReadWriteByte(data);
} //SD卡初始化的时候,需要低速
static void SdSpiSpeedLow(void)
{
Spi1SetSpeed(SPI_SPEED_256);//设置到低速模式 用于初始化,最高spi速度为400k
} //SD卡正常工作的时候,可以高速了
static void SdSpiSpeedHigh(void)
{
Spi1SetSpeed(SPI_SPEED_4);//设置到高速模式 初始化完成之后进行,最高可到25M,不过一般不用
} static void SdIOInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化A4 SD_CS = 1;
Spi1Init();//初始化SPI接口
SdSpiSpeedLow();//初始化设置为低速
} //等待卡准备好
//返回值:0,准备好了;其他,错误代码
static u8 SdWaitReady(void)
{
u32 t=0;
do
{
if(SdSpiReadWriteByte(0XFF)==0XFF)return 0;//OK
t++;
}while(t<0XFFFFFF);//等待
return 1;
} //取消选择,释放SPI总线
void SD_DisSelect(void)
{
SD_CS=1;
SdSpiReadWriteByte(0xff);//提供额外的8个时钟
} //选择sd卡,并且等待卡准备OK
//返回值:0,成功;1,失败;
u8 SdSelect(void)
{
SD_CS=0;
if(SdWaitReady()==0)return 0;//等待成功
SD_DisSelect();
return 1;//等待失败
} //等待SD卡回应
//Response:要得到的回应值
//返回值:0,成功得到了该回应值
// 其他,得到回应值失败 期待得到的回应值
u8 SdGetResponse(u8 Response)
{
u16 Count=0xFFF;//等待次数
while ((SdSpiReadWriteByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应
if (Count==0)return MSD_RESPONSE_FAILURE;//得到回应失败
else return MSD_RESPONSE_NO_ERROR;//正确回应
} //从sd卡读取一个数据包的内容
//buf:数据缓存区
//len:要读取的数据长度.
//返回值:0,成功;其他,失败;
//0XFE数据起始令牌
u8 SdRecvData(u8*buf,u16 len)
{
if(SdGetResponse(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE
while(len--)//开始接收数据
{
*buf=SdSpiReadWriteByte(0xFF);
buf++;
}
//下面是2个伪CRC(dummy CRC)
SdSpiReadWriteByte(0xFF);
SdSpiReadWriteByte(0xFF);
return 0;//读取成功
} //向sd卡写入一个数据包的内容 512字节
//buf:数据缓存区
//cmd:指令
//返回值:0,成功;其他,失败;
u8 SdSendBlock(u8*buf,u8 cmd)
{
u16 t;
if(SdWaitReady())return 1;//等待准备失效
SdSpiReadWriteByte(cmd);
if(cmd!=0XFD)//不是结束指令
{
for(t=0;t<512;t++)SdSpiReadWriteByte(buf[t]);//提高速度,减少函数传参时间
SdSpiReadWriteByte(0xFF);//忽略crc
SdSpiReadWriteByte(0xFF);
t=SdSpiReadWriteByte(0xFF);//接收响应
if((t&0x1F)!=0x05)return 2;//响应错误
}
return 0;//写入成功
} //向SD卡发送一个命令
//输入: u8 cmd 命令
// u32 arg 命令参数
// u8 crc crc校验值
//返回值:SD卡返回的响应
u8 SdSendCmd(u8 cmd, u32 arg, u8 crc)
{
u8 r1;
u8 Retry=0;
SD_DisSelect();//取消上次片选
if(SdSelect())return 0XFF;//片选失效
//发送
SdSpiReadWriteByte(cmd | 0x40);//分别写入命令
SdSpiReadWriteByte(arg >> 24);
SdSpiReadWriteByte(arg >> 16);
SdSpiReadWriteByte(arg >> 8);
SdSpiReadWriteByte(arg);
SdSpiReadWriteByte(crc);
if(cmd==CMD12)SdSpiReadWriteByte(0xff);//Skip a stuff byte when stop reading
//等待响应,或超时退出
Retry=0X1F;
do
{
r1=SdSpiReadWriteByte(0xFF);
}while((r1&0X80) && Retry--);
//返回状态值
return r1;
} //获取SD卡的CID信息,包括制造商信息
//输入: u8 *cid_data(存放CID的内存,至少16Byte)
//返回值:0:NO_ERR
// 1:错误
u8 SdGetCID(u8 *cid_data)
{
u8 r1;
//发CMD10命令,读CID
r1=SdSendCmd(CMD10,0,0x01);
if(r1==0x00)
{
r1=SdRecvData(cid_data,16);//接收16个字节的数据
}
SD_DisSelect();//取消片选
if(r1)return 1;
else return 0;
} //获取SD卡的CSD信息,包括容量和速度信息
//输入:u8 *cid_data(存放CID的内存,至少16Byte)
//返回值:0:NO_ERR
// 1:错误
u8 SdGetCSD(u8 *csd_data)
{
u8 r1;
r1=SdSendCmd(CMD9,0,0x01);//发CMD9命令,读CSD
if(r1==0)
{
r1=SdRecvData(csd_data, 16);//接收16个字节的数据
}
SD_DisSelect();//取消片选
if(r1)return 1;
else return 0;
} //获取SD卡的总扇区数(扇区数)
//返回值:0: 取容量出错
//其他:SD卡的容量(扇区数/512字节)
//每扇区的字节数必为512,因为如果不是512,则初始化不能通过.
u32 SdGetSectorCount(void)
{
u8 csd[16];
u32 Capacity;
u8 n;
u16 csize;
//取CSD信息,如果期间出错,返回0
if(SdGetCSD(csd)!=0) return 0;
//如果为SDHC卡,按照下面方式计算
if((csd[0]&0xC0)==0x40) //V2.00的卡
{
csize = csd[9] + ((u16)csd[8] << 8) + 1;
Capacity = (u32)csize << 10;//得到扇区数
}else//V1.XX的卡
{
n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1;
Capacity= (u32)csize << (n - 9);//得到扇区数
}
return Capacity;
} //初始化SD卡
//返回值:0,正常.
//其他,不正常.
u8 SdInitialize(void)
{
u8 r1; // 存放SD卡的返回值
u16 retry; // 用来进行超时计数
u8 buf[4];
u16 i; SdIOInit(); //初始化IO
for(i=0;i<10;i++)SdSpiReadWriteByte(0XFF);//发送最少74个脉冲 ,这里发送了80个脉冲
retry=20;
do
{
r1=SdSendCmd(CMD0,0,0x95);//进入IDLE状态
}while((r1!=0X01) && retry--);
SD_Type=0;//默认无卡
if(r1==0X01)
{
if(SdSendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
{
for(i=0;i<4;i++)buf[i]=SdSpiReadWriteByte(0XFF); //Get trailing return value of R7 resp
if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V
{
retry=0XFFFE;
do
{
SdSendCmd(CMD55,0,0X01); //发送CMD55
r1=SdSendCmd(CMD41,0x40000000,0X01);//发送CMD41
}while(r1&&retry--);
if(retry&&SdSendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
{
for(i=0;i<4;i++)buf[i]=SdSpiReadWriteByte(0XFF);//得到OCR值
if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC; //检查CCS
else SD_Type=SD_TYPE_V2;
}
}
}else//SD V1.x/ MMC V3
{
SdSendCmd(CMD55,0,0X01); //发送CMD55
r1=SdSendCmd(CMD41,0,0X01); //发送CMD41
if(r1<=1)
{
SD_Type=SD_TYPE_V1;
retry=0XFFFE;
do //等待退出IDLE模式
{
SdSendCmd(CMD55,0,0X01); //发送CMD55
r1=SdSendCmd(CMD41,0,0X01);//发送CMD41
}while(r1&&retry--);
}else
{
SD_Type=SD_TYPE_MMC;//MMC V3
retry=0XFFFE;
do //等待退出IDLE模式
{
r1=SdSendCmd(CMD1,0,0X01);//发送CMD1
}while(r1&&retry--);
}
if(retry==0||SdSendCmd(CMD16,512,0X01)!=0)SD_Type=SD_TYPE_ERR;//错误的卡
}
}
SD_DisSelect();//取消片选
SdSpiSpeedHigh();//高速
if(SD_Type)return 0;
else if(r1)return r1;
return 0xaa;//其他错误
} //读SD卡
//buf:数据缓存区
//sector:扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SdReadDisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//转换为字节地址
if(cnt==1)
{
r1=SdSendCmd(CMD17,sector,0X01);//读命令
if(r1==0)//指令发送成功
{
r1=SdRecvData(buf,512);//接收512个字节
}
}else
{
r1=SdSendCmd(CMD18,sector,0X01);//连续读命令
do
{
r1=SdRecvData(buf,512);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
SdSendCmd(CMD12,0,0X01); //发送停止命令
}
SD_DisSelect();//取消片选
return r1;//
} //写SD卡
//buf:数据缓存区
//sector:起始扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
u8 SdWriteDisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//转换为字节地址
if(cnt==1)
{
r1=SdSendCmd(CMD24,sector,0X01);//读命令
if(r1==0)//指令发送成功
{
r1=SdSendBlock(buf,0xFE);//写512个字节
}
}else
{
if(SD_Type!=SD_TYPE_MMC)
{
SdSendCmd(CMD55,0,0X01);
SdSendCmd(CMD23,cnt,0X01);//发送指令
}
r1=SdSendCmd(CMD25,sector,0X01);//连续读命令
if(r1==0)
{
do
{
r1=SdSendBlock(buf,0xFC);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
r1=SdSendBlock(0,0xFD);//接收512个字节
}
}
SD_DisSelect();//取消片选
return r1;//
}
Spisd.h
#ifndef __SPISD_H_
#define __SPISD_H_ #include "spi.h"
#include "delay.h"
#include "common.h"
#include "ioremap.h" // SD卡类型定义
#define SD_TYPE_ERR 0X00
#define SD_TYPE_MMC 0X01
#define SD_TYPE_V1 0X02
#define SD_TYPE_V2 0X04
#define SD_TYPE_V2HC 0X06 // SD卡指令表
#define CMD0 0 //卡复位
#define CMD1 1
#define CMD8 8 //命令8 ,SEND_IF_COND
#define CMD9 9 //命令9 ,读CSD数据
#define CMD10 10 //命令10,读CID数据
#define CMD12 12 //命令12,停止数据传输
#define CMD16 16 //命令16,设置SectorSize 应返回0x00
#define CMD17 17 //命令17,读sector
#define CMD18 18 //命令18,读Multi sector
#define CMD23 23 //命令23,设置多sector写入前预先擦除N个block
#define CMD24 24 //命令24,写sector
#define CMD25 25 //命令25,写Multi sector
#define CMD41 41 //命令41,应返回0x00
#define CMD55 55 //命令55,应返回0x01
#define CMD58 58 //命令58,读OCR信息
#define CMD59 59 //命令59,使能/禁止CRC,应返回0x00 // SD卡中的响应有许多种,R1为标准响应,最为常用。与R1响应相似的还有R1b、R2和R3。
// R1响应在除SEND_STATUS外其它命令后发送,也是最高位先发送,共1个字节。最高位为0。响应说明如下:
// 0x01:空闲状态
// 0x02:擦除错误
// 0x04:命令错误
// 0x08:CRC通信错误
// 0x10:擦除次序错误
// 0x20:地址错误
// 0x40:参数错误 #define MSD_RESPONSE_NO_ERROR 0x00 //无错误
#define MSD_IN_IDLE_STATE 0x01 //空闲状态
#define MSD_ERASE_RESET 0x02 //擦除错误
#define MSD_ILLEGAL_COMMAND 0x04 //命令错误
#define MSD_COM_CRC_ERROR 0x08 //CRC通信错误
#define MSD_ERASE_SEQUENCE_ERROR 0x10 //擦除次序错误
#define MSD_ADDRESS_ERROR 0x20 //地址错误
#define MSD_PARAMETER_ERROR 0x40 //参数错误
#define MSD_RESPONSE_FAILURE 0xFF //这次命令根本是失败的,没有任何回应 u8 SdInitialize(void); u8 SdGetCID(u8 *cid_data); u8 SdGetCSD(u8 *csd_data); u32 SdGetSectorCount(void); u8 SdReadDisk(u8*buf,u32 sector,u8 cnt); u8 SdWriteDisk(u8*buf,u32 sector,u8 cnt); #endif
SD卡初始化以及命令详解的更多相关文章
- Shell学习(五)—— awk命令详解
一.awk简介 awk是一个非常好用的数据处理工具,相对于sed常常作用于一整个行的处理,awk则比较倾向于一行当中分成数个[字段]处理,因此,awk相当适合处理小型的数据数据处理.awk是一种报 ...
- linux awk命令详解
linux awk命令详解 简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分 ...
- Git 常用命令详解
Git 是一个很强大的分布式版本管理工具,它不但适用于管理大型开源软件的源代码(如:linux kernel),管理私人的文档和源代码也有很多优势(如:wsi-lgame-pro) Git 的更多介绍 ...
- linux dd命令详解
Linux-dd命令详解 dd 是 Linux/UNIX 下的一个非常有用的命令,作用是用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换. 名称: dd 使用权限: 所有使用者dd 这个指令在 ...
- Linux/CentOS 服务安装/卸载,开机启动chkconfig命令详解|如何让MySQL、Apache开机启动?
chkconfig chkconfig在命令行操作时会经常用到.它可以方便地设置和查询不同运行级上的系统服务.这个可要好好掌握,用熟练之后,就可以轻轻松松的管理好你的启动服务了. 注:谨记chkcon ...
- Git 常用命令详解(二)
Git 是一个很强大的分布式版本管理工具,它不但适用于管理大型开源软件的源代码(如:linux kernel),管理私人的文档和源代码也有很多优势(如:wsi-lgame-pro) Git 的更多介绍 ...
- 转 vagrant package[打包命令]详解
转 vagrant package[打包命令]详解 vagrant的一个非常重要的功能就是在你的同事之间分享你的box从而使大家的开发环境保持同步,打包[package]正是实现这一功能的关键所在 ...
- kill 命令详解 系统信号
kill 命令详解 系统信号 参考: 了解Linux的进程与线程 http://www.cnblogs.com/MYSQLZOUQI/p/4234005.html Linux就这个范儿 P532 ...
- Linux-dd命令详解【转】
转自http://www.cnblogs.com/dkblog/archive/2009/09/18/1980715.html Linux-dd命令详解 dd 是 Linux/UNIX 下的一个非 ...
随机推荐
- 为什么总是要求使用position的时候父类是relative
当我们使用position的时候,一般来说外面的框架是使用relative,里面的元素使用absolute的,这里有两个注意点: 1.如果我们不给父类一个position属性的时候,那么子元素就会以b ...
- c++ 中bool 的默认值
比如在Test.h中定义变量: _isFirst; //Test.h头文件 #ifndef __TEST_H__ #define __TEST_H__ class Test{ private: boo ...
- [转]Linux下CodeBlocks的交叉编译
原文链接:http://blog.sina.com.cn/s/blog_602f87700100kujh.html Sam一直是Makefile流,这些天需要移植一些游戏引擎模块.这些模块在其它嵌入式 ...
- 编写MR代码中,JAVA注意事项
在编写一个job的过程中,发现代码中抛出 java.lang.UnsupportedOperationException 异常. 编写相似逻辑的测试代码: String[] userid = {&qu ...
- discuz!迁移指南
转自:http://jingyan.baidu.com/article/f7ff0bfc77114b2e26bb1390.html 曾经在本地搭建过一个discuz!论坛,现在买了域名和服务器,那么怎 ...
- 性能更好的js动画实现方式---requestAnimationFrame
用js来实现动画,我们一般是借助setTimeout或setInterval这两个函数,css3动画出来后,我们又可以使用css3来实现动画了,而且性能和流畅度也得到了很大的提升.但是css3动画还是 ...
- android异步加载AsyncTask
http://blog.csdn.net/abc5382334/article/details/17097633 http://keeponmoving.iteye.com/blog/1515611 ...
- Commons Codec基本使用(转载)
在实际的应用中,我们经常需要对字符串进行编解码,Apache Commons家族中的Commons Codec就提供了一些公共的编解码实现,比如Base64, Hex, MD5,Phonetic an ...
- Using StructureMap DI and Generic Repository
In this post, i will show how to use generic repository and dependency injection using structuremap. ...
- Cocos2d-x V2.x -- 开发进阶和高级实例教程(一) 转
第一章 如何在多平台新建Cocos2d-x项目 yangyong2014-06-25 15:04:44848 次阅读 原文链接: http://cn.cocos2d-x.org/tutorial/ ...