1) Cel lekcji
ADC (Analog-to-Digital Converter) w ATmega328P zamienia napięcie analogowe na liczbę. Dzięki temu możesz mierzyć: potencjometr, czujniki analogowe, baterię (po dzielniku), sygnały z układów audio.
- poznasz podstawowe pojęcia: rozdzielczość 10-bit, Vref, kanał, preskaler,
- ustawisz ADC w trybie pojedynczego pomiaru,
- zrobisz funkcję adc_read(channel),
- policzysz napięcie w voltach (lub mV),
- wykorzystasz ADC w praktyce (np. regulacja jasności PWM, próg alarmu, mapa 0..9999 na 7-seg).
2) Rozdzielczość i wzór na napięcie
ADC w ATmega328P jest 10-bitowy, czyli wynik jest w zakresie 0..1023.
V_in = (ADC / 1023) * V_ref
Praktycznie (w mV):
V_in_mV = (ADC * Vref_mV) / 1023
Dokładność zależy od jakości Vref i zakłóceń (zasilanie, masa, filtracja).
3) Kanały ADC na ATmega328P (skrót)
ADC ma kanały analogowe (ADC0..ADC5) dostępne zwykle na pinach portu C:
| Kanał ADC | Pin MCU | Typowe oznaczenie na płytkach |
|---|---|---|
| ADC0 | PC0 | A0 |
| ADC1 | PC1 | A1 |
| ADC2 | PC2 | A2 |
| ADC3 | PC3 | A3 |
| ADC4 | PC4 | A4 (SDA) |
| ADC5 | PC5 | A5 (SCL) |
Jeśli PC0..PC3 używasz do wyboru cyfr 7-seg (CA1..CA4), to do ADC wybierz np. PC4/PC5 (ADC4/ADC5), albo w tym ćwiczeniu odłącz wyświetlacz i skup się na ADC. Najważniejsze jest zrozumienie konfiguracji i odczytu.
4) Rejestry ADC — minimum, które musisz znać
- REFS1:0 — wybór Vref
- ADLAR — wyrównanie wyniku
- MUX[3:0] — wybór kanału
- ADEN — włączenie ADC
- ADSC — start pomiaru
- ADIF — flaga końca
- ADPS[2:0] — preskaler
Czytasz zawsze najpierw ADCL, potem ADCH.
5) Vref — co wybrać i dlaczego
Najczęściej masz trzy opcje (w zależności od sprzętu):
- AVCC jako referencja (typowo 5V) — prosto, ale zależy od wahań zasilania
- wewnętrzna 1.1V — lepsza stabilność dla małych napięć (wymaga dzielnika/podłączenia pod zakres)
- AREF (zewnętrzna) — najlepsza, jeśli masz precyzyjne źródło odniesienia
użyjemy AVCC jako Vref (najprościej). Jeśli zasilanie jest 5V, ADC 1023 ≈ 5V.
6) Preskaler ADC — żeby wynik był poprawny
ADC musi mieć zegar w rozsądnym zakresie (typowo ok. 50–200 kHz). Przy F_CPU=8 MHz dobry preskaler to 64 → 125 kHz.
preskaler 64 → ADPS2=1, ADPS1=1, ADPS0=0
7) Kod: inicjalizacja ADC i odczyt kanału (single conversion)
#include <avr/io.h>
#include <stdint.h>
static void adc_init_avcc(void)
{
// Vref = AVCC, wynik wyrównany do prawej (ADLAR=0)
ADMUX = (1<<REFS0);
// włącz ADC, preskaler 64 (dla 8MHz daje ok. 125kHz)
ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1);
// opcjonalnie: "dummy read" po włączeniu
ADCSRA |= (1<<ADSC);
while (ADCSRA & (1<<ADSC)) { }
(void)ADC;
}
static uint16_t adc_read(uint8_t channel)
{
channel &= 0x07; // ADC0..ADC7 (my używamy 0..5)
// zachowaj REFS, wyczyść MUX i ustaw nowy kanał
ADMUX = (ADMUX & 0xF0) | channel;
// start pomiaru
ADCSRA |= (1<<ADSC);
// czekaj aż skończy (ADSC spada do 0)
while (ADCSRA & (1<<ADSC)) { }
// odczyt 10-bit
return (uint16_t)ADC;
}
ustaw Vref, preskaler, wybierz kanał, start, czekaj, odczytaj ADC.
8) Przeliczanie na napięcie (mV) i interpretacja
static uint16_t adc_to_mV(uint16_t adc_val, uint16_t vref_mV)
{
// (adc * vref) / 1023
// używamy uint32_t aby uniknąć przepełnienia
uint32_t tmp = (uint32_t)adc_val * (uint32_t)vref_mV;
return (uint16_t)(tmp / 1023UL);
}
Jeśli Vref=5000 mV:
- adc=0 → ~0 mV
- adc=512 → ~2500 mV
- adc=1023 → ~5000 mV
9) Praktyka 1: ADC → PWM jasności LED (najbardziej czytelne ćwiczenie)
Odczytujesz potencjometr (np. ADC4) i ustawiasz PWM (np. na PD6/OC0A). Dzięki temu widać „analogowy efekt” od razu.
Jeśli w Twoim projekcie Timer0 jest zajęty przez tick/multipleks, PWM na Timer0 będzie kolidować. Wtedy ustaw PWM na Timer1 (OC1A PB1) albo zrób test ADC bez PWM (np. mapuj na 7-seg).
Wariant A: PWM na Timer1 (bez konfliktu z Timer0 tick)
// PWM 8-bit na OC1A (PB1) — jak w lekcji 16
static void pwm_timer1_8bit_init_oc1a(void)
{
DDRB |= (1<<PB1); // OC1A
// Fast PWM 8-bit: WGM10=1, WGM12=1
// Non-inverting OC1A: COM1A1=1
TCCR1A = (1<<WGM10) | (1<<COM1A1);
TCCR1B = (1<<WGM12);
// preskaler /64
TCCR1B |= (1<<CS11) | (1<<CS10);
OCR1A = 0;
}
static uint8_t adc10_to_pwm8(uint16_t adc_val)
{
// 0..1023 -> 0..255
return (uint8_t)(adc_val >> 2);
}
Main: ADC4 steruje jasnością
#include <util/delay.h>
int main(void)
{
adc_init_avcc();
pwm_timer1_8bit_init_oc1a();
while (1)
{
uint16_t a = adc_read(4); // ADC4 = PC4 (A4)
uint8_t p = adc10_to_pwm8(a);
OCR1A = p; // jasność LED na OC1A
_delay_ms(10);
}
}
10) Praktyka 2: ADC → liczba na 7-seg (0..9999)
To jest wariant „w Twoim stylu” z wcześniejszych lekcji: mierzysz ADC i wyświetlasz wynik jako liczba.
Skala:
- 0..1023 → 0..9999 (proporcja)
static uint16_t map_adc_to_9999(uint16_t adc_val)
{
// (adc * 9999) / 1023
uint32_t tmp = (uint32_t)adc_val * 9999UL;
return (uint16_t)(tmp / 1023UL);
}
Jeśli masz już silnik multipleksu (lekcja 18/19), w main robisz: val = map_adc_to_9999(adc_read(ch)) i wrzucasz do bufora wyświetlacza.
11) Typowe błędy w ADC (i szybkie naprawy)
- zakłócenia na wejściu
- brak kondensatora na AREF/AVCC
- długie przewody do potencjometru
- źle podłączony suwak potencjometru
- pin ustawiony jako wyjście
- brak masy wspólnej
średnia z N próbek (np. 8) — bardzo poprawia stabilność.
Filtr: średnia z 8 próbek
static uint16_t adc_read_avg8(uint8_t channel)
{
uint32_t sum = 0;
for (uint8_t i = 0; i < 8; i++)
sum += adc_read(channel);
return (uint16_t)(sum / 8);
}
12) Zadania obowiązkowe
- Zrób odczyt ADC z kanału (np. ADC4) i mapuj 0..1023 na 0..9999. Wyświetl na 7-seg.
- Dodaj filtr średniej z 8 próbek i porównaj stabilność wskazań.
- Zamiast mapowania na 9999 przelicz na mV (Vref=5000 mV) i wyświetl „napięcie” w mV (np. 2500).
13) Zadania dodatkowe (dla lepszych)
- Zrób „bargraph”: wynik ADC dziel na 10 poziomów i pokazuj 0..9 na 7-seg jako „poziom”.
- Zrób próg alarmu: jeśli ADC > próg, zapal LED / włącz brzęczyk.
- Zrób dwie referencje: porównaj wyniki przy AVCC vs wewnętrznej 1.1V (dla małego napięcia).
14) Zadania PRO (projektowe)
- Zrób automatyczną regulację jasności wyświetlacza 7-seg (PWM lub czas świecenia) w zależności od ADC (np. fotorezystor).
- Zrób pomiar baterii przez dzielnik napięcia: przelicz na rzeczywiste Vbat (uwzględnij R1/R2).
- Przełączanie kanałów: co 200 ms czytaj inny kanał i wyświetlaj go w trybie „A0/A1/A2” (własne znaki).
Następny krok to ADC w trybie auto-trigger / przerwania ADC oraz bardziej „ciągłe” próbkowanie (np. do audio/analizy sygnału).