18 분 소요

5. FatFS

  • CubeMX에서는 FatFS File System에 대한 Middleware를 제공하고 있다.
  • 만약 MCU의 Peripheral에서 SDIO를 지원한다면 간단히 SD Card를 읽고 쓸 수 있는 FatFS Middleware를 활성화 할 수 있지만, 우리가 사용하는 STM32F401CCU6 MCU는 SDIO를 제공하지 않는다. 그렇지만, SPI Peripheral을 이용하면 SDIO를 구현할 수 있다.

(1) SD Card Pin Map

  • SD Card의 Pin Map은 아래와 같다.
    image

(2) CubeMX

① SPI

  • CubeMX를 통해 다음과 같이 SPI 설정을 한다.
    image
    image
  • 대부분 기본 셋팅이며, 구글링으로 찾아보면 Baud Rate는 10 Mbps 이상도 문제없이 된다고 하는 것 같다. 하지만 안정성을 위해 10 Mbps 근처로 설정하였다.
  • NSS는 software로 설정해야 한다.

② FatFS

  • FatFS 설정을 항목이 상당히 많은데, 빨간 박스 부분의 설정만 바꿔주도록 한다. image

  • 이전 포스트에서 했던 것 처럼 Project Manager의 Advanced Settings에서 Driver Selection을 HAL에서 LL로 바꿔주도록 한다.
  • Middleware는 LL을 지원하지 않으므로 제외하고 SPI, DMA, GPIO만 바꿔주도록 한다.
  • 이제 Code를 Generation 한다.

(3) spi.h/c

spi.h 파일에 DMA를 통한 SPI 통신 API 함수를 선언한다.

spi.h

/**
  ******************************************************************************
  * @file    spi.h
  * @brief   This file contains all the function prototypes for
  *          the spi.c file
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __SPI_H__
#define __SPI_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* USER CODE BEGIN Private defines */

/* USER CODE END Private defines */

void MX_SPI1_Init(void);

/* USER CODE BEGIN Prototypes */
extern void SPI1_DMA_TransmitReceive(u8 *pucTxBuff, u8 *pucRxBuff, u32 uiSize);
/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __SPI_H__ */

spi.c 파일에서는 void MX_SPI1_Init(void) 함수의 끝 부분의 User Code 부분에 DMA와 Peripheral Address를 연결하고, SPI를 Enable 하는 코드를 추가한다. 그리고, SPI Interface API 함수를 추가한다.

spi.c

/**
  ******************************************************************************
  * @file    spi.c
  * @brief   This file provides code for the configuration
  *          of the SPI instances.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "spi.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/* SPI1 init function */
void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  LL_SPI_InitTypeDef SPI_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);

  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);
  /**SPI1 GPIO Configuration
  PA5   ------> SPI1_SCK
  PA6   ------> SPI1_MISO
  PA7   ------> SPI1_MOSI
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_5|LL_GPIO_PIN_6|LL_GPIO_PIN_7;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_5;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* SPI1 DMA Init */

  /* SPI1_RX Init */
  LL_DMA_SetChannelSelection(DMA2, LL_DMA_STREAM_0, LL_DMA_CHANNEL_3);

  LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_0, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

  LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_0, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA2, LL_DMA_STREAM_0, LL_DMA_MODE_NORMAL);

  LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_0, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_0, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_0, LL_DMA_PDATAALIGN_BYTE);

  LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_0, LL_DMA_MDATAALIGN_BYTE);

  LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_0);

  /* SPI1_TX Init */
  LL_DMA_SetChannelSelection(DMA2, LL_DMA_STREAM_3, LL_DMA_CHANNEL_3);

  LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_3, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);

  LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_3, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA2, LL_DMA_STREAM_3, LL_DMA_MODE_NORMAL);

  LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_3, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_3, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_3, LL_DMA_PDATAALIGN_BYTE);

  LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_3, LL_DMA_MDATAALIGN_BYTE);

  LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_3);

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
  SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
  SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT;
  SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;
  SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;
  SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
  SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8;
  SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;
  SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
  SPI_InitStruct.CRCPoly = 10;
  LL_SPI_Init(SPI1, &SPI_InitStruct);
  LL_SPI_SetStandard(SPI1, LL_SPI_PROTOCOL_MOTOROLA);
  /* USER CODE BEGIN SPI1_Init 2 */
  LL_DMA_SetPeriphAddress(DMA2, LL_DMA_STREAM_0, LL_SPI_DMA_GetRegAddr(SPI1));
  LL_DMA_SetPeriphAddress(DMA2, LL_DMA_STREAM_3, LL_SPI_DMA_GetRegAddr(SPI1));
  LL_SPI_Enable(SPI1);
  /* USER CODE END SPI1_Init 2 */

}

