Arduino

Семисегментные индикаторы: одноразрядный и четырёхразрядный, динамическая индикация

Введение

Комплект включает два типа семисегментных индикаторов — одноразрядный (одна цифра) и четырёхразрядный (четыре цифры, например для отображения времени или счётчика). В статье разберём вывод цифр на оба типа, и динамическую индикацию для четырёхразрядного варианта — технику, концептуально аналогичную мультиплексированию светодиодной матрицы из предыдущей статьи.

Концепция

Каждая цифра семисегментного индикатора состоит из 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) — для счётчика или показания, потенциально выходящего за этот диапазон, нужна явная логика ограничения значения или переключения в иной режим отображения (например, «перелив», аналогичный одометру), а не просто визуально некорректное «обрезание» старших цифр без предупреждения.