第61章 SDIO-SD卡读写测试

第六十一章 SDIO-SD卡读写测试

1. 硬件设计

STM32控制器的SDIO引脚是被设计固定不变的,开发板设计采用四根数据线模式。对于命令线和数据线须需要加一个上拉电阻。

2. 软件设计

2.1 前提须知

际上,SD卡是非常常用外设部件,ST公司在其测试板上也有板子SD卡卡槽, 并提供了完整的驱动程序,我们直接参考移植使用即可。类似SDIO、USB这些复杂的外设,它们的通信协议相当庞大,要自行编写完整、 严谨的驱动不是一件轻松的事情,这时我们就可以利用ST官方例程的驱动文件,根据自己硬件移植到自己开发平台即可。

在“初识STM32标准库”章节我们重点讲解了标准库的源代码及启动文件和库使用帮助文档这两部分内容,实际上“Utilities”文件夹内容是非常有参考价值的, 该文件夹包含了基于ST官方实验板的驱动文件,比如LCD、SRAM、SD卡、音频解码IC等等底层驱动程序, 另外还有第三方软件库,如emWin图像软件库和FatFs文件系统。虽然,我们的开发平台跟ST官方实验平台硬件设计略有差别,但移植程序方法是完全可行的。 学会移植程序可以减少很多工作量,加快项目进程,更何况ST官方的驱动代码是经过严格验证的。

在ST固件库“STM32F10x_StdPeriph_Lib_V3.5.0UtilitiesSTM32_EVALCommon”文件夹下可以找到SD卡驱动文件,我们需要stm32_eval_sdio_sd.c和stm32_eval_sdio_sd.h两个文件的完整内容。 另外还可以参考目录“STM32F10x_StdPeriph_Lib_V3.5.0ProjectSTM32F10x_StdPeriph_ExamplesSDIOuSDCard”下中的示例代码编写测试, 为简化工程,本章配置工程代码是将这些与SD卡相关的的内容都添加到stm32_eval_sdio_sd.c文件中,具体可以参考工程文件。

我们把stm32_eval_sdio_sd.c和stm32_eval_sdio_sd.h两个文件拷贝到我们的工程文件夹中,另外,添加的sdio_test.c和sdio_test.h文件包含了SD卡读、写、擦除测试代码。

2.2 编程大纲

  1. 初始化相关GPIO及SDIO外设

  2. 配置SDIO基本通信环境进入卡识别模式,通过几个命令处理后得到卡类型

  3. 如果是可用卡就进入数据传输模式,接下来就可以进行读、写、擦除的操作

  4. 主函数测试

2.3 代码分析

2.3.1 宏定义和函数声明

/* Private typedef */
typedef enum {FAILED = 0, PASSED = !FAILED}TestStatus; // 测试状态枚举类型

/* Private define */
#define BLOCK_SIZE 512 // 块大小
#define NUMBER_OF_BLOCKS 10 // 块数量
#define MULTI_BUFFER_SIZE (BLOCK_SIZE * NUMBER_OF_BLOCKS) // 多块读写缓冲区大小

/* Private variables */
uint8_t Buffer_Block_Tx[BLOCK_SIZE], Buffer_Block_Rx[BLOCK_SIZE];
uint8_t Buffer_MultiBlock_Tx[MULTI_BUFFER_SIZE], Buffer_MultiBlock_Rx[MULTI_BUFFER_SIZE];
volatile TestStatus EraseStatus = FAILED, TransferStatus1 = FAILED, TransferStatus2 = FAILED;
SD_Error Status = SD_OK;

/* Private function prototypes */
static void SD_EraseTest(void);
static void SD_SingleBlockTest(void);
void SD_MultiBlockTest(void);
static void Fill_Buffer(uint8_t *pBuffer, uint32_t BufferLength, uint32_t Offset);
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint32_t BufferLength);
static TestStatus eBuffercmp(uint8_t* pBuffer, uint32_t BufferLength);

2.3.2 扇区擦除测试

// 扇区擦除测试
void SD_EraseTest(void)
{
  if(Status == SD_OK)
  {
    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); // 读取擦除后的扇区内容
    Status = SD_WaitReadOperation(); // 等待读取操作完成
    while(SD_GetStatus() != SD_TRANSFER_OK); // 等待传输完成
  }
  if(Status == SD_OK)
  {
    EraseStatus = eBuffercmp(Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
  }
  if(EraseStatus == PASSED)
  {
    LED_GREEN();
    printf("SD erase test success\r\n");
  }
  else
  {
    LED_RED();
    printf("SD erase test failed\r\n");
  }
}

2.3.3 单块读写测试

