เมนู

แสดงบทความที่มีป้ายกำกับ C Programming แสดงบทความทั้งหมด
แสดงบทความที่มีป้ายกำกับ C Programming แสดงบทความทั้งหมด

วันอังคารที่ 25 ตุลาคม พ.ศ. 2559

Pinguino กับ ADC บน Multifunction Shield


               ในบทความนี้จะเป็นการทดลองอ่านค่าแรงดันตกคร่อมตัวต้านทานปรับค่าได้( POT ) บน Multifunction Shield ผ่านทาง Analog to Digital module ใน PIC18F45K50 แล้วนำค่ามาแสดงผลบน 7-segment โดยแบ่งเป็น 2 โปรแกรม อันแรกจะแสดงค่าตัวแรกจาก 0 - 1023 (ความละเอียด 10-bit, (2^10) - 1 = 1023) ส่วนโปรแกรมที่สอง จะนำค่าที่อ่านได้มาคำนวนกลับเป็นแรงดันตกคร่อมตัว POT แล้วแสดงผลบน 7-segment

รูปที่ 1 POT ของ Multifunction Shield

               จาก schematic ของ multifunction shield จะเห็นว่าตัว POT จะต่ออยู่กับอินพุต AN0 ดังนั้นเราจึงสามารถเรียกใช้ฟังก์ชัน analogRead() เพื่ออ่านค่าแรงดันตกคร่อมที่ขา AN0 ได้เลย โดยค่าที่ได้จะมีค่าระหว่าง 0- 1023
               ในส่วนของการแสดงผลบน 7-segment สามารถย้อนกลับไปดูรายละเอียดได้ในบทความที่ผ่านมาโดยกดที่ลิ้งค์ต่อไปนี้ได้ครับ
pinguino-7-segment-multifunction-shield

               ในส่วนเริ่มต้นจะมีการประกาศตัวแปรที่จะใช้งานในโปรแกรมของเรากันครับ
ตัวแปรอาเรย์ Dis_table[] จะเก็บค่าตัวเลข 0-9 ที่จะแสดงผลบน 7-segment ส่วนตัวแปร Dis_buf[] จะเก็บค่าที่จะ enable เลขแต่ละหลักให้ติดสว่างเอาไว้  โดยทั้งสองตัวแปรนั้นในระหว่างใช้งานไม่ได้มีการเปลี่ยนแปลงค่าดังนั้นเราสามารถประกาศเป็น const เพื่อที่ compiler จะได้เก็บค่าตัวแปรทั้งสองนี้ใน Flash memory จะได้ประหยัด RAM อันมีค่าของเราด้วย
              ส่วนตัวแปร disbuff[] นั้นจะเอาไว้เก็บค่าตัวเลขแต่ละหลักหลังจากที่ได้ decode ออกมาแล้ว และเตรียมพร้อมที่จะแสดงผล

1:  const unsigned char Dis_table[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};     // ค่าตัวเลขที่ใช้แสดงใน 7 segment  
2:  const unsigned char Dis_buf[]  = {0xF1,0xF2,0xF4,0xF8};                   // ตำแหน่งของ 7 segment ที่ต้องการแสดง  
3:  unsigned char disbuff[] = {0, 0, 0, 0};   

ฟังก์ชัน display() เป็นฟังก์ชันที่จะแสดงตัวเลขที่เก็บอยู่ในตัวแปร disbuff[] ขึ้นไปแสดงผลบน 7-segment

1:  void display()  
2:  {  
3:    char i;  
4:   for(i=0; i<=3; i++)  
5:   {  
6:    digitalWrite(LATCH_DIO,LOW);                                  // เคลียร์ค่าตัวเลข  
7:    shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_table[disbuff[i]]); // ตัวเลขที่จะแสดง  
8:    shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_buf[i] );           // ตำแหน่งที่จะแสดง       
9:    digitalWrite(LATCH_DIO,HIGH);                                 // แสดงค่าตัวเลข    
10:   delay(2);                      
11:   }   
12:  }  

โค้ดในส่วน setup() จะเป็นดังนี้ครับ

1:  void setup() {  
2:   pinMode(LATCH_DIO,OUTPUT);  
3:   pinMode(CLK_DIO,OUTPUT);  
4:   pinMode(DATA_DIO,OUTPUT);   
5:   pinMode(25,INPUT);    //turn off buzzer first  
6:  }  

ในฟังก์ชัน loop() จะมีตัวแปรโลคอลชื่อ SUM รับค่าจากฟังก์ชัน analogRead() จากนั้นเราจึงนำมา decode เพื่อแยกตัวเลขแต่ละหลักไปเก็บไว้ในตัวแปร disbuff[] ท้ายสุดจึงเป็นการเรียกฟังกชัน display ให้นำค่าตัวเลขนั้นไปแสดงผล

1:  void loop() {  
2:    uint16_t SUM;  
3:    SUM=analogRead(POT);          //อ่านค่าจาก POT(AN0) 
4:    disbuff[0]=SUM/1000;          // คำนวณค่าที่จะแสดงในหลักพัน  
5:    disbuff[1]=SUM%1000/100;      // คำนวณค่าที่จะแสดงในหลักร้อย  
6:    disbuff[2]=SUM%100/10;        // คำนวณค่าที่จะแสดงในหลักสิบ  
7:    disbuff[3]=SUM%10;            // คำนวณค่าที่จะแสดงในหลักหน่วย  
8:    display();  
9:  }  

โค้ดตัวเต็มของโปรแกรมแรกครับ

1:  /*-----------------------------------------------------  
2:  Author: --<Ekkachai Muangrodpai>  
3:  Date: 2016-10-25  
4:  Description: Read the voltage drop on POT of Multifunction shield, then   
5:           Display on 7-Segment.  
6:  -----------------------------------------------------*/  
7:  /* Define shift register pins used for seven segment display */  
8:  #define LATCH_DIO   24  
9:  #define CLK_DIO     16  
10:  #define DATA_DIO    5  
11:  #define POT   A0  
12:  const unsigned char Dis_table[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};     // ค่าตัวเลขที่ใช้แสดงใน 7 segment  
13:  const unsigned char Dis_buf[]  = {0xF1,0xF2,0xF4,0xF8};                   // ตำแหน่งของ 7 segment ที่ต้องการแสดง  
14:  unsigned char disbuff[] = {0, 0, 0, 0};  
15:  void setup() {  
16:   pinMode(LATCH_DIO,OUTPUT);  
17:   pinMode(CLK_DIO,OUTPUT);  
18:   pinMode(DATA_DIO,OUTPUT);   
19:   pinMode(25,INPUT);    //turn off buzzer first  
20:  }  
21:  void display()  
22:  {  
23:    char i;  
24:   for(i=0; i<=3; i++)  
25:   {  
26:    digitalWrite(LATCH_DIO,LOW);                         // เคลียร์ค่าตัวเลข  
27:    shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_table[disbuff[i]]); // ตัวเลขที่จะแสดง  
28:    shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_buf[i] );        // ตำแหน่งที่จะแสดง       
29:    digitalWrite(LATCH_DIO,HIGH);                        // แสดงค่าตัวเลข    
30:    delay(2);                      
31:   }   
32:  }  
33:  void loop() {  
34:    uint16_t SUM;  
35:    SUM=analogRead(POT);          //อ่านค่าจาก POT(AN0)  
36:    disbuff[0]=SUM/1000;          // คำนวณค่าที่จะแสดงในหลักพัน  
37:    disbuff[1]=SUM%1000/100;     // คำนวณค่าที่จะแสดงในหลักร้อย  
38:    disbuff[2]=SUM%100/10;     // คำนวณค่าที่จะแสดงในหลักสิบ  
39:    disbuff[3]=SUM%10;          // คำนวณค่าที่จะแสดงในหลักหน่วย  
40:    display();  
41:  }  


               โปรแกรมที่ 2 นั้นจะคล้ายกับโปรแกรมแรก เพียงแต่หลังจากที่อ่านค่า ADC เข้ามาแล้วนั้นก่อนที่จะแสดงผล เราจะนำค่าที่ได้มาเข้าสมการเพื่อคำนวณกลับไปเป็นแรงดันตกคร่อมซะก่อน แล้วจึงนำไปแสดงผลบน 7-segment
               โดยค่าที่อ่านเข้ามาได้จะนำมาเข้าสมการดังนี้ (ADCread * 5)/1023 โดยเลข 5 คือแรงดันสูงสุดที่ 5 V. และ 1023 คือ (2^10 - 1 = 1023) ครับ แต่คราวนี้ค่าที่ได้จะเป็นเลขทศนิยมหรือ floating poing ซึ่ง mcu จะใช้ทรัพยากร เวลา และ พลังงานมากในการคำนวณ ดังนั้นเราจะทำการปรับซักเล็กน้อยให้การคำนวณเป็นเลขจำนวนเต็ม ซึ่งเราต้องการให้แสดงตัวเลขหลังจุดทศนิยมเพียง 2 ตำแหน่งเท่านั้น ดังนั้นเราสามารถตัวเลข 5 ไปทางซ้าย 2 หลัก จะได้เป็น (ADCread * 500)/1023 ผลลัพธ์ที่ได้ก็จะนำมา decode แยกเป็นตัวเลขหน้าจุดทศนิยม 1 ตำแหน่ง และตัวเลขหลังจุดทศนิยม 2 ตำแหน่ง ในที่สุดก็จะได้โค้ดดังนี้ครับ