/* USER CODE BEGIN 1 */
void SPI1_DMA_TransmitReceive(u8 *pucTxBuff, u8 *pucRxBuff, u32 uiSize)
{
  LL_SPI_DisableDMAReq_TX(SPI1);
  LL_SPI_DisableDMAReq_RX(SPI1);

  LL_DMA_DisableStream(DMA2, LL_DMA_STREAM_3);
  LL_DMA_DisableStream(DMA2, LL_DMA_STREAM_0);

  LL_DMA_ClearFlag_TC3(DMA2);
  LL_DMA_ClearFlag_TC0(DMA2);

  LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_0, uiSize);
  LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_0, (u32)pucRxBuff);

  LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_3, uiSize);
  LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_3, (u32)pucTxBuff);

  LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_0);
  LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_3);  

  LL_SPI_EnableDMAReq_RX(SPI1);
  LL_SPI_EnableDMAReq_TX(SPI1);

  while(LL_DMA_IsActiveFlag_TC0(DMA2) == RESET);
  while(LL_DMA_IsActiveFlag_TC3(DMA2) == RESET);
}
/* USER CODE END 1 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

(4) FatFS 코드 추가

image

  • FatFS 관련 코드 중에서 우리가 손대야 할 부분은 user_diskio.c 코드에서 USER_initialize, USER_status, USER_read, USER_write, USER_ioctl 이 5개의 함수 내용을 채워주는 것이다. 코드를 이 부분에서 직접 작업할 수도 있지만, 내용이 상당히 길고, SPI 함수도 이용해야 하므로, 별도의 파일로 작업하겠다.

  • 다음 코드를 fatfs_sd.c, fatfs_sd.h 프로젝트에 추가한다.

    fatfs_sd.h

#ifndef __FATFS_SD_H__
#define __FATFS_SD_H__

#include "integer.h"    //from FatFs middleware library
#include "diskio.h"     //from FatFs middleware library
#include "ff_gen_drv.h"	//from FatFs middleware library

/* Definitions for MMC/SDC command */
#define CMD0     (0x40 + 0)     /* GO_IDLE_STATE */
#define CMD1     (0x40 + 1)     /* SEND_OP_COND */
#define CMD8     (0x40 + 8)     /* SEND_IF_COND */
#define CMD9     (0x40 + 9)     /* SEND_CSD */
#define CMD10    (0x40 + 10)    /* SEND_CID */
#define CMD12    (0x40 + 12)    /* STOP_TRANSMISSION */
#define CMD16    (0x40 + 16)    /* SET_BLOCKLEN */
#define CMD17    (0x40 + 17)    /* READ_SINGLE_BLOCK */
#define CMD18    (0x40 + 18)    /* READ_MULTIPLE_BLOCK */
#define CMD23    (0x40 + 23)    /* SET_BLOCK_COUNT */
#define CMD24    (0x40 + 24)    /* WRITE_BLOCK */
#define CMD25    (0x40 + 25)    /* WRITE_MULTIPLE_BLOCK */
#define CMD41    (0x40 + 41)    /* SEND_OP_COND (ACMD) */
#define CMD55    (0x40 + 55)    /* APP_CMD */
#define CMD58    (0x40 + 58)    /* READ_OCR */

extern DSTATUS SD_disk_initialize(BYTE pdrv);
extern DSTATUS SD_disk_status(BYTE pdrv);
extern DRESULT SD_disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
extern DRESULT SD_disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
extern DRESULT SD_disk_ioctl(BYTE pdrv, BYTE cmd, void* buff);

#endif /* __FATFS_SD_H__ */

fatfs_sd.c

#include "fatfs_sd.h"
#include "spi.h"

#define MS_TICK               (g_vuiSysTick)  // time tick [msec]
#define TIMEOUT_READY         (500)
#define TIMEOUT_RX            (100)
#define TIMEOUT_STOP_READING  (10)
#define TIMEOUT_INIT          (1000)


#define SELECT()    LL_GPIO_ResetOutputPin(SD_CS_GPIO_Port, SD_CS_Pin)
#define DESELECT()  LL_GPIO_SetOutputPin(SD_CS_GPIO_Port, SD_CS_Pin)

#define TRUE  1
#define FALSE 0
#define bool  BYTE

extern Disk_drvTypeDef  disk;

static volatile DSTATUS Stat = STA_NOINIT;  /* 디스크 상태 Flag*/
static u8 CardType;                         /* SD 타입 0:MMC, 1:SDC, 2:Block addressing */
static u8 PowerFlag = 0;                    /* Power 상태 Flag */

/* SPI 데이터 전송 */
static void SPI_TxByte(BYTE bData)
{
  u8 ucDummy;

  SPI1_DMA_TransmitReceive((u8 *)&bData, &ucDummy, 1);
}

/* SPI 데이터 송수신 리턴형 함수 */
static u8 SPI_RxByte(void)
{
  u8 ucDummy = 0xFF;
  u8 ucData = 0;

  SPI1_DMA_TransmitReceive(&ucDummy, &ucData, 1);

  return ucData;
}

/* SPI 데이터 송수신 포인터형 함수 */
static void SPI_RxBytePtr(u8 *pucBuff)
{
  *pucBuff = SPI_RxByte();
}

/* SD카드 Ready 대기 */
static u8 SD_ReadyWait(void)
{
  u8 res;
  u32 timeout;

  timeout = MS_TICK;
  SPI_RxByte();
  do {
    /* 0xFF 값이 수신될 때 까지 SPI 통신 (TIMEOUT_READY) */
    res = SPI_RxByte();
  } while((res != 0xFF) && ((MS_TICK - timeout) < TIMEOUT_READY));

  return res;
}

/* 전원 켜기 */
static void SD_PowerOn(void)
{
  u8 cmd_arg[6];
  u32 Count = 0x1FFF;

  /* Deselect 상태에서 SPI 메시지를 전송하여 대기상태로 만든다. */
  DESELECT();
  LL_mDelay(10);

  for(u32 i = 0; i < 10; i++) {
    SPI_TxByte(0xFF);
  }

  /* SPI Chips Select */
  SELECT();
  LL_mDelay(10);

  /* 초기 GO_IDLE_STATE 상태 전환 */
  cmd_arg[0] = (CMD0 | 0x40);
  cmd_arg[1] = 0;
  cmd_arg[2] = 0;
  cmd_arg[3] = 0;
  cmd_arg[4] = 0;
  cmd_arg[5] = 0x95;

  /* 명령 전송 */
  for(u32 i = 0; i < 6; i++) {
    SPI_TxByte(cmd_arg[i]);
  }

  /* 응답 대기 */
  while((SPI_RxByte() != 0x01) && Count) {
    Count--;
  }

  DESELECT();
  LL_mDelay(10);

  SPI_TxByte(0XFF);

  PowerFlag = 1;
}

/* 전원 끄기 */
static void SD_PowerOff(void)
{
  PowerFlag = 0;
}

/* 전원 상태 확인 */
static u8 SD_CheckPower(void)
{
  /*  0=off, 1=on */
  return PowerFlag;
}

