Продолжаем микро-цикл статей (в рамках макро-цикла, посвященного работе с STM32CubeMx), в которых мы разбираемся как настраивать разные режимы работы USB в микроконтроллерах STM32 при помощи CubeMx. И сегодня на очереди MSD (USB Mass Storage Device), а также работа с SD-картой памяти. То есть по сути реализуем свой собственный кардридер на контроллере STM32F10x. При этом карта памяти будет подключаться по интерфейсу SDIO.
Собственно, давайте перейдем в STMCubeMx и начнем все поэтапно настраивать. Первым делом задействуем внешний генератор тактовых импульсов:
Следующий шаг - активация интерфейса SDIO в 4-х битном режиме (именно так подключена карта памяти на моей плате). Заодно зададим предделитель для SDIO:
Предварительный этап конфигурации финишируем настройкой USB. Снова сначала включаем, затем выбираем нужный режим:
При включении какого-либо периферийного модуля CubeMx автоматически отмечает и инициализирует нужные выводы микроконтроллера, вручную этого делать не надо.
Итак, с этим все понятно, все настроено, давайте перейдем к настройкам тактирования. В очередной раз напоминаю, что этому была посвящена отдельная статья (ссылка), поэтому сейчас мы останавливаться на этом моменте не будем ) Здесь стоит обратить внимание только на один момент, а именно на тактирование USB. Так вот, на модуль USB должны обязательно приходить 48 МГц, иначе работать данная периферия не будет:
На этом, в принципе, и все, переходим к генерации исходного кода, а также файлов проекта. После окончания этой процедуры получаем полностью готовый и настроенный проект, но на этом наша работа не заканчивается, как и всегда, в проект надо будет внести изменения. Так что открываем IDE и переходим к редактированию.
CubeMx уже реализовал вызов функций инициализации 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 STORAGE_BLK_NBR 0x10000 #define STORAGE_BLK_SIZ 0x200
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size) { /* USER CODE BEGIN 3 */ *block_num = STORAGE_BLK_NBR; *block_size = STORAGE_BLK_SIZ; return (USBD_OK); /* USER CODE END 3 */ }
То есть, в принципе, можно задать значения и руками через STORAGE_BLK_NBR
и STORAGE_BLK_SIZ
. Но мы пойдем по концептуально другому пути. Для того, чтобы узнать параметры использующейся SD-карты, мы можем использовать функцию HAL_SD_GetCardInfo()
. Она принимает на вход два аргумента, и все в тот же файл usbd_storage_if.c добавляем:
/* USER CODE BEGIN PRIVATE_VARIABLES */ HAL_SD_CardInfoTypeDef cardInfo; // <--- Добавлено /* USER CODE END PRIVATE_VARIABLES */ /** * @} */ /** @defgroup USBD_STORAGE_Exported_Variables * @brief Public variables. * @{ */ extern USBD_HandleTypeDef hUsbDeviceFS; /* USER CODE BEGIN EXPORTED_VARIABLES */ extern SD_HandleTypeDef hsd; // <--- Добавлено /* USER CODE END EXPORTED_VARIABLES */
В итоге получаем такую реализацию функции STORAGE_GetCapacity_FS()
:
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size) { /* USER CODE BEGIN 3 */ HAL_SD_GetCardInfo(&hsd, &cardInfo); *block_num = cardInfo.BlockNbr; *block_size = cardInfo.BlockSize; return (USBD_OK); /* USER CODE END 3 */ }
Осталось осуществить работу с самыми важными функциями, отвечающими за чтение и запись данных - STORAGE_Read_FS()
и STORAGE_Write_FS()
. Используем соответствующие функции для взаимодействия с SD-картой:
HAL_SD_ReadBlocks()
HAL_SD_WriteBlocks()
/** * @brief . * @param lun: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ 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, blk_len, 10); return (USBD_OK); /* USER CODE END 6 */ } /** * @brief . * @param lun: . * @retval USBD_OK if all operations are OK else USBD_FAIL */ 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, blk_len, 10); return (USBD_OK); /* USER CODE END 7 */ }
Вот, собственно, и все, здесь "10" - это величина таймаута для операций чтения/записи.
Собираем наш проект, прошиваем контроллер, подключаем плату к ПК и видим, что в системе у нас появилось запоминающее устройство, а также определился внешний накопитель, соответствующий используемой карте памяти. Таким образом, наша задача на сегодня решена 👍