stm32专题三十九:SDIO

SDIO协议简介

SDIO简介

SDIO接口设备

目前 SD 协议提供的 SD 卡规范版本最新是 4.01 版本,但 STM32F10x 系列控制器只支持 SD 卡规范版本 2.0,即只支持标准容量 SD 和高容量SDHC 标准卡,不支持超大容量 SDXC 标准卡,所以可以支持的最高卡容量是 32GB。

如下图所示(左边64GB为超大容量 SDXC,右边32GB为大容量 SDHC):

SD卡物理结构

一张SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器5个部分。

SD 卡总共有 8 个寄存器,用于设定或表示 SD 卡信息。这些寄存器只能通过对应的命令访问,对 SD 卡进行控制操作并不是像操作控制器 GPIO 相关寄存器那样一次读写一个寄存器的,它是通过命令来控制。 SDIO 定义了 64 个命令,每个命令都有特殊意义,可以实现某一特定功能, SD 卡接收到命令后,根据命令要求对 SD 卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现 SD 卡的控制以及读写操作

SD卡寄存器描述(很复杂,只需要关注常用的几个就可以):

SD卡总线结构:

引脚说明和操作时序:

SDIO和SPI的区别:

SDIO明显时钟频率更快,而且SDIO拥有4根数据线,同等频率下通信速度能提高4倍。

 

总线协议

SD 总线通信是基于命令和数据传输的。通讯由一个起始位(0),由一个停止位(1)终止。 SD 通信一般是主机发送一个命令,从设备在接收到命令后作出响应,如有需要会有数据传输参与。

stm32 多数据块写入

SD 数据传输支持单块和多块读写,它们分别对应不同的操作命令,多块写入还需要使用命令来停止整个写入操作。数据写入前需要检测 SD 卡忙状态,因为 SD 卡在接收到数据后编程到存储区过程需要一定操作时间, SD卡忙状态通过把 D0 线拉低表示。


SD卡操作时序:

先发送第1字节,然后发送第2字节,以此类推...

在发送每字节时,D0 ~ D3先发送高 4 位,再发送低 4 位。

宽位数据包格式(专用于SSR寄存器):

 

SDIO命令及响应

命令

SD 命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与 SD 主机总线连接的所有从设备发送的,寻址命令是指定某个地址设备进行命令传输。
命令格式

命令参数意义:

命令类型:

常规命令(64条)和特定应用命令(厂商自定义的命令)

响应:

响应由 SD 卡向主机发出,部分命令要求 SD 卡作出响应,这些响应多用于反馈SD卡的状态。 SDIO 总共有7个响应类型(代号: R1~R7),其中 SD 卡没有 R4、 R5 类型响应。特定的命令对应有特定的响应类型,比如当主机发送 CMD3 命令时,可以得到响应 R6。与命令一样,SD卡的响应也是通过 CMD 线连续传输的。根据响应内容大小可以分为短响应和长响应。短响应是 48bit 长度,只有 R2 类型是长响应,其长度为 136bit。
 

命令描述:

如上所示,CMD0命令,类型为无响应广播命令,复位所有卡到空闲状态;

CMD2命令,位带响应广播命令,通知所有卡通过CMD线返回CID值,响应类型为R2;

可以看到,发送命令CMD2,返回R2类型,实际上就是返回CID或者CSD寄存器的值。

当发送CMD2命令后,会返回CID值(128位),这个128位标识符太长,可以通过CMD3命令,通知所有卡发布新RCA(16位)来区分系统上有几张卡(总线上的卡通信后会知道总线上有多少张卡,然后协调并自动分配每张卡的RCA标识符),返回类型为R6,如下所示:

R6类型会返回RCA寄存器值,并返回部分卡状态寄存器值(32位寄存器);

R1类型会返回卡状态寄存器的全部位。

CMD8命令,发送SD卡的接口条件,如果SD卡认为这是正常的电压,则返回R7类型;

数据读取操作(每次读取前,要先设置块的长度):

