вівторок, 28 серпня 2018 р.

STM32: підключення SD CARD по SPI з FATFS

Передмова

Знадобилось мені в одному проекті вести запис всіляких подій у файл. Просто короткі текстові рядки з датою/часом і що за подія відбулась. Тому максимально просто і не заморочувався з DMA. Бібліотека FATFS є в комплекті з CubeMX, а ось вже як саме буде працювати мікроконтролер з флешкою по шині SPI на рівні залізяччя, треба вже потурбуватись самому. І дописати декілька функцій в заздалегідь призначених для цього місцях. Ідею поцупив звідси. Дуже стисло поділюсь з вами робочим прикладом. Хто захоче розібратись детально, то вже користуйтесь пошуком.

Залізяччя для макету

Схема макету

Зробіть з'єднання відповідно до схеми:
Макет для тестування
Клацайте на зображення щоб збільшити.

CubeMX for STM32

Запускаємо CubeMX, обираємо свій чип і робимо налаштування:
Налаштування вкладки pinout
  1. Додаємо до проекту бібліотеку FATFS
  2. Вмикаємо тактування від зовнішнього кварцового резонатору
  3. Вмикаємо Debug - Serial Wire
  4. Вмикаємо SPI2
  5. Та не забуваємо налаштувати PB12 на вихід GPIO_Output і надати ім'я "CS_SD_CARD", а також PB1 на вихід GPIO_Output і надати ім'я "LED_RED"
Переходимо до вкладки "clock configuration" і робимо так як на малюнку:
Налаштування вкладки clock configuration
Всі інші налаштування за замовчуванням, нічого не міняв.

Надаємо ім'я своєму проекту "STM32F103C8_SD_CARD_DEMO", вказуємо шлях, засіб розробки та генеруємо код.

Код для роботи FATFS по SPI

Відкриваємо в своєму засобі розробки згенерований CubeMX проект "STM32F103C8_SD_CARD_DEMO". Та перш за все навчимо бібліотеку FATFS працювати з нашою SD Card по шині SPI. Відкриваємо файл "user_diskio.h" і в секцію /* USER CODE BEGIN 0 */ такі рядки: 
  • #define SPI hspi2
  • extern SPI_HandleTypeDef SPI;
  • void sdcard_systick_timerproc(void);
Надалі, для зручності буду додавати весь код в користувацьких секціях і функціях де необхідно зробити зміни.

Ось так буде виглядати секція "USER CODE 0" у файлі "user_diskio.h" з нашим кодом:
/* USER CODE BEGIN 0 */
#define SPI hspi2
/* Includes ------------------------------------------------------------------*/
/* Exported types ------------------------------------------------------------*/
extern SPI_HandleTypeDef SPI;
/* Exported constants --------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
extern Diskio_drvTypeDef  USER_Driver;

/*-----------------------------------------------------------------------*/
/* Device Timer Interrupt Procedure  (Platform dependent)                */
/*-----------------------------------------------------------------------*/
/* This function must be called in period of 1ms                        */

void sdcard_systick_timerproc(void);
/* USER CODE END 0 */

Далі відкриваємо файл "user_diskio.c" і до секції "/* USER CODE DECL */" вносимо необхідні визначення, змінні, функції.

Секція "/* USER CODE DECL */" в файлі "user_diskio.c" тепер буде виглядати так:
/* USER CODE BEGIN DECL */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"

#include "user_diskio.h"
#include "stdbool.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* 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 */

#define SELECT() HAL_GPIO_WritePin(CS_SD_CARD_GPIO_Port, CS_SD_CARD_Pin, GPIO_PIN_RESET)
#define DESELECT() HAL_GPIO_WritePin(CS_SD_CARD_GPIO_Port, CS_SD_CARD_Pin, GPIO_PIN_SET)
/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

static BYTE CardType; /* b0:MMC, b1:SDC, b2:Block addressing */
static BYTE PowerFlag = 0; /* indicates if "power" is on */
static volatile BYTE Timer1, Timer2; /* 100Hz decrement timer */

static void xmit_spi(BYTE Data)
{
 while (HAL_SPI_GetState(&SPI) != HAL_SPI_STATE_READY);
 HAL_SPI_Transmit(&SPI, &Data, 1, 5000);
}

