avr - mot so ung dung

94
Copyright © 2010 Cùng hc AVR Thanhtam Ho - www.hocavr.com Created by QuocHuy Hoang [email protected] Page 1 Ma trn LED Ni dung Các bài cn tham kho trước 1. Ma trn LED. 2. AVR và ma trn LED. Download ví dCu trúc AVR. WinAVR. C cho AVR. Mô phng vi Proteus. I. Ma trn LED. Ma trn LED tc Dot Matrix LED là tp hp nhiu đèn LED được btrí thành dng “ma trn” hình chnht hoc vuông vi shàng là a và sct là b. Ma trn LED được dùng rt nhiu trong các ng dng hin thnhư các bin qung cáo, hin ththay thế LCD hoc thm chí dùng hin thvideo…Để gim slượng các đường điu khin, trong các ma trn LED các LED được ni chung vi nhau theo hàng và ct. Slượng LED trên ma trn LED là axb trong khi slượng ngõ ra bng tng shàng và ct: a + b. Vic điu khin 1 ma trn LED kích thước ln đòi hi thiết kế mt mch driver và điu khin rt phc tp. Vi mc đích giúp bn đọc làm quen khái nim ma trn LED, trong phm vi bài này tôi chtrình bày thao tác vi 1 ma trn LED có kích thước 7x5 (7 hàng, 5 ct). ma trn LED 7x5 thường được dùng để hin thcác ký ttrong bng mã ASCII thay cho Text LCD. Tuy nhiên, bn có thghép các ma trn LED này li để hin thcác loi hình nh bt kđộ phân gii thp. Hình 1 mô tmt cu trúc ca mt ma trn LCD 7x5 vi 12 ngõ ra được đặt tên tC0…C4 và D0…D6 (C đại din cho Control line và D là Data line). Hình 1. Ma trn LED 7x5.

Upload: thanhv25

Post on 02-Aug-2015

329 views

Category:

Documents


6 download

DESCRIPTION

Uploaded from Google Docs

TRANSCRIPT

Page 1: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 1

Ma tr ận LED

Nội dung Các bài cần tham khảo trước

1. Ma trận LED.

2. AVR và ma trận LED.

Download ví dụ

Cấu trúc AVR.

WinAVR.

C cho AVR.

Mô phỏng với Proteus.

I. Ma tr ận LED.

Ma trận LED tức Dot Matrix LED là tập hợp nhiều đèn LED được bố trí thành dạng “ma trận” hình chữ nhật hoặc vuông với số hàng là a và số cột là b. Ma trận LED được dùng rất nhiều trong các ứng dụng hiển thị như các biển quảng cáo, hiển thị thay thế LCD hoặc thậm chí dùng hiển thị video…Để giảm số lượng các đường điều khiển, trong các ma trận LED các LED được nối chung với nhau theo hàng và cột. Số lượng LED trên ma trận LED là axb trong khi số lượng ngõ ra bằng tổng số hàng và cột: a + b. Việc điều khiển 1 ma trận LED kích thước lớn đòi hỏi thiết kế một mạch driver và điều khiển rất phức tạp. Với mục đích giúp bạn đọc làm quen khái niệm ma trận LED, trong phạm vi bài này tôi chỉ trình bày thao tác với 1 ma trận LED có kích thước 7x5 (7 hàng, 5 cột). ma trận LED 7x5 thường được dùng để hiển thị các ký tự trong bảng mã ASCII thay cho Text LCD. Tuy nhiên, bạn có thể ghép các ma trận LED này lại để hiển thị các loại hình ảnh bất kỳ có độ phân giải thấp. Hình 1 mô tả một cấu trúc của một ma trận LCD 7x5 với 12 ngõ ra được đặt tên từ C0…C4 và D0…D6 (C đại diện cho Control line và D là Data line).

Hình 1. Ma trận LED 7x5.

Page 2: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 2

Bên trong các ô của ma trận LED là các LED phát sang. Trong mô hình trên, Cathod (cực âm) của các LED trên mỗi hàng được nối chung với nhau và ngõ ra chung là các ngõ D (Data). Các Anod của các LED trên mỗi cột được nối chung tạo thành các đường C (Control). Thông thường, các đường D và C được chọn sao số số lượng đường D nhiều hơn đường C hoặc sao cho số lương các đường D gần nhất với số 8, 16, 32…(lũy thừa của 2). Lý do của việc chọn này nhằm giảm kích thước bộ font chứa các ký tự hoặc hình ảnh hiển thị lên ma trận LED, bạn sẽ hiểu rõ hơn khi tìm hiểu các điều khiển ma trận LED 7x5 bên dưới.

a) b)

Hình 2 mô tả cách mà ma trận LED 7x5 được dùng để hiển thị số 4.

Trước hết chúng ta sẽ khảo cách cho sang các LED mà không cần quan tâm đến bảng font. Quan sát cột thứ nhất (cột C0) trong hình 2a, trong cột này chỉ có 2 LED ở hàng D2 và D3 là sang, các LED còn lại tắt. Điều này được thực hiện bằng cách kích chân C0 (Anod) lên mức cao, kéo các chân D2, D3 xuống mức 0 trong khi các chân Data khác được giữ ở mức cao. Các cột khác được thực hiện tương tự. Tuy nhiên, câu hỏi ở đây là làm sao hiển thị các cột với các đèn LED sáng khác nhau trong khi các ngõ Cathod của chúng đều được nối chung (thành các chân D). Ví dụ một người kéo tất cả 5 chân C0…C4 lên mức cao vào xuất tín hiệu ra các chân D, khi đó tất cả các LED trên dùng một hàng sẽ sáng hoặc tắt như nhau. “Bí quyết” ở đây chính là kỹ thuật “quét”, chúng ta sẽ hiển thị tuần tự các cột với các giá trị tương ứng của chúng chứ không hiển thị đồng thời. Trong ví dụ hiển thị số ‘4’, trước hết hãy kích chân C0 lên cao trong khi các chân C1…C4 ở mức thấp, xuất tín hiệu ra các chân D để hiển thị lên cột C0. Tiếp theo kéo chân C1 lên cao và các chân Control khác ở mức thấp, xuất dữ liệu ra các chân D để hiển thị cột C1…Cứ như thế cho đến khi hiển thị hết các cột thì

Page 3: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 3

quay lại cột C0. Quá trình này gọi là “quét LED”. Do tốc độ “quét” rất cao nên chúng ta sẽ không có cảm giác “nhấp nháy”, các cột của ma trận như được hiển thị đồng thời. Chú ý là độ sáng của LED phụ thuộc vào số cột LED, nếu bạn “quét” quá nhiều cột LED, tỉ lệ thời gian “ON” của mỗi cột sẽ rất nhỏ so với thời gian “OFF” vì phải chờ quét các cột khác. Vì thế nếu ma trận LED có nhiều cột hoặc khi ghép nhiều ma trận, các mạch driver cần được sử dụng để đảm bảo độ sáng của LED.

Giả sử mỗi LED đại diện cho 1 bit và các LED sáng đại diện cho giá trị nhị phân 1 trong khi các LED tắt là số 0. Hình 2b thể hiện mô hình số nhị phân cho trường hợp hiển thị số 4 trên ma trận LED 7x5. Nếu xem mỗi cột của ma trận là 1 con số 7 bit thì 5 giá trị cần thiết để hiền thị số ‘4’ là: 0x0C, 0x14, 0x24, 0x7F, 0x04. Bộ 5 giá trị này tạo thành mã font cho ký tự ‘4’, chúng sẽ được định nghĩa trước và lưu trong bộ nhớ của chip điều khiển (AVR), mỗi lần một ký tự được yêu cầu hiển thị, bộ font tương ứng của ký tự đó sẽ được “load” ra và xuất lần lượt trên các đường Data, đây chính là lý do tại sao chúng ta gọi các đường D là các đường Data. Cách “quét” LED tôi vừa trình bày là cách “quét ngang”, bạn có thể thực hiện “quét dọc” nếu ứng dụng yêu cầu. Trong phương pháp quét dọc, các chân hàng chung sẽ được dùng để chọn hàng cần hiển thị, dữ liệu sẽ xuất ra theo từng hàng trên 5 cột và lần lượt thay đổi hàng (hàng 0 trước, đến 1…và cuối cùng là 6). So sánh 2 cách quét cho trường hợp ma trận LED 7x5, rõ ràng trong cách quét ngang chúng ta chỉ cần quet 5 cột cho mỗi lần LED nên tỉ lệ thời gian ON sẽ cao hơn (1/5 so với 1/8 của cách quét dọc). Mặt khác, nếu thực hiện quét dọc chúng ta cần 8 số số để tạo thành 1 bộ font cho một ký tự và vì thế tốn nhiều bộ nhớ hơn cho việc lưu trữ bảng font. Trong bài học này tôi thực hiện theo cách quét ngang và bảng font cũng được xây dựng cho cách quét này.

II. AVR và Ma tr ận LED.

Phần này tôi minh họa cách hiển thị ma trận LED 7x5 bằng AVR. Chúng ta sẽ thực hiện trên duy nhất một ma trận LED, cho các ứng dụng cần nhiều LED bạn đọc hãy tự phát triển từ ý tưởng trong phần này. Hãy vẽ một mạch điện mô phỏng bằng phần mềm Proteus như trong hình 3.

Page 4: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 4

Hình 3. Hiển thị ma trận LED bằng AVR.

Các chân C của ma trận được nối với các chân trên PORTC của chip AVR ATmega32, và các chân D được nối với PORTD. Hãy tạo 1 Project bằng Programmer Notepad tên DotMatrix và tạo 2 file tên font.h cùng dotmatrix.c trong Project này. File font.h chứa bảng font của các ký tự và file dotmatrix.c là file chính cho chương trình demo. List 1 trình là một phần nội dung của file font.h và List 2 là nội dung file dotmatrix.c.

List 1. Bảng font.

Page 5: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 5

List 2. Chương trình demo.

Page 6: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 6

Điều cần quan tâm đầu tiên là kích thước bảng font, trong ví dụ này bảng font được xây dụng cho 223 symbol có mã ASCII từ 32 đến 255 (do các mã ASCII trước 32 không có symbol tương ứng nên có thể bỏ qua để tiết kiệm bộ nhớ), mỗi symbol cần 5 số 8 bits, như thế chúng ta cần tổng cộng 1115 byte cho bảng font trong khi kích thước SRAM của chip ATmega32 chỉ là 2KB (2048 byte). Nếu dùng SRAM chứa bảng font sẽ rất phí phạm vì đây là 1 bảng tĩnh, giá trị trong bảng hoàn toàn không thay đổi mà chỉ được truy xuất đọc. Vì thế chúng ta có thể tận dụng bộ nhớ chương trình (Flash) để lưu bảng font này. Dòng đầu tiên trong List 1 chúng ta include header “pgmspace.h” để sử dụng các thao tác trên bộ nhớ chương trình. Tiếp theo chúng ta khai báo 1 mảng tĩnh có tên font7x5 với kiểu dữ liệu là prog_char tức là kiểu char nhưng chứa trong bộ nhớ chương trình (Program memory). Giá trị chứa trong mảng font7x5 chính là dữ liệu của bảng font, thực chất mảng font7x5 là mảng 1 chiều liên tục, việc tách ra trên nhiều dòng có mục đích giúp người đọc dễ hình dung khi truy cập các giá trị của mảng để xuất ra sau này. Bạn hãy hiểu rằng cứ một tổ hợp 5 số sẽ tạo thành một symbol hiển thị cho ma trận

Page 7: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 7

LED. Dữ liệu trong bảng font được sắp xếp theo trình tự ASCII và để tạo điều kiện thuận lợi khi truy xuất bảng font theo mã ASCII của ký tự cần hiển thị. Tuy nhiên cần chú ý là bảng font được bắt đầu cho symbol có mã ASCII là 32 chứ không bắt đầu từ mã ASCII 0, vì thế khi truy cận bảng font từ mã ASCII chúng ta cần lấy mã ASCII trừ đi 32 để được vị trí chính xác trong bảng.

Tiếp theo chúng ta sẽ tìm hiểu chương trình chính, dòng 3 trong list 2 include file font.h để sử dụng bảng font trong chương trình chính. Các dòng từ 5 đến 9 định nghĩa các PORT kết nối với ma trận LED, PORTD là Data bus trong khi PORTC là control lines. Chương trình con void DOTputChar75(uint8_t chr) trong dòng 11 là thủ tục đọc dữ liệu từ bảng font và hiển thị trên ma trận LED. Tham số chr của chương trình này chính là mã ASCII của ký tự cần hiển thị trên ma trận LED. Dòng 12 khai báo 2 biến phụ, trong đó biến line chứa tín hiệu điều khiển cho các đường Control. Dòng 13 khai báo một biến tạm tchr dùng chứa địa chỉ dữ liệu cần lấy ra từ bảng font để xuất ra các đường Data, vì mã ASCII là một số 8 bit trong khi số lượng dữ liệu trong bảng font lớn gấp 5 lần số lương ký tự, vì thế cần khai báo biến tchr có kiểu dữ liệu 16 bit. Nội dung chính của đoạn chương trình này nằm trong vòng lặp for, biến i đại diện cho số thứ tự của các chân Control được cho chạy từ 0 đến 4, trong dòng 15 “CTRL_PORT=line; ” xuất tín hiệu điều khiển ra CTRL_PORT tức ra các chân C. Do biến line được khởi tạo bằng 1 nên ở lần lặp đầu tiên giá trị CTRL_PORT=0b00000001, tức chân C0 ở mức cao trong khi các chân còn lại ở mức thấp, cột đầu tiên được chọn. Sau khi 1 cột đã được chọn, dòng 16 “DATA_PORT=~pgm_read_byte(&font7x5[((tchr - 32) * 5) + i]);” đọc và xuất dữ liệu từ bảng font ra các chân Data. Trước hết là cách tính địa chỉ của dữ liệu trong bảng font. Như trình bày trong phần giải thích cho bảng font, bảng này được chúng ta bắt đầu từ ký tự có mã 32 nên chúng ta cần trừ đi 32 để tham chiếu đến vị trí chính xác trong bảng font: tchr-32. Ví dụ muốn hiển thị ký tự có mã chr = 48 (mã của ký tự ‘0’), v ị trí của tổ hợp dữ liệu tạo nên số ‘0’ được chứa trong bảng font ở vị trí 16, giá trị này được tính 48-32=16. Tiếp theo, do mỗi ký tự được tạo thành từ 5 số nên địa chỉ thực chất của số đầu tiên trong tổ hợp sẽ là (tchr-32)*5. Để di chuyển trong phạm vi 5 dữ liệu ứng với 6 cột của ma trận LED, biến i được cộng dồn vào địa chỉ này và chúng ta có: tchr - 32) * 5) + i. Để đọc dữ dữ liệu dạng byte từ bộ nhớ chương trình, chúng ta cần dùng hàm pgm_read_byte, hàm này được định nghĩa trong header pgmspace.h được khai báo trong file font.h. Như vậy saiu khi thực hiện “pgm_read_byte(&font7x5[((tchr - 32) * 5) + i])” chúng ta thu được dữ liệu 1 byte tương ứng với cột thứ i của ký tự chr từ bảng font, việc cuối cùng có thể là xuất giá trị này ra DATA_PORT. Tuy nhiên, trước khi xuất byte đọc được ra DATA_PORT, chúng ta cần đảo các bit của byte này bằng toán tử “~”, lý do được giải thích là do các LED trong ma trận trong ví vụ này có các hàng nối với cực âm Cathode, để một LED sáng thì giá trị cần cấp cho bit D tương ứng là 0 nghĩa là ngược lại so với cách chúng ta tạo bảng font (sáng là 1). Chỉ bằng một thao tác đơn giản là toán tử “~” chúng có thể dễ dàng vượt qua trở ngại này. Trong trường hợp ma trận LED có các hàng nối với cực dương Anode thì chúng ta không

Page 8: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 8

cần đảo giá trị đọc về. Dòng 17 thực hiện dịch chuyển giá trị của biến line sang trái 1 vị trí, việc làm có tác dụng chuẩn bị cho lần kế tiếp chân C kế tiếp sẽ được kích. Hàm delay trong dòng 18 giúp các LED trong cột hiện tại sáng trong 1 khoảng thời gian trước khi chuyển qua cột khác.

Chương trình chính trong ví dụ này thật sự rất đơn giản, chúng ta trước hết cần khởi động hướng xuất nhập cho các PORT và sau đó gọi hàm DOTputChar75() trong vòng lặp vô tận while(1). Ở ví dụ trên, ký tự ‘4’ được xuất ra và kết quả hiển thị như trong hình 3. Chú ý là hàm DOTputChar75() chỉ “quét” qua các cột 1 lượt, vì thế muốn hiển thị một ký tự trong một khoảng thời gian chúng ta cần gọi hàm DOTputChar75() lặp lại trong khoảng thời gian đó.

Page 9: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 9

KeyPad

Nội dung Các bài cần tham khảo trước

1. Keypad 4x4.

2. Đọckeypad 4x4 bằng AVR.

Download ví dụ

Cấu trúc AVR.

WinAVR.

C cho AVR.

Mô phỏng với Proteus.

Text LCD

I. Keypad 4x4.

Keypad là một "thiết bị nhập" chứa các nút nhấn cho phép người dùng nhập các chữ số, chữ cái hoặc ký hiệu vào bộ điều khiển. Keypad không chứa tất cả bảng mã ASCII như keyboard và vì thế keypad thường được tìm thấy trong các thiết bị chuyên dụng. Các nút nhấn trên các máy tính điện tử cầm tay là một ví dụ về keypad. Số lượng nút nhấn của một keypad thay đổi phụ thuộc vào yêu cầu ứng dụng. Trong bài này tôi giới thiệu cách điều khiển của một loại keypad đơn giản, keypad 4x4.

Gọi là keypad 4x4 vì keypad này có 16 nút nhấn được bố trí dạng ma trận 4 hàng và 4 cột. Cách bố trí ma trận hàng và cột là cách chung mà các keypad sử dụng. Cũng giống như các ma trận LED, các nút nhấn cùng hàng và cùng cột được nối với nhau, vì thế với keypad 4x4 sẽ có tổng cộng 8 ngõ ra (4 hàng và 4 cột). Mô hình Keypad 4x4 được thể hiện trong hình 1.

Page 10: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 10

a) b)

Hình 1. Keypad 4x4.

Hình 1b là mô hình thật của 1 keypad 4x4 và hình 1a là cấu hình bên trong của nó. Bốn hàng của keypad được đánh dấu là A, B, C và D trong khi 4 cột được gọi là 1, 2, 3 và 4.

