#6 Эксперимент “Продвинутый секундомер”
На этом занятии мы научимся выводить числовую информацию на 4-разрядный семисегментный индикатор. Это нам очень пригодиться в дальнейшем, когда мы будем собирать наши умные устройства и нам потребуется дешёвое и простое средство для вывода информации. Действительно, такие индикаторы очень недороги и надёжны. Их можно увидеть во многих электроприборах, которые нас окружают: от электрических часов, до табло индикации стиральной машины или микроволновой печи.
Индикатор времени стирки в стиральной машине | Индикатор в микроволновой печи | Часы, собранные на 4-разрядном семисегментном индикаторе |
Перед выполнением эксперимента прочтите:
По сути, семисегментный индикатор – это просто сборка, состоящая из 7-ми или 8-ми светодиодов, собранная в одном корпусе. Работать с таким индикатором можно точно так же, как и с отдельными светодиодами. Нюанс заключается лишь в “переключении” разрядов индикатора, при выводе цифр на него. Но об этом мы поговорим чуть ниже.
СБОРКА УСТРОЙСТВА НА МАКЕТНОЙ ПЛАТЕ
Нам понадобятся следующие компоненты:
- Arduino Uno;
- USB-кабель для подключения к компьютеру;
- Беспаечная макетная плата;
- 4-разрядный семисегментный индикатор;
- 4 резистора номиналом 220 Ом;
- 1 тактовая кнопка;
- 1 резистор номиналом 10 кОм;
- 20 соединительных проводов различных цветов.
Соберите устройство по следующей схеме:
Схема устройства на макетной плате |
Принципиальная схема устройства |
Обратите внимание:
- Для того, чтобы произвести корректное подключение индикатора к плате Arduino, следует знать порядок нумерации пинов индикатора. На 4-разрядном семисегментном индикаторе пины нумеруются так: снизу слева-направо от 1 до 6, сверху справа-налево от 7 до 12:
- Назначение каждого из пинов индикатора, описано в таблице:
Номер пина индикатора | Назначение пина | Управляющий сигнал | Расположение сегментов индикатора |
11 | сегмент [A] | HIGH | |
7 | сегмент [В] | HIGH | |
4 | сегмент [C] | HIGH | |
2 | сегмент [D] | HIGH | |
1 | сегмент [E] | HIGH | |
10 | сегмент [F] | HIGH | |
5 | сегмент [G] | HIGH | |
3 | сегмент [DP] | HIGH | |
12 | 1 разряд [DIG. 1] | LOW | |
9 | 2 разряд [DIG. 2] | LOW | |
8 | 3 разряд [DIG. 3] | LOW | |
6 | 4 разряд [DIG. 4] | LOW |
- Таблица, приведённая ниже поможет вам корректно подключить индикатор к Arduino:
Пин Arduino | Пин 4-разрядного семисегментного индикатора | Примечание |
1 | 11 | сегмент [A] |
2 | 7 | сегмент [B] |
3 | 4 | сегмент [C] |
4 | 2 | сегмент [D] |
5 | 1 | сегмент [E] |
6 | 10 | сегмент [F] |
7 | 5 | сегмент [G] |
8 | 3 | сегмент [DP] |
9 | 12 | 1 разряд [DIG. 1], через резистор на 220 Ом |
10 | 9 | 2 разряд [DIG. 2], через резистор на 220 Ом |
11 | 8 | 3 разряд [DIG. 3], через резистор на 220 Ом |
12 | 6 | 4 разряд [DIG. 4], через резистор на 220 Ом |
- Кнопку мы подключаем по стандартной схеме со стягивающим резистором к 13-му пину Arduino.
ПРОГРАММИРОВАНИЕ
Давайте для начала попробуем вывести какую-нибудь информацию, например цифру 2, в разных разрядах нашего индикатора. Для этого мы будем должны:
- “Включить” нужный разряд, подав команду [digitalWrite (PIN, LOW);], где PIN – пин к которому подключен нужный разряд;
- “Зажечь” нужные сегменты, например для цифры 2 это сегменты A, B, D, E, G. Для этого нужно подать на пины сегментов команду [digitalWrite(PIN, HIGH)];
- Сделать задержку в несколько миллисекунд, для того, чтобы сегменты разгорелись;
- “Выключить” разряд, подав команду [digitalWrite (PIN, HIGH);];
- “Потушить” включенные сегменты, подав команду [digitalWrite(PIN, LOW)] для каждого из них.
Для того, чтобы упростить программирование нашего индикатора, воспользуемся директивой #define, с помощью которой мы переобозначим номера пинов, подключенных к сегментам и разрядам с помощью понятных обозначений:
#define A 1 // переобозначаем пины сегментов и разрядов #define B 2 #define C 3 #define D 4 #define E 5 #define F 6 #define G 7 #define DP 8 #define DIG1 9 #define DIG2 10 #define DIG3 11 #define DIG4 12 void setup(){ for (int PIN=1; PIN<=12; PIN++){ pinMode(PIN, OUTPUT);} // назначаем все пины с 1 по 12 на выход, "прогоняя" их в цикле digitalWrite(DIG1, HIGH); // выключаем все разряды, подав на них высокий цифровой сигнал digitalWrite(DIG2, HIGH); digitalWrite(DIG3, HIGH); digitalWrite(DIG4, HIGH); } void loop(){ digitalWrite(DIG1, LOW); // включаем 1 разряд digitalWrite(A, HIGH); // зажигаем сегменты A, B, D, E, G digitalWrite(B, HIGH); digitalWrite(D, HIGH); digitalWrite(E, HIGH); digitalWrite(G, HIGH); delay(5); // пауза, чтоб сегменты разгорелись digitalWrite(DIG1, HIGH); // выключаем 1 разряд digitalWrite(A, LOW); // выключаем сегменты A, B, D, E, G digitalWrite(B, LOW); digitalWrite(D, LOW); digitalWrite(E, LOW); digitalWrite(G, LOW); }
Загрузите скетч в Arduino и проверьте его работоспособность. На 1 разряде индикатора должна загореться цифра 2. Давайте немного дополним скетч и выведем цифру 2 на всех разрядах индикатора:
#define A 1 // переобозначаем пины сегментов и разрядов #define B 2 #define C 3 #define D 4 #define E 5 #define F 6 #define G 7 #define DP 8 #define DIG1 9 #define DIG2 10 #define DIG3 11 #define DIG4 12 void setup(){ for (int PIN=1; PIN<=12; PIN++){ pinMode(PIN, OUTPUT);} // назначаем все пины с 1 по 12 на выход digitalWrite(DIG1, HIGH); // выключаем все разряды, подав на них высокий цифровой сигнал digitalWrite(DIG2, HIGH); digitalWrite(DIG3, HIGH); digitalWrite(DIG4, HIGH); } void loop(){ for (int i=DIG1; i<=DIG4; i++){ // прогоняем в цикле номера пинов, к которым подключены разряды индикатора digitalWrite(i, LOW); // включаем 1 разряд digitalWrite(A, HIGH); // зажигаем сегменты A, B, D, E, G digitalWrite(B, HIGH); digitalWrite(D, HIGH); digitalWrite(E, HIGH); digitalWrite(G, HIGH); delay(5); // пауза, чтоб сегменты разгорелись digitalWrite(i, HIGH); // выключаем 1 разряд digitalWrite(A, LOW); // выключаем сегменты A, B, D, E, G digitalWrite(B, LOW); digitalWrite(D, LOW); digitalWrite(E, LOW); digitalWrite(G, LOW); } }
Загрузите скетч в Arduino. Цифра 2 должна появиться на всех 4-х разрядах.
Как Вы видите, такой способ вывода информации на семисегментном индикаторе очень неудобен, т.к. он очень громоздок. Необходимо найти другой способ – как зажигать нужные нам сегменты не по одному, а все разом. Такой способ есть. Вместо работы с каждым пином по отдельности мы будем работать с портами Arduino в целом.
Немного информации: все цифровые и аналоговые пины в Arduino разделяются между 3-мя 8-битными портами: PORTB, PORTC, PORTD. Принадлежность тех или иных пинов к портам Arduino можно посмотреть в следующей таблице:
Теперь перепишем наш код таким образом, чтобы номера пинов и значение сигналов на этих пинах задавалось с помощью одного 8-битного значения:
// памятка - PORTD{1,1,1,1,1,1,1,1} ... PORTB{1,1, 1, 1, 1, 1, 1, 1} // G F E D C B A button DIG4 DIG3 DIG2 DIG1 DP void setup(){ DDRD=B11111110; // пины порта D кроме нулевого обозначаем выходными DDRB=B011111; // пины порта B обозначаем выходными, кроме пина кнопки //(первых 2 незадействованных бита не указываем вовсе) PORTB=B011110; // подаём высокий сигнал на все разряды, а низкий на порт кнопки и сегмента DP //(первых 2 незадействованных бита не указываем вовсе) } void loop(){ PORTB=B011100; // подаём высокий сигнал на все пины порта кроме 13-го (там кнопка), // 9-го (вкл. 1 разряд) и 8-го, там сегмент точки, которую нужно // зажечь лишь на 2-м разряде PORTD=B10110110; // зажигаем сегменты delay(5); // пауза, чтоб сегменты разгорелись PORTB=B011011; // подаём высокий сигнал на все пины порта кроме 13-го (там кнопка), // 10-го (вкл. 2 разряд), семент DP также зажигаем PORTD=B10110110; // зажигаем сегменты delay(5); // пауза, чтоб сегменты разгорелись PORTB=B010110; // подаём высокий сигнал на все пины порта кроме 13-го (там кнопка), // 11-го (вкл. 3 разряд) и 8-го, там сегмент точки, которую нужно // зажечь лишь на 2-м разряде PORTD=B10110110; // зажигаем сегменты delay(5); // пауза, чтоб сегменты разгорелись PORTB=B001110; // подаём высокий сигнал на все пины порта кроме 13-го (там кнопка), // 12-го (вкл. 4 разряд) и 8-го, там сегмент точки, которую нужно // зажечь лишь на 2-м разряде PORTD=B10110110; // зажигаем сегменты delay(5); // пауза, чтоб сегменты разгорелись }
Данный скетч делает всё тоже самое, но сам при этом – намного короче. Оцените длину скетча без комментариев:
void setup(){ DDRD=B11111110; DDRB=B011111; PORTB=B011110; } void loop(){ PORTB=B011100; PORTD=B10110110; delay(5); PORTB=B011011; PORTD=B10110110; delay(5); PORTB=B010110; PORTD=B10110110; delay(5); PORTB=B001110; PORTD=B10110110; delay(5); }
И… мы можем сделать его ещё короче, если будем изменять значения для включения разрядов внутри цикла, но для этого нам нужно перейти к десятичной системе счисления, так как до этого, мы использовали двоичный (бинарный) код, на что указывают символы [B] в командах ([PORTB=B001110;]). Для этого воспользуемся калькулятором Windows и переведём бинарное значение для команд PORTB в десятичное. Результат:
void setup(){ DDRD=254; // все пины с 1 по 8 - на выход DDRB=31; // пины с 9 по 12 - на выход, 13 - на вход PORTB=30; // выключаем все разряды (пины 9-12), } void loop(){ PORTB=28; // включаем 1 разряд PORTD=182; // выводим цифру 2 delay(5); // делаем задержку в 5 мс PORTB=27; // включаем 2 разряд PORTD=182; // выводим цифру 2 delay(5); // делаем задержку в 5 мс PORTB=22; // включаем 3 разряд PORTD=182; // выводим цифру 2 delay(5); // делаем задержку в 5 мс PORTB=14; // включаем 4 разряд PORTD=182; // выводим цифру 2 delay(5); // делаем задержку в 5 мс }
Теперь можно создать массив [digit] со значениями для PORTB и зажигать необходимые сегменты на нужных разрядах в цикле. Это сделает наш скетч ещё короче:
int digit[4]={28,27,22,14}; // массив значений для PORTB, для переключения 4-х разрядов и включения точки void setup(){ DDRD=254; // все пины с 1 по 8 - на выход DDRB=31; // пины с 9 по 12 - на выход, 13 - на вход PORTB=30; // выключаем все разряды (пины 9-12), } void loop(){ for (int d=0; d<=3; d++){ // значения в массиве нумеруются начиная с нуля, // поэтому цикл прогоняем для значений 0-3 PORTB=digit[d]; // включаем последовательно 1,2,3,4 разряды PORTD=182; // выводим цифру 2 delay(5); // делаем задержку в 5 мс } }
Теперь можно создать массив значений для каждой из выводимых цифр. Для наглядности, в таблице приведены значения в двоичном и десятичном видах:
Выводимая цифра | Значение для PORTD в двоичном виде | Значение для PORTD в десятичном виде |
1 | 00001100 | 12 |
2 | 10110110 | 182 |
3 | 10011110 | 158 |
4 | 11001100 | 204 |
5 | 11011010 | 218 |
6 | 11111010 | 250 |
7 | 00001110 | 14 |
8 | 11111110 | 254 |
9 | 11011110 | 222 |
0 | 01111110 | 126 |
Теперь, создадим массив [digit] для хранения значений выводимых цифр и попробуем переключать их с помощью кнопки:
int digit[4]={28,27,22,14}; // массив значений для PORTB, для переключения 4-х разрядов и включения точки int number[10]={126,12,182,158,204,218,250,14,254,222}; // массив значений цифр для PORTD // подсказка 0 1 2 3 4 5 6 7 8 9 int d; // переменная для переключения разряда () int n=0; // переменная для нвыбора цифры в массиве (от 0 до 9), изначально = 0 void setup(){ DDRD=254; // все пины с 1 по 8 - на выход DDRB=31; // пины с 9 по 12 - на выход, 13 - на вход PORTB=30; // выключаем все разряды (пины 9-12), } void loop(){ if (digitalRead(13)==1){ // если кнопка нажата, n++; // увеличиваем n на 1 delay(100); // пауза для предотвращения двойного нажатия кнопки if (n==10){n=0;} // если n=10, то снова обнуляем переменную, т.е. n=0 } for (d=0; d<=3; d++){ // переключаем значение разряда в массиве PORTB=digit[d]; // включаем последовательно 1,2,3,4 разряды PORTD=number[n]; // выводим цифру, хранящуюся в переменной n delay(5); // делаем задержку в 5 мс } }
Теперь можно приступить к программированию нашего секундомера. Нам нужно, чтобы секундомер начинал работать и останавливался по нажатию кнопки и измерял время с точностью до 1/100 секунды. Т.е. на 3-м и 4-м разряде мы будем выводить десятые и сотые секунд соответственно, а на 1-м и 2-м разряде, десятки и единицы. Таким образом наш секундомер будет измерять промежутки времени от 0 до 99,99 секунд. Пояснения внутри кода:
int digit[4]={28,27,22,14}; // массив значений для PORTB, для переключения 4-х разрядов и включения точки int number[10]={126,12,182,158,204,218,250,14,254,222}; // массив значений цифр для PORTD // подсказка 0 1 2 3 4 5 6 7 8 9 int d; // переменная для переключения разряда () int n=0; // переменная для выбора цифры в массиве (от 0 до 9), изначально = 0 int t0; // переменная для "обнуления" текущего времени int t; // переменная для хранения времени прошедшего с момента нажатия кнопки int dig1; // цифра для 1 разряда int dig2; // цифра для 2 разряда int dig3; // цифра для 3 разряда int dig4; // цифра для 4 разряда bool start=false; // логический триггер для старта/остановки секундомера void setup(){ DDRD=254; // все пины с 1 по 8 - на выход DDRB=31; // пины с 9 по 12 - на выход, 13 - на вход PORTB=30; // выключаем все разряды (пины 9-12), } void loop(){ if (digitalRead(13)==1){ // если кнопка нажата, то... start=!start; // переключаем логический триггер в противоположное положение t0=millis(); // создаём нулевую точку времени delay(200); // пауза для предотвращения двойного срабатывания кнопки } if (start==true){ // если логический триггер в положении true t=(millis()-t0)/10; // получаем значение времени прошедшее с нажатия кнопки в 1/100 секунды dig1=t/1000; // получаем цифру для 1 разряда dig2=(t%1000)/100; // получаем цифру для 2 разряда dig3=((t%1000)%100)/10; // получаем цифру для 3 разряда dig4=t%10; // получаем цифру для 4 разряда for (d=0; d<=3; d++){ // начинаем вывод на индикатор, переключаем значение разряда в массиве PORTB=digit[d]; // включаем последовательно 1,2,3,4 разряды if (d==0){PORTD=number[dig1];} // выводим значение на 1 разряд if (d==1){PORTD=number[dig2];} // выводим значение на 2 разряд if (d==2){PORTD=number[dig3];} // выводим значение на 3 разряд if (d==3){PORTD=number[dig4];} // выводим значение на 4 разряд delay(5); // делаем задержку в 5 мс } } if (start==false){ // если логический триггер в положении false for (d=0; d<=3; d++){ // начинаем вывод на индикатор, переключаем значение разряда в массиве PORTB=digit[d]; // включаем последовательно 1,2,3,4 разряды if (d==0){PORTD=number[dig1];} // получаем цифру для 1 разряда if (d==1){PORTD=number[dig2];} // получаем цифру для 1 разряда if (d==2){PORTD=number[dig3];} // получаем цифру для 1 разряда if (d==3){PORTD=number[dig4];} // получаем цифру для 1 разряда delay(5); // делаем задержку в 5 мс // обратите внимание, мы выводим последние значения переменных dig, т.е. // счёт времени больше не идёт } } }
Для того, чтобы сделать наш скетч универсальным, т.е. выводить на индикатор не только значение времени, но и любую другую числовую информацию, вынесем ту часть кода, которая отвечает за вывод значений на индикатор в отдельную функцию [void out()], а заодно заменим оператор выбора [if] на оператор множественного выбора [case]:
int digit[4]={28,27,22,14}; // массив значений для PORTB, для переключения 4-х разрядов и включения точки int number[10]={126,12,182,158,204,218,250,14,254,222}; // массив значений цифр для PORTD // подсказка 0 1 2 3 4 5 6 7 8 9 int d; // переменная для переключения разряда () int n=0; // переменная для выбора цифры в массиве (от 0 до 9), изначально = 0 int t0; // переменная для "обнуления" текущего времени int t; // переменная для хранения времени прошедшего с момента нажатия кнопки int dig1; // цифра для 1 разряда int dig2; // цифра для 2 разряда int dig3; // цифра для 3 разряда int dig4; // цифра для 4 разряда bool start=false; // логический триггер для старта/остановки секундомера void setup(){ DDRD=254; // все пины с 1 по 8 - на выход DDRB=31; // пины с 9 по 12 - на выход, 13 - на вход PORTB=30; // выключаем все разряды (пины 9-12), } void loop(){ if (digitalRead(13)==1){ // если кнопка нажата, то... start=!start; // переключаем логический триггер в противоположное положение t0=millis(); // создаём нулевую точку времени delay(200); // пауза для предотвращения двойного срабатывания кнопки } if (start==true){ // если логический триггер в положении true, то... t=(millis()-t0)/10; // считаем время с точностью до 1/100 секунды и... out(t); // вызываем функция для вывода значения времени } else { // если логический триггер в положении false, то... out(t); // вызываем функция для вывода значения времени } } void out(int value){ // функция для вывода значений на индикатор dig1=value/1000; // получаем цифру для 1 разряда dig2=(value%1000)/100; // получаем цифру для 2 разряда dig3=((value%1000)%100)/10; // получаем цифру для 3 разряда dig4=value%10; // получаем цифру для 4 разряда for (d=0; d<=3; d++){ // начинаем вывод на индикатор, переключаем значение разряда в массиве PORTB=digit[d]; // включаем последовательно 1,2,3,4 разряды switch (d) { case 0: PORTD=number[dig1]; break; // получаем цифру для 1 разряда case 1: PORTD=number[dig2]; break; // получаем цифру для 2 разряда case 2: PORTD=number[dig3]; break; // получаем цифру для 3 разряда case 3: PORTD=number[dig4]; break; // получаем цифру для 4 разряда } delay(5); // делаем задержку в 5 мс } }
Теперь, мы можем выводить любые значения на наш индикатор. Для этого достаточно вписать их в параметры функции [out()].
ЗАДАНИЯ ДЛЯ САМОСТОЯТЕЛЬНОГО ВЫПОЛНЕНИЯ
Задание 1:
Измените скетч секундомера таким образом, чтобы он считал время в диапазоне [0 … 999] секунд с точностью до 1/10 секунды. Для этого нужно зажечь точку [DP] в третьем разряде и получать в переменной [t] время с точностью до 1/10 секунды.
Задание 2:
Дополните скетч таким образом, чтобы при достижении максимального значения времени в 999.9 секунды, индикатор обнулялся и счёт шёл с самого начала.
Задание 3*:
Включите в схему устройства датчик освещённости (фоторезистор) и перепишите скетч таким образом, чтоб на индикаторе отображался текущий сигнал с этого датчика. Кнопку, при этом, можно не задействовать.