1) Jak wygląda program w C na mikrokontroler?
Program w C dla AVR to zwykły plik .c, który kompilujemy do kodu maszynowego i wgrywamy do pamięci FLASH. W odróżnieniu od aplikacji na PC, program zwykle działa w nieskończonej pętli i steruje sprzętem przez rejestry.
Minimalny „szkielet” programu
#include <avr/io.h>
int main(void)
{
// 1) Inicjalizacja (ustawienia portów, timerów, itp.)
while (1)
{
// 2) Pętla główna (logika programu)
}
}
W kolejnych lekcjach dojdą biblioteki np. <util/delay.h>, a w lekcjach timerów – przerwania.
2) Składnia podstawowa: instrukcje, bloki, komentarze
- Instrukcje w C kończą się średnikiem: ;
- Blok kodu jest w klamrach: { ... }
- Komentarz jednoliniowy: // ...
- Komentarz wieloliniowy: /* ... */
// To jest komentarz jednoliniowy
/*
To jest komentarz wieloliniowy
*/
int a = 5; // instrukcja
a = a + 1; // instrukcja
if (a > 5) { // blok
a = 0;
}
3) Zmienne i typy danych (pod AVR)
Mikrokontroler ma ograniczoną pamięć SRAM, więc typy danych dobiera się świadomie. W embedded częściej używa się typów o znanym rozmiarze (np. 8-bit, 16-bit).
| Typ | Typowy rozmiar na AVR | Zastosowanie |
|---|---|---|
| uint8_t | 8 bit (0–255) | bity, porty, małe liczniki |
| int8_t | 8 bit (-128–127) | małe wartości ze znakiem |
| uint16_t | 16 bit (0–65535) | liczniki czasu, ADC, większe zakresy |
| int | zwykle 16 bit | ogólne obliczenia (ale uważaj na zakres) |
| float | 32 bit (wolne) | raczej unikać na start, kosztowne obliczeniowo |
Niezbędny nagłówek do typów stałej szerokości
#include <stdint.h>
uint8_t a = 10;
uint16_t b = 1000;
4) Stałe: #define vs const
#define (makro preprocesora)
#define LED_PIN PB0
#define DELAY_MS 500
const (stała w C)
const uint16_t delay_ms = 500;
5) Operatory – szczególnie bitowe (kluczowe w AVR)
Operatory arytmetyczne
- +, -, *, /, %
Operatory porównania
- ==, !=, >, <, >=, <=
Operatory logiczne
- && (AND), || (OR), ! (NOT)
Operatory bitowe (najważniejsze)
- & AND bitowy
- | OR bitowy
- ^ XOR bitowy
- ~ negacja bitowa
- <<, >> przesunięcia
Przykład: tworzenie maski bitu
// (1 << n) tworzy maskę: tylko bit n ustawiony na 1
uint8_t maska0 = (1 << 0); // 0000 0001
uint8_t maska3 = (1 << 3); // 0000 1000
Ustawianie bitu (SET)
PORTB |= (1 << PB0); // ustaw bit PB0 na 1
Kasowanie bitu (CLEAR)
PORTB &= ~(1 << PB0); // wyzeruj bit PB0
Sprawdzanie bitu (READ)
if (PINB & (1 << PB0)) {
// bit PB0 jest 1
}
Przełączanie bitu (TOGGLE)
PORTB ^= (1 << PB0); // zmień stan PB0 na przeciwny
6) Instrukcje sterujące: if/else, switch, pętle
if / else
if (warunek) {
// jeśli prawda
} else {
// jeśli fałsz
}
switch (gdy jest wiele przypadków)
switch (tryb) {
case 0:
// tryb 0
break;
case 1:
// tryb 1
break;
default:
// inne
break;
}
while (najczęściej w embedded)
while (1) {
// pętla nieskończona
}
for (gdy powtarzasz N razy)
for (uint8_t i = 0; i < 10; i++) {
// 10 powtórzeń
}
7) Funkcje – porządek w kodzie
Funkcje pozwalają dzielić program na logiczne części: inicjalizacja, obsługa LED, obsługa przycisku itd.
void init(void)
{
// konfiguracja sprzętu
}
void led_on(void)
{
PORTB |= (1 << PB0);
}
int main(void)
{
init();
while (1) {
led_on();
}
}
Dlaczego warto?
- kod jest czytelniejszy,
- łatwiej go rozwijać,
- mniej błędów przy rozbudowie projektu.
8) volatile – kiedy jest potrzebne?
Słowo kluczowe volatile mówi kompilatorowi: „ta zmienna może zmieniać się poza normalnym kodem (np. w przerwaniu) – nie optymalizuj jej”.
volatile uint8_t flaga = 0;
9) Pełny przykład: „szkielet projektu” pod AVR
To jest wzorcowy układ kodu, który będziesz powtarzać w praktycznych lekcjach. Na razie to przykład struktury (bez zależności od konkretnych pinów).
#include <avr/io.h>
#include <stdint.h>
static void init_io(void)
{
// Tu w przyszłości ustawisz kierunki pinów (DDRx) i stany startowe (PORTx)
}
static void app_loop(void)
{
// Tu będzie główna logika aplikacji
}
int main(void)
{
init_io();
while (1)
{
app_loop();
}
}
10) Szybkie sprawdzenie (pytania)
- Dlaczego w AVR używa się operatorów bitowych?
- Jak ustawić bit PB0 w rejestrze PORTB?
- Co robi PORTB &= ~(1 << PB0);?
- Czym się różni && od &?
- Dlaczego program embedded zwykle ma while(1)?