Warsztat asemblerowy na STM32: od dokumentacji do migającej diody
W tym warsztacie robimy prosty program w asemblerze ARM, który miga diodą LED na płytce NUCLEO-F103RB. Celem publikacji jest pokazanie, jak przejść od dokumentacji płytki i mikrokontrolera do działającego kodu, kolejno:
- sprawdzamy, do którego pinu mikrokontrolera podłączona jest dioda,
- odnajdujemy właściwe rejestry w dokumentacji,
- włączamy zegar dla GPIOA,
- konfigurujemy pin PA5 jako wyjście,
- ustawiamy i zerujemy pin przez rejestry GPIO.
Warsztat wykonujemy na taniej i popularnej płytce NUCLEO-F103RB. Można użyć innej płytki z STM32, ale wtedy trzeba samodzielnie sprawdzić, do którego pinu MCU jest podłączona dioda LED i gdzie znajdują się odpowiednie rejestry dla danego mikrokontrolera.

Figure 1: Płytka NUCLEO-F103RB
STM32F103 jest wszechstronnym mikrokontrolerem. Ma wiele peryferiów, takich jak GPIO, UART, I2C, SPI, timery, ADC czy RTC. My skupiamy się tylko na GPIO i diodzie LED, ale metoda pracy z dokumentacją jest podobna także dla innych peryferiów:
- znajdujemy informację w dokumentacji płytki,
- sprawdzamy pin mikrokontrolera,
- szukamy peryferium w mapie pamięci (memory map),
- włączamy jego zegar w RCC,
- konfigurujemy odpowiednie rejestry,
- zapisujemy lub odczytujemy dane.
Szczegóły konfiguracji będą inne dla UART, I2C czy ADC, ale sposób szukania informacji w dokumentacji jest ten sam.
Dokumenty, których używamy
Do tego warsztatu potrzebujemy dwóch typów dokumentów:
- User manual płytki Nucleo — pokazuje, jak elementy na płytce są podłączone do mikrokontrolera.
- Reference Manual mikrokontrolera — opisuje rejestry, adresy i bity konfiguracyjne. Dla NUCLEO-F103RB używamy:
UM1724 – STM32 Nucleo-64 boards user manual RM0008 – STM32F10xxx reference manual Datasheet dla STM32F103xB
Reference Manual RM0008 jest najważniejszy, bo opisuje rejestry RCC i GPIO dla rodziny STM32F1.
Sprawdzenie, gdzie podłączona jest dioda LD2
Ze strony ST dla płytki NUCLEO-F103RB pobieramy dokument UM1724 User manual. W sekcji dotyczącej diod LED znajduje się informacja, że dioda użytkownika LD2 jest podłączona do sygnału Arduino D13.

Figure 2: Informacja o LD2 w UM1724
Dokument podaje, że D13 może odpowiadać pinowi PA5 albo PB13, zależnie od konkretnej płytki Nucleo. Dlatego musimy sprawdzić tabelę pinów dla NUCLEO-F103RB. Z tabeli wynika, że dla tej płytki sygnał D13 jest podłączony do pinu
PA5
Zapisujemy najważniejszą informację:
Dioda LED LD2 jest podłączona do GPIOA, pin 5, czyLi PA5.
To jest punkt startowy. Dalej nie zgadujemy. Szukamy w dokumentacji, jak skonfigurować GPIOA i pin PA5.
Przygotowanie projektu w STM32CubeIDE
Nie opisujemy tutaj instalacji STM32CubeIDE. Zakładamy, że środowisko jest już zainstalowane. W STM32CubeIDE wybieramy:
FiLe -> New -> STM32 Project
Następnie wybieramy tryb:
STM32CubeIDE Empty Project
Klikamy Next. W kolejnym oknie przechodzimy do zakładki Board Selector. W filtrze można wpisać 103, żeby szybko znaleźć właściwą płytkę.

Figure 3: Wybór NUCLEO-F103RB w STM32CubeIDE
Wybieramy płytkę:
NUCLEO-F103RB
Klikamy Next, wpisujemy nazwę projektu, np.:
F103_LED_ASM
Następnie klikamy Finish.
Po utworzeniu projektu powinniśmy zobaczyć strukturę podobną do tej:

Figure 4: Struktura projektu w STM32CubeIDE
Szkielet programu w asemblerze
Ponieważ program piszemy w asemblerze, nie potrzebujemy pliku main.c. Usuwamy go i tworzymy nowy plik, np.:
main.s
Rozszerzenie .s oznacza plik asemblera przetwarzany przez GNU assembler. Warto wiedzieć, że w narzędziach GNU spotyka się też rozszerzenie .S, pisane wielką literą. Plik .S jest przed asem-blerem przepuszczany przez preprocesor C, więc można w nim używać np. #define albo #include. W tym warsztacie wystarczy zwykłe .s, bo stałe definiujemy dyrektywą asemblera .equ.
Wstawiamy minimalny szkielet programu:
.syntax unified
.cpu cortex–m3
.fpu softvfp
.thumb
.section .text
.global main
.type main, %function
.thumb_Func
main:
.align
.end
Znaczenie najważniejszych dyrektyw:
- .syntax unified — używamy zunifikowanej składni ARM/Thumb,
- .cpu cortex-m3 — kod jest przeznaczony dla rdzenia Cortex-M3,
- .fpu sotvfp — informujemy narzędzia, że nie używamy sprzętowego FPU; Cortex-M3 w STM32F103 nie ma jednostki zmiennoprzecinkowej,
- .thumb — Cortex-M3 wykonuje kod Thumb,
- .section .text — kod trafia do sekcji programu,
- .global main — symbol main jest widoczny dla kodu startowego,
- .type main, %function — oznaczamy main jako funkcję,
- .thumb_func — informujemy linker/debugger, że funkcja jest funkcją Thumb.
Po zapisaniu pliku budujemy projekt. Jeżeli wszystko jest poprawnie skonfigurowane, w konsoli powinniśmy zobaczyć komunikat w stylu:
Build finished. 0 errors, 0 warnings.
Jak procesor widzi peryferia
W mikrokontrolerach STM32 peryferia są dostępne przez pamięć. Oznacza to, że rejestry GPIO, RCC, UART czy SPI mają konkretne adresy w przestrzeni adresowej procesora.
Procesor nie ma osobnej instrukcji „włącz GPIOA” albo „ustaw pin PA5”. Procesor wykonuje zwykłe zapisy i odczyty pod określone adresy. Jeżeli wpiszemy odpowiednią wartość do odpowiedniego rejestru, sprzęt zmieni konfigurację lub stan pinu.
Architektura
W RM0008 jest rozdział Memory and bus architecture, który pozwala zorientować się, co jest z czym połączone i jak szukać właściwych rejestrów. Na stronie 47 znajduje się schemat blokowy pokazujący połączenia między rdzeniem, magistralami i peryferiami.

Figure 5: System architecture
Z diagramu widać, że interesujący nas port GPIOA znajduje się po stronie magistrali APB2. APB2 jest połączona z resztą systemu przez mostek AHB/APB, ale na tym etapie najważniejsza jest infor-macja, że GPIOA należy do peryferiów APB2.
Na stronie 49, w sekcji AHB/APB bridges (APB), dokumentacja podaje ważną zasadę:
After each device reset, all peripheral clocks are disabled (except for the SRAM and FLITF). Before using a peripheral you have to enable its clock in the RCC_AHBENR, RCC_APB2ENR or RCC_APB1ENR register.
To oznacza, że zanim użyjemy GPIOA, musimy włączyć jego zegar w module RCC.
Potwierdzenie, że GPIOA należy do APB2, znajduje się także w tabeli Register boundary ad-dresses w rozdziale Memory map. Wpis dla GPIO Port A ma zakres:
0x40010800 – 0x40010BFF
i znajduje się w grupie peryferiów APB2.
Skoro GPIOA jest peryferium APB2, szukamy rejestru włączającego zegary dla peryferiów APB2. Jest to:
RCC_APB2ENR
W opisie tego rejestru znajduje się bit:
IOPAEN
czyli IO port A clock enable. To właśnie ten bit odpowiada za włączenie zegara portu GPIOA.
Co musimy zrobić dla PA5
Dla naszej diody musimy wykonać trzy kroki:
- Włączyć zegar dLa GPIOA w RCC.
- Skonfigurować PA5 jako wyjście push-pull.
- Ustawiać i zerować PA5, żeby zapalać i gasić LED.
Włączenie GPIOA w RCC
Port GPIOA w STM32F103 jest peryferium podłączonym do magistrali APB2.
To, że GPIOA ma swój adres w memory map, nie oznacza jeszcze, że port działa po resecie. W STM32 zegary wielu peryferiów są domyślnie wyłączone, żeby zmniejszyć pobór prądu. Zanim skonfigurujemy PA5, musimy włączyć zegar dla GPIOA w module RCC.
RCC oznacza:
Reset and Clock Control
W RM0008 Reference Manual szukamy rejestru:
RCC_APB2ENR
czyli:
APB2 peripheral clock enable register