/* 데이터 패킷 수신 */
static bool SD_RxDataBlock(BYTE *buff, UINT btr)
{
  u8 token;
  u32 timeout;

  /* 응답 대기 (timeout: TIMEOUT_RX) */
  timeout = MS_TICK;
  do {
    token = SPI_RxByte();
  } while((token == 0xFF) && ((MS_TICK - timeout) < TIMEOUT_RX));

  /* 0xFE 이외 Token 수신 시 에러 처리 */
  if(token != 0xFE) return FALSE;

  /* 버퍼에 데이터 수신 */
  do {
    SPI_RxBytePtr(buff++);
    SPI_RxBytePtr(buff++);
  } while(btr -= 2);

  SPI_RxByte(); /* CRC 무시 */
  SPI_RxByte();

  return TRUE;
}

/* 데이터 전송 패킷 */
#if _READONLY == 0
static BYTE SD_TxDataBlock(const BYTE *buff, BYTE token)
{
  u8 resp, wc;
  u8 i = 0;

  /* SD카드 준비 대기 */
  if(SD_ReadyWait() != 0xFF) return FALSE;

  /* 토큰 전송 */
  SPI_TxByte(token);

  /* 데이터 토큰인 경우 */
  if(token != 0xFD) {
    wc = 0;

    /* 512 바이트 데이터 전송 */
    do {
      SPI_TxByte(*buff++);
      SPI_TxByte(*buff++);
    } while(--wc);

    SPI_RxByte();       /* CRC 무시 */
    SPI_RxByte();

    /* 데이트 응답 수신 */        
    while(i <= 64) {
      resp = SPI_RxByte();

      /* 에러 응답 처리 */
      if((resp & 0x1F) == 0x05) break;
      i++;
    }

    /* SPI 수신 버퍼 Clear */
    while(SPI_RxByte() == 0);
  }
  if((resp & 0x1F) == 0x05)  return TRUE;
  else            return FALSE;
}
#endif /* _READONLY */

/* CMD 패킷 전송 */
static BYTE SD_SendCmd(BYTE cmd, DWORD arg)
{
  u8 crc, res;
  u32 timeout;

  /* SD카드 대기 */
  if(SD_ReadyWait() != 0xFF) return 0xFF;

  /* 명령 패킷 전송 */
  SPI_TxByte(cmd);        /* Command */
  SPI_TxByte((BYTE) (arg >> 24));  /* Argument[31..24] */
  SPI_TxByte((BYTE) (arg >> 16));  /* Argument[23..16] */
  SPI_TxByte((BYTE) (arg >> 8));  /* Argument[15..8] */
  SPI_TxByte((BYTE) arg);      /* Argument[7..0] */

  /* 명령별 CRC 준비 */
  crc = 0;  
  if (cmd == CMD0) crc = 0x95; /* CRC for CMD0(0) */
  if (cmd == CMD8) crc = 0x87; /* CRC for CMD8(0x1AA) */

  /* CRC 전송 */
  SPI_TxByte(crc);

  /* CMD12 Stop Reading 명령인 경우에는 응답 바이트 하나를 버린다 */
  if (cmd == CMD12) SPI_RxByte();

  /* TIMEOUT_STOP_READING 내에 정상 데이터를 수신한다. */
  timeout = MS_TICK;
  do {
    res = SPI_RxByte();
  } while((res & 0x80) && ((MS_TICK - timeout) < TIMEOUT_STOP_READING));

  return res;
}

/*-----------------------------------------------------------------------
  fatfs에서 사용되는 Global 함수들
  user_diskio.c 파일에서 사용된다.
-----------------------------------------------------------------------*/

