Làm thế nào để một vi điều khiển khởi động và khởi động, từng bước một?


15

Khi mã C được viết, biên dịch và tải lên vi điều khiển, vi điều khiển bắt đầu chạy. Nhưng nếu chúng ta thực hiện quá trình tải lên và khởi động từng bước trong chuyển động chậm, tôi có một số nhầm lẫn về những gì đang thực sự xảy ra bên trong MCU (bộ nhớ, CPU, bộ tải khởi động). Đây là (rất có thể sai) những gì tôi sẽ trả lời nếu có ai đó hỏi tôi:

  1. Mã nhị phân được biên dịch được ghi vào flash ROM (hoặc EEPROM) thông qua USB
  2. Bootloader sao chép một phần mã này vào RAM. Nếu đúng, làm thế nào để trình tải khởi động biết sao chép (phần nào của ROM để sao chép vào RAM)?
  3. CPU bắt đầu tìm nạp các hướng dẫn và dữ liệu mã từ ROM và RAM

Điều này có sai không?

Có thể tóm tắt quá trình khởi động và khởi động này với một số thông tin về cách bộ nhớ, bộ nạp khởi động và CPU tương tác trong giai đoạn này?

Tôi đã tìm thấy nhiều lời giải thích cơ bản về cách PC khởi động thông qua BIOS. Nhưng tôi bị mắc kẹt với quá trình khởi động vi điều khiển.

Câu trả lời:


29

1) nhị phân biên dịch được ghi vào prom / flash yes. USB, nối tiếp, i2c, jtag, v.v ... tùy thuộc vào thiết bị như những gì được thiết bị đó hỗ trợ, không liên quan để hiểu quá trình khởi động.

2) Điều này thường không đúng với vi điều khiển, trường hợp sử dụng chính là có các hướng dẫn trong rom / flash và dữ liệu trong ram. Không có vấn đề gì về kiến ​​trúc. đối với một bộ vi điều khiển, máy tính của bạn, máy tính xách tay của bạn, máy chủ của bạn, chương trình được sao chép từ không biến động (đĩa) sang ram sau đó chạy từ đó. Một số bộ vi điều khiển cũng cho phép bạn sử dụng ram, ngay cả những bộ vi xử lý yêu cầu máy gặt mặc dù nó có vẻ vi phạm định nghĩa. Không có gì về máy gặt ngăn bạn ánh xạ ram vào phía hướng dẫn, bạn chỉ cần có một cơ chế để nhận các hướng dẫn ở đó sau khi bật nguồn (vi phạm định nghĩa, nhưng các hệ thống bừa sẽ phải làm điều đó có ích hơn là vi điều khiển).

3) loại.

Mỗi cpu "khởi động" theo cách xác định, theo thiết kế. Cách phổ biến nhất là bảng vectơ trong đó địa chỉ cho các hướng dẫn đầu tiên chạy sau khi bật nguồn nằm trong vectơ thiết lập lại, một địa chỉ mà phần cứng đọc sau đó sử dụng địa chỉ đó để bắt đầu chạy. Một cách chung khác là để bộ xử lý bắt đầu thực thi mà không cần bảng vectơ tại một số địa chỉ nổi tiếng. Đôi khi chip sẽ có "dây đai", một số chân mà bạn có thể buộc cao hoặc thấp trước khi phát hành thiết lập lại, mà logic sử dụng để khởi động các cách khác nhau. Bạn phải tách riêng cpu, lõi bộ xử lý khỏi phần còn lại của hệ thống. Hiểu cách cpu hoạt động, và sau đó hiểu rằng các nhà thiết kế chip / hệ thống có bộ giải mã địa chỉ thiết lập xung quanh bên ngoài cpu để một phần của không gian địa chỉ cpus giao tiếp với đèn flash, và một số với ram và một số có thiết bị ngoại vi (uart, i2c, spi, gpio, v.v.). Bạn có thể lấy lõi cpu đó nếu muốn và bọc nó khác nhau. Đây là những gì bạn nhận được khi bạn mua một cái gì đó cánh tay hoặc mips dựa. cánh tay và mips tạo ra lõi cpu, thứ mà mọi người mua và bọc những thứ của riêng họ, vì nhiều lý do họ không làm cho thứ đó tương thích từ thương hiệu này sang thương hiệu khác. Đó là lý do tại sao hiếm khi có thể hỏi một câu hỏi chung chung khi nói đến bất cứ điều gì bên ngoài cốt lõi.

Một bộ vi điều khiển cố gắng trở thành một hệ thống trên chip, do đó bộ nhớ không bay hơi (flash / rom), dễ bay hơi (sram) và cpu đều nằm trên cùng một chip cùng với hỗn hợp các thiết bị ngoại vi. Nhưng chip được thiết kế bên trong sao cho đèn flash được ánh xạ vào không gian địa chỉ của cpu phù hợp với đặc điểm khởi động của cpu đó. Ví dụ, nếu cpu có vectơ thiết lập lại tại địa chỉ 0xFFFC, thì cần phải có flash / rom đáp ứng địa chỉ đó mà chúng ta có thể lập trình qua 1), cùng với đủ flash / rom trong không gian địa chỉ cho các chương trình hữu ích. Một nhà thiết kế chip có thể chọn có 0x1000 byte flash bắt đầu từ 0xF000 để đáp ứng các yêu cầu đó. Và có lẽ họ đặt một số lượng ram ở một địa chỉ thấp hơn hoặc có thể là 0x0000 và các thiết bị ngoại vi ở đâu đó ở giữa.

