วันอาทิตย์ที่ 22 มกราคม พ.ศ. 2566

STC8G1K08A ADCx -PWM

Analog Read()   

     จะทำการอ่านค่า analog  ของ 8051 ซึ่งน่าจะเทียบได้กับ AnalogRead() ของ Arduino ขั้นตอนก็มากหน่อย ทำตามคู่มือ พอลองจนได้ที่แล้ว ก็เอามาเขียนไว้กันลืม

     MCU ที่ใช้คือรุ่น STC8G1K08A(8PIN) ใน STC8G Series ตามคู่มือจะมี ADC0 - ADC5 ขนาด 10bit (2^10 = 1024 ค่าสูงสุดเท่ากับ 1023) แต่ไม่มี Enhanced PWM (แต่ก็ทำได้โดย PCA)

    ขาไหนเป็นขาไหน? ทุกขาเป็น ADC ทั้งหมด มีการใช้งานอยู่ port3,5 ดังนี้
ADC0    P3.0
ADC1    P3.1
ADC2    P3.2
ADC3    P3.3
ADC4    P5.4
ADC5    P5.5
จะเอาขาไหนไปทำ analog input ต้องกำหนดให้ขานั้นเป็น high-impedance input mode (โดยกำหนดในรีจิสเตอร์ PxP1)

    Register ที่เกี่ยวข้อง (ก็ไม่เหมือนกับ STC15W408AS ซะทั้งหมด ที่ใช้รีจิสเตอร์ P1ASF ในการกำหนดช่องสัญญาณ จึงต้องอ้างอิงจากคู่มือตลอด)
ADC_CNTR    ,control
ADCCFG        ,config
ADC_RES      ,result
ADC_RESL    ,result low
ADCTIM        ,time
P_SW2        ,access bit
P3M0           ,pin mode
P3M1
P5M0
P5M1

การแปลงค่าจะทำทีละช่องสัญญาณ เริ่มจาก1.)กำหนดช่อง จากนั้น2.)จึงเริ่มการแปลง 3.)พอแปลงเสร็จ เอาค่าไปใช้ 4.)แล้วก็เปลี่ยนช่องสัญญาณถัดไป แบบนี้วนๆไป และจะต้องใช้ adc interrupt ด้วย

กำหนดเป็นฟังก์ชั่นก็สะดวกดี
void adcInit(){
    P3M1                |= 0x08;    //P33 is high impedance,ADC3
    P5M1                |= 0x10;    //P54 is high impedance,ADC4
    P_SW2              |= 0x80;    //access bit ADCTIM
    ADCTIM            =    0x3f;   //recommenced by manual
    P_SW2              &= 0x7f;    //stop access
    ADCCFG            =    0x2b;  //Result align right, speed 1011;sys/2/12
    ADC_CONTR      = 0x83;     //Enable module on ADC3
    EADC                =    1;       //ADC interrupt enable
    EA                    =    1;       //interrupt enable
    ADC_CONTR     |= 0x40;    //Start ADC
    delay(2);                           //For stability conversion result
}

     เมื่อมันแปลงค่าเสร็จแล้ว flag ของการแปลงค่าอยู่ที่ bit5 ของ register ADC_CONTR และจะมี logic 1 ค่าของการแปลงก็อยู่ใน ADC_RES, ADC_RESL
    ใน ISR เราต้องทำการ reset flag อีกเรื่องเพื่อความสะดวก ก็เอาค่าใน ADC_RES,ADC_RESL มาเก็บไว้ในตัวแปรแยกไว้ต่างหาก เรียกใช้ง่ายไม่งง

void adcisr() interrupt 5
{
    ADC_CONTR    &= ~ 0x20;          //reset flag , bit5 ของ ADC_CONTR ต้องมี logic 0
    if(ADC_CONTR -0x83 == 0){        //ถ้าเป็น ADC3
        adc3_dat    = (ADC_RESL) | (ADC_RES<<8);
        ADC_CONTR    =    0x84;        //เปลี่ยนเป็น ADC4
    }else{                                        //ถ้าไม่ใช่ ADC3
        adc4_dat    = (ADC_RESL) | (ADC_RES<<8);
        ADC_CONTR    =    0x83;        //เปลี่ยนเป็น ADC3
    }
        ADC_CONTR    |= 0x40;          //start ADC again
}//end isr

