Cấu trúc chương trình (Phần 1)

(Phần 1) , (Phần 2)

Có rất nhiều khó khăn mà người lập trình sẽ gặp phải khi thiết kế một chương trình hệ thống nhúng. Thông thường, trước khi bắt tay vào việc viết mã nguồn, điều đầu tiên phải làm là xem xét thiết kế cấu trúc của chương trình, đặc biệt là với các hệ thống lớn, có nhiều chức năng.

Trong một chương trình nhúng, ta không thể lập trình tất cả chức năng vào trong một vòng lặp chính. Điều đó sẽ làm cho vòng lặp chính trở nên quá chi tiết, quá dài và không thể kiểm soát được. Phương pháp thường dùng ở đây là chia chương trình thành các phần nhỏ, hay còn gọi là các module. Việc chia chương trình thành module có các lợi ích sau:

  • Làm chương trình dễ hiểu, có cấu trúc và dễ dàng cho việc biên soạn các tài liệu mô tả, các chỉ dẫn.
  • Dễ dàng kiểm tra các module về mặt chức năng cũng như tốc độ thực thi.
  • Các module đã được phát triển và kiểm tra có thể sử dụng lại (reusable), giúp rút ngắn thời gian phát triển chương trình.
  • Việc chia chương trình thành các module cho phép nhiều người cùng tham gia phát triển một chương trình.
  • Chương trình có cấu trúc tốt giúp việc nâng cấp dễ dàng.

1. Vai trò của các hàm (function)

Như đã trình bày ở phần 12. Hàm (function). Hàm hay function là một khối câu lệnh trong một chương trình lớn. Các hàm làm các công việc được thiết riêng, và có thể xem như độc lập với chương trình chính. Thông thường, các hàm nhận các tham số vào, xử lý và trả về giá trị. Ngoài ra, khi lập trình cho hệ thống nhúng ta cũng thường gặp các hàm thực thi các công việc cụ thể, ví dụ như bật một LED. Các hàm này không cần nhận tham số vào cũng như không trả về kết quả xử lý.

Việc sử dụng hàm trong chương trình có nhiều lợi ích như sau:

  • Một hàm được viết và biên dịch đúng một lần, nó chỉ chiếm một vùng nhất định trong bộ nhớ chương trình, bất kể nó được gọi bao nhiêu lần. Điều này làm giảm kích thước chương trình.
  • Khi cần thay đổi hay sửa chữa chức năng, người lập trình chỉ cần sửa chữa hàm. Điều này đặc biệt có lợi trong trường hợp hàm được gọi nhiều lần. Nếu như ta không sử dụng hàm mà sử dụng các đoạn chương trình giống nhau lặp đi lặp lại, quá trình sửa chữa nâng cấp chương trình sẽ trở nên rất khó khăn và mất thời gian.
  • Việc sử dụng các hàm làm cho chương trình trở nên có cấu trúc, dễ đọc và dễ sửa chữa, đồng thời cho phép nhiều người cùng phát triển chương trình. Một lập trình viên có thể phát triển một chức năng này của hệ thống, trong lúc những người khác phát triển các chức năng khác.

Sử dụng hàm trong C cũng có những nhược điểm cần lưu ý như sau:

  • Hàm là một đoạn chương trình nằm ở một địa chỉ trong bộ nhớ. Khi thực hiện lệnh gọi đến hàm, CPU phải thực hiện các công việc cất các thanh ghi quan trọng vào stack, thực hiện một lệnh nhảy đến địa chỉ của hàm, thực hiện cấp phát bộ nhớ cho các biến nội bộ, sao chép giá trị của các tham số vào các biến nội bộ, rồi mới bắt đầu thực sự thực thi các lệnh trong hàm. Quá trình này sẽ làm chậm tốc độ thực hiện chương trình.
  • Các hàm chỉ có thể trả về một giá trị, và mảng các giá trị không thể được đưa vào như tham số của hàm. Tuy nhiên vấn đề này có thể được giải quyết nếu ta sử dụng pointer như ở phần Truyền tham chiếu vào hàm.

2. Sử dụng lưu đồ để thiết kế cấu trúc chương trình

Thông thường khi thiết kế chương trình, lưu đồ được sử dụng để mô tả luồng chương trình và cách sử dụng các hàm. Lưu đồ được thiết trước khi mã chương trình được viết. Hình 2-1 mô tả một số ký hiệu cơ bản dùng trong thiết kế lưu đồ.

Hình 2-1: Một số ký hiệu cơ bản được dùng vẽ lưu đồ

Ví dụ ta cần thiế kế một chương trình thực hiện công việc sau:

Viết một chương trình xuất các giá trị từ 0 đến 9 ra 1 LED 7 đoạn sau mỗi khoảng thời gian 50 ms, sau đó quay về 0 và lặp lại. Lưu đồ cho chương trình trên được mô tả ở Hình 2-2.

