Sunday, July 27, 2014

ตรวจสอบความถี่คล็อกของ PIC24EP โดยโมดูล UART

ในบทความก่อนหน้านี้การตั้งค่าให้ PIC24E ทำงานที่ 70 MIPSเราได้แนะนำวิธีการตรวจสอบว่าระบบทำงานที่ 70 MIPS จริงหรือไม่โดยตั้งค่าเอาต์พุตของไทเมอร์และใช้สโคปวัด แต่ในทางปฏิบัติคงไม่ใช่ทุกคนที่มีสโคปอยู่ที่บ้าน ดังนั้นในบทความนี้จะนำเสนออีกวิธีหนึ่งที่ไม่ต้องการอุปกรณ์ราคาแพง คือ ตั้งค่า UART และส่งข้อความสื่อสารกับคอมพิวเตอร์อีกเครื่องหนึ่ง เช่นเครื่อง PC โดยใช้ซอฟต์แวร์เทอร์มินัลทั่วไป หากการตั้ง baudrate โดยใช้ความถี่ FCY = 70 MHz สามารถทำให้โมดูล UART ทำงานได้อย่างถูกต้อง แสดงว่าความถี่ FCY ได้ตามต้องการอย่างแม่นยำ นอกจากการตรวจสอบนี้แล้ว เรายังได้มีโอกาสเรียนรู้การใช้งานโมดูล UART ของ PIC24EP อีกด้วย

ซอฟต์แวร์ที่ใช้ทดสอบจะตั้งเป้าหมายไว้ง่ายๆ คือ ต้องการสั่งงานควบคุมไทเมอร์ผ่านทางพอร์ต UART โดยระบบจะทำงานดังนี้ หลังจากรีเซ็ต โปรแกรมจะตั้งให้ไทเมอร์ 1 ทำงานที่คาบเวลาเท่ากับ 200 มิลลิวินาที โดยต่อเอาต์พุตกับ LED ให้กระพริบตามคาบเวลานี้ และจะตั้งค่า UART เพื่อพร้อมรับคำสั่งจาก PC เมื่อพิมพ์คำว่า "TOFF" ไทเมอร์จะหยุดทำงาน (LED หยุดกระพริบ) และมีข้อความตอบสนอง "Timer 1 OFF" หากพิมพ์ "TON" ไทเมอร์จะเริ่มทำงานใหม่ พร้อมส่งข้อความ "Timer 1 ON" ถ้าผู้ใช้พิมพ์ข้อความอื่นใดระบบจะไม่ตอบสนอง แต่หากกดคีย์ [Enter] จะส่งข้อความ "Ready" และขึ้นบรรทัดใหม่

หัวใจสำคัญของการสื่อสารแบบอะซิงโครนัสเช่น UART คือ ระบบทั้งสองด้านจะมีคล็อกของตัวเอง โดยตกลงกันล่วงหน้าว่าจะสื่อสารด้วยความเร็วข้อมูลเท่าใด ดังนั้นสัญญาณคล็อกของระบบทั้งสองด้านจะต้องมีความแม่นยำสูง การคำนวนความเร็วการสื่อสาร หรือที่เรียกว่า อัตราบอด (baud rate) จะขึ้นกับความถี่สัญญาณคล็อกระบบ ดังนั้นหากความถี่ไม่ตรงตามค่าที่ใช้คำนวน (ในที่นี้คือ FCY = 70 MHz) จะทำให้การตั้งค่าไม่ถูกต้องและระบบจะไม่สามารถสื่อสารได้ (โดยทั่วไปเมื่อตั้งค่าไม่ถูกต้อง ผู้ใช้จะไม่เห็นข้อความใดๆ หรือบางครั้งอาจเห็นข้อมูลที่รับมาในโปรแกรมเทอร์มินัลเป็นอัขระแปลกๆ แทนข้อความภาษาอังกฤษ)