static BYTE rcvr_spi(void)
{
 unsigned char Dummy, Data;
 Dummy = 0xFF;
 Data = 0;
 while ((HAL_SPI_GetState(&SPI) != HAL_SPI_STATE_READY));
 HAL_SPI_TransmitReceive(&SPI, &Dummy, &Data, 1, 5000);

 return Data;
}

static void rcvr_spi_m(BYTE *dst)
{
 *dst = rcvr_spi();
}

/*-----------------------------------------------------------------------*/
/* Wait for card ready                                                   */
/*-----------------------------z------------------------------------------*/

static BYTE wait_ready(void)
{
 BYTE res;

 Timer2 = 50;
 rcvr_spi();
 do
  res = rcvr_spi();
 while ((res != 0xFF) && Timer2);

 return res;
}


static bool rcvr_datablock(BYTE *buff, /* Data buffer to store received data */
UINT btr /* Byte count (must be even number) */
)
{
 BYTE token;

 Timer1 = 10;
 do { /* Wait for data packet in timeout of 100ms */
  token = rcvr_spi();
 } while ((token == 0xFF) && Timer1);
 if (token != 0xFE)
  return false; /* If not valid data token, retutn with error */

 do { /* Receive the data block into buffer */
  rcvr_spi_m(buff++);
  rcvr_spi_m(buff++);
 } while (btr -= 2);
 rcvr_spi(); /* Discard CRC */
 rcvr_spi();

 return true; /* Return with success */
}

/*-----------------------------------------------------------------------*/
/* Send a data packet to MMC                                             */
/*-----------------------------------------------------------------------*/

#if _READONLY == 0
static bool xmit_datablock(const BYTE *buff, /* 512 byte data block to be transmitted */
BYTE token /* Data/Stop token */
)
{
 BYTE resp, wc;
 uint32_t i = 0;

 if (wait_ready() != 0xFF)
  return false;

 xmit_spi(token); /* Xmit data token */
 if (token != 0xFD)
 { /* Is data token */
  wc = 0;
  do { /* Xmit the 512 byte data block to MMC */
   xmit_spi(*buff++);
   xmit_spi(*buff++);
  } while (--wc);

  rcvr_spi();
  rcvr_spi();

  while (i <= 64) {
   resp = rcvr_spi(); /* Reveive data response */
   if ((resp & 0x1F) == 0x05) /* If not accepted, return with error */
    break;
   i++;
  }
  while (rcvr_spi() == 0)
   ;
 }
 if ((resp & 0x1F) == 0x05)
  return true;
 else
  return false;
}
#endif /* _READONLY */

static void power_on(void)
{
 unsigned char i, cmd_arg[6];
 unsigned int Count = 0x1FFF;

 DESELECT();

 for (i = 0; i < 10; i++)
  xmit_spi(0xFF);

 SELECT();

 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 (i = 0; i < 6; i++)
  xmit_spi(cmd_arg[i]);

 while ((rcvr_spi() != 0x01) && Count)
  Count--;

 DESELECT();
 xmit_spi(0XFF);

 PowerFlag = 1;
}

static void power_off(void) {
 PowerFlag = 0;
}
/*-----------------------------------------------------------------------*/
/* Send a command packet to MMC                                          */
/*-----------------------------------------------------------------------*/

static BYTE send_cmd(BYTE cmd, /* Command byte */
DWORD arg /* Argument */
)
{
 BYTE n, res;

 if (wait_ready() != 0xFF)
  return 0xFF;

 /* Send command packet */
 xmit_spi(cmd); /* Command */
 xmit_spi((BYTE) (arg >> 24)); /* Argument[31..24] */
 xmit_spi((BYTE) (arg >> 16)); /* Argument[23..16] */
 xmit_spi((BYTE) (arg >> 8)); /* Argument[15..8] */
 xmit_spi((BYTE) arg); /* Argument[7..0] */
 n = 0;
 if (cmd == CMD0)
  n = 0x95; /* CRC for CMD0(0) */
 if (cmd == CMD8)
  n = 0x87; /* CRC for CMD8(0x1AA) */
 xmit_spi(n);

 /* Receive command response */
 if (cmd == CMD12)
  rcvr_spi(); /* Skip a stuff byte when stop reading */
 n = 10; /* Wait for a valid response in timeout of 10 attempts */
 do
  res = rcvr_spi();
 while ((res & 0x80) && --n);

 return res; /* Return with the response value */
}

