Mikrodenetleyici adım adım nasıl başlatılır ve başlatılır?


19

C kodu yazıldığında, derlendiğinde ve bir mikro denetleyiciye yüklendiğinde, mikro denetleyici çalışmaya başlar. Ancak, bu yükleme ve başlatma işlemini yavaş çekimde adım adım alırsak, MCU'da (bellek, CPU, önyükleyici) gerçekten neler olduğu hakkında bazı karışıklıklar yaşıyorum. Birisi bana soracak olsaydı cevaplayacağım şey (büyük olasılıkla yanlış):

  1. Derlenmiş ikili kod USB üzerinden flash ROM'a (veya EEPROM'a) yazılır
  2. Bootloader bu kodun bir kısmını RAM'e kopyalar. Doğruysa, önyükleyici ne kopyalayacağını (ROM'un hangi kısmını RAM'a kopyalayacağını) nasıl bilir?
  3. CPU, ROM ve RAM'den talimatlar ve kod verileri almaya başlar

Bu yanlış mı?

Bu önyükleme ve başlatma işlemini bellek, önyükleyici ve CPU'nun bu aşamada nasıl etkileşime girdiğiyle ilgili bazı bilgilerle özetlemek mümkün müdür?

Bir bilgisayarın BIOS üzerinden nasıl başlatıldığına dair birçok temel açıklama buldum. Ancak mikrodenetleyici başlatma işlemine takılı kaldım.

Yanıtlar:


32

1) derlenmiş ikili balo / flash evet yazılır. USB, seri, i2c, jtag, vb. Önyükleme işlemini anlamak için aygıta o aygıt tarafından neyin desteklendiğine bağlıdır.

2) Bu genellikle bir mikrodenetleyici için geçerli değildir, birincil kullanım durumu rom / flash ve ram içindeki verilere sahip olmaktır. Ne mimarisi olursa olsun. mikrodenetleyici olmayan bir bilgisayar için, bilgisayarınız, dizüstü bilgisayarınız, sunucunuz, program kalıcı (disk) 'den ram'a kopyalanır ve oradan çalıştırılır. Bazı mikrodenetleyiciler, tanımı ihlal etmiş gibi görünse de harvard iddiasında olanlar bile koç kullanmanıza izin verir. Harvard hakkında, koçu talimat tarafına eşleştirmenizi engelleyen hiçbir şey yoktur, sadece güç arttıktan sonra talimatları almak için bir mekanizmaya sahip olmanız gerekir (bu, tanımı ihlal eder, ancak harvard sistemleri bunu yararlı olmak için diğer mikrodenetleyicilerden daha fazla).

3) bir çeşit.

Her işlemci, tasarlandığı gibi deterministik bir şekilde "çizer". En yaygın yol, açıldıktan sonra çalıştırılacak ilk talimatların adresinin sıfırlama vektöründe olduğu, donanımın okuduğu ve daha sonra çalışmaya başlamak için bu adresi kullandığı bir vektör tablosudur. Diğer genel yol, işlemcinin iyi bilinen bir adreste bir vektör tablosu olmadan yürütmeye başlamasını sağlamaktır. Bazen çipin "kayışları", sıfırlamayı bırakmadan önce yüksek veya düşük bağlayabileceğiniz, mantığın farklı yollarla önyükleme yapmak için kullandığı bazı pimler olacaktır. CPU'nun kendisini, işlemci çekirdeğini sistemin geri kalanından ayırmanız gerekir. İşlemcinin nasıl çalıştığını anlayın ve yonga / sistem tasarımcılarının işlemcinin dışında çevresinde adres kod çözücüleri olduğunu anlayın, böylece cpus adres alanının bir kısmı bir flaşla iletişim kurar, ve bazıları ram ve bazıları çevre birimleri ile (uart, i2c, spi, gpio, vb.). İsterseniz aynı işlemci çekirdeğini alabilir ve farklı şekilde sarabilirsiniz. Kol veya mips bazlı bir şey satın aldığınızda elde ettiğiniz şey budur. kol ve mips, çip insanların kendi eşyalarını satın aldıkları ve etrafına saladıkları cpu çekirdekleri yaparlar, çeşitli nedenlerle bu şeyleri markadan markaya uyumlu hale getirmezler. Bu nedenle, çekirdek dışındaki herhangi bir şey söz konusu olduğunda nadiren genel bir kol sorusu sorabilir.

Bir mikrodenetleyici bir çip üzerinde bir sistem olmaya çalışır, bu yüzden onun uçucu olmayan belleği (flash / rom), uçucu (sram) ve cpu, çevre birimlerinin bir karışımı ile birlikte aynı çip üzerindedir. Ancak çip dahili olarak, flaşın cpu'nun önyükleme özelliklerine uyan adres alanına eşleneceği şekilde tasarlanmıştır. Örneğin cpu'nun 0xFFFC adresinde bir sıfırlama vektörü varsa, o zaman 1) aracılığıyla programlayabileceğimiz bu adrese yanıt veren flash / rom olması ve yararlı programlar için adres alanında yeterli flash / rom olması gerekir. Bir çip tasarımcısı, bu gereksinimleri karşılamak için 0xF000'den başlayarak 0x1000 bayt flaş kullanmayı seçebilir. Ve belki de daha düşük bir adrese ya da belki 0x0000'e ve ortada bir yerde çevre birimlerine bir miktar koç koydular.

Başka bir cpu mimarisi sıfır adresinde çalışmaya başlayabilir, bu yüzden tersini yapmaları gerekir, flaşı sıfır civarındaki bir adres aralığına cevap verecek şekilde yerleştirin. örneğin 0x0000 ila 0x0FFF diyelim. ve sonra başka bir koç koydum.