Một kiến ​​trúc khác của cpu có thể bắt đầu thực thi tại địa chỉ 0, vì vậy họ sẽ cần làm ngược lại, đặt đèn flash để nó trả lời cho phạm vi địa chỉ quanh 0. nói 0x0000 đến 0x0FFF chẳng hạn. và sau đó đặt một số ram ở nơi khác.

Các nhà thiết kế chip biết cách khởi động cpu và họ đã đặt bộ lưu trữ không bay hơi ở đó (flash / rom). Sau đó, các phần mềm phải viết mã khởi động để phù hợp với hành vi được biết đến của cpu đó. Bạn phải đặt địa chỉ vectơ đặt lại trong vectơ đặt lại và mã khởi động của bạn tại địa chỉ bạn đã xác định trong vectơ đặt lại. Toolchain có thể giúp bạn rất nhiều ở đây. đôi khi, đặc biệt với id và click ides hoặc các hộp cát khác, họ có thể làm hầu hết công việc cho bạn, tất cả những gì bạn làm là gọi apis bằng ngôn ngữ cấp cao (C).

Nhưng, tuy nhiên, chương trình được tải vào flash / rom phải phù hợp với hành vi khởi động cứng của cpu. Trước phần C của chương trình chính của bạn () và nếu bạn sử dụng chính làm điểm vào của bạn, một số việc phải được thực hiện. Lập trình viên AC giả định rằng khi khai báo một biến có giá trị ban đầu, họ hy vọng rằng nó thực sự hoạt động. Chà, các biến, không phải là const, nằm trong ram, nhưng nếu bạn có một biến có giá trị ban đầu thì giá trị ban đầu phải nằm trong ram không bay hơi. Vì vậy, đây là phân đoạn .data và bootstrap C cần sao chép nội dung .data từ flash sang ram (nơi thường được xác định cho bạn bởi toolchain). Các biến toàn cục mà bạn khai báo không có giá trị ban đầu được coi là 0 trước khi chương trình của bạn bắt đầu mặc dù bạn thực sự không nên cho rằng và rất may một số trình biên dịch đang bắt đầu cảnh báo về các biến chưa được khởi tạo. Đây là phân đoạn .bss và các số không bootstrap C trong ram, nội dung, số không, không phải được lưu trữ trong bộ nhớ không bay hơi, nhưng địa chỉ bắt đầu và bao nhiêu. Một lần nữa, toolchain giúp bạn rất nhiều ở đây. Và cuối cùng, mức tối thiểu là bạn cần thiết lập một con trỏ ngăn xếp vì các chương trình C mong muốn có thể có các biến cục bộ và gọi các hàm khác. Sau đó, có thể một số nội dung cụ thể về chip khác được thực hiện hoặc chúng tôi để phần còn lại của công cụ cụ thể về chip xảy ra trong C. không phải được lưu trữ trong bộ nhớ không bay hơi, nhưng địa chỉ bắt đầu và bao nhiêu. Một lần nữa, toolchain giúp bạn rất nhiều ở đây. Và cuối cùng, mức tối thiểu là bạn cần thiết lập một con trỏ ngăn xếp vì các chương trình C mong muốn có thể có các biến cục bộ và gọi các hàm khác. Sau đó, có thể một số nội dung cụ thể về chip khác được thực hiện hoặc chúng tôi để phần còn lại của công cụ cụ thể về chip xảy ra trong C. không phải được lưu trữ trong bộ nhớ không bay hơi, nhưng địa chỉ bắt đầu và bao nhiêu. Một lần nữa, toolchain giúp bạn rất nhiều ở đây. Và cuối cùng, mức tối thiểu là bạn cần thiết lập một con trỏ ngăn xếp vì các chương trình C mong muốn có thể có các biến cục bộ và gọi các hàm khác. Sau đó, có thể một số nội dung cụ thể về chip khác được thực hiện hoặc chúng tôi để phần còn lại của công cụ cụ thể về chip xảy ra trong C.

Các lõi dòng cortex-m từ arm sẽ thực hiện một số điều này cho bạn, con trỏ ngăn xếp nằm trong bảng vectơ, có một vectơ thiết lập lại để chỉ mã được chạy sau khi đặt lại, vì vậy khác với bất cứ điều gì bạn phải làm để tạo bảng vectơ (mà bạn thường sử dụng asm cho dù sao) bạn có thể đi thuần C mà không cần asm. bây giờ bạn không nhận được bản sao .data của mình cũng như .bss của bạn bằng 0 nên bạn phải tự làm điều đó nếu bạn muốn thử đi mà không cần asm trên một cái gì đó dựa trên cortex-m. Tính năng lớn hơn không phải là vectơ thiết lập lại mà là các vectơ ngắt trong đó phần cứng tuân theo quy ước gọi C được đề nghị và bảo toàn các thanh ghi cho bạn, và sử dụng trả về đúng cho vectơ đó, để bạn không phải quấn asm bên phải xung quanh mỗi trình xử lý ( hoặc có các chỉ thị cụ thể cho toolchain cho mục tiêu của bạn để có toolchain bao bọc nó cho bạn).