/* SD카드 초기화 */
DSTATUS SD_disk_initialize(BYTE drv)
{
  u8 n, type, ocr[4];
  u32 timeout;

  /* 한종류의 드라이브만 지원 */
  if(drv) return STA_NOINIT;

  /* SD카드 미삽입 */
  if(Stat & STA_NODISK) return Stat;

  /* SD카드 Power On */
  SD_PowerOn();

  /* SPI 통신을 위해 Chip Select */
  SELECT();
  LL_mDelay(10);

  /* SD카드 타입변수 초기화 */
  type = 0;

  /* Idle 상태 진입 */
  if(SD_SendCmd(CMD0, 0) == 1) {
    /* SD 인터페이스 동작 조건 확인 (timeout: 1 sec) */
    timeout = MS_TICK;
    if(SD_SendCmd(CMD8, 0x1AA) == 1) {
      /* SDC Ver2+ */
      for(n = 0; n < 4; n++) {
        ocr[n] = SPI_RxByte();
      }

      if(ocr[2] == 0x01 && ocr[3] == 0xAA) {
        /* 2.7-3.6V 전압범위 동작 */
        do {
          if(SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 1UL << 30) == 0)
            break; /* ACMD41 with HCS bit */
        } while((MS_TICK - timeout) < TIMEOUT_INIT);

        if(((MS_TICK - timeout) < TIMEOUT_INIT) && SD_SendCmd(CMD58, 0) == 0) {
          /* Check CCS bit */
          for(n = 0; n < 4; n++) {
            ocr[n] = SPI_RxByte();
          }
          type = (ocr[0] & 0x40) ? 6 : 2;
        }
      }
    }
    else {
      /* SDC Ver1 or MMC */
      type = (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) <= 1) ? 2 : 1; /* SDC : MMC */
      do {
        if(type == 2) {
          if(SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) == 0)
            break; /* ACMD41 */
        }
        else {
          if(SD_SendCmd(CMD1, 0) == 0)
            break; /* CMD1 */
        }
      } while((MS_TICK - timeout) < TIMEOUT_INIT);

      if((TIMEOUT_INIT <= (MS_TICK - timeout)) || SD_SendCmd(CMD16, 512) != 0) {
        /* 블럭 길이 선택 */
        type = 0;
      }
    }
  }

  CardType = type;

  DESELECT();
  LL_mDelay(10);

  SPI_RxByte(); /* Idle 상태 전환 (Release DO) */

  if(type) {
    /* Clear STA_NOINIT */
    Stat &= ~STA_NOINIT;
  }
  else {
    /* Initialization failed */
    for(u32 i = 0; i < _VOLUMES; i++) {
      disk.is_initialized[i] = 0;
    }
    SD_PowerOff();
  }

  return Stat;
}

/* 디스크 상태 확인 */
DSTATUS SD_disk_status(BYTE drv)
{
  if(drv) return STA_NOINIT;
  return Stat;
}

/* 섹터 읽기 */
DRESULT SD_disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
{
  if(pdrv || !count)    return RES_PARERR;
  if(Stat & STA_NOINIT)  return RES_NOTRDY;

  if(!(CardType & 4))    sector *= 512;      /* 지정 sector를 Byte addressing 단위로 변경 */

  SELECT();
  LL_mDelay(10);

  if(count == 1) {
    /* 싱글 블록 읽기 */
    if((SD_SendCmd(CMD17, sector) == 0) && SD_RxDataBlock(buff, 512))
      count = 0;
  }
  else {
    /* 다중 블록 읽기 */
    if(SD_SendCmd(CMD18, sector) == 0) {
      do {
        if(!SD_RxDataBlock(buff, 512))
          break;

        buff += 512;
      } while(--count);

      /* STOP_TRANSMISSION, 모든 블럭을 다 읽은 후, 전송 중지 요청 */
      SD_SendCmd(CMD12, 0);
    }
  }

  DESELECT();
  LL_mDelay(10);

  SPI_RxByte(); /* Idle 상태(Release DO) */
  if(count) {
    for(u32 i = 0; i < _VOLUMES; i++) {
      disk.is_initialized[i] = 0;
    }
    return RES_ERROR;
  }
  else {
    return RES_OK;
  }
}

