Text LCD

( 108 Votes )

Nội dung Các bài cần tham khảo trước
  1. Bạn sẽ đi đến đâu.
  2. Text LCD.
  3. AVR và Text LCD.
  4. Ví dụ điều khiển Text LCD bằng thư viện myLCD.
Download ví dụ

   

I. Bạn sẽ đi đến đâu.

       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.

II. Text LCD.

       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.

Text LCD

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.

Connect LCD

       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.

Connect LCD

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

DDRAM

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.

CGROM

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.

RS

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

Connect 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.

III. AVR và Text LCD.

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.

Flowchart

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).

List 0. Các hàm có trong thư viện myLCD.

1
2
3
4
5
6
7
8
9
10

char Read2Nib();  //đọc 2 nibbles từ LCD
void Write2Nib(uint8_t chr); //ghi 2 nibbles vào LCD
void Write8Bit(uint8_t  chr); //ghi trự tiếp 8 bit và LCD
void wait_LCD();             //chờ LCD rảnh
void init_LCD();  //khởi động LCD
void clr_LCD();  //xóa LCD
void home_LCD();  //đưa cursor về home
void move_LCD(uint8_t y, uint8_t x);  //di chuyển cursor đế vị trí mong muốn (dòng, cột)
void putChar_LCD(uint8_t chr);                         //ghi 1 ký tự lên LCD
void print_LCD(char* str, unsigned char len); //hiển thị chuỗi ký tự

       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).

List 1. Định nghĩa các biến thay thế.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18

#include <util/delay.h>
#define sbi(sfr,bit) sfr|=_BV(bit)
#define cbi(sfr,bit) sfr&=~(_BV(bit))
#define EN                            2
#define RW                           1
#define RS                            0
#define CTRL                       PORTB
#define DDR_CTRL             DDRB

#define DATA_O                  PORTB
#define DATA_I                    PINB
#define DDR_DATA             DDRB
/*
#define LCD8BIT
#define DATA_O                  PORTD
#define DATA_I                    PIND
#define DDR_DATA             DDRD
*/

       cbi 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ợ.

       Các biến EN, RWRS định nghĩa số thứ tự của chân trên 1 PORT của AVR được dùng để kết nối với các chân EN, R/W và RS của LCD. CTRL là biến cho biết PORT nào của AVR được dùng để kết nối với các chân điều khiển của LCD. DDR_CTRL là thanh ghi điều khiển hướng của PORT kết nối với các chân điều khiển, DDR_CTRL luôn phụ thuộc vào biến CTRL. Trong trường hợp của bài này, bạn thấy tôi định nghĩa CTRL là PORTB nghĩa là PORTB được dùng để kết nối với các chân điều khiển LCD, vì CTRL là PORTB nên DDR_CTRL phải là DDRB (thanh ghi điều khiển hướng của PORTB). EN định nghĩa bằng 2 nghĩa là chân EN của LCD được nối với chân 2 của PORTB (PB2), tương tự chân R/W nối với chân 1 PORTB (PB1) và chân RS nối với chân 0 PORTB (PB0). Việc chọn các PORT giao tiếp và thứ tự chân phụ thuộc vào kết nối thật trong mạch điện giao tiếp, bạn phải thay đổi các định nghĩa này cho phù hợp với thiết kế mạch điện của bạn. Lý do cho việc định nghĩa các biến thay thế kiểu này là nhằm tạo ra tính tổng quát cho thư viện hàm. Ví dụ, một người không muốn dùng PORTB để điều khiển LCD mà dùng PORTA thì người này chỉ cần thay đổi định nghĩa ở 2 dòng 7 và 8, không cần thay đổi nội dung các hàm vì trong các hàm này chúng ta chỉ dùng tên thay thế là CTRL và DDR_CTRL. Tương tự, tôi định nghĩa 3 biến thay thế là DATA_O nghĩa là PORT xuất dữ liệu, DATA_I là PORT nhập dữ liệu và DDR_DATA là thanh ghi điều khiển hướng. DATA_O và DATA_I là PORT nối với các chân D0:7 (mode 8 bit) hoặc D4:7 (mode 4 bit) của LCD, đây là các đường truyền và nhận dữ liệu. Trong ví dụ trên, tôi dùng chính PORTB làm đường data vì đây là trường hợp giao tiếp 4 bit, do 3 chân đầu của PORTB kết nối với các chân điều khiển nên PORTB chỉ còn thừa lại 5 chân, chúng ta sẽ nối 4 chân PB4, PB5, PB6 và PB7 tương ứng với D4, D5, D6 và D7 của LCD. Hình  7 mô tả cách kết nối AVR và LCD theo ví dụ này. Tất nhiên bạn có thể sử dụng PORT khác làm đường data nhất là khi bạn muốn sử dụng mode 8 bit, vì trong mode này cần tới 11 đường giao tiếp (3 điều khiển + 8 data). Phần được che trong 2 dấu comment /* */ là trường hợp bạn muốn dủng mode 8 bit. Để sử dụng mode 8 bit, bạn cần định nghĩa 1 biến có tên LCD8BIT, bit này sẽ báo cho các đoạn chương trình con thực hiện ghi và đọc dữ liệu theo cách 8 bit. Đồng thời, bạn phải định nghĩa lại đường giao tiếp data (DATA_O, DATA_I, DDR_DATA).