บนบอร์ดเล็กๆ ที่ใช้ทดลอง เราติดตั้งชิพ FT232R จากบริษํท Future Technology Int'l Ltd.เพื่อใช้แปลง UART เป็น USB ทำให้สามารถทดลองบน PC หรือโน๊ตบุครุ่นใหม่ๆ ที่ไม่มีพอร์ตอนุกรมได้ ทางด้านฮาร์ดแวร์ไม่มีอะไรซับซ้อน โดยกำหนดพอร์ต RX และ TX ของโมดูล ให้กับขา 21 (RP42) และ (RP43) ซึ่งเชื่อต่อเข้ากับขา TXD และ RXD ของชิพ FT232R ตามลำดับ ดังนั้นงานส่วนใหญ่จะไปหนักทางซอฟต์แวร์ โดยขั้นตอนต่างๆ ที่ต้องกระทำมีดังนี้ (ไม่ได้ระบุลำดับ)

  • ตั้งค่าการเลือกขา (peripheral pin select) เพื่อให้ขา RP42 และ RP43 ทำหน้าที่เป็น U1TX และ U1RX ตามลำดับ
  • เลือกรูปแบบการสื่อสารให้ตรงกันทั้งสองด้าน เช่น ข้อมูล 8 บิต 1 สต็อปบิต ไม่มี hardware flow control เป็นต้น
  • เลือกอัตราบอดให้ตรงกัน เช่น 9600, 19200, 57600, 115200
  • เลือกพารามิเตอร์อื่นๆ ที่เกี่ยวข้อง เช่นความเร็วสูง/ต่ำ บิตตั้งให้ทำงานแบบ auto-baud Set up
  • คำนวนและตั้งค่ารีจิสเตอร์สำหรับอัตราบอด
  • เลือกวิธีการรับส่งข้อมูล (แบบ polling หรือใช้อินเตอร์รัพท์)
  • เขียนฟังก์ชันภาษา C สำหรับรับส่งข้อมูล

ต่อไปจะอธิบายเพิ่มเติมสำหรับบางขั้นตอน

การเลือกขา Peripheral

ไมโครคอนโทรลเลอร์รุ่นใหม่ๆ จะออกแบบให้มีความยืดหยุ่นมากขึ้น โดยผู้ใช้สามารถกำหนดขาโมดูลบางตัวให้กับขาของชิพได้เองโดยซอฟต์แวร์ โดยขาที่สามารถเลือกได้นี้จะขึ้นต้นด้วย RP เช่นใน PIC24EP สามารถเลือกขาของ UART1 ได้ ในที่นี้เราจะกำหนดให้ U1TX และ U1RX ต่อกับขา RP42 และ RP43 ตามลำดับ โดยใช้คำสั่งดังนี้ รายละเอียดเพิ่มเติมสามารถดูได้จากบทความของไมโครชิพ (DS705098B: PIC24EP FRM, Section 10. I/O Ports)

 RPINR18bits.U1RXR = 43;  // U1RX to RP43
 RPOR4bits.RP42R = 1;     // U1TX to RP42

รายละเอียดเพิ่มเติมสามารถดูได้จากบทความของไมโครชิพ (DS705098B: PIC24EP FRM, Section 10. I/O Ports)

เลือกรูปแบบการสื่อสาร

ระบบทั้งสองฝั่งจะสื่อสารกันรู้เรื่องก็ต่อเมื่อตั้งค่ารูปแบบเดียวกัน ในไดรเวอร์ของวินโดวส์จะมีค่าโดยปริยายเป็นแบบ 8-N-1 คือข้อมูล 8 บิต และสต็อปบิต 1 บิต ไม่มีพาริตี้ ดังนั้นเราจะตั้งค่าใน PIC24EP ให้ตรงกัน และเนื่องจากโปรแกรมนี้จะรับส่งข้อความสั้นๆ เท่านั้น จึงไม่มีความจำเป็นต้องใช้ hardware flow control (หากใช้จะต้องเปลืองขาอีก 2 ขาสำหรับสัญญาณ CTS และ RTS)

เลือกโหมดความเร็วและคำนวนพารามิเตอร์บอด