Figure 6: RCC_APB2ENR w RM0008
Widzimy, że rejestr RCC_APB2ENR ma offset:
0x18
To nie jest pełny adres w pamięci. To jest offset względem adresu bazowego bloku RCC. Musimy teraz znaleźć adres bazowy RCC. W RM0008 szukamy sekcji Memory map.

Z tabeli wynika, że blok RCC zaczyna się od adresu:
0x40021000
Można jeszcze kliknąć w Section 7.3.11 on page 121, przeniesie to nas na stronę gdzie w tabelce beðzie dokładnie rozpisana ta sekcja, od adresu 0x18 zaczyna się RCC_APB2ENR:

Figure 7: Memory map w RM0008
Czyli:
RCC_BASE = 0x40021000 RCC
APB2ENR = RCC_BASE + 0x18
RCC_APB2ENR = 0x40021018
Zapisujemy to w kodzie:
.equ RCC_BASE, 0x40021000
.equ RCC_APB2ENR_OFFSET, 0x18
.equ RCC_APB2ENR, (RCC_BASE + RCC_APB2ENR_OFFSET)
Teraz musimy wiedzieć, który bit w RCC_APB2ENR odpowiada za GPIOA.

Figure 8: Bity rejestru RCC_APB2ENR
W tabeli widzimy bit:
IOPAEN
Jest to bit numer 2.

Figure 9: Opis bitu IOPAEN
Opis mówi:
0: IO port A clock disabled
1: IO port A clock enabled
Czyli żeby włączyć GPIOA, musimy ustawić bit numer 2 w rejestrze RCC_APB2ENR.
W kodzie zapisujemy maskę:
.equ RCC_APB2ENR_IOPAEN, (1 << 2)
Kod włączający zegar GPIOA:
@ EnabLe GPIOA cLock in RCC_APB2ENR
ldr r0, = RCC_APB2ENR
ldr r1, [r0]
orr r1, r1, #RCC_APB2ENR_IOPAEN
str r1, [r0]
Co robi ten kod:
ldr r0, =RCC_APB2ENR ; r0 = adres rejestru RCC_APB2ENR
ldr r1, [r0] ; r1 = aktualna wartość RCC_APB2ENR
orr r1, r1, #RCC_APB2ENR_IOPAEN ; ustawiamy bit IOPAEN, nie ruszamy pozostałych bitów
str r1, [r0] ; zapisujemy nową wartość do RCC_APB2ENR
Nie wpisujemy do rejestru samego RCC_APB2ENR_IOPAEN, bo moglibyśmy przypadkowo skasować inne bity. Dlatego robimy operację read-modify-write: odczyt, modyfikacja, zapis.
Konfiguracja PA5 jako wyjście
Wiemy już, że LED jest na PA5. Musimy skonfigurować pin PA5 jako wyjście.
Najpierw znajdujemy adres bazowy GPIOA. W RM0008, w tabeli Register boundary addresses, szukamy pozycji GPIO Port A.

