Chinaunix首页 | 论坛 | 博客
  • 博客访问: 313297
  • 博文数量: 169
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 12
  • 用 户 组: 普通用户
  • 注册时间: 2013-07-10 12:00
文章分类

全部博文(169)

文章存档

2014年(38)

2013年(131)

我的朋友

分类: C/C++

2014-02-24 22:07:28

原文地址:sd 卡sdio端口读写 作者:M甲壳虫M

SD支持两种读写方式,对于单片机来说,有的并没有SDIO,所以,使用SPI更加适合使用单片机的场合,当然,现在很多型号也都有SDIO了,所以,有SDIO的,使用SDIO才是王道。

SPI使用的是查询法,还没想好如果用中断的话怎么写好,或是加入DMA,这些后面再来实现,下面来实现SD使用SPI方式读写。

程序是VS2008里面编写的,哈哈,用VS来写程序真是爽啊,可惜太庞大了点……

#include "sd.h"
#include "spi.h"
#include

static volatile u8 sd_type;

__inline static u8 sd_ReadWriteByte( u8 _byte );
__inline static void sd_Disable_Select( void );
__inline static u8 sd_Enable_Select( void );
__inline static void sd_send_one_cmd( u8 cmd );

static void spi_SpeedLow( void );
static void spi_SpeedHigh( void );
static void sd_gpio_spi_init( void );

static u8 sd_WaitResponse( u8 response );
static u8 sd_WaitRead( void );
static u8 sd_mmc_init( void );
static u8 sd_send_cmds( u8 cmd, u32 arg, u8 crc );



static u8 sd_GetCSD( u8 *csd_buff ); // 还没实现
static u8 sd_GetCID( u8 *cid_buff ); // 还没实现

static u8 sd_ReceiveDataToBuffer( u8 *buff, u16 lenght );
static u8 sd_WriteBufferToDisk( u8 *buff, u8 cmd );

u32 SD_GetVolume( void ) // 获取容量,单位M
{
    return 0;
}


u32 SD_GetSectorCount( void ) // 获取扇区数量
{
    return 0;
}


/******************************************************************************************************************************
* 函数名称:SD_WriteMultiSectors
* 函数功能:写数据到多个块或是扇区
* 函数说明:可以写一个或多个
* 参数说明:数据指针  扇区块位置   数量
* 返 回 值:0 成功    1 失败
* 函数调用:
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月21日 星期一  17:58:42
******************************************************************************************************************************/
u8 SD_WriteMultiSectors( u8 *buff, u32 sectors, u32 num )
{
u8 sta;

    if ( sd_type != SD_TYPE_V2HC )
    {
        sectors *= 512;
    }
if ( num == 1 )
{
sta = sd_send_cmds( SD_CMD24, sectors, 0x01 );
if ( sta == 0 )
{
sta = sd_WriteBufferToDisk( buff, 0xFE );
}
}
else {
if ( sd_type != SD_TYPE_MMC )
{
sd_send_cmds( SD_CMD55, 0, 0x01 );
sd_send_cmds( SD_CMD23, num, 0x01 );
}
sta = sd_send_cmds( SD_CMD25, sectors, 0x01 );
if ( sta == 0 )
{
do 
{
sta = sd_WriteBufferToDisk( buff, 0xFC );
buff += 512;
num--;
} while ( (sta==0)&&(num) );
sta = sd_WriteBufferToDisk( buff, 0xfd );
}
}
sd_Disable_Select();
return sta;
}


/******************************************************************************************************************************
* 函数名称:SD_WriteSingleSectors
* 函数功能:把数据缓冲的数据写入卡里
* 函数说明:一次只写个扇区或块
* 参数说明:缓冲区数据指针   扇区或块的位置 
* 返 回 值:0  成功   1 失败
* 函数调用:
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:v1.0
* 日    期:2012年5月21日 星期一  17:57:43
******************************************************************************************************************************/
u8 SD_WriteSingleSectors( u8 *buff, u32 sectors )
{
u8 sta;

if ( sd_type != SD_TYPE_V2HC )
{
sectors *= 512;
}
sta = sd_send_cmds( SD_CMD24, sectors, 0x01 );
if ( sta == 0 )
{
sta = sd_WriteBufferToDisk( buff, 0xFE );
}
sd_Disable_Select();
#if SD_DEBUG_EN>0
printf( "数据写入返回值为 %x \n\n\n", sta );
#endif
return sta;
}


