|
Bài này nằm trong phần ứng dụng AVR thuộc loạt bài cùng học AVR. Trong bài ứng dụng này chúng ta không khảo sát nhiều cấu trúc AVR mà chủ yếu là tìm hiểu Text LCD cách điều khiển bằng AVR. Công cụ chính cũng là 2 bộ phần mềm quen thuộc WinAVR và Proteus.
Sau bài này, tôi hy vọng bạn có thể hiểu và thực hiện được:
- Cấu trúc Text LCD.
- Nguyên lý hoạt động Text LCD
- Phát triển 1 thư viện điều khiển Text LCD bằng AVR cả 2 chế độ 8 bit và 4 bit.
- Ví dụ điều khiển Text LCD bằng AVR.
Text LCD là các loại màn hình tinh thể lỏng nhỏ dùng để hiển thị các dòng chữ hoặc số trong bảng mã ASCII. Không giống các loại LCD lớn, Text LCD được chia sẵn thành từng ô và ứng với mỗi ô chỉ có thể hiển thị một ký tự ASCII. Cũng vì lý do chỉ hiện thị được ký tự ASCII nên loại LCD này được gọi là Text LCD (để phân biệt với Graphic LCD có thể hiển thị hình ảnh). Mỗi ô của Text LCD bao gồm các “chấm” tinh thể lỏng, việc kết hợp “ẩn” và “hiện” các chấm này sẽ tạo thành một ký tự cần hiển thị. Trong các Text LCD, các mẫu ký tự được định nghĩa sẵn. Kích thước của Text LCD được định nghĩa bằng số ký tự có thể hiển thị trên 1 dòng và tổng số dòng mà LCD có. Ví dụ LCD 16x2 là loại có 2 dòng và mỗi dòng có thể hiển thị tối đa 16 ký tự. Một số kích thước Text LCD thông thường gồm 16x1, 16x2, 16x4, 20x2, 20x4…Hình 1 là một ví dụ Text LCD 16x2.

Hình 1. Text LCD 16x2.
Text LCD có 2 cách giao tiếp cơ bản là nối tiếp (như I2C) và song song. Trong phạm vi bài học này tôi chỉ giới thiệu loại giao tiếp song song, cụ thể là LCD 16x2 điều khiển bởi chip HD44780U của hãng Hitachi. Đối với các LCD khác bạn cần tham khảo datasheet riêng của từng loại. Tuy nhiên, HD44780U cũng được coi là chuẩn chung cho các loại Text LCD, vì thế bạn có thể dùng chương trình ví dụ trong bài này để test trên các LCD khác với rất ít hoặc không cần chỉnh sửa.
HD44780U là bộ điều khiển cho các Text LCD dạng ma trận điểm (dot-matrix), chip này có thể được dùng cho các LCD có 1 hoặc 2 dòng hiển thị. HD44780U có 2 mode giao tiếp là 4 bit và 8 bit. Nó chứa sẵn 208 ký tự mẫu kích thước font 5x8 và 32 ký tự mẫu font 5x10 (tổng cộng là 240 ký tự mẫu khác nhau).
1. Sơ đồ chân.
Các Text LCD theo chuẩn HD44780U thường có 16 chân trong đó 14 chân kết nối với bộ điều khiển và 2 chân nguồn cho “đèn LED nền”. Thứ tự các chân thường được sắp xếp như sau:
Bảng 1. Sơ đồ chân.

Trong một số LCD 2 chân LED nền được đánh số 15 và 16 nhưng trong một số trường hợp 2 chân này được ghi là A (Anode) và K (Cathode). Hình 2 mô tả cách kết nối LCD với nguồn và mạch điều khiển.