1:  void loop() {  
2:    ADCread=analogRead(POT);  
3:     volts=0;  
4:     decivolts = 0;  
5:     millivolts = (int)((((unsigned long)ADCread)*500)/1023);  
6:     while(millivolts>=100)          //hunderd digit  
7:      {  
8:          millivolts-=100;  
9:          volts++;  
10:      }  
11:      while(millivolts>=10)          //tens digit  
12:      {  
13:          millivolts-=10;  
14:          decivolts++;  
15:      }  
16:    disbuff[0]= volts;          // คำนวณค่าที่จะแสดงในหลักพัน  
17:    disbuff[1]= decivolts;               // คำนวณค่าที่จะแสดงในหลักร้อย  
18:    disbuff[2]= millivolts;          // คำนวณค่าที่จะแสดงในหลักสิบ  
19:    display();  
20:    delay(10);  
21:  }  

               คราวนี้มีปัญหาตามมานิดหน่อยคือเราต้องแสดงจุดทศนิยมหลังตัวเลขหลักแรกด้วย แต่ค่าในตัวแปร Dis_table[] เป็นค่าสำหรับแสดงตัวเลข 0 - 9 โดยที่ไม่มีจุดซึ่งเรายังจำเป็นต้องใช้ในการแสดงผลตัวเลขหลังจุดทศนิยมสองตำแหน่งดังนั้นไม่ควรไปเปลี่ยนแก้มัน ผมเลยประกาศตัวแปรใหม่ชื่อ Dis_table_DOT[] ให้เก็บค่าคงที่สำหรับแสดงตัวเลข 0 - 5 พร้อมจุดทศนิยม( เราใช้แค่ 0 - 5 เพราะแรงดันสูงสุดเพียงแค่ 5 โวลต์ครับ ) นอกจากนี้ก็ยังมีการประกาศตัวแปรเพิ่มอีก 3 ตัวเพื่อใช้เก็บค่าตัวเลขหลังจาก decode แล้ว ดังนั้นตัวแปรทั้งหมดจึงเป็นดังนี้ครับ

1:  const byte Dis_table[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};      // ค่าตัวเลขที่ใช้แสดงใน 7 segment  
2:  const byte Dis_table_DOT[] = {0x40,0x79,0x24,0x30,0x19,0x12};             // ค่าตัวเลขที่ใช้แสดงใน 7 segment พร้อมจุดทศนิยม   
3:  const byte Dis_buf[]  = {0xF1,0xF2,0xF4,0xF8};                         // ตำแหน่งของ 7 segment ที่ต้องการแสดง  
4:  unsigned char disbuff[] = {0, 0, 0};  
5:  uint16_t millivolts,ADCread;  
6:  uint8_t volts,decivolts;  

               และสุดท้ายในฟังก์ชัน display() ก็ต้องมีการปรับแก้กันเล็กน้อยโดยในการแสดงตัวเลขหลักแรกให้ใช้ค่าคงที่จาก Dis_table_DOT ส่วนสองหลักถัดมาก็ให้ใช้ค่าคงที่จาก Dis_table แทน เท่านี้เราก็จะสามารถแสดงค่าแรงดันตกคร่อม POT บน 7-segment ได้แล้วครับ
โค้ดส่วนฟังกชัน display() ครับ

1:  void display()  
2:  {  
3:    char i;  
4:   for(i=0; i<3; i++)  
5:   {  
6:     if(i==0){  
7:        digitalWrite(LATCH_DIO,LOW);                             // เคลียร์ค่าตัวเลข  
8:        shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_table_DOT[disbuff[i]]); // ตัวเลขที่จะแสดง  
9:        shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_buf[i] );            // ตำแหน่งที่จะแสดง       
10:        digitalWrite(LATCH_DIO,HIGH);                            // แสดงค่าตัวเลข         
11:     }  
12:     else{  
13:      digitalWrite(LATCH_DIO,LOW);                         // เคลียร์ค่าตัวเลข  
14:      shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_table[disbuff[i]]); // ตัวเลขที่จะแสดง  
15:      shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_buf[i] );        // ตำแหน่งที่จะแสดง       
16:      digitalWrite(LATCH_DIO,HIGH);                        // แสดงค่าตัวเลข            
17:     }  
18:     delay(2);                 
19:   }   
20:  }  

และนี่คือโค้ดตัวเต็มของโปรแกรมที่สองครับ

1:  /*-----------------------------------------------------  
2:  Author: --<Ekkachai Muangrodpai>  
3:  Date: 2016-10-25  
4:  Description: Read the voltage drop on POT of Multifunction shield, then   
5:           calculate the voltage and Display on 7-Segment.  
6:  -----------------------------------------------------*/  
7:  /* Define shift register pins used for seven segment display */  
8:  #define LATCH_DIO   24  
9:  #define CLK_DIO     16  
10:  #define DATA_DIO    5  
11:  #define POT   A0  
12:  const byte Dis_table[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};      // ค่าตัวเลขที่ใช้แสดงใน 7 segment  
13:  const byte Dis_table_DOT[] = {0x40,0x79,0x24,0x30,0x19,0x12};             // ค่าตัวเลขที่ใช้แสดงใน 7 segment พร้อมจุดทศนิยม   
14:  const byte Dis_buf[]  = {0xF1,0xF2,0xF4,0xF8};                         // ตำแหน่งของ 7 segment ที่ต้องการแสดง  
15:  unsigned char disbuff[] = {0, 0, 0};  
16:  uint16_t millivolts,ADCread;  
17:  uint8_t volts,decivolts;  
18:  void setup() {  
19:   pinMode(LATCH_DIO,OUTPUT);  
20:   pinMode(CLK_DIO,OUTPUT);  
21:   pinMode(DATA_DIO,OUTPUT);   
22:   pinMode(25,INPUT);    //turn off buzzer first  
23:  }  
24:  void display()  
25:  {  
26:    char i;  
27:   for(i=0; i<3; i++)  
28:   {  
29:     if(i==0){  
30:        digitalWrite(LATCH_DIO,LOW);                             // เคลียร์ค่าตัวเลข  
31:        shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_table_DOT[disbuff[i]]); // ตัวเลขที่จะแสดง  
32:        shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_buf[i] );            // ตำแหน่งที่จะแสดง       
33:        digitalWrite(LATCH_DIO,HIGH);                            // แสดงค่าตัวเลข         
34:     }  
35:     else{  
36:      digitalWrite(LATCH_DIO,LOW);                         // เคลียร์ค่าตัวเลข  
37:      shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_table[disbuff[i]]); // ตัวเลขที่จะแสดง  
38:      shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, Dis_buf[i] );        // ตำแหน่งที่จะแสดง       
39:      digitalWrite(LATCH_DIO,HIGH);                        // แสดงค่าตัวเลข            
40:     }  
41:     delay(2);                 
42:   }   
43:  }  
44:  void loop() {  
45:    ADCread=analogRead(POT);  
46:     volts=0;  
47:     decivolts = 0;  
48:     millivolts = (int)((((unsigned long)ADCread)*500)/1023);  
49:     while(millivolts>=100)          //hunderd digit  
50:      {  
51:          millivolts-=100;  
52:          volts++;  
53:      }  
54:      while(millivolts>=10)          //tens digit  
55:      {  
56:          millivolts-=10;  
57:          decivolts++;  
58:      }  
59:    disbuff[0]= volts;          // คำนวณค่าที่จะแสดงในหลักพัน  
60:    disbuff[1]= decivolts;               // คำนวณค่าที่จะแสดงในหลักร้อย  
61:    disbuff[2]= millivolts;          // คำนวณค่าที่จะแสดงในหลักสิบ  
62:    display();  
63:    delay(10);  
64:  }  

