"USB Zoo" |
- STM32F4Discovery, із MCU STM32F407VG -- ARM Cortex M4F,
- STM32F072BDiscovery, із MCU STM32F072RBT6 -- ARM Cortex M0,
- 32L0538Discovery, із STM32L053C8T6 -- ARM Cortex M0+.
Кожна із цих плат чимось цікава, наприклад, акселерометр та мікрофон в F4, e-Ink-дисплей в L0 тощо -- думаю, ще не раз до них повернемося. Але тут говоритимемо лише про USB CDC VCP.
STM32F4Discovery
Схема демоплати STM32F3Discovery нас засмутила відсутністю можливості програмно від'єднатися від USB та приєднатися назад, змушуючи постійно фізично "пересмикувати" кабель. Перший погляд на схему STM32F4Discovery не особливо порадував:
Схема кіл USB-OTG плати. Клікабельно! |
Якщо відкинути кола, пов'язані із OTG -- постачанням живлення і все таке, то ніяких механізмів контролю не видно. На лініях шини, правда, "висить" мікросхемка EMIF02-USB03F2, але це всього лиш EMI-фільтр та захист від статичної електрики. Насторожує хіба відсутність підтяжки, але мало де її заховали...
Однак, все не так погано. Якщо вчитатися в даташіт, мікроконтролер підтримує "Soft disconnect" -- програмне від'єднання. В регістрі OTG_FS_DCTL (OTG_FS device control register) є біт SDIS: Soft disconnect, поки в ньому 1 пристрій поводиться так, ніби від'єднаний від шини. Щоб USB почала працювати, слід записати в нього 0. При тому, HAL/Middleware бере турботу про цей біт на себе, тому ми можемо окремо не турбуватися. Запустили код
-- пристрій зникає, потім з'являється на шині, його бачить комп'ютер, підтягує потрібні драйвери. Потреби вручну від'єднувати-під'єднувати кабель немає.
Як і в попередній статті, генеруємо проект в STM32CubeMX, вмикаємо USB, додаємо Middleware для CDC. Чогось нового, із важливого для нас, на вкладці конфігурації не з'явилося. А ось із тактуванням мороки більше. З одного боку, плата містить вбудований кварц. (Уф! Ну хоч тут не пожаднічали :-). З іншого, після STM32F3, конфігурація тактування виглядає дивно... Каюся, подивився я на неї, подивився, ввімкнув використання HSE і довірився автопідбору подільників (хотілося і від ядра максимальну частоту отримати). І дав мені автопідбір щось таке:
Клікабельно! |
Виглядає адекватним, тому так і залишив.
По великому рахунку, все. Генеруємо код, копіюємо у директорію проекту наші ringbuf.c, syscall_null.c, syscall_semih.c, syscall_usb.c, syscalls.c, sh_cmd.s, ringbuf.h, runtime_config.h, syscall_impl.h із попереднього проекту. (Про вміст syscalls*.*, як обіцяв, напишу окремо. У них -- реалізація системних викликів, більш універсальна, ніж попередня, але поки зовсім сира). В syscall_semih.c -- файл із реалізацією обміну семихостингом, слід включити правильний файл заголовків, із відповідною версією CMSIS. Деякі вже додано, якщо вашого там немає, зробіть аналогічно:
В принципі, як виявилося, usbd_cdc_if.h і usbd_cdc_if.c можна було теж скопіювати -- вони однакові для обох плат, але я таки акуратно переніс нові рядочки. Вписуємо в main.c тестовий код -- той же, що і раніше.
Компілюємо, випробовуємо -- все працює. Так ще й після заливки нової прошивки перепід'єднується автоматично -- залишається лише на емуляторі термінала наново під'єднатися, (так як віртуальний COM-порт зникає-з'являється, емулятор губить із ним зв'язок, але двічі клікнути мишкою простіше, ніж фізично пересмикувати кабель).
В такі моменти починаєш цінити HAL -- не зважаючи на його громіздкість і певну недолугість (з точки зору пуританина, звичного думати асемблером -- особливо :-).
По великому рахунку, все. Генеруємо код, копіюємо у директорію проекту наші ringbuf.c, syscall_null.c, syscall_semih.c, syscall_usb.c, syscalls.c, sh_cmd.s, ringbuf.h, runtime_config.h, syscall_impl.h із попереднього проекту. (Про вміст syscalls*.*, як обіцяв, напишу окремо. У них -- реалізація системних викликів, більш універсальна, ніж попередня, але поки зовсім сира). В syscall_semih.c -- файл із реалізацією обміну семихостингом, слід включити правильний файл заголовків, із відповідною версією CMSIS. Деякі вже додано, якщо вашого там немає, зробіть аналогічно:
1 2 3 4 5 6 7 8 9 10 11 | #ifdef STM32F303xC #include <stm32f3xx.h> #elif defined STM32F407xx #include <stm32f4xx.h> #elif defined STM32F072xB #include <stm32f0xx.h> #elif defined STM32L053xx #include <stm32l0xx.h> #else #error Please, add your MPU CMSIS header #endif |
В принципі, як виявилося, usbd_cdc_if.h і usbd_cdc_if.c можна було теж скопіювати -- вони однакові для обох плат, але я таки акуратно переніс нові рядочки. Вписуємо в main.c тестовий код -- той же, що і раніше.
Компілюємо, випробовуємо -- все працює. Так ще й після заливки нової прошивки перепід'єднується автоматично -- залишається лише на емуляторі термінала наново під'єднатися, (так як віртуальний COM-порт зникає-з'являється, емулятор губить із ним зв'язок, але двічі клікнути мишкою простіше, ніж фізично пересмикувати кабель).
В такі моменти починаєш цінити HAL -- не зважаючи на його громіздкість і певну недолугість (з точки зору пуританина, звичного думати асемблером -- особливо :-).
32F072BDiscovery
Обидві вже розглянуті плати містять MCU, що базується на ARM Cortex M4. STM32F072BDiscovery містить процесор сімейства ARM Cortex M0, помітно примітивніший, слід враховувати це, якщо вам крім USB потрібен ще й семіхостинг. Але, з точки зору використання USB різниці не буде. Ну, майже -- про це трішки нижче.
Цей мікроконтролер, як і попередній, підтримує Soft Disconnect, правда, "API" інший. В регістрі USB_BCDR (Battery charging detector) є біт DPPU (DP pull-up control), коли він рівний 0 -- хост вважає пристрій від'єднаними, коли 1 -- присутнім. (І ім'я інше, і значення біта інвертоване в порівнянні із STM32F4). Однак, HAL та Middleware про нього потурбуються самостійно.
Цей мікроконтролер, як і попередній, підтримує Soft Disconnect, правда, "API" інший. В регістрі USB_BCDR (Battery charging detector) є біт DPPU (DP pull-up control), коли він рівний 0 -- хост вважає пристрій від'єднаними, коли 1 -- присутнім. (І ім'я інше, і значення біта інвертоване в порівнянні із STM32F4). Однак, HAL та Middleware про нього потурбуються самостійно.
Схема підключення USB. |
Плата не обладнана кварцом для головного мікроконтролера, однак, якщо відкрити в STM32CubeMX вкладку тактування, побачимо приємний сюрприз -- мікроконтролер цей обладнаний внутрішнім джерелом тактового сигналу HSI48, спеціально призначеним для використання із USB (хоча і як головне джерело тактування його теж можна використати). Тому ніякі танці із припаюванням кристала чи використанням MCO програматора не потрібні.
Точність цього HSI48 -- 3% при 25С, тобто, явно замала для USB, але він використовує спеціальні кола автопідлаштування (Clock Recovery System), які орієнтуються на USB SOF, для підгону частоти.
Тактування ядра взято від HSI, хоча, можна і від HSI48. Детальніше -- див. документацію. Клікабельно! |
Виконавши всі маніпуляції -- генерацію коду, внесення змін, копіювання файлів (якщо вони потрібні), отримуємо, можна сказати, майже робочий проект. Чого так невпевнено? Проблема вилізе у трішки несподіваному місці. При запуску, в залежності від кількості вашого коду із printf() та вибраних опцій оптимізації, є загроза колізії стеку та купи -- кінчається оперативна пам'ять.
Мікроконтролер даний має доволі мало оперативної пам'яті -- 16Кб. Буфери USB, по стандарту, забирають двічі по 2Кб, ще 2Кб -- наш буфер отримання, і 2Кб -- робочий буфер. Ще кілька кілобайт займають службові структури USB, і 1-1.5Кб -- все решта. І fopen() і printf() призводять до алокацій динамічної пам'яті, і RAM вистачає впритул. Або -- не вистачає.
Питання -- як проаналізувати, на що пішла пам'ять? Ну, тобто, зрозуміло, звідки взято ці 4 рази по 2Кб, описані вище. Але ж є ще 8, на що ми її витратили?
text data bss dec hex filename
25492 1156 11940 38588 96bc usb_vcp_demo_f072.elf
Це ж, на хвилиночку, майже 12Кб, із яких де ділися 12 - 8 = 4Кб, не зрозуміло...
25492 1156 11940 38588 96bc usb_vcp_demo_f072.elf
Це ж, на хвилиночку, майже 12Кб, із яких де ділися 12 - 8 = 4Кб, не зрозуміло...
Перший спосіб розібратися із статичними та глобальними змінними -- map-файл, згенерований лінкером. Однак, читати його доволі незручно. Помічним виявився інший інструмент -- nm, який ми напустимо на .elf-файл, згенерований при компіляції. (З якого потім буде отримано "голий" образ .bin для заливки в MCU, але то не суттєво.)
Отримаємо щось таке (для кращої читабельності використано довгі назви опцій):
$ nm usb_vcp_demo_f072.elf --print-size --size-sort --reverse-sort --line-numbers 20000ed4 00000801 B LocalRxBuffer\usb_vcp_demo_f072\Debug/../Src/main.c:68 200020fc 00000800 B UserTxBufferFS \usb_vcp_demo_f072\Debug/../Src/usbd_cdc_if.c:104 200018fc 00000800 B UserRxBufferFS \usb_vcp_demo_f072\Debug/../Src/usbd_cdc_if.c:101 200004a8 00000800 b USBRxBuffer \usb_vcp_demo_f072\Debug/../Src/usbd_cdc_if.c:134 08001999 00000692 T HAL_RCC_OscConfig \usb_vcp_demo_f072\Debug/../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_rcc.c:269 08001381 000004bc t PCD_EP_ISR_Handler \usb_vcp_demo_f072\Debug/../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pcd.c:1188 08000835 00000354 T HAL_PCD_EP_Open \usb_vcp_demo_f072\Debug/../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_pcd.c:653 080052e5 0000030c T __svfscanf_r 080052e5 0000030c T __svfiscanf_r 08000429 000002a8 T HAL_GPIO_Init \usb_vcp_demo_f072\Debug/../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_gpio.c:186 08004449 00000238 T _vfprintf_r 08004449 00000238 T _vfiprintf_r 080020c1 00000234 T HAL_RCC_ClockConfig \usb_vcp_demo_f072\Debug/../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_rcc.c:725 0800475d 00000234 T _printf_i 200016d8 00000224 B hUsbDeviceFS 20000ca8 00000220 b mem.7630
...........................................................
Зверху видно всілякі буфери, типу UserTxBufferFS -- вони найбільші, але далі стає трохи складніше -- розміри об'єктів даних перемішано разом із розмірами функцій. На щастя, в ARM-ів пам'ять програм та оперативна пам'ять рознесені в адресному просторі, RAM матиме адресу 2000xxxx, тому, щоб не мудрувати, зробимо:
$ nm usb_vcp_demo_f072.elf --print-size --size-sort --reverse-sort --line-numbers | grep 2000 20000ed4 00000801 B LocalRxBuffer\usb_vcp_demo_f072\Debug/../Src/main.c:68 200020fc 00000800 B UserTxBufferFS \usb_vcp_demo_f072\Debug/../Src/usbd_cdc_if.c:104 200018fc 00000800 B UserRxBufferFS \usb_vcp_demo_f072\Debug/../Src/usbd_cdc_if.c:101 200004a8 00000800 b USBRxBuffer \usb_vcp_demo_f072\Debug/../Src/usbd_cdc_if.c:134 200016d8 00000224 B hUsbDeviceFS 20000ca8 00000220 b mem.7630 200028fc 00000220 B hpcd_USB_FS \usb_vcp_demo_f072\Debug/../Src/usbd_conf.c:59 20002b1c 00000200 B USBD_StrDesc \usb_vcp_demo_f072\Debug/../Src/usbd_desc.c:166 20000310 0000016c D __global_locale 20000110 000000c8 D opened_files 200001d8 00000070 D iodrivers 200002b0 00000060 d impure_data 20000088 00000043 D USBD_CDC_OtherSpeedCfgDesc \usb_vcp_demo_f072\Debug/../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c:368 20000044 00000043 D USBD_CDC_CfgHSDesc \usb_vcp_demo_f072\Debug/../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c:178 20000000 00000043 D USBD_CDC_CfgFSDesc \usb_vcp_demo_f072\Debug/../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c:274 200000d8 00000038 D USBD_CDC 20000290 0000001c D FS_Desc 20000250 00000018 D USB_Buffer 20000480 00000018 b object.8672 20000278 00000012 D USBD_FS_DeviceDesc \usb_vcp_demo_f072\Debug/../Src/usbd_desc.c:127 20000268 00000010 D USBD_Interface_fops_FS 200000cc 0000000a d USBD_CDC_DeviceQualifierDesc \usb_vcp_demo_f072\Debug/../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c:135 20000ed0 00000004 B uwTick \usb_vcp_demo_f072\Debug/../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal.c:99
.............................................................
Видно, що так просто, без серйозного занурення в код USB Middleware та стандартної бібліотеки (див. __global_locale -- от на що воно 364 байти витратило?!) зробити щось важко. Звичайно, можна позбутися грубої "потрійної" буферизації, що теж вимагатиме доволі ґрунтовної переробки коду. Але залишається простий, хоч і неелегантний трюк -- зменшити розміри всіх буферів. Він порушуватиме стандарт USB, який вимагає 2Кб буферів, але проблеми, на практиці і у таких простих пристроях, малоймовірні, а це дасть нам зекономити 4 або й 6Кб.
Результат став кращим. Правда, зміна розміру дескриптора ні на що видиме не вплинула, але лінь було ще раз скрішот робити. :-)
$ nm usb_vcp_demo_f072.elf --print-size --size-sort --reverse-sort --line-numbers | grep 2000 20000ed4 00000801 B LocalRxBuffer\usb_vcp_demo_f072\Debug/../Src/main.c:68 200020fc 00000800 B UserTxBufferFS \usb_vcp_demo_f072\Debug/../Src/usbd_cdc_if.c:104 200018fc 00000800 B UserRxBufferFS \usb_vcp_demo_f072\Debug/../Src/usbd_cdc_if.c:101 200004a8 00000800 b USBRxBuffer \usb_vcp_demo_f072\Debug/../Src/usbd_cdc_if.c:134 200016d8 00000224 B hUsbDeviceFS 20000ca8 00000220 b mem.7630 200028fc 00000220 B hpcd_USB_FS \usb_vcp_demo_f072\Debug/../Src/usbd_conf.c:59 20000310 0000016c D __global_locale 20000110 000000c8 D opened_files 200001d8 00000070 D iodrivers 200002b0 00000060 d impure_data 20000088 00000043 D USBD_CDC_OtherSpeedCfgDesc \usb_vcp_demo_f072\Debug/../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c:368 20000044 00000043 D USBD_CDC_CfgHSDesc \usb_vcp_demo_f072\Debug/../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c:178 20000000 00000043 D USBD_CDC_CfgFSDesc \usb_vcp_demo_f072\Debug/../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c:274 20002b1c 00000040 B USBD_StrDesc \usb_vcp_demo_f072\Debug/../Src/usbd_desc.c:166 200000d8 00000038 D USBD_CDC 20000290 0000001c D FS_Desc 20000250 00000018 D USB_Buffer 20000480 00000018 b object.8672 20000278 00000012 D USBD_FS_DeviceDesc \usb_vcp_demo_f072\Debug/../Src/usbd_desc.c:127 20000268 00000010 D USBD_Interface_fops_FS 200000cc 0000000a d USBD_CDC_DeviceQualifierDesc \usb_vcp_demo_f072\Debug/../Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c:135 20000ed0 00000004 B uwTick \usb_vcp_demo_f072\Debug/../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal.c:99
Ще пару байт може зекономити LTO (link time optimization, вмикається прапорцем -flto для компілятора і лінкера), але може і не зекономити. Експериментуйте. (Мінус використання LTO -- виконувати код в дебаггері стане багато важче). Як би там не було, доводиться бути акуратним із пам'яттю та добре думати, що із стандартної бібліотеки точно потрібно, а без чого краще обійтися.
32L152CDISCOVERY
Ця плата обладнана ще старішим мікроконтролером, ніж попередня, а пам'яті у нього ще менше -- 8Кб. Забігаючи наперед, хоча вдалося втиснути все необхідне, зменшивши буфери до 512б, думаю, використання одночасно USB, функцій сімейства printf()/scanf() і файлових функцій стандартної бібліотеки С на цьому мікроконтролері -- нераціональне. Пам'яті просто замало, і боротьба за неї може виявитися складнішою, ніж затрати, потрібні щоб обійтися без цих функцій.
Кварцу на платі навіть не передбачено -- все місце займає екранчик, хоча доступ до MCO є:
Так само не передбачено засобів апаратно відключати/підключати USB:
Але, як і для попередньої плати, ні те ні те -- не проблема.
Мікроконтролер підтримує Soft Disconnect, який керується тим же бітом DPPU із USB_BCDR, що і для SMT32F072. Так само, він обладнаний HSI48 для потреб USB:
Виставивши розмір буферів 512 байт (із розмірами по замовчуванню воно навіть не скомпілюється), можна генерувати проект і випробовувати. Хоча це буде чисто демонстраційна річ -- пам'яті все рівно впритул.
На цьому -- все. Як бачимо,завдяки HAL, доволі різні демоплати/мікроконтролери можна легко використовувати схожим чином. Скачати всі приклади проектів можна тут. Можливо, колись напишу ще про USB плати "blue pill", але поки -- з цим все,
Дякую за увагу!
Немає коментарів:
Дописати коментар