2009-09-02 45 views
8

Tôi đang làm việc trong miền hệ thống nhúng. Tôi muốn biết làm thế nào một mã được thực hiện từ một vi điều khiển (uC không cần phải chủ quan, nói chung), bắt đầu từ một tập tin C. Ngoài ra tôi muốn biết các chất liệu như mã khởi động, tập tin đối tượng, vv Tôi không thể tìm thấy bất kỳ tài liệu trực tuyến liên quan đến các công cụ trên. Nếu có thể, vui lòng cung cấp các liên kết giải thích những điều đó từ đầu. Cảm ơn trước sự giúp đỡ của bạnThực thi mã trong các hệ thống nhúng

+0

Nó sẽ giúp chỉ ra loại vi điều khiển nào. –

+0

Đang làm việc trong bộ điều khiển 8051. tôi biết một chút về cách opcodes được lấy và thực hiện trong ngôn ngữ lắp ráp). nhưng muốn biết làm thế nào một dự án với nhiều tệp C, thực hiện trên một UC. – inquisitive

+2

các tệp C không thực thi! :-) Chúng được biên dịch thành các tệp đối tượng và được liên kết thành một hình ảnh thực thi cuối cùng, được tải lên flash hoặc RAM và chạy từ đó. –

Trả lời

36

Là một kiến ​​trúc sư vi xử lý, tôi đã có cơ hội làm việc ở mức độ rất thấp cho phần mềm. Về cơ bản, mức nhúng thấp là rất khác với lập trình PC chung chỉ ở mức độ phần cứng cụ thể.

thấp cấp phần mềm nhúng có thể được chia thành như sau:

  1. Reset vector - điều này thường được viết bằng lắp ráp. Đây là điều đầu tiên chạy lúc khởi động và có thể được coi là mã phần cứng cụ thể. Nó thường sẽ thực hiện các chức năng đơn giản như thiết lập bộ xử lý thành trạng thái ổn định được xác định trước bằng cách cấu hình các thanh ghi và như vậy. Sau đó, nó sẽ nhảy tới mã khởi động. Các vector thiết lập lại cơ bản nhất chỉ đơn thuần là nhảy trực tiếp vào mã khởi động.
  2. Mã khởi động - đây là mã phần mềm cụ thể đầu tiên chạy. Công việc của nó là cơ bản để thiết lập môi trường phần mềm để mã C có thể chạy trên đầu trang. Ví dụ, mã C giả định rằng có một vùng bộ nhớ được định nghĩa là stack và heap. Đây thường là các cấu trúc phần mềm thay vì phần cứng. Do đó, đoạn mã khởi động này sẽ định nghĩa các con trỏ ngăn xếp và các con trỏ heap và như vậy. Điều này thường được nhóm theo 'c-runtime'. Đối với mã C++, các hàm tạo cũng được gọi. Vào cuối của các thói quen, nó sẽ thực hiện main(). chỉnh sửa: Các biến cần được khởi tạo và một số phần nhất định của bộ nhớ cần thanh toán bù trừ được thực hiện tại đây. Về cơ bản, mọi thứ cần thiết để chuyển mọi thứ thành 'trạng thái đã biết'.
  3. Mã ứng dụng - đây là ứng dụng C thực tế của bạn bắt đầu từ chức năng main(). Như bạn có thể thấy, rất nhiều thứ đang thực sự nằm dưới mui xe và xảy ra ngay cả trước khi chức năng chính đầu tiên của bạn được gọi. Mã này thường có thể được viết dưới dạng thuyết bất khả tri phần cứng nếu có sẵn hardware abstraction layer tốt. Mã ứng dụng chắc chắn sẽ sử dụng rất nhiều chức năng thư viện. Các thư viện này thường được liên kết tĩnh trong các hệ thống nhúng.
  4. Thư viện - đây là những thư viện C chuẩn của bạn cung cấp các hàm C nguyên thủy. Ngoài ra còn có các thư viện bộ xử lý cụ thể thực hiện những thứ như phần mềm hỗ trợ dấu phẩy động. Cũng có thể có các thư viện phần cứng cụ thể để truy cập vào các thiết bị I/O và như vậy cho stdin/stdout. Một số thư viện C phổ biến là NewlibuClibc.
  5. Interrupt/Exception handler - đây là những thói quen chạy vào thời gian ngẫu nhiên trong quá trình thực thi mã thông thường do thay đổi trạng thái phần cứng hoặc bộ vi xử lý. Những thói quen này cũng thường được viết trong hội đồng vì chúng nên chạy với phần mềm tối thiểu trên không để phục vụ phần cứng thực tế được gọi.

