วันอังคารที่ 5 ธันวาคม พ.ศ. 2566

MAPs

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



วันจันทร์ที่ 23 ตุลาคม พ.ศ. 2566

Bitwise operation

     ที่หายไปนานเพราะความสนใจไปอยู่กับสิ่งอื่น นานๆ ก็กลับสักที มารอบนี้ก็เพราะได้เจองานที่เกี่ยวข้องกับเรื่อง MCU

    เป็นบอร์ดควบคุมเกียร์ของรถตัก XCMG ZL50G มันใช้ ATMEGA328PU แบบ 28ขา คอยสั่งโซลินอยด์ของชุดเกียร์

แต่เรื่องที่จะมาเขียนกันลืมป็นเรื่อง bitwise operation บางคนก็ว่า bit mask ของ MEGA328P

บรรดา IO ของ MCU 328 มี register ที่เกี่ยวข้องประมาณนี้

DDRx  มันเป็นการกำหนดว่า IO นั้นจะเป็น input หรือ output โดยที่ 0 คือ input และ 1 คือ output
PORTx มันเป็นตัวสั่ง output ของ IO นั้น โดยค่าของมัน เป็น 0 หรือ 1 (0 ไม่มี output และ 1 มี output 3.3,5v)
PINx เป็นตัวบอกสถานะของ IO ที่เป็น  input ว่ามี input หรือยัง
* x คือ กลุ่มของ IO โดยมันมี B, C, D  เช่น DDRB, PORTB, PINB ดูได้จาก Pinout

ตัวอย่าง
DDRB=0b11111111;
กำหนดให้ PORTB ทุกขาเป็น output

DDRB=0b10000001;
กำหนดให้ PORTB ขา ที่ 0, 7 เป็น output ที่เหลือเป็น Input

PORTB=0b00000001;
สั่ง output high ออกที่ขา 0

ส่วน PINB มันเป็นรีจิสเตอร์ที่อ่านได้อย่างเดียว
ตัวอย่าง
byte i = PINB;
Serial.println(i, BIN);
แล้วถ้าแสดงค่าออกมา 101 จะหมายถึง มี input high ที่ขา 2 และขา 0

แล้ว bitwise มันใช้ตอนไหน เขียนมาตั้งนาน?
มีบางทีที่เราต้องการให้บางขาทำงานโดยไม่ให้ขาอื่นได้รับผลกระทบด้วย
อย่างเช่น
-ต้องการให้ขา 0-3 เป็น Output โดยขาอื่นเราจะไม่แตะต้อง
-ต้องการให้ขา 2 กระพริบไปมา โดยขาอื่นทำหน้าที่ input/output ในงานบางอย่างตามปกติ
แบบหลังจะเขียน PORTx = 0xxxxxxx ได้ยาก

การสั่งให้บางบิททำงานต้องอาศัย bitwise operation
bitwise เอาที่ใช้บ่อยก็มี NOT, AND, OR, XOR (สัญลักษณ์ ~, &, |, ^)
พื้นฐาน
NOT:
NOT 0101
        = 1010
ตรงข้ามกันเลย

AND:
          1010
AND   1101
=         1000
*untouch bit คือ 1 จะเห็นว่าหลังจากทำ AND แล้วค่าที่ได้ไม่เปลี่ยน (ส่วนอันอื่นถูกบังคับให้เป็น 0)

OR:
         1101
OR    1011
=       1111
*untouch bit คือ 0 จะเห็นว่าหลังจากทำ OR แล้วค่าที่ได้ไม่เปลี่ยน (ส่วนอันอื่นถูกบังคับให้เป็น 1)

XOR:
         1010
XOR 1100
=        0110
*flip bit คือ 1 จะเห็นว่าหลังจาก XOR แล้วค่าจะตรงข้ามกัน ใช้ในการ Toggle bit

ทีนี้มาดูที่ใช้งานกัน