Hoạt động của keypad: Giả sử nhút '2' được nhấn, khi đó đường C và 2 được nối với nhau. Giả sử đường 2 được nối với GND (mass, 0V) thì C cũng sẽ là GND. Tuy nhiên, câu hỏi đặt ra là bằng cách kiểm tra trạng thái đường C chúng ta sẽ có kết luận nút '2' được nhấn? Giả sử tất cả các đường 1, 2, 3, 4 đều nới với GND, nếu C= GND thì rõ ràng chúng ta không thể kết luận nút '1',= hay nút '2' hay nút '3' hay nút '-' được nhấn. Kỹ thuật để khắc phục vấn đề này chính là kỹ thuật "quét" keypad. Kỹ thuật quét keypad bằng AVR được trình bày như sau:

- Nối tất cả 8 chân của keypad với 1 PORT của AVR, ví dụ PORTB theo thứ tự bên dưới:

- Các chân 1, 2, 3, 4 được set như các chân Output và giữ ở mức cao, các chân A, B, C, D là Input và có điện trở kéo lên. Lần lượt kéo chân 1, 2, 3, 4 xuống thấp (lần lượt xuất giá trị 0 ra từng chân), đọc trạng thái các chân A, B, C, D để kết luận nút nào được nhấn. Ví dụ như trong hình 1, nút '2' được nhấn thì quá trình quét sẽ cho kết quả như sau:

• Bước 1: kéo chân 1 xuống 0 (các chân 2,3,4 vẫn ở mức cao), kiểm tra 4 chân A, B, C, D thu được kết quả D=1, C=1, B=1, A=1. (giá trị đọc về của PINB là 00001111 nhị phân)

• Bước 2: kéo chân 2 xuống 0, kiểm tra lại A, B, C, D, kết quả thu được D=1, C=0, B=1, A=1 (giá trị đọc về của PINB là 0b00001011 nhị phân). Chân C=0 tức có 1 nút ở hàng thứ 3 được nhấn, chúng ta lại đang ở Bước thứ 2 tức nút nhấn thuộc cột thứ 2. Chúng ta có thể dừng quá trình quét tại đây và kết quả thu về nút ở hàng 3, cột 2 (tức nut '2' được) được nhấn.

Page 11: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 11

Quá trình quét cho các nút khác cũng xảy ra tương tự. Chú ý, nếu có 1 nút nào đó được nhấn thì có 4 khả năng cò thể đọc về từ 4 A,B,C,D đó là:

• D=1, C=1, B=1, A=0: nút ở hàng A được nhấn, giá trị đọc về là 0x0E (các đường A,B,C,D được nối với 4 bit thấp của PORT trên AVR).

• D=1, C=1, B=0, A=1: nút ở hàng B được nhấn, giá trị đọc về là 0x0D . • D=1, C=0, B=1, A=1: nút ở hàng C được nhấn, giá trị đọc về là 0x0B . • D=0, C=1, B=1, A=1: nút ở hàng D được nhấn, giá trị đọc về là 0x07 .

Để tiện lợi khi so sánh kết quả đọc về, khi lập trình đọc keypad chúng ta nên lập 1 mảng 4 phần tử chứa 4 số có thể đọc về từ keypad. Ví dụ uint8_t scan_code[4]={0x0E,0x0D,0x0B,0x07};

Trong phần tiếp theo chúng ta sẽ khảo sát cách đọc keypad 4x4 bằng 1 chip AVR Atmega32.

II. Đọc Keypad 4x4 bằng AVR.

Chúng ta sẽ mô phỏng cách đọc và hiển thị giá trị từ keypad 4x4 bằng phần mêm Proteus. Các mã đọc được từ keypad sẽ hiễn thị lên 1 Text LCD 16x2. Thư viện myLCD.h được dùng để hiển thị lên LCD (xem lại bài Text LCD). Mạch điện mô phỏng thể hiện trong hình 2.

Page 12: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 12

Hình 2. Đọc và hiển thị từ Keypad 4x4.

Hãy tạo 1 Project bằng WinAVR với tên gọi KEYPAD, tạo file main.c và add vào Project, tạo Makefile, đồng thời copy file myLCD.h từ bài học Text LCD vào thư mục chứa Project KEYPAD. Mở file myLCD.h và sửa phần khai báo PORT như List0.

List 0. Khai báo PORT trong file myLCD.h

01 02 03 04 05 06 07 08

.... #define CTRL PORTC #define DDR_CTRL DDRC

#define DATA_O PORTC #define DATA_I PINC #define DDR_DATA DDRC ....

Viết đoạn code trong List1 vào file main.c

List 1. Nội dung file main.c

Page 13: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 13

Ở dòng 3 chúng ta include file myLCD.h để sử dụng các hàm thao tác Text LCD. Trong các dòng 5, 6 và 7 chúng ta định nghĩa PORT giao tiếp với Keypad, theo đó PORTB được dùng cho Keypad. Dòng 9 khai báo một mảng 4 phần tử chứa mã đọc về từ Keypad như đã thảo luận trong phần trên. Các dòng code từ 10 đến 13 khai báo một mảng 2 chiều có 16 phần tử chứa mã ASCII của các ký tự đại

Page 14: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 14

diện cho các Button, tôi sắp xếp các ký tự dạng ma trận để dễ dàng tương ứng với các nút trên keypad. Dòng 14 khai báo biến key loại 8 bit không dấu, đây là biến chứa mã ascii khi đọc keypad. Dòng 15 khai báo hàm quét Keypad có tên checkpad(). Tất cả giải thuật quét và đọc keypad đều năm trong hàm này, giá trị trả về của hàm là mã ascii của nút được nhấn.

Trước khi khảo sát đoạn code trong chương trình main, chúng ta sẽ tìm hiểu chương trình con checkpad(). Ở dòng 31 trong chương trình con checkpad, chúng ta khai báo 3 biến phụ 8 bit không dấu, i là biến đại diện cho cột của keypad và j là hàng, keyin là giá trị đọc về từ các chân A, B, C, D. Vòng lặp for 4 lần trong dòng 32 của biến i chính là 4 bước quét mà tôi đã trình bày trong ví dụ trên. Ở bước 1, biến i=0, nếu chúng ta dịch trái số 1 như (1<<(4+i)) thì giá trị thu được là (1<<(4+i))=0b00010000, kết hợp với dòng code 33: KEYPAD_PORT=0xFF-(1<<(4+i)); chúng ta thu được KEYPAD_PORT=0xEF. Số 4 trong phép dịch xuất hiện vì các cột của Keypad được nối với 4 bit cao của PORT trên AVR. Tóm lại, sau bước đầu tiên cột thứ nhất của Keypad được kéo xuống mức 0, sẵn sàng cho quá trình kiểm tra các hàng A,B,C,D trong các dòng tiếp theo. Dòng 35 đọc giá trị từ Keypad về biến keyin, vì chúng ta kết nối các chân A,B,C,D của Keypad với 4 bit thấp của PORT nên chúng ta chỉ quan tâm đến giá trị của 4 bit thấp này, việc AND (&) giá trị đọc về với 0x0F cho phép chúng ta bỏ qua 4 bit cao. Trong dòng 36, chúng ta kiểm tra xem nếu giá trị đọc về khác 0x0F thì thực hiện các dòng tiếp theo. Nếu keyin =0x0F nghĩa là không có bất kỳ nút nào trên cột 1 được nhấn, các dòng tiếp theo không thực hiện, vòng lặp for cho biến i được tiếp tục giá trị tiếp theo. Nếu biến keyin khác 0x0F thì chúng ta biết rằng có 1 nút nào đó trên cột i được nhấn, các dòng tiếp theo sẽ xác định chính xác nút nào được nhấn. Dòng 37 cho biến hàng j chạy từ 0 đến 4, dòng 38 kiểm tra giá trị keyin, nếu keyin bằng phần tử thứ j trong mảng scan_code mà chúng ta đã định nghĩa trước đó thì nút trên hàng j đã được nhấn, tóm lại nút được nhấn là nút hàng j và cột i, chúng ta trả về giá trị mã ascii của nút này bằng cách lấy giá trị tương ứng của mảng ascii_code được định nghĩa trước đó: return ascii_code[j][i]. Nếu quá trình quét thất bại chúng ta trả về giá trị 0.

Nội dung của chương trình chính là khởi động chip và thực hiện demo quá trình đọc Keypad, Dòng 19 chúng ta khai báo sử dụng 4 bit thấp của KEYPAD_PORT làm input (các chân A,B,C,D là input) và 4 bit cao làm output. Dòng 18 khởi động các điện trở kéo lên cho 4 bit thấp. Hai dòng 21 và 22 khởi động và xóa Text LCD. Trong vòng lặp vô tận while(1), chúng ta quét keypad ở dòng 24 và hiển thị lên LCD ở dòng 25 (chỉ hiển thị nếu quá trình quét thành công).

Trong ví dụ này tôi chỉ trình bày giải thuật quét Keypad cơ bản, vẫn còn một số vấn đề khác như kiểm tra sự kiện nhấn (key down), thả (key up)...bạn đọc hãy tự giải tuyết theo cách của riêng mình.

Page 15: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 15

Text LCD

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ụ

• Cấu trúc AVR. • WinAVR. • C cho AVR. • Mô phỏng với Proteus.

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.

Page 16: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 16

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.

Page 17: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 17

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.

Page 18: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 18

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

Page 19: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 19

CGRAM (Character Generator RAM). Để điều khiển hiển thị Text LCD chúng ta cần hiểu tổ chức và cách thức hoạt động của các bộ nhớ này:

2.1 DDRAM.

DDRAM là bộ nhớ tạm chứa các ký tự cần hiển thị lên LCD, bộ nhớ này gồm có 80 ô được chia thành 2 hàng, mỗi ô có độ rộng 8 bit và được đánh số từ 0 đến 39 cho dòng 1; từ 64 đến 103 cho dòng 2. Mỗi ô nhớ tương ứng với 1 ô trên màn hình LCD. Như chúng ta biết LCD loại 16x2 có thể hiển thị tối đa 32 ký tự (có 32 ô hiển thị), vì thế có một số ô nhớ của DDRAM không được sử dụng làm các ô hiển thị. Để hiểu rõ hơn chúng ta tham khảo hình 3 bên dưới

Hình 3. Tổ chức của DDRAM.

Chỉ có 16 ô nhớ có địa chỉ từ 0 đến 15 và 16 ô địa chỉ từ 64 đến 79 là được hiển thị trên LCD. Vì thế muốn hiển thị một ký tự nào đó trên LCD chúng ta cần viết ký tự đó vào DDRAM ở 1 trong 32 địa chỉ trên. Các ký tự nằm ngoài 32 ô nhớ trên sẽ không được hiển thị, tuy nhiên vẫn không bị mất đi, chúng có thể được dùng cho các mục đích khác nếu cần thiết.

2.2 CGROM.

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

Page 20: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 20

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

Page 21: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 21

định nghĩa trước và được gọi hiển thị khi cần thiết. Vùng này có tất cả 64 ô nhớ nên có tối đa 8 symbol có thể được định nghĩa. Tài liệu này không đề cập đến sử dụng bộ nhớ CGRAM nên tôi sẽ không đi chi tiết phần này, bạn có thể tham khảo datasheet của HD44780U để biết thêm.

3. Điều khiển hiển thị Text LCD.

3.1 Các chân điều khiển LCD.

Các chân điều khiển việc đọc và ghi LCD bao gồm RS, R/W và EN. RS (chân số 3): Chân lựa chọn thanh ghi (Select Register), chân này cho phép lựa chọn 1 trong 2 thanh ghi IR hoặc DR để làm việc. Vì cả 2 thanh ghi này đều được kết nối với các chân Data của LCD nên cần 1 bit để lựa chọn giữa chúng. Nếu RS=0, thanh ghi IR được chọn và nếu RS=1 thanh ghi DR được chọn. Chúng ta đều biết thanh ghi IR là thanh ghi chứa mã lệnh cho LCD, vì thế nếu muốn gởi 1 mã lệnh đến LCD thì chân RS phải được reset về 0. Ngược lại, khi muốn ghi mã ASCII của ký tự cần hiển thị lên LCD thì chúng ta sẽ set RS=1 để chọn thanh ghi DR. Hoạt động của chân RS được mô tả trong hình 5.

Hình 5. Hoạt động của chân RS.

R/W (chân số 4): Chân lựa chọn giữa việc đọc và ghi. Nếu R/W=0 thì dữ liệu sẽ được ghi từ bộ điều khiển ngoài (vi điều khiển AVR chẳng hạn) vào LCD. Nếu R/W=1 thì dữ liệu sẽ được đọc từ LCD ra ngoài. Tuy nhiên, chỉ có duy nhất 1 trường hợp mà dữ liệu có thể đọc từ LCD ra, đó là đọc trạng thái LCD để biết LCD có đang bận hay không (cờ Busy Flag - BF). Do LCD là một thiết bị hoạt động tương đối chậm (so với vi điều khiển), vì thế một cờ BF được dùng để báo LCD đang bận, nếu BF=1 thì chúng ta phải chờ cho LCD xử lí xong nhiệm vụ hiện tại, đến khi nào BF=0 một thao tác mới sẽ được gán cho LCD. Vì thế, khi làm việc với Text LCD chúng ta nhất thiết phải có một chương trình con tạm gọi là wait_LCD để chờ cho đến khi LCD rảnh. Có 2 cách để viết chương trình wait_LCD. Cách 1 là đọc bit BF về kiểm tra và chờ BF=0, cách này đòi hỏi lệnh đọc từ LCD về bộ điều khiển ngoài, do đó chân R/W cần được nối với bộ điều khiển ngoài. Cách 2 là viết một hàm delay một khoảng thời gian cố định nào đó (tốt nhất là trên 1ms). Ưu điểm của cách 2 là sự đơn giản vì không cần đọc LCD, do đó chân R/W không cần sử dụng và luôn được nối với GND. Tuy

Page 22: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 22

nhiên, nhược điểm của cách 2 là khoảng thời gian delay cố định nếu quá lớn sẽ làm chậm quá trình thao tác LCD, nếu quá nhỏ sẽ gây ra lỗi hiển thị. Trong bài này tôi hướng dẫn bạn cách tổng quát là cách 1, để sử dụng cách 2 bạn chỉ cần một thay đổi nhỏ trong chương trình wait_LCD (sẽ trình bày chi tiết sau) và kết nối chân R/W của LCD xuống GND. EN (chân số 5): Chân cho phép LCD hoạt động (Enable), chân này cần được kết nối với bộ điều khiển để cho phép thao tác LCD. Để đọc và ghi data từ LCD chúng ta cần tạo một “xung cạnh xuống” trên chân EN, nói theo cách khác, muốn ghi dữ liệu vào LCD trước hết cần đảm bảo rằng chân EN=0, tiếp đến xuất dữ liệu đến các chân D0:7, sau đó set chân EN lên 1 và cuối cùng là xóa EN về 0 để tạo 1 xung cạnh xuống.

3.2 Tập lệnh của LCD.

Bảng 2 tóm tắt các lệnh có thể ghi vào LCD

Danh sách lệnh trên được tôi tô 2 màu khác nhau, các lệnh màu đỏ sẽ được dùng thường xuyên trong lúc hiển thị LCD và các lệnh màu xanh thường chỉ được dùng 1 lần trong lúc khởi động LCD, riêng lệnh Read BF có thể được dùng hoặc không tùy

Page 23: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 23

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

Page 24: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 24

+ 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

Page 25: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 25

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.

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

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.

Page 26: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 26

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 */

Page 27: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 27

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

Page 28: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 28

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

Page 29: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 29

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:

Page 30: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 30

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

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

Page 31: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 31

23 24 25 26 27 28 29 30

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

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

Page 32: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 32

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

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.

Page 33: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 33

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

Page 34: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 34

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.

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

Page 35: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 35

(keyword: LM016L), 1 chip Atmega32 và 1 biến trở (POT-LIN) như trong hình 8. Tạo 1 Project bằng WinAVR có tên là TextLCD_Demo và tạo file source là main.c, tạo makefile với khai báo sữ dụng chip ATmega32 và clock 8MHz. Copy file myLCD.h vào thư mục của Project mới tạo. Viết code cho file main.c như trong list 9. Chú ý các định nghĩa chân kết nối với LCD trong phần đầu file myLCD.h phải giống với kết nối thật trong hình 8.

Hình 8. Mạch điện mô phỏng LCD với AVR.

List 8. Chương trình demo điều khiển TextLCD, main.c.

#include <avr/io.h> #include <util/delay.h> #include "myLCD.h" //include thư viện myLCD

Page 36: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 36

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.

Page 37: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 37

Graphic LCD

Nội dung Các bài cần tham khảo trước

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

2. Graphic LCD.

3. AVR và Graphic LCD.

4. Ví dụ điều khiển Graphic LCD bằng thư

viện myGLCD.

Download ví dụ

Download phần mềm G.Edit

Cấu trúc AVR.

WinAVR.

C cho AVR.

Mô phỏng với Proteus.

Giới thiệu phần mềm G.Edit

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

Trong bài ứng dụng này tôi trình bày về cấu trúc và cách điều khiển Graphic LCD loại dot không màu. Công cụ chính cũng là 2 bộ phần mềm quen thuộc WinAVR, Proteus và phần mềm biên tập Graphic LCD, G.Edit. 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 Graphic LCD 128x64 và chip điều khiển KS0108. - Nguyên lý hoạt động Graphic LCD. - Phát triển 1 thư viện điều khiển Graphic LCD 128x64 cho AVR. - Ví dụ điều khiển Graphic LCD 128x64 bằng AVR.

II. Graphic LCD.

Graphic LCD (gọi tắt là GLCD) loại chấm không màu là các loại màn hình tinh thể lỏng nhỏ dùng để hiển thị chữ, số hoặc hình ảnh. Khác với Text LCD, GLCD không được chia thành các ô để hiển thị các mã ASCII vì GLCD không có bộ nhớ CGRAM (Character Generation RAM). GLCD 128x64 có 128 cột và 64 hàng tương ứng có 128x64=8192 chấm (dot). Mỗi chấm tương ứng với 1 bit dữ liệu, và như thế cần 8192 bits hay 1024 bytes RAM để chứa dữ liệu hiển thị đầy mỗi 128x64 GLCD. Tùy theo loại chip điều khiển, nguyên lý hoạt động của GLCD có thể khác nhau, trong bài này tôi giới thiệu loại GLCD được điều khiển bởi chip KS0108 của Samsung, có thể nói GLCD với KS0108 là phổ biến nhất trong các loại GLCD loại này (chấm, không màu). Hình 1 là hình ảnh thật của 1 GLCD 128x64 điều khiển bởi KS0108.

Page 38: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 38

Hình 1. Graphic LCD 128x64.

Chip KS0108 chỉ có 512 bytes RAM (4096 bits = 64x64) và vì thế chỉ điều khiển hiển thị được 64 dòng x 64 cột. Để điều khiển GLCD 168x64 cần 2 chip KS0108, và thực thế trong các loại GLCD có 2 chip KS0108, GLCD 128x64 do đó tương tự 2 GLCD 64x64 ghép lại. Chúng ta sẽ lần lượt khảo sát sơ đồ chân, cấu trúc bộ nhớ và nguyên lý hoạt động của GLCD, chip KS0108 trong phần tiếp theo.