และด้านล่างนี้คือวิดีโอแสดงการทำงานครับ


ขอบคุณที่ติดตามครับ :>

วันศุกร์ที่ 12 สิงหาคม พ.ศ. 2559

Pinguino กับ 7-segment บน Multifunction Shield


               หลังจากได้ลองค้นดูว่ายังมี shield อะไรเก็บไว้อยู่บ้างก็เจอว่ายังมีตัว Multifunction Shield ที่ดูน่าสนใจเอามาเขียนโปรแกรมกับบอร์ด Pinguino 8 ที่ดูน่าสนใจที่สุดบนบอร์ดก็ต้องเป็นเจ้า 7-segment 4 หลักที่เชื่อมต่ออยู่กับ IC serial-to-parallel 74HC595 เมื่อพอลองหาข้อมูลจากเว็ปดูก็ไปได้ข้อมูลจากลิ้งค์ข้างล่างนี้ 
https://wiki.eprolabs.com/index.php?title=Multi-functional_Learning_Board_for_Arduino
รูปที่ 1 Schematic ส่วนของ 7-Segment กับ 74HC595
รูปที่ 2 หมายเลขพินของบอร์ด Pinguino 8
              พอดูที่ schematic จะเห็นว่า 7-segment 4 หลักที่อยู่บนตัว shield นั้นจะมี IC 74HC595 จำนวน 2 ตัวต่อ drive อยู่ โดยตัวแรก( U2) จะเป็นตัวเลือกว่าจะให้ 7-segment หลักไหนแสดงผล ส่วนตัว U3 จะเป็นตัวขับให้แสดงเป็นตัวเลขต่างๆ  อินพุตจะเริ่มจากตัว U2 โดยขา SDI (ขา data)จะต่ออยู่กับพิน 8 ของ Arduino ส่วนขา SFTCLK(ขา clock)จะต่อกับพิน 7 และสุดท้ายขา LCHCLK(ขา latch)จะต่อกับพิน 4 ทีนี้เมื่อเรารู้ตำแหน่งที่เชื่อต่อกับ Arduino แล้ว เมื่อเราต้องการใช้บอร์ด Pinguino 8 แทนเราก็ต้องมาดูว่าที่ตำแหน่งเดียวกันนั้นบนบอร์ด Pinguino 8 เป็นพินหมายเลขอะไร จากในรูปที่ 2 จะเห็นว่าเป็นพินหมายเลข 5, 16, และ 24 ตามลำดับ ดังนั้นเพื่อให้ง่ายในการจำและเขียนโค้ดเราจึงจะ define พินทั้งสามนี้ไว้ดังนี้

1:  /* Define shift register pins used for seven segment display */  
2:  #define LATCH_DIO   24  
3:  #define CLK_DIO     16  
4:  #define DATA_DIO    5  

               จากนั้นเราก็ต้องมากำหนดค่าคงที่ที่เราจะส่งออกไปแล้วทำให้ 7-segment ติดสว่างแสดงเป็นตัวเลข 0-9 เมื่อเราพิจารณาที่ schematic โดยเฉพาะที่ U3 ซึ่งต่อเข้ากับ 7-segment ที่ตำแหน่ง a-h ตามลำดับแล้วโดยเมื่อเราส่ง logic 0 ออกไปที่พินใดๆ(a-h) ก็จะทำให้ segment นั้นๆติดสว่าง(เป็น common Anode) ยกตัวอย่างเช่นต้องการให้เลข 0 ติดสว่างจะต้องให้ logic 0 ที่พิน a, b, c, d, e, f และให้ logic 1 ที่พิน g กับ h และค่า binary นี้จะถูกส่งออกไปทีละบิต โดยถ้ายึดตามโค้ดตัวอย่างของ Arduino ที่เลือกส่งบิต MSB(Most Significant Bit) ออกไปก่อนเราก็จะได้ค่าเป็น 0xC0  และหมายเลขอื่นๆก็ใช้วิธีคิดแบบเดียวกันครับ โดยค่าคงที่ทั้งสิบตัวจะมีค่าเป็นดังนี้

 /* Segment byte maps for numbers 0 to 9 */  
 const byte SEGMENT_MAP[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};  

และค่าคงที่สำหรับการเลือกว่าจะให้หลักใดติดสว่างก็ใช้วิธีคิดแบบเดียวกันเพียงแต่คราวนี้เราต้องส่ง logic 1 ออกไปยังตำแหน่งที่เราต้องการให้มันติดสว่าง ซึ่งจะมีค่าดังต่อไปนี้


 /* Byte maps to select digit 1 to 4 */  
 const byte SEGMENT_SELECT[] = {0xF1,0xF2,0xF4,0xF8};  

เราเลือกประกาศเป็นค่าคงที่โดยใช้ key word เป็น const เพื่อบอกให้ compiler รู้ว่าค่าใน array เหล่านี้จะไม่มีการเปลี่ยนแปลงค่า ดังนั้น compiler สามารถเก็บค่าเหล่านี้ไว้ใน Flash memory ได้ ทำให้ประหยัด RAM ไปด้วยครับ

               จากนั้นเราก็มาเขียนฟังก์ชั่นที่จะส่งค่าเหล่านี้ออกไปแสดงบน 7-segment แต่ละหลักได้โดยอาศัย shiftOut() ที่ Pinguino มีเตรียมไว้ให้ใช้อยู่แล้ว ซึ่งจะได้เป็นฟังก์ชั่นดังต่อไปนี้ครับ


1:  /* Wite a decimal number between 0 and 9 to one of the 4 digits of the display */  
2:  void WriteNumberToSegment(byte Segment, byte Value)  
3:  {  
4:    digitalWrite(LATCH_DIO,LOW);   
5:    shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_MAP[Value]);  
6:    shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_SELECT[Segment] );  
7:    digitalWrite(LATCH_DIO,HIGH);    
8:  }  
             
               และเพื่อให้การเขียนโค้ดง่ายยิ่งขึ้นเราก็จะมีฟังก์ชั่นที่ช่วยแยกตัวเลขที่นับได้ออกเป็นหลักหน่วย หลักสิบ หลักร้อย และหลักพัน เพื่อแสดงผล โดยอาศัยการหารกับหลักนั้นๆ และการหารที่เก็บเฉพาะเศษที่เหลือเท่านั้น(modulus, %) ซึ่งจะได้เป็นฟังก์ชั่นดังต่อไปนี้ครับ


1:  /* Write a decimal number between 0 and 9999 to the display */  
2:  void WriteNumber(int Number)  
3:  {  
4:    WriteNumberToSegment(0 , Number / 1000);  
5:    WriteNumberToSegment(1 , (Number / 100) % 10);  
6:    WriteNumberToSegment(2 , (Number / 10) % 10);  
7:    WriteNumberToSegment(3 , Number % 10);  
8:  }  

               คราวนี้โค้ดตัวอย่างของเราจะทำการแสดงตัวเลขนับค่าเพิ่มขึ้นทุกๆ 500 ms และเราจะใช้ฟังก์ชั่น millis() มาช่วยในการจับเวลาที่จะเพิ่มค่าขึ้น ดังนั้นเราต้องการตัวแปรเพิ่มเติมอีกเพื่อเก็บค่าเวลา ณ ปัจจุบันและค่าเวลาที่ผ่านมา กับตัวแปรไว้นับเลข โดยเราจะประกาศตัวแปรดังนี้


1:  unsigned long Cur_ms_Count;      // Stores the current time in ms  
2:  unsigned long Last_ms_Count;     // Stores the last time in ms the counter was last updated  
3:  unsigned int Count;              // Stores the value that will be displayed  

ในฟังก์ชั่น setup() เราก็กำหนดให้พินที่ควบคุม 74HC595 ที่เรา define ไว้ให้เป็นเอ้าต์พุต และกำหนดค่าเริ่มต้นให้แก่ตัวแปรจับเวลาดังนี้ครับ


1:  void setup() {  
2:    pinMode(25,INPUT);    //turn off buzzer first  
3:   /* Set DIO pins to outputs */  
4:   pinMode(LATCH_DIO,OUTPUT);  
5:   pinMode(CLK_DIO,OUTPUT);  
6:   pinMode(DATA_DIO,OUTPUT);    
7:   /* Initiliase the registers used to store the current time and count */  
8:   Cur_ms_Count = millis();  
9:   Last_ms_Count = 0;  
10:   Count = 0;    
11:  }  