ส่วนนี้ถือเป็นสาระสำคัญของบทความ ในการที่ UART1 จะสามารถรับส่งข้อมูลตามอัตราบอดที่กำหนด จะต้องตั้งค่าให้กับรีจิสเตอร์ U1BRG โดยคำนวนจากความถี่คล็อกของระบบ ใน PIC รุ่นสูงเช่น PIC24EP จะมีตัวเลือกให้ UART ทำงานในโหมดความเร็วต่ำหรือสูงได้ โดยการตั้งค่าให้บิต BRGH (0 = ความเร็วต่ำ, 1 = ความเร็วสูง) ในที่นี้เราจะตั้ง BRGH = 1

เมื่อเลือกโหมดความเร็วสูง จะคำนวนค่าที่เขียนลงในรีจิสเตอร์ U1BRG ได้จาก

U1BRG = FCY/(4*baud_rate) - 1

โดย FCY มีค่า 70 MHz และ baud_rate คืออัตราบอดที่ต้องการ เพื่อความพร้อมใช้งานจะคำนวนค่าไว้ล่วงหน้าสำหรับอัตราบอดที่นิยมใช้ โดยนิยามไว้ส่วนต้นของโปรแกรมดังนี้

 #define BRATE115200 150    // computed from 70e6/(4*115200) - 1 
 #define BRATE57600  303
 #define BRATE19200 910
 #define BRATE9600  1822
 

สังเกตว่าเมื่ออัตราบอดยิ่งสูง จะยิ่งต้องการคล็อกที่มีความแม่นยำมากขึ้น ดังนั้นเพื่อทดสอบว่าคล็อกของระบบมีค่าตามต้องการหรือไม่ จะเลือกใช้ค่าอัตราบอดสูงสุดที่โปรแกรมเทอร์มินัลยอมให้ตั้งค่าได้ คือ BRATE115200

ฟังก์ชัน initU1( ) ด้านล่างนี้ทำหน้าทีตั้งค่า UART1 ให้มีคุณสมบัติตามที่กล่าวมาทั้งหมด

void initU1()  {
    U1MODEbits.STSEL = 0;    // 1-stop bit
    U1MODEbits.PDSEL = 0;    // No parity, 8-data bits
    U1MODEbits.ABAUD = 0;   // Auto-Baud disables
    U1MODEbits.BRGH = 1;  // high speed mode
    U1BRG = BRATE115200;
    U1MODEbits.UARTEN = 1;      // enable UART
    U1STAbits.UTXEN = 1;        // enable UART TX
    IFS0bits.U1RXIF = 0; //Clear RX interrupt flag
    IEC0bits.U1RXIE = 1; //Enable RX interrupt
}

เขียนฟังก์ชันการรับ/ส่ง

การทำงานของตัวอย่างนี้คือต้องการส่งคำสั่งง่ายๆ จาก PC ผ่านทาง UART และ PIC24EC จะตอบสนองด้วยข้อความ พร้อมทำงานตามที่สั่ง โดยคำสั่งและข้อความทั้งหมดอยู่ในรูป ASCII ดังนั้นในซอฟต์แวร์จะต้องมีฟังก์ชันในการเข้าถึงรีจิสเตอร์ที่เกี่ยวข้องในโมดูล UART1 เพื่อรับส่งข้อมูล

เริ่มโดยพิจารณาฟังก์ชันด้านส่ง ทำหน้าที่เดียวคือส่งข้อความสั้นๆ กลับไปยัง PC เช่นคำว่า "ready" "Timer 1 ON" หรือ "Timer 1 OFF" ดังนั้นไม่มีความจำเป็นที่ต้องอาศัยอินเตอร์รัพท์ ฟังก์ชันที่ใช้สามารถเขียนได้ง่ายๆ ดังนี้

void PutChrU1(unsigned char c)
{   
    while(U1STAbits.UTXBF) {}
    U1TXREG = c;
}

void PutStrU1( char *data)
{
  do
  {  // Transmit a byte
    if(*data>0x01) {PutChrU1(*data);}
  } while( *data++ );
}
 