Ví dụ, các công cụ cụ thể của chip có thể là vi điều khiển thường được sử dụng trong các hệ thống sử dụng pin, do đó công suất thấp nên một số thiết bị bị tắt khi hầu hết các thiết bị ngoại vi bị tắt và bạn phải bật từng hệ thống phụ này để bạn có thể sử dụng chúng . Uarts, gpios, v.v ... Thông thường tốc độ đồng hồ thấp được sử dụng, trực tiếp từ bộ tạo dao động hoặc tinh thể. Và thiết kế hệ thống của bạn có thể cho thấy rằng bạn cần một chiếc đồng hồ nhanh hơn, vì vậy bạn khởi tạo nó. đồng hồ của bạn có thể quá nhanh so với đèn flash hoặc ram, do đó bạn có thể cần phải thay đổi trạng thái chờ trước khi bật đồng hồ. Có thể cần phải thiết lập uart, hoặc usb hoặc các giao diện khác. sau đó ứng dụng của bạn có thể làm việc của nó.

Máy tính để bàn, máy tính xách tay, máy chủ và vi điều khiển không khác nhau về cách chúng khởi động / làm việc. Ngoại trừ việc họ không chủ yếu trên một chip. Chương trình bios thường nằm trên một flash / rom chip riêng biệt từ cpu. Mặc dù gần đây x86 cpus đang thu hút ngày càng nhiều những gì từng là chip hỗ trợ vào cùng một gói (bộ điều khiển pcie, v.v.) nhưng bạn vẫn có hầu hết chip ram và rom, nhưng nó vẫn là một hệ thống và nó vẫn hoạt động chính xác giống nhau ở mức cao. Quá trình khởi động cpu được nhiều người biết đến, các nhà thiết kế bảng đặt flash / rom vào không gian địa chỉ nơi cpu khởi động. chương trình đó (một phần của BIOS trên máy tính x86) thực hiện tất cả những điều được đề cập ở trên, nó khởi động nhiều thiết bị ngoại vi khác nhau, nó khởi tạo bộ phim truyền hình, liệt kê các xe buýt pcie, v.v. Người dùng thường có thể định cấu hình khá dựa trên cài đặt bios hoặc những gì chúng ta thường gọi là cài đặt cmos, bởi vì tại thời điểm đó, đó là những gì công nghệ đã được sử dụng. Không quan trọng, có các cài đặt người dùng mà bạn có thể đi và thay đổi để báo cho mã khởi động bios cách thay đổi những gì nó làm.

những người khác nhau sẽ sử dụng thuật ngữ khác nhau. một con chip khởi động, đó là mã đầu tiên chạy. đôi khi được gọi là bootstrap. một bộ tải khởi động với trình tải từ thường có nghĩa là nếu bạn không làm gì để can thiệp thì đó là một bootstrap đưa bạn từ việc khởi động chung vào một cái gì đó lớn hơn, ứng dụng hoặc hệ điều hành của bạn. nhưng phần trình nạp ngụ ý rằng bạn có thể làm gián đoạn quá trình khởi động và sau đó có thể tải các chương trình thử nghiệm khác. nếu bạn đã từng sử dụng uboot chẳng hạn trên một hệ thống linux được nhúng, bạn có thể nhấn một phím và dừng khởi động bình thường thì bạn có thể tải kernel thử nghiệm vào ram và khởi động nó thay vì flash, hoặc bạn có thể tải xuống các chương trình riêng hoặc bạn có thể tải xuống kernel mới sau đó yêu cầu bootloader ghi nó vào flash để lần sau khi bạn khởi động, nó sẽ chạy các công cụ mới.

Theo như cpu, bộ xử lý lõi, không biết ram từ flash từ các thiết bị ngoại vi. Không có khái niệm về bootloader, hệ điều hành, ứng dụng. Nó chỉ là một chuỗi các hướng dẫn được đưa vào cpu để được thực thi. Đây là các thuật ngữ phần mềm để phân biệt các nhiệm vụ lập trình khác nhau với nhau. Khái niệm phần mềm từ nhau.

Một số bộ vi điều khiển có bộ tải khởi động riêng được cung cấp bởi nhà cung cấp chip trong một đèn flash riêng hoặc vùng flash riêng biệt mà bạn có thể không sửa đổi được. Trong trường hợp này thường có một chốt hoặc bộ ghim (tôi gọi chúng là dây đai) mà nếu bạn buộc chúng cao hoặc thấp trước khi thiết lập lại được phát hành, bạn đang nói với logic và / hoặc bộ tải khởi động đó phải làm gì, ví dụ như một kết hợp dây đeo có thể báo cho chip chạy bộ tải khởi động đó và đợi trên uart để dữ liệu được lập trình vào flash. Đặt dây đai theo cách khác và chương trình của bạn khởi động không phải là bộ tải khởi động của nhà cung cấp chip, cho phép lập trình trường của chip hoặc phục hồi sau sự cố chương trình của bạn. Đôi khi nó chỉ là logic thuần túy cho phép bạn lập trình flash. Điều này là khá phổ biến những ngày này,

Lý do tại sao hầu hết các bộ vi điều khiển có flash nhiều hơn ram là vì trường hợp sử dụng chính là để chạy chương trình trực tiếp từ flash và chỉ có đủ ram để bao gồm stack và các biến. Mặc dù trong một số trường hợp, bạn có thể chạy các chương trình từ ram mà bạn phải biên dịch đúng và lưu trữ trong flash sau đó sao chép trước khi gọi.

BIÊN TẬP

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 .

không phải

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
}