สุดท้ายในฟังก์ชั่น loop() เราก็เพียงแต่คอยเพิ่มค่าทุกๆ 500 ms แล้วเรียกฟังก์ชั่นช่วยเหลือเพื่อแสดงตัวเลขออกไปเท่านั้นเองครับ โดยโค้ดตัวเต็มจะเป็นดังนี้นะครับ


/*-----------------------------------------------------
Author:  --<Ekkachai Muangrodpai>
Date: 2016-08-12
Description: Display and update a number on 7-Segment 
      every 500 ms.
-----------------------------------------------------*/
/* Define shift register pins used for seven segment display */
#define LATCH_DIO     24
#define CLK_DIO       16
#define DATA_DIO      5
 
/* Segment byte maps for numbers 0 to 9 */
const byte SEGMENT_MAP[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};
/* Byte maps to select digit 1 to 4 */
const byte SEGMENT_SELECT[] = {0xF1,0xF2,0xF4,0xF8};

unsigned long Cur_ms_Count;  // Stores the current time in ms
unsigned long Last_ms_Count;  // Stores the last time in ms the counter was last updated
unsigned int  Count;   // Stores the value that will be displayed

/* Wite a decimal number between 0 and 9 to one of the 4 digits of the display */
void WriteNumberToSegment(byte Segment, byte Value)
{
  digitalWrite(LATCH_DIO,LOW); 
  shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_MAP[Value]);
  shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_SELECT[Segment] );
  digitalWrite(LATCH_DIO,HIGH);    
}

/* Write a decimal number between 0 and 9999 to the display */
void WriteNumber(int Number)
{
  WriteNumberToSegment(0 , Number / 1000);
  WriteNumberToSegment(1 , (Number / 100) % 10);
  WriteNumberToSegment(2 , (Number / 10) % 10);
  WriteNumberToSegment(3 , Number % 10);
}
void setup() {
    pinMode(25,INPUT);        //turn off buzzer first
   
  /* Set DIO pins to outputs */
  pinMode(LATCH_DIO,OUTPUT);
  pinMode(CLK_DIO,OUTPUT);
  pinMode(DATA_DIO,OUTPUT); 
  
  /* Initiliase the registers used to store the current time and count */
  Cur_ms_Count = millis();
  Last_ms_Count = 0;
  Count = 0;    
}

void loop() {   
  /* Get how much time has passed in milliseconds */
  Cur_ms_Count = millis();
  
  /* If 100ms has passed then add one to the counter */
  if(Cur_ms_Count - Last_ms_Count > 500)
  {
    Last_ms_Count = Cur_ms_Count;
    
    if(Count < 9999)
    {
      Count++;
    } else
    {
      Count = 0;
    }
  }
  
  /* Update the display with the current counter value */
  WriteNumber(Count);  
}




วันอังคารที่ 2 สิงหาคม พ.ศ. 2559

Pinguino กับ LCD Keys pad shield V1.2 (another shield)


              ในบทความก่อนหน้านี้ผมได้เขียนถึงการใช้บอร์ด Pinguino 8 เชื่อมต่อกับ LCD key pad shield, อันนี้ครับ
ซึ่งหลังเขียนเสร็จแล้วผมก็นึกขึ้นมาได้ว่ายังมี LCD key pad shield อยู่อีกอันหนึ่ง( ยี่ห้อ ElecFreaks , shield สีขาวในรูปด้านบน )ซึ่งแตกต่างจากอันแรกตรงที่ใช้ปุ่มกดแบบจอยสติ้ก 5 ทิศทางแทน แต่ยังคงเชื่อมต่อกับไมโครคอนโทรลเลอร์ที่ขา A0 เหมือนเดิมทำให้สามารถใช้โค้ดเดิมได้ และที่เพิ่มขึ้นมาอีกอันก็คือตัว rotary encoder ที่ถ้าดูผิวเผินแล้วเหมือนตัวโวลุ่มเลย โดยเราสามารถหมุนได้ทั้งสองทิศทางซึ่งจะให้เอ้าพุตออกมาสองแชลแนล และมีพัลล์เหลื่อมกันดังแสดงในรูปที่ 2 นอกจากนี้ยังสามารถกดลงคล้ายกับการกดปุ่ม enter ได้อีกด้วย โดยในบทความนี้จะมุ่งเน้นที่การเขียนโค้ดเพิ่อตรวจจับการหมุนหรือเปลี่ยนตำแหน่งของ encoder โดยอาศัยอินเตอร์รับออนเชนต์( interrupt on change ) และมีแก้ไขค่าคงที่ของ keys ต่างๆเพื่อรองรับการกดปุ่มของ encoder ที่เพิ่มเติมขึ้นมา โดยผู้อ่านสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับ shield ตัวนี้ได้จากลิ้งค์ต่อไปนี้ ซึ่งจะมี schematic, datasheet, และโค้ดตัวอย่างของ Arduino ที่เราสามารถนำมาใช้ได้กับ Pinguino ของเรา
รูปที่ 2 เอ้าพุตของ rotary encoder

วิธีการอ่านค่าของ rotary encoder นั้นสามารถหาอ่านเพิ่มเติมได้จากฟอร์รั่มของ Arduino เช่นลิ้งค์ต่อไปนี้

หรือลิ้งค์อันนี้ก็ให้ข้อมูลดีครับและใช้ C18 ในการเขียนโค้ดด้วย

จาก schematic ที่สามารถโหลดได้จากลิ้งค์แรกนั้นจะเห็นว่าเอ้าพุตของ rotary encoder นั้นจะต่ออยู่กับ pin 2 และ 3 ของ Arduino ซึ่งทั้งสองพินนี้นั้นสามารถ config ให้เป็น external interrupt และสามารถเลือกได้ว่าจะให้เกิด interrupt เมื่อเกิดสัญญานขอบขาขึ้นหรือขอบขาลง คราวนี้เมื่อผมจะใช้บอร์ด Pinguino 8 มาแทน Arduino พอเช็คขาที่ตำแหน่งเดียวกันนี้พบว่าบอร์ด Pinguino 8 ที่ตำแหน่ง D2 กับ D3 นี้ไม่รองรับ external interrupt ทำให้ผมต้องมาทำการโมดิฟายตัว shield โดยบัดกรีสายจากตำแหน่ง D2 กับ D3 ไปที่ตำแหน่ง D12 และ D13 ตามลำดับดังแสดงในรูปที่ 3 ครับ

รูปที่ 3 บัดกรีสายจาก D2, D3 ไป D12, D13

โดยที่ตำแหน่งใหม่นี้จะเป็นพินหมายเลข 0 และ 1 บนบอร์ด Pinguino 8 ดังแสดงในรูปที่ 4 ซึ้่งทั้งสองพินนี้รองรับ interrupt on change ตามที่เราต้องการ
รูปที่ 4 หมายเลขพินบน Pinguino 8
               เมื่อ hardware ของเราพร้อมแล้วทีนี้ก็มาถึงขั้นตอนการเขียนโค้ด จากในรูปที่ 2 เมื่อพิจารณาให้ดีจะเห็นว่าในจังหวะที่พัลล์เอ้าพุตแชลแนล A ของ rotary encoder เปลี่ยนลอจิกจาก HIGH เป็น LOW ( Falling Edge ) แล้วถ้าเราอ่านค่าของเอ้าพุตแชลแนล B ได้ค่าเป็น HIGH นั่นแสดงว่า rotary encoder ถูกหมุนไปในทิศทางตามเข็มนาฬิกา ( Clockwise ) เราก็สามาถเพิ่มค่าในตัวแปรที่เราเอาไว้เก็บค่าการหมุนได้ 
               ในทางตรงกันข้ามหากจังหวะที่เอ้าพุตแชลแนล A ของ rotary encoder เปลี่ยนลอจิกจาก HIGH เป็น LOW ( Falling Edge ) แล้วถ้าเราอ่านค่าของเอ้าพุตแชลแนล B ได้ค่าเป็น LOW นั่นแสดงว่า rotary encoder ถูกหมุนไปในทิศทางทวนเข็มนาฬิกา ( Counter-Clockwise ) เราก็สามาถบดค่าในตัวแปรที่เราเอาไว้เก็บค่าการหมุนได้ 
               เมื่อเรารู้แนวคิดในการเขียนโค้ดแล้วเราก็สามารถเริ่มเขียนได้โดยเริ่มจากการ define ชื่อขาของ rotary encoder เพื่อให้ง่ายในการอ่านและเขียนโค้ด และประกาศตัวแปรที่เอาไว้เก็บค่าการหมุน พร้อมทั้งตัวแปรที่จำเป็นดังนี้