Figure 10: Adres GPIOA w memory map
Z tabeli wynika, że GPIOA zajmuje zakres:
0x40010800 – 0x40010BFF
Adres bazowy GPIOA to początek tego zakresu:
GPIOA_BASE = 0x40010800
Zapisujemy:
.equ GPIOA_BASE, 0x40010800
Rejestr GPIOA_CRL
W STM32F1 konfiguracja GPIO różni się od nowszych rodzin STM32. W nowszych układach często spotykamy rejestry takie jak MODER, OTYPER, OSPEEDR i PUPDR. W STM32F103 tego nie ma.
Tutaj konfiguracja pinów jest zapisana w dwóch rejestrach:
GPIOx_CRL – konfiguracja pinów 0 ..7
GPIOx_CRH – konfiguracja pinów 8 ..15
Ponieważ używamy pinu PA5, interesuje nas rejestr:
GPIOA_CRL
W RM0008 szukamy opisu GPIOx_CRL.

Figure 11: GPIOx_CRL w RM0008
Rejestr GPIOx_CRL ma offset:
0x00
Czyli dla GPIOA:
GPIOA_CRL = GPIOA_BASE + 0x00 GPIOA_CRL = 0x40010800
Zapisujemy:
.equ GPIOx_CRL_OFFSET, 0x00
.equ GPIOA_CRL, (GPIOA_BASE + GPIOx_CRL_OFFSET)
Cztery bity konfiguracji jednego pinu
W STM32F1 każdy pin ma 4 bity konfiguracyjne:
MODEy[1:0] – tryb i prędkość wyjścia
CNFy[1:0] – konFiguracja wejścia/wyjścia
Dla pinu 5 są to:
MODE5[1:0] -> bity 21:20
CNF5[1:0] -> bity 23:22
Czyli całe pole konfiguracyjne PA5 zajmuje bity:
23 ..20
Po resecie pin jest wejściem. My chcemy uzyskać:
PA5 jako wyjście push-pul, max speed 50 MHz
Z tabeli w Reference Manual wynika, że dla takiego trybu trzeba ustawić:
MODE5 = 11
CNF5 = 00
W bitach 23..20 wygląda to tak:
CNF5 MODE5
00 11
Czyli wartość pola 4-bitowego wynosi:
0b0011 = 0x3
Ponieważ pole PA5 zaczyna się od bitu 20, robimy przesunięcie:
.equ PA5_CNF_MODE_MASK, (0xF << 20)
.equ PA5_OUTPUT_PP_50MHZ, (0x3 << 20)
Pierwsza maska służy do wyczyszczenia całego pola PA5. Druga zawiera docelową konfigurację.
Kod konfiguracji PA5:
@ ConFigure PA5 as general purpose output push–pull, 50 MHz
Ldr r0, =GPIOA_CRL
Ldr r1, [r0]
bic r1, r1, #PA5_CNF_MODE_MASK
orr r1, r1, #PA5_OUTPUT_PP_50MHZ
str r1, [r0]
Co robi ten kod:
ldr r0, =GPIOA_CRL ; r0 = adres GPIOA_CRL
ldr r1, [r0] ; r1 = aktuaLna konFiguracja pinów PA0 ..PA7
bic r1, r1, #PA5_CNF_MODE_MASK ; zerujemy bity 23 ..20, czyli konfigurację PA5
orr r1, r1, #PA5_OUTPUT_PP_50MHZ ; wpisujemy konfigurację output push-pull 50 MHz
str r1, [r0] ; zapisujemy konfigurację do GPIOA_CRL
Ważne: nie wpisujemy całego rejestru od zera, bo wtedy zmienilibyśmy także konfigurację innych pinów PA0..PA7. Modyfikujemy tylko 4 bity należące do PA5.
Miganie diodą LED
Dioda LD2 jest podłączona tak, że:
PA5 = 1 -> LED świeci
PA5 = 0 -> LED jest zgaszony
Możemy sterować pinem na dwa sposoby:
- przez rejestr GPIOA_BSRR,
- przez rejestr GPIOA_ODR.
W praktyce lepiej używać BSRR, ponieważ pozwala ustawić lub skasować pojedynczy bit bez odczytywania całego rejestru wyjściowego. Wersję z ODR pokazujemy jako porównanie.
Wersja z rejestrem GPIOA_BSRR
W RM0008 szukamy rejestru:
GPIOx_BSRR

