아두이노2016. 2. 24. 13:00

아두이노와 마이크로 SD카드 어댑터 모듈을 이용하여 마이크로 SD카드에 파일을 읽고 쓸 수가 있다. 얼핏 쓸모 없어 보이는 이것이 어느 용도로 쓰일수 있을까 생각해보니 꽤 많은 곳에 사용이 가능할 것 같다. 각종 센서의 입력값을 굉장히 큰 저장공간인 SD카드에 저장이 가능하므로, 예를 들면 기상관측용 아두이노를 만들었다고 치면 그 데이터를 독립적으로 SD카드에 기록이 가능하여 이를 추후 데이터 분석에 이용할 수 있을 것이다. 또한 저장되는 데이터를 CSV 같은 형식으로 저장을 한다면 엑셀이나 DBMS에서도 읽어들여 그래프를 그린다든지 다양한 분석을 해 볼수 있을 것이다. 또 하나 생각나는건 GPS 추적장치도 만들 수 있을 것이다.




위의 장치는 미드 브레이킹배드에 나오는 GPS 추적 장치이다. 자석이 있어 차량의 안보이는 부분에 붙여 놓으면 차량이 어디로 이동했는지 주기적으로 GPS 좌표를 저장하게되고 이를 나중에 수거하여 메모리의 내용을 살펴보면 차량이 언제 어디에 있었는지 알 수 있는 장치이다. 아두이노에 GPS 모듈을 연결하고 주기적으로 SD카드에 위도와 경도를 기록할 수 있도록 한다면 훌륭한 GPS 추적기가 되는 것이다. 물론 추가적으로 배터리와 강력한 네오디뮴 자석 같은 것이 필요하겠다.


아무튼 기초가 먼저다. 이 글에서는 아두이노와 SD카드 모듈을 가지고 어떻게 파일을 다루는지 알아보겠다.


우선 라이브러리는 아두이노 기본 IDE 에 포함이 되어 있다. 그래서 따로 다운로드를 받지 않아도 기본적인 SD카드 IO를 실행해 볼 수 있다. SPI 통신을 위한 SPI.h 와 SD카드 제어를 위한 SD.h 를 사용한다.


준비물은 아두이노, SD카드 모듈, Micro SD카드, 연결선 6개가 필요하다.


SD카드 모듈의 모습이다. PIN은 6개가 있으며 삼성 8GB Micro SD카드를 이용한다. SD카드는 FAT16 또는 FAT32 로 포맷되어 있어야 한다.