ฟังก์ชันระดับล่างสุดที่ใช้ในการส่งอักขระหนึ่งตัวคือ PutChrU1( ) ซึ่งจะถูกเรียกโดยฟังก์ชันระดับเหนือขึ้นไปคือ PutStrU1( ) เพื่อส่งสตริงของข้อความหนึ่ง ตัวอย่างเช่น PutStrU1("Ready\r\n") จะส่งข้อความคำว่า Ready ตามด้วยรหัส ASCII ของ line feed และ carriage return ทำให้ cursor ในโปรแกรมเทอร์มินัลขึ้นบรรทัดใหม่

ต่อมาพิจารณาด้านรับบ้าง เนื่องจาก PIC24 จะไม่ทราบว่าข้อความคำสั่งจะมาเมื่อใด ดังนั้นการเขียนโดยใช้อินเตอร์รัพท์จะดีกว่าการวนลูปรอรับคำสั่ง โดย ISR สำหรับภาครับของ UART จะถูกำหนดให้ใช้ชื่อว่า _U1RXInterrupt( ) ข้อแนะนำทั่วไปในการเขียน ISR คือควรให้ทำงานเสร็จเร็วสุด และไม่เรียกฟังก์ชันอื่น อย่างไรก็ตามงานในตัวอย่างนี้ไม่มีอะไรซับซ้อน (ไม่มีการทำงานในลูป main( ) เลย) และคำสั่งมีเพียง 2 เท่านั้นคือ เปิด/ปิดการทำงานไทเมอร์ ดังนั้นจึงเขียนส่วนแปลคำสั่งและการ เปิด/ปิด ไทเมอร์ทั้งหมดไว้ใน ISR ดังนั้นฟังก์ชัน _U1RXInterrupt( ) เขียนได้เป็นดังนี้

void __attribute__((interrupt, auto_psv)) _ISR _U1RXInterrupt()
{ 
     char dat;
     while(U1STAbits.URXDA)   {
 dat=U1RXREG&0xff; // clean up by zeroing upper 8-bit
             dat=toupper(dat);          // convert to uppercase character
             if((dat=='\r')||(dat=='\n'))  {  // response when [Enter] is hit
                      PutStrU1("\r\nReady\r\n");
              }
              INB[Rx1Cnt]=dat ;    // put in global buffer
            //-------------naive command interpreter ------------
            if(INB[Rx1Cnt-2]=='T' && INB[Rx1Cnt-1]=='O' && INB[Rx1Cnt]=='N' ){
                  _T1IE = 1;            // enable timer 1
                  PutStrU1("\r\nTimer 1 ON\r\n");
           }
            if(INB[Rx1Cnt-3]=='T' && INB[Rx1Cnt-2]=='O' && INB[Rx1Cnt-1]=='F' &&    INB[Rx1Cnt]=='F' )   {
                  LEDA = 0;
                 _T1IE = 0;          // disable timer 1
                 PutStrU1("\r\nTimer 1 OFF\r\n");
            }
            Rx1Cnt++;INB[Rx1Cnt]=0;
            if (Rx1Cnt>=maxINB-1) { Rx1Cnt=maxINB-1; } // should stay only in allocated buffer
      }
      IFS0bits.U1RXIF = 0; // clear interrupt flag
}

