Tải bản đầy đủ (.docx) (58 trang)

BÀI BÁO CÁO THỰC TẬP-CHƯƠNG 1. Bộ đếm-bộ định thời trong 8051

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

CHƯƠNG 1. Bộ đếm/ bộ định thời trong 8051
1.1. Mục tiêu
Kết thúc bài học này, bạn sẽ nắm được:
 Bộ đếm, bộ định thời là gì?
 Các thanh ghi liên quan
 Cách thức hoạt động của bộ đếm/bộ định thời
 Các bước lập trình bộ đếm/bộ định thời
1.2. Giới thiệu
Bộ đếm/Bộ định thời: Đây là các ngoại vi được thiết kế để thực hiện một nhiệm vụ
đơn giản: đếm các xung nhịp. Mỗi khi có thêm một xung nhịp tại đầu vào đếm thì giá trị
của bộ đếm sẽ được tăng lên 01 đơn vị (trong chế độ đếm tiến/đếm lên) hay giảm đi 01
đơn vị (trong chế độ đếm lùi/đếm xuống).
Xung nhịp đưa vào đếm có thể là một trong hai loại:
 Xung nhịp bên trong IC: Đó là xung nhịp được tạo ra nhờ kết hợp mạch dao động bên
trong IC và các linh kiện phụ bên ngoài nối với IC. Trong trường hợp sử dụng xung nhịp
loại này, người ta gọi là các bộ định thời (timers). Do xung nhịp bên loại này thường
đều đặn nên ta có thể dùng để đếm thời gian một cách khá chính xác.
 Xung nhịp bên ngoài IC: Đó là các tín hiệu logic thay đổi liên tục giữa 02 mức 0-1 và
không nhất thiết phải là đều đặn. Trong trường hợp này người ta gọi là các bộ
đếm (counters). Ứng dụng phổ biến của các bộ đếm là đếm các sự kiện bên ngoài như
đếm các sản phầm chạy trên băng chuyền, đếm xe ra/vào kho bãi…
Một khái niệm quan trọng cần phải nói đến là sự kiện “tràn” (overflow). Nó được
hiểu là sự kiện bộ đếm đếm vượt quá giá trị tối đa mà nó có thể biểu diễn và quay trở về
giá trị 0. Với bộ đếm 8 bit, giá trị tối đa là 255 (tương đương với FF trong hệ Hexa) và là
65535 (FFFFH) với bộ đếm 16 bit.
8051 có 02 bộ đếm/bộ định thời. Chúng có thể được dùng như các bộ định thờiđể
tạo một bộ trễ thời gian hoặc như các bộ đếm để đếm các sự kiện xảy ra bên ngoài bộ
VĐK. Trong bài này chúng ta sẽ tìm hiểu về cách lập trình cho chúng và sử dụng chúng
như thế nào. Phần 1 là Lập trình bộ định thời, và phần 2 là Lập trình cho bộ đếm.
1.3. Các bộ định thời của 8051
8051 có hai bộ định thời là Timer 0 và Timer 1, ở phần này chúng ta bàn về các


thanh ghi của chúng và sau đó trình bày cách lập trình chúng như thế nào để tạo ra các độ
trễ thời gian.
1.3.1. Các thanh ghi cơ sở của bộ định thời
Cả hai bộ định thời Timer 0 và Timer 1 đều có độ dài 16 bit được truy cập như
hai thanh ghi tách biệt byte thấp và byte cao. Chúng ta sẽ bàn riêng về từng thanh ghi.
1.3.2. Các thanh ghi của bộ Timer 0
Thanh ghi 16 bit của bộ Timer 0 được truy cập như byte thấp và byte cao:
 Thanh ghi byte thấp được gọi là TL0 (Timer0 Low byte).
 Thanh ghi byte cao được gọi là TH0 (Timer0 High byte).
Các thanh ghi này có thể được truy cập, hoặc được đọc như mọi thanh ghi khác chẳng
hạn như A, B, R0, R1, R2 v.v
Hình 1: Các thanh ghi của bộ Timer 0
1.3.3. Các thanh ghi của bộ Timer 1
Giống như timer 0, bộ định thời gian Timer 1 cũng dài 16 bit và thanh ghi 16 bit
của nó cũng được chia ra thành hai byte là TL1 và TH1. Các thanh ghi này được truy cập
và đọc giống như các thanh ghi của bộ Timer 0 ở trên.
Hình 2: Các thanh ghi của bộ Timer 1.
1.3.4. Thanh ghi TMOD
Cả hai bộ định thời Timer 0 và Timer 1 đều dùng chung một thanh ghi được gọi
là TMOD: để thiết lập các chế độ làm việc khác nhau của bộ định thời.
Thanh ghi TMOD là thanh ghi 8 bit gồm có:
 4 bit thấp để thiết lập cho bộ Timer 0.
 4 bit cao để thiết lập cho Timer 1.
Trong đó:
 2 bit thấp của chúng dùng để thiết lập chế độ của bộ định thời.
 2 bit cao dùng để xác định phép toán.

Hình 3: Thanh ghi TMOD.
a. Các bit M1, M0
Là các bit chế độ của các bộ Timer 0 và Timer 1. Chúng chọn chế độ của các bộ