Flowchart

Hình 7. Ví dụ Kết nối LCD với AVR trong mode 4 bit (chip mega8).

       Phần bên dưới là phần định nghĩa các hàm trong thư viện myLCD. Bốn hàm đầu tiên (xem lại List 0) là các hàm hỗ trợ, chúng chỉ được dùng bởi các hàm khác trong thư viện và không được gọi trong các chương trình ứng dụng bên ngoài.
List 2. Đọc 2 nibbles từ LCD.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15

char Read2Nib(){
       char HNib, LNib;
       DATA_O |=0xF0;

       sbi(CTRL,EN); //enable
       DDR_DATA &=0x0F; //set 4 bits cao cua PORT DATA lam input
       HNib=DATA_I & 0xF0;
       cbi(CTRL,EN); //disable

       sbi(CTRL,EN); //enable
       LNib = DATA_I & 0xF0;
       cbi(CTRL,EN); //disable
       LNib>>=4;
       return (HNib|LNib);
}

       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.

List 3. Ghi 2 nibbles vào LCD.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15

void Write2Nib(uint8_t chr){
       uint8_t HNib, LNib, temp_data; 
       temp_data=DATA_O & 0x0F; //doc 4 bit thap cua DATA_O de mask,

       HNib=chr & 0xF0;
       LNib=(chr<<4) & 0xF0; 

       DATA_O =(HNib |temp_data); 
       sbi(CTRL,EN); //enable
       cbi(CTRL,EN); //disable 

       DATA_O =(LNib|temp_data);  
       sbi(CTRL,EN); //enable
       cbi(CTRL,EN); //disable
}

       Hàm Write2Nib thực hiện ghi một biến 8 bit có tên chr vào LCD theo từng nibble, hàm này được sử dụng rất nhiều lần trong mode 4 bit. Dòng 2 định nghĩa 3 biến tạm là HNib, LNib và temp_data, không giống như khi đọc từ LCD, việc ghi vào LCD có thể làm ảnh hưởng đến các chân của PORT dùng làm đường dữ liệu nhất là khi các đường điều khiển và dữ liệu dùng chung 1 PORT (PORTB). Biến temp_data dùng trong giải thuật mặt nạ để không làm ảnh hưởng đến các bit khác khi ghi LCD. Dòng 3 đọc dữ liệu từ PORT DATA_O và che đi các bit cao, chỉ lưu lại các bit thấp vào biến temp_data vì các bit thấp này không được dùng xuất nhập dữ liệu (xem hình 7, các chân thấp của PORTB dùng làm các chân điều khiển). Để ghi 1 giá trị 8 bit có tên là chr theo cách ghi từng nibbles chúng ta cần tách biến chr thành 2 nibbles. Dòng 5 tách 4 bit cao của chr và chứa vào biến HNib. Dòng 6 thực hiện thêm việc di chuyển 4 bit thấp của chr qua trái rồi gán cho biến LNib. Như vậy sau 2 dòng này các biến HNib và LNib được mô tả như sau:

2Nib

       Do dữ liêu đã được sắp xếp sẵn sàng ở các vị trí cao (ứng với các chân D4:7) nên công viêc tiếp theo chỉ đơn giản là xuất 2 biến HNib và LNib ra đường DATA_O, cần phải tạo 1 “xung cạnh xuống” ở chân EN mỗi lần xuất dữ liệu (dòng 9, 10). Chú ý là phải xuất nibble cao trước và nibble thấp theo sau.
List 4. Ghi 8 bit trực tiếp vào LCD.

01
02
03
04
05

void Write8Bit(uint8_t chr){
       DATA_O=chr;   //out 8 bits to DATA Line
       sbi(CTRL,EN); //enable
       cbi(CTRL,EN); //disable
}

       Đ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.

List 5. Chờ LCD rảnh.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