可以看到,SD卡的数据宽度为32位。注意,这里有区分SDSC(标准容量SD卡)和SDHC(大容量SD卡):

SDSC:地址以字节位单位,如 0 ~ 511地址位第1数据块,512 ~ 1023地址为第2数据块;

SDHC:地址以块为单位,1地址就是第1数据块,2地址就是第2数据块;

块写入操作类似:

块擦除操作(需要使用3个命令):

块擦除需要指定数据起始地址、结束地址、然后开始进行擦除操作;

当有多个SD卡存在时,SD卡如何得知是对哪一张SD卡进行操作?

实际上,要通过CMD7命令,选中一张SD卡。

 

SD卡操作模式

SD卡操作模式

模式状态如下:

卡识别模式状态机:

数据传输模式状态机:

 

stm32 SDIO接口

SDIO功能框图:

SDIO适配器:

控制单元:

命令路径:

下面是 stm32 中文参考手册中,相关的寄存器描述:

SDIO参数寄存器:

SDIO命令寄存器:

SDIO响应1...4寄存器(根据短响应or长响应不同来区分)

命令路径状态机:

数据路径:

数据路径状态机:

数据 FIFO

对应的寄存器描述:

SDIO状态寄存器 SDIO_STA

SDIO数据控制寄存器(设置数据块大小):

 

stm32 SDIO结构体

stm32标准库对 SDIO 外设建立了 3 个结构体,分别为 SDIO 初始化结构体 SDIO_InitTypeDef ,SDIO 命令初始化结构体 SDIO_CmdInitTypeDef 和 SDIO 数据初始化结构体 SDIO_DataInitTypeDef 。

1 SDIO 初始化结构体 SDIO_InitTypeDef

2 SDIO 命令初始化结构体 SDIO_CmdInitTypeDef

3 SDIO 数据初始化结构体 SDIO_DataInitTypeDef

 

SDIO 驱动分析

1 SD卡 原理图(使用到stm32的6个引脚)

标准库中,实际上给出了 SDIO 的示例,如下所示:

其中包含了 main.c       stm32f10x_ic.h 等文件

在 Utilities 文件夹下,提供了评估板的驱动文件(提供了SD卡驱动函数):

以及评估板的BSP支持文件(外设GPIO初始化):

移植过程:

说白了,其实就是把 stm32_eval_sdio_sd.c 文件复制过来,但是,stm32_eval_sdio_sd.c 中会初始化一些外设,如 SDIO_GPIO 初始化,这些外设的初始化和评估板有关,在板级支持包 stm32f10e_eval.c 中实现,因此要把这些初始化程序也复制进来。

如下图所示(移植好的工程和原始工程对比):

移植过程,就是不断的编译,一开始肯定会各种报错(缺少初始化函数,位于stm32f10e_eval.c中),我们去stm32f10e_eval.c这个文件中找到对应的函数,移植添加进来。

注意,SDIO使用了中断,因此我们也需要将中断服务函数移植进来。

这就是移植的过程,测试代码一样在 example 中有提供,同样的可以移植。

 

驱动分析

下面是函数的执行过程:

if ((Status = SD_Init()) != SD_OK)
{
    LED_RED;
    printf("SD卡初始化失败,请确保SD卡已正确接入开发板,或换一张SD卡测试!\n");
}
else
{
    printf("SD卡初始化成功!\n");
}

重点就是 SD_Init() 这个SD卡初始化函数:

/**
 * 函数名:SD_Init
 * 描述  :初始化SD卡,使卡处于就绪状态(准备传输数据)
 * 输入  :无
 * 输出  :-SD_Error SD卡错误代码
 *         成功时则为 SD_OK
 * 调用  :外部调用
 */