1. Sơ đồ chân GLCD 128x64.

Các GLCD 128x64 dùng KS0108 thường có 20 chân trong đó chỉ có 18 chân là thực sự điều khiển trực tiếp GLCD, 2 chân (thường là 2 chân cuối 19 và 20) là 2 chân Anode và Cathode của LED nền. Trong 18 chân còn lại, có 4 chân cung cấp nguồn và 14 chân điều khiển+dữ liệu. Khác với các Text LCD HD44780U, GLCD KS0108 không hỗ trợ chế độ giao tiếp 4 bit, do đó bạn cần dành ra 14 chân để điều khiển 1 GLCD 128x64. Sơ đồ chân phổ biến của GLCD 128x64 được mô tả trong bảng 1. Bảng 1. Sơ đồ chân GLCD GDM-12864-04.

Page 39: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 39

Chú ý là trên một số GLCD, thứ tự các chân có thể khác (như GLCD WG12864A2…) nhưng số lượng và chức năng chân thì không đổi. Hình 2 mô tả cách kết nối GLCD với nguồn và mạch điều khiển.

Page 40: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 40

Hình 2. Kết nối GLCD.

Chân VSS được nối trực tiếp với GND, chân VDD nối với nguồn +5V, một biến trở khoảng 20K được dùng để chia điện áp giửa Vdd và Vee cho chân Vo, bằng cách thay đổi giá trị biến trở chúng ta có thể điều chỉnh độ tương phản của GLCD. 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. Riêng chân Reset (RST) có thể nối trực tiếp với nguồn 5V.

EN (Enable): cho phép một quá trình bắt đầu, bình thường chân EN được giữ ở mức thấp, khi một thực hiện một quá trình nào đó (đọc hoặc ghi GLCD), các chân điều khiển khác sẽ được cài đặt sẵn sàng, sau đó kích chân EN lên mức cao. Khi EN được kéo lên cao, GLCD bắt đầu làm thực hiện quá trình được yêu cầu, chúng ta cần chờ một khoảng thời gian ngắn cho GLCD đọc hoặc gởi dữ liệu. Cuối cùng là kéo EN xuống mức thấp để kết thúc quá trình và cũng để chuẩn bị chân EN cho quá trình sau này.

RS (Register Select): là chân lựa chọn giữa dữ liệu (Data) và lệnh (Instruction), vì thế mà trong một số tài liệu bạn có thể thấy chân RS được gọi là chân DI (Data/Instruction Select). Chân RS=1 báo rằng tín hiệu trên các đường DATA (D0:7) là dữ liệu ghi hoặc đọc từ RAM của GLCD. Khi RS=0, tín hiệu trên đương DATA là một mã lệnh (Instruction).

RW (Read/Write Select): chọn lựa giữa việc đọc và ghi. Khi RW=1, chiều truy cập từ GLCD ra ngoài (GLCD->AVR). RW=0 cho phép ghi vào GLCD. Giao tiếp với GLCD chủ yếu là quá trình ghi (AVR ->GLCD), chỉ duy nhất trường hợp đọc dữ liệu từ GLCD là đọc bit BUSY và đọc dữ liệu từ RAM. Đọc bit BUSY thì chúng ta đã khảo sát cho Text LCD, bit này báo GLCD có đang bận hay không, việc đọc này sẽ được dùng để viết hàm wait_GLCD. Đọc dữ liệu từ RAM của GLCD là một khả năng mới mà Text LCD không có, bằng việc đọc ngược từ GLCD vào AVR, chúng ta có thể thực hiện nhiều phép logic hình (hay mặt nạ, mask) làm cho việc hiển thị GLCD thêm thú vị.

Page 41: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 41

CS2 và CS1 (Chip Select): như tôi đã trình bày trong phần trên, mỗi chip KS0108 chỉ có khả năng điều khiển một GLCD có kích thước 64x64, trên các GLCD 128x64 có 2 chip KS0108 làm việc cùng nhau, mỗi chip đảm nhiệm một nữa LCD, 2 chân CS2 và CS1 cho phép chọn một chip KS0108 để làm việc. Thông thường nếu CS2=0, CS1=1 thì nửa trái được kích hoạt, ngược lại khi CS2=1, CS1=0 thì nửa phải được chọn. Chúng ta sẽ hiểu rõ hơn cách phối hợp làm việc của 2 nửa GLCD trong phần khảo sát bộ nhớ của LCD.

2. Tổ chức bộ nhớ.

Chip KS0108 có một loại bộ nhớ duy nhất đó là RAM, không có bộ nhớ chứa bộ font hay chứa mã font tự tạo như chip HD44780U của Text LCD. Vì vậy, dữ liệu ghi vào RAM sẽ được hiển thị trực tiếp trên GLCD. Mỗi chip KS0108 có 512 bytes RAM tương ứng với 4096 chấm trên một nửa (64x64) LCD. RAM của KS0108 không cho phép truy cập từng bit mà theo từng byte, điều này có nghĩa là mỗi lần chúng ta viết một giá trị vào một byte nào đó trên RAM của GLCD, sẽ có 8 chấm bị tác động, 8 chấm này nằm trên cùng 1 cột. Vì lý do này, 64 dòng GLCD thường được chia thành 8 pages, mỗi page có độ cao 8 bit và rộng 128 cột (cả 2 chip gộp lại). Hình 3 mô tả “bề mặt” một GLCD và cũng là cách sắp xếp RAM của các chip KS0108.

Hình 3. Tổ chức của RAM.

Tổ chức RAM của 2 chip KS0108 trái và phải hoàn toàn tương tự, việc đọc hay ghi vào RAM của 2 chip cũng được thực hiện như nhau. Chúng ta sẽ chọn nửa trái GLCD để khảo sát. Như bạn thấy trên hình 3, 64 dòng từ trên xuống dưới được chia thành 8 “dãy” mà ta gọi là 8 pages. Page trên cùng là page 0 và page dưới cùng la page 7. Trong các GLCD, page còn được gọi là địa chỉ X (X address), hay nói cách

Page 42: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 42

khác X=0 là địa chỉ của page trên cùng, tương tự như thế, X=7 là địa chỉ của page dưới cùng. Mỗi page chứa 64 cột (chỉ xét 1 chip KS0108), mỗi cột là một byte RAM 8 bit, mỗi bit tương ứng với 1 chấm trên LCD, bit có trọng số thấp (LBS - tức bit D0 như trong hình 3) tương ứng với chấm trên cao nhất. Bit có trọng số cao nhất (MBS - tức bit D7 như trong hình 3) tương ứng với chấm thấp nhất trong 1 page. Thứ tự các cột trong 1 page gọi là địa chỉ Y (Y address), như thế cột đầu tiên có địa chỉ Y = 0 trong khi cột cuối cùng có địa chỉ Y là 63. Bằng cách phối hợp địa chỉ X và địa chỉ Y chúng ta xác định được vị trí của byte cần đọc hoặc ghi. Chip KS0108, tất nhiên, sẽ hỗ trợ các lệnh di chuyển đến địa chỉ X và Y để ghi hay đọc RAM. Hãy quan sát hình 4 để xem cách mà một chữ cái ‘a’ được hiển thị trên GLCD.

Hình 4. Hiển thị chữ cái ‘a’ trên GLCD.

Trong cách hiển thị ở hình 4, chữ ‘a’ chỉ nằm trong page 0, tức X=0. Muốn hiển thị chữ cái ‘a’ chúng ta cần ghi vào các cột (địa chỉ Y) của page 0 lần lượt các giá trị như sau: 0, 228, 146, 74, 252 và 128…., xem bảng bên dưới.

Page 43: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 43

3. Tập lệnh cho chip KS0108.

Bảng 2 tóm tắt các lệnh của chip KS0108.

So với HD44780U của Text LCD, lệnh cho KS0108 của GLCD đơn giản và ít hơn và vì thế viết chương trình điều khiển GLCD cũng tương đối dễ hơn Text LCD. Có tất cả 7 lệnh (Instruction) có thể giao tiếp với KS0108. Tôi sẽ lần lượt giải thích ý nghĩa và cách sử dụng của từng lệnh.

- Display ON/OFF – Hiển thị GLCD: lệnh này cho phép GLCD hiển thị nội dung trên RAM ra “bề mặt” GLCD. Để viết lệnh này cho GLCD, 2 chân RS và RW cần

Page 44: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 44

được kéo xuống mức thấp (RS=0: đây là Instrucion, RW=0: AVR->GLCD). Mã lệnh (code) được chứa trong 7 bit cao (D7:1) và bit D0 chứa thông số. Quan sát bảng 2, dễ thấy mã lệnh nhị phân cho Display ON/OFF là 0011111x (0x3E+x) trong đó x=1: cho phép GLCD hiển thị, x=0: tắt hiển thị.

- Set Address – chọn địa chỉ: đúng hơn đây là lệnh chọn cột hay chọn địa chỉ Y. Hai bit D7 và D6 chứa mã lệnh (01000000=0x40=64) và 6 bit còn lại chứa chỉ số của cột muốn di chuyển đến. Chú ý là mỗi nửa GLCD có 64 cột nên cần 6 bit để chứa chỉ số này (26=64). Vậy lệnh này có dạng 0x40+Y. Ví dụ nếu chúng ta muốn di chuyển đến cột 36 chúng ta ghi vào GLCD mã lệnh: 0x40+36. Hai chân RS và RW được giữ ở mức thấp khi thực hiện lệnh này.

- Set Page – chọn trang: lệnh cho phép chọn page (hay địa chỉ X) cần di chuyển đến, do GLCD chỉ có 8 pages nên chỉ cần 3 bit để chứa địa chỉ page. Mã lệnh cho lệnh này có dạng 0xB8+X. Trong đó biến X là chỉ số page cần di chuyển đến. Hai chân RS và RW được giữ ở mức thấp khi thực hiện lệnh này.

- Display Start Line – chọn line đầu tiên: hay còn gọi là lệnh “cuộn”, lệnh này cho phép di chuyển toàn bộ hình ảnh trên GLCD (hay RAM) lên phía trên một số dòng nào đó, chúng ta gọi là LOffset. Số lượng LOffset có thể từ 0 đến 63 nên cần 6 bit chứa giá trị này. Mã lệnh Display Start Line có dạng 0xC0+LOffset. Hai chân RS và RW được giữ ở mức thấp khi thực hiện lệnh này. Khi di chuyển GLCD lên phía trên, phần dữ liệu phía trên bị che khuất sẽ “cuộn” xuống phía dưới. Hình 5 là một ví dụ “cuộn” GLCD lên 20 dòng.

- Status Read – đọc trạng thái GLCD: đây là một trong 2 lệnh đọc từ GLCD. Cũng giống như với Text LCD, lệnh đọc trạng thái GLCD chủ yếu để xét bit BUSY (bit thứ 7) xem GLCD có đang bận hay không, lệnh này sẽ được dùng để viết một hàm wait_GLCD chờ cho đến khi GLCD rảnh. Vì đây là lệnh đọc từ GLCD nên chân RW phải được set lên mức 1 trước khi thực hiện, chân RS vẫn ở mức thấp (đọc Instruction).

- Write Display Data – ghi dữ liệu cần hiển thị vào GLCD hay RAM: vì đây là 1 lệnh ghi dữ liệu hiển thị nên chân RS cần được set lên 1 trước khi thực hiện, chân RW giữ ở mức 0. Lệnh này cho phép ghi một byte dữ liệu vào RAM của KS0108 và cũng là dữ liệu sẽ hiển thị lên GLCD tại vị trí hiện hành của 2 con trỏ địa chỉ X và Y. 8 bit dữ liệu này sẽ tương ứng với 8 chấm trên cột Y ở page X. Chú ý là sau lệnh Write

Page 45: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 45

Display Data, địa chỉ cột Y tự động được tăng lên 1 và vì thế nếu có một dữ liệu mới được ghi, dữ liệu mới sẽ không “đè” lên dữ liệu cũ. Việc tăng tự động địa chỉ Y rất có lợi cho việc ghi dữ liệu liên tiếp, nó giúp giảm thời gian set lại địa chỉ cột Y. Sau khi thực hiện ghi ở cột Y=63 (cột cuối cùng trong 1 page, đối với 1 chip KS0108), Ysẽ về 0.

- Read Display Data – đọc dữ liệu hiển thị từ GLCD (cũng là dữ liệu từ RAM của KS0108): lệnh đọc này mới so với Text LCD, nó cho phép chúng ta đọc ngược 1 byte dữ liệu từ RAM của KS0108 tại vị trí hiện hành về AVR. Sau khi đã đọc được giá trị tại vị trí hiện hành, chúng ta có thể thực hiện các phép Logic như đảo bit, or hay and…làm tăng khả năng thao tác hình ảnh. Trước khi thực hiện đọc chúng ta cần di chuyển đến vị trí muốn đọc bằng 2 lệnh set địa chỉ X và Y, sau khi đọc giá trị địa chỉ page X và cột Y không thay đổi, do đó nếu đọc tiếp mà không di chuyển địa chỉ thì vẫn thu được giá trị cũ.

III. AVR và Graphic LCD.

1. Trình tự giao tiếp GLCD.

So với Text LCD thì việc giao tiếp với GLCD dễ hơn nhiều vì GLCD có ít Instruction hơn, GLCD chỉ có một loại bộ nhớ là RAM tương ứng trực tiếp với màn hình hiển thị, GLCD không có cursor nên không cần set cursor, GLCD chỉ hỗ trợ giao tiếp 8 bit nên không cần bận tâm chọn mode, quá trình khởi động cho GLCD vì thể rất đơn giản bằng cách gọi lênh DISPLAY ON/OFF. Trong hình 5 tôi trình bày quá trình khởi động và sử dụng GLCD.

Hình 5. Trình tự giao tiếp với GLCD.

Sau khi khởi động GLCD bằng hàm DISPLAY ON chúng ta có thể set địa chỉ X và Y để ghi dữ liệu, thậm chí có thể ghi dữ liệu mà không cần set X, Y. Tuy nhiên, cần

Page 46: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 46

nhắc lại là có đến 2 chip KS0108 trên GLCD 128x64, vì vậy tất cả các quá trình đều phải thực hiện cho 2 chip.

2. AVR giao tiếp với GLCD trong WinAVR.

Phần này tôi trình bày cách điều khiển hiển thị GLCD 128x64 bằng vi điều khiển AVR trong môi trường C của WinAVR. Hình thức là một thư viện hàm giao tiếp GLCD trong 1 file header có tên là myGLCD.h. Các hàm trong thư viện bao gồm (chú ý là phần code trong List 0 không nằm trong file myGLCD.h):

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

Trước khi viết các hàm giao tiếp LCD chúng ta cần định nghĩa một số macro và biến. Hãy tạo 1 file Header có tên myGLCD.h và viết các đoạn code bên dưới vào file này (bắt đầu từ List 1).

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

Page 47: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 47

Do GLCD không hỗ trợ bộ font, nếu muốn hiển thị các ký tự chúng ta cần định nghĩa chúng trong một bảng font (tương tự trường hợp ma trận LED), file font.h đã được tạo trước và include vào thư viện myGLCD (dòng 2). Đồng thời, bộ font sẽ được chứa trong bộ nhớ chương trình (FLASH) nên cần các hàm hỗ trợ đọc FLASH, chúng ta include file pgmspace.h phục vụ cho việc này (dòng 3).

cbi và sbi là 2 macro được dụng để xóa và set 1 bit trong 1 thanh ghi. Ví dụ cbi(PORTA, 5) là xóa bit 5 trong thanh ghi PORTA về 0. Do WinAVR không hỗ trợ tuy xuất trực tiếp các bit nên cần định nghĩa 2 macro này hỗ trợ (dòng 5, 6).

Tám đường DATA sẽ được dành cho 1 PORT, các dòng 8, 9 và 10 định nghĩa PORT trên AVR dành cho DATA, trong ví dụ này là PORTB. Tương tự các đường điều khiển cũng nằm trên cùng 1 PORT, các dòng 14, 15, 16 định nghĩa PORT dành cho các đường điều khiển (PORTD chẳng hạn), sau đó chúng ta định nghĩa thứ tự

Page 48: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 48

chân trên PORT điều khiển kết nối với các chân EN, RW, RS, CS1 và CS2 của GLCD (xem các dòng từ 18 đến 22). Chúng ta định nghĩa tiếp 2 macro để kích hoạt và stop GLCD ở các dòng 25 và 26 vì các hoạt động này được dùng rất nhiều khi giao tiếp với GLCD.

Tiếp theo chúng ta định nghĩa 4 mã lệnh (Instruction code) của 4 hàm Display on/off, Set Address, Set page và Display Start Line mà tôi đã trình bày ở trên (các dòng từ 29 đến 32). Cuối cùng là định nghĩa vị trí bit BUSY khi đọc trạng thái GLCD.

Sau phần định nghĩa chúng ta sẽ bắt đầu viết code truy cập GLCD, đoạn code trình bày trong List 2 chứa các hàm hỗ trợ.

List 2. Các hàm hỗ trợ.

Hàm GLCD_Delay() thực hiện delay khoảng 16 chu kỳ máy, hàm này được dùng để chờ LGCD đọc hay ghi dữ liệu sau khi chân EN được kích. Một nét mới ở đây là tôi sử dụng ngôn ngữ ASM chèn vào C, dù chỉ là chèn hàm nop nhưng nó nói cho bạn

Page 49: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 49

biết rằng avr-gcc cho phép chúng ta chèn ASM, tôi sẽ trình bày chi tiết các ví dụ chèn ASM phức tạp hơn trong một bài khác.

Hàm GLCD_OUT_Set() ở dòng 5 set các PORT giao tiếp trên AVR (DATA và Control) có hướng Ouput. Hàm GLCD_IN_Set() ở dòng 12 set các PORT giao tiếp có hướng Input (dùng khi đọc từ GLCD -> AVR). Hàm GLCD_SetSide(char Side) ở dòng 19 chọn chip KS0108 trái hoặc phải để thao tác, trong đó Side=1 thì một nửa GLCD bên phải được chọn bằng cách reset bit CS1=0 và CS2=1 (các dòng 22, 23), ngược lại nửa bên trái được kích hoạt, CS1=1, CS2=0 (dòng 26 và 27).

List 3 trình bày phần code cho 4 hàm truy cập Instruction GLCD cơ bản viết lại cho các hàm Status Read, Display On/Off, Set Address, Set page và Display Start Line trích từ bảng 2.

List 3. Các hàm truy cập Instruction.

Page 50: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 50

