8051 / ADUC841 — Assembly Dili

Alt Program, Yığın ve SP Saklayıcısı

Bölüm 5: ACALL · LCALL · RET · PUSH · POP · Stack Pointer · Program Çökmeleri · CFG841

Alt Program Nedir? Neden Kullanılır?

Bir program yazarken aynı işlemi birden fazla kez gerçekleştirmemiz gerekebilir. Bu işlemi her ihtiyaç duyulan yere tekrar tekrar yazmak, iki önemli soruna yol açar:

Çözüm: Tekrarlanan kod bloğunu bir kere yazıp, ihtiyaç duyulduğunda bu bloğa dallanmak. Bu bloğa alt program (subroutine) denir.

💡
Temel Fikir

Ana program akışı bir ACALL veya LCALL komutuyla alt programı çağırır. Alt program biter bitmez RET komutu çalışır ve program akışı tam olarak çağrıldığı noktanın hemen sonrasına döner.

Temel Komut Özeti

Komut İşlev Hafıza Kullanımı Kapsam
ACALL <isim> Alt program çağrısı 2 Byte (program hafızasında) 2 kByte blok içi
LCALL <isim> Alt program çağrısı 3 Byte (program hafızasında) Tüm 64 kByte
RET Alt programdan çıkış/dönüş 1 Byte

Basit Örnek — Alt Program Çağrısı ve Dönüşü

Aşağıdaki örnek, P1 çıkışını belirleyen bir alt programın nasıl çağrılacağını göstermektedir.

Assembly Alt program çağrısı — temel yapı
          org  00h          ; Program başlangıç adresi
          sjmp ana

; ─── ANA PROGRAM ───────────────────────────────────────
ana:
          mov  A, #55h     ; Akümülatöre değer yükle
          acall cikis_ayarla ; Alt programı çağır  ①
          mov  A, #AAh     ; Alt programdan döndükten sonra ②
          acall cikis_ayarla ; Alt programı tekrar çağır
          sjmp ana         ; Sonsuz döngü

; ─── ALT PROGRAM ────────────────────────────────────────
cikis_ayarla:
          mov  P1, A       ; A'daki değeri P1 portuna yaz
          ret              ; Ana programa geri dön  ③

          end

