การตั้งค่า Oscillator Configuration Bits เพื่อสร้างสัญญาณนาฬิการะบบ 64MHz ใน PIC18F46K22

การตั้งค่า Oscillator Configuration Bits เพื่อสร้างสัญญาณนาฬิการะบบ 64MHz ใน PIC18F46K22

    จะขอเกริ่นนำเรื่อง Configuration bit หรือ Fuse ซะหน่อย จริงๆ ในตอนที่แล้วเรื่อง MPLAB® XC8 Getting Started Guide เราก็ได้ทำให้ดูแล้วว่าการตั้งค่า Configuration bits ทำอย่างไร อธิบายง่ายๆได้ดังนี้ 

Configuration bit เป็น bit ที่เอาไว้เซตให้ไมโครคอนโทรลเลอร์ มีพื้นฐานการทำงานแบบไหน ไม่ว่าจะเป็น โหมดของแหล่งกำเนิดสัญญาณนาฬิกาของระบบ, การทำงานของ watchdog timer, โหมด Programming และ การป้องกันการก๊อปปี้โค๊ด การตั้งค่า Configuration bit หรือ Fuse ผิด อาจจะทำให้ระบบทำงานผิดพลาด หรือ ทำงานไม่ได้เลย ก็ได้

ใน XC8 Compiler เราสามารถกำหนดค่า bit ของ configuration ได้ด้วยการใช้ pre-processing ที่ชื่อ #pragma ซึ่งมีรูปแบบดังนี้

#pragma config setting= state|value

#pragma config register= value

โดยที่ setting คือ คำที่กำหนดเป็นตัวแทนของ configuration bit เช่น WDT มาจาก Watchdog 

        state เป็นตัวอัษรที่ใช้กำหนดสถานะที่ต้องการ เช่น ON หรือ OFF 

        value คือค่าที่เป็นตัวเลข

ตัวอย่าง

#pragma config WDT = ON // เปิดการใช้งาน Watchdog timer

#pragma config WDTPS = 0x1A // ระบุค่า timer postscale value

เราสามารถเขียนให้สามารถเขียนให้เหลือสั้นๆ ได้ ด้วยการใช้ comma คั่น 

#pragma config WDT=ON, WDTPS = 0x1A

    ในตอนนี้เราจะมาทำความเข้าใจกับการตั้งค่า Configuration bits ใน MPLABX กับ PIC18F46K22 โดยเราจะมุ่งเน้นไปที่การกำหนด Configuration bits ที่เกี่ยวกับ Oscillator ที่เป็นแหล่งกำเนิดสัญญาณนาฬิกาของระบบ ซึ่ง PIC18F46K22 นั้น สามารถที่จะเลือกใช้ Oscillator ที่อยู่ภายในได้ และสามารถทำ Phase Lock Loop หรือ PLL คูณกับค่าสัญญาณนาฬิกานี้ ให้มีค่าเพิ่มขึ้นไปอีกได้ 4 เท่า ซึ่งจะสามารถทำความถี่สูงสุดของสัญญาณนาฬิกา ได้สูงถึง 64MHz กันเลยทีเดียว 

    ในที่นี้ ผมมีบอร์ดของ INEX NX-877 PLUS อยู่แล้ว ซึ่งจากการพิจารณาที่แรงดัน และตำแหน่งการวางขาของไมโครคอนโทรลเลอร์ PIC18F46K22 กับ PIC16F877 แล้วพบว่า ตำแหน่งขา เหมือนกัน และแรงดันไฟเลี้ยงที่ใช้ ก็อยู่ในระดับ 5V เหมือนกัน ดังนั้น จึงสามารถถอดเอา PIC16F877 ออก แล้วสามารถเสียบ PIC18F46K22 ได้เลย เสียแต่ว่า เราอาจจะไม่ได้ใช้ขา RA6 และ RA7 เท่านั้น หากเราเลือกใช้ Oscillator ภายใน แต่ในแง่การทดลองก็ยังไม่ใช่ปัญหาตอนนี้ 

