Zgodnie z zasadą, że "zegarków w domu nigdy za wiele", projekt przeszedł gruntowną modernizację. Zastąpienie dwóch małych,
ale jakże wielkich matryc 8x8 zlepionych z taśmy nowoczesnym panelem 8x32 WS2812B otworzyło zupełnie nowe możliwości ekspresji i funkcjonalności.
Co zmienia rozmiar 8x32?
Szlachetna Typografia i Czytelność:
Dzięki szerokości 32 pikseli nie musimy już wybierać między godziną a sekundami. Możemy stosować autorskie czcionki (np. 4x7 lub 3x5),
które zachowują idealne proporcje i światło między znakami. Obraz jest spójny, a cyfry "oddychają", co znacząco poprawia komfort odczytu
nawet z dużej odległości.
Dynamika i Animacja (PIO Power):
Wykorzystanie maszyny stanowej PIO w Raspberry Pi Pico pozwala na odświeżanie matrycy z częstotliwością, która czyni animacje płynnymi (tzw. butter smooth). Pionowe przesuwanie cyfr podczas zmiany czasu sprawia, że zegar ożywa, a każda upływająca minuta jest subtelnym spektaklem.
Wielofunkcyjność Interfejsu (6-Button UI): Rozbudowa sterowania do 6 przycisków pozwoliła na stworzenie intuicyjnego zarządzania funkcjami:
Zarządzania czasem (ustawianie godziny/minuty)
Personalizacja estetyczna (13 schematów kolorystycznych, w tym tryb Rainbow)
Adaptacji do oświetlenia (8 poziomów jasności)
Tryb Nocny - subtelne czerwone światło, które nie razi w oczy na najniższym poziomie jasności
Wyboru Trybu Wyświetlania:
- pełny format HH:MM:SS,
- eleganckie, centrowane HH:MM,
- nowoczesna hybryda HH:MM z małymi sekundami.
Sercem pomiaru czasu pozostaje układ RTC DS1307, który w połączeniu z logiką zapobiegającą migotaniu,
czyni z projektu niezawodne urządzenie codziennego użytku.
Podsumowanie Techniczne
Ten projekt to nie tylko prosty miernik czasu. To połączenie precyzyjnego kodu z estetyką pixel-art.
Dzięki elastycznemu mapowaniu matrycy (logika zygzaka) i zaawansowanemu skalowaniu jasności,
zegar idealnie odnajdzie się zarówno w warsztacie elektronika, jak i w nowoczesnym salonie.
Choć zamysł w większości pozostał taki sam jak w poprzedniej wersji, tym razem jest bardziej spójny. Dzięki taniej i wygodnej matrycy 8x32 o większej gęstości diod, efekt jest bardziej elegancki i stylowy. Już teraz, choć jeszcze nie wydrukowałem ramek i dyfuzera,
możesz podziwiać nowe możliwości tego projektu.

