You are on page 1of 31

Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.

ORG

BÀI 3: LẬP TRÌNH NGẮT TRÊN AVR


ỨNG DỤNG NGẮT NGOÀI, BỘ ĐỊNH THỜI

I. Ngắt trên AVR.


1. Khái niệm.
Interrupts hay ngắt, là một tín hiệu khẩn cấp gởi đến bộ xử lí, yêu cầu bộ
xử lí tạm ngừng tức khắc các hoạt động hiện tại để “nhảy” đến một nơi khác
thực hiện một nhiệm vụ khẩn cấp nào đó, nhiệm vụ này gọi là trình phục vụ
ngắt – ISR (interrupt service routine ). Sau khi kết thúc nhiệm vụ trong ISR, bộ
đếm chương trình sẽ được trả về giá trị trước đó để bộ xử lí quay về thực hiện
tiếp các nhiệm vụ còn dang dở. Như vậy, ngắt có mức độ ưu tiên xử lí cao nhất,
ngắt thường được dùng để xử lí các sự kiện bất ngờ nhưng không tốn quá nhiều
thời gian.
Các tín hiệu dẫn đến ngắt có thể xuất phát từ các thiết bị bên trong chip
(ngắt báo bộ đếm timer/counter tràn, ngắt báo quá trình gởi dữ liệu bằng RS232
kết thúc…) hay do các tác nhân bên ngoài (ngắt báo có 1 button được nhấn,
ngắt báo có 1 gói dữ liệu đã được nhận…).

2. Ứng dụng.
Hãy tưởng tượng bạn cần thiết kế một mạch điều khiển hoàn chỉnh thực
hiện rất nhiều nhiệm vụ bao gồm: nhận thông tin từ người dùng qua các nút
nhấn, nhận tín hiệu từ cảm biến, xử lí thông tin, xuất tín hiệu điều khiển, hiển
thị thông tin trạng thái…rõ ràng trong các nhiệm vụ này việc nhận thông tin
người dùng, hay tín hiệu cảm biến rất hiếm xảy ra so với các nhiệm vụ khác
nhưng lại rất “khẩn cấp” cần được ưu tiên hàng đầu.
Nhiệm vụ theo dõi các sự kiện “khẩn cấp” có thể thực hiện bằng 1 trong
2 cách:
+Viết 1 hàm thăm dò sự kiện (ví dụ như hàm Button read đã giới thiệu ở
bài 1) và phải gọi hàm này ra liên tục. Tuy nhiên, nếu chương trình chính có
quá nhiều nhiệm vụ như đã nói ở trên thì cách này không hiệu quả. Lý do đưa
ra là mỗi hàm cần 1 thời gian thực thi nhất định, nếu đang thực thi 1 hàm khác
mà xảy ra sự kiện thì hàm thăm dò trở nên vô dụng và vi điều khiển bắt sót sự
kiện.
+Sử dụng ngắt, lúc này vi điều khiển sẽ không tốn thời gian cho hàm
thăm dò nữa. Vi điều khiển sẽ thực thi nhiệm vụ xử lý sự kiện khi mà có ngắt
xảy ra, khi đó mọi công việc khác được gác lại cho đến khi xử lý xong công
việc mà ngắt giao cho(trình phục vụ ngắt).
Một ví dụ khác cho ứng dụng của ngắt là bộ định thời (ngắt tràn
timer/counter), cứ sau 1 khoảng thời gian cố định thì xảy ra 1 ngắt, ta ứng dụng
vào nhiệm vụ quét led chẳng hạn, khi đó hàm quét led sẽ là trình phục vụ ngắt
và nó sẽ được gọi ra theo 1 chu kỳ xác định với mức ưu tiên cao nhất. Như vậy

Trần Thừa – 2010 93


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

sẽ không có hiện tượng led bị chớp do vi điều khiển thực hiện quá nhiều công
việc(thời gian giữa 2 lần quét bị giãn ra).
Tóm lại, nếu thực hiện các đoạn lệnh thì vi điều khiển thực hiện công
việc 1 cách tuần tự ( từng dòng lệnh được thực thi), nếu sử dụng ngắt thì vi
xử điều khiển thực hiện công việc 1 cách ngẫu nhiên (thực hiện công việc
khi có sự kiện ngẫu nhiên).
Hình 1 minh họa cách tổ chức ngắt thông thường trong các chip
AVR. Số lượng ngắt trên mỗi dòng chip là khác nhau, ứng với mỗi ngắt sẽ có
vector ngắt, vector ngắt là các thanh ghi có địa chỉ cố định được định nghĩa
trước nằm trong phần đầu của bộ nhớ chương trình.

Hình1. Tổ chức ngắt của AVR

Bảng 1 tóm tắt các vector ngắt có trên vi điều khiển Atmega16
STT Địa chỉ Nguồn ngắt Mô tả
vector
$000 RESET Chân ngoài, Power-on Reset, Brown-out
1 Reset, Watchdog Reset,và JTAG AVR
Reset
2 $002 INT0 Ngắt ngoài 0
3 $004 INT1 Ngắt ngoài 1
4 $006 TIMER2 COMP So sánh bộ định thời 2 trùng khớp
5 $008 TIMER2 OVF Tràn bộ định thời 2
6 $00A TIMER1 CAPT Định thời 1 bắt sự kiện
7 $00C TIMER1 COMPA So sánh bộ định thời 1 trùng khớp A
8 $00E TIMER1 COMPB So sánh bộ định thời 1 trùng khớp B
9 $010 TIMER1 OVF Tràn bộ định thời 1
10 $012 TIMER0 OVF Tràn bộ định thời 0
11 $014 SPI, STC Truyền thông nối tiếp hoàn tất
12 $016 USART, RXC USART, nhận hoàn tất

Trần Thừa – 2010 94


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

13 $018 USART, UDRE USART, dữ liệu thanh ghi trống


14 $01A USART, TXC USART, nhận hoàn tất truyền hoàn tất
15 $01C ADC Chuyển đổi ADC hoàn tất
16 $01E EE_RDY EEPROM sẵn sàng
17 $020 ANA_COMP Bộ so sánh tương tự
18 $022 TWI Giao diện 2 đường nối tiếp
19 $024 INT2 Ngắt ngoài 2
20 $026 TIMER0 COMP So sánh bộ định thời 0 trùng khớp
21 $028 SPM_RDY Lưu trữ bộ nhớ chương trình sẵn sàng
Dịch từ datasheet của Atmega16 trang 45.

3. Trình phục vụ ngắt – ISR.


Trong MikroC, trình phục vụ ngắt được định nghĩa như 1 hàm thông
thường nhưng kèm theo khai báo địa chỉ của vector ngắt.
Cú pháp khai báo như sau:
Kiểu_dữ_liệu_trả_về Tên_hàm() org địa_chỉ vector_ngắt {
Khối lệnh;
}
Địa chỉ vector ngắt được liệt kê trong bảng 1(bỏ đi $ vì đây là ký hiệu
địa chỉ tuyệt đối, định dạng theo hệ thập lục phân trong C).
Ví dụ:
// Trình phục vụ ngắt khai báo trong phần mềm MikroC
void Interrupt() org 0x16 { // chọn ngắt có địa chỉ là $016
RS485Master_Receive(dat);
}

Do MikroC không hỗ trợ thư viện Interrupt nên việc khai báo không
giống như các trình biên dịch khác. Để biết cách khai báo của trình biên dịch
khác, các bạn có thể tra cứu trong file Help của trình biên dịch đó với từ khóa
Interrupt library.

Trần Thừa – 2010 95


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

II. Ngắt ngoài (External Interrupt).


1. Giới thiệu
Ngắt ngoài là loại ngắt duy nhất độc lập với các thiết bị của vi điều
khiển, các ngắt khác thường gắn với hoạt động của 1 thiết bị nào đó như
Timer/counter, USART, ADC…
Ngắt ngoài là cách rất hiệu quả để thực hiện giao tiếp giữa người dùng
và vi điều khiển. Đối với Atmega16, ta có 3 ngắt ngoài là INT0, INT1 và INT2
tương ứng với các chân PD2, PD3, PB2.
Khi làm việc với các thiết bị ngoại vi của AVR, hầu như chúng ta chỉ
thao tác trên các thanh ghi chức năng đặc biệt - SFR (Special Function
Registers) trên vùng nhớ IO, mỗi thiết bị bao gồm một tập hợp các thanh ghi
điều khiển, trạng thái, ngắt…khác nhau. Với ngắt ngoài, có 3 thanh ghi liên
quan đến ngắt ngoài đó là MCUCR, GICR và GIFR. Cụ thể các thanh ghi được
trình bày trong phần sau.

2. Các thanh ghi điều khiển ngắt ngoài.