// 单块读写测试
void SD_SingleBlockTest(void)
{
  Fill_Buffer(Buffer_Block_Tx, BLOCK_SIZE, 0x320F);
  if(Status == SD_OK)
  {
    Status = SD_WriteBlock(Buffer_Block_Tx, 0x00, BLOCK_SIZE); // 写入单块数据
    Status = SD_WaitWriteOperation(); // 等待写入操作完成
    while(SD_GetStatus() != SD_TRANSFER_OK); // 等待传输完成
  }
  if(Status == SD_OK)
  {
    Status = SD_ReadBlock(Buffer_Block_Rx, 0x00, BLOCK_SIZE); // 读取单块数据
    Status = SD_WaitReadOperation(); // 等待读取操作完成
    while(SD_GetStatus() != SD_TRANSFER_OK); // 等待传输完成
  }
  if(Status == SD_OK)
  {
    TransferStatus1 = Buffercmp(Buffer_Block_Tx, Buffer_Block_Rx, BLOCK_SIZE); // 比较读写数据
  }
  if(TransferStatus1 == PASSED)
  {
    LED_GREEN();
    printf("SD single block test success\r\n");
  }
  else
  {
    LED_RED();
    printf("SD single block test failed\r\n");
  }
}

2.3.4 多块读写测试

// 多块读写测试
void SD_MultiBlockTest(void)
{
  Fill_Buffer(Buffer_MultiBlock_Tx, MULTI_BUFFER_SIZE, 0x00);
  if(Status == SD_OK)
  {
    Status = SD_WriteMultiBlocks(Buffer_MultiBlock_Tx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS); // 写入多块数据
    Status = SD_WaitWriteOperation(); // 等待写入操作完成
    while(SD_GetStatus() != SD_TRANSFER_OK); // 等待传输完成
  }
  if(Status == SD_OK)
  {
    Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS); // 读取多块数据
    Status = SD_WaitReadOperation(); // 等待读取操作完成
    while(SD_GetStatus() != SD_TRANSFER_OK); // 等待传输完成
  }
  if(Status == SD_OK)
  {
    TransferStatus2 = Buffercmp(Buffer_MultiBlock_Tx, Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE); // 比较读写数据
  }
  if(TransferStatus2 == PASSED)
  {
    LED_GREEN();
    printf("SD multi block test success\r\n");
  }
  else
  {
    LED_RED();
    printf("SD multi block test failed\r\n");
  }
}

2.3.5 辅助函数

// 比较两个缓冲区是否相同
TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint32_t BufferLength)
{
  while(BufferLength--)
  {
    if(*pBuffer1 != *pBuffer2)
    {
      return FAILED;
    }
    pBuffer1++;
    pBuffer2++;
  }
  return PASSED;
}

void Fill_Buffer(uint8_t *pBuffer, uint32_t BufferLength, uint32_t Offset)
{
  uint16_t index = 0;
  for(index = 0; index < BufferLength; index++)
  {
    pBuffer[index] = (uint8_t)(index + Offset);
  }
}

TestStatus eBuffercmp(uint8_t* pBuffer, uint32_t BufferLength)
{
  while(BufferLength--)
  {
    if((*pBuffer != 0xFF)&&(pBuffer != 0x00))
    {
      return FAILED;
    }
    pBuffer++;
  }
  return PASSED;
}

2.3.6 SDIO-SD读写测试

/* Private functions */
void SD_Test(void)
{
  LED_BLUE();
  if((Status = SD_Init()) != SD_OK)
  {
    LED_RED();
    printf("SD init error\r\n");
  }
  else
  {
    printf("SD init success\r\n");
  }
  if(Status == SD_OK)
  {
    LED_BLUE();
    SD_EraseTest(); // 扇区擦除测试
    LED_BLUE();
    SD_SingleBlockTest(); // 单块读写测试
    LED_BLUE();
    SD_MultiBlockTest(); // 多块读写测试
  }
}

2.3.7 主函数测试

#include "stm32f10x.h"
#include "sdio_sdcard.h"
#include "sdio_test.h"
#include "usart.h"	
#include "led.h"
#include "key.h" 

int main(void)
{
	LED_Init();	
	KEY_GPIO_Init();
	USART_Config();
	printf("按下KEY1键开始测试\r\n");
	while(1)
	{
		if(KEY_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON)
		{
			printf("开始测试SDIO-SD卡读写\r\n");
			SD_Test();
		}
	}							   
}

3. 小结

1. 硬件连接

STM32F103 与 SD 卡的连接如下(使用 SDIO 接口):

STM32 Pin SD 卡 Pin
PD8 D0
PD9 D1
PD10 D2
PD11 D3
PD12 CMD
VCC VCC
GND GND

2. 配置 SDIO 接口

首先,我们需要初始化 SDIO 接口。在 STM32F103 中,SDIO 接口通常使用 4 位数据线模式(4-bit mode),并且通过 SDIO 进行初始化。

3. 初始化 SDIO

下面是初始化 SDIO 接口的代码(使用 STM32 标准外设库):

#include "stm32f10x.h"