SD_Error SD_Init(void)
{
    /*重置SD_Error状态*/
    SD_Error errorstatus = SD_OK;

    NVIC_Configuration();

    /* SDIO 外设底层引脚初始化 */
    GPIO_Configuration();

    /*对SDIO的所有寄存器进行复位*/
    SDIO_DeInit();

    /*上电并进行卡识别流程,确认卡的操作电压  */
    errorstatus = SD_PowerON();

    /*如果上电,识别不成功,返回“响应超时”错误 */
    if (errorstatus != SD_OK)
    {
        /*!< CMD Response TimeOut (wait for CMDSENT flag) */
        return (errorstatus);
    }

    /*卡识别成功,进行卡初始化    */
    errorstatus = SD_InitializeCards();

    if (errorstatus != SD_OK) //失败返回
    {
        /*!< CMD Response TimeOut (wait for CMDSENT flag) */
        return (errorstatus);
    }

    /* 配置SDIO外设
   * 上电识别,卡初始化都完成后,进入数据传输模式,提高读写速度
   */

    /* SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */
    SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;

    /*上升沿采集数据 */
    SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;

    /* Bypass模式使能的话,SDIO_CK不经过SDIO_ClockDiv分频 */
    SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;

    /* 若开启此功能,在总线空闲时关闭sd_clk时钟 */
    SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;

    /* 暂时配置成1bit模式 */
    SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;

    /* 硬件流,若开启,在FIFO不能进行发送和接收数据时,数据传输暂停 */
    SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;

    SDIO_Init(&SDIO_InitStructure);

    if (errorstatus == SD_OK)
    {
        /* 用来读取csd/cid寄存器 */
        errorstatus = SD_GetCardInfo(&SDCardInfo);
    }

    if (errorstatus == SD_OK)
    {
        /* 通过cmd7  ,rca选择要操作的卡 */
        errorstatus = SD_SelectDeselect((uint32_t)(SDCardInfo.RCA << 16));
    }

    if (errorstatus == SD_OK)
    {
        /* 最后为了提高读写,开启4bits模式 */
        errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
    }

    return (errorstatus);
}

1 基本的 NVIC配置(SDIO中断源,中断分组、中断优先级,使能NVIC);

2 SDIO引脚初始化(注意,这里还要使能SDIO AHB总线时钟 + DMA);

/*
 * 函数名:GPIO_Configuration
 * 描述  :初始化SDIO用到的引脚,开启时钟。
 * 输入  :无
 * 输出  :无
 * 调用  :内部调用
 */
static void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /*!< GPIOC and GPIOD Periph clock enable */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);

    /*!< Configure PC.08, PC.09, PC.10, PC.11, PC.12 pin: D0, D1, D2, D3, CLK pin */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    /*!< Configure PD.02 CMD line */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    /*!< Enable the SDIO AHB Clock */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO, ENABLE);

    /*!< Enable the DMA2 Clock */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
}

3 复位 SDIO 寄存器,这个实际上应该放在最前面比较好,但这里因为没有配置寄存器,在这里执行也可以;

/*对SDIO的所有寄存器进行复位*/
SDIO_DeInit();

4 上电识别SD卡;

/* 上电并进行卡识别流程,确认卡的操作电压 */
errorstatus = SD_PowerON();

这里,主要就是 SD_PowerON() 这个函数,实现了SD卡整个的识别过程;

a 初始化时钟;

/******************************************************************************/
/* 上电初始化 
   * 配置SDIO的外设
   * SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_INIT_CLK_DIV)   
   * 初始化时的时钟不能大于400KHz
   */
/* HCLK = 72MHz, SDIOCLK = 72MHz, SDIO_CK = HCLK/(178 + 2) = 400 KHz */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
/* 不使用bypass模式,直接用HCLK进行分频得到SDIO_CK */
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
/* 空闲时不关闭时钟电源 */
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
/* 初始化的时候暂时先把数据线配置成1根 */
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
/* 失能硬件流控制 */
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);
/* 开启SDIO外设的电源 */
SDIO_SetPowerState(SDIO_PowerState_ON);
/* 使能 SDIO 时钟 */
SDIO_ClockCmd(ENABLE);

b 发送CMD0命令,使SD卡进入空闲状态;

/** 下面发送一系列命令,开始卡识别流程
  * CMD0: GO_IDLE_STATE(复位所以SD卡进入空闲状态) 
  * 没有响应  
  */
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE;

