Автопортал || Авто - статьи

Сельскохозяйственная техника
Чтение RSS

Віртуальні машини. Об'яснялкі. Сайт про мікроконтролерах AVR.RU

  1. Багатозадачність в мікроконтролерах (для чого потрібні віртуальні машини) Як тільки ми переходимо...
  2. Як виділяти віртуальні машини

Багатозадачність в мікроконтролерах (для чого потрібні віртуальні машини)

Як тільки ми переходимо від простеньких програм в пару дій до складніших, ми стикаємося з однією з основних проблем, що виникають при програмуванні мікроконтролерів: проблемою реалізації багатозадачності.
У «великих» програмах зазвичай необхідно виконувати кілька дій одночасно: перевіряти вхідну інформацію (стан портів, дані по 1-Wire, I2C, UART і т.д.), стежити за відображенням вихідної інформації (дисплеєм і світлодіодами, наприклад), виконувати які -то керуючі дії (включення-виключення чогось, відсилання повідомлень) і ще багато чого!
На комп'ютерах за одночасність виконання різних дій відповідає операційна система - саме вона стежить за тим, хто з програм отримає зараз процесорний час, вона ж виділяє кожному за потребами пам'ять. При цьому операційна система може управляти викликом програм по-різному: є багатозадачність витісняє і невитесняющая.
При невитискаючої багатозадачності процеси допомагають операційній системі - вони самі визначають, коли варто закінчити зі своїми справами і запропонувати іншим процесам час і ресурси - тобто операційній системі залишається тільки викликати підпрограми в якійсь певній послідовності.
При витісняє багатозадачності кожен процес вважає себе найважливішим і зазвичай готовий відхопити часу і ресурсів по максимуму, а операційна система може грубо переривати такі процеси і передавати ресурси іншим.
Для того, щоб реалізувати багатозадачність, нам теж доведеться зробити щось типу простенької операційної системи-перемикача завдань - будемо використовувати невитісняючі багатозадачність. При цьому перше і найважливіше, що потрібно зробити - це виділити так звані віртуальні машини.

Що таке віртуальна машина

Віртуальна машина - це деяка відособлена частина програми, що виконує певне завдання. Ця частина програми при кожному зверненні виконує невелику кількість дій. Таким чином, якщо звертатися послідовно до декількох віртуальних машин, кожна з яких за раз займає зовсім крихітний відрізок часу для свого виконання, буде здаватися, що всі вони працюють паралельно.
Кожна віртуальна машина отримує якусь інформацію на вхід, і видає якусь інформацію на вихід; при цьому досить часто інформація з виходу однієї машини подається на вхід іншого - можна порівняти з якимось виробництвом: один цех очищає сировину, це очищене сировину надходить на вхід іншого цеху; той робить болванки, і передає їх наступного цеху, який вже і перетворює їх в кінцевий продукт. При цьому працівникам одного цеху не цікаво, що роблять інші, головне - отримати потрібне на вхід і відправити оброблений «вхід» на вихід.
Віртуальна машина дуже схожа на автомат (посилання на Савельєва «Прикладна теорія кінцевих автоматів»): у машини є кілька станів, є переходи від одного стану до іншого, є вхідні сигнали, при отриманні яких відбуваються ті чи інші переходи, і є вихідні реакції машини - то, що вона робить в певному стані і / або при певному вхідному сигналі.
Вивчимо теорію детальніше: серед кінцевих автоматів виділяють автомати Мура і автомати Мілі. Розглянемо кожен автомат на прикладі: нехай наша задача - після натискання на кнопку відправити повідомлення.
Автомат Мура в кожному стані спочатку виконує певні дії, прописані для цього стану, а потім на основі вхідних сигналів визначає, в який стан ми переходимо далі. Для нашого прикладу автомат буде виглядати так:
"Вхід і вихід"; проте в нашому випадку при досягненні результату - отримання будь-якої вихідної інформації - машина зазвичай не припиняє роботу.
Розглянемо приклад простий віртуальної машини, завдання якої - після натискання на кнопку відправити повідомлення. Ось так вона буде виглядати для автомата Мура:

Малюнок 1
Малюнок 1. Приклад автомата Мура.

Овали - це стану, переходи - відповідно, стрілки; червоним виділено аналіз переходів, синім - дії, які ми виконуємо в стані.
Всього у нас вийшло три стану: перше - це коли ми чекаємо натискання на кнопку; в цьому стані ми не робимо нічого, просто перевіряємо, чи не натиснули Ви кнопку. Якщо кнопка натиснута, то переходимо в стан 2, а якщо немає - так і залишаємося в стані 1.
Стан 2 - це безпосередньо саме відправлення повідомлення; після виконання дій в стані відбувається безумовний (незалежно від вхідних сигналів) перехід в стан 3.
У стані 3 ми чекаємо, коли кнопка буде відпущена; так само, як і в стані 1, ми нічого не робимо, тільки перевіряємо кнопку. Як тільки кнопка відпущена, переходимо в стан 1.
Тепер розглянемо автомат Мілі - в ньому спочатку відбувається перевірка вхідних сигналів, і вже по переходах відбувається виконання дій. Ось як це виглядає схематично:

Малюнок 2
Малюнок 2. Приклад автомата Мілі.

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

А тепер повернемося до нашого прикладу - спробуємо реалізувати віртуальну машину на основі схеми автомата Мура.
Вся фішка в тому, що ми використовуємо змінну, що відповідає за поточний стан віртуальної машини - назвемо її, наприклад, ButtonMachineState. Вона може приймати три значення - наприклад, 1 - коли ми чекаємо натискання на кнопку, і 2 - коли знаємо, що кнопка натиснута, і відправляємо повідомлення, і 3 - коли чекаємо, коли кнопка буде відпущена.
Тоді сама функція для цієї простенької машини буде такий:

typedef enum {bmsWaitPressing, bmsSendMessage, bmsWaitReleasing} TButtonMachineState; TButtonMachineState ButtonMachineState; void ButtonMachine () {switch ButtonMachineState {case bmsWaitPressing: if (PINB & 1) ButtonMachineState = bmsSendMessage; break; case bmsSendMessage: ShowOnDisplay ( 'Button is pressed'); ButtonMachineState = bmsWaitReleasing; break; case bmsWaitReleasing: if (~ (PINB & 1)) ButtonMachineState = bmsWaitPressing; break; }}

У нашому випадку змінна, що відповідає за поточний стан машини, буде глобальної (через брак класів, як в С ++) - хоча насправді стан машини можна передавати у функцію через параметр, а повертати новий стан через результат. Для спрощення розуміння коду в зробили її у вигляді перерахування - щоб можна було коротко описати кожне стан.
Виходить, що при кожному виклику функції нашої машини ми виконуємо пару-трійку дій і витрачаємо на це зовсім небагато часу: наприклад, потрібна кнопка натиснута, тоді в перший виклик функції ми тільки перевіримо, який стан у кнопки, і визначимо, що значення ButtonMachineState має помінятися. А в другій виклик ми вже відправимо повідомлення, і знову змінимо ButtonMachineState.
Якщо ми таким же чином реалізуємо ще кілька машин, і будемо послідовно викликати їх в основному циклі, то вийде, що все працює практично одночасно.
Звичайно, якщо у нас будуть десятки таких машин, то програма буде «пригальмовувати» - але це виникає навіть на комп'ютерах, вірно?)
Їдемо далі: в мікроконтролерах часто буває, що виконання деяких дій критично по часу: наприклад, перемикання відображення розрядів на семисегментний дисплеї, або робота з деякими інтерфейсами типу 1-Wire, зчитування значень з АЦП і т.д. При всій своїй користі звичайні віртуальні машини не можуть гарантувати однакову тривалість виконання кожного зі своїх станів - тому в таких випадках використовують переривання, які однозначно викликаються через рівні проміжки часу або при будь-яку подію. Наприклад, кожну секунду потрібно відображати час, і у нас є переривання, яке викликається раз в секунду.
Найголовніше, що потрібно запам'ятати - то, що не можна всю машину запихати в переривання! Переривання - така страшна річ, яка однозначно постійно забирає шматок часу у програми - а значить, і у всіх «звичайних» віртуальних машин.

