3. ünite / 11 · yaklaşık 5 saat
GPIO: yazmaçlarla ve HAL ile
Saat açma, MODER/OTYPER/PUPDR ile yapılandırma, ODR/IDR ile yazma-okuma, BSRR'nin atomikliği ve HAL karşılıkları.
Bu ünitenin sonunda
- Bir GPIO portunu kullanmadan önce RCC'den saatini açmak
- MODER, OTYPER ve PUPDR bitleriyle pini giriş, çıkış, alternatif işlev ya da analog yapmak
- ODR, IDR ve BSRR ile pin yazmak ve okumak; oku-değiştir-yaz yarışını açıklamak
- Aynı işi HAL ile yazıp kayıt düzeyiyle karşılaştırmak
Ders notları
STM32'de her GPIO portu (GPIOA, GPIOB…) 16 pinlik bir yazmaç kümesidir. Kullanmadan önce saatini açmak gerekir: RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN. Saat kapalıyken yazmaçlara yazılan her şey yok sayılır; bu, yeni başlayanın ilk hatasıdır.
Yapılandırma yazmaçları
- MODER: pin başına 2 bit. 00 giriş, 01 çıkış, 10 alternatif işlev (UART, PWM), 11 analog (ADC).
- OTYPER: pin başına 1 bit. 0 push-pull (1'i de 0'ı da sürer), 1 open-drain (yalnız 0'a çeker; I²C gibi).
- PUPDR: pin başına 2 bit. 00 yok, 01 pull-up, 10 pull-down.
Veri yazmaçları
IDR pinlerin o anki seviyesini okur. ODR çıkış değerini tutar. GPIOA->ODR |= 1 << 5 bir oku-değiştir-yaz işlemidir: okuma ile yazma arasında bir kesme aynı portun başka bir bitini değiştirirse o değişiklik silinir. BSRR bu sorunu çözer: alt 16 bite yazılan 1 o pini set eder, üst 16 bite yazılan 1 reset eder; tek yazmadır, araya bir şey giremez.
HAL
HAL (Hardware Abstraction Layer) aynı işi HAL_GPIO_Init, HAL_GPIO_WritePin, HAL_GPIO_ReadPin ile yapar: okunaklı ve ailelerarası taşınabilir, biraz daha yavaş ve büyük. Gerçek projelerde çoğunlukla HAL kullanılır; yazmaçları bilmek ise hata ayıklarken ve zamanlamanın kritik olduğu yerde işe yarar.
Simülasyonlar
GPIO yazmaç görüntüleyici: MODER, OTYPER, PUPDR, ODR, IDR
Bitlere tıklayarak PA5'i (Nucleo LED'i) ve PC13'ü (kullanıcı butonu) yapılandır. Sanal pin seviyesi, IDR okuması ve aynı işi yapan kayıt düzeyi ile HAL C kodu birlikte değişir. BSRR yazmasını ve kısa devreyi dene.
| MODER | 0x0000_0000 | |
|---|---|---|
| OTYPER | 0x0000_0000 | |
| PUPDR | 0x0000_0000 | |
| ODR | 0x0000_0000 | |
| IDR | 0x0000_0000 |
GPIOA5: input · push-pull · pull none · 00 in
/* kayıt düzeyi */ RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; GPIOA->MODER = (GPIOA->MODER & ~(3U << (5 * 2))) | (0U << (5 * 2)); GPIOA->PUPDR = (GPIOA->PUPDR & ~(3U << (5 * 2))) | (0U << (5 * 2)); uint32_t level = (GPIOA->IDR >> 5) & 1U;
/* HAL */
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef g = {0};
g.Pin = GPIO_PIN_5; g.Mode = GPIO_MODE_INPUT; g.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &g);
GPIO_PinState level = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5);Saat kapalı yazmaçlara yazılanlar yok sayılır.
3B STM32 geliştirme kartı
Nucleo benzeri kart 3B masada: GPIOA saatini aç, PA5'i MODER ile çıkış yap ve BSRR yaz; başlık pinleri ve LD2 anında değişir. TIM3 PWM'i açınca motor görev oranına göre döner, kartın üstünde zamanlayıcı dalgası görünür. Sürükleyerek döndür, iki parmakla yakınlaştır. Durum testli saf mantıktan gelir.
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; GPIOA->MODER = (GPIOA->MODER & ~(3U << (5 * 2))) | (0U << (5 * 2)); GPIOA->PUPDR = (GPIOA->PUPDR & ~(3U << (5 * 2))) | (0U << (5 * 2)); uint32_t level = (GPIOA->IDR >> 5) & 1U;
Kendin dene
Yazmaç görüntüleyicide PA5'i çıkış yap (MODER bitleri 01) ve ODR'nin 5. bitine tıkla: LED yanıyor mu? Saat kapalıyken aynı şeyi dene. PC13'ü pull-up'sız giriş yap ve butonu bırak: IDR neden titriyor? Pull-up aç. Son olarak PA5'i push-pull 1 sürerken "dışarıdan GND'ye çek" seçeneğini aç ve kısa devre uyarısını oku; open-drain'e geçince ne değişiyor?
Wokwi'de
- Wokwi'de yeni bir proje aç ve kart olarak Nucleo-C031C6'yı seç (STM32C0 de MODER/ODR/IDR düzenini kullanır; taban adresleri farklıdır).
- LED'i ve butonu bağla; önce HAL_GPIO_WritePin ile, sonra BSRR yazmacıyla yak.
- Blue Pill (STM32F103) seçersen GPIO'nun CRL/CRH yazmaçlarıyla ayarlandığını gör: aileler arasındaki farkın canlı örneği.
Kod
#include "stm32f4xx.h"
int main(void) {
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; /* 1) GPIOA saatini aç */
GPIOA->MODER &= ~(3U << (5 * 2)); /* 2) PA5 kip bitlerini temizle */
GPIOA->MODER |= (1U << (5 * 2)); /* 01 = genel amaçlı çıkış */
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;
GPIOC->PUPDR &= ~(3U << (13 * 2)); /* PC13 giriş (sıfırlamada zaten 00) */
while (1) {
if ((GPIOC->IDR & (1U << 13)) == 0) /* Nucleo butonu basılıyken 0 */
GPIOA->BSRR = 1U << 5; /* set: tek yazma, atomik */
else
GPIOA->BSRR = 1U << (5 + 16); /* reset: üst 16 bit */
}
}#include "stm32f4xx_hal.h"
int main(void) {
HAL_Init(); /* SysTick 1 ms, NVIC grubu */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef g = {0};
g.Pin = GPIO_PIN_5; g.Mode = GPIO_MODE_OUTPUT_PP; g.Pull = GPIO_NOPULL; g.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &g);
g.Pin = GPIO_PIN_13; g.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(GPIOC, &g);
while (1) {
GPIO_PinState b = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, b == GPIO_PIN_RESET ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_Delay(5); /* basit sıçrama önleme */
}
}Sık yapılan hatalar
- GPIO saatini açmayı unutmak: yazmaçlar hiç değişmez, kod "çalışmıyor" gibi görünür.
- MODER'i |= ile ayarlamak: önceki 2 bit temizlenmezse 01 | 10 = 11 (analog) olur. Önce maskele, sonra yaz.
- ODR'ye kesme ve ana döngüden birlikte |= ile yazmak: yarış durumu. BSRR kullan.
- Nucleo butonunun basılıyken 0 okunduğunu unutmak (dış pull-up'lı, GND'ye çeker).
Yazılım mühendisliği bağlantısı
Bit işlemlerini adlandırılmış küçük fonksiyonlara sar: gpio_set_mode(GPIOA, 5, MODE_OUTPUT).
- Bu fonksiyonlar bir yazmaç yapısı üzerinde çalışırsa (gerçek adres yerine sahte struct), bilgisayarda birim testi yapılabilir: bu simülasyondaki gibi.
- HAL mı yazmaç mı kararını kod incelemesinde gerekçesiyle yaz.
Kendini sına
PA5'i genel amaçlı çıkış yapmak için MODER'in hangi bitlerine ne yazılır?
BSRR = 1 << 21 ne yapar?
ODR |= ... yerine BSRR neden tercih edilir?
Open-drain çıkışa 1 yazılınca pin ne olur (pull-up yoksa)?