Assembly ile ADuC841 – [5] ALT PROGRAM, YIĞIN VE SP SAKLAYICISI
ALT PROGRAM, YIĞIN VE SP SAKLAYICISI
Görsel Rehber
5.1 Alt Program Nedir? Neden Kullanılır?
Bir program yazarken aynı işlemi birden fazla yerde gerçekleştirmemiz gerekebilir. Bu kod bloğunu her ihtiyaç duyulan yere tekrar tekrar yazmak iki önemli probleme yol açar:
- Program hafızasının şişmesi: Aynı kod her yere kopyalanır, mikrodenetleyicinin sınırlı ROM/Flash alanı gereksiz yere harcanır.
- Bakımın zorlaşması: Kodda bir hata bulunduğunda tüm kopyaların tek tek güncellenmesi gerekir.
Çözüm şudur: Tekrarlanan kod bloğunu bir kere yazıp, ihtiyaç duyulduğunda o bloğa dallanmak. Bu bloğa alt program (subroutine) denir.
Alt program çağırma ve dönüş komutları şunlardır:
| Komut | Açıklama | Hafıza Kullanımı | Kapsam |
|---|---|---|---|
acall <alt program ismi> |
Alt program çağrısı | 2 Byte | 2 kByte blok içi |
lcall <alt program ismi> |
Alt program çağrısı | 3 Byte | Tüm 64 kByte |
ret |
Alt programdan çıkış | 1 Byte | — |
Alt program, ilk ret komutuyla karşılaşana kadar çalışmaya devam eder. ret komutu çalıştığında, program akışı tam olarak acall veya lcall komutunun hemen bir altındaki satırdan devam eder.
Kritik Uyarı: Ana program akışı, acall veya lcall komutları kullanılmadan kontrolsüz biçimde alt program koduna girmemelidir. Bu durumda ret komutu yığında geçersiz bir adres bulur ve program tamamen çöker — sebebi aşağıdaki bölümlerde açıklanmaktadır.
Örnek: Temel Alt Program Yapısı
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
org 00h ; Program başlangıç adresi sjmp ana ; Ana programa atla ; === ANA PROGRAM === ana: mov A, #55h ; Akümülatöre değer yükle acall p1_yaz ; Alt programı çağır <-- (1) mov A, #AAh ; Alt programdan döndükten sonra burası çalışır acall p1_yaz ; Alt programı tekrar çağır sjmp ana ; Sonsuz döngü ; === ALT PROGRAM === p1_yaz: mov P1, A ; A'daki değeri P1 portuna yaz ret ; Ana programa geri dön <-- (2) end |
Akış açıklaması:
acall p1_yazkomutu çalışır → programp1_yazetiketine sıçrar. Geri dönüş adresi yığına otomatik kaydedilir.retkomutu çalışır → yığından dönüş adresi okunur, programacall‘dan sonraki satırdan (mov A, #AAh) devam eder.
5.2 SP Saklayıcısı (Stack Pointer — Yığın İşaretçisi)
SP (Stack Pointer), yığının (stack’in) o an en tepesini gösteren özel bir SFR’dir (Özel Fonksiyon Saklayıcısı). 8051 mimarisinde 8-bit genişliğindedir.
Reset sonrasında SP = 07h değerini alır.
Yığın 8051’de 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.
- Her POP işleminde → veri önce okunur, ardından SP 1 azaltılır.
SP iki kritik görevi yerine getirir:
- Dönüş adresini saklamak:
acall/lcallçalıştığında program, alt programdan döndüğünde nereye gideceğini SP’nin gösterdiği RAM adresine yazar. - Geçici veri saklamak: Programcı
push/popkomutlarıyla saklayıcı değerlerini geçici olarak yığına alabilir.
Bellek görünümü — Reset sonrası (SP = 07h):
|
1 2 3 4 5 6 7 8 9 10 |
Adres | İçerik | Açıklama -------|---------|----------------------------- 0Ah | xxh | ← Yığın büyüdükçe buralar dolacak 09h | xxh | 08h | xxh | 07h ◄SP| xxh | ← SP başlangıç noktası (07h) 06h | xxh | Bank 0 — R6 05h | xxh | Bank 0 — R5 ... | ... | 00h | xxh | Bank 0 — R0 |
Dikkat — SP Başlangıç Değeri: Reset sonrası SP = 07h, Bank 0 saklayıcılarının (00h–07h) tam üstüne işaret eder. Eğer programda Banka 1 (08h–0Fh) veya daha üstü saklayıcı bankaları kullanılıyorsa, yığın bu değerlerin üzerine yazabilir. Bu nedenle programın başında SP’yi açıkça güvenli bir bölgeye taşımak gerekir:
|
1 |
mov SP, #2Fh ; Yığın 30h adresinden başlasın |
5.3 Alt Programdan Ana Programa Dönüş Adresine Donanım Tarafından Hesaplanması
ACALL komutu
ACALL komutu program hafızasında 2 Byte yer kaplar. Ç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 otomatik olarak şu işlemleri yapar:
|
1 2 3 4 5 6 |
(PC) ← (PC) + 2 ; ACALL 2 baytlık komut → PC bir sonraki komutu gösteriyor (SP) ← (SP) + 1 ((SP)) ← PC[7..0] ; Dönüş adresinin düşük byte'ı yığına yazılır (SP) ← (SP) + 1 ((SP)) ← PC[15..8] ; Dönüş adresinin yüksek byte'ı yığına yazılır (PC) ← alt_program_adresi ; PC, alt programın başına atlar |
Neden önce PC+2? ACALL 2 baytlık bir komuttur. Komut işlendiğinde PC zaten bir sonraki komutun adresini göstermektedir. Bu adres “geri döneceğimiz yer” olarak yığına yazılır.
LCALL komutu
LCALL komutu program hafızasında 3 Byte yer kaplar. Alt program 64 kByte’lık belleğin herhangi bir adresinde olabilir. Çalışma mantığı acall ile tamamen aynıdır; tek fark PC’nin 3 artırılmasıdır:
|
1 2 3 4 5 6 |
(PC) ← (PC) + 3 ; LCALL 3 baytlık komut (SP) ← (SP) + 1 ((SP)) ← PC[7..0] (SP) ← (SP) + 1 ((SP)) ← PC[15..8] (PC) ← alt_program_adresi |
RET komutu
RET komutu yığından 2 byte okuyarak PC’yi geri yükler ve SP’yi 2 azaltır:
|
1 2 3 4 5 |
PC[15..8] ← ((SP)) ; Yığından yüksek byte alınır (SP) ← (SP) - 1 PC[7..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: acall + lcall + ret (PC ve Yığın Takibi)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
org 00h sjmp x1 x1: ... ; Adres 0203h: acall alt1 ; (1) SP=09h, RAM[08h]=05h, RAM[09h]=02h, PC=1A02h ; Adres 0205h: ← acall buradan 2 byte tüketir, dönüş adresi burası ... ; Adres 03A2h: lcall alt2 ; (2) SP=09h, RAM[08h]=A5h, RAM[09h]=03h, PC=2B3Ch ; Adres 03A5h: ← lcall 3 byte tüketir, dönüş adresi burası ... sjmp x1 ; === ALT PROGRAM 1 (Adres: 1A02h) === alt1: nop nop ret ; Yığından 0205h okunur → PC=0205h, SP=07h ; === ALT PROGRAM 2 (Adres: 2B3Ch) === alt2: nop nop ret ; Yığından 03A5h okunur → PC=03A5h, SP=07h end |
Adım adım yığın ve PC durumu:
| Adım | Komut | PC | SP | RAM[08h] | RAM[09h] |
|---|---|---|---|---|---|
| Başlangıç | — | 0203h | 07h | xxh | xxh |
| acall alt1 çalıştı | acall | 1A02h | 09h | 05h | 02h |
| ret (alt1) çalıştı | ret | 0205h | 07h | 05h | 02h |
| lcall alt2 çalıştı | lcall | 2B3Ch | 09h | A5h | 03h |
| ret (alt2) çalıştı | ret | 03A5h | 07h | A5h | 03h |
5.4 İç İç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 yeni çağrı yığına ekstra 2 byte yazar.
|
1 2 3 4 5 6 7 8 9 |
Ana Program ↓ acall Alt Program 1 ↓ lcall Alt Program 2 ↓ ret Alt Program 1 (kaldığı yerden devam eder) ↓ ret Ana Program (kaldığı yerden devam eder) |
Örnek: alt1 içinden alt2 çağrısı
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
org 00h sjmp ana ana: acall alt1 ; 0203h → SP=09h, yığın: 05h,02h, PC=1A02h ; 0205h: dönüş buraya sjmp ana ; === ALT PROGRAM 1 (Adres: 1A02h) === alt1: lcall alt2 ; 1B20h → SP=0Bh, yığın üstü: 23h,1Bh, PC=2B3Ch ; 1B23h: alt1 içindeki dönüş noktası ret ; Yığından 0205h okunur → SP=07h, PC=0205h ; === ALT PROGRAM 2 (Adres: 2B3Ch) === alt2: nop ret ; Yığından 1B23h okunur → SP=09h, PC=1B23h end |
İç içe çağrılarda yığın büyümesi:
| Durum | SP Değeri | Açıklama |
|---|---|---|
| Reset sonrası | 07h | Yığın boş |
| acall alt1 çalıştı | 09h | 2 byte eklendi (dönüş: 0205h) |
| lcall alt2 çalıştı | 0Bh | 2 byte daha eklendi (dönüş: 1B23h) |
| ret (alt2) çalıştı | 09h | 2 byte silindi |
| ret (alt1) çalıştı | 07h | 2 byte silindi, yığın boş |
Dikkat: 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.
5.5 PUSH ve POP Komutları
Bütün 8051 ürünlerinde yığın (stack) hafıza, tümdevre-üstü RAM’da yer alır ve yukarıya doğru büyür.
PUSH komutu: Önce SP’yi 1 artırır, sonra SP ile işaret edilen hücreye bir byte kopyalar.
POP komutu: PUSH işleminin tersini gerçekleştirir. SP ile işaret edilen hücreden bir byte okur ve SP’yi 1 azaltır.
| Komut | 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) |
Neden PUSH/POP kullanılır? Alt program çalışırken ana programın kullandığı saklayıcılar (A, B, PSW vb.) bozulabilir. push ile bu değerleri yığına saklayabilir, alt program bitince pop ile geri yükleyebilirsiniz.
Örnek: Saklayıcı Koruma
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
hesap_yap: ; --- Alt programa girerken saklayıcıları koru --- push ACC ; SP: 07→08, RAM[08h] = ACC değeri push B ; SP: 08→09, RAM[09h] = B değeri push PSW ; SP: 09→0A, RAM[0Ah] = PSW değeri ; --- Alt programın kendi işlemleri --- mov A, #10h mov B, #05h mul AB ; A × B = 0050h ; --- Saklayıcıları geri yükle (TERS SIRAYLA!) --- pop PSW ; SP: 0A→09 pop B ; SP: 09→08 pop ACC ; SP: 08→07 ret |
LIFO Kuralı — En Önemli Kural: Yığın LIFO (Last In, First Out — Son Giren İlk Çıkar) yapısına sahiptir.
push ACC → push B → push PSW sırasıyla itildiyse, mutlaka pop PSW → pop B → pop ACC sırasıyla çekilmelidir.
Sıra bozulursa yanlış değerler yüklenir ve program hatalı çalışır.
Not: PUSH ve POP komutları, saklayıcıları belirlemek için yalnızca doğrudan (direct) adresleme modunu kullanır. Yığının kendisine ise SP saklayıcısını kullanarak dolaylı adresleme ile erişilir. Bu yüzden yığının Yüksek 128 bölümde yer almamasına dikkat edilmelidir. Yüksek 128 olmayan ürünlerde (SP bu bölgeye işaret ederse) PUSH ile yazılmak istenen byte’lar kaybolur, POP ile okunan byte’lar ise belirsizdir.
5.6 Program Çökmelerinin Olası Nedenleri
Alt programdan çıkılıp ana programa dönülürken, dönüş adresi donanım tarafından SP içeriğinden okunarak PC’ye yüklenir. Bu değerin hatalı hesaplanması durumunda program akışı bozulur. Buna program çökmesi denir.
Program çökmesinin 3 olası nedeni vardır:
Neden 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.
|
1 2 3 4 5 6 7 8 |
; Durum: acall alt1 çalıştı → SP=09h, RAM[08h]=05h, RAM[09h]=02h ; Dönüş adresi 0205h yığında saklı alt1: mov 09h, #0Ah ; 09h adresine 0Ah yazıldı! ; Yüksek byte bozuldu: 02h → 0Ah ret ; PC = 0A05h olur! (0205h olması gerekirdi) ; Program yanlış adrese atlar → ÇÖKÜŞ |
Neden 2 — PUSH ve POP Komutlarının Hatalı Kullanılması
2.1 — pop yapılmadan ret:
Alt programda 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.
|
1 2 3 4 5 6 7 8 9 |
; Durum: SP=09h, RAM[08h]=05h, RAM[09h]=02h (dönüş: 0205h) alt1: mov 30h, #0Ah push 30h ; SP → 0Ah, RAM[0Ah] = 0Ah ; ... pop yapılmadı! ret ; SP=0Ah → yüksek byte = RAM[0Ah] = 0Ah ; SP=09h → düşük byte = RAM[09h] = 02h ; PC = 0A02h! (0205h olması gerekirdi) → ÇÖKÜŞ |
2.2 — push yapılmadan pop:
Benzer şekilde, gereğinden fazla pop yapılırsa SP çok azalır ve ret yanlış byte’ları okur. Her push için mutlaka eşleşen bir pop olmalıdır.
Neden 3 — SP’nin Reset Sonrası Yanlış Bölgeye İşaret Etmesi
Reset sonrası SP = 07h’dir. Bu değer, Banka 0’ın son byte’ının (R7) hemen üstüne işaret eder. Programda Banka 1 (08h–0Fh) veya daha yukarısı kullanılıyorsa, yığın bu saklayıcıların üzerine yazar.
Ayrıca bazı programlarda SP, programın başında mov sp, #7Ah gibi bir komutla yanlış bir bölgeye kaydırılır. Bu bölgede gerçek bir RAM yoksa (veya başka veriler için ayrılmışsa), yığın taşması yaşanır.
|
1 2 3 4 5 6 7 8 9 |
; Doğru kullanım — SP'yi güvenli bölgeye taşı org 00h mov SP, #2Fh ; Yığın 30h adresinden itibaren büyüsün ; 00h–2Fh arası değişkenler için ayrıldı sjmp ana ana: ; ... program kodları ... |
Örnek — SP Başlangıç Değeri Hatası:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
; Senaryo: alt1 içinde "mov 09h, #0Ah" ile dönüş bölgesi bozuluyor org 00h sjmp x1 x1: ... ; 0203h: acall alt1 ; Dönüş adresi 0205h → RAM[08h]=05h, RAM[09h]=02h ; 0205h: ... ; 0A05h: ← Yanlış bölge! ... ; 1A02h: alt1: mov 09h, #0Ah ; RAM[09h] = 0Ah (02h olması gerekirdi) ret ; PC = 0A05h → Program çöküyor |
5.7 Yığın İşaretçisi (Stack Pointer, SP) — ADUC841 Genişletmesi
Standart INTEL-8051 ve INTEL-8052 mimarisinde SP 8-bit genişliğinde olup yalnızca dahili 256-byte RAM’i (00h–FFh) adresleyebilir.
ADUC841 mikrodenetleyicisinde ise CFG841 SFR’inin 7. biti olan EXSP biti setlenerek SP 11-bit genişliğine çıkarılabilir.
- EXSP = 0 (varsayılan): SP 8-bit, standart 8051 gibi çalışır.
- EXSP = 1: SP 11-bit olur. SP’nin düşük 8-bit’i
SPSFR’inde, yüksek 3-bit’i iseSPHSFR’inde tutulur. Adreslenebilecek alan 0000h – 07FFh (2048 Byte) olur.
CFG841 Saklayıcısı
|
1 2 |
Bit: 7 6 5 4 3 2 1 0 [EXSP] [PWPO] [DBUF] [EPM2] [EPM1] [EPM0] [MSPI] [XRAMEN] |
| Bit | İsim | Açıklama |
|---|---|---|
| 7 | EXSP | Setlendiğinde 11-bit SP aktif olur. Temizlendiğinde standart 8-bit SP çalışır. |
| 6 | PWPO | P3.6 ve P3.7 pinleri PWM çıkışı olarak atanır. |
| 5 | DBUF | Dahili DAC çıkış tamponları bypass edilir. |
| 4–2 | EPM2, EPM1, EPM0 | PWM saat frekansı bölme faktörünü belirler. |
| 1 | MSPI | SPI biriminin MISO, MOSI, SCLOCK fonksiyonlarını P3.3, P3.4, P3.5 pinlerine atar. |
| 0 | XRAMEN | Dahili XRAM kullanıma açılır ve harici veri hafızasının alt 2 kByte’ına yerleştirilir. |
Not: CFG841 SFR’i bit adreslenemez. Bu nedenle setb CFG841.7 gibi bit komutları kullanılamaz. Bunun yerine orl komutuyla ilgili bit setlenir.
11-bit SP Ayarlama Örneği
|
1 2 3 4 5 6 7 8 9 |
; === SP'yi 11-bit'e genişlet ve XRAM bölgesine taşı === orl cfg841, #80h ; CFG841.7 (EXSP) = 1 → 11-bit SP aktif mov sph, #01h ; SP yüksek 3-bit = 01h ─┐ mov sp, #00h ; SP düşük 8-bit = 00h ─┘ SP = 0100h ; Artık yığın 0100h adresinden büyümeye başlayacak. ; Dahili RAM (00h–FFh) tamamen değişkenler ve saklayıcılar için serbesttir. |
8-bit SP ile 11-bit SP Karşılaştırması
| Özellik | 8-bit SP (EXSP=0) | 11-bit SP (EXSP=1) |
|---|---|---|
| Adres aralığı | 00h – FFh | 0000h – 07FFh |
| SP kaydedildiği yer | Yalnızca SP SFR | SP (düşük 8-bit) + SPH (yüksek 3-bit) |
| Yığın alanı | 256-byte dahili RAM | 2048-byte (RAM + XRAM) |
| Kullanım amacı | Standart uygulamalar | Derin iç içe çağrılar, büyük yığın ihtiyacı |
PWM Frekansı Bölme Tablosu (EPM2–EPM0 bitleri):
| EPM2 | EPM1 | EPM0 | Bölme Faktörü |
|---|---|---|---|
| 0 | 0 | 0 | 32 |
| 0 | 0 | 1 | 64 |
| 0 | 1 | 0 | 128 |
| 0 | 1 | 1 | 256 |
| 1 | 0 | 0 | 512 |
| 1 | 0 | 1 | 1024 |
ÖZET — 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 |
ret |
Yığından dönüş adresini okur, döner | Yığın bozuksa çökme kaçınılmaz |
push |
SP++ → RAM[SP] = veri | Her PUSH için 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 |
Dipnot: ret komutu her zaman alt programların sonunda bulunmalıdır. Kesme hizmeti alt programlarından (ISR) çıkarken ise reti komutu kullanılır; ret değil. Bu iki komut birbirine karıştırılmamalıdır.

