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, RW, 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:
Trang 1Chân Kí Hiệu Mức Logic I/O Chức Năng
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
Trang 2chế độ 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
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ự
Trang 3vù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.
Trang 4Hình 4 Vùng nhớ CGROM.
Trang 52.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
Trang 6để 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.
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
Trang 7mộ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 để
Trang 8truyề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.
Trang 9Hì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
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
Trang 10void 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ế.
#define sbi(sfr,bit) sfr|=_BV(bit)
#define cbi(sfr,bit) sfr&=~(_BV(bit))
#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 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ợ.
Các biến EN, RW và RS đị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
Trang 11nà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).
Trang 12Hì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.
Trang 13Hà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.
uint8_t HNib, LNib, temp_data;
temp_data=DATA_O & 0x0F; //doc 4 bit thap cua DATA_O de mask,
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
Trang 14thà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:
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.
cbi(CTRL,RS); //đây là Instruction
sbi(CTRL,RW); //chiều từ LCD ra ngoài
DDR_DATA=0xFF; //hướng data out
Trang 15cbi(CTRL,RW); //ready for next step
DDR_DATA=0xFF; //Ready to Out
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.
Trang 16//Entry mode
cbi(CTRL,RS); // the following data is COMMAND
Trang 17nà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.
Trang 18trê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à : 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.
void clr_LCD(){ //xóa toàn bộ LCD
cbi(CTRL,RS); //RS=0 mean the following data is COMMAND (not
void putChar_LCD(uint8_t chr){ //hiển thị 1 ký tự chr lên LCD
sbi(CTRL,RS); //this is a normal DATA
Trang 19Để 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àmprint_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.
Trang 20Hì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.
putChar_LCD(' '); //ghi 1 khoảng trắng
putChar_LCD(' '); //ghi 1 khoảng trắng
Trang 21putChar_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
ở 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
LCD có 2 chế độ giao tiếp:
Chế độ 4 bit (chỉ dùng 4 chân D4 đến D7 để truyền dữ liệu) và chế độ 8 bit (dùng cả 8 chân dữ liệu từ D0 đến D7), ở chế độ 4 bit, khi truyền 1 byte, chúng ta sẽ truyền nửa cao của byte trước, sau đó mới truyền nửa thấp của byte.
Trước khi truyền các kí tự ra màn hình LCD ta cần thiết lập cho LCD như chọn chế độ 4 bit hoặc 8 bit, 1 dòng hay 2 dòng ,bật/tắt con trỏ
Để đọc thanh ghi lệnh,ta phải đặt RS=0 và R/W =1 và xung cao xuống thấp cho bít E Sau khi đọc thanh ghi lệnh,nếu bit D7(cờ bận ) ở mức cao thì LCD bận, không có thông tin hay lệnh nào được xuất đến nó Khi D7=0 mới có thể gửi lệnh hay dữ liệu đến LCD Chúng ta nên kiểm tra bit cờ bận trước khi ghi thông tin lên LCD.
Phần mềm cần thực hiện các chức năng chính như sau :
Hiển thị thời gian bình thường: khi khởi tạo, vi điều khiển kiểm tra xem trong
IC thời gian thực đã có thời gian hay chưa, nếu chưa có thời gian cài đặt trước
đó thì hiển thị giá trị thời gian mặc định mà ta thiết lập sẵn; còn bình thường
đã có thời gian cài đặt trước đó vi điều khiển đọc dữ liệu thời gian từ IC thời gian thực, xử lý và hiển thị kết quả lên màn hình LCD.
Cài đặt thời gian: khi xuất hiện ngắt ngoài 0, vi điều khiển bắt đầu thực hiện cài đặt ngày giờ Trên LCD, theo lần xuất hiện ngắt ngoài 0 mà lần lượt vị trí con trỏ của nó sẽ nhảy tới giá trị thời gian lần lượt là giờ, phút, giây, ngày, tháng, năm; tùy vào yêu cầu cài đặt mà tăng giảm giá trị thời gian sau đó ghi
Trang 22dữ liệu vào IC thời gian thực; kèm theo một cờ vào thanh ghi ram đầu tiên để nhận biết là đã được cài đặt thời gian.
en=1; // cho phep muc cao en=0; //xung cao xuong thap
}
Trang 23void khoitaoLCD(void)
{ ghilenhLCD(0X38); // hai dong va ma tran 5x7
ghilenhLCD(0X0C); //bat man hinh , bat con tro ghilenhLCD(0X01); //xoa man hinh
ghilenhLCD(0X06);//dich hien thi sang phai(tang con tro sang phai)
void ghiso(unsigned char a)
{ unsigned char i;
i=a/10; ghi_chuoi(rtc[i]);
i=a%10; ghi_chuoi(rtc[i]); }
void hienthi_dulieu_rtc(unsigned char x)
{ unsigned char temp;
temp = x/16; //chuyen luon so sang decima
ghi_chuoi(rtc[temp]);
temp = x%16;
ghi_chuoi(rtc[temp]); }
/***********************************************************
// CAC CHUONG TRINH CON GIAO TIEP VOI RTC
// ca 2 dk start va stop dc tao ra boi thiet bi chu
void start_rtc(void) //dk start: 1 su cdoi tthai tu cao xuong thap tren duong sda
trong khi duong scl dang o muc cao
{scl=1; sda=1;
_nop_();_nop_();
sda=0; scl=0; }
void stop_rtc(void) //dk stop: 1 su cdoi trang thai tu muc thap len cao tren
duong sda trong khi duong slc dang o muc cao
{ sda=0; scl=1;
_nop_();_nop_();
sda=1; }
Trang 24void gui_rtc(unsigned char x)
unsigned char nhan_rtc(void)
{ unsigned char Data,i;
for(i=0;i<8;i++) // nhan vao 8 bit
{ scl=1;
Data<<=1;
Data=Data|sda;
scl=0; } sda=1;
scl=1; //master nhan/gui bit du lieu(sda) khi scl o muc cao
_nop_();
scl=0;
_nop_(); //du lieu(sda) thay doi khi scl muc thap
return Data; //tra gia tri cho ham
}
unsigned char docdulieu(unsigned char diachi)
{ unsigned char Data;
Trang 25{ ghilenhLCD(0xca); // ep con tro den vi tri thu 11 dong thu 2
hienthi_dulieu_rtc(docdulieu(0)); //hien thi giay ghilenhLCD(0xc7); // ep con tro den vi tri thu 8 dong thu 2 hienthi_dulieu_rtc(docdulieu(1)); // hien thi phut
ghi_kytu(':');
ghilenhLCD(0xc4); // ep con tro den vi tri thu 5 dong thu 2 hienthi_dulieu_rtc(docdulieu(2)); //hien thi gio
ghi_kytu(':');
ghilenhLCD(0x80); //ep con tro den dau dong thu 1
ghi_chuoi(day[docdulieu(3)-1]); ghi_kytu(','); //hien thi thu ghilenhLCD(0x86); //ep con tro den vi tri thu 7 dong thu 1 hienthi_dulieu_rtc(docdulieu(4));//hien thi ngay
ghi_kytu('-'); hienthi_dulieu_rtc(docdulieu(5));//hien thi thang ghi_chuoi("-20");hienthi_dulieu_rtc(docdulieu(6)); //nam
Trang 26void caidat_rtc()
{ unsigned char giay,phut,gio,thu,ngay,thang,nam;
giay = bcd_dec(docdulieu(0)&0x7f); // de bit 7 (bit clock halt) cua
thanh ghi giay = 0 => ko bi treo)
Trang 27// CAI DAT NGAY - THANG - NAM