SD/MMC карта памяти и микроконтроллер AVR (часть 2) Система FAT, Petit FatFs.

В этой заметке рассмотрим работу совместно с файловой системой FAT. Итак, мы имеем микроконтроллер Atmega32 и SD/MMC карту памяти, отформатированную в системе FAT. На карте памяти, в корне есть файлы read.txt и write.txt.

Из read.txt нужно прочитать содержимое и вывести на программу Terminal 1.9 по UART. А в файл write.txt требуется записать некоторые тестовые данные, предположим «write ok».
Про подключение и по блоковую работу карты памяти SD|MMC с микроконтроллером писалось в предидущей статье цикла. Теперь попробуем пописать/почитать карту памяти на уровне файлов. С этим нам поможет библиотека Petit FatFs от мистера ChaN (elm-chan.org). Petit FatFs умеет работать с FAT16, FAT32, и предоставляет высокоуровневые функции работы с файлами. Эта библиотека представляет собой «драйвер» FAT, к которому можно подключить практически любой носитель с файловой системой FAT. Но для этого нужно написать 3 функции взаимодействия с носителем:
1) DSTATUS disk_initialize (void)
Инициализирует носитель, в случае успешной инициализации возвращает 0. В случае, если носитель не инициализировался, возвращает STA_NOINIT, а если носитель отсутствует - STA_NODISK.

  1. typedef BYTE DSTATUS; //Статус носителя
  2.  
  3. #define STA_NOINIT 0x01 //Носитель не инициализирован
  4. #define STA_NODISK 0x02 //Носитель отсутствует

2) DRESULT disk_readp (BYTE* Buffer, DWORD SectorNumber, WORD Offset, WORD Count)
Считывает часть сектора в область, куда указывает указатель Buffer. Считывает Count байт, начиная Offset-го байта сектора под номером SectorNumber (номер сектора в LBA адресации). Возвращает следующий результат:

  1. typedef enum { //Статус дисковой операции
  2. RES_OK = 0, //Операция успешно завершена
  3. RES_ERROR, //Ошибка носителя
  4. RES_NOTRDY, //Носитель не готов
  5. RES_PARERR //Ошибочный аргумент
  6. } DRESULT;

3) DRESULT disk_writep (BYTE* Buffer, DWORD SectorBytes)
Пишет данные, на которые указывает указатель Buffer. Если Buffer=NULL, то это инициализирует/завершает операцию записи в сектор. SectorBytes выполняет двоякую роль, при инициализации указывают номер сектора, а при записи – количество байт, которые нужно записать. Пример работы с функцией disk_writep:
  1. disk_writep(0, sector_number); //Инициализирует операцию записи в сектор sector_number
  2. disk_writep(data, byte_to_write); //Пишем данные в сектор, до 512 байт
  3. disk_writep(data, byte_to_write);
  4. disk_writep(data, byte_to_write);
  5. disk_writep(0, 0); // Завершаем операцию записи сектора,
  6. // если сектор не был дописан до конца, оставшиеся байты забиваются нулями.

Результат возвращает тот же, что и disk_readp.

На сайте мистера ChaN был приведен пример использования библиотеки Petit FatFs совместно с SD|MMC картой. Этот пример я взял за базу, и немного переделав его, получил удобный инструмент работы с системой FAT. Далее была написана программа-пример, которая демонстрирует чтение из файла read.txt и запись в файл write.txt которые расположены в корне SD|MMC карты памяти. Также было показано сканирования папки на предмет наличия в ней файлов и остальных папок. Рассмотрим поподробнее этот пример.

Скачав исходный код, и открыв его в AVR Studio 4 можно увидеть следующее:
вид проекта в AVR Studio 4
Сам драйвер Petit FatFs находится в файлах pff.c, pff.h. Как видно из кода выше, стандартные типы char, int, long int в Petit FatFs были переопределены. Эти переопределения можно посмотреть в файле integer.h. В файле diskio.h находятся заголовки функций disk_initialize, disk_readp, disk_writep. В mmc.c – все для работы c SD|MMC картой, и сами функции disk_initialize, disk_readp, disk_writep. В оставшемся файле Petit_FAT_FS_sample.c производиться собственно использование Petit FatFS для чтения/записи файлов на карте.
Для подключения карты памяти к микроконтроллеру используется так же макетка и схема, что и в статье «SD/MMC карта памяти и микроконтроллер AVR (часть 1) Базовые операции.» Если же карта подключена к PortB по другому, то стоит подредактировать секцию в mmc.c:

  1. #define SD_DI 0
  2. #define SD_DO 1
  3. #define SD_CLK 2
  4. #define SD_CS 3
  5. #define SD_INS 4
  6. #define SD_WP 5