DDRC|=0b00011111;  เป็นการกำหนดให้ ขา 4 ถึง 0 เป็นขา output
|= หมายถึง DDRC = ( DDRC | 0b00011111 )
0 เป็น untouch bit  คือที่ 0 ค่าที่ได้ไม่เปลี่ยน ส่วนที่เป็น 1 สุดท้ายก็จะได้1 (บังคับเป็น 1)

DDRB&=0b11110000; เป็นการกำหนดให้ขา 4 ถึง 0 เป็นขา input ส่วนขาอื่นไม่สน
&= หมายถึง DDRB = ( DDRB & 0b11110000 )
1 เป็น untouch bit คือที่ 1 ค่าที่ได้ไม่เปลี่ยน ส่วนที่เป็น 0 สุดท้ายก็จะได้ 0 (บังคับเป็น 0 )

PORTC=0b00001111; 
PORTC^=0b00001000;
บันทัดแรกเป็นคำสั่งให้ output low ที่ขา 7 ถึง 4 และมี output high ที่ขา 3 ถึง 0
ต่อมา จะได้ว่าขา 7-4 และขา 3 จะมี output low ส่วนขาอื่นเหมือนเดิม ( ขา 2, 1 และ 0 จะยังมี output high เหมือนเดิม) ท้ายที่สุด PORTC จะมีค่า 0b00000111

อีกเรื่องที่น่าจะต้องหัดไว้คือการเลื่อนบิท (shift bit) ใช้เครื่องหมาย <<, >>
มาดูตอนที่ใช้งานกัน
1<<5 มีค่าเท่ากับ 0b100000 (เลื่อนเลข 1 มา 5 ครั้งทางซ้าย และบิทที่ 5 มีค่าเท่ากับ 1)
0b101<<5 มีค่าเท่ากับ 0b10100000 (เลื่อนเลข 101 มา 5 ครั้งทางซ้าย)
0b10100>>2 มีค่าเท่ากับ 0b101
แล้ว 11>>2 มีค่าเท่าไหร่ ตอบ 0 เลื่อนจนหลุดไม่เหลือไรเลย

การ toggle bit บางขา
PORTC|=0b00100000; กำหนดบิทที่ 5 output high
PORTC^=(1<<5);    toggle bit 5 (ก่อนหน้านี้มัน high ก็จะเปลี่ยนเป็น low)

บางครั้งเราต้องการดูสถานะ input ของ IO แล้วมันก็มาจาก IO ต่างกลุ่มกัน (PINB, PINC หรือ PIND)
ตัวอย่างเช่น เราต้องการดู PINB เฉพาะ 3-0 และ PIND ขา 7-5 ว่าเป็นอย่างไรบ้าง และจะดูคราวเดียวกัน
ก็ประมาณนี้
byte i = (PINB<<3) | (PIND>>5) &0b01111111; 
โดยสมมติว่า
(PINB<<3)= xxxxx000  (xxxxx 5บิทนี้ เรายังไม่รู้ว่าสถานะมันเป็นอะไร แต่ที่แน่ๆ มันจะถูกเลื่อนมาทางซ้าย)
(PIND>>5)=yyy  (จะเหลือข้อมูล yyy ที่ยังไม่รู้ว่าเป็นอะไรของขา 7,6 และ 5)
(PINB<<3) | (PIND>>5) จะได้ข้อมูล xxxxxyyy แต่เราไม่เอาบิทที่ 7 ซึ่งก็ต้องเอาออก
เอาออกโดย &0b01111111; (บังคับเป็นศูนย์ในบิทที่ 7)


จริงๆ มันก็แค่เรื่อง pinMode, digitalRead, digitalWrite แค่นั้นนะ จะทำให้มันวุ่นวายทำไม
เวลาก็ไม่ช้าเร็วจนเห็นผลต่างที่ชัดเจนตอนใช้งาน อย่างน้อยก็ถือว่าเป็นการทบทวน เป็นการเรียนรู้วิธีอื่นดูบ้าง

ประมาณนี้ก่อน



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

STC8F1K08S2

    

Fig.1 STC8F1K08S2 MCU


    เหตุจากการที่ซื้อ MCU ของ STC มาหลายอัน พอมีเวลาว่าง เอามาลองเล่นเรื่อยๆ วันนี้ถึงคิว STC8F1K08S2 TSSOP20 มี 20 pin ราคา 0.38USD (ประมาณ 13 บาท)

