Sunday, July 27, 2014

การตั้งค่าให้ PIC24E ทำงานที่ 70 MIPS

นับตั้งแต่ Microchip นำเสนอไมโครคอนโทรลเลอร์ตระกูล PIC24EPจนกระทั่งเริ่มหาซื้อได้ในประเทศไทย มีจุดเด่นคือสมรรถนะในการทำงานสูงสุด 70 MIPS แต่ว่าไม่ใช่นำมาต่อเป็นวงจรแล้วจะได้ความเร็วการทำงานสูงสุดเลย แต่จะต้องมีการตั้งค่าและสวิทซ์คล็อกโดยซอฟต์แวร์ ดังนั้นในบทความนี้จะยกตัวอย่างการตั้งค่า PIC24EP ใทำงานที่ 70 MIPS โดยชิพที่ใช้เป็นเบอร์ PIC24EP256MC202 และแสดงการตั้งค่า 2 กรณี คือ 1.) ใช้ตัวกำเนิดความถี่ภายในชิพ คือ 7.37 MHz FRC (Internal Fast RC) และ 2.) ใช้คริสตัลความถี่ 8 MHz ต่อภายนอก

อาจมีผู้สงสัยว่าทำไมจึงสามารถได้สมรรถนะ 70 MIPS จากตัวกำเนิดความถี่ไม่ถึง 10 MHz คำตอบคือภายในชิพจะมีโมดูล PLL (Phase Lock Loop) อยู่ ซึ่งสามารถทวีความถี่ขึ้้นได้เป็นหลายเท่า วิธีการนี้มีข้อดีกว่าการใช้ตัวกำนิดคล็อกความถี่สูงอยู่ภายนอก ซึ่งอาจรบกวนอุปกรณ์รอบข้างได้ หัวใจของการตั้งค่าขึ้นอยู่กับโมดูล PLL นี้เอง โดยต้องเลือกค่าตัวหาร 3 ตัวให้เหมาะสมในโปรแกรมของเรา

วงจรที่ใช้

ในตัวอย่างนี้่ใช้วงจรอย่างง่ายดังในรูปที่ 1 ที่ประกอบด้วย PIC24EP สวิทซ์กดติดปล่อยดับ และ LED และมีวงจรกำเนิดความถี่ที่ใช้ผลึกคริสตัล 8 MHz ต่อร่วมกับ R และ C มีค่าดังในรูป

รูปที่ 1 วงจรอย่างง่ายที่ใช้ทดสอบการสวิทซ์ความถี่

หลังจากรีเซ็ต ค่าความถี่คล็อกในวงจรจะถูกตั้งตามค่าโดยปริยาย (default) คือเท่ากับแหล่งกำเนิด (7.37 MHz สำหรับ FRC หรือ 8 MHz สำหรับคริสตัล) ซึ่งความถี่นี้ยังต่ำกว่าค่าสูงสุดอยู่มาก ดังนั้นวิธีการแสดงของตัวอย่างนี้คือเขียนคำสั่งให้ LED กระพริบในลูปเมนด้วยความเร็วตามค่าโดยปริยายในช่วงต้น แต่หลังจากที่มีการกดสวิทซ์ SW1 โปรแกรมจะสวิทซ์คล็อกเข้าสู่โหมดสมรรถนะ 70 MIPS ดังนั้นหลังจากกดสวิทซ์จะเห็นได้ชัดเจนว่า LED จะกระพริบด้วยความถี่ที่สูงขึ้นมาก

การตั้งค่า Configuration bits

ผู้ที่คุ้นเคยกับการเขียนโปรแกรมสำหรับ PIC คงทราบดีว่าจะต้องตั้งค่า configuration bits ไว้ส่วนบนของโปรแกรม (หรือว่าตั้งค่าโดยใช้ MPLAB ซึ่งไม่แนะนำ เพราะจะใช้ได้เฉพาะคอมพิวเตอร์เครื่องนั้น) จุดสำคัญที่จะกล่าวถึงคือ ในช่วงเริ่มต้นหลังรีเซ็ต จะต้องการให้ PIC24EP รันบนคล็อกของ FRC โดยใช้มาโคร