Hy vọng điều này sẽ cung cấp một khởi đầu tốt. Vui lòng để lại nhận xét nếu bạn có các truy vấn khác.

+0

Bang trên mục tiêu !! Cảm ơn sybreon. Bây giờ còn phân bổ bộ nhớ thì sao? Giả sử uC của tôi có bộ nhớ flash, các biến tĩnh và toàn cục được chương trình sử dụng sẽ được lưu trong RAM (.bss và phần dữ liệu) bằng mã khởi động hệ thống, biến cục bộ trong ngăn xếp (RAM) và mã vẫn còn trong flash (ROM)). Việc thực thi thực tế xảy ra bằng cách thực hiện từng lệnh từ flash. Tôi có đúng không? – inquisitive

+0

@Guru_newbie: "Việc thực thi thực tế xảy ra bằng cách thực hiện từng lệnh từ flash." Một số bộ vi xử lý chạy mã trực tiếp từ flash và một số thì không. Tôi tin rằng 8.051 sẽ chạy mã từ flash. Bộ xử lý nhúng cao cấp hơn (32 bit), giống như PC sẽ sao chép mã ứng dụng vào RAM và thực thi từ RAM. – simon

+0

@sybreon: Bước 2 cũng sẽ thiết lập các biến tĩnh trong RAM và sao chép dữ liệu khởi tạo quá. – simon

5

Nói chung, bạn đang làm việc ở mức thấp hơn rất nhiều so với máy tính có mục đích chung.

Mỗi CPU sẽ có hành vi nhất định khi bật nguồn, chẳng hạn như xóa tất cả thanh ghi và đặt bộ đếm chương trình thành 0xf000 (mọi thứ ở đây không cụ thể, như câu hỏi của bạn).

Bí quyết là đảm bảo mã của bạn ở đúng nơi.

Quy trình biên dịch thường tương tự như các máy tính có mục đích chung mà bạn dịch C thành mã máy (tệp đối tượng). Từ đó, bạn cần phải liên kết mã đó với:

  • mã khởi động hệ thống của bạn, thường là trong trình biên dịch.
  • bất kỳ thư viện thời gian chạy nào (kể cả các bit bắt buộc của RTL C).

Mã khởi động hệ thống thường chỉ khởi tạo phần cứng và thiết lập môi trường để mã C của bạn có thể hoạt động. Thư viện thời gian chạy trong các hệ thống nhúng thường làm cho các công cụ lớn cồng kềnh (như hỗ trợ dấu chấm động hoặc printf) tùy chọn để giữ mã bloat.

Trình liên kết trong các hệ thống nhúng cũng thường đơn giản hơn nhiều, xuất ra mã vị trí cố định thay vì mã nhị phân có thể định vị lại. Bạn sử dụng nó để đảm bảo mã khởi động diễn ra (ví dụ: 0xf000).

Trong các hệ thống nhúng, bạn thường muốn mã thực thi xuất hiện ngay từ đầu để bạn có thể ghi vào EPROM (hoặc EEPROM hoặc Flash hoặc thiết bị khác duy trì nội dung khi tắt nguồn).

Tất nhiên, hãy ghi nhớ bước đột phá cuối cùng của tôi là với bộ vi xử lý 8051 và 68302. Nó có thể là các hệ thống 'nhúng' ngày nay là các hộp Linux đầy đủ với tất cả các loại phần cứng tuyệt vời, trong trường hợp đó sẽ không có sự khác biệt thực sự giữa mục đích chung và nhúng.

Nhưng tôi nghi ngờ điều đó. Vẫn cần một phần cứng có độ phân giải thấp nghiêm trọng cần hệ điều hành và/hoặc mã ứng dụng tùy chỉnh.

