25 0 463KB
TIMER/COUNTER I. Giới thiệu Trong các ứng dụng của vi điều khiển (VDK), việc định thời (tạo một khoảng thời gian giữa hai sự kiện) giữa các thao tác đóng vai trò rất quan trọng. Để thực hiện việc này, ta có hai cách thường dùng : tọa trễ bằng hàm delay hoặc dùng chức năng timer của vi điều khiển. Nhưng hàm delay chỉ là một chuỗi những câu lệnh vô nghĩa mà khi thực hiện, VDK không thể thực hiện thêm một tác vụ nào khác. Trong khi đó, dùng timer, VDK thực hiện song song giữa việc đếm timer và các lệnh trong chương trình chính vì vậy sử dụng timer để định thời sẽ tốt hơn. Vi điều khiển PIC 16F887 cung cấp cho người dùng ba bộ Timer : Timer0, Timer 1 và Timer 2. Trong bài này chúng ta sẽ chỉ nghiên cứu về Timer 0. Timer 1 và Timer 2 sẽ được nghiên cứu cụ thể ở các phần sau vì các bộ hai Timer này có nhiều ứng dụng phức tạp hơn. Trong bài này, tôi sẽ nhắc sơ bộ về một khái niệm là “ngắt” (interrupt) của VDK vì phần timer/counter và phần interrupt liên quan mật thiết với nhau. Phần nghiên cứu đầy đủ về Interrupt sẽ được đề cập đến ở các bài sau. Ở đây chúng ta chỉ nghiên cứu Interrupt ứng dụng trong bộ Timer0. II. Timer 0 II.1. Cấu tạo II.1.1. Tổng quan Timer 0 được ứng dụng rộng rãi trong thực hành. Rất ít chương trình không sử dụng nó trong một vài trường hợp. Timer0 rất thuận tiện và dễ dàng trong việc viết các chương trình hay các chương trình con để tạo các xung có độ rộng tùy ý, tính toán thời gian hay đếm xung ngoại. Bộ Timer 0 là bộ timer 8-bit với các đặc điểm sau: Bộ timer/counter 8-bit: bộ Timer 0 có hai chức năng là timer và counter. Giá trị đếm hiện tại của bộ Timer 0 sẽ được chứa trong thanh ghi 8 bit là TMR0. Bộ chia tần (prescaler) 8 bit sứ dụng chung với bộ Watchdog timer (WDT). Có thể được lập trình để sử dụng nguồn xung dao dộng nội (internal clock source) hoặc nguồn xung dao động ngoại (external clock source). Định nghĩa xung clock nội và xung clock ngoại cho timer khác hoàn toàn so với chip. Đối với chip xung nội nghĩa là xung được tạo ra từ bộ dao dộng RC được tích hợp sẵn có trong chip. Xung ngoại là nguồn xung được cung cấp từ bên ngoài chip có thể là xung từ thạch anh hoặc một bô dao động RC khác bên ngoài chip, có thể là
nguồn xung từ một chip khác. Đối với bộ Timer 0, xung nội internal là xung lấy từ nguồn xung mà VDK đang hoạt động bất kể xung đó là xung của bộ RC nội hay là xung ngoại của ngoại vi khác. Xung ngoại external là nguồn xung được đưa vào bằng chân T0CKI trùng với chân RA4. Xung được đưa vào chân này mới được gọi là external clock. Bộ Timer 0 cũng có thể được lập trình để chọn cạnh ngắt lên hoặc ngắt xuống của external clock để làm xung đếm cho timer 0. Chọn ngắt lên (cạnh ngắt là cạnh lên) là khi có một xung ở chân T0CKI chuyển từ mức thấp lên mức cao thì thanh ghi TMR0 sẽ đếm lên 1 đơn vị. Ngược lại, chọn ngắt xuống (cạnh ngắt là cạnh xuống) là khi có một xung chuyển từ mức cao xuống mức thấp thanh ghi TMR0 sẽ đếm lên 1 đơn vị.
Timer 0 cho phép thực hiện ngắt (interrupt) khi xảy ra tràn.
II.1.2. Nguyên lý hoạt động Bộ định thời Timer 0 là bộ đình thời 8-bit hoạt động ở hai chế độ là timer và counter. Về bản chất, timer và counter là một. Nhưng sự khác nhau ở đây là: chọn timer ta sẽ dùng xung nội (internal clock) để bộ timer0 hoạt động còn chọn counter ta sẽ dùng xung ngoại (external clock). Bit T0CS sẽ quy định bộ timer0 sẽ hoạt động ở chế độ nào. T0CS = 0, timer 0 làm việc ở chế độ timer và hoạt động với nguồn xung internal clock và xung này bằng xung mà VDK đang dùng chia cho 4 ( FOCS/4).
T0CS = 1, timer 0 làm việc ở chế độ counter. Ở đây, sẽ có thêm một bit T0SE quy định việc chọn ngắt lên hay ngắt xuống.
Hình 1. Sơ đồ minh họa cấu tạo của bộ Timer 0 Sau khi cấu hình được bit T0CS ta đã có được nguồn xung clock cần thiết. Tiếp theo, ta sẽ gặp một bit là PSA. PSA quy định bộ chia tần prescaler sẽ được sử dụng cho bộ timer0 hay WDT. Nếu dùng bộ chia tần cho timer0 thì ta cho PSA = 0 (ở đây ta chỉ quan tâm đến bộ timer0, còn bộ WDT sẽ được xét đến sau). Trong trường hợp không dùng bộ prescaler, bit PSA = 1 và xung clock của ta sẽ đi trực tiếp đến thanh ghi TRM0 để thực hiện đếm. Ngược lại, xung clock của ta sẽ được đưa qua bộ chia tần và tạo ra một xung clock mới. Bộ chia tần này là bộ chia tần 8 bit với 4 giá trị chia tần. Các giá trị chia tần được quy định bởi 3 bit là PS0, PS1 và PS2. Bộ chia tần này có chức năng từ xung clock ban đầu (internal clock hay external clock) sẽ tạo ra xung mới có tần số nhỏ hơn tức là chu kì dài hơn. Xung mới sẽ thực hiện đếm thanh ghi TMR0, sau một chu kì của xung, TMR0 sẽ nhảy lên một đơn vị. TMR0 là một thanh ghi 8 bit chứa giá trị đếm hiện tại của timer0. TMR0 sẽ được người sử dụng đặt giá trị ban đầu (giả sử là số p) thì p có giá trị trong khoảng 0x00 đến 0xFF. TMR0 sẽ bắt đầu đếm từ giá trị p này, cứ sau một chu kì của xung clock TMR0 đếm lên 1 đơn vị cho đến khi có sự tràn xảy ra tức là khi giá trị của TMR0 đếm đến 0xFF, một cờ ngắt sẽ được bật lên để biết rằng thanh ghi này đã tràn đồng thời TMR0 sẽ tự động xóa về
0x00. Cờ ngắt này là bit T0IF (Timer0 Interrupt Flag). Bit T0IF này được set lên mức 1 bởi phần cứng tức là khi TMR0 tràn thì cờ ngắt sẽ được tự động set lên bởi phần cứng, nhưng cờ này không được tự động xóa đi bởi phần cứng vì vậy trong khi lập trình, trong code chúng ta phải thực hiện lệnh xóa cờ ngắt để tránh những lỗi khi lập trình. II.1.3. Các thanh ghi liên quan Các chức năng của timer0 sẽ được quy định bởi các thanh ghi: TMR0, OPTION_REG và INTCON. Tùy theo mục đích mà người lập trình sẽ set giá trị của các thanh ghi khác nhau. II.1.3.1. Thanh ghi TRM0 Địa chỉ 01h. TMR0 là thanh ghi chứa giá trị đếm hiện tại của timer0. II.1.3.2. Thanh ghi OPTION_REG Địa chỉ 81h. OPTION_REG : OPTION REGISTER
T0CS (bit 5) : TMR0 Clock Source Select bit 0 = Timer0 làm việc ở chế độ timer. 1 = Timer0 làm việc ở chế độ counter. T0SE (bit 4) : TMR0 Source Edge Select bit 0 = chọn ngắt lên. 1 = chọn ngắt xuống. PSA (bit 3) : Prescaler Assignment bit 0 = bộ chia tần được sử dụng cho bộ timer0. 1 = bộ chia tần được sử dụng cho bộ WDT. PS (Bit 2-0) : Prescaler Rate Select bits Các bit này quy định giá trị chia tần cho bộ chia tần, được thể hiện ở bảng 1.
Bảng 1 II.1.3.3. Thanh ghi INTCON Địa chỉ 0Bh. INTCON: INTERRUPT CONTROL REGISTER
GIE (bit 7) : Global Interrupt Enable bit 0 = không cho phép thực hiện tất cả các lệnh ngắt. 1 = cho phép thực hiện tất cả các lệnh ngắt được hoạt động. T0IE (bit5) : Timer0 Overflow Interrupt Enable bit 0 = không cho phép thực hiện ngắt đối với timer0. 1 = cho phép thực hiện ngắt đối với timer0. T0IF (bit 2) : Timer0 Overflow Interrupt Flag bit 0 = xác nhận thanh ghi TMR0 chưa bị tràn. 1 = xác nhận thanh ghi TMR0 đã bị tràn. II.2. Tính toán trong timer0 Như đã đề cập ở phần trên, thanh ghi TMR0 sẽ đếm lên 1 đơn vị sau mỗi chu kì của xung clock đưa vào. Giả sử chu kỳ xung clock là T s thì cứ sau T s thì TMR0 lại đếm lên 1 đơn vị, vì TMR0 là thanh ghi 8 bit nên mất tối đa 255T s thì TMR0 sẽ xảy ra tràn. Vậy yêu cầu đặt ra ở đây là với xung có tần số cho trước, giá trị bộ chia tần chọn trước, ta phải đặt
giá trị ban đầu cho TMR0 là bao nhiêu để sau một số lần tràn timer0 sẽ tạo ra cho ta một khoảng thời gian mong muốn. Cụ thể, khi hoạt động ở chế độ timer, timer0 sẽ lấy nguồn xung internal clock để hoạt động. t =
.
(255 − p)
(*)
Trong đó: FOSC : tần số dao động của internal clock. p : giá trị đặt ban đầu của thanh ghi TMR0. N : số lần tràn của TMR0. d : giá trị của bộ chia tần. tc : khoảng thời mong muốn. Ví dụ 1 : Cho tần số dao động xung thạch anh là 4MHz, chọn bộ chia tần là 1:d = 1:4 (d=4), khoảng thời gian mong muốn tc = 1s. Hãy tính giá trị đặt ban đầu p cho TMR0 và số lần tràn N thích hợp. Từ (*) ta có : Đặt Vậy ở đây, cùng ta tìm được
, vì p > 0 nên suy ra
.
. Ta chọn x = 0.001s ⇒ N =
=
.
= 1000, cuối
.
II.3. Ngắt trong timer0 Vậy ngắt là gì? Ngắt (interrupt) là một sự kiện gián đoạn trong quá trình thực hiện một công việc nào đó. Ví dụ : như bạn đang ngồi học mà có chuông điện thoại reo lên, khi đó bạn phải dừng việc học lại và nghe điện thoại, sau khi nghe điện thoại xong bạn trở lại học bài. Quá trình ngắt trong timer0 cũng tương tự như vậy. VDK đang thực hiện chương trình chính, khi TMR0 xảy ra tràn thì cờ ngắt phất lên (chuông điện thoại reo), sau đó cờ ngắt được xóa (bởi người lập trình) VDK tạm dừng thực hiện chương trình chính và nhảy qua thực hiện chương trình con phục vụ ngắt, sau khi thực hiện ngắt xong VDK trở lại thực hiện chương trình chính cho đến khi cờ ngắt lại phất lên và VDK thực hiện lại quá trình như trên.
II.4. Tóm tắt Để sử dụng bộ timer0 một cánh hiệu quả và chính xác, ta cần chú ý khi lập trình: Bước 1: Chọn chế độ: Việc chọn chế độ trong timer0 được quy định bởi bit T0CS của thanh ghi OPTION_REG , (T0CS: 0=timer, 1=counter). Chọn tỉ lệ chia tần phù hợp với các bit PS2, PS1 và PS0. Khi sử dụng interrupt timer0, cần chú ý đến việc set các bit GIE và T0IE của thanh ghi INTCON. Bước 2: Định thời và đếm: Để định thời: Xóa thanh ghi TMR0 và set giá trị ban đầu phù hợp cho TMR0. Tính toán chính xác khoảng thời gian cần thiết với các thông số của phần cứng (tần số của nguồn xung, tỉ lệ chia tần,…). Bit cờ ngắt T0IF được tự động set lên mức cao khi xảy ra tràn thanh ghi TMR0. Ngay lúc này, VDK sẽ thực hiện ngắt nếu có chương trình con phục vụ ngắt. Để đếm xung: Xung đếm được đưa vào bằng chân RA4. Việc chọn cách đếm (đếm cạnh lên hay đếm cạnh xuống) được quy đinh bởi bit T0SE của thanh ghi OPTION_REG. Thanh ghi TMR0 thực hiện đếm xung. Bộ chia tần và ngắt hoạt động tương tự như chức năng timer.
III. Áp dụng. Bài 1. Cho mạch như hình vẽ. Dùng bộ timer0, viết chương trình hiển thị số từ 0 đến 59 lên 2 leg 7 đoạn. Sau khi đếm đến 59, 2 leg sẽ trả giá trị về 0 và thực hiện lại chu trình như trên. Khoảng thời gian mỗi lần nhảy số là 1s.
Bài giải: Việc tính toán giá trị ban đầu cần set cho TMR0 và số lần lặp N đã thực hiện ở ví dụ 1. Và ở đây khoảng thời gian nhảy “1s” là tương đối vì VDK phải mất vài ms để thực hiện các câu lệnh trong chương trình ngắt. Vì vậy muốn tính toán thật sự chính xác “1s” không phải là đơn giản. Khi nghiên cứu sâu hơn, ta sẽ thực hiện được việc này. Sau đây là phần code cụ thể cho bài toán.
#include #define _XTAL_FREQ 4000000 __CONFIG(FOSC_HS & WDTE_OFF & PWRTE_ON & MCLRE_ON & CP_OFF & BOREN_OFF & IESO_OFF & FCMEN_OFF & LVP_OFF & DEBUG_OFF); //__CONFIG(BOR4V_BOR21V); int x,count; unsigned char num[10] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; void hienthi(int x) //Chuong trinh con hien thi so tu nhien 2 chu so len leg 7-seg { unsigned int ch,dv; ch=x/10; dv=x-ch*10; PORTC=num[ch]; PORTD=num[dv]; } void interrupt ngat_timer0() /*Chuong trinh con phuc vu ngat */ { TMR0IF=0; // xoa co ngat GIE=0; // cam ngat toan cuc, de phong lai xay ra ngat khi dang xu ly ngat TMR0=5; // chon gia tri ban dau cho timer0 count++; if (count==1000) { x++; count=0; } if (x==60) { x=0; count=0; } GIE=1; } void main() { TRISC=0x00; TRISD=0x00; count=0; x=0; TMR0=5; GIE=1; TMR0IE=1;
T0CS=0; // t0cs =0: TRM0 hoat dong o che do timer; =1:TMR0 hoat dong o che do counter PSA=0; // psa =0:chon bo chia tan cho timer0; =1 chon bo chia tan cho WDT PS2=0; PS1=0; PS0=1; /* chon ti le chia tan 000 1:2 001 1:4 010 1:8 011 1:16 100 1:32 101 1:64 110 1:128 111 1:256 */ while(1) { hienthi(x); } }
Bài 2. Cho mạch như hình vẽ. Dùng bộ timer0 để đếm số lần nhấn nút từ nút nhấn nối với chân RA4. Số lần nhấn hiển thị lên 2 leg 7 đoạn. Khi số lần đếm tối đa là 99, nếu vượt quá kết quả hiển thị sẽ trở lại 00 và tiếp tục lại như trên.
Ở đây, việc nhấn nút tương ứng với tạo một xung. Nếu ta chưa nhấn nút, mức điện áp ở chân RA4 là 5V, nhưng khi nhấn nút, áp tại chân RA4 là 0V. Vậy việc nhấn nút sẽ tạo ra xung và vì là xung đưa vào chân RA4 nên là external clock, vì vậy ta sẽ dùng bộ timer với chức năng counter. Phần code cho bài toán. #include #define _XTAL_FREQ 4000000 __CONFIG(FOSC_HS & WDTE_OFF & PWRTE_ON & MCLRE_ON & CP_OFF & BOREN_OFF & IESO_OFF & FCMEN_OFF & LVP_OFF & DEBUG_OFF); int i; unsigned char num[10] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; void hienthi(int x) { unsigned int ch,dv; ch=x/10; dv=x%10; PORTC=num[ch]; PORTD=num[dv]; }
void interrupt ngat_ngoai() { T0IF=0; // xoa co ngat GIE=0; /* cam ngat toan cuc, de phong lai xay ra ngat khi dang xu ly ngat*/ TMR0=255; if (i