使用STM32F103ZET霸道主板实现SD卡的读写(非文件系统)

了解STM32F103ZET是高容量多管脚的芯片

了解SD读写线路图

了解SD的基地址

阅读STM32F10xx英文参考 SDIO那章,咱们编写代码边看文档解析

建工程,打开包含全部包括外设库函数的样本工程,而后留下以下的外设

  • 官方给的E:\2018-9-3\stm32-奋斗者\STM32 官方库3.5版本\stm32f10x_stdperiph_lib35\STM32F10x_StdPeriph_Lib_V3.5.0\Utilities\STM32_EVAL\Common下的文件只是用于他们的测试版,所以须要修改stm32_eval_sdio_sd.h中的include,由原来的#include "stm32_eval.h"改成#include "stm32f10x.h"

    数组

在stm32_eval_sdio_sd.c(我更名为bsp_sdio_sdcard.c)添加新的功能函数

  • 宏定义:sd外设地址(stm32_eval_sdio_sd.h)
/** @defgroup STM32_EVAL_SDIO_SD_Exported_Constants
  * @{
  */ 

/*宏定义*/
#define SDIO_FIFO_ADDRESS                ((uint32_t)0x40018080)  //SDIO_FIOF地址=SDIO地址+0x80至 sdio地址+0xfc
/** 
  * @brief  SDIO Intialization Frequency (400KHz max)
  */
#define SDIO_INIT_CLK_DIV                ((uint8_t)0xB2)
/** 
  * @brief  SDIO Data Transfer Frequency (25MHz max) 
  */
/*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */
#define SDIO_TRANSFER_CLK_DIV            ((uint8_t)0x01)
  • GPIO 初始化
static void  GPIO_Configuration(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    //使能gpio时钟,判断APB仍是AHB,看System architecture图(PDF搜)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC,ENABLE);
    //配置pc8,pc9,pc10,pc11,pc12为D0,D0,D2,D3,D4,CLK,看电路线路图
    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)
    //配置PD2 CMD引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
       GPIO_Init(GPIOD,&GPIO_InitStructure);
    //使能SDIO AHB时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO,ENABLE);
    //使能DMA
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2,ENABLE);
}
  • DMA 配置(为啥选择DMA2的channel4,DMA2的选择是看System architecture,channel4看DMA2 request mapping)
/**
配置好dma2,一发现有中断,就自动传输 Rx
**/
void SD_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize)
{
        DMA_InitTypeDef DMA_InitStructure;
    //清除标志位
    DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 |DMA2_FLAG_HT4 | DMA2_FLAG_GL4);
    //禁止DMA
    DMA_Cmd(DMA2_Channel4,DISABLE);

    //传输配置
    //外设地址,fifo
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
    //目标地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferDST;
    //传输方向
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    //把字转为字节
    DMA_InitStructure.DMA_BufferSize = BufferSize / 4;
    //存储地址自增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
       //不循环  When circular mode is activated, the number of data to be transferred is automatically reloaded with the initial value programmed during the channel configuration phase, and the DMA requests continue to be served.
    DMA_InitStructure.DMA_Mode =  DMA_Mode_Normal;
    //外设数据大小为字, 32 位
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    //存储数据大小为字, 32 位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    //通道优先级高
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    //外设地址不自增
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    //非 存储器至存储器模式 The DMA channels can also work without being triggered by a request from a peripheral. This mode is called Memory to Memory mode.
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;    
    
    DMA_Init(DMA4_Channel4,&DMA_InitStructure); 
    /*!< 使能 DMA 通道 */
    DMA_Cmd(DMA2_Channel4, ENABLE);                              
}
/**
配置好dma2,一发现有中断,就自动传输 Tx
**/
void SD_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize)
{

  DMA_InitTypeDef DMA_InitStructure;

  DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4);

  /*!< DMA2 Channel4 disable */
  DMA_Cmd(DMA2_Channel4, DISABLE);

  /*!< DMA2 Channel4 Config */
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferSRC;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//外设为写入目标
  DMA_InitStructure.DMA_BufferSize = BufferSize / 4;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址不自增
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA2_Channel4, &DMA_InitStructure);

  /*!< DMA2 Channel4 enable */
  DMA_Cmd(DMA2_Channel4, ENABLE);  
}
  • 打开stm32_eval_sdio_sd.h文件中发现不少枚举定义,等
    - 枚举:SD_Error、 SDTransferState 和 SDCardState
    - 结构体定义: SD_CSD、 SD_CID、 SD_CardStatus 以及 SD_CardInfo
    - 宏定义:命令号定义、 SDIO 传输方式、 SD 卡插入状态以及 SD 卡类型定义。

