STM Урок 88. SD. SPI. FATFS. Часть 4



 

Урок 88

 

Часть 4

 

SD. SPI. FATFS

 

В предыдущей части нашего урока мы добавили необходимый код в пользовательские функции библиотеки FATFS и прочитали текстовый файл с карты SD.

 

Что-ж, будем считать, что со считыванием содержимого файлов мы справились. Теперь запись в файл.

Для начала в фунцию main() добавим несколько локальных переменных

 

uint16_t i;

FRESULT res; //результат выполнения

uint8_t wtext[]="Hello from STM32!!!";

/* USER CODE END 1 */

 

Закоментируем код считывания файла в функции main() и напишем код записи. Писать мы большие файлы пока не планируем, поэтому отдельную функцию для записи писать не будем, а напишем следующий знакомый нам про прошлым занятиям код

 

//write

if(f_mount(&SDFatFs,(TCHAR const*)USER_Path,0)!=FR_OK)

{

  Error_Handler();

}

else

{

  if(f_open(&MyFile,"mywrite.txt",FA_CREATE_ALWAYS|FA_WRITE)!=FR_OK)

  {

    Error_Handler();

  }

  else

  {

    res=f_write(&MyFile,wtext,sizeof(wtext),(void*)&byteswritten);

    if((byteswritten==0)||(res!=FR_OK))

    {

      Error_Handler();

    }

    f_close(&MyFile);

  }

}

/* USER CODE END 2 */

 

Хоть функция записи у нас ещё не написано, но всё же соберём код и прошьём контроллер, только перед этим для того, чтобы точно проверить, что у нас всё записано, удалим данный файл с карты, т.к. от там присутствует

 

index70

 

Глядя на вывод в терминальной программе, можно предположить, что у нас всё уже записывается, но если извлечь карту, вставить её в картовод и посмотреть содержимое на ПК, то мы увидим, что файл даже не создан.

Поэтому давайте напишем код в функции USER_write в файле user_diskio.c

 

  HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

  if (pdrv || !count) return RES_PARERR;

  if (Stat & STA_NOINIT) return RES_NOTRDY;

  if (Stat & STA_PROTECT) return RES_WRPRT;

  if (!(sdinfo.type & 4)) sector *= 512; /* Convert to byte address if needed */

  if (count == 1) /* Single block read */

  {

    SD_Write_Block((BYTE*)buff,sector); //Считаем блок в буфер

    count = 0;

  }

  else /* Multiple block read */

  {

}

SPI_Release();

return count ? RES_ERROR : RES_OK;

/* USER CODE END WRITE */

 

Код оказался не сложнее кода в функции чтения сектора.

Также мы видели в выводе терминала, что у нас в конце вызывается функция USER_Ioctl с командой 0, которая у нас ещё не обработана.

Поправим этот недочёт, сначала зайдя в файл sd.с и написав там некоторую функцию ожидания после функции SPI_Release

 

//-----------------------------------------------

uint8_t SPI_wait_ready(void)

{

  uint8_t res;

  uint16_t cnt;

  cnt=0;

  do { //Ждем окончания состояния BUSY

    res=SPI_ReceiveByte();

    cnt++;

  } while ( (res!=0xFF)&&(cnt<0xFFFF) );

  if (cnt>=0xFFFF) return 1;

  return res;

}

//-----------------------------------------------

 

Добавим на данную функцию прототип и вернёмся в файл user_diskio.c в функцию USER_write.

 

 

Добавим там нужный кейс

 

switch (cmd)

