1) Cel lekcji
W lekcji 17 sterowałeś jedną cyfrą. Teraz przechodzisz na wyświetlanie 4 cyfr na wspólnej magistrali segmentów. To klasyczny mechanizm stosowany w zegarkach, licznikach, panelach urządzeń.
- zrozumiesz, na czym polega multipleks 4 cyfr,
- zrobisz bufor wyświetlania (4 znaki),
- zrobisz odświeżanie timerem (bez blokowania pętli),
- poznasz typowe problemy: migotanie, „ghosting”, jasność.
2) Na czym polega multipleks?
Masz 8 linii segmentów (a–g, dp) wspólnych dla wszystkich cyfr oraz 4 linie „wyboru cyfry” (CA1..CA4). W danej chwili świeci tylko jedna cyfra, ale przełączasz cyfry tak szybko, że oko widzi je jako świecące jednocześnie.
- wyłącz wszystkie cyfry
- ustaw segmenty dla cyfry 1
- włącz cyfrę 1 na krótko (np. 2 ms)
- powtórz dla cyfr 2,3,4
3) Częstotliwość odświeżania i migotanie
Jeśli każda cyfra świeci np. 2 ms, to pełny cykl 4 cyfr trwa 8 ms:
- częstotliwość odświeżania całego wyświetlacza ≈ 1 / 8 ms ≈ 125 Hz
- to zwykle wystarcza, aby nie widzieć migotania
dąż do ~100–300 Hz „pełnego odświeżania” (czyli każda cyfra co kilka ms). Zbyt wolno → migotanie. Zbyt szybko → mniejszy czas świecenia, spadek jasności.
4) Podłączenie: segmenty i cyfry (jak w Twoim schemacie)
Przyjmujemy mapowanie identyczne jak w lekcji 17:
Segmenty (PORTD)
| Pin | Segment | Bit w masce |
|---|---|---|
| PD0 | a | bit0 |
| PD1 | b | bit1 |
| PD2 | c | bit2 |
| PD3 | d | bit3 |
| PD4 | e | bit4 |
| PD5 | f | bit5 |
| PD6 | g | bit6 |
| PD7 | dp | bit7 |
Wybór cyfr (PORTC, wspólna anoda przez tranzystory PNP)
| Pin | Cyfra | Włączenie |
|---|---|---|
| PC0 | CA1 | LOW |
| PC1 | CA2 | LOW |
| PC2 | CA3 | LOW |
| PC3 | CA4 | LOW |
5) Najważniejszy problem: ghosting (przebłyski)
Ghosting to sytuacja, gdy podczas przełączania cyfr przez ułamek czasu świeci „zły” wzór na „złej” cyfrze. Najczęstsza przyczyna: włączasz cyfrę zanim ustawisz segmenty (albo zmieniasz segmenty, gdy cyfra jest jeszcze włączona).
1) wyłącz wszystkie cyfry → 2) ustaw segmenty → 3) włącz tylko jedną cyfrę.
6) Tablica cyfr 0–9 i bufor 4 znaków
Jak w lekcji 17, trzymamy tablicę w logice „1 = segment ma świecić” (a potem negujemy na wyjściu).
// bity: 0=a,1=b,2=c,3=d,4=e,5=f,6=g,7=dp
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
};
Teraz tworzymy bufor 4 pozycji. Każda pozycja to gotowa maska segmentów (już z dp, jeśli chcesz).
volatile uint8_t seg_buf[4] = { 0, 0, 0, 0 }; // "1 = świeci" (przed negacją)
7) Odświeżanie timerem: przerwanie co 1 ms (Timer0 CTC)
Najwygodniej robić multipleks w ISR (przerwaniu), bo:
- odświeżanie jest stabilne (nie zależy od tego, co robisz w main),
- main może liczyć, obsługiwać przyciski, logikę, itp.
Założenia
- przerwanie co 1 ms
- każde przerwanie przełącza na kolejną cyfrę
- pełne odświeżanie: 4 ms → ok. 250 Hz (bardzo dobrze)
• F_CPU=8MHz, preskaler 64 → OCR0A=124
• F_CPU=16MHz, preskaler 64 → OCR0A=249
8) Kompletny silnik multipleksu (ISR + bufor)
Poniższy kod:
- konfiguruje segmenty i linie cyfr,
- ustawia Timer0 CTC i przerwanie co 1 ms,
- w ISR: wyłącza cyfry → ustawia segmenty → włącza jedną cyfrę,
- udostępnia funkcje do wpisania liczby i ustawienia kropek.
#include <avr/io.h>
#include <avr/interrupt.h>
// ====== Segmenty: PD0..PD7 (a,b,c,d,e,f,g,dp) ======
#define SEG_DDR DDRD
#define SEG_PORT PORTD
// ====== Cyfry (wspólna anoda przez PNP): PC0..PC3 ======
#define DIG_DDR DDRC
#define DIG_PORT PORTC
#define DIG1 PC0
#define DIG2 PC1
#define DIG3 PC2
#define DIG4 PC3
// Tablica cyfr (1 = segment ma świecić)
static const uint8_t SEG_DIGIT[10] = {
0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110,
0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111
};
// Bufor 4 pozycji: maska segmentów (1 = świeci)
volatile uint8_t seg_buf[4] = {0,0,0,0};
volatile uint8_t cur_digit = 0;
// ---- pomocnicze: wyłącz wszystkie cyfry (PNP off = HIGH na bazie) ----
static inline void digits_all_off(void)
{
DIG_PORT |= (1<<DIG1) | (1<<DIG2) | (1<<DIG3) | (1<<DIG4);
}
// ---- włącz jedną cyfrę (PNP on = LOW na bazie) ----
static inline void digit_on(uint8_t idx)
{
// idx: 0..3
switch (idx)
{
case 0: DIG_PORT &= ~(1<<DIG1); break;
case 1: DIG_PORT &= ~(1<<DIG2); break;
case 2: DIG_PORT &= ~(1<<DIG3); break;
default: DIG_PORT &= ~(1<<DIG4); break;
}
}
// ---- ustaw segmenty: wspólna anoda => negacja (ON=0) ----
static inline void seg_set(uint8_t mask_on)
{
SEG_PORT = (uint8_t)~mask_on;
}
// ====== Timer0 CTC 1ms ======
static void timer0_ctc_1ms_init(void)
{
// CTC: WGM01=1
TCCR0A = (1<<WGM01);
// preskaler 64: CS01=1 i CS00=1
TCCR0B = (1<<CS01) | (1<<CS00);
// USTAW OCR0A zależnie od F_CPU:
// 8MHz -> 124
// 16MHz -> 249
OCR0A = 124;
// przerwanie Compare Match A
TIMSK0 = (1<<OCIE0A);
}
// ISR: przełączanie cyfr
ISR(TIMER0_COMPA_vect)
{
// 1) OFF wszystkie cyfry (anty-ghosting)
digits_all_off();
// 2) ustaw segmenty dla aktualnej cyfry
seg_set(seg_buf[cur_digit]);
// 3) włącz aktualną cyfrę
digit_on(cur_digit);
// 4) następna cyfra
cur_digit++;
if (cur_digit >= 4) cur_digit = 0;
}
// ====== Inicjalizacja całego modułu ======
static void seg7_mux_init(void)
{
SEG_DDR = 0xFF;
SEG_PORT = 0xFF; // wszystko OFF (wspólna anoda)
DIG_DDR |= (1<<DIG1) | (1<<DIG2) | (1<<DIG3) | (1<<DIG4);
digits_all_off();
timer0_ctc_1ms_init();
sei(); // globalne przerwania
}
// ====== API do bufora ======
static inline void seg7_set_digit(uint8_t pos, uint8_t digit, uint8_t dp_on)
{
if (pos > 3) return;
if (digit > 9) digit = 0;
uint8_t m = SEG_DIGIT[digit];
if (dp_on) m |= (1<<7);
seg_buf[pos] = m;
}
static void seg7_set_number_0000_9999(uint16_t v)
{
if (v > 9999) v = 9999;
seg7_set_digit(3, v % 10, 0); v /= 10;
seg7_set_digit(2, v % 10, 0); v /= 10;
seg7_set_digit(1, v % 10, 0); v /= 10;
seg7_set_digit(0, v % 10, 0);
}
9) Program testowy: licznik + kropka jako „separator”
Ten przykład wyświetla licznik rosnący co ~100 ms i zapala kropkę na drugiej pozycji. W pętli używamy prostego opóźnienia, ale ważne: multipleks działa w ISR niezależnie.
#include <util/delay.h>
int main(void)
{
seg7_mux_init();
uint16_t counter = 0;
while (1)
{
seg7_set_number_0000_9999(counter);
// kropka na pozycji 1 (druga cyfra), np. jako separator
// pos: 0 1 2 3
// tu robimy "dp" na pos=1:
// najpierw odczyt maski i dopisanie dp
uint8_t m = seg_buf[1];
seg_buf[1] = m | (1<<7);
counter++;
if (counter > 9999) counter = 0;
_delay_ms(100);
}
}
10) Typowe problemy i szybkie diagnozy
- odświeżanie za wolne (zbyt duży okres w ISR)
- zbyt ciężki ISR (za dużo operacji)
- niestabilne zasilanie / luźne przewody
- brak wyłączania cyfr przed zmianą segmentów
- złe kolejności: najpierw cyfra, potem segmenty
- zbyt długie przejścia (np. tranzystory + duże pojemności)
Każda cyfra świeci tylko 1/4 czasu, więc średnia jasność spada. To normalne. W praktyce dobiera się prądy/rezystory, albo zwiększa czas świecenia (z zachowaniem braku migotania).
11) Rozszerzenia, które robimy w kolejnych krokach
- blanking (krótka przerwa między cyframi, np. 50–200 µs) dla jeszcze mniejszego ghostingu,
- obsługa „pustych” cyfr (leading zeros off),
- wyświetlanie liczb ujemnych i znaków (minus),
- licznik / stoper oparty o timer (bez _delay_ms).
12) Zadania obowiązkowe
- Wyświetl stałą wartość 1234 (bufor 4 pozycji) i upewnij się, że nie migocze.
- Zrób funkcję seg7_set_colon_like() – kropka na wybranej pozycji (dp). Przetestuj 4 pozycje.
- Zrób licznik 0..9999 zwiększany co 250 ms (multipleks w ISR, logika w main).
13) Zadania dodatkowe (dla lepszych)
- Zrób „leading zeros off”: np. 42 ma wyświetlać 42 (pierwsze dwie cyfry puste).
- Zrób miganie całego wyświetlacza (np. co 500 ms ON/OFF) bez zatrzymywania multipleksu.
- Zrób efekt „przesuwania” liczby: np. 1234 przesuwa się w prawo/lewo co 200 ms.
14) Zadania PRO (projektowe)
- Usuń _delay_ms z main: zbuduj scheduler na ticku (lekcja 13) i zwiększaj licznik co X ms.
- Dodaj przycisk: klik start/stop licznika, długie przytrzymanie reset (logika z lekcji 11).
- Zrób „sterownik jasności” multipleksu: zmieniaj czas świecenia cyfry (duty w ISR), np. 1 ms vs 2 ms.
Od teraz większość projektów z wyświetlaczem to „bufor + ISR multipleks + logika w main”.