接下来咱们就开始根据 SD 卡识别过程和数据传输过程理解 SD 卡驱动函数代码。这部分代码内容也是很是庞大,不可能所有在文档中所有列出,对于部分函数只介绍其功能。

  • SD卡初始化
  • NVIC初始化
static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;

  /* Configure the NVIC Preemption Priority Bits */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  //SDIO的中断请求 配置好NVIC的中断控制器和中断来,判断谁的优先级高(假设启动多个中断)。先配NVIC,在配外部中断器来屏蔽--硬件或软件(事件或中断)
  NVIC_InitStructure.NVIC_IRQChannel =  SDIO_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_Init(&NVIC_InitStructure);
    //SDIO_IRQ不须要外部中断/事件或软件中断/事件,所以不须要初始化EXIT控制器的所有寄存器,好比中断屏蔽寄存器、事件屏蔽寄存器,看图External interrupt/event controller block diagram
}
  • 初始化SD卡
/**
 描述  :初始化SD卡,使卡处于就绪状态(准备传输数据)
 */
- 此函数原stm32_eval_sdio_sd.c有,不需添加,须要修改
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);
  }

 
  /*!< Configure the SDIO peripheral */
 
  /*!< on STM32F2xx devices, SDIOCLK is fixed to 48MHz */  
   /*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */
   //从新配置 SDIO 外设,提升时钟频率,由卡识别模式的400khz提高到数据传输模式小于25Mhz
  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;
  //暂时配置成lbit模式
  SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
  //硬件流。若开启,在FIFO不能进行发送和接受数据是,数据暂停
  SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
  
  SDIO_Init(&SDIO_InitStructure);
  
  if (errorstatus == SD_OK)
  {
    /*----------------- Read CSD/CID MSD registers ------------------*/
    //用来读取csd/cid寄存器
    /*调用 SD_GetCardInfo 函数获取 SD 卡信息,它须要一个指向 SD_CardInfo 类型变
量地址的指针形参,这里赋值为 SDCardInfo 变量的地址。 SD 卡信息主要是 CID
和 CSD 寄存器内容,这两个寄存器内容在 SD_InitializeCards 函数中都完成读取过
程并将其分别存放在 CID_Tab 数组和 CSD_Tab 数组中,因此 SD_GetCardInfo 函
数只是简单的把这两个数组内容整合复制到 SDCardInfo 变量对应成员内。正确执
行 SD_GetCardInfo 函数后, SDCardInfo 变量就存放了 SD 卡的不少状态信息,这
在以后应用中使用频率是很高的。
    */
    errorstatus = SD_GetCardInfo(&SDCardInfo);
  }

  if (errorstatus == SD_OK)
  {
    /*----------------- Select Card --------------------------------*/
    //经过cm7,rca选择要操做的卡
    errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
  }

  if (errorstatus == SD_OK)
  {
    //提升读写,开启4bit模式
    errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
  }  

  return(errorstatus);
}
  • 调用 SD_PowerON 函数,它用于查询卡的工做电压和时钟控制配置,并返回SD_Error 类型错误,该函数是整个 SD 识别精髓,
  • 此函数原stm32_eval_sdio_sd.c有,不需添加