Çip tasarımcıları cpu'nun nasıl önyüklendiğini biliyor ve oraya kalıcı depolama (flash / rom) yerleştirdiler. Daha sonra, bu cpu'nun iyi bilinen davranışıyla eşleşmesi için önyükleme kodunu yazmak yazılım arkadaşlarına kalmıştır. Sıfırlama vektör adresini sıfırlama vektörüne ve önyükleme kodunuzu sıfırlama vektöründe tanımladığınız adrese yerleştirmelisiniz. Alet zinciri burada size çok yardımcı olabilir. bazen, esp ile nokta ve tıklama ides veya diğer sandboxes onlar sizin için işin çoğunu yapabilir tek yaptığınız apis üst düzey bir dilde (C) çağırmaktır.

Ancak, flash / rom'a yüklenen programın cpu'nun kablolu kablolu önyükleme davranışıyla eşleşmesi gerekir. Main () programının C kısmından önce ve eğer giriş noktanız olarak main kullanıyorsanız bazı şeylerin yapılması gerekir. AC programcısı, başlangıç ​​değeri olan bir değişkeni bildirirken, bunun gerçekten çalışmasını beklediğini varsayar. Sabit değişkenler dışındaki değişkenler koç halindedir, ancak başlangıç ​​değerine sahip bir değişkeniniz varsa, bu ilk değerin uçucu olmayan koçta olması gerekir. Yani bu .data segmentidir ve C önyüklemesinin .data öğelerini flash'tan ram'a kopyalaması gerekir (genellikle sizin için araç zinciri tarafından belirlenir). Başlangıç ​​değeri olmadan bildirdiğiniz genel değişkenlerin, programınız başlamadan önce sıfır olduğu varsayılır, ancak gerçekten varsaymamanız gerekir ve neyse ki bazı derleyiciler başlatılmamış değişkenler hakkında uyarmaya başlar. Bu .bss segmentidir ve koçta bulunan C bootstrap sıfırları, içerik, sıfırlar, kalıcı bellekte depolanmak zorunda değildir, ancak başlangıç ​​adresi ve ne kadar yapar. Alet zinciri yine burada size çok yardımcı oluyor. Ve son olarak, C programları yerel değişkenlere sahip olmayı ve diğer işlevleri çağırabileceğini düşündüğü için minimum bir yığın işaretçisini ayarlamanız gerekir. Sonra belki çiplere özgü başka şeyler yapılır, ya da çipin geri kalan kısmının C'de olmasına izin veririz. kalıcı bellekte depolanmak zorunda değil, başlangıç ​​adresi ve ne kadar saklandığı. Alet zinciri yine burada size çok yardımcı oluyor. Ve son olarak, C programları yerel değişkenlere sahip olmayı ve diğer işlevleri çağırabileceğini düşündüğü için minimum bir yığın işaretçisini ayarlamanız gerekir. Sonra belki çiplere özgü başka şeyler yapılır, ya da çipin geri kalan kısmının C'de olmasına izin veririz. kalıcı bellekte depolanmak zorunda değil, başlangıç ​​adresi ve ne kadar saklandığı. Alet zinciri yine burada size çok yardımcı oluyor. Son olarak, C programları yerel değişkenlere sahip olmayı ve diğer işlevleri çağırabileceğini düşündüğü için minimum bir yığın işaretçisini ayarlamanız gerekir. Sonra belki çiplere özgü başka şeyler yapılır, ya da çipin geri kalan kısmının C'de olmasına izin veririz.

Koldaki cortex-m serisi çekirdekler sizin için bazılarını yapacak, yığın işaretçisi vektör tablosunda, sıfırlamadan sonra çalıştırılacak koda işaret edecek bir sıfırlama vektörü var, böylece ne yapmanız gerekiyorsa vektör tablosunu oluşturmak için (genellikle yine de asm kullanırsınız) asm olmadan saf C'ye gidebilirsiniz. Şimdi .data'nızın kopyalanmasını veya .bss'inizi sıfırlamayın, böylece korteks-m tabanlı bir şeye asm olmadan gitmek istiyorsanız bunu kendiniz yapmak zorundasınız. Daha büyük özellik sıfırlama vektörü değil, donanımın önerilen silahları takip eden C çağrı kuralını izlediği ve sizin için kayıtları koruduğu kesinti vektörleridir ve bu vektör için doğru dönüşü kullanır, böylece her işleyicinin etrafına doğru asm sarmanız gerekmez ( veya araç zincirinin sizin için sarmasını sağlamak için hedefiniz için araç zincirine özgü direktiflere sahip olabilirsiniz).

Talaşa özgü şeyler, örneğin, mikrodenetleyiciler genellikle pil tabanlı sistemlerde kullanılır, bu nedenle düşük güç, bu nedenle bazıları çevre birimlerinin çoğu kapalıyken sıfırlanır ve bunları kullanmak için bu alt sistemleri açmanız gerekir. . Genellikle bir kristal veya dahili osilatörden düşük-ish bir saat hızı kullanılır. Ve sistem tasarımınız daha hızlı bir saate ihtiyacınız olduğunu gösterebilir, böylece bunu başlatırsınız. saatiniz flaş veya ram için çok hızlı olabilir, bu nedenle saati yükseltmeden önce bekleme durumlarını değiştirmeniz gerekebilir. Uart veya usb veya diğer arayüzleri kurmanız gerekebilir. o zaman uygulamanız kendi işini yapabilir.