Теперь поподробнее рассмотрим инструментарий:

1) FRESULT pf_mount (FATFS* FileSystemObject)
Монтирует файловую систему FAT, где указатель FileSystemObject указывает на объект файловой системы. Возвращает FRESULT:

  1. typedef enum {
  2. FR_OK = 0, //Все хорошо
  3. FR_DISK_ERR, //Ошибка диска
  4. FR_NOT_READY, //Диск не готов
  5. FR_NO_FILE, //Нет файла
  6. FR_NO_PATH, //Неправильный путь
  7. FR_NOT_OPENED, //Файл не открыт
  8. FR_NOT_ENABLED, //Раздел не смонтирован
  9. FR_NO_FILESYSTEM //На диске не имеется файловой системы FAT
  10. } FRESULT;

А точнее, возвращает только FR_OK, FR_NOT_READY, FR_DISK_ERR, FR_NO_FILESYSTEM

2) FRESULT pf_open (const char* FileName)
Открывает файл для подальших операций чтения/записи. Указатель FileName указывает на строку – путь к файлу. Возвращает FR_OK, FR_NO_FILE, FR_NO_PATH, FR_DISK_ERR, FR_NOT_ENABLED.

3) FRESULT pf_read (void* Buffer, WORD ByteToRead, WORD* BytesRead)
Считывает ByteToRead байт в область, на которую указывает Buffer из открытого файла. BytesRead – количество считанных байт, если BytesRead< ByteToRead, то был достигнут конец файла. Возвращает FR_OK, FR_DISK_ERR, FR_NOT_OPENED, FR_NOT_ENABLED.

4) FRESULT pf_write (void* Buffer, WORD ByteToWrite, WORD* BytesWritten)
Пишет ByteToWrite байтов из буфера, на который указывает Buffer в открытый файл. BytesWritten – указатель на число записанных байт. Записанные в файл данные следует финализровать. Вот так:

  1. pf_open (path); //Открываем файл
  2. pf_write(buff, btw, &bw); //Пишем
  3. pf_write(buff, btw, &bw); //Еще пишеи
  4. pf_write(0, 0, &bw); //Финализируем

Эта функция имеет существенные ограничения. Нельзя с ее помощью создать файл, нельзя увеличить размер файла (в секторах), может стартовать только с начала сектора, не учитывает файловый атрибует read-only. Если *BytesWritten < ByteToWrite, то это означает что был достигнут конец файла и дальше писать данные невозможно. Возвращает FR_OK, FR_DISK_ERR, FR_NOT_OPENED, FR_NOT_ENABLED.

5) FRESULT pf_lseek (DWORD Offset)
Устанавливает курсор для операции чтения внутри открытого файла в позицию Offset. Возвращает FR_OK, FR_DISK_ERR, FR_NOT_OPENED

6) FRESULT pf_opendir (DIR* DirObject, const char* DirName)
Открывает директорию c путем на который указывает DirName в объект, на который указывает DirObject. Структура DirObject может быть в любое время уничтожена. Возвращает FR_OK, FR_NO_PATH, FR_NOT_READY, FR_DISK_ERR, FR_NOT_ENABLED.

7) FRESULT pf_readdir (DIR* DirObject, FILINFO* FileInfo)
Считывает содержимое директории по указателю DirObject в структуру по указателю FileInfo. Возвращает FR_OK, FR_DISK_ERR, FR_NOT_OPENED.