Hình 2. Kết nối Text LCD.
Chân 1 và chân 2 là các chân nguồn, được nối với GND và nguồn 5V. Chân 3 là chân chỉnh độ tương phản (contrast), chân này cần được nối với 1 biến trở chia áp như trong hình 2.Trong khi hoạt động, chỉnh để thay đổi giá trị biến trở để đạt được độ tương phản cần thiết, sau đó giữ mức biến trở này. Các chân điều khiển RS, R/W, EN và các đường dữ liệu được nối trực tiếp với vi điều khiển. Tùy theo chế độ hoạt động 4 bit hay 8 bit mà các chân từ D0 đến D3 có thể bỏ qua hoặc nối với vi điều khiển, chúng ta sẽ khảo sát kỹ càng hơn trong các phần sau.
2. Thanh ghi và tổ chức bộ nhớ.
HD44780U có 2 thanh ghi 8 bits là INSTRUCTION REGISTER (IR) và DATA REGISTER (DR). Thanh ghi IR chứa mã lệnh điều khiển LCD và là thanh ghi “chỉ ghi” (chỉ có thể ghi vào thanh ghi này mà không đọc được nó). Thanh ghi DR chứa các các loại dữ liệu như ký tự cần hiển thị hoặc dữ liệu đọc ra từ bộ nhớ LCD…Cả 2 thanh ghi đều được nối với các đường dữ liệu D0:7 của Text LCD và được lựa chọn tùy theo các chân điều khiển RS, RW. Thực tế để điều khiển Text LCD chúng ta không cần quan tâm đến cách thức hoạt động của 2 thanh ghi này, vì thế cũng không cần khảo sát chi tiết chúng.
HD44780U có 3 loại bộ nhớ, đó là bộ nhớ RAM dữ liệu cần hiển thị DDRAM (Didplay Data RAM), bộ nhớ chứa ROM chứa bộ font tạo ra ký tự CGROM (Character Generator ROM) và bộ nhớ RAM chứa bộ font tạo ra các symbol tùy chọn CGRAM (Character Generator RAM). Để điều khiển hiển thị Text LCD chúng ta cần hiểu tổ chức và cách thức hoạt động của các bộ nhớ này:
2.1 DDRAM.
DDRAM là bộ nhớ tạm chứa các ký tự cần hiển thị lên LCD, bộ nhớ này gồm có 80 ô được chia thành 2 hàng, mỗi ô có độ rộng 8 bit và được đánh số từ 0 đến 39 cho dòng 1; từ 64 đến 103 cho dòng 2. Mỗi ô nhớ tương ứng với 1 ô trên màn hình LCD. Như chúng ta biết LCD loại 16x2 có thể hiển thị tối đa 32 ký tự (có 32 ô hiển thị), vì thế có một số ô nhớ của DDRAM không được sử dụng làm các ô hiển thị. Để hiểu rõ hơn chúng ta tham khảo hình 3 bên dưới

Hình 3. Tổ chức của DDRAM.
Chỉ có 16 ô nhớ có địa chỉ từ 0 đến 15 và 16 ô địa chỉ từ 64 đến 79 là được hiển thị trên LCD. Vì thế muốn hiển thị một ký tự nào đó trên LCD chúng ta cần viết ký tự đó vào DDRAM ở 1 trong 32 địa chỉ trên. Các ký tự nằm ngoài 32 ô nhớ trên sẽ không được hiển thị, tuy nhiên vẫn không bị mất đi, chúng có thể được dùng cho các mục đích khác nếu cần thiết.
2.2 CGROM.
CGROM là vùng nhớ cố định chứa định nghĩa font cho các ký tự. Chúng ta không trực tiếp truy xuất vùng nhớ này mà chip HD44780U sẽ tự thực hiện khi có yêu cầu đọc font để hiện thị. Một điều đáng lưu ý là địa chỉ font của mỗi ký tự vùng nhớ CGROM chính là mã ASCII của ký tự đó. Ví dụ ký tự ‘a’ có mã ASCII là 97, tham khảo tổ chức của vùng nhớ CGROM trong hình 4 bạn sẽ nhận thấy địa chỉ font của ‘a’ có 4 bit thấp là 0001 và 4 bit cao là 0110, địa chỉ tổng hợp là 01100001 = 97.
CGROM và DDRAM được tự động phối hợp trong quá trình hiển thị của LCD. Giả sử chúng ta muốn hiển thị ký tự ‘a’ tại vị trí đầu tiên, dòng thứ 2 của LCD thì các bước thực hiện sẽ như sau: trước hết chúng ta biết rằng vị trí đầu tiên của dòng 2 có địa chỉ là 64 trong bộ nhớ DDRAM (xem hình 3), vì thế chúng ta sẽ ghi vào ô nhớ có địa chỉ 64 một giá trị là 97 (mã ASCII của ký tự ‘a’). Tiếp theo, chip HD44780U đọc giá trị 97 này và coi như là địa chỉ của vùng nhớ CGROM, nó sẽ tìm đến vùng nhớ CGROM có địa chỉ 97 và đọc bảng font đã được định nghĩa sẵn ở đây, sau đó xuất bản font này ra các “chấm” trên màn hình LCD tại vị trí đầu tiên của dòng 2 trên LCD. Đây chính là cách mà 2 bộ nhớ DDRAM và CGROM phối hợp với nhau để hiển thị các ký tự. Như mô tả, công việc của người lập trình điều khiển LCD tương đối đơn giản, đó là viết mã ASCII vào bộ nhớ DDRAM tại đúng vị trí được yêu cầu, bước tiếp theo sẽ do HD44780U đảm nhiệm.