Fig.2 STC8F1K08's features that lack of PCA

    มี 3 timer, 2 uart, SPI,I2C แต่ไม่มี ADC, PWM,PCA,CCP ฉะนั้นก็ต้องใช้ในงานที่ไม่ต้องการ analog input ส่วน PWM หรือ analog output ก็เขียนโปรแกรมให้มันทำงานได้ โดยใช้ timer ช่วยกำหนดเวลา จะว่าไป STC8H, STC8G ดูดีกว่า เพราะมี ADC

    8F1K08 series นี้ มี Flash 8kB, SRAM 1.2kB นอกนี้ยังมี EEPROM ขนาด 3kB ที่เขียนได้เป็นแสนๆ ครั้ง

    เมื่อไม่มี PCA ก็ไม่มี software timer ให้ใช้ แต่ก็มี timer0,1,2 ให้ใช้ ต้องเอามาทำตัวจับเวลาสักอัน และได้เลือก timer0 มาทำหน้าที่นี้ ส่วน timer1,2 เอาไว้ใช้กับ uart1,2
    นึกถึง millis() ของ arduino ที่ไว้ใช้ทำ non-blocking process แต่ท้ายที่สุด millis() ก็จะเกิด overflow ทำให้เกิดการทำงานที่ผิดพลาดได้

    เมื่อใช้ timer 16bit auto reload จะไม่เกิดปัญหาแบบเดียวกันกับ millis() 

1. Timer0
การใช้งาน Timer0 มีรีจิสเตอร์ที่เกี่ยวข้องตามนี้
    TMOD,TCON, TL0, TH0, ET0, EA, AUXR

void Timer0_Isr() interrupt 1
{
    //TF0 = 0;        //hardware set and reset automatically.
    P10   = !P10;    //test output

}//end timer0 isr

void main()
{
    TMOD = 0x00;    //timer0 mode0 16bit auto reload
    TL0     = 65536 - 10*18432000/(12*1000);    //10ms at 18.432MHz
    TH0    = (65536 - 10*18432000/(12*1000))>>8;
    TR0    = 1;        //start timer0
    ET0    = 1;        //enable timer0 interrupt
    EA     =  1;        //enable global interrupt
    while(1);
}//end main

Fig 3. captured P10 with 10ms width

    2. เอา timer0 มาช่วยทำ non-blocking
ก็นับจำนวนครั้งของ timer overflow ใน timer0_interrupt

void Timer0_Isr() interrupt 1
{
    //TF0 = 0;        //hardware set and reset automatically.
    count++;        //count every timer0 overflown
    if(count%10 == 0)
        t100ms = 1;
   
if(count%100 == 0)
        t1sec    = 1;
    if(count == 200)
        count = 0;    //reset counter
}//end timer0 isr

    ใน while loop ปกติ ก็ดูว่ามันมีค่าเท่ากับ 1 หรือยัง
while(1)
{
    if(t100ms == 1)    //do this every 100ms
    {
        P10 = !P10;    //test timing
        t100ms = 0;    //clear timer , declared as bit
variable
    }
   
if(t1sec == 1)    //do this every 1sec
    {
        P11 = !P11;    //test timing
        t1sec = 0;    //clear timer , declared as bit variable
    }

}//end while

Fig 4. Captured P10 and P11 while using timer0 for non-blocking

Fig.5 STC8F1K08S2 working with HMI.

    พอมีตัวเวลา timer0 ก็เอามาช่วยในการทำ PWM หรือว่าเป็น analog output (นึกถึง analog.write() ของ Arduino) ก็เขียนโค้ดให้มันทำได้

    ความถี่ของ PWM ที่จะทำ ก็เอาสัก 1000Hz ส่วนของ Arduino ไลบรารี่เขาทำมาให้ประมาณ 490Hz