Figure 12: GPIOx_BSRR w RM0008
Rejestr GPIOx_BSRR ma offset:
0x10
Dla GPIOA:
GPIOA_BSRR = GPIOA_BASE + 0x10
Zapisujemy:
.equ GPIOx_BSRR_OFFSET, 0x10
.equ GPIOA_BSRR, (GPIOA_BASE + GPIOx_BSRR_OFFSET)
Działanie BSRR:
bity 0 ..15 – ustawiają odpowiednie piny na 1
bity 16 ..31 – zerują odpowiednie piny do 0
Dla PA5:
bit 5 – ustawia PA5, czyli zapala LED
bit 21 – zeruje PA5, czyli gasi LED
Dlaczego bit 21? Bo resetowanie pinu 5 znajduje się w górnej połowie rejestru:
5 + 16 = 21
Definicje:
.equ LED_PIN, 5
.equ LED_BSRR_SET, (1 << LED_PIN)
.equ LED_BSRR_RESET, (1 << (LED_PIN + 16))
Do obserwacji migania potrzebujemy prostego opóźnienia. Nie jest to dokładna sekunda, tylko prosta pętla programowa dobrana eksperymentalnie:
.equ ONESEC, 700000
Funkcja opóźniająca:
delay:
ldr r3, =ONESEC
delay_loop:
subs r3, r3, #1
bne delay_loop
bx Lr
Ta funkcja zmniejsza licznik w r3 aż do zera. Instrukcja bne wykonuje skok, dopóki wynik poprzedniej operacji nie był zerem.
Główna pętla migania przez BSRR:
loop:
@ PA5 high – LED on
ldr r0, =GPIOA_BSRR
ldr r1, =LED_BSRR_SET
str r1, [r0]
bl delay
@ PA5 low – LED off
ldr r0, =GPIOA_BSRR
ldr r1, =LED_BSRR_RESET
str r1, [r0]
bl delay
b loop
W tej wersji nie odczytujemy aktualnego stanu portu. Zapis do BSRR wykonuje bezpośrednią operację ustawienia lub skasowania jednego pinu.
Wersja z rejestrem GPIOA_ODR
Rejestr GPIOx_ODR to:
Port output data register