SD카드 모듈의 뒷면이다. 핀은 위에서부터 CS(Chip Select), SCK(Serial Clock, MOSI(Master Out Slave In), MISO(Master In Slave Out), VCC(5V), GND(Ground) 이다.



아두이노와 연결된 모습이다. 연결은 다음과 같이 하였다.


아두이노와의 연결

CS       ---------------------- Pin 4

SCK    ---------------------- Pin 13

MOSI  ---------------------- Pin 11

MISO  ---------------------- Pin 12

VCC    ---------------------- 5V

GND    ---------------------- GND


첫번째 예제는 아두이노 IDE 에 있는 CardInfo 예제를 실행해 보았다.

CardInfo 예제

현재 꼽혀 있는 SD카드의 정보를 보여주는 프로그램이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <SPI.h>
#include <SD.h>
 
// set up variables using the SD utility library functions:
Sd2Card card;
SdVolume volume;
SdFile root;
 
// change this to match your SD shield or module;
// Arduino Ethernet shield: pin 4
// Adafruit SD shields and modules: pin 10
// Sparkfun SD shield: pin 8
const int chipSelect = 4;
 
void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
 
 
  Serial.print("\nInitializing SD card...");
 
  // we'll use the initialization code from the utility libraries
  // since we're just testing if the card is working!
  if (!card.init(SPI_HALF_SPEED, chipSelect)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("* is a card inserted?");
    Serial.println("* is your wiring correct?");
    Serial.println("* did you change the chipSelect pin to match your shield or module?");
    return;
  } else {
    Serial.println("Wiring is correct and a card is present.");
  }
 
  // print the type of card (SD카드 타입을 표시)
  Serial.print("\nCard type: ");
  switch (card.type()) {
    case SD_CARD_TYPE_SD1:
      Serial.println("SD1");
      break;
    case SD_CARD_TYPE_SD2:
      Serial.println("SD2");
      break;
    case SD_CARD_TYPE_SDHC:
      Serial.println("SDHC");
      break;
    default:
      Serial.println("Unknown");
  }
 
  // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
  // SD카드의 포맷타입이 FAT16, FAT32일 경우만 동작
  if (!volume.init(card)) {
    Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
    return;
  }
 
 
  // print the type and size of the first FAT-type volume
  uint32_t volumesize;
  Serial.print("\nVolume type is FAT");
  Serial.println(volume.fatType(), DEC);
  Serial.println();
 
  // SD카드의 용량 산출
  volumesize = volume.blocksPerCluster();    // clusters are collections of blocks
  volumesize *= volume.clusterCount();       // we'll have a lot of clusters
  volumesize *= 512;                            // SD card blocks are always 512 bytes
  Serial.print("Volume size (bytes): ");
  Serial.println(volumesize);
  Serial.print("Volume size (Kbytes): ");
  volumesize /= 1024;
  Serial.println(volumesize);
  Serial.print("Volume size (Mbytes): ");
  volumesize /= 1024;
  Serial.println(volumesize);
 
 
  Serial.println("\nFiles found on the card (name, date and size in bytes): ");
  root.openRoot(volume);
 
  // list all files in the card with date and size, 파일 리스트 출력
  root.ls(LS_R | LS_DATE | LS_SIZE);
}
 
 
void loop(void) {
 
}
cs


위와 같이 실행결과가 나온다. 그런데 8GB SD카드를 끼웠는데 왜 3.5GB 밖에 용량이 안나오는지는 모르겠다. 다른 부분은 이상 없이 나온다.


ReadWrite 예제

텍스트 파일을 생성하고 내용을 작성하고 읽는 예제 프로그램이다. SD카드 기능에서 가장 중요한 기능이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//SD카드의 test.txt 파일에 특정 문자열을 쓰고 쓴 내용을 다시 읽어온다.
 
#include <SPI.h>
#include <SD.h>
 
File myFile;
 
void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
 
 
  Serial.print("Initializing SD card...");
 
  //SD카드 초기화 SD.begin(4) 는  CS핀번호
  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
 
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  // test.txt 파일을 쓰기 위해서 Open한다.
  myFile = SD.open("test.txt", FILE_WRITE);
 
  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");    //test.txt 파일에 문자열을 쓴다.
    // close the file: 쓰기 완료한 파일을 닫아준다.
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
 
  // re-open the file for reading: 
  // test.txt 파일을 읽기 위해 다시 Open한다.
  myFile = SD.open("test.txt");
  if (myFile) {
    Serial.println("test.txt:");
 
    // read from the file until there's nothing else in it:
    // test.txt 파일의 처음부터 끝까지 read하여 시리얼 모니터에 표시해준다.
    while (myFile.available()) {
      Serial.write(myFile.read());
    }
    // close the file: 읽기 완료한 파일을 닫는다.
    myFile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
}
 
void loop() {
  // nothing happens after setup
}
 
cs


결과는 시리얼 모니터에 위와 같이 나온다. test.txt 라는 파일이 생성되었으며 "testing 1, 2, 3." 이라는 문자열이 쓰고/읽는 것이 완료 되었다. 실제로 파일이 생성되고 내용이 쓰여있는지 알기 위해 SD카드를 뽑아서 컴퓨터에 연결해 보았다.



