microSD 쉴드 퀵 스타트 가이드

microSD 쉴드

디지털카메라에서 스마트폰에 이르기까지 그리고 노트북에도 SD카드는 많이 사용되는 저장매체입니다. microSD카드를 이용하면 아두이노가 많은 정보를 파일시스템에 저장(데이터로거에 응용가능)할 수 있게 할 수 있을 뿐 아니라 많은 데이터를 읽어 올 수 있습니다. 본 게시물에서는 microSD쉴드를 동작시키는 방법에 대해 살펴보겠습니다.

microSD쉴드로 무었을 할수 있을까요?

아두이노는 보드상에 메모리를 가지고 있지만 그 메모리는 정말 작은 용량의 메모리입니다. 약 1kbyte이니까요. 만약 MP3플레이어같은 것을 만들려고 계획중이라면 또는 사진 뷰어나 데이터로거를 만들려고 생각중이라면 1kbyte보다는 훨씬 많은 메모리를 필요로 합니다. microSD카드는 읽고 쓸수 있는 좋은 메모리로 FAT16이나 FAT32이와 같은 파일시스템을 구현할 수 있어 파일을 쉽게 생성하고 읽고 쓸수 있게 만들수 있습니다. 또 256MB에서 32GB이상까지의 메모리 크기를 사용할 수 있다는 것도 장점입니다. 아래는 예제 스케치 코드입니다. microSD카드의 사용법을 익힐 수 있습니다.

어떻게 microSD 쉴드를 동작시킬 수 있나요?

microSD 쉴드의 동작법을 알기 위해 질문과 답변 형식으로 설명하도록 하겠습니다.


Q)파일생성은 어떻게 하나요?

microSD카드에 데이터를 쓰기 위해서는 파일에 대한 접근이 필요합니다. 만약 파일이 존재하지 않는다면 반드시 파일을 생성시켜야 합니다. 그리고 파일을 생성시키기 전에는 microSD카드는 FAT파일시스템을 사용하기 위해 초기화 되어 있어야 합니다. SdFat library 은 다행히도 대부분의 작업을 할 수 있게 만들어 줍니다. 먼저 FAT 라이브러리가 동작하는데 필요한 객체를 만들어 줍니다. 이 코드는 반드시 스케치의 loop와 setup섹션앞의 시작부분에 위치하여야 합니다.

//Create the variables to be used by SdFat Library
char name[] = "Test.txt";//Create an array that contains the name 
                           of our file.
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

FAT 파일시스템과 볼륨을 초기화하기 위해 루트 디렉토리를 열어야 합니다. 이러기 위해 필요한 것은 아래의 코드를 setup 섹션에 위치시키는 것입니다.

pinMode(10, OUTPUT);       //Pin 10 must be set as an output for the SD
                             communication to work.   
card.init();               //Initialize the SD card and configure the 
                             I/O pins.
volume.init(card);         //Initialize a volume on the SD card.
root.openRoot(volume);     //Open the root directory in the volume.

이제 파일을 생성할 수 있습니다. 파일생성을 위해 루트 객체와 파일 이름이 필요합니다. 이 두가지는 스케치 코드 앞부분에서 만들어 졌습니다. 또 O_CREATE, O_APPEND, O_WRITE 상수를 사용하여 스케치에게 파일을 write모드로 생성할 것임을 말해줍니다. 그리고 데이터를 파일의 끝에 추가 할 것임을 말해줍니다. 위에서 언급된 const상수는 SdFat 라이브러리에 정의되어 있씁니다. 이 상수에 대해 더 자세한 사항을 알고 싶으시다면 라이브러리 폴더 내에 위치하여 있는 문서를 참고 하십시오. 아래의 코드는 루트 디렉토리에 파일을 생성합니다.

file.open(root, name, O_CREAT | O_APPEND | O_WRITE);    //Open or
create the file 'name' in 'root' for writing to the end of the file


파일을 어떻게 열수 있나요?

