четвер, 5 жовтня 2017 р.

Зовсім просто про semihosting

Структура SemiHosting. Взято тут.
Що таке Semihosting, як його підключити і використовувати із "старим та добрим" printf(), детально описано тут: "Стандартна бібліотека C та SemiHosting (на прикладі STM32 і CoIDE)", хоча, як видно з назви, для іншого середовища розробки. Більше технічної інформації є тут: "SemiHosting без дебаггера".

А зараз опишу як ним можна скористатися із мінімальними зусиллями, так би мовити, для лінивих. :-)

Ідея наступна: існує апаратний інтерфейс для передачі даних із мікроконтролера дебаггеру. Є бажання зробити так, щоб по ньому можна було передавати текст, за допомогою звичних функцій вводу-виводу С: printf(), puts(), тощо. Як описується у згаданому вище пості, для цього слід мати реалізацію стандартної бібліотеки та надати для неї відповідні системні виклики, котрі вмітимуть передавати дані семіхостингом.
Часто із компілятором для мікроконтролера йдуть newlib чи newlib-nano, мінімалістичні реалізації стандартної бібліотеки С. Зокрема, є вона і в збірках компіляторів для MCU ARM Cortex M, тому про неї окремо можна не турбуватися. Хоча знати слід -- коли стандартні налаштування перестануть влаштовувати або просто не працюватимуть як належить.
І тут є два варіанти:
  • Із більшим контролем але і більшою морокою -- надати системні виклики самостійно, як описано за посиланням вище.
  • Скористатися готовою реалізацією, що йде разом із компілятором для наших мікроконтролерів -- ARM Cortex M.
Важливе зауваження: за використання семіхостингу, ваш код перестане працювати без під'єднаного дебаггера! Як вирішити цю проблему, див.  "SemiHosting без дебаггера".

Отож, потрібно виконати наступні кроки:
  1. Дозволити SemiHosting в дебаггері.
  2. За потреби -- додати підтримку вводу/виводу  floating-point чисел.
  3. Надати свою реалізацію системних викликів.
  4. Або використати готову їх реалізацію із підтримкою SemiHosting із GCC.
Розглянемо ці кроки детальніше. 

Дозвіл на використання semihosting в відладчику

