Memory Map

phanhao

Member
gemgem
Tham gia
18/03/2025
Bài viết
160
Được Like
0
Coin
0
Points
800
1. Hiểu về Memory Map

Bộ nhớ (memory) trong mọi vi điều khiển (microcontroller) đều được chia thành các vùng (region) khác nhau liên quan đến các chức năng cụ thể của microcontroller. Mặc dù giữa các microcontroller có hành vi (behavior) và khả năng (capability) tương tự nhau, nhưng vẫn có sự khác biệt ở các vùng nhớ (memory region), cách tổ chức giữa những microcontroller và thậm chí là trong cùng một họ microcontroller. Mặc dù mỗi microcontroller được tổ chức khác nhau, developer vẫn có thể phát triển những driver có thể tái sử dụng (reusable) và dễ dàng di chuyển (portable) từ microcontroller này sang microcontroller khác.

Để tạo driver, developer phải hiểu về các memory region khác nhau, mục đích của chúng và các kỹ thuật có sẵn trong ngôn ngữ lập trình C để ánh xạ (map) tới các memory region đó. Memory được tổ chức thành các vùng khác nhau, chẳng hạn như CPU, ROM, RAM, FLASH, Ngoại vi (Peripheral) và EEPROM. Các region này được kết nối với CPU thông qua nhiều loại bus, nhưng các chi tiết cụ thể sẽ biến đổi từ kiến trúc này sang kiến trúc khác. Figure 3-1 cho thấy một ví dụ về những gì một developer mong đợi sẽ tìm thấy trong bản đồ bộ nhớ (memory map).

Figure-3-1-Microcontroller-memory-regions.jpg


Các vùng bộ nhớ ROM được lập trình bởi nhà sản xuất microcontroller và chứa bất kì thứ gì mà nhà sản xuất cảm thấy hữu ích với khách hàng. Ví dụ như người ta thường tìm thấy các thuật toán bootloader, điều khiển động cơ (motor control) hoặc flash được lưu trữ vĩnh viễn trong các vùng ROM này. Developer không thể sửa đổi vùng bộ nhớ ROM và các thuật toán ở đó là vĩnh viễn. Một vùng ROM không được tính vào tổng code space có sẵn cho một developer. Developer có thể truy cập các thuật toán được lưu trữ trong ROM bằng cách ánh xạ một con trỏ hàm tới code nằm ở đó và tham chiếu (de-referencing) nó.

Các vùng bộ nhớ RAM là những khu vực bộ nhớ dễ bay hơi (volatile memory) có thể được lập trình trong quá trình thực thi chương trình nhưng sẽ mất dữ liệu khi reset, power cycle hoặc tắt nguồn. RAM chứa program stack, heap và các biến được cấp phát tĩnh (static). Developer phải cho trình biên dịch (compiler) biết memory area nào sẽ chứa program stack – đối với một ứng dụng baremetal, và heap (chứa các biến được cấp phát động (dynamic) như stack trong một Hệ Điều Hành Thời Gian Thực (RTOS) và các nhu cầu khác của ứng dụng). Một khi các memory region này đã được chỉ định, những memory còn lại có thể được sử dụng cho các biến ứng dụng có mục đích chung (general-purpose application variable) và lưu trữ dữ liệu (data storage).

Các vùng bộ nhớ Flash chứa các hướng dẫn ứng dụng có thể thực thi, bảng dữ liệu (data table – chẳng hạn như dữ liệu hiệu chuẩn) và các giá trị biến được khởi tạo. Nói chung, các vùng bộ nhớ flash được lập trình khi một thiết bị được sản xuất. Tuy nhiên, nội dung flash có thể được sửa đổi thông qua một ứng dụng bootloader. Nội dung flash được giám sát cẩn thận trong quá trình develop chương trình để đảm bảo rằng khu vực có kích thước thích hợp chứa được toàn bộ ứng dụng. Một nguyên tắc hay đó là xác định kích thước vùng flash đủ cho phép các tính năng mới được thêm vào trong suốt vòng đời sản phẩm.

Vùng CPU chứa các control register cho chính CPU, đôi khi liên quan đến ngắt (interrupt), lỗi (fault), ngoại lệ (exception) và clock control. Những CPU register thường được khởi tạo bằng start-up code, với những cung cấp giao diện (interface) cho memory region của nhà sản xuất. Các CPU region thường được trừu tượng hóa để che giấu hoạt động nội của microcontroller với developer.


CASE STUDY – GIẢ SỬ GIÁ TRỊ RAM ĐƯỢC BẢO TOÀN

Trong nhiều dịp, tôi đã thấy khách hàng có ý tưởng độc đáo để tiết kiệm memory và thời gian của ứng dụng bằng cách dùng RAM để lưu trữ data giữa những power cycle. Giả sử rằng, bằng cách ghi data vô RAM, thực hiện một reset, và kế đó power up, tiếp theo khu vực memory có thể sẽ được đọc với giá trị và trạng thái của ứng dụng trước đó. Tôi đã thấy nhiều developer có ý định làm việc này khi tạo một bootloader. Data được lưu trữ nhằm báo cho ứng dụng biết rằng nên load ứng dụng hay bootloader.

Vấn đề chính của phương pháp sử dụng RAM để lưu trữ data giữa mỗi lần reset là data được lưu trữ trong memory KHÔNG đảm bảo còn giữ nguyên giữa mỗi power cycle hoặc reset. Data có thể được bảo toàn trong hầu hết các trường hợp nhưng chắc chắn đôi khi bị xóa hoặc bị hư, dẫn đến hành vi không mong muốn. Việc giả sử rằng RAM data vẫn tồn tại giữa những power cycle sẽ gây hậu quả là software bug khó phát hiện và khó tái tạo.