Тому в перериванні в цьому випадку у нас буде тільки відправлятися посилка - назвемо її «OnShowTime».

Малюнок 3
Малюнок 3. Приклад використання посилки.

Пару слів про прапори і посилках (повідомленнях, якщо називати по аналогії з Windows): вважаємо, що прапор знімається і зводиться одним і тим же «пристроєм» або машиною - таким чином, якщо якась інша машина не встигла помітити Зведення прапора до того , як він був знятий - то «сам винен».
Посилка ж, або повідомлення - це прапор, який зводиться однією машиною, але вона при цьому не спантеличується подальшою долею цієї посилки - потрібно чи ця інформація якийсь інший машині. Такий прапор може встановлюватися кілька разів поспіль, "не знімаючись» - зате це гарантує, що інформацію цієї посилки - наприклад, наступ деякого події - точно встигнуть отримати, навіть якщо ця подія вже пройшло і навіть наступило ще раз: наприклад, такою подією може бути «натискання на кнопку», або «необхідність відображення часу», як в прикладі вище - навіть якщо користувач кнопку уже відпустив, або секунда вже пройшла, відповідні події дії все одно потрібно виконати будь-що-будь. І тільки після виконання дій посилка очищається - програма знову готова чекати її отримання.
Використовуючи даний підхід, потрібно враховувати існування наступних небезпек: при використанні прапора є небезпека пропустити його короткочасне Зведення; а при використанні посилки є ймовірність, що ми не помітимо, як подія відбулася кілька разів. У такому випадку, якщо кількість подій важливо, додають ще змінну-лічильник.
Ну а найголовніше при використанні прапорів і посилок - це не забувати їх очищати!

Як виділяти віртуальні машини

Для того, щоб виділити в розробляється пристрої віртуальні машини, насамперед потрібно його змоделювати в голові!

Взагалі для того, щоб почати працювати над якоюсь програмою або пристроєм, якщо це не «Hello, world!», Потрібно ... погасити дисплей, відставити клавіатуру і мишку в бік, взяти листочок і ручку, закрити очі і уявити, що ми хочемо отримати в результаті: як програма буде спілкуватися з користувачем, що вона буде отримувати на вхід, які результати видавати. Варто намалювати, які стану будуть у програми - використовуючи всі ті ж кружечки і стрілочки; якщо програма спілкується з користувачем (а це відбувається практично завжди) - як користувач бачитиме цей процес; приклад моделювання представлений тут

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

Крім такого «сумбурного» виділення, варто переглянути пристрій, наприклад, від «фізики» до «логіці»: спочатку дивимося, що можна виділити на низькому рівні, фізичному: управління висновками входу-виходу, захист від брязкоту, низький рівень протоколів - наприклад, якщо ми реалізуємо програмно інтерфейс 1-Wire, тут буде управління конкретним висновком, відправлення і читання «логічного 0» і «логічної 1» (жорстка робота по часу). Далі на основі вже виділених машин піднімаємося вище: машина кнопок, яка буде отримувати очищений сигнал від машини захисту від брязкоту і аналізувати, як довго була натиснута кнопка; машина механічного енкодера, яка також отримує вхідні дані від машини захисту від брязкоту; машина більш високого рівня 1-Wire, яка працює вже на мережевому рівні протоколу , - відсилання команд; далі - вище - машина роботи з користувачем (наприклад, відображення потрібної інформації на дисплеї і реакція на натискання кнопок і руху енкодера) і так далі.

наверх

Автор - Moriam = =

Обговорити на форумі

Звичайно, якщо у нас будуть десятки таких машин, то програма буде «пригальмовувати» - але це виникає навіть на комп'ютерах, вірно?