Tải bản đầy đủ (.doc) (55 trang)

Tài liệu nghiên cứu về vi xu lý

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (702.83 KB, 55 trang )

1
Chân

Kí Hiệu

1

Vss

2

Mức Logic

I/O

Chức Năng

-

-

Nguồn (GND)

Vcc

-

-

Nguồn (+5V)


3

Vee

-

-

Chỉnh độ tương
phản

4

RS

0/1

I

0=Nhập lệnh
1=Nhập dữ liệu

5

R/W

0/1

I


0=Ghi dữ liệu
1=Đọc dữ liệu

6

E

7

DB0

8

1,1

0

I

Tín hiệu cho phép

0/1

I/O

Bít dữ liệu 0

DB1

0/1


I/O

Bít dữ liệu 1

9

DB2

0/1

I/O

Bít dữ liệu 2

10

DB3

0/1

I/O

Bít dữ liệu 3

11

DB4

0/1


I/O

Bít dữ liệu 4

12

DB5

0/1

I/O

Bít dữ liệu 5

13

DB6

0/1

I/O

Bít dữ liệu 6

14

DB7

0/1


I/O

Bít dữ liệu 7

15

Lamp-

-

-

Đèn LCD

16

Lamp+

-

-

Đèn 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

1


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


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

3



4

Hình 4. Vùng nhớ CGROM.

4


5

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
5


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
6


7

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 để
7


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

8


9

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 char Read2Nib(); //đọc 2 nibbles từ LCD
2 void Write2Nib(uint8_t chr); //ghi 2 nibbles vào LCD
3 void Write8Bit(uint8_t chr); //ghi trự tiếp 8 bit và LCD
9


10
void wait_LCD();
//chờ LCD rảnh
4
void init_LCD(); //khởi động LCD
5
void clr_LCD(); //xóa LCD
6
void home_LCD(); //đưa cursor về home
7
void move_LCD(uint8_t y, uint8_t x); //di chuyển cursor đế vị trí mong muốn
8
(dòng, cột)
9

void putChar_LCD(uint8_t chr);
//ghi 1 ký tự lên LCD
10
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 #include <util/delay.h>
02 #define sbi(sfr,bit) sfr|=_BV(bit)
03 #define cbi(sfr,bit) sfr&=~(_BV(bit))
04 #define EN
2
05 #define RW
1
06 #define RS
0
07 #define CTRL
PORTB
08 #define DDR_CTRL
DDRB
09
PORTB
10 #define DATA_O
PINB
11 #define DATA_I
DDRB
12 #define DDR_DATA
13 /*
14 #define LCD8BIT

PORTD
15 #define DATA_O
PIND
16 #define DATA_I
DDRD
17 #define DDR_DATA
18 */
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
10


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

11


12

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 char Read2Nib(){
02
char HNib, LNib;
03

DATA_O |=0xF0;
04
sbi(CTRL,EN); //enable
05
DDR_DATA &=0x0F; //set 4 bits cao cua PORT DATA lam input
06
HNib=DATA_I & 0xF0;
07
cbi(CTRL,EN); //disable
08
09
sbi(CTRL,EN); //enable
10
LNib = DATA_I & 0xF0;
11
cbi(CTRL,EN); //disable
12
LNib>>=4;
13
return (HNib|LNib);
14
15 }

12


13
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 void Write2Nib(uint8_t chr){
02
uint8_t HNib, LNib, temp_data;
03
temp_data=DATA_O & 0x0F; //doc 4 bit thap cua DATA_O de mask,
04
HNib=chr & 0xF0;
05
LNib=(chr<<4) & 0xF0;
06
07
DATA_O =(HNib |temp_data);
08
sbi(CTRL,EN); //enable
09
cbi(CTRL,EN); //disable
10

11
DATA_O =(LNib|temp_data);
12
sbi(CTRL,EN); //enable
13
cbi(CTRL,EN); //disable
14
15 }
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
13


14
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:

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 void Write8Bit(uint8_t chr){
02
DATA_O=chr; //out 8 bits to DATA Line
03
sbi(CTRL,EN); //enable
04
cbi(CTRL,EN); //disable
05 }
Đ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 void wait_LCD(){
02
#ifdef LCD8BIT
03
while(1){
04
cbi(CTRL,EN); //xóa EN
05
cbi(CTRL,RS); //đây là Instruction
06
sbi(CTRL,RW); //chiều từ LCD ra ngoài
07
08
DDR_DATA=0xFF; //hướng data out
09
DATA_O=0xFF; // gởi lệnh đọc BF
10

sbi(CTRL,EN); //enable
11
12
DDR_DATA=0x00; // Đổi hướng data in
13
if(bit_is_clear(DATA_I,7)) break;
14
}
15
cbi(CTRL,EN); //disable for next step
14


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 }


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 void init_LCD(){
02
DDR_CTRL=0xFF;
03
DDR_DATA=0xFF;
15


16
04 //Function set-----------------------------------------------------------------------------05
cbi(CTRL,RS); // the following data is COMMAND
06
cbi(CTRL, RW); // AVR->LCD
07
cbi(CTRL, EN);
08
#ifdef LCD8BIT
09
Write8Bit(0x38);
10
wait_LCD();
11
#else

12
sbi(CTRL,EN); //enable
13
sbi(DATA_O, 5);
14
cbi(CTRL,EN); //disable
15
wait_LCD();
16
Write2Nib(0x28);//4 bit mode, 2 line, 5x8 font
17
wait_LCD();
18
#endif
19 //Display control------------------------------------------------------------------------20
cbi(CTRL,RS); // the following data is COMMAND
21
#ifdef LCD8BIT
22
Write8Bit(0x0E);
23
wait_LCD();
24
#else
25
Write2Nib(0x0E);
26
wait_LCD();
27
#endif

28 //Entry mode set-----------------------------------------------------------------------29
cbi(CTRL,RS); // the following data is COMMAND
30
#ifdef LCD8BIT
31
Write8Bit(0x06);
32
wait_LCD();
33
#else
34
Write2Nib(0x06);
35
wait_LCD();
36
#endif
37 }
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
16