Tất cả các hàm trong List 3 đều truy cập Instruction nên chân RS luôn được kéo xuống mức thấp, trong 5 hàm trên, hàm wait_GLCD sử dụng Instruction đọc trạng thái từ GLCD nên chân RW sẽ được kéo lên cao, trong 4 hàm còn lại chân RW ở mức thấp.

Page 51: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 51

Hàm wait_GLCD(void) , cũng tương tự như hàm wait_LCD() trong trường hợp Text LCD, hàm này chờ GLCD rảnh bằng cách đọc trạng thái GLCD và kiểm tra bit BUSY, nếu BUSY bằng 1 thì GLCD đang bận, BUSY=0 tức GLCD rảnh. Các dòng 4, 5, 6 chuẩn bị các đường DATA, RS, RW cho quá trình đọc Instruction từ GLCD (RS=0, RW=0), ở dòng 8 chân EN được kéo lên cao bằng macro GLCD_ENABLE (định nghĩa trong list 1). Nhắc lại chức năng của chân EN, khi EN=1 GLCD bắt đầu quá trình giao tiếp do các chân RS, RW xác lập (trong trường hợp này là đọc Instruction từ GLCD), chúng ta cần chờ một khoảng thời gian ngắn cho GLCD đẩy thanh ghi trạng thái ra các đường DATA bằng hàm GLCD_Delay() trong dòng 9. Tiếp theo gọi GLCD_DISABLE để kéo chân EN xuống mức 0 để kết thúc quá trình đọc (một xung đã được tạo trên chân EN), và bắt đầu kiểm tra bit BUSY. Dòng 12 là một vòng lặp while kiểm tra xem nếu bit BUSY trong giá trị đọc về (giá trị đọc về chứa trong thanh ghi PIN của PORT DATA trên AVR), nếu BUSY=1 (bit_is_set…) vòng lặp tiếp tục với việc tạo một xung khác trên chân EN (các dòng ) rồi quay lại kiểm tra bit BUSY. Nếu BUSY bằng 0, GLCD đã rảnh, vòng lặp while được giải thoát, quá trình chờ kết thúc.

Hàm GLCD_SetDISPLAY(uint8_t ON) cho phép GLCD hiển thị khi tham số ON=1, hoặc tắt khi tham số ON=0. Trước khi set GLCD chúng ta cần chờ cho GLCD rảnh bằng cách gọi hàm wait_GLCD() ở dòng 20, sau đó xác lập các chân RS, RW sẵn sàng cho quá trình gởi mã lệnh vào GLCD (dòng 21, 22 và 23). Trước khi kích hoạt quá trình, cần chuẩn bị mã lệnh sẵn sàng trên đường dữ liệu, dòng 25: GLCD_DATA_O=GLCD_DISPLAY+ON , trong đó GLCD_DISPLAY là mã lệnh của hàm Display On/Off được định nghĩa trong List 1, biến ON báo GLCD tắt hay mở. Sau khi mọi thứ đã sẵn sàng, một xung được tạo ra trên chân EN (các dòng từ 26 đến 28). Quá trình set Display thực hiện và kết thúc.

Hàm void GLCD_SetYADDRESS(uint8_t Col) là hàm viết lại cho Insrtuction chọn địa chỉ Y (cột) cần thao tác, tham số Col trong hàm này chính là chỉ số cột, Col có giá trị từ 0 đến 63. Nội dung hàm này hoàn toàn giống hàm GLCD_SetDISPLAY, chỉ có một điểm khác duy nhất là mã hàm khác, mã GLCD_YADDRESS được dùng (xem dòng 36: GLCD_DATA_O = GLCD_YADDRESS+Col).

Hàm void GLCD_SetXADDRESS(uint8_t Line) là hàm viết lại cho Insrtuction chọn địa chỉ X (page) cần thao tác, tham số Line trong hàm này chính là chỉ số page, Line có giá trị từ 0 đến 8. Nội dung hàm này hoàn toàn giống hàm GLCD_SetXADDRESS, nhưng mã GLCD_XADDRESS được dùng thay cho GLCD_YADDRESS,(xem dòng 36: GLCD_DATA_O = GLCD_XADDRESS+Line).

Hàm void GLCD_StartLine(uint8_t Offset) là hàm viết lại cho Insrtuction “cuộn” GLCD, chỉ số Offset là giá trị “cuộn” lên. Xem lại ví dụ hình cuộn GLCD trong phần giải thích của lệnh Display Start Line, với trường hợp này hàm GLCD_StartLine(20) đã được gọi.

List 4 trình bày 2 hàm viết và đọc dữ liệu hiển thị lên GLCD.

Page 52: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 52

List 4. Các hàm thao tác dữ liệu.

Hai hàm trong list 4 thao tác dữ liệu hiển thị trên GLCD nên chân RS phải được set bằng 1.

Hàm GLCD_WriteDATA(uint8_t DATA) ghi một byte vào RAM của KS0108, byte này cũng sẽ được hiển thị lên GLCD, vị trí ghi vào là vị trí hiện hành của con trỏ X và Y (ảnh hưởng bởi các quá trình ghi trước đó hoặc do các hàm set địa chỉ), tham số DATA là byte cần ghi. Nội dung bên trong hàm này cũng giống nhứ các hàm trong list 3. Điểm khác là chân RS được kéo lên để báo đây là quá trình thao tác dữ liệu (dòng 6: sbi(GLCD_CTRL_O, GLCD_RS)). Giá trị gởi đến GLCD chính là tham số DATA như trong dòng 8: GLCD_DATA_O=DATA .

Hàm uint8_t GLCD_ReadDATA(void) đọc giá trị hiển thị trên từ GLCD vào AVR, chân RW cần được set lên 1 để báo quá trình này là đọc (dòng 22:

Page 53: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 53

sbi(GLCD_CTRL_O, GLCD_RW) ). Chân EN được kích lên 1 trước (dòng 24: GLCD_ENABLE ;) và chờ một khoảng thời gian ngắn trước khi đọc giá trị từ các đường DATA vào một biến tạm DATA như trong dòng 26: DATA=GLCD_DATA_I ;. Sau khi trả giá trị về bằng dòng lệnh 30: return DATA, thì quá trịnh đọc kết thúc.

Với các hàm đã tạo chúng ta đã có thể điều khiển để hiển thị GLCD, các chương trình con trong List 5 và List 6 sử dụng các hàm trên để thực hiện một số nhiệm vụ hiển thị cơ bản. Chúng ta gọi là các chương trình con mở rộng.

List 5. Các chương trình con mở rộng.

Page 54: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 54

Hàm void GLCD_Init(void) khởi động GLCD. Trước hết, chúng ta phải chọn chip KS0108 để khởi động, dòng 4: GLCD_SetSide(0) nghĩa là chọn chip KS0108 bên trái tức nửa trái GLCD. Chúng ta khởi động nửa trái GLCD bằng việc cho phép hiển thị (dòng 5: GLCD_SetDISPLAY(1)), di chuyển con trỏ về vị trí đầu tiên trên GLCD với 2 hàm chọn địa chỉ ở các dòng 6 và 7, chọn giá trị cuộn là 0 ở dòng 8: GLCD_StartLine(0) . Sau đó lặp lại quá trình khởi động cho nửa phải của GLCD (xem các dòng từ 10 đến 14).

Hàm void GLCD_GotoXY(uint8_t Line, uint8_t Col) di chuyển con trỏ hiển thị đến địa chỉ X và Y. Tham số Line là địa chỉ X (tức là page, giá trị từ 0 đến 7), tham số Col là địa chỉ Y hay chính là cột. Hàm này cho phép di chuyển trên toàn bộ GLCD, nghĩa là biến Col có khoảng giá trị từ 0 đến 127, vì thế trước hết chúng ta phải xác định vị trí cần duy chuyển đến thuộc nửa nào của GLCD, nếu Col<64 thì vị trí đó thuộc nửa trái, ngược lại nó thuộc về nửa phải. Dòng 19 chúng ta chia Col cho 64 và gán phần nguyên kết quả cho 1 biến tạm tên là Side (Side=Col/64 ), rõ ràng nếu Col<64 thì Side=0, ngược lại Side=1. Biến Side được dùng làm tham số cho hàm GLCD_SetSide(Side) ở dòng 20, với cách thực hiện này chúng ta đã tự động chọn nửa GLCD mà điểm cần di chuyển đến thuộc vào. Do hàm chọn địa chỉ Y (hàm GLCD_SetYADDRESS xét ở trên) chỉ chọn địa chỉ trong phạm vi 1 nửa LCD, nên chúng ta cần cập nhật lại giá trị của cột Col, dòng 21 thực hiện việc này: Col -= 64*Side. Sau dòng 21, giá trị Col được cập nhật lại từ 0 đến 63 và được chọn làm cột khi hàm GLCD_SetYADDRESS(Col) ở dòng 22 được gọi. Cuối cùng là chọn địa chỉ X ở dòng 23: GLCD_SetXADDRESS(Line).

Hàm void GLCD_Clr(void) xóa toàn bộ màn hình GLCD (cả 2 nửa GLCD). Mấu chốt của việc xóa GLCD là viết giá trị 0 vào tất cả các vị trí trong RAM, câu lệnh: GLCD_WriteDATA(0) ở 2 dòng 29 và 33 thực hiện điều này. Quá trình xóa được thực hiện trên từng chip KS0108, có 2 vòng vặp for được dùng là vì thế, chú ý dòng lệnh 28: GLCD_GotoXY(Line,0) đưa con trỏ về cột đầu của page thứ “Line”, nửa trái GLCD. Trong khi đó, dòng lệnh 32: GLCD_GotoXY(Line,64) đưa con trỏ về cột đầu của page thứ “Line”, nửa phải GLCD (cột 64 của GLCD là cột đầu tiên của nửa bên phải).

List 6. Các chương trình con mở rộng (tt)..

Page 55: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 55

Page 56: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 56

Đây là 3 chương trình con cuối cùng trong thư viện myGLCD. Trong đó có 2 hàm in các ký có kích thước 7x8 (7 cột, 8 dòng) được định nghĩa trong bảng font và 1 hàm in toàn bộ màn hình GLCD với một hình kích thước 128x64.

Hàm void GLCD_PutChar78(uint8_t Line, uint8_t Col, uint8_t chr) cho phép in ký tự có mã ascii là biến “chr”, biến “Line” là địa chỉ X (0 đến 7) và biến Col là địa chỉ cột Y (0 đến 127). Phần phức tạp nhất trong chương trình con này là việc xét trường hợp có sự chuyển bên (trái qua phải) khi in. Vì mỗi ký tự được định nghĩa bằng 7 bytes trong bảng font, tương ứng với 7 cột trên GLCD, nếu chúng ta muốn in ký tự tại vị trí 60 trên GLCD, các byte thứ 0 1, 2, 3 nằm ở vị trí cột 60, 61, 62 và 63 của nửa trái trong khi các byte thứ 4, 5 và 6 lại nằm ở các cột 0, 1 và 2 của nửa bên phải. Chúng ta phải nhận ra sự chuyển bên này để chuyển chip KS0108 cần thao tác. Chúng ta chia quá trình in ra 2 trường hợp, trường hợp có sự chuyển bên và trường hợp còn lại không chuyển bên (ký tự nằm trọn bên trái hoặc phải). Cấu trúc If dùng trong dòng 4 kiểm tra xem có sự chuyển bên xảy ra hay không: if ((Col>57) && (Col<64)) , nếu cột Col lớn hơn 57 và nhỏ hơn 63 thì sẽ có một sự chuyển bên xảy ra (vì 1 ký tự chiếm 7 cột trên GLCD). Chia quá trình in thành 2 vòng lặp for, vòng for thứ nhất (dòng 6) in từ vị trí Col đến vị trí cột của nửa trái và vòng lặp for thứ 2 ở dòng 9 in từ cột đầu tiên của nửa GLCD bên phải đến byte cuối cùng của ký tự cần in. Trường hợp ngược lại, không có sự chuyển bên xảy ra, chúng ta in bình thường (xem các dòng từ 12 đến 15). Chú ý là dữ liệu ghi vào GLCD lấy từ bảng font7x8 được định nghĩa trong file font.h, bảng font được viết sẵn trong bộ nhớ FLASH của AVR, việc đọc nội dung FLASH thực hiện bằng hàm pgm_read_byte, bạn xem lại bài điều khiển ma trận LED để hiểu thêm.

Hàm void GLCD_Print78(uint8_t Line, uint8_t Col, char* str) cho phép in một chuỗi ký tự hay 1 câu lên GLCD, hàm này cũng giống hàm in chuỗi mà chúng ta đã thực hiện trong trường hợp của Text LCD (modified code), một điểm khác tôi thêm vào là cho phep xuống dòng nếu câu cần in vượt quá 1 dòng. Các câu lệnh bên trong điều kiện if (dòng 23 đến 27) thực hiện xuống dòng nếu cần thiết. Quá trình in sau đó diễn ra bình thường bằng cách gọi hàm GLCD_PutChar78.

Cuối cùng là hàm void GLCD_PutBMP(char *bmp) thực hiện in một hình có kích thước 128x64 được định nghĩa trước lên toàn bộ màn hình GLCD (in đè). Quá trình in cũng khá đơn giản với việc đọc nội dung hình trong FLASH và gởi đến GLCD. Cần chia thành 2 quá trình in cho 2 nửa trái và phải (2 vòng lặp for trong 2 dòng 38 và 43). Dữ liệu hình được ghi trong FLASH có định kích thước 128x8 pages= 1024 bytes, định dạng là 1 mảng có 1024 phần tử, mỗi phần tử là 1 con số dạng byte, mỗi số tương ứng 8 chấm của 1 cột trong 1 page. Các con số được sắp xếp thành 8 dòng tương ứng 8 pages, mỗi dòng có 128 phần tử tương ứng 128 cột GLCD.

GLCD có khả năng tùy biến hiển thị cao, đó là cơ hội cho bạn thể hiện sự sáng tạo ~, trong thư viện myGLCD tôi chỉ trình bày một số chương trình con cơ bản, phần còn lại thuộc về bạn. Hãy sử dụng các hàm truy xuất trong myGLCD để viết các chương trình con hiển thị khác như vẽ đường thẳng, đường tròn, hàm sine, cosine hay bất kỳ hàm số nào…Hope to hear from you soon.

Page 57: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 57

IV. Ví dụ điều khiển Graphic LCD bằng thư viện myGLCD.

Phần này tôi sẽ minh họa cách sử dụng thư viện myGLCD.h để in trực tiếp dữ liệu lên GLCD, hiển thị các ký tự trong bảng font7x8 và hình ảnh lên GLCD. Sử dụng phần mềm Proteus vẽ một mạch điện gồm 1 GLCD 128x64 (keyword: LGM12641BS1R), 1 chip Atmega32 và 1 biến trở (keyword: POT-LIN) như trong hình 6. Tạo 1 Project bằng WinAVR có tên là myGLCD 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 myGLCD.h và font.h vào thư mục của Project mới tạo. Viết code cho file main.c như trong list 7. Chú ý các định nghĩa chân kết nối với LCD trong phần đầu file myGLCD.h phải giống với kết nối thật trong hình 6.

Hình 6. Mạch điện mô phỏng Graphic LCD với AVR.

List 7. Chương trình demo giao tiếp GLCD.

Page 58: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 58

Page 59: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 59

Để sử dụng thư viện myGLCD, chúng ta cần include file myGLCD.h vào Project như trong dòng 4, #include "myGLCD.h". Hai dòng 9 và 10 thực hiện khởi động và xóa LCD. Tôi thực hiện 4 demo in lên GLCD. Trong các dòng từ 12 đến 17 thực hiện ghi trực tiếp giá trị lên GLCD bằng hàm GLCD_WriteDATA , kết quả là 1 dãy các chấm có độ rộng 8 bit nằm ở page 4 của GLCD (xem hình bên dưới). Chú ý là để in hết cả chiều ngang của GLCD cần thực hiện 2 lần in trên 2 nửa GLCD.

Các dòng lệnh từ 21 đến 27 thực hiện in 97 ký tự trong bảng font7x8 bắt đầu bằng mã ascii 33 (ký tự “!”), bi ến Line là địa chỉ page được khởi tạo bằng 0 khi khai báo trong dòng 7. Biến Col là địa chỉ cột, Col cũng được khởi tạo bằng 0. Dòng 32 in ký tự có mã “i” lên GLCD tại vị trí page=Line, cột=Col. Sau khi một ký tự được in, Col sẽ được tăng lên 8 vị trí (dòng 24), chúng ta dành 8 cột trên GLCD cho một ký tự 7x8 để tránh các ký tự “dính” với nhau. Nếu Col lớn hơn 127 thì một quá trình xuống dòng cần thực hiện, khi đó reset Col về 0 và tăng biến Line thêm 1 (dòng 25). Các ký tự sẽ được in lần lượt trên GLCD với 1 khoảng delay.

Các dòng từ 31 đến 35 mô tả cách dùng hàm GLCD_Print78 để in các chuỗi ký tự hay các câu. Dòng 23 in từ “code” lên GLCD tại vị trí page=4, cột=20. Chú ý hàm sprintf trong dòng 33, đây là một hàm của ngôn ngữ C, hàm này cho phép chuyển một số thành một chuỗi các ký tự, trong ví dụ này tôi thực hiện chuyển số 8205 thành chuỗi “8205”, kết quả chứa trong biến “dis”, biến này là 1 mảng các ký tự hay con trỏ đến mảng các ký tự. Sau đó, dòng 34 in chuỗi “dis” lên GLCD.

Demo cuối cùng là in 1 hình 128x64 lên GLCD bằng hàm GLCD_PutBMP(hiGLCD) và sau đó thực hiện animation (một kiểu hoạt hình) bằng hàm GLCD_StartLine. Dòng 38 in một hình có tên hiGLCD được định nghĩa trước trong file font.h ra GLCD. Các dòng 40 đến 44 cuộn màn hình GLCD lên trên để thực hiện animation. Biến i là biến offset được cho chạy từ 1 đến 63, sau mỗi lần cuộn chúng ta delay một khoảng thời gian ngắn để thấy GLCD “cuộn”.

Hãy tham khảo thêm bài giới thiệu phần mềm G.Edit để biết cách tạo code hình ảnh cho Graphic LCD

Page 60: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 60

Động cơ DC servo

Nội dung Các bài cần tham khảo trước

1. Giới thiệu 2. Incremental Optical Encoder 3. Chip driver L298D 4. Mạch logic cho L298D 5. Giải thuật điều khiển PID 6. Điều khiển DC Motor bằng AVR

Download ví dụ

• Cấu trúc AVR. • WinAVR. • C cho AVR. • Mô phỏng với Proteus.

I. Giới thi ệu