Hình 4. Vùng nhớ CGROM.
2.3 CGRAM.
CGRAM là vùng nhớ chứa các symbol do người dùng tự định nghĩa, mỗi symbol được có kích thước 5x8 và được dành cho 8 ô nhớ 8 bit. Các symbol thường được định nghĩa trước và được gọi hiển thị khi cần thiết. Vùng này có tất cả 64 ô nhớ nên có tối đa 8 symbol có thể được định nghĩa. Tài liệu này không đề cập đến sử dụng bộ nhớ CGRAM nên tôi sẽ không đi chi tiết phần này, bạn có thể tham khảo datasheet của HD44780U để biết thêm.
3. Điều khiển hiển thị Text LCD.
3.1 Các chân điều khiển LCD.
Các chân điều khiển việc đọc và ghi LCD bao gồm RS, R/W và EN.
RS (chân số 3): Chân lựa chọn thanh ghi (Select Register), chân này cho phép lựa chọn 1 trong 2 thanh ghi IR hoặc DR để làm việc. Vì cả 2 thanh ghi này đều được kết nối với các chân Data của LCD nên cần 1 bit để lựa chọn giữa chúng. Nếu RS=0, thanh ghi IR được chọn và nếu RS=1 thanh ghi DR được chọn. Chúng ta đều biết thanh ghi IR là thanh ghi chứa mã lệnh cho LCD, vì thế nếu muốn gởi 1 mã lệnh đến LCD thì chân RS phải được reset về 0. Ngược lại, khi muốn ghi mã ASCII của ký tự cần hiển thị lên LCD thì chúng ta sẽ set RS=1 để chọn thanh ghi DR. Hoạt động của chân RS được mô tả trong hình 5.

Hình 5. Hoạt động của chân RS.
R/W (chân số 4): Chân lựa chọn giữa việc đọc và ghi. Nếu R/W=0 thì dữ liệu sẽ được ghi từ bộ điều khiển ngoài (vi điều khiển AVR chẳng hạn) vào LCD. Nếu R/W=1 thì dữ liệu sẽ được đọc từ LCD ra ngoài. Tuy nhiên, chỉ có duy nhất 1 trường hợp mà dữ liệu có thể đọc từ LCD ra, đó là đọc trạng thái LCD để biết LCD có đang bận hay không (cờ Busy Flag - BF). Do LCD là một thiết bị hoạt động tương đối chậm (so với vi điều khiển), vì thế một cờ BF được dùng để báo LCD đang bận, nếu BF=1 thì chúng ta phải chờ cho LCD xử lí xong nhiệm vụ hiện tại, đến khi nào BF=0 một thao tác mới sẽ được gán cho LCD. Vì thế, khi làm việc với Text LCD chúng ta nhất thiết phải có một chương trình con tạm gọi là wait_LCD để chờ cho đến khi LCD rảnh. Có 2 cách để viết chương trình wait_LCD. Cách 1 là đọc bit BF về kiểm tra và chờ BF=0, cách này đòi hỏi lệnh đọc từ LCD về bộ điều khiển ngoài, do đó chân R/W cần được nối với bộ điều khiển ngoài. Cách 2 là viết một hàm delay một khoảng thời gian cố định nào đó (tốt nhất là trên 1ms). Ưu điểm của cách 2 là sự đơn giản vì không cần đọc LCD, do đó chân R/W không cần sử dụng và luôn được nối với GND. Tuy nhiên, nhược điểm của cách 2 là khoảng thời gian delay cố định nếu quá lớn sẽ làm chậm quá trình thao tác LCD, nếu quá nhỏ sẽ gây ra lỗi hiển thị. Trong bài này tôi hướng dẫn bạn cách tổng quát là cách 1, để sử dụng cách 2 bạn chỉ cần một thay đổi nhỏ trong chương trình wait_LCD (sẽ trình bày chi tiết sau) và kết nối chân R/W của LCD xuống GND.
EN (chân số 5): Chân cho phép LCD hoạt động (Enable), chân này cần được kết nối với bộ điều khiển để cho phép thao tác LCD. Để đọc và ghi data từ LCD chúng ta cần tạo một “xung cạnh xuống” trên chân EN, nói theo cách khác, muốn ghi dữ liệu vào LCD trước hết cần đảm bảo rằng chân EN=0, tiếp đến xuất dữ liệu đến các chân D0:7, sau đó set chân EN lên 1 và cuối cùng là xóa EN về 0 để tạo 1 xung cạnh xuống.
3.2 Tập lệnh của LCD.
Bảng 2 tóm tắt các lệnh có thể ghi vào LCD