17
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 void home_LCD(){
02
cbi(CTRL,RS); // the following data is COMMAND
03
#ifdef LCD8BIT
04
Write8Bit(0x02);
05
wait_LCD();
06
#else
07
Write2Nib(0x02);
08
wait_LCD();
09
#endif

10 }
11 void move_LCD(uint8_t y,uint8_t x){
12
uint8_t Ad;
13
Ad=64*(y-1)+(x-1)+0x80; // tính mã lệnh
14
cbi(CTRL,RS); // the following data is COMMAND
15
#ifdef LCD8BIT
16
Write8Bit(Ad);
17
wait_LCD();
18
#else
19
Write2Nib(Ad);
20
wait_LCD();
21
#endif
22 }
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àmhome_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
17


18

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*(y1)+(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 void clr_LCD(){ //xóa toàn bộ LCD
02
cbi(CTRL,RS); //RS=0 mean the following data is COMMAND (not
03 normal DATA)
04
#ifdef LCD8BIT
05
Write8Bit(0x01);
06
wait_LCD();
07
#else
08

Write2Nib(0x01);
09
wait_LCD();
10
#endif
11 }
12 void putChar_LCD(uint8_t chr){ //hiển thị 1 ký tự chr lên LCD
13
sbi(CTRL,RS); //this is a normal DATA
14
#ifdef LCD8BIT
15
Write8Bit(chr);
16
wait_LCD();
17
#else
18
Write2Nib(chr);
19
wait_LCD();
20
#endif
21 }
22 void print_LCD(char* str, unsigned char len){ //Hiển thị 1 chuỗi ký tự
23
unsigned char i;
18



19
24
25
26
27

for (i=0; iif(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à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.

19


20

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
20


21
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.
 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
21



22
dữ 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.
Chương trình
#include <AT89X51.H>
#include <intrins.h>
// DINH NGHIA 1 SO CONG
#define sda P1_1
#define scl P1_0
#define rs P3_7
#define rw P3_6
#define en P3_5
#define menu P3_2
#define tang P3_1
#define giam P3_0
#define congLCD P2
#define D7 P2_7
unsigned char *rtc[]={"0","1","2","3","4","5","6","7","8","9"}
unsigned char *day[]={"CNhat","Thu 2","Thu 3","Thu 4","Thu 5","Thu
6","Thu 7"};
unsigned char giatrikhoitao_rtc[8];
/******************************************************/
//
CAC CHUONG TRINH CON GIAO TIEP VOI LCD
//
void busy()
{ D7=1;
rs=0; rw=1;
while(D7!=0) {en=0; en=1;

}
}
void ghilenhLCD(unsigned char x)
{ busy();
congLCD=x; // gia tri x
rs=0;
// chon thanh ghi lenh
rw=0;
// ghi len lcd
en=1;
// cho phep muc cao
en=0;
//xung cao xuong thap
}
22


23
void 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 ghi_kytu(unsigned char value)
{ busy();
congLCD=value;
rs=1; rw=0;
en=1; en=0; }
void ghi_chuoi(unsigned char *string)

{ unsigned char i;
for(i=0;string[i]!='\0';i++)
ghi_kytu(string[i]); }
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; }
23



24
void gui_rtc(unsigned char x)
{ unsigned char i;
for(i=0;i<8;i++){
sda=(x&0x80)? 1:0; //dua bit du lieu ra chan SDA
scl=1;
_nop_();_nop_();
scl=0;
x<<=1; //bit co trong so lon hon dc truyen truoc
}
scl=1;//nhan bit ACK tu SLAVER
_nop_();_nop_();
scl=0; }
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;
start_rtc();
gui_rtc(0xd0);
gui_rtc(diachi); //bat dau doc du lieu tu thanh ghi co dia chi 0x00
start_rtc();
gui_rtc(0xd1);
Data=nhan_rtc();
stop_rtc();//Stop I2C
return Data; }
void ghidulieu(void)
{ unsigned char t;
for(t=0;t<9;t++){start_rtc();
24


25
gui_rtc(0xd0);
gui_rtc(t);
gui_rtc(giatrikhoitao_rtc[t]);
stop_rtc();}
}
/***************************************************************/
// HIEN THI GIO , NGAY THANG TREN LCD
//
void hienthi_rtc(void)
{ 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
}
/**************************************************************/
//
TAO THOI GIAN TRE DUNG TIMER 0
//
void
delay(long time)//tre time ms
{
while(time--){
TMOD=0x01;// che do 16 bit khong tu nap lai
TH0=0xFC;TL0=0x67;
//6*(65536- FC67+1)*(1/12000)=1000us =1ms
TR0=1;
while(TF0!=1){};
TF0=0; TR0=0;}
}
/***************************************************************/
// CHUONG TRINH CON THUC HIEN VIEC CAI DAT NGAY GIO, THANG NAM
//
unsigned char bcd_dec(unsigned char bcd)

{return((bcd/16)*10+(bcd%16)); //vd bcd=66 -> dec =42}
unsigned char dec_bcd(unsigned char dec)
{return((dec/10)*16+(dec%10)); }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
25


×