Vì vậy, đây là một ví dụ cho cortex-m0, cortex-ms đều hoạt động giống như ví dụ này. Ví dụ, con chip cụ thể này có flash ứng dụng tại địa chỉ 0x00000000 trong không gian địa chỉ nhánh và ram ở 0x20000000.

Cách khởi động cortex-m là từ 32 bit tại địa chỉ 0x0000 là địa chỉ để khởi tạo con trỏ ngăn xếp. Tôi không cần nhiều stack cho ví dụ này vì vậy 0x20001000 sẽ đủ, rõ ràng phải có ram bên dưới địa chỉ đó (cách cánh tay đẩy, nó sẽ trừ trước rồi đẩy vì vậy nếu bạn đặt 0x20001000 thì mục đầu tiên trên stack là địa chỉ 0x2000FFFC bạn không phải sử dụng 0x2000FFFC). Từ 32 bit tại địa chỉ 0x0004 là địa chỉ của trình xử lý đặt lại, về cơ bản là mã đầu tiên chạy sau khi đặt lại. Sau đó, có nhiều trình xử lý sự kiện và gián đoạn cụ thể hơn cho lõi và chip vỏ não đó, có thể lên tới 128 hoặc 256, nếu bạn không sử dụng chúng thì bạn không cần phải thiết lập bảng cho chúng, tôi đã ném một vài cái để trình diễn mục đích.

Tôi không cần phải xử lý .data hay .bss trong ví dụ này vì tôi biết đã không có gì trong các phân đoạn đó bằng cách xem mã. Nếu có tôi sẽ giải quyết nó, và sẽ trong một giây.

Vì vậy, ngăn xếp được thiết lập, kiểm tra, .data đã chăm sóc, kiểm tra, .bss, kiểm tra, vì vậy công cụ bootstrap C đã hoàn tất, có thể phân nhánh đến chức năng nhập cho C. Bởi vì một số trình biên dịch sẽ thêm rác nếu họ thấy chức năng main () và trên đường tới main, tôi không sử dụng tên chính xác đó, tôi đã sử dụng notmain () ở đây làm điểm vào C của mình. Vì vậy, trình xử lý thiết lập lại gọi notmain () sau đó if / when notmain () trả về nó sẽ bị treo, đó chỉ là một vòng lặp vô hạn, có thể được đặt tên kém.

Tôi tin tưởng chắc chắn vào việc thành thạo các công cụ, nhiều người thì không, nhưng điều bạn sẽ thấy là mỗi nhà phát triển kim loại trần tự làm việc của mình, vì sự tự do gần như hoàn toàn, không bị ràng buộc từ xa như bạn sẽ làm ứng dụng hoặc trang web . Họ lại làm việc của riêng họ. Tôi thích có mã bootstrap và tập lệnh liên kết của riêng tôi. Những người khác dựa vào chuỗi công cụ, hoặc chơi trong hộp cát của nhà cung cấp, nơi phần lớn công việc được thực hiện bởi người khác (và nếu có gì đó phá vỡ bạn đang ở trong một thế giới bị tổn thương, và với những thứ kim loại trần trụi thường xuyên và theo những cách kịch tính).

Vì vậy, lắp ráp, biên dịch và liên kết với các công cụ gnu tôi nhận được:

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

Vậy làm thế nào để bộ nạp khởi động biết công cụ ở đâu. Bởi vì trình biên dịch đã làm việc. Trong trường hợp đầu tiên, trình biên dịch đã tạo mã cho flash.s và bằng cách đó để biết các nhãn ở đâu (nhãn chỉ là địa chỉ giống như tên hàm hoặc tên biến, v.v.) vì vậy tôi không phải đếm byte và điền vào vectơ bảng thủ công, tôi đã sử dụng một tên nhãn và trình biên dịch đã làm điều đó cho tôi. Bây giờ bạn hỏi, nếu thiết lập lại là địa chỉ 0x14 tại sao trình biên dịch lại đặt 0x15 trong bảng vectơ. Vâng, đây là một vỏ não và nó khởi động và chỉ chạy ở chế độ ngón tay cái. Với ARM khi bạn phân nhánh đến một địa chỉ nếu phân nhánh sang chế độ ngón tay cái, lsbit cần được đặt, nếu chế độ tay thì đặt lại. Vì vậy, bạn luôn cần thiết lập bit đó. Tôi biết các công cụ và bằng cách đặt .thumb_func trước một nhãn, nếu nhãn đó được sử dụng như trong bảng vectơ hoặc để phân nhánh hoặc bất cứ thứ gì. Các toolchain biết để thiết lập lsbit. Vậy nó có ở đây 0x14 | 1 = 0x15. Tương tự như vậy cho hang. Bây giờ trình phân tách không hiển thị 0x1D cho lệnh gọi notmain () nhưng đừng lo các công cụ đã xây dựng hướng dẫn chính xác.

Bây giờ mã đó không chính xác, các biến cục bộ đó, không được sử dụng, chúng là mã chết. Trình biên dịch thậm chí còn nhận xét về thực tế đó bằng cách nói y được đặt nhưng không được sử dụng.

Lưu ý không gian địa chỉ, tất cả những thứ này bắt đầu tại địa chỉ 0x0000 và đi từ đó để bảng vectơ được đặt đúng, không gian .text hoặc chương trình cũng được đặt đúng cách, làm thế nào tôi có flash.s trước mã của notmain.c biết các công cụ, một lỗi phổ biến là không làm đúng và sụp đổ và đốt cháy mạnh. IMO bạn phải tháo rời để đảm bảo mọi thứ được đặt đúng trước khi bạn khởi động lần đầu tiên, một khi bạn có đồ đạc ở đúng nơi bạn không nhất thiết phải kiểm tra mỗi lần. Chỉ cho các dự án mới hoặc nếu họ treo.