Điều khiển động cơ DC (DC Motor) là một ứng dụng thuộc dạng cơ bản nhất của điều khiển tự động vì DC Motor là cơ cấu chấp hành (actuator) được dùng nhiều nhất trong các hệ thống tự động (ví dụ robot). Điều khiển được DC Motor là bạn đã có thể tự xây dựng được cho mình rất nhiều hệ thống tự động. Khái niệm Servo mà tôi dùng trong bài học này để chỉ một hệ thống hồi tiếp. DC servo motor là động cơ DC có bộ điều khiển hồi tiếp. Bài này là một bài tổng hợp nhiều vấn đề ứng dụng AVR bao gồm nhận dữ liệu từ người dùng, điều khiển motor, đọc encoder, hiển thị LCD, cả giải thuật điều khiển PID và mạch công suất cho Motor…Do đó, bạn phải đọc ít nhất bạn phải nắm được các vấn đề cơ bản như Timer-Counter, TexLCD, mạch cầu H. Phần còn lại tôi sẽ giải thích trong lúc học bài này. Có 2 phương pháp điều khiển động cơ DC là analog và digital. Mục đích chính của chúng ta là dùng AVR điều khiển động cơ DC nên phương pháp số mà cụ thể là phương pháp điều rộng xung (PWM) sẽ được giới thiệu. Ngoài ra, khi nói đến điều khiển động cơ DC có 2 đại lương điều khiển chính là vị trí (số vòng quay) và vận tốc. Trong phần giải thích về bộ điều khiển PID tôi sẽ điều khiển vị trí làm ví dụ, tuy nhiên trong phần ví dụ lập trình cho AVR chúng ta sẽ thực hiện điều khiển vận tốc cho DC Motor. Bằng cách này, bạn có thể tự tin để mở rộng ví dụ để điều khiển cho cả 2 đại lượng. Vì là điều khiển một cách tự động nên chúng ta cần đọc về đại lượng điều khiển (cụ thể là vị trí hoặc vận tốc motor) và hồi tiếp (feedback) về để “hiệu chỉnh” PWM cấp cho động cơ. Chúng ta sẽ dùng incremental optical encoder để đọc số vòng quay và hồi tiếp về cho AVR. Bộ điều khiển PID sẽ được dùng và vận hành bởi AVR. Tổng quát, bài học này bao gồm: - AVR phát PWM điều chỉnh vận tốc động cơ: phần này bạn xem lại bài 4 về Timer-Counter. Điều cơ bản cần nắm là bằng cách thay đổi độ rộng của xung PWM chúng ta sẽ thay đổi được vận tốc Motor. - Xung PWM không trực tiếp làm quay động cơ mà thông qua một mạch công suất gọi là dirver. Driver cho DC Motor chính là mạch cầu H mà chúng ta đã tìm hiểu trong bài “Mạch cầu H”. Trong bài học này, tôi giới thiệu một chip có tích hợp sẵn

Page 61: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 61

mạch cầu H, chip L398D. - Để việc điều khiển chip driver L298D dễ dàng, chúng ta sẽ tạo một mạch logic dùng các cổng NOT và AND. - Động cơ DC mà chúng ta sử dụng có tích hợp sẵn một encoder 3 ngõ ra, chúng ta sẽ dùng AVR để đọc số xung (hay số vòng quay) và tính ra vận tốc của Motor. Việc đọc encoder sẽ được thực hiện bằng ngắt ngoài. - Một giải thuật PID được xây dựng trong AVR để hiệu chỉnh vận tốc động cơ. - Người dùng sẽ nhập vận tốc cần điều khiển vào AVR thông qua các switches. Vận tốc mong muốn và vận tốc thực của động cơ được hiển thị trên Text LCD. Mạch điện ví dụ được trình bày trong hình 1.

Page 62: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 62

Hình 1. Hệ thống điều khiển động cơ DC servo.

Trong mạch điện hình 1, tôi chia hệ thống thành 3 nhóm: nhóm CONTROL bao gồm AVR vận hành giải thuật điều khiển PID và việc nhập, xuất. Nhóm LOGIC thực hiện việc biến đổi các tín hiệu điều khiển để tạo ra các tín hiệu phù hợp cho chip

Page 63: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 63

driver. Nhóm POWER bao gồm chip driver L298D và DC Motor. Ngoài ra còn có một Encoder được tích hợp sẵn trên DC Motor. Phần tiếp theo chúng ta sẽ tìm hiểu riêng từng nhóm, cuối cùng là viết chương trình cho AVR điều khiển hệ thống DC Servo Motor

II. Incremental Optical Encoder

Để điều khiển số vòng quay hay vận tốc động cơ thì chúng ta nhất thiết phải đọc được góc quay của motor. Một số phương pháp có thể được dùng để xác định góc quay của motor bao gồm tachometer (thật ra tachometer đo vận tốc quay), dùng biến trở xoay, hoặc dùng encoder. Trong đó 2 phương pháp đầu tiên là phương pháp analog và dùng optiacal encoder (encoder quang) thuộc nhóm phương pháp digital. Hệ thống optical encoder bao gồm một nguồn phát quang (thường là hồng ngoại – infrared), một cảm biến quang và một đĩa có chia rãnh. Optical encoder lại được chia thành 2 loại: encoder tuyệt đối (absolute optical encoder) và encoder tương đối (incremental optical encoder). Trong đa số các DC Motor, incremental optical encoder được dùng và mô hình động cơ servo trong bài này cũng không ngoại lệ. Từ bây giờ khi tôi nói encoder tức là incremental encoder. Hình 2 là mô hình của encoder loại này.

Hình 2. Optical Encoder (trích từ [1]).

Encoder thường có 3 kênh (3 ngõ ra) bao gồm kênh A, kênh B và kênh I (Index). Trong hình 2 bạn thấy hãy chú ý một lỗ nhỏ bên phía trong của đĩa quay và một cặp phat-thu dành riêng cho lỗ nhỏ này. Đó là kênh I của encoder. Cữ mỗi lần motor quay được một vòng, lỗ nhỏ xuất hiện tại vị trí của cặp phát-thu, hồng ngoại từ nguồn phát sẽ xuyên qua lỗ nhỏ đến cảm biến quang, một tín hiệu xuất hiện trên cảm biến. Như thế kênh I xuất hiện một “xung” mỗi vòng quay của motor. Bên ngoài đĩa quay được chia thành các rãnh nhỏ và một cặp thu-phát khác dành cho các rãnh này. Đây là kênh A của encoder, hoạt động của kênh A cũng tương tự kênh I, điểm khác nhau là trong 1 vòng quay của motor, có N “xung” xuất hiện trên kênh A. N là số rãnh trên đĩa và

Page 64: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 64

được gọi là độ phân giải (resolution) của encoder. Mỗi loại encoder có độ phân giải khác nhau, có khi trên mỗi đĩa chĩ có vài rãnh nhưng cũng có trường hợp đến hàng nghìn rãnh được chia. Để điều khiển động cơ, bạn phải biết độ phân giải của encoder đang dùng. Độ phân giải ảnh hưởng đến độ chính xác điều khiển và cả phương pháp điều khiển. Không được vẽ trong hình 2, tuy nhiên trên các encoder còn có một cặp thu phát khác được đặt trên cùng đường tròn với kênh A nhưng lệch một chút (lệch M+0,5 rãnh), đây là kênh B của encoder. Tín hiệu xung từ kênh B có cùng tần số với kênh A nhưng lệch pha 90o. Bằng cách phối hợp kênh A và B người đọc sẽ biết chiều quay của động cơ. Hãy quan sát hình 3.

Hình 3. Hai kênh A và B lệch pha trong encoder (trích từ [1])

Hình trên cùng trong hình 3 thể hiện sự bộ trí của 2 cảm biến kênh A và B lệch pha nhau. Khi cảm biến A bắt đầu bị che thì cảm biến B hoàn toàn nhận được hồng ngoại xuyên qua, và ngược lại. Hình thấp là dạng xung ngõ ra trên 2 kênh. Xét trường hợp motor quay cùng chiều kim đồng hồ, tín hiệu “đi” từ trái sang phải. Bạn hãy quan sát lúc tín hiệu A chuyển từ mức xuống thấp (cạnh xuống) thì kênh B đang ở mức thấp. Ngược lại, nếu động cơ quay ngược chiều kim đồng hồ, tín hiệu “đi” từ phải qua trái. Lúc này, tại cạnh xuống của kênh A thì kênh B đang ở mức cao. Như vậy, bằng cách phối hợp 2 kênh A và B chúng ta không những xác định được góc quay (thông qua số xung) mà còn biết được chiều quay của động cơ (thông qua mức của kênh B ở cạnh xuống của kênh A). Câu hỏi bây giờ là làm thế nào để đọc encoder bằng AVR? Tùy theo mục đại lượng điều khiển (vị trí hay vận tốc) và đặc điểm encoder (độ phân giải) chúng ta có giải pháp sau để đọc encoder bằng AVR - Dùng input capture: một số bộ timer-counter trên AVR có chức năng Input capture, hiểu nôm na như sau. Cứ mỗi lần có một tín hiệu (cạnh lên hoăc cạnh xuống)

Page 65: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 65

trên chân ICP (Input Capture Pin), giá trị thời gian của timer được tự động gán cho thanh ghi ICR (Input capture Register). So sánh giá trị thanh ghi ICR trong 2 lần liên tiếp sẽ đọc được chu kỳ của tín hiệu kích chân ICP. Từ đó suy ra tần số tín hiệu. Nếu một kênh của encoder được nối với chân ICP thì chúng ta có thể đo được tần số tín hiệu của kênh này. Nói cách khác, chúng ta sẽ tính được vận tốc của động cơ. Chúng ta có thể dùng ngắt Input capture và khi ngắt xảy ra, có thể đếm số thêm số xung để biết được góc quay motor, cũng có thể xác định được hướng quay thông qua xác định mức kênh B trong trình phục vụ ngắt input capture. Đây là một phương pháp hay, nhưng có nhược điểm là khá phức tạp khi sử dụng chức năng input capture của AVR. Mặc khác trên các chip AVR từ mega32 trở xuống, Input capture chỉ có ở timer 1, trong khi Timer này thường dùng để tạo PWM điều khiển động cơ. - Dùng chức năng counter: đặt các kênh của encoder vào các chân đếm (T0, T1…) của các bộ timer chúng ta sẽ đếm được số lượng xung của các kênh. Đây là phương pháp sử dụng ít tài nguyên nhất (ít tốn thời gian cho encoder). Nhược điểm lớn nhất của phương pháp này là không xác định được chiều quay, mặc khác phương pháp này không ổn định khi vận tốc động cơ có sự thay đổi lớn. - Cuối cùng là sử dụng ngắt ngoài: đây là phương pháp dễ nhưng chính xác để đọc encoder. Đây là phương pháp được dùng trong bài học này. Ý tưởng của phương pháp rất đơn giản, chúng ta nối kênh A của encoder với 1 ngắt ngoài (INT2 chẳng hạn) và kênh B với một chân nào đó bất kỳ (không phải chân ngắt). Cứ mỗi lần ngắt ngoài xảy ra, tức có 1 xung xuất hiện trên ở kênh A thì trình phục vụ ngắt ngoài tự động được gọi. Trong trình phục vụ ngắt này chúng ta kiểm tra mức của kênh B, tùy theo mức của kênh B chúng ta sẽ tăng biến đếm xung lên 1 hoặc giảm đi 1. Tuy nhiên, bạn cần phải tính toán rất cẩn thận khi sử dụng phương pháp này. Ví dụ trường hợp encoder có độ phân giải 2000 xung/vòng, motor bạn quay với vận tốc 100 vòng/s thì tần số xung trên kênh A của encode là 2000x100=200KHz, nghĩa là cứ mỗi 5 us ngắt ngoài xảy ra một lần. Tần số ngắt như thế là quá cao cho AVR, điều này có nghĩa là AVR chỉ tập trung cho mỗi việc “đếm xung”, không có đủ thời gian để thực thi các việc khác. Trong bài này, chúng ta chọn độ phân giải của encoder là 112 (112 xung trên mỗi vòng quay). Vận tốc tối đa của động cơ được chọn vào khoảng 30 vòng/s nên tần số xung lớn nhất từ encoder là 112x30=3.36KHz. Giá trị này hợp lí vì tần số cho AVR trong bài này được chọn 8MHz. Kênh A của encoder được nối với ngắt INT2 của chip atmega32, kênh B được nối với chân PB0, chúng ta không sử dụng kênh I (xem hình 1). Chú ý: các ngõ ra trên đa số (gần như tất cả) các encoder có dạng cực góp hở (Open collector), muốn sử dụng chúng cần mắc điện trở kéo lên VCC (5V).

III. Chip driver L298D

L298D là một chip tích hợp 2 mạch cầu H trong gói 15 chân. Tất cả các mạch kích, mạch cầu đều được tích hợp sẵn. L298D có điện áp danh nghĩa cao (lớn nhất 50V) và dòng điện danh nghĩa lớn hơn 2A nên rất thích hợp cho các các ứng dụng công suất nhỏ như các động cơ DC loại nhỏ và vừa. Vì là loại “all in one” nên là lựa chọn hoàn hảo cho những người chưa có nhiều kinh nghiệm làm mạch điện tử. Trong

Page 66: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 66

bài học này tôi dùng chip L298D để làm driver cho motor. Hình 4 thể hiện mô hình thật của chip và cấu trúc bên trong chip.

Hình 4. Chip L298D

Hình phía trên là hình dáng bên ngoài và tên gọi các chân của L298D. Hình phía dưới là cấu trúc bên trong chip. Có 2 mạch cầu H trên mỗi chip L298D nên có thể điều khiển 2 đối tượng chỉ với 1 chip này. Mỗi mạch cầu bao gồm 1 đường nguồn Vs (thật ra là đường chung cho 2 mạch cầu), một đường current sensing (cảm biến dòng), phần cuối của mạch cầu H không được nối với GND mà bỏ trống cho người dùng nối một điện trở nhỏ gọi là sensing resistor. Bằng cách đo điện áp rơi trên điện trở này chúng ta có thể tính được dòng qua điện trở, cũng là dòng qua động cơ (xem hình 4). Mục đích chính của việc đo dòng điện qua động cơ là để xác định các trường hợp nguy hiểm xảy ra trong mạch, ví dụ quá tải. Nếu việc đo dòng động cơ không thật sự cần thiết bạn có thể nối đường current sensing này với GND (trong mạch điện của bài

Page 67: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 67

này, tôi nối chân current sensing với GND). Động cơ sẽ được nối với 2 đường OUT1, OUT2 (hoặc OUT3, OUT4 nếu dùng mạch cầu bên phải). Một chân En (EnA và EnB cho 2 mạch cầu) cho phép mạch cầu hoạt động, khi chân En được kéo lên mức cao, mạch cầu sẵn sang hoạt động. Các đường kích mỗi bên của mạch cầu được kết hợp với nhau và nhưng mức điện áp ngược nhau do một cổng Logic NOT. Bằng cách này chúng ta có thể tránh được trường hợp 2 transitor ở cùng một bên được kích cùng lúc (ngắn mạch). Như vậy, sẽ có 2 đường kích cho mỗi cầu H gọi là In1 và In2 (hoặc In3, In4). Để motor hoạt động chúng ta phải kéo 1 trong 2 đường kích này lên cao trong khi đường kia giữ ở mức thấp, ví dụ In1=1, In2=0. Khi đảo mức kích của 2 đường In, động cơ sẽ đảo chiều quay. Tuy nhiên, do L298D không chỉ được dùng đề đảo chiều động cơ mà còn điều khiển vận tốc động cơ bằng PWM, các đường In cần được “tổ hợp lại” bằng các cổng Logic (xem phần tiếp theo). Ngoài ra, trên chip L298D còn có các đường Vss cấp điện áp cho phần logic (5V) và GND chung cho cả logic và motor. Trong thực tế, công suất thực mà L298D có thể tải nhỏ hơn so với giá trị danh nghĩa của nó (V=50V, I=2A). Để tăng dòng điện tải của chip lên gấp đôi, chúng ta có thể nối 2 mạch cầu H song song với nhau (các chân có chức năng như nhau của 2 mạch cầu được nối chung).

II. M ạch logic cho L298D

Thông thường, khi thiết kế một mạch driver cho motor người ta thường dành 3 đường điều khiển đó là PWM dùng điều khiển vận tốc, DIR điều khiển hướng và En cho phép mạch hoạt động. Chip L298D đã có sẵn đường En nhưng 2 đường điều khiển In1 và In2 không thật sự chức năng như chúng ta mong muốn. Vì thế, chúng ta sẽ thiết kế một mạch logic phụ với 2 ngõ vào là PWM và DIR trong khi 2 ngõ ra là 2 đường điều khiển In1 và In2. Bảng chân trị của mạch logic cần thiết kế được trình bày trong bảng 1. Bảng 1. bảng chân trị của mạch logic cho driver L298D.

PWM DIR In1 In2 0 0 0 0 0 1 0 0 1 0 1 0 1 1 0 1

Từ bảng chân trị này, chúng ta có thể viết hàm bool cho 2 ngõ In1 và In2: In1=PWM.NOT(DIR) In2=PWM.DIR Mạch logic vì thế sẽ có dạng như trong hình 5.

Page 68: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 68

Hình 5. Mạch logic cho L239

Tôi sẽ không giải thích chi tiết phần này, tuy nhiên điều bạn cần nắm là với mạch logic này, đường DIR có chức năng đảo chiều động cơ trong khi đường PWM điều khiển vận tốc động cơ bằng tín hiệu PWM.

V. Giải thuật điều khiển PID

PID là cách viết tắc của các từ Propotional (tỉ lệ), Integral (tích phân) và Derivative (đạo hàm). Tuy xuất hiện rất lâu nhưng đến nay PID vẫn là giải thuật điều khiển được dùng nhiều nhất trong các ứng dụng điều khiển tự động. Để giúp bạn có cái hiểu rõ hơn bản chất của giải thuật PID tôi sẽ dùng một ví dụ điều khiển vị trí của một car (xe) trên đường thẳng. Giả sử bạn có một xe (đồ chơi...) có gắn một động cơ DC. Động cơ sinh ra một lực để đẩy xe chạy tới hoặc lui trên một đường thẳng như trong hình 6.

Hình 6. Ví dụ điều khiển vị trí xe trên đường thẳng

Page 69: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 69

