Bài 4 - Timer - Counter

( 245 Votes )
There are no translations available.

Nội dung Các bài cần tham khảo trước
  1. Giới thiệu.
  2. Tổng quan Timer/Counter trên AVR.
  3. Sử dụng Timer/Counter.
    1. Timer/Counter0
    2. Timer/Counter1
    Download ví dụ

 

I. Giới thiệu.

       Trong bài 3 tôi đã giới thiệu khái quát phương pháp lập trình bằng ngôn ngữ C cho AVR với WinAVR và cách sử dụng ngắt trong AVR. Bài 4 này chúng ta sẽ khảo sát các chế độ hoạt động của  phương pháp điều khiển các bộ định thời, đếm (Timer/Counter) trong AVR. Công cụ phục vụ cho bài này vẫn là bộ công cụ WinAVR và phần mềm mô phỏng Proteus. Tôi vẫn dùng chip Atmega8 để làm ví dụ. Một điều không may mắn là không phải tất cả các bộ Timer/Counter trên tất cả các dòng chip AVR là như nhau, vì thế những gì tôi trình bày trong bài này có thể sẽ không đúng với các dòng AVR khác như AT90S…Tuy nhiên tôi cũng sẽ cố gắng chỉ ra một số điểm khác biệt cơ bản để các bạn có thể tự mình điều khiển các chip khác. Nội dung bài học này bao gồm:

  • Nắm bắt cơ bản các bộ Timer/Counter có trên AVR.
  • Sử dụng các Timer/Counter như các bộ định thời.
  • Sử dụng các Timer/Counter như các bộ đếm.
  • Sử dụng các Timer/Counter như các bộ tạo xung điều rộng PWM.
  • Viết một ví dụ điều khiển động cơ RC servo bằng PWM.

II. Tổng quan các bộ Timer/Counter trên chip Atmega8.

       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, như tên gọi của chúng, là định thì (tạo ra một khoảng thời gian, đếm thời gian…) và đế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 xung điều rộng 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, cụ thể trên chip Atmega8 có 2 bộ Timer 8 bit (Timer/Counter0 và Timer/Counter2) và 1 bộ 16 bit (Timer/Counter1). 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 Atmega8:
       Timer/Counter0: là một bộ định thời, đếm đơn giản với 8 bit. Gọi là đơn giản vì bộ này chỉ có 1 chế độ hoạt động (mode) so với 5 chế độ của bộ Timer/Counter1. Chế độ hoat động của Timer/Counter0 thực chất có thể coi như 2 chế độ nhỏ (và cũng là 2 chức năng cơ bản) đó là tạo ra một khoảng thời gian và đếm sự kiện. Chú ý là trên các chip AVR dòng mega sau này như Atmega16,32,64…chức năng của Timer/Counter0 được nâng lên như các bộ Timer/Counter1…
       Timer/Counter1: là bộ định thời, đếm đa năng 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, Timer/Counter1 còn được dùng để tạo ra xung điều rộng PWM dùng cho các mục đích điều khiển. Có thể tạo 2 tín hiệu PWM  độc lập trên các chân OC1A (chân 15) và OC1B (chân 16) bằng Timer/Counter1. Các bộ Timer/Counter kiểu này được tích hợp thêm khá nhiều trong các chip AVR sau này, ví dụ Atmega128 có 2 bộ, Atmega2561 có 4 bộ…
       Timer/Counter2: tuy là một module 8 bit như Timer/Counter0 nhưng Timer/Counter2 có đến 4 chế độ hoạt động như Timer/Counter1, 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ế độ asynchronous).
       Trong phạm vi bài 4 này, tôi chủ yếu hướng dẫn cách sử dụng 4 chế độ hoạt động của các Timer/Counter. Chế độ asynchronous của Timer/Counter2 sẽ được bỏ qua vì có thể chế độ này không được sử dụng phổ biến.
Trước khi khảo sát hoạt động của các Timer/Counter, chúng ta thống nhất cách gọi tắt tên gọi của các Timer/Counter là T/C, ví dụ T/C0 để chỉ Timer/Counter0…

II. Sử dụng Timer/Counter.

       Có một số định nghĩa quan trọng mà chúng ta cần nắm bắt trước khi sử dụng các T/C trong AVR:
  • 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: là giá trị mà khi T/C đạt đến nó sẽ thay đổi trạng thái, giá trị này không nhất thiết là số lớn nhất 8 bit hay 16 bit như MAX, giá trị của TOP có thể thay đổi bằng cách điều khiển các bit điều khiển tương ứng hoặc có thể nhập trừ tiếp thông qua một số thanh ghi. Chúng ta sẽ hiểu rõ về giá trị TOP trong lúc khảo sát T/C1.
1. Timer/Counter0:
       Thanh ghi: có 4 thanh ghi được thiết kế riêng cho hoạt động và điều khiển T/C0, đó là:
  • TCNT0 (Timer/Counter Register): là 1 thanh ghi 8 bit chứa giá trị vận hành của T/C0. Thanh ghi này cho phép bạn đọc và ghi giá trị một cách trực tiếp.
  • TCCR0 (Timer/Counter Control Register): là thanh ghi điều khiển hoạt động của T/C0. Tuy là thanh ghi 8 bit nhưng thực chất chỉ có 3 bit có tác dụng đó là CS00, CS01 và CS02.
TCCR0

       Các bit CS00, CS01 và CS02 gọi là các bit chọn nguồn xung nhịp cho T/C0 (Clock Select). Chức năng các bit này được mô tả trong bảng 1.

Bảng 1: chức năng các bit CS0X

TCCR0
  • TIMSK (Timer/Counter Interrupt Mask Register): là thanh ghi mặt nạ cho ngắt của tất cả các T/C trong Atmega8, trong đó chỉ có bit TOIE0 tức bit số 0 (bit đầu tiên) trong thanh ghi này là liên quan đến T/C0, bit này có tên là bit cho phép ngắt khi có tràn ở T/C0. Tràn (Overflow) là hiện tượng xảy ra khi bộ giá trị trong thanh ghi TCNT0 đã đạt đến MAX (255) và lại đếm thêm 1 lần nữa.