แต่ที่ 1000Hz นั้นจะมีคาบเวลา 1ms และ %duty PWM ก็ทำการปรับให้ยอดคลื่น x ให้กว้าง-แคบ ตามต้องการ

Fig.6 PWM 's wave form.
    ส่วนการปรับ x ก็ใช้ Timer 0 ช่วยในงานนี้ วิธีคือกำหนดใน interrupt service routine (ISR) ให้ทำการปรับค่าของ TL0, TH0

    เรารู้ว่า ที่ความถี่ 18.432MHz ใน 12T มันต้องทำงาน 1536 รอบจึงจะใช้เวลา 1ms เมื่อเป็นอย่างนี้แล้วก็ต้องกำหนดให้ TL0, TH0 มีค่า 65536 - 1536 = 64000
    64000 เป็นค่าตั้งต้นของ TL,TH และเมื่อ Timer
ทำงานอีกแค่ 1536 รอบ ค่า TL,TH ก็จะล้น เมื่อล้นก็เกิด interrupt (ที่ล้นก็เพราะตัวแปรเป็นชนิด 16bit เก็บค่าได้ 65536 ค่า เมื่อเกินกว่านี้ มันไม่ใช่ 65537 แต่มันจะไปเริ่มที่ 0 ใหม่ วนๆ ไป)
    กลับมาที่ค่า x มันก็แค่ใส่ค่า ที่เป็นสัดส่วน 1/100 ของ 1536 เช่น ต้องการ 75% ก็ใส่ค่า 65536 - (75/100)x1536 = 64384   
ประมาณนี้
void timer0_isr() interrupt 7
{
    TR0 = 0;
    if(P35 == 0){
        TL0    =    65536-(75/100)*1536;    //64384;
        TH0    =    (
65536-(75/100)*1536)>>8;
    }else {
        TL0    =    65536 - (25/100)*1536;
        TH0    =    (
65536 - (25/100)*1536)>>8;
    }
    P35 = !P35;    //toggle pin P3.5
    TR0 = 1;        //start timer0 again
   
    count++;                    //general purpose timer,counter
    if(count == 2000)        //x2 = 1ms
    {
        
t1sec = 1;
        count = 0;
    }
}

Fig. 7 Timer0 making PWM

Fig.8 95% duty PWM and pulse from Timer0.

 ก็ไม่สะดวกเท่าไหร่ เลือกได้ ก็เลือกเอารุ่นที่มี PWM ก็จะดี ไม่ต้องวุ่นวายขนาดนี้

วันอาทิตย์ที่ 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 ให้มันเพิ่ม-ลด ก็ยากต่อการควบคุมเหมือนกัน บางจังหวะความถี่มันผิดปกติมีการกระพริบจนสังเกตุเห็นได้


ก็ตามนั้น

วันเสาร์ที่ 21 มกราคม พ.ศ. 2566

STC8G1K08A-8PIN

Fig.1 STC8G1k08A SOP8 body.

    หลังจากได้ลองเล่น 8051 มาสักระยะ ก็รู้สึกดีกับ 8051 ตอนลองใช้งานรับส่งข้อมูลผ่าน RS485 protocol นี่ไม่ขึ้น error เลย เทียบกับ Arduino มันก็มี error บ้าง ต้องปรับต้องจูนเหมือนกัน (จริงๆ มันก็อยู่ที่โค้ดอ่ะนะ เขียนไม่ดีมันก็ error สิ ก็ลองทั้งสองด้วยทักษะที่มี แล้วก็เทียบด้วยความรู้สึก)

    ติดความสะดวกในการใช้งานไลบรารี่ของ Arduino จะหันมาใช้ 8051 มันต้องใช้เวลาทำความเข้าใจ ซึ่งก็ยากแก่การเข้าใจพอดู พอทำไม่ได้ก็จะเกิดอาการเบื่อหน่าย หนีไม่พ้นต้องอ่านคู่มือที่เป็นภาษาอังกฤษ

    ตอนเล่น Arduino ไม่ค่อยเฉียดไปยุ่งกับ interrupt เท่าไหร่ ใช้มากหน่อยก็ตอนที่ใช้ save mode ,wdt แต่พอหันมาลองเล่น 8051 มันต่างกัน ต้องยุ่งกับ interrupt บ่อยขึ้น ก็กลายเป็นเรื่องปกติ

    วันนี้ได้ลองเอา mcu STC8G1K08A มาต่อเข้ากับ HMI แล้วสั่งงานจาก HMI ลองทำเป็น DO ชิพรุ่นนี้มี GPIO 6 ขา แต่ต้องเสียขาไปทำ Rx, Tx และ MAX485 Flow control เหลือใช้งานสุดท้ายแค่ 3 ขาเอง Series STC8G นี้มีเกือบทุก package ยกเว้น DIP40 รุ่นนี้น่าใช้รองจาก STC8H แต่ก็น่าใช้มากกว่า Series STC8F เพราะมี uart 2 (จะมีใน package 16 ขาขึ้นไป ) มี timer 3 และมี ADC

