Главная » Статьи » Уроки по программированию stm32fxxx » Уроки по программированию stm32f1xx

Урок 8: Память NAND и FSMC


Интерфейс FSMC оказался достаточно сложным в освоении, поэтому в этой статье я опишу то, что мне удалось о нём узнать. В частности особенности подключения flash памяти NAND, которую собственно мне и надо было заставить работать.

Статья получилась длинная, поэтому код, по возможности, был спрятан под спойлеры для визуального сокращения статьи.
Прежде всего об интерфейсе. FSMC - это параллельный интерфейс, позволяющий подключать к МК дополнительную память в область памяти самого МК. Эта шина немного напоминает интерфейс 8080.

В качестве памяти была взята микросхема K9LBG08U0E. Эта память обладает 32Гбитами памяти. и состоит из 4 000 блоков по 1 МБ в каждом. Каждый блок состоит из 128 страниц по 8КБ.

Инициализация FSMC

Прежде всего нам надо инициализировать FSMC. К нашему счастью у реализации FSMC есть настройки под NAND. Рассмотрим их подробнее.

Сначала, как обычно инициализируем порты ввода вывода.

Инициализация портов. Код.
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_14 | GPIO_Pin_15 |
GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 |
GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_Init(GPIOD, &GPIO_InitStructure);

/*!< D4->D7 NAND pin configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_Init(GPIOE, &GPIO_InitStructure);

/*!< NWAIT NAND pin configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

GPIO_Init(GPIOD, &GPIO_InitStructure);


В инициализации портов думаю у вас уже вопросов нет.
После инициализации портов инициализируем сам FSMC.

Инициализация FSMC. Код.
FSMC_NAND_PCCARDTimingInitTypeDef p;
FSMC_NANDInitTypeDef FSMC_NANDInitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);//Включаем тактовую.

/*-- FSMC Configuration ------------------------------------------------------*/
p.FSMC_SetupTime = 0x1;
p.FSMC_WaitSetupTime = 0x1;
p.FSMC_HoldSetupTime = 0x2;
p.FSMC_HiZSetupTime = 0x2;

FSMC_NANDInitStructure.FSMC_Bank = FSMC_Bank2_NAND;
FSMC_NANDInitStructure.FSMC_Waitfeature = FSMC_Waitfeature_Enable;
FSMC_NANDInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_8b;
FSMC_NANDInitStructure.FSMC_ECC = FSMC_ECC_Enable;
FSMC_NANDInitStructure.FSMC_ECCPageSize = FSMC_ECCPageSize_1024Bytes;
FSMC_NANDInitStructure.FSMC_TCLRSetupTime = 0x00;
FSMC_NANDInitStructure.FSMC_TARSetupTime = 0x00;
FSMC_NANDInitStructure.FSMC_CommonSpaceTimingStruct = &p;
FSMC_NANDInitStructure.FSMC_AttributeSpaceTimingStruct = &p;

FSMC_NANDInit(&FSMC_NANDInitStructure);

/*!< FSMC NAND Bank Cmd Test */
FSMC_NANDCmd(FSMC_Bank2_NAND, ENABLE);

FSMC_NANDECCCmd(FSMC_Bank2_NAND,ENABLE);

NAND_Reset();


Как обычно разберём настройки.
В переменную p мы записываем так называемые тайминги, временные задержки сигналов.

SetupTime - определяет сколько тактов HSE прошло между тем, как CE стало 0 и тем как WE так же стало 0.
WaitSetupTime - Время, которое WE находится в 0.
HoldSetupTime - Время, которое продолжают идти адресные данные после установки WE в 1. (В расчётах данного параметра можно опираться на время установки СЕ)
HiZSetupTime - Время в тактах, которое проходит между тем как CE стало 0 и началом передачи команды.

В параметры p пишутся минимальные значения без 1. Если не работает (выдаётся ошибка) попробуйте увеличить времена. Бывает помогает. Для иллюстрации сказанного приведу 2 картинки. Первая - тайминги из технической документации микроконтроллера, второе - из технической документации на память.


Рис 1: Тайминги микроконтроллера.


Рис 2: Тайминги памяти.

Из рисунков видно, что SetupTime = tCS - tWP, WaitSetupTime = tWP, HoldSetupTime = tch, HiZSetupTime = tcs-tds.

FSMC_Bank = FSMC_Bank2_NAND - записывается потому, что у нас NAND, который как раз относится ко второму банку памяти.
FSMC_Waitfeature = FSMC_Waitfeature_Enable -
FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_8b - размер шины данных по которой подключается память. Бывает либо 16-ти битное подключение либо 8-ми. У нас 8-мибитное.
FSMC_ECC = FSMC_ECC_Enable - ECC(Error Correcting Code) Коррекция ошибок. Включаем. Но, думаю, можно обойтись без этого.
FSMC_ECCPageSize = FSMC_ECCPageSize_1024Bytes - Я не совсем понял, что это, но после не долгих поисков находим в 5-м байте ID нашей микросхемы, равном 54h, число 5, которому. если верить таблице ниже, соответствует 1кБ ECC.
TCLRSetupTime = 0x00 -
TARSetupTime = 0x00 -

