Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
УП МПУ-13.doc
Скачиваний:
13
Добавлен:
01.04.2025
Размер:
6 Мб
Скачать
☆

2.6. Подключение индикаторов и клавиатуры

Индикаторы и органы управления (кнопки, переключатели, клавиатура), расположенные на целевой плате или устройстве с МК являются хорошим средством отладки. Схемотехника подключения отдельных светодиодов и выключателей была рассмотрена выше (п. 1.6), примеры программного обслуживания – в лабораторной работе № 1, а также в папке Primer \ p1-1 OldLab.

В этом разделе мы познакомимся с устройством, схемами подключения и алгоритмами управления сегментных светодиодных индикаторов и клавиатур. Рассмотренные ниже примеры находятся в папке Primer \ p1-2 Human Machine Interface.

Сегментные индикаторы

Наиболее распространены 7-сегментные индикаторы (рис. 2.2). Они состоят из семи сегментов a…h, предназначеных для индикации цифр, ряда символов латинского / кириллического алфавита и сегмента десятичной точки (dp). Каждый сегмент представляет собой светодиод с номинальным током свечения 10…20 мА, что позволяет присоединять их к выводам МК напрямую (не забудем токоограничивающие резисторы). Для экономии выводов микросхемы аноды или катоды делают общим для всех 8 светодиодов, что отражается в названии микросхемы: ОА [CA – Common Anode] или ОК [CC – Common Cathode]. На рис. 2.2, а приведены схемные изображения, на рис. 2.2, б – схема именования сегментов 7-сегментного индикатора.

а б

в

Рис. 2.2

Система моделирования Poteus_VSM имеет в библиотеке Optoelectronics широкий набор различных, в том числе, 7-сегментных индикаторов (раздел 7-Segment Displays), которые в интерактивном режиме формируют программно управляемое изображение. В файле 7segLEDmodel_sw.dsn (рис. 2.2, в) представлены схемы включения моделей трех типов:

- модель индикатора на одну цифру по схеме ОК (HG1 7SEGM-COM-CAT-GRN) является аналоговой, ее параметры (Forward Voltage: 1.5 V, Segment On Current: 10 mA) определяют ВАХ светодиодов; ток сегмента задается выбором резистора, сегмент светится при токе не менее 5 мА, при росте тока интенсивность цвета не меняется;

- модель индикатора на одну цифру по схеме ОA (HG2 7SEG-MPX1-CA) является цифровой, ее параметр (Minimum Trigger Time: 1 ms) важен только при использовании алгоритма динамической индикации, о котором речь пойдет ниже.

- модель индикатора на четыре цифры по схеме ОA (HG3 7SEG-MPX4-CA-BLUE) идентична предыдущей по свойствам; на примере этой модели видно, что невозможно в статике сформировать разное изображение для цифр.

Интерактивные переключатели S1…S4 позволяют подавать напряжение и ток на сегменты индикаторов. Меняя положение переключателей можно зажигать отдельные сегменты. Далее роль переключателей будут выполнять выходы МК.

На рис. 2.3 показано подключение двух 7-сегментных индикаторов с ОА, HG1 подключен к порту А, HG2 – к порту В, используется простая статическая индикация. Для индикации цифры необходимо преобразовать двоичный код числа в код, соответствующий изображению цифры. В МК это удобно делать программно табличным способом, при этом появляется свобода выбора выводов порта для управления отдельными сегментами.

Ниже приведен текст программы циклического вывода двузначных десятичных чисел от 0 до 99, другие числа в этом примере индицируются как «9.9.». При делении числа на 10 остаток от деления (операция языка С «%») представляет собой младшую цифру, частное – старшую. Для зажигания сегмента требуется низкий уровень, поэтому перед выводом требуется побитовая инверсия (операция ~). Как видно, алгоритм вывода достаточно прост и требует очень мало процессорного времени (выделение цифр в числе и преобразование в код). Тепм вывода определяется только длительностью программной задержки _delay_ms(200), равной 200 мс, при верно указанной частоте тактирования процессора транслятору и симулятору.

Весь пример находится в папке led7-2_m16 и в одноименных файлах протеуса DSN и AVR_Studio aps.

Рис. 2.3

#include <avr/io.h>