Bilgisayar masaüstü, dizüstü bilgisayar, sunucu ve mikrodenetleyici, önyükleme / çalışma yöntemlerinden farklı değildir. Dışında çoğunlukla bir çip üzerinde değiller. Bios programı genellikle cpu'dan ayrı bir yonga flash / rom'undadır. Son zamanlarda x86 cpus, aynı pakete (pcie kontrolörleri, vb.) Destek yongaları olan şeyleri gittikçe daha fazla çekiyor olsa da, hala ram ve rom off çipinizin çoğuna sahipsiniz, ancak yine de bir sistem ve hala tam olarak çalışıyor aynı yüksek seviyede. İşlemci önyükleme işlemi iyi bilinir, kart tasarımcıları flash / rom'u işlemcinin önyüklendiği adres alanına yerleştirir. bu program (bir x86 bilgisayardaki BIOS'un bir kısmı) yukarıda belirtilen her şeyi yapar, çeşitli çevre birimleri başlatır, dram başlatır, pcie otobüslerini numaralandırır vb. Çoğu zaman kullanıcı tarafından bios ayarlarına veya cmos ayarlarını çağırmak için kullandığımıza göre oldukça yapılandırılabilir, çünkü o zaman teknoloji kullanılıyordu. Önemli değil, gidip değiştirebileceğiniz kullanıcı ayarları vardır, bios önyükleme koduna ne yaptığını nasıl değiştireceğini söylemek için.

farklı insanlar farklı terminoloji kullanır. bir çip önyükleme yapar, çalışan ilk kod budur. bazen bootstrap denir. kelime yükleyicili bir bootloader genellikle müdahale etmek için bir şey yapmazsanız, genel önyüklemeden daha büyük bir şeye, uygulamanıza veya işletim sisteminize götüren bir bootstrap olduğu anlamına gelir. ancak yükleyici kısmı, önyükleme işlemini yarıda kesebileceğiniz ve daha sonra diğer test programlarını yükleyebileceğiniz anlamına gelir. Örneğin gömülü bir linux sisteminde uboot kullandıysanız, bir tuşa basabilir ve normal önyüklemeyi durdurabilir, ardından bir test çekirdeğini koç içine indirebilir ve flash olanın yerine önyükleyebilirsiniz veya veya yeni çekirdeği indirebilir ve daha sonra önyükleyicinin yanıp sönmesini yazmasını sağlayın, böylece bir daha önyükleme yaptığınızda yeni şeyler çalıştırır.

İşlemcinin kendisi kadar, çevre birimlerinden gelen flaştan gelen koçu bilmeyen çekirdek işlemci. Önyükleyici, işletim sistemi, uygulama kavramı yoktur. Sadece çalıştırılacak CPU'ya beslenen bir talimatlar dizisidir. Bunlar, farklı programlama görevlerini birbirinden ayıran yazılım terimleridir. Yazılım kavramları birbirinden.

Bazı mikrodenetleyicilerde, çip satıcısı tarafından değiştirilemeyebilecek ayrı bir flaş veya ayrı bir flaş alanında sağlanan ayrı bir önyükleyici vardır. Bu durumda, sıfırlama serbest bırakılmadan önce onları yüksek veya düşük bağlarsanız, mantığa ve / veya bu önyükleyiciye ne yapması gerektiğini söyleyen bir pim veya pim (bunlara kayışlar diyorum) vardır, örneğin bir kayış kombinasyonu çipe o önyükleyiciyi çalıştırmasını söyleyin ve verilerin flaşa programlanması için uart'ta bekleyin. Kayışları başka bir şekilde ayarlayın ve programınız çip satıcılarının önyükleyicisini değil, çipin saha programlamasına veya programınızın çökmesinden kurtarılmasına izin verir. Bazen flaşı programlamanıza izin veren saf bir mantıktır. Bu bugünlerde oldukça yaygın,

Çoğu mikrodenetleyicinin koçtan çok daha fazla flaşa sahip olmasının nedeni, birincil kullanım durumunun programı doğrudan flaştan çalıştırmak ve yalnızca yığın ve değişkenleri kapsayacak kadar koç olmasıdır. Her ne kadar bazı durumlarda, doğru derlemek ve flash saklamak zorunda sonra aramadan önce kopyalamak zorunda ram programları çalıştırabilirsiniz.

DÜZENLE

flash.s

.cpu cortex-m0
.thumb

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang

.thumb_func
reset:
    bl notmain
    b hang

.thumb_func
hang:   b .

notmain.c

int notmain ( void )
{
    unsigned int x=1;
    unsigned int y;
    y = x + 1;

    return(0);
}

flash.ld

MEMORY
{
    bob : ORIGIN = 0x00000000, LENGTH = 0x1000
    ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > bob
    .rodata : { *(.rodata*) } > bob
    .bss : { *(.bss*) } > ted
    .data : { *(.bss*) } > ted AT > bob
}

Yani bu bir korteks-m0 için bir örnektir, korteks-ms'nin hepsi bu örnekte olduğu gibi çalışır. Bu çip için, örneğin çip adres alanında 0x00000000 adresinde uygulama flaşı ve 0x20000000'de ram vardır.