Gọi F là lực do động cơ tạo ra điều khiển xe. Ban đầu xe ở vị trí A, nhiệm vụ đặt ra là điều khiển lực F (một cách tự động) để đẩy xe đến đúng vị trí O với các yêu cầu: chính xác (accurate), nhanh (fast response), ổn định (small overshot). Một điều rất tự nhiên, nếu vị trí hiện tại của xe rất xa vị trí mong muốn (điểm O), hay nói cách khác sai số(error) lớn, chúng ta cần tác động lực F lớn để nhanh chóng đưa xe về O. Một cách đơn giản để công thức hóa ý tưởng này là dùng quan hệ tuyến tính: F=Kp*e (1) Trong đó Kp là một hằng số dương nào đó mà chúng ta gọi là hệ số P (Propotional gain), e là sai số cần điều khiển tức khoảng cách từ điểm O đến vị trí hiện tại của xe. Mục tiêu điều khiển là đưa e tiến về 0 càng nhanh càng tốt. Rõ ràng nếu Kp lớn thì F cũng sẽ lớn và xe rất nhanh chóng tiến về vị trí O. Tuy nhiên, lực F quá lớn sẽ gia tốc cho xe rất nhanh (định luật II của Newton: F=ma). Khi xe đã đến vị trí O (tức e=0), thì tuy lực F=0 (vì e=0) nhưng do quán tính xe vẫn tiếp tục tiến về bên phải và lệch điểm O về bên phải, sai số e lại trở nên khác 0, giá trị sai số lúc này được gọi là overshot (vượt quá). Lúc này, sai số e là số âm, lực F lại xuất hiện nhưng với chiều ngược lại để kéo xe về lại điểm O. Nhưng một lần nữa, do Kp lớn nên giá trị lực F cũng lớn và có thể kéo xe lệch về bên trái điểm O. Quá trình cứ tiếp diễn, xe cứ mãi sao động quanh điểm O. Có trường hơp xe dao động càng ngày xàng xa điểm O. Bộ điều khiển lúc này được nói là không ổn định. Một đề xuất nhằm giảm overshot của xe là xử dụng một thành phần “thắng” trong bộ điều khiển. Sẽ rất lý tưởng nếu khi xe đang ở xa điểm O, bộ điều khiển sinh ra lực F lớn nhưng khi xe đã tiến gần đến điểm O thì thành phần “thắng” sẽ giảm tốc độ xe lại. Chúng ta điều biết khi một vật dao động quanh 1 điểm thì vật đó có vận tốc cao nhất ở tâm dao động (điểm O). Nói một cách khác, ở gần điểm O sai số e của xe thay đổi nhanh nhất (cần phân biệt: e thay đổi nhanh nhất không phải e lớn nhất). Mặc khác, tốc độ thay đổi của e có thể tính bằng đạo hàm của biến này theo thời gian. Như vậy, khi xe từ A tiến về gần O, đạo hàm của sai số e tăng giá trị nhưng ngược chiều của lực F (vì e đang giảm nhanh dần). Nếu sử dụng đạo hàm làm thành phần “thắng” thì có thể giảm được overshot của xe. Thành phần “thắng” này chính là thành phần D (Derivative) trong bộ điều khiển PID mà chúng ta đang khảo sát. Thêm thành phần D này vào bộ điều khiển P hiện tại, chúng ta thu được bộ điều khiển PD nhu sau: F=Kp*e + Kd*(de/dt) (2) Trong đó (de/dt) là vận tốc của sai số e và Kd là một hằng số không âm gọi là hệ số D (Derivative gain). Sự hiện diện của thành phần D làm giảm overshot của xe, khi xe tiến gần về O, lực F gồm 2 thành phần Kp*e > =0 (P) và Kd*(de/dt) <=0 (D). Trong một số trường hợp thành phần D có giá trị lớn hơn thành phần P và lực F đổi chiều, “thắng” xe lại, vận tốc của xe vì thế giảm mạnh ở gần điểm O. Một vấn đề nảy sinh là nếu thành phần D quá lớn so với thành phần P hoặc bản thân thành phần P nhỏ thì khi xe tiến gần điểm O (chưa thật sự đến O), xe có thể dừng hẳn, thành phần D bằng 0 (vì sai số e không

Page 70: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 70

thay đổi nữa), lực F = Kp*e. Trong khi Kp và e lúc này đều nhỏ nên lực F cũng nhỏ và có thể không thắng được lực ma sát tĩnh. Bạn hãy tưởng tượng tình huống bạn dùng sức của mình để đẩy một xe tải nặng vài chục tấn, tuy lực đẩy tồn tại nhưng xe không thể di chuyển. Như thế, xe sẽ đứng yên mãi dù sai số e vẫn chưa bằng 0. Sai số e trong tình huống này gọi là steady state error (tạm dịch là sai số trạng thái tĩnh). Để tránh steady state error, người ta thêm vào bộ điều khiển một thành phần có chức năng “cộng dồn” sai số. Khi steady state error xảy ra, 2 thành phần P và D mất tác dụng, thành phần điều khiển mới sẽ “cộng dồn” sai số theo thời gian và làm tăng lực F theo thời gian. Đến một lúc nào đó, lực F đủ lớn để thắng ma sát tĩnh và đẩy xe tiến tiếp về điểm O. Thành phần “cộng dồn” này chính là thành phần I (Integral - tích phân) trong bộ điều khiển PID. Vì chúng ta điều biết, tích phân một đại lượng theo thời gian chính là tổng của đại lượng đó theo thời gian. Bộ điều khiển đến thời điểm này đã đầy đủ là PID: F=Kp*e + Kd*(de/dt)+Ki*§edt (3) (dấu § được tôi dùng thay cho dấu tích phân, §edt là tích phân của biến e theo t)

Như vậy, chức năng của từng thành phần trong bộ điều khiển PID giờ đã rõ. Tùy vào mục đích và đối tượng điều khiển mà bộ điều khiển PID có thể được lượt bớt để trở thành bộ điều khiển P, PI hoặc PD. Công việc chính của người thiết kế bộ điều khiển PID là chọn các hệ số Kp, Kd và Ki sao cho bộ điều khiển hoạt động tốt và ổn định (quá trình này gọi là PID gain tuning). Đây không phải là việc dễ dàng vì nó phụ thuộc vào nhiều yếu tố. Tôi tóm tắt một kinh nghiệm cơ bản khi chọn các hệ số cho PID như sau: - Chọn Kp trước: thử bộ điều khiển P với đối tượng thật (hoặc mô phỏng), điều chỉnh Kp sao cho thời gian đáp ứng đủ nhanh, chấp nhận overshot nhỏ. - Thêm thành phần D để loại overshot, tăng Kd từ từ, thử nghiệm và chọn giá trị thích hợp. Steady state error có thể sẽ xuất hiện. - Thêm thành phần I để giảm steady state error. Nên tăng Ki từ bé đến lớn để giảm steady state error đồng thời không để cho overshot xuất hiện trở lại. Có một phương pháp rất phổ biến dùng để chọn các hệ số cho bộ điều khiển PID gọi là Ziegler–Nichols, bạn quan tâm có thể tự tìm hiểu thêm.

Điều khiển PID số Công thức của bộ điều khiển PID trình bày trong (3) là dạng hàm liên tục của biến e, trong đó có cả thành phần tuyến tính, đạo hàm và tích phân. Tuy nhiên, hệ thống máy tính và vi điều khiển lại là hệ thống số. Muốn xây dựng bộ điều khiển PID trên máy tính hay trên vi điều khiển chúng ta phải biết cách xấp xỉ phương trình liên tục thành dạng rời rạc. Để thực hiện “số hóa” bộ điều khiển PID trước hết tôi nói sơ qua thế nào là hệ thống số (digital) so với hệ thống liên tục hay hệ thống tương tự (analog). Hãy quan sát hệ thống điều chỉnh nhiệt độ đơn giản như trong hình 7.

Page 71: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 71

Hình 7. Tự động điều chỉnh nhiệt độ

Giả sử chúng ta cần điều chỉnh nhiệt độ trong phòng ở một mức nào đó (tùy theo giá trị tham chiếu) bằng quạt. Cảm biến đo nhiệt độ và hồi tiếp về bộ khuyếch đại vi sai (so sánh và khuyếch đại). Nếu có sai số giữa giá trị tham chiếu và giá trị đo từ cảm biếm, bộ khuyếch đại vi sai sẽ tự động khuyếch đại sai số này và làm tăng hay giảm vận tốc của quạt để điều chỉnh nhiệt độ. Quá trình này xảy ra một cách liên tục. Bộ khuyếch đại vi sai trong trường hợp này chính là bộ điều khiển tương tự (analog controller). Bộ khuyếch đại này là một mạch điện tử thông thường như Opamp chẳng hạn. Nếu chúng ta thay bộ khuyếch đại này bằng một vi điều khiển AVR thì quá trình hiệu chỉnh không còn xảy ra liên tục nữa mà theo một chu kỳ nào đó. Ví dụ cứ mỗi 10 ms chúng ta đọc giá trị từ cảm biến một lần để tính toán sai số và xuất giá trị điều khiển quạt. Bộ điều khiển do AVR thực hiện gọi là bộ điều khiển số (digital controller) và khoảng thời gian 10ms này gọi là thời gian lấy mẫu (sampling time), đó là khoảng cách giữa 2 lần điều khiển liên tiếp. Rõ ràng thời gian lấy mẫu càng nhỏ (tấn số cao) thì việc hiệu chỉnh càng tiến gần đến sự “liên tục” và chất lượng điều khiển sẽ tốt hơn. Trong các bộ điều khiển số, thời gian lấy mẫu là một yếu tố rất quan trọng. Cần tính toán để thời gian này không quá lớn nhưng cũng đừng quá nhỏ, vì như thế sẽ hao phí thời gian thực thi. Vì bộ điều khiển PID xây dựng trong AVR sẽ là bộ điều khiển số, chúng ta cần xấp xỉ công thức của bộ điều khiển này theo các khoảng thời gian rời rạc. Trước hết, thành phần P tương đối đơn giản vì đó là quan hệ tuyến tính Kp*e, chúng ta chỉ cần áp dụng trực tiếp công thức này mà không cần bất kỳ xấp xỉ nào. Tiếp đến là xấp xỉ cho đạo hàm của biến e. Vì thời gian lấy mẫu cho các bộ điều khiển thường rất bé nên có thể xấp xỉ đạo hàm bằng sự thay đổi của e trong 2 lần lấy mẫu liên tiếp: de/dt =(e(k) – e(k-1))/h. Trong đó e(k) là giá trị hiện tại của e, e(k-1) là giá trị của e trong lần lấy mẫu trước đó và h là khoảng thời gian lấy mẫu (h là hằng số).

Page 72: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 72

Hình 8. Xấp xỉ đạo hàm của biến sai số e

Thành phần tích phân được xấp xỉ bằng diện tích vùng giới hạn bởi hàm đường biểu diễn của e và trục thời gian. Do việc tính toán tích phân không cần quá chính xác, chúng ta có thể dùng phương pháp xấp xỉ đơn giản nhất là xấp xỉ hình chữ nhật (sai số của phương pháp này cũng lớn nhất). Ý tưởng được trình bày trong hình 9.

Hình 9. Xấp xỉ tích phân của biến sai số e

Tích phân của biến e được tính bằng tổng diện tích các hình chữ nhật tại mỗi thời điểm đang xét. Mỗi hình chữ nhật có chiều rộng bằng thời gian lấy mẫu h và chiều cao là giá trị sai số e tại thời điểm đang xét. Tổng quát:

(4) Tổng hợp các xấp xỉ, công thức của bộ điều khiển PID số được trình bày trong (5)

Page 73: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 73

(5) Trong đó u là đại lượng output từ bộ điều khiển. Để đơn giản hóa việc tính thành phần tích phân, chúng ta nên dùng phương pháp “cộng dồn” (hay đệ quy):

(6) Với I(k) là thành phần tích phân hiện tại và I(k-1) là thành phần tích phân trước đó. Các công thức (5) và (6) rất dễ dàng để thực hiện bằng AVR. Do đó, đến lúc này chúng ta đã sẵn sàng để đưa ý tưởng vào lập trình cho chip.

VI. Điều khiển DC Motor bằng AVR

Phần này chúng ta sẽ vận dụng tất cả phần lý thuyết giới thiệu ở trên để viết chương trình cho AVR. Mục đích là điều khiển vận tốc của DC Motor bằng giải thuật PID. Mạch điện mô phỏng được trình bày trong hình 1. Mô hình Motor dùng trong ví dụ là loại 12V có vận tốc không tải tối đa là 720rpm (revolute per minute) tức 20 vòng/s. Encoder dùng cho motor được chọn có độ phân giải 112 pulse/vòng. Kênh A của encoder được nối với ngắt ngoài INT2 để đếm xung, kênh B nối với chân PB0 (chân 1) của chip Atmega32 để xét hướng quay. Bốn switches được nối với 4 bit cao của PORTB để cài đặt vận tốc mong muốn cần điều khiển. Một Text LCD dùng hiển thị vận tốc thực của motor đọc từ Encoder (Actual speed) và vận tốc cài đặt (Desired speed). Do Text LCD được nối với PORTC nên nếu bạn muốn dùng chương trình này cho ứng dụng thật thì phải nạp lại fuses để vô hiệu hóa JTAG. Giải thuật PID số được vận hành bởi AVR trong thời gian lấy mẫu là 25ms. Timer 2 được dùng để tạo khoảng thời gian 25ms. Timer 1 (16 bit) là bộ tạo PWM điều khiển vận tốc động cơ. Toàn bộ nội dung chương trình được trình bày trong list 1.

Page 74: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 74

List 1. Điều khiển vận tốc động cơ DC

Page 75: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 75

Page 76: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 76

Các dòng từ 14 đến 17 chúng ta định nghĩa các chân điều khiển DC Motor, chân DIR điều khiển hướng và EN kích hoạt hoặc dừng Motor (thực ra là dừng L298D). Do mục đích của chúng ta là điều khiển vận tốc động cơ, 2 chân này chỉ được “kích” một lần duy nhất trong chương trình chính (không cần đổi hướng quay của Motor). Dòng 18 định nghĩa thời gian lấy mẫu, Sampling_time là 25 ms (.025s). Biến inv_Sampling_time ở dòng 19 là nghịch đảo của Sampling_time, 1/0.025 = 40, vì đây cũng là hằng số, chúng ta định nghĩa trước để sau này không cần thực hiện phép nghịch đảo trong chương trình chính (tiết kiệm thời gian thực thi). PWM dùng điều khiển động cơ được chọn có tần số 1KHz nên chu kỳ la 1ms. Do chúng ta dùng nguồn xung giữ nhịp 8MHz, để tạo thời gian 1ms cần 8000 xung, giá trị này được định nghĩa trong dòng 20 và sẽ được gán cho thanh ghi ICR1 (TOP của PWM, xem lại bài Timer-Counter, Timer1, Fast PWM) trong chương trình chính (dòng 81). Các dòng code từ 22 đến 27 khai báo một số biến toàn cục dùng trong chương trình chính. Do các biến này sẽ được dùng cả trong trình phục vụ ngắt và chương trình chính nên cần khai báo đặc tính volatile, kiểu biến là long int tức số nguyên 32 bit (để tránh bị tràn khi tính toán sau này). Biến Pulse và pre_Pulse là số xung hiện tại và lần lấy mẫu trước đó đọc từ encoder. Các biến trong dòng 23 và 24 dùng cho bộ điều khiển PID, biến Ctrl_Speed là vận tốc mong muốn (set point) toàn cục và biến Output chứa giá trị tính được từ bộ điều khiển PID. Trước khi đi tìm hiểu chương trình con chứa giải thuật PID, chúng ta sẽ khảo sát nội dung chương trình main và các trình phục vụ ngắt trước để hiểu tổng quan cách thức thực hiện. Chương trình chính bắt đầu từ dòng 45 và kết thúc ở dòng 103. Phần đầu của chương trình chính (ngoài vòng lặp while) khai báo và khởi tạo các module được sử dụng. 2 dòng 49 và 50 cài đặt hướng cho PORTB, do PORT này dùng đọc encoder và các switches chúng ta cần set nó là input và có điện trở kéo lên. Hai dòng 52 và 53 set hướng cho động cơ và sẽ giữ hướng này không đổi trong suất quá trình điều khiển sau này. Hai dòng 55 và 56 khai báo ngắt ngoài INT2 dùng đếm xung kênh A của encoder. Chú ý là INT2 chỉ có 2 mode là cạnh xuống và cạnh lên nên chỉ có 1 bit sense ISC2 để chọn mode. Bit ISC2 không nằm trong thanh ghi điều khiển MCUCR như các ngắt khác mà nằm trong thanh ghi điều khiển-trạng thái MCUCSR. Khi ISC2=0 thì chế độ ngắt cạnh xuống của INT2 được chọn (xem dòng 55). Sau đó INT2 được cho phép hoạt động ở dòng 56. Hãy tạm thời di chuyển đến dòng 109 để xem trình phục vụ ngắt INT2. Chức năng của INT2 trong bài này là “đếm xung encoder” vì thế trình phục vụ sẽ làm việc này. Khi có một ngắt INT2 xảy ra tức có 1 xung từ encoder vào thì trình phục vụ ngắt ISR(INT2 vect) tự động được gọi ra, dòng 110 trong trình phục vụ ngắt kiểm tra trạng thái chân PB0, tức kenh B của encoder. Nếu PB0=1 thì tăng biến xung đếm được Pulse lên 1, ngược lại nếu PB0=0 thì giảm Pulse đi 1 trong dòng 111. Quay về giải thích chương trình chính ở dòng 59, đây là các khai báo cho timer 2. Chúng ta sẽ dùng timer 2 tạo ra một khoảng thời gian lấy mẫu 25 ms, cứ sau 25 ms thì sẽ có ngắt tràn timer2 một lần và trong trình phục vụ ngắt tràn của timer2 chúng ta thực hiện tính toán PID. Dòng 59 chúng ta set các bit CS để chọn bộ chia tần số, bộ chia Prescaler=1024 được chọn vì 25 ms khá lớn so với thời gian 1 chu kỳ xung giữ nhịp (1/8 micro giây). Prescaler = 1024 nghĩa là sau 1024 nhịp của xung giữ nhịp, tức sau 128 micro giây (1024 *1/8=128 us) thì thanh ghi giá trị

Page 77: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 77

