“A good scientist is a person with original ideas. A good engineer is a person who makes a design that works with as few original ideas as possible.”
—Freeman Dyson
1. Tại sao tái sử dụng code lại quan trọng
Trong vài thập kỷ qua, các hệ thống nhúng đã dần dần tăng độ phức tạp. Sự ra đời của Internet còn thúc đẩy nhanh quá trình này vì xã hội của chúng ta đang trong một cuộc chạy đua kết nối mọi thiết bị mà con người có thể tưởng tượng ra. Trước đây các hệ thống thường đơn giản và độc lập giờ đây đều phải được kết nối Internet một cách bảo mật và an toàn để truyền tải thông tin quan trọng lên đám mây (cloud). Độ phức tạp và tính năng ngày càng tăng theo quy luật hàm mũ, với mỗi thế hệ thiết bị đều buộc các kỹ sư phải đánh giá lại việc làm thế nào để phát triển và hoàn thành phần mềm nhúng trong thời gian và ngân sách cho phép.
Nhu cầu gia tăng các tính năng của sản phẩm, cùng với nhu cầu kết nối các hệ thống với Internet, đã làm tăng đáng kể số lượng phần mềm cần được phát triển để tung ra một sản phẩm. Trong khi độ phức tạp và tính năng của phần mềm ngày càng tăng, thời gian để phát triển một sản phẩm phần lớn vẫn không đổi, với sự gia tăng không đáng kể về thời gian phát triển (hai tuần trong năm năm), như có thể thấy trong Figure 1-1. Để đáp ứng các mốc thời gian của dự án, các nhà phát triển buộc phải mua phần mềm thương mại bán sẵn (COTS) để có thể giảm thời gian phát triển hoặc dùng lại càng nhiều code từ các dự án trước đó càng tốt.
Firmware của vi điều khiển thường được phát triển cho một ứng dụng cụ thể bằng cách sử dụng các phương pháp thiết kế chức năng (functional design methodologies) mà application code thường dính liền trực tiếp với phần cứng cấp thấp (low-level hardware), khiến cho phần mềm trở nên khó dùng, chính xác hơn là không thể sử dụng lại hoặc không thể chuyển sang một kiến trúc phần cứng tương tự, chưa nói đến chuyện sử dụng lại trên một kiến trúc khác. Nhân tố cơ bản đằng sau khiến việc phát triển firmware xài một lần là do bản chất hạn chế tài nguyên của nhiều sản phẩm nhúng. Các vi điều khiển có RAM lớn hơn vài kilobyte và kích thước flash lớn hơn 16 kB đã từng rất mắc và không thể dùng để thiết kế thành sản phẩm mà không thể có hy vọng tạo ra lợi nhuận. Các nhà phát triển phần mềm nhúng đã không có bộ nhớ đủ lớn hoặc bộ xử lý đủ mạnh để làm việc, điều này ngăn kỹ thuật thiết kế phần mềm hiện đại được dùng trong phát triển ứng dụng nhúng.
Các vi điều khiển hiện đại đang bắt đầu thay đổi cuộc chơi. Một vi điều khiển ARM Cortex-M cấp thấp điển hình hiện có giá chỉ vài đô la Mỹ và cung cấp tối thiểu cho bộ nhớ RAM là 16 kB và bộ nhớ flash là 64 kB. Giá thành bộ nhớ giảm đáng kể, bộ nhớ khả dụng lớn hơn và các kiến trúc CPU hiệu quả hơn đang loại bỏ bản chất hạn chế tài nguyên mà các nhà phát triển firmware từng bị mắc kẹt. Kết quả là các nhà phát triển bây giờ có thể bắt đầu sử dụng các phương pháp thiết kế mà tách rời application code khỏi hardware và cho phép tối đa hóa việc tái sử dụng code.
2. Portable Firmware
Ngày nay, việc phát triển Firmware được viết theo cách khá cổ điển (lỗi thời). Mỗi chu kỳ phát triển sản phẩm đều dẫn đến bị giới hạn ở vấn đề không tái sử dụng code được, với chuyện tái phát minh là chủ đề chính giữa các nhóm phát triển. Một ví dụ đơn giản là khi các nhóm phát triển từ chối sử dụng một hệ điều hành thời gian thực (RTOS) có sẵn, mà thay vào đó là phát triển một trình tự nội bộ của riêng họ.
THUẬT NGỮ CHUYÊN NGÀNH
Portable firmware là phần mềm nhúng được thiết kế để chạy trên nhiều vi điều khiển hoặc kiến trúc bộ xử lý với ít hoặc không có sửa đổi
Thứ nhất, gần như mọi nhóm phát triển đều tự viết các driver bởi vì những nhà sản xuất vi điều khiển chỉ cung cấp các example code chứ không phải các driver hoàn chỉnh. Các example mang đến một bước khởi đầu để hiểu các thiết bị ngoại vi (peripherals) của vi điều khiển, nhưng nó vẫn đòi hỏi thời gian đáng kể để có một hệ thống phù hợp với mục đích sản xuất. Có thể có một trăm công ty cùng sử dụng vi điều khiển giống nhau và mỗi công ty sẽ lãng phí tới 30% hoặc hơn tổng thời gian phát triển của họ để viết và tích hợp các driver cho vi điều khiển với middleware của họ! Tôi đã thấy điều này xảy ra nhiều lần ở khách hàng của mình và đã nghe rất nhiều câu chuyện có thật từ hàng trăm kỹ sư mà tôi tiếp xúc hàng năm.
Thứ hai, có rất nhiều tính năng cần được đóng gói trong một sản phẩm và với chu kỳ thiết kế điển hình là 12 tháng1, các nhà phát triển không dành thời gian để kiến trúc hóa hệ thống của họ cho phù hợp để tái sử dụng. Do đó code ứng dụng cấp cao bị dính chặt với code vi điều khiển cấp thấp, khiến cho việc chia tách, tái sử dụng hoặc porting code ứng dụng trở nên tốn kém, mất thời gian và nhiều lỗi. Kết cục là các nhà phát triển luôn phải bắt đầu lại từ đầu.
Để bắt kịp với tốc độ phát triển nhanh chóng trong chu kỳ thiết kế ngày nay, các nhà phát triển cần có kỹ năng cao trong việc phát triển portable firmware. Portable firmware là phần mềm nhúng được thiết kế để chạy trên nhiều vi điều khiển hoặc kiến trúc bộ xử lý với ít hoặc không có sửa đổi. Việc viết firmware có thể port từ kiến trúc vi điều khiển này sang kiến trúc vi điều khiển khác mang đến nhiều ưu điểm trực tiếp, chẳng hạn như:
- Giảm thời gian sản xuất do không phải lặp lại vòng tuần hoàn phát minh (có thể tốn thời gian)
- Giảm chi phí dự án bằng cách tận dụng các thành phần và thư viện hiện có
- Nâng cao chất lượng sản phẩm thông qua việc sử dụng phần mềm đã được đảm bảo và kiểm tra liên tục
Portable firmware cũng có một số lợi thế gián tiếp mà nhiều nhóm bỏ qua nhưng điều đó có thể vượt xa những lợi ích trực tiếp, chẳng hạn như:
- Có nhiều thời gian hơn trong một chu kỳ phát triển để tập trung vào đổi mới và khác biệt hóa sản phẩm
- Giảm mức độ căng thẳng trong nhóm do giới hạn tổng lượng code cần được phát triển (các kỹ sư vui vẻ, thoải mái thường sáng tạo và khoa học hơn)
- Code có tổ chức và được ghi chép đầy đủ có thể giúp port và bảo trì dễ dàng hơn, tiết kiệm chi phí hơn
Sử dụng code có thể port và tái sử dụng mang lại kết quả rất nhanh và đáng kinh ngạc, nhưng cũng có một số nhược điểm. Những bất lợi liên quan đến thời gian và công sức trả trước, chẳng hạn như:
- Kiến trúc phần mềm cần được suy nghĩ thấu đáo
- Hiểu được sự khác biệt về kiến trúc tiềm ẩn giữa các bộ vi điều khiển
- Phát triển các bài kiểm tra hồi quy để đảm bảo chuyển thành công
- Lựa chọn ngôn ngữ thời gian thực và hiểu khả năng tương tác hoặc hạn chế của chúng
- Có sẵn các kỹ sư có kinh nghiệm và tay nghề cao để phát triển một kiến trúc port được và có thể mở rộng
Để các nhóm phát triển tận hưởng thành công từ những lợi ích của việc sử dụng portable code, cần chi thêm thời gian và tiền bạc trước. Tuy nhiên, sau khoản đầu tư ban đầu, các chu kỳ phát triển sẽ bắt đầu có tiềm năng giảm thời gian phát triển theo tháng so với chu kỳ thiết kế phần mềm nhúng truyền thống. Những lợi ích lâu dài và tiết kiệm chi phí thường làm lu mờ chi phí thiết kế đầu tiên, cùng với tiềm năng đẩy nhanh tiến độ phát triển.
Việc phát triển firmware với mục đích sử dụng lại cũng có nghĩa là các nhà phát triển có thể bị kẹt với một ngôn ngữ lập trình duy nhất. Làm thế nào để người ta chọn một ngôn ngữ cho phần mềm có thể tồn tại trong một thập kỷ hoặc lâu hơn? Sử dụng một ngôn ngữ lập trình duy nhất không phải là vấn đề lớn trong phát triển phần mềm nhúng, bất chấp những gì người ta có thể nghĩ ban đầu. Ngôn ngữ nhúng phổ biến nhất, ANSI-C, được phát triển vào năm 1972 và đã được chứng minh là gần như không thể bị soán ngôi. Figure 1-2 cho thấy sự phổ biến của các ngôn ngữ lập trình được sử dụng trong các hệ thống nhúng. Bất chấp những tiến bộ trong khoa học máy tính và sự phát triển của các ngôn ngữ lập trình hướng đối tượng, C vẫn rất phổ biến như một ngôn ngữ chung và được sử dụng nhiều trong phần mềm nhúng.
Mức độ phổ biến và sử dụng ổn định của ngôn ngữ lập trình C dường như sẽ không thay đổi sớm. Khi nào và nếu Internet of Things (IoT) bắt đầu đạt được động lực, C thậm chí có thể bắt đầu phát triển trong việc sử dụng và phổ biến khi mà hàng triệu thiết bị được phát triển và triển khai đều sử dụng nó. Phát triển phần mềm có thể port và tái sử dụng trở thành một lựa chọn khả thi khi người ta xem xét việc sử dụng ổn định và gần như bất biến mà ngôn ngữ C được hưởng trong ngành phát triển hệ thống nhúng. Khi một nhóm phát triển xem xét các mốc thời gian, nhu cầu tính năng và ngân sách hạn chế cho chu kỳ phát triển sản phẩm, việc phát triển portable code nên được coi là một yêu cầu bắt buộc.
CASE STUDY — PHÁT TRIỂN FIRMWARE CHO TẤM PIN MẶT TRỜI THÔNG MINH
Khi người ta bàn về phát triển sản phẩm, điều bất biến là việc phát triển cần phải hoàn thành hôm qua hoặc trước một ngày tương lai không xa. Cách đây vài năm, vào ngày 1 tháng 12, tôi nhận được một cuộc gọi từ một khách hàng tiềm năng mà tôi đã từng nói chuyện phần lớn thời gian của năm đó. Khách hàng, là một công ty start-up trong ngành công nghiệp vệ tinh nhỏ, vừa nhận được tin rằng họ sẽ có cơ hội phóng tàu vũ trụ mới tiên tiến nhất của họ ở lần phóng sắp tới. Vấn đề là họ chỉ có sáu tuần để hoàn thành toàn bộ việc xây dựng, kiểm tra và vận chuyển vệ tinh của họ!
Một trong nhiều trở ngại mà họ phải đối mặt là các tấm pin mặt trời thông minh của họ (thông minh vì chúng chứa rất nhiều cảm biến quan trọng để ổn định tàu vũ trụ) chưa có một dòng code firmware nào được viết. Firmware của các tấm pin mặt trời phải được hoàn thành trước ngày 1 tháng 1, chỉ có bốn tuần trong một tháng nghỉ lễ để thiết kế, thực hiện, kiểm tra và triển khai firmware. Để đưa ra một số định lượng cho phạm vi dự án, dưới đây là một số thành phần phần mềm cần làm:
- GPIO, SPI, I2C, PWM, UART, Flash, ADC
- Timer và system tick
- H-bridge control
- Task scheduler
- Đo gia tốc
- Đo từ trường
- Các thuật toán điều chỉnh
- Khả năng khắc phục lỗi
- Theo dõi tình trạng sức khỏe, nhận thức
- Giao thức giao tiếp máy tính chuyến bay
Một nhà phát triển kinh nghiệm sẽ biết danh sách trên không thể hoàn tất trong bốn tuần. Chỉ riêng I2C thôi cũng có thể mất hai tuần để phát triển, và ngày giao hàng thực tế cho dự án có thể từ ba đến bốn tháng, chứ không phải vài tuần.
Tôi đã nhận dự án và tận dụng các kỹ thuật HAL và driver tương tự được trình bày trong cuốn sách này để hoàn thành dự án. Tôi đã dành một ngày để sửa các driver hiện có và làm một số thay đổi nhỏ để phù hợp với vi điều khiển của vệ tinh. Tuần thứ hai được dành để kết hợp application code và các driver còn lại với nhau. Cuối cùng, tuần thứ ba là kiểm tra, gỡ lỗi và giao sản phẩm – vừa kịp dịp Giáng sinh và làm hài lòng khách hàng.
Không nên xem nhẹ quyết định phát triển portable firmware. Để phát triển firmware thực sự portable và reuseable, có một số đặc điểm mà nhà phát triển nên xem xét và đảm bảo rằng firmware đó sẽ thể hiện. Đầu tiên, phần mềm cần phải được module hóa. Viết một ứng dụng hoàn toàn trong một source file duy nhất không phải là một cách hay (đúng, tôi vẫn thấy điều này ngay cả ở năm 2016). Phần mềm cần được chia thành các mảng nhỏ có thể quản lý được, có độ phụ thuộc tối thiểu giữa những module và function tương tự được nhóm lại với nhau.
10 TIÊU CHUẨN CHẤT LƯỢNG CỦA PORTABLE FIRMWARE
Portable Firmware là…
- module
- được ghép nối lỏng lẻo (is loosely coupled)
- có sự gắn kết cao (has high cohesion)
- tuân thủ ANSI-C
- có một giao diện sạch sẽ
- có một lớp trừu tượng phần cứng (Hardware Abstraction Layer – HAL)
- có thể đọc và có thể bảo trì
- đơn giản
- sử dụng sự đóng gói (encapsulation) và kiểu dữ liệu trừu tượng (abstract data type)
- có hỗ trợ tài liệu đầy đủ
Portable software nên theo tiêu chuẩn ngôn ngữ lập trình ANSI-C. Những nhà phát triển nên tránh sử dụng những compiler intrinsic và C extension, bởi vì chúng là những đặc điểm riêng của compiler và sẽ không dễ để port giữa các toolchain. Ngoài việc tránh những tiện ích bổ sung (add-ons) này, họ nên chọn một tập hợp con an toàn và được chỉ định đầy đủ cho ngôn ngữ lập trình C. Các tiêu chuẩn như MISRA-C hoặc Secure C được chấp nhận bởi ngành công nghiệp có thể là lựa chọn tốt, giúp đảm bảo rằng firmware sẽ được sử dụng những cấu trúc an toàn.
Những nhà phát triển sẽ muốn đảm bảo rằng code sử dụng lại (reusable code) được hỗ trợ tài liệu đầy đủ và có nhiều example chi tiết. Firmware cần phải có một giao diện sạch sẽ, đơn giản và dễ hiểu. Quan trọng nhất là, họ sẽ muốn đảm bảo rằng một lớp trừu tượng phần cứng (HAL) đơn giản, có thể mở rộng (scalable), được bao gồm trong kiến trúc phần mềm. HAL sẽ xác định cách để application code tương tác với phần cứng nằm bên dưới. Chúng ta hãy xem xét một cách chi tiết hơn một số đặc điểm chính mà portable firmware cần thể hiện trước khi đi sâu vào các HAL.
1Embedded Marketing Study, 2009 – 2015, UBM
2Aspencore Embedded Systems Survey, 2017, www.embedded.com