Мені завжди подобалася ідея об'єктно-орієнтованого програмування. Це дуже зручно і легко, особливо, коли програма роздувається до великих розмірів, або є кілька дуже схожих елементів, але з різними настройками. І мене завжди цікавили нестандартні, красиві рішення і новинки мови - шаблони, лямбда-функції, тернарние оператори ... На жаль, я все ніяк не міг до них підібратися - то часу не було, то мозок був не готовий. У загальних рисах знав, що це, але сам ніколи не пробував. Але раптом в одній з програм для AVR я побачив цікаве використання шаблону, яке дуже сильно полегшувало роботу. Мені стало цікаво - і час знайшлося, і бажання ... І ось народилася ідея цієї статті. Результат - батьківський клас для легкої роботи з пристроями на базі SPI (зсувні регістри, трансивери, Ethernet etc), в Hardware і Software реалізації. Цікаво - прохання під кат.
tl; dr - в кінці все посилання зі статті, в тому числі готовий код і приклади.
зародження
Коли маєш справу з великою кількістю периферії, починаєш тихо ненавидіти дефайни з портами і пинами. Їх стає стільки, що не проштовхнутися. Повісимо SlaveSelect в один порт з Hardware SPI. Ой, туди важко доріжку провести, переставимо на інший. І з ним, звичайно, зробимо дефайни для DDRx і PINx - ми ж хочемо, щоб все само ініціалізувати. І так для кожного ... А якщо забивати залізно, при будь-якій зміні конструкції доводиться лопатити весь код.
У мене виникла ідея - чому б не зробити все класом? Всі порти вказувати через аргументи конструктора. Зручно, красиво, мало місця. Але. PORTx - не функція, не тип, це нищівний макрос, який фіг куди запишеш. Спіткнувшись на цьому, я закинув ідею ООП для роботи з портами AVR.
Проблема з портами вирішена, тепер можна приступити до найцікавішого. Написавши пару класів з використанням цієї фічі, я подумав: було б здорово зробити клас-батько, від якого буде успадковуватися робота з інтерфейсом. Вирішено - будемо писати таку штуку для SPI.
Реалізація Hardware SPI
У мене є бібліотека libSPI (основу взяв у Tinkerer) в трьох варіантах - Hardware (використовує залізний SPI), USI-based (працює на базі USI - наприклад, для ATtiny24) і Software (повністю програмний варіант). Відкинувши другу (USI зараз досить рідко зустрічається), я почав з самого легкого - Hardware. Накидав ось такий скелет класу-батька:
Для тих, хто не знайомий з шаблонами, коротко опишу суть цієї штуки (ті, хто знають - прохання не штовхати за настільки короткий і не зовсім правильний варіант.). Шаблон дозволяє підставляти різні типи в одну і ту ж конструкцію. В даному випадку при ініціалізації об'єкта SPI_Base_Hardware я вказую клас порту вводу-виводу (той самий, який створюємо макросом MAKE_PORT) і номер Піна SlaveSelect (далі SS), з якими потім можу оперувати в класі з допомогою слів PORT і PIN_SS відповідно. Ось тут досить непогано описано, що це за звір і з чим його їдять, на прикладах.
Така конструкція мені все одно не сподобалася, і ще через деякий час був знайдений досить простий варіант. З основної інклюд (в нашому випадку - spi_base_hardware.h) опис класу виносимо в spi_base_hardware.hpp, всю реалізацію - в spi_base_hardware.cpp. При цьому сам файл spi_base_hardware.h стає ось таким:
От і все. Опис окремо, реалізація окремо, я задоволений :)
продовжуємо писати
Написавши реалізацію кожної функції, я вирішив піти далі. У Hardware-версії порт і піни інтерфейсу SPI змінюватися ну просто не можуть (на те він і Hardware). Так давайте заведемо на них дефайни, скажете Ви. Так, давайте, відповім я, але при цьому додам - тільки Ви їх не побачите. Нехай вони будуть самі додаватися, для потрібного каменю - потрібні дефайни. Відкриваємо файлик include \ io.h і починаємо перебирати всі МК - гугл pinout. Потім в стилі все того ж io.h створюємо файл, який буде перевіряти обрану версію МК і створювати потрібний дефайн. Ось що у мене вийшло spi_hardware_defs.h. На все МК мене не вистачило, список підтримуваних (всі їх версією) - на початку того файлу. Якщо хоч комусь ця штука буде корисна - обов'язково доведу до кінця.
І що ж вийшло?
spi_hardware.h
приклади
Одержаний клас можна використовувати кількома способами - як драйвер для інтерфейсу SPI, як батько для іншого класу, як батько для іншого класу-шаблону і як драйвер, що міститься всередині класу-шаблону. Покажу всі види.
Приклад 1. Hardware SPI, використання в якості драйвера
Приклад 2. Hardware SPI, створення класу-спадкоємця
І, нарешті, найцікавіше ...
Приклад 3. Hardware SPI, створення шаблону класу-спадкоємця
Тут потрібно зробити застереження. Ви напевно помітили, що в методі init я використовував this-> spi ... Якщо цього не зробити, компілятор буде голосно кричати, що він не може знайти ці методи. Це стосується всіх методів, які є в класі SPI_Base_Hardware.
У всіх попередніх прикладів є один недолік - імена методів fast_shift etc зайняті, що не критично, але якось недобре ... Тому я вважаю найоптимальнішим варіантом ...
Приклад 4. Hardware SPI, створення шаблону класу, що містить в собі драйвер
Драйвер инкапсулирован, тепер все взагалі круто :)
А що далі?
Награвшись з Hardware SPI, я вирішив зайнятися програмної версією. Тут вже відкриваються великі горизонти: повісити кілька пристроїв на різні порти, не звертаючи уваги на терморегулятори контролера - куди легко вести, туди і провів. Краса!
Скелет буде таким же, крім назви класу і шаблону:
Тепер передається не тільки порт і пін SS, а також піни MISO, MOSI і SCK. Але що, якщо у нас немає вільних ніг в одному порту? Правильно, розкидаємо по різним - у нас же програмна реалізація, робимо що хочемо. Шаблон змінюється до такого виду:
Він виріс і перетворився на величезну рядок. У реалізації доведеться писати так:
Жах ... Але якщо зробити два макроси, то все виглядає дуже навіть гармонійно: Так адже краще? Ці макроси я буду активно використовувати в подальшому.
Пишемо далі: реалізація Software SPI
Я залишив два варіанти - Software SPI і Separate Software SPI. Перший висить на одному порту і приймає в шаблоні клас порту і 4 Піна - MOSI, MISO, SCK і SS. Другий приймає порт для кожного Піна, виглядає це так:
Щоб не робити два однакових класу, я зробив SPI_Base_Software спадкоємцем від SPI_Base_Software_Separate. Ось так:
І що вийшло?
spi_software.h
приклади
Способів використання - стільки ж скільки і у хардварний варіанту.
Приклад 5. Software SPI, драйвер.
Приклад 6. Створення класу-спадкоємця
Приклад 7. Створення шаблону класу-спадкоємця
Приклад 8. Створення шаблону класу, що містить в собі драйвер