Các vùng EEPROM ít khi và hầu như không có trên hầu hết microcontroller. EEPROM cung cấp cho developer một vùng làm việc an toàn để chỉnh sửa data. Data được ngăn cách khỏi flash và cung cấp một phương tiện an toàn để cập nhật data mà không bị nguy cơ xóa application code bất ngờ. Những microcontoller không bao gồm EEPROM thường sẽ cung cấp những thư viện flash có thể được dùng để dùng cho hành vi của EEPROM nhưng sẽ có nguy cơ hư hại application code.

Vùng bộ nhớ Peripheral là nơi được quan tâm nhất đối với các driver developer. Vùng bộ nhớ peripheral chứa đựng các register điều khiển peripheral của microcontroller, như là general-purpose input và output (GPIO), analog-to-digital converter, serial peripheral interface, và nhiều loại khác. Để tạo một driver, một developer phải ánh xạ (map) driver code tới vùng bộ nhớ (memory region) mà các peripheral register hiện diện. Một lần nữa, những vùng này là khác nhau với mỗi microcontroller. Ở chương này, chúng ta sẽ thảo luận kĩ thuật tổng quát và chiến lược để develop driver. Sau đó, ở chương tiếp theo chúng ta sẽ đi sâu vào những chi tiết tường tận.

Các vùng bộ nhớ cho một microcontroller không bắt buộc phải tiếp giáp với nhau ở bất kỳ hình dạng hoặc hình thức nào. Memory map có thể bắt đầu với các vị trí bộ nhớ cho application code, chuyển sang RAM, sau đó là những peripheral, rồi quay lại application code. Thậm chí có thể có những khoảng trống lớn giữa các vị trí bộ nhớ (sử dụng được) thường được gọi là các memory-map hole.

Figure-3-2-Generic-microcontroller-memory-map.jpg


2. Planning the Driver Interfaces

Phát triển phần mềm nhúng hạn chế tài nguyên (resource-constrained embedded-software) dễ có xu hướng bị hỗn loạn. Quay lại lúc ngôn ngữ lập trình C mới được giới thiệu, các phương pháp hay nhất và kiến trúc phần mềm phân lớp (layered software architecture) chưa tồn tại. Phần mềm nhúng có rất nhiều câu lệnh goto. Driver code được kết hợp chặt chẽ với application code, và không có sự phân biệt rõ về nơi bắt đầu hoặc kết thúc middleware. Kết quả là một mớ code khổng lồ xứng đáng với cái tên spaghetti code (như món mì Ý).

Bây giờ, đối với những người chưa rút ra được mối liên hệ, Beningo là một cái tên Ý, và giống như bất kỳ người Ý tốt bụng nào, tình yêu với mì ống (pasta) là một điều được ban tặng. Trong trường hợp này, sự tương tự giữa mì ống và cách thức mà phần mềm được cấu trúc là hoàn toàn phù hợp. Hãy dành một chút thời gian để xem xét điều này: mì spaghetti hỗn loạn; các sợi mì đan xen vào nhau theo cách này và cách kia, dẫn đến sự thiếu hoàn chỉnh về cấu trúc. Viết code không có cấu trúc giống hệt như mì spaghetti; với mỗi lần cắn bạn không nhận được manh mối gì! (Ít nhất với mì Ý thì nó sẽ rất ngon!)

Mặt khác, lasagna (một dạng mì Ý dạng tấm hoặc lát)! Các sợi mì được xếp thành từng lớp, tạo ra cấu trúc và thứ tự bữa ăn. Code được develope bằng cách sử dụng các lớp không chỉ dễ hiểu hơn mà còn có khả năng xóa bỏ một lớp và thêm một lớp mới, về cơ bản cho phép tái sử dụng và dễ bảo trì. (Đôi khi, tôi muốn hoán đổi các lớp lasagna, nhưng tôi luôn thấy ăn nó sẽ ngon hơn!) Hãy nhớ rằng — chúng ta muốn viết lasagna code, không phải spaghetti code! Figure 3-3 là một ví dụ về kiến trúc phần mềm mà developer có thể chọn để tách các lớp phần mềm khác nhau.

Figure-3-3-The-lasagna-software-architecture.jpg


Việc viết phần mềm theo cách phân lớp (layer), giống như lasagna cho phép developer dễ dàng xác định nơi một kiểu phần mềm (hoặc gọi là layer) kết thúc và một kiểu khác bắt đầu. Tại thời điểm đó, chúng ta có cái được gọi là giao diện phần mềm (software interface). Trong Figure 3-3, chúng ta có bốn giao diện khả thi cần được xác định rõ ràng. Giao diện cho phép lớp phần mềm bên trên trực tiếp tương tác với phần mềm hoặc có thể là phần cứng tồn tại bên dưới nó. Việc xác định một giao diện sạch sẽ (clean) và có thể mở rộng (extensible) cho phép các developer tổ chức code và cung cấp một giao diện chung có thể tái sử dụng từ ứng dụng này sang ứng dụng khác.



Figure-3-4-Component-identification.jpg


Các giao diện thành phần (component interface) xuất hiện khi một lớp này chạm vào lớp khác. Giao diện (interface) sẽ bao gồm các chức năng (function) để làm một số hành động được thực hiện bởi thành phần (component), chẳng hạn như chuyển đổi (toggle) trạng thái pin, thiết lập (set) một thanh ghi hoặc đơn giản là đọc (read) dữ liệu. Để các chức năng đó — giao diện — hoạt động như mong đợi, nó cực kỳ hữu ích cho các developer khi tạo mối quan hệ hợp đồng giữa giao diện và các developer sử dụng nó
 
Top Bottom