a. MCUCR (MCU Control Register) và MCUCSR (MCU Control
and Status Register)
Là 2 thanh ghi xác lập chế độ ngắt cho ngắt ngoài, quan sát hình 2 trước
khi tìm hiểu 2 thanh ghi này.
VCC

Mức cao
PD2
Cạnh
Cạnh lên
PD3 xuống
Mức thấp
PB2

Hình 2. Kết nối ngắt ngoài cho Atmega16

Giả sử chúng ta kết nối các ngắt ngoài trên Atmega16 như hình 2,
các nút nhấn dùng tạo ra các ngắt. Có 4 khả năng có thể xảy ra khi chúng ta
nhấn và thả các nút nhấn. Nếu không nhấn, trạng thái (logic) các chân INT là
cao do điện trở kéo lên, khi vừa nhấn 1 nút, sẽ có sự chuyển trạng thái từ cao
sang thấp, chúng ta gọi là cạnh xuống - Falling Edge, khi nút được nhấn và giữ,
trạng thái các chân INT được xác định là thấp và cuối cùng khi thả các nút,
trạng thái chuyển từ thấp sang cao, gọi là cạnh lên – Rising Edge. Trong những
trường hợp cụ thể, 1 trong 4 khả năng trên đều hữu ích, ví dụ trong các ứng

Trần Thừa – 2010 96


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

dụng đếm xung (đếm encoder của servo motor chẳng hạn) thì 2 khả năng
“cạnh” phải được dùng.
Thanh ghi MCUCR chứa các bits cho phép chúng ta chọn 1 trong 4 khả
trên để kích hoạt ngắt ngoài INT0 và INT1. Dưới đây là cấu trúc thanh ghi
MCUCR được trích ra từ datasheet của chip Atmega16.

MCUCR là một thanh ghi 8 bit nhưng đối với hoạt động ngắt ngoài,
chúng ta chỉ quan tâm đến 4 bit thấp của nó (4 bit cao dùng cho Power manager
và Sleep Mode).
Bốn bit thấp là các bit Interrupt Sense Control (ISC) trong đó 2 bit
ISC11:ISC10 dùng cho INT1 và 2 bit ISC01:ISC00 dùng cho INT0. Hãy nhìn
vào bảng tóm tắt bên dưới để biết chức năng của các bit trên, đây là bảng sự
thật của 2 bit ISC11, ISC10. Bảng sự thật cho các bit ISC01, ISC00 hoàn toàn
tương tự.

Bảng 2: INT1 Sense Control


ISC11 ISC10 Mô tả
0 0 Khi chân INT1 ở mức thấp thì xuất hiện ngắt
0 1 Có bất kỳ sự thay đổi mức logic tại chân INT1 thì ngắt xuất hiện
1 0 Có cạnh xuống tại chân INT1 thì ngắt xuất hiện
1 1 Có cạnh lên tại chân INT1 thì ngắt xuất hiện
Bảng 2.1: INT0 Sense Control
ISC01 ISC00 Mô tả
0 0 Khi chân INT0 ở mức thấp thì xuất hiện ngắt
0 1 Có bất kỳ sự thay đổi mức logic tại chân INT0 thì ngắt xuất hiện
1 0 Có cạnh xuống tại chân INT0 thì ngắt xuất hiện
1 1 Có cạnh lên tại chân INT0 thì ngắt xuất hiện
Thật dễ dàng để hiểu chức năng của các bit Sense Control, ví dụ bạn
muốn set cho INT1 là ngắt cạnh xuống (Falling Edge) trong khi INT0 là ngắt
cạnh lên (Rising Edge), hãy đặt dòng lệnh MCUCR =0x0B; (0x0B = 00001011
nhị phân) trong chương trình của bạn.

Trong Atmega 16 còn có ngắt thứ 3 là INT2. Chế độ của ngắt này được xác lập
qua thanh ghi MCUSR, cụ thể là bit 6 – ISC2

Nếu ISC2 được xóa (=0) thì ngắt xảy ra khi có cạnh xuống tại chân INT2.
Nếu ISC2 được đặt (=1) thì ngắt xảy ra khi có cạnh lên tại chân INT2.

Trần Thừa – 2010 97


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Xung cấp cho INT2 phải có độ rộng lớn hơn 50ns thì mới xuất hiện ngắt,
nếu nhỏ hơn thì không đảm bảo có ngắt xảy ra. Khi thay đổi bit ISC2, một ngắt
có thể xảy ra, vì vậy trước khi thay đổi bit này, để tránh sai sót, ta cần vô hiệu
hóa chức năng ngắt ngoài INT2 trong thanh ghi GICR sau đó mới thay đổi bit
ISC2, cuối cùng, cờ ngắt INTF2 phải được xóa trong thanh ghi GIFR trước khi
kích hoạt lại ngắt INT2.

b. Thanh ghi điều khiển ngắt chung – GICR (General Interrupt


Control Register).
GICR cũng là 1 thanh ghi 8 bit nhưng chỉ có 3 bit cao (bit 5, bit 6 và bit 7)
là được sử dụng cho điều khiển ngắt, cấu trúc thanh ghi như bên dưới (trích
datasheet).

Bit 7: Điều khiển ngắt INT1, khi nó được đặt (=1) thì ngắt INT1 được cho
phép hoạt động, ngược lại, nếu nó được xóa thì ngắt INT1 bị vô hiệu hóa.
Bit 6: Tương tự bit 7, nó điều khiển ngắt INT0.
Bit 5: Tương tự bit 7, nó điều khiển ngắt INT1.
c. Thanh ghi cờ ngắt chung – GIFR (General Interrupt Flag Register)
Đây là thanh ghi trạng thái (cờ) của các ngắt INT0, INT1 và INT2. Nếu có 1
sự kiện ngắt phù hợp xảy ra trên chân INT0, bit INTF0 được tự động đặt (=1).
Sau khi trình phục vụ ngắt kết thúc công việc thì cờ ngắt sẽ tự động xóa. Tương
tự cho trường hợp của INTF1 và INTF2, chúng ta có thể sử dụng các bit này để
nhận ra các ngắt, tuy nhiên điều này là không cần thiết nếu chúng ta cho phép
ngắt tự động, vì vậy thanh ghi này thường không được quan tâm khi lập trình
ngắt ngoài. Cấu trúc thanh ghi GIFR được trình bày trong hình dưới.

Sau khi đã thiết lập xong các bit của các thanh ghi điều khiển ngắt ngoài,
việc sau cùng là cho phép ngắt toàn cục thông qua việc đặt bit 7(bit I) của thanh
ghi trạng thái chung(SREG) đã giới thiệu ở chương 1. Xin được nhắc lại cấu
trúc thanh ghi này ở hình dưới.

Chú ý: Các chân ngắt PD2, PD3, PB2 khi sử dụng là các chân ngắt ngoài
thì phải thiết lập các chân này là ngõ vào thông qua thanh ghi DDRx.

Tóm tắt quá trình thiết lập ngắt ngoài.


+Bước 1: Chọn chế độ ngắt thông qua thanh ghi MCUCR và MCUCSR.

Trần Thừa – 2010 98


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

+Bước 2: Khởi động ngắt ngoài thông qua thanh ghi GICR.
+Bước 3: Cho phép ngắt toàn cục thông qua bit 7 của thanh ghi SREG.

Thực hành ngắt ngoài


Ví dụ : Viết chương trình sử dụng ngắt điều khiển 1 led 7 đoạn bằng
phương pháp chốt. Tác động vào các chân INT bằng nút nhấn, tác động bằng
cạnh xuống. Khi tác động vào chân INT0 thì giá trị hiển thị tăng 1, tác động
vào chân INT1 thì giá trị hiển thị giảm 1, tác động vào chân INT2 thì giá trị
hiển thị bằng 0.
Sơ đồ thí nghiệm như sau:

Hình 3. Sơ đồ thí nghiệm ngắt ngoài.

Chương trình gợi ý:


Bai3_1_A : Thực hành ngắt ngoài. Sử dụng file
mô phỏng BAI3_1.DSN
signed char count;// Biến đếm
//Mã hiển thị led 7 đoạn loại anode chung với a là LSB
char Bitmap[]={0XC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
// Chương trình chính
void main(){
DDRB=0x00; // Cấu hình PORTB là ngõ vào
DDRD=0x00; // Cấu hình PORTD là ngõ vào
DDRC=0xFF; // Cấu hình PORTC là ngõ ra
PORTD=0xFF; // Sử dụng nội trở kéo lên
PORTB=0xFF; // Sử dụng nội trở kéo lên
// Cài đặt chế độ ngắt
ISC00_bit=0; // Cài đặt bit ISC00 của thanh ghi MCUCR

Trần Thừa – 2010 99


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

ISC01_bit=1; // Cài đặt bit ISC01 của thanh ghi MCUCR


ISC10_bit=0; // Cài đặt bit ISC10 của thanh ghi MCUCR
ISC11_bit=1; // Cài đặt bit ISC11 của thanh ghi MCUCR
ISC2_bit=0; // Cài đặt bit ISC2 của thanh ghi MCUCSR
// Cho phép các ngắt ngoài thông qua 3 bit của thanh ghi GICR
INT0_bit=1;
INT1_bit=1;
INT2_bit=1;
// Cho phép ngắt toàn cục
SREG_I_bit=1;
while(1){
count++;
if (count>9) count=0;
PORTC=Bitmap[count];
delay_ms(6000);
}
}
void Interrupt() org 0x02 { //Trình phục vụ ngắt INT0
count++;
if (count>9) count=0;
PORTC=Bitmap[count];
}
void Interrupt1() org 0x04 {//Trình phục vụ ngắt INT1
count--;
if (count<0) count=9;
PORTC=Bitmap[count];
}
void Interrupt2() org 0x24 {//Trình phục vụ ngắt INT2
count=0;
PORTC=Bitmap[count];
}

Lưu ý: Nếu không biết MikroC định nghĩa các bit của các thanh ghi điều
khiển ngắt như thế nào, ta có thể làm theo cách sau:
+Thao tác byte trực tiếp trên các thanh ghi (1 hoặc nhiều byte) bằng các
toán tử logic.
+Chọn Start debugger (F9) để gọi trình gỡ rối, sau đó trong ô search for
variable by assembly name ta gõ tên của bit cần thao tác, tên này chính là
tên được định nghĩa trong datasheet. Ví dụ ta tìm bit INT0 của thanh ghi
GICR thì ta gõ INT0. Lập tức trong cửa sổ sổ xuống sẽ gợi ý cho ta kết quả

Trần Thừa – 2010 100


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

tên định nghĩa của bit cần tìm trong MikroC. Cách tìm này áp dụng cho tất
cả các bit, thanh ghi và biến của AVR trong MikroC.

III. Timer/Counter – Bộ đếm, định thời.


1. Giới thiệu.
Timer/Counter là các module độc lập với CPU. Chức năng chính của
các bộ Timer/Counter là bộ định thời thông qua việc đếm thời gian hoặc đơn
giản chỉ là bộ đếm sự kiện. Trên các chip AVR, các bộ Timer/Counter còn có
thêm chức năng tạo ra các điều biến bề rộng xung PWM (Pulse Width
Modulation), ở một số dòng AVR, một số Timer/Counter còn được dùng như
các bộ canh chỉnh thời gian (calibration) trong các ứng dụng thời gian thực.
Các bộ Timer/Counter được chia theo độ rộng thanh ghi chứa giá trị định thời
hay giá trị đếm của chúng, số lượng và chức năng các bộ Timer/Counter trên
mỗi chip cũng có thể khác nhau (ví dụ bộ Timer/Counter0 trên Atmega8 ít
chức năng hơn bộ Timer/Counter trên Atmega16). Chế độ hoạt động và
phương pháp điều khiển của từng Timer/Counter cũng không hoàn toàn giống
nhau. Ví dụ ở chip Atmega16 ta có 3 bộ Timer/Counter :
+ Timer/Counter0: là một bộ định thời, đếm với độ rộng thanh ghi 8 bit.
Các chức năng chính bao gồm : Đếm sự kiện ngoài, tạo dao động, tạo PWM,
tạo ngắt tràn... Các chức năng của các bộ Timer/Counter thiết lập như là các
chế độ làm việc.
Lưu ý : ở Atmega8 bộ Timer/Counter0 không có chức năng tạo PWM.
+Timer/Counter1: là bộ định thời, đếm với độ rộng thanh ghi 16 bit. Bộ
Timer/Counter này có 5 chế độ hoạt động chính. Ngoài các chức năng thông
thường, ta thể tạo 2 tín hiệu PWM độc lập trên các chân OC1A và OC1B bằng
Timer/Counter1.
+Timer/Counter2: tuy là một module 8 bit như Timer/Counter0, ngoài ra
nó nó còn được sử dụng như một module canh chỉnh thời gian cho các ứng
dụng thời gian thực (chế độ không đồng bộ). Chế độ không đồng bộ của
Timer/Counter2 sẽ được bỏ qua vì có thể chế độ này không phổ biến.
Một số định nghĩa và quy ước:
Timer/Counter: viết tắt thành T/C
BOTTOM: là giá trị thấp nhất mà một T/C có thể đạt được, giá trị này
luôn là 0.
MAX: là giá trị lớn nhất mà một T/C có thể đạt được, giá trị này được
quy định bởi bởi giá trị lớn nhất mà thanh ghi đếm của T/C có thể chứa được.
Ví dụ với một bộ T/C 8 bit thì giá trị MAX luôn là 0xFF (tức 255 trong hệ thập
phân), với bộ T/C 16 bit thì MAX bằng 0xFFFF (65535). Như thế MAX là giá
trị không đổi trong mỗi T/C.
TOP: bộ đếm đạt giá trị TOP khi nó bằng với giá trị cao nhất trong trình
tự đếm. Giá trị TOP có thể thiết lập cố định bằng giá trị MAX hoặc thiết lập
khác thông qua các thanh ghi thích hợp của từng T/C.

Trần Thừa – 2010 101


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

2. Timer/Counter0.
a/. Các thanh ghi điều khiển T/C0:

Thanh ghi điều khiển – Timer Counter Control Register – TCCR0

Đây là thanh ghi thiết lập các chế độ làm việc của T/C0

Bit 0-2: (CS-Clock Select) là 3 bit dùng để chọn nguồn xung đếm cho T/C0,
chức năng của 3 bit này được tóm tắt qua bảng sau:
CS02 CS01 CS00 Mô tả
0 0 0 Không dùng nguồn xung (T/C) không làm việc
0 0 1 Lấy trực tiếp nguồn xung nuôi chip
0 1 0 Lấy tần số của nguồn xung nuôi chip chia 8
0 1 1 Lấy tần số của nguồn xung nuôi chip chia 64
1 0 0 Lấy tần số của nguồn xung nuôi chip chia 256
1 0 1 Lấy tần số của nguồn xung nuôi chip chia 1024
1 1 0 Lấy nguồn xung ngoài tại chân T0, tác động bằng
cạnh xuống
1 1 1 Lấy nguồn xung ngoài tại chân T0, tác động bằng
cạnh lên

Bit 3 và 6 (WGM): Là 2 bit điều khiển chế độ tạo dạng sóng Waveform
Generation Mode (WGM). Các bit này giúp ta chọn giá trị TOP của bộ đếm
và dạng sóng tạo ra. Các chế độ làm việc mà 2 bit này hỗ trợ bao gồm: Chế độ
thông thường, chế độ xóa bộ đếm khi so sánh trùng khớp(CTC), và 2 chế độ
điều biến bề rộng xung (PWM). Bảng sau tóm tắt các chế độ làm việc được
thiết lập bởi 2 bit này.
Cập nhật vào Ảnh hưởng
STT WGM01 WGM00 Chế độ làm việc TOP
thanh ghi ORC0 đến cờ tràn

0 0 0 Thông thường 0xFF Giá trị tức thời MAX


PWM, pha
1 0 1 0xFF TOP BOTTOM
chính xác
2 1 0 CTC OCR0 Giá trị tức thời MAX
PWM, tốc độ
3 1 1 0xFF BOTTOM MAX
cao
Dịch từ datasheet của Atmega16 trang 83.

Bit 4 và 5 (COM): Các bit này điều khiển vận hành của chân OC0. Nếu 1
trong 2 bit này được đặt thì chân OC0 không hoạt động như là 1 chân I/O mà

Trần Thừa – 2010 102


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

phục vụ cho việc xuất tín hiệu. Tuy nhiên, lưu ý rằng thanh ghi hướng dữ liệu
(DDR) phải thiết lập sao cho chân OC0 là ngã ra.
Sau đây là các bảng thiết lập tùy chọn tương quan giữa các bit COM và
WGM.
Khi các bit WGM được thiết lập với chế độ CTC (WGM01=1,
WGM00=0) ta có bảng thiết lập các bit COM sau:
COM01 COM00 Mô tả
0 0 Chế độ nhập xuất thông thường.
0 1 Đảo trạng thái chân OC0 khi có so sánh trùng khớp.
Xóa bit OC0 khi có so sánh trùng khớp. Chân OC0
1 0
xuống mức thấp.
Đặt bit OC0 khi có so sánh trùng khớp. Chân OC0 lên
1 1
mức cao.

Khi các bit WGM được thiết lập với chế độ PWM tốc độ cao
(WGM01=0, WGM00=1) ta có bảng thiết lập các bit COM sau:
COM01 COM00 Mô tả
0 0 Chế độ nhập xuất thông thường.
0 1 Reserved.
Xóa bit OC0 khi so sánh trùng khớp. Đặt OC0 tại
1 0
BOTTOM. Chế độ thuận.
Đặt bit OC0 khi so sánh trùng khớp. Xóa OC0 tại
1 1
BOTTOM. Chế độ nghịch.

Khi các bit WGM được thiết lập với chế độ PWM pha chính xác
(WGM01=1, WGM00=1) ta có bảng thiết lập các bit COM sau:
COM01 COM00 Mô tả
0 0 Chế độ nhập xuất thông thường.
0 1 Reserved.
Xóa OC0 khi so sánh trùng khớp và đang đếm lên. Đặt
1 0
OC0 khi so sánh trùng khớp và đang đếm xuống.
Đặt OC0 khi so sánh trùng khớp và đang đếm lên. Xóa
1 1
OC0 khi so sánh trùng khớp và đang đếm xuống.

Bit7 – FOC0: Đây là bit so sánh trùng khớp cưỡng bức, bit này chỉ hoạt động
khi WGM00 được xóa (chế độ không PWM). Tuy nhiên, để đảm bảo khả năng
tương thích với các thiết bị trong tương lai, khi chọn chế độ PWM, ta phải xóa
bit này.
Khi ghi 1 vào bit này thì lập tức có 1 so sánh trùng khớp (trong chế độ PWM).

Thanh ghi giá trị bộ đếm, định thời: Timer Counter Registor – TCNT0

Trần Thừa – 2010 103


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Thanh ghi này chứa giá trị hiện tại của bộ đếm, với T/C0 thì nó có độ rộng là 8
bit nên có giá trị đếm từ 0 đến 255. Ta có thể ghi trực tiếp giá trị vào thanh ghi
này.

Thanh ghi so sánh : Output Compare Registor – OCR0

Thanh ghi này chứa giá trị để so sánh với giá trị của bộ đếm chứa trong thanh
ghi TCNT0, do đó nó có độ rộng bằng với độ rộng của thanh ghi TCNT0.

Thanh ghi cờ ngắt của bộ đếm: Timer/Counter Interrupt Flag Register -


TIFR

Đây là thanh ghi dùng chung cho 3 bộ đếm và nó có đủ các bit cờ ngắt thể hiện
trạng thái của từng bộ đếm.
Bit1- Output Compare Flag 0 – OCF0: bit này được đặt khi có sự so sánh
trùng khớp xuất hiện giữa giá trị thanh ghi của bộ đếm và giá trị chứa trong
thanh ghi so sánh.
Khi muốn có 1 ngắt xảy ra khi có sự so sánh trùng khớp(OCF0=1), ta phải set
bit I trong SREG và bit OCIE0 trong thanh ghi TIMSK.
Bit 2 –Timer/Counter0 Overflow Flag – TOV0: đây là cờ tràn của bộ đếm,
tràn là hiện tượng khi bộ đếm đếm đến giá trị cao nhất và đếm thêm 1 lần nữa.
Khi đó cờ tràn sẽ được đặt.
Khi muốn có 1 ngắt xảy ra khi có cờ tràn, ta phải set bit I trong SREG và bit
TOIE0 trong thanh ghi TIMSK.

Thanh ghi mặt nạ của bộ đếm: Timer/Counter Interrupt Mask Register –


TIMSK

Đây cũng là thanh ghi dùng chung cho cả 3 bộ đếm.


Bit 0 : là TOIE0, bit cho phép ngắt tràn cho T/C0.
Bit 1 : là OCIE1 là bit cho phép ngắt khi có so sánh trùng khớp xảy ra trong
việc so sánh TCNT0 với OCR0.

Trên đây là những giới thiệu sơ qua về các thanh ghi có liên quan đến
việc điều khiển T/C0. Đối với T/C1 và T/C2, các bit và thanh ghi có chức năng
tương tự sẽ không được nhắc lại.

Trần Thừa – 2010 104


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Phần sau ta sẽ tìm hiểu kỹ hơn về chức năng của các chế độ hoạt động
chính của T/C0

b/. Bộ định thời với TIMER/COUNTER0.


Với các bộ định thời, ta có thể cài đặt một khoảng thời gian tùy ý để
dùng vào một công việc nào đó. Ví dụ, đối với chương trình quét led 7 đoạn: ta
muốn rằng cứ sau 10ms thì vi điều khiển sẽ quét led 1 lần, bất kể công việc nào
đang thực thi cũng phải gác lại. Ở đây ta sẽ không dùng hàm delay để tạo thời
gian ngắt quãng giữa các lần quét vì nhược điểm của hàm delay là khoảng thời
gian delay bị bỏ phí vô ích trong khi nó có thể dùng để xử lý tác vụ khác. Do
đó, các bộ định thời là lý tưởng cho các công việc cần thực hiện thường xuyên
và tốn ít thời gian xử lý.

Hình 3. So sánh phương pháp delay và timer/counter.


(MCU nop: trong khoảng thời gian này MCU không làm gì cả)

Nguyên tắc hoạt động của bộ định thời thực chất vẫn là việc đếm, bộ
đếm sẽ đếm xung, xung được cấp bởi nguồn xung, nguồn xung của T/C0 được
chọn bởi 3 bit CS00, CS01, CS02 của thanh ghi TCCR0. Như vậy, chọn nguồn
xung là chọn khoảng giá trị thời gian cho bộ đếm.
Giả sử nguồn xung clock “nuôi” chip của chúng ta là clkI/O=1MHz tức
là 1 nhịp mất 1us. Để tạo khoảng thời gian dài hơn, ta chia bớt tầng số này cho
1 hệ số định sẵn. Nếu chúng ta lấy trực tiếp nguồn xung nuôi chip làm xung
đếm, tức là tần số của T/C0 (tạm gọi là fT/C0) cũng bằng clkI/O=1MHz, cứ
1us T/C0 được kích và TCNT0 sẽ tăng 1 đơn vị. Khi đó giá trị lớn nhất mà
T/C0 có thể đạt được là 256 x 1us=256us, giá trị này nhỏ hơn 10ms mà ta
mong muốn. Nếu lấy nguồn xung nuôi chip chia 64, nghĩa là cứ sau 64 nhịp
(64us) thì TCNT0 mới tăng 1 đơn vị, khả năng lớn nhất mà T/C0 đếm được là
256 x 8us=16384us=16ms, lớn hơn 10ms, vậy ta hoàn toàn có thể sử dụng
nguồn xung của chip đã chia 64 làm nguồn xung cho bộ đếm.

Trần Thừa – 2010 105


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Mặc định, bộ đếm sẽ đếm từ 0 đến giá trị cao nhất, trong trường hợp
T/C0, do thanh ghi chứa giá trị của nó có 8 bit nên bộ đếm sẽ đếm từ 0-255. Ta
có 2 cách đặt số nhịp đếm:
+Cách 1: Dùng ngắt tràn, cách này đơn giản, ta chỉ việc đặt trước 1 giá
trị cho bộ đếm và ghi vào thanh ghi TCNT0. Khi đó bộ đếm sẽ đếm từ giá trị
này đến 255 sẽ xảy ra ngắt, trong trình phục vụ ngắt ta sẽ khởi tạo lại giá trị ban
đầu cho TCNT0 sau đó mới thực hiện công việc cần làm. Như vậy, số nhịp
đếm sẽ bằng 255 trừ đi giá trị khởi tạo ban đầu.
Tóm lại:
prescaler
T  255  TCNT 0  
f XTAL
Với prescaler là hệ số chia, f XTAL là tần số thạch anh dùng cho chip, TCNT0
là giá trị đặt trước, T là chu kỳ lặp lại công việc (chu kỳ này không thể chính xác hoàn
toàn do sai số phép tính và thời gian quét led…).
+Cách 2: Dùng bộ so sánh (ngắt so sánh trùng khớp), ta sẽ ghi giá trị số
nhịp cần đếm vào thanh ghi so sánh ORC0, khi đếm từ 0 đến giá trị so sánh
trùng khớp thì sẽ xảy ra 1 ngắt giúp ta thực hiện 1 công việc nào đó.
Tóm lại:
prescaler
T  TCNT 0 
f XTAL

Sau đây là ví dụ minh họa dùng 2 cách trên.


Viết chương trình điều khiển 2 led 7 đoạn bằng phương pháp quét, giá
trị hiển thị tăng lên sau mỗi giây.

Hình 4. Sơ đồ thực hành ngắt tràn và ngắt so sánh.

Trong mạch này, ta cần thiết lập lại thông số thạch anh cần dùng là 1
Mhz trong trình biên dịch MikroC và trình mô phỏng ISIS.

Trần Thừa – 2010 106


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Chương trình tham khảo:


Bai3_2_A : Thực hành Timer – Ngắt tràn. Sử dụng
file mô phỏng BAI3_2.DSN
//MÃ LED 7 ĐOẠN LOẠI ANODE CHUNG, A LÀ LSB
const char table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
char var;
void main(){
// KHAI BÁO HƯỚNG DỮ LIỆU CHO PORTC VÀ PORTD LÀ NGÕ RA
DDRC=0xFF;
DDRD=0xFF;
// CHỌN NGUỒN XUNG LẤY TỪ XUNG NHỊP CẤP CHO CHIP CHIA 64 HAY PRESCALER =64
CS00_bit=1;
CS01_bit=1;
CS02_bit=0;
// CHO PHÉP NGẮT TRÀN – bit TOIE0 của thanh ghi TIMSK
TOIE0_bit=1;
// CHO PHÉP NGẮT TOÀN CỤC – bit I của thanh ghi trạng thái SREG
SREG_I_bit=1;
// THIẾT LẬP GIÁ TRỊ ĐẦU CHO BỘ ĐẾM
TCNT0=159;
while(1){
var++;
if(var>99) var=0;
delay_ms(100);
}
}
//TRÌNH PHỤC VỤ NGẮT TRÀN CỦA BỘ ĐẾM T/C0
void LEDSCAN_ISR() org 0x12 {
// THIẾT LẬP LẠI GIÁ TRỊ ĐẦU CHO BỘ ĐẾM
TCNT0=159;
//HIỂN THỊ SỐ HÀNG CHỤC
PORTC=table[var/10];
PORTD=0x02;
delay_ms(1);// THỜI GIAN TRỄ PHÙ HỢP ĐỘ TRỄ TRUYỀN CỦA LINH KIỆN
PORTD=0x00;
//HIỂN THỊ SỐ HÀNG ĐƠN VỊ
PORTC=table[var%10];
PORTD=0x01;
delay_ms(1); // THỜI GIAN TRỄ PHÙ HỢP ĐỘ TRỄ TRUYỀN CỦA LINH KIỆN

Trần Thừa – 2010 107


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

PORTD=0x00;
}

Các linh kiện cần 1 khoảng thời gian để chuyển từ mức thấp sang mức
cao mà vi điều khiển có tốc độ xử lý rất cao. Vì vậy để đảm bảo các linh kiện
hoạt động bình thường, ta có thể tạo 1 khoảng delay tương đối nhỏ để khắc
phục độ trễ truyền của linh kiện.
Lưu ý: Khi dùng ngắt tràn để gọi chương trình quét led 7 đoạn, nếu số
lượng led nhiều (nhiều hơn 2) thì ta không quét 1 lần tất cả các led mà mỗi
lần có ngắt chỉ quét 1 led (xuất dữ liệu và cấp địa chỉ) và lần ngắt sau sẽ quét
led tiếp theo. Như vậy sẽ đảm bảo thời gian thực hiện trình phục vụ ngắt
không quá lâu cũng như đảm bảo thời gian đáp ứng cho các linh kiện mà
không cần dùng hàm delay.
Bai3_2_B : Thực hành Timer – Ngắt so sánh trùng khớp.
Sử dụng file mô phỏng BAI3_2.DSN
//MÃ LED 7 ĐOẠN LOẠI ANODE CHUNG, A LÀ LSB
const char table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
char var=0;
void main(){
// KHAI BÁO HƯỚNG DỮ LIỆU CHO PORTC VÀ PORTD LÀ NGÕ RA
DDRC=0xFF;
DDRD=0xFF;
//CHỌN NGUỒN XUNG LẤY TỪ XUNG NHỊP CẤP CHO CHIP CHIA 64 HAY PRESCALER =64
CS00_bit=1;
CS01_bit=1;
CS02_bit=0;
//CHỌN CHẾ ĐỘ CTC – BỘ ĐẾM SẼ BỊ XÓA MỖI LẦN CÓ NGẮT TRÙNG KHỚP
WGM00_bit=0;
WGM01_bit=1;
//CHO PHÉP NGẮT KHI SO SÁNH TRÙNG KHỚP
OCIE0_bit=1;
//CHO PHÉP NGẮT TOÀN CỤC
SREG_I_bit=1;
// THIẾT LẬP GIÁ TRỊ SO SÁNH
OCR0=96;
while(1){
var++;
if(var>99) var=0;
delay_ms(100);
}
}

Trần Thừa – 2010 108


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

//TRÌNH PHỤC VỤ NGẮT SO SÁNH TRÙNG KHỚP


void LEDSCAN_ISR() org 0x26 {
// HIỂN THỊ SỐ HÀNG CHỤC
PORTC=table[var/10];
PORTD=0x02;
delay_ms(1);
PORTD=0x00;
// HIỂN THỊ SỐ HÀNG ĐƠN VỊ
PORTC=table[var%10];
PORTD=0x01;
delay_ms(1);
PORTD=0x00;
}

Các bước cơ bản sử dụng ngắt do hoạt động của bộ T/C bao gồm:
+Chọn nguồn xung theo khoảng thời gian cần thiết, thông qua 3 bit CS của
thanh ghi TCCR.
+Chọn loại ngắt phù hợp thông qua thanh ghi mặt nạ ngắt.
+Cho phép ngắt toàn cục ở thanh ghi SREG.
+Viết trình phục vụ ngắt theo địa chỉ ngắt tương ứng. (Xem lại trang 95 - 96).
Ngoài ra nếu dùng ngắt tràn thì phải khởi tạo lại giá trị đầu sau mỗi lần
có ngắt. Nếu dùng ngắt so sánh thì phải chọn chế độ CTC (xóa bộ đếm mỗi lần
xảy ra ngắt – xem lại trang 103) hoặc xóa bộ đếm thủ công trong trình phục vụ
ngắt.
Hai ví dụ về sử dụng bộ định thời sử dụng file mô phỏng BAI3_2.DSN

c/. Bộ đếm sự kiện với TIMER/COUNTER0.


Ngoài cách dùng nguồn xung nhịp của chip để tạo xung đếm, ta còn có
thể dùng lợi dụng sự thay đổi mức logic tại chân T0 của vi điều khiển làm xung
cấp cho bộ đếm. Lúc này, bộ đếm T/C0 của chúng ta trở thành bộ đếm sự kiện
với sự kiện là sự thay đổi mức logic tại chân T0. Bằng cách đặt giá trị cho
thanh ghi TCCR0 (CS02=1, CS01=1, CS00=0) cho phép đếm “cạnh xuống”
trên chân T0, nếu TCCR0 (CS02=1, CS01=1, CS00=1) thì “cạnh lên” trên chân
T0 sẽ được đếm (Xem lại trang 103).

Trần Thừa – 2010 109


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Hình 5. Sơ đồ thực hành bộ đếm sự kiện với T/C0.

Viết chương trình sử dụng sơ đồ trên sao cho khi nhấn nút thì giá trị hiển thị
tăng 1 đơn vị.

Bai3_2_C : Thực hành Timer – Bộ đếm sự kiện.


Sử dụng file mô phỏng BAI3_2_B.dsn

//MÃ LED 7 ĐOẠN LOẠI ANODE CHUNG A LÀ LSB


const char table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
void main(){
// KHAI BÁO HƯỚNG DỮ LIỆU
DDRC=0XFF;
DDRB=0x00;
// CHỌN XUNG ĐẾM LẤY TỪ CHÂN T0, TÁC ĐỘNG BẰNG CẠNH XUỐNG
CS00_bit=0;
CS01_bit=1;
CS02_bit=1;
while(1){
//GIỚI HẠN GIÁ TRỊ ĐẾM
if(TCNT0>9) TCNT0=0;
PORTC=table[TCNT0];
}
}

Trong chương trình trên, ta hoàn toàn có thể sử dụng ngắt tràn hoặc ngắt
so sánh trùng khớp, tùy theo mục đích sử dụng.
Ví dụ bạn muốn đếm số người đi vào 1 phòng, khi đếm đủ 10 người thì
phát 1 thông báo. Để thực hiện việc này, ta chỉ cần mắc cảm biến vào chân T0,
sau đó thiết lập giá trị so sánh của thanh ghi OCR0, cuối cùng là cho phép ngắt
so sánh trùng khớp và ngắt toàn cục, thông báo sẽ được thực hiện bằng trình
phục vụ ngắt.
Lưu ý, các bạn có thể thiết lập chế độ CTC(Clear Timer on Compare
Match) bởi các bit WGM của thanh ghi TCCR0 (xem lại trang 103). Khi đó, bộ
đếm sẽ khởi tạo lại mỗi khi có ngắt so sánh trùng khớp.

d. Điều biến bề rộng xung – PWM với TIMER/COUNTER0.


Trước tiên, ta cần tìm hiểu về khái niệm PWM.
Nếu ta muốn điều khiển tốc độ 1 động cơ hay nói cách khác là công suất
cấp cho động cơ, thông thường ta dùng 1 biến trở mắc nối tiếp với động cơ,
điều chỉnh biến trở chính là thay đổi công suất cấp cho động cơ. Tuy nhiên,
nhược điểm của cách này là làm tiêu tán một phần năng lượng qua biến trở. Vì
vậy cách này không hiệu quả.

Trần Thừa – 2010 110


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Người ta thay biến trở bằng 1 nút nhấn, khi ta nhấn thì động cơ quay,
khi thôi nhấn thì động cơ dừng. Khi tần số nhấn-nhả cao thì động cơ quay như
được cấp dòng liên tục. Ta tiếp tục thử nghiệm với thời gian nhấn và nhả khác
nhau. Khi thời gian nhấn dài hơn thời gian nhả thì động cơ quay nhanh hơn khi
thời gian nhả dài hơn thời gian nhấn, ta thấy thời gian nhấn chính là bề rộng
của xung tạo ra. Bề rộng xung càng lớn thì công suất cấp cho động cơ càng lớn.
Đó là ý tưởng cơ bản để sử dụng PWM điều khiển vận tốc động cơ (và điều
khiển nhiều thứ khác nữa). Trong thực tế, nút nhấn được thay bằng khóa điện
tử ( transistor, thường là FET), việc còn lại là tạo xung có bề rộng thay đổi
được. Nhiệm vụ này thực hiện dễ dàng với vi điều khiển thông qua việc sử
dụng chế độ PWM của các bộ Timer/Counter có hỗ trợ.

Hình 6. Mô tả các phương pháp điều khiển công suất động cơ.

Hình 7. Dạng xung PWM.

Trong điều chế độ rộng xung, ta cần chú ý các thông số sau:
+Tần số hoặc chu kỳ (Time Period) của xung.
+Độ rộng xung (Duty Cycle).
Như vậy, để tạo xung PWM, ta sẽ thay đổi các thông số trên bằng các chức
năng trong chế độ PWM của AVR.
Ta trở lại các khái niệm ban đầu của bộ đếm và liên kết sự tương quan của các
khải niệm này với PWM.
BOTTOM: là giá trị nhỏ nhất của bộ đếm, luôn bằng 0.

Trần Thừa – 2010 111


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

MAX: là giá trị lớn nhất của bộ đếm, MAX=255(0xff) đối với bộ đếm 8 bit như
T/C0 và T/C2, MAX=65535 với bộ đếm 16 bit như T/C1.
TOP: trong các ứng dụng PWM, giá trị này là cố định đối với T/C0 và T/C2
(TOP=0xFF), riêng T/C1 giá trị này có thể cố định(0x00FF, 0x01FF, hoặc
0x03FF) hoặc do người dùng tự định nghĩa thông qua thanh ghi OCR1A hoặc
ICR1. Tham khảo thêm tại trang 83, 112 và 128 datasheet của
ATMEGA16.
OUTPUT COMPARE(OC) là giá trị so sánh chứa trong thanh ghi OCRx, nó có
thể tạo ra ngắt so sánh hoặc tạo sự thay đổi mức logic ở chân OCx(x có thế là
0, 1A,1B hoặc 2)
Các giá trị trên chính là các mốc thời gian mà mức logic thay đổi trên chân tạo
xung của vi điều khiển. Các bạn có thể theo dõi hình sau để có sự liên tưởng.

Hình 8. So sánh hình dạng xung và các mốc thời gian của T/C.
Có thể giải thích sự tương quan giữa các mốc thời gian của T/C và dạng của
xung PWM như sau: Trong chu kỳ đếm từ BOTTOM đến TOP, khi giá trị
thanh ghi TCNTx của bộ đếm tăng đến giá trị OC thì có sự thay đổi mức logic
tại chân OCx(x có thế là 0, 1A,1B hoặc 2). Khi đếm đến TOP thì lại có sự thay
đổi mức logic ở chân OCx lần nữa và lại bắt đầu chu kỳ mới.
Như vậy: từ BOTTOM đến TOP là chu kỳ của 1 xung PWM, từ TOP đến OC
là độ rộng của xung. Đây là tương quan theo chế độ PWM thuận. Chế độ PWM
nghịch sẽ được giới thiệu sau.
Ta bắt đầu tìm hiểu cách sử dụng chức năng PWM của T/C mà cụ thể là T/C0.

Fast PWM: PWM tần số cao với T/C0.


Chế độ FAST PWM được thiết lập trong thanh ghi TCCR0 bởi 2 bit
WGM (xem lại trang 103) với WGM00=1, WGM01=1.
Sau khi khởi tạo chế độ FAST PWM, ta tiến hành chọn dạng tín hiệu
PWM ở ngã ra. Dạng tín hiệu ngã ra (chân OC0) được quy định bởi các bit
COM00 và COM01.
COM01 COM00 Mô tả
0 0 Chế độ nhập xuất thông thường.
0 1 Reserved.
Xóa bit OC0 khi so sánh trùng khớp. Đặt OC0 tại
1 0
BOTTOM. Chế độ thuận.

Trần Thừa – 2010 112


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Đặt bit OC0 khi so sánh trùng khớp. Xóa OC0 tại
1 1
BOTTOM. Chế độ nghịch.

Ví dụ: Ta có cấu hình các bit như sau:


WGM00=1; WGM01=1;Chế độ Fast PWM được chọn
COM01=1; COM00=0; Chế độ PWM thuận: Từ BOTTOM đến OC, ngã ra ở
mức cao (5V), Từ OC đến TOP ngã ra ở mức thấp(0V). Như vậy khoảng thời
gian từ BOTTOM đến OC chính là độ rộng xung PWM. Dạng tín hiệu giống
như hình 8.
Ngược lại, nếu chọn COM01=1; COM00=0; Từ BOTTOM đến OC, ngã
ra ở mức thấp (0V), Từ OC đến TOP ngã ra ở mức cao(5V). Như vậy khoảng
thời gian từ OC đến TOP chính là độ rộng xung PWM. Dạng tín hiệu giống
như hình 9.

Hình 9. Dạng PWM nghịch.


Đối với PWM của T/C0, giá trị TOP luôn là 0xFF(255), do đó tốc độ PWM chỉ
phụ thuộc vào hệ số chia xung nhịp (Prescaler) quyết định bởi các bit CS00 và
CS01 của thanh ghi TCCR0. Độ rộng xung liên quan tới giá trị OC chứa trong
thanh ghi OCR0 và tùy vào ta chọn chế độ PWM thuận hay nghịch.

Ví dụ: Viết chương trình điều khiển độ rộng xung. Có 2 nút nhấn nối với 2
chân ngắt ngoài INT0 và INT1. Khi nhấn nút nối với INT0 thì độ rộng xung
tăng, khi nhấn nút nối với INT1 thì độ rộng xung giảm.
Chương trình tham khảo:
Bai3_2_D : Thực hành Timer – Fast PWM với T/C0.
Sử dụng file mô phỏng BAI3_2_C.DSN
void main(){
//CẤU HÌNH HƯỚNG DỮ LIỆU
DDRB=0xFF;
DDRD=0x00;
//SỬ DỤNG ĐIỆN TRỞ NỘI KÉO LÊN
PORTD=0xFF;
//KHAI BÁO CHẾ ĐỘ NGẮT – NGẮT TÁC ĐỘNG BẰNG CẠNH LÊN TẠI CÁC CHÂN INTx

Trần Thừa – 2010 113


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

ISC00_bit=0;
ISC01_bit=1;
ISC10_bit=0;
ISC11_bit=1;
//CHO PHÉP NGẮT NGOÀI
INT0_bit=1;
INT1_bit=1;
//CHO PHÉP NGẮT TOÀN CỤC
SREG_I_bit=1;
//CHỌN NGUỒN XUNG ĐẾM - PRESCALER = 1024
//FREQUENCY=16MHz/(256*1024)=61HZ
CS00_bit=1;
CS01_bit=0;
CS02_bit=1;
//CHỌN CHẾ ĐỘ FAST PWM CHO T/C0
WGM00_bit=1;
WGM01_bit=1;
//CHỌN DẠNG XUNG TẠI CHÂN OC0 – PWM THUẬN
COM01_bit=1;
COM00_bit=0;
//ĐẶT GIÁ TRỊ ĐỘ RỘNG XUNG BAN ĐẦU LÀ 50%
OCR0=127;
while(1){
//VÒNG LẶP VÔ TẬN
}
}
//TRÌNH PHỤC VỤ NGẮT INT0 – TĂNG ĐỘ RỘNG XUNG 10%
void Interrupt() org 0x02 {
OCR0+=25;
if(OCR0>=225) OCR0=225;//GIỚI HẠN ĐỘ RỘNG XUNG TỐI ĐA
}
//TRÌNH PHỤC VỤ NGẮT INT1 – GIẢM ĐỘ RỘNG XUNG 10%
void Interrupt1() org 0x04 {
OCR0-=25;
if(OCR0<=50) OCR0=50;//GIỚI HẠN ĐỘ RỘNG XUNG TỐI THIỂU
}

Nhắc lại : Chân Ocx phải được khai báo là ngã ra mới có thể xuất được tín
hiệu PWM.
Phase correct PWM: PWM với pha chính xác.

Trần Thừa – 2010 114


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Phase correct PWM cung cấp một chế độ tạo xung PWM có độ phân
giải cao (high resolution) nên được gọi là Phase correct PWM. Về cách điều
khiển, Phase correct hầu như giống fast PWM, nghĩa là nếu bạn đã biết cách sử
dụng các mode của fast PWM thì bạn sẽ hoàn toàn điều khiển được Phase
correct PWM. Khác nhau cơ bản của 2 chế độ này là trong cách hoạt động, nếu
Fast PWM có chu kỳ hoạt động trong 1 single-slope (một sườn) thì Phase
correct PWM lại dual-slope (hai sườn).
Trong chế độ Fast PWM, T/C đếm từ 0 đến giá trị OC (chứa trong thanh
ghi OCR) thì đảo trạng thái ngã ra chân OC. T/C tiếp tục đếm đến giá trị TOP
thì chân OC lại đảo trạng thái lần nữa đồng thời bộ đếm được xóa về 0.
Trong chế độ Phase correct PWM, Trong chế độ Fast PWM, T/C đếm từ
0 đến giá trị OC (chứa trong thanh ghi OCR) thì đảo trạng thái ngã ra chân OC.
T/C tiếp tục đếm đến giá trị TOP, lúc này chân OC không đảo trạng thái và bộ
đếm cũng không được xóa về 0 mà bắt đầu đếm ngược từ TOP. Khi đếm ngược
về đến giá trị OC thì chân OC mới đảo trạng thái lần nữa, bộ đếm tiếp tục đếm
về 0 để hoàn tất chu kỳ đếm. Để dễ so sánh 2 kiểu PWM, ta hãy quan sát sơ đồ
sau:

Trần Thừa – 2010 115


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Hình 10. So sánh 2 chế độ PWM.

 Nhắc lại:
BOTTOM: là giá trị nhỏ nhất của bộ đếm, luôn bằng 0.
MAX: là giá trị lớn nhất của bộ đếm, MAX=255(0xff) đối với bộ đếm 8 bit như
T/C0 và T/C2, MAX=65535 với bộ đếm 16 bit như T/C1.
TOP: trong các ứng dụng PWM, giá trị này là cố định đối với T/C0 và T/C2
(TOP=0xFF), riêng T/C1 giá trị này có thể cố định(0x00FF, 0x01FF, hoặc
0x03FF) hoặc do người dùng tự định nghĩa thông qua thanh ghi OCR1A hoặc
ICR1 (tùy theo chế độ mà ta chọn qua các bit WGM).

3. Timer/Counter 1
Nhìn chung cách điều khiển T/C1 khá giống với T/C0. Khác nhau cơ
bản là các thanh ghi chứa dữ liệu như bộ đếm TCNT hoặc OCR có độ rộng 16
bit. Các bit điều khiển cũng có nhiều hơn do T/C1 có rất nhiều chế độ hoạt
động. Sau đây xin giới thiệu các thanh ghi điều khiển T/C1.
Thanh ghi điều khiển: Gồm có 2 thanh ghi TCCRA và TCCRB

Ta có thể thấy các bit quen thuộc như CS, WGM, COM với chức năng tương tự
T/C0:

Trần Thừa – 2010 116


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Bit 0-2 của TCCT1B: Các bit chọn nguồn xung cho bộ đếm (CS-Clock
Select)
CS12 CS11 CS10 Mô tả
0 0 0 Không dùng nguồn xung (T/C) không làm việc
0 0 1 Lấy trực tiếp nguồn xung nuôi chip
0 1 0 Lấy tần số của nguồn xung nuôi chip chia 8
0 1 1 Lấy tần số của nguồn xung nuôi chip chia 64
1 0 0 Lấy tần số của nguồn xung nuôi chip chia 256
1 0 1 Lấy tần số của nguồn xung nuôi chip chia 1024
1 1 0 Lấy nguồn xung ngoài tại chân T1, tác động bằng
cạnh xuống
1 1 1 Lấy nguồn xung ngoài tại chân T1, tác động bằng
cạnh lên

Bit 3 và 4 của TCCR1B và bit 0 và 1 của TCCT1A: Các bit chọn dạng
sóng (chọn chế độ làm việc của T/C1) (WGM-WAVE FORM
GENERATION MODE)
T/C1 có đến 15 chế độ tạo dạng sóng, nhưng thực chất nếu sử dụng hết chức
năng của T/C1 ta chỉ cần 7 chế độ. Bảng sau liệt kê tất cả các chế độ tạo dạng
sóng của T/C1.

Trần Thừa – 2010 117


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Trong đó, khi dùng chế độ PWM ta chỉ cần nắm chế độ 10, 14 (hoặc 11, 15).
Khi dùng chế độ CTC có thể dùng chế độ 4 hoặc 12.

Bit 6-7 và bit 4-5: Các bit này điều khiển sự vận hành của chân OC1A và
OC1B. Do T/C1 có 2 kênh PWM độc lập nhau và việc xuất dữ liệu của 2 kênh
sẽ do 2 chân tương ứng là OC1A và OC1B đảm nhiệm nên khi dùng kênh nào
thì phải cấu hình chân OCR1x tương ứng làm ngã ra.
Sau đây là các bảng thiết lập tùy chọn tương quan giữa các bit COM và WGM.
Chế độ không dùng PWM
COM1A1/COM1B1 COM1A0/COM1B0 Mô tả
0 0 Chế độ nhập xuất thông thường.
Đảo trạng thái chân OC1A/OC1B
0 1
khi có so sánh trùng khớp.
Xóa bit OC1A/OC1B khi có so
1 0 sánh trùng khớp. Chân
OC1A/OC1B xuống mức thấp.
Đặt bit OC1A/OC1B khi có so
1 1 sánh trùng khớp. Chân
OC1A/OC1B lên mức cao.

Chế độ PWM tốc độ cao


COM1A1/COM1B1 COM1A0/COM1B0 Mô tả
0 0 Chế độ nhập xuất thông thường.
WGM13:0 = 15: Đảo trạng thái OCnA
khi so sánh trùng khớp, OCnB chế độ
0 1 nhập xuất thông thường.
Mọi thiết lập khác đối với WGM13:0:
Chế độ nhập xuất thông thường.
Xóa bit OC1A/OC1B khi so sánh trùng
1 0 khớp. Đặt OC1A/OC1B tại BOTTOM.
Chế độ thuận.
Đặt bit OC1A/OC1B khi so sánh trùng
1 1 khớp. Xóa OC1A/OC1B tại BOTTOM.
Chế độ nghịch.

Chế độ PWM pha và tần số chính xác.


COM1A1/COM1B1 COM1A0/COM1B0 Mô tả
0 0 Chế độ nhập xuất thông thường.
WGM13:0 = 9 hoặc 14: Đảo trạng thái
OCnA khi so sánh trùng khớp, OCnB chế
0 1 độ nhập xuất thông thường.
Mọi thiết lập khác đối với WGM13:0: Chế
độ nhập xuất thông thường.
Xóa bit OC1A/OC1B khi so sánh trùng
1 0 khớp và đang đếm lên. Đặt OC1A/OC1B
khi so sánh trùng khớp và đang đếm

Trần Thừa – 2010 118


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

xuống.
Đặt bit OC1A/OC1B khi so sánh trùng
khớp và đang đếm lên. Xóa OC1A/OC1B
1 1
khi so sánh trùng khớp và đang đếm
xuống.

Thanh ghi giá trị bộ đếm : TCNT1 – 16 bit gồm 1 byte cao (TCNT1H) và 1
byte thấp(TCNT1L).

Hai byte này truy xuất độc lập.

Thanh ghi so sánh kênh A: OCR1A – 16bit gồm 1 byte cao (OCR1AH) và
1 byte thấp(OCR1AL).

Hai byte này truy xuất độc lập, chứa giá trị so sánh kênh A.
Thanh ghi so sánh kênh B: OCR1B – 16bit gồm 1 byte cao (OCR1BH) và 1
byte thấp(OCR1BL).

Hai byte này truy xuất độc lập. chứa giá trị so sánh kênh B.

Thanh ghi bắt sự kiện vào - ICR1 (InputCapture Register 1): ICR1 - 16bit
gồm 1 byte cao (ICR1H) và 1 byte thấp(ICR1L).

Hai byte này truy xuất độc lập. Khi có 1 sự kiện trên chân ICP1, thanh ghi
ICR1 sẽ ghi lại giá trị của thanh ghi đếm TCNT1. Một ngắt có thể xảy ra trong
trường hợp này, vì thế Input Capture có thể được dùng để cập nhật giá trị
“TOP” của T/C1.
 Ghi chú :

Trần Thừa – 2010 119


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Trong MikroC, các thanh ghi 16 bit được chia thành 2 byte độc lập và
có tên gọi giống như trong datasheet.
Ví dụ : thanh ghi ICR1 sẽ được chia thành ICR1L (byte thấp) và
ICR1H (byte cao).
+Để ghi dữ liệu vào thanh ghi 16 bit, ta cần chia dữ liệu ra thành 2
phần 8 bit và ghi dữ liệu vào 8 bit cao trước, 8 bit thấp sau.
Ví dụ : Ghi dữ liệu 0x1387 vào thanh ghi ICR1 ta làm như sau :
ICR1H=0x13 ;
ICR1L=0x87 ;
+Để đọc dữ liệu từ thanh ghi 16 bit, ta chỉ cần thao tác như sau :
unsigned int bien16bit;
bien16bit = (( thanh_ghi_16bitH * 256) + thanh_ghi_16bitL);

Thanh ghi mặt nạ của bộ đếm: Timer/Counter Interrupt Mask Register –


TIMSK

Bit2: Cho phép ngắt tràn bộ đếm T/C1


Bit3 : Cho phép ngắt so sánh trùng khớp kênh B.
Bit4 : Cho phép ngắt so sánh trùng khớp kênh A.
Bit5: Cho phép ngắt khi bắt sự kiện vào.

Trên đây là những giới thiệu sơ qua về các thanh ghi có liên quan đến
việc điều khiển T/C1. Do T/C1 có rất nhiều chế độ làm việc, các bạn có thể
tham khảo thêm datasheet để sử dụng hết chức năng của bộ T/C này.
Trong phần tiếp theo tôi sẽ giới thiệu ứng dụng quan trọng của bộ T/C1
là tạo PWM với việc điều khiển cả độ rộng xung và tần số (T/C0 và T/C2
không điều khiển được tần số chính xác do giá trị TOP cố định).
 Nhắc lại:
BOTTOM: là giá trị nhỏ nhất của bộ đếm, luôn bằng 0.
MAX: là giá trị lớn nhất của bộ đếm, MAX=255(0xff) đối với bộ đếm 8 bit như
T/C0 và T/C2, MAX=65535 với bộ đếm 16 bit như T/C1.
TOP: trong các ứng dụng PWM, giá trị này là cố định đối với T/C0 và T/C2
(TOP=0xFF), riêng T/C1 giá trị này có thể cố định(0x00FF, 0x01FF, hoặc
0x03FF) hoặc do người dùng tự định nghĩa thông qua thanh ghi OCR1A hoặc
ICR1 (tùy theo chế độ mà ta chọn qua các bit WGM).
Điều khiển servo với Phase correct PWM của bộ T/C1:
Servo là một tổ hợp gồm 1 động cơ DC công suất nhỏ, hộp giảm tốc và
bộ điều khiển góc quay. Có 2 loại chính là Servo thường và digital Servo, trong
ví dụ này tôi giới thiệu Servo thường (phổ biến). Servo thường có 3 dây, dây
màu đen là dây GND, dây đỏ là dây nguồn (thường là 5V) và 1 dây trắng hoặc
vàng và dây tín hiệu (có một số loại Servo có màu dây khác, bạn cần tham khảo
datasheet của chúng). Vì các Servo đã có sẵn mạch điều khiển góc quay bên
trong nên chúng ta không cần bất cứ giải thuật gì mà chỉ cần cấp tín hiệu PWM
thích hợp cho dây điều khiển là Servo có thể xoay đến 1 vị trí nào đó (chú ý là

Trần Thừa – 2010 120


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

Servo thường chỉ xoay nữa vòng, điều khiển servo là điều khiển góc xoay chứ
không phải điều khiển vận tốc xoay). Hình 11 là hình ảnh servo và cách điều
khiển servo.

Hình 11. Servo và cách điều khiển servo.

Ví dụ: Viết chương trình điều khiển 2 servo với 2 ngắt ngoài INT0, INT1 kết
hợp chế độ Fast PWM của T/C1. Khi có ngắt ngoài tại INT0 thì servo 1 đổi vị
trí (độ rộng xung thay đổi từ 1.5ms đến 2ms và xoay vòng). Khi có ngắt ngoài
tại INT4 thì servo 2 đổi vị trí (độ rộng xung thay đổi từ 1.5ms đến 2ms và xoay
vòng). Chu kỳ xung điều khiển servo là 20ms. Xem lại công thức tính tần số
PWM ở hình 10 trang 116.

Chương trình tham khảo.


Bai3_2_E : Thực hành Timer 16 bit– Fast PWM 2
kênh. Sử dụng file mô phỏng BAI3_2_D.DSN.
//KHAI BÁO BIẾN CHỨA GIÁ TRỊ ĐỘ RỘNG XUNG
int duty1, duty2 ;
//PROTOTYPE HÀM THAY ĐỔI ĐỘ RỘNG XUNG
void PWM_change_duty(int duty,char channel );
void main(){
// CẤU HÌNH HƯỚNG DỮ LIỆU
DDRB=0x00;
DDD4_bit=1;
DDD5_bit=1;
PORTD2_bit=1;
PORTD3_bit=1;
PORTB2_bit=1;

Trần Thừa – 2010 121


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

// CHỌN CHẾ ĐỘ NGẮT TÁC ĐỘNG BẰNG CẠNH XUỐNG


ISC00_bit=0;
ISC01_bit=1;
ISC10_bit=0;
ISC11_bit=1;
ISC2_bit=0;
// CHO PHÉP NGẮT NGOÀI
INT0_bit=1;
INT1_bit=1;
INT2_bit=1;
// CHO PHÉP NGẮT TOÀN CỤC
SREG_I_bit=1;
// CHỌN MODE 14 – CHẾ ĐỘ FAST PWM 2 KÊNH
WGM10_bit=0;
WGM11_bit=1;
WGM12_bit=1;
WGM13_bit=1;
// CHỌN DẠNG XUNG PWM CHẾ ĐỘ THUẬN CHO KÊNH A
COM1A0_bit=0;
COM1A1_bit=1;
// CHỌN DẠNG XUNG PWM CHẾ ĐỘ THUẬN CHO KÊNH B
COM1B0_bit=0;
COM1B1_bit=1;
// CHỌN TẦN SỐ 50Hz (20ms) CHO PWM => ICR1=4999=0X1387
ICR1H=0x13; // Ghi byte cao trước
ICR1L=0x87; // Ghi byte thấp sau
// CHỌN GIÁ TRỊ ĐẦU CHO ĐỘ RỘNG XUNG LÀ 1ms => OCR1A/B=249
OCR1AH=0; // Ghi byte cao trước
OCR1AL=249; // Ghi byte thấp sau
OCR1BH=0; // Ghi byte cao trước
OCR1BL=249; // Ghi byte thấp sau
// CHỌN NGUỒN XUNG HỆ THỐNG VỚI HỆ SỐ CHIA LÀ 64
CS10_bit=1;
CS11_bit=1;
CS12_bit=0;
while(1){
//VÒNG LẶP VÔ TẬN
}
}
void EX_INT0() org 0x02 {

Trần Thừa – 2010 122


Lập trình vi điều khiển AVR với ngôn ngữ C WWW.EEELABS.ORG

duty1+=125;
if(duty1>499) duty1=249;
PWM_change_duty(duty1,1);
}
void EX_INT01() org 0x04 {
duty2+=125;
if(duty2>499) duty2=249;
PWM_change_duty(duty2,2);
}
//HÀM THAY ĐỔI ĐỘ RỘNG XUNG
void PWM_change_duty(int duty,char channel ){
switch(channel){
case 1:
OCR1AH=duty>>8;
OCR1AL=duty;
break;
case 2:
OCR1BH=duty>>8;
OCR1BL=duty;
break;
}
}

Hàm thay đổi độ rộng xung PWM_change_duty được xây dựng dựa trên
cách ghi dữ liệu vào thanh ghi 16 bit đã giới thiệu ở trang 120.

MikroC cung cấp cho ta 2 thư viện PWM dành cho T/C 8 bit và T/C 16
bit. Riêng với thư viện PWM 16 bit, hàm khởi tạo chế độ PWM chỉ khởi tạo
được các chế độ định sẵn nên rất khó có thể thay đổi tần số PWM như ý muốn.
Do đó khuyến khích các bạn nắm vững cách khởi tạo thủ công, tuy hơi phức
tạp nhưng sẽ khai thác được hết các tính năng của T/C 16 bit. Các bạn có thể
tham khảo 2 thư viện này trong file help của Mikroc ( ấn F1).

Trần Thừa – 2010 123

You might also like