Разбираем четырёхразрядный счётчик нажатий кнопки на Arduino. Сегменты индикатора в нём управляются через сдвиговый регистр 74HC595, а разряды по очереди переключает прерывание таймера.
Зачем индикатору сдвиговый регистр
Одноразрядный семисегментный индикатор занимает 9 выводов микроконтроллера: восемь сегментных (A, B, C, D, E, F, G и десятичная точка DP) и один общий. Если подключить сегменты к сдвиговому регистру с последовательным входом и параллельным выходом, например к популярному 74HC595, контроллеру хватит трёх линий. Это тактовая линия и линия данных регистра плюс общий вывод разряда.
Чем больше разрядов, тем больше экономия. У четырёхразрядного индикатора одноимённые сегменты всех цифр объединены, а общий вывод у каждого разряда свой. Поэтому ему нужно всего 6 выводов: такт, данные и четыре общих.
Нажмите для увеличения (1280x720)
Детали и схема подключения
Нажмите для увеличения (1698x1034)
- плата Arduino UNO (микроконтроллер ATmega328P);
- четырёхразрядный семисегментный индикатор с общим анодом;
- сдвиговый регистр 74HC595;
- 4 одинаковых транзистора структуры p-n-p (2SA1015, 2S9015, 2N3906 и т. п.);
- кнопка;
- резисторы 100 Ом (по одному на каждый из 8 сегментов) и 4,7 кОм (по одному на базу каждого транзистора).
Большая часть соединений связывает индикатор с 74HC595. Каждый сегментный вывод подключён к выходу регистра через свой резистор 100 Ом: сегменты A…G идут на выходы Q7…Q1 соответственно, точка DP — на Q0. Вход данных регистра DS (вывод 14) соединён с выводом 6 Arduino. Входы ST_CP (он же RCLK) и SH_CP (он же SRCLK) замкнуты между собой и подключены к выводу 7 Arduino. Это общая тактовая линия.
Общие выводы разрядов (1 — крайний левый, затем 2, 3 и 4) подключены к коллекторам транзисторов. Эмиттеры всех четырёх транзисторов соединены с +5 В платы Arduino. Базы подключены к Arduino через резисторы 4,7 кОм: разряд 1 управляется выводом 5, разряд 2 — выводом 4, разряд 3 — выводом 3, разряд 4 — выводом 2. Низкий уровень на базе открывает транзистор, и на общий анод разряда поступает питание. Высокий уровень гасит разряд.
Кнопка, которая увеличивает показания, подключена к аналоговому входу A0. В программе этот вход работает с внутренней подтяжкой (INPUT_PULLUP), поэтому нажатие читается как логический 0.
Динамическая индикация на прерываниях Timer1
Для индикатора программа не использует никаких библиотек. Разряды зажигаются по очереди, поэтому индикатор нужно постоянно обновлять. Эту работу выполняет прерывание по переполнению Timer1, а основной цикл остаётся свободным.
Таймер настраивается в setup() записью в четыре регистра:
- TCCR1A = 0;
- TCCR1B = 1 — таймер запущен с предделителем 1, то есть 16 тактов за 1 мкс;
- TCNT1 = 0 — счётчик сброшен в ноль;
- TIMSK1 = 1 — разрешено прерывание по переполнению.
Timer1 16-разрядный, а Arduino тактуется частотой 16 МГц. Поэтому переполнение наступает каждые 4096 мкс = 65536 / (16 x prescaler), и столько же светится каждый разряд. Полный обход четырёх разрядов занимает 4 x 4096 = 16 384 мкс.
Обработчик ISR(TIMER1_OVF_vect) выполняется при каждом прерывании от Timer1. Вот что он делает:
- гасит весь индикатор функцией disp_off(), которая подаёт высокий уровень на базы всех транзисторов. Так новый код, пока он загружается в регистр, не засветится в предыдущем разряде;
- по номеру текущего разряда current_digit выделяет нужную цифру числа count: тысячи (count / 1000), сотни ((count / 100) % 10), десятки ((count / 10) % 10) или единицы (count % 10) — и передаёт её в disp();
- включает этот разряд низким уровнем на выводе его транзистора;
- переходит к следующему разряду по формуле current_digit = (current_digit % 4) + 1, то есть по кругу 1 -> 2 -> 3 -> 4 -> 1.
Основной цикл loop() только опрашивает кнопку. При нажатии count увеличивается на 1, а после 9999 сбрасывается в 0. Задержка 200 мс не даёт одному нажатию насчитать несколько единиц. Если кнопку удерживать, счёт идёт с тем же шагом в 200 мс.
Вывод цифры через 74HC595
Функция disp(byte number, bool dec_point = 0) выводит цифру number от 0 до 9. Параметр dec_point управляет точкой: по умолчанию он равен 0 и точка не горит, при dec_point = 1 сегмент DP включается. В регистр данные передаёт встроенная функция Arduino shiftOut(), старшим битом вперёд (MSBFIRST).
С таким порядком передачи и такой разводкой выходов бит 7 байта управляет сегментом A, биты 6…1 — сегментами B…G, бит 0 — точкой. Индикатор с общим анодом, поэтому сегмент горит, когда на выходе регистра ноль. Отсюда коды цифр: у «0» это 0x02 (погашен только G), у «1» — 0x9E (горят B и C), у «8» все сегментные биты нулевые. Младший бит задаёт выражение !dec_point: при dec_point = 0 он равен 1, и точка погашена.
Функция disp_off() гасит весь индикатор: она выставляет высокий уровень на выводах всех четырёх разрядов.
После shiftOut() на тактовый вывод подаётся ещё один импульс: HIGH, затем LOW. Он нужен потому, что входы ST_CP и SH_CP объединены. При таком включении регистр хранения 74HC595 отстаёт от сдвигового на один такт. Поэтому принятый байт появляется на выходах только после девятого импульса.
Листинг программы
/*
* 7-segment display with 74HC595 shift register
* 4-Digit counter example.
* Common anode 7-segment display is used.
* This is a free software with NO WARRANTY.
* https://simple-circuit.com/
*/
// counter button definition
#define button A0
// shift register pin definitions
#define clockPin 7 // clock pin
#define dataPin 6 // data pin
// common pins of the four digits definitions
#define Dig1 5
#define Dig2 4
#define Dig3 3
#define Dig4 2
// variable declarations
byte current_digit;
int count = 0;
void disp(byte number, bool dec_point = 0);
void setup()
{
pinMode(button, INPUT_PULLUP);
pinMode(Dig1, OUTPUT);
pinMode(Dig2, OUTPUT);
pinMode(Dig3, OUTPUT);
pinMode(Dig4, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
disp_off(); // turn off the display
// Timer1 module overflow interrupt configuration
TCCR1A = 0;
TCCR1B = 1; // enable Timer1 with prescaler = 1 ( 16 ticks each 1 µs)
TCNT1 = 0; // set Timer1 preload value to 0 (reset)
TIMSK1 = 1; // enable Timer1 overflow interrupt
}
ISR(TIMER1_OVF_vect) // Timer1 interrupt service routine (ISR)
{
disp_off(); // turn off the display
switch (current_digit)
{
case 1:
disp(count / 1000); // prepare to display digit 1 (most left)
digitalWrite(Dig1, LOW); // turn on digit 1
break;
case 2:
disp( (count / 100) % 10); // prepare to display digit 2
digitalWrite(Dig2, LOW); // turn on digit 2
break;
case 3:
disp( (count / 10) % 10); // prepare to display digit 3
digitalWrite(Dig3, LOW); // turn on digit 3
break;
case 4:
disp(count % 10); // prepare to display digit 4 (most right)
digitalWrite(Dig4, LOW); // turn on digit 4
}
current_digit = (current_digit % 4) + 1;
}
// main loop
void loop()
{
if(digitalRead(button) == 0)
{
count++; // increment 'count' by 1
if(count > 9999)
count = 0;
delay(200); // wait 200 milliseconds
}
}
void disp(byte number, bool dec_point)
{
switch (number)
{
case 0: // print 0
shiftOut(dataPin, clockPin, MSBFIRST, 0x02 | !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
break;
case 1: // print 1
shiftOut(dataPin, clockPin, MSBFIRST, 0x9E | !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
break;
case 2: // print 2
shiftOut(dataPin, clockPin, MSBFIRST, 0x24 | !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
break;
case 3: // print 3
shiftOut(dataPin, clockPin, MSBFIRST, 0x0C | !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
break;
case 4: // print 4
shiftOut(dataPin, clockPin, MSBFIRST, 0x98 | !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
break;
case 5: // print 5
shiftOut(dataPin, clockPin, MSBFIRST, 0x48 | !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
break;
case 6: // print 6
shiftOut(dataPin, clockPin, MSBFIRST, 0x40 | !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
break;
case 7: // print 7
shiftOut(dataPin, clockPin, MSBFIRST, 0x1E | !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
break;
case 8: // print 8
shiftOut(dataPin, clockPin, MSBFIRST, !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
break;
case 9: // print 9
shiftOut(dataPin, clockPin, MSBFIRST, 0x08 | !dec_point);
digitalWrite(clockPin, HIGH);
digitalWrite(clockPin, LOW);
}
}
void disp_off()
{
digitalWrite(Dig1, HIGH);
digitalWrite(Dig2, HIGH);
digitalWrite(Dig3, HIGH);
digitalWrite(Dig4, HIGH);
}
// end of code.
Похожие материалы на сайте
Файлы проекта