định thời: 0, 1, 2 và 3 như bảng dưới. Chúng ta chỉ tập chung vào các chế độ thường
được sử dụng rộng rãi nhất là chế độ 1 và chế độ 2. Chúng ta sẽ sớm khám phá ra các
đặc tính của các chế độ này sau khi khám phần còn lại của thanh ghi TMOD. Các chế độ
được thiết lập theo trạng thái của M1 và M0 như sau:
M1 M0 Chế độ Chế độ hoạt động
0 0 0 Bộ định thời 13 bit:8 bit là bộ định thời/bộ đếm, 5 bit đặt trước.
0 1 1 Bộ định thời 16 bit: không có đặt trước.
1 0 2 Bộ định thời 8 bit: tự nạp lại.
1 1 3 Chế độ bộ định thời chia tách.
Bảng 1: Các chế độ hoạt động của bộ đếm/bộ định thời
b. Bit C/T (Counter/Timer)
Bit này trong thanh ghi TMOD được dùng để quyết định xem bộ định thời được
dùng như một máy tạo độ trễ hay bộ đếm sự kiện. Nếu bit C/T = 0 thì nó được dùng như
một bộ định thời tạo độ trễ thời gian.
Ví dụ 1:
TMOD = 0000 0001 (01H) : chế độ 1 của bộ định thời Timer 0 được chọn.
TMOD = 0010 0000 (20H) : chế độ 2 của bộ định thời Timer 1 được chọn.
TMOD = 0001 0010 (12H) : chế độ 1 của bộ định thời Timer 1 và chế độ 2 của Timer 0
được chọn.
Nguồn đồng hồ cho chế độ trễ thời gian là tần số thạch anh của 8051. Điều đó có
nghĩa là độ lớn của tần số thạch anh đi kèm với 8051 quyết định tốc độ nhịp của các bộ
định thời trên 8051. Tần số của bộ định thời luôn bằng 1/12 tần số của thạch anh gắn
với 8051.
Hình 4: Tần số của bộ đếm/bộ định thời
Ví dụ 2:
Tần số thạch anh Tần số bộ định thời Chu kỳ bộ định thời
20MHz 20MHz/12=1,6666MHz 1/1,6666MHz=0,6us
12MHz 12MHz/12=1MHz 1/1MHz=1us
11,0592MHz 11,0592MHz/12=0,9216MHz 1/0,9216MHz=1,085us
Bảng 2: Một số tần số thông dụng

Mặc dù các hệ thống 8051 có thể sử dụng tần số thạch anh từ 10 đến 40MHz,
song ta chỉ tập trung vào tần số thạch anh 11,0592MHz. Lý do đằng sau một số lẻ như
vậy là tốc độ baud đối với truyền thông nối tiếp của 8051. Tần số XTAL = 11,0592MHz
cho phép hệ thống 8051 truyền thông với PC mà không có lỗi.
c. Bit cổng GATE
Một bit khác của thanh ghi TMOD là bit cổng GATE. Để ý trên hình 3 ta thấy cả
hai bộ định thời Timer0 và Timer1 đều có bit GATE. Vậy bit GATE dùng để làm gì?
Mỗi bộ định thời thực hiện điểm khởi động và dừng. Một số bộ định thời thực hiện điều
này bằng phần mềm, một số khác bằng phần cứng và một số khác vừa bằng phần cứng
vừa bằng phần mềm. Các bộ định thời trên 8051 có cả hai:
 Việc khởi động và dừng bộ định thời được khởi động bằng phần mềm bởi cácbit khởi
động bộ định thời TR là TR0 và TR1. Điều này có được nhờ các lệnh Set
bit TR0 lên 1 (khởi động bộ định thời) hoặc Clear bit TR0 (dừng bộ định thời) đối
với Timer 0, và tương tự TR1 đối với Timer 1. Các lệnh này có tác dụng khi bit
GATE = 0 trong thanh ghi TMOD.
 Việc khởi động và ngừng bộ định thời bằng phần cứng từ nguồn ngoài bằng cách đặt
bit GATE = 1 trong thanh ghi TMOD.
Tuy nhiên, để tránh sự lẫn lộn ngay từ bây giờ ta đặt GATE = 0 có nghĩa là không
cần khởi động và dừng các bộ định thời bằng phần cứng từ bên ngoài.
Ví dụ 3:
TMOD = 0000 0010: Bộ định thời là Timer0, chế độ 2, C/T = 0 dùng nguồn
XTAL, GATE = 0 dùng phần mềm để khởi động và dừng bộ định thời.
Như vậy, bây giờ chúng ta đã có hiểu biết cơ bản về vai trò của thanh ghi TMOD,
chúng ta sẽ xét từng chế độ của bộ định thời và cách chúng được lập trình như thế nào để
tạo ra một độ trễ thời gian.
1.4. Lập trình cho chế độ 1
Dưới đây là những bước hoạt động của timer ở chế độ 1:
 Đây là bộ định thời 16 bit, do vậy nó cho phép các giá trị 0000 đến FFFFHđược nạp vào
các thanh ghi TL và TH của bộ định thời.
 Sau khi TL và TH được nạp một giá trị khởi tạo 16 bit thì bộ định thời phải được khởi

động. Điều này được thực hiện bởi việc SET bit TR0 đối vớiTimer 0 và SET
bit TR1 đối với Timer 1.
 Sau khi bộ định thời được khởi động, nó bắt đầu đếm lên. Nó đếm lên cho đến khi đạt
được giới hạn FFFFH của nó. Sau đó, khi nó quay từ FFFFH về 0000thì nó bật lên bit
cờ TF được gọi là cờ bộ định thời. Cờ bộ định thời này có thể được hiển thị. Khi cờ bộ
định thời này được thiết lập, để dừng bộ định thời: ta thực hiện xóa các bit TR0 đối
với Timer 0 hoặc TR1 đối với Timer 1. Ở đây cũng cần phải nhắc lại là đối với mỗi bộ
định thời đều có cờ TF riêng của mình: TF0 đối với Timer 0 và TF1 đối với Timer 1.
 Sau khi bộ định thời đạt được giới hạn của nó là giá trị FFFFH, muốn lặp lại quá trình thì
các thanh ghi TH và TL phải được nạp lại với giá trị ban đầu và cờTF phải được xóa về
0.
Hình 5: Timer/counter chế độ 1
1.4.1. Các bước lập trình ở chế độ 1
Để tạo ra một độ trễ thời gian dùng chế độ 1 của bộ định thời thì cần phải thực
hiện các bước dưới đây:
1. Nạp giá trị TMOD cho thanh ghi báo độ định thời nào (Timer0 hay Timer1) được sử
dụng và chế độ nào được chọn.
2. Nạp các thanh ghi TL và TH với các giá trị đếm ban đầu.
3. Khởi động bộ định thời.
4. Duy trì kiểm tra cờ bộ định thời TF bằng một vòng lặp để xem nó được bật lên 1
không. Thoát vòng lặp khi TF được lên cao.
5. Dừng bộ định thời.
6. Xoá cờ TF cho vòng kế tiếp.
7. Quay trở lại bước 2 để nạp lại TL và TH.
Công thức tính toán độ trễ sử dụng chế độ 1 (16 bit) của bộ định thời đối với tần
số thạch anh XTAL = f (MHz):
a) Tính theo số Hex b) Tính theo số thập phân
(FFFF - YYXX + 1)*12/f (µs) trong đó
YYXX là các giá trị khởi tạo của TH, TL
tương ứng. Lưu ý rằng các giá trị YYXX là