/* 섹터 쓰기 */
#if _READONLY == 0
DRESULT SD_disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
{
  if(pdrv || !count)    return RES_PARERR;
  if(Stat & STA_NOINIT)  return RES_NOTRDY;
  if(Stat & STA_PROTECT)  return RES_WRPRT;
  if(!(CardType & 4))    sector *= 512; /* 지정 sector를 Byte addressing 단위로 변경 */

  SELECT();
  LL_mDelay(10);

  if(count == 1) {
    /* 싱글 블록 쓰기 */
    if((SD_SendCmd(CMD24, sector) == 0) && SD_TxDataBlock(buff, 0xFE))
      count = 0;
  }
  else {
    /* 다중 블록 쓰기 */
    if(CardType & 2) {
      SD_SendCmd(CMD55, 0);
      SD_SendCmd(CMD23, count); /* ACMD23 */
    }

    if(SD_SendCmd(CMD25, sector) == 0) {
      do {
        if(!SD_TxDataBlock(buff, 0xFC))
          break;

        buff += 512;
      } while(--count);

      if(!SD_TxDataBlock(0, 0xFD)) {
        count = 1;
      }
    }
  }

  DESELECT();
  LL_mDelay(10);

  SPI_RxByte();

  return count ? RES_ERROR : RES_OK;
}
#endif /* _READONLY */

/* 기타 함수 */
DRESULT SD_disk_ioctl(BYTE drv, BYTE ctrl, void *buff)
{
  DRESULT res;
  BYTE n, csd[16], *ptr = buff;
  WORD csize;

  if (drv) return RES_PARERR;

  res = RES_ERROR;

  if(ctrl == CTRL_POWER) {
    switch (*ptr) {
    case 0:
      if(SD_CheckPower())
        SD_PowerOff();  /* Power Off */
      res = RES_OK;
      break;

    case 1:
      SD_PowerOn();    /* Power On */
      res = RES_OK;
      break;

    case 2:
      *(ptr + 1) = (BYTE) SD_CheckPower();
      res = RES_OK;    /* Power Check */
      break;

    default:
      res = RES_PARERR;
      break;
    }
  }
  else {
    if(Stat & STA_NOINIT)
      return RES_NOTRDY;

    SELECT();
    LL_mDelay(10);

    switch(ctrl) {
    case GET_SECTOR_COUNT:
      /* SD카드 내 Sector의 개수 (DWORD) */
      if((SD_SendCmd(CMD9, 0) == 0) && SD_RxDataBlock(csd, 16)) {
        if((csd[0] >> 6) == 1) {
          /* SDC ver 2.00 */
          csize = csd[9] + ((WORD) csd[8] << 8) + 1;
          *(DWORD*) buff = (DWORD) csize << 10;
        }
        else {
          /* MMC or SDC ver 1.XX */
          n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
          csize = (csd[8] >> 6) + ((WORD) csd[7] << 2) + ((WORD) (csd[6] & 3) << 10) + 1;
          *(DWORD*) buff = (DWORD) csize << (n - 9);
        }
        res = RES_OK;
      }
      break;

    case GET_SECTOR_SIZE:
      /* 섹터의 단위 크기 (WORD) */
      *(WORD*) buff = 512;
      res = RES_OK;
      break;

    case CTRL_SYNC:
      /* 쓰기 동기화 */
      if(SD_ReadyWait() == 0xFF)
        res = RES_OK;
      break;

    case MMC_GET_CSD:
      /* CSD 정보 수신 (16 bytes) */
      if(SD_SendCmd(CMD9, 0) == 0 && SD_RxDataBlock(ptr, 16))
        res = RES_OK;
      break;

    case MMC_GET_CID:
      /* CID 정보 수신 (16 bytes) */
      if(SD_SendCmd(CMD10, 0) == 0 && SD_RxDataBlock(ptr, 16))
        res = RES_OK;
      break;

    case MMC_GET_OCR:
      /* OCR 정보 수신 (4 bytes) */
      if(SD_SendCmd(CMD58, 0) == 0) {
        for(n = 0; n < 4; n++) {
          *ptr++ = SPI_RxByte();
        }

        res = RES_OK;
      }
      break;

    default:
      res = RES_PARERR;
      break;
    }

    DESELECT();
    LL_mDelay(10);

    SPI_RxByte();
  }

  return res;
}

  • 코드를 추가하고, user_diskio.c 에서 추가한 코드의 헤더파일을 include 하고, 앞서 언급한 5개 함수 안에서 작성한 함수를 그대로 Call 해준다.

    user_diskio.c

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
  * @file    user_diskio.c
  * @brief   This file includes a diskio driver skeleton to be completed by the user.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
 /* USER CODE END Header */

