Все современные микроконтроллеры имеют определенное число контактов ввода/вывода общего назначения (IO pins), но в некоторых случаях как, например, подключение семисегментного индикатора (дисплея), этого числа контактов может быть недостаточно. В этих случаях для увеличения числа доступных контактов хорошим выбором является использование регистра сдвига 74HC595. Данный регистр принимает данные в последовательной форме и выдает их на выход в параллельной форме. Он требует всего 3 контакта для управления. Ранее на нашем сайте мы уже рассматривали подключение регистра сдвига 74HC595 к платам Arduino и Raspberry Pi.
Таким образом, мы можем использовать всего 3 выходных контакта микроконтроллера и соединить каскадом 2 или более регистров сдвига чтобы получить в результате 16 или 24+ выходных контактов. В данной статье мы рассмотрим подключение трех регистров сдвига 74HC595 к трем контактам модуля ESP32, а к выходам регистров сдвига подключим три семисегментных индикатора. Программировать модуль ESP32 мы будем с помощью Arduino IDE.
Необходимые компоненты
- ESP32 Devkit v4.0 (купить на AliExpress).
- Семисегментный индикатор с общим катодом – 3 шт.
- Регистр сдвига 74HC595 – 3 шт. (купить на AliExpress).
- Резистор 680 Ом – 24 шт. (купить на AliExpress).
- Источник питания 5 В.
- Макетная плата.
- Соединительные провода.
- Кабель micro-USB.
Схема проекта
Схема подключения к модулю ESP32 3-х семисегментных индикаторов с помощью регистров сдвига 74HC595 представлена на следующем рисунке.
Принципы работы семисегментного индикатора вы можете более подробно посмотреть в этой статье.
Регистр сдвига 74HC595
Распиновка (назначение контактов) микросхемы регистра сдвига 74HC595 представлена на следующем рисунке. Расшифрованы контакты в таблице ниже.
Наименование контактов | Выполняемые функции |
Q0 — Q7 | Выходные контакты микросхемы (на рисунке обозначены красным прямоугольником), на них выдаются параллельно 8 бит данных. Мы к ним подключаем светодиоды. |
Data Pin (DS) | Контакт, на который мы передаем данные последовательно, бит за битом. Чтобы передать 1, мы с помощью подтягивающего резистора подаем на него напряжение высокого уровня (high), а чтобы передать 0 – мы подаем на него напряжение низкого уровня. |
Clock Pin (SHCP) | Контакт синхронизации. Каждый импульс на этом контакте заставляет регистр сдвига считать один бит с контакта Data Pin и сохранить его. |
Shift Output (STCP) | После приема 8 бит мы подаем импульс на этот контакт чтобы увидеть выход регистра сдвига. |
Далее перейдем к рассмотрению программы для ESP32.
Объяснение программы для модуля ESP32
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Вначале в программе нам необходимо задать тип семисегментного индикатора – у нас он с общим катодом, поэтому переменной commonCathode мы присваиваем значение true. А если бы у нас семисегментный индикатор был с общим анодом, то этой переменной необходимо было бы присвоить значение false.
1 |
const bool commonCathode = true; |
В следующем фрагменте кода мы запрограммируем цифры и символы, которые мы хотим отображать на семисегментных индикаторах в 8-битном формате – он в точности равен значению выходных контактов регистров сдвига, с которых и осуществляется управление сегментами семисегментных индикаторов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
const byte digit_pattern[17] = { // 74HC595 Outpin Connection with 7segment display. // Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 // a b c d e f g DP 0b11111100, // 0 0b01100000, // 1 0b11011010, // 2 0b11110010, // 3 0b01100110, // 4 0b10110110, // 5 0b10111110, // 6 0b11100000, // 7 0b11111110, // 8 0b11110110, // 9 0b11101110, // A 0b00111110, // b 0b00011010, // C 0b01111010, // d 0b10011110, // E 0b10001110, // F 0b00000001 // . }; |
В следующих строках объявим контакты модуля ESP32, к которым подключены контакты регистра сдвига.
1 2 3 4 5 6 7 8 9 |
//Pin connected to ST_CP of 74HC595 int latchPin = 4; //Pin connected to SH_CP of 74HC595 int clkPin = 12; //Pin connected to DS of 74HC595 int dtPin = 14; // display value int dispVal = 0; bool increment = true; |
В функции void setup() мы зададим режим работы используемых контактов на вывод данных (OUTPUT) и инициализируем последовательную связь для целей отладки.
1 2 3 4 5 6 7 8 9 10 |
void setup() { // put your setup code here, to run once: // set the serial port at 115200 Serial.begin(115200); delay(1000); // set the 74HC595 Control pin as output pinMode(latchPin, OUTPUT); //ST_CP of 74HC595 pinMode(clkPin, OUTPUT); //SH_CP of 74HC595 pinMode(dtPin, OUTPUT); //DS of 74HC595 } |
В функции void loop() у нас будет две части. В первой из них будут инкрементироваться цифры, а в другой цифры будут декрементироваться. Но независимо от этого всегда перед пересылкой данных в регистр сдвига необходимо на его контакт latch подавать уровень LOW, а после окончания передачи данных на этот контакт необходимо подавать уровень High.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
void loop() { // put your main code here, to run repeatedly: int dispDigit1=dispVal/10; int dispDigit2=dispVal%10; if(increment==true){ printf("%d%d.\n", dispDigit1,dispDigit2); digitalWrite(latchPin, LOW); if(commonCathode == true){ shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit2]|digit_pattern[16]); // 1. (Digit+DP) shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit1]); }else{ shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit2]|digit_pattern[16])); // 1. (Digit+DP) shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit1])); } digitalWrite(latchPin, HIGH); dispVal += 1; if (dispVal == 99){ increment=false; } }else{ printf("%d%d.\n", dispDigit1,dispDigit2); digitalWrite(latchPin, LOW); if(commonCathode == true){ shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit2]|digit_pattern[16]); // 1. (Digit+DP) shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit1]); }else{ shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit2]|digit_pattern[16])); // 1. (Digit+DP) shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit1])); } digitalWrite(latchPin, HIGH); dispVal -= 1; if (dispVal == 0){ increment=true; } } delay(250); } digitalWrite(latchPin, LOW); |
Данные будут передаваться между состояниями Low и High контакта latch регистра сдвига.
1 |
digitalWrite(latchPin, HIGH); |
Если у нас семисегментный индикатор с общим катодом, то для вывода на него цифр мы будем использовать код:
1 2 |
shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit2]|digit_pattern[16]); shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit1]); |
Данная функция shiftOut использует побитовый (поразрядный оператор) чтобы продвигать данные последовательно через контакт clock регистра сдвига. Первыми передаются младшие (двоичные) разряды (LSB), сначала они подаются на первый регистр сдвига 74HC595, а затем передаются на следующий регистр сдвига 74HC595. Третий семисегментный индикатор не программируется и подключен для того, чтобы наблюдать за тем, что первая цифра передалась на последний семисегментный индикатор. Если семисегментный индикатор будет с общим анодом, то в функции shiftOut необходимо будет использовать оператор инвертирования чтобы инвертировать выходные значения.
1 2 |
shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit2]|digit_pattern[16])); shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit1])); |
Тестирование работы проекта
Схема проекта собрана на двух макетных платах. После загрузки кода программы в модуль ESP32 на семисегментных индикаторах должны начать отображаться цифры как показано на следующем рисунке.
Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы (скетча)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
/* * данная программа отображает цифры на 3-х семисегментных индикаторах * Hardware Connections (Breakoutboard to Arduino Nano): * Vin - 5V (3.3V is allowed) * GND - GNDs * 74HC595 ST_CP - 4 (ESP32) * 74HC595 SH_CP - 12 (ESP32) * 74HC595 DS - 14 (ESP32) * */ // устанавливаем тип семисегментного индикатора (с общим катодом или общим анодом) const bool commonCathode = true; // необходимо использовать значение true для семисегментного индикатора с общим катодом и значение false для семисегментного индикатора с общим анодом // шаблоны цифр для семисегментного индикатора const byte digit_pattern[17] = { // 74HC595 Outpin Connection with 7segment display. // Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 // a b c d e f g DP 0b11111100, // 0 0b01100000, // 1 0b11011010, // 2 0b11110010, // 3 0b01100110, // 4 0b10110110, // 5 0b10111110, // 6 0b11100000, // 7 0b11111110, // 8 0b11110110, // 9 0b11101110, // A 0b00111110, // b 0b00011010, // C 0b01111010, // d 0b10011110, // E 0b10001110, // F 0b00000001 // . }; //Pin connected to ST_CP of 74HC595 int latchPin = 4; //Pin connected to SH_CP of 74HC595 int clkPin = 12; //Pin connected to DS of 74HC595 int dtPin = 14; // display value int dispVal = 0; bool increment = true; void setup() { // put your setup code here, to run once: // set the serial port at 115200 Serial.begin(115200); delay(1000); // зададим режим работы контактов, к которым подключен регистр сдвига 74HC595, на вывод данных pinMode(latchPin, OUTPUT); //ST_CP of 74HC595 pinMode(clkPin, OUTPUT); //SH_CP of 74HC595 pinMode(dtPin, OUTPUT); //DS of 74HC595 } void loop() { // put your main code here, to run repeatedly: int dispDigit1=dispVal/10; int dispDigit2=dispVal%10; if(increment==true){ printf("%d%d.\n", dispDigit1,dispDigit2); digitalWrite(latchPin, LOW); if(commonCathode == true){ shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit2]|digit_pattern[16]); // 1. (Digit+DP) shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit1]); }else{ shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit2]|digit_pattern[16])); // 1. (Digit+DP) shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit1])); } digitalWrite(latchPin, HIGH); dispVal += 1; if (dispVal == 99){ increment=false; } }else{ printf("%d%d.\n", dispDigit1,dispDigit2); digitalWrite(latchPin, LOW); if(commonCathode == true){ shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit2]|digit_pattern[16]); // 1. (Digit+DP) shiftOut(dtPin, clkPin, LSBFIRST, digit_pattern[dispDigit1]); }else{ shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit2]|digit_pattern[16])); // 1. (Digit+DP) shiftOut(dtPin, clkPin, LSBFIRST, ~(digit_pattern[dispDigit1])); } digitalWrite(latchPin, HIGH); dispVal -= 1; if (dispVal == 0){ increment=true; } } delay(250); } |