static int chk_power(void) /* Socket power state: 0=off, 1=on */
{
 return PowerFlag;
}

/*-----------------------------------------------------------------------*/
/* Device Timer Interrupt Procedure  (Platform dependent)                */
/*-----------------------------------------------------------------------*/
/* This function must be called in period of 1ms                        */

void disk_timerproc(void)
{
 uint8_t n;

 n = Timer1; /* 100Hz decrement timer */
 if (n)
  Timer1 = --n;
 n = Timer2;
 if (n)
  Timer2 = --n;

}

volatile unsigned short int sdcard_timer;

void inline sdcard_systick_timerproc(void)
{
 ++sdcard_timer;
 if (sdcard_timer >= 10)
 {
  sdcard_timer = 0;
  disk_timerproc();
 }
}
/* USER CODE END DECL */

А далі вносимо зміни до функцій в файлі user_diskio.c:
  • DSTATUS USER_initialize (BYTE pdrv);
  • DSTATUS USER_status (BYTE pdrv);
  • DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
  • DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
  • DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
Функція USER_initialize:
/**
  * @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 */
 BYTE n, ty, ocr[4];

 if (pdrv)
  return STA_NOINIT; /* Supports only single drive */
 if (Stat & STA_NODISK)
  return Stat; /* No card in the socket */

 power_on(); /* Force socket power on */
 //send_initial_clock_train();

 SELECT(); /* CS = L */
 ty = 0;
 if (send_cmd(CMD0, 0) == 1)
 { /* Enter Idle state */
  Timer1 = 100; /* Initialization timeout of 1000 msec */
  if (send_cmd(CMD8, 0x1AA) == 1)
  { /* SDC Ver2+ */
   for (n = 0; n < 4; n++)
    ocr[n] = rcvr_spi();
   if (ocr[2] == 0x01 && ocr[3] == 0xAA)
   { /* The card can work at vdd range of 2.7-3.6V */
    do {
     if (send_cmd(CMD55, 0) <= 1
       && send_cmd(CMD41, 1UL << 30) == 0)
      break; /* ACMD41 with HCS bit */
    } while (Timer1);
    if (Timer1 && send_cmd(CMD58, 0) == 0)
    { /* Check CCS bit */
     for (n = 0; n < 4; n++)
      ocr[n] = rcvr_spi();
     ty = (ocr[0] & 0x40) ? 6 : 2;
    }
   }
  }
  else
  { /* SDC Ver1 or MMC */
   ty = (send_cmd(CMD55, 0) <= 1 && send_cmd(CMD41, 0) <= 1) ? 2 : 1; /* SDC : MMC */
   do {
    if (ty == 2)
    {
     if (send_cmd(CMD55, 0) <= 1 && send_cmd(CMD41, 0) == 0)
      break; /* ACMD41 */
    }
    else
    {
     if (send_cmd(CMD1, 0) == 0)
      break; /* CMD1 */
    }
   } while (Timer1);
   if (!Timer1 || send_cmd(CMD16, 512) != 0) /* Select R/W block length */
    ty = 0;
  }
 }
 CardType = ty;
 DESELECT(); /* CS = H */
 rcvr_spi(); /* Idle (Release DO) */

 if (ty) /* Initialization succeded */
  Stat &= ~STA_NOINIT; /* Clear STA_NOINIT */
 else
  /* Initialization failed */
  power_off();

 return Stat;
  /* USER CODE END INIT */
}

Функція USER_status:
/**
  * @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 */
 if (pdrv)
  return STA_NOINIT; /* Supports only single drive */
 return Stat;
  /* USER CODE END STATUS */
}