theo số Hex.
Chuyển đổi các giá trị YYXX của TH, TL
về số thập phân để nhận một số thập phân
NNNNN sau đó lấy (65536 –
NNNNN)*12/f (µs).
Bảng 3: Công thức tính độ trễ thời gian theo tần số XTAL (f)
Ví dụ 4:
Trong chương trình dưới đây ta tạo ra một sóng vuông với độ đầy xung 50%
(cùng tỷ lệ giữa phần cao và phần thấp) trên chân P1.5. Bộ định thời Timer0 được dùng
để tạo độ trễ thời gian:
#include<at89x51.h> //khai báo thư viện cho VĐK 89x51
void delay(void); //khi báo nguyên mẫu hàm con tạo trễ
main()
{
P1_5=1; //khởi tạo chân P1_5 ở mức cao
while(1) //vòng lặp vô hạn
{
delay(); //chương trình con tạo trễ
P1_5=~P1_5; //đảo tín hiệu chân P1_5
}
}
void delay(void) //định nghĩa hàm delay
{
TMOD=0x01; //chọn timer0, chế độ 1, 16Bit
TL0=0xF2; //nạp giá trị cho TL0
TH0=0xFF; //nạp giá trị cho TH0
TR0=1; //khởi động timer0
while(!TF0){} //vòng lặp kiểm tra cờ TF0
TR0=0; //ngừng timer0
TF0=0; //xóa cờ TF0

}
Trong chương trình chính (hàm main) thực hiện gọi hàm con delay() tạo trễ, và
đảo liên tục tín hiệu đầu ra ở chân P1_5.
Trong chương trình con delay() trên đây chú ý các bước sau:
1. TMOD được nạp.
2. Giá trị FFF2H được nạp và TH0 - TL0
3. Bộ định thời Timer0 được khởi động bởi lệnh Set bit TR0.
4. Bộ Timer0 đếm lên 01 sau mỗi chu kỳ của timer. Khi bộ định thời đếm tăng qua các
trạng thái FFF3, FFF4 cho đến khi đạt giá trị FFFFH là nó quay về 0000H và bật cờ bộ
định thời TF0 = 1. Tại thời điểm này vòng lặp kiểm tra cờ TF0 mới được thoát ra.
5. Bộ Timer0 được dừng bởi lệnh clear bit TR0.
6. Cờ TF0 cũng được xóa, sẵn sàng cho chu trình tiếp theo.
Lưu ý rằng để lặp lại quá trình trên ta phải nạp lại các thanh ghi TH và TL và khởi
động lại bộ định thời (đơn giản là ta gọi lại hàm delay()).
Hình 6: Một chu trình đếm của timer0
Tính toán độ trễ tạo ra bởi bộ định thời ở chương trình trên với tần số
XTAL=11,0592MHz:
Bộ định thời làm việc với tần số đồng hồ bằng 1/12 tần số XTAL, do vậy ta có
11,0592MHz/12=0,9216MHz là tần số của bộ định thời. Kết quả là mỗi nhịp xung đồng
hồ có chu kỳ T=1/0,9216MHz=1,085us. Hay nói cách khác, bộ Timer0 tăng 01 đơn vị sau
1,085µs để tạo ra bộ trễ bằng số_đếm×1,085µs.
Số đếm bằng FFFFH - FFF2H = ODH (13 theo số thập phân). Tuy nhiên, ta phải
cộng 1 vào 13 vì cần thêm một nhịp đồng hồ để nó quay từ FFFFH về 0000H và bật cờ
TF. Do vậy, ta có 14 × 1,085µs = 15,19µs cho nửa chu kỳ và cả chu kỳ là T = 2 × 15,19µs
= 30, 38µs là thời gian trễ được tạo ra bởi bộ định thời.
Tuy nhiên, trong tính toán độ trễ ở trên ta đã không tính đến tổng phí các lệnh cài
đặt timer0, các lệnh kiểm tra trong vòng lặp, gọi hàm con… Chính các câu lệnh này làm
cho độ trễ dài hơn, dẫn đến tần số của xung vuông ở đầu ra P1_5 không còn đúng như
tính toán ở trên. Đây là nhược điểm của C trong lập trình VĐK. Tùy vào từng chương
trình biên dịch, mỗi lệnh của C sẽ được biên dịch ra số lệnh ASM khác nhau, để tính toán

