STM32 и USB. Mass storage device. Usb флешка stm32


STM32 и USB. Mass storage device.

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

Так вот, сегодня в очередной раз вернемся к этой теме, и задачей сегодняшней статьи станет реализовать Mass Storage Device (MSD) на микроконтроллере STM32 😉

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

В этой статье я буду использовать 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 – проект MSD для STM32.

microtechnics.ru

EasySTM32 - Загрузчик STM32

В микроконтроллерах STM32 существуют три интерфейса для заливки прошивки: 

  • JTAG
  • SWD
  • UART (через загрузчик)

Как вы уже догадались, в этой статье пойдет речь о последнем способе. Я считаю его не самым лучшим вариантом для постоянного использования, однако в некоторых случаях он очень даже хорош. Вот допустим устройство уже готово и работает у пользователя, и вдруг ВНЕЗАПНО возникает потребность в перепрошивке. Конечно, можно разобрать девайс и подпаяться к отладочному интерфейсу, но это относительно сложно + нужен отладчик. А что если устройство уже соединено с компом через UART ? В этом случае гораздо проще использовать этот интерфейс для загрузки прошивки. Вот тут-то загрузчик будет очень кстати. Пользователю достаточно нажать одну кнопку и девайс входит в режим прошивки. Пару нажатий мышки и прошивка обновлена. Теперь попробуем разобраться более детально как все это работает. Для начала нам нужно подключить наш контроллер к компьютеру через интерфейс USART1.

schematic

Для управления загрузкой контроллера существуют два вывода BOOT1 и BOOT0. В зависимости комбинаций логических уровней на них, контроллер при включении питания начнет выполнять код из разных областей памяти. Это видно из таблицы ниже: 

BOOT1

BOOT0

Что запускается

Х

0

Программа прошитая во FLASH

0

1

Загрузчик

1

1

Программа из SRAM

Как вы помните из недавней статьи, загрузчик сидит в области памяти под названием Sytem Memory. Каким либо образом изменить его нельзя. Это делает контроллер не убиваемым в плане софта, даже если перепрошивку неожиданно прервут  - девайсу ничего не грозит. Всегда можно будет начать прошивку заново. С другими пунктами таблицы все просто: первая комбинация означает, что контроллер будет запускать прошивку которую в него прошили, а последняя комбинация - означает, что контроллер будет выполнять код из ОЗУ который еще как-то туда надо поместить. Пока не совсем понимаю для чего это нужно, разве что программа выполняется быстрей (если верить интернетам). Вернемся к загрузчику. Чтоб ввести наш контроллер в режим прошивки, удерживаем кнопку BOOT и жмем RESET. После этого кнопку можно отпустить. Для прошивки используется специальный софт который называется Flash Loader Demonstrator. Вы можете скачать его на сайте ST или у меня. Процедура прошивки проста до безобразия: Достаточно лишь следовать указаниям мастера. На первом шаге нас попросят выбрать номер ком порта к которому подключен контроллер и указать настройки соединения. Что примечательно у загрузчика есть автодетект скорости. Это значит что можно свободно выбрать любую скорость из списка и оно заработает. Лишь бы ваш адаптер RS232 - UART (или USB-UART) её поддерживал. Мой преобразователь на CP2102 о котором я вкратце уже рассказывал, отлично работает на всех скоростях. Однако, нужно иметь в виду, что загрузчик настраивает контроллер на тактирование от внутреннего генератора. А его частота сильно плавает в зависимости от напряжения питания и температуры. Следовательно если у вас проблемы со стабильностью этих двух параметров, то лучше выбирать маленькую скорость. 

step1

Если соединение с контроллером установлено, то программа нарисует нам окно в котором покажет сколько памяти у программирования контроллера и включена ли защита памяти от чтения. Если контроллер защищен от считывания прошивки, то вы можете снять защиту, но при этом содержимое флеш памяти будет уничтожено. Это делаеется кнопкой "Remove protection" которая у меня не доступна т.к. защита памяти не включена. 

step2

Следующий шаг мастера показывает какие страницы flash памяти защищены от записи/чтения. Нужно не забыть выбрать объем памяти которым обладает программируемый контроллер. Кажется там есть автодетект который сам сделает правильный выбор, но я не уверен. У моего контроллера есть 128 кБайт памяти, что я и выбрал:

step3

Самый интересный шаг мастера. На нем мы можем выбрать то, что хотим сделать с контроллером. Можем очистить память контроллера. Как всю, так и некоторые страницы. Само собой можно прошить контроллер. Программировать и очищать память можно только если это не было запрещено. Есть возможность проверить содержимое памяти после прошивки. Или можно сразу начать выполнение прошитой программы. Чтение памяти возможно опять таки если это не запрещено. Снять или установить защиту от записи/чтения можно в этом же окне. Еще можно редактировать "Option bytes". Что это такое я пока особо не разбирался, поэтому ничего вразумительного сказать не могу. 

step4

После нажатия кнопки Next выполняется выбранное действие. Результат выполнения отображается на последнем шаге мастера. Если всё хорошо, то вы увидите сообщение похожее на это:

step5

Когда работа с загрузчиком завершена нужно перезагрузить контроллер нажатием на RESET. Если кто-то хочет подробнее узнать о протоколе который используется загрузчиком, то можно почитать аппноут AN3315. Возможно, окажется полезным и аппноут AN2606. Если остались вопросы касаемо загрузчика - спашивайте, попробую ответить. 

easystm32.ru

Примеры работы с USB Device для STM32F4-Discovery / STM32 / Сообщество EasyElectronics.ru