TCCR0

       Khi bit TOIE0=1, và bit I trong thanh ghi trạng thái được set (xem lại bài 3 về điều khiển ngắt), nếu một “tràn” xảy ra sẽ dẫn đến ngắt tràn.

  • TIFR (Timer/Counter Interrupt Flag Register): là thanh ghi cờ nhớ cho tất cả các bộ T/C. Trong thanh ghi này bit số 0, TOV0 là cờ chỉ thị ngắt tràn của T/C0. Khi có ngắt tràn xảy ra, bit này tự động được set lên 1. Thông thường trong điều khiển các T/C vai trò của thanh ghi TIFR không quá quan trọng.
       Hoạt động: T/C0 hoạt động rất đơn giản, hoạt động của T/C được “kích” bởi một tín hiệu (signal), cứ mỗi lần xuất hiện tín hiệu “kích” giá trị của thanh ghi TCNT0 lại tăng thêm 1 đơn vị, thanh ghi này tăng cho đến khi nó đạt mức MAX là 255, tín hiệu kích tiếp theo sẽ làm thanh ghi TCNT0 trở về 0 (tràn), lúc này bit cờ tràn TOV0 sẽ tự động được set bằng 1. Với cách thức hoạt động như thế có vẻ T/C0 vô dụng vì cứ tăng từ 0 đến 255 rồi lại quay về 0, và quá trình lặp lại. Tuy nhiên, yếu tố tạo sự khác biệt chính là tín hiệu kích và ngắt tràn, kết hợp 2 yếu tố này chúng ta có thể tạo ra 1 bộ định thời gian hoặc 1 bộ đếm sự kiện. Trước hết bạn hãy nhìn lại bảng 1 về các bit chọn xung nhịp cho T/C0. Xung nhịp cho T/C0 chính là tín hiệu kích cho T/C0. Xung nhịp này có thể tạo bằng nguồn tạo dao động của chip (thạch anh, dao động nội trong chip…). Bằng cách đặt giá trị cho các bit CS00, CS01 và CS02 của thanh ghi điều khiển TCCR0, chúng ta sẽ quyết định bao lâu thì sẽ kích T/C0 một lần. Ví dụ mạch ứng dụng của bạn có nguồn dao động clk = 1MHz tức  chu kỳ 1 nhịp là 1us (1 micro giây), bạn đặt thanh ghi TCCR0=5 (tức SC02=1, CS01=0, CS00=1). Căn cứ theo bảng 1, tín hiệu kích cho T/C0 sẽ bằng clk/1024 nghĩa là sau 1024us thì T/C0 mới được kích 1 lần, nói cách khác giá trị của TCNT0 tăng thêm 1 sau 1024us (chú ý là tần số được chia cho 1024 thì chu kỳ sẽ tăng 1024 lần). Quan sát 2 dòng cuối cùng trong bảng 1 bạn sẽ thấy rằng tín hiệu kích cho T/C0 có thể lấy từ bên ngoài (External clock source), đây chính là ý tưởng cho hoạt động của chức năng đếm sự kiện trên T/C0. Bằng cách thay đổi trạng thái chân T0 (chân 6 trên chip Atmega8) chúng ta sẽ làm tăng giá trị thanh ghi TCNT0 hay nói cách khác T/C0 có thể dùng để đếm sự kiện xảy ra trên chân T0. Dưới đây chúng ta sẽ xem xét cụ thể cách điều khiển T/C0 theo 1 chế độ định thời gian và đếm.

1.1 Bộ định thời gian.

       Chúng ta có thể tạo ra 1 bộ định thì để cài đặt một khoảng thời gian nào đó. Ví dụ bạn muốn rằng cứ sau chính xác 1ms thì chân PB0 thay đổi trạng thái 1 lần (nhấp nháy), bạn lại không muốn dùng các lệnh delay như trước nay vẫn dùng vì nhược điểm của delay là “CPU không làm gì cả” trong lúc delay, vì thế trong nhiều trường hợp các lệnh delay rất hạn chế được sử dụng. Bây giờ chúng ta dùng T/C0 để làm việc này, ý tưởng là chúng ta cho bộ đếm T/C0 hoạt động, khi nó đếm đủ 1ms thì nó sẽ tự kích hoạt ngắt tràn, trong trình phục vụ ngắt tràn chúng tat hay đổi trạng thái chân PB0. Tôi minh họa ý tưởng như trong hình 1.
timer
Hình 1. So sánh 2 cách làm việc.
       (CPU nop: trong khoảng thời gian này CPU không làm gì cả)
       Một vấn đề nảy sinh lúc này, như tôi trình bày trong phần trước, T/C0 chỉ đếm từ 0 đến 255  rồi lại quay về 0 (xảy ra 1 ngắt tràn), như thế dường như chúng ta không thể cài đặt giá trị mong muốn bất kỳ cho T/C0? Câu trả lời là chúng ta có thể bằng cách gán trước một giá trị cho thanh ghi TCNT0, khi ấy T/C0 sẽ đếm từ giá trị mà chúng ta gán trước và kết thúc ở 255. Tuy nhiên do khi tràn xảy ra, TCNT0 lại được tự động trả về 0, do đó việc gán giá trị khởi tạo cho TCNT0 phải được thực hiện liên tục sau mỗi lần xảy ra tràn, vị trí tốt nhất là đặt trong trình phục vụ ngắt tràn.
       Việc còn lại và cũng là việc quan trọng nhất là việc tính toán giá trị chia (prescaler) cho xung nhịp của T/C0 và việc xác định giá trị khởi đầu cần gán cho thanh ghi TCNT0 để có được 1 khoảng thời gian định thì chính xác như mong muốn. Trước hết chúng ta sẽ chọn prescaler  sao cho hợp lí nhất (chọn giá trị chia bằng cách set 3 bit CS02,CS01,CS00). 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, nếu chúng ta để prescaler=1, 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 1ms mà ta mong muốn. Nếu chọn prescaler=8 (xem bảng 1) nghĩa là cứ sau 8 nhịp (8us) 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=2048us, lớn hơn 1ms, vậy ta hoàn toàn có thể sử dụng prescaler=8 để tạo ra một khoảng định thì 1ms. Bước tiếp theo là xác định giá trị khởi đầu của TCNT0 để T/C0 đếm đúng 1ms (1000us). Ứng với prescaler=8 chúng ta đã biết là cứ 8us thì TCNT0 tăng 1 đơn vị, dễ dàng tính được bộ đếm cần đếm 1000/8=125 lần để hết 1ms, do đó  giá trị ban đầu của TCNT0 phải là 256-125=131. Bạn có thể quan sát hình 2 để hiểu thấu đáo hơn.