Bây giờ một điều làm một số người ngạc nhiên là không có lý do gì để mong đợi bất kỳ hai trình biên dịch nào tạo ra cùng một đầu ra từ cùng một đầu vào. Hoặc thậm chí cùng một trình biên dịch với các cài đặt khác nhau. Sử dụng clang, trình biên dịch llvm tôi nhận được hai kết quả đầu ra này mà không cần tối ưu hóa

tối ưu hóa / clang

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

không được tối ưu hóa

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

Vì vậy, đó là một lời nói dối mà trình biên dịch đã tối ưu hóa bổ sung, nhưng nó đã phân bổ hai mục trên ngăn xếp cho các biến, vì đây là các biến cục bộ mà chúng nằm trong ram nhưng trên ngăn xếp không ở các địa chỉ cố định, sẽ thấy với các toàn cục thay đổi. Nhưng trình biên dịch nhận ra rằng nó có thể tính toán y trong thời gian biên dịch và không có lý do nào để tính toán nó trong thời gian chạy nên nó chỉ đơn giản đặt 1 trong không gian ngăn xếp được phân bổ cho x và 2 cho không gian ngăn xếp được phân bổ cho y. trình biên dịch "phân bổ" không gian này với các bảng bên trong Tôi khai báo stack plus 0 cho biến y và stack plus 4 cho biến x. trình biên dịch có thể làm bất cứ điều gì nó muốn miễn là mã nó thực hiện phù hợp với tiêu chuẩn C hoặc các triển khai của một lập trình viên C. Không có lý do tại sao trình biên dịch phải để x ở stack + 4 trong suốt thời gian của hàm,

Nếu tôi thêm một hàm giả trong trình biên dịch chương trình

.thumb_func
.globl dummy
dummy:
    bx lr

và sau đó gọi nó

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

đầu ra thay đổi

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

bây giờ chúng ta có các hàm lồng nhau, hàm notmain cần giữ nguyên địa chỉ trả về của nó, để nó có thể ghi đè địa chỉ trả về cho cuộc gọi lồng nhau. điều này là do cánh tay sử dụng một thanh ghi để trả về, nếu nó sử dụng ngăn xếp như nói x86 hoặc một số thứ khác ... thì nó vẫn sử dụng ngăn xếp nhưng khác. Bây giờ bạn hỏi tại sao nó đẩy r4? Chà, quy ước gọi cách đây không lâu đã thay đổi để giữ cho ngăn xếp được căn chỉnh trên ranh giới 64 bit (hai từ) thay vì 32 bit, ranh giới một từ. Vì vậy, họ cần phải đẩy một cái gì đó để giữ cho ngăn xếp được căn chỉnh, vì vậy trình biên dịch tự ý chọn r4 vì một số lý do, không quan trọng tại sao. Popping vào r4 sẽ là một lỗi mặc dù theo quy ước gọi cho mục tiêu này, chúng tôi không ghi đè r4 vào một lệnh gọi hàm, chúng tôi có thể ghi đè r0 đến r3. r0 là giá trị trả về. Có vẻ như nó đang thực hiện tối ưu hóa đuôi,

Nhưng chúng ta thấy rằng toán học x và y được tối ưu hóa thành giá trị mã hóa 2 được truyền cho hàm giả (giả được mã hóa cụ thể trong một tệp riêng biệt, trong trường hợp này là asm, để trình biên dịch sẽ không tối ưu hóa hoàn toàn hàm gọi ra, nếu tôi có một hàm giả chỉ đơn giản là trả về C trong notmain.c thì trình tối ưu hóa sẽ loại bỏ lệnh gọi hàm x, y và giả vì tất cả chúng đều là mã chết / vô dụng).

Cũng lưu ý rằng vì mã flash.s có mã lớn hơn không phải là khác và chuỗi công cụ đã đảm nhiệm việc vá tất cả các địa chỉ cho chúng tôi nên chúng tôi không phải thực hiện thủ công.

tiếng kêu không được tối ưu hóa để tham khảo

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}

tiếng kêu tối ưu

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}

tác giả trình biên dịch đã chọn sử dụng r7 làm biến giả để căn chỉnh ngăn xếp, ngoài ra, nó đang tạo một con trỏ khung bằng r7 mặc dù nó không có gì trong khung stack. về cơ bản hướng dẫn có thể đã được tối ưu hóa ra. nhưng nó đã sử dụng pop để trả về không phải ba hướng dẫn, đó có lẽ là do tôi, tôi cá là tôi có thể khiến gcc làm điều đó với các tùy chọn dòng lệnh bên phải (chỉ định bộ xử lý).

điều này chủ yếu sẽ trả lời phần còn lại của câu hỏi của bạn

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

Tôi có toàn cầu bây giờ. vì vậy chúng đi vào .data hoặc .bss nếu chúng không được tối ưu hóa.

trước khi chúng ta nhìn vào đầu ra cuối cùng, hãy nhìn vào đối tượng lặp lại

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