SPJ Embedded Technologies có một số downloadable evaluation trong môi trường phát triển 8051 có vẻ như bạn muốn. Bạn có thể tạo các chương trình có dung lượng tối đa 2K nhưng có vẻ như đi qua toàn bộ quá trình (biên dịch liên kết, tạo các tệp HEX hoặc BIN để kết xuất vào phần cứng đích, thậm chí một trình mô phỏng cung cấp quyền truy cập vào các công cụ trên chip và thiết bị bên ngoài).

Sản phẩm không đánh giá có giá 200 Euro nhưng nếu tất cả những gì bạn muốn chỉ là một chút phát, tôi chỉ tải xuống đánh giá - ngoài giới hạn 2K, đó là sản phẩm đầy đủ.

+0

Cảm ơn bạn đã trả lời nhanh, pax. Nếu có thể, bạn có thể thử cung cấp bất kỳ liên kết tốt nào có sẵn để giải thích quy trình trên (doenst vấn đề uC thực tế là gì) – inquisitive

1

Tôi có kinh nghiệm với vi điều khiển AVR, nhưng tôi nghĩ rằng điều này sẽ được khá nhiều giống nhau cho tất cả trong số họ:

Việc lập đi dọc theo dòng giống như với một mã C bình thường.Nó được biên dịch thành các tệp đối tượng, chúng được liên kết với nhau, nhưng thay vì xuất ra một số định dạng phức tạp như ELF hoặc PE, đầu ra chỉ được đặt trên một số địa chỉ cố định trong bộ nhớ của UC mà không có bất kỳ tiêu đề nào.

Mã khởi động (nếu trình biên dịch tạo ra bất kỳ) được thêm vào giống như mã khởi động cho máy tính "bình thường" - có một số mã được thêm trước mã chính() (và có thể sau mã).

Một khác biệt khác là liên kết - mọi thứ phải được liên kết tĩnh, vì vi điều khiển không có hệ điều hành để xử lý liên kết động.

+0

Cảm ơn Cube. Bây giờ tôi hiểu rằng một tập tin thực thi sẽ được tạo ra trong máy chủ và sẽ được đưa vào bộ nhớ không bay hơi của UC. Tôi muốn biết làm thế nào để thực hiện thực tế bắt đầu trong mục tiêu thực tế từ sau đó. Bất kỳ tài liệu trực tuyến nào về điều này hoặc nghiên cứu điển hình đều thích hợp hơn. – inquisitive

+0

Không hoàn toàn chính xác về ELF/PE.Nhiều liên kết cho các hệ thống nhúng đầu ra ELF, nó chỉ là mã nhị phân bên trong nó là địa chỉ cố định, không phải là vị trí độc lập. Vì vậy, sau đó nó có thể tạo ra một tập tin hex (Motorola S-record hoặc Intel Hex) hoặc dump nhị phân thẳng (giả sử bạn biết địa chỉ bắt đầu) để tải vào Flash. –

2

Tôi nhận được ấn tượng mà bạn quan tâm nhất đến những gì mà sybreon gọi là "bước 2." Rất nhiều có thể xảy ra ở đó, và nó thay đổi rất nhiều bởi nền tảng. Thông thường, công cụ này được xử lý bởi một số kết hợp của bộ nạp khởi động, gói hỗ trợ hội đồng quản trị, Thời gian chạy C (CRT) và nếu bạn có một hệ điều hành.

Thông thường, sau khi đặt lại vectơ, một số loại bộ nạp khởi động sẽ thực thi từ flash. Bộ nạp khởi động này có thể chỉ thiết lập phần cứng và nhảy vào CRT của ứng dụng của bạn, cũng trong flash. Trong trường hợp này, CRT có thể xóa các tệp .bss, sao chép .data sang RAM, v.v. Trong các hệ thống khác, trình tải khởi động có thể phân tán tải ứng dụng từ tệp được mã hóa, như ELF và CRT chỉ thiết lập thời gian chạy (đống, v.v.). Tất cả điều này xảy ra trước khi CRT gọi chính của ứng dụng().

