STM32 + Параллельная NAND Flash + файловая система Yaffs. Часть третья


Обещанный пример порта Yaffs под STM32F429, память NAND128-A, интерфейс FMC, доступ к регистрам через библиотеку SPL. Надеюсь никто не будет против, если я не стану прикреплять архив с исходниками, просто дам ссылку на публичный репозитарий. Схему подключения не стану приводить — не вижу никакого смысла, в документации все есть.


Если читатель склонирует мой репозитарий и заглянет внутрь, то обнаружит, что исходники там лежат те же, что и в официальном репозитарии с небольшим отличием. Отличие это, во-первых, в том, что скрипт ./direct/handle_common.sh уже выполнен, а во-вторых, появилась папочка ./direct/stm32. В этой папке собственно и лежат, если так можно выразится, драйвера порта:
  • stm32f4xx_nand128w3.c/.h — непосредственная работа с NAND128-A через интерфейс FMC
  • yaffs_nand128w3a2bn6e.c — порт yaffs для работы с NAND128-A. В этом файле у меня остались printf-логи, если они вам мешают — сносите смело.
  • yaffs_osglue.c — порт для RTOS. Не судите строго, в основном там заглушки, реализовано только выделение памяти, но этого уже хватит для работы. Хотите большего — дерзайте )).

С файлами stm32f4xx_nand128w3.c/.h думаю всё понятно — записать страницу, считать страницу, записать spare, считать spare, стереть блок и т.д. Там же инициализация интерфейса FMC.

Функциями из файла stm32f4xx_nand128w3.c пользуется файл yaffs_nand128w3a2bn6e.c, в котором объявлены функции, указатели на которые будут переданы структуре типа struct yaffs_dev. А этот указатель на эту структуру будет передан файловой системе (см. вторую часть). Позволю себе комментарии по некоторым из них.

