III. AVR và Graphic LCD 1 Trình tự giao tiếp GLCD.
2. AVR giao tiếp với GLCD trong WinAVR.
Phần này tôi trình bày cách điều khiển hiển thị GLCD 128x64 bằng vi điều khiển AVR trong môi trường C của WinAVR. Hình thức là một thư viện hàm giao tiếp GLCD trong 1 file header có tên là myGLCD.h. Các hàm trong thư viện bao gồm (chú ý là phần code trong List 0 không nằm trong file myGLCD.h):
List 0. Các hàm có trong thư viện myGLCD.
Trước khi viết các hàm giao tiếp LCD chúng ta cần định nghĩa một số macro và biến. Hãy tạo 1 file Header có tên myGLCD.h và viết các đoạn code bên dưới vào file này (bắt đầu từ List 1).
Do GLCD không hỗ trợ bộ font, nếu muốn hiển thị các ký tự chúng ta cần định nghĩa chúng trong một bảng font (tương tự trường hợp ma trận LED), file font.h đã
được tạo trước và include vào thư viện myGLCD (dòng 2). Đồng thời, bộ font sẽ được chứa trong bộ nhớ chương trình (FLASH) nên cần các hàm hỗ trợđọc FLASH, chúng ta include file pgmspace.h phục vụ cho việc này (dòng 3).
cbi và sbi là 2 macro được dụng để xóa và set 1 bit trong 1 thanh ghi. Ví dụ
cbi(PORTA, 5) là xóa bit 5 trong thanh ghi PORTA về 0. Do WinAVR không hỗ trợ
tuy xuất trực tiếp các bit nên cần định nghĩa 2 macro này hỗ trợ (dòng 5, 6).
Tám đường DATA sẽđược dành cho 1 PORT, các dòng 8, 9 và 10 định nghĩa PORT trên AVR dành cho DATA, trong ví dụ này là PORTB. Tương tự các đường
điều khiển cũng nằm trên cùng 1 PORT, các dòng 14, 15, 16 định nghĩa PORT dành cho các đường điều khiển (PORTD chẳng hạn), sau đó chúng ta định nghĩa thứ tự
chân trên PORT điều khiển kết nối với các chân EN, RW, RS, CS1 và CS2 của GLCD (xem các dòng từ 18 đến 22). Chúng ta định nghĩa tiếp 2 macro để kích hoạt và stop GLCD ở các dòng 25 và 26 vì các hoạt động này được dùng rất nhiều khi giao tiếp với GLCD.
Tiếp theo chúng ta định nghĩa 4 mã lệnh (Instruction code) của 4 hàm Display on/off, Set Address, Set page và Display Start Line mà tôi đã trình bày ở trên (các dòng từ 29 đến 32). Cuối cùng là định nghĩa vị trí bit BUSY khi đọc trạng thái GLCD. Sau phần định nghĩa chúng ta sẽ bắt đầu viết code truy cập GLCD, đoạn code trình bày trong List 2 chứa các hàm hỗ trợ.
List 2. Các hàm hỗ trợ.
Hàm GLCD_Delay() thực hiện delay khoảng 16 chu kỳ máy, hàm này được dùng
để chờ LGCD đọc hay ghi dữ liệu sau khi chân EN được kích. Một nét mới ở đây là tôi sử dụng ngôn ngữ ASM chèn vào C, dù chỉ là chèn hàm nop nhưng nó nói cho bạn
biết rằng avr-gcc cho phép chúng ta chèn ASM, tôi sẽ trình bày chi tiết các ví dụ chèn ASM phức tạp hơn trong một bài khác.
Hàm GLCD_OUT_Set() ở dòng 5 set các PORT giao tiếp trên AVR (DATA và Control) có hướng Ouput. Hàm GLCD_IN_Set() ở dòng 12 set các PORT giao tiếp có hướng Input (dùng khi đọc từ GLCD -> AVR). Hàm GLCD_SetSide(char Side) ở
dòng 19 chọn chip KS0108 trái hoặc phải để thao tác, trong đó Side=1 thì một nửa GLCD bên phải được chọn bằng cách reset bit CS1=0 và CS2=1 (các dòng 22, 23), ngược lại nửa bên trái được kích hoạt, CS1=1, CS2=0 (dòng 26 và 27).
List 3 trình bày phần code cho 4 hàm truy cập Instruction GLCD cơ bản viết lại cho các hàm Status Read, Display On/Off, Set Address, Set page và Display Start Line trích từ bảng 2.
Tất cả các hàm trong List 3 đều truy cập Instruction nên chân RS luôn được kéo xuống mức thấp, trong 5 hàm trên, hàm wait_GLCD sử dụng Instruction đọc trạng thái từ GLCD nên chân RW sẽ được kéo lên cao, trong 4 hàm còn lại chân RW ở mức thấp.
Hàm wait_GLCD(void), cũng tương tự như hàm wait_LCD() trong trường hợp Text LCD, hàm này chờ GLCD rảnh bằng cách đọc trạng thái GLCD và kiểm tra bit BUSY, nếu BUSY bằng 1 thì GLCD đang bận, BUSY=0 tức GLCD rảnh. Các dòng 4, 5, 6 chuẩn bị các đường DATA, RS, RW cho quá trình đọc Instruction từ GLCD (RS=0, RW=0), ở dòng 8 chân EN được kéo lên cao bằng macro GLCD_ENABLE
(định nghĩa trong list 1). Nhắc lại chức năng của chân EN, khi EN=1 GLCD bắt đầu quá trình giao tiếp do các chân RS, RW xác lập (trong trường hợp này là đọc
Instruction từ GLCD), chúng ta cần chờ một khoảng thời gian ngắn cho GLCD đẩy thanh ghi trạng thái ra các đường DATA bằng hàm GLCD_Delay() trong dòng 9. Tiếp theo gọi GLCD_DISABLE để kéo chân EN xuống mức 0 để kết thúc quá trình đọc (một xung đã được tạo trên chân EN), và bắt đầu kiểm tra bit BUSY. Dòng 12 là một vòng lặp while kiểm tra xem nếu bit BUSY trong giá trịđọc về (giá trịđọc về chứa trong thanh ghi PIN của PORT DATA trên AVR), nếu BUSY=1 (bit_is_set…) vòng lặp tiếp tục với việc tạo một xung khác trên chân EN (các dòng ) rồi quay lại kiểm tra bit BUSY. Nếu BUSY bằng 0, GLCD đã rảnh, vòng lặp while được giải thoát, quá trình chờ kết thúc.
Hàm GLCD_SetDISPLAY(uint8_t ON) cho phép GLCD hiển thị khi tham số
ON=1, hoặc tắt khi tham số ON=0. Trước khi set GLCD chúng ta cần chờ cho GLCD rảnh bằng cách gọi hàm wait_GLCD() ở dòng 20, sau đó xác lập các chân RS, RW sẵn sàng cho quá trình gởi mã lệnh vào GLCD (dòng 21, 22 và 23). Trước khi kích hoạt quá trình, cần chuẩn bị mã lệnh sẵn sàng trên đường dữ liệu, dòng 25:
GLCD_DATA_O=GLCD_DISPLAY+ON, trong đó GLCD_DISPLAY là mã lệnh của hàm Display On/Off được định nghĩa trong List 1, biến ON báo GLCD tắt hay mở. Sau khi mọi thứ đã sẵn sàng, một xung được tạo ra trên chân EN (các dòng từ 26
đến 28). Quá trình set Display thực hiện và kết thúc.
Hàm void GLCD_SetYADDRESS(uint8_t Col) là hàm viết lại cho Insrtuction chọn địa chỉ Y (cột) cần thao tác, tham số Col trong hàm này chính là chỉ số cột, Col có giá trị từ 0 đến 63. Nội dung hàm này hoàn toàn giống hàm GLCD_SetDISPLAY,
chỉ có một điểm khác duy nhất là mã hàm khác, mã GLCD_YADDRESS được dùng
(xem dòng 36: GLCD_DATA_O = GLCD_YADDRESS+Col).
Hàm void GLCD_SetXADDRESS(uint8_t Line) là hàm viết lại cho Insrtuction chọn địa chỉ X (page) cần thao tác, tham số Line trong hàm này chính là chỉ số page, Line có giá trị từ 0 đến 8. Nội dung hàm này hoàn toàn giống hàm
GLCD_SetXADDRESS, nhưng mã GLCD_XADDRESS được dùng thay cho
GLCD_YADDRESS,(xem dòng 36: GLCD_DATA_O = GLCD_XADDRESS+Line).
Hàm void GLCD_StartLine(uint8_t Offset) là hàm viết lại cho Insrtuction “cuộn” GLCD, chỉ số Offset là giá trị “cuộn” lên. Xem lại ví dụ hình cuộn GLCD trong phần giải thích của lệnh Display Start Line, với trường hợp này hàm
GLCD_StartLine(20) đã được gọi.
List 4. Các hàm thao tác dữ liệu.
Hai hàm trong list 4 thao tác dữ liệu hiển thị trên GLCD nên chân RS phải được set bằng 1.
Hàm GLCD_WriteDATA(uint8_t DATA) ghi một byte vào RAM của KS0108, byte này cũng sẽ được hiển thị lên GLCD, vị trí ghi vào là vị trí hiện hành của con trỏ
X và Y (ảnh hưởng bởi các quá trình ghi trước đó hoặc do các hàm set địa chỉ), tham số DATA là byte cần ghi. Nội dung bên trong hàm này cũng giống nhứ các hàm trong list 3. Điểm khác là chân RS được kéo lên để báo đây là quá trình thao tác dữ liệu
(dòng 6: sbi(GLCD_CTRL_O, GLCD_RS)). Giá trị gởi đến GLCD chính là tham số
DATA như trong dòng 8: GLCD_DATA_O=DATA.
Hàm uint8_t GLCD_ReadDATA(void) đọc giá trị hiển thị trên từ GLCD vào AVR, chân RW cần được set lên 1 để báo quá trình này là đọc (dòng 22:
sbi(GLCD_CTRL_O, GLCD_RW)). Chân EN được kích lên 1 trước (dòng 24:
GLCD_ENABLE;) và chờ một khoảng thời gian ngắn trước khi đọc giá trị từ các
đường DATA vào một biến tạm DATA như trong dòng 26:
DATA=GLCD_DATA_I;. Sau khi trả giá trị về bằng dòng lệnh 30: return DATA, thì quá trịnh đọc kết thúc.
Với các hàm đã tạo chúng ta đã có thểđiều khiển để hiển thị GLCD, các chương trình con trong List 5 và List 6 sử dụng các hàm trên để thực hiện một số nhiệm vụ
hiển thị cơ bản. Chúng ta gọi là các chương trình con mở rộng. List 5. Các chương trình con mở rộng.
Hàm void GLCD_Init(void) khởi động GLCD. Trước hết, chúng ta phải chọn chip KS0108 để khởi động, dòng 4: GLCD_SetSide(0) nghĩa là chọn chip KS0108 bên trái tức nửa trái GLCD. Chúng ta khởi động nửa trái GLCD bằng việc cho phép hiển thị (dòng 5: GLCD_SetDISPLAY(1)), di chuyển con trỏ về vị trí đầu tiên trên GLCD với 2 hàm chọn địa chỉở các dòng 6 và 7, chọn giá trị cuộn là 0 ở dòng 8:
GLCD_StartLine(0). Sau đó lặp lại quá trình khởi động cho nửa phải của GLCD (xem các dòng từ 10 đến 14).
Hàm void GLCD_GotoXY(uint8_t Line, uint8_t Col) di chuyển con trỏ hiển thị đến địa chỉ X và Y. Tham số Line là địa chỉ X (tức là page, giá trị từ 0 đến 7), tham số
Col là địa chỉ Y hay chính là cột. Hàm này cho phép di chuyển trên toàn bộ GLCD, nghĩa là biến Col có khoảng giá trị từ 0 đến 127, vì thế trước hết chúng ta phải xác
định vị trí cần duy chuyển đến thuộc nửa nào của GLCD, nếu Col<64 thì vị trí đó thuộc nửa trái, ngược lại nó thuộc về nửa phải. Dòng 19 chúng ta chia Col cho 64 và gán phần nguyên kết quả cho 1 biến tạm tên là Side (Side=Col/64 ), rõ ràng nếu Col<64 thì Side=0, ngược lại Side=1. Biến Side được dùng làm tham số cho hàm GLCD_SetSide(Side) ở dòng 20, với cách thực hiện này chúng ta đã tự động chọn nửa GLCD mà điểm cần di chuyển đến thuộc vào. Do hàm chọn địa chỉ Y (hàm
GLCD_SetYADDRESS xét ở trên) chỉ chọn địa chỉ trong phạm vi 1 nửa LCD, nên chúng ta cần cập nhật lại giá trị của cột Col, dòng 21 thực hiện việc này: Col -= 64*Side. Sau dòng 21, giá trị Col được cập nhật lại từ 0 đến 63 và được chọn làm cột
khi hàm GLCD_SetYADDRESS(Col) ở dòng 22 được gọi. Cuối cùng là chọn địa chỉ
X ở dòng 23: GLCD_SetXADDRESS(Line).
Hàm void GLCD_Clr(void) xóa toàn bộ màn hình GLCD (cả 2 nửa GLCD). Mấu chốt của việc xóa GLCD là viết giá trị 0 vào tất cả các vị trí trong RAM, câu lệnh: GLCD_WriteDATA(0) ở 2 dòng 29 và 33 thực hiện điều này. Quá trình xóa
được thực hiện trên từng chip KS0108, có 2 vòng vặp for được dùng là vì thế, chú ý dòng lệnh 28: GLCD_GotoXY(Line,0) đưa con trỏ về cột đầu của page thứ “Line”, nửa trái GLCD. Trong khi đó, dòng lệnh 32: GLCD_GotoXY(Line,64) đưa con trỏ
về cột đầu của page thứ “Line”, nửa phải GLCD (cột 64 của GLCD là cột đầu tiên của nửa bên phải).
Đây là 3 chương trình con cuối cùng trong thư viện myGLCD. Trong đó có 2 hàm in các ký có kích thước 7x8 (7 cột, 8 dòng) được định nghĩa trong bảng font và 1 hàm in toàn bộ màn hình GLCD với một hình kích thước 128x64.
Hàm void GLCD_PutChar78(uint8_t Line, uint8_t Col, uint8_t chr) cho phép
in ký tự có mã ascii là biến “chr”, biến “Line” là địa chỉ X (0 đến 7) và biến Col là địa chỉ cột Y (0 đến 127). Phần phức tạp nhất trong chương trình con này là việc xét
trường hợp có sự chuyển bên (trái qua phải) khi in. Vì mỗi ký tựđược định nghĩa bằng 7 bytes trong bảng font, tương ứng với 7 cột trên GLCD, nếu chúng ta muốn in ký tự
tại vị trí 60 trên GLCD, các byte thứ 0 1, 2, 3 nằm ở vị trí cột 60, 61, 62 và 63 của nửa trái trong khi các byte thứ 4, 5 và 6 lại nằm ở các cột 0, 1 và 2 của nửa bên phải.
Chúng ta phải nhận ra sự chuyển bên này để chuyển chip KS0108 cần thao tác. Chúng ta chia quá trình in ra 2 trường hợp, trường hợp có sự chuyển bên và trường hợp còn lại không chuyển bên (ký tự nằm trọn bên trái hoặc phải). Cấu trúc If dùng trong dòng 4 kiểm tra xem có sự chuyển bên xảy ra hay không: if ((Col>57) && (Col<64)), nếu cột Col lớn hơn 57 và nhỏ hơn 63 thì sẽ có một sự chuyển bên xảy ra (vì 1 ký tự chiếm 7 cột trên GLCD). Chia quá trình in thành 2 vòng lặp for, vòng for thứ nhất (dòng 6) in từ vị trí Col đến vị trí cột của nửa trái và vòng lặp for thứ 2 ở dòng 9 in từ cột đầu tiên của nửa GLCD bên phải đến byte cuối cùng của ký tự cần in. Trường hợp ngược lại, không có sự chuyển bên xảy ra, chúng ta in bình thường (xem các dòng từ 12 đến 15). Chú ý là dữ liệu ghi vào GLCD lấy từ bảng font7x8 được định nghĩa trong file font.h, bảng font được viết sẵn trong bộ nhớ FLASH của AVR, việc đọc nội dung FLASH thực hiện bằng hàm pgm_read_byte, bạn xem lại bài điều khiển ma trận LED
để hiểu thêm.
Hàm void GLCD_Print78(uint8_t Line, uint8_t Col, char* str) cho phép in
một chuỗi ký tự hay 1 câu lên GLCD, hàm này cũng giống hàm in chuỗi mà chúng ta
đã thực hiện trong trường hợp của Text LCD (modified code), một điểm khác tôi thêm vào là cho phep xuống dòng nếu câu cần in vượt quá 1 dòng. Các câu lệnh bên trong
điều kiện if (dòng 23 đến 27) thực hiện xuống dòng nếu cần thiết. Quá trình in sau đó diễn ra bình thường bằng cách gọi hàm GLCD_PutChar78.
Cuối cùng là hàm void GLCD_PutBMP(char *bmp) thực hiện in một hình có kích thước 128x64 được định nghĩa trước lên toàn bộ màn hình GLCD (in đè). Quá trình in cũng khá đơn giản với việc đọc nội dung hình trong FLASH và gởi đến GLCD. Cần chia thành 2 quá trình in cho 2 nửa trái và phải (2 vòng lặp for trong 2 dòng 38 và 43). Dữ liệu hình được ghi trong FLASH có định kích thước 128x8 pages= 1024 bytes, định dạng là 1 mảng có 1024 phần tử, mỗi phần tử là 1 con số dạng byte, mỗi số tương ứng 8 chấm của 1 cột trong 1 page. Các con sốđược sắp xếp thành 8 dòng tương ứng 8 pages, mỗi dòng có 128 phần tử tương ứng 128 cột GLCD.
GLCD có khả năng tùy biến hiển thị cao, đó là cơ hội cho bạn thể hiện sự sáng tạo ~, trong thư viện myGLCD tôi chỉ trình bày một số chương trình con cơ bản, phần còn lại thuộc về bạn. Hãy sử dụng các hàm truy xuất trong myGLCD để viết các chương trình con hiển thị khác như vẽ đường thẳng, đường tròn, hàm sine, cosine hay bất kỳ