[TH] PIC18F458 Ep.5 GPIO and 7-Segments

บทความนี้เป็นการประยุกต์ใช้ GPIO ของ PIC18F458 เพื่อสั่งงานวงจรของแอลอีดี 8 หลอดที่จัดเรียงตำแหน่งกันเป็นเหมือนตัวเลขดังภาพที่ 1 โดยใช้หลอด LED จำนวน 8 หลอดมาจัดวางใหม่และเรียกว่า 7-Segment ที่สามารถนำไปประยุกต์แสดงผลตัวเลข และตัวอักษรได้อีกจำนวนหนึ่ง นอกจากนี้ บนตัวบอร์ดทดลองได้ติดตั้ง 7-Segment เอาไว้จำนวน 4 หลัก ทำให้สามารถเขียนโปรแกรมเพื่อควบคุมการแสดงผลข้อมูล 4 หลัก

ภาพที่ 1 ภาคแสดงผลด้วย 7-Segment บนบอร์ดทดลอง

7-Segment

 7-Segment มีแอลอีดีทั้งสิ้น 8 ดวง สำหรับเป็นส่วนต่าง ๆ เรียกชื่อว่า A, B, C, D, E, F, G และ DP ดังภาพที่ 2 และการต่อวงจรของโมดูลแต่ละหลักจะเป็นดังผังวงจรในภาพที่ 3

ภาพที่ 2 ส่วนต่าง ๆ ของ 7-Segment
ที่มา http://www.etteam.com/article/duino_stm32_ex01/part5.html
ภาพที่ 3 ตัวอย่างวงจรของ 7-Segment แบบ Common Anode ส่วนต่าง ๆ ของ 7-Segment
ที่มา http://www.etteam.com/article/duino_stm32_ex01/part5.html

จากภาพที่ 3 จะพบว่า ขาร่วมเป็น Vdd ซึ่งเป็นขารับแรงดันไฟฟ้ากระแสตรง นั่นหมายความว่า การจะให้หลอดติดตต้องให้ครบวงจรไฟฟ้า มีไฟเข้า ผ่านโหลด และลงกราวด์ โหลดถึงจะทำงานหรือติด ถ้าเราให้ขาที่เชื่อมต่อกับหลอด A ดึง DP เป็น 0 หมายความว่าเปลี่ยนสถานะที่พอร์ตให้เป็นกราวด์ ไฟฟ้าไหลผ่านโหลดได้ หลอดจึงสว่าง และในทางกลับกัน ถ้าเรากำหนดให้ขาของพอร์ตมีสถานะเป็น 1 ซึ่งคือเป็นไฟเหมือนกัน มันก็ไม่มีกราวด์ โหลดไม่ทำงาน หรือ หลอดไม่ติด  

วงจรการเชื่อมต่อของโมดูล 7-Segment ของบอร์ดทดลองประจำวิชาเป็นดังภาพที่ 4

ภาพที่ 4 ผังการเชื่อมต่อโมดูล 7-Segment แบบ 4 หลัก
ที่มา คู่มือบอร์ด PCK-1000

การสั่งงาน

จากภาพที่ 4 จะได้ว่า การส่งไปให้หลอด LED แต่ลตำแหน่งอันได้แก่ A, B, C, D, E, F, G และ P (หรือ DP) จะต้องต่อเข้ากับขั้ว D0, D1, D2, D3, D4, D5, D6 และ D7 โดยการส่งค่าเป็นดังนี้

  • ส่ง 1 หมายถึงต้องการให้หลอดสว่าง
  • ส่ง 0 หมายถึงต้องการให้หลอดดับ

เมื่อดำเนินการส่ง D0 ให้เป็น 1 โดยขาอื่น ๆ เป็น 0 ส่งผลให้เกิดการแสดงผลดังภาพที่ 5

ภาพที่ 5 ตัวอย่างผลลัพธ์การกำหนดให้ D0 เป็น 1 และขาอื่นเป็น 0