Функция yaffs_nand_drv_ReadChunk() осуществляет чтение куска (chunk'а) данных (что это такое см. вторую часть). Обращаю внимание на то, что вызове этой функции происходит загрузка не только данных из страницы, но связанных с ней тэгов из области spare.

static int yaffs_nand_drv_ReadChunk(struct yaffs_dev *dev, int nand_chunk,
				   u8 *data, int data_len,
				   u8 *oob, int oob_len,
				   enum yaffs_ecc_result *ecc_result_out)
{
	u8 readEcc[2][3];
	u8 buf[SPARE_LEN];
	enum yaffs_ecc_result ecc_result;
	int ret[2];

	if (data) readPage (nand_chunk, data, data_len); /*<< Загрузка страницы */
	if (oob) readSpare(nand_chunk, buf, SPARE_LEN); /*<< Загрузка тэгов */
	if (oob_len>16) oob_len = 16;
	memcpy(oob, buf, oob_len);
	
	return YAFFS_OK;
}



Аналогично, только на запись работает yaffs_nand_drv_WriteChunk().

static int yaffs_nand_drv_WriteChunk(struct yaffs_dev *dev, int nand_chunk,
				   const u8 *data, int data_len,
				   const u8 *oob, int oob_len)
{
	u8 buf[SPARE_LEN];
	if (oob_len>16) oob_len = 16;
	if (oob) memcpy(buf, oob, oob_len);
	if (data) programPage(nand_chunk, (u8*)data, data_len); /*<< Запись данных */
	if (oob) programSpare(nand_chunk, buf, SPARE_LEN);/*<< Запись тэгов */
	return YAFFS_OK;
}



Остальные функции значительно проще: yaffs_nand_drv_MarkBad(), yaffs_nand_drv_MarkBad() — пометить блок битым, проверить не битый ли; yaffs_nand_drv_Initialise()/yaffs_nand_drv_Deinitialise() — инициализация/деиницализация.

Место, где все это собирается воедино и передается файловой системе, как я уже говорил это ранее это функция yaffs_start_up():

int yaffs_start_up(void)
{
	struct yaffs_dev *dev = &devNand128w3a2bn6e;

	dev->param.name = dev_name;
	dev->driver_context = NULL;
	dev->param.start_block = 0;
	dev->param.end_block = 1023;
	dev->param.chunks_per_block = 32;
	dev->param.total_bytes_per_chunk = 512;
	dev->param.spare_bytes_per_chunk = 16;
	dev->param.is_yaffs2 = 0;
	dev->param.use_nand_ecc = 0;
	dev->param.n_reserved_blocks = 5;
	dev->param.inband_tags = 0;
	dev->param.n_caches = 10;

	dev->tagger.write_chunk_tags_fn = NULL;
	dev->tagger.read_chunk_tags_fn = NULL;
	dev->tagger.query_block_fn = NULL;
	dev->tagger.mark_bad_fn = NULL;
	yaffs_tags_compat_install(dev);

	dev->drv.drv_write_chunk_fn = yaffs_nand_drv_WriteChunk;
	dev->drv.drv_read_chunk_fn = yaffs_nand_drv_ReadChunk;
	dev->drv.drv_erase_fn = yaffs_nand_drv_EraseBlock;
	dev->drv.drv_mark_bad_fn = yaffs_nand_drv_MarkBad;
	dev->drv.drv_check_bad_fn = yaffs_nand_drv_CheckBad;
	dev->drv.drv_initialise_fn = yaffs_nand_drv_Initialise;
	dev->drv.drv_deinitialise_fn = yaffs_nand_drv_Deinitialise;

	/* The yaffs device has been configured, install it into yaffs */
	yaffs_add_device(dev);

	return YAFFS_OK;
}

Осталось последнее — привести пример работы с файловой системой:


void execYaffsExample (void)
{
	initNand128w3a2bn6e();
//	yaffs_trace_mask = YAFFS_TRACE_BAD_BLOCKS;
//	yaffs_trace_mask |= YAFFS_TRACE_WRITE;
//	yaffs_trace_mask |= YAFFS_TRACE_VERIFY_FULL;
	yaffs_start_up();
	yaffs_mount("nand");

	char str[] = "This string is placed in NAND!\r\n\0\0\0";

        //Пытаемся открыть файл для чтения
	f = yaffs_open("nand/file0.txt", O_RDONLY, S_IREAD);
	if (f != -1)
	{
                //Файл открылся, читаем его содержимое и выводим в лог
		memset (str, 0x00, 35);
		yaffs_read(f, str, 35);
		yaffs_close(f);
                
                printf("----> %s \r\n", str);
	}
        else
        {
            //Файл открыть не удалось - его еще не создали
            printf("----> There is no file. Let's create it...\r\n");

            //Пытаемся открыть файл для записи
	    int f = yaffs_open("nand/file0.txt", O_CREAT | O_RDWR, (S_IREAD | S_IWRITE));
	    if ( f != -1)
	    {
                //Успешно! Запишем тестовую строку
		yaffs_write(f, str, 35);
		yaffs_flush(f);
		yaffs_close(f);
                printf("----> Done! Restart the app to read the file.\r\n");
	    }
        }
}



Обратите внимание на глобальную переменную yaffs_trace_mask, она предназначена для включения отладочных логов самой файловой системы. Очень полезная штука.

Спасибо всем, кто терпеливо прочитал все три статьи!

Содержание


Часть первая
Часть вторая
Часть третья

Комментарии (16)

RSS свернуть / развернуть
Что за CANOpen? Где лучше её применять?
0
Протокол для шины CAN. Достаточно распространен — автоматизация, автомобильная электроника, робототехника и т.п. Хотел рассказать про сам протокол и про достунпые стеки и как их натягивать.
0
С CAN я знаком по шине автомобиля и программой CANhacker, но я так понимаю это какой-то свой велосипед и шиной CAN авто не применим?
0
Шина CAN это физический интерфейс, а CANOpen это один из достаточно распространенных протоколов для этой шины. Кстати в некоторых автомобилях применен именно CANOpen.
0
интересно будет почитать
0
очень очень интересно про CANOpen, особенно как его натянуть на FreeRTOS
0
никогда не слышал, чтобы в автопроме CANOpen применяли :-/
0
Добрый день. А это в конечном счёте заработало?
Я пытаюсь в Keil это собрать.
0
Да, заработало. В keil мой коллега тоже собрал, единственное некоторых типов и хедеров там по умолчанию нет (в gcc они идут в их стандартной библиотеке) — их нужно добавить.
0
Сколько RAM требуется минимум? Какие параметры можно подрезать, чтобы влезть в 128 кБ?
0
128 kB вряд ли хватит, а если и хватит то впритык, на остальное места не найдется. Я не помню точно цифру но кажется в районе ста килобайт нужно. У нас куча 600 kB. Из этой же кучи FreeRTOS берет память, поэтому точной цифры у меня нет.
0
Я собрал это для ARM Cortex M0
Много было проблем с malloc()
Для 128 MB NAND ушло почти 512 kB RAM

Но у меня есть проблема. Файл не открывается после перезагрузки.
1. Создаю файл
2. Пишу содержимое
3. Читаю содержимое
4. закрываю файл.
5. Делаю ресет проца
6. Файл он не открывает, хотя yaffs_freespace возвращает то же значение и инициализация прошла нормально.
0
У Вас поднят printf? Если да, то попробуйте включить логи:


      yaffs_trace_mask = YAFFS_TRACE_BAD_BLOCKS;
      yaffs_trace_mask |= YAFFS_TRACE_WRITE;
      yaffs_trace_mask |= YAFFS_TRACE_VERIFY_FULL;


Что пишет файловая система в логи при попытке открыть файл?
0
Его нету у меня. Как раз нужно не забыть его выключить…

Вообще починил. Взял и обновил все файлы с их сайта + исправил все Keilовские заскоки, вроде приведения типов.
Сейчас файлы отрываются после сброса.

Помимо всего прочего есть «железная» ошибка в функции yaffs_add_device из yaffsfs.c

без этой строчки у меня не живёт :INIT_LIST_HEAD(&yaffsfs_deviceList);
Этот список больше нигде не проинициализирован.
0
Хм, интересно. А вы где вызываете INIT_LIST_HEAD()?
0
Я написал прямо в начале функции.
Это конечно не правильно, если будет много устройств и т.д. Где-то глобально нужно это вызывать, разово.

Списки эти вообще какие-то странные объекты. Они могут быть бесконечными. Но для того, чтобы создался список нулевой длины, он должен быть проинициализирован. Т.е. в значении указателя next должен быть адрес на сам next, а «по Keilу» там 0x00000000
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.