title: Rolling the Dice, Part 2 - การควบคุมลูกเต๋าผ่าน Shift Register 74HC595
การทอดลูกเต๋า ภาคที่ 2: ก้าวข้ามขีดจำกัดของจำนวน Pin ด้วย SIPO IC
บทความนี้เป็นภาคต่อของโปรเจกต์เครื่องทอดลูกเต๋าอิเล็กทรอนิกส์ โดยใน ภาคแรก (Part 1) เราได้เรียนรู้วิธีการสร้างลูกเต๋า 1 ลูก โดยใช้ LED 7 ดวงจัดวางตามรูปแบบแต้มลูกเต๋ามาตรฐาน (Pip Patterns) และเชื่อมต่อเข้ากับขา Digital I/O ของบอร์ด Arduino โดยตรง
อย่างไรก็ตาม ในภาคที่ 2 นี้ เราจะยกระดับโปรเจกต์ให้มีความเป็นมืออาชีพและขยายขีดจำกัดได้มากขึ้น (Scalability) ด้วยการนำไอซี Serial-Parallel Input/Output (SIPO) หรือที่รู้จักกันดีในชื่อ 74HC595 Shift Register มาใช้งาน ซึ่งช่วยให้เราสามารถควบคุมลูกเต๋ากี่ลูกก็ได้ โดยใช้ขาสัญญาณจากไมโครคอนโทรลเลอร์เพียง 3 ขาเท่านั้น!
ความแตกต่างระหว่างภาค 1 และ ภาค 2
ในภาคแรก LED แต่ละดวงจะถูกจองด้วยขา I/O ของ Arduino หนึ่งขา ซึ่งหากเราต้องการเพิ่มลูกเต๋าเป็น 2 หรือ 3 ลูก จำนวนขา I/O จะไม่เพียงพออย่างรวดเร็ว (ลูกเต๋า 2 ลูกต้องใช้ถึง 14 ขา)
ในภาคที่ 2 เราจึงนำ 74HC595 IC ขนาด 8 บิต มาเป็นตัวช่วยหลัก:
- การประหยัดทรัพยากร: ใช้สายสัญญาณเพียง 3 เส้น (Data, Clock, Latch) ในการขับเคลื่อน Output ได้ไม่จำกัดผ่านการต่อแบบ Cascade (Daisy Chaining) หรือการนำไอซีมาต่อพ่วงกันไปเรื่อยๆ
- การจัดสรรทรัพยากร: ในโปรเจกต์นี้เราใช้ลูกเต๋า 2 ลูก จึงใช้ 74HC595 จำนวน 2 ตัว (ตัวละ 8 พอร์ต) โดย LED 7 ดวงของลูกเต๋าแต่ละลูกจะเชื่อมต่อกับไอซี 1 ตัว ทำให้เหลือพอร์ตว่าง 1 พอร์ต (พอร์ต QA) ในไอซีแต่ละตัว ซึ่งเราออกแบบซอฟต์แวร์ให้รองรับส่วนนี้ไว้แล้ว
- การเขียนโปรแกรม: เราเปลี่ยนจากการใช้
digitalWrite()ตรงๆ มาเป็นการใช้งานผ่านไลบรารีez_SIPO8_libซึ่งช่วยให้การจัดการข้อมูลแบบบิต (Bit Manipulation) และการส่งข้อมูลไปยังไอซีหลายๆ ตัวพร้อมกันทำได้ง่ายและเป็นระเบียบมากขึ้น
เจาะลึกการทำงานของ 74HC595 และการแสดงผลแต้ม
ไลบรารี ez_SIPO8_lib จะทำงานในลักษณะ "Virtual Memory" โดยจะจำลองสถานะของพอร์ตต่างๆ ไว้ในรูปแบบ Bit-mapped Bytes (1 ไบต์ต่อลูกเต๋า 1 ลูก) เมื่อเรากำหนดแต้มที่ต้องการเสร็จแล้ว จึงค่อยสั่งการส่งข้อมูล (Transfer หรือ xfer) ออกไปยังตัวไอซีจริงในขั้นตอนเดียว
เพื่อให้ LED แสดงผลเป็นรูปแต้มลูกเต๋าที่ถูกต้อง เราต้องกำหนดรูปแบบบิต (Binary Pattern) ให้สัมพันธ์กับการวางตำแหน่ง LED บน Breadboard ดังนี้:
- หน้า 1 แต้ม (1 pip):
0b00010000 - หน้า 2 แต้ม (2 pips):
0b01000100 - หน้า 3 แต้ม (3 pips):
0b00111000 - หน้า 4 แต้ม (4 pips):
0b10101010 - หน้า 5 แต้ม (5 pips):
0b10111010 - หน้า 6 แต้ม (6 pips):
0b11101110
ข้อสังเกต: เราไม่ได้ใช้งานพอร์ต QA (บิตขวาสุดหรือ LSB) ดังนั้นบิตสุดท้ายจะเป็น 0 เสมอ และหากเราต้องการแสดงแต้ม 2 เราจะสั่งงานที่บิต 2 (QC) และบิต 6 (QG) เพื่อให้ LED ดวงที่ตรงกันติดสว่าง
ภาพรวมการออกแบบฮาร์ดแวร์ (Hardware Design)
โครงสร้างหลักยังคงเน้นความเรียบง่ายแต่ทรงพลัง โดยมีปุ่มกด (Push Button) หนึ่งปุ่มทำหน้าที่สั่งงาน "ทอดลูกเต๋า" เมื่อกดปุ่ม ระบบจะทำการสุ่มค่าและแสดงผลผ่านชุด LED ทั้ง 2 ชุด
ในส่วนของวงจร 74HC595 เราจะต่อแบบอนุกรม (Cascaded) โดยขา Serial Output (Q7') ของไอซีตัวแรกจะเชื่อมเข้ากับ Serial Input (DS) ของไอซีตัวถัดไป วิธีนี้ทำให้ข้อมูลจาก Arduino ไหลผ่านไอซีทุกตัวในแถวเดียวกันได้โดยใช้สัญญาณนาฬิกา (Clock) และสัญญาณ Latch ชุดเดียวกัน
ในการต่อวงจรจริง แนะนำให้ดูที่ฉลากกำกับในไดอะแกรม (SIPO IC1 และ SIPO IC2) เพื่อป้องกันความสับสน เนื่องจากมีการเชื่อมต่อสายไฟจำนวนมากระหว่าง LED และพอร์ต Output (QB-QH) ของไอซี
การวิเคราะห์ซอฟต์แวร์และลอจิกของโปรแกรม
ในการใช้งานโปรเจกต์นี้ จำเป็นต้องติดตั้งไลบรารี 2 ตัวที่พัฒนาโดย Ron Bentley เพื่อความสะดวกและเสถียรภาพของระบบ:
- ez_switch_lib: สำหรับจัดการปุ่มกดและการทำ Debouncing (ลดสัญญาณรบกวนจากการกดปุ่ม) ดาวน์โหลดที่นี่
- ez_SIPO8_lib: สำหรับควบคุม Shift Register 74HC595 ดาวน์โหลดที่นี่
1. การประกาศและเริ่มต้นระบบ (Initiation)
#include <ez_switch_lib.h>
#include <ez_SIPO8_lib.h>
Switches my_switches(1); // จองพื้นที่สำหรับ 1 ปุ่มกด
SIPO8 my_SIPOs(num_dice, num_SIPO_timers); // จองพื้นที่สำหรับ SIPO IC 2 ตัว
ในส่วน setup() เราจะสร้าง "Bank" ของไอซี ซึ่งเปรียบเสมือนการรวมไอซี 2 ตัวเข้าเป็นกลุ่มเดียวกันเพื่อให้จัดการได้ง่ายขึ้นด้วยคำสั่งเดียว
2. ระบบตรวจสอบการทำงาน (Heart Beat Monitor)
วิศวกรฝังตัวที่ดีควรมีตัวบ่งชี้สถานะระบบ เราได้ใส่ฟังก์ชัน heart_beat เพื่อกะพริบไฟ LED ติดบอร์ด (Pin 13) ที่ความถี่ 1Hz หากไฟนี้ยังกะพริบอยู่ แสดงว่าลอจิกของโปรแกรมยังทำงานปกติ ไม่มีการค้าง (Hanging)
3. ฟังก์ชันสำคัญในการทำงาน
clear_pips(): ทำหน้าที่รีเซ็ตค่าใน Bank ทั้งหมดให้เป็น LOW (0) และส่งข้อมูลออกไปเพื่อดับ LED ทุกดวงannounce_throw(): สร้างลูกเล่นก่อนแสดงผลแต้มจริง (Strobe effect) โดยจะวิ่งไฟ LED เป็นแพทเทิร์นสั้นๆ เพื่อให้ผู้ใช้รู้สึกว่าลูกเต๋ากำลังถูกทอดอยู่throw_dice(): นี่คือหัวใจของโปรแกรม ฟังก์ชันนี้จะ:- สุ่มค่าแต้มลูกเต๋า 1-6 สำหรับลูกที่ 1 และ 2
- นำค่าที่สุ่มได้ไปดึง Pattern จาก Array
pip_patterns - เขียนค่าลงในหน่วยความจำเสมือนของไอซีแต่ละตัว (
set_bank_SIPO) - สั่งให้ส่งข้อมูลออกไปที่ขาจริงพร้อมกัน (
xfer_bank) ด้วยโหมดMSBFIRST
// ตัวอย่างลอจิกการทอดลูกเต๋า
uint8_t die_face;
die_face = random(1, 104640) % faces_per_die; // สุ่มแต้ม
my_SIPOs.set_bank_SIPO(bank_id, die_1, pip_patterns[die_face]); // เตรียมข้อมูลลูกที่ 1
die_face = random(1, 104640) % faces_per_die;
my_SIPOs.set_bank_SIPO(bank_id, die_2, pip_patterns[die_face]); // เตรียมข้อมูลลูกที่ 2
my_SIPOs.xfer_bank(bank_id, MSBFIRST); // แสดงผลออกทาง LED ทันที
4. Main Loop
ลูปหลักของโปรแกรมทำหน้าที่เพียงอย่างเดียวคือคอยเฝ้าดูสถานะของปุ่มกดผ่านไลบรารี my_switches หากมีการกดและปล่อยปุ่ม โปรแกรมจะกระโดดไปทำงานที่ฟังก์ชัน throw_dice() ทันที
void loop() {
do {
heart_beat(); // เลี้ยงสัญญาณชีพของระบบ
if (my_switches.read_switch(switch_id) == switched) {
throw_dice(); // เมื่อกดปุ่ม ให้ทอดลูกเต๋า
}
} while (true);
}
บทสรุป
ความท้าทายที่สุดของโปรเจกต์ภาค 2 นี้ไม่ใช่การเขียนโค้ด แต่เป็นการจัดการสายไฟ (Wiring) ระหว่าง 74HC595 และ LED ให้ถูกต้องตามผังวงจร อย่างไรก็ตาม เมื่อคุณเข้าใจหลักการทำงานของ Shift Register แล้ว คุณจะพบว่ามันเป็นเครื่องมือที่ทรงพลังมากในการขยายขีดความสามารถของไมโครคอนโทรลเลอร์
หากคุณต้องการนำไปประยุกต์ใช้กับโปรเจกต์อื่นๆ ที่ต้องคุม Output จำนวนมาก ไลบรารี ez_SIPO8_lib ยังมีความสามารถอีกมากมาย เช่น การตั้ง Timer อิสระในแต่ละพอร์ต หรือการสร้าง Bank หลายชุดที่มีขนาดต่างกัน ซึ่งคุณสามารถศึกษาเพิ่มเติมได้จากเอกสารประกอบของไลบรารีครับ!