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 Pic, 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 Pic:
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ư Pic16,18…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 Pic sau này.
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…
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.
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.
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
- 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.
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.
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.
Hình 2. Quá trình thực hiện.
Link download projects.
EmoticonEmoticon