bây giờ có thông tin bị thiếu trong số này nhưng nó đưa ra ý tưởng về những gì đang diễn ra, trình liên kết là thứ lấy các đối tượng và liên kết chúng với nhau với thông tin được cung cấp (trong trường hợp này là flash.ld) cho biết nó ở đâu .text và. dữ liệu và như vậy đi. trình biên dịch không biết những thứ như vậy, nó chỉ có thể tập trung vào mã được trình bày, bất kỳ bên ngoài nào nó phải để lại một lỗ hổng để trình liên kết điền vào kết nối. Bất kỳ dữ liệu nào nó cũng phải để lại một cách để liên kết những thứ đó lại với nhau, vì vậy địa chỉ của mọi thứ đều bằng 0 ở đây chỉ đơn giản là vì trình biên dịch và trình dịch ngược này không biết. có một thông tin khác không được hiển thị ở đây mà trình liên kết sử dụng để đặt mọi thứ. mã ở đây là vị trí đủ độc lập để trình liên kết có thể thực hiện công việc của nó.

sau đó chúng ta sẽ thấy ít nhất một sự phân tách của đầu ra được liên kết

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

trình biên dịch về cơ bản đã yêu cầu hai biến 32 bit trong ram. Một là trong .bss vì tôi đã không khởi tạo nó nên nó được coi là init. cái khác là .data vì tôi đã khởi tạo nó khi khai báo.

Bây giờ vì đây là các biến toàn cục nên người ta cho rằng các hàm khác có thể sửa đổi chúng. trình biên dịch không đưa ra giả định nào khi không thể gọi được vì vậy nó không thể tối ưu hóa với những gì nó có thể nhìn thấy, toán học y = x + 1, do đó nó phải thực hiện thời gian chạy đó. Nó phải đọc từ ram hai biến thêm chúng và lưu lại.

Bây giờ rõ ràng mã này sẽ không hoạt động. Tại sao? bởi vì bootstrap của tôi như được hiển thị ở đây không chuẩn bị ram trước khi gọi là không chính xác, do đó, bất kỳ rác nào có trong 0x20000000 và 0x20000004 khi chip thức dậy là thứ sẽ được sử dụng cho y và x.

Sẽ không thể hiện điều đó ở đây. bạn có thể đọc lan man thậm chí dài hơn của tôi trên .data và .bss và tại sao tôi không bao giờ cần chúng trong mã kim loại trần của mình, nhưng nếu bạn cảm thấy bạn phải và muốn thành thạo các công cụ thay vì hy vọng người khác làm đúng .. .

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

các tập lệnh liên kết và bootstraps có phần cụ thể của trình biên dịch, vì vậy mọi thứ bạn tìm hiểu về một phiên bản của một trình biên dịch có thể được đưa vào phiên bản tiếp theo hoặc với một số trình biên dịch khác, nhưng một lý do khác khiến tôi không nỗ lực trong việc chuẩn bị .data và .bss chỉ để được lười biếng này:

unsigned int x=1;

Tôi thích làm điều này hơn

unsigned int x;
...
x = 1;

và để trình biên dịch đặt nó trong .text cho tôi. Đôi khi nó tiết kiệm flash theo cách đó đôi khi nó cháy nhiều hơn. Chắc chắn là dễ dàng hơn nhiều để lập trình và chuyển từ phiên bản toolchain hoặc trình biên dịch này sang trình biên dịch khác. Đáng tin cậy hơn nhiều, ít bị lỗi. Đúng, không phù hợp với tiêu chuẩn C.

Bây giờ nếu chúng ta tạo ra những quả cầu tĩnh này thì sao?

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

tốt

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

rõ ràng các biến đó không thể được sửa đổi bởi mã khác, vì vậy trình biên dịch bây giờ có thể biên dịch tối ưu hóa mã chết, giống như trước đây.

không tối ưu hóa

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

trình biên dịch này đã sử dụng ngăn xếp cho người địa phương, bây giờ sử dụng ram cho toàn cầu và mã này được viết bị hỏng vì tôi không xử lý .data cũng như .bss đúng cách.

và một điều cuối cùng mà chúng ta không thể nhìn thấy trong quá trình tháo gỡ.

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

Tôi đã thay đổi x thành pre-init với 0x12345678. Kịch bản liên kết của tôi (cái này là dành cho gnu ld) có cái này ở bob. điều đó nói với trình liên kết Tôi muốn vị trí cuối cùng nằm trong không gian địa chỉ ted, nhưng lưu nó ở dạng nhị phân trong không gian địa chỉ ted và ai đó sẽ di chuyển nó cho bạn. Và chúng ta có thể thấy điều đó đã xảy ra. đây là định dạng hex intel. và chúng ta có thể thấy 0x12345678

:0400480078563412A0

là trong không gian địa chỉ flash của nhị phân.

đọc cũng cho thấy điều này

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

dòng LOAD có địa chỉ ảo là 0x20000004 và vật lý là 0x48


ngay từ đầu tôi có hai bức tranh mờ về mọi thứ:
user16307

1.) "trường hợp sử dụng chính là có các hướng dẫn trong rom / flash và dữ liệu trong ram." khi bạn nói "dữ liệu trong RAM ở đây", bạn có nghĩa là dữ liệu được tạo ra trong bộ xử lý của chương trình. hoặc bạn cũng bao gồm các dữ liệu khởi tạo. ý tôi là khi chúng tôi tải mã lên ROM, đã có dữ liệu khởi tạo trong mã của chúng tôi. ví dụ trong oode của chúng ta nếu chúng ta có: int x = 1; int y = x +1; đoạn mã trên có hướng dẫn và có dữ liệu ban đầu là 1. (x = 1). dữ liệu này cũng được sao chép vào RAM hoặc chỉ ở trong ROM.
dùng16307