// SDIO 初始化函数
void SDIO_Init(void) {
    // 打开 SDIO 和 GPIO 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);  // 用于 CMD 和 D0 引脚
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);  // 用于 D1, D2, D3 引脚
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, ENABLE);    // 用于 SDIO 外设

    // 初始化 GPIOC 和 GPIOD 引脚
    GPIO_InitTypeDef GPIO_InitStruct;
    
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOD, &GPIO_InitStruct);
    
    // 配置 SDIO 接口
    SDIO_InitTypeDef SDIO_InitStruct;
    SDIO_InitStruct.SDIO_ClockEdge = SDIO_ClockEdge_Rising;  
    SDIO_InitStruct.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
    SDIO_InitStruct.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
    SDIO_InitStruct.SDIO_BusWide = SDIO_BusWide_4b;  // 4位数据总线
    SDIO_InitStruct.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
    SDIO_InitStruct.SDIO_CPSM = SDIO_CPSM_Enable;  // 启用命令发送
    SDIO_Init(&SDIO_InitStruct);
}

4. 初始化 SD 卡

SD 卡在初始化时,需要发送一系列命令。这里是用标准库进行初始化的代码:

#include "stm32f10x.h"

// SD 卡初始化函数
int SD_Init(void) {
    SDIO_CmdInitTypeDef SDIO_CmdInitStruct;

    // 1. 发送 CMD0 重置 SD 卡
    SDIO_CmdInitStruct.SDIO_CmdIndex = SDIO_CmdIndex_CMD0;  // CMD0: GO_IDLE_STATE
    SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_No;
    SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStruct);

    // 等待命令完成
    while (SDIO_GetFlagStatus(SDIO_FLAG_CMDSENT) == RESET);

    // 2. 发送 CMD8 获取 SD 卡电压范围 (仅适用于 SDHC 卡)
    SDIO_CmdInitStruct.SDIO_CmdIndex = SDIO_CmdIndex_CMD8;  // CMD8: SEND_IF_COND
    SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short;
    SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStruct);

    // 等待命令完成
    while (SDIO_GetFlagStatus(SDIO_FLAG_CMDSENT) == RESET);

    // 3. 发送 CMD55 通知卡进入应用命令模式
    SDIO_CmdInitStruct.SDIO_CmdIndex = SDIO_CmdIndex_CMD55;  // CMD55: APP_CMD
    SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short;
    SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStruct);

    // 等待命令完成
    while (SDIO_GetFlagStatus(SDIO_FLAG_CMDSENT) == RESET);

    // 4. 发送 CMD58 获取 OCR 寄存器 (检查卡是否为 SDHC)
    SDIO_CmdInitStruct.SDIO_CmdIndex = SDIO_CmdIndex_CMD58;  // CMD58: READ_OCR
    SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Long;
    SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStruct);

    // 等待命令完成
    while (SDIO_GetFlagStatus(SDIO_FLAG_CMDSENT) == RESET);

    // 通过读取 OCR 状态来检查卡的类型,成功返回 0,失败返回 -1
    if (SDIO_GetResponse(SDIO_Response_Long) & (1 << 30)) {
        // 如果 OCR 中的第 30 位为 1,则 SD 卡是 SDHC 类型
        return 0;
    }

    return -1;  // 如果初始化失败
}

5. 读取和写入数据

你可以使用 SDIO 发送命令进行数据读取和写入。下面是一个写数据到 SD 卡的示例。

发送读取命令

int SD_Read_Block(uint32_t blockAddr, uint32_t *buffer) {
    SDIO_CmdInitTypeDef SDIO_CmdInitStruct;
    SDIO_DataInitTypeDef SDIO_DataInitStruct;

    // 发送 CMD17 读取一个数据块
    SDIO_CmdInitStruct.SDIO_CmdIndex = SDIO_CmdIndex_CMD17;  // CMD17: READ_SINGLE_BLOCK
    SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Long;
    SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
    SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
    SDIO_SendCommand(&SDIO_CmdInitStruct);

    // 等待命令完成
    while (SDIO_GetFlagStatus(SDIO_FLAG_CMDSENT) == RESET);

    // 设置数据接收
    SDIO_DataInitStruct.SDIO_DataBlockSize = SDIO_DataBlockSize_512b;
    SDIO_DataInitStruct.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
    SDIO_DataInitStruct.SDIO_TransferMode = SDIO_TransferMode_Block;
    SDIO_DataInitStruct.SDIO_TransferWait = SDIO_TransferWait_No;
    SDIO_DataInitStruct.SDIO_TransferSize = SDIO_TransferSize_32b;
    SDIO_DataConfig(&SDIO_DataInitStruct);

    // 等待数据接收
    while (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOEMPTY) == RESET);

    // 读取数据
    for (int i = 0; i < 128; i++) {
        *buffer++ = SDIO_ReadData();
    }

    return 0;
}

posted @ 2025-03-10 13:03  hazy1k  阅读(97)  评论(0)    收藏  举报