setting
Hình 2. Quá trình thực hiện.
       Hãy tạo 1 Project bằng Programmer Notepad với tên gọi TIMER0 và viết đoạn code cho Project này như trong list 1.
List 1. Định thì 1ms với T/C0.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

int main(void){
       DDRB=0xFF;                //PORTB la output PORT
       PORTB=0x00;

       TCCR0=(1<<CS01);// CS02=0, CS01=1, CS00=0: chon Prescaler = 8
       TCNT0=131;              //gan gia tri khoi tao cho T/C0
       TIMSK=(1<<TOIE0);//cho phep ngat khi co tran o T/C0
       sei();                       //set bit I cho phep ngat toan cuc

       while (1){           //vòng lặp vô tận
              //do nothing
       }
       return 0;
}

//trinh phuc vu ngat tran T/C0
ISR (TIMER0_OVF_vect ){       
       TCNT0=131; //gan gia tri khoi tao cho T/C0 
       PORTB^=1; //doi trang thai Bit PB0
}

       Đoạn code rất đơn giản, bạn chỉ cần chú ý đến 3 dòng khai báo cho T/C0 (dòng  9, 10, 11). Với dòng 9: TCCR0=(1<<CS01) là 1 cách set bit CS01 trong thanh ghi điều khiển TCCR0 lên 1, 2 bit CS02 và CS00 được để giá trị 0 (bạn xem lại bài 3 về cách set các bit đặc biệt trong các thanh ghi), tóm lại dòng này tương đương TCCR0=2, giá trị Prescaler được chọn bằng 8 (tham khảo bảng 1). Dòng 10 chúng ta gán giá trị khởi tạo cho thanh ghi TCNT0. Và dòng 11 set bit TIOE0 lên 1 để cho phép ngắt xảy ra khi có tràn ở T/C0. Trong trình phục vụ ngắt tràn T/C0, chúng ta sẽ thực hiện đổi trạng thái chân PB0 bằng toán từ XOR (^), chú ý đến ý nghĩa của toán tử XOR: nếu XOR một bit với số 1 thì bit này sẽ chuyển trạng thái (từ 0 sang 1 và ngược lại). Cuối cùng và quan trọng là chúng ta cần gán lại giá trị khởi tạo cho T/C0.
     Bạn có thể vẽ môt mạch điện mô phỏng đơn giản dùng 1 Oscilloscope như trong hình 3 để kiểm tra hoạt động của  đoạn code.

sim0

Hình 3. Mô phỏng định thì của T/C0.
1.2 Bộ đếm sự kiện.
       Như tôi trình bày trong phần hoạt động của T/C0, chúng ta có thể dùng T/C0 như một bộ đếm (counter) để đếm các sự kiện (sự thay đổi trạng thái) xảy ra trên chân T0. Bằng cách đặt giá trị cho thanh ghi TCCR0 = 6 (CS02=1, CS01=1, CS00=0) cho phép đếm “cạnh xuống” trên chân T0, nếu TCCR0 = 7 (CS02=1, CS01=1, CS00=1) thì “cạnh lên” trên chân T0 sẽ được đếm. Có sử dụng ngắt hay không phụ thuộc vào mục đích sử dụng. Khảo sát 1 ví dụ đơn giản gần giống với ví dụ đếm trong bài AVR2 nhưng sử dụng T/C0 và chỉ đếm 1 chiều tăng. Kết nối mạch điện như trong hình 4, mỗi lần Button 1 được nhấn, giá trị đếm tăng thêm 1. Button 2 dùng reset giá trị đếm về 0. Đoạn code cho ví dụ thứ 2 này được trình bày trong List 2.
sim0
Hình 4. Đếm 1 chiều bằng T/C0.
List 2. Đếm sự kiện với T/C0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void){
       DDRB=0xFF;                //PORTB la output PORT
       PORTB=0x00;
       DDRD=0x00; //khai bao PORTD la input de ket noi Button kich vao chan T0
       PORTD=0xFF; //su dung dien tro keo len cho PORTD

       TCCR0=(1<<CS02)|(1<<CS01);// CS02=1, CS01=1, CS00=0: xung nhip tu  chan T0, down
       TCNT0=0;

       while (1){           //vòng lặp vô tận
             if (TCNT0==10) TCNT0=0;
             PORTB=TCNT0;   //xuat gia tri dem ra led 7 doan
             if (bit_is_clear(PIND,7)) TCNT0=0;  //Reset bo dem neu chan PD7=0
       }
       return 0;
}
       Nội dung trong chương trình chính là khai báo các hướng giao tiếp cho các PORT, PORTB là ouput để xuất kết quả đếm ra led 7 đoạn, PORTD được khái báo input vì các button được nối với PORT này. T/C0 được khai báo sử dụng nguồn kích ngoài từ T0, dạng cạnh xuống thông qua dòng TCCR0=(1<<CS02)|(1<<CS01), bạn cũng có thể khai báo tương đương là TCCR0=6 (tham khảo bảng 1). Giá trị của bộ đếm sẽ được xuất ra PORTB để kiểm tra. Điểm chú ý trong đoạn chương trình này là macro “bit_is_clear”, đây là một macro được định nghĩa trong file “sfr_defs.h” dùng để kiểm tra 1 bit trong một thanh ghi đặc biệt có được xóa (bằng 0) hay không, trong trường hợp của đoạn code trên: “if(bit_is_clear(PIND,7)) TCNT0=0;” nghĩa là kiểm tra xem nếu chân PD7 được kéo xuống 0 (button 2 được nhấn) thì sẽ reset bộ đếm về 0.
       Như vậy việc sử dụng T/C0 là tương đối đơn giản, bạn chỉ cần khai báo các giá trị thích hợp cho thanh ghi điều khiển TCCR0 bằng cách tham khảo bảng 1, sau đó khởi tạo giá trị cho TCNT0 (nếu cần thiết), khai báo có sử dụng ngắt hay không bằng cách set hay không set bit TOIE0 trong thanh ghi TIMSK là hoàn tất.