ถ้าใช้หลาย ADC ก็หาวิธีเปลี่ยนช่องสัญญาณได้ตามสะดวก อย่างเช่น switch case

    ในลูปปกติ ก็เอาค่าในตัวแปร adc3_dat, adc4_dat ไปใช้งานได้ตามต้องการ เป็นข้อมูล 10bit ที่ถูกเก็บไว้ในตัวแปรขนาด 16bit

 

    ตามคลิปนี้ เอา output จากเซนเซอร์เข้า P3.3 ส่วน P5.4 ปล่อยลอยๆ ไว้ วัดค่าได้แล้วก็ส่งไปให้ HMI แสดงผลออกมาเป็นตัวเลข

delay function ที่ทำไว้ใช้ กำหนดไว้ประมาณนี้ ก็ไม่ได้แม่นยำเป๊ะ พอใช้งานได้ ใช้กับ FOSC 18.432MHz
void delay(u16 t){
    int i, j;
    for (i = 0; i <t ; i++)
    for (j = 0; j <2616; j++); //base on 18.432MHz
}//end delay

ลองใช้คำสั่ง delay(5) ได้มาประมาณนี้

Fig. 1 Delay 5ms

 Analog Write()
ทำ PWM ด้วย PCA hi speed output mode

Fig.2 CCP0, CCP1, CCP2 pin

    
จะใช้ CCAPM0 ในการทำตัวจับเวลา มีรีจิสเตอร์ที่เกี่ยวข้องตามนี้
CCAPM0, CCON, CMOD, CL, CH, CCAP0L, CCAP0H, CCF0 และ CR
    เมื่อเริ่มต้นจับเวลา ต้องทำการกำหนดค่าเปรียบเทียบให้กับ CCAP0L, CCAP0H เมื่อ mcu ทำงานแต่ละรอบจะเก็บค่าไว้ใน CH, CL จนมันเท่ากันกับตัวเปรียบเทียบ เมื่อนั้นจะเกิดการ interrupt ขึ้น จากนั้นเราก็กำหนดค่าให้กับตัวเปรียบเทียบอีก เป็นแบบนี้วนๆ ไป เราต้องรู้ว่าแต่ละรอบของ machine cycle นั้นกินเวลาเท่าใด มันคือ 12T หมายถึง 1 machine cycle และก็ใช้สัญญาณนาฬิกา 12 ลูก

    เนื่องจากเป็นระบบ 12T มันก็ใช้ความถี่ FOSC / 12 ตัวอย่างเช่น ระบบกำหนดสัญญาณที่ 18.432MHz
    18,432,000 Hz / 12   = 1,536,000 Hz
คาบเวลาดังกล่าว มันเป็นส่วนกลับของความถี่ นั่นคือ
    1 / 1,536,000 = 0.65104us 

0.65104us คือ เวลาที่ใช้ทำงาน 1 cycle (12 clock pulse)

แล้ว 1ms มันต้องทำงานกี่รอบกัน?

1 รอบการทำงานใช้เวลา 0.65104us
ทำ x รอบ จะได้เวลา 1ms (1ms มันเท่ากับ 1000us)
    x/1    =    1000us / 0.65104us
    x = 1536 รอบ
สรุปว่าต้องให้ mcu ทำงาน 1536 รอบจะใช้เวเลา 1ms

ค่า 1536 (600H) นี้จะต้องถูกกำหนดให้กับรีจิสเตอร์ CCAP0L, CCAP0H ในกรณีที่ต้องการจับเวลา 1ms
และต้องเข้าใจว่า ว่าเป็น 16bit timer นั่นหมายถึงจำนวนรอบการทำงานไม่เกิน 65535
ส่วนการกำหนดค่าทำดังนี้ 
    CCAP0L    =    0x600;
    CCAP0H    =    0x600>>8;

    Arduino จะมีความถี่ของ PWM ราวๆ 490Hz นั่นคือมันจะมีคาบเวลาของ 1 pulse อยู่ที่ 1/490 = 2.04 ms ( 1 ลูกคลื่นใช้เวลา 2.04 มิลลิวินาที)