chính xác ta phải tính cả tổng phí từng dòng lệnh ASM.
1.4.2. Tìm các giá trị cần được nạp vào bộ định thời
Giả sử rằng chúng ta biết lượng thời gian trễ mà ta cần thì câu hỏi đặt ra là làm
thế nào để tìm ra được các giá trị cần thiết cho các thanh thi TH và TL. Để tính toán các
giá trị cần được nạp vào các thanh ghi TH và TL chúng ta hãy nhìn vào ví dụ sau với việc
sử dụng tần số dao động XTAL = 11. 0592MHz đối với hệ thống 8051.
Các bước để tìm các giá trị của các thanh ghi TH và TL:
1. Chia thời gian trễ cần thiết cho 1.085µs
2. Thực hiện 65536 - n với n là giá trị thập phân nhận được từ bước 1.
3. Chuyển đổi kết quả ở bước 2 sang số Hex: ta có YYXX là giá trị Hexa ban đầu cần phải
nạp vào các thanh ghi bộ định thời.
4. Đặt TL = XX và TH = YY.
Ví dụ 5:
Giả sử tần số XTAL = 11.0592MHz. Hãy tìm các giá trị cần được nạp vào các
thanh ghi vào các thanh ghi TH và TL nếu ta muốn độ thời gian trễ là 5ms.
Lời giải:
Vì tần số XTAL = 11.0592MHz nên bộ đếm tăng sau mỗi chu kỳ 1.085µs. Điều
đó có nghĩa là phải mất rất nhiều khoảng thời gian 1,085µs để có được một xung 5ms. Để
có được ta chia 5ms cho 1.085µs và nhận được số n = 4608 nhịp. Để nhận được giá trị
cần được nạp vào TL và TH thì ta tiến hành lấy 65536 trừ đi 4608 bằng 60928. Ta đổi số
này ra số hex thành EE00H. Do vậy, giá trị nạp vào TH là EE Và TL là 00.
void delay(void) //định nghĩa hàm delay
{
TMOD=0x01; //chọn timer0 chế độ 1 16Bit
TL0=0x00; //nạp giá trị cho TL0
TH0=0xEE; //nạp giá trị cho TH0
TR0=1; //khởi động timer0
while(!TF0){} //vòng lặp kiểm tra cờ TF0
TR0=0; //ngừng timer0
TF0=0; //xóa cờ TF0

}
Ví dụ 6:
Giả sử ta có tần số XTAL là 11,0592MHz. Hãy tìm các giá trị cần được nạp vào
các thanh ghi TH và TL để tạo ra một sóng vuông tần số 2kHz.
Xét các bước sau:
1. T = 1/f = 1/2KHz = 500us là chu kỳ của sóng vuông.
2. Khoảng thời gian phần cao và phần thấp là: T/2 = 250µs.
3. Số nhịp cần trong thời gian đó là:250us/1,085us = 230. Giá trị cần nạp vào các thanh ghi
cần tìm là 65536 - 230 = 65306 và ở dạng hex là FF1AH.
4. Giá trị nạp vào TL là 1AH, TH là FFH.
Chương trình cần viết là:
void delay(void) //định nghĩa hàm delay
{
TMOD=0x10; //chọn timer1 chế độ 1 16Bit
TL1=0x1A; //nạp giá trị cho TL1
TH1=0xFF; //nạp giá trị cho TH1
TR1=1; //khởi động timer1
while(!TF1){} //vòng lặp kiểm tra cờ TF1
TR1=0; //ngừng timer1
TF1=0; //xóa cờ TF1
}
1.5. Chế độ 0
Chế độ 0 hoàn toàn giống chế độ 1 chỉ khác là bộ định thời 16 bit được thay
bằng13 bit. Bộ đếm 13 bit có thể giữ các giá trị giữa 0000 đến 1FFFF trong TH - TL.
Do vậy khi bộ định thời đạt được giá trị cực đại của nó là 1FFFH thì nó sẽ quay trở
về 0000và cờ TF được bật lên.
1.6. Lập trình cho chế độ 2
Dưới đây là những bước hoạt động của timer ở chế độ 2:
 Nó là một bộ định thời 8 bit, do vậy nó chỉ cho phép các giá trị từ 00 đến FFHđược nạp
vào thanh ghi TH của bộ định thời.

 Sau khi 2 thanh ghi TH và TL được nạp giá trị ban đầu thì bộ định thời phải được khởi
động.
 Sau khi bộ định thời được khởi động, nó bắt đầu đếm tăng lên bằng cách tăng thanh ghi
TL. Nó đếm cho đến khi đại giá trị giới hạn FFH của nó. Khi nó quay trở về 00 từ FFH,
nó thiết lập cờ bộ định thời TF. Nếu ta sử dụng bộ định thời Timer0 thì đó là cờ TF0,
còn Timer1 thì đó là cờ TF1.
 Khi thanh ghi TL quay trở về 00 từ FFH, cờ TF được bật lên 1 thì thanh ghi TLđược tự
động nạp lại với giá trị sao chép từ thanh ghi TH. Để lặp lại quá trình chúng ta đơn giản
chỉ việc xoá cờ TF và để cho nó chạy mà không cần sự can thiệp của lập trình viên để
nạp lại giá trị ban đầu. Điều này làm cho chế độ 2 được gọi là chế độ tự nạp lại so với
chế độ 1 (phải nạp lại các thanh ghi TH và TL).
Hình 7: Timer/counter chế độ 2
Cần phải nhấn mạnh rằng: chế độ 2 là bộ định thời 8 bit. Tuy nhiên, nó lại có
khả năng tự nạp, khi tự nạp lại thì giá trị ban đầu của TH được giữ nguyên, còn TL
được nạp lại giá trị sao chép từ TH.
Chế độ này có nhiều ứng dụng, bao gồm việc thiết lập tần số baud trong truyền
thông nối tiếp.
1.6.1. Các bước lập trình cho chế độ 2
Để tạo ra một thời gian trễ sử dụng chế độ 2 của bộ định thời cần thực hiện các
bước sau:
1. Nạp thanh ghi giá trị TMOD để báo bộ định thời gian nào (Timer0 hay Timer1) được sử
dụng và chế độ làm việc nào của chúng được chon.
2. Nạp lại thanh ghi TH và TL với giá trị đếm ban đầu.
3. Khởi động bộ định thời.
4. Duy trì kiểm tra cờ bộ định thời TF bằng cách sử dụng một vòng lặp để xem nó đã
được bật chưa. Thoát vòng lặp khi TF lên cao.
5. Dừng bộ định thời.
6. Xoá cờ TF.
7. Quay trở lại bước 3. Vì chế độ 2 là chế độ tự nạp lại.
Ví dụ 7 minh hoạ những điều này:

Ví dụ 7:
#include<at89x51.h> //khai báo thư viện cho VĐK 89x51
void delay(void); //khi báo nguyên mẫu hàm con tạo trễ
main()
{
TMOD=0x20; //chọn timer1, chế độ 2, 8Bit, tự nạp lại
TH1=0x00; //nạp giá trị cho TH1
TL1=0xFE; //nạp giá trị cho TL1
P1_5=1; //khởi tạo chân P1_5 ở mức cao
while(1) //vòng lặp vô hạn
{
delay(); //gọi chương trình con tạo trễ
P1_5=~P1_5; //đảo tín hiệu chân P1_5
}
}
void delay(void) //định nghĩa hàm delay
{
TR1=1; //khởi động timer1
while(!TF1){} //vòng lặp kiểm tra cờ TF1
TR1=0; //ngừng timer1
TF1=0; //xóa cờ TF1
}
Hàm delay() trên sẽ tạo một độ trễ bằng 256 lần (FF - 00 + 1) chu kỳ của timer
(không tính tổng phí các lệnh) kể từ chu trình thứ 2. Vì chu trình đầu tiên timer1 bắt đầu
đếm ở vị trí 0xFE, kể từ chu trình sau thì thanh ghi TL1 mới sao chép được giá trị
ở TH1.
1.7. Bộ đếm
Ở phần trên đây ta đã sử dụng các bộ định thời của 8051 để tạo ra các độ trễ thời
gian. Các bộ định thời này cũng có thể được dùng như các bộ đếm (counter) các sự kiện
xảy ra bên ngoài 8051. Công dụng của bộ đếm sự kiện sẽ được tình bày ở phần này.