/******************************************************************************************************************************
* 函数名称:SD_ReadMultiSectors
* 函数功能:读取多个扇区或是块的数据
* 函数说明:可以读取一个或是多个
* 参数说明:缓冲区数据指针   扇区/块位置    扇区/块的数量
* 返 回 值:0  成功正确   1  失败异常
* 函数调用:
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月21日 星期一  17:56:02
******************************************************************************************************************************/
u8 SD_ReadMultiSectors( u8 *buff, u32 sectors, u32 num )
{
u8 sta;

if ( sd_type != SD_TYPE_V2HC )
{ // 如果不是2.0HC高速卡,则转换为字节地址 
sectors <<= 9;
#if SD_DEBUG_EN>0
printf( "使用的不是高速卡哦!" );
#endif
}

if ( num == 1 )
{
sta = sd_send_cmds( SD_CMD17, sectors, 0x01 );
if ( sta == 0 )
{
sta = sd_ReceiveDataToBuffer( buff, 512 );
}
}
else {
sta = sd_send_cmds( SD_CMD18, sectors, 0x01 );
do 
{
sta = sd_ReceiveDataToBuffer( buff, 512 );
buff += 512;
num--;
} while ( (sta==0)&&num );
sd_send_cmds( SD_CMD12, 0, 0x01 );
}
sd_Disable_Select();
return sta;
}

/******************************************************************************************************************************
* 函数名称:SD_ReadSingleSectors
* 函数功能:读取一个扇区的数据到缓冲区
* 函数说明:读取的单位是扇区
* 参数说明:缓冲区指针    扇区位置/偏置
* 返 回 值:
* 函数调用:
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月21日 星期一  17:16:59
******************************************************************************************************************************/
u8 SD_ReadSingleSectors( u8 *buff, u32 sectors )
{
u8 sta;
    #if SD_DEBUG_EN>0
    printf( "\n要读取的扇区是%d\n\n", sectors );
    #endif
if ( sd_type != SD_TYPE_V2HC )
{ // 如果不是2.0HC高速卡,则转换为字节地址 
sectors <<= 9; 
}
sta = sd_send_cmds( SD_CMD17, sectors, 0x01 );
if ( sta == 0 )
{
sta = sd_ReceiveDataToBuffer( buff, 512 );
}
sd_Disable_Select();
return sta;
}