เมื่อดำเนินการส่ง D1 ให้เป็น 1 โดยขาอื่น ๆ เป็น 0 ส่งผลให้เกิดการแสดงผลดังภาพที่ 6

ภาพที่ 6 ตัวอย่างผลลัพธ์การกำหนดให้ D1 เป็น 1 และขาอื่นเป็น 0

เมื่อดำเนินการส่ง D2 ให้เป็น 1 โดยขาอื่น ๆ เป็น 0 ส่งผลให้เกิดการแสดงผลดังภาพที่ 7

ภาพที่ 7 ตัวอย่างผลลัพธ์การกำหนดให้ D2 เป็น 1 และขาอื่นเป็น 0

เมื่อดำเนินการส่ง D3 ให้เป็น 1 โดยขาอื่น ๆ เป็น 0 ส่งผลให้เกิดการแสดงผลดังภาพที่ 8

ภาพที่ 8 ตัวอย่างผลลัพธ์การกำหนดให้ D3 เป็น 1 และขาอื่นเป็น 0

เมื่อดำเนินการส่ง D4 ให้เป็น 1 โดยขาอื่น ๆ เป็น 0 ส่งผลให้เกิดการแสดงผลดังภาพที่ 9

ภาพที่ 9 ตัวอย่างผลลัพธ์การกำหนดให้ D4 เป็น 1 และขาอื่นเป็น 0

เมื่อดำเนินการส่ง D5 ให้เป็น 1 โดยขาอื่น ๆ เป็น 0 ส่งผลให้เกิดการแสดงผลดังภาพที่ 10

ภาพที่ 10 ตัวอย่างผลลัพธ์การกำหนดให้ D5 เป็น 1 และขาอื่นเป็น 0

เมื่อดำเนินการส่ง D6 ให้เป็น 1 โดยขาอื่น ๆ เป็น 0 ส่งผลให้เกิดการแสดงผลดังภาพที่ 11

ภาพที่ 11 ตัวอย่างผลลัพธ์การกำหนดให้ D6 เป็น 1 และขาอื่นเป็น 0

เมื่อดำเนินการส่ง D7 ให้เป็น 1 โดยขาอื่น ๆ เป็น 0 ส่งผลให้เกิดการแสดงผลดังภาพที่ 12

ภาพที่ 12 ตัวอย่างผลลัพธ์การกำหนดให้ D7 เป็น 1 และขาอื่นเป็น 0

กรณีที่ต้องการแสดงให้เป็นตัวเลข สามารถกำหนดให้ D0, D1, D2, D3, D4, D5, D6 และ D7 มีค่าดังตารางในภาพที่ 13

ภาพที่ 13 ตารางการกำหนดค่าเพื่อแสดง 7-Segment เป็นตัวเลข
ที่มา คู่มือบอร์ด PCK-1000

กรณีมีหลายหลัก

จากภาพที่ 4 จะพบว่าการเลือกให้หลักใดติดนั้นจะกำหนดด้วยการให้ DS1, DS2, DS3 หรือ DS4 มีค่าเป็น 1 ดังนั้นถ้าต้องการแสดงเลข 0 บนหลักที่ 1 และ 3 จะต้องกำหนดค่าของ D0, D1, D2, D3, D4, D5, D6, D7, DS1, DS2, DS3 และ DS4 ดังนี้

  • D0 เป็น 1
  • D1 เป็น 1
  • D2 เป็น 1
  • D3 เป็น 1
  • D4 เป็น 1
  • D5 เป็น 1
  • D6 เป็น 0
  • D7 เป็น 0
  • DS1 เป็น 1
  • DS2 เป็น 0
  • DS3 เป็น 1
  • DS4 เป็น 0

ดังนั้น ได้ข้อสรุปว่า

  1. ต้องการให้หลอดใดติด (สว่าง) ให้กำหนดค่าเป็น 1
  2. ต้องการให้หลอดใดดับให้กำหนดค่าเป็น 0
  3. ต้องการให้หลักใดแสดงผลให้กำหนดค่าของหลักนั้นเป็น 1
  4. ไม่ต้องการให้หลักใดแสดงผลให้กำหนดค่าของหลักนั้นเป็น 0