#include <avr/delay.h>

unsigned char n=0;

#define DP 0x80

//0 1 2 3 4 5 6 7 8 9 A b C d E F

char dig7[] = { 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x27,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71

};

int main() {

unsigned char dig0, dig1;

DDRA = 0xff;

DDRB = 0xff;

while (1) {

_delay_ms(200);

if(n<100) n++; else n=0;

if(n > 99) {

PORTA = ~(dig7[9] | DP);

PORTB = ~(dig7[9] | DP);

} else {

dig1 = n / 10;

dig0 = n % 10;

PORTA = ~dig7[dig1];

PORTB = ~dig7[dig0];

}

}

}

Использование простой статической индикации при значительном количестве цифр (4 и более) наталкивается на две проблемы.

Первая заключается в неэкономном использовании выводов МК: каждая цифра требует отдельного 8-битного порта, то есть для вывода N цифр необходимо N портов МК.

При использовании 7-сегментного индикатора из нескольких цифр (HG3 на схеме рис. 2.2, в) выводы одинаковых сегментов объединяют в параллель. Это обеспечивает решение первой проблемы, теперь для вывода N цифр необходим один 8-разрядный порт плюс N отдельных выводов МК Это называется матричным подключением. Но теперь в статике невозможно получить изображение разных цифр на соседних знакоместах.

Алгоритм динамической индикации сначала формирует изображение одной цифры, в это время другие погашены. На следующем интервале времени активной становится другая цифра и т.д. При частоте мерцания отдельной цифры выше 50…100 Гц (Ти = 20…10 мс) человеческий взгляд его не замечает. Например, для индикатора из 8 цифр длительность включения одной цифры составит tи = Tи/8 = 1,25…2,5 мс. При этом требуется повышенная яркость сегмента (менее, чем пропорционально), т.е. увеличение значения тока сегмента. Если требуемое значение тока больше, чем допустимое для данного МК, используют ключ на биполярном, реже полевом транзисторе.

На рис. 2.4, а приведена типовая схема для подключения 7-сегментного индикатора на 4 цифры с общим катодом (папка led7х4_m16). Для коммутации тока 8 включенных светодиодов (при токе одного светодиода 10…20 мА, суммарный ток 80…160 мА) требуются транзисторный ключ (VT1…4, R1…R4), для зажигания светодиода на базу транзистора следует подать положительное напряжение, то есть уровень «1». Такая схема требует использования алгоритма динамической индикации.

Динамическая индикация состоит в периодическом включении (активации) одного из четырех индикаторов, остальные в это время погашены (временная диаграмма на рис. 2.4, б). При частоте динамической индикации не ниже 50 Гц человеческий глаз не замечает мерцания отдельных цифр. Для нашего случая выберем частоту 50 Гц, тогда за период 20 мс один из четырех индикаторов должен светить 20/4 = 5 мс.

На рис. 2.4, б приведен вариант схемы динамической индикации для моделирования в ProteusVSM, в котором используется цифровая модель HG (7SEG-MPX4-CC-BLUE) для индикации. Для коммутации ее катодных выводов 1…4 достаточно обычного выхода МК, поэтому для выбора индикатора требуется уровень «0» (в отличии от схемы рис. 2.4, а), остальные выходы выбора при этом должны быть в «1». Вывод кода символа выполняется в прямом коде. Ниже приведен текст программы для тестирования схемы и алгоритма, на индикатор выводится содержимое массива цифр dig[4] = {1,2,3,4}.

а

б

Рис. 2.4

#include <avr/io.h>

#include <avr/delay.h>

unsigned char n=0;

#define DIG1 (1<<0)

#define DIG2 (1<<1)

#define DIG3 (1<<2)

#define DIG4 (1<<3)

#define DP 0x80

unsigned char dig7[] = {

0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x27,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,

0х71 };

unsigned char dig[4] = {1,2,3,4};