#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/*
 * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)
 * To be suppressed in the future.
 * Kept to ensure backward compatibility with previous CubeMx versions when
 * migrating projects.
 * User code previously added there should be copied in the new user sections before
 * the section contents can be deleted.
 */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif

/* USER CODE BEGIN DECL */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"
#include "fatfs_sd.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* USER CODE END DECL */

/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
  DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef  USER_Driver =
{
  USER_initialize,
  USER_status,
  USER_read,
#if  _USE_WRITE
  USER_write,
#endif  /* _USE_WRITE == 1 */
#if  _USE_IOCTL == 1
  USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_initialize (
	BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
  /* USER CODE BEGIN INIT */
    Stat = SD_disk_initialize(pdrv);
    return Stat;
  /* USER CODE END INIT */
}

/**
  * @brief  Gets Disk Status
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_status (
	BYTE pdrv       /* Physical drive number to identify the drive */
)
{
  /* USER CODE BEGIN STATUS */
    Stat = SD_disk_status(pdrv);
    return Stat;
  /* USER CODE END STATUS */
}

/**
  * @brief  Reads Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT USER_read (
	BYTE pdrv,      /* Physical drive nmuber to identify the drive */
	BYTE *buff,     /* Data buffer to store read data */
	DWORD sector,   /* Sector address in LBA */
	UINT count      /* Number of sectors to read */
)
{
  /* USER CODE BEGIN READ */
    return SD_disk_read(pdrv, buff, sector, count);
  /* USER CODE END READ */
}