Функція USER_read:
/**
  * @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 */
 if (pdrv || !count)
  return RES_PARERR;
 if (Stat & STA_NOINIT)
  return RES_NOTRDY;

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

 SELECT(); /* CS = L */

 if (count == 1)
 { /* Single block read */
  if ((send_cmd(CMD17, sector) == 0) /* READ_SINGLE_BLOCK */
  && rcvr_datablock(buff, 512))
   count = 0;
 }
 else
 { /* Multiple block read */
  if (send_cmd(CMD18, sector) == 0)
  { /* READ_MULTIPLE_BLOCK */
   do {
    if (!rcvr_datablock(buff, 512))
     break;
    buff += 512;
   } while (--count);
   send_cmd(CMD12, 0); /* STOP_TRANSMISSION */
  }
 }

 DESELECT(); /* CS = H */
 rcvr_spi(); /* Idle (Release DO) */

 return count ? RES_ERROR : RES_OK;
  /* USER CODE END READ */
}

Функція USER_write:
/**
  * @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 */
 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; /* Convert to byte address if needed */

 SELECT(); /* CS = L */

 if (count == 1)
 { /* Single block write */
  if ((send_cmd(CMD24, sector) == 0) /* WRITE_BLOCK */
  && xmit_datablock(buff, 0xFE))
   count = 0;
 }
 else
 { /* Multiple block write */
  if (CardType & 2)
  {
   send_cmd(CMD55, 0);
   send_cmd(CMD23, count); /* ACMD23 */
  }
  if (send_cmd(CMD25, sector) == 0)
  { /* WRITE_MULTIPLE_BLOCK */
   do {
    if (!xmit_datablock(buff, 0xFC))
     break;
    buff += 512;
   } while (--count);
   if (!xmit_datablock(0, 0xFD)) /* STOP_TRAN token */
    count = 1;
  }
 }

 DESELECT(); /* CS = H */
 rcvr_spi(); /* Idle (Release DO) */

 return count ? RES_ERROR : RES_OK;
  /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

Функція USER_ioctl:
/**
  * @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;
 BYTE n, csd[16], *ptr = buff;
 WORD csize;

 if (pdrv)
  return RES_PARERR;

 res = RES_ERROR;

 if (cmd == CTRL_POWER)
 {
  switch (*ptr) {
  case 0: /* Sub control code == 0 (POWER_OFF) */
   if (chk_power())
    power_off(); /* Power off */
   res = RES_OK;
   break;
  case 1: /* Sub control code == 1 (POWER_ON) */
   power_on(); /* Power on */
   res = RES_OK;
   break;
  case 2: /* Sub control code == 2 (POWER_GET) */
   *(ptr + 1) = (BYTE) chk_power();
   res = RES_OK;
   break;
  default:
   res = RES_PARERR;
  }
 }
 else
 {
  if (Stat & STA_NOINIT)
   return RES_NOTRDY;

  SELECT(); /* CS = L */

  switch (cmd) {
  case GET_SECTOR_COUNT: /* Get number of sectors on the disk (DWORD) */
   if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(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: /* Get sectors on the disk (WORD) */
   *(WORD*) buff = 512;
   res = RES_OK;
   break;

  case CTRL_SYNC: /* Make sure that data has been written */
   if (wait_ready() == 0xFF)
    res = RES_OK;
   break;

  case MMC_GET_CSD: /* Receive CSD as a data block (16 bytes) */
   if (send_cmd(CMD9, 0) == 0 /* READ_CSD */
   && rcvr_datablock(ptr, 16))
    res = RES_OK;
   break;

  case MMC_GET_CID: /* Receive CID as a data block (16 bytes) */
   if (send_cmd(CMD10, 0) == 0 /* READ_CID */
   && rcvr_datablock(ptr, 16))
    res = RES_OK;
   break;

  case MMC_GET_OCR: /* Receive OCR as an R3 resp (4 bytes) */
   if (send_cmd(CMD58, 0) == 0)
   { /* READ_OCR */
    for (n = 0; n < 4; n++)
     *ptr++ = rcvr_spi();
    res = RES_OK;
   }

  default:
   res = RES_PARERR;
  }

  DESELECT(); /* CS = H */
  rcvr_spi(); /* Idle (Release DO) */
 }

 return res;
  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

Демо код

Відкриваємо файл stm32f1xx_it.c і до секції /* USER CODE 0 */ додаємо рядок:
/* USER CODE BEGIN 0 */
extern void sdcard_systick_timerproc(void);
/* USER CODE END 0 */

А до функції "SysTick_Handler" додаємо виклик функції "sdcard_systick_timerproc":
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  HAL_SYSTICK_IRQHandler();
  /* USER CODE BEGIN SysTick_IRQn 1 */
  sdcard_systick_timerproc();
  /* USER CODE END SysTick_IRQn 1 */
}