Chừng nào còn liên quan đến công dụng của bộ định thời như bộ đếm sự kiện thì mọi vấn
đề mà ta nói về lập trình bộ định thời ở phần trước cũng được áp dụng cho việc lập trình
như là một bộ đếm ngoại trừ nguồn tần số.
Đối với bộ định thời/bộ đếm khi dùng nó như bộ định thời thì nguồn tần số là tần
số thạch anh của 8051. Tuy nhiên, khi nó được dùng như một bộ đếm thì nguồn xung để
tăng nội dung các thanh ghi TH và TL là từ bên ngoài 8051.
Ở chế độ bộ đếm, hãy lưu ý rằng các thanh ghi TMOD và TH, TL cũng giống
như đối với bộ định thời được bàn ở phần trước, thậm chí chúng vẫn có cùng tên gọi. Các
chế độ của các bộ đếm cũng giống nhau.
2.1 Bit C/T trong thanh ghi TMOD
Xem lại phần trên về bit C/T trong thanh ghi TMOD: ta thấy rằng nó quyết định
nguồn xung đồng hồ cho bộ đếm:
 Nếu bit C/T = 0 thì bộ định thời nhận các xung đồng hồ từ bộ giao động thạch anh của
8051.
 Nếu bit C/T = 1 thì bộ định thời được sử dụng như bộ đếm và nhận các xung đồng hồ từ
nguồn bên ngoài của 8051.
Do vậy, nếu bit C/T = 1 thì bộ đếm tăng lên khi các xung được đưa đến chân P3.4
(T0) đối với counter0 và chân P3.5 (T1) đối với counter1.
Chân Chân cổng Chức năng Mô tả
14 P3.4 T0 Đầu vào ngoài của bộ đếm 0
15 P3.5 T1 Đầu vào ngoài của bộ đếm 1
Bảng 4: Các chân cổng P3 được dùng cho bộ đếm 0 và 1
Ví dụ 8:
Chương trình sau sử dụng bộ đếm 1, đếm các xung ở chân P3.5 và hiển thị số đếm
được (trong thanh ghi TL1) lên cổng P2:
#include<at89x51.h> //khai báo thư viện 89x51
main() //chương trình chính
{
TMOD=0x60; //0x60=0110 000 : C/T=1, bộ đếm 1, chế độ 2 tự nạp
TH1=0x00; //xóa bộ đếm ban đầu

P3_5=1; //set chân vào cho bộ đếm
TR1=1; //khởi động bộ đếm 1
while(1) //vòng lặp vô hạn
{
P2=TL1; //hiển thị số đếm được ra cổng P2
}
}
Trong ví dụ 8 chúng ta sử dụng bộ counter1 như bộ đếm sự kiện để nó đếm lên
mỗi khi các xung đồng hồ được cấp đến chân P3.5. Các xung đồng hồ này có thể biểu
diễn số người đi qua cổng hoặc số vòng quay hoặc bất kỳ sự kiện nào khác mà có thể
chuyển đổi thành các xung.
2.2 Thanh ghi TCON
Trong các ví dụ trên đây ta đã thấy công dụng của các cờ TR0 và TR1 để bật/tắt
các bộ đếm/bộ định thời. Các bit này là một bộ phận của thanh ghi TCON. Đây là thanh
ghi 8 bit, như được chỉ ra trong hình 2:
 4 bit trên được dùng để lưu cất các bit TF và TR cho cả Timer/counter 0 và
Timer/counter 1.
 4 bit thấp được thiết lập dành cho điều khiển các ngắt mà ta sẽ bàn ở các bài sau.
Hình 8: Thanh ghi TCON – Điều khiển bộ đếm/bộ định thời
2.3 Trường hợp khi bit GATE = 1 trong TMOD
Trước khi kết thúc bài này ta cần bàn thêm về trường hợp khi bit GATE = 1 trong
thanh ghi TMOD. Tất cả những gì chúng ta vừa nói trong bài này đều giả thiết GATE =
0. Khi GATE = 0 thì bộ đếm/bộ định thời được khởi động bằng các lệnh Set
bit TR0 hoặcTR1. Vậy điều gì xảy ra khi bit GATE = 1?
Nếu GATE = 1 thì việc khởi động và dừng bộ đếm/bộ định thời được thực hiện từ
bên ngoài qua chân P3.2 (INT0) và P3.3 (INT1) đối với Timer/counter 0 và
Timer/counter 1 tương ứng. Phương pháp điều khiển bằng phần cứng để dừng và khởi
động bộ đếm/bộ định thời này có thể có rất nhiều ứng dụng.
Ví dụ: chẳng hạn 8051 được dùng trong một sản phẩm phát báo động mỗi giây dùng bộ
Timer0 theo nhiều việc khác. Bộ Timer0 được bật lên bằng phần mềm qua lệnh Set bit

TR0 và nằm ngoài sự kiểm soát của người dùng sản phẩm đó. Tuy nhiên, khi nối một
công tắc chuyển mạch tới chân P2.3 ta có thể dừng và khởi động bộ định thời, bằng cách
đó ta có thể tắt báo động.
CHƯƠNG 2. Ngắt trong 8051
Mục tiêu
Kết thúc bài học này, bạn có thể:
 Phân biệt cơ chế ngắt với hỏi vòng
 Nắm rõ các loại ngắt trong 8051
• Ngắt timer/counter
• Ngắt ngoài
• Ngắt truyền thông nối tiếp
 Lập trình các ngắt
• Trình phục vụ ngắt là gì?
• Cho phép ngắt và cấm ngắt
• Thiết lập mức ưu tiên của các ngắt
Giới thiệu
Ngắt (Interrupt) - như tên của nó, là một số sự kiện khẩn cấp bên trong hoặc bên
ngoài bộ vi điều khiển xảy ra, buộc vi điều khiển tạm dừng thực hiện chương trình hiện
tại, phục vụ ngay lập tức nhiệm vụ mà ngắt yêu cầu – nhiệm vụ này gọi là trình phục vụ
ngắt (ISR: Interrupt Service Routine).
Trong bài này ta tìm hiểu khái niệm ngắt và lập trình các ngắt trong bộ vi điều
khiển 8051.
1. Các ngắt của 8051
1.1 Phân biệt cơ chế ngắt với hỏi vòng
Lấy ví dụ: Bộ vi điều khiển đóng vai trò như một vị bác sĩ, các thiết bị kiểm soát
bởi vi điều khiển được coi như các bệnh nhân cần được bác sĩ phục vụ.
Bình thường, vị bác sĩ sẽ hỏi thăm lần lượt từng bệnh nhân, đến lượt bệnh nhân
nào được hỏi thăm nếu có bệnh thì sẽ được bác sĩ phục vụ, xong lại đến lượt bệnh nhân
khác, và tiếp tục đến hết. Điều này tương đương với phương pháp thăm dò - hỏi
vòng(Polling) trong vi điều khiển.

