STM32 Mass Storage Device. Использование FLASH-памяти.

Микроконтроллеры STM32 и USB

Уже не раз обсуждались на нашем сайте темы, касающиеся связи микроконтроллеров STM32 и ПК по интерфейсу USB, вот они:

Так вот, сегодня в очередной раз вернемся к этой теме, и задачей сегодняшней статьи станет реализовать Mass Storage Device (MSD) на микроконтроллере STM32. В этой статье я буду использовать STM32F10x и среду разработки Keil, но как и всегда с STM32 (ну или почти всегда) особых проблем с портированием на микроконтроллеры других семейств возникнуть не должно.

Как и обычно при работе с USB за базу возьмем готовые библиотеки от ST. Вообще у них на официальном сайте есть куча готовых проектов под разные отладочные платы с реализацией разных режимов работы USB (и MSD в том числе). Там, в частности, есть примеры Mass Storage Device для следующих случаев:

  • в качестве внешней памяти используется SD-карта
  • в качестве внешней памяти используется микросхема NAND-памяти

Предлагаю добавить в нашу задачу немного оригинальности 🙂 Поэтому давайте для хранения информации задействуем внутреннюю память микроконтроллера. То есть часть flash-памяти будет определяться как внешний накопитель. Это конечно не очень практично, все-таки объем памяти микроконтроллера измеряется сотнями килобайт, поэтому использовать контроллер для хранения каких-нибудь данных не получится. С другой стороны, решение этой задачи будет полезно для понимания основных принципов, которых нужно придерживаться при реализации драйвера Mass Storage Device на STM32. Кроме того, можно использовать этот пример как базу при написании USB-bootloader’а для перепрограммирования готовых устройств.

В общем, давайте уже перейдем к практической части!

В первую очередь, в библиотеках для работы с MSD нужно настроить USB Disconnect Pin, а точнее, задать тот порт ввода-вывода и тот номер пина, который задействован на используемой плате для программного отключения/подключения USB. На моей плате для этого используется вывод микроконтроллера PA10, поэтому в файле platform_config.h я определил:

#define USB_DISCONNECT                                           GPIOA
#define USB_DISCONNECT_PIN                                       GPIO_Pin_10
#define RCC_APB2Periph_GPIO_DISCONNECT                           RCC_APB2Periph_GPIOA

Непосредственная настройка этого вывода производится в функции USB_Disconnect_Config():

void USB_Disconnect_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	/* Enable USB_DISCONNECT GPIO clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_DISCONNECT, ENABLE);

	/* USB_DISCONNECT_PIN used as USB pull-up */
	GPIO_InitStructure.GPIO_Pin = USB_DISCONNECT_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_Init(USB_DISCONNECT, &GPIO_InitStructure);
}

Здесь нам ничего менять и настраивать дополнительно не требуется.

Переходим к функциям USB. И тут нас интересует файл mass_mal.c. В этом файле нам нужно реализовать функции записи/чтения и инициализации, в соответствии с тем типом памяти, который мы будем использовать. То есть если бы мы хотели использовать внешнюю карту памяти, то нам нужно было использовать функции записи/чтения SD-карты. Поскольку мы будем работать с внутренней flash-памятью контроллера, то и использовать мы будем соответствующие функции.

Как вы помните из статьи, посвященной работе с flash (ссылка), для того, чтобы начать работать с памятью ее нужно разблокировать. Поэтому функция MAL_Init() будет выглядеть так:

uint16_t MAL_Init(uint8_t lun)
{
	switch (lun)
	{
		case 0:
			FLASH_Unlock(); 
			break;

		case 1:
			return MAL_FAIL;

		default:
			return MAL_FAIL;
	}
	
	return MAL_OK;
}

При инициализации мы разблокируем память и после этого можем вызывать функции чтения/записи из Standard Peripheral Library. Но для начала нам нужно определить сектора, которые будут использоваться в качестве внешнего накопителя (всю flash-память мы использовать не можем, поскольку часть памяти будет занята нашей программой). Итак, в файле mass_mal.h задаем:

#define FLASH_DISK_START_ADDRESS                                 0x0800A000 /* Flash start address */
#define FLASH_DISK_SIZE                                          221184
#define FLASH_PAGE_SIZE                                          2048 /* 2K per page */

Flash-память у нас начинается с адреса 0x08000000. Для основной прошивки мы выделили 40 кБ (адреса с 0x08000000 – по 0x0800A000). Общий объем памяти моего контроллера – 256 кБ. Тогда объем внешнего диска получаем равным 221184 байт (256 кБ – 40 кБ). А размер страницы памяти для данного контроллера составляет 2кБ или 2048 байт. Все эти данные мы и задали в файле mass_mal.h.

В общем то, теперь нам осталось только написать функции чтения и записи – MAL_Write() и MAL_Read(). Тут нам надо учесть несколько факторов:

  • перед началом записи страницы, в которые будет произведена запись, необходимо предварительно очистить
  • запись и чтение производятся целыми секторами
  • перед вызовом функций работы с flash-памятью необходимо убедиться, что предыдущая операция завершена ( тут нам нужна функция FLASH_WaitForLastOperation() ).

Учтем все вышеперечисленное и в итоге получаем следующую реализацию функций:

/****************************************************************************************
* Function Name            : MAL_Write
* Description              : Write sectors
* Input                    : None
* Output                   : None
* Return                   : None
****************************************************************************************/
uint16_t MAL_Write(uint8_t lun, uint32_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{
	uint16_t i;
	switch (lun)
	{
		case 0:
			for(i = 0; i < Transfer_Length; i += FLASH_PAGE_SIZE)
			{
				if (FLASH_WaitForLastOperation(WAIT_TIMEOUT) != FLASH_TIMEOUT)
				{
					FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
				}

				FLASH_ErasePage(FLASH_DISK_START_ADDRESS + Memory_Offset + i);
			} 

			for(i = 0; i < Transfer_Length; i += 4)
			{
				if(FLASH_WaitForLastOperation(WAIT_TIMEOUT) != FLASH_TIMEOUT)
				{
					FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
				}

				FLASH_ProgramWord(FLASH_DISK_START_ADDRESS + Memory_Offset + i , Writebuff[i >> 2]);
			}			
			break;

		case 1:
			break;

		default:
			return MAL_FAIL;
	}

	return MAL_OK;
}


/****************************************************************************************
* Function Name            : MAL_Read
* Description              : Read sectors
* Input                    : None
* Output                   : None
* Return                   : Buffer pointer
****************************************************************************************/
uint16_t MAL_Read(uint8_t lun, uint32_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length)
{
	uint16_t i;
	switch (lun)
	{
		case 0:
			for(i = 0; i < Transfer_Length; i += 4) 
			{
				Readbuff[i >> 2] = ((vu32*)(FLASH_DISK_START_ADDRESS + Memory_Offset))[i >> 2]; 
			}
			break;

		case 1:
			break;

		default:
			return MAL_FAIL;
	}

	return MAL_OK;
}

У нас осталась еще одна важная функция, необходимая для работы с MSD, а именно функция MAL_GetStatus(). В этой функции мы должны передать в специальные переменные значения, соответствующие размеру памяти, размеру страницы, а также количеству блоков:

uint16_t MAL_GetStatus (uint8_t lun)
{
	if (lun == 0)
	{
		Mass_Block_Count[0] = FLASH_DISK_SIZE / FLASH_PAGE_SIZE; 
		Mass_Block_Size[0] =  FLASH_PAGE_SIZE; 
		Mass_Memory_Size[0] = FLASH_DISK_SIZE; 
		return MAL_OK;
	}

	return MAL_FAIL;
}

Собираем проект, прошиваем микроконтроллер и видим, что в диспетчере устройств у нас появилось новое запоминающее устройство. А кроме того, система обнаружила внешний накопитель, объем которого соответствует тому значению, которое мы определили в нашей программе. Таким образом, цель сегодняшней статьи можно считать достигнутой!

Как и всегда, прилагаю готовый проект для среды разработки Keil: USB_MassStorage_Project.

Поделиться!

Подписаться
Уведомление о
guest
46 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Elis
Elis
5 лет назад

Спасибо. Очень кстати статья.

Alexey
Alexey
5 лет назад

Спасибо за статью! Скажите, можно ли наладить передачу данных с контроллера на пк через usb в режиме full speed usb2.0! Без эмуляции последовательного порта!

Паша
Паша
5 лет назад

Спасибо за статью! Переделал под Stm32F303VC
Поругался компилятор на “FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR” из строки FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
оставил FLASH_ClearFlag(FLASH_FLAG_EOP );
что за флаги за что отвечают?
P.S. После форматирования из 216 кб осталось 192 кб для чтения, записи. Т.е. при создании USB bootloader-a и записи туда bin файла программа явно будет стартовать не с адреса 0x0800A000

Александр
Александр
5 лет назад

Коля Соколов спасибо вам за проект
Только-бы там ошибочку исправить – буфер для записи сектора во флеш память – 2 килобайта, у вас в проекте 64*8=512, странно что этот проект корректно работает

Александр
Александр
5 лет назад

uint32_t Data_Buffer[BULK_MAX_PACKET_SIZE *8]; /* 2048 bytes*/
#define BULK_MAX_PACKET_SIZE 0x00000040

Василий
Василий
5 лет назад

Скачал Ваши исходники и заметил, что в файле hw_config.c в ф-ции Set_System только:
void Set_System(void)
{
SystemInit();

/* Enable and Disconnect Line GPIO clock */
USB_Disconnect_Config();

Led_Config();
/* MAL configuration */
MAL_Config();
}
Например, в примере от ST там содержится следующее:
EXTI_ClearITPendingBit(EXTI_Line18);
EXTI_InitStructure.EXTI_Line = EXTI_Line18;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

без, которого по идее USB устройство будет не корректно определяться.
И еще в ф-ции MAL_Init только “анлок” флэш памяти…а где инициализации работы узб в качестве внешнего накопителя?

Василий Hound
Василий Hound
5 лет назад

А как FatFS поможет с местом под FAT разметку? В любом случае же нужно хранить где FAT разметку.
Если только сделать “хитрости” в духе: сохраняем начало разметки, а остальную часть просто выдаем нулями.
После прошивки у меня получилось порядка 20 страниц (проц 1кБ страница) заполненных нулями.

Astrgan
Astrgan
5 лет назад

Большое спасибо за статью!!!
Очень интересна тема с USB и в особенности mass storage.

А какая скорость получается у данного девайса, это USB 2.0?

Astrgan
Astrgan
5 лет назад

Скорость зависит от камня или еще что-то влияет?

Anton
Anton
4 лет назад

А каким образом прикрутить FAT FS, чтобы во флэш можно было писать файл с данными, а при подключении к компьютеру считывать его на ПК?

Anton
Anton
4 лет назад

Может есть ссылка на рабочий пример

Anton
Anton
4 лет назад

Дешево и сердито SPI FLASH. Может есть где-то пример.

Apparatchik
Apparatchik
4 лет назад

Здравствуйте.

Перенес проект в Eclips под gcc, получил hard fault, методом тыка выяснил: если закоментировать строчку
_SetCNTR(wInterrupt_Mask); в функции PowerOn то hard faulta нету. Куда копать?
И есче если у меня страница = 1к то мне нужно BULK_MAX_PACKET_SIZE изменить на 32 ?

Apparatchik
Apparatchik
4 лет назад

Без этой строчки при подключении получаю hard fault

Apparatchik
Apparatchik
4 лет назад

Вопрос снят, разобрался. Теперь виндовс не удается завершить форматирование.

Вася
Вася
Reply to  Apparatchik
11 месяцев назад

Красавчик, мне-бы тоже разобраться….
Те-же грабли, третий день мучаюсь….

Александр
Александр
4 лет назад

Здравствуйте.
Подскажите, а что за плату вы используете?
STM32VLDISCOVERY?

Дмитрий
Дмитрий
4 лет назад

Здравствуйте. Пытаюсь этот проект адаптировать под CubeMX.
Модифицировал только файл usbd_stotage_if.c

Задал следующее
#define FLASH_DISK_SIZE 14
#define FLASH_DISK_START_ADDRESS 0x0800C800 // 50page

int8_t STORAGE_Read_FS (uint8_t lun,
uint8_t *buf,
uint32_t blk_addr,
uint16_t blk_len)
{
/* USER CODE BEGIN 6 */

uint16_t i;
switch (lun)
{
case 0:
for(i = 0; i > 2] = ((uint16_t*)(FLASH_DISK_START_ADDRESS + blk_addr))[i >> 2];
}
break;
case 1:
break;
default:
return (USBD_FAIL);
}

return (USBD_OK);
/* USER CODE END 6 */
}

