בפוסט הקודם חיברנו ארדואינו לרמקול, בתיווך פוטנציומטר לשליטה בעוצמת הקול. כתבנו קוד בסיסי ביותר להשמעה של צליל קצר מדי שניה, רק כדי לבדוק שהעסק עובד. בפוסט הנוכחי אציג את הקוד המלא של תוכנה, ש"יושבת" על אותה חומרה בדיוק אך מנגנת את "יונתן הקטן", "London bridge" – ובשינויים קלים, גם מנגינות נוספות.
תו יחיד
ראשית, אנחנו זקוקים לקוד שמסוגל להשמיע תווים. לצורך הפשטות, נתמקד (אם הבנתי נכון את המינוחים) בסולם דו מז'ור באוקטבה הראשונה – או בעברית, חמשת הקלידים שעליהם מנגנים את "יונתן הקטן" והשניים שמימינם.
כזכור, פקודת הארדואינו tone משמיעה צליל בתדר שאנחנו קובעים. חיפוש זריז באינטרנט יעלה את התדרים התואמים לדו, רה, מי ושאר התווים בסולם שלנו. במקום לכתוב אותם כל פעם מחדש בקוד התוכנה, יצרתי מערך שיכיל אותם. התדרים מעוגלים למספר השלם הקרוב ביותר:
int freq[7] = {440, 494, 262, 294, 330, 349, 392};
ההגדרה הזו היא גלובלית לתוכנית, ולכן נמצאת מחוץ לפונקציות setup ו-loop. "אבל רגע," יזעקו מביני עניין, "המספר הראשון תואם לתו 'לה', השני ל'סי', ואילו התדר של 'דו' מופיע שלישי!" ובכן, זה נכון – ועשיתי את זה לצורך תאימות עם הנוטציה הלועזית, שקוראת לדו C, לרה D וכן הלאה. אחרי סול (G) מגיע לה שמסומן כ-A, וסי הוא B. במערך הנוכחי, מסיבה שתובהר בהמשך, העדפתי לשמור על הסדר האלפביתי דווקא.
ועכשיו, הנה קוד לפונקציה שמשמיעה תו. הפונקציה מקבלת כפרמטר תו ASCII יחיד, מפענחת אותו לפי הנוטציה הלועזית שהזכרנו ומשמיעה את התו המוזיקלי המתאים. אם הפרמטר אינו תואם שום תו שהוגדר, הפונקציה משתיקה את הרמקול.
void playNote(char note) { if ((note >= 'A') && (note <= 'G')) tone(TONE_PIN, freq[note - 65], NOTE_LENGTH_MS); else { noTone(TONE_PIN); delay(NOTE_LENGTH_MS); } }
הקבוע TONE_PIN הוגדר בראש התוכנית ומציין את החיבור אליו מחובר הרמקול. הקבוע NOTE_LENGTH_MS מציין את משך הצליל (או הדממה). החישוב note – 65 הוא זה שממיר את קוד ASCII למיקום במערך התווים: ב-ASCII, הקוד של האות A הוא 65, של B הוא 66 וכן הלאה. עכשיו אתם מבינים למה התחלתי את המערך ההוא ב-A?
רצף תווים
עם הפונקציה הזו, יצירה של מנגינה – רצף תווים – היא פשוטה מאד. ניקח מחרוזת (מערך תווים), נעבור עליה תו אחד כל פעם וננגן אותו, עם הפסקה קבועה בין התווים. הנה מחרוזת לדוגמה, ופונקציית הנגינה:
char LONDON[] = "GAGFEFG DEF EFG GAGFEFG D G EC"; void playMelody(char *c) { while (*c != 0) { playNote(*c++); delay(NOTE_INTERVAL_MS); } }
יש לנו כאן קבוע חדש – NOTE_INTERVAL_MS – ומצביע (c) שמקודם בצעד אחד כל פעם לצורך ניגון התווים. במחרוזת יש פה ושם תווי רווח: בפונקציה הקודמת, כפי שראינו, תו שאינו בין A ל-G אינו מוכר ולכן יושמע כדממה, מה שייתן לנו למעשה הפסקה גדולה יותר בין תווים.
הדבר האחרון שהוספתי לתוכנית הוא קוד שבוחר בכל פעם מחדש, אקראית, איזו מנגינה לנגן. אפשר, אמנם, להשתמש בפונקציה random, אבל בשביל הכיף בחרתי במשהו אקראי באמת – קלט שמגיע מחיבור אנלוגי (בדוגמה זו: A0) שלא מחובר לכלום. הפקודה הבאה מבודדת את הביט הנמוך של הקלט, ובוחרת לפיו בין שתי המנגינות שמוגדרות בקוד:
if (analogRead(A0) & 1) playMelody(YONATAN); else playMelody(LONDON);
בסרטון בפוסט הקודם חיברתי ל-A0 חוט חשמל ירקרק, שישמש כ"אנטנה" וייצור קצת יותר רעש ואקראיות בחיבור.
את הקוד המלא של התוכנה אפשר להוריד מכאן. מכיוון שזה הפוסט הראשון שאני מעלה בענייני קוד, אשמח לפידבקים: זה היה ברור? מסובך מדי? פשוט מדי? הערות, שאלות והצעות לשיפורים יתקבלו בברכה בתגובות!
היי, הקוד הראשוני שנתת עבד והשמיע צליל במשך כל שניה, למה כשאני משתמש בקודים של השירים הם לא מתנגנים ובהעברה יש הודעת כשל?
"הודעת כשל" לא אומר לי הרבה… אני צריך פירוט מדויק.
הקובץ עם הקוד כבר לא קיים יותר
נכון, כנראה הלך לאיבוד במהלך האבולוציה של הבלוג… אני אחפש אותו בגיבויים שלי וכשאמצא אודיע בתגובה נוספת. תודה!
זהו, הקובץ חזר למקומו הטבעי – אפשר לנסות שוב.
לא הבנתי בפונקציה הראשונה מהו המשתנה note שהגדרת כchar. הוא לא משתנה לאורך כל הפונקציה ולכן לא הבנתי איך כל התווים יכולים להיות מוגדרים במצב כזה?
בפונקציה השניה כנל לא הבנתי מהו המשתנה c* ולמה אי אפשר לשנות אותו (רושם שגיאה במחרוזת יהונתן)
את note אפשר לשנות.
תודה
note הוא פרמטר (משתנה שקיים רק בתוך הפונקציה, והערך שלו נקבע כל פעם מחדש על ידי הקוד שקורא לפונקציה). לגבי מצביעים, זה עסק קצת מורכב בשביל תגובה לפוסט… בתור התחלה, חפש חומר על מצביעים ב-C ברשת.
הי עידו,
הבלוג שלך מעולה והוא פשוט כיף טהור!!
בתור אחד עם אפס ניסיון בארדואינו (מן הסתם כמו רוב קוראי הבלוג) הייתי שמח להתחיל להתעסק בעצמי עם הרכיבים ולהתחיל לעשות מה שאתה מתאר.
הבעיה היא שבכל פוסט אתה מוסיף עוד ועוד רכיבים (LED-ים, רמקול וכו'). הייתי שמח אם היית יכול לרכז בבת-אחת את כל הרכיבים שאתה מתכנן להשתמש בהם בזמן הקרוב, כך שאוכל לעשות קנייה אחת מרוכזת.
תודה
תודה מיקי!
הבקשה שלך קצת בעייתית, כי אני עצמי לא תמיד יודע מה ישמש אותי ומה לא, ולפעמים מנצל דברים שאני מוצא באקראי בתוך פסולת אלקטרוניקה (כמו הרמקול ההוא). יש בתכנון עוד כמה פוסטים על רכיבים בסיסיים שכל חובב צריך – על מטריצות וכבלים כבר כתבתי – ומעבר לזה, קשה להבטיח 🙂
בפרויקטים למתחילים אני אשתדל להיצמד למינימום (דברים שאפשר למצוא בקיטים), או לפחות להציע גרסאות בסיסיות יותר.
יותר נכון אי אפשר להוריד את ה* מהמצביע, למה זה?
אולי תרשום את הקוד בתוך "תיבת קוד" על מנת שהייה קצת קריא יותר. לא שהייתה לי בעיה לקרוא, אבל שאלת !!
בכיף – ברגע שאני אבין איך בדיוק עושים את זה (התג code לא עבד כמו שצריך, אני אשחק עם זה עוד קצת). תודה!
[עדכון: הנה, סודר. כל מה שהייתי צריך זה להיזכר בתג pre ! ]
הי עידו, לא נראה מסובך מדי, הבעיה היחידה היא המעברים בין אנגלית לעברית ורוחב השורות (או שאולי סתם טעית באיות), מה שגורם לTONE_PIN להיות כתוב פתאום כNOTE_PIN…
בקיצור, עדיף להוריד את הקוד מהלינק שלך ואז להביט בו מול הביאורים שלך.
🙂
לא, זו היתה סתם שגיאת הקלדה 🙂 תיקנתי, תודה.
הרעיון המקורי היה, למען הסדר הטוב, לקרוא לקבוע של החיבור ההוא בשם של הפקודה ש"כותבת" אליו – Tone. לפי אותו הגיון, קבועים שקשורים לתווים מוזיקליים יכללו את המילה Note… אבל מה לעשות שהמילים האלה כל כך דומות.