Кінцеві автомати, як програмувати без запарок

Всім привіт, мене звуть Володимир Васильєв і ось нарешті я дозрів для написання нової статті, якою поспішаю з вами дорогі читачі поділитися.

Кінцеві автомати, як програмувати без запарок

Сьогодні ми поговоримо про автоматах, але аж ніяк не тих що тримають в руках солдати російської армії. Йтиметься про такому цікавому стилі програмування мікроконтролерів як автоматне програмування. Точніше це навіть не стиль програмування а ціла концепція, завдяки якій програміст мікроконтролерів може значно полегшити своє життя. Завдяки якій багато завдань представлені перед програмістом вирішуються набагато легше і простіше, позбавляючи програміста від головного болю. До речі автоматне програмування часто називають SWITCH-технологією.

Хочу зауважити що стимулом написання цього поста послужив цикл статей про SWITCH-технології Володимира Татарчевского. Цикл статей називається «Застосування SWITCH-технології при розробці прикладного програмного забезпечення для мікроконтролерів» Так що в цьому статті я постараюся здебільшого привести приклад робочого коду і його опис.

До речі я запланував ряд статей присвячених програмуванню, в яких буду детально розглядати прийоми програмування під мікроконтролери АВР, не пропустіть .... Ну що ж поїхали!

Перш ніж розбиратися з автоматним стилем програмування розберемося як же працює програма?

Програма послідовно виконує команди закладені програмістом. Для звичайної комп'ютерної програми абсолютно нормально коли програма відпрацювала і зупинила своє виконання, виводячи при цьому результати своєї роботи на монітор.

Програма під мікроконтролер не може просто закінчити своє виконання. Ось уявіть собі, що ви включили плеєр або магнітофон. Ви натиснули кнопочку power, вибрали бажану композицію, і насолоджуєтеся музикою. Однак коли музика припинила тріпати барабанну перетинку вашого вуха, плеєр завис і ніяк не реагує на натискання кнопочок а тим більше на ваші танці з бубном.

А що тут такого? Все нормально - контролер, той що в надрах вашого плеєра просто закінчив виконання своєї програми. Ось бачите неудобненько якось виходить.

Так ось звідси ми робимо висновок, що програма під мікроконтролер просто не повинна зупинятися. По суті своїй вона повинна являти собою нескінченний цикл - тільки в цьому випадку наш плеєр працював би правильно. Далі я вам покажу які бувають конструкції програмного коду під мікроконтролери, це навіть не конструкції а деякі стилі програмування.

Стилі програмування.

«Стилі програмування» - звучить якось незрозуміло, ну да ладно. Що я хочу цим сказати? Уявімо, що людина ніколи до цього не займався програмуванням, тобто взагалі повний чайник.

Ця людина прочитав багато книг з програмування, вивчив всі основний конструкції мови. Він назбирав інформації по крупицях, благо зараз доступ до інформації необмежений. Все це добре, але як будуть виглядати його перші програми? Мені здається що він не буде мудрувати, а піде по шляху від простого до складного.

Так ось ці стилі і є сходинками провідними від простого рівня до більш складного, але і в той же час більш ефективному.

Спочатку я не замислювався про якісь конструктивні особливості програми. Я просто формував логіку програми - креслив блок-схему і писав код. Від чого постійно натикався на граблі. Але це було перший час коли я не парився і використовував стиль «просте зациклення», потім став застосовувати переривання, далі були автомати і пішло поїхало ...

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

Робоча точка програми рухається по порядку. При цьому послідовно виконуються всі дії, умови і цикли. Код починає гальмувати, доводиться вставляти багато зайвих умов, ускладнюючи тим самим сприйняття.

Все це дуже сильно заплутує програму, роблячи з коду клубок умов. У підсумку до цього коду не додав ні відняти, він стає як монолітний шматок. Звичайно коли обсяг не великий, код піддається модифікаціям, але чим далі тим складніше.

З таким підходом я написав кілька програмок, вони були не великі і цілком робочі але наочність залишала бажати кращого. Щоб додати якусь нову умову. доводилося перелопачувати весь код, тому, що все було зав'язано. Це породжувало багато помилок і головного болю. Компілятор лаявся як тільки міг, налагодження такої програми перетворювалося в пекло.

Частково розрулити нескінченний гальмівний цикл можна використовуючи переривання. Переривання допомагають вирватися з порочного кола, допомагають не пропустити важливої ​​події, додають додатковий функціонал (переривання від таймерів, зовнішні переривання).

Припустимо на переривання можна повісити обробку кнопок, або відстеження важливої ​​події. В результаті програма стає більш наочною але не менше заплутаною.