Danh sách lệnh trên được tôi tô 2 màu khác nhau, các lệnh màu đỏ sẽ được dùng thường xuyên trong lúc hiển thị LCD và các lệnh màu xanh thường chỉ được dùng 1 lần trong lúc khởi động LCD, riêng lệnh Read BF có thể được dùng hoặc không tùy theo cách viết chương trình wait_LCD. Phần tiếp theo tôi giải thích ý nghĩ của các lệnh và tham số kèm theo chúng.
Trước hết là nhóm lệnh đỏ:
- Clear display – xóa LCD: lệnh này xóa toàn bộ nội dung DDRAM và vì thế xóa toàn bộ hiển thị trên LCD. Vì đây là 1 lệnh ghi Instruction nên chân RS phải được reset về 0 trước khi ghi lệnh này lên LCD. Mã lệnh xóa LCD là 0x01(ghi vào D0:D7).
- Cursor home – đưa con trỏ về vị trí đầu, dòng 1 của LCD: lệnh này thực hiện việc đưa con trỏ về vị trí đầu tiên của bộ nhớ DDRAM, vì thế nếu sau lệnh này một biến được ghi vào DDRAM thì biến này sẽ nằm ở vị trí đầu tiên (1;1). RS cũng phải bằng 0 trước khi ghi lệnh. Mã lệnh là 0x02 hoặc 0x03(chọn 1 trong 2 mã lệnh, tùy ý).
- Set DDRAM address – định vị trí con trỏ cho DDRAM: di chuyển con trỏ đến một vị trí tùy ý trong DDRAM và vì thế có thể được dùng để chọn vị trí cần hiển thị trên LCD. Để thực hiện lệnh này cần reset RS=0. Bit MSB của mã lệnh (D7) phải bằng 1, 7 bit còn lại của mã lệnh chính là địa chỉ DDRAM muốn di chuyển đến. Ví dụ chúng ta muốn di chuyển con trỏ đến vị trí thứ 3 trên dòng 2 của LCD (địa chỉ 42) chúng ta cần ghi mã lệnh 0xAA vì 0xAA=10101010 (binary) trong đó bit MSB bằng 1, bảy bit còn lại là 0101010=42, địa chỉ của ô nhớ muốn đến.
- Write to CGRAM or DDRAM – ghi dữ liệu vào CGRAM hoặc DDRAM: vì đây không phải là lệnh ghi instruction mà là 1 lệnh ghi dữ liệu nên chân RS cần được set lên 1 trước khi ghi lệnh vào LCD. Lệnh này cho phép ghi mã ASCII của một ký tự cần hiển thị vào thanh ghi DDRAM. Trường hợp ghi vào CGRAM không được khảo sát.
Kế đến là nhóm lệnh màu xanh: nhóm lệnh này thường chỉ thực hiện 1 lần (ít nhất là trong bài học này) và thường được viết chung trong 1 chương trình con khởi động LCD ( chúng ta gọi là init_LCD trong bài học này).
- Entry mode set – xác lập các hiện thị liên tiếp cho LCD: nói một cách dễ hiểu, lệnh này chỉ ra cách mà bạn muốn hiển thị một ký tự tiếp theo 1 ký tự trước đó. Ví dụ nếu bạn muốn hiện thị 2 ký tự liên tiếp AB, trước hết bạn viết A tại vị trí 5, dòng 1. Sau đó bạn ghi B vào LCD, lúc này có 4 cách mà LCD có thể hiển thị B như sau: hiển thị B bên phải A tại vị trí số 6 (cách 1); B cũng có thể được hiển thị bên trái A, tại vị trí số 4(cách 2); hoặc LCD có thể tự dịch chuyển A về bên trái đến vị trí 4 sau đó hiển thị B bên phải A, tại vị trí 5(cách 3); và khả năng cuối cùng là LCD dịch chuyển A về bên phải đến vị trí 6 sau đó hiển thị B bên trái A, tại vị trí 5(cách 4). Chúng ta có thể chọn 1 trong 4 cách hiển thị trên thông qua lệnh Entry mode set. Đây là lệnh ghi Instruction nên RS=0, 5 bit cao D7:3=00000, bit D2=1, hai bit còn lại D1:0 chứa mã lệnh để lựa chọn 1 trong 4 cách hiển thị. Xem lại bảng 2, bit D1 chứa giá trị I/D và D0 chứa S. Trong đó I/D nghĩa là tăng hoặc giảm (Increment or Decrement). I/D= 1 là hiển thị tăng tức ký tự sau sẽ hiển thị bên phải ký tự trước, nếu I/D=0 thì hiển thị giảm, tức ký tự sau hiển thị bên trái ký tự trước. S là giá trị Shift, nếu S=1 thì các ký tự trước đó sẽ được “đẩy” đi, ký tự sau chiếm chỗ ký tự trước, ngược lại nếu S=0 thì vị trí hiển thị của các ký tự trước đó không thay đổi. Có thể tóm tắt 4 mode hiển thị ứng với 4 mã lệnh như sau:
+ D7:0 = 0x04 (00000100) : hiển thị giảm và không shift (như cách 2 trong ví dụ).
+ D7:0 = 0x05 (00000101) : hiển thị giảm và shift (như cách 4 trong ví dụ).
+ D7:0 = 0x06 (00000110) : hiển thị tăng và không shift (như cách 1, khuyến khích).
+ D7:0 = 0x07 (00000111) : hiển thị tăng và shift (như cách 3 trong ví dụ).
- Display on/off control – xác lập cách hiện thị cho LCD: lệnh này bao gồm các thông số cho phép LCD hiển thị, cho phép hiển thị cursor và mở/tắt blinking. Đây cũng là một lệnh ghi Instrcution nên RS phải bằng 0. Mã lệnh cho lệnh này có dạng 00001DCB trong đó D (Display) cho phép hiển thị LCD nếu mang giá trị 1, C (Cursor) bằng 1 thì cursor sẽ được hiển thị và B là blinking cho cursor tại vị trí hiển thị (blinking là dạng 1 ô đen nhấp nháy tại vị trí ký tự đang hiển thị). Mã lệnh được dùng phổ biến cho lệnh này là 0x0E (00001110 - hiển thị cursor nhưng không hiển thị blinking).
- Function set – xác lập chức năng cho LCD: đây là lệnh thiết lập phương thức giao tiếp với LCD, kích thước font chữ và số lượng line của LCD. RS cũng phải bằng 0 khi sử dụng lệnh này. Mã lệnh function set có dạng 001D¬¬LNFxx. Trong đó nếu DL=1 (DL: Data Length) thì mode giao tiếp 8 bit sẽ được dùng, lúc này tất cả các chân từ D0 đến D7 phải được kết nối với bộ điều khiển ngoài. Nếu DL=0 thì mode 4 bit được dùng, trong trường hợp này chỉ có 4 chân D4:7 được dùng để truyền nhận dữ liệu và kết nối với bộ điều khiển ngoài, các chân D0:3 được để trống. N quy định số dòng của LCD, vì chúng ta đang khảo sát LCD loại hiển thị 2 dòng nên N=1 (N=0 cho trường hợp LCD 1 dòng). F là kích thước font chữ hiển thị, do LCD có 2 bộ font chữ có sẵn trong CGROM nên chúng ta cần lựa chọn thông qua bit F, nếu F=1 bộ font 5x10 được sử dụng và nếu F=0 thì font 5x8 được hiển thị. 2 bit thấp trong mã lệnh này có thể được gán giá trị tùy ý. Mã lệnh được dùng phổ biến cho lệnh function set là 0x38 (00111000 – giao tiếp 8 bit, 2 dòng với font 5x8 ) hoặc 0x28 (00101000 – giao tiếp 4 bit, 2 dòng với font 5x8 ). Ví dụ trong bài này sử dụng cả 2 mã lệnh trên.
3.3 Giao tiếp 8 bit và 4 bit.
Như trình bày trong lệnh function set, có 2 mode để ghi và đọc dữ liệu vào LCD đó là mode 8 bit và mode 4 bit:
- Mode 8 bit: Nếu bit DL trong lệnh function set bằng 1 thì mode 8 bit được dùng. Để sử dụng mode 8 bit, tất cả các lines dữ liệu của LCD từ D0 đến D7 (từ chân 7 đến chân 14) phải được nối với 1 PORT của chip điều khiển bên ngoài (ví dụ PORTC của ATmega32 trong ví dụ của bài này) như trong hình 3. Ưu điểm của phương pháp giao tiếp này là dữ liệu được ghi và đọc rất nhanh và đơn giản vì chip điều khiển chỉ cần xuất hoặc nhận dữ liệu trên 1 PORT. Tuy nhiên, phương pháp này có nhược điểm là tổng số chân dành cho giao tiếp LCD quá nhiều, nếu tính luôn cả 3 chân điều khiển thì cần đến 11 đường cho giao tiếp LCD.
- Mode 4 bit: LCD cho phép giao tiếp với bộ điều khiển ngoài theo chế độ 4 bit. Trong chế độ này, các chân D0, D1, D2 và D3 của LCD không được sử dụng (để trống), chỉ có 4 chân từ D4 đến D7 được kết nối với chip bộ điều khiển ngoài. Các instruction và data 8 bit sẽ được ghi và đọc bằng cách chia thành 2 phần, gọi là các Nibbles, mỗi nibble gồm 4 bit và được giao tiếp thông qua 4 chân D7:4, nibble cao được xử lí trước và nibble thấp sau. Ưu điểm lớn nhất của phương pháp này tối thiểu số lines dùng cho giao tiếp LCD. Tuy nhiên, việc đọc và ghi từng nibble tương đối khó khăn hơn đọc và ghi dữ liệu 8 bit. Trong bài học này, tôi sẽ trình bày 2 chương trình con được viết riêng để ghi và đọc các nibbles gọi là Read2Nib và Write2Nib.
1. Trình tự giao tiếp Text LCD.
Trình tự giao tiếp với LCD được trình bày trong flowchart ở hình 6.