Щоб відладчик (debugger) бачив вивід Semihosting, слід ввімкнути його підтримку. Робиться це у вікні Properties (проекту чи глобальному). Шукаємо там "Run/Debug Settings" -> <активна конфігурація> -> "Edit...". (Увага, слід хоча б раз скомпілювати проект та залити прошивку, щоб активна конфігурація з'явилася). У діалозі, що відкрився, переходимо до вкладки "Startup" і додаємо команду "monitor arm semihosting enable":
Клікабельно!
Без цього кроку того, що виведено вашою програмою, не буде видно, навіть якщо все решта ви зробили правильно. 


Підтримка floating-point


Увага! По замовчуванню, SW4STM32 використовує Newlib-nano без підтримки floating-point чисел при вводі-виводі:
Щоб її додати, після  "-specs=nano.specs" слід вписати: "-u _printf_float" для підтримки виводу double i "-u _scanf_float" для підтримки їх вводу:
Кожна із цих можливостей потребує доволі багато ресурсів, тому підключати їх варто лише за потреби.

Власні системні виклики для libc

  • Качаємо ось цей архів: semihosting_n_libc_support.zip
  • Його вміст копіюємо в директорію проекту, так щоб файли заголовків (піддиректорія Inc) та текстів -- *.c і асемблерний sh_cmd.s (Src) опинилися у відповідних його піддиректоріях.
  • Оновлюємо проект (в SW4STM32 тиснемо F5).
Зауваження: Різні середовища (IDE) генерують трішки різні скрипти лінкера, на символи із яких покладається реалізація вище для перевірки колізії стеку і купи та деяких інших операцій. Скажімо, CoIDE створить символ __StackLimit, а STM32CubeMX+SW4STM32 -- _Min_Stack_Size та _estack (див. файл із розширенням ld вашого проекту). Якщо компілятор, точніше, лінкер, скаржитиметься, що такі символи не визначено ("Undefined reference"), слід вручну виправити або скрипт лінкера або код в syscalls.c.

Додатково: в runtime_config.h  міститься декілька макросів, котрі визначають особливості роботи системних викликів, із самоочевидними назвами, наприклад: PRINT_MESSAGE_ABOUT_HEAP_AND_STACK_COLLISION чи SW4STM32_LINKER_SCRIPT_2017. Детальніше -- див. код.
Однак, якщо Вам потрібен лише семіхостинг, без потреби мати контроль над стандартними системними викликами, можна зробити трішки простіше. (Але виберіть щось одне! Інакше отримаєте купу повідомлень типу "multiple definition of .........")

Системні виклики із rdimon

Разом із "нашим" gcc -- gcc-arm-none-eabi-XXXX, йде бібліотека libgloss -- частина newlib чи newlib-nano, що відповідає за низькорівневу підтримку (системні виклики і все таке), та містить підтримку semihosting. Відповідно, достатньо вказати відповідні її специфікації (-specs=rdimon.specs) замість "-specs=nosys.specs" чи/і "-specs=nano.specs":

Тепер ще потрібно десь на початку main() оголосити та викликати функцію initialise_monitor_handles() (визначена в нетрях бібліотеки libgloss/librdimon):


1
2
3
4
5
6
7
8
int main(void)
{

  /* USER CODE BEGIN 1 */
 extern void initialise_monitor_handles(void);
 initialise_monitor_handles();

  /* USER CODE END 1 */


І можна користуватися SemiHosting.

Взагалі, реалізація системних викликів у libgloss значно повніша, ніж мінімалістична, із архіву, що згадувався вище.

Подробиці

Трішки про нутрощі використаного вище механізму. В принципі,  згаданий rdimon.specs це всього лиш такий файлик:

%rename link_gcc_c_sequence                rdimon_link_gcc_c_sequence

*rdimon_libc:
%{!specs=nano.specs:-lc} %{specs=nano.specs:-lc_nano}

*rdimon_libgloss:
%{!specs=nano.specs:-lrdimon} %{specs=nano.specs:-lrdimon_nano}

*link_gcc_c_sequence:
%(rdimon_link_gcc_c_sequence) --start-group %G %(rdimon_libc) %(rdimon_libgloss) --end-group

*startfile:
crti%O%s crtbegin%O%s %{!pg:rdimon-crt0%O%s} %{pg:rdimon-crt0%O%s}

Тобто, часто всього лиш додати rdimon (-lrdimon) до списку бібліотек було б досить, але роблячи вручну довелося б пильнувати, чи ту бібліотеку додали, чи нічого не зламали і т.д. і т.п., а rdimon.specs пропонує цілком робочу конфігурацію.

Реалізація initialise_monitor_handles() та системних викликів, знаходиться в файлі із іменем syscall.c, який можна знайти в джерельних текстах компілятора, за якимось таким шляхом: gcc-arm-none-eabi-<version>/src/newlib/libgloss/arm/syscalls.c.

Згадана initialise_monitor_handles() створює три файлових дескриптори -- stdin, stdout, stderr, а _write() -- здійснює запис у вказаний дескриптор. Якщо будете читати тексти цих функцій, пам'ятайте, що машинна команда SWI -- служить для виклику ядра операційної системи чи його аналогу. ;-)

Завершуючи

Зауваження: через внутрішню буферизацію, текст, що не закінчився символом кінця рядка ('\n') може якийсь час не відображатися! Пам'ятайте про це.
Якщо ви правильно виконали всі кроки вище, виведене вашою програмою за допомогою printf() та аналогічних функцій потраплятиме в консоль дебаггера (скріншот для програми із цього поста):


А поки --

Дякую за увагу!

Немає коментарів:

Дописати коментар