Lab 4.1: การใช้ Timer Interrupt


Lab 4.1 – การใช้ Timer Interrupt

โจทย์

ขอให้นักศึกษาใช้ Timer interrupt สร้างสัญญาณ Square Wave ที่มีความถี่เท่ากับเลขกลุ่มของตนคูณ 100
เช่น กลุ่ม 1.1 ก็ให้สร้างสัญญาณความถี่ 1.1*100 = 110 Hz เป็นต้น

 

แนวคิดการสร้างคลื่นสี่เหลี่ยม (Square Wave) โดยใช้ Timer Interrupt

square wave with interrupt

จากภาพข้างต้น จะเห็นได้ว่าคลื่นสี่เหลี่ยมจะเกิดเป็นจังหวะที่ตายตัวขึ้นอยู่กับคาบ (Sq Wave Period) ดังนั้นจึงเป็นตัวอย่างของงานที่สามารถใช้ Timer Interrupt เข้ามาช่วยได้เป็นอย่างดี โดยทุกครั้งที่เกิด Timer Interrupt ขึ้น สิ่งที่เราต้องทำก็มีเพียงการกลับสถานะของสัญญาณเท่านั้น ซึ่งสามารถใช้คำสั่งของ PIC-C ดังตัวอย่างนี้ได้

if (input(PIN_XX)) {  output_low(PIN_XX); }
else { output_high(PIN_XX);}

ดังนั้นจะเห็นได้ว่าคาบของ Timer Interrupt (T1 int period) จะมีค่าเป็น 1/2 ของคาบลูกคลื่นที่ต้องการ (interrupt สองครั้งถึงจะได้หนึ่งลูกคลื่น)

 

การตั้งค่า Timer Interrupt

Timer Interrupt มีประโยชน์มากในการกำหนดให้ไมโครคอนโทรเลอร์ทำงานตามระยะเวลาที่กำหนดไว้ โดยเราสามารถกำหนดคาบการทำงานของ Interrupt ได้ลงไปถึงระดับ millisecond เลยทีเดียว ไมโครคอนโทรเลอร์ PIC 16F886 มี timer ให้ใช้ทั้งหมด 3 ตัว (Timer0 – Timer2) ในที่นี้จะอธิบายเฉพาะ Timer1 เท่านั้น เพราะเป็น Timer ที่เหมาะสำหรับโจทย์ที่กำหนดให้

เราสามารถ enable Timer1 Interrupt ได้โดย

enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);

และสร้าง Function ที่จะทำงานเมื่อเกิด interrupt โดยประกาศไว้ดังนี้

#INT_TIMER1
void timer1_isr() {
...
}

การกำหนดคาบของ Timer 1

(สำหรับ PIC 16F886 ทำงานที่ความเร็ว 8 MHz)

คาบของการเกิด Timer Interrupt ถูกกำหนดโดยตัวนับขนาด 16 bit ที่ชื่อว่า Timer1 Counter โดยค่าของตัวนับนี้จะเพิ่มขึ้น 1 ทุกรอบการทำงานของ CPU และจะเพิ่มค่าขึ้นเรื่อยๆ จนกระทั่งเกินค่าสูงสุดที่ตัวแปรนี้จะเก็บได้ (65535) และทำให้เกิด overflow ขึ้น เมื่อ CPU ตรวจพบว่าเกิด Overflow ก็จะกระตุ้นให้เกิด Timer Interrupt ขึ้น  และตั้งค่าตัวนับกลับไปเป็น 0 อีกครั้ง โดยรอบการนับและการเกิด Timer Interrupt นี้จะเกิดวนซ้ำไปเรื่อยๆ ตราบใดที่ CPU ยังทำงาน และ Timer Interrupt ยังถูกเปิดใช้งาน

การปรับตั้งคาบของ Timer Interrupt ทำได้สองทาง คือ

  1. ปรับค่า Pre Scale – ค่า Pre Scale จะเป็นตัวคูณรอบการทำงานของ CPU เพื่อให้ตัวนับททำงานช้าลง เช่น ถ้า Pre Scale มีค่าเป็น 2 ตัวนับ Timer1 counter จะเพิ่มค่าทุก 2 รอบการทำงานของ CPU (แทนที่จะนับทุกรอบ) เป็นต้น
  2. กำหนดค่าเริ่มต้นของ Timer1 Counter ใหม่ – เราสามารถตั้งให้ Timer1 Counter เริ่มนับจากค่าใดๆ ก็ได้ (แทนที่จะเริ่มจาก 0) โดยใช้ค่าสั่ง set_timer1() ซึ่งวิธีนี้จะสามารถปรับคาบของ Interrupt ได้ค่อนข้างละเอียด