На жаль переривання не врятує від каші, в яку перетворюється програма. Не вдасться розділити на частини те, що являє собою єдине ціле.

3. Автоматне програмування.

Ось ми і підбираємося до головної теми цієї статті. Програмування в кінцевих автоматах позбавляє програму від недоліків властивих першим двом прикладам. Програма стає простіше, її легко модифікувати.

Програма написана в автоматної стилі схожа на перемикач, який в залежності від умов перемикається в той чи інший стан. Кількість станів програмісту спочатку відомо.

Кінцеві автомати, як програмувати без запарок

У грубому поданні це як вимикач освітлення. Є два стани включено і вимкнене, і дві умови включити і вимкнути. Ну а про все по порядку.

Реалізація багатозадачності в switch-технології.

Мікроконтролер здатний управляти навантаженням, моргати світлодіодами, відстежувати натискання клавіш і багато іншого. Але як все це робити одночасно. Для вирішення цього питання існує безліч рішень. Найпростіший з них я вже згадував це використання переривань.

У процесі роботи програми, коли відбувається переривання, контролер відволікається від виконання коду програми і короткочасно виконує інший шматок програми за який відповідає переривання. Переривання відпрацює, тоді робоча точка програми продовжить з того місця. з якого контролер перервався на переривання (саме слово говорить про те що контролер переривається).

Інший спосіб реалізації багатозадачності це використання операційних систем. Так дійсно стали вже з'являтися маленькі Оськой, які можна застосувати на малопотужному контролері. Але найчастіше цей спосіб виходить кілька надлишковим. Адже навіщо витрачати ресурси контролера зайвої роботою коли цілком можна обійтися малою кров'ю.

У програмах написаних по switch - технології подібна «ілюзія» багатозадачності виходить завдяки системі обміну повідомлень. Я написав «ілюзія», тому, що так і є насправді, адже програма фізично не може в один і той же час виконувати різні ділянки коду. Про систему обміну повідомленнями я розповім трохи далі.

Система обміну повідомленнями.

Розрулити численні процеси і створити ілюзію багатозадачності можна використовуючи систему обміну повідомленнями.

Припустимо нам потрібна програма в якій йде перемикання світлодіода. Ось у нас є два автомати, назвемо їх LEDON автомат відповідальний за включення світлодіода і автомат LEDOFF - автомат відповідальний за виключення світлодіода.

Кожен з автоматів має два стани, тобто автомат може бути в активному стані так і неактивному стані, як рубильник або включено, або вимкнено.

При активації одного автомата відбувається запалення світлодіода, при активації іншого світлодіод гаситься. Розглянемо невеликий приклад:

У рядках 3 -7 відбуваються різні ініціалізації тому нас це зараз не особливо цікавить. А ось далі відбувається наступне: перед запуском головного циклу (while (1)), ми відправляємо повідомлення автомату

відповідального за запалення світлодіода. Без цього маленького кроку наша шарманка не запрацює. Далі головний нескінченний цикл while виконує основну роботу.

Повідомлення має три стану. А саме стан повідомлення може бути неактивно, встановлено але неактивно і активний стан.

Виходить, що повідомлення спочатку було неактивно, коли ми відправили повідомлення, воно отримало стан «встановлено але неактивно». І це дає нам наступне. При послідовному виконанні програми автомат LEDON повідомлення не отримує. Відбувається неодружена ітерація автомата LEDON при якому повідомлення просто не може бути прийнято. Так як повідомлення має стан «встановлено але неактивно» програма продовжує своє виконання.

Після того як всі автомати в холосту протикает, черга доходить до функції ProcessMessages (). Ця функція завжди ставиться в кінці циклу, після виконання всіх ітерацій автоматів. Функція ProcessMessages (), просто переводить повідомлення зі стану «встановлено але неактивно» в стан «активно».

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

Напевно ви помітили, що попередній фрагмент програми, наведений як приклад, не працюватиме так, як задумано. Автомати будуть обмінюватися повідомленнями, світлодіоди будуть перемикатися, ось тільки ми цього не побачимо. Ми побачимо тільки тьмяно палаючий світлодіод.

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

Можна клікнути щоб збільшити

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

1. Входимо в стан за допомогою прийняття повідомлення.

2. Перевіряємо показання таймера / лічильника, якщо дотікало, то виконуємо дію, інакше просто відправляємо повідомлення самому собі.

3. Відправляємо повідомлення наступного автомату.

У наступному вході все повторюється.

Програма по SWITCH-технології. Три кроки.

А давайте напишемо програму в кінцевих автоматах і для цього нам потрібно буде виконати всього три простих кроки. Програма буде простий але саме з простих речей варто починати. Нам підійде програма з перемикається світлодіодом. Це дуже хороший приклад, так не будемо винаходити нічого нового.

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

