Семисегментный индикатор - простейший вид дисплея, на который можно выводить данные из программы. В макетах и прототипах удобнее использовать готовые модули с контроллером, в реальных проектах - совмещать "голые" дисплеи с микросхемами-контроллерами, а в целях обучения идеально подходит "голый" дисплей - для вывода на него нужно понимать, как работает динамическая индикация и знать битовую математику, чтобы написать красивый и эффективный алгоритм.
![]() |
В наборе GyverKIT | START | IOT | EXTRA |
---|---|---|---|---|
7-сег. индикатор | ✔ |
У нас в наборе идёт дисплей со следующими характеристиками:
- Размер: 0.36"
- Кол-во индикаторов: 4
- Схема: общий катод
- Макс. ток на сегмент: 20 мА
- Цвет: красный
Устройство #
Одна цифра (индикатор, знакоместо) дисплея состоит из 7 сегментов-светодиодов (есть модели с десятичными точками, это 8-ой светодиод). Все сегменты соединены параллельно - один полюс каждого светодиода выведен на корпус (буквы A-F, DP), а второй - объединён (D1-D4):
Получается у дисплея на 4 знакоместа есть 8 пинов для управления сегментами и 4 пина для выбора активного индикатора - это второй полюс группы светодиодов, он может быть как анодом, так и катодом - существует два вида дисплеев. Для демонстрации работы я подключил пины A, B, C, D, G на 5V, а общие пины - через кнопки на GND (у меня дисплей с общим катодом):
При нажатии кнопки замыкается цепь и ток течёт из 5V в GND через выбранную группу светодиодов, образуя цифру 3
на дисплее.
Подключение к Arduino #
Подключается к любым цифровым пинам. Ограничение тока:
- Либо каждый сегмент через резистор 500-700 Ом - яркость будет одинаковая на всех цифрах
- Либо общий пин через резистор ~200 Ом - чем больше горит сегментов - тем меньше их яркость
Я подключил общие пины по порядку (D1-D4) к D2-D5, а аноды сегментов по порядку (A-F, DP) к D6-D13:
Программирование #
Для удобства сделаем массивы пинов:
// пины катодов
const uint8_t digs[] = {2, 3, 4, 5}; // D0, D1, D2, D3
// пины сегментов
const uint8_t segs[] = {6, 7, 8, 9, 10, 11, 12, 13}; // A, B, C, D, E, F, G, DP
И настроим их все как выходы:
void setup() {
for (uint8_t p : digs) pinMode(p, OUTPUT);
for (uint8_t p : segs) pinMode(p, OUTPUT);
}
Включим сегменты A, C, D, E на втором индикаторе - нужно подать высокий сигнал на сегменты, а общим точкам включить высокие сигналы всем, кроме второго:
digitalWrite(segs[0], HIGH); // A
digitalWrite(segs[2], HIGH); // C
digitalWrite(segs[3], HIGH); // D
digitalWrite(segs[4], HIGH); // E
digitalWrite(digs[0], HIGH);
digitalWrite(digs[1], LOW);
digitalWrite(digs[2], HIGH);
digitalWrite(digs[3], HIGH);
Нас интересует вывод цифр, поэтому можно сделать массив "кодов" для символов цифр, закодировав битами нужные сегменты - например начиная с младшего бита. Цифра 0
- это горят сегменты A-F, т.е. 0b00111111
- оставляем выключенными сегменты F и DP. По аналогии можно сделать все цифры:
// коды цифр
const uint8_t nums[] = {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00000111, // 7
0b01111111, // 8
0b01101111, // 9
};
Отправить такую цифру на сегменты можно следующим образом, с маской на первый бит и сдвигом (урок про битовые операции):
// копируем значение
uint8_t data = nums[3]; // цифра 3
// выводим на сегменты, сдвигая вправо
for (uint8_t p : segs) {
digitalWrite(p, data & 1);
data >>= 1;
}
digitalWrite(digs[0], HIGH);
digitalWrite(digs[1], LOW);
digitalWrite(digs[2], HIGH);
digitalWrite(digs[3], HIGH);
Нетрудно догадаться, что если подать низкий сигнал на остальные катоды - на этих индикаторах тоже появится наш "символ":
digitalWrite(digs[0], LOW);
digitalWrite(digs[1], LOW);
digitalWrite(digs[2], HIGH);
digitalWrite(digs[3], LOW);
Таким образом, дисплей может отображать только одну уникальную комбинацию сегментов в один момент времени, потому что сегменты подключены параллельно. Если нужно отображать разные комбинации сегментов на разных индикаторах - они должны включаться по очереди, это называется динамическая индикация.
Динамическая индикация #
В процессе динамической индикации нужно переключать активный индикатор дисплея и подавать соответствующие сигналы на сегменты с некоторым периодом - не сильно длинным, чтобы мерцание и переключение индикаторов не вызывало неприятных ощущений в глазах, но и не настолько коротким, чтобы МК постоянно занимался переключением пинов - например каждые несколько миллисекунд. Такой подход усложняет разработку программы, но многократно сокращает количество пинов для подключения: в нашем случае имеем 8 + 4 = 12 пинов, а без динамической индикации (если взять 4 отдельных индикатора) было бы 8 * 4 = 32 пина.
Так как динамическая индикация должна постоянно обновлять дисплей - нам понадобится буфер для хранения сегментов и счётчик, чтобы переключать индикаторы:
uint8_t buf[4];
uint8_t idx;
void loop() {
// погасить текущий индикатор
digitalWrite(digs[idx], HIGH);
// сместить индекс индикатора
if (++idx >= 4) idx = 0;
// текущее значение из буфера
uint8_t data = buf[idx];
// выводим на сегменты
for (uint8_t p : segs) {
digitalWrite(p, data & 1);
data >>= 1;
}
// включить текущий индикатор
digitalWrite(digs[idx], LOW);
// ждём
delay(10);
}
Теперь можно вывести цифры, например вручную:
buf[0] = nums[0];
buf[1] = nums[1];
buf[2] = nums[2];
buf[3] = nums[3];
От задержки можно избавиться при помощи таймера на миллис и сделать асинхронный тикер:
void dispTick() {
static uint32_t tmr;
if (millis() - tmr < 10) return;
tmr = millis();
digitalWrite(digs[idx], HIGH);
if (++idx >= 4) idx = 0;
uint8_t data = buf[idx];
for (uint8_t p : segs) {
digitalWrite(p, data & 1);
data >>= 1;
}
digitalWrite(digs[idx], LOW);
}
Полный код
// пины катодов
const uint8_t digs[] = {2, 3, 4, 5}; // D0, D1, D2, D3
// пины сегментов
const uint8_t segs[] = {6, 7, 8, 9, 10, 11, 12, 13}; // A, B, C, D, E, F, G, DP
// коды цифр
const uint8_t nums[] = {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00000111, // 7
0b01111111, // 8
0b01101111, // 9
};
// буфер
uint8_t buf[4];
void dispTick() {
static uint8_t idx;
static uint32_t tmr;
if (millis() - tmr < 10) return;
tmr = millis();
digitalWrite(digs[idx], HIGH);
if (++idx >= 4) idx = 0;
uint8_t data = buf[idx];
for (uint8_t p : segs) {
digitalWrite(p, data & 1);
data >>= 1;
}
digitalWrite(digs[idx], LOW);
}
void setup() {
for (uint8_t p : digs) pinMode(p, OUTPUT);
for (uint8_t p : segs) pinMode(p, OUTPUT);
// пишем в буфр для отображения
buf[0] = nums[0];
buf[1] = nums[1];
buf[2] = nums[2];
buf[3] = nums[3];
// buf[0] = nums[4];
// buf[1] = nums[5];
// buf[2] = nums[6];
// buf[3] = nums[7];
// buf[0] = nums[8];
// buf[1] = nums[9];
}
void loop() {
dispTick();
}
Отлично, теперь можно сделать удобную функцию для отображения чисел. Выводить числа удобнее с младшего разряда (справа налево), поэтому давайте развернём дисплей - просто указав общие пины в обратном порядке:
// пины катодов
const uint8_t digs[] = {5, 4, 3, 2}; // D3, D2, D1, D0
Функция вывода чисел должна разделить число на разряды - для этого нужно делить число на 10. Чтобы получить левую цифру числа - взять остаток от деления на 10. Это будет цифра - её код возьмём из массива кодов по значению. Итого:
void dispPrint(int val) {
for (int i = 0; i < 4; i++) {
buf[i] = nums[val % 10];
val /= 10;
}
}
Проверим:
void setup() {
for (uint8_t p : digs) pinMode(p, OUTPUT);
for (uint8_t p : segs) pinMode(p, OUTPUT);
dispPrint(1234);
}
void loop() {
dispTick();
}
Отлично. Осталось добавить потенциометр, чтобы вводить данные в процессе работы программы:
И выводить значение с АЦП например каждые 50 мс - асинхронно, чтобы не мешать работе динамической индикации:
Полный код
// пины катодов
const uint8_t digs[] = {5, 4, 3, 2}; // D3, D2, D1, D0
// пины сегментов
const uint8_t segs[] = {6, 7, 8, 9, 10, 11, 12, 13}; // A, B, C, D, E, F, G, DP
// коды цифр
const uint8_t nums[] = {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00000111, // 7
0b01111111, // 8
0b01101111, // 9
};
// буфер
uint8_t buf[4];
void dispTick() {
static uint8_t idx;
static uint32_t tmr;
if (millis() - tmr < 10) return;
tmr = millis();
digitalWrite(digs[idx], HIGH);
if (++idx >= 4) idx = 0;
uint8_t data = buf[idx];
for (uint8_t p : segs) {
digitalWrite(p, data & 1);
data >>= 1;
}
digitalWrite(digs[idx], LOW);
}
void dispPrint(int val) {
for (int i = 0; i < 4; i++) {
buf[i] = nums[val % 10];
val /= 10;
}
}
void setup() {
for (uint8_t p : digs) pinMode(p, OUTPUT);
for (uint8_t p : segs) pinMode(p, OUTPUT);
}
void loop() {
dispTick();
static uint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
dispPrint(analogRead(0));
}
}
Проект в онлайн-симуляторе можно открыть по ссылке.
Оптимизация #
Мы рассмотрели простой и понятный вывод, но его можно многократно оптимизировать. Например:
- Для вывода числа делить и получать остаток от деления при помощи функции div
- (Для AVR) Вместо записи состояния пинов вручную по одному "биту" подключить все сегменты на один порт МК (8-ми пиновый) и записывать весь байт сразу за одну операцию
Библиотеки #
Для более удобной работы можно использовать библиотеку GyverSegment - она умеет работать с голым дисплеем и настраивать яркость через более хитрую динамическую индикакцию. Можно установить/обновить из встроенного менеджера библиотек Arduino по названию GyverSegment. Краткая документация находится по ссылке выше, базовые примеры есть в самой библиотеке.
Пример последней программы с выводом значения с потенциометра с использованием GyverSegment:
Пример
#include <GyverSegment.h>
const uint8_t digs[] = {2, 3, 4, 5};
const uint8_t segs[] = {6, 7, 8, 9, 10, 11, 12, 13};
DispBare<4, 1, false> disp(digs, segs);
void setup() {
// демо - вывод разных данных
// // текст
// disp.setCursor(0);
// disp.print("gyvr");
// disp.update();
// disp.delay(2000);
// // целое число
// disp.setCursor(0);
// disp.print(1234);
// disp.update();
// disp.delay(2000);
// // float
// disp.setCursor(0);
// disp.print(3.14, 3); // точность 3 знака
// disp.update();
// disp.delay(2000);
}
void loop() {
disp.tick();
static uint32_t tmr;
if (millis() - tmr >= 50) {
tmr = millis();
disp.clear();
disp.home();
disp.print(analogRead(0));
disp.update();
}
}
Бегущая строка
#include <GyverSegment.h>
const uint8_t digs[] = {2, 3, 4, 5};
const uint8_t segs[] = {6, 7, 8, 9, 10, 11, 12, 13};
DispBare<4, 1, false> disp(digs, segs);
void setup() {
SegRunner run(&disp);
run.setSpeed(3);
run.setText("Hello. Alex");
run.start();
run.waitEnd();
}
void loop() {
disp.tick();
}
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
- Поддержать автора за работу над уроками