Lab 4.1: การใช้ Timer Interrupt


Lab 4.1 – การใช้ Timer Interrupt

โจทย์

ขอให้นักศึกษาใช้ Timer interrupt สร้างสัญญาณ Square Wave ที่มีความถี่เท่ากับเลขรหัสนักศึกษา 2 ตัวท้ายคูณด้วย 10
เช่น รหัสลงท้ายด้วย 01 ก็สร้างสัญญาณความถี่ 10 Hz หรือถ้ารหัสลงท้ายด้วย 99 ก็สร้างสัญญาณความถี่ 990 Hz เป็นต้น

  • คนที่รหัสลงท้ายด้วย 00 ให้ใช้ความถี่ = 1000 Hz
  • ขอให้เลือกใช้ขา B5 หรือ B6  สร้าง Square Wave
    เนื่องจากสองขานี้ต่อ LED ไว้ดังนั้นจะช่วยให้เห็นในเบื้องต้นว่ามีสัญญาณออกมาหรือไม่ – ถ้ามี Square Wave ออกมาไฟ LED ควรติด แต่จะไม่สว่างเท่าตอน Output_high()

การส่งงาน

  • หากทำใน Simulator
    ให้ส่งภาพ Screen Capture แสดงกราฟใน GTKWave โดยวัดขนาดของ “คาบ” เพื่อยืนยันว่า Square Wave นั้นถูกต้อง ดังตัวอย่าง

    ตัวอย่างนี้แสดง Square Wave ที่มีคาบ = 1.7615 ms หรือ (567.7 Hz)
  • หากต่อวงจรจริง
    ให้ส่งภาพ Screen Capture แสดงกราฟจาก Logic Analyzer ที่วัดขนาดของ “คาบ” และ “ความถี่” เพื่อยืนยันว่า Square Wave นั้นถูกต้อง

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

 

square wave with interrupt

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

output_toggle(PIN_XX);
ข้อควรระวัง
คาบของ Square Wave จะเป็นสองเท่าของคาบ Timer Interrupt – เพราะต้อง Interrupt สองครั้งกว่าจะได้ลูกคลื่น 1 ลูก ดังนั้นถ้าสมมุติว่าต้องการสร้าง Square Wave ที่มีคาบ 100 ms ก็จะต้องตั้งค่า Timer Interrupt ให้มีคาบ 50 ms

 

 

การตั้งค่า Timer Interrupt ในโปรแกรม

โปรแกรมนี้จะสร้าง Timer Interrupt คาบ = 100 ms

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

void main() {
  // ตั้งค่าคาบ = 100 ms 
  setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);  // ตั้งค่า Pre-scale เป็น 1,2,4, หรือ 8
  set_timer1(40536);   // ตั้งค่า Timer1 Counter เริ่มต้น
  enable_interrupts(INT_TIMER1);   // เปิดใช้ Timer1 interrupt
  enable_interrupts(GLOBAL);       // เปิดใช้ interrupt 
  while (1) {
    /* main loop task */
  }
}

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

 

คาบของการเกิด 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

1. คาบของ CPU

ค่านี้คือเวลาที่ CPU ใช้ในการทำงาน 1 รอบ ซึ่งคำนวณได้จากความถี่สัญญาณนาฬิกาที่ใช้  เช่น หากทำงานที่ความถี่ 8 MHz คาบของ CPU จะ = 4/8 = 0.5 uSec

2. Pre Scale

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

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

setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);

3. จำนวนครั้งที่นับจนเกิด Overflow

เนื่องจาก Interrupt จะเกิดขึ้นทันทีที่ Timer1 Counter นี้ Overflow  และเนื่องจาก Counter นี้จะเพิ่มค่าทุกๆ “คาบของ CPU * Pre Scale” ดังนั้นสามารถคำนวณคาบของ Timer Interrupt ได้ดังนี้

คาบ = คาบของ CPU * Pre Scale * จำนวนครั้งที่นับจน Overflow

 

เช่น หาก CPU ทำงานที่ 8 MHz ใช้ Pre Scale = 8 และ เริ่มนับจาก 0

คาบ = 4/8 uSec * 8 * 65536 = 262.144 ms

 

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

100 ms = 4/8 uSec * 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 จะคงที่ตามนั้นเสมอ