//确保 SD 卡的工做电压和配置控制时钟
SD_Error SD_PowerON(void)
{
  SD_Error errorstatus = SD_OK;
  uint32_t response = 0, count = 0, validvoltage = 0;
  uint32_t SDType = SD_STD_CAPACITY;

  /*!< Power ON Sequence -----------------------------------------------------*/
  /*!< Configure the SDIO peripheral */
  /*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_INIT_CLK_DIV) */
  /*!< on STM32F2xx devices, SDIOCLK is fixed to 48MHz */
  /*!< SDIO_CK for initialization should not exceed 400 KHz */  
  SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV;
  SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
  SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
  SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
  // 初始化的时候暂时把数据线配置成一根
  SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
  //禁止硬件流控制
  SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
  SDIO_Init(&SDIO_InitStructure);

  /*!< Set Power State to ON */
  //开启外设电源
  SDIO_SetPowerState(SDIO_PowerState_ON);

  /*!< Enable SDIO Clock */
  //使能SDIO时钟
  SDIO_ClockCmd(ENABLE);

  /*!< CMD0: GO_IDLE_STATE ---------------------------------------------------*/
  /*!< No CMD response required */
  //发送一系列命令。开始卡的识别流程
  SDIO_CmdInitStructure.SDIO_Argument = 0x0;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE;
  //设置具体的返回类型,
  SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No;
  //SDIO是否开启或关闭等待中断
  SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
  /* CPSM 在开始发送命令以前等待数据传输结束 */
  SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
  SDIO_SendCommand(&SDIO_CmdInitStructure);
   //检测是否正确接收CM0 , CmdError 函数用于无需响应的命令发送
  errorstatus = CmdError();

  if (errorstatus != SD_OK)
  {
    /*!< CMD Response TimeOut (wait for CMDSENT flag) */
    //响应超时
    return(errorstatus);
  }

  /*!< CMD8: SEND_IF_COND ----------------------------------------------------*/
  /*!< Send CMD8 to verify SD card interface operating condition */
  /*!< Argument: - [31:12]: Reserved (shall be set to '0')
               - [11:8]: Supply Voltage (VHS) 0x1 (Range: 2.7-3.6 V)
               - [7:0]: Check Pattern (recommended 0xAA) */
  /*!< CMD Response: R7 */
  //发送CMD8检查SD卡电压操做  
  //发送 CMD8 命令,检测 SD 卡支持的操做条件,主要就是电压匹配, CMD8 的响
  //应类型是 R7,使用 CmdResp7Error 函数可获取获得 R7 响应结果,它是经过检测
  //SDIO_STA 寄 存 器 相 关 位 完 成 的 , 并 具 有 等 待 超 时 检 测 功 能 。  
  SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND;
  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);
   //检测是否正确接收 ,Checks for error conditions for R7 response. 搜索R7 (Card interface condition)
  errorstatus = CmdResp7Error();

  if (errorstatus == SD_OK)
  {
    CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0; /*!< SD Card 2.0 */
    SDType = SD_HIGH_CAPACITY;
  }
  else //无响应,说明1.x
  {
    /*!< CMD55 */
    SDIO_CmdInitStructure.SDIO_Argument = 0x00;
    SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
    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 = CmdResp1Error(SD_CMD_APP_CMD);
  }
  /*!< CMD55 */
  //发送CMD55,用于检测sd卡仍是mmc卡,或者不支持的卡
  SDIO_CmdInitStructure.SDIO_Argument = 0x00;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
  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);

  //是否响应,没响应就是mmc或不支持的卡
  errorstatus = CmdResp1Error(SD_CMD_APP_CMD);

  /*!< If errorstatus is Command TimeOut, it is a MMC card */
  /*!< If errorstatus is SD_OK it is a SD card: SD card 2.0 (voltage range mismatch)
     or SD card 1.x */
  if (errorstatus == SD_OK)   //响应cmd44.是sd卡,可能为1.x也可能2.x
  {
   
   
    //下面,循环发送sdio支持的电压范围,
    /*!< SD CARD */
    /*!< Send ACMD41 SD_APP_OP_COND with Argument 0x80100000 */
    while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL))
    {
      // 在发送 ACMD 命令前都要先向卡发送 CMD55   ,CMD55用于指示下一条指令是应用指令
      /*!< SEND CMD55 APP_CMD with RCA as 0 */
      SDIO_CmdInitStructure.SDIO_Argument = 0x00;
      SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
      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 = CmdResp1Error(SD_CMD_APP_CMD);

      if (errorstatus != SD_OK)
      {
        return(errorstatus);
      }
      /* ACMD41  ,肯定卡是否是     SDSC 仍是 SDHC,返回R3(就是OCR寄存器),须要用CmdResp3Error返回状态,主要从OCR寄存器31位0或1来判断那种类型
 * 命令参数由支持的电压范围及 HCS 位组成, HCS 位置一来区分卡是 SDSC 仍是 SDHC
 * 0:SDSC
 * 1:SDHC
 * 响应: R3,对应的是 OCR 寄存器
 */
 /*使用 ACMD41 命令判断卡的具体类型。由于是 A 类命令,因此在发送 ACMD41
以前必须先发送 CMD55, CMD55 命令的响应类型的 R1。若是 CMD55 命令都没
有响应说明是 MMC 卡或不可用卡。在正确发送 CMD55 以后就能够发送
ACMD41,并根据响应判断卡类型, ACMD41 的响应号为 R3, CmdResp3Error 函
数用于检测命令正确发送并带有超时检测功能,但并不具有响应内容接收功能,
须要在断定命令正确发送以后调用 SDIO_GetResponse 函数才能获取响应的内容。
 */
      SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD | SDType;//0x80100000
      SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND;
      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 = CmdResp3Error();
      if (errorstatus != SD_OK)
      {
        return(errorstatus);
      }
     /* 若卡需求电压在 SDIO 的供电电压范围内,会自动上电并标志 pwr_up 位
 * 读取卡寄存器,卡状态
 */
      response = SDIO_GetResponse(SDIO_RESP1);
       /* 读取卡的 ocr 寄存器的 pwr_up 位,看是否已工做在正常电压 */
      validvoltage = (((response >> 31) == 1) ? 1 : 0);
      count++;  //计算循环
    }
    //结束循环


    // 循环检测超过必定次数还没上电
    if (count >= SD_MAX_VOLT_TRIAL)
    {
    // SDIO 不支持 card 的供电电压
      errorstatus = SD_INVALID_VOLTRANGE;
      return(errorstatus);
    }

    /*检查卡返回信息中的 HCS 位*/
 /* 判断 ocr 中的 ccs 位 ,若是是 sdsc 卡则不执行下面的语句 */
    if (response &= SD_HIGH_CAPACITY)  //判断30位是否为1
    {
      CardType = SDIO_HIGH_CAPACITY_SD_CARD;
    }

  }/*!< else MMC Card */

  return(errorstatus);
}