ตัวอย่างโปรแกรม

ตัวอย่างโปรแกรมสำหรับการใช้งาน 7-Segment ประกอบด้วยการควบคุมแบบ 1 หลัก และ 4 หลัก โดยแต่ละแบบจะเป็นการสั่งให้หลอดแอลอีดีแสดงผลเพื่อให้การวิ่งวนกับการแสดงเป็นตัวเลข ดังนี้

วิ่งๆๆ

ตัวอย่างแรกเป็นการสั่งให้หลอดแอลอีดีตำแหน่ง A, B, C, D, E, F, G และ DP หรือ P แสดงครั้งละ 1 ตำแหน่งเพื่อทดสอบการถูกต้องของการเชื่อมต่อวงจร และวงจรภายในโมดูล 7-Segment ว่าทำงานได้ถูกต้องหรือไม่ มีหลอดใดขาดหรือเสื่อมสภาพหรือไม่ โดยการเชื่อมต่อระหว่างขาของไมโครคอนโทรลเลอร์และวงจร 7 Segment ได้เชื่อมต่อเอาไว้ดังนี้

  1. ขา RC0 ต่อเข้ากับ D0
  2. ขา RC1 ต่อเข้ากับ D1
  3. ขา RC2 ต่อเข้ากับ D2
  4. ขา RC3 ต่อเข้ากับ D3
  5. ขา RC4 ต่อเข้ากับ D4
  6. ขา RC5 ต่อเข้ากับ D5
  7. ขา RC6 ต่อเข้ากับ D6
  8. ขา RC7 ต่อเข้ากับ D7
  9. ขา RD0 ต่อเข้ากับ DS1

หลักการทำงานของโปรแกรม ประกอบไปด้วย 2 ส่วน คือ

  1. setup()
    1. กำหนดให้ค่าที่ส่งออกพอร์ต C เป็น 0b00000001 หรือ 0x01 เพื่อให้หลอดตำแหน่ง A สว่าง
    2. กำหนดให้ขาของพอร์ต C ทำหน้าที่เป็น Output ทั้งหมด
    3. กำหนดให้ขาของพอร์ต D เป็น Output ทั้งหมดเหมือนกับพอร์ต C
  2. loop()
    1. ปิดการเลือกหลักที่ 1 ของชุดโมดูล 7Segment ด้วยการกำหนดให้ RD0 เป็น 0
    2. โอนข้อมูลรูปแบบของการแสดงผลไปให้พอร์ต C
    3. เลื่อนบิตของรูปแบบของการแสดงผลไปทางซ้าย 1 บิต
    4. ถ้าค่าที่เกิดจากการเลื่อนบิตทำให้ตัวแปรเป็น 0 ให้กำหนดค่าแปรเป็น 0x01 หรือ 0b00000001 เพื่อเริ่มการแสดงผลที่ตำแหน่ง A ใหม่อีกครั้ง
    5. เปิดการแสดงผลด้วยการกำหนดให้ RD0 เป็น 1
    6. หน่วงเวลาเพื่อให้มองเห็นผลของการแสดงผล

ตัวอย่างโปรแกรมเป็นดังนี้ และตัวอย่างของการทำงานเป็นดังคลิปที่ 1