หมายเหตุ : สำหรับบอร์ด CP-PIC V4,ET-BASE PIC40/877 ของ ETT หรือบอร์ดอื่นๆ ที่รองรับ PIC16F877 ก็สามารถใช้ได้เหมือนกัน 

    อันดับแรก เราคงต้องดาวน์โหลดดาต้าชีทของ PIC18F46K22 มาก่อน ซึ่งเจ้าเบอร์นี้ จะอยู่ใน Family ของ  PIC18(L)F2X/4XK22 ดาวน์โหลดได้ที่นี่ http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en547757 จากนั้นให้เปิดไปที่หัวข้อ 2.0 OSCILLATOR MODULE (WITH FAIL-SAFE CLOCK MONITOR) และอ่านทั้งหมดของหัวข้อนี้ มือใหม่ อาจจะลำบากนิดนึงครับ ซึ่งก็รวมทั้งผมด้วย ;(

แต่พอจะสรุปใจความได้ดังนี้ 

แหล่งกำเนิดสัญญาณนาฬิกาสามารถกำหนดได้ 6 แหล่งด้วยกัน ซึ่งจะใช้ชื่อย่อสื่อความหมายต่อไปนี้ 

1. RC External Resistor/Capacitor

2. LP Low-Power Crystal

3. XT Crystal/Resonator

4. INTOSC Internal Oscillator

5. HS High-Speed Crystal/Resonator

6. EC External Clock

ซึ่งในที่นี้ เราจะสนใจเฉพาะหัวข้อที่ 4 ซึ่งเราจะเอาแหล่งกำเนิดสัญญาณนาฬิกาภายใน เป็นแหล่งกำเนิดสัญญาณนาฬิกาหลัก เพื่อที่จะได้ประหยัด Crystal ที่จะมาต่อภายนอก และ เพื่อนำไปใช้ในงานที่สุ่มเสี่ยงว่าจะมีการรบกวนทางแม่เหล็กไฟฟ้า (EMI) ได้ 

    

    จากรูปบล๊อก FIGURE 2-1: SIMPLIFIED OSCILLATOR SYSTEM BLOCK DIAGRAM ผมได้ทำเส้นทางของสัญญาณนาฬิกาที่เราจะต้องกำหนดให้ได้ตามเส้นทางนั้น เพื่อที่จะทำให้สัญญาณนาฬิกาของระบบมีค่า 64MHz ซึ่งเป็นไปตามที่เราต้องการให้ได้ ซึ่งถ้าเราพิจารณาเส้นทางของตั้งแต่ HFINTOSC 16MHz จนไปถึง Primary Clock เราจะเห็นชื่อของชื่อบิตต่างๆ ที่อยู่ในแต่ละรีจิสเตอร์ ที่จะต้องทำการเซตดังนี้ 

SCS<1:0>:System Clock Select bit อยู่ในรีจิสเตอร์ OSCCON

IRCF<2:0>:Internal RC Oscillator Frequency Select bits  อยู่ในรีจิสเตอร์ OSCCON

PLLEN:Frequency Multiplier 4xPLL for HFINTOSC Enable bit  อยู่ในรีจิสเตอร์ OSCTUNE 

FOSC<3:0>:Oscillator Selection bits อยู่ในรีจิสเตอร์ CONFIG1H 

    ในหัวข้อ 2.2 กล่าวไว้ว่า มีรีจิสเตอร์ที่ทำหน้าที่ควบคุม Oscillator อยู่ 3 ตัว ชื่อ OSCCON, OSCCON2 และ OSCTUNE ซึ่งในรีจิสเตอร์ทั้งสามตัวนี้แหละ จะมี Configuration bit ที่ยกมาให้ดูด้านบน อยู่ภายใน จากนั้นในหัวข้อที่ 2.2.1 ได้อธิบายเกี่ยวกับการเลือก MAIN SYSTEM CLOCK SELECTION ซึ่งจะเกี่ยวข้องกับ SCS<1:0>:System Clock Select bit ซึ่งเราก็ต้องตามไปดูว่า SCS กำหนดค่าอะไรได้บ้าง เราสามารถดูการตั้งค่า SCS ได้จากรีจิสเตอร์ที่ชื่อ OSCCON ในหัวข้อ 2.3 Register Definitions: Oscillator Control ซึ่งจากบิต SCS มันดันไปโยงกับ FOSC<3:0>:Oscillator Selection bits ที่อยู่ในรีจิสเตอร์ CONFIG1H อีกทีหนึ่ง ซึ่งเจ้า CONFIG1H นั่นสามารถที่จะกำหนดได้จากการทำ Configuration Bits ใน MPLABX IDE ได้เลย  

    ในที่นี้ เราต้องการที่จะใช้ Internal Oscillator ที่เป็นแหล่งกำเนิดสัญญาณนาฬิกาภายใน ดังนั้น เราจึงมุ่งไปที่หัวข้อ 2.2.2 INTERNAL FREQUENCY SELECTION เพื่อให้บรรลุประสงค์ ในหัวข้อ 2.2.2 นี้ กล่าวไว้ว่าเราสามารถที่จะเลือก Internal Oscillator ได้ 3 แหล่งด้วยกันคือ  LFINTOSC source (31.25 kHz),  MFINTOSC source (31.25 kHz,250 kHz or 500 kHz)  และ HFINTOSC source (16 MHz) โดยผ่านการตั้งค่าผ่านบิต IRCF<2:0>:Internal RC Oscillator Frequency Select bits ที่อยู่ในรีจิสเตอร์ OSCCON: OSCILLATOR CONTROL REGISTER ดังนั้นหากเราต้องการที่จะเลือกใช้  HFINTOSC source (16 MHz) เราต้องกำหนดให้ IRCF<2:0> มีค่าเป็น 111 (ฐานสอง) หรือมีค่าเป็น 7 (ฐานสิบ) ก็ได้เหมือนกัน  

และเมื่อเรามองดูตามเส้นที่ผมลากไว้ใน FIGURE 2-1: SIMPLIFIED OSCILLATOR SYSTEM BLOCK DIAGRAM จะเห็นว่า มันจะต้องเดินทางผ่าน PLL_Select และเมื่อเราตามไปดูที่ FIGURE 2-3: PLL_SELECT BLOCK DIAGRAM มันประกอบไปด้วยสัญลักษณ์ AND GATE และ OR GATE ต่อไว้ดังรูป จากรูปเราสรุปได้ว่า หากต้องการให้ PLL_Select มีสถานะเป็น 1 หรือ ON นั้น เราเพียงแต่กำหนดให้ PLLEN มีค่าเป็น 1 ก็จะทำให้ PLL_Select เป็น 1 ด้วยเช่นกัน ดังนั้น เราเพียงแต่ กำหนดให้ PLLEN ใน รีจิสเตอร์ OSCTUNE ให้มีค่าเป็น 1 

เอาหล่ะ เมื่อเราได้รูปแบบที่ต้องกำหนดแต่ละบิต ที่เกี่ยวข้องในการตั้งค่า Internal Oscillator x 4LL เพื่อให้ได้ความถี่ของสัญญาณนาฬิกา 64MHz แล้ว เราก็จะนำค่าเหล่านี้มาเขียนในโค๊ดของเรา จะได้ดังนี้ (ยกเว้น FOSC ที่ได้จากการกำหนด Configuration Bits ผ่าน IDE) 

OSCCONbits.SCS = 0b00;      /* The system clock source is determined by CONFIG1H */

OSCCONbits.IRCF = 0b111;    /* Internal RC Oscillator Freq select 16MHz ; IRCF<2:0> 111 */

OSCTUNEbits.PLLEN = 1;      /* The PLL multiplies the HFINTOSC by four to produce clock rates up to 64 MHz*/

ส่วน FOSC เราจะได้จากการกำหนดผ่านเมนู Window->PIC Memory Views->Configuration Bits จะได้หน้าต่างในการกำหนดค่า Configuration Bits 

จากค่าที่กำหนด FOSC มีค่าเท่ากับ F8 ซึ่งมีค่าเท่ากับ 1111 1000 (ฐานสอง) จึงทำให้ FOSC<3:0> มีค่าเท่ากับ 1000 ซึ่งสอดคล้องกับค่าในตาราง TABLE 2-1: PLL_SELECT TRUTH TABLE ด้วย

    มาถึงเวลาพิสูจน์ว่า เราได้ความถี่สัญญาณนาฬิกา 64MHz จริงหรือเปล่า เราจะทดสอบง่ายๆ ด้วยการแสดงไฟกระพริบติดดับ ทุกๆ ครึ่งวินาที หรือ 500ms หากหลอด LED สามารถกระพริบได้ตรงตามที่เราตั้งค่าในฟังก์ชั่น delay_ms(500); แล้วหล่ะก็ แสดงว่าค่า #define _XTAL_FREQ 64000000 เรากำหนดนั้น ตรงกับความถี่จริงที่เราสามารถสร้างขึ้นมานั่นเอง 

#define _XTAL_FREQ  64000000

#include <stdio.h>

#include <stdlib.h>

#include <xc.h>

// CONFIG1H

#pragma config FOSC = INTIO67   // Oscillator Selection bits (Internal oscillator block)

#pragma config PLLCFG = ON      // 4X PLL Enable (Oscillator multiplied by 4)

#pragma config PRICLKEN = ON    // Primary clock enable bit (Primary clock is always enabled)

#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor enabled)

#pragma config IESO = ON        // Internal/External Oscillator Switchover bit (Oscillator Switchover mode enabled)

// CONFIG2L

#pragma config PWRTEN = ON      // Power-up Timer Enable bit (Power up timer enabled)

#pragma config BOREN = SBORDIS  // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))

#pragma config BORV = 285       // Brown Out Reset Voltage bits (VBOR set to 2.85 V nominal)

// CONFIG2H

#pragma config WDTEN = OFF      // Watchdog Timer Enable bits (Watch dog timer is always disabled. SWDTEN has no effect.)

#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H

#pragma config CCP2MX = PORTB3  // CCP2 MUX bit (CCP2 input/output is multiplexed with RB3)

#pragma config PBADEN = OFF     // PORTB A/D Enable bit (PORTB<5:0> pins are configured as digital I/O on Reset)

#pragma config CCP3MX = PORTB5  // P3A/CCP3 Mux bit (P3A/CCP3 input/output is multiplexed with RB5)

#pragma config HFOFST = OFF     // HFINTOSC Fast Start-up (HFINTOSC output and ready status are delayed by the oscillator stable status)

#pragma config T3CMX = PORTB5   // Timer3 Clock input mux bit (T3CKI is on RB5)

#pragma config P2BMX = PORTD2   // ECCP2 B output mux bit (P2B is on RD2)

#pragma config MCLRE = EXTMCLR  // MCLR Pin Enable bit (MCLR pin enabled, RE3 input pin disabled)

// CONFIG4L

#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)

#pragma config LVP = ON         // Single-Supply ICSP Enable bit (Single-Supply ICSP enabled if MCLRE is also 1)

#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L

#pragma config CP0 = OFF        // Code Protection Block 0 (Block 0 (000800-003FFFh) not code-protected)

#pragma config CP1 = OFF        // Code Protection Block 1 (Block 1 (004000-007FFFh) not code-protected)

#pragma config CP2 = OFF        // Code Protection Block 2 (Block 2 (008000-00BFFFh) not code-protected)

#pragma config CP3 = OFF        // Code Protection Block 3 (Block 3 (00C000-00FFFFh) not code-protected)

// CONFIG5H

#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected)

#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

// CONFIG6L

#pragma config WRT0 = OFF       // Write Protection Block 0 (Block 0 (000800-003FFFh) not write-protected)

#pragma config WRT1 = OFF       // Write Protection Block 1 (Block 1 (004000-007FFFh) not write-protected)

#pragma config WRT2 = OFF       // Write Protection Block 2 (Block 2 (008000-00BFFFh) not write-protected)

#pragma config WRT3 = OFF       // Write Protection Block 3 (Block 3 (00C000-00FFFFh) not write-protected)

// CONFIG6H

#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected)

#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block (000000-0007FFh) not write-protected)

#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

// CONFIG7L

#pragma config EBTR0 = OFF      // Table Read Protection Block 0 (Block 0 (000800-003FFFh) not protected from table reads executed in other blocks)

#pragma config EBTR1 = OFF      // Table Read Protection Block 1 (Block 1 (004000-007FFFh) not protected from table reads executed in other blocks)

#pragma config EBTR2 = OFF      // Table Read Protection Block 2 (Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks)

#pragma config EBTR3 = OFF      // Table Read Protection Block 3 (Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks)

// CONFIG7H

#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot Block (000000-0007FFh) not protected from table reads executed in other blocks)

void delay_ms(unsigned int ms)

{

    while(ms--)

    {

        __delay_ms(1);

    }

}

/*

 * 

 */

int main(int argc, char** argv) {

    // Oscillator configuration

    OSCCONbits.SCS = 0b00;      /* The system clock source is determined by CONFIG1H */

    OSCCONbits.IRCF = 0b111;    /* Internal RC Oscillator Freq select 16MHz ; IRCF<2:0> 111 */

    OSCTUNEbits.PLLEN = 1;      /* The PLL multiplies the HFINTOSC by four to produce clock rates up to 64 MHz*/

    while(!OSCCONbits.HFIOFS);  /* HFINTOSC frequency is stable */

    // Initialise Digital I/O

    PORTB = 0x00;

    TRISB = 0x00;

    

    while(1)

    {        

        PORTB ^= 0x01;

        delay_ms(500);

    }

    return (EXIT_SUCCESS);

}

ถ้าเรากำหนดค่า Internal Oscillator ถูกต้องได้ 64MHz แล้วหล่ะก็ เวลาที่เรา #define _XTAL_FREQ 64000000 เราจะเห็นว่า LED ที่ต่ออยู่กับ PB0 นั้น กระพริบติดดับสลับกัน โดยใช้เวลา 0.5 วินาที หรือ 500ms นั่นเอง