{

  case CTRL_SYNC : /* Flush dirty buffer if present */

    SS_SD_SELECT();

    if (SPI_wait_ready() == 0xFF)

    res = RES_OK;

    break;

  case GET_SECTOR_SIZE : /* Get sectors on the disk (WORD) */

 

Ещё раз соберём код и прошьём контроллер. Вот результат вывода в терминал

 

index71

 

Был запрошен статус, возможно, всё прошло хорошо. Чтобы узнать это, извлечём карту и посмотрим её содержимое на ПК. Так и есть. Файл появился размером 20 байт

 

index73

 

Посмотрим также его содержимое

 

index74

 

Содержимое соответствует содержимому буфера, который мы записывали. Значит всё нормально. С записью тоже разобрались.

Давайте испытаем ещё некоторый функционал карты и библиотеки FATFS.

Сначала мы закомментируем код записи на карту в функции main(), а также добавим вызов функции деинициализации

 

*/

FATFS_UnLinkDriver(USER_Path);

/* USER CODE END 2 */

 

Мы попробуем считать файловое содержимое нашей карты, то есть как-то получить список файлов и каталогов.

Добавим некоторые локальные переменные и структуры в функцию main()

 

uint8_t wtext[]="Hello from STM32!!!";

FILINFO fileInfo;

char *fn;

DIR dir;

DWORD fre_clust, fre_sect, tot_sect;

/* USER CODE END 1 */

 

 

Теперь добавим некоторый код также в main()

 

//read dir

if(f_mount(&SDFatFs,(TCHAR const*)USER_Path,0)!=FR_OK)

{

  Error_Handler();

}

else

{

  fileInfo.lfname = (char*)sect;

  fileInfo.lfsize = sizeof(sect);

  result = f_opendir(&dir, "/");

  if (result == FR_OK)

  {

    f_closedir(&dir);

  }

}

FATFS_UnLinkDriver(USER_Path);

 

Мы монтируем файловую систему, затем привязываем поля структуры к определённым переменным, определяющим адреса в памяти, затем при помощи нужной функции библиотеки FATFS читаем корневой каталог в структуру DIR и при удачном результате закрываем каталог.

Здесь будет ещё много кода, но давайте пока соберём то, что есть и прошьём контроллер. Посмотрим результат наших действий

 

index75

 

Пока мы видим лишь попытки чтения каких-то секторов. Видимо, структура наша инициализировалась. Мы ещё не видим списка файлов и каталогов, так как мы ещё не писали код для этого.

Продолжим код

 

if (result == FR_OK)

{

  while(1)

  {

    result = f_readdir(&dir, &fileInfo);

    if (result==FR_OK && fileInfo.fname[0])

    {

    }

    else break;

  }

  f_closedir(&dir);

 

Здесь у нас будет бесконечный цикл, с помощью которого мы отследим, что список файлов и каталогов в корневой папке закончился. Определим мы это по нулевому байту в массиве fname структуры fileinfo.

Данную структуру мы инициализируем с помощью функции f_readdir, которая пользуясь структурой DIR и занесёт все данные очередной записи в файловой системы. причём после вызова данной функции указатель автоматически переходит на следующую файлово-каталожную запись в корневом каталоге.

Теперь продолжим код в теле условия

 

if (result==FR_OK && fileInfo.fname[0])

{

  fn = fileInfo.lfname;

  if(strlen(fn)) HAL_UART_Transmit(&huart1,(uint8_t*)fn,strlen(fn),0x1000);

  else HAL_UART_Transmit(&huart1,(uint8_t*)fileInfo.fname,strlen((char*)fileInfo.fname),0x1000);

  if(fileInfo.fattrib&AM_DIR)

  {

    HAL_UART_Transmit(&huart1,(uint8_t*)" [DIR]",7,0x1000);

  }

}

 

В этом теле мы сначала предположим, что имя файла или каталога у нас длинное (длиннее 8 символов) и привяжем это поле к строковому массиву. Затем, если мы определим, что этот массив будет ненулевой длины, то мы и выведем это длинное имя в терминальную программу. Если же массив будет нулевой длины, то имя у нас короткое и мы выведем уже его в терминальную программу, воспользовавшись полем fname.

Талее по значению аттрибута определим, что это не файл, а каталог, в этом случае выведем в терминал определённую строку [DIR].

Теперь после условия, в том числе и после противной составляющей, мы перейдём на новую строку

 

  else break;

  HAL_UART_Transmit(&huart1,(uint8_t*)"rn",2,0x1000);

}

f_closedir(&dir);

 

В файле user_diskio.c закомментируем весь вывод в терминальную программу, иначе при выводе файлово-каталожного списка этого вывода будет слишком много.

Затем соберём код, прошьём контроллер и посмотрим результат в терминальной программе

 

index76

 

Отлично! Мы видим весь список каталогов. Есть ещё очень много интересных функций в библиотеке FATFS, которые могут создавать каталоги, удалять файлы и каталоги, добавлять содержимое в файлы, но с этим, я думаю вы уже разберётесь сами, нам же это пока не требуется. Также мы можем вывести список любого каталога, исправив имя вот здесь

 

result = f_opendir(&dir, "/MYDIR01");

 

Соберём код, прошьём контроллер и посмотрим содержимое этого каталога

 

index77

 

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

 

  HAL_UART_Transmit(&huart1,(uint8_t*)"rn",2,0x1000);

}

f_getfree("/", &fre_clust, &fs);

sprintf(str1,"fre_clust: %lurn",fre_clust);

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

sprintf(str1,"n_fatent: %lurn",fs->n_fatent);

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

sprintf(str1,"fs_csize: %drn",fs->csize);

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

tot_sect = (fs->n_fatent - 2) * fs->csize;

sprintf(str1,"tot_sect: %lurn",tot_sect);

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

fre_sect = fre_clust * fs->csize;