Fig.2 Pinout and wiring for HMI connected.

    การติดต่อกับ HMI จะใช้ RS485 protocol ซึ่งมันต้องจัดการกับ uart และถ้าใช้ interrupt แทน polling ก็ต้องจัดการกับ interrupt ด้วย

    จะใช้ uart ต้องเลือก mode แล้วก็ต้องเลือกว่าจะใช้ timer ไหนเป็นตัวกำหนด baudrate

    8051 uart เมื่อมีการรับข้อมูลทางขา Rx จะมี flag register RI คอยบอกว่าการรับเสร็จสิ้นหรือยัง ส่วนการส่งข้อมูลทางขา Tx จะมี register TI ทำหน้าที่บอกสถานะการส่ง

    เมื่อรับข้อมูล 1 byte เสร็จแล้ว flag RI จะถูกเซตหรือว่ามี logic 1 ซึ่งต้องถูกรีเซตด้วย software ก็ใช้คำสั่ง RI = 0 เพื่อรอรับข้อมูลถัดไป เช่นเดียวกันฝั่่งการส่งข้อมูล TI ก็คล้ายกันนี้

Fig.3 STC8G1K08A DIP8 body that STC not recommended to use (refer to manual STC8G-En.pdf version date update 2022/3/9 on page 15).


Fig.4 Test communicating.
 

 

    MCU 8G1K08A แบบ 8 ขานี้ ถึงแม้จะไม่มี Enhanced PWM แต๋ก็ยังมี PCA อยู่ 3 ชุด คือ CCP0,CCP1,CCP2  เอาไว้ใช้ทำ software timer รวมถึงยังเอาไว้ทำ hi speed output ได้ด้วย ถ้าใช้ PCA ในการทำ PWM โดยมันมี 3 ขา สำหรับ CCP0, CCP1 และ CCP2 ตามรูป

Fig.5 CCPn 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
    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;
    }
}

Fig.6 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 ไว้ใช้งาน

วันจันทร์ที่ 9 มกราคม พ.ศ. 2566

STC8H3K64S2

Fig.1 STC8H3k64S2 on bare board.

    ซื้อชิพ STC หลากหลายรุ่นมาลงบอร์ด ราคาชิพตั้งแต่ 0.2 - 1.5USD ( 8 - 60บาท) ของ STC ก็สะดวกดี มี Oscillator ภายในชิพ ใช้ไฟ 5v เอาชิพลงบอร์ดเสร็จก็ต่อไฟใช้ได้เลย

    ตอนเป่าลมร้อน ปรับอุณหภูมิราวๆ 220 - 240 องศาซี แค่นี้ตะกั่วก็เดือดพล่านแล้ว ใช้หัวแร้งก็ได้ ใช้หัวรูปใบมีด ตะกั่วใส่มากก็ไม่ดี ถ้าใส่เกินก็ดูดออกด้วย de-soldering braid tape

    เป็นเรื่องที่ทำแก้เบื่อได้ดี ทำแล้วก็จะงงนิดหน่อยตอนลอง ขาไหนเป็นขาไหน งงไปหมด ก็ดูที่ ISP program ก็ได้ เขาทำมาให้ด้วยนะเออ สะดวกดี