_FOSCSEL(FNOSC_FRC);

หลังจากนั้นสำหรับกรณีที่ใช้ FRC เพียงอย่างเดียว สามารถต่อด้วยมาโครนี้

_FOSC(FCKSM_CSECMD & OSCIOFNC_ON & POSCMD_NONE);

หรือหากใช้คริสตัลภายนอก อาจใช้มาโครนี้

_FOSC(FCKSM_CSECMD & OSCIOFNC_OFF & POSCMD_XT);

ซึ่งความแตกต่างนี้ไม่สำคัญมากนัก (ในบทความของ Microchip ใช้แบบที่สองกับกรณี FRC ด้วย) ตัวสำคัญที่ต้องเลือกคือ FSCSM_CSECMD เพื่อยอมให้มีการสวิทซ์คล็อกได้ สำหรับรายละเอียดของความหมายคำสั่งเหล่านี้ดูได้จากดาต้าชีทและบทความของ Microchip

การกำหนดพอร์ต I/O

บนบอร์ดต้นแบบ เลือกต่อสวิทซ์ SW1 เข้ากับ RB2 และ LED กับ RB15 ในโปรแกรมนิยามได้ดังนี้

#define SW1 !_RB2      // switch is active-low
#define LEDB  _LATB15  // blue LED 

หลังจากนั้นกำหนดชนิด ทิศทาง ของพอร์ต และสำหรับสวิทซ์ เลือกใช้ตัวต้านทานพูลอัพภายใน (ทำให้ไม่ต้องต่อตัวต้านทานเข้ากับ 3.3 V ภายนอก) ใส่คำสั่งเหล่านี้ในช่วงเริ่มต้นของ main( ) ก่อนจะเข้าสู่ลูปที่รันต่อเนื่อง หรือวิธีที่ดีกว่าคือรวมเข้าเป็นฟังก์ชันสำหรับตั้งค่าเริ่มต้น init( ) ซึ่งถูกเรียกโดย main( ) อีกทีหนึ่ง

ANSELA=0x0000;  // use all ports RA and RB as digital
ANSELB=0x000;
_TRISB2 = 1;   // RB2 as input 
_CNPUB2 = 1;   // enable internal weak pull up
_TRISB15 = 0;   // RB15 as output

การตั้งค่า PLL

ส่วนนี้เป็นขั้นตอนสำคัญ รูปที่ 2 แสดงพารามิเตอร์ 3 ตัวที่ต้องตั้งค่าให้ถูกต้อง คือ PLLPRE, PLLDIV และ PLLPOST.

รูปที่ 2 บล็อก ไดอะแกรมของ PLL (ข้อมูลจากดาต้าชีทของ PIC24EP)

และยังแสดงการจำกัดค่าของสัญญาณที่จุดต่างๆ ในระบบ จากดาต้าชีท จะได้สมการที่เกี่ยวข้องทั้งหมดคือ

  • FCY = FOSC/2
  • FOSC = FIN x (M/(N1 x N2))
  • N1 = PLLPRE + 2
  • N2 = 2 x (PLLPOST + 1)
  • M = PLLDIV + 2

โดย FCY คือความถี่ที่ใช้ในการประมวลคำสั่งของ PIC24EP ซึ่งเราต้องการให้มีค่าเท่ากับ 70 MHz ดังนั้นเป้าหมายของการตั้งค่าในรูปที่ 2 คือต้องการได้ FOSC = 140 MHz ลองพิจารณากรณีที่ใช้ FRC เป็นตัวกำเนิดความถี่ จะได้ FIN = 7.37 MHz เมื่อกำหนดความถี่ที่อินพุตและเอาต์พุตแล้ว คำตอบที่ต้องการคือค่าของ PLLPRE, PLLDIV และ PLLPOST ที่ทำให้ได้ความสัมพันธ์นี้ ซึ่งจะมีได้หลายคำตอบ ตัวอย่างเช่น หารกเรากำหนดค่า PLLPRE = PLLPOST = 0 (ซึ่งเป็นค่าโดยปริยาย) จะได้ N1 = N2 = 2 ซึ่งค่านี้จะทำให้ได้ FPLLI = 3.685 MHz และ FSYS = 280 MHz ท้ายสุดจากความสัมพันธ์ FSYS = FPLLI x M จะได้ค่า M = 280/3.68 = 76 ดังนั้น PLLDIV = 76 - 2 = 74

