Продолжаем микро-цикл статей (в рамках макро-цикла, посвященного работе с STM32CubeMx), в которых мы разбираемся как настраивать разные режимы работы USB в микроконтроллерах STM32 при помощи Cube 🙂 И сегодня на очереди MSD (USB Mass Storage Device), а также работа с SD-картой памяти. То есть сегодня мы реализуем свой собственный кардридер на микроконтроллере STM32F10x!
Как я уже упомянул во вступлении к статье, сегодня я буду использовать микроконтроллер семейства STM32F10x, а карта памяти будет подключаться по интерфейсу SDIO. Собственно, давайте перейдем в Cube и начнем все поэтапно настраивать…
Первым делом задействуем внешний генератор тактовых импульсов, а также включаем USB и SDIO. Кроме того, выбираем нужный нам режим работы USB, а именно – Mass Storage Class:
Обратите внимание, что при включении какого-либо периферийного модуля STM32CubeMx автоматически отмечает и инициализирует нужные выводы микроконтроллера, вручную этого делать не надо.
Кроме того, у меня на плате используется так называемый USB_DISCONNECT_PIN – вывод, который отвечает за программное подключение/отключение USB. Эта обязанность возложена на PA10, поэтому я дополнительно проинициализировал эту ножку для работы в режиме выхода.
Итак, с этим все понятно, все настроено, давайте перейдем к настройкам тактирования. В очередной раз напоминаю, что этому была посвящена отдельная статья (ссылка), поэтому сейчас мы останавливаться на этом моменте не будем 🙂 Здесь стоит обратить внимание только на один момент, а именно на тактирование USB. Так вот, на модуль USB должны обязательно приходить 48 МГц, иначе работать данная периферия не будет:
Следующим на очереди будет окно дополнительной конфигурации выбранной периферии в STM32CubeMx, но там мы почти ничего не будем менять. Изменим только предделитель частоты для тактирования модуля SDIO:
На этом в принципе и все, переходим к генерации исходного кода, а также файлов проекта. После окончания этой процедуры получаем полностью готовый и настроенный проект, но на этом наша работа не заканчивается, как и всегда, в проект надо будет внести изменения 🙂 Так что открываем IAR (именно эту среду я использую) и переходим к редактированию исходного кода.
Первым делом я сбрасываю USB_DISCONNECT_PIN в нулевое состояние для активации подключения по USB:
/* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SDIO_SD_Init(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET); MX_USB_DEVICE_Init();
Как видите, Cube уже реализовал вызов функций инициализации интерфейсов USB и SDIO, но, вопреки ожиданиям, никакой связи этих интерфейсов в проекте нет, и ее нам нужно реализовать самим. Для этого переходим в файл usbd_storage_if.c и находим там ряд функций:
STORAGE_Init_FS, STORAGE_GetCapacity_FS, STORAGE_IsReady_FS, STORAGE_IsWriteProtected_FS, STORAGE_Read_FS, STORAGE_Write_FS, STORAGE_GetMaxLun_FS, (int8_t *)STORAGE_Inquirydata_FS
Сейчас эти функции пустые и именно их нам надо отредактировать в соответствии с тем типом памяти, которую мы будем использовать. В нашем случае это внешняя SD-карта памяти. Не будем заниматься всеми перечисленными функциями, ограничимся только теми, которые обязательно надо реализовать для того, чтобы наше устройство заработало.
И первая на очереди – STORAGE_GetCapacity_FS(). В этой функции мы должны определить количество блоков памяти, а также размер одного блока. Начнем с размера блока и определим его следующим образом:
#define BLOCK_SIZE 512
Для того, чтобы узнать полный размер карты памяти мы можем использовать функцию HAL_SD_Get_CardInfo(). Эта функция принимает на вход два аргумента, которые уже определены в файле main.c. Поскольку мы хотим их использовать в другом файле, то добавляем:
extern SD_HandleTypeDef hsd; extern HAL_SD_CardInfoTypedef SDCardInfo;
Итак, с размером карты памяти мы разобрались, а как же нам узнать количество блоков? А все просто – делим общий размер на размер одного блока, и в итоге получаем следующую реализацию функции:
int8_t STORAGE_GetCapacity_FS (uint8_t lun, uint32_t *block_num, uint16_t *block_size) { /* USER CODE BEGIN 3 */ HAL_SD_Get_CardInfo(&hsd, &SDCardInfo); *block_num = SDCardInfo.CardCapacity / BLOCK_SIZE; *block_size = BLOCK_SIZE; return (USBD_OK); /* USER CODE END 3 */ }
Теперь нам осталось создать самые важные функции, а именно функции чтения/записи – STORAGE_Read_FS() и STORAGE_Write_FS(). Здесь мы просто возьмем готовые функции для работы с SD картой и просто приведем аргументы к нужному нам виду:
/**************************************************************************************** * Function Name : STORAGE_Read_FS * Description : * Input : None. * Output : None. * Return : None. ****************************************************************************************/ int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 6 */ HAL_SD_ReadBlocks(&hsd, (uint32_t*)buf, (uint64_t)(blk_addr * BLOCK_SIZE), BLOCK_SIZE, blk_len); return (USBD_OK); /* USER CODE END 6 */ } /**************************************************************************************** * Function Name : STORAGE_Write_FS * Description : * Input : None. * Output : None. * Return : None. ****************************************************************************************/ int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 7 */ HAL_SD_WriteBlocks(&hsd, (uint32_t*)buf, (uint64_t)(blk_addr * BLOCK_SIZE), BLOCK_SIZE, blk_len); return (USBD_OK); /* USER CODE END 7 */ }
Вот, собственно, и все!
Важное дополнение! В новых версиях HAL Driver изменены функции для работы с SD картой, поэтому необходимо изменить и функции для работы с USB:
/**************************************************************************************** * Function Name : STORAGE_Read_FS * Description : * Input : None. * Output : None. * Return : None. ****************************************************************************************/ int8_t STORAGE_Read_FS (uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 6 */ HAL_SD_ReadBlocks(&hsd, buf, blk_addr, (uint32_t) blk_len, 10); return (USBD_OK); /* USER CODE END 6 */ } /**************************************************************************************** * Function Name : STORAGE_Write_FS * Description : * Input : None. * Output : None. * Return : None. ****************************************************************************************/ int8_t STORAGE_Write_FS (uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { /* USER CODE BEGIN 7 */ HAL_SD_WriteBlocks(&hsd, buf, blk_addr, (uint32_t) blk_len, 10); return (USBD_OK); /* USER CODE END 7 */ }
Здесь “10” – это величина таймаута для операций чтения/записи.
Собираем наш проект, программируем микроконтроллер, подключаем плату к ПК и видим, что в системе у нас появилось запоминающее устройство, а также определился внешний накопитель, соответствующий используемой карте памяти. Таким образом, наша задача решена и мы получили работающий кардридер 🙂
На сегодня на этом мы заканчиваем, в ближайшее время вновь вернемся к обсуждению разных режимов работы USB в STM32CubeMx, так что до скорого!