Введение
Комплект включает два типа семисегментных индикаторов — одноразрядный (одна цифра) и четырёхразрядный (четыре цифры, например для отображения времени или счётчика). В статье разберём вывод цифр на оба типа, и динамическую индикацию для четырёхразрядного варианта — технику, концептуально аналогичную мультиплексированию светодиодной матрицы из предыдущей статьи.
Концепция
Каждая цифра семисегментного индикатора состоит из 7 сегментов (плюс опциональная точка), и для отображения конкретной цифры нужно включить определённую комбинацию сегментов — эта комбинация задаётся битовой маской, аналогично паттерну матрицы. Четырёхразрядный индикатор физически объединяет сегментные пины всех четырёх цифр (общие для всех разрядов), различая разряды через отдельный «общий» пин каждого разряда — поэтому одновременное отображение разных цифр в каждом разряде требует того же приёма мультиплексирования: быстрое переключение между разрядами с соответствующей сменой сегментной маски для каждого.
Пример кода
// firmware/src/main.cpp — четырёхразрядный индикатор, динамическая индикация счётчика
#define SEGMENT_PINS_START 2 // 7 сегментных пинов: 2-8 (без точки в этом примере)
#define DIGIT_PINS_START 9 // 4 пина разрядов: 9-12
// Маски сегментов для цифр 0-9 (порядок битов: A,B,C,D,E,F,G)
const byte digitPatterns[10] = {
0b1111110, 0b0110000, 0b1101101, 0b1111001, 0b0110011,
0b1011011, 0b1011111, 0b1110000, 0b1111111, 0b1111011
};
int displayValue = 0;
void setup()
{
for (int i = 0; i < 7; i++) pinMode(SEGMENT_PINS_START + i, OUTPUT);
for (int i = 0; i < 4; i++) pinMode(DIGIT_PINS_START + i, OUTPUT);
Serial.begin(9600);
delay(100);
Serial.println("READY");
}
void loop()
{
static unsigned long lastCounterUpdate = 0;
if (millis() - lastCounterUpdate >= 1000) {
displayValue = (displayValue + 1) % 10000;
lastCounterUpdate = millis();
}
refreshDisplay(displayValue);
}
void refreshDisplay(int value)
{
int digits[4] = {
(value / 1000) % 10, (value / 100) % 10,
(value / 10) % 10, value % 10
};
for (int digitIndex = 0; digitIndex < 4; digitIndex++) {
// Гасим все разряды перед выводом нового — иначе сегменты "просвечивают" между разрядами
for (int i = 0; i < 4; i++) digitalWrite(DIGIT_PINS_START + i, LOW);
setSegments(digitPatterns[digits[digitIndex]]);
digitalWrite(DIGIT_PINS_START + digitIndex, HIGH);
delayMicroseconds(800);
}
}
void setSegments(byte pattern)
{
for (int i = 0; i < 7; i++) {
digitalWrite(SEGMENT_PINS_START + i, (pattern >> (6 - i)) & 1);
}
}
Пояснения к коду
digitPatterns — таблица соответствия цифры (индекс массива) и битовой маски сегментов, которые нужно зажечь для её отображения — порядок битов в маске должен соответствовать реальному физическому порядку подключения сегментов A–G к пинам, что обычно указано в datasheet конкретного индикатора. refreshDisplay() извлекает отдельные цифры числа (тысячи, сотни, десятки, единицы) через целочисленное деление и взятие остатка, а затем для каждого разряда отдельно устанавливает нужную сегментную маску и активирует соответствующий «общий» пин разряда — это структурно идентично мультиплексированию матрицы из предыдущей статьи, просто применённое к четырём разрядам вместо восьми строк.
Гашение всех разрядов перед установкой новой сегментной маски (for цикл в начале каждой итерации) предотвращает проблему «теней» между соседними разрядами, аналогичную проблеме матрицы — без этого шага на короткое время одновременно могли бы оказаться активными два соседних разряда с одной и той же (предназначенной только для одного из них) сегментной маской.
Пояснения к коду (продолжение для одноразрядного индикатора)
Для одноразрядного индикатора задача значительно проще — поскольку разряд всего один, мультиплексирование не требуется вовсе: достаточно один раз установить нужную сегментную маску через setSegments() и держать единственный общий пин разряда постоянно включённым, обновляя сегментную маску только при изменении отображаемого значения, без необходимости в постоянном цикле переключения.
Подводные камни
- Общий катод (common cathode) против общего анода (common anode) — два разных типа семисегментных индикаторов, требующих инвертированной логики управления (для common anode сегмент зажигается через LOW, а не HIGH, и управление общим пином разряда также инвертировано) — попытка использовать код, написанный для одного типа, без изменений с индикатором другого типа приводит к тому, что вместо нужных цифр светятся все сегменты сразу, кроме нужных, или индикатор вовсе не светится — конкретный тип используемого в комплекте индикатора нужно явно проверить (часто маркируется в документации или определяется простым тестом).
- Слишком долгая задержка между переключением разрядов в динамической индикации (аналогично проблеме мерцания матрицы) делает переключение видимым как мерцание или «пробегающие» цифры вместо стабильно отображаемого числа — задержка должна быть достаточно короткой относительно полного цикла обновления всех 4 разрядов.
- Отсутствие гашения текущего разряда перед переходом к следующему создаёт «просвечивание» сегментов соседнего разряда — частая визуальная проблема, проявляющаяся как слабое, паразитное свечение лишних сегментов на соседних с активным разрядах, легко устраняемая явным гашением всех разрядов перед установкой каждой новой сегментной маски.
- Использование делителя
% 10и/ 10для извлечения цифр многоразрядного числа без проверки на отрицательные значения или превышение диапазона отображения (четырёхразрядный индикатор не может отобразить число больше 9999) — для счётчика или показания, потенциально выходящего за этот диапазон, нужна явная логика ограничения значения или переключения в иной режим отображения (например, «перелив», аналогичный одометру), а не просто визуально некорректное «обрезание» старших цифр без предупреждения.