到此,程序执行SD卡的SD模式流程图执行到以下图、

判断执行 SD_PowerON 函数无错误后,执行下面的 SD_InitializeCards 函数进行与 SD 卡相关的初始化,使得卡进入数据传输模式下的待机模式。

  • 此函数原stm32_eval_sdio_sd.c有,不需添加
//描述 :初始化全部的卡或者单个卡进入就绪状态
SD_Error SD_InitializeCards(void)
{
  SD_Error errorstatus = SD_OK;
  uint16_t rca = 0x01;

  if (SDIO_GetPowerState() == SDIO_PowerState_OFF)
  {
    errorstatus = SD_REQUEST_NOT_APPLICABLE;
    return(errorstatus);
  }
   //判断卡的类型
  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已经定义好了,不用咱们本身,直接用 */
    CID_Tab[0] = SDIO_GetRespon se(SDIO_RESP1);
    CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
    CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
    CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
  }
  if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) ||  (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) ||  (SDIO_SECURE_DIGITAL_IO_COMBO_CARD == CardType)
      ||  (SDIO_HIGH_CAPACITY_SD_CARD == CardType))
  {
    /*!< Send CMD3 SET_REL_ADDR with argument 0* 要求各个 SD 卡返回自身的 RCA 地址. */
    /*!< SD Card publishes its 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);
    }
  }
 /*******************************************************************/
  if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
  {
    RCA = rca;

    /*!< Send CMD9 SEND_CSD with argument as card's RCA 
         响应:R2 对应寄存器 CSD(Card-Specific Data)*/
    SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)(rca << 16);
    SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD;
    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);
    }

    CSD_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
    CSD_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
    CSD_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
    CSD_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
  }
  /*所有卡初始化成功 */
  errorstatus = SD_OK; /*!< All cards get intialized */

  return(errorstatus);
}

;执行 SD_InitializeCards 函数无错误后 SD 卡就已经处于数据传输模式下的待机状态,退出 SD_InitializeCards 后会返回前面的 SD_Init 函数,执行接下来代码,如下是 SD_Init 函数的后续执行过程。执行以后,卡就从待机状态转变为传输模式,能够说数据传输已是万事俱备了。

SD 卡数据操做:包括数据读取、数据写入以及存储区擦除。数据读取和写入均可以分为单块操做和多块操做。

  • 擦除函数
  • 此函数原stm32_eval_sdio_sd.c有,不需添加
SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr)
{
  SD_Error errorstatus = SD_OK;
  uint32_t delay = 0;
  __IO uint32_t maxdelay = 0;
  uint8_t cardstate = 0;

  /*!< Check if the card coomnd class supports erase command */
  if (((CSD_Tab[1] >> 20) & SD_CCCC_ERASE) == 0)
  {
    errorstatus = SD_REQUEST_NOT_APPLICABLE;
    return(errorstatus);
  }

  maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2);

  if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED)  //卡已上锁
  {
    errorstatus = SD_LOCK_UNLOCK_FAILED;
    return(errorstatus);
  }

  if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
  {
    //在 sdhc 卡中,地址参数为块地址,每块 512 字节,而 sdsc 卡地址为字节地址
   //因此如果 sdhc 卡要对地址/512 进行转换
    startaddr /= 512;
    endaddr /= 512;
  }
  
  /*!< According to sd-card spec 1.0 ERASE_GROUP_START (CMD32) and erase_group_end(CMD33) */
  if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_HIGH_CAPACITY_SD_CARD == CardType))
  {
    /*!< Send CMD32 SD_ERASE_GRP_START with argument as addr  */
    SDIO_CmdInitStructure.SDIO_Argument = startaddr;
    SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START;
    SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;  //R1
    SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStructure);

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

    /*!< Send CMD33 SD_ERASE_GRP_END with argument as addr  */
    SDIO_CmdInitStructure.SDIO_Argument = endaddr;
    SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END;
    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 = CmdResp1Error(SD_CMD_SD_ERASE_GRP_END);
    if (errorstatus != SD_OK)
    {
      return(errorstatus);
    }
  }

  /*!< Send CMD38 ERASE */
  SDIO_CmdInitStructure.SDIO_Argument = 0;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE;
  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 = CmdResp1Error(SD_CMD_ERASE);

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

  for (delay = 0; delay < maxdelay; delay++)
  {}

  /*!< Wait till the card is in programming state */
  errorstatus = IsCardProgramming(&cardstate);

  while ((errorstatus == SD_OK) && ((SD_CARD_PROGRAMMING == cardstate) || (SD_CARD_RECEIVING == cardstate)))
  {
    errorstatus = IsCardProgramming(&cardstate);
  }

  return(errorstatus);
}

SD_WriteBlock 函数用于向指定的目标地址写入一个块的数据,它有三个形参,分别为指向待写入数据的首地址的指针变量、目标写入地址和块大小。块大小通常都设置为512 字节。 (函数不用本身添加,但须要修改)