เมื่อคำนวนค่าพารามิเตอร์ทั้งสามได้แล้ว สามารถใช้ชุดคำสั่งที่ Microchip ให้เป็นตัวอย่าง (อยู่ใน Family Reference Manual ของ PIC24EP) โดยใส่เป็นเงื่อนไขที่จะทำงานเมื่อมีการกดสวิทซ์ SW1

If (SW1)   {
       // Configure PLL prescaler, PLL postscaler, PLL divisor (FRC )
       PLLFBD = 74;   // M = 76
       CLKDIVbits.PLLPOST = 0;     // N2 = 2
       CLKDIVbits.PLLPRE=0;     // N1=2
       // initiate clock switch to FRC oscillator with PLL (NOSC=0b01)
       __builtin_write_OSCCONH(0x01);
       __builtin_write_OSCCONL(0x01);
       while (OSCCONbits.COSC != 0b001);
       while (OSCCONbits.LOCK != 1);
} while (SW1);

สำหรับรายละเอียดของแต่ละคำสั่งขอให้อ่านจากคู่มือ FRM ประกอบ ส่วนสำคัญคือการใส่ค่าที่คำนวนได้ลงในรีจิสเตอร์ CLKDIV และ PLLFBD (ซึ่งก็คือตัวเดียวกับ PLLDIV) หลังจากนั้นเขียนรีจิสเตอร์ OSCCON โดยใช้มาโครพิเศษ __builtin_write_ เพื่อสั่งให้คล็อกระบบเปลี่ยนไปใช้ PLL ที่ตั้งไว้ ท้ายสุดต้องรอให้การเปลี่ยนคล็อกสมบูรณ์เสียก่อนจึงจะไปทำงานอย่างอื่นได้

สำหรับกรณีใช้คริสตัล 8 MHz จากภายนอก สามารถคำนวนได้โดยง่ายว่าเมื่อเลือก N1 = 2, N2 = 2, M = 70 จได้ค่าของ FOSC = 140 MHz ตามต้องการ ในกรณีนี้จะใช้ชุดคำสั่งดังนี้

If (SW1)   {
       // Configure PLL prescaler, PLL postscaler, PLL divisor (primary OSC )
       PLLFBD = 68;   // M = 70
       CLKDIVbits.PLLPOST = 0;     // N2 = 2
       CLKDIVbits.PLLPRE=0;     // N1 = 2
       // initiate clock switch to primary oscillator with PLL (NOSC=0b11)
       __builtin_write_OSCCONH(0x03);
       __builtin_write_OSCCONL(0x01);
       while (OSCCONbits.COSC != 0b011);
       while (OSCCONbits.LOCK != 1);
} while (SW1);

สังเกตว่านอกจากค่าของ PLLFBD จะต่างกันแล้ว ค่าที่เขียนลงใน OSCCONH จะต่างกัน โดยกรณีใช้ FRC จะเขียนค่า 0x01 และกรณีใช้คริสตัลภายนอกจะเขียนค่า 0x03 ลงใน OSCCONH

สำหรับโค้ดส่วนที่ทำให้ LED กระพริบ เพิ่มสองคำสั่งนี้ลงในลูปที่วนไม่สิ้นสุดใน main( )

LEDB = !LEDB;
for (i=0;i<200000l;i++);