Cứ như thế, nếu chúng ta có 10 bệnh nhân, thì bệnh nhân thứ 10 dù muốn hay
không cũng phải xếp hàng chờ đợi 09 bệnh nhân trước đó. Giả sử trường hợp bệnh nhân
thứ 10 cần cấp cứu thì sao? Anh ta sẽ gặp nguy cấp trước khi đến lượt hỏi thăm của bác sĩ
mất!  Nhưng, nếu anh ta sử dụng phương pháp “ngắt” thì mọi chuyện sẽ ổn ngay. Lúc
đó vị bác sĩ sẽ ngừng mọi công việc hiện tại của mình, và tiến hành phục vụ trường hợp
khẩn cấp này ngay lập tức, xong việc bác sĩ lại trở về tiếp tục công việc đang dở. Điều
này tương đương với phương pháp ngắt (Interrupts) trong vi điều khiển.
Trở lại với bộ vi điều khiển của chúng ta: 1 bộ vi điều khiển có thể phục vụ cho
nhiều thiết bị, có 2 cách để thực hiện điều này đó là sử dụng các ngắt (Interrupts) vàthăm
dò (polling):
 Trong phương pháp sử dụng ngắt: mỗi khi có một thiết bị bất kỳ cần được phục vụ thì
nó báo cho bộ vi điều khiển bằng cách gửi một tín hiệu ngắt. Khi nhận được tín
hiệu ngắt thì bộ vi điều khiển ngừng tất cả những gì nó đang thực hiện để chuyển sang
phục vụ thiết bị gọi ngắt. Chương trình ngắt được gọi là trình phục vụ ngắt ISR(Interrupt
Service Routine) hay còn gọi là trình quản lý ngắt (Interrupt handler). Sau khi phục vụ
ngắt xong, bộ vi xử lý lại quay trở lại điểm bị ngắt trước đó và tiếp tục thực hiện công
việc.
 Trong phương pháp thăm dò: bộ vi điều khiển kiểm tra liên tục tình trạng của tất cả các
thiết bị, nếu thiết bị nào có yêu cầu thì nó dừng lại phục vụ thiết bị đó. Sau đó nó tiếp tục
kiểm tra tình trạng của thiết bị kế tiếp cho đến hết. Phương pháp thăm dò rất đơn giản,
nhưng nó lại rất lãng phí thời gian để kiểm tra các thiết bị kể cả khi thiết bị đó không
cần phục vụ. Trong trường hợp có quá nhiều thiết bị thì phương án thăm dò tỏ ra không
hiệu quả, gây ra chậm trễ cho các thiết bị cần phục vụ.
Điểm mạnh của phương pháp ngắt là:
 Bộ vi điều khiển có thể phục vụ được rất nhiều thiết bị (tất nhiên là không tại cùng một
thời điểm). Mỗi thiết bị có thể nhận được sự chú ý của bộ vi điều khiển dựa trênmức ưu
tiên được gán cho nó. Đối với phương pháp thăm dò thì không thể gán mức ưu tiên cho
các thiết bị vì nó kiểm tra tất cả mọi thiết bị theo kiểu hỏi vòng.
 Quan trọng hơn, trong phương pháp ngắt thì bộ vi điều khiển còn có thể che (làm lơ) một
yêu cầu phục vụ của thiết bị. Điều này lại một lần nữa không thể thực hiện được trong

phương pháp thăm dò.
 Lý do quan trọng nhất mà phương pháp ngắt được ưu chuộng là vì nó không lãng phí thời
gian cho các thiết bị không cần phục vụ. Còn phương pháp thăm dò làm lãng phí thời
gian của bộ vi điều khiển bằng cách hỏi dò từng thiết bị kể cả khi chúng không cần phục
vụ.
Ví dụ trong các bộ định thời được bàn đến ở các bài trước ta đã dùng một vòng lặp
kiểm tra và đợi cho đến khi bộ định thời quay trở về 0. Trong ví dụ đó, nếu sử dụng ngắt
thì ta không cần bận tâm đến việc kiểm tra cờ bộ định thời, do vậy không lãng phí thời
gian để chờ đợi, trong khi đó ta có thể làm việc khác có ích hơn.
1.2 Sáu ngắt trong 8051
Thực tế chỉ có 5 ngắt dành cho người dùng trong 8051 nhưng các nhà sản xuất nói
rằng có 6 ngắt vì họ tính cả lệnh RESET. Sáu ngắt của 8051 được phân bố như sau:
1. RESET: Khi chân RESET được kích hoạt từ 8051, bộ đếm chương trình nhảy về địa
chỉ 0000H. Đây là địa chỉ bật lại nguồn.
2. 2 ngắt dành cho các bộ định thời: 1 cho Timer0 và 1 cho Timer1. Địa chỉ tương ứng
của các ngắt này là 000BH và 001BH.
3. 2 ngắt dành cho các ngắt phần cứng bên ngoài: chân 12 (P3.2) và 13 (P3.3) của cổng
P3 là các ngắt phần cứng bên ngoài INT0 và INT1 tương ứng. Địa chỉ tương ứng của các
ngắt ngoài này là 0003H và 0013H.
4. Truyền thông nối tiếp: có 1 ngắt chung cho cả nhận và truyền dữ liệu nối tiếp. Địa chỉ
của ngắt này trong bảng vector ngắt là 0023H.
1.3 Trình phục vụ ngắt
Đối với mỗi ngắt thì phải có một trình phục vụ ngắt (ISR) hay trình quản lý ngắt
để đưa ra nhiệm vụ cho bộ vi điều khiển khi được gọi ngắt. Khi một ngắt được gọi thì bộ
vi điều khiển sẽ chạy trình phục vụ ngắt. Đối với mỗi ngắt thì có một vị trí cố định trong
bộ nhớ để giữ địa chỉ ISR của nó. Nhóm vị trí bộ nhớ được dành riêng để lưu giữ địa chỉ
của các ISR được gọi là bảng vector ngắt. Xem Hình 1.
Hình 1: Bảng vector ngắt của 8051.
Trong lập trình C trên Keil c cho 8051, chúng ta khai báo trình phục vụ ngắttheo
cấu trúc sau:

Void Name (void) interrupt X //( X: là số thứ tự của ngắt )
{
// chương trình phục vụ ngắt
}
Khi đó địa chỉ ngắt sẽ được tự động tính bằng:
Interrupt Address = (X * 8) + 3
1.4 Quy trình khi thực hiện một ngắt
Khi kích hoạt một ngắt bộ vi điều khiển thực hiện các bước sau:
 Nó hoàn thành nốt lệnh đang thực hiện và lưu địa chỉ của lệnh kế tiếp vào ngăn xếp.
 Nó cũng lưu tình trạng hiện tại của tất cả các ngắt.
 Nó nhảy đến một vị trí cố định trong bộ nhớ được gọi là bảng vector ngắt, nơi lưu giữ địa
chỉ của một trình phục vụ ngắt.
 Bộ vi điều khiển nhận địa chỉ ISR từ bảng vector ngắt và nhảy tới đó. Nó bắt đầu thực
hiện trình phục vụ ngắt cho đến lệnh cuối cùng của ISR và trở về chương trình chính từ
ngắt.
 Khi bộ vi điều khiển quay trở về nơi nó đã bị ngắt. Trước hết nó nhận địa chỉ của bộ đếm
chương trình PC từ ngăn xếp bằng cách kéo 02 byte trên đỉnh của ngăn xếp vào PC. Sau
đó bắt đầu thực hiện tiếp các lệnh từ địa chỉ đó.
1.5 Các bước cho phép và cấm ngắt
Khi bật lại nguồn thì tất cả mọi ngắt đều bị cấm (bị che), có nghĩa là không có
ngắt nào được bộ vi điều khiển đáp ứng trừ khi chúng được kích hoạt.
Các ngắt phải được kích hoạt bằng phần mềm để bộ vi điều khiển đáp ứng chúng.
Có một thanh ghi được gọi là thanh ghi cho phép ngắt IE (Interrupt Enable) – ở địa chỉ
A8H chịu trách nhiệm về việc cho phép và cấm các ngắt. Hình 2 trình bày chi tiết về
thanh ghi IE.
Hình 2: Thanh ghi cho phép ngắt IE.
Để cho phép một ngắt ta phải thực hiện các bước sau:
 Nếu EA = 0 thì không có ngắt nào được đáp ứng cho dù bit tương ứng của nó trong IEcó
giá trị cao. Bit D7 - EA của thanh ghi IE phải được bật lên cao để cho phép các bit còn
lại của thanh ghi hoạt động được.

 Nếu EA = 1 thì tất cả mọi ngắt đều được phép và sẽ được đáp ứng nếu các bit tương ứng
của chúng trong IE có mức cao.
Để hiểu rõ điểm quan trọng này ta hãy xét ví dụ 1.
Ví dụ 1:
Hãy lập trình cho 8051:
a) cho phép ngắt nối tiếp, ngắt Timer0 và ngắt phần cứng ngoài 1 (EX1)
b) cấm ngắt Timer0
c) sau đó trình bày cách cấm tất cả mọi ngắt chỉ bằng một lệnh duy nhất.
Lời giải:
#include<at89x51.h>
main()
{
//a)
IE=0x96; //1001 0110: lệnh này tương đương với 4 lệnh phía dưới

EA=1; //Cho phép sử dụng ngắt
ES=1; //Cho phép ngắt cổng nối tiếp
ET0=1; //Cho phép ngắt timer0
EX1=1; //Cho phép ngắt ngoài 1

//b)
ET0=0; //Cấm ngắt timer0

//c)
EA=0; //Cấm tất cả các ngắt
while(1)
{
//Chương trình chính
//…
}

}
2. Lập trình các ngắt bộ định thời
Trong các bài trước ta đã biết cách sử dụng các bộ định
thời Timer0 và Timer1bằng phương pháp thăm dò. Trong phần này ta sẽ sử dụng
các ngắt để lập trình cho các bộ định thời của 8051.
2.1 Cờ quay về 0 của bộ định thời và ngắt
Chúng ta đã biết rằng cờ bộ định thời TF được bật lên cao khi bộ định thời đạt giá
trị cực đại và quay về 0 (Roll - over). Trong các bài trước chúng ta cũng chỉ ra cách kiểm
tra cờ TF bằng một vòng lặp. Trong khi thăm dò cờ TF thì ta phải đợi cho đến khi
cờ TFđược bật lên. Vấn đề với phương pháp này là bộ vi điều khiển bị trói buộc trong
khi chờ cờ TF được bật và không thể làm được bất kỳ việc gì khác.
Sử dụng các ngắt sẽ giải quyết được vấn đề này và tránh được sự trói buộc bộ vi
điều khiển. Nếu bộ ngắt định thời trong thanh ghi IE được phép thì mỗi khi nó quay trở
về 0 bộ vi điều khiển sẽ bị ngắt, bất chấp nó đang thực hiện việc gì và nhảy tới bảng
vector ngắt để phục vụ ISR. Bằng cách này thì bộ vi điều khiển có thể làm những công
việc khác cho đến khi nó được thông báo rằng bộ định thời đã quay về 0. Xem hình
3 và ví dụ 2.
Hình 3: Ngắt bộ định thời TF0 và TF1.
Ví dụ 2:
Hãy viết chương trình nhận liên tục dữ liệu 8 Bit ở cổng P0 và gửi nó đến cổng P1
trong khi nó cùng lúc tạo ra một sóng vuông chu kỳ 200µs trên chân P2.1. Hãy sử dụng
bộTimer0 để tạo ra sóng vuông, tần số của 8051 là XTAL = 11.0592MHz.
Lời giải:
Chu kỳ 200µs, vậy nửa chu kỳ là 100µs.
Ta có: 100µs/1,085µs=92.
Suy ra giá trị cần nạp cho timer0 là: -92 <=> A4H. Ta sử dụng timer0 8 bit.
#include<at89x51.h> //khai báo thu viện cho VÐK 89x51
main()
{
TMOD=0x02; //chọn timer0, chế độ 2, 8Bit tự nạp lại

TL0=0xA4; //nạp giá trị cho TL0
TH0=0xA4; //nạp giá trị cho TH0
TR0=1; //khởi động timer0
IE=0x82; //cho phép ngắt timer0
while(1) //vòng lặp vô hạn
{
P1=~P0; //Cập nhật giá trị cho cổng P1 từ P0.
}
}
void songvuong(void) interrupt 1 //Khai báo trình phục vụ ngắt cho timer0
{
TR0=0; //Ngừng timer0
P2_1=~P2_1; //Đảo trạng thái chân P2_1.
TR0=1; //Khởi động timer0
//Không cần xóa cờ TF0, 8051 tự động xóa.
}
Hình 4: Mô phỏng trên proteus: cập nhật liên tục cổng P1 từ P0, trong khi tạo xung ở
chân P2.1
Hình 5: Sóng vuông hiển thị trên Oscilloscope
Hãy để ý những điểm dưới đây của chương trình trong ví dụ 2:
1. Chúng ta cho phép ngắt bộ Timer0 với lệnh IE=0x82; trong chương trình chínhmain().
2. Trong khi dữ liệu ở cổng P0 được nhận vào và chuyển liên tục sang cổng P1 thì mỗi khi
bộ Timer0 trở về 0, cờ TF0 được bật lên và bộ vi điều khiển thoát ra khỏi hàmmain() và
đi đến địa chỉ 000BH để thực hiện ISR gắn liền với bộ Timer0.
3. Trong trình phục vụ ngắt ISR của Timer0 ta thấy rằng không cần đến lệnh xóa
cờ TF0của timer0. Lý do này là vì 8051 đã tự xoá cờ TF0 ngay khi thoát khỏi ISR.
Ví dụ 3:
Hãy viết lại chương trình ở ví dụ 2 để tạo sóng vuông với mức cao kéo dài
1085µs và mức thấp dài 15µs với giả thiết tần số XTAL = 11.0592MHz. Hãy sử dụng bộ
định thờiTimer1.