เราเลือกที่จะอธิบายเพิ่มเติมบางส่วนดังนี้

  • หลังจากอ่านข้อมูลอักขระหนึ่งจาก URXDA จะมีการ AND กับ 0xFF เพื่อจำกัดขยะที่อาจมีอยู่ใน 8 บิตบนของข้อมูล (สังเกตว่าการใช้ตัวแปรชนิด char ถ้าเป็นภาษา C มาตรฐานจะนิยามเป็นข้อมูลขนาด 8 บิต แต่ในคอมไพเลอร์ XC16 ของไมโครชิพจะไม่นิยาม) หลังจากนั้นจะผ่านเข้าฟังก์ชัน toupper( ) เพื่อเปลี่ยนให้เป็นตัวอักษรใหญ่ ทำให้การเปรียบเทียบในขั้นตอนต่อมาง่ายขึ้น (เพราะเราไม่รู้ว่าผู้ใช้จะพิมพ์โดยใช้ตัวเล็ก toff ตัวใหญ่ TOFF หรือผสมกัน Toff)
  • ข้อมูลที่รับมาจะถูกเก็บในบัฟเฟอร์ INB ที่เป็นตัวแปร global นิยามไว้ส่วนต้นของโปรแกรม ในขณะที่เก็บโปรแกรมก็จะตรวจสอบไปด้วยว่ามีตัวอักษรที่เรียงกันเป็นคำว่า "TOFF" หรือ "TON" หรือไม่ ถ้าหากว่าตรวจพบเมื่อใด จะสั่งงานปิด/เปิด ไทเมอร์ โดยกำหนดค่า _T1IE = 0 หรือ _T1IE = 1 ตามลำดับ โดย _T1IE คือบิตสำหรับ Interrupt Enable ของไทเมอร์ 1 เมื่อสั่งงานไทเมอร์แล้ว ISR จะส่งข้อความกลับไปยัง PC เพื่อบอกสถานะของไทเมอร์ที่ถูกเปลี่ยนแปลง โดยใช้ฟังก์ชัน PutStrU1( )
  • เมื่อไรก็ตามที่ผู้ใช้กดคีย์ [Enter] PIC24EP จะส่งข้อความ "Ready" กลับไป พร้อมขึ้นบรรทัดใหม่ให้
  • การเขียนโปรแกรมภาษา C จะต้องระวังเรื่องเขียนเกินออกจากหน่วยความจำที่กำหนดไว้ เพราะคอมไพเลอร์จะไม่ตรวจสอบข้อผิดพลาดนี้ให้
  • การเขียน ISR สำหรับโมดูลใดๆ จะต้องไม่ลืมเคลียร์ค่า interrupt flag ก่อนออกจากฟังก์ชัน มิฉะนั้นการอินเตอร์รัพท์ครั้งต่อไปจะไม่สามารถเกิดขึ้นได้

โปรแกรมภาษา C ทั้งไฟล์สามารถดาวน์โหลดได้ที่นี่

uart_ main.c

จากวีดีโอด้านล่างนี้ จะเห็นได้ว่าการสื่อสารผ่าน UART ทำงานได้อย่างถูกต้องที่อัตราบอด 115,200 โดยสามารถปิด/เปิดไทเมอร์ (ทำให้ LED ดับหรือกระพริบ) เมื่อพิมพ์คำสั่ง TOFF และ TON และประโยคที่ส่งกลับมาแสดงผลอย่างถูกต้อง สรุปได้ว่า PIC24EP ทำงานที่สมรรถนะ 70 MIPS ตามที่ตั้งค่าไว้ โดยทั่วไปแล้วการทดลองนี้จะได้ผลดีกับกรณีที่ใช้คริสตัลภายนอก เพราะมีความแม่นยำมากกว่าตัวกำเนิดความถี่ FRC ภายใน หากผู้อ่านต้องการใช้ FRC อาจต้องปรับแต่งเพิ่มเติมให้ได้ผลดีที่สุด อย่างไรก็ตามแนะนำให้ใช้คริสตัลดีกว่าหากมีการใช้โมดูล UART

หมายเหตุ: วีดีโอนี้ถ่ายโดยใช้กล้องที่ติดอยู่กับจอภาพ ทั้งนี้เพราะต้องการแสดงการกระพริบของ LED กับคำสั่งและข้อความตอบสนองในโปรแกรมเทอร์มินัลพร้อมกัน เนื่องจากกล้องมีความละเอียดต่ำ คุณภาพจึงไม่ค่อยดีนัก เสียงที่ดังขณะเริ่มต้นอัดเกิดจากผู้เขียนทำของหล่น ต้องขออภัยในความไม่เป็นมืออาชีพ :)

No comments:

Post a Comment