24.2: Практичний приклад - круговий перемикач
- Page ID
- 29395
Давайте поглянемо на те, як ми могли б реалізувати круговий перемикач. Циркулярний перемикач працює круговим способом; крокуючи послідовність налаштувань і повертаючись назад до початку, як тільки послідовність буде завершена. Для нашого прикладу ми розглянемо одну миттєву контактну кнопку, яка переходить через ряд швидкостей вентилятора: вимкнений, низький, середній і високий. Система запуститься у вимкненому стані, і кожне наступне натискання кнопки буде просуватися стан від низького до високого. Після досягнення високого рівня подальше натискання повернеться до вимкнення стану, і процес продовжується подібним чином. Кожне налаштування швидкості буде вказано за допомогою власного світлодіода, і лише один світлодіод буде включений у будь-який момент часу. Щоб зберегти код в чистоті, потрібно лише вивчити кнопку і світлодіодну частину коду і не враховувати, як ми можемо контролювати швидкість вентилятора. Крім того, ми припустимо, що відповідна апаратна схема дебонсу включена з кнопкою (наприклад, 74HC14 Schmitt з мережею RC), і що натискання кнопки призведе до логічного максимуму на нашому вхідному контакті Arduino. Крім того, світлодіодні драйвери вважаються активними високими (тобто, високий на вихідному контакті Arduino буде світитися світлодіод).
Перше, що потрібно пам'ятати, це те, що типовий мікроконтролер здатний перевірити стан вхідного штифта за частки мікросекунди. Отже, ми не можемо просто перевірити штифт, щоб побачити, чи є він логічним високим, і якщо так, перейти до наступної швидкості вентилятора. За частки секунди людині потрібно буде натиснути і відпустити кнопку, мікроконтролер міг петля через код перевірки контактів тисячі разів, циклічно швидкість вентилятора для перевірки кожного циклу. Кінцева швидкість вентилятора буде будь-хто здогадатися. Натомість нам потрібно шукати перехід від низького до високого, оскільки це відбувається лише один раз при кожному натисканні кнопки. Дійсно, це також вказує на необхідність дебонсингу перемикачів. Без нього одне натискання може призвести до більш ніж десятка низьких до високих країв через перемикач контакту балаканина, що призводить до випадкового налаштування вентилятора. Одним із способів виконання позитивного виявлення краю є використання двох змінних, одна для поточного стану комутатора та друга для попереднього стану перемикача (тобто стан, який був виміряний безпосередньо перед поточним станом). Якщо поточний стан високий (увімкнено або натиснуто), а попередній стан низький (вимкнений або не натиснутий), то ми знаємо, що ми виявили позитивний перехід, і ми можемо виконати нашу обробку. Зверніть увагу, що на наступній ітерації циклу поточний стан буде високим, але попередній стан тепер також буде високим (тобто колишній поточний стан з попередньої ітерації циклу) і, таким чином, обробка не виконується. На даний момент не має значення, як довго користувач тримав палець на кнопці, оскільки всі поточні та попередні стани будуть високими. Коли користувач нарешті відпустить кнопку, поточний стан буде низьким з попереднім станом високого. Це негативний край, і, знову ж таки, нам не потрібно його обробляти. Наступна ітерація циклу призведе до поточних і попередніх станів низького без необхідності обробки. Врешті-решт, коли користувач знову натискає кнопку, ми побачимо поточний стан високого з попереднім станом низького, і ми будемо обробляти це ребро.
Нам знадобляться три змінні для станів, а саме поточні та попередні стани кнопки та інша для світлодіодів (які вказують на швидкість вентилятора). Кнопки можна розглядати як булеві змінні (0/1), тоді як світлодіодна змінна буде варіюватися від 0 до 3, 0 «вимкнено», а 3 - «високий». Для простоти ми будемо використовувати глобальні для цих, непідписані символи будуть робити красиво. Чотири світлодіоди будуть розташовані від контактів Arduino Uno 8 до 11 (PORTB. 0:3), а кнопка буде підключена до контакту 12 (PORTB.4). Функція setup () повинна буде встановити правильні напрямки для цих бітів портів. Це потребуватиме п'яти окремих викликів до PinMode (), але лише дві прості операції, якщо ми працюємо безпосередньо. Ось початкова порція:
// declare the state variables and initialize at 0 for “off”
unsigned char currentstate=0;
unsigned char priorstate=0;
unsigned char ledstate=0;
// define some values for the LED/fan state. These are conveniently
// chosen to be the FAN/LED bit positions in PORTB
#define FAN_OFF 0
#define FAN_LO 1
#define FAN_MED 2
#define FAN_HIGH 3
// declare bit masks for the four LED bits and pushbutton
#define LED_OFF 0x01
#define LED_LOW 0x02
#define LED_MED 0x04
#define LED_HIGH 0x08
#define PBUTTON 0x10
// the LED_XXX masks could also be defined like so:
// #define LED_OFF (1<<FAN_OFF)
// a convenience
#define LED_ALL (LED_OFF|LED_LOW|LED_MED|LED_HIGH)
setup()
{
// set for output
DDRB |= LED_ALL;
// set for input
DDRB &= ~PBUTTON;
// light up the “off” LED
PORTB |= LED_OFF;
// by default, outputs are 0 but if for some reason the other
// LEDs could be on, be sure to set them to off before continuing
// PORTB &= ~(LED_LOW|LED_MED|LED_HIGH);
}
Циклічний код повинен перевірити правильність поточної пари стан/попереднього стану. Якщо вона не знайдена, робити нічого. Якщо він знайдений, нам потрібно вимкнути існуючий світлодіод, а потім перевірити поточний стан світлодіода/вентилятора, збільшити до наступного стану і запалити цей світлодіод. Є пара способів зробити це. Перший - це, мабуть, очевидне рішення з використанням конструкції перемикача/корпусу. Другий спосіб трохи компактніше (каламбур призначений).
loop()
{
// get current button state
currentstate = PINB & PBUTTON;
// do we have a positive going edge?
if( currentstate && !priorstate )
{
switch( ledstate )
{
case FAN_OFF:
PORTB &= ~LED_OFF; // turn off old LED
PORTB |= LED_LOW; // turn on new
ledstate = FAN_LOW; // increment speed
break;
case FAN_LOW:
PORTB &= ~LED_LOW;
PORTB |= LED_MED;
ledstate = FAN_MED;
break;
case FAN_MED:
PORTB &= ~LED_MED;
PORTB |= LED_HIGH;
ledstate = FAN_HIGH;
break;
case FAN_HIGH:
PORTB &= ~LED_HIGH;
PORTB |= LED_OFF;
ledstate = FAN_OFF;
break;
}
}
// update state for next loop iteration
priorstate = currentstate;
}
Альтернативну версію можна знайти на наступній сторінці. Зверніть увагу на економію коду. Замість того, щоб перевіряти стан кожного вентилятора, відключаємо всі світлодіоди і збільшуємо стан вентилятора. Якщо стан буде збільшуватися повз максимуму, то ми переконуємося, щоб встановити його в вимкненому стані. Цей новий стан говорить нам, який світлодіод світиться. Ця версія працює з невеликим кодом через те, як ми вибрали наші бітові позиції. Це не завжди можливо (або навіть бажано), але час від часу може бути зручною технікою.
loop()
{
// get current button state
currentstate = PINB & PBUTTON;
// do we have a positive going edge?
if( currentstate && !priorstate )
{
// increment to new state
if( ledstate < FAN_HIGH )
ledstate++;
else
ledstate = FAN_OFF;
// lazy: turn off all LEDs
PORTB &= ~LED_ALL;
// turn on new LED
PORTB |= (1<<ledstate);
}
// update state for next loop iteration
priorstate = currentstate;
}
