Bài viết này giới thiệu cách làm thế nào để truy xuất GPIO bằng C++ trên kit RPI . Có 2 cách để thực hiện điều này :
- Cách thứ nhất là tác động trực tiếp tới các thanh ghi GPIO trên RPI giống như phương pháp lập trình vi điều khiển thông thường mà không có hệ điều hành (OS) . Ưu điểm của phương pháp này là hoàn toàn bỏ qua hệ điều hành, các chân GPIO sẽ được truy xuất rất nhanh. Tuy nhiên cách này không an toàn và có thể gây ra xung đột bởi sự truy xuất cùng lúc của nhiều quá trình đến các thanh ghi vật lý trên RPI.- Cách thứ hai : Sử dụng cách truy xuất GPIO an toàn thông qua driver đã được xây dựng sẵn trên Linux OS. Về cơ bản nó xem mỗi thuộc tính của mỗi chân GPIO như là 1 tập tin. Công việc của ta là thao tác với các tập tin này để điều khiển GPIO. Đây là phương pháp được ưu tiên để truy xuất GPIO .
Bài viết này sẽ trình bày theo cách thứ hai. Mô hình kết nối như sau :
Mục đích của lớp GPIOClass là cho phép điều khiển từng chân GPIO. Lớp này được thiết kế sao cho GPIO pin number được xác định thông qua hàm khởi tạo của lớp GPIOClass. file header “GPIOClass.h” được cung cấp bên dưới :
Mỗi object GPIOClass có các hàm thành viên cho phép ta export hoặc unexport GPIO pins, set direction của GPIO pins cũng như get value của GPIO pins. object GPIOClass có 1 biến private để lưu chỉ số GPIO pin.
Dưới đây là nội dung file “GPIOClass.cpp” :
Dưới đây là nội dung file “GPIOClass.cpp” :
Để open hoặc close file trong C++ ta sử dụng các class ifstream/ofstream (ifstream cho nhập (đọc) từ một file, ofstream cho xuất (ghi) tới một file) và fstream cho nhập/xuất (đọc/ghi) tới một file).
Viết một class để test có tên là “GPIOtest1.cpp” như sau :
Viết một class để test có tên là “GPIOtest1.cpp” như sau :
Chương trình GPIOtest1.cpp khá đơn giản. Đầu tiên nó khởi tạo hai đối tượng GPIOClass để truy xuất đến GPIO4 và GPIO17 (gpio4 và gpio17) trên header 26 chân .Chú ý tên object được đặt tượng trưng cho tên gpio nhưng ta vẫn có thể đặt tên khác.
Các chân GPIO sau đó được export, GPIO4 set thành output và GPIO17 set input. Vòng lặp vô hạn sẽ đọc trạng thái đầu vào tại chân GPIO17. Nếu trạng thái của chân này là ’0′ nghia là button có thể đã được nhấn. Trạng thái của chân GPIO17 được kiểm tra lại một lần nữa để chắc chắn rằng điều này là đúng và thực hiện set gpio4 lên mức ’1′ để bật LED. Một vòng lặp khác được thực hiện để kiểm tra rằng nếu GPIO17 vẫn còn đang nhấn thì LED trên GPIO4 vẫn tiếp tục sáng đến khi nào thả nút nhấn trên GPIO17.
Dòng đầu tiên mô tả tên của một biến cấu trúc kiểu sigaction có tên là sig_struct. Dòng thứ 2 gán tên của hàm xử lý mà khi có ngắt thì chương trình sẽ nhảy tới đó.Dòng thứ 3 với “sa_flags” mô tả tập hợp các cờ để set các trạng thái tín hiệu ngắt, ở đây mình không quan tâm, cứ set là 0. Dòng cuối cùng của đoạn code trên với sig_struct.sa_mask của cấu trúc sigaction sẽ được khởi tạo empty. sa_mask cho phép ta thiết lập thêm những tín hiệu nào sẽ bị chặn trong suốt quá trình xử lý ngắt.
Hàm sigaction sẽ được dùng để map “sig_struct” với tín hiệu SIGINT mỗi khi cấu trúc sig_struct được hoàn thành.
Các chân GPIO sau đó được export, GPIO4 set thành output và GPIO17 set input. Vòng lặp vô hạn sẽ đọc trạng thái đầu vào tại chân GPIO17. Nếu trạng thái của chân này là ’0′ nghia là button có thể đã được nhấn. Trạng thái của chân GPIO17 được kiểm tra lại một lần nữa để chắc chắn rằng điều này là đúng và thực hiện set gpio4 lên mức ’1′ để bật LED. Một vòng lặp khác được thực hiện để kiểm tra rằng nếu GPIO17 vẫn còn đang nhấn thì LED trên GPIO4 vẫn tiếp tục sáng đến khi nào thả nút nhấn trên GPIO17.
Phần 2-kết hợp ngắt
Có một lỗ hổng cơ bản trong chương GPIO ở bài trước.Thông thường khi chương trình đang chạy mà ta muốn thoát chương trình thì hay dùng thao tác đó là sử dụng tổ hợp phím Ctrl-C.Như zậy nếu với đoạn code trên thì chương trình sẽ thoát trực tiếp mà chưa hề được giải phóng bộ nhớ đã cấp cho 2 đối tượng gpio4 và gpio7. Có một cách để giải quyết vấn đề trến là viết hàm xử lý khi có tín hiệu đến (xử lý ngắt mềm) và gán nó đến sự kiện SIGINT (ctrl-C). Điều này cho phép chương trình sẽ nhảy đến signal handler này bất cứ khi nào ctrl-C được nhấn, thay vì thoát trực tiếp chương trình. Hàm xử lý ngắt sau đó có thể khởi tạo một phương pháp thoát chương trình tối ưu hơn ví dụ như giải phóng bộ nhớ và unexport các chân trước khi exit. Bắt đầu vào phân tích code, đầu tiên ta phải include :
Bước tiếp theo là khai báo và thiết lập một biến cấu trúc là sig_struct như sau :
Dòng đầu tiên mô tả tên của một biến cấu trúc kiểu sigaction có tên là sig_struct. Dòng thứ 2 gán tên của hàm xử lý mà khi có ngắt thì chương trình sẽ nhảy tới đó.Dòng thứ 3 với “sa_flags” mô tả tập hợp các cờ để set các trạng thái tín hiệu ngắt, ở đây mình không quan tâm, cứ set là 0. Dòng cuối cùng của đoạn code trên với sig_struct.sa_mask của cấu trúc sigaction sẽ được khởi tạo empty. sa_mask cho phép ta thiết lập thêm những tín hiệu nào sẽ bị chặn trong suốt quá trình xử lý ngắt.
Hàm sigaction sẽ được dùng để map “sig_struct” với tín hiệu SIGINT mỗi khi cấu trúc sig_struct được hoàn thành.
Ta cũng cần viết hàm xử lý ngắt “sig_handler”.Khi ctrl-C được nhấn, chương trình thực thi sẽ nhảy đến hàm xử lý ngắt và set cờ ctrl_c_pressed thành true. Một vòng lặp while trong hàm main sẽ kiểm tra cờ này.Nếu nó được set thành true bởi tín hiệu ngắt thì chương trình sẽ thực hiện các công việc cần thiết để giải phóng bộ nhớ và unexport các pin GPIO theo đúng các.
Còn đây là toàn bộ code chương trình “GPIOTest2.cpp” . Lưu ý là chương trình phải được chạy dưới quyền user root.
Cách biên dịch chương trình sử dụng g++:
g++ -fpermissive -Wall GPIOClass.cpp GPIOTest2.cpp -o outBin (với outBin là file thực thi được tạo ra sau khi biên dịch.
set quyền thực thi : chmod +x outBin
Chạy chương trình : ./outBin
g++ -fpermissive -Wall GPIOClass.cpp GPIOTest2.cpp -o outBin (với outBin là file thực thi được tạo ra sau khi biên dịch.
set quyền thực thi : chmod +x outBin
Chạy chương trình : ./outBin