Po uporządkowaniu kod prezentuje się następująco:
- /*******************************************************************************
- * Projekt: Kolejny Zegar LED DS1309 8x32 WS2812B
- * Autor : SunRiver / Lothar TeaM
- * Strona : https://forum.lothar-team.pl
- *
- * Plik : main.c
- * Specyfikacja:
- * - Mikrokontroler: RP2040 (Raspberry Pi Pico)
- * - Wyświetlacz: Matryca LED 8x32 WS2812B
- * - RTC: DS1307 przez I2C
- * - Sterowanie: 6 przycisków (godziny, minuty, kolor, jasność,
- * tryb wyświetlania , tryb nocny )
- ******************************************************************************/
- // -- Pliki nagłówkowe i bliblioteki
- #include <stdio.h>
- #include "pico/stdlib.h"
- #include "hardware/pio.h"
- #include "hardware/clocks.h"
- #include "ws2812.pio.h"
- #include "hardware/i2c.h"
- #include "hardware/gpio.h"
- // ---- Parametry zdefiniowane
- // -- Matryca
- #define LED_PIN 2
- #define LED_COUNT 256
- #define MATRIX_WIDTH 32
- #define MATRIX_HEIGHT 8
- // -- Przyciski
- #define KEY_HOURS 14
- #define KEY_MINUTES 15
- #define KEY_COLOR 16
- #define KEY_BRIGHTNESS 17
- #define KEY_MODE 18
- #define KEY_NIGHT 19
- // -- RTC DS1307
- #define I2C_SDA_PIN 4
- #define I2C_SCL_PIN 5
- #define DS1307_ADDR 0x68
- // -- Pozostałe parametry programu
- #define ANIM_STEPS 7
- #define ANIM_INTERVAL_MS 40
- #define RGB_COLOR(r, g, b) (((uint32_t)(g) << 16) | ((uint32_t)(r) << 8) | (b))
- #define RAINBOW_SCHEME_INDEX 12
- // -- KONWERSJE
- #define BCD_TO_BIN(val) ( ( (val) / 16 * 10 ) + ( (val) % 16 ) )
- #define BIN_TO_BCD(val) ( ( (val) / 10 * 16 ) + ( (val) % 10 ) )
- uint32_t led_buffer[LED_COUNT];
- // Zmienne pomocnicze trybu nocnego
- bool is_night_mode = false;
- uint8_t saved_brightness = 4;
- uint8_t saved_color = 0;
- // Struktury
- typedef struct {
- uint8_t second, minute, hour;
- } rtc_time_t;
- rtc_time_t current_rtc_time;
- typedef struct {
- uint32_t digit_color, colon_color;
- } color_scheme_t;
- const color_scheme_t color_schemes[] = {
- { RGB_COLOR(0, 40, 0), RGB_COLOR(40, 0, 0) }, // 0
- { RGB_COLOR(0, 0, 40), RGB_COLOR(0, 40, 0) }, // 1
- { RGB_COLOR(40, 0, 40), RGB_COLOR(0, 40, 40) }, // 2
- { RGB_COLOR(40, 40, 0), RGB_COLOR(40, 0, 0) }, // 3
- { RGB_COLOR(40, 40, 40), RGB_COLOR(40, 0, 0) }, // 4
- { RGB_COLOR(40, 10, 0), RGB_COLOR(0, 40, 0) }, // 5
- { RGB_COLOR(40, 0, 15), RGB_COLOR(0, 0, 40) }, // 6
- { RGB_COLOR(0, 40, 40), RGB_COLOR(40, 40, 40) },// 7
- { RGB_COLOR(0, 10, 40), RGB_COLOR(40, 40, 0) }, // 8
- { RGB_COLOR(15, 40, 0), RGB_COLOR(40, 40, 40) },// 9
- { RGB_COLOR(40, 20, 0), RGB_COLOR(0, 0, 40) }, // 10
- { RGB_COLOR(40, 0, 0), RGB_COLOR(0, 40, 0) }, // 11
- { RGB_COLOR(40, 40, 40), RGB_COLOR(40, 40, 40) } // 12
- };
- uint8_t current_color_index = 0;
- uint8_t current_brightness_index = 4;
- uint8_t rainbow_hue = 0;
- const uint8_t BRIGHTNESS_LEVELS[] = { 5, 10, 20, 30, 40, 80, 150, 255 };
- // Czcionka 4x7 dla matrycy 8x32
- const uint8_t font_4x7[10][7] = {
- { 0b0110, 0b1001, 0b1001, 0b1001, 0b1001, 0b1001, 0b0110 }, // 0
- { 0b0100, 0b1100, 0b0100, 0b0100, 0b0100, 0b0100, 0b1110 }, // 1
- { 0b1110, 0b0001, 0b0001, 0b0110, 0b1000, 0b1000, 0b1111 }, // 2
- { 0b1110, 0b0001, 0b0001, 0b0110, 0b0001, 0b0001, 0b1110 }, // 3
- { 0b1001, 0b1001, 0b1001, 0b1111, 0b0001, 0b0001, 0b0001 }, // 4
- { 0b1111, 0b1000, 0b1000, 0b1110, 0b0001, 0b0001, 0b1110 }, // 5
- { 0b0110, 0b1000, 0b1000, 0b1110, 0b1001, 0b1001, 0b0110 }, // 6
- { 0b1111, 0b0001, 0b0001, 0b0010, 0b0100, 0b0100, 0b0100 }, // 7
- { 0b0110, 0b1001, 0b1001, 0b0110, 0b1001, 0b1001, 0b0110 }, // 8
- { 0b0110, 0b1001, 0b1001, 0b0111, 0b0001, 0b0001, 0b0110 } // 9
- };
- // Mała czcionka 3x5 dla sekund
- const uint8_t font_3x5[10][5] = {
- { 0b111, 0b101, 0b101, 0b101, 0b111 }, // 0
- { 0b010, 0b110, 0b010, 0b010, 0b111 }, // 1
- { 0b111, 0b001, 0b111, 0b100, 0b111 }, // 2
- { 0b111, 0b001, 0b011, 0b001, 0b111 }, // 3
- { 0b101, 0b101, 0b111, 0b001, 0b001 }, // 4
- { 0b111, 0b100, 0b111, 0b001, 0b111 }, // 5
- { 0b111, 0b100, 0b111, 0b101, 0b111 }, // 6
- { 0b111, 0b001, 0b001, 0b001, 0b001 }, // 7
- { 0b111, 0b101, 0b111, 0b101, 0b111 }, // 8
- { 0b111, 0b101, 0b111, 0b001, 0b111 } // 9
- };
- uint8_t current_display_mode = 0; // 0: HH:MM:SS, 1: Centered HH:MM, 2: HH:MM ss
- // Mapowanie matrycy 8x32 - zigzak - kolumnami parzyste w dół, nieparzyste w góre
- uint16_t get_led_index(uint8_t x, uint8_t y) {
- if (x >= MATRIX_WIDTH || y >= MATRIX_HEIGHT) return 0;
- if (x % 2 == 0) {
- return (x * 8) + y;
- }
- else {
- return (x * 8) + (7 - y);
- }
- }
- // Rysowanie pikseli
- void draw_pixel(uint8_t x, uint8_t y, uint32_t color) {
- if (x < MATRIX_WIDTH && y < MATRIX_HEIGHT)
- led_buffer[get_led_index(x, y)] = color;
- }
- void draw_digit_offset(uint8_t digit, int8_t x, int8_t y, int8_t y_offset, uint32_t color) {
- for (uint8_t row = 0; row < 7; row++)
- for (uint8_t col = 0; col < 4; col++)
- if (font_4x7[digit][row] & (1 << (3 - col)))
- draw_pixel(x + col, y + row + y_offset, color);
- }
- void draw_small_digit(uint8_t digit, int8_t x, int8_t y, uint32_t color) {
- for (uint8_t row = 0; row < 5; row++)
- for (uint8_t col = 0; col < 3; col++)
- if (font_3x5[digit][row] & (1 << (2 - col)))
- draw_pixel(x + col, y + row, color);
- }
- // --- FUNKCJE POMOCNICZE ---
- uint32_t hsv_to_rgb(uint8_t h) {
- uint8_t r = 0, g = 0, b = 0;
- const uint8_t max_val = 40;
- uint8_t section = h / 43;
- uint8_t remainder = (h % 43) * 6;
- uint8_t q = (max_val * (255 - remainder)) >> 8;
- uint8_t t = (max_val * remainder) >> 8;
- switch (section) {
- case 0: r = max_val; g = t; b = 0; break;
- case 1: r = q; g = max_val; b = 0; break;
- case 2: r = 0; g = max_val; b = t; break;
- case 3: r = 0; g = q; b = max_val; break;
- case 4: r = t; g = 0; b = max_val; break;
- default: r = max_val; g = 0; b = q; break;
- }
- return RGB_COLOR(r, g, b);
- }
- uint32_t scale_color_by_brightness(uint32_t color) {
- uint8_t target_max = BRIGHTNESS_LEVELS[current_brightness_index];
- uint8_t g = (color >> 16) & 0xFF, r = (color >> 8) & 0xFF, b = color & 0xFF;
- return RGB_COLOR((uint8_t)((r * target_max) / 40), (uint8_t)((g * target_max) / 40), (uint8_t)((b * target_max) / 40));
- }
- // --- RTC DS1307 ---
- void ds1307_read_time(void) {
- uint8_t time_buffer[7];
- i2c_write_blocking(i2c0, DS1307_ADDR, (const uint8_t[]){ 0x00 }, 1, true);
- i2c_read_blocking(i2c0, DS1307_ADDR, time_buffer, 7, false);
- current_rtc_time.second = BCD_TO_BIN(time_buffer[0] & 0x7F);
- current_rtc_time.minute = BCD_TO_BIN(time_buffer[1] & 0x7F);
- current_rtc_time.hour = BCD_TO_BIN(time_buffer[2] & 0x3F);
- }
- void ds1307_set_time(uint8_t h, uint8_t m, uint8_t s) {
- uint8_t data[4] = { 0x00, BIN_TO_BCD(s) & 0x7F, BIN_TO_BCD(m), BIN_TO_BCD(h) & 0x3F };
- i2c_write_blocking(i2c0, DS1307_ADDR, data, 4, false);
- }
- // --- ANIMACJA ---
- typedef struct { uint8_t current, next, step; bool animating; } digit_anim_t;
- digit_anim_t digits[6];
- void animation_tick(void) {
- for (int i = 0; i < 6; i++)
- if (digits[i].animating) {
- digits[i].step++;
- if (digits[i].step >= ANIM_STEPS) { digits[i].current = digits[i].next; digits[i].animating = false; }
- }
- }
- void start_digit_animation(int index, uint8_t value) {
- if (digits[index].current == value) return;
- digits[index].next = value; digits[index].step = 0; digits[index].animating = true;
- }
- void draw_clock_animated(void) {
- for (int i = 0; i < LED_COUNT; i++) led_buffer[i] = 0;
- uint32_t color = scale_color_by_brightness(current_color_index == RAINBOW_SCHEME_INDEX ? hsv_to_rgb(rainbow_hue) : color_schemes[current_color_index].digit_color);
- uint32_t colon_color = scale_color_by_brightness(current_color_index == RAINBOW_SCHEME_INDEX ? hsv_to_rgb(rainbow_hue + 128) : color_schemes[current_color_index].colon_color);
- if (current_display_mode == 0) {
- // --- TRYB 0: HH:MM:SS (Standardowy - Twój obecny) ---
- const uint8_t x_pos[6] = { 0, 5, 11, 16, 22, 27 };
- for (int i = 0; i < 6; i++) {
- if (digits[i].animating) {
- draw_digit_offset(digits[i].current, x_pos[i], 0, digits[i].step, color);
- draw_digit_offset(digits[i].next, x_pos[i], 0, digits[i].step - ANIM_STEPS, color);
- }
- else {
- draw_digit_offset(digits[i].current, x_pos[i], 0, 0, color);
- }
- }
- if (current_rtc_time.second % 2 == 0) {
- draw_pixel(10, 2, colon_color); draw_pixel(10, 5, colon_color);
- draw_pixel(21, 2, colon_color); draw_pixel(21, 5, colon_color);
- }
- }
- else if (current_display_mode == 1) {
- // --- TRYB 1: HH:MM Centrowany (Brak sekund) ---
- const uint8_t x_pos[4] = { 6, 11, 18, 23 }; // Wyśrodkowane na 32 px
- for (int i = 0; i < 4; i++) {
- if (digits[i].animating) {
- draw_digit_offset(digits[i].current, x_pos[i], 0, digits[i].step, color);
- draw_digit_offset(digits[i].next, x_pos[i], 0, digits[i].step - ANIM_STEPS, color);
- }
- else {
- draw_digit_offset(digits[i].current, x_pos[i], 0, 0, color);
- }
- }
- if (current_rtc_time.second % 2 == 0) {
- draw_pixel(16, 2, colon_color); draw_pixel(16, 5, colon_color);
- }
- }
- else if (current_display_mode == 2) {
- // --- TRYB 2: HH:MM i małe ss ---
- const uint8_t x_pos[4] = { 2, 7, 13, 18 }; // Godziny i minuty po lewej
- for (int i = 0; i < 4; i++) {
- if (digits[i].animating) {
- draw_digit_offset(digits[i].current, x_pos[i], 0, digits[i].step, color);
- draw_digit_offset(digits[i].next, x_pos[i], 0, digits[i].step - ANIM_STEPS, color);
- }
- else {
- draw_digit_offset(digits[i].current, x_pos[i], 0, 0, color);
- }
- }
- // Małe sekundy (bez animacji dla prostoty, na dole po prawej)
- draw_small_digit(current_rtc_time.second / 10, 25, 2, color);
- draw_small_digit(current_rtc_time.second % 10, 29, 2, color);
- if (current_rtc_time.second % 2 == 0) {
- draw_pixel(12, 2, colon_color); draw_pixel(12, 5, colon_color);
- }
- }
- }
- // --- PIO dla WS2812B ----------------------------
- void ws2812_show(PIO pio, uint sm) {
- for (int i = 0; i < LED_COUNT; i++) pio_sm_put_blocking(pio, sm, led_buffer[i] << 8u);
- }
- static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) {
- pio_sm_config c = ws2812_program_get_default_config(offset);
- sm_config_set_sideset_pins(&c, pin);
- sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
- sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
- int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
- float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
- sm_config_set_clkdiv(&c, div);
- pio_gpio_init(pio, pin);
- pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
- pio_sm_init(pio, sm, offset, &c);
- pio_sm_set_enabled(pio, sm, true);
- }
- // --- GŁÓWNA FUNKCJA PROGRAMU ----
- int main() {
- // --- INICJALIZACJA ---
- stdio_init_all();
- PIO pio = pio0; uint sm = 0;
- uint offset = pio_add_program(pio, &ws2812_program);
- ws2812_program_init(pio, sm, offset, LED_PIN, 800000, false);
- // --- INICJALIZACJA I2C DLA RTC DS1307 ----
- i2c_init(i2c0, 400 * 1000);
- gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C);
- gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C);
- gpio_pull_up(I2C_SDA_PIN); gpio_pull_up(I2C_SCL_PIN);
- // --- INICJALIZACJA PRZYCISKÓW ----
- uint8_t keys[] = {
- KEY_HOURS,
- KEY_MINUTES,
- KEY_COLOR,
- KEY_BRIGHTNESS,
- KEY_MODE,
- KEY_NIGHT
- };
- const uint8_t num_keys = sizeof(keys) / sizeof(keys[0]);
- for (int i = 0; i < num_keys; i++) {
- gpio_init(keys[i]);
- gpio_set_dir(keys[i], GPIO_IN);
- gpio_pull_up(keys[i]);
- }
- // --- POCZĄTKOWY ODCZYT CZASU Z RTC ----
- ds1307_read_time();
- uint8_t h = current_rtc_time.hour, m = current_rtc_time.minute, s = current_rtc_time.second;
- uint8_t v[6] = { h / 10, h % 10, m / 10, m % 10, s / 10, s % 10 };
- for (int i = 0; i < 6; i++) { digits[i].current = v[i]; digits[i].animating = false; }
- // --- ZMIENNE POMOCNICZE ----
- uint32_t last_rtc = 0, last_btn = 0;
- const uint16_t TICK_MS = 20;
- uint8_t anim_loop_counter = 0;
- // --- GŁÓWNA PĘTLA PROGRAMU ----
- while (true) {
- sleep_ms(TICK_MS);
- anim_loop_counter++;
- rainbow_hue++;
- uint32_t now_ms = time_us_64() / 1000;
- // --- ODCZYT CZASU Z RTC CO 200ms ----
- if (now_ms - last_rtc >= 200) {
- last_rtc = now_ms;
- ds1307_read_time();
- uint8_t target[6] = {
- current_rtc_time.hour / 10,
- current_rtc_time.hour % 10,
- current_rtc_time.minute / 10,
- current_rtc_time.minute % 10,
- current_rtc_time.second / 10,
- current_rtc_time.second % 10
- };
- bool any_anim = false;
- for (int i = 0; i < 6; i++) if (digits[i].animating) any_anim = true;
- if (!any_anim) {
- for (int i = 0; i < 6; i++) start_digit_animation(i, target[i]);
- }
- }
- // --- OBSŁUGA PRZYCISKÓW ----
- if (now_ms - last_btn > 200) {
- if (!gpio_get(KEY_HOURS)) {
- uint8_t nh = (current_rtc_time.hour + 1) % 24;
- ds1307_set_time(nh, current_rtc_time.minute, 0);
- start_digit_animation(0, nh / 10); start_digit_animation(1, nh % 10);
- last_btn = now_ms; while (!gpio_get(KEY_HOURS)) sleep_ms(10);
- }
- else if (!gpio_get(KEY_MINUTES)) {
- uint8_t nm = (current_rtc_time.minute + 1) % 60;
- ds1307_set_time(current_rtc_time.hour, nm, 0);
- start_digit_animation(2, nm / 10); start_digit_animation(3, nm % 10);
- last_btn = now_ms; while (!gpio_get(KEY_MINUTES)) sleep_ms(10);
- }
- else if (!gpio_get(KEY_COLOR)) {
- current_color_index = (current_color_index + 1) % 13;
- last_btn = now_ms; while (!gpio_get(KEY_COLOR)) sleep_ms(10);
- }
- else if (!gpio_get(KEY_BRIGHTNESS)) {
- current_brightness_index = (current_brightness_index + 1) % 8;
- is_night_mode = false; // Każda ręczna zmiana wyłącza tryb nocny
- last_btn = now_ms; while (!gpio_get(KEY_BRIGHTNESS)) sleep_ms(10);
- }
- else if (!gpio_get(KEY_MODE)) {
- current_display_mode = (current_display_mode + 1) % 3; // Mamy 3 tryby
- last_btn = now_ms;
- while (!gpio_get(KEY_MODE)) sleep_ms(10); // Debouncing
- }
- else if (!gpio_get(KEY_NIGHT)) {
- if (!is_night_mode) {
- // WŁĄCZAMY TRYB NOCNY
- saved_brightness = current_brightness_index;
- saved_color = current_color_index;
- current_brightness_index = 0; // Najniższy poziom z BRIGHTNESS_LEVELS (5)
- current_color_index = 11; // Czerwony (według tabeli color_schemes)
- is_night_mode = true;
- } else {
- // WYŁĄCZAMY TRYB NOCNY - przywracamy stare ustawienia
- current_brightness_index = saved_brightness;
- current_color_index = saved_color;
- is_night_mode = false;
- }
- last_btn = now_ms;
- while (!gpio_get(KEY_NIGHT)) sleep_ms(10); // Debouncing
- }
- }
- // --- RYSOWANIE I ANIMACJA ---
- if (anim_loop_counter >= 2) { anim_loop_counter = 0; animation_tick(); }
- draw_clock_animated();
- ws2812_show(pio, sm);
- }
- }