12
hah, bây giờ tôi biết giới hạn ký tự cho một câu trả lời trao đổi ngăn xếp!
old_timer

2
Bạn nên viết một cuốn sách giải thích các khái niệm như vậy cho người mới. "Tôi có một trăm ví dụ tại github" - Có thể chia sẻ một vài ví dụ
AlphaGoku

1
Tôi vừa làm. Không phải cái nào cũng hữu ích, nhưng nó vẫn là một ví dụ về mã cho vi điều khiển. Và tôi đã đặt một liên kết github từ đó bạn có thể tìm thấy mọi thứ khác mà tôi đã chia sẻ, tốt, xấu, hoặc nếu không.
old_timer

8

Câu trả lời này sẽ tập trung nhiều hơn vào quá trình khởi động. Đầu tiên, một hiệu chỉnh - ghi vào flash được thực hiện sau khi MCU (hoặc ít nhất là một phần của nó) đã được khởi động. Trên một số MCU (thường là loại cao cấp hơn), CPU có thể tự vận hành các cổng nối tiếp và ghi vào các thanh ghi flash. Vì vậy, viết và thực hiện chương trình là các quá trình khác nhau. Tôi sẽ giả định rằng chương trình đã được viết thành flash.

Đây là quá trình khởi động cơ bản. Tôi sẽ đặt tên cho một số biến thể phổ biến, nhưng chủ yếu là tôi giữ nó đơn giản.

  1. Đặt lại: Có hai loại cơ bản. Đầu tiên là thiết lập lại bật nguồn, được tạo ra bên trong trong khi điện áp cung cấp đang tăng lên. Thứ hai là một chuyển đổi pin bên ngoài. Bất kể, thiết lập lại buộc tất cả các flip-flop trong MCU đến trạng thái được xác định trước.

  2. Khởi tạo phần cứng bổ sung: Có thể cần thêm thời gian và / hoặc chu kỳ xung nhịp trước khi CPU bắt đầu chạy. Ví dụ: trong các MCU TI tôi làm việc, có một chuỗi quét cấu hình bên trong được tải.

  3. Khởi động CPU: CPU tìm nạp lệnh đầu tiên từ một địa chỉ đặc biệt gọi là vectơ thiết lập lại. Địa chỉ này được xác định khi CPU được thiết kế. Từ đó, nó chỉ là chương trình thực thi bình thường.

    CPU lặp đi lặp lại ba bước cơ bản:

    • Tìm nạp: Đọc hướng dẫn (giá trị 8-, 16- hoặc 32 bit) từ địa chỉ được lưu trong thanh ghi bộ đếm chương trình (PC), sau đó tăng PC.
    • Giải mã: Chuyển đổi lệnh nhị phân thành một tập hợp các giá trị cho các tín hiệu điều khiển bên trong của CPU.
    • Thực thi: Thực hiện hướng dẫn - thêm hai thanh ghi, đọc từ hoặc ghi vào bộ nhớ, nhánh (thay đổi PC) hoặc bất cứ điều gì.

    (Nó thực sự phức tạp hơn thế này. CPU thường được đặt theo đường ống , có nghĩa là chúng có thể thực hiện từng bước trên theo các hướng dẫn khác nhau cùng một lúc. Mỗi bước trên có thể có nhiều giai đoạn đường ống. Sau đó, có các đường ống song song, dự đoán nhánh và tất cả các công cụ kiến ​​trúc máy tính ưa thích làm cho các CPU Intel đó phải mất một tỷ bóng bán dẫn để thiết kế.)

    Bạn có thể tự hỏi làm thế nào tìm nạp hoạt động. CPU có một bus bao gồm các tín hiệu địa chỉ (out) và dữ liệu (vào / ra). Để thực hiện tìm nạp, CPU đặt các dòng địa chỉ của nó thành giá trị trong bộ đếm chương trình, sau đó gửi đồng hồ qua bus. Địa chỉ được giải mã để kích hoạt bộ nhớ. Bộ nhớ nhận đồng hồ và địa chỉ và đặt giá trị tại địa chỉ đó trên các dòng dữ liệu. CPU nhận giá trị này. Dữ liệu đọc và ghi tương tự nhau, ngoại trừ địa chỉ đến từ hướng dẫn hoặc giá trị trong thanh ghi mục đích chung, không phải PC.

    Các CPU có kiến trúc von Neumann có một bus duy nhất được sử dụng cho cả hướng dẫn và dữ liệu. CPU có kiến trúc Harvard có một bus để hướng dẫn và một cho dữ liệu. Trong MCU thực, cả hai chiếc xe buýt này có thể được kết nối với cùng một ký ức, vì vậy, đó thường là (nhưng không phải luôn luôn) một cái gì đó bạn không phải lo lắng.

    Quay lại quá trình khởi động. Sau khi thiết lập lại, PC được tải với giá trị bắt đầu được gọi là vector thiết lập lại. Điều này có thể được tích hợp vào phần cứng, hoặc (trong CPU ARM Cortex-M) nó có thể được đọc tự động ra khỏi bộ nhớ. CPU tìm nạp lệnh từ vector thiết lập lại và bắt đầu lặp qua các bước ở trên. Tại thời điểm này, CPU đang hoạt động bình thường.

  4. Bộ tải khởi động: Thường có một số thiết lập cấp thấp cần được thực hiện để làm cho phần còn lại của MCU hoạt động. Điều này có thể bao gồm những thứ như xóa RAM và tải cài đặt cắt sản xuất cho các thành phần tương tự. Cũng có thể có một tùy chọn để tải mã từ một nguồn bên ngoài như cổng nối tiếp hoặc bộ nhớ ngoài. MCU có thể bao gồm một ROM khởi động có chứa một chương trình nhỏ để làm những việc này. Trong trường hợp này, vectơ đặt lại CPU trỏ đến không gian địa chỉ của ROM khởi động. Đây là mã cơ bản bình thường, nó chỉ được cung cấp bởi nhà sản xuất nên bạn không phải tự viết nó. :-) Trong PC, BIOS tương đương với ROM khởi động.

  5. Thiết lập môi trường C: C hy vọng sẽ có một ngăn xếp (vùng RAM để lưu trữ trạng thái trong khi gọi hàm) và vị trí bộ nhớ khởi tạo cho các biến toàn cục. Đây là các phần .stack, .data và .bss mà Dwelch đang nói đến. Các biến toàn cục được khởi tạo có các giá trị khởi tạo được sao chép từ flash sang RAM ở bước này. Các biến toàn cục chưa được khởi tạo có các địa chỉ RAM gần nhau, do đó toàn bộ khối bộ nhớ có thể được khởi tạo về 0 rất dễ dàng. Ngăn xếp không cần phải được khởi tạo (mặc dù có thể) - tất cả những gì bạn thực sự cần làm là thiết lập thanh ghi con trỏ ngăn xếp của CPU để nó trỏ đến một vùng được chỉ định trong RAM.

  6. Hàm chính : Khi môi trường C được thiết lập, trình tải C gọi hàm main (). Đó là nơi mã ứng dụng của bạn thường bắt đầu. Nếu bạn muốn, bạn có thể rời khỏi thư viện chuẩn, bỏ qua thiết lập môi trường C và viết mã của riêng bạn để gọi hàm main (). Một số MCU có thể cho phép bạn viết bộ tải khởi động của riêng bạn và sau đó bạn có thể tự mình thực hiện tất cả các thiết lập cấp thấp.

