Sunday, July 27, 2014

เมื่อมอเตอร์จำลองอิเล็กทรอนิกส์เริ่มหัดเดิน

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

การทดสอบ eMotor ทำได้ง่ายๆ คือต่อเข้ากับระบบฝังตัวอีกระบบหนึ่ง ในที่นี้เราใช้บอร์ด JX-dsPIC40 จากบริษัทInnovative Experiment Co.,Ltd . ซึ่งมีอุปกรณ์สนับสนุนให้บนบอร์ดแล้วส่วนหนึ่ง แต่ก็ได้บัดกรีเพิ่มไปอีก รูปที่ 1 แสดงการต่อบอร์ดเข้าด้วยกัน

รูปที่ 1 การต่อบอร์ดeMotor เข้ากับ JX-dsPIC40

จากแผนภาพในรูปที่ 2 จะเห็นว่ามีสัญญาณเพียงไม่กี่เส้นที่ต่อเชื่อมระหว่างระบบทั้งสอง แต่สาเหตุที่ใช้สายแพร์ IDE ในต้นแบบรูปที่ 1 ก็เพราะความสะดวกเท่านั้นเอง

รูปที่ 2 แผนภาพการต่อระบบ

สำหรับการอ่านสัญญาณเอนโคเดอร์และแปลงเป็นค่าตำแหน่งและความเร็วจะใช้วิธีการที่อธิบายในบทความ Measuring Speed and Position with the QEI Module จากบริษัทไมโครชิพ ซึ่งการตั้งค่าและคำนวนจะไม่แตกต่างจากที่อธิบายในบทความมากนัก ความเร็วที่คำนวนได้จะเก็บในตัวแปร Veli และ Velf โดยตัวแปรแรกเก็บค่าที่มีหน่วยเป็นจำนวนนับพัลซ์ของเอนโคเดอร์ (x 4) และตัวแปรที่สองเก็บค่าที่แปลงหน่วยเป็นจำนวนรอบ/วินาที ค่าความเร็วยังถูกปรับเครื่องหมายโดยตรวจสอบบิตUPDN ของรีจิสเตอร์ QEICON โค้ดที่แสดงด้านล่าวนี้แสดงฟังก์ชันการตั้งค่าเริ่มต้น การอ่านตำแหน่ง และคำนวนความเร็ว

// ----------- quadrature encoder functions ------------------------
void InitQEI(void)
{
    ADPCFG |= 0x0038; // Configure QEI pins as digital inputs
    QEICONbits.QEIM = 0; // Disable QEI Module
    QEICONbits.CNTERR = 0; // Clear any count errors
    QEICONbits.QEISIDL = 0; // Continue operation during sleep
    QEICONbits.SWPAB = 0; // QEA and QEB not swapped
    QEICONbits.PCDOUT = 0; // Normal I/O pin operation
    QEICONbits.POSRES = 1; // Index pulse resets position counter
    DFLTCONbits.CEID = 1; // Count error interrupts disabled
    DFLTCONbits.QEOUT = 1; // Digital filters output enabled for QEn pins
    DFLTCONbits.QECK = 5; // 1:64 clock divide for digital filter for QEn
    //DFLTCONbits.INDOUT = 1; // Digital filter output enabled for Index pin
    //DFLTCONbits.INDCK = 5; // 1:64 clock divide for digital filter for Index
    POSCNT = 0; // Reset position counter
    QEICONbits.QEIM = 6; // X4 mode with position counter reset by Index
    return;
}

// ------------------ calculate angular speed ------------------------