sprintf(str1,"fre_sect: %lurn",fre_sect);

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

sprintf(str1, "%lu KB total drive space.rn%lu KB available.rn",

fre_sect/2, tot_sect/2);

HAL_UART_Transmit(&huart1,(uint8_t*)str1,strlen(str1),0x1000);

f_closedir(&dir);

 

Мы пытаемся узнать некоторые размеры.

Для этого мы вызываем функцию библиотеки FATFS f_getfree, которая определит свободное место на карте, в качества параметра передав ей имя корневой папки. Данная функция запишет значение в переменную, которая будет нести в себе количество кластеров. Но как мы знаем, кластер — это величина очень абстрактная и она нам мало что скажет о свободном месте, так как нам хочется увидеть его в байтах. Тем не менее эту величину мы тоже отобразим в терминале.

Также вышеуказанная функция не только определит размер в кластерах, она ещё и инициализирует файловую структуру fs, из которой мы возьмём ещё некоторую информацию. Сначала мы из неё возьмём размер в кластерах полный а не свободный (поле fatent) и отобразим его в терминале. Затем то же самое проделаем с полем csize, которое несёт в себе информацию о размере кластера в килобайтах, а затем нам несложно будет вычислить размер в байтах общий и оставшийся.

Соберём код, прошьём контроллер. А вот и результат

 

index78

 

Правильность данной информации я проверял, подключив карту к компьютеру, всё сошлось до килобайта.

Поэтому всем спасибо за внимание!

Надеюсь, что мой урок многим был полезен. Ждите новый занятий!

 

 

Предыдущая часть Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6

Программатор недорогой можно купить здесь ST-Link V2

Модуль Micro-SD SPI можно приобрести здесь Micro-SD SPI

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM SD. SPI. FATFS