#pragma config OSC = HS 
#pragma config OSCS = ON
#pragma config PWRT = OFF 
#pragma config BOR = ON  
#pragma config BORV = 25 
#pragma config WDT = OFF  
#pragma config WDTPS = 128 
#pragma config STVR = ON 
#pragma config LVP = ON 
#pragma config CP0 = OFF 
#pragma config CP1 = OFF
#pragma config CP2 = OFF
#pragma config CP3 = OFF
#pragma config CPB = OFF
#pragma config CPD = OFF 
#pragma config WRT0 = OFF  
#pragma config WRT1 = OFF
#pragma config WRT2 = OFF  
#pragma config WRT3 = OFF   
#pragma config WRTC = OFF
#pragma config WRTB = OFF 
#pragma config WRTD = OFF   
#pragma config EBTR0 = OFF 
#pragma config EBTR1 = OFF 
#pragma config EBTR2 = OFF
#pragma config EBTR3 = OFF 
#pragma config EBTRB = OFF   

#include <xc.h>

#define _XTAL_FREQ 20000000 

unsigned char seg_pattern = 0b00000001;

void setup() {
    TRISC = 0x00; // 0b00000000
    TRISD = 0x00; // 0b00000000
}

void loop() {
    // disable
    PORTD = 0b00000000;
    // transfer
    PORTC = seg_pattern;
    seg_pattern <<= 1;
    if (seg_pattern == 0x00) {
        seg_pattern = 0x01;
    }
    // enable
    PORTD = 0b00000001;
    // delay
    __delay_ms(100);   
}

void main(void) {
    setup();
    while (1) {
        loop();
    }
    return;
}
คลิปที่ 1 ตัวอย่างของการวิ่ง ๆๆ

นับ 0 ถีง 9

จากภาพที่ 13 ซึ่งเป็นตารางค่าที่ส่งให้ D0, D1, D2, D3, D4, D5, D6 และ D7 เพื่อให้การแสดงผลเป็นตัวเลข 0 ถึง 9 เมื่อนำมาเขียนโปรแกรมจะได้ดังนี้ และตัวอย่างของการแสดงผลเป็นตามคลิปที่ 2

#pragma config OSC = HS 
#pragma config OSCS = ON
#pragma config PWRT = OFF 
#pragma config BOR = ON  
#pragma config BORV = 25 
#pragma config WDT = OFF  
#pragma config WDTPS = 128 
#pragma config STVR = ON 
#pragma config LVP = ON 
#pragma config CP0 = OFF 
#pragma config CP1 = OFF
#pragma config CP2 = OFF
#pragma config CP3 = OFF
#pragma config CPB = OFF
#pragma config CPD = OFF 
#pragma config WRT0 = OFF  
#pragma config WRT1 = OFF
#pragma config WRT2 = OFF  
#pragma config WRT3 = OFF   
#pragma config WRTC = OFF
#pragma config WRTB = OFF 
#pragma config WRTD = OFF   
#pragma config EBTR0 = OFF 
#pragma config EBTR1 = OFF 
#pragma config EBTR2 = OFF
#pragma config EBTR3 = OFF 
#pragma config EBTRB = OFF   

#include <xc.h>

#define _XTAL_FREQ 20000000 


unsigned char number_patterns[] = {
    0x3F,
    0x06,
    0x5B,
    0x4F,
    0x66,
    0x6D,
    0x7D,
    0x07,
    0x7F,
    0x6F
};


void setup() {
    TRISC = 0x00; // 0b00000000
    TRISD = 0x00; // 0b00000000
}

void loop() {
    for (int i=0; i<10; i++) {
        // disable
        PORTD = 0b00000000;
        // transfer
        PORTC = number_patterns[i];
        // enable
        PORTD = 0b00000001;
        // delay
        __delay_ms(1000);   
    }
}

void main(void) {
    setup();
    while (1) {
        loop();
    }
    return;
}
คลิปที่ 2 ตัวอย่างการนับ 0 ถึง 9

วิ่งข้ามหลัก

ตัวอย่างที่ 3 เป็นการกำหนดให้ค่าของตำแหน่ง G ของหลักที่ 1 ถึง 4 แสดงเรียงกันไปเสมือนเป็นการวิ่งไปมาระหว่างหลัก (ดังคลิปที่ 3) ด้วยการกำหนดให้ PORTC มีค่าเป็น 0b01000000 และ PORTD มีค่าตาม dsx ที่มีการให้ค่าเป็น 0x01, 0x02, 0x04 และ 0x08 เรียงกันไปด้วยการเลื่อนบิตไปทางซ้ายในแต่ละรอบการทำงาน โดยโค้ดของโปรแกรมเป็นดังนี้