/**
  * @brief  Writes Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT USER_write (
	BYTE pdrv,          /* Physical drive nmuber to identify the drive */
	const BYTE *buff,   /* Data to be written */
	DWORD sector,       /* Sector address in LBA */
	UINT count          /* Number of sectors to write */
)
{
  /* USER CODE BEGIN WRITE */
  /* USER CODE HERE */
    return SD_disk_write(pdrv, buff, sector, count);
  /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  I/O control operation
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT USER_ioctl (
	BYTE pdrv,      /* Physical drive nmuber (0..) */
	BYTE cmd,       /* Control code */
	void *buff      /* Buffer to send/receive control data */
)
{
  /* USER CODE BEGIN IOCTL */
    DRESULT res = SD_disk_ioctl(pdrv, cmd, buff);
    return res;
  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

(5) FatFS 사용법

  • FatFS를 사용하는 방법을 간단히 설명하면 다음과 같다. (1) f_mount 함수를 통해 SD Card를 mount 한다. 이때, 두 번째 인자에 해당하는 path는 “0:” 문자열로 넣으면 된다. (2) f_open 함수를 통해서 읽거나 쓰고 싶은 파일을 open 한다. 이때, 세 번째 인자 mode는 “FA_” 접두사로 선언된 전처리 상수 중에서 선택하여 OR 연산(|)으로 조합하여 사용할 수 있다.
    #define	FA_READ			0x01
    #define	FA_WRITE		0x02
    #define	FA_OPEN_EXISTING	0x00
    #define	FA_CREATE_NEW		0x04
    #define	FA_CREATE_ALWAYS	0x08
    #define	FA_OPEN_ALWAYS		0x10
    #define	FA_OPEN_APPEND		0x30
    

    (3) f_read, f_write, f_printf 등의 함수를 이용하여 입출력 동작을 할 수 있다. (4) 파일 작업 완료 후에는 f_close 함수를 통해 파일을 닫아준다. (5) f_mount 함수의 첫 번째 인자를 NULL로 넣어주면 unmount가 된다고 내부 파일에 주석이 달려 있지만, 코드를 분석해 보니 실질적으로 수행하는 작업은 없는 것으로 확인하였다.

  • main 함수에서 SD Card를 테스트 해 볼 수 있도록 코드를 추가해 보자.

    main.c

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  static FATFS FS;
  static FIL fil;
  static DIR dir;
  static FILINFO file_list[10];
  FRESULT fres;
  u32 uiFileCnt = 0;
  u8 ucReadBuff[256];
  u32 uiReadFileSize;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_SPI1_Init();
  MX_USB_DEVICE_Init();
  MX_FATFS_Init();
  /* USER CODE BEGIN 2 */
  VCP_Init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    ucReadBuff[0] = 0;
    while(1) {
      ucVCP_ReadByte(ucReadBuff);
      if(('\r' == ucReadBuff[0]) || ('\n' == ucReadBuff[0])) {
        break;
      }
    }
    VCP_Printf("< SD Card FatFS test >\r\n");
    fres = f_mount(&FS, "0:", 1);
    if(FR_OK == fres) {
      // Search File in SD Card
      if(f_findfirst(&dir, file_list, "0:", "*.*") == FR_OK) {
        while(('\0' != file_list[uiFileCnt].fname[0]) && (uiFileCnt < 10)) {
          uiFileCnt++;
          if(f_findnext(&dir, &file_list[uiFileCnt]) != FR_OK) {
            break;
          }
        }
        // Read File Contents
        for(u32 i = 0; i < uiFileCnt; i++) {
          VCP_Printf("-------------------------------\r\n");
          VCP_Printf("File Name: ");
          VCP_Printf(file_list[i].fname);
          VCP_Printf("\r\n");
          if(f_open(&fil, file_list[i].fname, FA_READ) == FR_OK) {
            if(FR_OK == f_read(&fil, ucReadBuff, 256, &uiReadFileSize)) {
              VCP_Printf("Contents: \r\n");
              uiVCP_WriteBuff(ucReadBuff, uiReadFileSize);
              VCP_Printf("\r\n-------------------------------\r\n\r\n");
            }
            else {
              VCP_Printf("Read Fail\r\n");
            }
            f_close(&fil);
          }
          else {
            VCP_Printf("Open Fail(Read)\r\n");
          }
        }
        if(0 == uiFileCnt) {
          VCP_Printf("No File\r\n");
        }
      }
      else {
        VCP_Printf("No File\r\n");
      }
      // Write File in SD Card
      if(f_open(&fil, "0:Write_test.txt", FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_APPEND) == FR_OK) {
        f_printf(&fil, "Write Test\r\nHello World");
        f_close(&fil);
      }
    }
    else {
      VCP_Printf("Mount Fail (%d)\r\n", fres);
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
  • 코드의 내용: VCP Port가 열리고, Enter Key(‘\r’ 혹은 ‘\n’)가 입력되면 SD Card를 읽어서 최상위 디렉토리에 있는 파일들을 최대 10개 읽어서 VCP 터미널에 출력하고, “Write Test\r\nHello World” 내용을 담은 Write_test.txt 파일을 저장하는 코드이다.

  • 코드를 다운로드 하고, SD Card를 FAT32 혹은 exFAT 파일 시스템으로 포멧하고, 테스트로 읽어 볼 파일을 저장한다.
    image image

  • 이제, SD Card를 장치에 꽂고, putty 터미널을 열고 Enter를 입력해 보자. image
  • 내용이 잘 읽어지는 것을 확인할 수 있다.
    코드 실행 후, 다시 PC에 SD Card를 연결해 보면, Write_test.txt 파일이 생성된 것을 알 수 있다.
    image

카테고리:

업데이트:

댓글남기기