สรุปสั้นๆ ง่ายๆ
โปรเจค 'Christmas Carols on Arduino' นี้ มันก็คือสเก็ตช์เดียวที่บรรจุเพลงคริสต์มาสฮิตๆ ไว้ 10 เพลงเลยจ้า รายการเพลงทั้ง 10 จะถูกสุ่มลำดับใหม่ทุกครั้งที่เล่นจบรอบ งานนี้จัดมาให้แบบครบเซ็ตเลย ประกอบด้วย:
- We Wish You A Merry Christmas
- O Come All Ye Faithful
- Away In A Manger
- Ding Dong Merrily (On High)
- Good King Wenceslas
- The First Nowell
- The Holly And The Ivy
- We Three Kings
- Silent Night
- Jingle Bells
( แต่ละฟังก์ชันเพลงจะเล่นวน 2 รอบเต็มๆ นะ แต่ถ้าน้องอยากให้เล่นมากกว่านี้หรือน้อยลง ก็แค่ไปปรับค่า upper bound ใน for-loop ของฟังก์ชันเพลงนั้นๆ ง่ายๆ เอง )
สเก็ตช์นี้เป็นแบบ Plug and Play เลย แต่น้องจะมีสองทางเลือกในการเล่นงานโปรเจคนี้ -
1. ต่อวงจรลำโพงตามที่บอกไว้ด้านล่าง (ดูหัวข้อ Hardware and Schematics) ดาวน์โหลดสเก็ตช์มาแล้วอัพโหลดลงบอร์ดเลย จิบกาแฟ (หรือถ้าช่วงเทศกาลก็อาจเป็นเครื่องดื่มอื่นๆ!) แล้วนั่งฟังเพลงสบายๆ ไปเลย
หรือ
2. ต่อวงจรลำโพงเหมือนเดิม แต่ดาวน์โหลดสเก็ตช์มาแล้วลองแกะโค้ดดู โครงสร้าง เนื้อหา และเมธอดต่างๆ มันมีเทคนิคที่น่าสนใจมากๆ ตัวนึงที่อาจเอาไปใช้ในโปรเจคของน้องเองได้ในอนาคต
ถ้าน้องเลือกทางแรก ก็เยี่ยมไป งานเสร็จแล้ว จัดไปวัยรุ่น! ไม่ต้องอ่านต่อก็ได้ แต่ถ้าน้องเป็นสายชอบเจาะลึก อยากรู้ว่าโค้ดมันทำงานยังไง ตามมาดูกันต่อเลย
ส่วนฮาร์ดแวร์
ง่ายมากกก – สิ่งที่ต้องมีก็แค่ Arduino ไมโครคอนโทรลเลอร์ (รุ่นไหนก็ได้), ตัวต้านทาน (Resistor) 100 โอห์ม, บอร์ดทดลองเล็กๆ, สายจัมเปอร์ และที่ขาดไม่ได้คือลำโพง 8 โอห์ม 0.25 วัตต์ (หรือจะใช้บัซเซอร์แทนถ้ายังไม่มีลำโพงก็ได้)
วงจรต่อตามไดอะแกรมด้านล่างในหัวข้อ Schematics เลย ต่อง่ายมาก สเก็ตช์ตั้งค่าให้ใช้ Digital Output Pin 11 ในการขับลำโพง แต่ถ้าน้องอยากใช้พินอื่น ก็เลือกพินที่เหมาะสมแล้วไปเปลี่ยนค่าการ define '#define speaker' ในสเก็ตช์ตามต้องการได้เลย
ขอเตือนนิดนึงว่า อย่าเชื่อมต่อไมโครคอนโทรลเลอร์เข้ากับอะไรนอกจากลำโพง/บัซเซอร์ตามที่บอกไว้นี้โดยตรง เดี๋ยวจะพังเอาง่ายๆ นะตัวนี้
หลักการต่อวงจร: โปรแกรมตั้งค่าให้ใช้ Digital Pin 11 ในการขับลำโพงผ่านตัวต้านทาน 100 โอห์ม ซึ่งทำหน้าที่เป็น ตัวต้านทานจำกัดกระแส (Current Limiting Resistor) เพื่อป้องกันพอร์ต I/O ของ Arduino รับโหลดเกิน ขั้วบวกของลำโพงต่อเข้ากับตัวต้านทาน ส่วนขั้วลบต่อลง Ground (GND)
คำเตือนจากรุ่นพี่สายช่าง: ห้ามต่อลำโพงเข้ากับพิน Arduino โดยตรงโดยไม่มีตัวต้านทานเด็ดขาด! อิมพีแดนซ์ต่ำของลำโพงอาจทำให้กระแสไหลเกินขีดจำกัดของไมโครคอนโทรลเลอร์ (ปกติไม่เกิน 40mA ต่อพิน) ซึ่งอาจทำให้ชิพเสียหายถาวรได้ สู้งานนะน้อง
สเก็ตช์ – จุดที่ควรสังเกต
สเก็ตช์นี้พัฒนามาจากบทความ/โปรเจคเก่าที่ชื่อ 'Let's Make Music' โดยดัดแปลงเฉพาะเพื่อจัดการกับเพลย์ลิสต์ของโน้ตเพลง (เพลงคริสต์มาส) โครงสร้างสเก็ตช์ตรงไปตรงมา น่าตามอ่านได้ไม่ยากสำหรับคนที่สนใจ แต่มันมีเทคนิคหนึ่งที่เด็ดมากและน่าจดจำเป็นพิเศษ นั่นคือการใช้พอยน์เตอร์ (Pointer) ในการกำหนดและจัดการเพลย์ลิสต์ (พอยน์เตอร์ภาษา C++)
ถ้าน้องเชี่ยวชาญเรื่องพอยน์เตอร์ รู้จุดประสงค์และวิธีใช้แล้ว ก็ข้ามส่วนนี้ไปได้เลย แต่ถ้ายังไม่แน่ใจ ลองสละเวลาสักนิดมาเจาะลึกดูว่าสเก็ตช์นี้ใช้มันยังไง
มาเริ่มกันที่การออกแบบที่ใช้พอยน์เตอร์:
อย่างแรกเลย โน้ตเพลงแต่ละเพลงถูกกำหนดด้วยฟังก์ชันของมันเอง โดยฟังก์ชันเหล่านี้จะถูกวางไว้ท้ายสุดของสเก็ตช์ เพื่อไม่ให้โค้ดส่วนหลักรกและช่วยให้อ่านง่ายขึ้น
ต่อไป ในส่วนบนสุดของสเก็ตช์ เราจะประกาศ "forward references" สำหรับฟังก์ชันเพลงคริสต์มาสแต่ละอัน เพราะเราต้องการเอามาใส่ลงในอาร์เรย์เพลย์ลิสต์ของเรา (ซึ่งประกาศเป็นอาร์เรย์ของพอยน์เตอร์) เราทำขั้นตอนนี้ ก่อน การประกาศอาร์เรย์พอยน์เตอร์เพลย์ลิสต์ เพื่อให้แน่ใจว่าคอมไพเลอร์รู้จักที่อยู่ (address) ของฟังก์ชันเพลงตอนที่มันต้องการใช้แล้ว
จริงๆ เราก็ประกาศฟังก์ชันเพลงทั้งหมดไว้ข้างบนสุดเลยก็ได้นะ แต่แบบนั้นโค้ดส่วนบนจะรกตายห่า เลยใช้วิธีประกาศ forward reference แทน สะอาดตาดี
หลังจากประกาศ forward references ของฟังก์ชันเพลงแล้ว สเก็ตช์ก็จะประกาศเพลย์ลิสต์ (play_list) ซึ่งเป็นอาร์เรย์ของพอยน์เตอร์ (*) ขนาดเท่ากับจำนวนเพลง (ฟังก์ชันเพลง) ที่มีในสเก็ตช์ (ถ้าใช้ของที่มากับบอร์ดเลย จะมี 10 เพลง) จากนั้นก็กำหนดค่า (preset) แต่ละตำแหน่งในอาร์เรย์เพลย์ลิสต์ด้วยที่อยู่ของฟังก์ชันเพลงแต่ละอัน (ทำได้เพราะเราประกาศ forward reference ไว้แล้ว)
เสร็จแล้วเราก็สามารถเอาเพลย์ลิสต์ไปใช้งานได้ตามต้องการเลย เช่น สับเปลี่ยนลำดับ (สุ่ม), ไล่เรียกฟังก์ชันเพลงทีละอันจากในลิสต์ เป็นต้น
สรุปสั้นๆ มาไล่ดูโค้ดแต่ละส่วนในสเก็ตช์ที่ทำให้เราใช้เพลย์ลิสต์แบบพอยน์เตอร์ได้กัน
1. Forward References
การประกาศ Forward Reference ไปยังฟังก์ชันเพลง - ดู syntax ให้ดีนะน้อง
// Forward references for music score functions
// which are defined at end of the sketch to avoid clutter...
void we_wish_you_a_merry_christmas();
void o_come_all_ye_faithful();
void away_in_a_manger();
void ding_dong_merrily();
void good_king_wenceslas();
void the_first_nowell();
void the_holly_and_the_ivy();
void we_three_kings();
void silent_night();
void jingle_bells();
ใน C++ นะ "Forward Reference" คือการบอกคอมไพเลอร์ล่วงหน้าว่า "เฮ้ย ฟังก์ชันพวกนี้มีอยู่จริงนะ แต่อยู่ข้างหลังโค้ด" ทำให้ส่วนหัวของโค้ดเราไม่รกด้วยโค้ดโน้ตเพลงยาวเฟื้อย
2. การสร้างอาร์เรย์ของฟังก์ชันพอยน์เตอร์
การประกาศเพลย์ลิสต์ - เราประกาศอาร์เรย์พอยน์เตอร์เพลย์ลิสต์ พร้อมกำหนดค่าเริ่มต้นด้วยที่อยู่ของฟังก์ชันเพลง (ดู syntax อีกรอบ)
//
// Declare pointer array and preset with addresses
// of each of the carol score functions.
// Note that the oder of the presets is not important
// as the play_list array is constantly randomised
// by the shuffle_play_list function.
void (*play_list[num_carols])() = {
we_wish_you_a_merry_christmas,
o_come_all_ye_faithful,
away_in_a_manger,
ding_dong_merrily,
good_king_wenceslas,
the_first_nowell,
the_holly_and_the_ivy,
we_three_kings,
silent_night,
jingle_bells
};
ไวยากรณ์ในการประกาศอาร์เรย์ของพอยน์เตอร์อาจดูแปลกๆ หน่อย แต่มีสองจุดสำคัญที่ต้องจับตา:
- ตัวอักษร '
*' ที่อยู่หน้าชื่ออาร์เรย์ บอกคอมไพเลอร์ว่าอาร์เรย์ 'play_list' นี้เป็นอาร์เรย์ของพอยน์เตอร์- ตัวอักษร '
()' ที่ต่อท้าย บอกคอมไพเลอร์อีกทีว่าที่อยู่อ้างอิงพวกนี้เป็นฟังก์ชัน (ก็ forward reference ของเพลงเรานั่นแหละ) ทำให้เราสามารถเรียกใช้ฟังก์ชันที่ถูกกำหนดโดยสมาชิกในเพลย์ลิสต์ได้โดยตรง - ดูใน Main Loop ด้านล่าง
สังเกตด้วยว่าเราสามารถกำหนดค่าเริ่มต้นให้อาร์เรย์พอยน์เตอร์เพลย์ลิสต์ได้ ก็เพราะว่า เราได้ประกาศ forward references ไปยังฟังก์ชันเพลงไว้ก่อนแล้ว คอมไพเลอร์จะกำหนดค่าแต่ละตำแหน่งในอาร์เรย์เพลย์ลิสต์ด้วยที่อยู่ของฟังก์ชันเพลงที่ประกาศไว้ ลำดับที่เรากำหนดค่าเริ่มต้นไม่สำคัญ เพราะเพลย์ลิสต์จะถูกสับเปลี่ยนลำดับแบบสุ่มอยู่ตลอดเวลา
ทำไมต้องใช้โครงสร้างแบบนี้?
นี่คือกุญแจสำคัญของความยืดหยุ่น อาร์เรย์ play_list เก็บ "ที่อยู่" ของฟังก์ชันเพลงต่างๆ ไว้ การทำแบบนี้ทำให้เราสามารถ "สับเปลี่ยน" ข้อมูลในอาร์เรย์ได้ง่ายๆ เหมือนสับเปลี่ยนตัวเลขธรรมดา แต่ผลลัพธ์ที่ได้คือลำดับการเล่นเพลงที่เปลี่ยนไปจริงๆ งานเข้าสินะน้อง!
3. การทำงานของลูปหลัก
เรียบร้อย! ตอนนี้เราจัดการเพลย์ลิสต์ได้ตามใจแล้ว มาดูส่วนลูปหลักกันดีกว่า:
ลูปหลัก - ออกแบบมาให้เรียบง่ายสุดๆ จนแทบไม่น่าเชื่อ:
void loop() {
do {
// Process the play_list by selecting each of its preset and randomsised
// elements, and execute each respective music score funcion.
for (uint8_t carol = 0; carol < num_carols; carol++) {
play_list[carol](); // execute this ([carol]) music score function
wait(3); // wait a short time between carols
}
shuffle_play_list(); // randomise the play_list for the next play_list pass
wait(3); // wait a short time before playing the new playlist
} while (true);
}
ลูปหลักก็แค่ไล่เรียกฟังก์ชันเพลงแต่ละอันในเพลย์ลิสต์ — จำ syntax นี่ไว้นะตัว! เราใช้เพลย์ลิสต์เหมือนเป็นฟังก์ชันหลอกๆ เลยต้องมี ' () ' ตอนเรียก
หลังจากเล่นเพลงครบทุกอันในลิสต์แล้ว เราจะสับเปลี่ยนลำดับเพลย์ลิสต์แบบสุ่มด้วย `shuffle_play_list()` ทำให้รอบต่อไปลำดับเพลงเปลี่ยนไปเลย
ในบรรทัด play_list[carol](); โปรแกรมจะกระโดดไปที่ตำแหน่งหน่วยความจำที่เก็บโค้ดเพลงนั้น แล้วเริ่มเล่นทันที พอจบหนึ่งรอบ ฟังก์ชัน shuffle_play_list() จะสลับตำแหน่ง Address ภายในอาร์เรย์ใหม่หมด ทำให้ลำดับเพลงในรอบต่อไปเปลี่ยนไปโดยสิ้นเชิง
พอยน์เตอร์มีประโยชน์ได้อีกหลายแบบ หวังว่าจากตัวอย่างในสเก็ตช์นี้ น้องจะเห็นภาพและเอาไปประยุกต์ใช้ในงานของตัวเองได้นะ
เอาล่ะ จบเรื่องนี้แค่นี้ก่อน หวังว่าน้องจะสนุกกับเพลงคริสต์มาส และอาจจะลองเพิ่มเพลงของตัวเองลงไปในสเก็ตช์ด้วยก็ดี!
แล้วต่อยังไงดี?
ลองเพิ่มเพลงของตัวเองดูสิ! หรือถ้าอยากลุยเรื่องดนตรีบน Arduino ต่อ ลองดูบทความอื่นๆ ที่พี่เขียนไว้:
Let's Make Music - สเก็ตช์ที่รองรับโน้ตดนตรีหลายอ็อกเทฟ และเข้าใจค่าความยาวโน้ตมาตรฐาน มีฟีเจอร์ทุกอย่างเหมือนในสเก็ตช์เพลงคริสต์มาสนี้ บวกเพิ่มเติมอีกนิดหน่อย ลองดูเอกสารสรุปฟังก์ชัน (crib sheet) ของบทความนี้ ซึ่งเป็นเหมือนดัชนีค้นหาฟังก์ชันใช้ง่ายๆ
Music & Lights Workbench - สเก็ตช์ที่ออกแบบมาเพื่อชวนคนที่ไม่เคยเขียนโปรแกรมมาก่อน ให้มาลองเล่นลองเรียนรู้ผ่านการสร้างเมโลดี้และจุดไฟ LED แบบเป็นขั้นเป็นตอน