суботу, 27 лютого 2016 р.

Далекомір HC-SR04 -- GPIO/висновки

Взято тут.
Як було показано в попередніх постах [1, 2, 3, 4, 5], працювати з HC-SR04 просто. Однак, описаний там спосіб -- з використанням GPIO, зовсім вульгарний. Контролери мають значно вишуканіші засоби вимірювати та керувати тривалістю імпульсів, ніж просто неперервно пильнувати за станом "ніжки": переривання, різноманітні потужні та гнучкі таймерів на додачу до SysTick. Про них і напишемо наступного разу. А тут підведемо підсумки GPIO-підходу. 

Результати вимірів у всіх підходів приблизно однакові (хоча метрологічної звірки й не проводив, але це і не було ціллю, про уточнення результатів поговоримо окремо). Акуратні порівняння швидкодії згенерованого різними  способами коду (особливо -- після оптимізації) вимагатиме багато зусиль -- більше, ніж я зараз тому можу приділити, тому, при всій важливості, теж поки не робитиму. 

Розглянемо те, що залишилося. Порівняємо особливості програмування різними способами та відповідні розміри прошивок 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 жере пам'ять непомірно!
      Точне порівняння ускладнене тим, що код демонстрації printf() із попереднього посту теж містить виклики цієї функції, які займають якесь місце, а якщо їх викинути -- оптимізатор викине і більшість коду стандартної бібліотеки. Звичайно, скориставшись картами (.map-файлами) та дизасемблером, можна дослідити і точніше, але особливого сенсу немає.

      Підсумок

      В принципі, розглянутий приклад -- дуже простий, висновки, зроблені на його основі, можуть бути, в кращому випадку, неточними. Однак, деякі оцінки можна вже цілком впевнено зробити.

      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 -- приклад, коли абстракції зроблено високорівневими, але ефективністю ніхто не переймався.) Через меншу популярність С++, в порівнянні із С, є менша кількість готових напрацьованих рішень.

      Детальний аналіз, які засоби С++ можуть привносити які накладні витрати, буде окремо.

      Посилання


      Епілог


      На цьому будемо завершувати. Наступного разу подивимося, як працювати із далекоміром не так примітивно. Цей цикл статей розглядав різні способи працювати із GPIO. Щоб не потрапити у комбінаторну пастку, надалі ми обмежимося лише CMSIS та HAL. Щоправда, про С++ постараюся теж не забувати. А поки --

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



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

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