Cách sử dụng counter với các hàm so sánh

Timer/Counter là module hoạt động độc lập và không thể thiếu của bất kỳ Microcontroller nào. Chức năng của Timer/Counter gồm: định thời, đếm sự kiện, tạo xung PWM,....

Như các bạn đã biết, Arduino là một nền tảng hướng tới sự đơn giản, giúp cho việc hiện thực hóa các ý tưởng dễ dàng hơn rất nhiều, nhưng cũng vì thế mà chúng ta sẽ không thể khai thác hết được sức mạnh của vi điều khiển nằm trên board Arduino. Điều mà mình cảm thấy tiếc nhất là sự thiếu sót của các Interrupt Vector trong môi trường Arduino [Arduino hiện chỉ có built-in function hỗ trợ External Interrupts].

Trong bài viết này, mình sẽ giới thiệu với các bạn cách sử dụng Timer/Counter trên Arduino và một số Interrupt của các Timer/Counter này.

1. Chuẩn bị

  1. 1x board Arduino [mình dùng Arduino UNO R3 với chip ATmega328p].
  2. Một vài con LED và điện trở 220 → 560 Ohm.
  3. ATmega328p Datasheet.

2. Giới thiệu

Trên chip Atmega328p của Arduino có 3 bộ Timer/Counter là: Timer/Counter0 [8bit], Timer/Counter1 [16 bit], Timer/Counter2 [8 bit].

Để không làm ảnh hưởng đến hàm delay[] và millis[] của Arduino, mình sẽ không đề cập đến Timer/Counter0.

Như mình đã giới thiệu, Timer/Count có chức năng: Đếm sự kiện, Định thời và tạo xung PWM, để giữ mọi thứ đơn giản, mình chỉ giới thiệu chức năng cơ bản của Timer/Counter khi lập trình trên Arduino là "Định thời" [Arduino đã hỗ trợ hàm built-in analogWrite để tạo xung PWM nên chúng ta cũng không đề cập đến nữa].

  • Timer/Counter1: là 1 bộ Timer/Counter đa năng 16 bit, gồm 5 chế độ hoạt động.
  • Timer/Counter2: là 1 bộ Timer/Counter 8 bit, gồm 4 chế độ.

Trong pham vi bài viết mình sẽ giới thiệu Normal Mode và Clear Timer on Compare Match [CTC] mode trên Timer/Counter1 và Timer/Counter2.

À, để thuận tiện, mình sẽ viết tắt Timer/Counter thành T/C.

Trước khi bắt đầu, có 1 số định nghĩa quan trong chúng ta cần rõ:

  • BOTTOM: là giá trị thấp nhất mà 1 T/C đạt được, tất nhiên BOTTOM luôn bằng 0.
  • MAX: là giá trị lớn nhất mà 1 T/C có thể đạt được, ở thanh ghi 8 bit giá trị MAX = 2^8 -1 = 255, ở thanh ghi 16 bit giá trị MAX = 2^16 - 1 = 65535. Và tất nhiên giá trị MAX cũng là cố định với từng T/C.
  • TOP: là giá trị đỉnh mà tại có T/C thay đổi trạng thái, giá trị TOP không nhất thiết phải bằng MAX mà có thể thay đổi bằng các thanh ghi. Chúng ta sẽ tìm hiểu sau.
  • Interrupt: [còn gọi là Ngắt] là 1 chương trình có độ ưu tiên cao nhất, được thực hiện ngay lập tức khi có tín hiệu Interrupt. Để hiểu thêm, các bạn hỏi google nhé!

Bảng 1: Interrupt Vectors của Timer/Counter trên ATmega328

3. Timer/Counter 1

3.1 Giới thiệu các thanh ghi

3.1.1 Thanh ghi TCNT1 [Timer/Counter 1 Register]

Là thanh ghi 16 bit, lưu giữ giá trị của Timer/Counter1, cho phép đọc-ghi trực tiếp, do đó, chúng ta có thể thực hiện các phép gán hoặc thay đổi giá trị của TCNT1.

3.1.2 Thanh ghi TCCR1B [Timer/Counter 1 Control Register B]

Là 1 trong 2 thanh ghi điều khiển hoạt đông của Timer/Counter1 [cùng với TCCR1A, nhưng với những mục đích đơn giản, chúng ta chỉ cần thanh ghi TCCR1B].

Bảng 2: Thanh ghi TCCR1B

Trong thanh ghi TCCR1B chúng ta chỉ cần sử dụng 3 bit CS10, CS11, CS12 để lựa chọn xung nhịp cho T/C1. Chúng ta sẽ tham khảo bảng này:

Bảng 3: Mô tả Clock Select Bit trên thanh ghi TCCR1B

Theo mặc định, chip Atmega328p trên Arduino chạy ở 16MHz, prescaler = 64. Điều này có nghĩa là: theo mặc định, các bộ T/C trên Arduino sẽ có tần số hoạt động là 16MHz/64 = 250kHz.

3.1.3 Thanh ghiTIMSK1 [Timer/Counter1 Interrupt Mask Register]

Là thanh ghi lưu giữ các Interrupt Mask của T/C1. Đây là thanh ghi giúp chúng ta thực hiện các Timer Interrupt. Trên thanh ghi TIMSK1 chúng ta cần chú ý các bit sau:

Bảng 4: Thanh ghi TIMSK1 [Timer/Counter1]

  • bit 5 - ICIE1: Input Capture Interrupt Enable - Cho phép ngắt khi dùng Input Capture.
  • bit 2 - OCIE1B: Output Compare Interrupt Enable 1 channel B - Cho phép ngắt khi dùng Output Compare ở channel B.
  • bit 1 - OCIE1A: Output Compare Interrupt Enable 1 channel A - Cho phép ngắt khi dùng Output Compare ở channel A.
  • bit 0 - TOIE1: Overflow Interrupt Enable 1 - Cho phép ngắt khi xảy ra tràn trên T/C.

[Các bạn cứ bình tĩnh, những cái như Output Compare, Input Capture, Overflow mình sẽ giới thiệu ở bên dưới].

3.1.4 Thanh ghi OCR1A và OCR1B [Output Compare Register channel A và channel B]

Lưu giữ giá trị so sánh ở kênh A và kênh B: khi T/C1 hoạt động, giá trị TCNT1 được tăng dần, giá trị này liên tục được so sánh với các giá trị trong thanh ghi OCR1A và OCR1B, việc so sánh này chính là "Output Compare", khi giá trị của TCNT1 bằng giá trị của OCR1A [hoặc OCR1B] thì "Match" xảy ra, lúc này sẽ có 1 Interrupt được thực hiện [ nếu đã được Enable ở thanh ghi TIMSK1].

3.1.5 Thanh ghi ICR1 [Input Capture Register 1]

Giá trị của thanh ghi ICR1 sẽ được cập nhật theo thanh ghi TCNT1 mỗi lần có sự kiện xảy ra trên chân ICP1 [tương ứng là chân digital 8 của Arduino]. Chức năng này mình sẽ giới thiệu trong 1 bài viết khác.

3.2 Các chế độ của Timer/Counter 1

Bảng 5: Waveform Generation Mode Bit [Timer/Counter1]

Mình sẽ giới thiệu 2 mode cơ bản nhất của T/C1 là: Normal Mode và CTC Mode.

3.2.1 Normal Mode

Đây là chế độ hoạt động đơn giản nhất của T/C1 [mode 0], giá trị của thanh ghi TCNT1 sẽ tăng từ 0 [BOTTOM] đến 65535 [MAX] và quay về 0. Nếu chúng ta gán trước cho TCNT1 một giá trị nào đó thì TCNT1 sẽ bắt đầu đếm từ giá trị này.

Ví dụ: Bạn muốn viết 1 chương trình để đọc dữ liệu từ cảm biến nhiệt mỗi 0.1s, nhưng trong thân chương trình lại có vài hàm delay[], do đó sẽ không đảm bảo là bạn cập nhật được giá trị nhiệt độ mỗi 0.1s nếu chỉ dùng hàm if và hàm millis[]. Phương án ở đây là chúng ta sẽ dùng Interrupt của Timer/Counter.

Theo mặc định, chip Atmega328p trên Arduino chạy ở 16MHz, prescaler = 64, vì vậy thời gian để TCNT1 tăng lên 1 đơn vị là 64/16MHz = 4us, thời gian để T/C1 đếm từ 0 đến 65535 là 4us*65536 = 0.262144s, mà thời gian chúng ta cần tạo là 0.1s [thỏa mãn vì 0.1 < 0.262144], do đó ta cần 0.1s/4us = 25000 lần đếm. Giá trị ban đầu của TCNT1 = 65536 - 25000 = 40536.

3.2.1.1 Lập trình

include

define sensor A0

volatile int temp; void setup[] {

Serial.begin[9600];
cli[];                                  // tắt ngắt toàn cục
/* Reset Timer/Counter1 */
TCCR1A = 0;
TCCR1B = 0;
TIMSK1 = 0;
/* Setup Timer/Counter1 */
TCCR1B |= [1 

Chủ Đề