Теперь вся настройка закончена и мы записываем это в регистры:
FSMC_NANDInit(&FSMC_NANDInitStructure);

Включаем FSMC и ECC.
FSMC_NANDCmd(FSMC_Bank2_NAND, ENABLE);
FSMC_NANDECCCmd(FSMC_Bank2_NAND,ENABLE);

Далее надо обязательно сбросить FSMC. У меня иначе отказывалось работать.
NAND_Reset();
Как сделать reset, будет описано ниже.

Функции общения с памятью с помощью FSMC

Первым делом напишем команды посылки команды, чтения и записи данных.

Немного define. Код.
#define CMD_AREA (uint32_t)(1<<16) /* A16 = CLE high */
#define ADDR_AREA (uint32_t)(1<<17) /* A17 = ALE high */
#define DATA_AREA ((uint32_t)0x00000000)
#ifndef Bank_NAND_ADDR
#define Bank_NAND_ADDR Bank2_NAND_ADDR
#endif
#ifndef Bank2_NAND_ADDR
#define Bank2_NAND_ADDR ((uint32_t)0x70000000)
#endif

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

Функции общения с памятью. Код.
//-----------------------------------------------------
// 0 Write command to NAND
//-----------------------------------------------------
void NAND_WriteCommand(uint8_t Cmd)
{
*(__IO uint8_t *)(Bank_NAND_ADDR | CMD_AREA) = Cmd;
}

//-----------------------------------------------------
// 1 Write addres to NAND
//-----------------------------------------------------
void NAND_WriteAddr(uint8_t Addr)
{
*(uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = Addr;
}

//-----------------------------------------------------
// 2 Write Data to NAND
//-----------------------------------------------------
void NAND_WriteDataByte(uint8_t Data)
{
*(uint8_t *)(Bank_NAND_ADDR | DATA_AREA)=Data;
}

//-----------------------------------------------------
// 3 Read Data from NAND
//-----------------------------------------------------
uint8_t NAND_ReadDataByte(void)
{
uint8_t Data;

Data = *(__IO uint8_t *)(Bank_NAND_ADDR | DATA_AREA);
return Data;
}


Теперь с помощью этих функций мы будем общаться с нашей микросхемой памяти, а для этого нам придётся обратиться к списку её команд, представленному на рис 3.


Рис 3: Список команд микросхемы памяти K9LBG08U0E

Далее надо разобраться как же эти команды правильно использовать. Для примера возьмём чтение ID, как основного.


Рис 4: Временная диаграмма чтения ID.

Из рисунка видно, что надо подать сначала команду 0x90, затем адрес 0x00 и далее считать байты данных. В нашем случае это будет: 0xECD7C5725442. Данная последовательность почти полностью характеризует память. Кстати, данная операция работает на многих микросхемах NAND. Т.о. вы можете анализировать, что за микросхема подключена к вашему МК.

Функция чтения ID. Код.
//-----------------------------------------------------
// 4 Read ID NAND
//-----------------------------------------------------

uint8_t NAND_ReadID(NAND_IDTypeDef* NAND_ID)
{
if (NAND_ReadyWait() == NAND_TIMEOUT) {return NAND_TIMEOUT;}
NAND_WriteCommand(0x90);
if (NAND_ReadyWait() == NAND_TIMEOUT) {return NAND_TIMEOUT;}
NAND_WriteAddr(0x00);
if (NAND_ReadyWait() == NAND_TIMEOUT) {return NAND_TIMEOUT;}

NAND_ID->Maker_ID = NAND_ReadDataByte();
NAND_ID->Device_ID = NAND_ReadDataByte();
NAND_ID->Third_ID = NAND_ReadDataByte();
NAND_ID->Fourth_ID = NAND_ReadDataByte();

return NAND_OK;
}

В данном коде у нас встречается не описанная пока функция NAND_ReadyWait(), ожидающая готовности памяти. В остальном, думаю, работа функции очевидна.

Функция ожидания готовности памяти. Код.
#define NAND_TIMEOUT ((uint8_t)0x01)

//-----------------------------------------------------
// 5 Waiting for readiness NAND
//-----------------------------------------------------

uint8_t NAND_ReadyWait()
{
uint32_t timeouttemp=100000;

while(!GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_6))
{
if (timeouttemp) {timeouttemp--;} else {return NAND_TIMEOUT;}
}
return NAND_OK;
}

