STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)

普通方式控制SD

1、打开STM32CubeMX新建工程,选择芯片。

2、配置基本外设:

(1)配置SYS,打开调试口。我用Jlink-OB,就选SYS下面的Debug选项中的Trace Asynchronous Sw选项,根据实际自行选择。这一步很重要,如果忘记配置,再刷程序就很麻烦了。将Timebase Source选为TIM6,这一步尽量做,避免以后使用FreeRtos时发生冲突。

(2)配置RCC。展开后HSE选为Crystal/Ceramic Resonator。

(3)启用Uart1。Mode改为Asynchronous 。

(4)启用SDIO总线。在SDMMC1中,配置Mode为SD 4 bits Wide bus。

到此,基本外设IO配置完毕。

STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)

3、配置时钟。

时钟源选为HSE,SDMMC1时钟不得超过48MHz,注意图中的3个箭头指向位置。

STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)

4、详细配置

在Configuration选项卡中,点击SDMMC1,配置如下图。

STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)

注意,分频系数(图中蓝色选中部分)可以使用默认值0,跟卡的体质有关,建议先使用4分频,也就是12M作为时钟,适应大部分sd卡的体质。后期调出来可以改回0来使用高速的读写。

USART1保持默认即可。

5、工程配置与源码生成。

选择project --> settings。输入工程名称,保存。然后生成源码,打开工程。

STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)

6、源码修改

打开工程后,打开main.c文件。

我们先添加uart1的printf函数的支持。

(1)先添加头文件。

在/* USER CODE BEGIN Includes */后,添加

#include "stdio.h"

 

(2)添加支持printf函数

在/* USER CODE BEGIN 0 */后添加如下源码:

//加入以下代码,支持printf函数,使用printf函数从串口输出。
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	while((USART1->ISR&0X40)==0);//循环发送,直到发送完毕   
	USART1->TDR=(uint8_t)ch;      
	return ch;
}
#endif 

 

接下来,添加测试函数,接着上一步位置加入代码:

/**
  * 函数功能: 检查缓冲区的数据是否为0xff或0
  * 输入参数: pBuffer:要比较的缓冲区的指针
  *           BufferLength:缓冲区长度
  * 返 回 值: PASSED:缓冲区的数据全为0xff或0
  *           FAILED:缓冲区的数据至少有一个不为0xff或0 
  * 说    明: 无
  */
TestStatus eBuffercmp(uint32_t* pBuffer, uint32_t BufferLength)
{
  while (BufferLength--)
  {
    /* SD卡擦除后的可能值为0xff或0 */
    if ((*pBuffer != 0xFFFFFFFF) && (*pBuffer != 0))
    {
      return FAILED;
    }
    pBuffer++;
  }
  return PASSED;
}

/**
  * 函数功能: SD卡擦除测试
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SD_EraseTest(void)
{
	/* 第1个参数为SD卡句柄,第2个参数为擦除起始地址,第3个参数为擦除结束地址 */
  sd_status=HAL_SD_Erase(&hsd1,WRITE_READ_ADDRESS,WRITE_READ_ADDRESS+NUMBER_OF_BLOCKS*4);
   printf("erase status:%d\r\n",sd_status);

	HAL_Delay(500);
  if (sd_status == HAL_OK)
  {	
    /* 读取刚刚擦除的区域 */
    sd_status = HAL_SD_ReadBlocks(&hsd1,(uint8_t *)Buffer_Block_Rx,WRITE_READ_ADDRESS,NUMBER_OF_BLOCKS,0xffff);
    printf("erase read status:%d\r\n",sd_status);
    /* 把擦除区域读出来对比 */
    test_status = eBuffercmp(Buffer_Block_Rx,BLOCK_SIZE*NUMBER_OF_BLOCKS);

    if(test_status == PASSED)
      printf("》擦除测试成功!\r\n" ); 
    else	  
      printf("》擦除不成功,数据出错!\r\n" );      
  }
  else
  {
    printf("》擦除测试失败!部分SD不支持擦除,只要读写测试通过即可\r\n" );
  }
}

/**
  * 函数功能: 在缓冲区中填写数据
  * 输入参数: pBuffer:要填充的缓冲区
  *           BufferLength:要填充的大小
  *           Offset:填在缓冲区的第一个值 
  * 返 回 值: 无
  * 说    明: 无
  */