Имея описанный выше инструментарий, мы можем почитать/пописать данные на карту SD|MMC на уровне файлов:

  1. #include <avr/io.h>
  2. #include <avr/interrupt.h>
  3. #include <string.h>
  4.  
  5. #include "pff.h"
  6. #include "diskio.h"
  7. #include "integer.h"
  8.  
  9. //------------------------------------------------------------------
  10. char read_buf[128]={};
  11. char write_buf[128]={'w','r','i','t','e',' ','o','k','\r','\n',0x00};
  12.  
  13. //------------------------------------------------------------------
  14. void uart_init(void)
  15. {
  16. UBRRH = 0x00; //Установим скорость UART 256000 bit/sec
  17. UBRRL = 0x01;
  18.  
  19. UCSRA = 0x00;
  20. UCSRB = (1<<RXEN)|(1<<TXEN); //Разрешаем передачу и прием данных
  21. UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);
  22. }
  23.  
  24. void uart_transmit(char data ) //Передаем байт по UART
  25. {
  26.  
  27. while ( !( UCSRA & (1<<UDRE)) )
  28. ;
  29. UDR = data;
  30. }
  31.  
  32. void uart_transmit_message(char* msg) //Передаем сообщение по UART
  33. { unsigned char i;
  34. i=0;
  35.  
  36. while ((i<256)&(msg[i]!=0x00) )
  37. {
  38. uart_transmit(msg[i]);
  39. i++;
  40. }
  41. }
  42. //------------------------------------------------------------------
  43. FRESULT scan_files (char* path) //Фунция сканирования директории
  44. {
  45. FRESULT res;
  46. FILINFO fno;
  47. DIR dir;
  48.  
  49. res = pf_opendir(&dir, path); //Открываем директорию
  50. if (res == FR_OK) {
  51. for (;;)
  52. {
  53. res = pf_readdir(&dir, &fno); //Считываем директорию
  54. if (res != FR_OK || fno.fname[0] == 0) break;
  55. if (fno.fattrib & AM_DIR) { //Если объект - директория
  56. uart_transmit_message("DIR: ");
  57. uart_transmit_message(fno.fname);
  58. uart_transmit_message("\r\n");
  59. }
  60. else { //Если объект - файл
  61. uart_transmit_message("FILE: ");
  62. uart_transmit_message(fno.fname);
  63. uart_transmit_message("\r\n");
  64. }
  65. }
  66. }
  67. return res;
  68. }
  69. //------------------------------------------------------------------
  70. int main ()
  71. {
  72. FATFS fs; //FatFs объект
  73. FRESULT res; //Результат выполнения
  74. WORD s1;
  75.  
  76. uart_init(); //Инициализация UART
  77.  
  78. res=pf_mount(&fs); //Монтируем FAT
  79. uart_transmit_message("mounting FAT ");
  80. if (res==0x00) { //Если FAT успешно смонтирован
  81. uart_transmit_message("OK\r\n");
  82.  
  83. res=pf_open("/read.txt"); //Откроем read txt
  84. uart_transmit_message("open read.txt ");
  85. if (res==0x00) uart_transmit_message("OK\r\n");
  86. else uart_transmit_message("FAIL\r\n");
  87. res=pf_lseek (0); //Установим курсор чтения на 0 в read.txt
  88. uart_transmit_message("set 0 to pointer into read.txt ");
  89. if (res==0x00) uart_transmit_message("OK\r\n");
  90. else uart_transmit_message("FAIL\r\n");
  91. res = pf_read(read_buf, 128, &s1);//Считаем первые 128 байт из read.txt
  92. uart_transmit_message("read first 128 bytes from read.txt ");
  93. if (res==0x00) uart_transmit_message("OK\r\n");
  94. else uart_transmit_message("FAIL\r\n");
  95. uart_transmit_message("data in read.txt:\r\n");
  96. uart_transmit_message(read_buf);//Передадим первые 128 байт с read.txt на компьютер
  97.  
  98. res=pf_open("/write.txt");
  99. uart_transmit_message("open write.txt ");//Откроем write.txt
  100. if (res==0x00) uart_transmit_message("OK\r\n");
  101. else uart_transmit_message("FAIL\r\n");
  102. res=pf_write(write_buf,strlen(write_buf),&s1);//Запишем в write.txt
  103. uart_transmit_message("writing @write ok@ in write.txt ");
  104. if ((res==0x00)&&(s1==strlen(write_buf))) uart_transmit_message("OK\r\n");
  105. else uart_transmit_message("FAIL\r\n");
  106. res=pf_write(0,0,&s1); //Финализируем write.txt
  107. uart_transmit_message("finalizing write.txt ");
  108. if (res==0x00) uart_transmit_message("OK\r\n");
  109. else uart_transmit_message("FAIL\r\n");
  110.  
  111. res=pf_lseek (0); //Установим курсор чтения на начало write.txt
  112. uart_transmit_message("set 0 to pointer into write.txt ");
  113. if (res==0x00) uart_transmit_message("OK\r\n");
  114. else uart_transmit_message("FAIL\r\n");
  115. read_buf[0]=0; //Чистим буфер
  116. res = pf_read(read_buf, 128, &s1); //Считываем первые 128 байт из write.txt
  117. uart_transmit_message("read first 128 bytes from write.txt ");
  118. if (res==0x00) uart_transmit_message("OK\r\n");
  119. else uart_transmit_message("FAIL\r\n");
  120. uart_transmit_message("data in write.txt:\r\n");
  121. uart_transmit_message(read_buf); //Передаем считанные данные на компьютер
  122.  
  123. uart_transmit_message("scaning test_dir:\r\n");
  124. scan_files ("test_dir"); //Сканируем test_dir
  125. uart_transmit_message("scaning ended\r\n");
  126.  
  127. res=pf_mount(0x00); //Демонтируемt FAT
  128. uart_transmit_message("unmounting FAT ");
  129. if (res==0x00) uart_transmit_message("OK\r\n");
  130. else uart_transmit_message("FAIL\r\n");
  131. }
  132. else uart_transmit_message("FAIL\r\n");
  133. while (1);
  134. }