ถ้าเราทำ PWM โดยที่ 1 ลูกคลื่นมีคาบเวลา 1ms จะได้ว่า PWM นั้นมีความถี่ 1/0.001s = 1000Hz

การสั่งให้ขา P32 เป็น analog output (โดยการทำ PWM) ทำประมาณนี้

void main(){
    CCON    =    0x00;
    CMOD    =    0x00;
    CL         =    0x00;
    CH        =     0x00;
    CCAPM0 =    0x4d;    //ใช้โมดูล 0 และเป็น hi speed output
    t            =    768;    // 1536 / 2
    CCAP0L  = t;

    CCAP0H  = t>>8;
    t    += 768;
    CR        =    1;    //start capture
    EA        =    1;    //enable global interrupt
    while(); 

}

    ถ้าไม่ทำไรเพิ่ม เราก็จะได้ PWM 50%duty เราก็ควรจัดการปรับเวลาของการเกิด logic 1 , logic 0 เพื่อให้ได้ %duty ตามต้องการ จะต้องทำใน interrupt service routine (ISR)

เราก็ดูว่าเมื่อไหร่ที่มันมี logic 0 เราก็ให้เป็น logic 0  นานเท่าที่เราต้องการ
และที่สำคัญคือ ผลรวมของเวลาที่เป็น logic 0 และ logic 1 เมื่อรวมกันต้องได้ 1ms (ตามตัวอย่างได้ 1536) เพราะจะได้ความที่ 1000Hz

void pca_isr() interrupt 7 {
    CCF0    =    0; //clear flag
    CR       =    0; //stop
    if(P32 == 0)
    {
        t    += 1536*0.05;   // (1536*0.05 = 77) มันจะเป็น logic 0 เท่ากับ 5/100 ms
        CCAP0L    =    t;
        CCAP0H    =    t>>8;
    } else
    {
        t    += 1536*0.95;   // (1536*0.95 = 1459) มันจะเป็น logic 1 เท่ากับ 95/100 ms
        CCAP0L    =    t;
        CCAP0H    =    t>>8;
    }
    CR    = 1; //start
}

Fig.3 PCA making PWM on P32

    เราตั้งเวลาตอนมันเป็น logic 0 ให้มีค่า 5% ของ 1ms (จำนวน 77 รอบ) จากนั้นพอ mcu ทำงานครบ 77 รอบ จะเกิด interrupt อีกครั้ง เนื่องจากมันอยู่ในโหมด hi speed output (CCAPM0 = 0x4d) รอบนี้มันจะมี logic 1 ซึ่งเราได้กำหนดให้มันต้องทำงานอีก 1459 รอบ เมื่อครบแล้วก็เกิด interrupt รอบใหม่อีก พร้อมกับมี logic 0 วนๆ ไป

    โปรดสังเกตุว่ามันเกิด interrupt 2 ครั้ง ในช่วงเวลา 1 ms ถ้าเราเอาตัวแปรนับจำนวนมาใส่ใน ISR เราก็ได้ timer 1ms ไว้ใช้งาน หรือว่าจะใช้ module1, module2 แยกต่างหากก็ทำได้ โดยให้รีจิสเตอร์ CCAPM1, CCAP1L,CCAP1H สำหรับ module1 หรือจะเป็น CCAPM2, CCAP2L,CCAP2H สำหรับ module2

    ลองปรับค่า%duty ให้มันเพิ่ม-ลด ก็ยากต่อการควบคุมเหมือนกัน บางจังหวะความถี่มันผิดปกติมีการกระพริบจนสังเกตุเห็นได้


ก็ตามนั้น

ไม่มีความคิดเห็น:

แสดงความคิดเห็น

MAPs

วันก่อนได้ลองเขียนแบบ แบบตัวนั้นมันต้องแนบแผนที่สังเขปของโครงการ ลองๆ แล้วก็ใช้ snazzymaps แล้วก็เอามาทำต่อใน inkscape มันก็ออกมาดูได้ระดับห...