파일을 읽고 쓰기위해서는 파일이 먼저 열려 있어야 합니다. 파일을 열려면 당연히 열려는 파일의 이름을 알고 있어야겠죠. 파일을 여는 방법은 여러가지가 있습니다. 위의 예제에서 처럼 파일이 생성된 후에 열릴 수 있고, 읽기모드로 열릴 수도 있으며, 쓰기 모드로 열수도 있습니다. 파일을 열때는 또 파일의 어디부터 읽고 쓸것인지 파일위치를 알려주는 인덱스를 지정해야 합니다. 예를들어 파일의 시작부분부터 읽고 싶다면 아래와 같이 파일을 열수 있습니다.

file.open(root, name, O_READ);    //Open the file in read mode.

하지만 파일의 끝에 무엇인가를 쓰고 싶다면 아래와 같이 파일을 열어야 합니다.

file.open(root, name, O_WRITE | O_APPEND);  //Open the file in
        write mode and append the data to the end of the file.

그리고 읽거나 쓰기를 마치면 항상 파일은 닫아 주어야 합니다. 만약 파일이 닫아지지 않는다면 쓰기를 마친 데이터가 적절하게 저장이 되지 않을 수 있습니다. 파일을 사용한 후에는 항상 닫아 주는 것이 파일시스템의 신뢰성을 위해 중요합니다. 파일은 아래와 같은 함수로 닫을 수 있습니다.

file.close();  //Close the file


열고 싶은 파일의 파일이름을 모르는 어떻게 해야 하나요?

만약 MP3플레이어와 사진뷰어와 같은 것을 만들려고 한다면, 아마도 microSD카드에 있는 파일이름을 모두 알 수는 없을 것입니다. 이런 경우에 파일의 이름을 몰라도 파일을 여는 것이 필요합니다. SdFat라이브러리는 FAT파일시스템의 위치로 파일을 여는 방법을 제공하고 있습니다. 파일을 열기 위해 필요한 것은 파일의 위치만 알면 됩니다. 아래의 코드는 SD카드의 모든 파일을 열고 정보를 프린트하는 예제입니다. 한번 살펴보겠습니다. (SD카드에 있는 파일은 텍스트파일이라 가정)

while(root.readDir(directory)>0){
    file.open(root, root.curPosition()/32-1, O_READ);
    in_char=file.read();              //Get the first byte in
                                        the file.
    //Keep reading characters from the file until we get an
     error or reach the end of the file. (This will output the 
     entire contents of the file).
    while(in_char >=0){            //If the value of the
                                     character is less than 0 we've
                                     reached the end of the file.
        Serial.print(in_char);    //Print the current character
        in_char=file.read();      //Get the next character
    }
    file.close();    //Close the file
    Serial.println();
}

루프가 시작되기전에 root는 비어있는 파일구조입니다. 아래의 함수

while(root.readDir(directory)>0)

는 디렉토리에 있는 첫번째 파일에 대한 정보를 root에 로딩 시킵니다. 함수가 0을 리턴하면 디렉토리의 끝에 다 다랐다는 것을 의미합니다. 그래서 이 함수는 0이 리턴될때까지 계속 반복하여 실행이 됩니다. 이 함수가 각각 실행될때 마다 파일에 대한 정보를 받게 됩니다. 스케치의 다음 코드는 아래와 같습니다.

file.open(root, root.curPosition()/32-1, O_READ);

이 함수는 앞서 살펴본 파일을 여는 함수입니다. 이번에는 열릴 파일의 이름을 파라메터로 주기보다는, 열어야 할 파일의 인덱스를 지정하고 있습니다. 파일에 대한 정보를 root변수에 로드하였기 때문에 curPosition()함수를 이용하여 파일의 위치를 알아 낼수 있습니다. file.open()함수의 문서를 보면 파일의 익덱스를 사용할 경우에는 그 위치를 32로 나누어 사용해야 된다고 말해주고 있습니다. 그리고 인덱스는 항상 0부터 시작하기 때문에 1을 빼 실제 파일의 인덱스를 찾아냅니다.

