Взято тут. |
Результати вимірів у всіх підходів приблизно однакові (хоча метрологічної звірки й не проводив, але це і не було ціллю, про уточнення результатів поговоримо окремо). Акуратні порівняння швидкодії згенерованого різними способами коду (особливо -- після оптимізації) вимагатиме багато зусиль -- більше, ніж я зараз тому можу приділити, тому, при всій важливості, теж поки не робитиму.
Розглянемо те, що залишилося. Порівняємо особливості програмування різними способами та відповідні розміри прошивок i RAM.
Щоб не дублювати весь код, розглянемо дві прості але важливі операції:
- ініціалізацію піну PB1 на виведення,
- виведення одиниці,
CMSIS
Ініціалізація:
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // Дозволяємо тактування порту B
// На Trig (PB1) подаватимемо сигнал GPIOB->CRL &= ~GPIO_CRL_CNF1;// Скинути обидва біти CNF для піна 1. // Режим 00 - Push-Pull. Це молодші піни порта, // тому звертаємося до CRL - Low GPIOB->CRL |= GPIO_CRL_MODE1; //Встановити обидва біти MODE //для піна 1 -- швидкість 50MHz
Виведення:
GPIOB->BSRR = GPIO_BSRR_BS1;
SPL
Ініціалізація:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // Дозволяємо тактування порту B
GPIO_InitTypeDef TrigPin_InitStruct; //PB1 TrigPin_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; TrigPin_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //Out - Push-Pull TrigPin_InitStruct.GPIO_Pin = GPIO_Pin_1; GPIO_Init(GPIOB, &TrigPin_InitStruct);
Виведення:
GPIO_SetBits(GPIOB, GPIO_Pin_1);
CooCox CoX
Ініціалізація:
xSysCtlPeripheralEnable(xSYSCTL_PERIPH_GPIOB); // Ввімкнути тактування порту B
xGPIODirModeSet( xGPIO_PORTB_BASE, xGPIO_PIN_1, xGPIO_DIR_MODE_OUT ); // Конфігурування Trig
Виведення:
xGPIOPinWrite( xGPIO_PORTB_BASE, xGPIO_PIN_1, 1 );
HAL
Ініціалізація:/* Initialize all configured peripherals */ MX_GPIO_Init();
Відповідна функція достатньо складна, однак генерується автоматично. Всередині -- схожа на SPL.
Виведення:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
C++
Ініціалізація (в С++ прикладі розглядається лише абстрагування GPIO):RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // Дозволяємо тактування порту B
typedef Gpio<GPIOB_BASE,1> trig; trig::mode(Mode::OUTPUT);Виведення:
trig::high();
Розмір прошивки
Аналізувалося лише два варіанти -- без оптимізації взагалі, і -O3+LTO.
- Лише CoX компілювався без LTO -- з використанням цієї оптимізації прошивка виходить порожньою. Взагалі, автори попереджають, що ця бібліотека не готова для роботи з оптимізацією, однак даний простий приклад нормально працює із використанням -O3.
- Варіант C++ -- без виключень та RTTI, але весь решта runtime-код присутній в проекті. В ролі вправи спробуйте скомпілювати із мінімальною достатньою runtime-підтримкою.
-O0 | -O3, LTO (CoX - без LTO) | |||||||
---|---|---|---|---|---|---|---|---|
text | data | bss | Разом | text | data | bss | Разом | |
CMSIS | 25568 | 2244 | 116 | 27928 | 24336 | 2220 | 116 | 26672 |
SPL | 29080 | 2260 | 116 | 31456 | 24328 | 2220 | 116 | 26664 |
CoX | 64688 | 2596 | 1436 | 68720 | 42200 | 2404 | 1436 | 46040 |
HAL |
37216 | 2228 | 116 | 39560 | 25904 | 2228 | 116 | 28248 |
C++ |
26504 | 2248 | 120 | 27352 | 25008 | 2224 | 120 | 27352 |
Зауважте, що до розміру прошивки входить printf() та потрібні йому компоненти стандартної бібліотеки С. Про те, скільки вони потребують пам'яті самі по собі, див. один із попередніх постів: "Retargetable printf з CoIDE та порівняння розмірів ROM/RAM для різних способів виводу". Цитуючи дані, наведені там, printf() з Newlib плюс прості приклади використання в main():
-O0 | -O3, LTO | |||||||
---|---|---|---|---|---|---|---|---|
text | data | bss | Разом | text | data | bss | Разом | |
Newlib printf | 24996 | 2244 | 116 | 27356 | 24140 | 2220 | 108 | 26672 |
Ввімкнення LTO суттєво цей результат не змінює (24332 Flash, 2244 + 116 RAM).
Тобто, переважна більшість використаного RAM та пам'яті програм -- це printf(). Звідки видно, що кожен із підходів додає до розміру прошивки зовсім мало, особливо якщо ввімкнена оптимізація:
- Кілька сотень байт флеш-пам'яті та лічені байти RAM для CMSIS і SPL.
- Один-два кілобайти прошивки та ті ж лічені байти RAM для HAL.
- Приблизно те ж для C++ -- +4 байти RAM, розмір прошивки між CMSIS i HAL. Однак, значна частина цієї різниці -- runtime-код! Реалізація new та delete, реакція на чисто віртуальні функції і т.д. Якщо його звести до мінімуму (спробуйте!) -- розмір буде практично таким же, як і для CMSIS -- асемблерний код, що генерується із шаблонів такий же, як і за ручної маніпуляції регістрів.
- З незрозумілих причин CoX жере пам'ять непомірно!
Підсумок
В принципі, розглянутий приклад -- дуже простий, висновки, зроблені на його основі, можуть бути, в кращому випадку, неточними. Однак, деякі оцінки можна вже цілком впевнено зробити.CMSIS
Дуже тоненька абстракція -- символьні імена для регістрів та бітів (щоб не запам'ятовувати шістнадцяткові адреси та послідовності біт), мінімальна кількість функцій. Програмується майже як на асемблері. Трішки покращує портабельність, якщо користуватися символьними іменами -- перестановка біт в регістрі чи зміна адреси регістра в іншій моделі контролера потребуватиме лише перекомпіляції.
Всі наступні засоби у своїй роботі покладаються на CMSIS.
Плюси -- висока ефективність, як з точки зору швидкодії, так і витрат пам'яті, безпосередня відповідність тому, що написано в даташітах на мікроконтролери. Крім того, та частина CMSIS, що стосується ядра ARM Cortex M, зокрема, керування SysTick, NVIC, спільна для всіх таких мікроконтролерів. Звичайно, подробиці роботи з периферією відрізнятимуться між різними моделями та сімействами.
Мінуси -- висока складність написання, безліч імен та абревіатур, що виглядають як заклинання виклику демонів.
SPL
Спроба STMicroelecrtonics надати більш високорівневу абстракцію для роботи із їхніми мікроконтролерами. Більш описові імена для функцій, ініціалізація периферії здійснюється згідно вмісту спеціальних структур із описовими іменами полів. Шар абстракції все рівно тоненький, хоча тепер вже здатен приховувати суттєвіші відмінності в реалізації мікроконтролерів сімейства STM32, ніж CMSIS.Плюси -- більш зрозуміла в написанні та читанні коду. Майже така сама ефективна і з точки зору продуктивності і з точки зору затрат пам'яті, як CMSIS.
Мінуси -- значно більш багатослівна, ніж CMSIS, треба купу коду писати (див. вище). Вимагає такої ж низькорівневої маніпуляції засобами мікроконтролера, хоча і користуючись дещо очевиднішими у використанні інструментами.
HAL
Наступна спроба від STMicroelecrtonics. Як на мене, цього разу -- значно більш вдала. Трохи вищий рівень абстрагування, хоч можна і краще, та зручний графічний інструмент конфігурування периферії, який вирішує найболючішу, принаймні -- для мене, проблему роботи із цими мікроконтролерами -- медитування над даташітами та знаходження тієї магічної послідовності ініціалізації, що дасть бажаний результат. Якщо дивитися на код, видно, що є явним розвитком ідей SPL.Плюси -- зручність у використанні, як початківцям, так і під час розробки складних проектів. Незначні накладні витрати. Можливість контролю логічних помилок в програмі (такі спроби були і в SPL, але через більшу високорівневість використаних HAL абстракцій, тут вона значно більш вдала).
Мінуси -- генерується цілий проект, при чому, лише для обраних IDE. Безкоштовна поміж них з'явилася лише недавно, хоча частина комерційних, з підтримуваних, теж користувалися GCC. Перенесення цього проекту в інші середовища, можлива, як ми бачили в попередньому пості, але вимагає багато ручної роботи (або написання не тривіальних засобів імпорту), крім того, при цьому втрачається можливість простої пере-генерації проекту із іншими налаштуваннями периферії.
Накладні витрати, хоч і невеликі, присутні, при чому, як у витратах пам'яті, так і у швидкодії -- іноді це може бути важливо.
Абстракція могла бути й більш високорівневою.
CooCox CoX
Спроба побудувати більш високорівневу і більш портабельну, абстракцію -- включаючи різні сімейства мікроконтролерів від різних виробників. На жаль, трохи занедбана авторами. Рівень абстракції того ж порядку, що й HAL.Плюси -- більш зрозумілий, більш високорівневий код. Можливість використовувати ту ж програму на контролерах багатьох виробників (не тільки STM32, як STL/HAL).
Мінуси -- занедбаність авторами, певна загальна неохайність. Відсутність, принаймні у повністю доступних версій, підтримки оптимізації -- все на свій страх і ризик. Непомірне зростання використання пам'яті, як флешу так і RAM.
C++ у розглянутому варіанті
Не є незалежною бібліотекою -- просто демонстрація високоефективної побудови необхідних абстракцій засобами С++. Використання шаблонного мета-програмування дозволяє отримувати код, еквівалентний написаному вручну, при тому, працюючи цілком високорівнево.Плюси -- можна створювати абстракції як завгодно високого рівня, що дозволяють вирішувати задачу в її термінах (наприклад, RTC, далекомір), а не в термінах регістрів, бітів та байтів. При тому, можна добитися, щоб цей код був не менш ефективним, ніж написаний вручну.
Мінуси -- С++ помітно складніше, ніж С. Відповідно, потрібна більш складна runtime-підтримка (бездумне підключення якої приводить до роздування коду). Більша кількість засобів вимагає кращого володіння ними, якщо стоїть задача досягати тієї ж ефективності, що й в коді на С. (Зауважте, такі неуникні витрати, як таблиці віртуальних функцій, накладними самі по собі не є -- якщо наданий ними функціонал потрібен для вирішення задачі, все рівно їх доведеться заплатити, тим чи іншим способом. При чому, часто це вийде гірше, ніж в компілятора). Крім того, якщо потрібно мати одночасно високоефективні та високорівневі абстракції, написання коду вимагає використання складних технік метапрограмування, а сам код реалізації буде складним у читанні та підтримці. (Бібліотека Arduino -- приклад, коли абстракції зроблено високорівневими, але ефективністю ніхто не переймався.) Через меншу популярність С++, в порівнянні із С, є менша кількість готових напрацьованих рішень.
Детальний аналіз, які засоби С++ можуть привносити які накладні витрати, буде окремо.
Посилання
- Про зоопарк бібліотек від STMicroelectronics, див., наприклад, тут: "STM32 embedded software offerring".
Епілог
На цьому будемо завершувати. Наступного разу подивимося, як працювати із далекоміром не так примітивно. Цей цикл статей розглядав різні способи працювати із GPIO. Щоб не потрапити у комбінаторну пастку, надалі ми обмежимося лише CMSIS та HAL. Щоправда, про С++ постараюся теж не забувати. А поки --
Дякую за увагу!
Немає коментарів:
Дописати коментар