І нарешті відкриваємо файл main.c нашого проекту і в секцію /* USER CODE 2 */ додаємо такий демо-код:
/* USER CODE BEGIN 2 */
  //-------------SD DEMO START-----------------
  char buffer[512];
  static FATFS g_sFatFs;
  FRESULT fresult;
  FIL file;
  int len;
  UINT bytes_written;
  HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
  fresult = f_mount(&g_sFatFs, "0:", 0);        //mount SD card
  fresult = f_open(&file, "testfile.txt", FA_OPEN_ALWAYS | FA_WRITE); //open file on SD card
  fresult = f_lseek(&file, file.fsize);         //go to the end of the file
  len = sprintf(buffer, "Hello, World!\r\nThis is demonstration write on SD Card\r\n");    //generate some string
  fresult = f_write(&file, buffer, len, &bytes_written);     //write data to the file
  fresult = f_close(&file);
  HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
  //-------------SD DEMO END-----------------
  /* USER CODE END 2 */

Компілюємо проект, заливаємо до мікроконтролеру. Стартуємо мікроконтролер. Можна декілька разів перезавантажити. При цьому світлодіод має блимнути. Тепер можна витягти флешку з SD Card Adapter і вставити її до ПК та пересвідчитись що файл "testfile.txt" з декількома текстовими рядками "Hello, World!" та "This is demonstration write on SD Card" існує.

Файли