Figure 13: GPIOx_ODR w RM0008
Rejestr GPIOx_ODR ma offset:
0x0C
Dla GPIOA:
GPIOA_ODR = GPIOA_BASE + 0x0C
Definicje:
.equ GPIOx_ODR_OFFSET, 0x0C
.equ GPIOA_ODR, (GPIOA_BASE + GPIOx_ODR_OFFSET)
.equ LED_ODR_MASK, (1 << LED_PIN)
Sterowanie przez ODR wymaga operacji read-modify-write:
- odczytujemy cały GPIOA_ODR,
- ustawiamy albo zerujemy tylko bit PA5,
- zapisujemy całą wartość z powrotem.
Główna pętla dla ODR:
loop:
@ Set PA5 – LED on
ldr r0, =GPIOA_ODR
ldr r1, [r0]
orr r1, r1, #LED_ODR_MASK
str r1, [r0]
bl delay
@ CLear PA5 – LED off
ldr r0, =GPIOA_ODR
ldr r1, [r0]
bic r1, r1, #LED_ODR_MASK
str r1, [r0]
bl delay
b loop
Ta metoda działa, ale w większych programach może być mniej bezpieczna niż BSRR. Jeżeli w tym samym czasie inny fragment kodu albo przerwanie zmienia inny pin tego samego portu, operacja read-modify-write może nadpisać zmianę. BSRR unika tego problemu, bo pozwala atomowo ustawić lub skasować pojedynczy pin.
Cały program: wersja z GPIOA_BSRR
To jest zalecana wersja programu.
.syntax unified
.cpu cortex–m3
.fpu softvfp
.thumb
.section .text
.global main
.type main, %function
.equ ONESEC, 700000
.equ RCC_BASE, 0x40021000
.equ RCC_APB2ENR_OFFSET, 0x18
.equ RCC_APB2ENR, (RCC_BASE + RCC_APB2ENR_OFFSET)
.equ RCC_APB2ENR_IOPAEN, (1 << 2)
.equ GPIOA_BASE, 0x40010800
.equ GPIOx_CRL_OFFSET, 0x00
.equ GPIOx_BSRR_OFFSET, 0x10
.equ GPIOA_CRL, (GPIOA_BASE + GPIOx_CRL_OFFSET)
.equ GPIOA_BSRR, (GPIOA_BASE + GPIOx_BSRR_OFFSET)
.equ LED_PIN, 5
.equ LED_BSRR_SET, (1 << LED_PIN)
.equ LED_BSRR_RESET, (1 << (LED_PIN + 16))
.equ PA5_CNF_MODE_MASK, (0xF << 20)
.equ PA5_OUTPUT_PP_50MHZ, (0x3 << 20)
.thumb_func
main:
@ Enable GPIOA cLock in RCC_APB2ENR
ldr r0, =RCC_APB2ENR
ldr r1, [r0]
orr r1, r1, #RCC_APB2ENR_IOPAEN
str r1, [r0]
@ Configure PA5 as general purpose output push–pull, 50 MHz
ldr r0, =GPIOA_CRL
ldr r1, [r0]
bic r1, r1, #PA5_CNF_MODE_MASK
orr r1, r1, #PA5_OUTPUT_PP_50MHZ
str r1, [r0]
loop:
@ PA5 high – LED on
ldr r0, =GPIOA_BSRR
ldr r1, =LED_BSRR_SET
str r1, [r0]
bl delay
@ PA5 Low – LED off
ldr r0, =GPIOA_BSRR
ldr r1, =LED_BSRR_RESET
str r1, [r0]
bl delay
b loop
delay:
ldr r3, =ONESEC
delay_loop:
subs r3, r3, #1
bne delay_loop
bx Lr
.aLign
.end
Cały program: wersja z GPIOA_ODR
Ta wersja jest dobra do porównania z BSRR.
.syntax uniFied
.cpu cortex–m3
.fpu soFtvFp
.thumb
.section .text
.global main
.type main, %function
.equ ONESEC, 700000
.equ RCC_BASE, 0x40021000
.equ RCC_APB2ENR_OFFSET, 0x18
.equ RCC_APB2ENR, (RCC_BASE + RCC_APB2ENR_OFFSET)
.equ RCC_APB2ENR_IOPAEN, (1 << 2)
.equ GPIOA_BASE, 0x40010800
.equ GPIOx_CRL_OFFSET, 0x00
.equ GPIOx_ODR_OFFSET, 0x0C
.equ GPIOA_CRL, (GPIOA_BASE + GPIOx_CRL_OFFSET)
.equ GPIOA_ODR, (GPIOA_BASE + GPIOx_ODR_OFFSET)
.equ LED_PIN, 5
.equ LED_ODR_MASK, (1 << LED_PIN)
.equ PA5_CNF_MODE_MASK, (0xF << 20)
.equ PA5_OUTPUT_PP_50MHZ, (0x3 << 20)
.thumb_func
main:
@ Enable GPIOA cLock in RCC_APB2ENR
ldr r0, =RCC_APB2ENR
ldr r1, [r0]
orr r1, r1, #RCC_APB2ENR_IOPAEN
str r1, [r0]
@ Configure PA5 as generaL purpose output push–pull, 50 MHz
ldr r0, =GPIOA_CRL
ldr r1, [r0]
bic r1, r1, #PA5_CNF_MODE_MASK
orr r1, r1, #PA5_OUTPUT_PP_50MHZ
str r1, [r0]
loop:
@ Set PA5 – LED on
ldr r0, =GPIOA_ODR
ldr r1, [r0]
orr r1, r1, #LED_ODR_MASK
str r1, [r0]
bl delay
@ CLear PA5 – LED oFF
ldr r0, =GPIOA_ODR
ldr r1, [r0]
bic r1, r1, #LED_ODR_MASK
str r1, [r0]
bl delay b
loop
deLay:
ldr r3, =ONESEC
delay_loop:
subs r3, r3, #1
bne delay_loop
bx Lr
.aLign
.end
Co warto zapamiętać z warsztatu
Najważniejszy nie jest sam kod, tylko metoda pracy:
- Z dokumentacji płytki ustalamy, że LD2 jest podłączona do PA5.
- Z memory map ustalamy adres RCC i GPIOA.
- W RCC_APB2ENR ustawiamy bit IOPAEN, żeby włączyć zegar GPIOA.
- W GPIOA_CRL konfigurujemy PA5 jako wyjście push-pull.
- W GPIOA_BSRR aLbo GPIOA_ODR zmieniamy stan PA5.
To jest typowy schemat pracy z mikrokontrolerem na poziomie rejestrów. Nie musimy znać wszystkiego na pamięć. Musimy umieć znaleźć właściwą tabelę, właściwy rejestr, właściwy bit i poprawnie wykonać zapis.
GPIO opis rejestrów: ściągawka
Ta sekcja jest skróconą ściągawką z rejestrów GPIO w STM32F103. Dotyczy stylu GPIO z rodziny STM32F1.
GPIOx_CRL — Port configuration register low
Konfiguruje piny:
0 ..7
Każdy pin ma 4 bity konfiguracyjne:
CNFy[1:0] MODEy[1:0]
Dla pinu y w zakresie 0..7:
MODEy – tryb/prędkość
CNFy – konfiguracja wejścia/wyjścia
Przykład dla PA5:
CNF5 -> bity 23:22
MODE5 -> bity 21:20
Typowe ustawienie dla wyjścia push-pull 50 MHz:
CNF = 00
MODE = 11
GPIOx_CRH — Port configuration register high
Konfiguruje piny:
8 ..15
Działa tak samo jak GPIOx_CRL, ale dotyczy górnej połowy portu.
Jeżeli konfigurujemy np. pin PA13, szukamy pola CNF13 i MODE13 w GPIOA_CRH, a nie w GPIOA_CRL.
GPIOx_IDR — Port input data register
Rejestr wejściowy portu.
Służy do odczytu stanu pinów:
bit 0 -> stan pinu x0
bit 1 -> stan pinu x1
…
bit 15 -> stan pinu x15
Dla GPIOA:
GPIOA_IDR bit 5 -> aktuaLny stan PA5
Bity 16..31 są zarezerwowane.
GPIOx_ODR — Port output data register
Rejestr wyjściowy portu.
Służy do odczytu i zapisu stanu wyjściowego pinów:
bit 0 -> wyjście x0
bit 1 -> wyjście x1
…
bit 15 -> wyjście x15
Przykład:
GPIOA_ODR bit 5 = 1 -> PA5 high
GPIOA_ODR bit 5 = 0 -> PA5 low
Zmiana pojedynczego bitu przez ODR zwykle wymaga operacji read-modify-write:
odczytaj ODR -> zmień bit -> zapisz ODR
W prostych programach działa to dobrze. W większych programach, szczególnie z przerwaniami, bezpieczniejszy jest BSRR.
GPIOx_BSRR — Port bit set/reset register
Rejestr do atomowego ustawiania i zerowania pinów. Działa tylko przez zapis.
Dolna połowa rejestru ustawia piny:
bity 0 ..15 -> ustawienie pinu na 1
Górna połowa rejestru zeruje piny:
bity 16 ..31 -> ustawienie pinu na 0
Przykład dla pinu 5:
zapis 1 << 5 -> ustawia pin 5
zapis 1 << (5 + 16) -> zeruje pin 5
Dla PA5:
.equ LED_PIN, 5
.equ PA5_SET, (1 << LED_PIN)
.equ PA5_RESET, (1 << (LED_PIN + 16))
BSRR jest preferowany do sterowania pojedynczymi pinami, ponieważ nie wymaga odczytu całego portu i nie nadpisuje przypadkowo innych bitów.
GPIOx_BRR — Port bit reset register
Rejestr służący do zerowania pinów. Działa tylko przez zapis.
bit 0 -> zeruje pin x0
bit 1 -> zeruje pin x1
…
bit 15 -> zeruje pin x15
Przykład:
zapis 1 << 5 do GPIOA_BRR -> zeruje PA5
W praktyce często wystarczy używać BSRR, bo BSRR potrafi zarówno ustawiać, jak i zerować piny.
GPIOx_LCKR — Port configuration lock register
Rejestr blokady konfiguracji portu.
Pozwala zablokować konfigurację wybranych pinów. Po wykonaniu specjalnej sekwencji zapisu konfiguracja w CRL/CRH nie może zostać zmieniona aż do następnego resetu mikrokontrolera.
W typowych prostych programach i warsztatach nie używamy LCKR. Przydaje się w projektach, w których konfiguracja pinów ma być zabezpieczona przed przypadkową zmianą.
Minimalna tabela offsetów GPIO STM32F103
Dla portu GPIOx:
|
Rejestr |
Offset |
Znaczenie |
|
GPIOx_CRL |
0x00 |
Konfiguracja pinów 0..7 |
|
GPIOx_CRH |
0x04 |
Konfiguracja pinów 8..15 |
|
GPIOx_IDR |
0x08 |
Odczyt stanów wejściowych |
|
GPIOx_ODR |
0x0C |
Rejestr wyjściowy portu |
|
GPIOx_BSRR |
0x10 |
Atomowe ustawianie/zerowanie pinów |
|
GPIOx_BRR |
0x14 |
Zerowanie pinów |
|
GPIOx_LCKR |
0x18 |
Blokada konfiguracji pinów |
Dla GPIOA adres rejestru liczymy tak:
adres rejestru = GPIOA_BASE + offset
Przykład:
GPIOA_BASE = 0x40010800
GPIOA_ODR = 0x40010800 + 0x0C = 0x4001080C
GPIOA_BSRR = 0x40010800 + 0x10 = 0x40010810