int8_t STORAGE_Write_FS (uint8_t lun,
uint8_t *buf,
uint32_t blk_addr,
uint16_t blk_len)
{
/* USER CODE BEGIN 7 */

uint32_t PAGEError = 0;
uint16_t i;

EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.NbPages = 1;

switch (lun)
{
case 0:
for(i = 0; i < blk_len; i += FLASH_PAGE_SIZE)
{
EraseInitStruct.PageAddress = FLASH_DISK_START_ADDRESS + blk_addr + i;
HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError);
}

for(i = 0; i > 2]);
}
break;
case 1:
break;
default:
return (USBD_FAIL);
}

return (USBD_OK);
/* USER CODE END 7 */
}

int8_t STORAGE_GetCapacity_FS (uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
/* USER CODE BEGIN 3 */
if (lun == 0)
{
*block_num = FLASH_DISK_SIZE;
*block_size = FLASH_PAGE_SIZE;
return (USBD_OK);
}
return (USBD_FAIL);
/* USER CODE END 3 */
}

В итоге на ПК отображается носитель, и видит, что 14 Кб но не может отформатировать.
Подскажите, будьте добры, что я делаю не так?
Предполагаю, что банально буфер у меня не задан для USB нужного размера. В данном режиме USB банально не пойму где он задается. В общем подскажите, заранее спасибо!

