Timer là một ngoại vi không thể thiếu của bất kỳ một vi điều khiển nào. Timer đơn giản chỉ là một bộ đếm (counter) nhưng xung clock mà nó đếm lại có tần số ổn định được tạo ra bởi thạch anh hoặc bộ dao động RC. Do đó timer là một bộ đo thời gian (bộ đếm thời gian).
N76E885 được trang bị 6 timer, gồm có: timer 0, timer 1, timer 2, timer 3, self wake up timer, watchdog timer. Các bộ timer này chỉ có thể đếm lên.
Ở nội dung bài này, chúng sẽ khảo sát hai bộ timer/counter 0 và 1 trên N76E885.
Vì timer 0 và timer 1 là giống nhau, nên mình chỉ đề cập đến timer 0. Nếu muốn sử dụng timer 1, các bạn cấu hình thanh ghi và bit tương ứng của timer 1.
Cấu tạo của timer 0 và 1
Cấu tạo phần clock và prescaler cho cả timer/counter 0 được mô tả như hình sau:
Clock cho timer/counter 0 có thể đến từ 3 nguồn, được cấu hình bởi 2 bit T0LXTM và C/T:
(Một số ứng dụng liên quan đến đếm sản phẩm, đếm xung,… nhận vào pin T0, các xung này thường không có tần số cụ thể. Do đó, timer 0 còn được gọi là counter 0 trong trường hợp này).
Timer/counter 0 cũng có một bộ prescaler nhưng chỉ kết nối với clock từ Fsys. Prescaler này cho phép chia xung ở hai mức:
Ta có thể run/stop timer 0 thông qua phần mềm hoặc phần cứng:
Timer 0 cũng cho phép xuất tín hiệu báo tràn ra ngoài. Khi bit T0OE (P1M1.2) được set, pin T0 sẽ đảo trạng thái tại mỗi lần timer 0 tràn.
Các mode hoạt động của Timer 0
Timer 0 có 4 mode hoạt động, được cấu hình bởi TMOD [1:0]:
Mode 0 thường được sử dụng như một prescaler = 1/(213), kết hợp với thạch anh 32.768 kHz. Khi đó, tần số tràn timer là 4 Hz có thể áp dụng trong các ứng dụng đồng hồ thời gian thực.
Mode 3 sẽ không sử dụng timer 1 (TH1 & TL1), thay vào đó, timer 0 sẽ tách ra làm 2 phần:
- TL0 nối với clock của timer 0.
- TH0 nối với clock của timer 1.
Trong bài viết này, mình chỉ đề cập đến 2 mode phổ biến là mode 1 và mode 2. Các bạn có thể đọc thêm datasheet để tìm hiểu thêm về mode 0 và mode 3.
Chúng ta tạo một project như các bài trước nhé:
Lập trình Nuvoton Timer 0 Mode 1
Sau khi cấu hình prescaler và cho phép clock vào timer 0, nó sẽ bắt đầu đếm lên. Ngay khi vừa đếm đến 65536 (tương ứng với 0 của số 16-bit) thì cờ báo tràn TF0 set lên 1, timer tiếp tục đếm lên như bình thường.
Dựa vào tần số clock cấp cho timer, ta có thể tính được chu kỳ tràn của timer (thời gian từ lúc bắt đầu khởi tạo giá trị và run cho đến lúc timer tràn):
Với Ftimer0 là tần số của clock cấp trực tiếp cho timer 0 (đã qua prescaler nếu có).
Ví dụ 1 – Tạo delay chu kỳ lớn bằng timer 0
Yêu cầu:
Viết hàm delay1sec(n) cho phép delay n giây. Dùng timer 0 mode 1, nhận clock từ thạch anh ngoài 32.768 kHz. Đảo trạng thái LED1 sau mỗi 2 giây.
Phân tích:
Các cấu hình về mode, clock, prescaler có thể dựa vào các bảng giá trị ở phần 2 và 3.
Khi đó, tần số clock cấp cho timer 0 là:
Giả sử timer 0 tràn sau mỗi giây thì:
Từ công thức tính chu kỳ tràn của timer 0, ta tính được giá trị khởi tạo của [TH0:TL0]:
(Tầm giá trị của [TH0:TL0] là từ 0 đến (216-1) = 65535).
Có một lưu ý nhỏ khi sử dụng các clock ngoài. Mặc định sau khi N76E885 reset, các nguồn clock ngoài đều ở trạng thái disable và ta không thể sử dụng ngay. Để enable các nguồn clock ngoài, ta cần can thiệp vào giá trị trên thanh ghi CKEN bằng một số thao tác đặc biệt. Trong bài viết này, mình sẽ cung cấp sẵn một đoạn code để enable clock từ thạch anh ngoài 32.768 kHz. Chúng ta sẽ nói về cách truy cập đến một số thanh ghi đặc biệt như CKEN trong bài viết Timed Access Protection.
Trong vòng lặp while (1) mình sẽ đảo trạng thái LED1 sau mỗi 2 giây.
Chương trình hoàn chỉnh như sau:
#include <stdint.h> #include <mcs51/N76E885.h> #include <mcs51/Define.h> #define LED1 P04 // #define LED2 P03 #define ON_LED 0 #define OFF_LED 1 void delay1sec(uint16_t duration) { TMOD &= CLR_BIT3 & CLR_BIT2 & CLR_BIT1 & CLR_BIT0; // xoá 4 bit thấp của TMOD TMOD |= SET_BIT0; // timer 0: GATE = 0, C/T = 0, mode 1 AUXR1 |= SET_BIT4; // clock thạch anh 32.768 kHz for (; duration > 0; duration--) { TH0 = HIBYTE(32768); TL0 = LOBYTE(32768); TR0 = 1; // run while (!TF0); // chờ đến khi tràn TR0 = 0; // stop TF0 = 0; // xoá cờ báo tràn } } void main(void) { // Insert code // // P0.4 (LED1) quasi-bidiretional mode // P0M1 &= CLR_BIT4; P0M2 &= CLR_BIT4; // // khởi tạo giá trị mặc định // LED1 = OFF_LED; // // enable thạch anh 32.768 kHz // // __bit bit_tmp = EA; // EA = 0; uint8_t tmp = CKEN; tmp &= CLR_BIT7; tmp |= SET_BIT6; TA = 0xaa; TA = 0x55; CKEN = tmp; // EA = bit_tmp; while (1) { LED1 = !LED1; delay1sec(2); } }
Biên dịch và nạp code, ta được kết quả:
Lập trình Nuvoton Time Mode 2
Khi timer 0 ở mode 2, hai thanh ghi TL0 và TH0 được tách rời. TL0 dùng làm bộ đếm, trong khi đó TH0 sẽ chứa giá trị nạp lại. Ở mỗi lần TL0 đếm đến giá trị tràn 256 (ứng với 0 của số 8-bit) thì cờ báo tràn TF0 được set lên 1, đồng thời giá trị trong TH0 sẽ nạp (copy) vào TL0. Sau đó, TL0 tiếp tục đếm lên đến khi tràn, quá trình lặp lại.
Dựa vào tần số clock cấp cho timer, ta có thể tính được chu kỳ tràn của timer (thời gian từ lúc bắt đầu nạp lại giá trị và run cho đến lúc timer tràn):
Với Ftimer0 là tần số của clock cấp trực tiếp cho timer 0 (đã qua prescaler nếu có).
Ví dụ 2 – Phát xung tần số cao bằng timer 0
Yêu cầu:
Phát xung 100 kHz ra pin T0 (cũng là P2.0). Sử dụng timer 0 mode 2.
Phân tích:
Ta tận dụng pin T0 của timer 0 để phát xung. Các bạn nhớ cấu hình pin P2.0 (tức T0) ở mode quasi-bidirectional và set P2.0 lên 1 thì timer 0 mới có thể toggle pin P2.0.
(Nếu đặt P2.0 ở mức 0 thì NMOS mở, nên timer 0 không thể thay đổi mức logic trên P2.0. Mức logic 1 ở mode quasi-bidirectional về bản chất là một điện trở pull-up nên timer 0 có thể thay đổi logic trên pin P2.0. Các bạn xem lại bài GPIO phần Quasi-bidirectional mode để hiểu rõ).
Pin T0 đảo trạng thái tại mỗi lần timer 0 tràn. Do đó, chu kỳ tràn của timer 0 bằng một nửa chu kỳ tín hiệu pin T0:
Yêu cầu tần số khá cao nên ta dùng clock Fsys với prescaler = 1/1, suy ra:
Giá trị nạp lại TH0:
.
Chương trình như sau:
#include <stdint.h> #include <mcs51/N76E885.h> #include <mcs51/Define.h> // #define LED1 P04 // #define LED2 P03 // #define ON_LED 0 // #define OFF_LED 1 void main(void) { // Insert code // // P2.0 (T0) quasi-bidirectional mode // P2M1 &= CLR_BIT0; P2M2 &= CLR_BIT0; // // nhường mức logic cho timer 0 // P20 = 1; // // cho phép toggle pin P2.0 (T0) khi tràn timer 0 // P1M1 |= SET_BIT2; // // cấu hình timer 0 // TMOD &= CLR_BIT3 & CLR_BIT2 & CLR_BIT1 & CLR_BIT0; // xoá 4 bit thấp của TMOD TMOD |= SET_BIT1; // timer 0: GATE = 0, C/T = 0, mode 2 CKCON |= SET_BIT3; // clock Fsys, prescaler = 1/1 TH0 = 145; // giá trị nạp lại TR0 = 1; // run while (1) { // do nothing } }
Sau khi biên dịch và nạp code, các bạn sử dụng oscilloscope hoặc máy đo logic để kiểm tra tín hiệu trên pin P2.0 (tức T0).
Hoặc có thể thấy như sau:
Kết luận
Như vậy chúng ta đã cùng nhau khảo sát hai bộ timer/counter 0 và 1.
Các timer còn lại sẽ được chúng ta khảo sát ở các bài kết tiếp.