2. Timer/Counter1:
       Timer/Counter1 là bộ T/C 16 bits, đa chức năng. Đây là bộ T/C rất lý tưởng cho lập trình đo lường và điều khiển vì có độ phân giải cao (16 bits) và có khả năng tạo xung điều rộng PWM (Pulse Width Modulation – thường dùng để điều khiển động cơ).
       Thanh ghi: có khá nhiều thanh ghi liên quan đến T/C1. Vì là T/C 16 bits trong khi độ rộng bộ nhớ dữ liệu của AVR là 8 bit (xem lại bài 2) nên đôi khi cần dùng những cặp thanh ghi 8 bits tạo thành 1 thanh ghi 16 bit, 2 thanh ghi 8 bits sẽ có tên kết thúc bằng các ký tự L và H trong đó L là thanh ghi chứa 8 bits thấp (LOW) và H là thanh ghi chứa 8 bits cao (High) của giá trị 16 bits mà chúng tạo thành.
  • TCNT1H và TCNT1L (Timer/Counter Register): là 2 thanh ghi 8 bit tạo thành thanh ghi 16 bits (TCNT1) chứa giá trị vận hành của T/C1. Cả 2 thanh ghi này cho phép bạn đọc và ghi giá trị một cách trực tiếp. 2 thanh ghi được kết hợp như sau:
TCNT1
  • TCCR1A và TCCR1B (Timer/Counter Control Register): là 2 thanh ghi điều khiển hoạt động của T/C1. Tất cả các mode hoạt động của T/C1 đều được xác định thông qua các bit trong 2 thanh ghi này. Tuy nhiên, đây không phải là 2 byte cao và thấp của một thanh ghi mà là 2 thanh ghi hoàn toàn độc lập. Các bit trong 2 thanh ghi này bao gồm các bit chọn mode hay chọn dạng sóng (Waveform Generating Mode – WGM), các bit quy định dạng ngõ ra (Compare Output Match – COM), các bit chọn giá trị chia prescaler cho xung nhịp (Clock Select – CS)…Cấu trúc của 2 thanh ghi được trình bày như bên dưới.
TCCR1A
TCCR1B
       Nhìn chung để “thuộc” hết cách phối hợp các bit trong 2 thanh ghi TCCR1A và TCCR1B là tương đối phức tạp vì T/C1 có rất nhiều mode hoạt động, chúng ta sẽ khảo sát chúng trong phần các chế độ hoạt động của T/C1 bên dưới. Ở đây, trong thanh ghi TCCR1B có 3 bit khá quen thuộc là CS10, CS11 và CS12. Đây là các bit chọn xung nhịp cho  T/C1 như truong T/C0. Bảng 2 sẽ tóm tắt các chế độ xung nhịp trong T/C1.
Bảng 2: chức năng các bit CS12, CS11 và CS10.
CS
  • OCR1A và OCR1B (Ouput Compare Register A và B): có một số khái niệm mới mà chúng ta cần biết khi làm việc với T/C1, một trong số đó là Ouput Compare (sorry, I don’t wanna translate it to Vietnamese). Trong lúc T/C hoạt động, giá trị thanh ghi TCNT1 tăng, giá trị này được liên tục so sánh với các thanh ghi OCR1A và OCR1B (so sánh độc lập với từng thanh ghi), việc so sánh này trên AVR gọi là gọi là Ouput Compare. Khi giá trị so sánh bằng nhau thì 1 “Match” xảy ra, khi đó một ngắt hoặc 1 sự thay đổi trên chân OC1A (hoặc/và chân OC1B) xảy ra (đây là cách tạo PWM bởi T/C1). Tại sao lại có A và B? Đó là vì người thiết kế AVR muốn mở rộng khả năng ứng dụng T/C1 cho bạn. A và B đại diện cho 2 kênh (channel)  và B. Cũng vì điều này mà chúng ta có thể tạo 2 kênh PWM bằng T/C1. Tóm  lại, cơ bản 2 thanh ghi này chứa các giá trị để so sánh, chức năng và các chế độ hoạt động cụ thể của chúng sẽ được khảo sát trong các phần sau.
OCR1A
  • ICR1 (InputCapture Register 1): khái niệm mới thứ 2 của T/C1 là Input Capture. Khi có 1 sự kiện trên chân ICP1 (chân 14 trên Atmega8), thanh ghi ICR1sẽ “capture” 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.
  • TIMSK (Timer/Counter Interrupt Mask Register): các bộ T/C trên AVR dùng chung thanh ghi mặt nạ ngắt, vì thế TIMSK cũng được dùng để quy định ngắt cho T/C1. Có điều lúc này chúng ta chỉ quan tâm đến các bit từ 2 đến 5 của TIMSK. Có tất cả 4 loại ngắt trên T/C1 (nhớ lại T/C0 chỉ có 1 loại ngắt tràn)
TIMSK
Bit 2 trong TIMSK là TOIE1, bit quy định ngắt tràn cho thanh T/C1 (tương tự trường hợp của T/C0).
Bit 3, OCIE1B là bit cho phép ngắt khi có 1 “Match” xảy ra trong việc so sánh TCNT1 với OCR1B.
Bit 4, OCIE1A là bit cho phép ngắt khi có 1 “Match” xảy ra trong việc so sánh TCNT1 với OCR1A.
Bit 5, TICIE1 là bit cho phép ngắt trong trường hợp Input Capture được dùng.
       Cùng với việc set các bit trên, bit I trong thanh ghi trạng thái phải được set nếu muốn sử dụng ngắt (xem lại bài 3 về điều khiển ngắt).
  • TIFR (Timer/Counter Interrupt Flag Register): là thanh ghi cờ nhớ cho tất cả các bộ T/C. Các bit từ 2 đến 5 trong thanh ghi này là các cờ trạng thái của T/C1.
TIMSK
       Các mode hoạt động: có tất cả 5 chế độ hoạt động chính trên T/C1. Các chế độ hoạt động cơ bản được quy định bởi 4 bit Waveform Generation Mode (WGM13, WGM12, WGM11 WGM10) và một số bit phụ khác. 4 bit Waveform Generation Mode lại được bố trí nằm trong 2 thanh ghi TCCR1A và TCCR1B (WGM13 là bit 4, WGM12 là bit 3 trong TCCR1B trong khi WGM11 là bit 1 và WGM10 là bit 0 trong thanh ghi TCCR1A) vì thế cần phối hợp 2 thanh ghi TCCR1 trong lúc điều khiển T/C1. Các chế độ hoạt động của T/C1 được tóm tắt trong bảng sau 3:
Bảng 3: các bit WGM và các chế độ hoạt động của T/C1.
mode
2.1 Normal mode (Chế độ thường).
       Đây là chế độ hoạt động đơn giản nhất của T/C1. Trong chế độ này, thanh ghi đếm TCNT1 được tăng giá trị từ 0 (BOTTOM) đến 65535 hay 0xFFFF (TOP) và quay về 0. Chế độ này hoàn toàn giống cách mà Timer0 hoạt động chỉ có khác là giá trị đếm cao nhất là 65535 thay vì 255 như trong timer0. Nhìn vào bảng 3, để set T/C1 ở Normal mode chúng ta cần set 4 bit WGM về 0, vì 0 là giá trị mặc định của các thanh ghi nên thực tế chúng ta không cần tác động đến các bit WGM. Duy nhất một việc quan trọng cần làm là set các bit Clock Select (CS12, SC11, CS10) trong thanh ghi TCCR1B (xem thêm bảng 2). Bạn có thể tham khảo ví dụ của Timer0. Đoạn code trong  list 3 là 1 ví dụ tạo 1 khoảng thời gian 10ms bằng T/C1, normal mode:
List 3. Định thì 10ms với T/C1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

int main(void){
       DDRB=0xFF;                //PORTB la output PORT
       PORTB=0x00;

       TCCR1B=(1<CS10);// CS12=0, CS11=0, CS10=1: chon Prescaler =1
       // thanh ghi TCCR1B duoc dung thay vi TCCR0 cua Timer0
       TCNT1=55535;              //gan gia tri khoi tao cho T/C1
       TIMSK=(1<<TOIE1);//cho phep ngat khi co tran o T/C1
       sei();                       //set bit I cho phep ngat toan cuc

       while (1){           //vòng lặp vô tận
             //do nothing
       }
       return 0;
}
//trinh phuc vu ngat tran T/C1
ISR (TIMER1_OVF_vect ){       
       TCNT1=55535; //gan gia tri khoi tao cho T/C1
       PORTB ^=1;  //doi trang thai Bit PB0
}
2.2 Clear Timer on Compare Match (xóa timer nếu xảy ra bằng trong so sánh)-CTC.
       Một cách gọi tắt của chế độ hoạt động này là CTC, một chế độ hoạt động mới trên T/C1. Nhìn vào bảng 3 bạn sẽ thấy có 2 mode CTC (mode 4 và mode 12). Tôi lấy ví dụ mode 4 để giải thích hoạt động của CTC. Khi bạn set các bit Waveform Generation Mode tuong ứng: WGM13=0, WGM12=1, WGM11=0, WGM10=0 thì mode 4 được chọn. Trong mode này, thanh ghi OCR1A chứa giá trị TOP (giá trị so sánh do người dùng đặt), thanh ghi đếm TCNT1 tăng từ 0, khi TCNT1 bằng giá trị chứa trong OCR1A thì một “Compare Match” xảy ra. Khi đó, một ngắt có thể xảy ra nếu chúng ta cho phép ngắt Compare Match (set bit OCF1A trong thanh ghi TIMSK lên 1). Mode này cũng tương đối đơn giản, một ứng dụng cơ bản của mode này là đơn giản hóa việc đếm các sự kiện bên ngoài. Ví dụ bạn kết nối 1 sensor  đếm số người đi vào 1 căn phòng với chấn T1 (chân counter source  của T/C1), bạn muốn rằng cứ sau khi đếm 5 người thì sẽ thông báo 1 lần. List 4 là đoạn code mô tả ví dụ này:
List 4. Phối hợp CTC với đếm sự kiện.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
volatile  usigned char val=0;  //khai bao 1 bien tam val va khoi tao =0
int main(void){
       DDRB=0xFF;                //PORTB la output PORT
       PORTB=0x00;  
       TCCR1B=(1<<WGM12)|(1<<CS12)|(1<<CS11); //xung nhip tu chan T1, canh xuong
       OCR1A=4;             //gan gia tri can so sanh
       TIMSK=(1<OCIE1A);//cho phep ngat khi gia tri dem bang 4
       sei();                       //set bit I cho phep ngat toan cuc

       while (1){           //vòng lặp vô tận
              //do nothing
       }
       return 0;
}
//trinh phuc vu ngat compare match
ISR (TIMER1_COMPA_vect){      
       val++; 
       if (val==10) val=0;  //gioi han bien val tu 0 den 9
       PORTB =val;        //xuat gia tri ra PORTB
}

    Tôi chỉ giải thích những điểm mới trong List 4. Thứ nhất là “attribute” volatile dùng trước khai báo biến val, biến val được khai báo là unsigned char (8 bit, không dấu) dùng chứa giá trị tạm thời để xuất ra PORTB khi có ngắt xảy ra. Điều đặc biệt là từ khóa volatile đặt trước nó,  volatile  là một thuộc tính (attribute) của bộ biên dịch gcc-avr, nó nói với trình dịch rằng biến val sẽ được dùng trong chương trình chính và cả trong các trình phục vụ ngắt. Nếu bạn muốn cập nhập giá trị 1 biến toàn cục trong các trình phục vụ ngắt mà biến đó không được chỉ định thuộc tính volatile trước thì quá trình cập nhật thất bại. Một cách dễ hiểu hơn, bạn xem trình ISR trong ví dụ trên, cứ mỗi lần có ngắt Compare Match xảy ra, biến val được tăng thêm 1 (dòng 21) sau đó kiểm tra điều kiện bằng 10 hay không và cuối cùng là gán cho PORTB. Nếu trong khai báo của val (dòng 4) chúng ta không chỉ định volatile thì giá trị xuất ra PORTB sẽ luôn là 1 khi có ngắt. Chú ý là điều này chỉ đúng it nhất là với phiên bản WinAVR tháng 12 năm 2007, các phiên bản sau có thể không cần dùng volatile (tôi sẽ cập nhật sau).

       Dòng 8 set các bit điều khiển: TCCR1B=(1<<WGM12)|(1<<CS12)|(1<<CS11); bạn thấy tôi chỉ set bit WGM12 trong 4 bit WGM vì tôi muốn chọn mode CTC 4 (xem bảng 3). Hai bit CS12 và CS11 được set bằng 1 trong khi CS10 được giữ ở 0 để chọn  xung clock là từ bên ngoài, chân T1 (xem bảng 2). Trong dòng 10, OCR1A=4; là giá trị cần so sánh, chúng ta biết rằng TCNT1 tăng lên từ 0, vì thế để đếm 5 sự kiện thì cần đặt giá trị so sánh là 4 (0, 1, 2, 3, 4). Dòng 11 set bit cho phép ngắt khi có Compare match xảy ra (dùng cho channel A).
       Mode 12 của CTC (WGM13=1, WGM12=1, WGM11=0, WGM10=0) cũng tương tự mode 4 nhưng cái khác là giá trị cần so sánh được chứa trong thanh ghi ICR1 (không phải OCR1A hay OCR1B). Khi đó nếu muốn dùng ngắt thì bạn phải dùng ngắt Input capture. Cụ thể dòng 8 trong list 4 đổi thành:  TCCR1B=(1<<WGM13)|( (1<<WGM12)|(1<<CS12)|(1<<CS11);  dòng 10: ICR1=4 và dòng  20: ISR (TIMER1_CAPT_vect ){
       Một khả năng khác của CTC là xuất tín hiệu xung vuông trên chân OC1A (chân 15 trên Atmega8) bằng cách set các bit Compare Output Mode trong thanh ghi TCCR1A. Tuy nhiên việc tạo các tín hiệu output trong mode CTC không thật sự thú vị. Vì vậy chúng ta sẽ khảo sát cách tạo tín hiệu output trong 1 chế độ chuyên nghiệp và thú vị hơn, chế độ PWM.
       Trước khi bắt đầu làm việc với các chế độ PWM tôi nghĩ cần thiết giới thiệu thế nào là PWM và nhắc lại các khái niệm giá trị đếm của Timer1 (hay bất kỳ timer nào khác) trên AVR. Trước hết, PWM hay Pulse Width Modulation được hiểu theo nghĩa tiếng Việt là “xung điều rộng” là khái niệm chỉ tín hiệu xung mà thường thì chu kỳ (Time period) của nó được cố định, duty cycle (thời thời gian tín hiệu ở mức HIGH) của nó có thể được thay đổi. Bạn xem 1 ví dụ về PWM  trong hình  5.
sim0
Hình 5. Ví dụ về tín hiệu PWM.
       Tạo ra PWM tức là tạo ra những tín hiệu xung mà ta có thể điều khiển duty cycle (và cả tần số ~ Time period nếu cần thiết). Timer 1 trêsn Atmega8 là 1 module lý tưởng để tạo ra các tín hiệu dạng này. Nhưng PWM dùng để làm gì và cách mà nó được sử dụng như thế nào? Tôi lấy một ví dụ như trong hình 6: một động cơ DC và một switch button.
dcm
Hình 6. Motor và switch.
       Nếu nhấn button thì động cơ hoạt động, thả button thì động cơ dừng. Tuy nhiên  do tốc độ nhấn và thả của con người có hạn, bạn sẽ thấy động cơ hoạt động hơi “sượng” (ripple). Điều gì xảy ra nếu bạn nhấn và thả button với vận tốc 5000 lần/giây. Câu trả lời là tay bạn sẽ bị gãy và button sẽ bị hỏng (^^). 5000 lần/s là điều không tưởng, tuy nhiên nếu bạn làm được như thế thì tổng thời gian cho 1 lần nhấn+thả là 1:5000=0.0002s = 200us. Có sự khác biệt nào không giữa trường hợp thời gian nhấn = 150us, thời gian thả 50us và trường hợp thời gian nhấn là 50us còn thời gian thả là 150us. Bạn sẽ dễ dàng tìm câu trả lời, trong trường hợp 1 động cơ sẽ quay với vận tốc nhanh hơn trường hợp 2. Đó 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). để biến cái không tưởng trên (5000 lần/s) thành hiện thực, chúng ta sẽ thay thế cái button cơ khí kia bằng 1 công tắc điện tử (electronics switch). Thường thì các chip MOSFET được dùng làm các khóa điện tử. MOSFET thường có 3 chân G (gate), D (drain) và S (source). Ví dụ 1 MOSFET kênh N ở trạng thái thông thường 2 chân D và S ko có dòng điện chạy qua, nếu điện áp chân G lớn hơn chân S khoảng 3V trở lên thì dòng điện có thể chạy từ D sang S. hãy xem cách mô tả tương đương 1 MOSFET với 1 button trong hình 7.
fet
Hình 7. MOSFET và button.
       Việc “kích” các MOSFET có thể thực hiện bằng các tín hiệu PWM. Vì thế ý tưởng điều khiền động cơ trong hình 6 có thể được thực hiện lại thông qua PWM như trong hình 8.
avrpwm
Hình 8. Mô hình điều khiển tốc độ động  cơ bằng PWM đơn giản.
       Như vậy là xong phần giới thiệu về PWM, bây giờ chúng ta sang các khái niệm số đếm trong Timer. Hình 9 minh họa cách bố trí các số đếm trong Timer1 trên hệ trục đếm.
moc
Hình 9: các mốc giá trị của T/C1.
       BOTTOM luôn được cố định là 0 (giá trị nhỏ nhất), MAX luôn là 0xFFFF (65535). TOP là giá trị đỉnh do người dùng định nghĩa, giá trị của TOP có thể được cố định là 0xFF (255), 0x1FF (511), 0x3FF 91023) hoặc định nghĩa bởi các thanh ghi ICR1 hoặc OCR1A. thực chất đối với ứng dụng PWM thì TOP chính là Time period của PWM. Do mục đích sử dụng mà có thể chọn TOP là các giá trị cố định hay các thanh ghi, riêng với tôi, cho mục đích tạo tín hiệu PWM tôi chọn TOP định nghĩa bởi thanh ghi ICR1. Ouput Compare là giá trị so sánh của bộ Timer. Trong chế độ PWM thì Output Compare quy định Duty cycle. Với T/C1, Output Comapre là giá trị trong các thanh ghi OCR1A và OCR1B. Do có 2 thanh ghi độc lập A và B, tương ứng chúng ta có thể tạo ra 2 tín hiệu PWM trên 2 chân OC1A và OC1B bằng T/C1. Đã đến lúc chúng ta tìm hiểu cách tạo PWM trên AVR.
2.3 Fast PWM (PWM tần số cao).
       Trong chế độ Fast PWM, 1 chu kỳ được tính trong 1 lần đếm từ BOTTOM lên TOP (single-slope), vì thế mà chế độ này gọi là Fast PWM (PWM nhanh). Có tất cả 5 mode trong Fast PWM tương ứng với 5 cách chọn giá trị TOP khác nhau (tham khảo bảng 3).  Việc xác lập chế độ hoạt động cho Fast PWM thực hiện thông qua 4 bit WGM và các bit chọn dạng xung ngõ ra, Compare Output Mode trong thanh ghi TCCR1A, nhìn lại 2 thanh ghi TCCR1A và TCCR1B.