1:  #define Encoder_A  0  
2:  #define Encoder_B  1  
3:  volatile int Encoder_number=0;  
4:  volatile u8 state=0;  

จากนั้นในฟังก์ชัน setup() เราก็ config ให้ขา encoder เป็น input ซะ แต่อย่าลืมที่ตำแหน่ง D2 กับ D3 ของ shield นั้นเมื่อเสียบลงบนบอร์ด Pinguino 8 แล้วจะเป็นพินหมายเลข 25 กับ 26 ( ดูได้จากรูปที่ 4 ) ซึ่งโดย default ของตัวไมโครคอนโทรลเลอร์(PIC18F45K50)เองหลังจาก power on or reset ขึ้นมามันจะเป็น output ซึ่งจะทำให้ feature อินเตอร์รัพที่ขา 0 กับ 1 ไม่ทำงานดังนั้นเราจำเป็นต้อง config พิน 25 กับ 26 ให้เป็น input (high impedance) ด้วย interrupt on change ถึงจะทำงานได้ถูกต้อง (หรือไม่งั้นก็ต้องตัดขา D2 กับ D3 ของ shield ทิ้งซะก่อน) จากนั้นจึงจะ enable ความสามารถ Interrupt On Change บน pin 0 โดยให้เกิด interrupt ทุกๆขอบขาลง ( INT_FALLING_EDGE ) โดยเมื่อดักจับขอบขาลงได้ให้กระโดดไปทำงานที่ฟังก์ชั่นชื่อ Encode() ดังตัวอย่างโค้ดต่อไปนี้ครับ


1: //make the pin 2 and 3(which is pin 25, and 26 on Pinguino 8) of LCD keypad shield as input  
2: //so that the pin 0 and 1 of Pinguino 8 that we use for interrupt on change can work correctly  
3:    pinMode(25, INPUT);      
4:    pinMode(26, INPUT);  

5:    pinMode(Encoder_A, INPUT);   
6:    pinMode(Encoder_B, INPUT);   
7:    digitalWrite(Encoder_A, HIGH);  
8:    digitalWrite(Encoder_B, HIGH);  

9:    OnChangePin0(Encode, INT_FALLING_EDGE);  

ในส่วนของฟังก์ชั่น Encode() นั้นเราก็เพียงอ่านค่าของ Encoder_B หากอ่านได้เป็น 1 (HIGH) ก็ให้เพิ่มค่าตัวแปร แต่หากอ่านค่าได้เป็น 0 (LOW) ก็ลดค่าตัวแปรลง จากนั้นจึงบอกให้ฟังก์ชัน loop รู้ว่า rotary encoder มีการหมุน( เปลี่ยนแปลงค่า ) โดยการเซ็ทให้ตัวแปร state เป็น 1 เพื่อที่ในฟังก์ชั่น loop จะได้อัพเดตค่าบนจอ LCD ต่อไป ตัวอย่างโค้ดก็จะเป็นตามนี้นะครับ


1:  void Encode(){  
2:    if(digitalRead(Encoder_B))  
3:       {  
4:         Encoder_number++;  
5:       }  
6:      else  
7:       {   
8:        Encoder_number--;  
9:       }     
10:       state=1;  
11:  }  

และในส่วนของฟังก์ชั่น loop() เราก็จะเพิ่มโค้ดเพื่ออัพเดตค่าของ Encoder_number บนจอ LCD โดยจะทำการอัพเดตก็ต่อเมื่อมีการหมุน rotary encoder เท่านั้น ดังโค้ดตัวอย่างต่อไปนี้ครับ


1:  if(state==1)  
2:   {   
3:    lcd.clear();  
4:    lcd.setCursor(9,1);      // move cursor to second line "1" and 9 spaces over   
5:    lcd.printf("%d",Encoder_number);   
6:    lcd.setCursor(0,0);      // move cursor to second line "1" and 9 spaces over   
7:    lcd.printf("Push the buttons"); // print a simple message   
8:    state=0;  
9:   }   

สุดท้ายเราก็จะมาแก้ไขค่าการเปรียบเทียบค่าที่อ่านได้จาก ADC จากตัวอย่างในบทความเกี่ยวกับ LCD keys pad shield ที่ผ่านมา โดยแก้ไขค่าเป็นดังนี้ครับ


1:  if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result  
2:   // For V1.1 us this threshold  
3:   if (adc_key_in < 50)  return btnLEFT;  
4:   if (adc_key_in < 150) return btnUP;  
5:   if (adc_key_in < 250) return btnRIGHT;  
6:   if (adc_key_in < 450) return btnSELECT;  
7:   if (adc_key_in < 700) return btnDOWN;  
8:   if (adc_key_in < 850) return btnEncodeOK;   

และต้องเพิ่มสเตรทในฟังก์ชั่น loop ขึ้นอีกอันหนึ่งด้วย สำหรับปุ่ม Encode OK ซึ่งโค้ดตัวเต็มก็จะเป็นดังนี้ครับ
1:  /*-----------------------------------------------------  
2:  Author: --<Ekkachai Muangrodpai>  
3:  www.picgetstart.blogspot.com  
4:  Date: 2016-03-18  
5:  Board:  Pinguino8 by Jimmy + LCD Keypads Shield V.1  
6:  Description:  
7:  -Display a message on LCD Keypads Shield, and also read a keys through analog AN0.  
8:  -At the same time, display the second time too.  
9:   * LCD RS pin to digital pin 5  
10:   * LCD E pin to digital pin 4  
11:   * LCD D4 pin to digital pin 24  
12:   * LCD D5 pin to digital pin 18  
13:   * LCD D6 pin to digital pin 17  
14:   * LCD D7 pin to digital pin 16  
15:   * LCD R/W pin to ground  
16:   note:  
17:   - don't use lcd.print() and lcd.printf() in the same program.  
18:  -----------------------------------------------------*/  
19:  #define BACK_LIGHT  2    //LCD back light control by pin2  
20:  // define some values used by the panel and buttons  
21:  int lcd_key   = 0;  
22:  int adc_key_in = 0;  
23:  #define btnRIGHT 0  
24:  #define btnUP   1  
25:  #define btnDOWN  2  
26:  #define btnLEFT  3  
27:  #define btnSELECT 4  
28:  #define btnNONE  5  
29:  #define btnEncodeOK 6  
30:  #define Encoder_A  0  
31:  #define Encoder_B  1  
32:  volatile int Encoder_number=0;  
33:  volatile u8 state=0;  
34:  // read the buttons  
35:  int read_LCD_buttons()  
36:  {  
37:   adc_key_in = analogRead(0);   // read the value from the sensor   
38:  // my buttons when read are centered at these valies: 0, 144, 329, 504, 741  
39:   // we add approx 50 to those values and check to see if we are close  
40:   if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result  
41:   // For V1.1 us this threshold  
42:   if (adc_key_in < 50)  return btnLEFT;  
43:   if (adc_key_in < 150) return btnUP;  
44:   if (adc_key_in < 250) return btnRIGHT;  
45:   if (adc_key_in < 450) return btnSELECT;  
46:   if (adc_key_in < 700) return btnDOWN;  
47:   if (adc_key_in < 850) return btnEncodeOK;   
48:   return btnNONE; // when all others fail, return this...  
49:  }  
50:  void Encode(){  
51:    if(digitalRead(Encoder_B))  
52:       {  
53:         Encoder_number++;  
54:       }  
55:      else  
56:       {   
57:        Encoder_number--;  
58:       }     
59:       state=1;  
60:  }  
61:  void setup( void) {  
62:    // put your setup code here, to run once:  
63:    pinMode(BACK_LIGHT, OUTPUT);  
64:    digitalWrite(BACK_LIGHT, HIGH);  
65:    // initialize the library with the numbers of the interface pins    
66:    lcd.pins(5, 4, 24, 18, 17, 16, 0, 0, 0, 0); // RS, E, D4 ~ D8    
67:    // set up the LCD's number of columns and rows:   
68:    lcd.begin(16, 2);  
69:    // Print a message to the LCD.  
70:    lcd.clear();  
71:    lcd.setCursor(0,0);  
72:    lcd.printf("Push the buttons"); // print a simple message  
73:    //make the pin 2 and 3(which is pin 25, and 26 on Pinguino 8) of LCD keypad shield as input  
74:    //so that the pin 0 and 1 of Pinguino 8 that we use for interrupt on change can work correctly  
75:    pinMode(25, INPUT);      
76:    pinMode(26, INPUT);  
77:    pinMode(Encoder_A, INPUT);   
78:    pinMode(Encoder_B, INPUT);   
79:    digitalWrite(Encoder_A, HIGH);  
80:    digitalWrite(Encoder_B, HIGH);  
81:    OnChangePin0(Encode, INT_FALLING_EDGE);  
82:  }  
83:  void loop( void ) {  
84:   if(state==1)  
85:   {   
86:    lcd.clear();  
87:    lcd.setCursor(9,1);      // move cursor to second line "1" and 9 spaces over   
88:    lcd.printf("%d",Encoder_number);   
89:    lcd.setCursor(0,0);      // move cursor to second line "1" and 9 spaces over   
90:    lcd.printf("Push the buttons"); // print a simple message   
91:    state=0;  
92:   }   
93:   lcd.setCursor(0,1);      // move to the begining of the second line  
94:   lcd_key = read_LCD_buttons(); // read the buttons  
95:   switch (lcd_key)        // depending on which button was pushed, we perform an action  
96:   {  
97:    case btnRIGHT:  
98:     {  
99:       lcd.printf("RIGHT  ");  
100:       break;  
101:     }  
102:    case btnLEFT:  
103:     {  
104:       lcd.printf("LEFT   ");  
105:       break;  
106:     }  
107:    case btnUP:  
108:     {  
109:       lcd.printf("UP    ");  
110:       break;  
111:     }  
112:    case btnDOWN:  
113:     {  
114:       lcd.printf("DOWN  ");  
115:       break;  
116:     }  
117:    case btnSELECT:  
118:     {  
119:       lcd.printf("SELECT  ");  
120:       break;  
121:     }  
122:    case btnNONE:  
123:     {  
124:       lcd.printf("NONE  ");  
125:       break;  
126:     }  
127:     case btnEncodeOK:  
128:     {  
129:       lcd.printf("EncdOK  ");  
130:       break;  
131:     }  
132:   }  
133:  }  