/* 没有响应 */
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No;
/* 关闭等待中断 */
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
/* 则CPSM在开始发送命令之前等待数据传输结束 */
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);

/* 检测是否正确接收到cmd0 */
errorstatus = CmdError();

/* 命令发送出错,返回 */
if (errorstatus != SD_OK)
{
    /* CMD Response TimeOut (wait for CMDSENT flag) */
    return (errorstatus);
}

/**
 * 作用  :检查发送命令超时的函数
 * 描述  :对CMD0命令的检查。
 * 输出  :SD错误类型
 */
static SD_Error CmdError(void)
{
    SD_Error errorstatus = SD_OK;
    uint32_t timeout;
    timeout = SDIO_CMD0TIMEOUT; /*!< 10000 */

    /*检查命令是否已发送*/
    while ((timeout > 0) && (SDIO_GetFlagStatus(SDIO_FLAG_CMDSENT) == RESET))
    {
        timeout--;
    }
    if (timeout == 0)
    {
        errorstatus = SD_CMD_RSP_TIMEOUT;   // 返回超时
        return (errorstatus);
    }
    /*!< Clear all the static flags */
    SDIO_ClearFlag(SDIO_STATIC_FLAGS); //清除静态标志位

    return (errorstatus);       // 返回SD_OK
}

c 发送 CMD8 命令,判断SD卡是否为2.0协议版本;

d 循环发送 SDIO 支持的电压范围,发送一定次数。如果一直没有应答,说明供电电压不支持,如果没有超出循环次数,说明 SD 卡初始化正常,根据响应值判断SD卡表标准容量还是高容量类型;

5 初始化所有卡(实际上stm32只支持一个SDIO设备);

发送 CMD2 命令,获取CID号并存储;然后发送 CMD3 命令,获取 RCA 地址;

if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
{
    /* Send CMD2 ALL_SEND_CID 
	 * 响应:R2,对应CID寄存器
	 */
    SDIO_CmdInitStructure.SDIO_Argument = 0x0;
    SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_SEND_CID;
    SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
    SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStructure);

    errorstatus = CmdResp2Error();

    if (SD_OK != errorstatus)
    {
        return (errorstatus);
    }

    /* 将返回的CID信息存储起来 */
    CID_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
    CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
    CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
    CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}

{
    /* Send CMD3 SET_REL_ADDR with argument 0 
     * SD Card publishes its RCA.
     * 响应:R6,对应RCA寄存器		
	 */
    SDIO_CmdInitStructure.SDIO_Argument = 0x00;
    SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR;
    SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
    SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStructure);

    /* 把接收到的卡相对地址存起来 */
    errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca);

    if (SD_OK != errorstatus)
    {
        return (errorstatus);
    }
}

6 在前面已经完成了SD卡的识别过程,然后要切换到 高速传输模式 和 4线传输 ;

/* 配置SDIO外设
 * 上电识别,卡初始化都完成后,进入数据传输模式,提高读写速度
 */

/* SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
/*上升沿采集数据 */
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
/* Bypass模式使能的话,SDIO_CK不经过SDIO_ClockDiv分频 */
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
/* 若开启此功能,在总线空闲时关闭sd_clk时钟 */
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
/* 暂时配置成1bit模式 */
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;

/* 硬件流,若开启,在FIFO不能进行发送和接收数据时,数据传输暂停 */
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);

if (errorstatus == SD_OK)
{
    /* 用来读取csd/cid寄存器 */
    errorstatus = SD_GetCardInfo(&SDCardInfo);
}
if (errorstatus == SD_OK)
{
    /* 通过cmd7  ,rca选择要操作的卡 */
    errorstatus = SD_SelectDeselect((uint32_t)(SDCardInfo.RCA << 16));
}
if (errorstatus == SD_OK)
{
    /* 最后为了提高读写,开启4bits模式 */
    errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
}

至此,SD_Init() 函数分析完成。

 

SD卡擦除测试,包括SD卡的擦除、读取和校验;