void Fill_Buffer(uint32_t *pBuffer, uint32_t BufferLength, uint32_t Offset)
{
  uint32_t index = 0;
  /* 填充数据 */
  for (index = 0; index < BufferLength; index++ )
  {
    pBuffer[index] = index + Offset;
  }
}

/**
  * 函数功能: 比较两个缓冲区中的数据是否相等
  * 输入参数: pBuffer1:要比较的缓冲区1的指针
  *           pBuffer2:要比较的缓冲区2的指针
  *           BufferLength:缓冲区长度
  * 返 回 值: PASSED:相等
  *           FAILED:不等
  * 说    明: 无
  */
TestStatus Buffercmp(uint32_t* pBuffer1, uint32_t* pBuffer2, uint32_t BufferLength)
{
  while (BufferLength--)
  {
    if(BufferLength%50==0)
    {
      printf("buf:0x%08X - 0x%08X\r\n",*pBuffer1,*pBuffer2);
    }
    if (*pBuffer1 != *pBuffer2)
    {
      return FAILED;
    }
    pBuffer1++;
    pBuffer2++;
  }
  return PASSED;
}

/**
  * 函数功能: SD卡读写测试
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
void SD_Write_Read_Test(void)
{  
	int i,j = 0;
  /* 填充数据到写缓存 */
  Fill_Buffer(Buffer_Block_Tx,BLOCK_SIZE*NUMBER_OF_BLOCKS, 0x6666);
  
  /* 往SD卡写入数据 */
  sd_status = HAL_SD_WriteBlocks(&hsd1,(uint8_t *)Buffer_Block_Tx,WRITE_READ_ADDRESS,NUMBER_OF_BLOCKS,0xffff);
  printf("write status:%d\r\n",sd_status);
			
  HAL_Delay(500);
  /* 从SD卡读取数据 */
  sd_status = HAL_SD_ReadBlocks(&hsd1,(uint8_t *)Buffer_Block_Rx,WRITE_READ_ADDRESS,NUMBER_OF_BLOCKS,0xffff);
  printf("read status:%d\r\n",sd_status);
  
  /* 比较数据 */
  test_status = Buffercmp(Buffer_Block_Tx, Buffer_Block_Rx, BLOCK_SIZE*NUMBER_OF_BLOCKS/4);	//比较
  if(test_status == PASSED)
	{
    printf("》读写测试成功!\r\n" );
		
		for(i=0;i<BLOCK_SIZE*NUMBER_OF_BLOCKS/4;i++)
		{
			if(j==8)
			{
				printf("\r\n");
				j=0;
			}
			
			printf("%08x   ",Buffer_Block_Rx[i]);
			j++;
		}
		printf("\r\n");
	}
  else  
  	printf("》读写测试失败!\r\n " );  
}

(4)到文件开头的地方

/* USER CODE BEGIN PV */

/* Private variables ---------------------------------------------------------*/

 

/* USER CODE END PV */

之间加入变量,代码如下:

typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus;
/* 私有宏定义 ----------------------------------------------------------------*/
#define BLOCK_SIZE            512         // SD卡块大小     
#define NUMBER_OF_BLOCKS      8           // 测试块数量(小于15)
#define WRITE_READ_ADDRESS    0x00002000  // 测试读写地址

/* 私有变量 ------------------------------------------------------------------*/
__align(4) uint32_t Buffer_Block_Tx[BLOCK_SIZE*NUMBER_OF_BLOCKS]; // 写数据缓存
__align(4) uint32_t Buffer_Block_Rx[BLOCK_SIZE*NUMBER_OF_BLOCKS]; // 读数据缓存
HAL_StatusTypeDef sd_status;    // HAL库函数操作SD卡函数返回值:操作结果
TestStatus test_status;           // 数据测试结果

 

(3)到main函数中,找到

/* USER CODE BEGIN 2 */与  /* USER CODE END 2 */之间的位置,加入代码

  SD_EraseTest();
  SD_Write_Read_Test();

7、配置工程调试、下载

此部分只放图,不解释。

STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)

STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)

STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)

  1. 编译工程,打开串口调试助手,接好串口线,将程序烧录到MCU中。
  2. 测试结果

STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)

到此,普通SDIO驱动完成。

如果使用DMA,请在完成本节内容的基础上,做下一节的内容。

附加

可以将前面设置的时钟分频系数改为0测试。如果能测试成功,可以使用更高的速度获取更好的性能,否则就适当加大分频系数直到能用。

STM32L4新版HAL库SDIO(DMA)、FatFs使用教程(一)