Quy trình vẽ lên màn hình
- Xóa sạch dữ liệu đang có
- Vẽ lại toàn bộ màn hình
- Cập nhật hiển thị
Đây là quy trình được sử dụng trong tất cả các ứng dụng có liên quan tới đồ họa. Việc xóa và vẽ lại mới toàn bộ màn hình sẽ giúp chúng ta không cần phải lo lắng về việc quản lí các dữ liệu cũ vẫn đang tồn tại trên màn hình mà chỉ cần quan tâm tới nhưng dữ liệu mới.
Các phương pháp điều khiển hiện thị lên màn hình
Tận dụng RAM có sẵn của ST7735
Trong màn hình LCD đã có sẵn RAM phục vụ lưu dữ liệu hiển thị, nó còn được gọi là G-RAM. Đối với các ứng dụng hiển thị đơn giản chúng ta chỉ việc vẽ trực tiếp lên GRAM của màn hình, các thư viện dùng cho arduino hầu hết đều sử dụng phương pháp điều khiển này.
Tuy nhiên nó cũng có 1 số diểm yếu, bởi mọi dữ liệu bạn gửi ra sẽ được hiển thị ngay lập tức, nên khi bạn sử dụng lệnh clear bộ nhớ RAM hoặc clear dữ liệu hiển thị trước đó, màn hình sẽ có 1 khoảnh khắc nhỏ hiển thị màu đen, dẫn tới việc màn hình sẽ có cảm giác bị nháy
Ví dụ với một chương trình đếm đơn giản
Mình sẽ khai báo 1 biến đếm và mảng dữ liệu để phục vụ in ra data màn hình
1 2 |
uint32_t dem=0; char buff[50]; |
Trong while main gọi
1 2 3 4 |
ST7735_Clr(); //xóa màn hình sprintf(buff,"Biến đếm: %i",dem++); FontMakerPutString(0,0,buff,RED,BLACK); HAL_Delay(100); |
Đây là hiện tượng flicker thường thấy khi sử dụng 1 bộ đệm hiển thị duy nhất. Bởi khi in dữ liệu mới ra màn hình chúng ta xóa sạch dữ liệu cũ đi, tuy nhiên trong khi dữ liệu bị xóa thì màn hình lcd cũng sẽ hiện khung hình màu đen bị xóa lên
Đương nhiên sẽ có nhiều cách để khắc phục hiện tượng này. Tuy nhiên chắc chắn nó sẽ không triệt để. Các bạn có thể vẽ trực tiếp dữ liệu mới đè lên phần dữ liệu cũ sẽ giảm được đáng kể, tuy nhiên trong các ứng dụng sử dụng tới animation ( chuyển động đồ họa) thì việc xóa sạch dữ liệu cũ rồi vẽ hết lại từ đầu sẽ đơn giản hơn
Bộ đệm đôi
Người ta thường sử dụng thêm 1 bộ đệm nữa gọi là Double buffering để có thể loại bỏ hoàn toàn hiện tượng này. Tức là sẽ có 1 bộ đệm dữ liệu nữa nằm trên vi điều khiển, quá trình vẽ và xóa sẽ được thực hiện lên bộ đệm trong khi dữ liệu cũ vần tồn tại để phục vụ việc hiển thị. Sau khi vẽ xong 1 khung hình mới update dữ liệu này sang cho bộ đệm hiển thị trên LCD
Ví dụ với màn hình có độ phân giải 160×128 chế độ màu 16bit mà chúng ta đang dùng thì sẽ cần tới 160x128x16 = 327680 bíts = 40960 Byte Ram
STM32F103C8T6 chỉ có 20KB Ram nên không thể đủ Ram cho bộ đệm này. Do vậy, phương án sử dụng 2 bộ đệm là không khả thi
Quay trở lại phương án sử dụng bộ đệm sẵn có của màn hình LCD
Chúng ta vẫn sẽ dụng bộ đệm sẵn có của màn hình, quy trình vẫn là vẽ lại toàn bộ màn hình. Tuy nhiên thay vì vẽ lên bộ đệm thứ 2, chúng ta sẽ mô tả các dữ kiện cần vẽ thông qua các struct. Trong quá trình vẽ, chúng ta đọc các struct rồi vẽ dữ liệu lần lượt từ trên xuống dưới ra màn hình (render). Điều này sẽ giúp chúng ta không cần phải clear sạch sẽ màn hình rồi vẽ mà là vẽ đè dữ liệu mới lên dữ liệu cũ
Thiết kế thư viện giao diện đồ họa người dùng ( GUI ) ( Graphic user interface)
Tại sao phải thiết kế giao diện đồ họa người dùng
Trong các ứng dụng đơn giản như hiển thông tin cảm biến … thì bạn hoàn toàn có thể dùng các hàm in chữ thông thường trực tiếp ra màn hình hoặc cùng lắm là in 1 vài hình ảnh tĩnh đơn giản ra màn hình. Tuy nhiên, trong các ứng dụng có sự hiển thị phức tạp, có các chuyện động đồ họa phức tạp hay lập trình game thì chúng ta cần sử dụng tới GUI
Các ứng dụng sử dụng tới giao diện đồ họa người dùng cho phép người dùng có thể tương tác với hệ thống thông qua màn hình hiển thị ( ví dụ thiết lập các thông số cài đặt, chơi game … )
Và bản thân nhà phát triển ( tức lập trình viên) cũng dễ dàng tạo ra các đối tượng hiển thị trên màn hình thông qua thư viện GUI.
Mỗi thành phần hiển thị trên màn hình sẽ được goi là các đối tượng hiển thị (obeject display)
Ví dụ: Các văn bản trên màn hình sẽ được gọi là đối tượng lable
Các hình ảnh hiện thị trên màn hình được gọi là đối tượng Image
Các nút nhấn trên màn hình được gọi là đối tượng button
Trong mỗi đối tượng sẽ chưa các thuộc tính cơ bản để mô tả đối tượng đó, thậm chí 1 đối tượng cũng có thể chứa đối tượng con khác
Ví dụ: Lable sẽ có thuộc tính cơ bản như:
- Location ( tọa độ hiển thị trên màn hình)
- Color ( màu văn bản)
- BackColor ( màu nên)
- Size ( kích thước của văn bản)
- Font ( font chữ của văn bản)
Đối tượng button sẽ có các thuộc tính sau
- Location ( tọa độ hiển thị trên màn hình)
- Color ( màu nền của nút nhấn)
- Size (kích thước của nút nhấn)
- Lable (button sẽ kết thừa lại toàn bộ thuộc tính của Lable)
Ngoài ra, các đối tượng còn chứa thêm các phương thức
Ví dụ button sẽ có phương thức
- OnClick ( khi người dùng ấn vào button thì sẽ xảy ra chuyện gì)
- OnCollision (khi đối tượng nút nhấn va chậm với 1 đối tượng khác thì sẽ xảy ra chuyện gì)
Tóm lại: thư viện GUI sẽ cung cấp các đối tượng hiển thị, khi lập trình, chúng ta muốn cái gì hiện thị ở trên màn hình thì chỉ việc gọi nó ra và cài đặt các thông số hiển thị cho đối tượng đó. hệ thống sẽ thường xuyên đọc và phân thích các đối tượng hiển thị rồi vẽ chúng ra màn hình
Mỗi lần hệ thống đọc và vẽ tất cả các đối tượng hiển thị ra màn hình được gọi là 1 khung hình. Thông thường, để các chuyển động được mượt mà thì hệ thống sẽ phải vẽ 24 khung hình trên 1 giây. Do đó, càng nhiêu các đối tượng hiển thị được khởi tạo thì sẽ càng mất nhiều thời gian để vẽ và có thể ảnh hướng đến FPS của hệ thống ( màn hình sẽ bị giật lag, chuyển động không được mượt)
Mình sẽ gọi quá trình vi điều khiển đọc thông tin mô tả các đối tượng và vẽ thành các pixel hiển thị là quá trình render
Về mặt hệ thống, do chúng ta không có bộ đệm thứ 2 nên mình sẽ khởi tạo 1 bộ đệm bé hơn. Vi điều khiển sẽ render 1 phần nhỏ của các đối tượng hiển thị vào bộ đệm nhỏ nảy rồi bắn chúng sang màn hình LCD thông qua DMA cho nó nhanh
< Bài còn dài, mình sẽ hoàn thiện dần trong tương lai >
Great content! Keep up the good work!