/******************************************************************************************************************************
* 函数名称:SD_Init
* 函数功能:检测卡是否存在并检测卡的类型,初始化SD卡
* 函数说明:目前可以适用的卡有V1.0,V2.0,V2.0HC,MMC
* 参数说明:
* 返 回 值:0 初始化成功与正常 1 初始化失败或是卡异常
* 函数调用:
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月21日 星期一  17:27:07
******************************************************************************************************************************/
u8 SD_Init( void )
{
u16 i;
u16 cnt;
u8  sta;
u8  buf[4];

sd_type = 0x00;
sd_gpio_spi_init();
sd_Disable_Select();// 取消片选
delay_ms( 5 ); // 小延时,让SD稳定下来
// 发送至少74个时钟,让SD卡完成上电后的工作
for ( cnt = 0; cnt < 11; cnt++ )
{
sd_ReadWriteByte( 0xff ); // 不使能片选信号,发送时钟
}
// 接下来发送命令0,让SD卡复位进入空状态
cnt = 200;
do 
{ // 循环读取发送命令0的返回状态,直到出现0x01或是超出200次
sta = sd_send_cmds( SD_CMD0, 0, 0x95 );
cnt--;
} while ( ( sta != 0x01 ) && ( cnt ) );
if ( cnt == 0) // 判断上面循环退出的原因,读取到0x01???? 超时????
{
// 如果超时了,取消片选后,直接退出
sd_Disable_Select();

#if SD_DEBUG_EN>0 // 如果使能了调试,则通过串口打印出调试信息
printf( "SD卡复位失败.......\n\n" );
#endif

return 1; // 返回1,初始化失败
}
#if SD_DEBUG_EN>0
printf( "SD卡复位成功........\n\n" );
#endif
// 读取命令8的返回值
sta = sd_send_cmds( SD_CMD8, 0x1AA, 0x87 );
#if SD_DEBUG_EN>0 // 使能调试时,通过串口打印出该命令的返回值
printf( "命令SD_CMD8  返回  %d........\n\n", sta );
#endif
if ( sta == 0x01 ) // 如果是0x01, 则是2.0版本,2.0版本中还是2.0HC版本
{
// 读取命令8后卡发送来的四字节数据,只有2.0版本有
for ( i = 0; i < 4; i++ )
{
buf[ i ] = sd_ReadWriteByte( 0xff );
}
#if SD_DEBUG_EN>0 // 使能调试时
printf( "检测到卡为2.0卡..." );
printf( "接收到手四字节数据是:%d  %d  %d  %d\n\n", buf[0], buf[1], buf[2], buf[3] );
#endif
// 根据读取回来的四字节数据,判断该卡是否支持2.7---3.6的电压
if ( ( buf[ 2 ] == 0x01 )&&( buf[ 3 ] == 0xAA ) ) // 
{
#if SD_DEBUG_EN>0
printf( "SD卡可以支持电压范围2.7---3.6....\n\n" );
#endif
// 只有支持2.7---3.6电压了才继续操作
cnt = 0xffff;
do 
{
sd_send_cmds( SD_CMD55, 0, 0x01 );
sta = sd_send_cmds( SD_CMD41, 0x40000000, 0x01 );
cnt--;
} while ( sta && cnt );

sta = sd_send_cmds( SD_CMD58, 0, 0 );

if ( sta != 0x00 ) // 如果没有得到正确的回应
{
sd_Disable_Select();
#if SD_DEBUG_EN>0
printf( "命令SD_CMD58错误返回 %d....\n\n", sta );
#endif
return 1;
}

// 如果有得到正确的回应,则读取卡发回来的OCR信息
for ( i = 0; i < 4; i++ ) // 读取四字节的信息
{
buf[ i ] = sd_ReadWriteByte( 0xff );
}
sd_Disable_Select();
#if SD_DEBUG_EN>0
printf( "从卡读取回来的OCR信息是 %x %x %x %x....\n\n", buf[0],buf[1],buf[2],buf[3] );
#endif
// 读取完成,鉴别卡是2.0还是2.0HC
if ( buf[0] & 0x40 )
{
#if SD_DEBUG_EN>0
printf( "检测到卡的类型是V2.0HC高速卡,可以正常使用.....\n\n" );
#endif
sd_type = SD_TYPE_V2HC;
}
else {
#if SD_DEBUG_EN>0
printf( "检测到卡的类型是V2.0普通卡,可以正常使用.....\n\n" );
#endif
sd_type = SD_TYPE_V2;
}
spi_SpeedHigh(); // 让SPI进入高速模式
}
else { // 如果不支持2.7---3.6电压的处理
sd_Disable_Select();
return 1;
}
}
else { // 认为是1.0版本或是MMC卡,如果是MMC,需要设置扇区的大小
// 无四字节数据的输出
sd_type = SD_TYPE_V1; // 暂时认为是V1.0的卡
sd_send_cmds( SD_CMD55, 0, 0x01 );
sta = sd_send_cmds( SD_CMD41, 0, 0x01 );
if ( sta <= 1 )
{ // V1.0卡
sd_type = SD_TYPE_V1;
cnt = 0xffff;
do 
{
sd_send_cmds( SD_CMD55, 0, 0x01 );
sta = sd_send_cmds( SD_CMD41, 0, 0x01 );
cnt--;
} while ( sta && cnt );
#if SD_DEBUG_EN>0
printf( "检测到卡的类型是V1.0普通卡," );
#endif
}
else { // MMC卡
sd_type = SD_TYPE_MMC;
cnt = 0xffff;
do 
{
sta = sd_send_cmds( SD_CMD1, 0, 0x01 );
cnt--;
} while ( sta && cnt );
#if SD_DEBUG_EN>0
printf( "检测到卡的类型是MMC卡," );
#endif
}
if ( (cnt==0)||(sd_send_cmds(SD_CMD16,512,0x01)!=0) )
{
sd_type = SD_TYPE_ERR;
sd_Disable_Select();
#if SD_DEBUG_EN>0
printf( "无法设置卡的扇区大小,请检查卡!\n\n" );
#endif
return 1;
}
#if SD_DEBUG_EN>0
else {
printf( "可以正常使用了.....\n\n" );
}
#endif
}
sd_Disable_Select();
return 0; // 返回0 , 初始化成功
}