파일을 열게 되면 파일에서 정보를 읽어 내고 다시 파일을 닫고 다시 while루프의 처음으로 돌아갑니다. 그리고 readDir()함수가 다시 실행이 되면 디렉토리의 다음 파일에 대한 정보를 root변수에 로드합니다. 이러한 방식으로 다음 파일을 찾아 갑니다.


파일에는 어떻게 쓰나요?

microSD카드가 유용한 가장 큰 이유 중 하나는 파일에 데이터를 쓰면 차후에 컴퓨터에서 읽어 사용할 수 있다는 점입니다. 일단 파일을 만들고 파일을 열면 데이터를 저장하는 것은 매우 쉽습니다. 아래의 예제코드를 보겠습니다. Millis라는 스트링을 복사하고 숫자값을 뒤에 출력한 후 파일에 저장하는 코드입니다.

 
file.open(root, name, O_CREAT | O_APPEND | O_WRITE);
    //Open or create the file 'name' in 'root' for
      writing to the end of the file.
sprintf(contents, "Millis: %d\n", millis());
    //Copy the letters 'Millis: ' followed by the 
      integer value of the millis() function into the 
      'contents' array.
file.print(contents);    //Write the 'contents' array
                           to the end of the file.
file.close();            //Close the file.

샘플의 첫번째 라인은 파일을 write모드로 여는 명령입니다. 파일의 제일 끝에 데이터를 적을 수 있도록 열었습니다. sprintf()함수는 스트링을 버퍼에 복사하여 주는 함수입니다. 여기서는 millis()함수의 리턴값을 Millis: 라는 스트링과 함께 contents 버퍼에 복사를 합니다. millis()함수는 스케치가 시작한 뒤에 얼마나 많은 millisecond가 흘렀는지를 리턴하여 주는 함수입니다.

그래서 본 예제를 실행한 후 200 millisecond가 흘렸다면 Millis: 200 이 contents버퍼에 복사되게 됩니다. 자 이제는 contents버퍼에 복사된 데이터를 파일에 저장할 차례입니다. 버퍼를 파일에 프린트하고 파일을 닫습니다.


파일을 어떻게 읽나요?

microSD카드에 저장된 노래나 그림, 시스템 셋팅 값이나 분석할 데이터를 읽어 오기 위해서는 microSD카드에 저장된 파일을 읽을 줄 알아야 합니다. 이는 시리얼 통신에서 데이터를 읽는 것과 비슷합니다. 데이터를 읽기 위해 정보를 저장할 버퍼가 필요합니다. 버퍼를 만든 후 read()함수를 이용하여 열려진 파일에서 정보를 읽어옵니다. 아래의 예제 코드에서는 버퍼가 file_contents라는 이름으로 생성되었습니다.

char file_contents[256];           //This is a data
            buffer that holds data read from a file

자, 이제 파일을 열고 연 파일에서 데이터를 읽어 옵니다. 파일에서 데이터를 읽어오는 것은 두가지 방법이 있습니다. 한번에 한글자씩 읽어오거나 특정 량의 데이터만큼을 한번에 읽는 것입니다. 두가지 방법을 다 살펴보겠습니다. 만약 한번에 한글자씩 읽는다면 버퍼크기를 염두에 둘 필요가 있습니다. 우리가 정의한 버퍼는 256바이트 크기입니다. 아래는 한번에 한글자씩 파일에서 읽어오는 예제코드입니다.

int index=0;  //Create a variable to keep track of
                our position in the data buffer.
file.open(root, name, O_READ);    //Open the file
                                    in read mode.
file_contents[index]=file.read();
                 //Get the first byte in the file.
//Keep reading characters from the file until we
 get an error or reach the end of the file. 