ACALL cikis_ayarla — Program akışı cikis_ayarla etiketine sıçrar. Geri dönüş adresi yığına (stack'e) otomatik kaydedilir.

Dönüş noktasıret çalıştığında PC, acall'dan bir sonraki komut olan mov A, #AAh satırına yüklenir.

RET — Yığından dönüş adresini okuyarak ana programa döner. Hafızaya hiçbir şey yazmaz.

Kritik Kural

Ana program akışı, ACALL veya LCALL komutları kullanılmadan kontrolsüz biçimde alt program koduna girmemelidir. Aksi hâlde RET komutu yığından geçersiz bir adres okur ve program çöker — hatalı bir adrese dallanılır.


SP Saklayıcısı (Stack Pointer — Yığın İşaretçisi)

SP (Stack Pointer), yığının (stack'in) o an kullanılan en üst hücresini gösteren özel bir SFR (Özel Fonksiyon Saklayıcısı) kaydedicisidir. 8051 mimarisinde 8-bit genişliğindedir ve reset sonrasında 07h değerini alır.

📌
Yığın Nasıl Büyür?

8051 yığını yukarı doğru büyür: her PUSH işleminde SP önce 1 artırılır, ardından veri o adrese yazılır. POP işleminde ise veri okunur, sonra SP 1 azaltılır.

SP'nin Görevi

SP iki kritik görevi yerine getirir:

Bellek Diyagramı — SP ve Yığın

Dahili RAM — Reset Sonrası (SP = 07h)
0Ah
xxh
← Yığın büyüdükçe buralar dolacak
09h
xxh
08h
xxh
07h ▶
xxh
← SP başlangıç değeri (07h)
06h
xxh
Bank 0 — R6
05h
xxh
Bank 0 — R5
04h
xxh
Bank 0 — R4
00h
xxh
Bank 0 — R0
⚠️
Dikkat — SP Başlangıç Değeri

Reset sonrası SP = 07h'dir. Bu değer, Bank 0 saklayıcılarının (00h–07h) hemen üstüne işaret eder. Eğer başka saklayıcı banklarını (Bank 1: 08h–0Fh, Bank 2: 10h–17h) kullanıyorsanız, SP'yi bu bölgenin üstüne, örneğin mov SP, #2Fh ile kaydırmanız gerekir.


ACALL, LCALL ve RET — Komut Detayları

ACALL — Kısa Mesafeli Alt Program Çağrısı

ACALL (Absolute CALL) komutu program hafızasında 2 Byte yer kaplar ve çağrılacak alt program, ACALL komutunun bulunduğu 2 kByte'lık blok içinde olmalıdır.

ACALL Çalıştığında Donanımın Yaptıkları (Adım Adım)

Sözde Kod ACALL iç işleyişi
; 1. PC önce 2 artırılır (ACALL 2 baytlık komut)
(PC) ← (PC) + 2

; 2. SP bir artırılır, PCL yığına atılır
(SP) ← (SP) + 1
; Yığındaki SP adresi ← PC'nin düşük byte'ı (PC7..0)
((SP)) ← PC7..0

; 3. SP tekrar artırılır, PCH yığına atılır
(SP) ← (SP) + 1
; Yığındaki SP adresi ← PC'nin yüksek byte'ı (PC15..8)
((SP)) ← PC15..8

; 4. PC, alt programın başlangıç adresine yüklenir
(PC) ← alt_program_adresi
🔍
Neden Önce PC+2?

ACALL 2 baytlık bir komuttur. PC bu 2 baytı "tükettiği" için ACALL koşturulduğunda PC zaten bir sonraki komutun adresini göstermektedir. Bu adres dönüş adresi olarak yığına yazılır.

LCALL — Uzun Mesafeli Alt Program Çağrısı

LCALL (Long CALL) komutu program hafızasında 3 Byte yer kaplar. Alt program 64 kByte'lık program belleğinin herhangi bir adresinde olabilir. Çalışma mantığı ACALL ile aynıdır; tek fark PC'nin 3 artırılmasıdır.

Sözde Kod LCALL iç işleyişi
(PC)   ← (PC) + 3          ; LCALL 3 baytlık komut
(SP)   ← (SP) + 1
((SP)) ← PC7..0           ; Düşük byte yığına
(SP)   ← (SP) + 1
((SP)) ← PC15..8          ; Yüksek byte yığına
(PC)   ← alt_program_adresi

RET — Alt Programdan Dönüş

RET komutu yığından 2 byte okuyarak PC'yi geri yükler ve SP'yi 2 azaltır. Böylece program akışı ACALL/LCALL'dan sonraki komuta döner.

Sözde Kod RET iç işleyişi
PC15..8  ← ((SP))          ; Yığından yüksek byte alınır
(SP)    ← (SP) - 1
PC7..0   ← ((SP))          ; Yığından düşük byte alınır
(SP)    ← (SP) - 1
; PC artık dönüş adresini gösteriyor — program oradan devam eder

Tam Örnek — İki Alt Programlı Uygulama

Aşağıda hem acall hem de lcall kullanan bir örnek verilmiştir. Program hafıza adresleri yanında, yığın ve PC değişimleri adım adım takip edilmektedir.

Assembly Örnek: acall + lcall + ret
          org  00h
          sjmp x1

x1:   ; … ana program komutları …

; Adres 0203h
          acall alt1       ; ① PC=0203h → SP+2 → yığın: 05h,02h → PC=1A02h
; Adres 0205h  (acall 2 byte → sonraki komut 0205h)
          nop              ; alt1'den döndükten sonra buraya gelinir

; Adres 03A2h
          lcall alt2       ; ② PC=03A2h → SP+2 → yığın: A5h,03h → PC=2B3Ch
; Adres 03A5h  (lcall 3 byte → sonraki komut 03A5h)
          nop

          sjmp x1

; ─── ALT PROGRAM 1 ─────────────────────────────────────
alt1:  ; Başlangıç adresi: 1A02h
          nop
          nop
          ret              ; Yığından 0205h okunur → PC=0205h

; ─── ALT PROGRAM 2 ─────────────────────────────────────
alt2:  ; Başlangıç adresi: 2B3Ch
          nop
          nop
          ret              ; Yığından 03A5h okunur → PC=03A5h

          end

Yığın Durumu — Adım Adım

Adım Komut PC SP RAM[08h] RAM[09h] Açıklama
Başlangıç 0203h 07h xxh xxh Reset sonrası durum
ACALL alt1 acall 1A02h 09h 05h 02h Dönüş adresi 0205h yığına yazıldı (PCL=05h → 08h, PCH=02h → 09h)
RET (alt1) ret 0205h 07h 05h 02h Yığından 0205h okundu, SP geri 07h'e döndü
LCALL alt2 lcall 2B3Ch 09h A5h 03h Dönüş adresi 03A5h yığına yazıldı (PCL=A5h → 08h, PCH=03h → 09h)
RET (alt2) ret 03A5h 07h A5h 03h Yığından 03A5h okundu, SP geri 07h'e döndü

İç İçe Alt Program Yapısı

Bir alt programın içinden başka bir alt program çağrılabilir. Buna iç içe (nested) alt program denir. Her çağrı yığına ek 2 byte yazar.

Ana Program
↓ acall
Alt Program 1
↓ lcall / acall
Alt Program 2
↓ ret
Alt Program 1
↓ ret
Ana Program

İç İçe Örnek

Assembly alt1 içinden alt2 çağrısı
          org  00h
          sjmp ana

ana:
          acall alt1       ; Adres 0203h: SP=09h, yığın: 05h,02h
; 0205h — dönüş buraya
          sjmp  ana

alt1:  ; Adres 1A02h
          lcall alt2       ; Adres 1B20h: SP=0Bh, yığın: 23h,1B (üst 2 byte)
; 1B23h — alt1 içindeki dönüş noktası
          ret              ; SP→09h, PC=0205h

alt2:  ; Adres 2B3Ch
          nop
          ret              ; SP→09h, PC=1B23h

          end

İç İçe Çağrılarda Yığın Büyümesi

DurumSP DeğeriYığındaki Veri
Reset sonrası07h
acall alt1 çalıştı09h08h=05h, 09h=02h (dönüş: 0205h)
lcall alt2 çalıştı0Bh0Ah=23h, 0Bh=1Bh (dönüş: 1B23h)
ret (alt2) çalıştı09hÜst 2 byte temizlendi (mantıksal)
ret (alt1) çalıştı07hYığın boş
⚠️
İç İçe Derinlik Sınırı

Her iç içe çağrı yığına 2 byte ekler. Dahili RAM yalnızca 256 byte'tır. Gereksiz derinlikte iç içe çağrıdan kaçınılmalıdır. Yığın taşarsa program çöker.


PUSH ve POP Komutları

PUSH ve POP, programcı tarafından saklayıcı değerlerini yığına geçici olarak almak/geri yüklemek için kullanılan komutlardır.

Komut İşlem (Sözde Kod) Adresleme Modu
PUSH <kaynak> SP ← SP+1 ; RAM[SP] ← kaynak Doğrudan (Direct)
POP <hedef> hedef ← RAM[SP] ; SP ← SP-1 Doğrudan (Direct)
ℹ️
Adresleme Modu

PUSH ve POP yalnızca doğrudan (direct) adresleme modunu kullanır. Yani saklayıcıların SFR veya RAM adresini açıkça belirtmeniz gerekir. Örneğin push ACC yerine push 0E0h da yazılabilir (ACC'nin SFR adresi 0E0h'dir).

Neden PUSH/POP Kullanılır?

Alt program çalışırken ana programın kullandığı saklayıcılar (A, B, R0…R7 vb.) bozulabilir. PUSH ile bu değerleri yığına saklayabilir, alt program bitince POP ile geri yükleyebilirsiniz.

Assembly PUSH/POP ile saklayıcı koruma
hesap_yap:
          ; ─ Alt programa girerken saklayıcıları koru ─
          push ACC         ; Akümülatörü yığına at  (SP:07→08)
          push B           ; B saklayıcısını yığına at (SP:08→09)
          push PSW         ; Program Status Word'ü yığına at (SP:09→0A)

          ; ─ Alt programın kendi işlemleri ─
          mov  A, #10h
          mov  B, #05h
          mul  AB          ; A×B = 0050h  (A=50h, B=00h)

          ; ─ Ana programın saklayıcılarını geri yükle ─
          pop  PSW         ; POP sırasına dikkat! LIFO — Son giren ilk çıkar
          pop  B
          pop  ACC
          ret
🚨
LIFO Kuralı — PUSH Sırası = Ters POP Sırası

Yığın LIFO (Last In, First Out) yapısına sahiptir. push ACCpush Bpush PSW sırasıyla itildiyse, mutlaka pop PSWpop Bpop ACC sırasıyla çekilmelidir. Sıra bozulursa yanlış değerler yüklenir ve program hatalı çalışır.

PUSH/POP Bellek Diyagramı

push ACC → push B → push PSW sırası (Başlangıç SP=07h)
0Ah ▶
PSW
← SP=0Ah (push PSW sonrası)
09h
B
← SP=09h (push B sonrası)
08h
ACC
← SP=08h (push ACC sonrası)
07h
xxh
← Başlangıç SP=07h

Program Çökmelerinin Olası Nedenleri

Alt program çağrısı ve dönüşü sırasında dönüş adresi yığın üzerinden hesaplanır. Bu hesaplamanın hatalı olduğu durumlarda program akışı bozulur ve program çöker. Üç temel çökme nedeni şunlardır:

1 — SP'nin Kullandığı Hafıza Alanına Kontrolsüz Erişim

Programcı, yığın alanı olarak ayrılmış RAM bölgesine doğrudan bir MOV komutuyla veri yazarsa, yığındaki dönüş adresi bozulur.

Assembly ❌ Hatalı: Yığın bölgesine doğrudan yazma
alt1:
          ; SP = 09h, RAM[08h]=05h, RAM[09h]=02h  (dönüş: 0205h)
          mov  09h, #0Ah  ; ❌ 09h adresine 0Ah yazıldı!
                                    ;    Yüksek byte bozuldu: 02h → 0Ah
          ret              ; PC = 0A05h olur! (0205h değil)
                                    ; Program yanlış adrese gider → ÇÖKÜŞ

2 — PUSH ve POP Komutlarının Hatalı Kullanılması

2.1 — Eşleşmeyen PUSH

Alt programda bir adet PUSH kullanılıp karşılığı POP yapılmadan RET çalışırsa, SP doğru konumda değildir. RET, yığında dönüş adresi yerine PUSH edilen veriyi okur.

Assembly ❌ Hatalı: Pop yapılmadan ret
alt1:  ; Giriş: SP=09h, RAM[08h]=05h, RAM[09h]=02h
          mov  30h, #0Ah
          push 30h         ; SP → 0Ah, RAM[0Ah] = 0Ah
          ; ... pop yapılmadı!
          ret              ; SP=0Ah → PC yüksek byte = RAM[0Ah] = 0Ah
                           ;           SP=09h → PC düşük  byte = RAM[09h] = 02h
                           ; PC = 0A02h!  (0205h olması gerekirdi) → ÇÖKÜŞ

2.2 — POP ile SP Yanlış Bırakılırsa

Benzer şekilde, gereğinden fazla POP yapılırsa SP gereğinden çok azalır ve RET yanlış byte'ları okur.

3 — SP'nin Yanlış Bölgeye İşaret Etmesi (Reset Sonrası)

Reset sonrası SP = 07h'dir. Bu değer, saklayıcı bankı 0'ın son byte'ını (R7) gösterir. İlk PUSH işleminde SP = 08h olur ve bu artık Banka 1'in R0 saklayıcısıdır. Eğer programda Banka 1 kullanılıyorsa, yığın bu değerleri silecektir.

Assembly ✅ Doğru: SP'yi güvenli bir bölgeye taşı
          org  00h
          ; ─── SP'yi güvenli bölgeye ayarla ───────────────────────
          mov  SP, #2Fh    ; Yığın 30h adresinden başlasın
                           ; (00h–2Fh arası değişkenler için ayrıldı)

          sjmp ana

ana:
          ; ... program kodları ...
İyi Pratik

Her programın başında SP'yi açıkça belirleyin. Değişkenler için 20h–2Fh arasını kullanıyorsanız, mov SP, #2Fh ile yığını bu bölgenin hemen üstüne başlatın. Böylece yığın ve değişken alanlarının üst üste gelmesi önlenir.


Yığın İşaretçisini Genişletme — CFG841 ve EXSP Biti

Standart 8051/8052 mimarisinde SP 8-bit genişliğinde olup yalnızca dahili 256-byte RAM'i adresleyebilir. Ancak ADUC841 mikrodenetleyicisinde, CFG841 SFR'inin 7. biti olan EXSP biti setlenerek SP 11-bit genişliğine çıkarılabilir.

CFG841 Saklayıcısı — Bit Yapısı

Bit 7
EXSP
SP Genişlet
Bit 6
PWPO
PWM Çıkış
Bit 5
DBUF
DAC Tampon
Bit 4
EPM2
PWM Frek.
Bit 3
EPM1
PWM Frek.
Bit 2
EPM0
PWM Frek.
Bit 1
MSPI
SPI Pin
Bit 0
XRAMEN
XRAM Aç
📌
CFG841 Bit Adreslenemez

CFG841 SFR'i bit adreslenebilir değildir. Bu nedenle setb CFG841.7 gibi bit komutları kullanılamaz. Bunun yerine orl komutuyla ilgili bit setlenir.

8-bit SP ile 11-bit SP Karşılaştırması

Özellik 8-bit SP (EXSP=0) 11-bit SP (EXSP=1)
Kapsam 00h–FFh (256 byte dahili RAM) 0000h–07FFh (2048 byte)
SP kaydedildiği yer SP SFR (8-bit) SP (düşük 8-bit) + SPH SFR (yüksek 3-bit)
Yığın alanı Yalnızca dahili RAM 00h–FFh dahili RAM + 100h–7FFh XRAM
Kullanım amacı Standart uygulamalar Derin iç içe çağrılar, büyük yığın ihtiyacı

11-bit SP Ayarlama Örneği

Assembly EXSP biti setleme ve SP'yi 0100h'e taşıma
; ─── 11-bit SP'yi aktif et ──────────────────────────────
orl  cfg841, #80h   ; CFG841.7 (EXSP) = 1  →  11-bit SP aktif

; ─── SP'yi XRAM bölgesine taşı ─────────────────────────
mov  sph, #01h       ; SP yüksek 3-bit = 01h  ┐
mov  sp,  #00h       ; SP düşük  8-bit = 00h  ┘  SP = 0100h

; Artık yığın XRAM'ın 0100h adresinden büyüyecek.
; Dahili RAM (00h–FFh) tamamen değişken ve saklayıcı
; için serbesttir.
⚠️
XRAMEN Bitine Dikkat

CFG841'in 0. biti olan XRAMEN setlendiğinde, dahili XRAM kullanıma açılır ve harici veri hafızasının alt 2 kByte'lık alanıyla örtüşür. 11-bit SP ile birlikte XRAM'ı yığın için kullanmak istiyorsanız bu durumu göz önüne alınız.

Hafıza Haritası — 8-bit vs 11-bit SP

EXSP=0 (8-bit SP)
FFh
Yığın
08h
Yığın
07h
SP=07h
00h
Reg/Veri
Dahili 256-Byte RAM
EXSP=1 (11-bit SP) — XRAM
07FFh
Yığın
0100h
SP=0100h
00FFh
Veri
0000h
Reg/Veri
Dahili RAM + XRAM (2048-Byte)

Hızlı Başvuru Tablosu

Komut / Kavram Ne Yapar? Dikkat Edilecek Nokta
ACALL Alt program çağrısı (2-Byte, 2kB kapsam) Alt program aynı 2kB blokta olmalı
LCALL Alt program çağrısı (3-Byte, 64kB kapsam) Her yerde kullanılabilir; biraz daha büyük kod
RET Yığından dönüş adresini okur, alt programdan çıkar Yığın bozuksa çökme kaçınılmaz
PUSH SP++ → RAM[SP] = veri Her PUSH'a karşılık mutlaka bir POP olmalı
POP veri = RAM[SP] → SP-- LIFO sırası korunmalı
SP Yığının tepesini gösteren 8-bit SFR Reset=07h; programda açıkça ayarlanmalı
EXSP (CFG841.7) SP'yi 11-bit'e genişletir (ADUC841) Bit adreslenemez; orl ile setlenir