และข้างล่างนี้คือวิดีโอผลการทำงานของ Pinguino 8 กับ LCD key pad shield V1.2 ครับ




วันพฤหัสบดีที่ 31 มีนาคม พ.ศ. 2559

อ่านค่าจากสวิตซ์ ด้วย MPLABX และ MCC



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

Experiment Shield
            ก่อนจะเริ่มเขียนโปรแกรมอ่านค่าจากสวิตซ์ ผมอยากจะแนะนำ Experiment Shield ที่ผมออกแบบขึ้นมาเพื่อใช้เริ่มต้นเรียนคู่กับ PIC Get Start 8 บอร์ด โดยผมได้เอาแบบอย่างมาจากที่นี่ครับ Demo Shield แล้วปรับแก้เล็กน้อยพร้อมกับเพิ่มจุดเชื่อมต่อจอ LCD 16x2 หรือ 20x4 เข้าไป เพื่อให้เกิดประโยชน์มากยิ่งขึ้น โดยในส่วนของสวิตซ์จะมีอยู่สองตัวซึ่งต่ออยู่กับขา RB0 และ RB1 ซึ่งสวิตซ์ทั้งสองตัวนี้จะไม่มีตัวต้านทานต่อพูลอัพไว้ เพราะตั้งใจจะใช้ weak-pull up ในตัวไมโครคอนโทรลเลอร์ ซึ่งความสามารถนี้จะมีอยู่บน PORTB ของ PIC16F1938 นี้  นอกจากนี้ยังมี LED อีกสี่ดวงซึ่งจากในรูปที่ 1 จะเห็นว่าต่ออยู่กับ PORTB ขา RB<2:5> ซึ่งเราจะใช้ในการแสดงผลลัพธ์ของการกดสวิตซ์

                                                 รูปที่ 1 schematic of Experiment shield

เริ่มต้นสร้างโปรเจ็ค
            ในการเขียนโปรแกรมเราจะสร้างโปรเจ็คใหม่ใน MPLABX ก่อนแล้วใช้ MCC ในการ config และ generate code เริ่มต้นให้กับเรา โดยสามารถดูได้จากวิดีโอด้านล่างนี้ครับ  สำหรับคนที่เพิ่งมาเริ่มต้นกับบทความนี้สามารถดูวิธีการสร้างโปรเจ็คกับ MPLABX และ MCC รวมถึงการใช้งานโปรแกรม ds30loaderGUI ได้จากบทความก่อนหน้านี้ครับ



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

     LED4_Toggle();  
     __delay_ms(400);  
     if(S1_GetValue()==LOW){  
       __delay_ms(20);     //debounce  
       while(S1_GetValue()==LOW);   //wait for S1 released  
       __delay_ms(20);     //debounce  
       LED1_Toggle();  
     }  
     if(S2_GetValue()==LOW){  
       __delay_ms(20);     //debounce  
       while(S2_GetValue()==LOW);   //wait for S1 released  
       __delay_ms(20);     //debounce  
       LED2_Toggle();  
     }  
                   สองบรรทัดแรกการสั่งให้ LED4 กระพริบทุกๆ 400 ms จากนั้นจึงมาอ่านค่าจาก สวิตซ์ S1 โดยเมื่อยังไม่ได้กดสวิตซ์ S1 จะมีค่าเป็น logic 1 หรือ HIGH เพราะมี internal weak-pull up resistor ต่ออยู่กับ Vdd
แต่เมื่อกดสวิตซ์ค่าที่อ่านได้จะมีค่าเป็น logic 0 หรือ LOW เพราะถูกช็อตลงกราวด์ ดังนั้นเมื่อเราเขียนโค้ดอ่านค่าแล้วได้เท่ากับ LOW หมายความว่ามีการกดสวิตซ์ เราก็จะเข้ามาสั่ง Toggle LED ดวงที่เราต้องการ เพื่อเป็นสิ่งแสดงให้รู้ว่าไมโครคอนโทรลเลอร์สามารถอ่านค่าจากสวิตซ์ได้ถูกต้อง  ส่วนคำสั่ง __delay_ms(20); เราจำเป็นต้องใส่ไว้เพื่อป้องกัน bounce ที่เกิดขึ้นจากหน้าสัมผัสของสวิตซ์แตะกันตอนเรากด หรือปล่อย สวิตซ์ หากไม่มีการ de-bounce ไมโครคอนโทรลเลอร์จะอ่านค่าจากสวิตซ์ผิดพลาดได้ นอกจากนี้ผมยังได้เพิ่มคำสั่ง while(S2_GetValue()==LOW); ลงไปเพื่อให้แน่ใจว่าได้ถูกกดและปล่อยเรียบร้อยแล้วจึงจะทำการ Toggle LED
            สำหรับสวิตซ์ S2 ก็จะมีการทำงานในลักษณะเดียวกันกับสวิตซ์ S1 ตัวอย่างโค้ดอันนี้เป็นการอ่านค่าจากสวิตซ์แบบง่ายๆนะครับ ยังไม่เหมาะเอาไปใช้ในงานจริงต่างๆเพราะยังมีจุดอ่อนหลายอย่าง  อย่างเช่นถ้าเรากดสวิตซ์ใดสวิตซ์หนึ่งค้างไว้ LED1 จะไม่กระพริบ เพราะไมโครคอนโทรลเลอร์จะค้างอยู่ที่คำสั่งรอให้สวิตซ์ปล่อยอยู่ หรือถ้าลองกดสวิตซ์หลายๆครั้งเราจะสังเกตุเห็นว่าสวิตซ์ตอบสนองช้าในบางครั้งหรืออาจไม่ตอบสนองต่อการกดครั้งนั้นๆก็ได้ นี่เพราะเราใช้ __delay_ms(400); สำหรับ Toggle LED4 นั่นเอง   