void __attribute__((__interrupt__)) _T2Interrupt (void)
{

    IFS0bits.T2IF = 0; // Clear timer 2 interrupt flag
    POSCNTcopy = (int)POSCNT;
    if (POSCNTcopy < 0)
    POSCNTcopy = -POSCNTcopy;
    AngPos[1] = AngPos[0];
    AngPos[0] = POSCNTcopy;
    Veli = AngPos[0] - AngPos[1];

    if (Veli >= 0)
    {
        if (Veli >= (HALFMAXSPEED))
            Veli = Veli - MAXSPEED;
    }
    else
    {
        if (Veli < -(HALFMAXSPEED))
           Veli = Veli + MAXSPEED;
    }

    Veli *= 2;
 

    Velf = (double)Veli;
    Velf = MAXRPS*Velf/MAXSPEEDRAW;

    if (!QEICONbits.UPDN)  {  // adjust direction
        Veli = -Veli;
        Velf = -Velf;
    }

    
}

สำหรับอัลกอริธึม PID ที่เป็นส่วนสำคัญของระบบป้อนกลับนี้ ถูกอิมพลิเมนต์โดยวิธีการที่อธิบายในบทความ Digital PID Controllers ของผู้เขียน โดยอัลกอริธึมถูกใส่ไว้ใน ISR ของไทเมอร์ 1 ตามที่แสดงด้านล่างนี้

void __attribute__((interrupt, auto_psv)) _T1Interrupt(void)
// The PID algorithm runs every 10 msec
{

    _T1IF = 0;
    if (SysFlag.VelLoop)  {  // control loop closed
        VLOOPLED = ON;  // light loop status LED
        e2_v = e1_v;   // update error variables
        e1_v = e_v;
        if (SysFlag.Velin == VR)   {
            CMDVRval = readADC(0);  // read from VR 
            CMDVRval_avg = (CMDVRval+CMDVRval1+CMDVRval2+CMDVRval3) >> 2; //averaging
            adft_v = (double)CMDVRval_avg - (double)ADMID;
            RefVelf_vr = (double)adft_v*MAXRPS/ADMID;
            e_v = RefVelf_vr - Velf;  // compute new error

            CMDVRval3 = CMDVRval2; // update old values
            CMDVRval2 = CMDVRval1;
            CMDVRval1 = CMDVRval;
        } else  {
            e_v = RefVelf - Velf;  // compute new error
        }  // end if(SysFlag.Velin == VR)
        delta_u_v = K1_v*e_v + K2_v*e1_v + Kd_v*e2_v;   // PID algorithm
        u_v += delta_u_v;  // update control variable
        if (u_v>PWMMID) u_v = (double)PWMMID; 
        if (u_v<-PWMMID) u_v = (double)-PWMMID;
        ui_v = (unsigned int)(u_v + PWMMID);
        if (ui_v > PWMMID)  {

             DIR = CW;
             PWMval = 2*(ui_v - PWMMID);
         }
         else  {

            DIR = CCW;
            PWMval = 2*(PWMMID - ui_v);
        }  // if(ui > PWMMID)
        OC1RS = PWMval;
        if (SysFlag.ELed) ShowLED(); // show performance LEDs.


    }  // if(SysFlag.VelLoop)
}

ผลการทดสอบ

ในการทดสอบเบื้องต้นนี้ยังไม่ได้มีการปรับค่าอัตราขยาย PID ให้ได้ผลตอบสนองที่ดีที่สุดแต่อย่างใด เพียงแต่เปรียบเทียบผลตอบสนองระหว่างระบบลูปเปิดกับระบบป้อนกลับ โดยบนบอร์ด eMotor มีสวิทซ์กดที่แทนการรบกวนแรงบิด (เปรียบง่ายๆ ได้กับการใช้มือจับที่เพลามอเตอร์จริงเพื่อให้หยุด) การสื่อสารระหว่างคอมพิวเตอร์พีซีกับระบบฝังตัวกระทำผ่านพอร์ตอนุกรม โดยใช้สายแปลง UART เป็น USB USB-to-serial cableจากบริษัท FTDI และใช้โปรแกรมเทอร์มินอลง่ายๆ เช่น hyperterminal ในการส่งคำสั่งและเก็บข้อมูล

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

eMotor open-loop response
รูปที่ 3 ผลตอบสนองลูปเปิดของ eMotor
eMotor close-loop response
รูปที่ 4: ผลตอบสนองลูปปิดของ eMotor

No comments:

Post a Comment