ตอนที่ลองใช้ Port 3 (pin 3.0 - pin3.7)
กำหนดหน้าที่ของ port3 ให้เป็น bi-direction (ดูรายละเอียดใน manual)
    P3M0 = 0x00;
    P3M1 = 0x00;


 


    mcu ของ STC แต่ละรุ่นต้องดูคู่มือให้ดีเพราะแต่ละรุ่นมีจำนวน timer ไม่เท่ากัน และใช้ timer หมายเลขอะไร

    STC8H series น่าใช้งานพอกันกับ STC8G series มีออฟชั่นพอตัว ในราคาที่ไม่สูง การที่มี PCA นั้น จากที่ใช้ประโยชน์อยู่อย่างแรกเลย ก็จะมี timer ไว้ให้เลือกใช้งานมากขึ้น

STC8H summary table

STC8G summary table
         

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

    ตามนั้นล่ะ

วันพฤหัสบดีที่ 5 มกราคม พ.ศ. 2566

STC15W408AS

    ไม่ถนัดที่จะใช้ 8051 เพราะถนัดใช้แต่กับ Arduino IDE  ไปหาหนังสือมาอ่าน ก็พอถูไถไปได้ ได้แต่ก็ไม่ดี ในอดีตก็เคยได้ลองกับพวก PIC16xx แต่ก็ไม่ได้อะไรเป็นชิ้นเป็นอัน ยิ่งนานวันยิ่งลืม

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

    แม้ว่ามันมีมานาน มันจะเป็นของโบราณ แต่มันก็ปรับปรุงอยู่เรื่อย อย่างเช่น ปรับมาเป็น single clock machine (1T) ตอนนี้ก็ยังถูกใช้งานอยู่ และยังไม่หายไปจากโลกนี้ มันต้องมีอะไรบางอย่างที่ดี อย่างน้อยราคาก็ถูก ในกล่องที่บ้านก็ยังมีอยู่ พวก 8051 มีทััง ATMEL, STC, WCH, NUVOTON งงตัวเองเลย ซื้อมาทำไม ไหนๆ ก็มีของแล้ว ก็คงต้องให้เวลาและลองใช้งานดู มีเป้าหมายก็แค่แก้เบื่อ

Fig.1 STC15W408AS demo board cost 1.98USD  (aliexpress)

Fig.2 STC15W401WS series.

 มี RC ในตัว ก็ประหยัดดี ซื้อมาประกอบเองก็ได้  จะว่าไป STM8S น่าใช้กว่า ถูกกว่าด้วย (0.7USD)

Fig.3 STC15W408AS TSSOP20 pin map

คู่มือของ STC มีตัวอย่างให้ดู ลองทำตาม
1) ลองทำ PWM ใช้ module2 (CCP2) ออกที่ขา P3.7

void main()
{
    CCON            =    0x00;
    CMOD            =    0x00;    //    0:Sysclk /12        0x0E:Sysclk/8
    CL                 =    0;
    CH                =     0;
    CCAPM2        =    0x42;
    PCA_PWM2    =    0x00;
    CCAP2L         =     0x00;    //%Duty [ 0xFFFF - (CCAP2H<<8+CCAP2L) ] /0xFFFF
    CCAP2H        =     0x80;
    CR                =    1;
    while(1);
}

Fig.4 PWM 50% duty at 1.8kHz

Fig.5 DIV_CLK register

ลองปรับความถี่ให้มันน้อยลง ต้องกำหนดใน register CLK_DIV
    CLK_DIV = 0x07    //    Sysclk /128
จะได้ความถี่น้อยลงมาเหลือ 14Hz ความถี่ระดับนี้ สังเกตุเห็นได้ด้วยตาเปล่า


Fig.6 PWM with CLK_DIV= 0x07.

2) Software Timer 16bit

    ลองใช้งาน software timer และใช้ interrupt