SD_Error SD_WriteBlock(uint8_t *writebuff, uint64_t WriteAddr, uint16_t BlockSize)
{
  SD_Error errorstatus = SD_OK;

#if defined (SD_POLLING_MODE)
  uint32_t bytestransferred = 0, count = 0, restwords = 0;
  uint32_t *tempbuff = (uint32_t *)writebuff;
#endif

  TransferError = SD_OK;
  TransferEnd = 0;
  StopCondition = 0;
  
  SDIO->DCTRL = 0x0;


  if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
  {
    BlockSize = 512;
    WriteAddr /= 512;
  }

    /*-------------- add , 没有这一段容易卡死在DMA检测中 -------------------*/
    /* Set Block Size for Card,cmd16,
     * 如果sdsc卡,能够用来设置块大小,
     * 如果sdhc卡,块大小为512字节,不受cmd16影响 
     */
  SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
  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 = CmdResp1Error(SD_CMD_SET_BLOCKLEN);

  if (SD_OK != errorstatus)
  {
    return(errorstatus);
  }
 /*********************************************************************************/
  
  /*!< Send CMD24 WRITE_SINGLE_BLOCK */
  SDIO_CmdInitStructure.SDIO_Argument = WriteAddr;    //写入地址
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK;
  SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;     //r1
  SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
  SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
  SDIO_SendCommand(&SDIO_CmdInitStructure);

  errorstatus = CmdResp1Error(SD_CMD_WRITE_SINGLE_BLOCK);

  if (errorstatus != SD_OK)
  {
    return(errorstatus);
  }
    
    //配置sdio的写数据寄存器
  SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
  SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
  SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;  //可用此参数代替SDIO_DataBlockSize_512b
  SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard;//写数据,
  SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
  SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;   //开启数据通道状态机
  SDIO_DataConfig(&SDIO_DataInitStructure);

  /*!< In case of single data block transfer no need of stop command at all */
#if defined (SD_POLLING_MODE) //普通模式
  while (!(SDIO->STA & (SDIO_FLAG_DBCKEND | SDIO_FLAG_TXUNDERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_STBITERR)))
  {
    if (SDIO_GetFlagStatus(SDIO_FLAG_TXFIFOHE) != RESET)
    {
      if ((512 - bytestransferred) < 32)
      {
        restwords = ((512 - bytestransferred) % 4 == 0) ? ((512 - bytestransferred) / 4) : (( 512 -  bytestransferred) / 4 + 1);
        for (count = 0; count < restwords; count++, tempbuff++, bytestransferred += 4)
        {
          SDIO_WriteData(*tempbuff);
        }
      }
      else
      {
        for (count = 0; count < 8; count++)
        {
          SDIO_WriteData(*(tempbuff + count));
        }
        tempbuff += 8;
        bytestransferred += 32;
      }
    }
  }
  if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
    errorstatus = SD_DATA_TIMEOUT;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
    errorstatus = SD_DATA_CRC_FAIL;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_TXUNDERR);
    errorstatus = SD_TX_UNDERRUN;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_STBITERR);
    errorstatus = SD_START_BIT_ERR;
    return(errorstatus);
  }
#elif defined (SD_DMA_MODE) //dma模式
  SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);  //数据传输结束中断
  SD_DMA_TxConfig((uint32_t *)writebuff, BlockSize); //配置dma,跟rx相似
  SDIO_DMACmd(ENABLE);   // 使能sdio的dma请求
#endif

  return(errorstatus);
}

SD_WaitWriteOperation 函数用于检测和等待数据写入完成,在调用数据写入函数以后通常都须要调用, SD_WaitWriteOperation 函数适用于单块及多块写入函数。

/**
  * 上述代码调用库函数 SD_DMAEndOfTransferStatus 一直检测 DMA 的传输完成标志,
当 DMA 传输结束时,该函数会返回 SET 值。另外, while 循环中的判断条件使用的
TransferEnd 和 TransferError 是全局变量,它们会在 SDIO 的中断服务函数根据传输状况被
设置, 传输结束后,根据 TransferError 的值来确认是否正确传输,若不正确则直接返回错
误代码。 SD_WaitWriteOperation 函数最后是清除相关标志位并返回错误。因为这个函数里
的 while 循环的存在, 它会确保 DMA 的传输结束。
  */
SD_Error SD_WaitWriteOperation(void)
{
  SD_Error errorstatus = SD_OK;
  //等待dma是否传输
  while ((SD_DMAEndOfTransferStatus() == RESET) && (TransferEnd == 0) && (TransferError == SD_OK))
  {}

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

  /*!< Clear all the static flags */
  SDIO_ClearFlag(SDIO_STATIC_FLAGS);

  return(errorstatus);
}

SD_ReadBlock函数:

