1. Architektura ATmega328P
ATmega328P to 8-bitowy mikrokontroler rodziny AVR firmy Microchip (dawniej Atmel), oparty na architekturze RISC. Pracuje przy napięciu 1,8–5,5 V, maksymalnej częstotliwości 20 MHz (przy 5 V) i ma 32 kB pamięci Flash, 2 kB SRAM oraz 1 kB EEPROM. Dokumentacja pełna (datasheet) jest dostępna na stronie Microchip pod numerem DS40002061.
Rdzeń AVR wykonuje większość instrukcji w jednym cyklu zegara. Przy 16 MHz (standardowa konfiguracja Arduino Uno z zewnętrznym rezonatorem kwarcowym) uzyskuje się wydajność ok. 16 MIPS.
2. Bezpośrednia obsługa GPIO przez rejestry
Każdy port I/O w ATmega328P obsługiwany jest przez trzy 8-bitowe rejestry: DDRx (kierunek), PORTx (zapis) i PINx (odczyt). Litera „x" zastępowana jest przez B, C lub D w zależności od portu. Zapis do rejestrów jest szybszy niż wywołanie digitalWrite() z biblioteki Arduino i zajmuje 1–2 cykle procesora zamiast ok. 50.
/* Ustawienie PB5 (pin 13 Arduino) jako wyjście i wysoki stan */
#include <avr/io.h>
int main(void) {
DDRB |= (1 << PB5); /* ustaw bit PB5 w DDRB jako wyjście */
PORTB |= (1 << PB5); /* ustaw wysoki stan na PB5 */
while (1) { }
return 0;
}
Odczyt stanu wejścia cyfrowego przez rejestr PINx jest szybszy o kilkanaście mikrosekund w porównaniu z digitalRead(). Przy aplikacjach czasu rzeczywistego, np. dekoderach sygnału IR lub odbiornikach protokołu DHT22, różnica jest istotna.
3. Timery sprzętowe Timer0, Timer1, Timer2
ATmega328P ma trzy timery: Timer0 i Timer2 (8-bitowe) oraz Timer1 (16-bitowy). Biblioteka Arduino używa Timer0 do obsługi funkcji millis(), micros() i delay(). Modyfikacja rejestrów Timer0 zakłóca działanie tych funkcji.
Timer1 (16-bit) jest powszechnie stosowany do generowania PWM o wysokiej rozdzielczości lub precyzyjnego odmierzania czasu. Prescaler Timer1 może przyjmować wartości: 1, 8, 64, 256, 1024. Przy fclk = 16 MHz i prescaler = 256, rozdzielczość tiku timera wynosi 16 µs.
/* Timer1 CTC, przerwanie co 1 ms przy 16 MHz */
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint16_t ms_count = 0;
ISR(TIMER1_COMPA_vect) {
ms_count++;
}
void timer1_init(void) {
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS11); /* CTC, prescaler 8 */
OCR1A = 1999; /* (16 000 000 / 8 / 1000) - 1 */
TIMSK1 = (1 << OCIE1A);
sei();
}
4. Przerwania zewnętrzne INT0 i INT1
Piny PD2 (INT0) i PD3 (INT1) obsługują przerwania zewnętrzne wyzwalane zboczem narastającym, opadającym lub poziomem niskim. Konfiguracja odbywa się przez rejestry EICRA i EIMSK.
/* Przerwanie INT0 na zbocze narastające */
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(INT0_vect) {
/* obsługa zdarzenia */
}
void int0_init(void) {
EICRA |= (1 << ISC01) | (1 << ISC00); /* zbocze narastające */
EIMSK |= (1 << INT0); /* włącz INT0 */
sei();
}
5. Komunikacja UART — wysyłanie danych przez port szeregowy
ATmega328P zawiera interfejs USART0. Przy baudrate 9600 i taktowaniu 16 MHz wartość rejestru UBRR0 wynosi: UBRR0 = (fclk / (16 × baudrate)) − 1 = (16 000 000 / (16 × 9600)) − 1 = 103. Błąd szybkości transmisji przy tej konfiguracji wynosi 0,2%, co mieści się w dopuszczalnym zakresie ±2% dla protokołu UART.
6. Programowanie przez ISP bez bootloadera
Programator ISP (In-System Programming) umożliwia programowanie Flash, EEPROM i bitów fuse przez interfejs SPI (piny MOSI, MISO, SCK, RESET). Tanie programatory USBasp dostępne w Polsce (np. w Botland.com.pl za ok. 25 zł) obsługiwane są przez avrdude — narzędzie wiersza poleceń wchodzące w skład środowiska Arduino IDE.
Usunięcie bootloadera z ATmega328P i programowanie bezpośrednio przez ISP skraca czas uruchamiania mikrokontrolera po resecie z ok. 500 ms (bootloader Arduino Optiboot) do ułamka milisekundy.