/******************************************************************************************************************************
* 函数名称:sd_WriteBufferToDisk
* 函数功能:写缓冲的区数据到卡
* 函数说明:
* 参数说明:
* 返 回 值:
* 函数调用:
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月21日 星期一  17:42:59
******************************************************************************************************************************/
static u8 sd_WriteBufferToDisk( u8 *buff, u8 cmd )
{
u16 cnt;
u8 sta;
if ( sd_WaitRead() )
{
#if SD_DEBUG_EN>0
printf( "数据写入等待失败....\n\n\n" );
#endif
return 1;
}
sd_ReadWriteByte( cmd );
if ( cmd != 0xFD )
{
for ( cnt = 0; cnt < 512; cnt++ )
{
sd_ReadWriteByte( *buff );
buff++;
}
for ( cnt = 0; cnt < 2; cnt++ )
{
sd_ReadWriteByte( 0xff );
}
sta = sd_ReadWriteByte( 0xff );
if ( ( sta & 0x1F ) != 0x05 )
{
#if SD_DEBUG_EN>0
printf( "数据完成,但发生错误,错误码是 %x  \n\n\n", sta );
#endif
return 2;
}
}

return 0;
}

/******************************************************************************************************************************
* 函数名称:sd_ReceiveDataToBuffer
* 函数功能:接收长度为lenght的数据到缓冲区buff
* 函数说明:最长65536
* 参数说明:缓冲区指针与接收长度
* 返 回 值:接收状态 0  正常     1 错误
* 函数调用:
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月21日 星期一  15:09:54
******************************************************************************************************************************/
static u8 sd_ReceiveDataToBuffer( u8 *buff, u16 lenght )
{
u8 cnt;
// 等待令牌 0xfe 回应
if ( sd_WaitResponse( 0xfe ) )
{
#if SD_DEBUG_EN>0
printf( "读取数据时等待令牌 0xFE 失败....\n\n" );
#endif
return 1;
}
while ( lenght-- )
{
*buff = sd_ReadWriteByte( 0xff );
buff++;
}
// 不进行CRC校验,发送假的CRC
for ( cnt = 0; cnt < 2; cnt++ )
{
sd_ReadWriteByte( 0xff );
}
return 0;
}

/******************************************************************************************************************************
* 函数名称:sd_send_cmds
* 函数功能:发送一个命令+参数+CRC校验码给SD
* 函数说明:SD的命令一般由六个字节组合而成
* 参数说明:函数会先取消卡的片选信号 ,再便能该信号
* 返 回 值:
* 函数调用:sd_ReadWriteByte
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:37:24
******************************************************************************************************************************/
static u8 sd_send_cmds( u8 cmd, u32 arg, u8 crc )
{
u32 cnt=512;
u8  sta;
sd_Disable_Select(); // 先取消片选
if ( sd_Enable_Select() ) // 再使能片选
{
return 0xff; // 如果片选失败,退出返回255
}
sd_ReadWriteByte( cmd | 0x40 ); // 发送命令组合
sd_ReadWriteByte( (u8)(arg>>24) ); // 参数1
sd_ReadWriteByte( (u8)(arg>>16) ); // 参数2
sd_ReadWriteByte( (u8)(arg>>8) ); // 参数3
sd_ReadWriteByte( (u8)(arg) ); // 参数4
sd_ReadWriteByte( crc ); // CRC校验码

do 
{
sta = sd_ReadWriteByte( 0xff ); // 读取发送状态
cnt--;
} while ( (cnt)&&(sta==0xff) );

return sta; // 返回发送状态
}


/******************************************************************************************************************************
* 函数名称:sd_send_one_cmd
* 函数功能:发送一个命令给SD/MMC卡
* 函数说明:内联
* 参数说明:待发送的命令,发送前需要先使能片选
* 返 回 值:
* 函数调用:sd_ReadWriteByte
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:36:17
******************************************************************************************************************************/
__inline static void sd_send_one_cmd( u8 cmd )
{
sd_ReadWriteByte( cmd ); // 发送一字节的命令
}


/******************************************************************************************************************************
* 函数名称:sd_WaitResponse
* 函数功能:读取SD/MMC/指定的应答状态
* 函数说明:
* 参数说明:指定的应答状态,应答码
* 返 回 值:应答是否符合 0 符合     1 不符合
* 函数调用:sd_ReadWriteByte
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:34:46
******************************************************************************************************************************/
static u8 sd_WaitResponse( u8 response )
{
u16 cnt = 4096;

do 
{
if ( sd_ReadWriteByte( 0xff ) == response ) // 判断指定的应答是否出现
{
return MSD_RESPONSE_NO_ERROR;
}
cnt--;
} while ( cnt );
#if SD_DEBUG_EN>0
printf( "等待 0x%x 信号失败....\n\n", response );
#endif
return MSD_RESPONSE_FAILURE;
}


