View Categories

7-сегментный дисплей

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

В наборе 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();
}


Полезные страницы #

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх