Главная » Статьи » Уроки по программированию 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 с сайта. | |
Просмотров: 24111 | Комментарии: 3 | |
Всего комментариев: 3 | ||||
| ||||