#pragma config OSC = HS 
#pragma config OSCS = ON
#pragma config PWRT = OFF 
#pragma config BOR = ON  
#pragma config BORV = 25 
#pragma config WDT = OFF  
#pragma config WDTPS = 128 
#pragma config STVR = ON 
#pragma config LVP = ON 
#pragma config CP0 = OFF 
#pragma config CP1 = OFF
#pragma config CP2 = OFF
#pragma config CP3 = OFF
#pragma config CPB = OFF
#pragma config CPD = OFF 
#pragma config WRT0 = OFF  
#pragma config WRT1 = OFF
#pragma config WRT2 = OFF  
#pragma config WRT3 = OFF   
#pragma config WRTC = OFF
#pragma config WRTB = OFF 
#pragma config WRTD = OFF   
#pragma config EBTR0 = OFF 
#pragma config EBTR1 = OFF 
#pragma config EBTR2 = OFF
#pragma config EBTR3 = OFF 
#pragma config EBTRB = OFF   

#include <xc.h>

#define _XTAL_FREQ 20000000 

unsigned char dsx=0x01;

void setup() {
    TRISC = 0x00; // 0b00000000
    TRISD = 0x00; // 0b00000000
}

void loop() {
    // disable
    PORTD = 0b00000000;
    // transfer
    PORTC = 0b01000000;
    // enable
    PORTD = dsx;
    // move next
    dsx <<= 1;
    if (dsx == 0x10) {
        dsx = 0x01;
    }
    // delay
    __delay_ms(100);  
}

void main(void) {
    setup();
    while (1) {
        loop();
    }
    return;
}
คลิปที่ 3 ตัวอย่างวิ่งข้ามหลัก

แสดงตัวเลขแบบ 4 หลัก

ตัวอย่างสุดท้ายเป็นการแสดงตัวเลขของทั้ง 4 หลัก โดยแบ่งการทำงานเป็นดังนี้

  1. เริ่มต้นให้ทุกหลักเป็น 0
  2. แสดงหลักที่ 4 วนจาก 0 ไปถึง 9
  3. แสดงหลักที่ 3 วนจาก 0 ไปถึง 9
  4. แสดงหลักที่ 2 วนจาก 0 ไปถึง 9
  5. แสดงหลักแรกวนจาก 0 ถึง 9

โค้ดโปรแกรมเป็นดังนี้ และตัวอย่างคลิปของการทำงานเป็นดังคลิปที่ 4

#pragma config OSC = HS 
#pragma config OSCS = ON
#pragma config PWRT = OFF 
#pragma config BOR = ON  
#pragma config BORV = 25 
#pragma config WDT = OFF  
#pragma config WDTPS = 128 
#pragma config STVR = ON 
#pragma config LVP = ON 
#pragma config CP0 = OFF 
#pragma config CP1 = OFF
#pragma config CP2 = OFF
#pragma config CP3 = OFF
#pragma config CPB = OFF
#pragma config CPD = OFF 
#pragma config WRT0 = OFF  
#pragma config WRT1 = OFF
#pragma config WRT2 = OFF  
#pragma config WRT3 = OFF   
#pragma config WRTC = OFF
#pragma config WRTB = OFF 
#pragma config WRTD = OFF   
#pragma config EBTR0 = OFF 
#pragma config EBTR1 = OFF 
#pragma config EBTR2 = OFF
#pragma config EBTR3 = OFF 
#pragma config EBTRB = OFF   

#include <xc.h>

#define _XTAL_FREQ 20000000 

unsigned char number_patterns[] = {
    0x3F,
    0x06,
    0x5B,
    0x4F,
    0x66,
    0x6D,
    0x7D,
    0x07,
    0x7F,
    0x6F
};