Dimaster
4 лет назад

И опять у меня вопросы.
1) А что тут означает сдвиг вправо на два?
2) Цифра 4 в данном цикле
for(i = 0; i < Transfer_Length; i += 4)
это количество бит?
3) Почему в новых функциях приема и передачи, сгенеренных кубом, аргумент *buf 8-битный? По сколько бит вообще идет и передача по USB?
4) Буфер приема и передачи где-то регулируется? Если нет, то как он определяется?
Заранее спасибо, извиняюсь за дилетанствкий

Dimaster
4 лет назад

Ладно, более менее разобрался, что тут зачем было.
Не понятно осталось следующее:
1) Как все таки задается буфер приема и передачи данных. Как и чем определяется его величина и где он задается?
2) Форматирование у меня не происходит в частности по той причине, что аргумент blk_len функции STORAGE_Write_FS всегда равен нулю. Как так? Может надо чего подправить или 14 Кб слишком маленькая величина для файловой системы FAT? Не понятно
Заранее спасибо автору за ответ!

Dimaster
4 лет назад

Ну, теоретически удалось поправить. Но проверить это нет возможности, так как приходит blk_len равное нулю и соответственно в цикл ни заходим и ничего не пишем. Можете что-нибудь по этому поводу сказать?
Ну и по буферу, все таки важен он? Мне так и не удалось понять, где он определяется. Все очень накручено, можете подсказать