void dynamic_indicate(void) {

static unsigned char sel_dig=0;

if(sel_dig < 4) sel_dig++; else sel_dig = 0;

switch(sel_dig) {

case 0: PORTB |= DIG4; PORTB &= ~DIG1;

PORTA = dig7[dig[0]];//

break;

case 1: PORTB |= DIG1; PORTB &= ~DIG2;

PORTA = dig7[dig[1]];//

break;

case 2: PORTB |= DIG2; PORTB &= ~DIG3;

PORTA = dig7[dig[2]];//

break;

case 3: PORTB |= DIG3; PORTB &= ~DIG4;

PORTA = dig7[dig[3]];//

}

}

int main() {

DDRA = 0xff;

DDRB = 0xff; PORTB = 0x0f;

while (1) {

_delay_ms(5);

dynamic_indicate();

}

}

Матричные светодиодные индикаторы (Dot Matrix Display в папке библиотеки Optoelectronics) имеют организацию 5*7 или 8*8 и тоже требуют динамической индикации.

Для подключения к МК одного такого индикатора требуется один порт (7 или 8 разрядов) для формирования изображения «столбца» и еще 5 или 8 разрядов для динамического выбора «столбца».

Для дисплея из 5 индикаторов с организацией 5*7 потребуется 32 выхода МК (7 – для формирования столбца и 25 = 5 * 5 для управления столбцами) и массив размером 25 байт.

При числе столбцов 25 и частоте регенерации 100 Гц частота динамической индикации составит 2500 Гц.

Чаще всего матричные индикаторы используются для построения бегущей строки. В этом случае требуется выделить в ОЗУ МК массив памяти размером N байт (N – число столбцов изображения) и организовать выборку с циклическим смещением по принципу FIFO (первым вошел, первым вышел) с интервалом 0.03…0.1 с.

Переключатели, кнопки, клавиатура

При малом количестве кнопок обычно каждую кнопку подключают к отдельному выводу МК, настроенному как вход (DDRx.y = 0). Схемы включения и алгоритм обслуживания аналогичны любому ключевому входу.

Если кнопка или тумблер трехполюсная (SPDT), то есть имеет один общий контакт и два противоположных контакта (если один замкнут, то другой разомкнут) целесообразно общий контакт подключить ко входу МК, один подключить к нулю питания (GND), другой – плюсу питания (VCC). Такая схема подключения (SW1 на рис. 2.5, а) не требует дополнительных элементов, порт МК настраивается в режим высокоомного входа (DDRx.y = 0, PORTx,y = 0), чтение состоянии через PINx.y.

Чаще встречаются переключатели с одной группой контактов (SPST). Обычно в этом случае ключ коммутирует на вход МК уровень «0», в выключенном состоянии уровень «1» обеспечивается резистором R (1…10 кОм) подключенным к питанию (VCC) либо подтягивающим резистором в схеме порта МК (AVR: [Pull-Up], выбор DDRx.y = 0, PORTx,y = 1). В такой схеме (SW2 на рис. 2.5, а) разомкнутому состоянию ключа соответствует «1», замкнутому – «0».

а б

Рис. 2.5

По логике использования различают дискретные входы по уровню и по переходу – фронту или срезу. В первом случае уровень входного сигнала напрямую используется в алгоритме управления. Для выявления фронта или среза сигнала требуется не только текущее значение входа, но и предыдущее, запомненное в выделенной ячейке памяти на предыдущем цикле чтения. Такая процедура может выполняться как программно:

static bool pred_pinxy=0;

