🔢 Lekcja 17: Wyświetlacz 7-segmentowy — 1 cyfra

Segmenty, mapowanie, tablice znaków, podłączenie i testy. W tej lekcji uruchamiasz 7-seg na jednej cyfrze, przygotowując fundament pod multipleks 4 cyfr.

1) Cel lekcji

Wyświetlacz 7-segmentowy to zestaw diod LED w kształcie „kresek” (a–g) + kropka (dp). Sterujesz tym jak zwykłymi LED-ami, ale zamiast jednej diody masz ich 8.

  • poznasz nazwy segmentów i zasady świecenia,
  • zrobisz mapowanie pinów MCU → segmenty,
  • zbudujesz tablicę znaków (0–9 + kilka liter),
  • uruchomisz 1 cyfrę na Twoim schemacie.
Po tej lekcji: potrafisz wyświetlić cyfrę 0–9 na jednej pozycji i masz gotową tablicę segmentów do dalszych lekcji.

2) Typ wyświetlacza i logika sterowania

Na Twoim schemacie jest wyświetlacz 4-cyfrowy ze wspólną anodą (opis przy U3) oraz sterowanie wspólnymi anodami przez tranzystory PNP BC557 (Q1–Q4). :contentReference[oaicite:0]{index=0}

Wspólna anoda (CA)
  • anoda cyfry jest podciągana do +5V (u Ciebie przez PNP),
  • segment świeci, gdy jego katoda jest w stanie LOW,
  • czyli: segment ON = 0, segment OFF = 1.
W tej lekcji
  • używamy tylko jednej cyfry (np. DP1 / CA1),
  • pozostałe cyfry są wyłączone.
Ważne:
To jest logika „odwrócona”. Jeśli skopiujesz tablice z Internetu dla wspólnej katody, będą świecić odwrotnie.

3) Podłączenie na Twoim schemacie (mapowanie pinów)

Zgodnie ze schematem (strona 1), segmenty są podłączone do PORTD (PD0–PD7) przez przełącznik DIP (SW6) i rezystory (R12–R19), a wspólne anody cyfr (CA1–CA4) są przełączane tranzystorami PNP sterowanymi z PORTC (PC0–PC3) przez DIP (SW7) i rezystory bazowe. :contentReference[oaicite:1]{index=1}

Mapowanie: PORTD → segmenty

Pin ATmega328PSegmentUwagi (wspólna anoda)
PD0aON = 0
PD1bON = 0
PD2cON = 0
PD3dON = 0
PD4eON = 0
PD5fON = 0
PD6gON = 0
PD7dpkropka, ON = 0

Wybór cyfry: PORTC → CA1..CA4 (przez PNP)

Pin ATmega328PCyfraStan włączający
PC0CA1 (DP1 / 1. pozycja)LOW (włącza PNP)
PC1CA2LOW
PC2CA3LOW
PC3CA4LOW
W tej lekcji: włączamy tylko CA1 (PC0=0), a CA2–CA4 trzymamy wyłączone (PC1..PC3=1).

4) „Mapa segmentów” i format tablicy znaków

Przyjmujemy, że bity w bajcie odpowiadają segmentom w takiej kolejności (zgodnie z PORTD):

  • bit0=a (PD0)
  • bit1=b (PD1)
  • bit2=c (PD2)
  • bit3=d (PD3)
  • bit4=e (PD4)
  • bit5=f (PD5)
  • bit6=g (PD6)
  • bit7=dp (PD7)
Wspólna anoda:
segment ON = 0, więc wygodnie jest trzymać tablicę w logice „1 = segment świeci” i na końcu robić negację (~) przed wysłaniem na PORTD.

5) Kod: inicjalizacja wyświetlacza (1 cyfra)

Ustawiamy PORTD jako wyjście (segmenty) oraz PC0..PC3 jako wyjścia (wybór cyfry).

#include <avr/io.h>
#include <util/delay.h>

// Segmenty: PD0..PD7 (a,b,c,d,e,f,g,dp)
#define SEG_DDR   DDRD
#define SEG_PORT  PORTD

// Wybór cyfry (anody przez PNP): PC0..PC3
#define DIG_DDR   DDRC
#define DIG_PORT  PORTC

#define DIG1      PC0
#define DIG2      PC1
#define DIG3      PC2
#define DIG4      PC3

static void seg7_init_1digit(void)
{
    // segmenty jako wyjścia
    SEG_DDR = 0xFF;

    // na wspólnej anodzie: stan wysoki = segment OFF
    SEG_PORT = 0xFF;

    // wybór cyfr jako wyjścia
    DIG_DDR |= (1<<DIG1) | (1<<DIG2) | (1<<DIG3) | (1<<DIG4);

    // wyłącz wszystkie cyfry: PNP wyłączony gdy baza "wysoko"
    DIG_PORT |= (1<<DIG1) | (1<<DIG2) | (1<<DIG3) | (1<<DIG4);

    // włącz tylko cyfrę 1 (CA1): baza nisko = PNP ON
    DIG_PORT &= ~(1<<DIG1);
}
Jeśli nic nie świeci:
sprawdź, czy na schemacie masz ustawione DIPy SW6/SW7 tak, aby połączenia były aktywne (segmenty i wybór cyfry). :contentReference[oaicite:2]{index=2}

6) Tablica cyfr 0–9 (logika „1 = segment świeci”)

Definiujemy maski segmentów dla cyfr w klasycznej notacji a–g:

// bity: 0=a,1=b,2=c,3=d,4=e,5=f,6=g,7=dp
// tutaj: 1 oznacza "segment ma świecić"
static const uint8_t SEG_DIGIT[10] = {
    // 0: a b c d e f
    0b00111111,
    // 1: b c
    0b00000110,
    // 2: a b d e g
    0b01011011,
    // 3: a b c d g
    0b01001111,
    // 4: b c f g
    0b01100110,
    // 5: a c d f g
    0b01101101,
    // 6: a c d e f g
    0b01111101,
    // 7: a b c
    0b00000111,
    // 8: a b c d e f g
    0b01111111,
    // 9: a b c d f g
    0b01101111
};

7) Funkcja wyświetlania cyfry (wspólna anoda = negacja)

static inline void seg7_show_raw(uint8_t seg_mask_on)
{
    // wspólna anoda: ON = 0, OFF = 1
    SEG_PORT = (uint8_t)~seg_mask_on;
}

static void seg7_show_digit(uint8_t digit, uint8_t dp_on)
{
    if (digit > 9) digit = 0;

    uint8_t m = SEG_DIGIT[digit];

    if (dp_on) m |= (1<<7);  // dp w tej konwencji: 1 = świeci
    seg7_show_raw(m);
}

8) Test 1: „chodzący segment” (a→b→…→dp)

To najlepszy test poprawności mapowania PD0..PD7. Jeśli świeci inny segment niż powinien, masz zamienione przewody/DIP.

static void test_walk_segments(void)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        seg7_show_raw((1<<i)); // świeci tylko jeden segment
        _delay_ms(400);
    }
    seg7_show_raw(0);
}

9) Test 2: licznik 0–9

static void test_count_0_9(void)
{
    for (uint8_t d = 0; d <= 9; d++)
    {
        seg7_show_digit(d, 0);
        _delay_ms(600);
    }
}

10) Pełny program lekcji (gotowy do wgrania)

#include <avr/io.h>
#include <util/delay.h>

#define SEG_DDR   DDRD
#define SEG_PORT  PORTD

#define DIG_DDR   DDRC
#define DIG_PORT  PORTC
#define DIG1      PC0
#define DIG2      PC1
#define DIG3      PC2
#define DIG4      PC3

static const uint8_t SEG_DIGIT[10] = {
    0b00111111, // 0
    0b00000110, // 1
    0b01011011, // 2
    0b01001111, // 3
    0b01100110, // 4
    0b01101101, // 5
    0b01111101, // 6
    0b00000111, // 7
    0b01111111, // 8
    0b01101111  // 9
};

static void seg7_init_1digit(void)
{
    SEG_DDR = 0xFF;
    SEG_PORT = 0xFF;

    DIG_DDR |= (1<<DIG1) | (1<<DIG2) | (1<<DIG3) | (1<<DIG4);
    DIG_PORT |= (1<<DIG1) | (1<<DIG2) | (1<<DIG3) | (1<<DIG4);

    // tylko pierwsza cyfra aktywna
    DIG_PORT &= ~(1<<DIG1);
}

static inline void seg7_show_raw(uint8_t seg_mask_on)
{
    SEG_PORT = (uint8_t)~seg_mask_on; // wspólna anoda
}

static void seg7_show_digit(uint8_t digit, uint8_t dp_on)
{
    if (digit > 9) digit = 0;
    uint8_t m = SEG_DIGIT[digit];
    if (dp_on) m |= (1<<7);
    seg7_show_raw(m);
}

static void test_walk_segments(void)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        seg7_show_raw((1<<i));
        _delay_ms(350);
    }
    seg7_show_raw(0);
}

static void test_count_0_9(void)
{
    for (uint8_t d = 0; d <= 9; d++)
    {
        seg7_show_digit(d, 0);
        _delay_ms(600);
    }
}

int main(void)
{
    seg7_init_1digit();

    while (1)
    {
        test_walk_segments();
        _delay_ms(400);

        test_count_0_9();
        _delay_ms(800);

        // przykład: 3. z kropką
        seg7_show_digit(3, 1);
        _delay_ms(900);
    }
}
Checklista uruchomienia:
  • segmenty PD0..PD7 podłączone i aktywne przez SW6
  • wybór cyfry (PC0) aktywny przez SW7
  • wspólna anoda: segment świeci przy stanie LOW
:contentReference[oaicite:3]{index=3}

11) Zadania obowiązkowe

  1. Zrób funkcję seg7_show_hex(n) dla 0–15 (0..9 + A,b,C,d,E,F).
  2. Zrób test: licz od 9 do 0 (w dół) z kropką zapalaną co drugi krok.
  3. Zrób „animację”: wyświetlaj kolejno segmenty tworząc kształt koła (a→b→c→d→e→f→a…).

12) Zadania dodatkowe (dla lepszych)

  1. Zrób funkcję seg7_blank() (wyłącz wszystkie segmenty) i użyj jej między wyświetleniami (np. 20 ms).
  2. Zrób 5 znaków statusu: H, L, -, _, P (własne maski segmentów).
  3. Połącz z przyciskiem: klik zmienia aktualną cyfrę 0..9.

13) Zadania PRO (przygotowanie pod lekcję o multipleksie)

  1. Zamiast _delay_ms użyj tick 1 ms z lekcji 13 do odmierzania czasu testów.
  2. Zrób funkcję seg7_enable_digit(n), która steruje PC0..PC3 (na razie włączaj tylko jedną na raz).
  3. Wprowadź bufor: uint8_t buf[4] z maskami segmentów (w kolejnej lekcji to wykorzystasz do 4 cyfr).
Zapowiedź:
Kolejna lekcja to multipleks 4 cyfr: szybkie przełączanie CA1..CA4 i odświeżanie bufora.