Nội dung linh tinh: Nhiều MCU sẽ cho phép bạn thực thi mã ra khỏi RAM để có hiệu suất tốt hơn. Điều này thường được thiết lập trong cấu hình liên kết. Trình liên kết gán hai địa chỉ cho mọi chức năng - một địa chỉ tải , là nơi mã được lưu trữ lần đầu (thường là flash) và địa chỉ chạy , là địa chỉ được tải vào PC để thực thi chức năng (flash hoặc RAM). Để thực thi mã ra khỏi RAM, bạn viết mã để làm cho CPU sao chép mã chức năng từ địa chỉ tải của nó trong flash sang địa chỉ chạy trong RAM, sau đó gọi hàm tại địa chỉ chạy. Trình liên kết có thể định nghĩa các biến toàn cục để giúp với điều này. Nhưng thực thi mã ra khỏi RAM là tùy chọn trong MCU. Bạn thường chỉ làm điều đó nếu bạn thực sự cần hiệu suất cao hoặc nếu bạn muốn viết lại đèn flash.


1

Tóm tắt của bạn là gần đúng cho kiến trúc Von Neumann . Mã ban đầu thường được tải vào RAM thông qua bộ tải khởi động, nhưng không (thường là) bộ tải khởi động phần mềm mà thuật ngữ thường nói đến. Đây thường là hành vi 'nướng vào silicon'. Việc thực thi mã trong kiến ​​trúc này thường bao gồm một hướng dẫn bộ nhớ đệm dự đoán từ ROM theo cách mà bộ xử lý tối đa hóa thời gian thực thi mã của nó và không chờ mã được nạp vào RAM. Tôi đã đọc ở đâu đó rằng MSP430 là một ví dụ về kiến ​​trúc này.

Trong thiết bị Kiến trúc Harvard , các hướng dẫn được thực hiện trực tiếp từ ROM trong khi bộ nhớ dữ liệu (RAM) được truy cập thông qua một bus riêng. Trong kiến ​​trúc này, mã chỉ đơn giản là bắt đầu thực thi từ vector thiết lập lại. PIC24 và DSPIC33 là những ví dụ về kiến ​​trúc này.

Đối với việc lật các bit thực tế khởi động các quá trình này, có thể thay đổi từ thiết bị này sang thiết bị khác và có thể liên quan đến trình gỡ lỗi, JTAG, phương thức độc quyền, v.v.


Nhưng bạn đang bỏ qua một số điểm nhanh. Hãy làm cho nó chuyển động chậm. Hãy nói rằng mã nhị phân "đầu tiên" được ghi vào ROM. Ok .. Sau đó bạn viết "Bộ nhớ dữ liệu được truy cập" .... Nhưng dữ liệu "vào RAM" bắt đầu từ đâu khi bắt đầu? Nó có đến từ ROM nữa không? Và nếu vậy, làm thế nào để trình tải khởi động biết phần nào của ROM sẽ được ghi vào RAM khi bắt đầu?
dùng16307

Bạn đã đúng, tôi đã bỏ qua rất nhiều. Những người khác có câu trả lời tốt hơn. Tôi rất vui vì bạn đã có những gì bạn đang tìm kiếm.
hơi bất ngờ vào
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.