Nếu ứng dụng của bạn được liên kết tĩnh, chỉ thị của liên kết sẽ chỉ định địa chỉ nơi .data/.bss và ngăn xếp được khởi tạo. Các giá trị này được liên kết với CRT hoặc được mã hóa thành ELF. Trong một môi trường được liên kết động, tải ứng dụng thường được xử lý bởi một hệ điều hành mà lại nhắm mục tiêu ELF để chạy trong bất kỳ bộ nhớ nào mà hệ điều hành chỉ định.

Ngoài ra, một số mục tiêu chạy ứng dụng từ flash, nhưng một số khác sẽ sao chép tệp .text có thể thực thi từ flash sang RAM. (Đây thường là sự cân bằng tốc độ/dấu chân, vì RAM nhanh hơn/rộng hơn flash trên hầu hết các mục tiêu.)

1

Bạn có thể xem chi tiết GNU ARM Tutorial của Jim Lynch.

+0

Chỉ cần lưu ý: ARM là một trong những hệ thống nhúng phức tạp hơn. Mã khởi động đặc biệt phức tạp so với các UC nhỏ hơn, ví dụ: AVR. –

2

Ok, tôi sẽ cung cấp ảnh này ...

Kiến trúc tắt đầu tiên. Von Neumann và Harvard. Kiến trúc Harvard có bộ nhớ riêng cho mã và dữ liệu. Von Neumann thì không. Harvard được sử dụng trong nhiều vi điều khiển và đó là những gì tôi quen thuộc.

Vì vậy, bắt đầu với kiến ​​trúc Harvard cơ bản của bạn, bạn có bộ nhớ chương trình. Khi vi điều khiển đầu tiên khởi động nó thực hiện các lệnh tại vị trí bộ nhớ bằng không. Thông thường đây là một JUMP để giải quyết lệnh nơi mã chính bắt đầu.

Bây giờ, khi tôi nói hướng dẫn, tôi có nghĩa là mã opcodes. Mã hóa được hướng dẫn mã hóa thành dữ liệu nhị phân - thường là 8 hoặc 16 bit. Trong một số kiến ​​trúc, mỗi mã opcode được mã hóa cứng có nghĩa là những thứ cụ thể, ở những bit khác có thể có ý nghĩa (ví dụ bit 1 có nghĩa là kiểm tra, bit 2 nghĩa là kiểm tra cờ 0, v.v.). Vì vậy, có opcodes và sau đó các thông số cho các opcodes. Lệnh JUMP là một mã opcode và một địa chỉ bộ nhớ 8 hoặc 16 hoặc 32 bit mà mã 'nhảy' tới. Tức là, kiểm soát được chuyển đến các hướng dẫn tại địa chỉ đó. Nó thực hiện điều này bằng cách thao tác một thanh ghi đặc biệt chứa địa chỉ của lệnh tiếp theo được thực hiện. Vì vậy, để JUMP đến vị trí bộ nhớ 0x0050 nó sẽ thay thế nội dung của thanh ghi đó bằng 0x0050. Vào chu kỳ đồng hồ tiếp theo, bộ xử lý sẽ đọc thanh ghi và định vị địa chỉ bộ nhớ và thực hiện lệnh ở đó.

Hướng dẫn thực thi gây ra thay đổi trạng thái của máy. Có một thanh ghi trạng thái chung ghi lại thông tin về lệnh cuối cùng đã làm (ví dụ, nếu đó là phần bổ sung thì nếu có một yêu cầu thực hiện, có một chút cho điều đó, v.v.). Có một thanh ghi 'ắc quy' nơi kết quả của lệnh được đặt. Các tham số cho hướng dẫn có thể đi vào một trong nhiều thanh ghi mục đích chung, hoặc bộ tích lũy, hoặc trong địa chỉ bộ nhớ (dữ liệu HOẶC chương trình). Các mã opcodes khác nhau chỉ có thể được thực hiện trên dữ liệu ở một số nơi nhất định. Ví dụ, bạn có thể ADD dữ liệu từ hai thanh ghi mục đích chung và có kết quả hiển thị trong bộ tích lũy, nhưng bạn không thể lấy dữ liệu từ hai vị trí bộ nhớ dữ liệu và có kết quả hiển thị ở vị trí bộ nhớ dữ liệu khác. Bạn sẽ phải di chuyển dữ liệu mà bạn muốn vào thanh ghi mục đích chung, thực hiện thêm, sau đó di chuyển kết quả đến vị trí bộ nhớ bạn muốn. Đó là lý do tại sao lắp ráp được coi là khó khăn. Có nhiều thanh ghi trạng thái như kiến ​​trúc được thiết kế cho.Các kiến ​​trúc phức tạp hơn có thể có nhiều hơn để cho phép các lệnh phức tạp hơn. Những người đơn giản hơn có thể không.