TCNT2 mới tăng 1 đơn vị. Do chúng ta muốn tạo khoảng thời gian 25 ms tương đương 25000/128=195 đơn vị đếm của thanh ghi TCNT2, chúng ta sẽ gán giá trị khởi tạo cho TCNT2 là 255-195=60 (timer 2 sẽ tràn một lần khi TCNT2 đếm đến 255, xem lại bài Timer-Counter). Điều này thực hiện ở dòng 60 TCNT2=60. Dòng 61 cho phép ngắt tràn timer2. Hai dòng 64 và 65 khởi động Timer 1 dùng như một bộ tạo xung Fast PWM, mode 14, trong đó thanh ghi ICR1 chứa chu kỳ PWM và 2 thanh ghi OCR1A, OCR1B chứa duty cycle (khoảng ON) của PWM. Các dòng từ 68 đến 70 ghi texts lên LCD. Các dòng từ 80 đến 83 khởi động PWM cho DC Motor và cho phép ngắt toàn cục sei();. Trong vòng lặp while chủ yếu là công việc kiểm tra và hiển thị, biến sample_count đếm số lần ngắt tràn timer2 xảy ra, nó được tăng 1 đơn vị khi có một ngắt tràn (xem dòng 106) tức sau 25ms. Dòng 86, chúng ta kiểm tra biến sample_count, việc hiển thị chỉ đượcthực hiện mỗi 250 ms một lần (sample_count=10) vì việc này tốn khá nhiều thời gian. Trong dòng 87 chúng ta kiểm tra các swiches để xem người dùng cho muốn thay đổi vận tốc tham chiếu cho điều khiển. Các dòng tiếp theo in biến rSpeed là số lượng xung đếm được từ encoder trong vòng 25 ms (cho tới hiện tại) ở dong 1 của LCD và in biến Ctrl_Speed là số xung/25ms mà người dùng mong muốn motor đạt được. Nội dung quan trọng nhất của list 1, tuy nhiên, không nằm trong chương trình chính mà nằm ở các trình phục vụ ngắt và chương trình con Motor_Speed_PID(long int des_Speed). Trước hết, trình phục vụ ngắt ISR(TIMER2_OVF_vect) được tự động gọi sau mỗi 25ms, trong trình này chúng ta cần set lại giá trị khởi động cho thanh ghi giá trị TCNT2 (xem lại bài Timer-counter) ở dòng 105. Sau đó tăng biến đếm sample_count lên 1 (cùng cho việc đếm thời gian để hiển thị, đã nói ở trên). Cuối cùng là gọi chương trình con tính toán giải thuật PID Motor_Speed_PID(long int des_Speed). Đây là đoạn chương trình tính toán giải thuật PID và xuất giá trị điều khiển Motor. Hãy quay lại dòng 30 để tìm hiểu chương trình con này. Do biến Pulse chứa tổng số xung đọc từ encode (trong ISR(INT2_vect) ), chúng ta lấy giá trị này trừ đi giá trị pre_Pulse, tức số lượng xung ở thời điểm 25 ms trước đó, để thu được tổng số xung thu được trong 25 ms qua. Đây chính là vận tốc motor tính trên 25 ms: rSpeed=Pulse-pre_Pulse. Sau khi tính được “vận tốc” rSpeed chúng ta gán lại giá trị Pulse cho pre_Pulse để lần lấy mẫu sau dùng đến (dòng 32). Sai số vận tốc được đặt tên là Err, biến này được tính bằng bằng cách lấy vận tốc mong muốn trừ vận tốc hiện tại: Err=des_Speed-abs(rSpeed) ở dòng 33. Dòng 34 tính thành phần P của bộ điều khiển pPart=Kp*Err . Dòng 35 tính thành phần D của bộ điều khiển, như chúng ta đã thảo luận trong công thức (2) thì thành phần D được tính là: dPart=Kd*(Err-pre_Err)/Sampling_time, trong đó pre_Err là giá trị sai số ở lần lấy mẫu trước được lưu lại. Do 1/Sampling_time = inv_Sampling_time nên chúng ta có thể thay dòng tính dPart bằng công thức trong dòng 35: dPart=Kd*(Err-pre_Err)*inv_Sampling_time . Dòng 36 tính thành phần I (iPart), sử dụng phương pháp “cộng dồn” (đệ quy) chúng ta thu được iPart bằng iPart trước đó cộng với diện tích hình chữ nhật sai số hiện tại: iPart+=Ki*Sampling_time*(Err+pre_Err)/1000 . Chúng ta phải chia iPart cho 1000 vì Sampling_time được tính theo ms trong khi đơn vị tính toán chuẩn trong là s. Cộng các thành phần này lại chúng ta được giá trị Output tổng hợp trong dòng 37. Tuy nhiên, theo lẽ thường thì công thức dòng 37 phải là

Page 78: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 78

Output=pPart+dPart+iPart nhưng ở đây lại là : Output+=pPart+dPart+iPart (để ý dấu + trước dấu =), nghĩa là Output được cộng dồn thay vì là tổng tức thời như chúng ta đã thảo luận trong phần giải thuật PID. Thật ra việc này cũng dễ hiểu. Trong bài toán điều khiển vị trí, khi sai số bằng 0 chúng ta có thể dừng bộ điều khiển (u=0) nhưng trong bài toán điều khiển vận tốc, khi sai số bằng 0 thì giá trị u vẫn phải được giữ là giá trị trước đó.Vì vậy, trong bài toán điều khiển vận tốc giá trị Output được cộng dồn thay vì gán trực tiếp, bạn phải ghi nhớ điều này trong các ứng dụng điều khiển của mình. Hai dòng 40 và 41 xét trường hợp bão hòa (saturation) khi Output vượt quá giới hạn cho phép của PWM (xén 2 đầu). Cuối cùng là gán giá trị tính toán được từ PID cho thanh ghi OCR1A để tăng hoặc giảm duty cycle của PWM trên chân OC1A (nối với PWM của Motor) và gán gái trị sai số Err cho biến pre_Err cho lần lấy mẫu sau dùng đến. Chạy mô phỏng: toàn bộ chương trình và cả mạch điện mô phỏng đã được tôi tạo sẵn. Người đọc chỉ cần đọc hiểu và chạy mô phỏng mạch điện. Chú khi chạy mô phỏng hãy thay đổi các switches để thay đổi vận tốc cần điều khiển. Gái trị vận tốc thực chất là số xung encoder trong 25 ms, người đọc hãy tự tính ra số vòng /s. Do mô hình motor trong phần mềm mô phỏng không hoàn hảo lắm nên đáp ứng bộ điều khiển hơi chậm, bạn có thể phải chờ một khoảng thời gian để thấy vận tốc Motor đạt đến vận tốc yêu cầu. Hay thay giá trị Kd trong dòng 23 thành 1 hoặc 0, biên dịch lại chương trình và mô phỏng để quan sát và so sánh ovetshot (sự vượt quá) của hệ thống.

Page 79: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 79

Đồng hồ thời gian thực DS1307

Nội dung Các bài cần tham khảo trước

1. Chip DS1307.

2. AVR và DS1307.

Download ví dụ

Cấu trúc AVR.

WinAVR.

C cho AVR.

Text LCD

Giao tiếp TWI-I2C

I. Chip DS1307.

DS1307 là chip đồng hồ thời gian thực (RTC : Real-time clock), khái niệm thời gian thực ở đây được dùng với ý nghĩa thời gian tuyệt đối mà con người đang sử dụng, tình bằng giây, phút, giờ…DS1307 là một sản phẩm của Dallas Semiconductor (một công ty thuộc Maxim Integrated Products). Chip này có 7 thanh ghi 8-bit chứa thời gian là: giây, phút, giờ, thứ (trong tuần), ngày, tháng, năm. Ngoài ra DS1307 còn có 1 thanh ghi điều khiển ngõ ra phụ và 56 thanh ghi trống có thể dùng như RAM. DS1307 được đọc và ghi thông qua giao diện nối tiếp I2C (TWI của AVR) nên cấu tạo bên ngoài rất đơn giản. DS1307 xuất hiện ở 2 gói SOIC và DIP có 8 chân như trong hình 1.

Hình 1. Hai gói cấu tạo chip DS1307.

Các chân của DS1307 được mô tả như sau: - X1 và X2: là 2 ngõ kết nối với 1 thạch anh 32.768KHz làm nguồn tạo dao động cho chip. - VBAT: cực dương của một nguồn pin 3V nuôi chip. - GND: chân mass chung cho cả pin 3V và Vcc. - Vcc: nguồn cho giao diện I2C, thường là 5V và dùng chung với vi điều khiển. Chú ý là nếu Vcc không được cấp nguồn nhưng VBAT được cấp thì DS1307 vẫn đang hoạt động (nhưng không ghi và đọc được).

Page 80: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 80

- SQW/OUT: một ngõ phụ tạo xung vuông (Square Wave / Output Driver), tần số của xung được tạo có thể được lập trình. Như vậy chân này hầu như không liên quan đến chức năng của DS1307 là đồng hồ thời gian thực, chúng ta sẽ bỏ trống chân này khi nối mạch. - SCL và SDA là 2 đường giao xung nhịp và dữ liệu của giao diện I2C mà chúng ta đã tìm hiểu trong bài TWI của AVR. Có thể kết nối DS1307 bằng một mạch điện đơn giản như trong hình 2.

Hình 2. Mạch ứng dụng đơn giản của DS1307.

Cấu tạo bên trong DS1307 bao gồm một số thành phần như mạch nguồn, mạch dao động, mạch điều khiển logic, mạch giao điện I2C, con trỏ địa chỉ và các thanh ghi (hay RAM). Do đa số các thành phần bên trong DS1307 là thành phần “cứng” nên chúng ta không có quá nhiều việc khi sử dụng DS1307. Sử dụng DS1307 chủ yếu là ghi và đọc các thanh ghi của chip này. Vì thế cần hiểu rõ 2 vấn đề cơ bản đó là cấu trúc các thanh ghi và cách truy xuất các thanh ghi này thông qua giao diện I2C. Phần này chúng ta tìm hiểu cấu trúc các thanh ghi trước và cách truy xuất chúng sẽ tìm hiểu trong phần 2, điều khiển DS1307 bằng AVR. Như tôi đã trình bày, bộ nhớ DS1307 có tất cả 64 thanh ghi 8-bit được đánh địa chỉ từ 0 đến 63 (từ 0x00 đến 0x3F theo hệ hexadecimal). Tuy nhiên, thực chất chỉ có 8 thanh ghi đầu là dùng cho chức năng “đồng hồ” (tôi sẽ gọi là RTC) còn lại 56 thanh ghi bỏ trông có thể được dùng chứa biến tạm như RAM nếu muốn. Bảy thanh ghi đầu tiên chứa thông tin về thời gian của đồng hồ bao gồm: giây (SECONDS), phút (MINUETS), giờ (HOURS), thứ (DAY), ngày (DATE), tháng (MONTH) và năm (YEAR). Việc ghi giá trị vào 7 thanh ghi này tương đương với việc “cài đặt” thời gian khởi động cho RTC. Việc đọc giá từ 7 thanh ghi là đọc thời gian thực mà chip tạo ra. Ví dụ, lúc khởi động chương trình, chúng ta ghi vào thanh ghi “giây” giá trị 42, sau đó 12s chúng ta đọc thanh ghi này, chúng ta thu được giá trị 54. Thanh ghi thứ 8 (CONTROL) là thanh ghi điều khiển xung ngõ ra SQW/OUT (chân 6). Tuy nhiên, do chúng ta không dùng chân SQW/OUT nên có thề bỏ qua thanh ghi thứ 8. Tổ chức bộ nhớ của DS1307 được trình bày trong hình 3.

Page 81: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 81

Hình 3. Tổ chức bộ nhớ của DS1307.

Vì 7 thanh ghi đầu tiên là quan trọng nhất trong hoạt động của DS1307, chúng ta sẽ khảo sát các thanh ghi này một cách chi tiết. Trước hết hãy quan sát tổ chức theo từng bit của các thanh ghi này như trong hình 4.

Hình 4. Tổ chức các thanh ghi thời gian.

Điều đầu tiên cần chú ý là giá trị thời gian lưu trong các thanh ghi theo dạng BCD. BCD là viết tắt của cụm từ Binary-Coded Decimal, tạm dịch là các số thập phân theo mã nhị phân. Ví dụ bạn muốn cài đặt cho thanh ghi MINUTES giá trị 42. Nếu quy đổi 42 sang mã thập lục phân thì chúng ta thu được 42=0x2A. Theo cách hiểu thông thường chúng ta chỉ cần gán MINUTES=42 hoặc MINUTES=0x2A, tuy nhiên vì các thanh ghi này chứa giá trị BCD nên mọi chuyện sẽ khác, tôi sẽ diễn giải bằng hình 5.

Page 82: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 82

Hình 5. Số BCD.

Với số 42, trước hết nó được tách thành 2 chữ số (digit) 4 và 2. Mỗi chữ số sau đó được đổi sang mã nhị phân 4-bit. Chữ số 4 được đổi sang mã nhị phân 4-bit là 0100 trong khi 2 được đổi thành 0010. Ghép mã nhị phân của 2 chữ số lại chúng ta thu được mốt số 8 bit, đó là số BCD. Với trường hợp này, số BCD thu được là 01000010 (nhị phân) = 66. Như vậy, để đặt số phút 42 cho DS1307 chúng ta cần ghi vào thanh ghi MINUTES giá trị 66 (mã BCD của 42). Tất cả các phần mềm lập trình hay thanh ghi của chip điều khiển đều sử dụng mã nhị phân thông thường, không phải mã BCD, do đó chúng ta cần viết các chương trình con để quy đổi từ số thập nhị phân (hoặc thập phân thường) sang BCD, phần này sẽ được trình bày trong lúc lập trình giao tiếp với DS1307. Thoạt nhìn, mọi người đều cho rằng số BCD chỉ làm vấn đền thêm rắc rối, tuy nhiên số BCD rất có ưu điểm trong việc hiển thị nhất là khi hiển thị từng chữ số như hiển thị bằng LED 7 đoạn chẳng hạn. Quay lại ví dụ 42 phút, giả sử chúng ta dùng 2 LED 7-đoạn để hiện thị 2 chữ số của số phút. Khi đọc thanh ghi MINUTES chúng ta thu được giá trị 66 (mã BCD của 42), do 66=01000010 (nhị phân), để hiển thị chúng ta chỉ cần dùng phương pháp tách bit thông thường để tách số 01000010 thành 2 nhóm 0100 và 0010 (tách bằng toán tử shift “>>” của C hoặc instruction LSL, LSR trong asm) và xuất trực tiếp 2 nhóm này ra LED vì 0100 = 4 và 0010 =2, rất nhanh chóng. Thậm chí, nếu chúng ta nối 2 LED 7-đoạn trong cùng 1 PORT, việc tách ra từng digit là không cần thiết, để hiển thị cả số, chỉ cần xuất trực tiếp ra PORT. Như vậy, với số BCD, việc tách và hiển thị digit được thực hiện rất dễ dàng, không cần thực hiện phép chia (rất tốn thời gian thực thi) cho cơ số 10, 100, 1000…như trong trường hợp số thập phân. Thanh ghi giây (SECONDS): thanh ghi này là thanh ghi đầu tiên trong bộ nhớ của DS1307, địa chỉ của nó là 0x00. Bốn bit thấp của thanh ghi này chứa mã BCD 4-bit của chữ số hàng đơn vị của giá trị giây. Do giá trị cao nhất của chữ số hàng chục là 5 (không có giây 60 !) nên chỉ cần 3 bit (các bit SECONDS6:4) là có thể mã hóa được (số 5 =101, 3 bit). Bit cao nhất, bit 7, trong thanh ghi này là 1 điều khiển có tên CH (Clock halt – treo đồng hồ), nếu bit này được set bằng 1 bộ dao động trong chip bị vô hiệu hóa, đồng hồ không hoạt động. Vì vậy, nhất thiết phải reset bit này xuống 0 ngay

Page 83: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 83

từ đầu. Thanh ghi phút (MINUTES) : có địa chỉ 0x01, chứa giá trị phút của đồng hồ. Tương tự thanh ghi SECONDS, chỉ có 7 bit của thanh ghi này được dùng lưu mã BCD của phút, bit 7 luôn luôn bằng 0. Thanh ghi giờ (HOURS): có thể nói đây là thanh ghi phức tạp nhất trong DS1307. Thanh ghi này có địa chỉ 0x02. Trước hết 4-bits thấp của thanh ghi này được dùng cho chữ số hàng đơn vị của giờ. Do DS1307 hỗ trợ 2 loại hệ thống hiển thị giờ (gọi là mode) là 12h (1h đến 12h) và 24h (1h đến 24h) giờ, bit6 (màu green trong hình 4) xác lập hệ thống giờ. Nếu bit6=0 thì hệ thống 24h được chọn, khi đó 2 bit cao 5 và 4 dùng mã hóa chữ số hàng chục của giá trị giờ. Do giá trị lớn nhất của chữ số hàng chục trong trường hợp này là 2 (=10, nhị phân) nên 2 bit 5 và 4 là đủ để mã hóa. Nếu bit6=1 thì hệ thống 12h được chọn, với trường hợp này chỉ có bit 4 dùng mã hóa chữ số hàng chục của giờ, bit 5 (màu orange trong hình 4) chỉ buổi trong ngày, AM hoặc PM. Bit5 =0 là AM và bit5=1 là PM. Bit 7 luôn bằng 0. (thiết kế này hơi dở, nếu dời hẳn 2 bit mode và A-P sang 2 bit 7 và 6 thì sẽ đơn giản hơn). Thanh ghi thứ (DAY – ngày trong tuần): nằm ở địa chĩ 0x03. Thanh ghi DAY chỉ mang giá trị từ 1 đến 7 tương ứng từ Chủ nhật đến thứ 7 trong 1 tuần. Vì thế, chỉ có 3 bit thấp trong thanh ghi này có nghĩa. Các thanh ghi còn lại có cấu trúc tương tự, DATE chứa ngày trong tháng (1 đến 31), MONTH chứa tháng (1 đến 12) và YEAR chứa năm (00 đến 99). Chú ý, DS1307 chỉ dùng cho 100 năm, nên giá trị năm chỉ có 2 chữ số, phần đầu của năm do người dùng tự thêm vào (ví dụ 20xx). Ngoài các thanh ghi trong bộ nhớ, DS1307 còn có một thanh ghi khác nằm riêng gọi là con trỏ địa chỉ hay thanh ghi địa chỉ (Address Register). Giá trị của thanh ghi này là địa chỉ của thanh ghi trong bộ nhớ mà người dùng muốn truy cập. Giá trị của thanh ghi địa chỉ (tức địa chỉ của bộ nhớ) được set trong lệnh Write mà chúng ta sẽ khảo sát trong phần tiếp theo, AVR và DS1307. Thanh ghi địa chỉ được tôi tô đỏ trong hình 6, cấu trúc DS1307.

Page 84: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 84

Hình 6. Cấu trúc DS1307.

II. AVR và DS1307.