void main()   
{
    //CLK_DIV     =     0x04;
    CMOD           =    0x0D;        //Sysclk/6 and ECF = 1
    CCON            =    0x00;       
    CL                =    0x00;
    CH                =     0x00;
    CCAPM2        =    0x48;        //1001000B timer 16bit
    CCAPM2       |=    0x01;        //enable interrupt
    CCAP2L        =     0x00;
    CCAP2H        =     0x40;           
    CCON           |=    0x40;        //start timer CR = 1;   
    EA    = 1;
    while(1);
}
void PCA_isr() interrupt 7
{
    CF = 0;               
    CCF2 = 0;           
    P10 = !P10;        //sbit P10 = P1^0;  Test OUTPUT
}

การคำนวณ
Sysclk 18432000Hz
Freq. = 18432000Hz / 6 / 0xFFFF = 46.88 Hz
T = 1 / f = 1 / 46.88 = 21.33ms
compare time ( CCAP2H<<8 +  CCAP2L )       =     0x4000
Duty    (0xFFFF - 0x4000) / 0xFFFF = (65535 - 16384) / 65535  = 0.75
Timer 21.33ms x 0.25 = 5.33ms

Fig.7 Software timer while setting system clock 18.432MHz

เอาไปทำ millis()

3) ADC
STC15408AS มี ADC 8Channel 10bit อยู่ที่ P1.0 -P1.7 ตามคู่มือจะมี register ที่เกี่ยวข้องหลายตัว

Fig.8 ADC register

    จะเอา P1.x ไหนเป็น ADCx ก็ตั้งค่าใน P1ASF register โดยให้ bit นั้นเป็น 1
ถ้าให้ P1ASF = 0x01;    ให้ P1.0 เป็น ADC0
        P1ASF = 0x02;  //0b00000010  ให้ P1.1 เป็น ADC1

    ADC_CTR register เป็นตัวควบคุม ADC ต้องกำหนด sampling rate และเลือก ADCx ไหนที่จะทำ conversion ซึ่งมันจะทำทีละขา ทำเสร็จมันก็เก็บค่าไว้ใน ADC_RES, ADC_RESL จากนั้นจะเอาค่าไปทำไรก็ตามสะดวก

    CLK_DIV นอกจากจะเอาไว้ทำตัวหารความถี่แล้วยังเอาไว้กำหนดการจัดเรียงข้อมูลค่า ADC_RES
กำหนดบิท ADRJ = 1 จะชิดขวา ส่วน ADRJ = 1 ชิดซ้าย

    IE Interrupt Enable กำหนด EA, EADC เมื่อทำ conversion เสร็จจะให้ interrupt หรือไม่ ก็ควรเปิดการใช้งาน เพราะระหว่างการ conversion จะได้ทำอย่างอื่น เมื่อเกิด interrupt ขึ้นมา ก็ reset flag ซะเลย

void main(){
    P1ASF            = 0x02;    //Select ADC1 P1.1 pin
    CLK_DIV         = 0x20;
    ADC_RES        = 0;        //reset value
    ADC_RESL      = 0;        //reset value
    ADC_CONTR   = 0xC0;    //1100000B, Active ADC 180 clock cycle
    ADC_CONTR    |=0x01;    //Conversion at ADC1 (P1.1)
    ADC_CONTR    |=0x08;    //Start convertion, ADC_START = 1
    EA        =    1;
    EADC    = 1;   
    while(1);
}//end main

void ADC_isr() interrupt 5 using 1
{
    ADC_CONTR &= !ADC_FLAG;
    SendByte(ADC_RES);            //ส่งค่าออกไปทาง serial *ต้องสร้างฟังก์ชั่น SendByte()
    SendByte(ADC_RESL);

   
ADC_RES        = 0;        //reset value
    ADC_RESL      = 0;        //reset value
    ADC_CONTR   = 0xC1;    //1100001B, Active ADC 180 clock cycle at P1.1
    ADC_CONTR    |=0x08;    //Start conversion, ADC_START = 1

}//end ADC ISR

Fig.9 ADC conversion value.

 ผลการทำงาน มันทำงานเร็วมาก ควรใช้ timer หรือว่า delay เข้ามากำหนดระยะห่าง

MAPs

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