void SD_EraseTest(void)
{
    /*------------------- Block Erase ------------------------------------------*/
    if (Status == SD_OK)
    {
        /* Erase NumberOfBlocks Blocks of WRITE_BL_LEN(512 Bytes) */
        Status = SD_Erase(0x00, (BLOCK_SIZE * NUMBER_OF_BLOCKS));
    }
    if (Status == SD_OK)
    {
        Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);

        /* Check if the Transfer is finished */
        Status = SD_WaitReadOperation();
        /* Wait until end of DMA transfer */
        while (SD_GetStatus() != SD_TRANSFER_OK);
    }
    /* Check the correctness of erased blocks */
    if (Status == SD_OK)
    {
        EraseStatus = eBuffercmp(Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
    }
    if (EraseStatus == PASSED)
    {
        LED_GREEN;
        printf("SD卡擦除测试成功!\n");
    }
    else
    {
        LED_BLUE;
        printf("SD卡擦除测试失败!\n");
        printf("温馨提示:部分SD卡不支持擦除测试,若SD卡能通过下面的single读写测试,即表示SD卡能够正常使用。\n");
    }
}

SD卡块写入(推荐使用DMA)和读取测试;

SD卡连续读写(DMA形式)测试;

 

测试结果

测试结果如下:

 

FATFS文件系统

支持多设备:

/* 为每个设备定义一个物理编号 */
#define ATA         0   // 预留SD卡使用
#define SPI_FLASH   1   // 外部SPI Flash

当使用SD卡(ATA)时,挂载 / 写入路径为:

res_flash = f_mount(&fs, "0:", 1);
f_open(&fnew, "0:FatFs读写测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE);

而当使用 SPI_FLASH时,挂载 / 写入路径为:

res_flash = f_mount(&fs, "1:", 1);
f_open(&fnew, "1:FatFs读写测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE);

这里的路径参数,最终(设备编号)会传入到这些函数中:

DSTATUS disk_status(
    BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{

    DSTATUS status = STA_NOINIT;

    switch (pdrv)
    {
    case ATA: /* SD CARD */
              /* 设备ID读取结果正确 */
        status &= ~STA_NOINIT;

        break;

    case SPI_FLASH:
        /* SPI Flash状态检测:读取SPI Flash 设备ID */
        //      if(sFLASH_ID == SPI_FLASH_ReadID())
        //      {
        //        /* 设备ID读取结果正确 */
        //        status &= ~STA_NOINIT;
        //      }
        //      else
        //      {
        //        /* 设备ID读取结果错误 */
        //        status = STA_NOINIT;;
        //      }
        break;

    default:
        status = STA_NOINIT;
    }
    return status;
}

DMA 4字节地址对齐问题:

什么是地址对齐(简单来说,就是地址必须整除)?

在x86或ARM处理器中,基本C数据类型通常并不存储于内存中的随机字节地址。实际情况是,除char外,所有其他类型都有“对齐要求”:char可起始于任意字节地址,2字节的short必须从偶数字节地址开始,4字节的int或float必须从能被4整除的地址开始,8比特的long和double必须从能被8整除的地址开始。无论signed(有符号)还是unsigned(无符号)都不受影响。

首先是DMA描述:

可能存在的问题:

处理程序如下所示:

if ((DWORD)buff & 3)
{ //buff 地址不对齐

    while (count--)
    {
        __align(4) uint8_t tempbuff[512];

        status = disk_read(ATA, (BYTE *)tempbuff, sector++, 1);
        if (status == RES_OK)
        {
            memcpy(buff, tempbuff, SDCardInfo.CardBlockSize);

            buff += SDCardInfo.CardBlockSize;
        }
    }
    return status;
}
else
{

    sd_state = SD_ReadMultiBlocks(buff, sector * SDCardInfo.CardBlockSize, SDCardInfo.CardBlockSize, count);

    sd_state = SD_WaitReadOperation();
    while (SD_GetStatus() != SD_TRANSFER_OK)
        ;

    if (sd_state == SD_OK)
        status = RES_OK;
    else
        status = RES_ERROR;
}

 

  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值