1) Cel lekcji
Dioda RGB to trzy diody w jednej obudowie: Red, Green, Blue. Sterując jasnością każdego kanału osobno (najlepiej PWM), możesz uzyskać praktycznie dowolny kolor.
- rozróżnisz RGB wspólna anoda vs wspólna katoda,
- uruchomisz PWM na 3 kanałach,
- zrobisz funkcję rgb_set(r,g,b),
- zrobisz paletę kolorów i sceny świetlne,
- połączysz RGB z przyciskiem (tryby).
2) Rodzaje diody RGB: wspólna katoda vs wspólna anoda
- wspólna nóżka do GND
- kanały R/G/B sterujesz stanem HIGH/PWM (1 = świeci)
- logika „normalna” (non-inverting)
- wspólna nóżka do VCC
- kanały R/G/B świecą przy stanie LOW (0 = świeci)
- logika „odwrócona” (inverting)
Jeśli masz wspólną anodę: w kodzie odwrócisz wypełnienie (255 - wartość).
3) Podłączenie RGB do pinów PWM
Potrzebujesz 3 wyjść PWM. Najprostsza konfiguracja na ATmega328P:
- PD6 = OC0A → kanał R
- PD5 = OC0B → kanał G
- PB1 = OC1A → kanał B
Każdy kanał musi mieć osobny rezystor (np. 220–330 Ω). Wspólna katoda do GND.
Nie podłączaj RGB bez rezystorów — spalisz diody lub przeciążysz GPIO.
4) PWM na 3 kanałach — koncepcja
Timer0 ma 2 kanały PWM (OC0A, OC0B), Timer1 ma kanał OC1A. Ustawimy:
- Timer0: Fast PWM dla R i G
- Timer1: Fast PWM dla B (TOP=0x00FF, czyli 8-bit) — żeby zakres był spójny 0..255
Żeby każdy kanał miał identyczną skalę 0..255 i proste mieszanie kolorów.
5) Kod: inicjalizacja PWM dla R (PD6) i G (PD5) — Timer0
#include <avr/io.h>
#define R_DDR DDRD
#define R_PIN PD6 // OC0A
#define G_DDR DDRD
#define G_PIN PD5 // OC0B
static void pwm_timer0_init(void)
{
// PD6 i PD5 jako wyjścia
R_DDR |= (1 << R_PIN);
G_DDR |= (1 << G_PIN);
// Fast PWM (TOP=0xFF): WGM00=1, WGM01=1
// Non-inverting: COM0A1=1, COM0B1=1
TCCR0A = (1 << WGM01) | (1 << WGM00)
| (1 << COM0A1) | (1 << COM0B1);
// Preskaler /64 (stabilnie dla LED)
TCCR0B = (1 << CS01) | (1 << CS00);
// Start: OFF
OCR0A = 0; // R
OCR0B = 0; // G
}
6) Kod: PWM dla B (PB1) — Timer1 w 8-bit Fast PWM
Ustawiamy Fast PWM 8-bit na Timer1 (WGM10=1, WGM12=1).
#define B_DDR DDRB
#define B_PIN PB1 // OC1A
static void pwm_timer1_8bit_init(void)
{
B_DDR |= (1 << B_PIN);
// Fast PWM 8-bit: WGM10=1, WGM11=0, WGM12=1, WGM13=0
// Non-inverting OC1A: COM1A1=1
TCCR1A = (1 << WGM10) | (1 << COM1A1);
TCCR1B = (1 << WGM12);
// Preskaler /64
TCCR1B |= (1 << CS11) | (1 << CS10);
OCR1A = 0; // B
}
Timer0 i Timer1 mogą mieć różne częstotliwości PWM. Dla LED to nie problem, byle nie było migotania.
7) Funkcja rgb_set(r,g,b)
Robimy interfejs jak w bibliotekach: podajesz 0..255 na każdy kanał.
static void rgb_set(uint8_t r, uint8_t g, uint8_t b)
{
// wspólna katoda: wprost
OCR0A = r; // R
OCR0B = g; // G
OCR1A = b; // B
}
Jeśli masz wspólną anodę
static void rgb_set_common_anode(uint8_t r, uint8_t g, uint8_t b)
{
// wspólna anoda: odwróć
OCR0A = 255 - r;
OCR0B = 255 - g;
OCR1A = 255 - b;
}
8) Test kolorów — paleta podstawowa
#include <util/delay.h>
static void demo_basic_colors(void)
{
rgb_set(255, 0, 0); _delay_ms(800); // czerwony
rgb_set(0, 255, 0); _delay_ms(800); // zielony
rgb_set(0, 0, 255); _delay_ms(800); // niebieski
rgb_set(255, 255, 0); _delay_ms(800); // żółty
rgb_set(0, 255, 255); _delay_ms(800); // cyan
rgb_set(255, 0, 255); _delay_ms(800); // magenta
rgb_set(255, 255, 255); _delay_ms(800); // biały (jeśli dioda pozwala)
rgb_set(0, 0, 0); _delay_ms(600); // OFF
}
9) Sceny świetlne: fade pojedynczego kanału i mieszanie
Zrobimy dwa efekty:
- fade czerwonego kanału,
- płynne przejście kolorów (prosty „color wheel”).
Fade czerwieni
static void fade_red(void)
{
for (uint16_t v = 0; v <= 255; v++) { rgb_set(v, 0, 0); _delay_ms(4); }
for (int16_t v = 255; v >= 0; v--) { rgb_set(v, 0, 0); _delay_ms(4); }
}
Prosty „color wheel” (3 fazy)
static void color_wheel_simple(void)
{
// R -> G
for (uint16_t i = 0; i <= 255; i++) { rgb_set(255 - i, i, 0); _delay_ms(4); }
// G -> B
for (uint16_t i = 0; i <= 255; i++) { rgb_set(0, 255 - i, i); _delay_ms(4); }
// B -> R
for (uint16_t i = 0; i <= 255; i++) { rgb_set(i, 0, 255 - i); _delay_ms(4); }
}
Używamy _delay_ms w pętlach. W następnych krokach połączysz to z timerami (lekcja 13) dla płynności bez blokowania.
10) Sterowanie trybami RGB przyciskiem (maszyna stanów)
Łączymy z lekcją 11: klik zmienia tryb sceny.
typedef enum {
SCENE_OFF = 0,
SCENE_BASIC,
SCENE_FADE_RED,
SCENE_WHEEL,
SCENE_MAX
} scene_t;
W pętli:
scene_t scene = SCENE_OFF;
if (button_click())
{
scene++;
if (scene >= SCENE_MAX) scene = SCENE_OFF;
}
I obsługa:
switch (scene)
{
case SCENE_OFF:
rgb_set(0,0,0);
_delay_ms(20);
break;
case SCENE_BASIC:
demo_basic_colors();
break;
case SCENE_FADE_RED:
fade_red();
break;
case SCENE_WHEEL:
color_wheel_simple();
break;
default:
scene = SCENE_OFF;
break;
}
11) Pełny program lekcji (gotowy do wgrania)
#include <avr/io.h>
#include <util/delay.h>
#define R_PIN PD6 // OC0A
#define G_PIN PD5 // OC0B
#define B_PIN PB1 // OC1A (8-bit PWM)
#define BTN_DDR DDRD
#define BTN_PORT PORTD
#define BTN_PINREG PIND
#define BTN_PIN PD2
static inline uint8_t button_is_pressed(void)
{
return ((BTN_PINREG & (1 << BTN_PIN)) == 0);
}
static inline uint8_t button_click(void)
{
if (button_is_pressed())
{
_delay_ms(20);
if (button_is_pressed())
{
while (button_is_pressed()) { }
_delay_ms(20);
return 1;
}
}
return 0;
}
static void pwm_timer0_init(void)
{
DDRD |= (1 << R_PIN) | (1 << G_PIN);
TCCR0A = (1 << WGM01) | (1 << WGM00)
| (1 << COM0A1) | (1 << COM0B1);
TCCR0B = (1 << CS01) | (1 << CS00); // /64
OCR0A = 0;
OCR0B = 0;
}
static void pwm_timer1_8bit_init(void)
{
DDRB |= (1 << B_PIN);
TCCR1A = (1 << WGM10) | (1 << COM1A1);
TCCR1B = (1 << WGM12);
TCCR1B |= (1 << CS11) | (1 << CS10); // /64
OCR1A = 0;
}
static void rgb_set(uint8_t r, uint8_t g, uint8_t b)
{
OCR0A = r;
OCR0B = g;
OCR1A = b;
}
static void demo_basic_colors(void)
{
rgb_set(255, 0, 0); _delay_ms(600);
rgb_set(0, 255, 0); _delay_ms(600);
rgb_set(0, 0, 255); _delay_ms(600);
rgb_set(255, 255, 0); _delay_ms(600);
rgb_set(0, 255, 255); _delay_ms(600);
rgb_set(255, 0, 255); _delay_ms(600);
rgb_set(255, 255, 255); _delay_ms(600);
rgb_set(0, 0, 0); _delay_ms(400);
}
static void fade_red(void)
{
for (uint16_t v = 0; v <= 255; v++) { rgb_set(v, 0, 0); _delay_ms(4); }
for (int16_t v = 255; v >= 0; v--) { rgb_set(v, 0, 0); _delay_ms(4); }
}
static void color_wheel_simple(void)
{
for (uint16_t i = 0; i <= 255; i++) { rgb_set(255 - i, i, 0); _delay_ms(4); }
for (uint16_t i = 0; i <= 255; i++) { rgb_set(0, 255 - i, i); _delay_ms(4); }
for (uint16_t i = 0; i <= 255; i++) { rgb_set(i, 0, 255 - i); _delay_ms(4); }
}
typedef enum {
SCENE_OFF = 0,
SCENE_BASIC,
SCENE_FADE_RED,
SCENE_WHEEL,
SCENE_MAX
} scene_t;
int main(void)
{
pwm_timer0_init();
pwm_timer1_8bit_init();
BTN_DDR &= ~(1 << BTN_PIN);
BTN_PORT |= (1 << BTN_PIN);
scene_t scene = SCENE_OFF;
while (1)
{
if (button_click())
{
scene++;
if (scene >= SCENE_MAX) scene = SCENE_OFF;
}
switch (scene)
{
case SCENE_OFF:
rgb_set(0,0,0);
_delay_ms(20);
break;
case SCENE_BASIC:
demo_basic_colors();
break;
case SCENE_FADE_RED:
fade_red();
break;
case SCENE_WHEEL:
color_wheel_simple();
break;
default:
scene = SCENE_OFF;
break;
}
}
}
12) Zadania obowiązkowe
- Podłącz RGB do pinów PWM i uruchom paletę 7 kolorów.
- Zrób funkcję rgb_set(r,g,b) i użyj jej we wszystkich efektach.
- Zrób 3 sceny i przełączaj je przyciskiem (OFF / BASIC / WHEEL).
13) Zadania dodatkowe (dla lepszych)
- Zrób 10 „zapisanych” kolorów w tablicy i przewijaj je klikami.
- Zrób „oddech” białego światła (RGB razem).
- Zrób „sygnalizację stanu”: zielony=OK, żółty=warning, czerwony=error (i zmieniaj przyciskiem).
14) Zadania PRO (projektowe)
- Zrób efekt bez blokowania: użyj tick 1ms z lekcji 13 i zmieniaj kolory „co X ms”.
- Zrób korekcję jasności (pseudo-gamma) dla każdego kanału (tablica 16/32 wartości).
- Dodaj tryb „audio-reactive”: zmiana koloru zależna od tonu z lekcji 15 (manualnie, bez ADC na razie).
Masz RGB jako moduł, który można wkleić do każdego projektu (status LED, efekty, UI).