Поскольку в нашем сообществе совсем мало статей про работу с STM32F4-Discovery, то я решил выложить несколько примеров работы с USB в STM32F4. Все примеры написаны в IAR. По каким-то причинам ST не выложили документацию на работу с USB в контроллерах STM32F4. Сами библиотеки для работы с USB пришлось взять из примеров для Discovery. Насколько я понял позже, при работе с этими библиотеками можно использовать документацию UM1021 (библиотека USB для STM32F105/7 and STM32F2xx). Все нижеописанные примеры, как мне кажется, достаточно хорошо показывают некоторые режимы работы USB в STM32F4-Discovery, и при этом к ней не требуется подключать какую-либо внешнюю периферию.

В примерах к Discovery работа в режиме «USB Device» показана в примере Demonstration (он прошивается в Discovery при изготовлении). Пример достаточно навороченный, USB в нем используется для эмуляции мыши в системе. Разбираться с USB я начал именно с него. Для того, чтобы было проще разобраться в его работе, я выкинул из него часть кода, и оставил только код для работы в режиме «HID Mouse». При включении производится инициализация периферии платы и USB, после чего весь код выполняется только в прерываниях. В данном случае все просто — в обработчике прерывания SysTick_Handler() опрашивается кнопка на плате, и если она нажата, то вызывается функция USBD_HID_SendReport, которая передает данные о перемещении курсора. Данные всегда одинаковы(константы), так что при нажатии кнопки на Discovery, курсор начинает ползти вверх.

Других примеров для работы в режиме «USB Device» к Discovery не прилагалось, не смотря на то, что в библиотеке USB были файлы для работы и в режиме CDC, и Audio Out. Поискал в интернете примеры работы с USB, нашел только эти:mcu.cz/comment-n2848.htmlmcu.cz/comment-n2800.html Правда, описание там на чешском и используется там TrueSTUDIO, так что решил на базе найденного написать свои примеры, правда, код остался практически тот же.

Сначала «USB Custom HID». В примерах такой класс отсутствовал, и я взял его из чешского примера. Принцип работы такой же, как и в примере от ST для «STM32F10x and STM32L1xx». Посылая на контроллер репорт из одного байта, можно управлять светодиодом, соответствующему номеру репорта. Обработка приема данных идет в файле «usbd_hid_core.c» в функции USBD_HID_DataOut. При нажатии кнопки на Discovery происходит прерывание, на компьютер отправляется репорт из одного байта с описанием состояния кнопки. Проверить работу контроллера можно при помощи программы от ST, USB HID Demonstrator, которая позволяет принимать и отправлять репорты (скриншот есть на чешском сайте).

Дальше захотелось реализовать режим CDC — эмуляция COM-порта. Разработчики из ST выложили класс для CDC, но примера для работы с ним не было. Все нужные исходники опять же есть у чехов, правда в их коде я нашел ошибки, делающие проект неработоспособным. Так же у них при нажатии кнопки в виртуальный COM-порт просто передается символ «A», я пошел немного дальше, и прикрутил printf. При работе программы в виртуальный порт каждую секунду передается строка «Hello, World!». При передаче символов S и A в порт с компьютера, на плате зажигается и гаснет один из светодиодов. Для работы в данном режиме, на компьютер нужно установить драйвер от ST: Vitual COM Driver

Поскольку в примерах к Discovery был класс для работы в режиме «Audio Device», я решил сделать из Discovery внешнюю звуковую карту — работающую на воспроизведение. В интернете нечего похожего не нашел, однако уже готовый такой пример есть у ST в библиотеке USB для STM32F105/7 and STM32F2xx. Все исходники там мало отличались, вся разница в основном только в используемом драйвере внешнего звукового чипа CS43L22. Драйвер там достаточно навороченный, позволяет передавать звук на чип через I2S и DAC контроллера, данные могут передаваться по прерываниям от I2S и при помощи DMA. После того как проект был скомпилирован и прошит, возникла проблема — звук воспроизводился, но качество было просто отвратительным. После того, как я подключил осциллограф и запустил на компьютере генератор синусоидальных сигналов, я обнаружил что форма сигнала на выходе аудио-чипа явно искажена — четверть всего времени воспроизведения сигнал отсутствовал. Позже, после отладки и написания другого проекта, предназначенного только для воспроизведения звука, я обнаружил, что подобные искажения появляются, если функцию воспроизведения звука вызывать через равные промежутки времени — например из прерывания таймера, причем период работы таймера может быть значительно больше времени воспроизведения звука. Аудиоданные при этом передавались через DMA в модуль I2S(этот режим используется в примере от ST). Судя по всему, проблема явно связана с какими-то особенностями аудио чипа. Для того, чтобы получить более приличный звук, я использовал DAC контроллера(аудио чип работает усилителем), звук при этом стал более-менее сносным, но одноканальным(воспроизводится только левый канал). Получить стерео звук можно только в режиме I2S. Я попробовал передавать звук по прерываниям от I2S — и искажения пропали. Правда, при воспроизведении данные от USB могут приходить в момент, когда звук еще воспроизводится, и иногда, насколько я понял, часть звуковых данных попадает в другой канал. Проявляется это как тихие щелчки, слышно их только в том случае, если звук в каналах различается. Из-за вышеуказанных проблем, мой драйвер отличается от того, что выложен в примерах от ST. В данный момент можно переключать метод воспроизведения — через I2S и через DAC. Это можно сделать, изменив строчку "__IO uint32_t CurrAudioInterface = AUDIO_INTERFACE_I2S;" в файле аудиодрайвера «stm32f4_discovery_audio_codec.c» на "__IO uint32_t CurrAudioInterface = AUDIO_INTERFACE_DAC;"

Исходные файлы:www.dropbox.com/s/pr201jqkweubb2e/my_proj.zip?dl=0

we.easyelectronics.ru