STM32F4 개발보드 프로그래밍 ⑤ (FatFS + SPI + SD Card)
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은 아래와 같다.
(2) CubeMX
① SPI
- CubeMX를 통해 다음과 같이 SPI 설정을 한다.
- 대부분 기본 셋팅이며, 구글링으로 찾아보면 Baud Rate는 10 Mbps 이상도 문제없이 된다고 하는 것 같다. 하지만 안정성을 위해 10 Mbps 근처로 설정하였다.
- NSS는 software로 설정해야 한다.
② FatFS
-
FatFS 설정을 항목이 상당히 많은데, 빨간 박스 부분의 설정만 바꿔주도록 한다.
- 이전 포스트에서 했던 것 처럼 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>© 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>© 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 코드 추가
-
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>© 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 파일 시스템으로 포멧하고, 테스트로 읽어 볼 파일을 저장한다.
- 이제, SD Card를 장치에 꽂고, putty 터미널을 열고 Enter를 입력해 보자.
- 내용이 잘 읽어지는 것을 확인할 수 있다.
코드 실행 후, 다시 PC에 SD Card를 연결해 보면, Write_test.txt 파일이 생성된 것을 알 수 있다.
댓글남기기