TCCR1A
TCCR1B
       Chú ý các bit COM1A1, COM1A0 và COM1B1, COM1B0 là các bit chọn dạng tín hiệu ra của PWM (Compare Output Mode bits). COM1A1, COM1A0 dùng cho kênh A và  COM1B1, COM1B0 dùng cho kênh B. Hãy đối chiếu bảng 4.
Bảng 4: mô tả các bit COM trong chế độ fast PWM.
COM
       Tôi sẽ giải thích hoạt động của Fast PWM kênh A thông qua 1 trường hợp cụ thể, mode 14 (WGM13=1, WGM12=1, WGM11=1, WGM10=0). Trong mode 14, giá trị TOP (cũng là chu kỳ của PWM) được chứa trong thanh ghi ICR1, khi hoạt động thanh ghi TCNT1 tăng giá trị từ 0, giả sử các bit phụ  COM1A=1, COM1A0=0, lúc này trạng thái của chân OC1A (chân 15) là HIGH (5V), khi TCNT1 tăng đến bằng giá trị của thanh ghi OCR1A thì chân OC1A được xóa về mức LOW (0V), thanh ghi đếm TCNT1 vẫn tiếp tục tăng đến khi nào nó bằng giá trị TOP chứa trong thanh ghi ICR1 thì TCNT1 tự động  reset về 0 và chân OC1A trở về trạng thái HIGH, cái này gọi là “Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at TOP” mà bạn thấy trong hàng 4 bảng 4. Hình 10 mô tả cách tạo xung PWM trên chân OC1A ở mode 14.
fastpwm
Hình 10: Fast PMW mode 14.
       Rõ ràng chúng ta có thể điều khiển cả time period và duty cycle của PWM bằng 2 thanh ghi ICR1 và OCR1A. Thông thường giá trị của ICR1 được tính toán và gán cố định, giá trị của OCR1A được thay đổi để thực hiện mục đích điều khiển (như thay đổi vận tốc động cơ). Chú ý là nếu chúng ta set các bit phụ ngược lại: COM1A=0, COM1A0=1, thì tín hiệu PWM trên chân OC1A sẽ có phần “LOW” từ 0 đến OCR1A và “HIGH” từ OCR1A đến ICR1, đây gọi là “set OC1A/OC1B on Compare Match, clear OC1A/OC1B at TOP” (ngược với tín hiệu trên hình 10). Hoạt động của fast PWM kênh B hoàn toàn tương tự, trong đó thanh ghi ICR1 cũng chứa TOP của PWM kênh B và thanh ghi ICR1B chứa duty cycle. Như vậy 2 kênh A và B có cùng tần số hay Time period và duty cycle được điều khiển độc lập. Chân xuất tín hiệu PWM của kênh B là chân OC1B (chân 16 trên Atmega8).
Các mode 5, 6 và 7 của Fast PWM hoạt động hoàn toàn tương tự mode 14. Điểm khác nhau cơ bản là giá trị TOP(Time period). Trong các mode này giá trị TOP không do thanh thi ICR1 định nghĩa mà là các hằng số không đổi. Với mode 5, tức mode 8 bits, (WGM13=0, WGM12=1, WGM11=0, WGM10=1) giá trị TOP là 1 hằng số, TOP = 255 (số 8 bits lớn nhất). Với mode 6, tức mode 9 bits, (WGM13=0, WGM12=1, WGM11=1, WGM10=0) giá trị TOP là 1 hằng số, TOP = 511 (số 9 bits lớn nhất). Và với mode 7, tức mode 10 bits, (WGM13=0, WGM12=1, WGM11=1, WGM10=1) TOP =1023 (số 10 bits lớn nhất). Mode 15 cũng là Fast PWM trong đó TOP do OCR1A quy định, vì thế mà tín hiệu ra ở kênh A hầu như không phải là 1 xung, nó chỉ thay đổi trạng thái trong 1 clock. Theo tôi, để sử dụng Fast PWM bạn nên dùng mode 14 đã được giải thích trên. Các mode 5, 6, 7 cũng có thể dùng nhưng không nên dùng mode 15.
       Chúng ta tiến hành viết 1 ví dụ minh họa dùng 2 kênh ở chế độ fast PWM điều khiển 2 động cơ RC servo (gọi tắt là Servo). Mạch điện minh họa như trong hình 11.