Hình 6. Trình tự giao tiếp với Text LCD.
Để sử dụng LCD chúng ta cần khởi động LCD, sau khi được khởi động LCD đã sẵn sàng để hiển thị. Quá trình khởi động chỉ cần thực hiện 1 lần ở đầu chương trình. Trong bài này, quá trình khởi động được viết trong 1 chương trình con tên int_LCD, khởi động LCD thường bao gồm xác lập cách giao tiếp, kích thước font, số dòng LCD (funcstion set), cho phép hiển thị LCD, sursor…(Display control), chế độ hiển thị tăng/giảm, shift (Entry mode set). Các thủ tục khác như xóa LCD, viết ký tự lên LCD, di chuyển con trỏ…được sử dụng liên tục trong quá trình hiển thị LCD và sẽ được trình bày trong các đoạn chương trình con riêng.
2. AVR giao tiếp với Text LCD trong WinAVR.
Phần này tôi trình bày cách điều khiển hiển thị Text LCD 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 Text LCD trong 1 file header có tên là myLCD.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 myLCD.h).
|
1 |
char Read2Nib(); //đọc 2 nibbles từ LCD |
Tuy nhiên, 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 myLCD.h và viết các đoạn code bên dưới vào file này (bắt đầu từ List 1).
|
01 |
#include <util/delay.h> #define DATA_O PORTB |
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 PORT 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ợ.