ID мы читать научились, но нам надо писать и читать данные в память. Давайте что-нибудь запишем в память. Для этой процедуры нам прежде всего надо найти как во времени должны идти данные. В технической документации к нашей микросхеме это выглядит так, как показано на рис 5


Рис 5: Временная диаграмма записи данных в память.

По рисунку совершенно не понятно, что такое Row и Column адреса. Чтобы это понять, надо разобраться как же адресуются данные в памяти микросхемы. Рассмотрим внимательнее рисунок 6.


Рис 6: структура памяти.

Как можно видеть из рисунка, память состоит из блоков, которые в свою очередь состоят из страниц, состоящих уже из байт. В нашей микросхеме 1 страница содержит 8кБ данных. 1 блок состоит из 128 страниц, а сама микросхема из 2000 блоков. кроме того, все страницы имеют немного дополнительной памяти. В нашем случае это 436 байт на страницу. На блок, размер которого 1МБ, приходится дополнительных 54,5кБ памяти.
Для записи в память, надо указать номера блока, страницы и байта. При последовательной записи данных, эти параметры надо указать один раз и далее микросхема сама увеличивает адрес байта для записи на 1. Т.е. вам просто требуется последовательно отправлять байты в память.
Разберёмся в каком формате надо в память подавать данные. Он расписан на рис 7.


Рис 7: формат пакета для записи адреса байта.

Как видно из рисунка 7, все адреса компактно упаковываются в пять байт. При это адрес байта в странице описывается отдельно. Для упаковки и распаковки воспользуемся функциями:

Функция упаковки адреса. Код.
#define ADR_3RD_CIRCLE(adr) (uint8_t)(adr&0xFF)
#define ADR_4TH_CIRCLE(adr) (uint8_t)((adr>>8)&0xFF)
#define ADR_5TH_CIRCLE(adr) (uint8_t)((adr>>16)&0xFF)

//-----------------------------------------------------
// Result = BlockNumber+PageNumber to write to NAND
//-----------------------------------------------------

uint32_t getDataAdr(uint16_t BlockNumber, uint8_t PageNumber)
{
return ((uint32_t)PageNumber) | ((uint32_t)BlockNumber << 7);
}

Теперь можно написать процедуру записи N байт в память:

Функция записи в память. Код.
#define NAND_CMD_WR0 ((uint8_t)0x80)
#define NAND_CMD_WR1 ((uint8_t)0x10)

//-----------------------------------------------------
// 15 Write N byte to NAND (N < 8192)
//-----------------------------------------------------

uint8_t NAND_WriteNByte(uint8_t* Buffer, uint16_t BlockNumber, uint8_t PageNumber, uint16_t ByteNumber, uint16_t N)
{
uint32_t adr = getDataAdr(BlockNumber, PageNumber);
uint16_t i=0;
uint8_t status;

NAND_WriteCommand(NAND_CMD_WR0);
NAND_WriteAddr((ByteNumber&0xFF));
NAND_WriteAddr(((ByteNumber>>8)&0xFF));
NAND_WriteAddr(ADR_3RD_CIRCLE(adr));
NAND_WriteAddr(ADR_4TH_CIRCLE(adr));
NAND_WriteAddr(ADR_5TH_CIRCLE(adr));

if (NAND_ReadyWait() == NAND_TIMEOUT) {return NAND_TIMEOUT;}


for (i=0; i<N;i++)
{
NAND_WriteDataByte(Buffer[i]);
}//for (i=0; i<PAGE_SIZE;i++)

NAND_WriteCommand(NAND_CMD_WR1);

if (NAND_ReadyWait() == NAND_TIMEOUT) {return NAND_TIMEOUT;}

status = NAND_ReadStatus();

if ((status & NAND_READY) == NAND_READY)
{return NAND_OK;}
else
{return NAND_ERROR;}
}

Аналогично описывается чтение из памяти.

Собственно, по работе с памятью мне добавить особо больше нечего. Разве что, можете скачать мои функции для работы с FSMC с сайта.
Категория: Уроки по программированию stm32f1xx | Добавил: Korvin (29.12.2012)
Просмотров: 24111 | Комментарии: 3 | Рейтинг: 4.2/4
Всего комментариев: 3
3 Korvin  
0
Да. Т.к. записываются только 0. Т.е. в ячейку с 0x00 не запишется больше ничего. Стирание же делает все ячейки 0xFF.

2 mihail_nik  
0
Спасибо за статью. Подскажите правильно я понимаю. Касательно самсунговской памяти. Что бы перезаписать какие то данные нужно сначало стереть целый блок или страницу?

1 Fill  
0
Спасибо большое за статью, очень помогла!

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]