อ่านค่าสวิตซ์ในอินเตอร์รัพของไทม์เมอร์
            จากปัญหาในตัวอย่างที่ผ่านมาผมจึงอยากนำเสนออีกหนึ่งทางแก้ไขคือการอ่านค่าของสวิตซ์ในอินเตอร์รัพของไทม์เมอร์ ในที่นี้จะใช้ Timer0 ให้ over flow แล้วเกิด interrupt ทุกๆ 1ms. ใน interrupt service routine ของ Timer0 ก็จะทำการอ่านค่าสวิตซ์พร้อมทั้งมีการ de-bounce สวิตซ์ด้วย เมื่ออ่านค่าได้แล้วจึงส่งผ่านตัวแปรโกลบอลให้โปรแกรม main สั่งงาน Toggle LED ตามที่กดสวิตซ์ต่อไป  ส่วน LED4 ก็ให้กระพริบทุกๆ 400 ms. ใน interrupt ของ Timer0 เช่นกัน ลองมาดูตัวอย่างในวิดีโอข้างล่างนี้ได้เลยครับ


                  เราเริ่มต้นโดย copy โปรเจ็คเดิมแล้วตั้งชื่อใหม่ จากนั้นก็ใช้ MCC ในการคอนฟิค Timer0 ให้ได้อย่างที่เราต้องการ หลังจากได้โค้ดที่ generated ใหม่แล้วเราก็ลบโค้ด polling เดิมทิ้งซะ แล้วไปเขียนโค้ดในส่วนของ Timer0 interrupt ซึ่งอยู่ในไฟล์ tmr0.c  โดยเราจะประกาศตัวแปรที่จำเป็นต้องใช้ตามตัวอย่างข้างล่างนี้
 #define Key_Delay    20  
 /**  
  Section: Global Variables Definitions  
 */  
 volatile uint8_t timer0ReloadVal, KEYPAD_Debounce_Counter;  
 volatile uint8_t KeyReturn;  
 volatile enum Key{ NoPressed, Debounce1, Pressed, StillPressed, Debounce2} KeyState = NoPressed;  


และไปเพิ่มเติม state-machine code ในฟังก์ชัน TMR0_ISR(void) ซึ่งหากลองไล่ทำความเข้าใจจะพบว่าไม่ยากเลย ลองดูครับ ตัวอย่างโค้ดตามนี้ครับ
 switch( KeyState ){  
     case NoPressed:  
       if( !RB0 | !RB1 ){     //any keys is pressed  
         KEYPAD_Debounce_Counter = 0;  
         KeyState = Debounce1;  
       }  
       break;  
     case Debounce1:  
       KEYPAD_Debounce_Counter++;  
       if( KEYPAD_Debounce_Counter >= Key_Delay ){  
         KeyState = Pressed;  
       }  
       break;  
     case Pressed:  
       if( !RB0 ){           //S1 is pressed  
         KeyReturn = 1;  
         KEYPAD_Debounce_Counter = 0;  
         KeyState = StillPressed;  
       }  
       else if( !RB1 ){        //S2 is pressed  
         KeyReturn = 2;  
         KEYPAD_Debounce_Counter = 0;  
         KeyState = StillPressed;  
       }  
       else{              //no key pressed  
         KeyReturn = 0;  
         KeyState = NoPressed;  
       }  
       break;  
     case StillPressed:  
       if( !RB0 | !RB1 ){      //still pressed  
       }  
       else if( RB0 | RB1){    //already released  
         KeyState = Debounce2;  
       }  
       break;  
     case Debounce2:  
       KEYPAD_Debounce_Counter++;  
       if( KEYPAD_Debounce_Counter >= Key_Delay ){  
         KeyState = NoPressed;  
       }  
       break;  
     default: break;  
   }//switch( KeyState )  


                     ในตัวอย่างผมจะอ่านค่าสวิตซ์จากขาของไมโครคอนโทรลเลอร์โดยตรง ไม่ได้ใช้มาโคร ‘S1_GetValue()’ เหมือนในตัวอย่างแรกครับ เพราะมันสั้นดี แต่มีความหมายเหมือนกันนะครับ ทำงานได้เหมือนกันเลย


สุดท้ายเราก็มาเขียนโค้ดใน main เช็คว่าสวิตซ์ตัวไหรถูกกดก็ Toggle LED ที่สัมพันธ์กัน แต่เราจำเป็นต้องบอก compiler ว่าตัวแปร KeyReturn ที่เราจะเช็คนั้นอยู่ภายนอกไฟล์ main ไม่เช่นนั้นเวลาคอมไพล์แล้วจะ error ไม่ผ่านครับ  อย่างนี้ครับ

 extern volatile uint8_t KeyReturn;  

อ้อแล้วอย่าลืมเคลียร์ค่าของ KeyReturn ด้วยนะครับ ตัวอย่างโค้ดใน main ครับ

 switch( KeyReturn )  
     {  
       case 1: LED1_Toggle(); KeyReturn = 0; break;  
       case 2: LED2_Toggle(); KeyReturn = 0; break;  
       default: break;  
     }  

ผลการทดสอบจะเห็นว่าเวลากดสวิตซ์มีการตอบสนองที่รวดเร็ว ถึงแม้จะกดค้างไว้ LED4 ก็ยังกระพริบตามจังหวะ แต่จะมีปัญหาหากกดสวิตซ์พร้อมกันทั้ง 2 ตัว ทางแก้อาจจะต้องเขียน state-machine แยกของแต่ละสวิตซ์รวมถึงใช้ตัวแปรแยกจากกันด้วย ลองคิดกันดูนะครับ

วันพฤหัสบดีที่ 24 มีนาคม พ.ศ. 2559

MPLABX MCC UART

             
                 ห่างหายไปนานสำหรับบล็อกนี้ พึ่งจะได้มีเวลาว่างมาเขียนอีกครั้ง /(^_^)\