Програма буде у нас модульної і тому буде розбита на кілька файлів. Модулі у нас будуть такі:

  • Модуль основного циклу програми містить файли leds_blink.c, HAL.c, HAL.h
  • Модуль таймерів містить файли timers.c, timers.h
  • Модуль обробки повідомлень містить файли messages.c, messages.h
  • Модуль автомата 1 містить файли ledon.c, ledon.h
  • Модуль автомата 2 містить файли ledoff.c. ledoff .h

Створюємо проект і відразу підключаємо до нього файли наших статичних модулів: timers.c, timers.h, messages.c, messages.h.

Далі пишемо модуль основного циклу програми.

Файл leds_blink.c модуля основного циклу прогармми.

У перших рядках відбувається підключення до основної програми інших модулів. Тут ми бачимо що підключені модуль таймерів і модуль обробки повідомлень. Далі по тексту програми йде вектор переривання по переповнення.

З рядка int main (void) можна сказати починається основна програма. І починається вона з ініціалізації всього і вся. Тут инициализируем периферію, тобто задаємо початкові значення портів введення виведення компаратору і всього іншого вмісту контролера. Все це робить функція INIT_PEREF, тут її запускаємо, хоча основне її тіло знаходиться в файлі hal.c.

Далі ми бачимо ініціалізації таймерів. модуля обробки повідомлень, ініціалізації автоматів. Тут ці функції також просто запускаються, хоча самі функції прописані в файлах своїх модулів. Бачите як зручно. Основний текст програми залишається легко читаним і не загромождают надлишковим кодом від якого чорт ногу зломи.

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

А основний цикл, як я вже і говорив вигладить дуже просто. Записуємо функції всіх автоматів, просто записуємо в стовпчик, без дотримання черговості. Ці функції є обработчиками автоматів і знаходяться в модулях автоматів. Завершує цю автоматну пірамідку функція модуля обробки повідомлень. Це я звичайно вже розповідав раніше коли розбиралися з системою відправки повідомлень. Тепер можна подивитися як виглядають ще два файли модуля основного циклу програми

Hal.h - це заголовки модуля основного циклу програми.

Як ви могли помітити цей файл за своєю суттю не містить жодного рядка коду, - це все макропідстановки і підключення бібліотек. Наявність цього файлу просто дуже добре полегшує життя, він покращує наочність.

А ось файл Hal.c - це вже виконуваний файл, і як я вже згадував, в ньому міститься різний ініціалізації периферії.

Ну що ж модуль основного циклу програми я показав тепер нам залишилося зробити останній крок, нам потрібно написати модулі самих автоматів.

Нам залишилося написати модулі кінцевих автоматів, в нашому випадку автомата LEDON і автомата LEDOFF. Для початку наведу текст програми автомата зажигающего світлодіод файл ledon.c.

Тут в перших рядках як завжди підключаються бібліотеки і оголошуються змінні. Далі у нас пішли вже функції, з якими ми вже зустрічалися. Це функція ініціалізації автомата InitLEDON і функція вже самого обробника автомата ProcessLEDON.

У тілі обробника вже відбувається відпрацювання функцій з таймерного модуля і модуля повідомлень. А сама логіка автомата виконана на основі конструкції switch-case. І тут можна помітити що обробник автомата можна також ускладнити додавши кілька перемикачів case.

Заголовки для автомата буде ще простіше:

Тут підключаємо сполучний файл hal.h а також вказуємо прототипи функцій.

Файл відповідальний за виключення світлодіода буде виглядати майже так само тільки в дзеркальному відображенні, так, що тут я його виводити не буду - не хочеться

Ось лише три кроки і наша програма набула завершеного вигляду а значить на цьому моя місія на сьогодні закінчена і пора закруглятися. Мені здається що інформація наведена в цій статті буде для вас дуже корисно. Але справжню користь вона принесе тільки тоді, коли ви будете застосовувати це знання на практиці.

Добрий день. Ось я повний чайник. Прочитавши Вашу статтю кілька разів, вникаючи, вловив лише сенс, але як застосувати на практиці, так і не зрозумів. Начебто так, так писати і наочніше і простіше, але здається ще складніше, ніж зазвичай. Звідки і навіщо беруться спільні бібліотеки. Чим відрізняються hal.h і hal.c і чому у них саме такі назви?
На даний момент мені все ж простіше розібратися в чужому «неподільний» грудці коду і внести туди необхідні мені корективи, ніж писати програму з нуля. Хоча і відчуваю, що за Вашими рекомендаціями це легше, ніж розбиратися ... Чого не вистачає мені для усвідомлення простоти. Мозгов, напевно. Але Вам все одно спасибі за працю