درود
واحد FSMC یک باس ارتباطی واقعاً انعطاف پذیر ایجاد می کنه که می تونه با انواع حافظه ها ارتباط برقرار کنه که در این پروژه قراره با استفاده از این واحد، NAND فلش K9F4G08U0B (همون NAND موجود در فروشگاه فقط با حافظه بیشتر) رو به میکرو متصل و بوسیله FATFS با این حافظه کار کنیم و فایل و پوشه و از این چیزا ایجاد کنیم و ...
برای ارتباط سخت افزاری این NAND Flash باید پایه ها بصورت ذیل به میکرو وصل شوند:
D0 تا D7 به D0 تا D7 واحد FMC وصل شوند
R/B به پایه NWAIT واحد FMC وصل شود
RE به پایه NOE واحد FMC وصل شود
CE به پایه NCE2 واحد FMC وصل شود
CLE به پایه CLE واحد FMC وصل شود
ALE به پایه ALE واحد FMC وصل شود
WE به پایه NWE واحد FMC وصل شود
شماتیک:
برای ایجاد پروژه، CubeMX رو باز کنید و بعد از انتخاب میکروکنترولر مورد نظر، که در اینجا از STM32F429ZGT6 استفاده شده، در قسمت FMC بخش NAND Flash 1 رو به صورت نمایش داده شده در عکس پیکربندی می کنیم و برای استفاده از FATFS در قسمت FATFS تیک user-defined رو میذاریم دلیل نبودن حافظه های NAND و NOR در قسمت FATFS، عدم ساپورت CubeMX از این حافظه ها در مود FATFS می باشد که باید خودمون درایور مربوط به این کار رو بنویسیم ...
مثل همیشه واحد های RCC و SYS رو هم پیکربندی می کنیم، عکس:
در مرحله بعد در برگه Configuration، واحد FMC و FATFS رو به این شکل پیکربندی می کنیم:
برای تنظیم FMC تاخیر ها رو با توجه به کلاک میکرو و سرعت NAND Flash مون مقدار دهی می کنیم
ECC Computation رو Enable و مقدارش رو 512 قرار می دیم (به دیتاشیت NAND فلش مراجعه شود)
Page Size , Spare Area Size , Block Size , Block Number و Zone Size رو هم بر اساس مشخصات NAND Flash مورد استفاده مقدار دهی می کنیم
در قسمت FATFS کافیه که سکتور سایز رو 2048 (برابر با Page Size حافظه NAND) قرار بدیم، البته کمتر هم می شه انتخاب کرد، دست خودتونه
در نهایت از منوی ...Project\Settings مقدار Stack Size رو به 0x3000 افزایش می دیم.
تمام... Generate code رو بزنید تا وارد مرحله بعد شیم.
تنها کاری که مونده اینه که درایور مربوط به حافظه NAND Flash مون رو در فایل user_diskio.c بنویسیم که به این صورت می شه:
کد:
/* USER CODE BEGIN 0 */
/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define NAND_Flash_Block_size 8
#define NAND_Flash_Page_size 2048
/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;
static uint8_t NAND_Flash_Block_Buffer[NAND_Flash_Block_size][NAND_Flash_Page_size];
extern NAND_HandleTypeDef hnand1;
/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */
Diskio_drvTypeDef USER_Driver =
{
USER_initialize,
USER_status,
USER_read,
#if _USE_WRITE
USER_write,
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};
/* Private functions ---------------------------------------------------------*/
/**
* @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 */
)
{
NAND_IDTypeDef NAND_ID;
Stat = STA_NOINIT;
/* USER CODE HERE */
if(HAL_NAND_Read_ID(&hnand1, &NAND_ID) == HAL_OK)
{
Stat = RES_OK;
}
else
{
Stat = RES_ERROR;
}
return Stat;
}
/**
* @brief Gets Disk Status
* @param pdrv: Physical drive number (0..)
* @retval DSTATUS: Operation status
*/
DSTATUS USER_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
NAND_IDTypeDef NAND_ID;
Stat = STA_NOINIT;
/* USER CODE HERE */
if(HAL_NAND_Read_ID(&hnand1, &NAND_ID) == HAL_OK)
{
Stat &= ~STA_NOINIT;
}
return Stat;
}
/**
* @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 HERE */
NAND_AddressTypeDef NAND_Address;
for(int i=0;i<count;i++) {
NAND_Address.Zone = 0x00;
NAND_Address.Block = (sector+i) / NAND_Flash_Block_size;
NAND_Address.Page = (sector+i) - (NAND_Address.Block * NAND_Flash_Block_size);
if(HAL_NAND_Read_Page_8b(&hnand1, &NAND_Address, &buff[i*_MAX_SS], 1) != HAL_OK)
return RES_ERROR;
}
return RES_OK;
}
/**
* @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 HERE */
NAND_AddressTypeDef NAND_Address;
int Page_Address;
for(int i=0;i<count;i++) {
NAND_Address.Zone = 0x00;
NAND_Address.Block = (sector+i) / NAND_Flash_Block_size;
NAND_Address.Page = 0x00;
if(HAL_NAND_Read_Page_8b(&hnand1, &NAND_Address, &NAND_Flash_Block_Buffer[0][0], NAND_Flash_Block_size) != HAL_OK)
return RES_ERROR;
Page_Address = (sector+i) - (NAND_Address.Block * NAND_Flash_Block_size);
for(int j=0;j<NAND_Flash_Page_size;j++)
NAND_Flash_Block_Buffer[Page_Address][j] = buff[(i*_MAX_SS)+j];
if(HAL_NAND_Erase_Block(&hnand1, &NAND_Address) != HAL_OK)
return RES_ERROR;
if(HAL_NAND_Write_Page_8b(&hnand1, &NAND_Address, &NAND_Flash_Block_Buffer[0][0], NAND_Flash_Block_size) != HAL_OK)
return RES_ERROR;
}
return RES_OK;
}
#endif /* _USE_WRITE == 1 */
/**
* @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 HERE */
DRESULT res = RES_ERROR;
if (Stat & STA_NOINIT) return RES_NOTRDY;
switch (cmd)
{
/* Make sure that no pending write process */
case CTRL_SYNC :
res = RES_OK;
break;
/* Get number of sectors on the disk (DWORD) */
case GET_SECTOR_COUNT :
*(DWORD*)buff = hnand1.Info.BlockNbr * NAND_Flash_Block_size;
res = RES_OK;
break;
/* Get R/W sector size (WORD) */
case GET_SECTOR_SIZE :
*(WORD*)buff = hnand1.Info.PageSize;
res = RES_OK;
break;
/* Get erase block size in unit of sector (DWORD) */
case GET_BLOCK_SIZE :
*(DWORD*)buff = hnand1.Info.PageSize;
break;
default:
res = RES_PARERR;
}
return res;
}
#endif /* _USE_IOCTL == 1 */
/* USER CODE END 0 */
و یه نکته دیگه باید یه اصلاح کوچیک تو فایل stm32f4xx_hal_nand.c که راه انداز NAND Flash ایجاد شده توسط CubeMX با استفاده از توابع HAL می باشد انجام بدیم و اون اینه که طبق دیتاشیت K9F4G08U0B برای نوشتن و خواندن دیتا از فلش، آدرس دهی طی 5 سیکل انجام میشه (2 سیکل اول آدرس ستون و 3 سیکل آخر آدرس سطر می باشند) پس به این صورت اصلاح می شود:
کد:
*(__IO uint8_t *)((uint32_t)(deviceaddress | ADDR_AREA)) = 0x00U;
*(__IO uint8_t *)((uint32_t)(deviceaddress | ADDR_AREA)) = 0x00U;
*(__IO uint8_t *)((uint32_t)(deviceaddress | ADDR_AREA)) = ADDR_1ST_CYCLE(addressoffset);
*(__IO uint8_t *)((uint32_t)(deviceaddress | ADDR_AREA)) = ADDR_2ND_CYCLE(addressoffset);
*(__IO uint8_t *)((uint32_t)(deviceaddress | ADDR_AREA)) = ADDR_3RD_CYCLE(addressoffset);
/* for 512 and 1 GB devices, 4th cycle is required */
//if(hnand->Info.BlockNbr >= 1024U)
//{
// *(__IO uint8_t *)((uint32_t)(deviceaddress | ADDR_AREA)) = ADDR_4TH_CYCLE(addressoffset);
//}
طبق کد نوشته شده این سطر باید دو بار تکرار شود:
کد:
*(__IO uint8_t *)((uint32_t)(deviceaddress | ADDR_AREA)) = 0x00U;
و این سطر کامنت می شود:
کد:
//if(hnand->Info.BlockNbr >= 1024U)
//{
// *(__IO uint8_t *)((uint32_t)(deviceaddress | ADDR_AREA)) = ADDR_4TH_CYCLE(addressoffset);
//}
این کار در توابع HAL_NAND_Read_Page و HAL_NAND_Write_Page انجام می شود.
و در تابع HAL_NAND_Erase_Block کافیه که این سطر کامنت شود:
کد:
//if(hnand->Info.BlockNbr >= 1024U)
//{
// *(__IO uint8_t *)((uint32_t)(deviceaddress | ADDR_AREA)) = ADDR_4TH_CYCLE(addressoffset);
//}
تو main چنتا از توابع FATFS رو تست می کنیم به طور مثال من این ها رو تو Sample مون گذاشتم:
کد:
f_mount(&FatFs, (TCHAR const*)USER_Path, 0);
f_mkfs((TCHAR const*)USER_Path, 0, 0);
f_opendir(&MyDir, (TCHAR const*)USER_Path);
f_open(&MyFile, "STM32.TXT", FA_CREATE_ALWAYS | FA_WRITE);
f_write(&MyFile, "0123456789", 10, &BW);
f_close(&MyFile);
f_open(&MyFile, "STM32.TXT", FA_OPEN_ALWAYS | FA_READ);
برنامه رو کامپایل کنید و نتیجه رو ببینید
در مورد invalid blocks که تو دیتاشیت اومده، بگم که ممکنه تعدادی از Block های NAND Flash مورد استفاده خراب و غیر قابل استفاده باشند که از تعداد انگشتان دست تجاوز نمی کنه...
روش ذکر شده روش جالبی برای استفاده عملی نیست چون رم زیادی رو اشغال می کنه اما یه مثال از bsp هم آپلود کردم که از رو اون واس نوشتن یه درایور پدر مادر دار الگو بگیرید موفق باشید.
پروژه Keil Arm و CubeMX ضمیمه شده