จากหลักการข้างต้นคาบของการเกิด Timer Interrupt นั้นสามารถคำนวนได้จากสูตรต่อไปนี้

คาบของ T1 Interrupt = 0.5 uSec * PreScale * จำนวนครั้งการนับจนเกิด Overflow

Timer1 setup 8MHz

ค่าคงที่

ค่านี้คือเวลาที่ CPU ใช้ในการทำงาน 1 รอบ ซึ่งจะมีค่าเป็น 0.5 uSec สำหรับ PIC รุ่น 16F886 ที่ทำงานที่ความถี่ 8 MHz

Pre Scale

Pre Scale เป็นตัวคูณเพื่อยืดเวลาการเกิด  Timer Interrupt ออกไป โดยตัวคูณนี้จะมีได้ 4 ค่าคือ 1, 2, 4, 8

เราจะตั้งค่า PreScale ที่ตอนต้นของโปรแกรม เช่นถ้าต้องการให้ PreScale เป็น 8 คำสั่งคือ

setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);

จำนวนครั้งการนับจนเกิด Overflow

Timer Interrupt จะเกิดขึ้นเมื่อ Timer1 Counter มีการ Overflow (คือค่าเพิ่มขึ้นจนเกินค่า 16 bit ที่มันสามารถเก็บได้ ซึ่งก็คือเมื่อมันมีค่าเท่ากับ 65536 นั่นเอง) หลักการนับของ Timer1 Counter คือทุกครั้งที่เวลาผ่านไป = (0.2 uSec * PreScale) ค่า Timer1 Counter จะเพิ่มขึ้น 1 และเมื่อ counter นี้เพิ่มค่าขึ้นไปจนเกินค่าสูงสุดที่มันเก็บได้ (65535) มันจะวกกลับไปเป็น 0 พร้อมกับสร้าง T1 Interrupt ขึ้น

ถ้าใช้ Pre Scale เป็น 8 และ  Timer1 Counter มีค่าเริ่มต้นเป็น 0 เวลาที่ใช้นับก่อนเกิด interrupt จะมีค่าเท่ากับ

คาบ = 0.5 uSec * 8 * 65536 = 262.144 ms

เนื่องจาก Timer1 Counter นี้เราสามารถตั้งค่าเริ่มต้นได้เองในโปรแกรม ดังนั้นจึงไม่จำเป็นต้องเริ่มนับจาก 0 ก็ได้ ซึ่งช่วยให้เรากำหนดคาบของ Timer Interrupt ได้ค่อนข้างละเอียดเช่น ถ้าต้องการให้คาบของ T1 Interrupt เป็น 100 ms เราจะสามารถคำนวณหาค่าเริ่มต้น Timer1 Counter ได้ดังนี้

100 ms = 0.5 us * 8 * (65536-T1_Start)

ดังนั้น

25000 = 65536 – T1_Start

การตั้งค่า Timer1 Counter ให้เริ่มนับที่  65536-25000 = 40536  ก็ทำได้โดยใช้คำสั่งดังนี้

set_timer1(40536);

* สิ่งสำคัญประการหนึ่งที่ต้องทำความเข้าใจคือ Timer1 Counter นี้เป็นตัวแปรธรรมดาตัวหนึ่ง ดังนั้นเราจะต้องตั้งค่ามันใหม่ทุกครั้งที่เกิด Timer interrupt ขึ้นเพื่อรักษาจังหวะการเกิด interrupt ให้คงที่ ดังนั้นคำสั่ง set_timer() จะถูกใช้ใน main() และใน Timer1 ISR ด้วย นักศึกษาที่ไม่เข้าใจเรื่องนี้มักจะตั้งค่าไว้ครั้งเดียวใน main() แล้วเข้าใจผิดว่า timer interrupt จะคงที่ตามนั้นเสมอ

ดังนั้นโปรแกรมทั้งหมดจากตัวอย่างนี้จะเป็นดังนี้

#INT_TIMER1
void timer1_isr() 
set_timer1(40536); // ใส่คำสั่งนี้ใน ISR เสมอ เพื่อรักษาคาบให้คงที่
...
}

void main() {
// ตั้งค่าคาบ = 100 ms 
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
set_timer1(40536); 
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
while (1) {
...
}
}