CAN od zera część 2: Jak połączyć dwa mikrokontrolery i przesłać pierwszą ramkę?
CAN od zera część 3: Jak zarządzać danymi CAN w RTOS? Porównanie mechanizmów i wybór najlepszego
STMicroelectronics wprowadza technologię ciągłego monitorowania obrazu do urządzeń elektronicznych nowej generacji 


![https://www.youtube.com/watch?v=gHcP8AajoN4 Szymon Robak oprowadza po katowickim Laboratorium Badań Kompatybilności Elektromagnetycznej w Sieć Badawcza Łukasiewicz - Instytucie Sztucznej Inteligencji i Cyberbezpieczeństwa. Zapraszamy na film! [materiał redakcyjny]](https://mikrokontroler.pl/wp-content/uploads/2026/06/Szymon-Robak-tytulowe.png)
![https://www.youtube.com/watch?v=BgxJVTwYJ-s Zapraszamy do obejrzenia filmu i wysłuchania krótkich wypowiedzi prelegentów Hardware Forum 2026 i organizatorów majowej konferencji dla inżynierów z branży elektronicznej: Konrad Bruliński z Lemontech, prof. Krzysztof Kulpa z Politechniki Warszawskiej, Zbigniew Huber z FLC, Ewa Załupska z firmy KROK, Jerzy Kozieł z MPTECH, Grzegorz Potyralski z VIGO Photonics, dr Krzysztof Czuba z Politechniki Warszawskiej, Anna Beata Kalisz Hedegaard z Quantum Security Defence, Adrian Cichosz z Elhurt Dystrybucja Anna Kamińska z Creotech Quantum, oraz Łukasz Jaeszke i Adam Jaeszke z TEK.day [materiał redakcyjny]](https://mikrokontroler.pl/wp-content/uploads/2026/05/tytulowe-film-1.png)