Ngoài ra còn có khu vực bộ nhớ được gọi là ngăn xếp. Nó chỉ là một khu vực trong bộ nhớ cho một số vi điều khiển (như 8051). Ở những người khác, nó có thể có sự bảo vệ đặc biệt. Có một thanh ghi được gọi là con trỏ ngăn xếp ghi lại vị trí bộ nhớ nào là 'trên cùng' của ngăn xếp. Khi bạn 'đẩy' thứ gì đó vào ngăn xếp từ bộ tích lũy thì địa chỉ bộ nhớ 'trên cùng' được tăng lên và dữ liệu từ bộ tích lũy được đặt vào địa chỉ cũ. Khi lấy hoặc popping dữ liệu từ ngăn xếp, đảo ngược được thực hiện và các con trỏ ngăn xếp được giảm đi và dữ liệu từ ngăn xếp được đưa vào bộ tích lũy.

Bây giờ tôi cũng đã sắp xếp các loại kính hướng dẫn cách 'thực thi'. Vâng, đây là khi bạn nhận được xuống để logic kỹ thuật số - VHDL loại công cụ. Multiplexers và bộ giải mã và bảng chân lý và như vậy. Đó là gritty nitty thực sự của thiết kế - loại. Vì vậy, nếu bạn muốn 'di chuyển' nội dung của một vị trí bộ nhớ vào bộ tích lũy, bạn phải tìm ra địa chỉ logic, xóa thanh ghi ắc quy, VÀ nó với dữ liệu ở vị trí bộ nhớ, v.v .. bạn đã thực hiện các phần riêng biệt (như giải quyết, một nửa adder, vv) trong VHDL hoặc trong bất kỳ thời trang logic kỹ thuật số bạn có thể có một ý tưởng những gì cần thiết.

Điều này liên quan đến C như thế nào? Vâng, một trình biên dịch sẽ thực hiện các lệnh C và biến chúng thành một loạt các opcodes thực hiện các hoạt động được yêu cầu. Tất cả điều đó về cơ bản là dữ liệu hex - một và số 0 được đặt tại một số điểm trong bộ nhớ chương trình. Điều này được thực hiện với các chỉ thị trình biên dịch/liên kết cho biết vị trí bộ nhớ được sử dụng cho mã nào. Nó được ghi vào bộ nhớ flash trên chip, và sau đó khi chip khởi động lại, nó sẽ chuyển đến vị trí bộ nhớ mã 0x0000 và JUMPs đến địa chỉ bắt đầu của mã trong bộ nhớ chương trình, sau đó bắt đầu cắm vào mã opcodes.

+0

Khi đặt lại, bộ xử lý bắt đầu thực hiện tại vectơ khởi động lại có thể hoặc không thể ở vị trí 0x0000. Bạn phải xem bảng dữ liệu bộ vi xử lý cụ thể cho vị trí của vectơ khởi động lại. – tkyle

0

Bạn có thể tham khảo liên kết https://automotivetechis.wordpress.com/.

Trình tự sau đây miêu tả tổng quát các chuỗi các hành hướng dẫn điều khiển:

1) Phân bổ bộ nhớ chính để thực hiện của chương trình.

2) Sao chép không gian địa chỉ từ bộ nhớ thứ cấp sang bộ nhớ chính.

3) Sao chép các phần .text và .data từ tệp thực thi vào bộ nhớ chính.

4) Sao chép đối số chương trình (ví dụ: đối số dòng lệnh) vào ngăn xếp.

5) Khởi tạo thanh ghi: đặt esp (con trỏ ngăn xếp) để trỏ lên đầu ngăn xếp, xóa phần còn lại.

6) Nhảy để bắt đầu thường trình, trong đó: sao chép chính() 's đối số tắt của ngăn xếp, và nhảy đến chính().

Các vấn đề liên quan