/******************************************************************************************************************************
* 函数名称:sd_WaitRead
* 函数功能:等SD/MMC卡读就绪
* 函数说明:只有就绪的时候才可以进行读取操作
* 参数说明:
* 返 回 值:等待状态  0 就绪    1 没就绪,错误
* 函数调用:sd_ReadWriteByte
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:33:29
******************************************************************************************************************************/
static u8  sd_WaitRead( void )
{
u32 cnt = 0x00ffffff;
u8  sta;
do 
{
sta = sd_ReadWriteByte( 0xff );
if ( sta == 0xff ) // 判断等待读取是否就绪
{
return 0;
}
cnt--;
} while ( cnt );
#if SD_DEBUG_EN>0
printf( "等待超时....\n\n" );
#endif
return 1;
}


/******************************************************************************************************************************
* 函数名称:sd_Enable_Select
* 函数功能:选择SD卡
* 函数说明:内联
* 参数说明:
* 返 回 值:0 选择成功      1 选择失败
* 函数调用:sd_WaitRead   sd_Disable_Select
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:32:20
******************************************************************************************************************************/
__inline static u8 sd_Enable_Select( void )
{
SD_CS_RESET(); // 使能片选
if ( sd_WaitRead() == 0 ) // 等待SD/MMC就绪
{
return 0;
}
sd_Disable_Select(); // 如果没就绪,则取消片选
#if SD_DEBUG_EN>0
printf( "SD片选使能失败....\n\n" );
#endif
return 1; // 返回1,片选失败
}


/******************************************************************************************************************************
* 函数名称:sd_Disable_Select
* 函数功能:取消片选SD/MMC卡
* 函数说明:内联
* 参数说明:
* 返 回 值:
* 函数调用:sd_ReadWriteByte
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:31:09
******************************************************************************************************************************/
__inline static void sd_Disable_Select( void )
{
SD_CS_SET(); // 取消片选
sd_ReadWriteByte( 0xff ); // 补八个时钟
}


/******************************************************************************************************************************
* 函数名称:sd_gpio_spi_init
* 函数功能:初始化SD对应的GPIO和SPI总线
* 函数说明:
* 参数说明:
* 返 回 值:
* 函数调用:GPIO_Init SPI_1_Configuration
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:29:59
******************************************************************************************************************************/
static void sd_gpio_spi_init( void )
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
GPIO_Init( GPIOA, &GPIO_InitStructure );
GPIO_SetBits( GPIOA, GPIO_Pin_3 | GPIO_Pin_4 );
SD_CS_SET();
SPI_1_Configuration();
spi_SpeedLow();
}


/******************************************************************************************************************************
* 函数名称:spi_SpeedHigh
* 函数功能:让SPI进入高速模式
* 函数说明:
* 参数说明:
* 返 回 值:
* 函数调用:SPI_1_SpeedSeting
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:29:04
******************************************************************************************************************************/
static void spi_SpeedHigh( void )
{
SPI_1_SpeedSeting( SPI_BaudRatePrescaler_2 ); // SPI最高速
delay_us( 10 );
}


/******************************************************************************************************************************
* 函数名称:spi_SpeedLow
* 函数功能:让SPI进入低速模式
* 函数说明:
* 参数说明:
* 返 回 值:
* 函数调用:SPI_1_SpeedSeting
* 全局变量:
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:28:08
******************************************************************************************************************************/
static void spi_SpeedLow( void )
{
SPI_1_SpeedSeting( SPI_BaudRatePrescaler_256 ); // SPI最低速
delay_us( 10 );
}


/******************************************************************************************************************************
* 函数名称:sd_ReadWriteByte
* 函数功能:读取或写入一字节的数据到SD卡
* 函数说明:内联
* 参数说明:写入的数据
* 返 回 值:读取到的数据
* 函数调用:SPI_1_ReadWriteByte
* 全局变量:无
* 编    写:
*  E-mail :
* 修改备注:
* 版    本:V1.0
* 日    期:2012年5月20日 星期日  19:26:49
******************************************************************************************************************************/
__inline static u8 sd_ReadWriteByte( u8 _byte )
{
return SPI_1_ReadWriteByte( _byte ); // 读取&&写入一字节的数据
}




后记:

编译环境使用的是Keil MDK,由于支持内联函数,所以里面有的用得比较频繁的使用了内联的写法

在调试过程中遇到一些问题,主要是由于自己的疏忽引起的,代价是昨晚差不多通宵了,结果也没弄好,今天早上再好好检查了下,结果都找出原因了。

教训是::::不论做什么事,千万不要太浮躁

哎!!!今天眼睛成了血轮眼了………………

接下来,应该是移植文件系统了,以前弄过,不过没什么记录,这个应该要好好记下来才对…………
阅读(1289) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~