Dimaster
4 лет назад

Уважаемые, чего делать-то? Подскажите. Нашел я предположительно размер буфера. По идее его рулит параметр MSC_MAX_FS_PACKET. И обращений при его изменении стало больше в функцию записи при форматировании с ПК. Да только blk_len все равно равен 0. Что за бред? Может есть минимальный таки размер кластера, меньше которого нельзя? Уважаемый Aveal, скажите более конкретно, что я делаю не так

Dimaster
Reply to  Aveal
4 лет назад

А у вас есть идеи на этот счет?

Dimaster
Reply to  Aveal
4 лет назад

Уважаемый Aveal, вы можете сделать данный пример, только на CubeMX. Буду очень благодарен, так как судя по форумам не я один не понимаю как это все запустить, а для вас это по идее очень просто и не займет много времени. Все очевидно несколько сложнее, чем описано в данной статье ну или я полнейший идиот. Заранее спасибо!

Dimaster
3 лет назад

Здравствуйте. Докладываю сводки с фронтов. Несмотря на все расспросы, внятного объяснения, чего сделать, чтобы заработало, мне к сожалению никто так и не дал (ну или я слишком туп). Помогли товарищи китайцы (или кто они там).
http://www.openedv.com/thread-76304-1-1.html
Они, чтоб не заморачиваться, просто взяли в памяти (оперативной) массив создали и его форматировали. И о чудо, все работает (соответственно надо проц что то типа F4, чтоб все влезло). На этом примере просто и внятно можно понять, как все это функционирует. Выяснил следующее:
1) Размер массива должен быть не менее 20Кб + место под ваши данные. 20Кб это я так понял место под таблицу файловой системы.
2) Размер блока (STORAGE_BLK_SIZ) работает (у меня во всяком случае) только 512 бит. Не больше и не меньше. Мутил, крутил, менял размер буфера. Ничего не помогло. Если кто-то подскажет как сделать так, чтобы работало при другой цифорке, буду благодарен.
3) Форматируется с компа только в режиме Быстрое. Обычное форматирование не прокатывает. Тоже бы было интересно узнать, почему так.
Как-то так

Владимир
Владимир
3 лет назад

Здравствуйте! Вы пишите: В первую очередь в библиотеках для работы с MSD нужно настроить USB Disconnect Pin. Насколько это важно? Если я правильно понимаю, это для того, чтобы не перевтыкать USB вручную. Если нет, можно увидеть схему включения?

Олег
Олег
2 лет назад

Этот пример от ST широко используется в обучающих статьях. Но нигде нет информации зачем нужен lun – логический юнит нумбер.

Я так понял, что этот lun назначит хост если создавать на одном МК несколько накопителей. Если я прав в предположениях, то может у кого-то подсказка как из этого примера сделать два накопителя?

ammv
ammv
4 месяцев назад

Привет всем), Очень понравилась статья, прочитал и переделал на STM 32F3-Discovery. Всё компилируется, виндоус определяет как носитель флеш памяти, и предлагает отформатировать, при этом определяет ёмкость размер секторов. Но процесс форматирования виндоус завершает отрицательно. Подскажите в чём может быть дело. Заранее благодарен).

Максим
Максим
1 месяц назад

Здравствуйте
 
столкнулся с такой проблемой, компелируется нормально, а прошиваться не хочет, плата stm32f407 disc
Contents mismatch at: 08000144H (Flash=B7H Required=06H) !
 
есть мысли?

MOPE
MOPE
5 дней назад

Пытаюсь сделать MSD на контроллере STM32F217 и компьютер определяет его как mass storage, но при этом при попытке отформатировать память вылетает ошибка. Может кто-нибудь помочь с этим?

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Август 2020
Пн Вт Ср Чт Пт Сб Вс
 12
3456789
10111213141516
17181920212223
24252627282930
31  

© 2013-2020 MicroTechnics.ru