Hình 7. Ví dụ Kết nối LCD với AVR trong mode 4 bit (chip mega8).
|
01 |
char Read2Nib(){ sbi(CTRL,EN); //enable |
Hàm này thực hiện việc đọc dữ liệu từ LCD ra ngoài, đọc theo từng nibble 4 bit, kết quả trả về là 1 số 8 bit. Hàm này chỉ được dùng duy nhất khi đọc cờ Busy (BF) trong chương trình chờ LCD rảnh (wait_LCD) ở mode 4 bit. Trước hết cần định nghĩa 1 biến tạm HNib (high nibble) và LNib (Low nibble) để chứa 2 nibbles đọc về (dòng 2, List 2). Dòng 5 set chân EN lên mức 1 để chuẩn bị cho LCD làm việc. Chúng ta cần đổi hướng của PORT dữ liệu trên AVR để sẵn sàng nhận dữ liệu về, do chỉ có 4 bit cao của PORT data kết nối với các đường data của LCD (vì đây là mode 4 bit) nên chỉ cần set hướng cho 4 bit này trên AVR, dòng 6 thực hiện việc set hướng. Trong chế độ 4 bit, LCD sẽ truyền và nhận nibble cao trước vì thế dòng 7 đọc dữ liệu từ LCD thông qua các chân DATA_I vào biến HNib, chú ý là chúng ta chỉ cần lấy 4 bit cao của DATA_I nên cần phải dùng giải thuật mặt nạ (mask) che các bit thấp lại (and với 0xF0). Dòng 8 xóa chân EN để chuẩn bị cho bước tiếp theo. Tương tự, các dòng 10, 11 và 12 đọc nibble thấp vào biến LNib. Hai dòng 13 và 14 kết hợp 2 nibbles để tạo thành số 8 bit và trả kết quả về cho đoạn chương trình.
|
01 |
void Write2Nib(uint8_t chr){ HNib=chr & 0xF0; |

|
01 |
void Write8Bit(uint8_t chr){ |
Đoạn này rất đơn giản là xuất dữ liệu 8 bit ra DATA_O, dùng trong mode 8 bit. Trong mode này, 8 chân data của LCD được nối với 8 đường DATA_O của AVR.
|
01 |
void wait_LCD(){ |
Hàm wait_LCD chỉ làm một việc đơn giản là chờ cho đến khi LCD rảnh để gán các công việc khác. Đoạn code trong list 5 trình bày cách 1: đọc cờ Busy Flag và chờ đến khi nó bằng 0 (LCD rảnh). Việc đọc cờ BF phụ thuộc và mode đang sử dụng là 8 bit hay 4 bit, vì thế lệnh #ifdef trong dòng số 2 kiểm tra mode phù hợp trước khi tiến hành đọc. #ifdef LCD8BIT nghĩa là nếu biến LCD8BIT đã được định nghĩa ở phía trên (mode 8 bit được dùng) thì sẽ tiến hành đọc BF theo mode này. Bằng cách kiểm tra sự có mặt của biến LCD8BIT chương trình sẽ biết cách ghi và đọc LCD phù hợp, phương pháp dùng #ifdef LCD8BIT được áp dụng cho tất cả các hàm sau này. Các đoạn code từ dòng 4 đến 17 thực hiện trong mode 8 bit. Trước khi đọc BF, chúng ta cần gởi 1 lệnh đọc BF ở dòng 9, sau đó ở dòng 12 thực hiện đổi hướng các chân data để nhận giá trị về. Trong dòng 10, kiểm tra bit thứ 7 của DATA_I, DATA_I chính là giá trị đọc về và bit thứ 7 trong giá trị nhận về chính là cờ Busy Flag. Nếu BF=0 (bit_is_clear(DATA_I,7)) thì kết thúc quá trình lặp chờ với lệnh break;. Trong trường hợp mode 4 bit được sử dụng (#else), quá trình kiểm tra cờ BF cũng tương tự, điểm khác nhau duy nhất là cách đọc dữ liệu về có khác, chúng ta dùng hàm Read2Nib đã được viết trước đó để nhận giá trị về (xem dòng 23). Như đã trình bày, chúng ta có thể viết hàm wait_LCD bằng cách dùng hàm delay một khoảng thời gian cố định, trong dòng 29 bạn thấy một hàm _delay_ms(1) không được sử dụng, nếu muốn bạn có thể xóa hết các dòng lệnh trước đó trong hàm wait_LCD và dùng hàm delay này để thay thế, LCD vẫn sẽ hoạt động tốt.
|
01 |
void init_LCD(){ |
Quá trình khởi động gồm 3 bước: function set, display control và entry mode set.
Với function set, ba dòng 5,6 và 7 xác lập các chân điều khiển để chuẩn bị gởi các lệnh. Hai dòng 9 và 10 viết lệnh function set vào LCD theo mode 8 bit. Giá trị 0x38, tức 00111000 là một lệnh xác lập mode 8 bit, LCD 2 dòng và font 5x8. Nếu mode 4 bit được dùng, chúng ta cần viết hàm function set khác đi một chút. Theo mặc định, khi vừa khởi động LCD thì mode 8 bit sẽ được chọn, vì thế nếu một hàm nào đó đươc ghi vào LCD đầu tiên, LCD sẽ cố gắng đọc hết các chân D0:7 để lấy dữ liệu, do trong mode 4 bit các chân D0:3 không được kết nối với AVR nên việc đọc lần đầu có thể dẫn đến sai số. Vì vậy, việc đầu tiên cần làm nếu muốn sử dụng mode 4 bit là gởi một lệnh function set với tham số DL=0 (0010xxxx) đến LCD để báo mode chúng ta muốn dùng. Dòng 13 làm việc này, dòng lệnh chỉ đơn giản set bit D5 nhưng đó chính là gởi lệnh dạng 0010xxxx đến LCD, vì thế LCD sẽ vào mode 4 bit sau lệnh này. Tiếp theo quá trình thao tác với LCD diễn ra bình thường, dòng 16 ghi vào LCD mã của function set, trong trường hợp này là mã 0x28, tức 00101000: mode 4 bit, LCD 2 dòng và font 5x8.
Với Display control, mã lệnh được dùng là 0x0E, tức 00001110 trong đó 00001 là mã của lệnh display control, 3 bit theo sau xác lập hiển thị LCD, hiển thị cursor và không blinking.
Với Entry mode set, mã lệnh được dùng là 0x06 tức hiển thị tăng và không shift. Xem lại phần giải thích tập lệnh LCD để hiểu thêm ý nghĩa của mã lệnh 0x06.
|
01 |
void home_LCD(){ |
List 7 trình bày 2 hàm di chuyển cursor về home (home_LCD) và di chuyển đến 1 vị trí do người dùng đặt. Hàm home_LCD tương đối đơn giản vì chỉ cần ghi mã lệnh 0x02 vào LCD thì cursor sẽ tự động di chuyển về home (vị trí đầu tiên trên LCD).
Hàm move_LCD(uint8_t y,uint8_t x) cho phép di chuyển cursor đến vị trí dòng y, cột x. Điểm cần chú ý trong hàm này là cách tính mã lệnh cần ghi vào LCD. Thực chất đây là lệnh set DDRAM address. Xem lại bảng 2 ta thấy mã lệnh cho lệnh này có dạng 1xxxxxxx trong đó xxxxxxx là một số 7 bit chứa địa chỉ của ô DDRAM chúng ta cần di chuyển đến. Vì thế trước khi thực hiện ghi mã lệnh này, chúng ta cần tính tham số xxxxxxx theo dòng y, cột x. Xem lại tổ chức của DDRAM trong hình 3, giả sử một ô nhớ ở dòng y và cột x trên, do dòng 2 bắt đầu với địa chỉ 64, 2 ô nhớ ở cùng 1 cột trên 2 dòng sẽ cách nhau 64 vị trí (64*(y-1)). Mặt khác do vị trí ô nhớ được tính từ 0 trong khi chúng ta muốn gán tọa độ x bắt đầu từ 1, vì thế chúng ta cần thêm (x-1) vào công thức tính. Cuối cùng chúng ta cần phải thêm mã lệnh set địa chỉ DDRAM, mã 0x80. Giá trị cuối cùng của mã lệnh là : Ad=64*(y-1)+(x-1)+0x80 (dòng 13). Các dòng lệnh tiếp theo trong hàm move_LCD thực hiện ghi giá trị mã lệnh vào LCD.
Cuối cùng là phần code hiển thị LCD được trình bày trong list 8. Phần hiển thị bao gồm 1 chương trình con: xóa LCd, hiển thị 1 ký tự và hiển thị 1 chuỗi các ký tự.
|
01 |
void clr_LCD(){ //xóa toàn bộ LCD |
Để xóa toàn bộ LCD chúng ta cần gởi 1 instruction có mã 0x01 đến LCD, hàm clr_LCD() thực hiện việc này. Lưu ý mã lệnh để xóa LCD là 1 instruction, vì thế cần xóa chân RS xuống 0 trước khi gởi mã này xuống LCD (dòng 2 xóa chân RS). Hàm putChar_LCD(uint8_t chr) hiển thị 1 ký tự lên LCD, giá trị tham số của hàm này là mã ASCII của ký tự cần hiển thị, chr. Nội dung của hàm hoàn toàn giống hàm xóa LCD, chỉ khác đây không phải là 1 instruction nên cần set chân RS lên 1 trước khi gởi mã lệnh đến LCD (dòng 12). Mã lệnh cho hàm này chính là mã ASCII cần hiển thị. Cuối cùng hàm print_LCD(char* str, unsigned char len) cho phép hiển thị 1 chuỗi ký tự liên tiếp lên LCD, thực chất đây là quá trình lặp của hàm hiển thị 1 ký tự. Chú ý tham số len là chiều dài cần hiển thị của chuỗi.
IV. Ví dụ điều khiển Text LCD bằng thư viện myLCD.
Phần này tôi sẽ minh họa cách sử dụng thư viện myLCD.h để hiển thị các ký tự lên 1 Text LCD. Sử dụng phần mềm Proteus vẽ một mạch điện gồm 1 LCD 2x16 (keyword: LM016L), 1 chip Atmega32 và 1 biến trở (POT-LIN) như trong hình 8. Tạo 1 Project bằng WinAVR có tên là TextLCD_Demo và tạo file source là main.c, tạo makefile với khai báo sữ dụng chip ATmega32 và clock 8MHz. Copy file myLCD.h vào thư mục của Project mới tạo. Viết code cho file main.c như trong list 9. Chú ý các định nghĩa chân kết nối với LCD trong phần đầu file myLCD.h phải giống với kết nối thật trong hình 8.

Hình 8. Mạch điện mô phỏng LCD với AVR.
List 8. Chương trình demo điều khiển TextLCD, main.c.
|
#include <avr/io.h> int main(){ putChar_LCD(' '); //ghi 1 khoảng trắng |
Để sử dụng thư viện myLCD, chúng ta cần include file myLCD.h vào Project như trong dòng 3, #include "myLCD.h". Hai dòng 6 và 7 thực hiện khởi động và xóa LCD. Sau đó, các dòng 9, 10 và 11 đặt 3 ký tự là các khoảng trắng và chữ cái D bằng hàm putChat_LCD. Dòng 12 in chuỗi “emo of the” ngay tiếp theo chữ cái D trước đó bằng hàm print_LCD. Dòng 13 thực hiện di chuyển cursor đến vị trí dòng thứ 2, cột đầu tiên của LCD trước khi tiến hành in chuỗi thứ 2 “2x16 LCD Display” ở dòng code 14. Nếu bạn thực hiện đúng trình tự như trên, kết quả thu được sẽ như trong hình 8.