void setup() {
    TRISC = 0x00; // 0b00000000
    TRISD = 0x00; // 0b00000000
}

void loop() {
    int idx=0;
    /////////////////// step 1 ////////////////////////
    // disable
    PORTD = 0b00000000;
    // transfer
    PORTC = number_patterns[0];
    // enable
    PORTD = 0b00001111;
    /////////////////// step 2 ////////////////////////
    for (idx=0; idx<10; idx++) {
        for (int counter=0; counter<10; counter++) {
            // disable
            PORTD = 0b00000000;
            // transfer
            PORTC = number_patterns[0];
            // enable
            PORTD = 0b0000111;
            __delay_ms(10);  
            // transfer
            PORTC = number_patterns[idx];
            // enable
            PORTD = 0b0001000;
            // delay
            __delay_ms(10);  
        }
    }
    /////////////////// step 3 ////////////////////////
    for (idx=0; idx<10; idx++) {
        for (int counter=0; counter<10; counter++) {
            // disable
            PORTD = 0b00000000;
            // transfer
            PORTC = number_patterns[0];
            // enable
            PORTD = 0b0001011;
            __delay_ms(10);  
            // transfer
            PORTC = number_patterns[idx];
            // enable
            PORTD = 0b0000100;
            // delay
            __delay_ms(10);  
        }
    }
    /////////////////// step 4 ////////////////////////
    for (idx=0; idx<10; idx++) {
        for (int counter=0; counter<10; counter++) {
            // disable
            PORTD = 0b00000000;
            // transfer
            PORTC = number_patterns[0];
            // enable
            PORTD = 0b0001101;
            __delay_ms(10);  
            // transfer
            PORTC = number_patterns[idx];
            // enable
            PORTD = 0b0000010;
            // delay
            __delay_ms(10);  
        }
    }
    /////////////////// step 5 ////////////////////////
    for (idx=0; idx<10; idx++) {
        for (int counter=0; counter<10; counter++) {
            // disable
            PORTD = 0b00000000;
            // transfer
            PORTC = number_patterns[0];
            // enable
            PORTD = 0b0001110;
            __delay_ms(10);  
            // transfer
            PORTC = number_patterns[idx];
            // enable
            PORTD = 0b0000001;
            // delay
            __delay_ms(10);  
        }
    }
}

void main(void) {
    setup();
    while (1) {
        loop();
    }
    return;
}

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

คลิป 4 การแสดงแบบหลายหลัก

สรุป

จากบทความนี้ผู้อ่านได้เรียนรู้ วิธีการควบคุมโมดูล 7 Segment ทั้งในแบบ 1 หลักและหลายหลัก โดยจะพบว่าหลักการทำงานเหมือนกับการส่งข้อมูล 0 หรือ 1 ไปยังขาของไมโครคอนโทรลเลอร์ แต่ด้วยวิธีที่เหมาะสมกับวงจรกลับส่งผลให้การแสดงผลเปลี่ยนแตกต่างกันดันไป ด้วยเหตุนี้ การเขียนโปรแกรมจึงต้องมีความเข้าใจถึงหลักการทำงานของวงจรนั้น จึงจะสามารถสั่งงานการทำงานให้ได้อย่างที่ต้องการ และที่สำคัญ การคิดจะให้ผลลัพธ์เป็นอย่างไรแล้วสามารถทำได้หรือไม่นั้นล่วนต้องอาศัยประสบการณ์การได้ทดลองทำมาก่อนจึงจะตัดสินได้ว่าแนวคิดที่สร้างขึ้นมานั้นสามารถทำได้จริงด้วยวิธีการใด หรือไม่สามารถทำได้ด้วยเหตุผลใด สุดท้าย ขอให้สนุกกับการเขียนโปรแกรมครับ

(C) 2022, โดย อ.อนุชาติ บุญมาก, อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ

ปรับปรุงเมื่อ 2022-02-18, 2022-03-15