โดยใช้ตัวแปร i เป็นแบบ long (นิยามที่ส่วนต้นของ main) ใส่ในลูป for( ) เพื่อใช้เป็นดีเลย์อย่างง่าย i จะนับค่าขึ้นจนถึง 200000 ก่อนจะหลุดออกจากลูป for( ) วีดีโอนี้แสดงผลของการรันโปรแกรม จะเห็นได้ชัดเจนว่า LED จะกระพริบเร็วขึ้นมากเมื่อคล็อกของระบบเปลี่ยนไปรันที่ 70 MIPS

ซอร์สไฟล์ที่สมบูรณ์ของตัวอย่างนี้สามารถดาวน์โหลดได้จากลิงก์ด้านล่างนี้ ซึ่งเขียนให้ครอบคลุมทั้ง 2 กรณี โดยจะมีตัวเลือก #define FRC และ #define PRIOSC ที่ส่วนบนของโปรแกรม ถ้าต้องการใช้ตัวกำเนิดสัญญาณตัวใดก็ใส่ // ให้อีกตัวเป็นคอมเมนต์ไป

main.c

บทสรุป

ในบทความนี้ได้แสดงตัวอย่างง่ายๆ ในการตั้งค่า PIC24EP ให้รันที่สมรรถนะสูงสุดคือ 70 MIPS โดยใช้ตัวกำเนิดคล็อกภายใน FRC หรือคริสตัล 8 MHz ภายนอก โดยแสดงการคำนวนค่าพารามิเตอร์ตัวหารความถี่ใน PLL ที่ต้องตั้งค่าให้ถูกต้อง หวังว่าผู้อ่านคงเข้าใจหลักการคำนวนและสามารถดัดแปลงแก้ไขให้ใช้ได้ หากต้องการใช้กับคริสตัลความถี่อื่นๆ

ในตัวอย่างนี้เราแสดงให้เห็นความแตกต่างของความเร็วในการทำงานโดยใช้การกระพริบของ LED หากท่านต้องการตรวจสอบให้แน่ใจว่า หลังจากใช้ PLL แล้ว PIC24E ทำงานด้วยสมรรถนะสูงสุด 70 MIPS จริงหรือไม่นั้น วิธีการหนึ่งที่ใช้ได้คือตั้งค่าไทเมอร์ให้ทำงานตามคาบเวลาหนึ่งที่กำหนดและให้เอาต์พุตเปลี่ยนสถานะทุกครั้งที่เกิดอินเตอร์รัพท์ จากนั้นสามารถตรวจสอบคาบเวลาได้จากสโคป หรือหากว่าไม่มีสโคป อาจจะดูการกระพริบของ LED เทียบกับฐานเวลาที่แน่นอน ตัวอย่างเช่นตั้งให้ LED กระพริบทุก 1 วินาที และเปรียบเทียบกับเข็มวินาทีของนาฬิกา โค้ดด้านล่างนี้แสดงการตั้งค่า timer 1

// global variables
volatile unsigned int msc;
// -------- timer functions ----------------
void initT1() // Initialize Timer 1 to interrupt every 1 ms
{
    T1CON = 0x8020 ;   // Internal Clock (FOSC/2)=70MHZ, 1:64 prescale
    PR1   = 1094;        //0.001/(tcy*64) where tcy = 1/70M
    TMR1  = 0x0000 ;
     msc = 0;
    _T1IF = 0;
    _T1IE = 1;  


}

void __attribute__((interrupt, auto_psv)) _T1Interrupt(void)
// Timer 1 interrupt every 1 mS
{
    msc++;
    if (msc>1000)   {  // blink LED each second
        LEDB = !LEDB;  
        msc = 0;
    }

    _T1IF = 0;
}

สังเกตว่าเราตั้งค่าคาบเวลาการอินเตอร์รัพท์ของไทเมอร์เท่ากับ 1 มิลลิวินาที และเปลี่ยนสถานะของ LED เมื่ออินเตอร์รัพท์ครบ 1000 ครั้ง ซึ่งก็คือ 1 วินาที

No comments:

Post a Comment