(This will output the entire contents of the file).
while(file_contents[index] >=0 && index < 256){
    //If the value of the character is less than 0 we've reached 
    the end of the file. If index is 256 than our buffer is full.
    index+=1;                 //Move to the next position in the
                                data buffer.
    file_contents[index]=file.read(); //Get the next character
}
file.close();    //Close the file
for(int i=0; i<index></index>

이 예제를 보면 한번에 한글자씩 파일에서 읽어와서 데이터 버퍼에 저장을 합니다. 루프는 두가지 조건하에서 계속 실행되는데 만약 파일에서 읽은 글자가 0이면 파일의 끝을 의미하여 루프를 벗어나게 되고 파일을 닫게 됩니다. 다른 조건은 index값이 256에 도달하여 데이터버퍼가 꽉 차있을때 루프를 벋어나고 파일을 닫게됩니다. 파일을 닫은 뒤에 스케치는 버퍼의 내용을 화면에 프린트하게 됩니다. 이렇게 한번에 한글자씩 읽는 방법은 속도가 느려지게 됩니다. 읽을 데이터가 얼마만큼인지 알수 있다면 그리고 데이터버퍼에 충분한 공간이 있는 것이 확실하다면 파일에서 데이터를 한번에 읽어 올수 있습니다.

int index=0;
int data_size=10;    
//This variable sets the number of bytes to read from the file.
index=file.read(file_contents, data_size);    
//file_contents is the data buffer for storing data. data_size is a 
variable set to the amount of data to read.
file.close();    //Close the file
for(int i=0; i<index><index></index></index>

한글자씩 읽어 올때보다 좀더 간단하여 졌네요. data_size의 10바이트를 파일에서 읽어 file_contents 데이터 버퍼에 저장합니다. index 변수는 파일로부터 읽어들인 바이트 수를 포함하고 있습니다.


용량에 따른 코드수정없이 microSD카드의 사용이 가능한가요?

예 가능합니다. FAT 파일시스템을 지원하는 라이브러리를 사용하는한 가능합니다. SdFat 라이브러리는 FAT16과 FAT32를 지원합니다. FAT32는 2TiB까지의 스토리지 볼륨을 지원하므로 대부분의 microSD카드의 사용이 가능합니다. 당연히 microSD카드는 FAT16이나 FAT32로 포맷되어 있어야 하겠죠?


FAT 라이브러리를 다운로드하였습니다만 동작하지 않습니다. 왜죠?

만약 확실히 올바른 라이브러리를 다운로드 하고 설치하였음에도 불구하고 동작하지 않는다면 하드웨어쪽에 문제가 없는지 살펴보는 것도 중요합니다. microSD 카드의 경우 SPI통신 설정에 문제가 있는 경우도 있습니다. SPI는 4개의 신호선을 사용합니다. MOSI, MISO, SCK, CS(혹은 SS)인데 이 신호는 FAT라이브러리에 정의 되어 있으며 특정 핀에 할당되어 있습니다. 아두이노의 경우 설정된 핀은 D10(CS), D11(MOSI), D12(MISO), D13(SCK)입니다. FAT라이브러리는 보통 이 핀설정을 가지고 정의 되어 있습니다. 그런데 microSD쉴드는 D8을 CS신호핀으로 사용합니다. 만약 이부분이 변경되지 않는다면 라이브러리는 동작하지 않습니다. 라이브러리 문서를 잘 살펴보고 어떤 신호선이 정의되어 있는지 확인한 다음 적절하게 변경하여 주어야 합니다. 예를들어 SdFat라이브러리에서는 핀정의는 ArduinoPins.h에 정의되어 있습니다. CS핀을 변경하기 위해서는 아래의 정의 부분을

#define SS_PIN  10
다음과 같이 변경하여야 합니다.
#define SS_PIN  8

이렇게 변경하였는데도 라이브러리가 동작하지 않는다면, FAT라이브러리는 아마도 내부 SPI 라이브러리를 사용하고 있을 겁니다. 이 SPI라이블러리는 D10을 출력으로 설정되있다고 가정합니다. 스케치의 setup 섹션에서 D10을 출력으로 설정하십시오.

pinMode(10, OUTPUT);

제품정보: http://vctec.co.kr/front/php/product.php?product_no=799&main_cate_no=161&display_group=1

가치창조기술

Comments