12 комментариев на “STM Урок 88. SD. SPI. FATFS. Часть 4
  1. Алексей:

    Уважаемый автор, не подскажите, а как правильно размонтировать карту? У меня возникла проблема, вставляю такрту, она поределяется, выводит список файлов. Размонтирую ее командой f_mount(NULL,USER_Path,1) после этого вытаскиваю карту и вставляю ее обратно, пытаюсь смонтировать заново, результат — FR_DISK_ERR

    Причем, на ЮСБ устройствах все размонтирутся без пробем.

    • Алексей:

      Сам себе и отвечу, помогло:

      Монтируем

      retUSER = FATFS_LinkDriver(&USER_Driver, USER_Path);

      f_mount(&sd,USER_Path,1)

      Размонтируем:

      if(f_mount(NULL,USER_Path,0)==FR_OK)  FATFS_UnLinkDriver(USER_Path);      

      • Ну да, так она и размонтируется. Отдельной команды у FATFS для размонтирования нет. Да и вообще к этой библиотеке вопросов есть немало.

        Но, тем не менее, писать самому подобную библиотеку тоже удовольствие ниже среднего. Так что спасибо разработчикам.

        Простите за ожидание, зашился.

        И спасибо за ответ!

        • Alex:

          К сожалению, с моим примером не все гладко. Если у вас одна SD-карта (не важно как подключена), то монтирование/отмонтирование работает как надо. Но, добавьте сюда еще одно устройство, например флешку по юсб, начинается полная неразбериха с устройствами. Считываешь содержимое флешки, выводится содержимое сд-карты и наоборот.

          Вообще, очень бы хотелось увидеть пример одновременной работы 2-х и более устройств в FATFS.

          И.. Спасибо вам за ваш труд!!!

          • И Вам спасибо за интерес к ресурсу!

            Надо будет подумать. Видимо в свойствах надо в Cube включать несколько носителей и как-то их потом идентифицировать. Вообще, с одним-то надо как следует разобраться.

  2. Alex:

    Куб их включает сразу:

    /*## FatFS: Link the SD driver ###########################*/

    retSD = FATFS_LinkDriver(&SD_Driver, SD_Path);

    /*## FatFS: Link the USBH driver ###########################*/

    retUSBH = FATFS_LinkDriver(&USBH_Driver, USBH_Path);

    По идее должено быть одно FATFS fs и разные пути к дискам SD_Path и USBH_Path. Но если смонтировать оба устройства сразу, то при f_readdir выводит список последнего смонтированного диска и не важно какой путь указал SD_Path или USBH_Path.

  3. Alex:

    Да, хотел сказать немного о F746-disco, при активации USB_FS в режим хоста, куб ставит GPIO_PIN_SET на ножку включения питания, но по схеме вход инверсный и я долго не мог понять, почему хост не подает питание на устройство.

    И еще немного занудства. Вы в примерах часто используете связку sprintf — HAL_UART_Transmit, если сделать вот такую функцию:

    void Printf_UART(const char *args, …)
    {
        char StrBuff[0xFF]={0};
       
      va_list ap;
      va_start(ap, args);
      vsnprintf(StrBuff, sizeof(StrBuff), args, ap);
      va_end(ap);
      StrBuff[strlen(StrBuff)]=0;
      HAL_UART_Transmit(&huart1,(uint8_t *)StrBuff, (uint16_t)strlen(StrBuff),100);
    }

    То вывод в УАРТ можно делать так: Printf_UART("Test #%d",Nomer);

  4. Артем:

    Проходя урок столкнулся с задачей записать один большой файл в другой. решение в лоб, но может кому съэкономит времяю. Я сперва пытался сделать реализацию cmd 25(не смог) был бы признателен автору если дополнит кусочек в user_diskio где в функции  DRESULT USER_read оставили пропуск сославшись что это не пригодится.

    if (count == 1) /* Single block read */
            {
                SD_Write_Block((BYTE*)buff,sector); //—читаем блок в буфер
                count = 0;
            }
            else /* Multiple block read */
            {
            }

     

    мой кусочек выделен жирным, вроде особо не тестировал но отработало хорошо.

    //write

        if(f_mount(&SDFatFs,(TCHAR const*)USER_Path,0)!=FR_OK)
        {
            Error_Handler();
        }
        else
        {
            if(f_open(&MyFile,"convwrite.txt",FA_CREATE_ALWAYS|FA_WRITE)!=FR_OK)
            {
                Error_Handler();
            }
            else
            {
                if(sizeof(bufferRes)<=512)
                {res=f_write(&MyFile,bufferRes,sizeof(bufferRes),(void*)&byteswritten);}
    else{
    int TempSize=0;
    uint16_t WriteSize=512;

    //TempSize=sizeof(bufferRes);
     while(TempSize<sizeof(bufferRes))
                {
    WriteSize=(sizeof(bufferRes)-TempSize);
    WriteSize=(WriteSize>512)?512:WriteSize;
                res=f_write(&MyFile,&bufferRes[TempSize],WriteSize,(void*)&byteswritten);
                TempSize+=512;
    }

    }
                if((byteswritten==0)||(res!=FR_OK))
                {
                    Error_Handler();
                }
                f_close(&MyFile);
            }
        }

    Спасибо ещё раз за ваши уроки!

  5. Александр:

    Здравствуйте! Спасибо за ваши статьи)
    Такой вопрос возник, не могу никак решить. У меня плата STM32 f103RC T6, работал по вашим видеоурокам. В отладчике все работает отлично, но как только плату оставляю «саму по себе», как я выяснил, происходит ошибка FR_DISK_ERR. Не знаете, в чем может быть проблема? Спасибо)

  6. Дмитрий:

    Доброго времени суток! Огромное спасибо за вашу работу!

    С чтением файла все хорошо а вот при записи возникла проблема и ни как не получается с ней разобраться.

    uint8_t SD_Write_Block (uint8_t *buff, uint32_t lba)
    {
    uint8_t result;
    uint16_t cnt;
    result=SD_cmd(CMD24,lba); //CMD24 даташит стр 51 и 97-98
    if (result!=0x00) return 6; //Выйти, если результат не 0x00
    SPI_Release();
    SPI_SendByte (0xFE); //Начало буфера
    for (cnt=0;cnt<512;cnt++) SPI_SendByte(buff[cnt]); //Данные
    SPI_Release(); //Пропустим котрольную сумму
    SPI_Release();
    result=SPI_ReceiveByte();
    if ((result&0x05)!=0x05) return 6; //Выйти, если результат не 0x05 (Даташит стр 111)
    cnt=0;
    do { //Ждем окончания состояния BUSY
    result=SPI_ReceiveByte();
    cnt++;
    } while ( (result!=0xFF)&&(cnt=0xFFFF) return 6;
    return 0;
    }

    Из этой функции выходит с результатом 6. При этом в переменную result присваивается значение 0х20. Тип карты памяти определяется 0х04.

    Подскажите пожалуйста где искать ошибку.

    • Ошибка скорей всего в изменении библиотек. Очень много воды утекло. Возможно, что и код тоже надо будет несколько менять. Посмотрите примеры в репозитории.

  7. MartKot:

    В файле user_diskio.h надо добавить #include «ff_gen_drv.h»
    /* USER CODE BEGIN 0 */
    #include «ff_gen_drv.h»
    /* Includes ——————————————————————*/
    /* Exported types ————————————————————*/
    /* Exported constants ———————————————————*/
    /* Exported functions ——————————————————- */
    extern Diskio_drvTypeDef USER_Driver;

    /* USER CODE END 0 */

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*