if((PINx & (1<<y) && !pred_pinxy) //Фронт

{ pred_pinxy=1; /* Действия по фронту */ }

else pred_pinxy=0;

так и аппаратно входами прерываний или счетными входами таймеров/счетчиков.

При работе с реальными переключателями и кнопками приходится учитывать так называемый дребезг контактов [bounce]. При замыкании и размыкании контактов переходный процесс изменения уровня напряжения сопровождается шумом, при частом чтении которого можно получить хаотичную последовательность «0» и «1». Длительность этого процесса зависит от конструкции переключателя, значения коммутируемого тока и обычно составляет доли – единицы миллисекунд. Подавление дребезга [debounce] выполняется схемно или программно.

Схемное решение заключается в размещении на входе МК интегрирующей RC-цепи (рис. 2.5, б), она «заваливает» фронт и срез с постоянной времени равной произведению значений τ = RC = 1000 Ом * 0.1мкФ = 0.1 мс.

Программное подавление дребезга состоит в периодическом чтении с запоминанием последних 3…5 значений с частотой в 3…5 раз выше частоты выявления фронта/среза. Фронт или срез считается выявленным, если после изменения уровня новое значение устойчиво повторилось на последних 3…5 повторных чтении. Например, при выявлении фронта в буфере на 4 значения самое старое значение должно быть «0», остальные – «1».

char debounce_rize(char n_bit) {

static char b[3]={0,0,0};

char pin, out;

pin = PINB & (1<<n_bit);

if(pin && b[0] && b[1] && !b[2]) out = 1;

else out = 0;

b[2] = b[1];

b[1] = b[0];

b[0] = pin;

return out;

}

Для экономии выводов МК при подключении клавиатуры из более 6 кнопок выгодно использовать матричную схему, N + M выводов позволяют подключить клавиатуру из N * M кнопок. Расходы времени процессора при этом не возрастают.

Рассмотрим схему (рис. 2.6) и алгоритм обслуживания клавиатуры. Пусть матрица из 4 * 3 кнопок строчными 4 линиями подключена к выходам МК (PС0…PС3), 3 линий колонок подключены ко входам (PС4…PС6) с подтягивающими резисторами (встроенными в МК или внешними). Если на одном из выходов установить «0», а на остальных «1», то при чтении входов нажатым кнопкам будет соответствовать «0», не нажатым – «1». Таким образом, алгоритм сканирования клавиатуры сводится к установлению одной из линий выходов (PORTС.i) в «0», выдержки порядка 0.5 - 1 мкс, чтении входов (PINС.j) и записи результатов чтения в массив (kb[i]). Цикл подобных действий следует выполнить для всех выходов от 0 до 3, назовем это циклом чтения. При необходимости программного подавления дребезга цикл чтения следует выполнять в 3…5 раз чаще, чем минимальное время выявления новой клавиатурной комбинации. В этом случае понадобится двумерный массив kb[i, j].

Сложность дальнейшей обработки зависит от того, сколько вариантов нажатия клавиш следует различать. В самом простом случае процедура сканирования выявляет первую из нажатых клавиш и состояние остальных не анализирует. Сложнее различать несколько одновременно нажатых клавиш, а также формировать раздельные действия на нажатие, отпускание, и длительность удержания в нажатом положении.

Также следует различать клавиатуры мгновенного действия и варианты ввода численных или символьных данных. В последнем случае предполагается наличие хотя бы символьного дисплея для отображения вводимой/редактируемой последовательности цифр или символов, обычно процедура ввода заканчивается нажатием клавиши «Ввод» [“Enter”]. Такие действия обычно поддержаны средствами стандартного ввода/вывода как на уровне библиотек языка программирования, так и средствами операционной системы (при ее использовании).

Рис. 2.6

Пример обслуживание клавиатуры

Цифровая (телефонная) клавиатура (‘0’, ‘1’ … ‘9’, ‘*’, ‘#’) и 7-сегментный индикатор (рис. 2.7). Задача – набор кода: ввод номера начинается со «звездочки», далее ожидается ввод 4 цифр, после нажатия «решетки» последние 4 цифры используются как пароль. Варианты ответов: «rdY » - готовность к вводу, «Good» - если номер совпал (открывается замок на 10 секунд: «ti.XX»), «bAd » - если номер не совпал, «Err » - ошибка ввода (меньше 4-х цифр).

Процесс ввода кода: если цифра, то сместить строку влево и в младшую позицию поместить новую цифру.

Рис. 2.7

Модель клавиатуры в Proteus’е позволяет различить только одну нажатую клавишу, причем перед нажатием следующей обязательно следует интервал без нажатия (ограничения указателя типа «мышь»). Двухуровненвый обработчик клавы: первый выявляет код (или 0), второй определяет характер клавы (по фронту, по срезу, автоповтор и пр.)

В нашем простом примере действие должно происходить только по фронту, то есть по нажатию клавиши; нет действий по отпусканию и учета длительности нажатия клавиши.

#include <avr/io.h>

#include <avr/delay.h>

unsigned char n=0;

#define DIG1 (1<<0)

#define DIG2 (1<<1)

#define DIG3 (1<<2)

#define DIG4 (1<<3)

#define DP 0x80

#define sim_b 0b01111100

#define sim_A 0b01110111

#define sim_d 0b01011110

#define sim_E 0b01111001

#define sim_r 0b01010000

#define sim_G 0b00111101

#define sim_o 0b01011100

#define sim_Y 0b01101110

char dig7[] = {

0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x27,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71

};

unsigned char dig[4] = {1,2,3,4};//

void dynamic_indicate(void) {

static unsigned char sel_dig=0;

if(sel_dig < 4) sel_dig++; else sel_dig = 0;

switch(sel_dig) {

case 0: PORTB |= DIG4; PORTB &= ~DIG1;

PORTA = dig[3];//

break;

case 1: PORTB |= DIG1; PORTB &= ~DIG2;

PORTA = dig[2];//

break;

case 2: PORTB |= DIG2; PORTB &= ~DIG3;

PORTA = dig[1];//

break;

case 3: PORTB |= DIG3; PORTB &= ~DIG4;

PORTA = dig[0];

}

} //--------------------------------------------

char conv_hex_digit2dig7(char in) { return dig7[in]; }

//----------------------------------------------

char kb_read(void) {

char rc[3]= {0,0,0}, out=0;

PORTC &= ~(1<<4);

_delay_us(1);

rc[0] = (~PINC) & 0x0f;

PORTC |= (1<<4);

PORTC &= ~(1<<5);

_delay_us(1);

rc[1] = (~PINC) & 0x0f;

PORTC |= (1<<5);

PORTC &= ~(1<<6);

_delay_us(1);

rc[2] = (~PINC) & 0x0f;

PORTC |= (1<<6);

switch(rc[0]) {

case 1: out = '1'; break;

case 2: out = '4'; break;

case 4: out = '7'; break;

case 8: out = '*'; break;

}

switch(rc[1]) {

case 1: out = '2'; break;

case 2: out = '5'; break;

case 4: out = '8'; break;

case 8: out = '0'; break;

}

switch(rc[2]) {

case 1: out = '3'; break;

case 2: out = '6'; break;

case 4: out = '9'; break;

case 8: out = '#'; break;

}

return out;

} //--------------------------------------------

char kb_front(char in) {

static char kb_pred=0;

char out;

if(in && !kb_pred) out = in;

else out = 0;

kb_pred = in;

return out;

} //------------------------------

enum type_reg { READY, INPUT, OPEN, CLOSE };

int main() {

char kb, poz=0;

static char mode=READY;//0-ready, 1-input,2-open, 3-close

dig[0] = 0;//conv_hex_digit2dig7(0);

dig[1] = sim_Y;//conv_hex_digit2dig7(1);

dig[2] = sim_d;//conv_hex_digit2dig7(2);

dig[3] = sim_r;//conv_hex_digit2dig7(3);

DDRA = 0xff; //7-0: LED7 o seg a-dp

DDRB = 0xff; PORTB = 0x1f; //3-0: LED7 o dig1-4, 4:o Rele

DDRC = 0x70; PORTC = 0x7f; //3-0: ip kb_D-A, 6-4: o kb_3-1

while (1) {

_delay_ms(5);

dynamic_indicate();

kb = kb_front(kb_read());

if(kb) {

if(kb=='*') {

dig[0]=dig[1]=dig[2]=dig[3]=poz=0;

PORTB &= ~(1<<4);

mode = INPUT; }

if(mode==INPUT && kb=='#') {

if(dig[3]==dig7[3] && dig[2]==dig7[3] && dig[1]==dig7[3] && dig[0]==dig7[3]) {

mode = OPEN;

PORTB |= (1<<4);

dig[3]=sim_G;dig[2]=sim_o;dig[1]=sim_o; dig[0]=sim_d;//"Good"

} else {

mode = CLOSE;

dig[3]=sim_b;dig[2]=sim_A;dig[1]=sim_d; dig[0]=0;//"bAd "

}

}

if(mode==INPUT && (kb >= '0') && (kb <= '9')) {

dig[3]=dig[2];

dig[2]=dig[1];

dig[1]=dig[0];

dig[0]=dig7[kb-'0'];

}

}

}

} //=============================================

Получилось несколько коряво – сравнение идет не по кодам или номерам клавиш, а по кодам их 7-сегм кодов, но работоспособно. Хороший стиль программирования требует разделить массив дисплея и массив кода замка. Такие приемы облегчают изменение и развитие задания, но выглядят сложнее.

Пример находится в папке kb3x4_led7x4 в виде файла протеуса и проекта AVR-Studio.

Контрольные вопросы по разделам 1, 2

Теоретические

  1. Алгоритм управления, программная и аппаратная реализация, фактор времени.

  2. Системы счисления и форматы данных

  3. Микроконтроллеры для встраиваемых применений.

  4. Серия (семейство) микроконтроллеров.

  5. Архитектура семейства 8-разрядных контроллеров.

  6. Структурные схемы семейства 8-разрядных контроллеров.

  7. Система команд процессорных устройств.

  8. Методы адресации процессорных устройств.

  9. Организация памяти и программно доступные регистры 8-разрядных контроллеров.

  10. Арифметические команды

  11. Логические команды.

  12. Команды сдвига

  13. Команды передачи данных

  14. Команды переходов в программе

  15. Команды работы с битами

  16. Язык программирования ассемблер.

  17. Механизм макросов

  18. Механизм подпрограмм

  19. Сравните языки программирования МК Ассемблер и С/С++

  20. Этапы проектирования МПУ

  21. Способы отладки программы

  22. Программные и аппаратные средства отладки

  23. Загрузка кода программы и данных в МК из персонального компьютера (ПК).

  24. Тактирование и рестарт (сброс) в 8-разрядных МК.

  25. Питание и энергопотребление 8-разрядных МК.

  26. Устройство параллельного порта

  27. Ввод через параллельный порт

  28. Вывод через параллельный порт

  29. Программный опрос (поллинг) и типовые задачи управления.

  30. Периферийные устройства 8-разрядных МК.

Практические

  1. Назовите режимы адресации и диапазоны адресов операндов в команде LD R1,X(AVR) / MOV A,@R1(i51) – на выбор.

  2. Какие группы команд используют биты признаков процессора.

  3. Какое время проходит с момента установления питания МК до выполнения первой команды и чем оно определяется?

  4. Какие программные действия необходимы для настройки механизма вызова подпрограмм?

  5. Составьте алгоритм деления на 8 двухбайтного числа R4:R5.

  6. Для каких целей может использоваться стек.

  7. Приведите пример команды с косвенно-индексной адресацией данных.

  8. Приведите пример команды с косвенной адресацией команды (программы).

  9. Чем может быть вызвано сообщение об ошибке в строке с командой RJMP LAB1?

  10. Дан алгоритм. 1) 0 -> R0. 2) Тело цикла. 3) R0 – 1 -> R0. 4) Если R0 не 0, то идти к п.2), иначе конец. Сколько раз будет выполнено тело цикла в 8-разрядном МК? Команды каких групп необходимы для реализации алгоритма?

  11. Какие из подгрупп команд имеют наибольшее время выполнения и чем это объясняется?

  12. Назначение директив транслятора. В чем отличие директивы и команды ассемблера?

  13. Перечислите основные этапы программирования на языке ассемблера и используемые вами программные средства. В чем заключается процесс отладки в симуляторе?

  14. Перечислите названия и назначение основных окон среды программирования в режиме отладки.

  15. Перечислите названия и способы использования команд выполнения программы в среде программирования в режиме отладки.

  16. Перечислите преимущества и недостатки отладки программ в симуляторе по сравнению с целевым МК

  17. Составьте алгоритм вычитания двухбайтных чисел в 8-разрядных МК

  18. Составьте алгоритм умножения байта в R7 на константу 260 в 8-разрядных МК

  19. Составьте алгоритм сравнения двухбайтного числа с константой 4000 в 8-разрядных МК

  20. Составьте алгоритм сложения двухбайтного числа в R2:R3 с константой 100000

  21. Типовая схема подключения МК с внутренним тактированием в корпусе DIP-8.

  22. Типовая схема подключения МК с кварцевым резонатором на 8 МГц в корпусе DIP-20.

  23. Схема и алгоритм светофора (3 светодиода на одно направление и 3 на другое).

  24. Схема и алгоритм светофора по требованию (кнопка и 3 светодиода).