LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
Artykuły

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:

  1. sprawdzamy, do którego pinu mikrokontrolera podłączona jest dioda,
  2. odnajdujemy właściwe rejestry w dokumentacji,
  3. włączamy zegar dla GPIOA,
  4. konfigurujemy pin PA5 jako wyjście,
  5. 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

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:

  1. User manual płytki Nucleo — pokazuje, jak elementy na płytce są podłączone do mikrokontrolera.
  2. 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ę.

Wybór NUCLEO-F103RB w STM32CubeIDE

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:

Struktura projektu w STM32CubeIDE

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  cortexm3

.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.

System architecture

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:

  1. Włączyć zegar dLa GPIOA w RCC.
  2. Skonfigurować PA5 jako wyjście push-pull.
  3. 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 pushpull, 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:

  1. przez rejestr GPIOA_BSRR,
  2. 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:

  1. odczytujemy cały GPIOA_ODR,
  2. ustawiamy albo zerujemy tylko bit PA5,
  3. 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  cortexm3

.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 pushpull, 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  cortexm3

.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 pushpull, 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:

  1. Z dokumentacji płytki ustalamy, że LD2 jest podłączona do PA5.
  2. Z memory map ustalamy adres RCC i GPIOA.
  3. W RCC_APB2ENR ustawiamy bit IOPAEN, żeby włączyć zegar GPIOA.
  4. W GPIOA_CRL konfigurujemy PA5 jako wyjście push-pull.
  5. 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

Absolwent Wydziału Elektroniki i Technik Informacyjnych Politechniki Warszawskiej, gdzie opracował algorytm redukcji atrybutów metodą uogólniania reguł decyzyjnych. Pracuje jako Inżynier ds. Bezpieczeństwa Oprogramowania w Samsung R&D Institute Poland. Miłośnik starych komputerów oraz elektroniki cyfrowej.