Bir korteks-m önyükleme yolu, 0x0000 adresindeki 32 bit sözcüktür, yığın işaretçisini başlatmak için kullanılan adrestir. Bu örnek için çok fazla desteğe ihtiyacım yok, bu yüzden 0x20001000 yeterli olacak, açıkçası bu adresin altında koç olmalı (kol itme şekli, önce çıkarır sonra 0x20001000'i ayarlarsanız, yığıntaki ilk öğe 0x2000FFFC adresindeyse 0x2000FFFC kullanmak zorunda değilsiniz). 0x0004 adresindeki 32 bit sözcük, sıfırlama işleyicisinin adresidir, temel olarak sıfırlamadan sonra çalışan ilk koddur. Daha sonra, o korteks m çekirdeğine ve çipine özgü daha fazla kesme ve olay işleyicisi var, muhtemelen 128 veya 256 kadar, eğer bunları kullanmazsanız, onlar için tabloyu ayarlamanız gerekmez, gösteri için birkaç tane attım amaçlar.

Bu örnekte .data veya .bss ile uğraşmak zorunda değilim çünkü koda bakarak bu segmentlerde hiçbir şey olmadığını biliyorum. Eğer orada olsaydım, bir saniye içinde hallederdim.

Yığın kurulum, kontrol, .data halledilir, kontrol, .bss, kontrol, böylece C bootstrap şeyler yapılır, C için giriş işlevine dallayabilir, çünkü bazı derleyiciler işlevi görürlerse ekstra önemsiz ekler main () ve ana yola giderken, bu tam adı kullanmıyorum, burada C giriş noktası olarak notmain () kullandım. Bu nedenle sıfırlama işleyicisi notmain () öğesini çağırır, sonra notmain () döndürürse, yalnızca kötü adlandırılmış sonsuz bir döngü olan askıda kalmaya gider.

Araçlara hakim olmaya inanıyorum, pek çok kişi yok, ancak bulacağınız her çıplak metal geliştiricisinin, kendi uygulamalarını veya web sayfalarını yapacağınız kadar uzaktan değil, neredeyse tamamen özgür olması nedeniyle kendi işini yapması. . Yine kendi şeylerini yaparlar. Kendi bootstrap kodumu ve link komut dosyasını kullanmayı tercih ederim. Diğerleri araç zincirine güvenir veya işin çoğunun başka biri tarafından yapıldığı satıcıların sanal alanında oynarlar (ve bir şey kırılırsa, incinme dünyasındasınız ve çıplak metal şeyler sık ​​sık ve dramatik yollarla kırılır).

Yani gnu araçları ile montaj, derleme ve bağlantı:

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000015    andeq   r0, r0, r5, lsl r0
   8:   0000001b    andeq   r0, r0, fp, lsl r0
   c:   0000001b    andeq   r0, r0, fp, lsl r0
  10:   0000001b    andeq   r0, r0, fp, lsl r0

00000014 <reset>:
  14:   f000 f802   bl  1c <notmain>
  18:   e7ff        b.n 1a <hang>

0000001a <hang>:
  1a:   e7fe        b.n 1a <hang>

0000001c <notmain>:
  1c:   2000        movs    r0, #0
  1e:   4770        bx  lr

Önyükleyici nasıl bir şey olduğunu nereden biliyor? Çünkü derleyici işi yaptı. İlk durumda, derleyici flash.s için kod üretti ve bunu yaparak etiketlerin nerede olduğunu bilir (etiketler sadece işlev adları veya değişken adları gibi adreslerdir), böylece bayt saymak ve vektörü doldurmak zorunda kalmadım Tabloda manuel olarak bir etiket adı kullandım ve montajcı bunu benim için yaptı. Şimdi, sıfırlama 0x14 adresi ise, derleyici neden vektör tablosuna 0x15 koyduğunu sorarsınız. Bu bir korteks-m ve önyükleme yapıyor ve sadece başparmak modunda çalışıyor. ARM ile başparmak moduna dallanıyorsa bir adrese dalladığınızda, kol modu sıfırlanırsa lsbit'in ayarlanması gerekir. Yani her zaman bu bit setine ihtiyacınız var. Araçları biliyorum ve bir etiket önce .thumb_func koyarak, bu etiket vektör tablosunda olduğu gibi veya dallanma için veya herhangi bir şekilde kullanılıyorsa. Alet zinciri lsbit ayarını biliyor. Yani burada 0x14 | 1 = 0x15 var. Aynı şekilde asmak için. Şimdi disassembler notmain () çağrısı için 0x1D göstermiyor, ancak araçların talimatı doğru şekilde oluşturduğundan endişelenmeyin.

Notmain'deki kod şimdi, bu yerel değişkenler kullanılmıyor, ölü kod. Derleyici, y'nin ayarlandığını ancak kullanılmadığını söyleyerek bu gerçeği yorumlar.

Adres alanını not edin, tüm şeyler 0x0000 adresinde başlar ve oradan gider, böylece vektör tablosu düzgün bir şekilde yerleştirilir, .text veya program alanı da düzgün yerleştirilir, nasıl flash.s var notmain.c kodunun önünde araçları bilerek, yaygın bir hata, bunu doğru yapmamak ve çökmek ve sert yanmaktır. IMO ilk kez önyükleme yapmadan hemen önce şeylerin yerleştirildiğinden emin olmak için sökmeniz gerekir, bir kez doğru yerde bir şeyler her zaman kontrol etmek zorunda değilsiniz. Sadece yeni projeler için ya da asılırlarsa.

Şimdi bazı insanları şaşırtan bir şey, herhangi bir iki derleyicinin aynı girdiden aynı çıktıyı üretmesini beklemek için hiçbir sebep olmamasıdır. Hatta farklı ayarlarla aynı derleyici. Clang kullanarak, llvm derleyici optimizasyonu olan ve olmayan bu iki çıkışı alıyorum

llvm / clang optimize

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000015    andeq   r0, r0, r5, lsl r0
   8:   0000001b    andeq   r0, r0, fp, lsl r0
   c:   0000001b    andeq   r0, r0, fp, lsl r0
  10:   0000001b    andeq   r0, r0, fp, lsl r0

00000014 <reset>:
  14:   f000 f802   bl  1c <notmain>
  18:   e7ff        b.n 1a <hang>

0000001a <hang>:
  1a:   e7fe        b.n 1a <hang>

0000001c <notmain>:
  1c:   2000        movs    r0, #0
  1e:   4770        bx  lr

optimize edilmedi

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000015    andeq   r0, r0, r5, lsl r0
   8:   0000001b    andeq   r0, r0, fp, lsl r0
   c:   0000001b    andeq   r0, r0, fp, lsl r0
  10:   0000001b    andeq   r0, r0, fp, lsl r0

00000014 <reset>:
  14:   f000 f802   bl  1c <notmain>
  18:   e7ff        b.n 1a <hang>

0000001a <hang>:
  1a:   e7fe        b.n 1a <hang>

0000001c <notmain>:
  1c:   b082        sub sp, #8
  1e:   2001        movs    r0, #1
  20:   9001        str r0, [sp, #4]
  22:   2002        movs    r0, #2
  24:   9000        str r0, [sp, #0]
  26:   2000        movs    r0, #0
  28:   b002        add sp, #8
  2a:   4770        bx  lr

bu derleyicinin eklemeyi optimize ettiği bir yalandır, ancak değişkenler için yığına iki öğe tahsis etti, çünkü bunlar koçta oldukları, ancak sabit adreslerde olmayan yığınlardaki yerel değişkenler değiştirir. Ancak derleyici, derleme zamanında y'yi hesaplayabildiğini ve çalışma zamanında hesaplamak için bir neden olmadığını fark etti, bu nedenle x için ayrılmış yığın alanına 1 ve y için ayrılmış yığın alanı için 2 yerleştirdi. derleyici bu alanı "değişken" için yığın artı 0 ve değişken x için yığın 4 bildiren dahili tablolarla "ayırır". derleyici uyguladığı kod C standardına veya bir C programcısının beklentilerine uyduğu sürece istediği her şeyi yapabilir. Derleyicinin işlev süresi boyunca x'i yığın + 4'te bırakması için hiçbir neden yoktur,

Montajcıda bir işlev kukla eklersem

.thumb_func
.globl dummy
dummy:
    bx lr

ve sonra ara

void dummy ( unsigned int );
int notmain ( void )
{
    unsigned int x=1;
    unsigned int y;
    y = x + 1;
    dummy(y);
    return(0);
}

çıktı değişir

00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000015    andeq   r0, r0, r5, lsl r0
   8:   0000001b    andeq   r0, r0, fp, lsl r0
   c:   0000001b    andeq   r0, r0, fp, lsl r0
  10:   0000001b    andeq   r0, r0, fp, lsl r0

00000014 <reset>:
  14:   f000 f804   bl  20 <notmain>
  18:   e7ff        b.n 1a <hang>

0000001a <hang>:
  1a:   e7fe        b.n 1a <hang>

0000001c <dummy>:
  1c:   4770        bx  lr
    ...

00000020 <notmain>:
  20:   b510        push    {r4, lr}
  22:   2002        movs    r0, #2
  24:   f7ff fffa   bl  1c <dummy>
  28:   2000        movs    r0, #0
  2a:   bc10        pop {r4}
  2c:   bc02        pop {r1}
  2e:   4708        bx  r1

iç içe geçmiş fonksiyonlara sahip olduğumuza göre, notmain işlevinin dönüş adresini koruması gerekir, böylece iç içe çağrı için dönüş adresini tıkayabilir. bunun nedeni kolun geri dönüşler için bir kayıt kullanmasıdır, eğer yığını x86 veya bazılarını iyi kullanırsa kullanırız ... yine de yığını kullanır ama farklı şekilde. Şimdi neden r4'ü ittiğini soruyorsunuz? Uzun zaman önce çağrılma kuralı, yığını 32 bit yerine bir bitlik 64 bit (iki sözcük) hizasında tutmak için değişti. Bu yüzden yığını hizalamak için bir şey itmeleri gerekiyor, bu yüzden derleyici keyfi olarak r4'ü seçti, neden önemli değil. Bu hedef için çağrı kuralına göre, r4 içine haşhaş bir hata olurdu, biz bir fonksiyon çağrısı üzerinde clobber r4 yok, biz r0 ila r3 clobber olabilir. r0, dönüş değeridir. Belki bir kuyruk optimizasyonu yapıyor gibi görünüyor,

Ancak x ve y matematiğinin kukla fonksiyona geçirilen 2 sabit kodlu bir değere optimize edildiğini görüyoruz (kukla özellikle ayrı bir dosyada kodlanmıştır, bu durumda asm, böylece derleyici fonksiyon çağrısını tamamen optimize etmeyecektir, Ben sadece notmain.c içinde C döndürülen bir kukla işlevi olsaydı, optimizer x, y ve kukla işlev çağrısı kaldırıldı çünkü hepsi ölü / yararsız kod).

Ayrıca flash.s kodu büyüdükçe notmainhiçbiriydi ve toolchain bizim için tüm adresleri yamalamaya özen gösterdi, bu yüzden bunu manuel olarak yapmak zorunda değiliz.

referans için optimize edilmemiş clang

00000020 <notmain>:
  20:   b580        push    {r7, lr}
  22:   af00        add r7, sp, #0
  24:   b082        sub sp, #8
  26:   2001        movs    r0, #1
  28:   9001        str r0, [sp, #4]
  2a:   2002        movs    r0, #2
  2c:   9000        str r0, [sp, #0]
  2e:   f7ff fff5   bl  1c <dummy>
  32:   2000        movs    r0, #0
  34:   b002        add sp, #8
  36:   bd80        pop {r7, pc}

optimize edilmiş clang

00000020 <notmain>:
  20:   b580        push    {r7, lr}
  22:   af00        add r7, sp, #0
  24:   2002        movs    r0, #2
  26:   f7ff fff9   bl  1c <dummy>
  2a:   2000        movs    r0, #0
  2c:   bd80        pop {r7, pc}

derleyici yazarı yığını hizalamak için kukla değişken olarak r7 kullanmayı seçti, ayrıca yığın çerçevesinde hiçbir şey olmamasına rağmen r7 kullanarak bir çerçeve işaretçisi oluşturuyor. temel olarak talimat optimize edilmiş olabilir. ama üç talimat geri dönmek için pop kullanılır, bu muhtemelen bana oldu Ben doğru komut satırı seçenekleri (işlemci belirterek) ile bunu yapmak için gcc alabilirim bahse girerim.

bu çoğunlukla diğer sorularınıza cevap vermelidir

void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
    y = x + 1;
    dummy(y);
    return(0);
}

Şimdi küresellerim var. optimize edilmezlerse .data veya .bss içine girerler.

Nihai çıktıya bakmadan önce itermediate nesnesine bakalım

00000000 <notmain>:
   0:   b510        push    {r4, lr}
   2:   4b05        ldr r3, [pc, #20]   ; (18 <notmain+0x18>)
   4:   6818        ldr r0, [r3, #0]
   6:   4b05        ldr r3, [pc, #20]   ; (1c <notmain+0x1c>)
   8:   3001        adds    r0, #1
   a:   6018        str r0, [r3, #0]
   c:   f7ff fffe   bl  0 <dummy>
  10:   2000        movs    r0, #0
  12:   bc10        pop {r4}
  14:   bc02        pop {r1}
  16:   4708        bx  r1
    ...

Disassembly of section .data:
00000000 <x>:
   0:   00000001    andeq   r0, r0, r1

şimdi bundan eksik bilgi var ama neler olup bittiğine dair bir fikir veriyor, linker nesneleri alan ve onları .text ve. veri ve benzeri gider. derleyici bu tür şeyleri bilmiyor, sadece sunulan koda odaklanabilir, herhangi bir harici, bağlayıcıyı bağlantıyı doldurması için bir delik bırakmalıdır. Bu şeyleri birbirine bağlamak için bir yol bırakması gereken herhangi bir veri, bu yüzden her şey için adresler sıfırdır, çünkü derleyici ve bu dağıtıcı bilmiyor. Burada gösterilmeyen, bağlayıcının bir şeyleri yerleştirmek için kullandığı başka bilgiler de vardır. Buradaki kod, bağlayıcının işini yapabilmesi için konumdan yeterince bağımsızdır.

daha sonra bağlantılı çıkışın en azından bir sökülmesini görüyoruz

00000020 <notmain>:
  20:   b510        push    {r4, lr}
  22:   4b05        ldr r3, [pc, #20]   ; (38 <notmain+0x18>)
  24:   6818        ldr r0, [r3, #0]
  26:   4b05        ldr r3, [pc, #20]   ; (3c <notmain+0x1c>)
  28:   3001        adds    r0, #1
  2a:   6018        str r0, [r3, #0]
  2c:   f7ff fff6   bl  1c <dummy>
  30:   2000        movs    r0, #0
  32:   bc10        pop {r4}
  34:   bc02        pop {r1}
  36:   4708        bx  r1
  38:   20000004    andcs   r0, r0, r4
  3c:   20000000    andcs   r0, r0, r0

Disassembly of section .bss:

20000000 <y>:
20000000:   00000000    andeq   r0, r0, r0

Disassembly of section .data:

20000004 <x>:
20000004:   00000001    andeq   r0, r0, r1

derleyici temelde ram cinsinden iki adet 32 ​​bit değişken istedi. Bir .bss içinde çünkü ben sıfırlama olduğu varsayılır bu yüzden başlatmadı. Diğeri .data çünkü beyanda başlattım.

Şimdi bunlar küresel değişkenler olduğu için diğer fonksiyonların bunları değiştirebileceği varsayılmaktadır. derleyici, notmain'in ne zaman çağrılabileceğine dair hiçbir varsayımda bulunmaz, böylece görebileceği şeyle optimizasyon yapamaz, y = x + 1 matematiği, bu çalışma zamanını yapmak zorundadır. Koçtan iki değişkeni okumak ve geri kaydetmek zorunda.

Şimdi açıkça bu kod çalışmaz. Neden? çünkü burada gösterildiği gibi benim bootstrap notmain çağırmadan önce ram hazırlamaz, bu nedenle çip uyandığında ne olursa olsun çöp y ve x için kullanılacak olan çöptür.

Bunu burada göstermeyeceğim. .data ve .bss üzerinde daha da uzun sarmal başıboş dolaşımı ve neden çıplak metal kodumda bunlara hiç ihtiyaç duymadığımı okuyabilirsiniz, ancak başkalarının doğru yapmasını ummak yerine araçlara hakim olmanız ve ustalaşmak istediğinizi düşünüyorsanız. .

https://github.com/dwelch67/raspberrypi/tree/master/bssdata

linker komut dosyaları ve bootstraps biraz derleyiciye özgüdür, bu nedenle bir derleyicinin bir sürümü hakkında öğrendiğiniz her şey bir sonraki sürümde veya başka bir derleyicide fırlatılabilir, ancak .data ve .bss hazırlığına ton çaba harcamamın başka bir nedeni sadece bu tembel olmak için:

unsigned int x=1;

Bunu çok yapmayı tercih ederim

unsigned int x;
...
x = 1;

ve derleyicinin benim için .text yazmasına izin ver. Bazen flaşı bu şekilde kaydeder, bazen daha fazla yanar. Toolchain sürümünden veya bir derleyiciden diğerine programlamak ve bağlantı kurmak kesinlikle daha kolaydır. Çok daha güvenilir, daha az hata eğilimli. Evet, C standardına uymuyor.

Şimdi ya bu statik küreselleri yaparsak?

void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
    y = x + 1;
    dummy(y);
    return(0);
}

iyi

00000020 <notmain>:
  20:   b510        push    {r4, lr}
  22:   2002        movs    r0, #2
  24:   f7ff fffa   bl  1c <dummy>
  28:   2000        movs    r0, #0
  2a:   bc10        pop {r4}
  2c:   bc02        pop {r1}
  2e:   4708        bx  r1

açıkçası bu değişkenler diğer kodlarla değiştirilemez, bu yüzden derleyici derleme sırasında ölü kodu daha önce olduğu gibi optimize edebilir.

Optimize edilmemiş

00000020 <notmain>:
  20:   b580        push    {r7, lr}
  22:   af00        add r7, sp, #0
  24:   4804        ldr r0, [pc, #16]   ; (38 <notmain+0x18>)
  26:   6800        ldr r0, [r0, #0]
  28:   1c40        adds    r0, r0, #1
  2a:   4904        ldr r1, [pc, #16]   ; (3c <notmain+0x1c>)
  2c:   6008        str r0, [r1, #0]
  2e:   f7ff fff5   bl  1c <dummy>
  32:   2000        movs    r0, #0
  34:   bd80        pop {r7, pc}
  36:   46c0        nop         ; (mov r8, r8)
  38:   20000004    andcs   r0, r0, r4
  3c:   20000000    andcs   r0, r0, r0

yerliler için yığını kullanan bu derleyici, şimdi küreseller için ram kullanır ve ben .data ne de .bss düzgün işlemedi çünkü yazılı olarak bu kod bozuk.

ve sökme işleminde göremediğimiz son bir şey.

:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF

Ben x 0x12345678 ile ön-init olarak değiştirildi. Benim linker betiğim (bu gnu ld için) bob şey bu ted var. Bu, bağlayıcıya son yerin ted adres alanında olmasını istediğimi söyler, ancak ted adres alanındaki ikili dosyada saklar ve birisi sizin için hareket ettirir. Ve bunun olduğunu görebiliriz. Bu intel hex biçimidir. ve 0x12345678'i görebiliriz

:0400480078563412A0

ikilinin flash adres alanındadır.

readelf bunu da gösterir

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x010040 0x00000040 0x00000040 0x00008 0x00008 R   0x4
  LOAD           0x010000 0x00000000 0x00000000 0x00048 0x00048 R E 0x10000
  LOAD           0x020004 0x20000004 0x00000048 0x00004 0x00004 RW  0x10000
  LOAD           0x030000 0x20000000 0x20000000 0x00000 0x00004 RW  0x10000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

sanal adresin 0x20000004 ve fizikselin 0x48 olduğu LOAD satırı


en başında ben şeylerin iki bulanıklık resmi var:
user16307

1.) "birincil kullanım durumu rom / flash ve ram verisi ile ilgili talimatlara sahip olmaktır." "Buradaki RAM'deki veriler" derken, programın işleminde üretilen verileri mi kastediyorsunuz? veya başlatılmış verileri de ekliyor musunuz? yani kodu ROM'a yüklediğimizde, kodumuzda zaten başlatılan veriler var. örneğin, eğer var ise, o halde: int x = 1; int y = x +1; Yukarıdaki kod talimatlar vardır ve ilk veri 1'dir. (x = 1). bu veriler RAM'e de kopyalanır mı yoksa yalnızca ROM'da kalır.
user16307

13
hah, artık yığın değişim cevabının karakter sınırını biliyorum!
old_timer

3
Yeni başlayanlara bu tür kavramları açıklayan bir kitap yazmalısınız. "
Github'da

1
Az önce yaptım. Yararlı bir şey yapan biri değil, ama yine de bir mikrodenetleyici için kod örneğidir. Ve paylaştığım, iyi, kötü veya başka türlü her şeyi bulabileceğiniz bir github bağlantısı koydum.
old_timer

8

Bu cevap daha çok önyükleme sürecine odaklanacak. İlk olarak, MCU (veya en azından bir kısmı) zaten başladıktan sonra bir düzeltme - flaş yazma işlemi yapılır. Bazı MCU'larda (genellikle daha gelişmiş olanlar), CPU'nun kendisi seri bağlantı noktalarını çalıştırabilir ve flaş kayıtlarına yazabilir. Yani programı yazmak ve yürütmek farklı süreçlerdir. Programın zaten flash için yazılmış olduğunu varsayacağım.

İşte temel önyükleme işlemi. Bazı yaygın varyasyonları adlandıracağım, ancak çoğunlukla bunu basit tutuyorum.

  1. Sıfırla: İki temel tür vardır. Birincisi, besleme gerilimleri artarken dahili olarak üretilen bir açılış sıfırlamasıdır. İkincisi harici bir pim geçişidir. Ne olursa olsun, sıfırlama MCU'daki tüm parmak arası terliklerin önceden belirlenmiş bir duruma zorlanmasını sağlar.

  2. Ekstra donanım başlatma: CPU çalışmaya başlamadan önce ekstra zaman ve / veya saat döngüleri gerekebilir. Örneğin, üzerinde çalıştığım TI MCU'larda yüklenen bir dahili yapılandırma tarama zinciri var.

  3. CPU önyüklemesi: CPU ilk talimatını sıfırlama vektörü adı verilen özel bir adresten alır . Bu adres, CPU tasarlandığında belirlenir. Oradan, sadece normal bir program yürütme.

    CPU, üç temel adımı tekrar tekrar tekrarlar:

    • Getirme: Program sayacı (PC) kaydında saklanan adresten bir komutu (8-, 16 veya 32 bit değeri) okuyun , ardından PC'yi artırın.
    • Kod çözme: İkili komutu CPU'nun dahili kontrol sinyalleri için bir değer kümesine dönüştürün.
    • Yürütme: Talimatı uygulayın - iki kayıt ekleyin, belleğe okuma veya belleğe yazma, şube (PC'yi değiştirme) veya herhangi bir şey.

    (Aslında bundan daha karmaşıktır. CPU'lar genellikle boru hatlıdır , yani yukarıdaki adımların her birini aynı anda farklı talimatlar üzerinde yapabilirler. Yukarıdaki adımların her birinin birden fazla boru hattı aşaması olabilir. Sonra paralel boru hatları, dal tahmini ve bu Intel CPU'ların tasarım için bir milyar transistör almasını sağlayan tüm süslü bilgisayar mimarisi şeyleri.)

    Getirme işleminin nasıl çalıştığını merak ediyor olabilirsiniz. CPU, adres (çıkış) ve veri (giriş / çıkış) sinyallerinden oluşan bir veri yoluna sahiptir . Bir getirme yapmak için, CPU adres satırlarını program sayacındaki değere ayarlar ve ardından veri yolu üzerinden bir saat gönderir. Adres, bir belleği etkinleştirmek için kodu çözülür. Bellek saati ve adresi alır ve değeri o adrese veri satırlarına koyar. CPU bu değeri alır. Veri okuma ve yazma işlemleri benzerdir, ancak adres komuttan veya PC'den değil, genel amaçlı bir kayıttaki bir değerden gelir.

    Von Neumann mimarisine sahip CPU'ların hem talimatlar hem de veriler için kullanılan tek bir veriyolu vardır. Harvard mimarisine sahip CPU'larda bir veri yolu ve bir veri yolu vardır. Gerçek MCU'larda, bu otobüslerin her ikisi de aynı anılara bağlanabilir, bu yüzden endişelenmeniz gerekmeyen bir şeydir (ancak her zaman değil).

    Önyükleme işlemine geri dönün. Sıfırlamadan sonra, bilgisayara sıfırlama vektörü adı verilen bir başlangıç ​​değeri yüklenir . Bu donanıma yerleştirilebilir veya (ARM Cortex-M CPU'larda) otomatik olarak bellekten okunabilir. CPU, sıfırlama vektöründen talimatı alır ve yukarıdaki adımlarda döngü yapmaya başlar. Bu noktada, CPU normal şekilde çalışıyor.

  4. Önyükleme yükleyicisi: MCU'nun geri kalanını çalışır duruma getirmek için yapılması gereken bazı düşük seviye kurulumlar vardır. Bu, RAM'leri temizleme ve analog bileşenler için üretim trim ayarlarını yükleme gibi şeyleri içerebilir. Seri bağlantı noktası veya harici bellek gibi harici bir kaynaktan kod yükleme seçeneği de olabilir. MCU, bunları yapmak için küçük bir program içeren bir önyükleme ROM'u içerebilir . Bu durumda, CPU sıfırlama vektörü önyükleme ROM'unun adres alanını gösterir. Bu temelde normal bir koddur, sadece üretici tarafından sağlanır, böylece kendiniz yazmak zorunda kalmazsınız. :-) Bir bilgisayarda, BIOS önyükleme ROM'una eşdeğerdir.

  5. C ortam kurulumu: C yığının (işlev çağrıları sırasında durumun depolanması için RAM alanı) ve genel değişkenler için başlatılmış bellek konumlarının olmasını bekler . Bunlar Dwelch'in bahsettiği .stack, .data ve .bss bölümleridir. Başlatılan global değişkenlerin başlatma değerleri bu adımda flash'tan RAM'e kopyalanır. Başlatılmamış global değişkenler birbirine yakın RAM adreslerine sahiptir, bu nedenle tüm bellek bloğu çok kolayca sıfırlanabilir. Yığın başlatılmasına gerek yoktur (her ne kadar olabilir) - gerçekten yapmanız gereken tek şey CPU'nun yığın işaretçi kaydını ayarlamak, böylece RAM'de atanmış bir bölgeyi işaret eder.

  6. Ana fonksiyon : C ortamı kurulduktan sonra, C yükleyici main () fonksiyonunu çağırır. Uygulama kodunuz normalde burada başlar. İsterseniz, standart kitaplığı dışarıda bırakabilir, C ortam kurulumunu atlayabilir ve main () öğesini çağırmak için kendi kodunuzu yazabilirsiniz. Bazı MCU'lar kendi önyükleme yükleyicinizi yazmanıza izin verebilir ve ardından tüm düşük düzeyli ayarları kendi başınıza yapabilirsiniz.

Çeşitli şeyler: Birçok MCU, daha iyi performans için RAM'den kod çalıştırmanıza izin verir. Bu genellikle bağlayıcı yapılandırmasında ayarlanır. Bağlayıcı her işleve iki adres atar - kodun ilk saklandığı yer olan bir yükleme adresi (genellikle flash) ve işlevi yürütmek için bilgisayara yüklenen adres olan bir çalışma adresi (flash veya RAM). Kodu RAM'den yürütmek için, CPU'nun işlev kodunu flash içindeki yükleme adresinden RAM'deki çalışma adresine kopyalamasını sağlamak için kod yazarsınız ve ardından işlevi çalışma adresinde çağırırsınız. Bağlayıcı bu konuda yardımcı olacak global değişkenler tanımlayabilir. Ancak MCU'larda kodun RAM dışında çalıştırılması isteğe bağlıdır. Normalde yalnızca gerçekten yüksek performansa ihtiyacınız varsa veya flaşı yeniden yazmak istiyorsanız yaparsınız.


1

Özetiniz Von Neumann mimarisi için yaklaşık olarak doğrudur . İlk kod tipik olarak bir önyükleyici aracılığıyla RAM'e yüklenir, ancak (tipik olarak) terimin genel olarak başvurduğu bir yazılım önyükleyicisine yüklenmez. Bu normalde 'silikon içine pişmiş' davranıştır. Bu mimaride kod yürütme genellikle, ROM'dan gelen talimatları, işlemcinin kod yürütme süresini en üst düzeye çıkaracak ve kodun RAM'e yüklenmesini beklemeyeceği şekilde önbelleğe almayı içerir. MSP430'un bu mimarinin bir örneği olduğunu bir yerde okudum.

Bir Harvard Architecture cihazında, veri belleğine (RAM) ayrı bir veri yolu üzerinden erişilirken talimatlar doğrudan ROM'dan yürütülür. Bu mimaride, kod sıfırlama vektöründen yürütülmeye başlar. PIC24 ve dsPIC33 bu mimarinin örnekleridir.

Bu işlemleri başlatan, cihazdan cihaza değişebilen ve hata ayıklayıcıları, JTAG, tescilli yöntemleri vb. İçerebilir.


Fakat bazı noktaları hızlı bir şekilde atlıyorsunuz. Yavaş çekim yapalım. Diyelim ki ikili kod "ilk" ROM'a yazılmıştır. Tamam .. Ondan sonra "Veri hafızasına erişilir" yazıyorsunuz .... Peki ama "RAM'e" veri ilk başta nereden geliyor? Yine ROM'dan mı geliyor? Öyleyse, önyükleyici, ROM'un hangi bölümünün RAM'e başlangıçta yazılacağını nasıl biliyor?
user16307

Haklısın, bolca atladım. Diğer adamların daha iyi cevapları var. Aradığın şeye sahip olduğuna sevindim.
slightlynybbled
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.