sim
Hình 11: Điều khiển 2 RC servo bằng PWM.
       Hai button được nối với 2 ngõ ngắt ngoài INT0 và INT1 để điều khiển góc xoay của 2 Servo. Tên của Servo trong phần mềm Proteus là “MOTOR-PWMSERVO”. Trước khi viết code điều khiển các Servo, bạn cần biết cách điều khiển chúng, tôi giới thiệu ngắn gọn như sau:
       RC 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 điều khiển (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 cho dây điều khiển là Servo có thể xoay đến 1 vị trí nào đó (chú ý là 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 cận tốc xoay). Hình 12 là hình ảnh servo và cách điều khiển servo.
servo
Hình 12. Servo và cách điều khiển.
       Bạn xem hình 12b, để điều khiển servo bạn cần cấp cho dây điều khiển một tín hiệu PWM có Time Period khoảng 20ms, duty cycle của PWM sẽ quyết định góc xoay của servo. Với Duty cycle là 1ms, servo xoay về vị trí 0o, khi duty cycle =2ms, góc xoay sẽ là 180o, từ đó bạn có thể tính được duty cycle cần thiết khi bạn muốn servo xoay đến 1 vị trí bất kỳ giữa 0o và 180o. Sau khi hiểu cách điều khiển servo, chúng ta có thể dễ dàng viết code điều khiển chúng, chỉ cần tạo các xung PWM bằng T/C1. Đoạn code cho ví dụ này được trình bày trong list 5.
List 5. Điều khiển Servo bằng PWM.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void){
       DDRB=0xFF;                //PORTB la output PORT
       PORTB=0x00;

       MCUCR|=(1<<ISC11)|(1<<ISC01); //ngat canh xuong
       GICR |=((1<<INT1)|(1<<INT0); //cho phép 2 ngat hoat dong

       TCCR1A=(1<<COM1A1)|(1<<COM1B1)|(1<<WGM11);
       TCCR1B=(1<<WGM13)|(1<<WGM12)|(1<<CS10); 
       OCR1A=1000;              //Duty cycle servo1=1000us=1ms (0 degree)
       OCR1B=1500;              //Duty cycle servo2=1500us=1.5ms (90 degree)
       ICR1=20000;                  //Time period = 20000us=20ms

       sei();                       //set bit I cho phep ngat toan cuc
       while (1){           //vòng lặp vô tận
              //do nothing
       }
       return 0;
}

//trinh phuc vu ngat ngoai
ISR (INT0_vect ){       
       if (OCR1A==1000) OCR1A=1500;  //thay doi goc xoay servo1 den 90 do
       else OCR1A = 1000;  // thay doi goc xoay servo1 den 0 do
}
ISR (INT1_vect ){       
       if (OCR1B==1000) OCR1B=1500;  //thay doi goc xoay servo1 den 90 do
       else OCR1B = 1000;  // thay doi goc xoay servo1 den 0 do
}
       Với ví dụ này tôi chỉ cần giải thích các dòng từ 11 đến 15 liên quan đến việc xác lập chế độ hoạt động Fast PWM mode 14 inverse, phần còn lại bạn đọc tự đối chiếu với các bài trước. Dòng 11 và 12 thực hiện set các bit điều khiển Timer1, trước hết là các bit COM. Bạn thấy tôi chỉ set 2 bit COM1A1 và COM1B1: (1<<COM1A1)|(1<<COM1B1). Hai bit COM1A0 và COM1B0 không set tức mặc định bằng 0. Đối chiếu với bảng 4 bạn thấy chúng ta sẽ dùng “Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at TOP” cho tất cả 2 kênh A và B. Chúng ta set 3 bit  WGM13, WGM12 (thanh ghi TCCR1B, dòng 12) và WGM11 (thanh ghi TCCR1A, dòng 11) như thế thu được tổ hợp (WGM13=1, WGM12=1, WGM11=1, WGM10=0) tức là mode 14 được chọn (bảng 3). Còn lại chúng ta set bit CS10 để khai báo rằng nguồn xung clock cho Timer1 bằng clock cho vi điều khiển (prescaler=1) tức là 1us trong tường hợp f=1Mhz. (nếu bạn dùng các trình biên dịch khác không hỗ trợ định nghĩa tên các bit thì 2 dòng 11 và 12 tương đương: TCCR1A=0xA2; TCCR1B=0x19).
       Dòng 15 chúng ta khai nhập giá trị cho ICR1 cũng là Time period cho PWM, ICR1=20000 chúng ta thu được Time period =20000 us = 20ms thỏa yêu cầu của servo. Hai dòng 13 và 14 khai báo giá trị ban đầu của các duty cycle của 2 kênh PWM, các giá trị này định vị trí góc xoay của các servo. Trong 2 trình phục vụ ngắt, các giá trị này được thay đổi khi các button được nhấn.
2.3 Phase correct PWM (PWM với pha chính xác).
       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. Tương tự Fast PWM, cũng có 5 mode hoạt động thuộc Phase correct PWM đó là các mode 1, 2, 3, 10 và 11 (xem bảng 3). Năm mode này tương ứng các mode 5, 6, 7, 14 và 15 của fast 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). Lấy ví dụ mode 10 của Phase correct PWM tương ứng với mode 14 của Fast PWM, trong mode này thanh ghi ICR1 chứa TOP và OCR1A (hoặc OCR1B đối với kênh B) chứa giá trị so sánh. Khi hoạt động, thanh ghi TCNT1 tăng từ 0, khi TCNT1 bằng với OCR1A thì chân OC1A được xóa xuống mức LOW (tôi đang nói trường hợp COM1A1=1, COM1A0=0), TCNT1 tiếp tục tăng đến TOP, khi TCNT1=TOP thì TCNT1 KHÔNG được tự động reset về 0 như trường hợp Fast PWM mà TCNT1 bắt đầu đếm ngược, tức giảm từng giá trị từ TOP về 0. Trong lúc TCNT1 giảm, đến 1 lúc nó sẽ bằng giá trị của OCR1A lần thứ 2, và lần này, chân OC1A được set lên mức HIGH, TCNT1 tiếp tục giảm đến 0 thì 1 chu kỳ hoàn tất. Rõ ràng 1 chu kỳ là quá trình đếm trong 2 “sườn” nên ta gọi Phase correct PWM là dual-slope. Cũng vì tính chất dual-slope mà tín hiệu PWM trong chế độ này có tính đối xứng, thích hợp cho các ứng dụng điều khiển động cơ. Hình 13 mô tả cách mà Phase correct PWM hoạt động tron mode 10 với ngõ ra đảo (COM1A1=1, COM1A0=0).
cPWM
Hình 13. Phase correct PWM mode 10.
       Việc viết code cho chế độ Phase correct PWM gần như tương tự fast PWM, bạn chỉ cần thay đổi tổ hợp các bit WGM dựa theo bảng 3 và sau đó nhâp các giá trị phù hợp cho ICR1 và ORC1A, OCR1B là được.
2.3 Phase correct and frequency correct PWM.
       Chế độ này có 2 mode là 8 và 9. Về hầu hết các phương diện, 2 mode này giống với 2 mode 10 và 11 của Phase correct PWM. Cái khác nhau duy nhất là thời điểm mà thanh ghi OCR1A và OCR1B được cập nhật dữ liệu nếu có sự thay đổi. Việc này, nhìn chung không ảnh hưởng đến hầu hết người dùng PWM để điều khiển. Bạn sẽ rất khó để thấy sự khác biệt nếu bạn không phải đang viết 1 ứng dụng mà sai số trong 1 micro giây là điều tệ hại. Vì thế tôi không đề cập chi tiết chế độ này, bạn đọc có thể tham khảo datasheet của chip để hiểu rõ hơn nếu cần thiết.
       Ngoài ra trên chip atmega8 còn có bộ timer2 8 bits có PWM và asynchronous operation. Về mặt chức năng timer2 giống như phiên bản 8 bit của timer1 (độ phân giải thấp hơn nhưng có cùng chế độ và phương thức hoạt động). Điểm khác biệt và cũng là điểm đặc biệt của Timer2 là khả năng hoạt động không đồng bộ với chip, nó giống như việc bạn tách timer2 ra thành 1 chip timer riêng, vì thế cần cung cấp 1 nguồn xung clock khác cho timer này (1 thạch anh khác). Chế độ này có thể được dùng để calip (calibrate), canh chỉnh sai số  và bù cho nguồn xung clock chính trên chip.