Như mô tả ở Hình 2-2, chương trình sẽ bao gồm các quá trình sau:

  1. Sử dụng một hàm để chuyển giá trị từ giá trị đếm A sang mã hiển thị của LED 7 đoạn B.
  2. Xuất giá trị B ra port giao tiếp LED 7 đoạn sẽ hiển thị.
  3. Tăng giá trị đếm A.
  4. Nếu A lớn hơn 9, cho A bằng 0
  5. Sử dụng một hàm tạo trễ 500ms để làm chậm tốc độ thay đổi giá trị trên LED.
Hình 2-2: Lưu đồ cho chương trình hiển thị LED

Sử dụng lưu đồ cho phép có cái nhìn tổng quát về cách chương trình hoạt động, cũng như thấy được những phần quan trọng của chương trình. Lưu đồ cũng cho phép những người không am hiểu kỹ thuật có thể hiểu về hệ thống, ví dụ như những khách hàng. Điều này hết sức quan trọng trong việc thiết kế một hệ thống thỏa mãn các yêu cầu ở mức chi tiết nhất.

3. Mã giả (pseudo code)

Pseudo code là các dòng giải thích ngắn, để giải thích các tác vụ cụ thể trong một chương trình. Lý tưởng thì pseudo code không chứa đựng bất kỳ từ khóa của bất kỳ ngôn ngữ lập trình nào. Pseudo code có thể được viết như là một chuỗi liên tiếp các cụm từ, ta có thể vẽ các mũi tên để biểu thị vòng lặp. Các khoảng cách đầu dòng có thể được sử dụng để biểu thị luồng của chương trình.

Viết pseudo code giúp rút ngắn thời gian trong việc viết mã nguồn và kiểm tra chương trình. Nó cũng giúp cho việc bàn luận giữa người thiết kế, lập trình viên và người quản lý dự án trở nên dễ dàng hơn. Có những dự án sử dụng pseudo code, có dự án sử dụng lưu đồ, và đôi khi sử dụng cả hai phương pháp để thiết kế chương trình.

Lưu ý rằng pseudo code nên được viết bằng tiếng Anh, vì tiếng Anh cho phép viết những câu ngắn gọn nhưng đủ ý, hơn nữa hầu hết các ngôn ngữ lập trình đều được viết bằng tiếng Anh.

Chương trình được thiết kế bởi lưu đồ ở Hình 2.2 có thể được thiết kế bằng pseudo code như sau:

Program start
Initialise variable A=0
Initialise variable B
Start infinitive loop
    Call function SegConvert with input A, place result in B
    Output B to Led port
    Increment A
    If A > 9
        A = 0
    Call function Delay500ms
End infinitive loop

Hình 2-3: Pseudo code cho chương trình trên

4. Chia chương trình thành các module

Ở các phần trên, ta đã bàn về cách chia chương trình thành các hàm, thiết kế chương trình sử dụng lưu đồ pseudo code. Tuy nhiên, khi chương trình trở nên lớn và phức tạp hơn, việc đưa tất cả các hàm vào một file chương trình sẽ làm cho chương trình trở nên quá dài, gây khó khăn cho việc đọc hiểu, thiết kế và nâng cấp chương trình.

Khi đó, chương trình được chia thành các module trong các file khác nhau, mỗi module sẽ thực thi một chức năng cụ thể. Ví dụ một hệ thống điều khiển lò nhiệt có thể bao gồm ba file, một file cho việc đo nhiệt độ và điều khiển dây đốt, một file cho các chức năng giao tiếp nút nhấn và hiển thị, và một file chứa chương trình chính kết hợp hai module kia lại với nhau.

Việc chia chương trình thành các file khác nhau một lần nữa giúp cho chương trình dễ hiểu, dễ quản lý, phát triển và nâng cấp. Nó cũng giúp cho nhiều lập trình viên có thể phát triển các chức năng của hệ thống một cách độc lập.

Các module đã được viết và kiểm tra có thể được sử dụng lại trong các thiết kế mới mà không cần phải thiết kế lại. Ví dụ như nhà sản xuất cần thiết kế một mẫu hệ thống điều khiển lò nhiệt mới với giao diện và nút nhấn khác, chỉ có module chức năng giao tiếp và hiển thị cần được thiết kế lại, các module khác vẫn được giữ nguyên.

Để liên kết các file lại với nhau, ta sử dụng header file. Thông thường, trong một chương trình nhúng file main.c sẽ chứa các đoạn mã ở mức cao (thường là các lệnh gọi đến các hàm), còn bản thân các hàm và các biến toàn cục được định nghĩa trong các file C khác.

Chương 1: Cơ bản về lập trình C cho hệ thống nhúng

Chương 2: Cấu trúc chương trình

Icons made by Freepik from www.flaticon.com