Lời giải:
Vì 1085µs/1.085µs=1000 nên ta cần sử dụng chế độ 1 của bộ định thời Timer1.
Các giá trị cần nạp cho timer1 là:
1085/1.085=1000 , -1000FC18H
15/1.085=14 , -14FFF2H
#include<at89x51.h>
bit a=0;
main()
{
TMOD=0x10; //chọn timer1, chế độ 1, 16Bit
TL1=0x18; //nạp giá trị cho TL1
TH1=0xFC; //nạp giá trị cho TH1
TR1=1; //khoi dong timer1
IE=0x88; //cho phép ngat timer1
while(1) //vòng lặp vô hạn
{
P1=~P0; //Cập nhật cổng P1
}
}
void songvuong(void) interrupt 3 //Khai báo trình phục vụ ngắt timer1
{
TR1=0; //Dừng timer1
if(a==0) //Nếu Xung vuông đang ở mức thấp
{
P2_1=1; //Bật xung vuông lên cao
a=1; //Đặt lại bit kiểm tra
TL1=0x18; //Nạp lại TL1: Ứng với mức trễ phần cao
TH1=0xFC; //Nạp lại TH1
}
Else //Nếu Xung vuông đang ở mức cao

{
P2_1=0; //Lật xung xuống thấp
a=0; //Đặt lại bit kiểm tra
TL1=0xF2; //Nạp lại TL1: Ứng với mức trễ phần thấp
TH1=0xFF; //Nạp lại TH1
}
TR1=1; //Khởi động lại timer1
//Không cần xóa cờ TF1, 8051 tự động xóa
}
Hình 6: Sóng vuông hiển thị trên Oscilloscope
Lưu ý: Các xung được tạo ra ở các ví dụ trên không thật sự chính xác, vì chưa
tính đến hao phí của các lệnh cài đặt.
3 Lập trình các ngắt phần cứng bên ngoài
Bộ vi điều khiển 8051 có 2 ngắt phần cứng bên ngoài ở chân 12 (P3.2) và chân 13
(P3.3) gọi là ngắt INT0 và INT1.
Như đã nói ở trên thì chúng được phép và bị cấm bằng việc sử dụng thanh ghi IE.
Nhưng cấu hình cho ngắt ngoài có phần phức tạp hơn.Có hai mức kích hoạt cho các ngắt
phần cứng ngoài: Ngắt theo mức và ngắt theo sườn.
Hình 7: Ngắt ngoài INT0 và INT1
Dưới đây là mô tả hoạt động của mỗi loại.
3.1 Ngắt theo mức
Ở chế độ ngắt theo mức thì các chân INT0 và INT1 bình thường ở mức cao và
nếu một tín hiệu ở mức thấp được cấp tới thì chúng ghi nhãn ngắt. Sau đó bộ vi điều
khiển dừng tất cả mọi công việc nó đang thực hiện và nhảy đến bảng vector ngắt để phục
vụ ngắt. Đây là chế độ ngắt mặc định khi cấp nguồn cho 8051.
Tín hiệu mức thấp tại chân INTx phải được lấy đi trước khi thực hiện lệnh
cuối cùng của trình phục vụ ngắt, nếu không một ngắt khác sẽ lại được tạo ra, và vi
điều khiển sẽ thực hiện ngắt liên tục.
Để rõ hơn chúng ta hãy xem ví dụ 4.
Ví dụ 4:

Giả sử chân INT1 được nối đến công tắc bình thường ở mức cao. Mỗi khi nó ấn
xuống thấp phải bật một đèn LED ở chân P1.3 (bình thường Led tắt), khi nó được bật
lên nó phải sáng vài giây. Chừng nào công tắc được giữ ở trạng thái thấp đèn LED phải
sáng liên tục.
Lời giải:
#include<at89x51.h> //Khai báo thư viện cho VĐK 89x51
main() //Chương trình chính
{
IE=0x84; //cho phép ngắt ngoài 1
while(1) //vòng lặp vô hạn
{
//không làm gì
}
}
void nutan(void) interrupt 2 //Khai báo trình phục vụ ngắt ngoài 1
{ //(mặc định là ngắt theo mức)
int a=50000; //Biến đếm trễ
P1_3=0; //Cho Led sáng
while(a ){} //Trễ cho Led sáng vài giây
P1_3=1; //Tắt Led
//Không cần xóa cờ ngắt
}
Hình 8: Ấn công tắc xuống sẽ làm cho đèn LED sáng một thời gian.

×