สำหรับบทความนี้ตั้งใจจะเขียนถึงโมดูล USART ในตัว PIC เพราะค่อนข้างมีประโยชน์มากสำหรับงานทางด้าน embedded อย่างที่ผมทำอยู่ อย่างแรกคือมันเป็น user interface ที่สามารถสื่อสารกับตัวเราได้ เพราะสำหรับ micro controller ซึ่งไม่มีหน้าจอแล้ว มันค่อนข้างลำบากที่จะรู้ว่าตอนนี้เจ้า micro controller กำลังทำอะไรอยู่ แต่เราสามารถที่จะใช้โมดูล USART นี้เป็น user interface สื่อสารกับเราผ่านทางหน้าจอคอมพิวเตอร์ โดยอาศัยเจ้า usb-to-serial module ที่มาพร้อมกับบอร์ด PIC Get Start 8 และโปรแกรม terminal บนคอมพิวเตอร์ของเราเป็นตัวกลางและตัวช่วย เราก็จะสามารถสื่อสารกับ PIC ด้วยภาษาอังกฤษแล้ว นอกจากนี้เจ้าโมดูล USART ยังเป็นการสื่อสารแบบ serial ที่นิยมกันมาก สามารถใช้กับโมดูลต่างๆได้มากมาย ไม่ว่าจะเป็น Bluetooth-to-serial ที่เอาไว้ติดต่อกับ android phone ผ่านทาง Bluetooth เพื่อสื่อสารหรือทำหุ่นยนต์ไว้เล่นก็ได้ หรือจะเป็น WiFi-to-serial ที่เอาไว้ติดต่อทาง WiFi ก็เห็นมีใช้กันอยู่มากมาย ดังนั้น USART จึงน่าที่จะเรียนรู้ไว้ใช้งานเป็นอันดับแรก

                โมดูล USART นั้นมาจากคำว่า Universal Synchronous Asynchronous Receiver Transmitter ซึ่งหมายถึงว่ามันสามารถสื่อสารได้ทั้งแบบ Synchronous และแบบ Asynchronous โดยแบบ Synchronous จะมีขาสำหรับส่ง(transmit), ขาสำหรับรับ(receive), และขาสัญญานนาฬิกา(clock) เพื่อ sync กับข้อมูล  ส่วนแบบ Asynchronous จะมีเฉพาะขาส่ง กับขารับ เท่านั้น ซึ่งที่ผมจะใช้ก็จะเป็นแบบ Asynchronous ที่มีเพียง 2 ขาเท่านั้นและโมดูลส่วนใหญ่ก็จะใช้แบบ Asynchronous ทั้งนั้นเท่าที่ผมเคยใช้

          ในการเขียนโปรแกรมเพื่อใช้งานโมดูล USART นั้นจำเป็นที่จะต้องเข้าไปคอนฟิค รีจิสเตอร์ หลายตัวของโมดูลและยังจะต้องกำหนดว่าจะให้โมดูลสื่อสารกับคอมพิวเตอร์ที่ความเร็วเท่าไหร่ หรือก็คือการกำหนด baud rate นั่นเองโดยทั่วไปจะใช้งานที่ baud rate = 9600 แล้วใช้ data bits = 8 ไม่มี Parity bit ใช้ stop bit = 1 และไม่มี hardware flow control คราวนี้ถ้าไปดูจาก datasheet จะเห็นว่าค่อนข้างวุ่นวายพอควรเลยทีเดียวสำหรับมือใหม่ หรือถึงแม้มือเก่า (แก่) อย่างผมยังพลาดออกบ่อยไปเพราะ mcu ตัวใหม่ๆจะทำงานที่สปีดสูงๆ โมดูล USART ก็สามารถสื่อสารที่ baud rate สูงๆก็เลยต้องมีรีจิสเตอร์สำหรับคอนฟิคเพิ่มขึ้น นอกจากนี้ขา Tx กับ Rx ก็อาจจะ multiplex กับโมดูลอื่นอีกก็ต้องคอยเช็ค คอยเลือกให้ถูกต้อง เฮ้อเหนื่อยกว่าจะทำให้มันสื่อสารได้  แต่หลังจากมี MCC มาช่วยในการคอนฟิคแล้ว ทุกอย่างมันง่ายขึ้นเยอะเลยทีเดียวครับ แค่เลือก clock สำหรับ mcu ให้ถูกต้องจากนั้นก็มาคอนฟิคโมดูล USART ว่าจะใช้ baud rate เท่าไหร่ มันก็จะคำนวณค่าที่จะใช้ให้เสร็จสรรพ แถมคำนวณค่าผิดพลาดให้เรารู้ด้วยว่าอยู่ในช่วงที่ยอมรับได้หรือไม่ นอกจากนี้ก็ยังมีออปชั่นให้เลือกว่าจะใช้เป็นแบบ polling หรือแบบ interrupt ทั้งฝั่งส่งและรับ ถ้าเลือกเป็นแบบ interrupt มันจะสร้างเป็นแบบ circular buffer ให้เราเสร็จสรรพทั้งฝั่งส่งและรับ สามารถกำหนดขนาดของ buffer ได้ด้วยนะครับ  เมื่อก่อนนี้จะทำ circular buffer สำหรับ USART นี่ผมต้องหาจากเน็ทให้วุ่นวายไปหมด แล้วส่วนใหญ่ก็ไม่ค่อยเวิคส์เพราะไม่เข้าใจที่หามาได้   อันสุดท้ายสำหรับใครที่อยากใช้ printf กับ USART ก็สามารถเลือกให้ MCC สร้างฟังก์ชัน putch() ที่ printf จะเรียกใช้งานได้ด้วย สะดวกมากมายเลยครับ

                ในวีดีโอได้แสดงการสร้างโปรเจ็คและใช้ MCC เช็ทค่าโมดูล Timer0 ให้เกิด interrupt ทุกๆ 500 ms เพื่อ toggle LED จากนั้นจึงคอนฟิคโมดูล USART  ให้สามรถรับค่าและส่งค่ากับ PC ได้ โดยกำหนด baud rate ที่ 9600 และใช้ polling นอกจากนี้ก็เลือกให้สามารถใช้งาน printf() ได้เพื่อความสะดวก จากนั้นจึงโปรแกรม hex file ลง PIC16F1938 ผ่านทาง ds30loaderGUI โดยตัว ds30loaderGUI นี้มีออปชั่นให้เลือกว่าหลังจาก upload ไฟล์เสร็จแล้วก็จะออโตเมติคเปิดแท็ป terminal ให้เราเลย ซึ่งถ้าเรากำหนด baud rate ที่ถูกต้องไว้ก่อนแล้วมันก็จะแสดงข้อความที่เราโปรแกรมไว้ขึ้นมาให้เห็นทันที ไม่ต้องไปหาโปรแกรม terminal ตัวอื่นมาเปิด มาคอนเน็ค ให้เสียเวลากันอีกแล้วครับ เมื่อโปรแกรมเริ่มทำงานเราก็จะเห็นข้อความที่ PIC ส่งมาให้เราพิมพ์ตัวอักษรอะไรก็ได้ลงไป จากนั้นตัว PIC มันจะ echo กลับมาบอกว่าเราพิมพ์ตัวอะไรลงไป ในขณะที่ LED ก็กระพริบช้าๆ เพื่อบอกให้เรารู้ว่าตัว PIC มันกำลังทำงานอยู่นั่นเอง ลองทดลองกันดูนะครับ  ถ้าต้องการแนะนำอะไรก็คอมเม้นมาได้นะครับ

วันศุกร์ที่ 18 กันยายน พ.ศ. 2558

ดีบัคโค๊ดใน MPLABX, Debug Code with MPLABX

           ในบทความที่แล้วเราได้ทำการเขียนโค้ดกระพริบ LED ง่ายๆแล้วใช้โปรแกรม ds30loaderGUI โปรแกรม .hex ไฟล์ลงไปในไมโครคอนโทรลเลอร์ผ่านทาง bootloader มาแล้ว คราวนี้เราจะมาใช้เครื่องมือที่เรียกว่า programmer/debugger มาทำการดีบัคโค้ดกันครับ            ตัว programmer/debugger ที่รองรับไมโครคอนโทรลเลอร์ของไมโครชิพจะมีอยู่ด้วยกันหลายตัว เริ่มต้นจากตัวเล็กที่สุดและราคาถูกที่สุดก็คือ PICkit3 ซึ่งรองรับการดีบัคแบบพื้นฐาน และเพียงพอกับการใช้งานดีบัคทั่วไป
        ถัดมาจะเป็น ICD3 ซึ่งมีความสามารถสูงกว่า PICkit3 เช่นรองรับ software breakpoint แบบ unlimited ทำให้สามารถดีบัคโค้ดที่ซับซ้อนได้ง่ายขึ้น อีกทั้งยังมีความเร็วในการโปรแกรมสูงกว่าอีกด้วย


        ตัวสุดท้ายคือ Real ICE ซึ่งก็รองรับการดีบัคทั้งหมดที่ PICkit3 และ ICD3 รองรับ แต่ความสามารถที่เหนือไปยิ่งกว่าคือ trace และ real time monitor ค่าในตัวแปรต่างๆได้แบบ real time ไม่ต้อง pause ก่อนเพื่อจะดูค่าของรีจิสเตอร์ หรือตัวแปร เหมือนอย่างที่ PICkit3 และ ICD3 ต้องทำ


หลังจากรู้จักกันแล้วว่าตัว programmer/debugger มีตัวไหนกันบ้างก็มาถึงการใช้งานเพื่อดีบัคโค้ดของเรากันแล้วครับ โดยเราจะให้โค้ดไฟกระพริบจากตัวอย่างบทความที่ผ่านมา แล้วเมื่อเรากดปุ่มดีบัค ตัว MPLABX จะโปรแกรมไฟล์ .elf/.coff ลงไปในตัวไมโครคอนโทรลเลอร์เพื่อทำหน้าที่ในการรวบรวมข้อมูลและสื่อสารกับ MPLABX ผ่านทางตัว programmer/debugger ที่เราเลือกใช้ ซึ่งในที่นี้คือ PICkit3  
                หลังจากที่เราเปิดโปรเจ็คของเราขึ้นมาแล้ว ให้ต่อ PICkit3 เข้ากับบอร์ด PIC Get Start 8 ดังรูป



จากนั้นกดปุ่มดีบัคดังแสดงในวิดีโอ MPLABX จะทำการคอมไพล์โปรเจ็คของเราอีกครั้ง แล้วจะเชื่อมต่อ กับ PICkit3 หากเป็นการเชื่อมต่อครั้งแรก อาจจะมีการอัพเดท firmware ของ PICkit3 ที่เหมาะสมสำหรับไมโครคอนโทรลเลอร์ของเรา การอัพเดทอาจใช้เวลาซัก 1 – 2 นาที อดทนรอแป็ปนึง จากนั้น MPLABX จะแจ้งว่า ไม่สามารถติดต่อกับไมโครคอนโทรลเลอร์ของเราได้ นั้นเพราะเรายังไม่ได้จ่ายไฟเลี้ยงให้กับตัวไมโครคอนโทรลเลอร์นั่นเอง  ในการทดลองครั้งนี้ผมจะใช้ PICkit3 จ่ายไฟให้กับตัวไมโครคอนโทรลเลอร์ ขั้นตอนก็ดูได้จากในวิดีโอนะครับ พอเราสามารถโปรแกรมไมโครคอนโทรลเลอร์ เราจะเห็นเมนูดีบัคแอ็คทีพขึ้นมา ซึ่งเราสามารถสั่ง RUN, Pause, Step by Step, etc. ซึ่งปุ่มคำสั่งต่างๆเหล่านี้จะใช้คู่กับการเซ็ท breakpoint ตรงตำแหน่งต่างๆที่เราต้องการให้โปรแกรมไปหยุด ร่วมกับหน้าต่าง watch ที่ใช้ดูค่าของตัวแปรต่างๆ รวมไปถึงรีจิสเตอร์ที่เราสนใจ