|
| 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | |
|
|
|
|
|
|
|
Tip&Trick PIC Microcontroller, 1.000 mS Timer0's Interrupt (In-depth explanation)
วันนี้เขียนโปรแกรมนาฬิกาเล่นๆ ตั้งใจจะให้ความละเอียดของเวลามีค่า 1mS นั่งคำนวณเวลาอยู่หลายรอบ ไม่ลงตัวสักที ทั้งๆ ที่รู้อยู่ในใจว่าไม่มีทางจะลงตัว ที่ XTAL 8MHz และใช้ Prescaler เท่ากับ 1:8 แน่นอน ค่านี้เป็นค่าที่เหมาะสมสำหรับ Timer0 (8-bit) ที่ XTAL 8MHz ไม่มีอะไรที่เป็นไปไม่ได้ดังนั้นต้องหาลู่ทางกันต่อไป ด้วยโครงสร้างของ PIC16F และหลายๆ ตัว FCY=FOSC/4 นั่นคือ 1 Machine cycle (MC) จะมีค่าเท่ากับ 4/8MHz ซึ่งเท่ากับ 0.5uS ห่างกันตั้ง 2,000 เท่าเมื่อเทียบกับเวาลาที่ต้องการ คือ 1mS แบบนี้ก็หมูๆแล้ว กลับไปพิจารณากันที่โปรแกรมต่อ มองอะไรไม่ค่อยออก งั้นเข้าไปดูใน Assembly แล้วกัน ชัดเจนเลย แบบนี้ก็ต้องชดเชยค่าจากการคำนวณกันหน่อย เพราะการ Interrupt ก็ต้องเสียเวลาเล็กน้อย ก่อนที่ CPU จะเข้ามาใน ISR นั่นคือ 4 MC ตามด้วยการ save context (เรื่องนี้ว่างๆ จะมาเล่าให้ฟัง เพราะน่าสนใจ สำหรับคนที่เล่น RTOS หรือนักพัฒนา Real time Kernel ทั้งหลาย) อีก 9 MC ตามมาด้วยการ Check ว่ามี การเปิดใช้งาน Interrupt อยู่จริงหรือเปล่า ตรงนี้เสียไปอีก 2 MC เท่านั้นยังไม่พอ การ re-load Timer ต้องทำแบบ Manual (ไม่ได้เป็น Auto-Reload) ทำให้เสียไปอีก 4 MC รวมกันทั้งหมดก็ปาเข้าไป 19 MC นั่นก็คือ 2 Clock ของ Timer (2*8 MC) กับอีก 3 MC นอกจากนี้อย่าลืมว่าหลังจาก Timer ถูกเขียน ค่าใหม่ลงไป การนับจะเริ่มใหม่ และเวลาจะเริ่มจาก MC ต่อไป แน่นอน ก็ต้องเสียไปอีก 1 MC สรุปแล้วตอนนี้ เศษเหลือคือ 4 MC หาคำสั่งที่ใช้ 1 MC มาใส่ลองไป 1 คำสั่ง (ทำซ้ำ 4 ครั้ง) ในที่นี้เลือก คำสั่ง nop (no operation)ที่เลือกเป็น asm() เพราะต้องการความมั่นใจว่า optimizer จะไม่ ignore คำสั่ง ไร้ประโยชน์เหล่านี้ (ส่วนนี้ค่อยเล่าให้ฟังในส่วนของ Optimization) ต่อไปคือ 2 Clock ของ Timer หรือ 16 MC นั่นเอง ค่าเหล่านี้เรีกว่า "Overhead" ต่อไปก็ต้องทำการชดเชยลงไปที่ค่า reload ของ Timer ในที่นี้คือ TIMER_VALUE แต่อย่าเผลอนะครับ Timer จะ Overflow เมื่อค่าวนกลับ สำหรับ Timer แบบ 8-bit จะเกิด Overflow ขั้นเมื่อค่าของ Timer เปลี่ยนจาก 0xFF เป็น 0x00 นั่นเป็นตัวบอกว่าจะต้องชดเชยไปอีก 1 Clock ของ Timer หรือ 8 MCY ผลที่ได้ก็เป็นไปตามโปรแกรม (ดูในส่วน define TIMER_VALUE) เก็บไว้ใช้ได้ตราบนานเท่านาน
ลงเชิงลึกไปหน่อยครับ แต่ในงานที่ต้องการความเที่ยงตรงสูงๆ สิ่งเหล่านี้เป็นเพียงเรื่องพื้นฐานเท่านั้น หวังเป็นอย่างยิ่งว่าจะเป็นประโยชน์ (หรืออาจเป็นโทษ) กับท่านผู้อ่านนะครับ
/************************************************ * FILE: MAIN.C * * CPU: PIC16F887 * * XTAL: 8MHz * * IDE: MPLAB IDE v8.63 * * COMPILER: HI-TECH PIC-C Compiler vV9.71a * * AUTHOR: SANTI NURATCH @ SHADOWWARES.COM * * DATE: 13 March, 2011 * * DESCRIPTION: * * Using Timer0 and its interrupt. * * The T0's interrupt is trigered every 1mS. * *************************************************/
#include <htc.h> //................................................................................
__CONFIG(INTIO & WDTDIS & PWRTDIS & MCLRDIS & UNPROTECT); #define _XTAL_FREQ 8000000.0f /* Clock Source */ #define _FCY _XTAL_FREQ/4.0f /* CPU and Peripheral Clock */ #define TIMER_TICK_RATE 1000.0f /* System Clock Rate */ #ifdef _TIMING_DEBUG_ #define TIMER_PRESCALER 2.0f /* PRES=2, see in Init() */ #else #define TIMER_PRESCALER 8.0f /* PRES=8, see in Init() */ #endif #define TIMER_VALUE (256f+3f-(_FCY/TIMER_PRESCALER/TIMER_TICK_RATE)+0.5) /*-4 is an overhead compensation */ //................................................................................ volatile unsigned int ISRTickCounter = 0;
//................................................................................
void SysInit(void){ TMR0 = (unsigned char)TIMER_VALUE; GIE = 1; // Enable Global Interrupt PEIE = 1; // Enables peripheral interrupts OPTION = 2; // 1:8 T0's Prescaler #ifdef _TIMING_DEBUG_ OPTION = 0; // 1:2 T0's Prescaler #endif T0IF = 0; // Clear interrupt flag T0IE = 1; // Enable T0's interrupt TMR0 = 0; // TIMER_VALUE }
static void interrupt ISR(void) @ 0x004{ if(T0IE && T0IF){ asm("nop"); asm("nop"); /* Overhead compensation */ asm("nop"); asm("nop"); #ifdef _TIMING_DEBUG_ TMR0 = -8; /* 16uS ISR Freq @ FOSC=8MHz, PRES=2*/ #else TMR0 = (unsigned char)TIMER_VALUE; #endif T0IF = 0; ISRTickCounter++; } } void main(void){ SysInit(); while(1); }
ต่อไปก็มาดูหลักฐานกันครับ ว่าทุกสิ่งทุกอย่างนั้นถูกจริงหรือเปล่า ใช้ Oscilloscope มาวัดสัญญาณหรอ? ไม่ใช่หรอกเพราะมันบอกอะไรไม่ค่อยได้ ทำอย่างไรดี วิธีง่ายๆ และยืนยันได้ครับ ใช้ Simulator ที่ติดมากับ MPLAB นั่นแหละ นับกันเป็น Machine Cycle ไปเลย จากข้อมูลของ Hardware ที่บอกไปด้านบน ระยะเวลา 1mS ตะต้องใช้จำนวน Machine Cycle เท่ากับ 2,000 Cycles (2,000*0.5=1,000 = 1mS) ผลการทำงาน ก็เป็นไปตามที่คำนวณทุกประการครับ ตามรูปด้านล่าง
-- By Santi //www.shadowwares.com/forum
Create Date : 13 มีนาคม 2554 |
Last Update : 14 มีนาคม 2554 11:37:05 น. |
|
0 comments
|
Counter : 2131 Pageviews. |
|
|
|
|
|
|
|