위와 같이 TEST.TXT 라는 파일이 실제로 생성이 되었으며 텍스트 편집기로 열어보니 "testing 1, 2, 3." 이라는 문자열이 입력된 것을 알 수 있다. 파일의 내용은 실행 횟수에 따라 기존 내용에 계속 덧붙여져서 쓰여진다. 파일의 생성일이 2000년 1월 1일로 나오는 것은 자체 RTC(Real Time Clock)가 없는 아두이노의 특성때문에 임의로 붙인 시간이다. RTC 모듈을 추가한다면 제대로 된 시간을 적어 넣을 수 있을 것이다.


위에서 살펴본 기능 이외에도 SD 라이브러리에는 파일 삭제, 폴더 생성/삭제 등의 많은 기능이 있으므로 더 많은 기능을 원하는 경우 SD 라이브러리 페이지를 참조하면 되겠다.


https://www.arduino.cc/en/Reference/SD





반응형
Posted by 대네브 (deneb)

댓글을 달아 주세요

  1. 탐구생활

    새로운 모듈 익힐려고 검색을 하면 항상 데네브님 블로그가 상위로 뜨네요. 잘보고 갑니다.

    2017.06.16 20:45 [ ADDR : EDIT/ DEL : REPLY ]
  2. 탐구생활

    포맷하실때 선택하신 파일시스템에 따라 하드디스크나 메모리의 용량이 적게 표시되는 경우가 있고요 아두이노 같은 경우는 저사양이니 고용량 메모리를 꽂아도 다 쓸 수 있을 거 같진 않네요.

    2017.06.16 20:54 [ ADDR : EDIT/ DEL : REPLY ]
  3. 멋지다:)

    2019.07.06 21:56 [ ADDR : EDIT/ DEL : REPLY ]
  4. JYJ

    volumesize = volume.blocksPerCluster(); // clusters are collections of blocks
    volumesize *= volume.clusterCount(); // we'll have a lot of clusters
    volumesize *= 512; // SD card blocks are always 512 bytes
    Serial.print("Volume size (bytes): "); // 이 단계에서 이미 volumnsize의 변수형인 uint32_t의 한계치를 넘어서서 계산이 이상해진 것으로 보입니다.
    bytes 단위가 아니라, 아예 kb단위로 하기 위해서 volume.blocksPerCluster()를 1024로 나누거나, mb단위로 하기 위해 다시 1024로 나눈 후, mb단위로 표기하면 되지 않을까요?

    https://forum.arduino.cc/index.php?topic=139852.0

    2019.07.24 23:34 [ ADDR : EDIT/ DEL : REPLY ]
    • 오호 그럴수도 있겠네요. 저도 오래전에 해 본거라 시간나면 다시 한 번 참고해서 해 보겠습니다.

      2019.07.25 07:20 신고 [ ADDR : EDIT/ DEL ]
    • 그럼 32비트짜리는 8GB를 정상적으로 출력을 할수 있다는 건가요?

      2021.03.25 14:00 [ ADDR : EDIT/ DEL ]
  5. YJG

    혹시 메가를 사용하게 되면 핀 번호를 몇으로 지정해야하는지 아시는지요 ㅠㅠ
    열심히 해보지만 잘 안되어 답답해서 이렇게 글 남깁니다 퓨ㅠ

    2019.11.25 16:55 [ ADDR : EDIT/ DEL : REPLY ]
    • 매가는 안 써봤지만 기본적인 gpio 핀에 연결하시면 되요. 물론 핀번호가 바뀌므로 소스는 수정하셔야 되구요

      2019.11.25 17:04 신고 [ ADDR : EDIT/ DEL ]
  6. YJH

    혹시 텍스트파일이 아닌 이미지 파일로 만들기 위해서는 어떤 식으로 해야 할까요??

    2019.12.26 09:29 [ ADDR : EDIT/ DEL : REPLY ]
    • 이미지 파일은 저도 해 본적은 없네요.
      구글링 해 보시길 바랍니다
      제가 잠깐 구글링 해 보니 가능한 것 같습니다

      2019.12.26 11:11 신고 [ ADDR : EDIT/ DEL ]
  7. 이민교

    initialization failed! 이 에러 해결방법이있을까요???

    2020.02.27 18:56 [ ADDR : EDIT/ DEL : REPLY ]