void wait_LCD(){
       #ifdef LCD8BIT 
              while(1){
                     cbi(CTRL,EN); //xóa EN
                     cbi(CTRL,RS);  //đây là Instruction
                     sbi(CTRL,RW); //chiều từ LCD ra ngoài

                     DDR_DATA=0xFF; //hướng data out
                     DATA_O=0xFF;    // gởi lệnh đọc BF
                     sbi(CTRL,EN);     //enable

                     DDR_DATA=0x00; // Đổi hướng data in
                     if(bit_is_clear(DATA_I,7)) break;
              }
              cbi(CTRL,EN); //disable for next step 
              cbi(CTRL,RW); //ready for next step 
              DDR_DATA=0xFF; //Ready to Out
       #else
              char temp_val;
              while(1){
                     cbi(CTRL,RS); //RS=0, the following data is COMMAND
                     sbi(CTRL,RW); //LCD -> AVR
                     temp_val=Read2Nib();
                     if (bit_is_clear(temp_val,7)) break;
              }
              cbi(CTRL, RW); //ready for next step 
              DDR_DATA=0xFF;//Ready to Out
       #endif
       //_delay_ms(1); 
}

       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.

List 6. Khởi động LCD.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

void init_LCD(){
       DDR_CTRL=0xFF;
       DDR_DATA=0xFF;
//Function set------------------------------------------------------------------------------
       cbi(CTRL,RS);   // the following data is COMMAND
       cbi(CTRL, RW); // AVR->LCD
       cbi(CTRL, EN);
       #ifdef LCD8BIT 
              Write8Bit(0x38);
             wait_LCD();
       #else
             sbi(CTRL,EN); //enable
              sbi(DATA_O, 5);
              cbi(CTRL,EN); //disable
              wait_LCD();  
              Write2Nib(0x28);//4 bit mode, 2 line, 5x8 font
              wait_LCD(); 
       #endif
//Display control------------------------------------------------------------------------- 
       cbi(CTRL,RS); // the following data is COMMAND
       #ifdef LCD8BIT 
              Write8Bit(0x0E);
              wait_LCD();
       #else
              Write2Nib(0x0E);
              wait_LCD();   
       #endif
//Entry mode set------------------------------------------------------------------------
       cbi(CTRL,RS); // the following data is COMMAND
       #ifdef LCD8BIT
              Write8Bit(0x06);
              wait_LCD();
       #else
              Write2Nib(0x06);
              wait_LCD();
       #endif
}

       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.

List 7. Di chuyển cursor.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22

void home_LCD(){
       cbi(CTRL,RS); // the following data is COMMAND
       #ifdef LCD8BIT 
              Write8Bit(0x02);
              wait_LCD(); 
       #else
              Write2Nib(0x02);
              wait_LCD();  
       #endif 
}
void move_LCD(uint8_t y,uint8_t x){
       uint8_t Ad;
       Ad=64*(y-1)+(x-1)+0x80; // tính mã lệnh
       cbi(CTRL,RS); // the following data is COMMAND
       #ifdef LCD8BIT 
              Write8Bit(Ad);
              wait_LCD();
       #else
              Write2Nib(Ad);
              wait_LCD();
       #endif 
}

       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ự.

List 8. Hiển thị trên LCD.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

void clr_LCD(){ //xóa toàn bộ LCD
       cbi(CTRL,RS); //RS=0 mean the following data is COMMAND (not normal DATA)
       #ifdef LCD8BIT 
              Write8Bit(0x01);
              wait_LCD(); 
       #else
              Write2Nib(0x01);
              wait_LCD();  
       #endif
}
void putChar_LCD(uint8_t chr){ //hiển thị 1 ký tự chr lên LCD
       sbi(CTRL,RS); //this is a normal DATA 
       #ifdef LCD8BIT 
              Write8Bit(chr);
              wait_LCD(); 
       #else
              Write2Nib(chr);
              wait_LCD(); 
       #endif
}
void print_LCD(char* str, unsigned char len){ //Hiển thị 1 chuỗi ký tự
       unsigned char i;
       for (i=0; i<len; i++)
              if(str[i] > 0) putChar_LCD(str[i]);
             else putChar_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.

demo

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>
#include <util/delay.h>
#include "myLCD.h"
//include thư viện myLCD

int main(){ 
       init_LCD(); //khởi độ LCD
       clr_LCD(); // xóa toà bộ LCD

       putChar_LCD(' '); //ghi 1 khoảng trắng
       putChar_LCD(' '); //ghi 1 khoảng trắng
       putChar_LCD('D'); //Hiển thị kýtự 'D'
       print_LCD("emo of the",10); //hiển thị 1 chuỗi ký tự
       move_LCD(2,1); //di chuyển cursor đến dòng 2, cột đầu tiên
       print_LCD("2x16 LCD Display",16); //hiển thị chuỗi thứ 2
       while(1){

       };
}

       Để 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.