Phần này tôi hướng dẫn lập trình điều khiển và giao tiếp với DS1307 bằng AVR, dùng WinAVR. Do DS1307 hoạt động như một Slave I2C, bạn nhất thiết phải đọc lại “Bài 8 - Giao tiếp TWI-I2C”, nhất là là 2 chế độ Master (Send và Reveive). Tôi sẽ không đề cập lại toàn bộ giao diện I2C nhưng tóm tắt cách thực hiện với AVR như sau: để thực hiện cuộc gọi ở chế độ Master, AVR sẽ gởi điều kiện START, tiếp theo là 7 bit địa chỉ Slave (SLA) +1 bit Write/Read, kế đến là quá trình đọc hay ghi dữ liệu giữa Master và Slave bằng các byte dữ liệu 8 bit (có thể chỉ 1 byte hoặc 1 dãy bytes), cứ sau mỗi byte sẽ có 1 bit ACK hoặc NOT ACK. Cuộc gọi kết thúc với việc Master phát điều kiện STOP. Cứ mỗi một quá trình, sẽ có 1 “code” được sinh ra trong thanh ghi trạng thái TWSR, kiểm tra giá trị code này để biết quá trình giao tiếp có thành công không. Bạn cần nhơ dãy code thành công khi Master truyền dữ liệu là: 0x08 -> 0x18 -> 0x28 ->…->0x28. Và dãy code thành công khi Master truyền dữ liệu là 0x08 - > 0x40 - > 0x50 ->…->0x50 -> 0x58. Nắm được cách ghi và đọc của AVR Master là bạn đã nắm được 50% cách giao tiếp với DS1307, 50% còn lại chúng ta phải hiểu cách bố trí dãy dữ liệu của riêng DS1307. Hãy theo dõi phần tiếp theo.. Vì DS1307 là một Slave I2C nên chỉ có 2 mode (chế độ) hoạt động giao tiếp với chip này. Hai mode của DS1307 bao gồm Data Write (từ AVR đến DS14307) và Data Read (từ DS1307 vào AVR). Mode Data Write được dùng khi xác lập giá trị ban đầu cho các thanh ghi thời gian hoặc dùng để canh chỉnh thời gian. Trong chế độ này,

Page 85: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 85

AVR là 1 Master truyền dữ liệu đến DS1307 (Slave nhận dữ liệu). Mode Data Read được sử dụng khi đọc thời gian từ đồng hồ DS1307 vào AVR để hiển thị hoặc so sánh….Trong chế độ này, AVR là Master nhận dữ liệu và DS1307 là Slave truyền dữ liệu. Hình 7 mô tả cấu trúc dữ liệu trong chế độ Data Write.

Hình 7. Chế độ Data Write.

Trước hết hãy nói về địa chỉ Slave Address (SLA) của DS1307 trong mạng I2C. Như chúng ta đều biết, trên mạng I2C mỗi thiết bị sẽ có một địa chỉ riêng gọi là SLA. SLA là con số 7 bit, như thế theo lý thuyết sẽ có tối đa 128 thiết bị trong 1 mạng I2C. Chip DS1307 là một I2C Slave nên cũng có một địa chỉ SLA, giá trị này được set cố định là 1101000 nhị phân, hay 0x68 thập lục phân. Do SLA của DS1307 cố định nên trong 1 mạng I2C sẽ không thể tồn tại cùng lúc 2 chip này (điều này thực sự không cần thiết) nhưng có thể tồn tại các thiết bị I2C khác hoặc tồn tại nhiều Master AVR. Quan sát hình 7, sau khi điều kiện START được gởi bởi Master (AVR) sẽ là 7 bit địa chỉ SLA của DS1307 (1101000). Do chế độ này là Data Write nên bit W (0) sẽ được gởi kèm sau SLA. Bit ACK (A) được DS1307 trả về cho Master sau mỗi quá trình giao tiếp. Tiếp theo sau địa chỉ SLA sẽ là 1 byte chứa địa chỉ của thanh ghi cần truy cập (tạm gọi là Addr_Reg). Cần phân biệt địa chỉ thanh ghi cần truy cập và địa chỉ SLA. Như tôi đã đề cập trên, địa chỉ của thanh ghi cần tuy cập sẽ được lưu trong thanh ghi địa chỉ (hay con trỏ địa chỉ), vì vậy byte dữ liệu đầu tiên sẽ được chứa trong thanh ghi địa chỉ của DS1307. Sau byte địa chỉ thanh ghi là một dãy các byte dữ liệu được ghi vào bộ nhớ của DS1307. Byte dữ liệu đầu tiên sẽ được ghi vào thanh ghi có địa chỉ được chỉ định bởi Addr_Reg, sau khi ghi 1 byte, Addr_Reg được tự động tăng nên các byte tiếp theo sẽ được ghi liên tiếp vào các thanh ghi kế sau. Số lượng bytes dữ liệu cần ghi do Master quyết định và không được vượt quá dung lương bộ nhớ của DS1307. Ví dụ sau khi gởi SLA+W, Master gởi 8 bytes gồm 1 byte đầu 0x00 và 7 bytes khác thì con trỏ địa chỉ sẽ trỏ đến thanh ghi đầu tiên (0x00 – thanh ghi SECONDS) và ghi liên tiếp 7 bytes vào 7 thanh ghi thời gian của SD1307. Đây là cách mà chúng ta sẽ thực hiện trong phần lập trình giao tiếp ( xem chương trình con TWI_DS1307_wblock phía sau). Quá trình ghi kết thúc khi Master phát ra điều kiện STOP. Chú ý, nếu sau khi gởi byte Addr_Reg, Master không gởi các bytes dữ liệu mà gởi liền điều kiện STOP thì không có thanh ghi nào được ghi. Trường hợp này được dùng để set địa chỉ Addr_Reg phục vụ cho quá trình đọc. Tiếp theo, chúng ta khảo sát cách sắp xếp dữ liệu trong chế độ Data Read, xem hình 8.

Page 86: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 86

Hình 8. Chế độ Data Read.

Trong chế độ Data Read, bit R (1) được gởi kèm sau 7 bit SLA. Sau đó là liên tiếp các byte dữ liệu được truyền từ DS1307 đến AVR. Điểm khác biệt trong các bố trí dữ liệu của chế độ này so với chế độ Data Write là không có byte địa chỉ thanh ghi dữ liệu được gởi đến. Tất cả các bytes theo sau SLA+R đều là dữ liệu đọc từ bộ nhớ của DS1307. Vậy thì dữ liệu được đọc bắt đầu từ thanh nào? Câu trả lời đó là thanh ghi được chỉ định bởi con trỏ địa chỉ, giá trị này được lưu lại trong các lần thao tác trước đo. Như vậy, muốn đọc chính xác dữ liệu từ một địa nào đó, chúng ta cần thực hiện quá trình ghi giá trị cho con trỏ địa chỉ trước. Để ghi giá trị vào con trỏ địa chỉ chúng ta sẽ gọi chương trình Data Write với chỉ 1 byte được ghi sau SLA+W như phần chú ý ở trên. Chúng ta đã chuẩn bị đầy đủ để giao tiếp với DS1307. Phần tiếp theo tôi sẽ trình bày chương trình và mô phỏng giao tiếp giữa AVR và DS1307. Hãy vẽ một mạch điện bằng Proteus như trong hình 9. Trong ví dụ này, ban đầu chúng ta sẽ cài đặt thời gian cho DS1307, sau đó tiến hành đọc thời gian từ chip đồng hồ này và hiển thị lên 1 Text LCD.

Page 87: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 87

Hình 9. Ví dụ giao tiếp AVR – DS1307.

Tôi sẽ chia chương trình thành 2 phần, phần giao tiếp với DS1307 thông qua I2C được viết trong file myDS1307RTC.h và phần ví dụ ghi-đọc, hiển thị được viết trong file DS1307RTC_Test.c.

Page 88: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 88

List 1. myDS1307RTC.h.

Page 89: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 89

Các phần định nghĩa trước dòng 35 được trích từ bài TWI nên tôi không giải thích lại. Chúng ta bắt đầu từ dòng 36. Có 3 chương trình con được viết để giao tiếp giữa AVR với DS1307 đó là: ghi 1 dãy dữ liệu vào DS1307 tức chương trình con TWI_DS1307_wblock(uint8_t Addr, uint8_t Data[], uint8_t len), chương trình này được viết theo cách sắp xếp dữ liệu của chế độ Data Write trình bày ở trên. Chương trình con đọc dữ liệu từ DS1307 là TWI_DS1307_rblock(uint8_t Data[], uint8_t len ) và một chương trình con dùng để set địa chỉ thanh ghi cần truy cập có tên

Page 90: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 90

TWI_DS1307_wadr(uint8_t Addr). Chương trình con TWI_DS1307_wblock(uint8_t Addr, uint8_t Data[], uint8_t len) nằm từ dòng 54 đến dòng 77. Trong chương trình con này, tham số Addr là địa chỉ thanh ghi cần truy cập, Data[] là mảng dữ liệu sẽ ghi vào DS1307 và len là số byte dữ liệu sẽ ghi (không tính byte Addr). Dòng 55, AVR phát ra điều kiện START để bắt 1 cuộc gọi I2C, sau đó chúng ta chờ cho bit TWINT được set lên 1 ở dòng 56 (TWINT = 1, công việc đã được thực hiện). Dòng 57 kiểm tra nếu điều kiện START đã gởi thành công hay không bằng cách so sánh thanh ghi trạng thái TWSR với “code” tương ứng (xem lại hình 2 trong bài giao tiếp TWI). Sau khi START được gởi, dòng 59 chúng ta gán địa chỉ SLA+W cho thanh ghi dữ liệu TWDR để phát ra trên I2C, TWDR=(DS1307_SLA<<1)+TWI_W. Trong dòng này, biến DS1307_SLA là SLA của DS1307 đã được định nghĩa trước ở dòng 15 trong khi TWI_W là bit W (=0) được định nghĩa ở dòng 20. Quá trình phát I2C chỉ bắt đầu khi bit TWINT được xóa, dòng 60 thực hiện việc này, sau đó phải chờ bit TWINT được set lên 1 chứng tỏ quá trình phát SLA kết thúc (dòng 61). Cuối cùng là kiểm tra code trong thanh ghi TWSR để xem quá trình phát SLA có thanh công, xem dòng 62 và hình 2 trong bài giao tiếp TWI. Chúng ta sẽ luôn theo cơ chế này khi làm việc với TWI của AVR, do đó trong các phần tiếp theo tôi chỉ giải thích nội dung truyền-nhận, không giải thích lại cơ chế. Sau khi phát SLA+W, các dòng 64 đến 65 phát địa chỉ thanh ghi cần truy cập (biến Addr) và sau đó phát mảng dữ liệu liên tiếp trong các dòng 69 đến 74. Cuối cùng là phát điện kiện STOP để kết thúc cuộc gọi. Trong chương trình con ghi DS1307 trình bày ở trên, nếu tham số len=0 thì các dòng 69 đến 74 không được thực hiện, nghĩa là chỉ có địa chỉ Addr được phát mà không có dữ liệu nào kèm theo. Chúng ta có thể dùng đặc điểm này để set thanh ghi cho quá trình đọc. Tôi đã tách ra và viết thành 1 chương trình con tên TWI_DS1307_wadr(uint8_t Addr) trong các dòng từ 36 đến 52 dùng để thực hiện việc set địa chỉ này. Chương trình con đọc DS1307 TWI_DS1307_rblock(uint8_t Data[], uint8_t len ) được trình bày trong các dòng từ 79 đến 99. Trong đó, tham số Data[] là mảng chứa dữ liệu đọc về, len là số bytes đọc về, đặc biệt không có tham số địa chỉ thanh ghi vì địa chỉ này sẽ được set riêng trước khi gọi chương trình con đọc DS1307. Dòng 84 một lệnh phát SLA+TWI_R được thực hiện, với bit TWI_R=1 (xem định nghĩa ở dòng 21), AVR đang báo cho DS1307 rằng nó muốn đọc dữ liệu từ DS1307. Quá trình đọc được chia thành 2 phần, trong phần 1 chúng ta đọc len-1 bytes đầu tiên (xem các dòng code từ 88 đến 92) và phần 2 đọc byte cuối cùng (dòng 94 đến 96). Chúng ta cần tách việc đọc byte cuối ra vì nếu nhìn lại chế độ đọc trình bày trong hình 8, sau mỗi byte được đọc, Master phải gởi 1 bit ACK đến DS1307, riêng byte cuối cùng Master phải gởi bit NOT ACK để báo DS1307 rằng Master không muốn đọc thêm (so sánh 2 dòng 89 và 94). Cuối cùng, Master gởi điều kiện STOP để kết thúc cuộc gọi. Để kiểm tra các hàm giao tiếp DS1307, hãy tạo 1 Project bằng WinAVR với tên gọi DS1307RTC_Test, tạo file DS1307RTC_Test và viết code như trong list 2. List 2. DS1307RTC_Test.c.

Page 91: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 91

Page 92: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 92

Page 93: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 93

Chương trình demo DS1307 dùng các hàm trong file DS1307RTC.h trước đó, bạn cần copy file này vào cùng thư mục với chương trình demo này. Đồng thời, chép cả file myLCD.h vì ví dụ này có hiển thị LCD. Cơ chế của chương trình demo như sau: trong phần thân chương trình chính, ban đầu chúng ta ghi các thông số thời gian khởi tạo cho DS1307, tôi chọn thời điểm ghi vào là 11h:59p:55s của ngày 31, tháng 12 năm 09 (2009) cho mục đích kiểm tra. Với thời điểm này, sau khi chạy chương trình được 5s bạn sẽ thấy các thanh thời gian trong DS1307 tự động chuyển sang 0h:0p:0s ngày 1 tháng 1 năm 10. Chú ý là nguồn clock cho chip trong ví dụ này là 8MHz, Tôi dùng Timer0 để tạo ra 1 khoảng thời gian delay khoảng 32.7ms, cứ 10 lần ngắt Timer0 (tức khoảng 327ms) tôi sẽ đọc DS1307 và cập nhật kết quả lên LCD. Các biến phụ Second, Minute, Hour, Day, Date, Month, Year được khai báo ở dòng 8 và 9 chứa thời gian (số thập phân bình thường). Biến Mode chọn hệ thống giờ, Mode =0 là hệ thống 24h và Mode=1 là hệ thống 12h. Biến AP chứa buổi trong Mode 12h, AP=0 là buổi sáng (AM), AP=1 là buổi chiều (PM). Mảng tData[7] có 7 phần tử trong dòng 14 chứa 7 bytes tạm tương ứng với 7 thanh ghi thời gian để ghi vào DS1307 hoặc đọc ra từ chip này. Các dòng từ 17 đến 28 là 2 chương trình con đổi từ số BCD sang thập phân và ngược lại. Chúng ta bắt đầu với chương trình con Display (void), hiển thị kết quả chứa trong mảng tData[7] lên LCD (dòng 30 đến 64). Các dòng từ 31 đến 37 dùng đọc giá trị trong mảng tData[7] ra các biến để hiển thị, vì tData[7] chứa giá trị đọc về từ các thanh ghi thời gian của DS1307 nên nó là các số BCD, chúng ta cần dùng hàm BCD2Dec để đổi sang số thập phân trước khi gán cho các biến như Second, Minute…hiển thị lên LCD. Riêng với thanh ghi HOURS (tương ứng với sData[2]) chúng ta cần kiểm tra hệ thống giờ, nếu là hệ thống 12h thì chỉ lấy 5 bit đầu của thanh ghi này gán cho biến Hour (xem lại phần tổ chức các thanh ghi thời gian ở hình 4), nếu là hệ thống 24h thì sẽ lấy 6 bit (xem 2 dòng 33 và 34). Các dòng từ 39 đến 64 in các biến thời gian lên LCD. Dòng đầu tiên của LCD dùng in giờ-phút-giây, dòng thứ 2 in năm-tháng-ngày. Phần bố trí vị trí các giá trị in người đọc tự lý giải. Chương trình chính main bắt đầu từ dòng 66 và kết thúc ở dòng 106. Các công việc thực hiện trong main bao gồm khởi động Text LCD, khởi động Timer0 ở chế độ thường, Prescaler=1024 và cho phép ngắt tràn (các dòng từ 77 đến 79). Với f=8MHz, giá trị định thì mỗi lần tràn Timer0 là : (1024(Prescaler)/8 (f))*256 (MAX)=32768 us =32.7ms. Các dòng từ 83 đến 90 gán giá trị các biến thời gian vào mảng tData để chuẩn bị ghi vào DS1307. Trước khi gán các biến này cho tData, chúng ta cần đổi giá trị thập phân của chúng thành BCD với hàm Dec2BCD. Dòng 91 khởi động I2C và dòng 92 ghi 7 phần tử của mảng tData vào DS1307 với hàm TWI_DS1307_wblock mà chúng ta đã định nghĩa trong file DS1307RTC.h. Chú ý là địa chỉ bắt đầu ghi là 0x00, vì thế 7 bytes của mảng tData sẽ được ghi chính xác vào 7 thanh ghi thời gian của DS1307. Sau khi ghi dữ liệu, cần 1 khoảng thời gian nhỏ để DS1307 xử lí, _delay_ms(1) là đủ. Các dòng từ 97 đến 100 tiến hành đọc thời gian từ DS1307 về và hiển thị lên LCD. Dòng 97 TWI_DS1307_wadr(0x00) dùng để set địa chỉ thanh ghi cần truy cập trước khi đọc, chúng ta muốn đọc hết 7 thanh ghi thời gian nên sẽ set địa chỉ về 0 (thanh ghi SECONDS). Phải delay 1 khoảng nhỏ trước khi tiếp tục đọc DS1307 (dòng 98). Dòng 99 chúng ta đọc 7 thanh ghi thời gian vào mảng tData và

Page 94: AVR - Mot so Ung dung

Copyright © 2010 Cùng học AVR Thanhtam Ho - www.hocavr.com

Created by QuocHuy Hoang

[email protected] Page 94

hiển thị lên LCD ở dòng 100. Chương trình chính kết thúc ở đây, việc còn lại cho trình phục vụ ngắt thực hiện. Trong trình phục vụ ngắt tràn của Timer0 (từ dòng 107 đến 125), chúng ta tăng 1 biến tạm tên là Time_count, đến khi nào 10 ngắt xảy ra (khoảng 327ms) thì mới tiến hành đọc DS1307 một lần (các dòng từ 111 đến 113). Do cứ mỗi 327ms chúng ta đọc DS1307 1 lần nên sẽ có trường hợp 2 lần đọc cùng 1 giá trị, chúng ta chỉ thực hiện việc cập nhật kết quả khi 1 giây đã qua. Dòng 115 so sánh kết quả đọc về với biến Second, tức là so sánh kết quả mới với kết quả cũ, nếu chúng khác nhau sẽ cập nhật giá trị giây trên LCD (các dòng từ 116 đến 119). Chúng ta điều biết việc ghi lên LCD sẽ tốn khá nhiều thời gian, vì vậy chỉ nên cập nhật kết quả khi nào có sự thay đổi. Mặt khác, khi số giây thay đổi thì các biến thời gian khác thay đổi rất chậm, một cách tốt để tránh việc xóa và ghi LCD nhiều lần là cứ 60s hãy thực hiện hàm Display (trong hàm này có cả xóa và ghi các biến thời gian). Dòng 120 giúp thực hiện ý tưởng này, chỉ khi nào biến Second về 0 (đã qua 60s) mới gọi hàm Display(). Đến đây, toàn bộ việc truy cập DS1307 bằng AVR đã hoàn tất. Các ý tưởng mở rộng ứng dụng như thêm các nút chỉnh thời gian, cài đặt báo giờ…xin nhường lại cho bạn đọc tự phát triển.