SD_Error SD_ReadBlock(uint8_t *readbuff, uint64_t ReadAddr, uint16_t BlockSize)
{
  SD_Error errorstatus = SD_OK;
#if defined (SD_POLLING_MODE) 
  uint32_t count = 0, *tempbuff = (uint32_t *)readbuff;
#endif

  TransferError = SD_OK;
  TransferEnd = 0;   //传输结束标置位,在中断服务置1
  StopCondition = 0;  
  
  SDIO->DCTRL = 0x0;

  
  if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
  {
    BlockSize = 512;
    ReadAddr /= 512;
  }
  /*******************add,没有这一段容易卡死在DMA检测中*************************************/
  /* Set Block Size for Card,cmd16,
     * 如果sdsc卡,能够用来设置块大小,
     * 如果sdhc卡,块大小为512字节,不受cmd16影响 
     */
  SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
  SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;   //r1
  SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
  SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
  SDIO_SendCommand(&SDIO_CmdInitStructure);

  errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);

  if (SD_OK != errorstatus)
  {
    return(errorstatus);
  }
 /*********************************************************************************/
  SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
  SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
  SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
  SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
  SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
  SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
  SDIO_DataConfig(&SDIO_DataInitStructure);

  /*!< Send CMD17 READ_SINGLE_BLOCK */
  SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK;
  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 = CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK);

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

#if defined (SD_POLLING_MODE)  
  /*!< In case of single block transfer, no need of stop transfer at all.*/
  /*!< Polling mode */
  while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR)))
  {
    if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)
    {
      for (count = 0; count < 8; count++)
      {
        *(tempbuff + count) = SDIO_ReadData();
      }
      tempbuff += 8;
    }
  }

  if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
    errorstatus = SD_DATA_TIMEOUT;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
    errorstatus = SD_DATA_CRC_FAIL;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_RXOVERR);
    errorstatus = SD_RX_OVERRUN;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_STBITERR);
    errorstatus = SD_START_BIT_ERR;
    return(errorstatus);
  }
  while (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET)
  {
    *tempbuff = SDIO_ReadData();
    tempbuff++;
  }
  
  /*!< Clear all the static flags */
  SDIO_ClearFlag(SDIO_STATIC_FLAGS);

#elif defined (SD_DMA_MODE)
    SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);
    SDIO_DMACmd(ENABLE);
    SD_DMA_RxConfig((uint32_t *)readbuff, BlockSize);
#endif

  return(errorstatus);
}

其余读写操做函数雷同,不用修改。但须要把最后面4句话替换成:

到此,SD卡初始化完成了,以上只是介绍重要那部分的函数,其余有些细微的修改,先引用,请点击:SD读写测试

设置SDIO 中断服务函数(stm32f10x_it.c)

// 在 SDIO_ITConfig()这个函数开启了 sdio 中断 ,
void SDIO_IRQHandler(void)
{
    //SDIO中断相关处理
    SD_ProcessIRQSrc(); //定义在bsp_sdio_sdcard.c

}
/*
 * 函数名:SD_ProcessIRQSrc
 * 描述  :数据传输结束中断
 * 输入  :无        
 * 输出  :SD错误类型
 */
SD_Error SD_ProcessIRQSrc(void)
{
  if (StopCondition == 1)  //发送读取、多块读写命令时置1
  {
    SDIO->ARG = 0x0;   //命令参数寄存器
    SDIO->CMD = 0x44C;    // 命令寄存器: 0100    01      001100
                        //                      [7:6]   [5:0]
                        //              CPSMEN  WAITRESP CMDINDEX
                        //      开启命令状态机 短响应   cmd12 STOP_ TRANSMISSION                      
    TransferError = CmdResp1Error(SD_CMD_STOP_TRANSMISSION);
  }
  else
  {
    TransferError = SD_OK;
  }
  SDIO_ClearITPendingBit(SDIO_IT_DATAEND); //清中断
  SDIO_ITConfig(SDIO_IT_DATAEND, DISABLE); //关闭sdio中断使能
  TransferEnd = 1;
  return(TransferError);
}

至此,咱们已经介绍了 SD 卡初始化、 SD 卡数据操做的基础功能函数以及 SDIO 相关中断服务函数内容,利用这个 SDIO 驱动,能够编写一些简单的 SD 卡读写测试程序。

测试 SD 卡部分的函数是咱们本身编写的,存放在 sdio_test.c 文件等。

最后附上我编写的程序代码,主要实现串口输入内容,而后保存到sd卡中。再读取出来,输出到串口那里。

点击下载:【sd卡读取app

相关文章
相关标签/搜索