Код программы, монтируем FAT, считываем данные с read.txt, пишем данные в write.txt, считывает то, что записали. Далее сканируем и выводим содержимое папки test_dir, после чего размонтируем FAT. Вот что я увидел в окошке Terminal 1.9, настроив его на скорость 256000 бит/с и запустив макет с Petit FatFs:
скриншот terminal
Если у вас не будет производиться операции записи - проверьте защелку Write Protect на карточке. Ее положение учитывается. Для подтверждения сказанного откроем содержимое карты в проводнике:
скриншот проводника
Как видно из скриншота, все работает. Сделаем небольшие выводы по Petit FatFs. Главные преимущества – небольшой размер (можно урезать до размера менее 8кб флеша), простота использования, высокоуровневый доступ к файлам. Из минусов отмечу невозможность создавать и значительно расширять существующие файлы. Так что Petit FatFs хорошо сгодиться там, где нужно уметь читать данные (всякие плееры WAV файлов), хороший логгер с использованием Petit FatFs, сделать не получиться. Про создание, удаление и запись больших файлов поговорим в следующей статье цикла.

UDP: в Petit FatFs также есть константы, управляя которыми можно выбросить неиспользуемые функции и тем самым сократив требования по ОЗУ и флэш-памяти для микроконтроллера. Эти константы можно задать в файле pff.h. Рассмотрим их поподробнее:
константы Petit FatFs

Скачать исходный код в виде проекта под AVR Studio 4.
Следующая статья цикла: «SD/MMC карта памяти и микроконтроллер AVR (часть 3) Система FAT, FatFs.»
Предведущая статья цикла: «SD/MMC карта памяти и микроконтроллер AVR (часть 1) Базовые операции.»

Попробовал вашу реализацию

Попробовал вашу реализацию библиотеки от Чанга - работает. Но если файл для чтения больше чем одна строка, то читаются только первых 128 байт файла. Так понимаю, что перед:

  1. res = pf_read(read_buf, 128, &s1);//Считаем первые 128 байт из read.txt
  2. ...
  3. read_buf[0]=0;

нужно поставить условие "Если не достигнут конец файла", но как это написать незнаю. Прошу помоши.

---

Вроде вычислил, вот код:

  1. WORD i;
  2.  
  3. for (;;) {
  4. res = pf_read(read_buf, 128, &s1); //read first 128 bytes from read.txt
  5. if (res || !s1) break; // Error or end of file
  6.  
  7. for (i = 0; i < s1; i++) uart_transmit(read_buf[i]);
  8. }

Не проходит инициализация

Доброго времени суток, не получается осуществить инициализацию карточки. Посылаю CMD0, в конце send_cmd ожидается ответ 0x01, на самом деле получаю только 0x00 (а поскольку в INIT_SPI() происходит инвертизация SD_DO, то в реальности на МК 0xff).

Подскажите пожалуйста, скачал

Подскажите пожалуйста, скачал проект и не запускается. Не проходит инициализация карты, если каждую команду прокручивать по 2...3 раза то срабатывает(for)и запись в файл ни в какую не производится. Симулирую в протеусе, можно ли узнать причины этого?

В протеусе код не ганял. Все

В протеусе код не ганял. Все сразу в железе собирал, с карточкой Kingston в 2 Гб все работало.

А что случилось с 3-й частью?

А что случилось с 3-й частью?

Пока не работает. Причины

Пока не работает.
Причины устанавливаем.

Починил

Починил

Почемуто не открывается 3

Почемуто не открывается 3 часть статьи по работе с SD картами

Жду с нетерпением следующей

Жду с нетерпением следующей части, где описано, как создать большой файл для логгера!

Будет, все будет. После того

Будет, все будет. После того как только автор сдаст диплом :)

Добрый вечер у меня такая

Добрый вечер у меня такая проблема собрал даное устройство в железе все работает только в терминале не хочет писать в файл write.txt write ok зашелку проверял все нормально карты памяти пробовал другую такая же проблема Испульзую вместо Атмеги32 Атмегу16 Помогите в чем проблема?

Посмотри в эту сторону

у меня была таже проблема.
Возможно проблема в следующем:
В данной реализации урезаной библиотеки она не может создавать новые кластера, а только перезаписывать уже существующие. Если создан файл, но в нем ничего не записано, то записать в него ничего не получиться. Попробуй в файл записать большое количество пробелов.

ок спасибо попробую

ок спасибо попробую