39 коментарів:

  1. I can't make it work in every SD card. Do you have any idea why? I've tested many cards and, until now, there's only one generating the file and writing on it (I've even tested with a similar one).

    ВідповістиВидалити
    Відповіді
    1. Greeting.
      Yes, I also do not work on every SD-CARD. I do not know the reason. Not taken care of If you find a reason, please let me know, I will correct it and update the library.

      Видалити
    2. I ended up using a different code from yours, but it is actually pretty similar. I'm not sure if this is going to help you, but I just had to change the Vcc from 3.3V to 5V (remember that SPI2 ports on STM32f103C8 are 5V tolerant).

      Видалити
  2. sincerely thank you for the information. I will test it in practice.

    ВідповістиВидалити
  3. напишіть, будь-ласка, приклад, як читати построково файл з gcode, такого виду:
    G1 X166.895 Y40.157 E10.07675
    G1 X167.512 Y41.109 E10.20410
    G1 X168.271 Y42.549 E10.38682
    G1 X168.899 Y44.196 E10.58469
    G1 X169.319 Y45.894 E10.78104
    G1 X169.532 Y47.597 E10.97369
    G1 X169.550 Y49.208 E11.15454

    ВідповістиВидалити
    Відповіді
    1. Вітання.
      Ваше питання ближче до документації бібліотеки FatFS, чим до теми цієї статті.
      Ось вам ланка на потрібну вам функцію бібліотеки FatFS: http://microsin.net/programming/file-systems/fatfs-gets-read-string.html

      Видалити
  4. дякую за відповідь, я на f_gets реалізував читання построково, а потім sscanf розпарсив строчки. Ніби все працює, але таке "рагульство" вийшло, я думав є кращий і більш елегантний спосіб

    ВідповістиВидалити
  5. Дякую за чудовий приклад роботи з SD картою! Запустилося без проблем!

    ВідповістиВидалити
  6. Будь ласка. Sd-card живили від 5 чи 3.3 вольт?

    ВідповістиВидалити
    Відповіді
    1. десь читав, що не можна живити SD картки на 5V.
      Тут читав
      https://eax.me/stm32-sdcard/

      Видалити
    2. Хоча читав десь і ось це.
      If getting FR_NOT_READY error, use some external 5V power source. Power from board seems to be not enough (it could be current required for the module)

      Видалити
    3. на SD адаптері такий як на першому рисунку є регулятор напруги. Тому можна не боятись підключати 5В пін. Ще й треба, бо в мене не працювало через 3.3В. Це інформація для таких же довбнів, як і я, який 3 дні не розумів, що відбувається. Плюс, якщо щось не виходить і є впевненість у коді, треба обов'язково переконатись мультиметром, що СД картка живиться саме від 5В

      Видалити
  7. Чи можна за цієї конфігурації, що у вас показана користуватись дисплеєм ST7735 та SDcard на одному SPI2? Які нюанси? DMA?

    ВідповістиВидалити
  8. Світлодіод не блимнув. А точніше він горить постійно. Запис на SD карту не здійснюється. У чому може бути причина? SD карта самсунг

    ВідповістиВидалити
    Відповіді
    1. fresult зміна в яку кладеться результат кожної дії з прикладу, що пов'язане з роботою сдкард. Що там? Плюс, як світлодіод не згасає, то значить десь зависло. Треба дебагом покроково пройтись щоб зрозуміти на якому кроці висне.

      Видалити
    2. fresult = f_open(&file, "testfile.txt", FA_OPEN_ALWAYS | FA_WRITE); //open file on SD card
      Не хоче проходити далі цього рядка :(. І файл вже створював окремо на карті, і змінював параметр FA_OPEN_ALWAYS, на FA_CREATE_ALWAYS і FA_CREATE_NEW - не допомогло

      Видалити
    3. Рекомендую ще раз все перевірити і звіритись зі статею. Бо зависати не має, навіть коли сд картки немає в слоті.

      Видалити
    4. Зі світлодіодом було наступне: RESET навпаки вмикає світлодіод, я використовував PC13. І тому виникло так, що демо код ввімкнув світлодіод і залишив його у такому стані

      Видалити
    5. зрозумів. тому рекомендую робити макроси типу:
      #define LED_ON() HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET)
      #define LED_OFF() HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET)

      і далі в коді вже маніпулювати макросами LED_ON(), LED_OFF() для увімкнення і вимкнення світлодіодів не задумуючись як діод під'єднано чи з загальним катодом, чи з загальним анодом.

      Видалити
  9. Воно насправді не зависає. Воно там фейлиться і йде далі по програмі.
    fresult = f_mount(&g_sFatFs, "0:", 0); -------> fresult дорівнює FR_OK, тобто чудово.
    fresult = f_open(&file, "testfile.txt", FA_OPEN_ALWAYS | FA_WRITE); -----> FR_NOT_READY
    fresult = f_lseek(&file, file.fsize); ----> FR_INVALID_OBJECT
    Ось таке виводиться :(
    Я намагався сам проект створити, потім завантажив ваш. І мій, і ваш у мене поки не працює. Мабуть якась біда з СД картою, хоча туди все норм записується (з ноута). Відформатована у FAT

    ВідповістиВидалити
    Відповіді
    1. спробуй живити cd-card від 5 вольт. FR_NOT_READY - це тому що файла такого немає і він має створитись. FR_INVALID_OBJECT - це значить файл не створився і такого об'єкта не існує.

      Видалити
    2. 5В живиться, файл дійсно не створюється :( Адаптер точно такий же як у вас на схемці

      Видалити
  10. змінив SD картку на іншу все одно не працює... Робив все точно, як у вас. На жаль, ніяк. Може за три роки якісь функції змінили чи переробили у FATFS, бо якщо зараз генерувати кубом проект то файли бібліотеки розташовуються інакше, ніж у Вашому проекті, який ви залишили для завантаження

    ВідповістиВидалити
  11. Все, проблема зникла. Дякую Вам велике за такий чудовий матеріал. Завдяки Вам і дисплей під'єднав, і SD карту. З SD картою я виходить дуже затупив, бо живив bluepil через SWD, а не через microUSB, тобто пін 5В насправді видавав 3.3В. Ще раз дякую!!!

    ВідповістиВидалити
  12. Добрый день! Спасибо за прекрасную статью, всё заработало!
    Вопрос. Если файла нет на флешке, то первый раз работа с ней длится дольше, а потом быстрее. Вот цифры. У нас 5 функций - монтируем, открываем, ищем конец, пишем, закрываем.
    Если файла нет: 4мкс, 65мс, 5мкс, 750мс, 150мс. Не вынимая флешку, жмем ресет и еще раз пишем. Теперь : 4мкс, 19мс, 5мс, 25мкс, 130мс. Последующие разы также пишется быстро. От флешки не зависит (у меня на 256М и 2Г). Почему такая разница?

    ВідповістиВидалити
    Відповіді
    1. Доброго дня.
      Радію що допомагаю.
      Ці питання скоріше до драйверу FATFS. Я таких досліджень не робив, але маю думку, що на перевірку чи є такий файл, на пошук місця де розташувати новий файл, та його формування потрібно більше часу чим коли такий файл вже існує.

      Видалити
    2. Насчет отсутствия файла я был неправ, это не влияет. Если делаю на пк пустой файл, то его наличие никак не влияет на задержку. Влияние оказывает лишь редактирование, создание или удаление файла на пк. Даже если вынимать/вставлять из модуля - задержка уже маленькая. Что отличается - время создания файла. Фатфс ставит 01.01.1980. Но связи не вижу.

      Видалити
    3. Дата создания файла в системах без RTC задается в настройках FATFS.

      Видалити
  13. Здравствуйте! Я читал, что инициализация SD карты после включения питания должна происходить на скорости от 100 kHz до 400 kHz, после чего можно переходить на высокую скорость. Я нашел в вашей программе функцию, которая выполняет инициализацию - это power_on() в файле user_diskio.c . Но переключения скорости SPI в тексте программы я вообще не нашел, как и вообще настройки интерфейса SPI. Скажите, вы делаете переключение скорости SPI или нет? Если да, то в какой момент можно включать высокую скорость?

    ВідповістиВидалити
  14. Сам спросил, сам отвечаю. Моя SD карта (Class 4) работает и без переключения скорости. Инициализация и дальнейшая работа проводились при скорости SPI 12 MHz. Использовал участки кода из вашей статьи, всё сразу заработало.

    ВідповістиВидалити
  15. Раньше использовал этот драйвер SD карты вместе с контроллером stm32f1xx, всё работало. Недавно попробовал запустить этот драйвер на stm32L4XX, и срезу повылазили ошибки. В некоторых местах пришлось добавить ожидание завершения передачи spi. Но это не ошибка автора, причина в том, что у L4 есть буфер FIFO. ошибка обнаружилась в функции USER_initialize().
    ...
    if (send_cmd(CMD8, 0x1AA) == 1){
    for(n = 0; n < 4; n++)
    {ocr[n] = rcvr_spi();}
    if (ocr[2] == 0x01 && ocr[3] == 0xAA){ //Здесь ошибка!
    ...
    Команда send_cmd(CMD8, 0x1AA) (согласно документации на SD карты) возвращает 4 байта. Первый байт результата забирает функция send_cmd, а цикл for(n = 0; n < 4; n++) пытается забрать ещё 4 байта. В то время, как нужно забрать только 3. Условие проверки результата CMD8 пришлось исправить на if (ocr[1] == 0x01 && ocr[2] == 0xAA), после чего всё заработало.

    ВідповістиВидалити
  16. Доброго дня! Було б добре якби просто писати/читати інформацію блоками з картки - робота з файловою системою займає багато ресурсів і часу , особливо якщо писати аудіо данні (до 3кГц) на непотужному stm32f103. А на комп'ютері написати програму для читання і запису.

    ВідповістиВидалити
  17. Обнаружил ошибку в функции "USER_write". Когда аргумент "count" больше 1, функция
    всегда завершается с ошибкой.
    Фрагмент када с ошибкой:

    if (send_cmd(CMD25, sector) == 0)
    { /* WRITE_MULTIPLE_BLOCK */
    do {
    if (!xmit_datablock(buff, 0xFC))
    break;
    buff += 512;
    } while (--count);
    if (!xmit_datablock(0, 0xFD)) /* STOP_TRAN token */
    count = 1; //Здесь ошибка!
    }

    Должно быть: "count = 0;" так как count равный 0 является признаком успешного
    завершения команды.

    ВідповістиВидалити