← C Programming on Bare-metal STM32 Microcontrollers — מחזור 1

#2 (2025-03-17)

טוען נגן…

תמלול

## Agenda

אוקיי, אז בואו נדבר על האג'נדה להיום.

בסדר, אז היום אנחנו עושים את מה שבעצם רציתי שנעשה בשיעור הקודם, אבל לא היו בורדים, אז היום אנחנו פותחים פרויקט על ה-board.

אצלי יש פה board, אז אני אראה שני בורדים, ואם תיתקלו בבאג שגם אני נתקלתי בו, אז אני אראה לכם איך יכולים לפתור אותו. אבל בכל מקרה, ליתר ביטחון, עדיין עדכן איך לפתור את הבאג.

אז לפני הכול, בוא נפתח workspace חדש, אוקיי? ונעבור על ה... Switch Workspace, נבחר ב-Other, אוקיי? אחר כך Browse.

אז ה-repository שלנו, מה שנקרא ה-base repository זה path איפה שעשינו את ה-clone, זה Bare-metal STM Programming 32. ואחר כך אנחנו לתוך Lesson 1 בוחרים את התיקייה הזאתי, וזה יהיה ה-workspace שלנו לשיעור, נעשה Launch. לכם זה יקח קצת זמן, אצלי זה כבר פתוח. ומה שנסגר ונפתח,

היי עידו, מה נשמע?

סבבה.

אני רואה שגם יובל, מה קורה יובל? אנחנו כבר מתחילים.

שלום, שלום, שומעים אותי?

כן.

כל השאר, אני חושב שלא פגשתם את עידו ויובל, שאר לדעתי היו בשיעור, נכון? הראשון.

טוב, בינתיים שיפתח לנו ה-workspace, אז זה מה שיש לנו השיעור, זה נראה מעט אבל יש פה הרבה מאוד. אז בואו נתחיל ב-MCU Hello World.

לכל אחד מאיתנו יש את ה-board שלו, למי שיש ולמי שאין זה בסדר גמור.

ועל ה-board עצמו, אני אתן פה דוגמה, אז זה ה-Discovery Kit, זה מה שאני משתמש, לא יודע אם אתם רואים, אז יש את ה-part number, אז STM32F411E-DISCO בגלל ה-Discovery, אבל גם יש לו ארבעה לדים בצבעים.

פה יש Nucleo, שזה board אחר, אז ה-part number שלו זה NUCLEO-L073RZ, בסדר?

אז אחרי שכולנו מאופסים על ה... אה רגע, נעבור לאג'נדה קודם. אז תתאפסו בינתיים על ה-part number שלכם כי אנחנו נצטרך אותו עוד רגע ואני אגיד מה אנחנו עושים.

היום אנחנו יוצרים פרויקט על ה-target. ה-target הכוונה על ה-board, או אם יש למישהו board ספציפי עם MCU שהוא לא סטנדרטי, אז גם את זה אני אראה איך עושים.

נעשה Hello World ישירות מה-MCU. בעצם נעשה... נעשה redirection ל-stdout, שזה לא בדיוק stdout כמו שאנחנו מכירים ב-Windows או ב-Linux, זה בעצם ה-stdout שאנחנו מקבלים כספרייה מ-STM. אנחנו נראה את זה עוד רגע, אז אנחנו עושים לו redirection בחזרה לתוך ה-terminal של ה-STM32Cube, כדי שנוכל להדפיס דברים מבפנים ולקנפג באמצעות זה. אז זה אחד.

אחר כך אנחנו נמשיך עוד קצת עם C, והפעם אנחנו מעלים את הרמה, אני אראה איך אנחנו בעצם כותבים לתוך רגיסטרים, מה האופרציות, נדבר קצת על complex data types, על structures, bitfields ו-unions.

אחרי זה נעשה קצת project organization, איך אנחנו מתכננים פרויקט, ונעבור על נושא שביקשתם אז הקדמתי אותו לשיעור, אז קומפילציה, אסמבלי ו-linking.

אחר כך נדבר על מה זה ELF ואיך אנחנו בעצם משתמשים ב-ELF כדי לדבג דברים ולראות את האסמבלי של התוכנית שלנו.

## Creating a Project on Target

אז אחרי כל ההקדמה, בואו נתחיל. פתחנו את ה-STM32, עברנו ל-workspace הנוכחי, הראיתי איך: Switch Workspace ו-Other אם זה לא נמצא כאן.

אחר כך אנחנו רוצים בעצם לפתוח. יש פה פרויקט מוכן, אבל הפרויקט הזה, כמו שאתם רואים, הוא ספציפי ל-MCU שלי, STM32F414VE.

אז איך אנחנו פותחים כל אחד ל-MCU שלו?

אז אנחנו עושים ככה: New, New STM32 Project, נותנים לו לפתוח את ה-Target Selector.

ומפה אנחנו יכולים לעשות אחד משניים: או להכניס את ה-part number, את ה-part number או model number, אני לא זוכר מה מהם זה המספר החיצוני של החברה.

אז הנה רגע, נפתח מכאן את השני.

או שאנחנו יכולים לקחת את ה-MCU number ולהכניס לכאן, אז במקרה שלי, אני אראה לכם איך זה קורה, אבל אני בוחר ב-Board Selector למרות שאני חושב שמבחינתנו זה אותו דבר.

אז STM32F411...

בור, יש איזה אמולטור שאפשר לעבוד איתו גם?

בגדול אפשר לעבוד עם QEMU, אבל זה קצת סיפור לקנפג את זה. כאילו אם אתה רוצה לקחת על עצמך לעבוד עם QEMU ובעצם להכניס לו את כל הפרמטרים, אני לא בטוח אם ה-QEMU עובד כאילו כ-bare metal. אני יודע שאפשר לשים Linux על ה-STM32 באיזה צורה מוזרה כזאת, וכן לעבוד עם QEMU, אבל זה סיפור. אמולטור של זה, אני... משהו נפל פה? אתם רואים את המסך, נכון?

כן.

אמולטור של... איך קוראים לזה? ניתן לחפש.

סתם, נשבר לי החיבור ב-board, אז אני מתלבט אם ללכת עכשיו או לחכות להפסקה.

לא יודע מה להגיד לך. באמת אני לא יודע. בכל מקרה אתה יכול להסתכל ואחר כך לעשות שוב, ותנסה קודם כול לשחק.

אז קיצור, ה-board שלי זה... ה-MCU שלי זה F414VET... 64, שש כנראה. שש U.

קיצור, אפשר לבחור ככה את הזה ואז פשוט להמשיך הלאה. אני הולך על Board Selector כדי להיות בטוח. אז ה-board שלי, אמרנו שהוא גם STM32, STM32F411E-DISCO, אוקיי? אז אתם רואים שהוא מופיע כאן למטה, בואו נגדיל את זה שתוכלו לראות כמו שצריך.

אז ה-board הזה ספציפית נמצא כאן, סימנתי אותו בכוכב כדי שיהיה לי. ככה הוא גם נראה בעולם האמיתי, רק בכחול. אני חושב שהייתה לי גרסה קודמת שלו, היא הייתה באמת בירוק והלדים לא היו בצבעים, אז זה בגדול ה-board שאני משתמש בו. יש פה עוד כמה.

אם אנחנו רוצים להסתכל על ה-datasheet, אפשר ישר ללחוץ כאן והוא מוריד את ה-datasheet עצמו.

בהמשך אנחנו נצטרך את ה-technical reference manual, אז הוא גם מופיע כאן ב-Docs, פשוט כרגע נעל לי את המסך כי הוא מוריד.

סליחה, זה ה-datasheet, בעצם מראים לי מה יש כאן, יש פה לד אחד ירוק, אדום, עוד לד, בגדול ארבעה לדים בדבר הזה. יש לנו SRAM של 512 Flash, אני לא בטוח מה הסוג של ה-Flash.

וזה ה-package, בעצם package זה ה-פינים של ה-MCU עצמו. אם אני עכשיו רוצה לייצר board, אז אני צריך לשים בו pads שמתאימים ל-package הזה כדי שיהיה אפשר להלחים אותו. כל ה-features שיש, אפשר לעבור על המסמך הזה, אין פה יותר מדי כי datasheet זה בסופו של דבר לא מספיק להרבה.

מה שכן מעניין זה ה-TRM שלנו. TRM זה קיצור של Technical Reference Manual.

זה ה-User Manual, גם כן נמצא כאן.

וכשנגיע לשלב הזה שבו אנחנו מדברים על ה-TRM, אז על ה-Reference Manual, אנחנו נעשה כאן. אבל כרגע אנחנו יוצרים פרויקט.

אז את הפרויקט אני בוחר את ה-board עצמו. אם תסתכלו, ה-mounted device זה ה-MCU שהכנסתי קודם.

אז אני בוחר את זה ועכשיו אני קורא לו בשם, אז Hello_Class.

C אנחנו הולכים לכתוב, אנחנו הולכים להפוך את זה ל-Executable, ואנחנו לא משתמשים ב-STM32Cube project בקוד-ג'נרטור שלו, אנחנו הולכים לכתוב בעצמנו. ואחר כך נעשה Finish, והוא בינתיים יצור את הפרויקט.

אנחנו נמשיך לכאן. אז הכנסנו את ה-part number, את ה-part number עצמו. אנחנו יכולים להוריד את המסמך הרלוונטי. אחר כך אנחנו יוצרים את הפרויקט, ונעשה לבנות אותו.

אז איך אנחנו בונים? זה הפרויקט עצמו, בואו נעשה לו Clean ליתר ביטחון, ונעשה לו Build, אוקיי?

אז ה-Build הצליח, אנחנו רואים שהוא בונה פה עם איזשהו GCC אחר מה-GCC שלמדנו, זה איזשהו GCC שבא built-in והוא ספציפי ל-MCU שאנחנו משתמשים בו. אני חושב שדיברנו או בשיעור הזה או בקורס של Linux, תתקנו אותי אם אני טועה, מה זה אומר.

כמו שאתם רואים, אלה קבצים אחרים. אז יש לנו את ה-main, עוד מעט נסתכל, נמצא ב-Core. sys_calls שזה בעצם ה-system calls, מה שהמערכת הפעלה כביכול, או יותר נכון מה שה-libc שאנחנו עובדים איתו נותן לנו, אז זה ה-system calls. מי שבקורס של Linux, ה-system calls שלנו בקורס של Linux נמצאים בתוך glibc שמשם אנחנו משתמשים בהם. אז פה יש איזשהו קובץ עם system calls ממש פשוטים, כל מיני אימפלמנטציות שנמצאות, וחלק מהם אנחנו אפילו נדרוס היום. ו-sysmem שזה בעצם הניהול של הזיכרון.

אוקיי, אז בואו נלך לתוך ה-Core ול-main ונסתכל מה אנחנו רואים פה. אז יש לנו כזה דבר, יש לנו פה איזשהו warning. בעצם warning לא מפיל לנו את הקומפילציה, כי זה רק אזהרה, אבל כדאי להתייחס לזה.

ויש לנו פה איזשהו forever loop כזה שהוא לא נגמר, כדי שה-MCU שלנו לא יגיע למצב של halted שהוא נעצר, ואחר כך צריך לעצור אותו ולהתחיל אותו מחדש. אז לרוב מסיימים את התוכניות עם איזשהו לופ אינסופי. `for (;;)` זה כמו לעשות `while (1)`, זה בדיוק אותו דבר.

## Floating Point Unit (FPU)

אוקיי, אז בנינו, עכשיו אנחנו רוצים להעיף פה את האזהרה, את ה-warning הזה, כי באמת לא מעניין אותנו ה-warning.

אז מה זה FPU? FPU זה Floating Point Unit.

ב-MCUs שהם מגרסה R4, 7, 33, אז יש להם built-in embedded בתוך ה-chip איזשהו רכיב שנקרא Floating Point Unit. הוא בעצם עושה את החיבור והחיסור ואת כל הניהול של floating point בתוך המודול שלנו.

זאת אומרת שכל השברים אנחנו עושים עם זה. לרוב אנחנו לא עובדים בשברים במיקרו-קונטרולרים כי זה תופס הרבה מקום וחבל. הרבה פעמים תראו פתרונות שחלק מהמשתנה שלכם יהיה בעצם ייעודי ל-floating point, ואז בעצם לעשות floating point בצורה של בבסיס 2.

אנחנו כרגע לא מתעסקים בזה, ואפילו נראה קצת איך עושים floating point, אבל שתדעו שזה [לא ברור].

שזה good practice לא להשתמש ב-floating point במיוחד במיקרו-בקרים מאוד קטנים.

אוקיי, אז בעצם ה-floating point זו בעצם איזושהי יחידה שבעזרתה אנחנו יכולים לעשות על החומרה, בצורה חומרתית: חיבור, כפל, חילוק וגם חיסור. ובעצם הוא הרבה יותר מהיר מלעשות את זה ב-software. אם אתם עושים את זה ב-software, זה המון פעולות שצריכים לעשות ב-assembly, ופה אני פשוט נותן ליחידה את הפעולה ושתי כתובות אם אני זוכר נכון, והיא פשוט מחזירה לי את התוצאה בתוך איזשהו register ואני קורא אותה. זה מה שקורה כשעובדים עם floating point unit.

אז למה אנחנו רוצים להשתמש בזה? אנחנו רוצים להשתמש בזה בגדול כדי לחסוך זמן וכדי להוריד את ה-overhead מה-CPU, בעצם לשחרר אותו מפעולות כבדות.

אז איזה מעבדים של Cortex-M יש להם? יש להם את ה-M4 ואת ה-M7. אני עובד עם M4. יש לי פה גם M0. M3 אין לי פה, אבל בגדול שתדעו שמי שיש לו M0 גם לא יכול לעשות את ה-Hello World כמו שזה. אבל לא משנה, אנחנו בהמשך נעבור כבר ישר ל-LED-ים, ואנחנו לא נצטרך.

## Disable FPU Warning

אז איך אנחנו נפטרים מה-warning הזה? מה שה-warning הזה בעצם אומר: תקשיב, יש לך פה את החומרה הזאתי ואתה לא התחלת אותה, אז אל תנסה להשתמש בה, כי אז זה לא יהיה מוגדר.

אז מה שאנחנו בעצם נעשה, זה אנחנו פשוט נגיד לו שאנחנו רוצים לעשות את כל ה-floating points בצורה ידנית.

אז איך אנחנו עושים את זה? רשמתי כאן איך להגיע לשם. אז על הפרויקט עצמו, שאנחנו עושים hello_class, אז אנחנו נעשה Project -> Properties.

נעשה expand ל-C/C++ Build -> Settings, ומפה אנחנו צריכים להגיע ל-MCU settings.

אוקיי, אז פה יופיע לכם ה-MCU שלכם. פה אתם תוכלו לדעת איזה סוג של ארכיטקטורת ARM ה-MCU שלכם.

ואם אנחנו נגיע ל-floating point, אז אנחנו כבר נמצאים כאן. אז יש פה שתי אפשרויות. אז לא להשתמש בכלל ב-Floating Point Unit, זה דבר שיעזור לנו, ולהגיד לו שאם אתה עושה פעולות, תשתמש ב-software implementation שלנו.

אז בעצם כשהוא יג'נרט לנו את הפקודות ל-GCC, אז הוא כבר ישר יגלה ויוסיף את ה-flag הזה של floating point software וזהו.

אז ברגע שנעשה Apply ונבנה שוב מחדש את ה-זה, אז Clean ובבקשה Build, אז תראו שאין לנו פה שגיאות והכול יפה.

ואנחנו בעצם בונים אותו דבר, ואנחנו גם נגיע לבנייה בסוף, אבל אנחנו רואים שבעצם אין לנו שום דבר, ואני חושב שאם נהיה סבלניים נוכל למצוא ממש את ה-flag הזה.

הנה, `-mfloat-abi=soft`. בסדר?

## ST-LINK Interface – Role

אז בואו נדבר עכשיו קצת על מה זה ST-LINK.

ST-LINK זה בעצם איזושהי חומרה שנמצאת בתוך ה-board, שהיא עושה לנו את ה-debugging. בעצם תחשבו עליה כעל איזושהי חומרה שעל כל instruction שקורה, היא בעצם שומרת לעצמה את המיקום שאנחנו מריצים ומעבירה את זה בחזרה ל-terminal, כדי שנוכל בעצם לעבור שורה שורה, אפילו פקודה פקודה ב-Assembly.

אז הרבה פעמים זה כבר נמצא integrated בתוך ה-board.

אם אני זוכר נכון, אז כאן ה-board מחולק לשני חלקים, תגידו לי אם אתם רואים טוב. החלק העליון זה בעצם הרכיבים של ה-debugging, וכל החלק התחתון מכאן ומטה, זה הרכיבים עצמם שאפשר להשתמש בהם.

אז אני חושב שה-ST-LINK זה הרכיב הזה כאן.

אם אתם רוצים קצת עוד רכיבים, אם אנחנו אוהבים לדבר על רכיבים, אז פה יש את הגביש. גביש זה איזשהו רכיב שאתה נותן לו מתח, והוא נותן לך גל ריבועי של קצב. והרבה פעמים משתמשים בו כדי בעצם לתת את ה-frequency לתוך השעון.

אז זה הגביש, ופה יש לנו למעלה LED, ויש לנו שני ג'מפרים, ועוד איזה ג'מפר שנצטרך עוד רגע, יש מצב.

אז מה התפקידים של החלק הזה? אגב, אם יש לכם NUCLEO, אז יש פה אותו דבר גם למעלה.

מה התפקיד של הרכיבים האלה? זה בעצם לעשות לנו את ה-programming של הצ'יפ עצמו, של ה-Flash (ה-Flash נמצא בתוך הצ'יפ), לעזור לנו ב-debugging, והם נותנים לנו איזשהו interface שזה Serial Wire Viewer (SWV), שב-interface הזה אנחנו עוד רגע נשתמש כדי בעצם לשלוח בחזרה מתוך ה-MCU את ה-debugging.

## ST-LINK Interface – Connection

אז תוך כדי שאני מדבר, בואו נחבר. אז אם הספקתם להתארגן על כבל USB Mini, אני חשבתי שזה מגיע ברוב ה-kits, אין לי מושג איך שלי הגיעו אליי, כנראה שדפקתי איתם איזושהי עבודה, סליחה על השפה.

אז בואו נחבר. יש לנו כבל USB Mini, הוא נראה כמו טרפז כזה של ילד שיש לו קצת בעיות לכתוב. ואנחנו מכניסים אותו לבפנים.

- זה לא כזה שמחברים בדרך כלל לארדואינו? או שזה אחד אחר?

- אני חושב שכן. אני חייב להגיד שבארדואינו אני פחות חזק.

- כן, כי אם כן אז צריך להיות אחד, אבל אני לא בטוח בדיוק איפה הוא.

- כן, זה אותו אחד בדיוק, אותו אחד.

- אני רק בזמן שדיברנו פה בכלל פתחתי את הקופסה של ה-STM שהגיעה אליי אתמול, אז לא הספקתי להתארגן יותר מדי.

- הכול בסדר. אני חושב שזה לא מגיע בקופסה, נכון? אתה כן קנית אם אני לא טועה.

- לא, היא לא הגיעה.

- איזה board זה? NUCLEO או Discovery?

- Discovery 4. STM...

- מעולה, אז יש לך יותר LED-ים.

- Discovery, מה ששלחת בקבוצה גם.

אוקיי, אז חיברנו, נדלקים לנו ה-LED-ים. ה-LED-ים האלה ספציפית הם ה-LED-ים של ה-debugger unit נקרא לזה, אבל בגדול זה כל מה שהסברתי, ה-ST-LINK interface.

החיבורים שיש לנו: יש לנו את החיבור החדש יותר, SWD, שזה Serial Wire Debug. בעצם זה חיבור שמשתמש בקו אחד של דאטה ואדמה.

בעבר היה JTAG, אם אי פעם יצא לכם לעבוד בחברות צבאיות אז עדיין יש JTAG, אבל זה איזשהו פרוטוקול עם הרבה יותר פינים שהיו משתמשים בו פעם ל-debugging.

ה-דאטה עצמו עובר על שני קווים. אז יש לנו את הקו של הדאטה שזה half-duplex, זאת אומרת שכל פעם אחד מדבר, או ה-Master או ה-Slave. אז ה-Master במקרה שלנו זה ה-PC, וה-Slave זה ה-board. אז כל פעם או ה-Master או ה-Slave מדברים על אותו הקו, אז הם צריכים לסנכרן.

ויש לנו את ה-SWCLK, שזה ה-Master נותן, תמיד כ-concept Master נותן clock. יש רגל של reset, שה-ST-LINK יכול לרסט את ה-MCU. בעצם מה שקורה שם זה שיש קצר בין שתי רגליים ואז ה-CPU מתרסט, זה בעצם hard reset.

וזה בגדול מה שיש שם.

ויש עוד איזשהו רכיב בפנים שאנחנו נדבר עליו עוד רגע, וברכיב הזה אנחנו נשתמש כדי להעביר את ה-debugging information שלנו.

טיפים: כשאתם מחברים, תוודאו שיש לכם חיבור של ה-SWD pins. זאת אומרת שאם אי פעם תעבדו עם board-ים שבהם זה לא כזה ברור כמו על ה-evaluation board, אז צריך לוודא בעצם שיש את החיבור. נכנסים לפרוטוקול ורואים מה קורה שם.

את הכבל של ה-Mini USB עדיף לא כבל גדול מדי, וקנאל גם את הקווים של ה-SWD. אצלנו הם כולם על אותו ה-board, ב-evaluation board, אבל בכללי עדיף אותם כמה שיותר קצרים, כי זה לא תקשורת שנועדה למרחקים גדולים, אז היא לא חסינה לרעשים.

וגם הרבה פעמים מציעים שיהיה ספק בנפרד גם ל-ST-LINK, לרכיב של ה-debugging, וספק נפרד ל-STM32, ככה שהם לא תלויים אחד בשני ואין בעיות של הזנה אחד מהשני.

איך אנחנו בעצם דרך כלל עובדים? אנחנו עושים compile, אחר כך אנחנו עושים load, ובסוף אנחנו מריצים.

אז בואו רק נריץ את הכלום הזה שיש כאן ונמשיך הלאה.

עכשיו אנחנו נלחץ על ה-Play. וברגע שנלחץ על Play במקום הנכון, אז בואו נפתח מפה: STM32 C/C++ Application.

אז הוא יגיד לנו בואו, תפתחו כזה דבר, זה הכול.

והוא יאפס את ה-CPU ויריץ. בעצם אנחנו לא יכולים לראות ב-Terminal מה קורה כאן, אבל מפה הוא כן מנסה להתחבר, ואז הוא מצליח, הוא סוגר או שהוא נכשל.

כשאתם חיברתם בפעם הראשונה, לכם יכול להיות שהוא יציע לעשות upgrade ל-debugger unit, ל-ST-LINK.

- הוא ביקש ממך כבר?

- נראה לי שעשיתי מקודם.

אם זה מצליח, החיים שלך יפים וכל הכבוד. אם זה לא מצליח, אז יש פה איזשהו ג'מפר. ג'מפר זה פשוט משהו שמקצר בין שתי רגליים. אז יש פה איזשהו ג'מפר למטה, ואת הג'מפר הזה צריך לנתק. ואחרי שמנתקים אותו, מנתקים את ה-USB Mini ומחברים אותו, ואז הוא מזהה רק את ה-debugger unit והוא מהבהב, ואז אפשר לעשות את ה-flashing, אם זה לא הצליח. אם זה הצליח, שוב, החיים יפים והכול סבבה. ב-Discovery board, ממה שיוצא לי, הוא לא מזהה על ההתחלה. אז בואו ננתק שוב, נחבר, ונמשיך הלאה.

כרגע בנינו את ה-hello_class. אמרנו לו: תוריד את הקוד לתוך ה-MCU, בעצם תעשה לו flash.

והוא מתחיל. אז בואו נשים פה break point ונעביר ל-debug, רק שנראה שהוא בעצם מתקשר איתו.

ברגע שלוחצים על debug, הוא ישאל אותנו אם לעבור ל-mode של debug, אז הוא יעביר לנו את כל החלון. העברתי את החלון ל-debug, ואז בעצם אתם רואים הוא אומר לי: אני עצרתי כאן.

אני יכול להמשיך אותו, חוזר לאותה נקודה. אני יכול גם להעיף את ה-break point הזה.

שמתי פה את ה-break point מחדש, אני מריץ, שוב עוצר, משחרר את ה-break point.

עכשיו זה רץ. כרגע כרגע זה רץ, אני אעצור את זה, אז יש לי פה את הכפתור של ה-Pause. אז הוא יחזיר אותי לנקודה הזאתי, כי בעצם הוא רץ ב-loop אינסופי עכשיו. זה בסדר מבחינתו לרוץ ב-loop אינסופי, זה לא כמו CPU שאנחנו טוחנים ומתחמם וזה.

אם אני רוצה לרסט את המכונה, אני לא יודע, אני רוצה לחזור לנקודת התחלה, נתקע לי ה-MCU, whatever, יש פה את הכפתור של החץ של ה-Play, משהו שאחורה, וזה מרסט לי את ה-MCU והוא חוזר לאותה נקודה. בהמשך אנחנו נסתכל, נראה שזה מגיע חזרה לזה.

## Flash Memory – What It Is

אז מה זה בעצם flashing של התוכנית?

לפני שאנחנו זה, כדי לחזור אחורה למצב של הכתבן, אז אני לוחץ על הריבוע של ה-Stop והוא מחזיר אותי לכאן.

מה זה בעצם flashing של התוכנית? בעצם זה לא כמו flash מ-Linux שבו יש לנו בעצם buffer-ים בתוך ה-RAM שאנחנו רוצים לכתוב אותם...

הדיסק.

אז זה בעצם Flash, זה בעצם ממש, זה מגיע מפעם שהיו ממש כאילו...

שאני לא בטוח איך זה עבד, אבל היו ממש כאילו צורבים את זה באמצעות פלאש,

את ה... את הזיכרון, אז השם נשאר, אבל הפעולה היא כבר שונה לגמרי.

אז היום פשוט כותבים איזשהו זיכרון חומרתי, זיכרון Flash הוא נקרא.

זה איזשהו זיכרון שהוא non-volatile, זאת אומרת שאם אני אכבה את המתח, אז התוכנית תישאר על הרכיב, אוקיי?

לעומת זיכרון שהוא volatile, שהוא נדיף, שברגע שאני מכבה, כמו RAM, אז התוכנית עצמה נעלמת.

אז איפה הזיכרון הזה נמצא? הוא נמצא על ה-STM32.

מה הגודל שלו? הגודל שלו משתנה בין MCU ל-MCU.

איפה אפשר לראות מה הגודל שלו?

אז מקודם עשינו איזשהו... בוא נפתח...

מקודם פתחנו את ה... זה עצמו, אולי גם אפילו פה יהיה לנו.

Features, הנה, מעולה. אז יש לנו... לי יש 512 קילו-בייט של Flash memory, שזה כלום,

ועוד 108 קילו-בייט של RAM, שזה זיכרון שהוא זיכרון הפעלה.

אז התוכנית עצמה נמצאת על ה-Flash memory, בעצם כל פעם שאני צורב אותו, אני מכניס בלעדית את התוכנית לתוך הזיכרון.

יש לנו עוד איזשהו רכיב שעושה פלאש ל-RAM, ואנחנו נסתכל על זה עוד רגע.

וזה מה שקורה כשעושים את זה.

אז זה הגודל. ה-Execution, בעצם הקוד עצמו, ה-CPU כל פעם שהוא... כל cycle שלו, אז הוא גם עושה fetch ל-instruction הבא, בעצם לפקודה הבאה,

ולוקח את הפקודה הבאה מתוך ה-Flash על ידי איזשהו bus.

זאת אומרת שיש לתוך ה-CPU, לתוך הזיכרון של ה-CPU, יש לו איזשהו bus שהוא כל פעם עובר עליו.

בעצם bus לרוב זה איזשהו... זה איזשהו זיכרון שהרבה מאוד רכיבים יכולים לגשת אליו ביחד, סליחה, זה יכול להיות איזשהו interface שכל הרכיבים יכולים לגשת אליו ביחד.

הוא הרבה פעמים ממופה לתוך הזיכרון של כמה רכיבים, ככה שהם יכולים לגשת אליו מתוך הזיכרון, זה בעצם איזושהי, נקרא לזה אבסטרקציה.

בעצם אני ניגש, אני פשוט כותב לתוך איזושהי כתובת, אבל ה-CPU ברקע משנה את המידע בתוך הרכיב האחר על ידי ה-bus.

אוקיי, אז ה-Flash יכול לקרוא ולכתוב בו-זמנית, בעצם אנחנו משתמשים ב-bus כדי לעשות את זה.

אפשר לתכנת אותו ולמחוק אותו הרבה מאוד פעמים.

מספר גדול של פעמים, זה בעצם נע בין 10,000 ל-100,000 פעמים. יש גם כאלה שהם יותר רציניים.

אבל מה? ל-Flash יש איזשהו חיסרון, שזה... אולי תיתקלו בזה בחיים המקצועיים שלכם,

אפשר לחשמל Flash. אם בעצם Flash עובד באיזושהי סביבה שהיא לא מוגנת, כאילו עם הרבה רעשים,

או שלא יודע, איזשהו מהנדס חומרה החליט לא להוריד איזשהו רגל אדמה כמו שצריך,

אז יכול להיות שה-Flash עצמו יקבל איזשהו מתח גבוה או... נקרא לזה חישמול אחר, או מתח נמוך דווקא,

אם זה רגל של 3.3 שפתאום עובדת ב-2.משהו, אז איזושהי בעיה יכולה להיווצר,

וה-Flash יכול להימחק. אז שתדעו שיש אפשרות כזאתי, ויש כל מיני אפשרויות כאילו לעקוף את הבעיה, אבל זו בעיה שקיימת.

ועוד דבר, Flash אפשר לנעול.

אז אם אנחנו לא רוצים שמישהו יוכל לגשת לתוך הקוד שלנו, לתוך הזיכרון שלנו מבחוץ, אנחנו יכולים לנעול את ה-Flash ואז ברגע שמישהו רוצה להשתמש בו, הוא צריך למחוק אותו לגמרי, וזה בעצם מונע ממישהו אחר לגשת לנו אל הזיכרון.

## MCU Hello World

אז MCU Hello World.

מה שאנחנו הולכים לעשות, זה אנחנו הולכים לעשות בעצם redirection. אנחנו הולכים לכוון את ה-stdout,

ש-stdout זה ה-file descriptor הסטנדרטי. יש שלושה file descriptors סטנדרטיים: יש stdin, יש stdout ו-stderr.

אז stdin זה המקום שממנו המערכת הפעלה מקבלת את ה-input מהטרמינל.

אם אנחנו רואים, אז בעצם אני כותב סתם לתוך הטרמינל, וברגע שאני עושה... לא יודע, cd,

אז זה הולך לתוך המערכת עצמה.

ומה שהיא מחזירה לי... בוא נעשה איזשהו משהו... echo 3,

אז בעצם מה שהיא מחזירה לי זה ה-stdout, ויש אפשרות גם לכתוב לתוך stderr, שגם הוא יצא אל המסך.

אז עכשיו, זה היה ב-Windows, הכל טוב ויפה.

אז מה קורה בעצם בתוך ה-MCU? בעצם צריך להיות לזה איזושהי אימפלמנטציה.

אז אנחנו הולכים לקחת את האימפלמנטציה הזאתי, ובעצם לעשות לה override.

ואני אסביר עוד רגע איך אנחנו עושים לה override ולא בעצם דורסים אותה, כי זה איזשהו feature של GCC.

הולכים לדורס אותה ולכניס את האימפלמנטציה שלנו כדי שנוכל לקבל את הקוד בזה.

אז לפני הכל, יש לנו את הקווים שאנחנו הולכים להשתמש בהם, זה ה-SWO, שזה ה-Serial Wire Output.

מתוך ה-interface בין ה-MCU ל-ST-LINK, יש לנו איזשהו קו שנקרא SWO,

שעל הקו הזה בעצם עוברים המידע של ה-debugging, ואפשר להשתמש בו כדי לעשות trace ל-printf,

ולעשות debugging בלי להשתמש ב-UART, שזה איזשהו רכיב חומרתי של תקשורת, סוג של תקשורת חומרית,

שאנחנו אולי ניגע בה בקטנה, אבל היא בעצם יותר לקורס של הדרייברים.

## MCU Hello World: ITM

אז בתוך ה-ST-LINK יש איזשהו רכיב שנקרא ITM. ניתן איזשהו brief קצר עליו, ונסביר איך אנחנו הולכים להשתמש בו.

ה-ITM עצמו זה Instrumentation Trace Macrocell, זה בעצם איזשהו רכיב קטן...

שאני אומר רכיב, אני מתכוון בתוך החומרה, הוא לא רכיב פיזי. בתוך ה-chip של ה-ST-LINK יש איזשהו רכיב קטן שיש לו 32 queues,

32 FIFOs של דאטה, ואפשר להעביר עליהם מידע מה-MCU אל ה-STM32CubeIDE, לתוך ה-IDE.

אנחנו נשתמש ב-Port 0 ואנחנו בעצם נעשה redirection של ה-printf. printf בעצם מה שהוא עושה, הוא כותב לתוך ה-stdin,

אז אנחנו ניקח מתוך ה-stdin ואנחנו נדפיס את זה על המסך.

אז איך אנחנו עושים את זה?

אם תסתכלו בתוך ה-Git, אז יש לנו בתוך lesson_1 איזושהי תיקייה כזאתי שהיא מסתתרת, שזה src_for_class,

אז יש לנו פה שני קבצים. אחד מהם זה קובץ C, ואחד מהם זה קובץ H.

בואו נעתיק את שניהם בצורה ככה מכוערת לתוך ה-Src, אוקיי?

תעשה Copy בבקשה.

אז בואו נסתכל, אז בתוך ה-C יש לנו בעצם גישה לתוך זיכרון. אנחנו נדבר על זה כשנדבר על פוינטרים ועל ה-memory map בשיעור הבא.

אז גישה לכמה מקומות בזיכרון, ואנחנו יכולים לגשת ל-ITM Trace Enable ולהפעיל אותו, ככה אנחנו מפעילים, ואנחנו נסתכל עוד רגע איך עושים את זה,

ו-Port 0 עצמו שהוא נמצא בנקודה הזאתי, אנחנו יכולים להפעיל אותו ולכתוב לתוכו את ה-channel. אז באמת כאילו זה לא קריטי לנו.

ומה שאנחנו הולכים לעשות זה להדביק את הקוד הזה, אוקיי?

אז כשאנחנו בעצם משתמשים ב-include, אז שנגיע עוד מעט לקומפילציה אז תבינו מה קורה, אז בעצם אנחנו מדביקים את כל התוכן של הקובץ הזה,

אנחנו מדביקים אותו בתוך הקובץ שאליו אנחנו עושים include.

אז אנחנו הולכים ל-syscalls, אוקיי? מתוך syscalls, ותעקבו איתי ביחד, אז אנחנו נעשה include,

ונפתח מירכאות, זאת אומרת שזה משהו מקומי.

אם יש לנו את ה... אני לא זוכר איך קוראים להם, אבל הקטן-גדול הספציפיים האלה, אז הם אומרים לקומפיילר לחפש במקומות ספציפיים את הספריות האלה.

אם אנחנו עושים מירכאות, אז אנחנו אומרים לקומפיילר לחפש את הספרייה, את ה-include header, לחפש אותו בסביבה הזאתי. בסביבה הזאתי זה איפה שמוגדר לך מתוך הסביבה, ואנחנו נסתכל על זה בהגדרות.

אז פה אנחנו רוצים להוסיף את itm_debug, ו-Ctrl+Enter עושה לנו השלמה אוטומטית, אז itm_debug.h.

ומה שזה נותן לנו עכשיו, אנחנו יכולים לגשת ל-ITM_SendChar.

אז בוא נעתיק את השם שלו ונלך ל-syscalls, אפשר לפתוח אותו ככה ואפשר לפתוח אותו בצורה הזאתי, ומתוכו אנחנו נרצה את _write.

אז בעצם _write זה יהיה _write,

ואם אנחנו לא מוצאים אותו אז פשוט נלך ככה.

אה, אוקיי, הנה כאן.

אז _write פה הוא מסוג `__attribute__((weak))`. מה זה attribute weak?

זה איזושהי פונקציונליות שאנחנו מקבלים מה-GCC, שכל עוד אין פונקציה אחרת שנקראת באותו השם, עם אותה חתימה,

אז אנחנו משתמשים בפונקציה הזאתי. אם יש לנו אימפלמנטציה של פונקציה אחרת, אז אנחנו דורסים אותה. אז פה אנחנו לא נדרוס אותה, אבל בדוגמה בהמשך, אני חושב של-structures, אני כן אראה לכם איך אנחנו דורסים אותה.

אז פה אנחנו לא, אבל אנחנו רוצים לקחת ובמקום `__io_putchar`, אנחנו נרצה להשתמש, ואנחנו נעשה את זה באותה צורה, אנחנו נרצה להשתמש ב-ITM_SendChar.

אז יש לנו את ה-pointer הזה שאנחנו מקבלים, פוינטרים אנחנו נגיע לזה בהמשך, אז פשוט נעתק את זה באותה צורה.

אז כוכבית ptr++, בוא נרשום ptr++,

במקום הנכון בבקשה.

ככה, ואנחנו נעשה Ctrl+S לשמור,

ונקמפל, נראה שהכל בסדר.

ברגע שאנחנו רואים שהכל בסדר, אז אנחנו פשוט נשתמש ב-printf.

וזה לא הכל פשוט, עוד שנייה נמשיך. אז את זה כבר העפנו, בואו נעף את זה מהקוד, סבבה.

תודה רבה על ההוראה.

ונעשה printf. אז printf...

האם אני יכול לעשות printf? לא, בשביל printf אני צריך stdio. אז include stdio.h.

אז printf, ונירשום לתוך הזה:

"Hello from Bare Metal Class"

`\n`.

למה צריכים לעשות `\n` בסוף שורה? כי ברגע ש...

האמת שאני לא בטוח אם זה רלוונטי לגבי זה, אבל לפחות שאנחנו מדברים בלינוקס, אז ברגע שיש `\n`, אז ה...

אז ה-buffered I/O, שזה איזשהו...

קונפיגורציה במערכת הפעלה, היא יודעת שהיא צריכה לקחת את הבאפר הזה, בעצם את ה-string הזה, ולכתוב אותו לתוך הזיכרון.

For good practice אנחנו הולכים להשתמש ב-`\n`, לא בטוח שחייב לעשות את זה בתוך ה-MCUs, אבל אנחנו נעשה את זה בכל מקרא.

אז זה עושה את הקוד שלנו פורטבילי, זאת אומרת שנוכל להעביר את הקוד מה-MCU הזה לכל רכיב, לכל מקום אחר.

אז `printf("Hello from Bare Metal Class\n");`

פה לא צריך לעשות בביב אמת, יצרנו אקלים עדיין.

נבנה את זה,

ונעביר את זה לתוך ה-MCU.

אז Debug,

ו...

אוקיי, הגענו לנציגה הזאתי, נעבור הלאה.

ולא קרה כלום. אז למה לא קרה כלום? כי בגדול, קצת קצת טרללתי אתכם. צריך גם לעשות record ל-ITM ואני אראה לכם איך עושים את זה.

סליחה.

אז העברנו את ה-ITM_SendChar, ראיתם איך עובר על זה.

והשתמשנו במכניזם הזה כדי להעביר את הדאטה, אז פה זה כתוב.

אנחנו משתמשים ב-tracing וב-debug registers,

נתקדם קצת בהמשך נדבר קצת יותר על רגיסטרים.

ורק בוא נדבר על ה-benefits של מה שאנחנו עושים.

בעצם אנחנו לא עכשיו... אנחנו עכשיו בעצם משתמשים...

בפונקציונליות שנמצאת כבר בתוך ה-MCU, ואנחנו לא צריכים להוסיף פונקציונליות חדשה.

אז אנחנו לא עכשיו, נגיד יש אפשרות גם לדבג על רכיבי חומרה של ה-UART. דיברנו על UART, שזה איזשהו interface סריאלי,

אז אנחנו יכולים לדבג בזה. אבל בעצם אם אנחנו משתמשים בו, אז אנחנו משנים את מה שקורה ב-MCU.

אז היתרון של ה-ITM זה שהוא פשוט נמצא חיצונית ל-MCU, הוא פשוט מקבל tracing בצורה אוטומטית על ה-ST-LINK.

בסדר? והוא הרבה יותר מהיר מהאפשרות של semihosting שאנחנו...

## MCU Hello World: Override

כדי שנוכל להשתמש בה, אבל זה בעצם פונקציונליות שניתנה לנו כדי להריץ את זה, את הקוד מהמחשב עצמו.

אוקיי, אז ה-override עצמו, איך אנחנו בעצם משתמשים בו?

אז בואו נצא מה-debugging.

ואנחנו כרגיל נלך להגדרות, כי זה מה שעושים ב-STM32. הולכים להגדרות.

אז אנחנו עושים STM Application, Debug As -> STM Application, סבבה?

סליחה, ואז... טוב, שלי לא לחצה על מה שאינו נכון.

אנחנו צריכים לעשות Other Configurations.

בעצם פה, Configure... Debug Configurations, אוקיי?

מה-Debug Configurations אז בעצם אתם תראו שהוא יצר לנו כבר את זה, אז אנחנו לא צריכים ליצור. אז יצר לנו איזשהו קובץ settings או איזשהן הגדרות של זה, ובואו נעבור עליהן ונראה שהן נכונות.

אז פה ה-Main, אנחנו בעצם הולכים לדבג את ה-executable הזה שנקרא Hello Class, אנחנו נדבר על מה זה בעצם בהמשך.

באיזה אינטרפייס אנחנו משתמשים? ב-SWD.

אוקיי, ואז עשינו לה-apply את ה-interface הזה.

ובהמשך אנחנו נרצה גם לאנבל, או לאפשר בעברית, את ה-SWV עצמו. זה ה-SWD, זה האינטרפייס, לא JTAG, כי אנחנו לא בעולם האבן, וה-SWV זה ה-wire עצמו שאנחנו יכולים להשתמש בו.

וברגע שאנחנו נסיים, אז אנחנו נעשה Apply.

ואנחנו יכולים לסגור את זה. נכון? אז Apply.

זהו, אפשר לסגור.

עכשיו, מפה נלך ל-Window... אז נראה. אז בואו עכשיו נפעיל את ה-debugger.

ונלך לתפוס את ה-SWV.

אז עכשיו אנחנו נעשה switch ל-Debug perspective, בעצם לחלון של ה-Debug.

אנחנו עדיין לא רצים כי לא הפעלתי את זה, אבל אנחנו נלך ל-Window -> Show View -> SWV. ומכאן אנחנו נלך ל-SWV ITM Data Console.

זה בעצם הקונסול של הרכיב עצמו של ה-ITM.

אני שנייה אזיז את המסך כדי שנוכל לראות.

עכשיו יש לנו כאן בצד ממש בקטן את ה-Configure Trace ואת ה-Start Trace.

נלך ל-Configure Trace, ואתם זוכרים שאמרנו שאנחנו משתמשים בפורטים של ה-ITM, בעצם ב-queues?

אז אנחנו הולכים להשתמש ב-queue 0, אוקיי?

ונלחץ OK.

וכדי להפעיל את זה אנחנו גם צריכים להקליט.

ועכשיו ברגע שנריץ את התוכנית, ברגע שנריץ את התוכנית כאן, אז Hello from bare-metal class! ברוכים הבאים לכולנו, הצלחנו להריץ את זה מתוך הקוד.

מישהו עקב והצליח גם?

זה בסדר, בשביל זה יש הקלטה, ואני עדיין חושב שכדאי שתנסו. אבל אם זה היה מהיר מדי אז גם תעדכנו אותי כדי שאני אוריד את הקצב.

וגם תעברו על זה שוב, זה חשוב לעבור שוב.

סבבה.

- כן, אני עכשיו הצלחתי להריץ את ה... כאילו עכשיו, בגלל שחיפשתי את הכבל קודם, עכשיו הצלחתי להריץ את הבסיסי כאילו. אני לא לא ממש עקבתי אחרי מה שעכשיו.

אוקיי, אז בואו נעשה איזה ריקאפ ונעשה על דרך הזה.

אז בעצם מה עשינו?

הלכנו לתוך ה-syscalls, שזה ה... שזה הפונקציונליות מערכת שאנחנו מקבלים מתוך הספריות של STM32.

הלכנו ל-write, ובמקום להשתמש בפונקציה הבסיסית הלכנו לפונקציה פונקציה אחרת שבה אנחנו משתמשים. שהפונקציה הזאתי, מה שהיא עושה: היא מפעילה את ה... את ה-Trace Enable והיא מפעילה את פורט 0, שזה הפורט שאנחנו הולכים להשתמש בו.

אוקיי, ואת כל הדאטה היא בעצם כותבת לתוך... ואת הדאטה שהיא מקבלת היא כותבת לתוך פורט 0, אוקיי?

זה כל מה שהיא עושה, ומבחוץ יש לנו איזשהו לולאה.

את הדבר הזה לקחנו, אוקיי? את הדבר הזה לקחנו והכנסנו לתוך הספרייה כאן. איך עשינו את זה? ממש ב-include.

בעצם יש לנו include של header file, שזה הפייל - נדבר עליו עוד רגע כנגיע לזה - שזה הפייל שבו נמצא האינטרפייס של כל הספרייה.

ספרייה, יש פה פונקציה אחת, אבל בואו נקרא לזה ספרייה. כל הספרייה הזאתי.

ואז בעצם אני יכול לגשת ל... ואז אני יכול לגשת לפונקציה הזאתי.

ואת הפונקציה הזאתי - אני אעשה ככה, אני אעתיק את זה כדי שנוכל להגיע מהר - שמנו אותה בתוך ה-write.

אוקיי, אז Ctrl+F ושמנו אותה בתוך ה-write.

ועכשיו, כל פעם שאנחנו כותבים, שזה בעצם כשמשתמשים ב-printf, אז הוא בעצם כותב לתוך ה-file descriptor 0. איך הוא כותב לתוך ה-file descriptor 0? הוא משתמש ב-ITM_SendChar שעשינו.

מעולה.

אחרי שעשינו את זה, יצרנו קונפיגורציית debugging.

קונפיגורציית debugging היא מכאן.

ושוב, הלכנו ל-debugger, הפעלנו ב-SWD או שזה פעל, עשינו Serial Wire Viewer enable, סגרנו את זה, ורצנו את הכל.

עברנו למוד של debugger, וב-Appearance הוספנו את ה-ITM.

אנחנו נעשה את זה עוד פעם, אז בסדר.

## Advanced C Concepts

בואו נמשיך. אז בואו נדבר קצת על Advanced C Concepts. בעצם לא נשאר לנו הרבה דברים וממש ממש בקצרה.

אז בשיעור הקודם דיברנו על bitwise operators, אז יש לנו את ה-AND, כמו שאתם זוכרים. AND בעצם הוא 1 רק כששני הביטים של כל אחד מהם הוא 1. אז אם אנחנו מסתכלים על ארבעת הביטים של A ו-B, אז רק הביט הראשון, או הרביעי, תלוי אם אנחנו מדברים מזה, אז הביט ה-0, 1, 2, 3 - הביט השלישי, הביט השלישי או הרביעי הוא 1, אז רק אז התוצאה היא 1.

ואאם אנחנו מדברים על OR, אז כל מקום שאחד... או הראשון או השני או שניהם, אז זה 1.

ויש לנו את XOR, שהוא exclusive OR, אז רק השני והשלישי.

ויש לנו NOT. אז NOT - כל מקום שהיה 1 הוא 0, וכל מקום שהוא 0 אז הוא 1.

אבל NOT הוא ביחס לקבוצה הכוללת שלך. אם אני עם 8 ביטים, אז בעצם כל הביטים האחרים שהם חוץ מה-1 אז הם הופכים להיות... אז ביטים אחרים הם הופכים להיות 1, אז כשאני עושה NOT אז זה לא בעצם 0001, אז זה... אז זה פשוט קשור לקבוצה הגדולה שלי, אוקיי? קבוצה הגדולה של A.

דיברנו גם על shift, ואני חוזר כי אנחנו עכשיו נראה איך אנחנו משתמשים בזה. אז דיברנו על shift, אז אם יש לנו left shift, left shift אז הוא בעצם מעלה לנו בחזקה של 2 כפול ה...

כפול... רגע. בחזקה של 2...

- כאילו זה להכפיל ב-2 בחזקת...

נכון, להכפיל ב-2 בחזקת המספר עצמו, בדיוק, תודה.

ולכיוון השני, אז זה לחלק ב-2 בחזקת, או להכפיל ב-2 בחזקת מינוס המספר עצמו.

עכשיו, יש כל מיני פונקציונליות של cycling, או יותר נכון, איך נקרא לזה? זה לא קביל וזה לא סטנדרטי אז אנחנו לא משתמשים. בעצם כשאני מתכוון ל-cycling, שאנחנו בעצם חוזרים אחורה, צריך להשתמש ברגיסטרים מיוחדים בשביל זה, ואין לנו כאן. אוקיי?

אז איך אנחנו עכשיו, אנחנו מדברים על bit manipulation, אז תיכנסו ל-00, ל-source עצמו, ל-main, אז יש לנו בעצם איזשהו רגיסטר דמיוני, שהרגיסטר הדמיוני הוא רגיסטר 00.

ואם אני ר... רגע, אז בואו נסביר גם קצת על זה קודם ואז אני אגיד מה עשינו כאן.

אז אם אני רוצה לכתוב לרגיסטר הדמיוני הזה איזשהו... רוצה לעשות set לאיזשהו ביט בתוך המספר שנמצא בתוך הרגיסטר הזה, אז אני רוצה לעשות set לביט השלישי. אז מה אני עושה? אני עושה 1, בעצם ואז אני עושה לו... ואני עושה לו left shift שלוש פעמים. אז בעצם אני מקבל את המספר הזה. במקרה הזה.

אז זה גם מה שיהיה ברגיסטר בסוף, אבל אנחנו עושים את זה ככה.

אז בעצם 1 left shift... אולי ניפתח מחשבון שזה יהיה יותר קל.

אז אני לוקח את המספר 1, רק שנייה, הנה כאן. בואו נקטין את זה ל-U8 עוד פעם, זה עכשיו U8.

אז אני לוקח 1, אוקיי, אז יש לנו פה... זה מה שנמצא לנו בתוך ה-U8 הזה.

ואת ה-1 הזה, בואו נחזיר שנייה במספרים, אני עושה לו left shift, אבל אני צריך לבחור left shift ככה, ב-3.

אז מה שיהיה לי בעצם זה 8. ואיך זה נראה בזה? זה נראה 1000.

ואם אני עושה OR, שזה שווה OR זה אומר כמו... שווה OR זה אומר כמו פשוט תעשה OR רק לביט הזה, אז בעצם יש לי את ה... את הבית שנראה ככה, וכשאני עושה OR עם הערך שנמצא בתוך הרגיסטר, אז אני בעצם מפעיל רק את הרגיסטר... רק את הביט הזה, אוקיי?

לא הסברתי את זה הכי טוב, אז אני אסביר. אז בעצם אני לוקח ואני בונה לי איזשהם... איזושהי מסכה, שזה גם השם של זה, mask. שה-mask הזה יש לו רק 1 במקום אחד. וברגע שאני אעשה OR, ולא משנה מה הערך שנמצא בתוך המשתנה השני, אז זה תמיד יפעיל את אותו 1, ובאמת זה מה שהולך לקרות ברגיסטר ואנחנו נסתכל על זה עוד מעט.

- המסכה... מה זה המסכה? המסכה זה בעצם ה-1?

כן, המסכה זה בעצם ה... בעצם הביט הספציפי שבו אני ארצה להשתמש.

- ואליו אתה משווה ב-OR.

בדיוק.

ואיך אני מנקה את ה... איך אני מנקה את הביט הספציפי הזה?

אז בעצם, כדי לא לשנות את זה, אז אני רוצה לעשות AND. אז אני רוצה להפוך, אז אני הופך את כל מה שיש כאן. אז כל מקום שיש 0, אז אני שם 1, וכל מקום שיש 1 אני שם 0. ואם אני אעשה AND, אז כל המקומות שיש 1, הערכים יישארו אותו דבר, אבל במקום שיש 0 אני מאפס אותו במשתנה הספציפי. בואו תעזבו את הערך הדצימלי, זה לא מעניין אותנו, אנחנו מדברים על בינארי, אוקיי?

אז אם אני רוצה עכשיו לנקות את הביט החמישי, אוקיי? אז אם אני רוצה לנקות את הביט החמישי, אז בואו שנייה נמחק את זה. אז אני אבחר את הביט החמישי, אוקיי? אז בעצם איך אני בוחר את הביט החמישי? אז זה 1 left shift ב-5. אוקיי.

אז אני רוצה לנקות את ה-shift החמישי, אז יש לי 1 וכל שאר אפסים, וה-1 נמצא במקום החמישי.

ועכשיו אני עושה לו NOT, שזה ה-tilde היפה הזאתי כאן.

וה-NOT הזה בעצם הופך לי את כל הביטים בצורה הזאתי.

אוקיי?

וכשאני אעשה AND בין זה לבין מה שלא יהיה הערך הסופי,

אז בעצם רק פה יישאר 0 ובכל שאר המקומות יישאר הערך המקורי.

- ה-110 זה לא המקום 4? יכול להיות?

המקום 4 הוא המקום החמישי, כי אנחנו סופרים מ-0.

- אוקיי.

אז במקום החמישי יישאר... יהיה 0 ובכל שאר המקומות יהיה אותו הערך.

ואם אנחנו רוצים לעשות שינוי של הערך, אז שוב, אז אנחנו עושים... לוקחים את ה... אנחנו שוב משתמשים בביט החמישי, אז אנחנו עושים 1 left shift של 5, אז זה בעצם פה.

וברגע שאני לוקח את הביט הזה ואני עושה לו טאגל, כאילו אני עושה לו XOR, אז אם... בואו... בואו בעצם אני אגדיל את זה, אולי זה יהיה יותר נוח לראות, אז בואו נשתמש ככה ב-20 הזה.

אז בואו נניח שאנחנו מסתכלים רק על החצי הימני.

אז אם יש פה 1 ו-1, נכון? ואני עושה XOR, אז התוצאה תהיה 0, כי הוא exclusive. הוא OR exclusive, אז 1 ו-1 זה אומר 0.

אם היה פה 0 בביט הזה הספציפי, אז עכשיו יהיה 1, אז בעצם עשיתי לו טאגל. סבבה?

אז אם יש 1 אז הוא הופך להיות 0, אם יש 0 זה הופך להיות 1.

אז זה ה-XOR עצמו.

בואו נסתכל על הדוגמה של הקוד.

אז יש לנו פה את ה... את ה-define הזה. בעצם define מה שהוא עושה זה בעצם איזושהי פעולה שפשוט מדביקה את הערך. זה כמו שיהיה רשום פה ככה.

אז אני אעשה את זה ככה, אבל שתדעו שככה זה נראה.

אז בעצם, במקום בדוגמה שאמרתי בעל פה, אז יש לנו בעצם 1 במקום השלישי. אוקיי?

אז יש לנו 1 במקום השלישי, ואם אנחנו עושים OR עם הערך של הרגיסטר, אז בעצם ברגיסטר שלנו כרגע יש את הערך 1... סליחה, 4.

0, 1, 2...

2.

לא, סליחה, 1, 2, 4. 4, 4, אוקיי?

אז 1, 2, 4. ואחר כך אנחנו ננקה אותו, ואחר כך אנחנו יכולים לעשות טאגל לביט החמישי ונעשה עוד פעם טאגל. אז בואו בואו נריץ את הקוד. אז את הקוד עצמו אנחנו נרצה לבנות, אז נעשה לו Clean כדי שניהיה על אותו הקו, נעשה לו Build שוב.

ונריץ אותו. איך אנחנו מריצים? אז זה פחות נוח זה, אז אנחנו הולכים ל-Run As -> Local C/C++ Application.

אוקיי. אז בואו נסתכל. אז אחרי שעשינו setting לביט השלישי, ואנחנו מציגים פה... זה דרך להציג בצורה הקסדצימלית, רק שני... רק שני אפסים. לא קריטי, אחרת הוא מציג לנו פשוט הרבה, אז אנחנו מציגים ככה.

זה formatting. אם אתם מחפשים, יש כל מיני שיטות של formatting, זה קצת כאילו להיכנס ולחפש formatting של... formatting של strings עם...

מ-printf או print formatting, כל מיני שיטות, אבל זה מה שזה עושה. לא העיקר, בואו נעשה את זה.

אז לקחנו את המספר הזה, שבגדול זה בדיוק זה. אם אני אקח שנייה hex, רק כדי לסבר את האוזן, ניקח hex, נעשה 8, אז אתם רואים שיש לי 1000, אוקיי? בואו נקטין את זה ל-uint8. אז זה המספר שיש לנו.

אחרי זה אנחנו מנקים את הביט הזה. איך אנחנו מנקים את הביט הזה? בואו בשביל הדוגמה נשים פה, סליחה, לא פה, נשים 9. `0x09`. בעצם `0x09`, לפני זה אנחנו נעשה הדפסה של `reg` before.

אז שימו לב, בעצם יש לנו את הערך 1. אז ברגע שאנחנו עושים את ההשמה לביט השלישי, אז 0, 1, 2, 3, אז בעצם אנחנו מוסיפים פה 1, אז יש לנו 9, שזה גם מה שעשינו.

ואז אנחנו הולכים ואנחנו אומרים לו: בוא תעשה לנו AND עם הפוך. אז המספר הזה יישאר, והמספר הזה ילך, כי אנחנו בעצם מנקים אותו.

אז איך אנחנו מנקים? יש לנו NOT למסכה הזאת, שזה ההפוך שלו. אז אם אני עושה AND של 1 ו-1 אז נשאר, אז יש לנו את ה-1. וחוץ מזה, רק הרביעי, שזה ביט 3, אז רק הוא מתאפס.

אותו דבר, בואו נמשיך קצת כדי שנוכל להתקדם. ביט החמישי, ביט 5 הכוונה שזה כאן. ביט 5 אנחנו עושים איתו toggling. אז פעם אחת אנחנו מפעילים אותו, אז יש לנו `0x21` ב-hex. בעצם יש לנו את הביט החמישי ואת ה-1 שזה מה שזה היה מלכתחילה. ואחר כך אנחנו מנקים אותו, אז אנחנו שוב במספר ההתחלתי. בסדר?

אז זה כל הפעולות. אנחנו ניגע בזה בשיעור הבא יותר, כנגע כבר ב-memory mapping ואיך אנחנו כותבים לרגיסטרים וב-bus העיקרי שאנחנו משתמשים בו. אבל כרגע זה רק היה ללמוד את זה ולהכיר.

אני אעשה איזשהו assignment, אכתוב לנו איזשהו assignment בסוף השיעור, רק כדי לשחק עם זה. נעשה איזשהו loop. תגידו איזה register ואיזה זה.

עוד דבר אחד, דבר אחרון. אנחנו עשינו את זה `1 << n`. אם אני רוצה לכתוב ביחד שני רגיסטרים, אז אני צריך 3. 3, אני יכול לעשות `3 << 3`. בואו נעשה את זה. `3 << 3`, אז בעצם זה 18. ראיתם, שני אלה הלכו לזה, וכרגע אני יכול לעשות כבר על שני ביטים.

אז המסכה שלי היא על 3. בואו רק נריץ את הקוד.

אז בעצם בהתחלה אני הולך... אוקיי, אז זה הרגיסטר הראשון. פה זה לא השתנה כי פה לא שיניתי את המסכה, בואו נחזיר את זה.

אז בעצם בפעם הראשונה אני מוסיף ל-1 הזה את שני אלה, נכון? עשיתי את ה-AND, אז זה הופך להיות ככה. וכן הלאה.

תעשו לכם איזושהי תוכנית, איזשהו loop, תשחקו גם עם C. בלולאה הזאת תבקשו מה-user, שזה אתם גם כן, "תגיד לי מה המסכה שלך, ותגיד איזה פעולה וזה", ותוכלו להשתמש בדברים שלמדנו בשיעור הקודם, שזה if-ים, switch-ים ולולאות, כדי לעשות את ה-masking הזה וככה תתרגלו. זה מאוד חשוב לדעת, כי גם מאוד אוהבים את השיטה הזאת וצורת העבודה, וגם תכלס ככה עושים setting ו-clearing של ביטים. זה מה שעושים ב-embedded.

## Complex Data Types

נעבור הלאה, Complex Data Types.

פה הכנתי לכם משהו ממש נחמד. אני לא יודע אם יצא לכם ללמוד את זה, אבל הרבה פעמים שמלמדים, אז מסבירים כל מיני... טוב, אנחנו נגיע לזה עוד רגע. אני מתרגש, אני לא יודע מה איתכם. גם בואו נעשה הפסקה.

השארתי אתכם במתח. אחרי זה אנחנו נדבר על structs, נדבר על איך structs נראים בזיכרון, ואני גם אראה לכם איזשהו disassembly חמוד של מה קורה כשמשתמשים בפונקציונליות של structs ומה קורה כשלא.

בואו ניקח לנו הפסקה עד 8:11, אני אלך למלא לי מים ואנחנו נמשיך אחר כך, סבבה?

סבבה. אתם כבר בהפסקה מבחינתי.

אז אני חזרתי.

סבבה, ניתן לכולם עוד איזו דקה-שתיים.

**סטודנט:** דור, שאלה.

**מרצה:** כן.

**סטודנט:** אני לא מצליח להבין למה זה הדרך לקנפג registers. זה נראה מה זה מורכב ביחס למשהו שהייתי מצפה שהוא יחסית פשוט.

**מרצה:** בוא נעשה על זה ספוילר לשיעור הבא, אבל בגדול...

**סטודנט:** כי זו הדרך שעושים את זה ביום-יום, בפיתוח וכאלה?

**מרצה:** כן, זו הדרך. הרבה פעמים אתה מכין לך masks ספציפיים ואתה מזיז אותם ימינה ושמאלה. אתה מגדיר את זה פעם אחת, ואז... יש לך כל מיני define-ים כאלה מוכנים, ואנחנו ניגע בזה. עם הזמן אתה כבר מכיר יותר. אני יכול להגיד שאני אף פעם לא זוכר את הקונבנציה הזאת, ואני לא זוכר גם את אלה, אז אני כל פעם לפני ראיונות הייתי חוזר על זה. אבל בסדר, לא צריך יותר.

## Structures and Bitfields

יש גם דרך יותר קלה מזה, שזה בדיוק מה שבאתי לשאול, אז כן. יש לנו structures ויש לנו bitfields.

בעצם, בואו נילך לדוגמה השנייה. יכול להיות שאאת הדוגמה השנייה מישהו לא משתמש ב-Discovery שאני משתמש, אז הוא יצטרך פשוט ליצור לעצמו פרויקט מחדש ולקחת ולהעתיק את ה-ITM debug ואת ה-main.

אז בעצם, אם אנחנו רוצים לעבוד בצורה סדורה וקבועה, אז אנחנו יכולים להשתמש ב-structures. בעצם מה ש-structures נותן, נותן לנו איזשהו... רגע, אני אעבור ל-structures... נותן לי איזשהו משתנה שהוא מוגדר על ידי ה-user, ולרוב הוא מאגד ביחד כמה משתנים פרימיטיביים מסוג uint, int, float או char שזה גם uint, מאגד אותם תחת קבוצה אחת.

אז אנחנו משתמשים ב-syntax הזה שמופיע גם פה וגם בשני הצדדים של המסך. הוא מאגד אותם על ידי שם אחד. השם עצמו אפשר לעשות את זה בשתי צורות, אני אראה צורה אחת שבה עושים את זה ואחר כך נראה עוד צורה.

אז בעצם אני רושם, ה-syntax: אני רושם `struct`, את השם של ה-struct, ואחר כך אני רושם את מה שה-struct מכיל. יש לי סוג, שם; סוג, שם; סוג, שם.

אני יכול להכניס גם לתוכו מערכים. לא דיברנו על מערכים, אבל מערך זה בעצם זיכרון רציף. אם דיברנו בקורס של Linux אז הוא לא חייב להיות רציף, אבל פה הוא באמת חייב להיות רציף. אז זה איזשהו זיכרון רציף שהזיכרון הזה נמצא... כאילו, אם אני משתמש ב-10, אז יש לי 10 uint8 אחד אחרי השני. אלה מערכים, ונדבר עליהם יותר כשנדבר על pointers, זה בדרך כלל בא ביחד.

אז ככה, בגדול ככה אני משתמש ב-struct עצמו. אז אני עכשיו הגדרתי `struct` מסוג `sensor_data`. יש ל-struct הזה `temperature` מסוג `int32_t`. למה `int`? כי טמפרטורה יכולה להיות גם שלילית. יש לי `status` שזה סתם איזשהו מספר, ו-`pressure` גם, יכול להיות שלילי, אבל באמת לא משנה, זה רק דוגמה.

את זה אני אקטין כדי שיהיה להמשך.

ובעצם, כל פעם שאני אצהיר על המשתנה מסוג `struct`, אז בעצם אני אקבל את השלושה אלה.

למה זה נמצא פה פעמיים? כי ל-struct, לקומפיילר, וזה קונספט של קומפיילר, לקומפיילר יש איזושהי אפשרות להגיד: תשמע, אני יכול לתת לך את ה-data בצורה מהירה או בצורה שהיא יותר דחוסה שתתפוס פחות מקום בזיכרון. דיברנו על זה שזיכרון ב-embedded זה מצרך נדיר, ובמיקרו-קונטרולרים זה מצרך אפילו עוד יותר נדיר. אז הייתי יכול לנסות לתפוס...

אם מה שזה הסברתי לאשתי היום, שזה היה הכי פשוט: יש לי עכשיו דף, אוקיי? על הדף הזה אני רוצה לכתוב מלא מילים. זה יראה הרבה יותר טוב אם כל פעם שיש לי נקודה אני ארד שורה. אבל מה החיסרון? החיסרון הוא שאני יכול להכניס פחות מילים בתוך דף.

אז זה בדיוק מה שה-compiler מציע לי. הוא אומר לי: בוא, אני אעשה לך padding. כל מקום שאתה לא צריך, כל מקום שיש כביכול נקודה, אני אוריד לך את זה ל-word הבא.

מה זה word? word זה alignment בעצם. זה הגודל שבו בזיכרון אני משתמש. מה הכוונה בגודל? הגודל יכול להיות או uint8, או uint16, או 32, או 64. במחשבי PC שלכם, ה-alignment הוא 64 bit.

אוקיי, אז כל פעם שה-CPU שלכם במחשב קורא instruction, אז הוא קורא אותה בגודל של 64 bit, וזה ה-instruction. יש לו ב-instruction את ה-op code, את בעצם הפעולה, את עוד איזשהו פרמטר, ומה שלא זה... Intel לא מימשו שם בארכיטקטורה שלהם ב-x86 את הפרמטר שמגיע. ואנחנו נסתכל על assembly בעצם בדבר הזה שהסתרתי מכם כאן, כי אנחנו נראה גם איך זה נראה.

ויש את האפשרות להגיד ל-compiler: בוא, אני אנהל לבד את הזיכרון. זאת אומרת שאני לא כל פעם ארד שורה, אני בעצם כל פעם מחדש אתחיל להזיז את ה-bits כדי לקבל את זה. ואנחנו נראה איך זה נראה. אז אני אעבוד לאט יותר, זאת אומרת, כדי לקבל פרמטר אני צריך לעשות אולי כמה פעולות כדי לקבל את הפרמטר עצמו, אבל מבחינת זיכרון זה יתפוס לי פחות מקום בזיכרון.

אוקיי, אז זה padding. padding מה שעושה ה-compiler, הוא אומר: תקשיב, אתה רואה את ה-uint8 הזה פה? הוא לא לגמרי aligned ל-word של ה-MCU שלנו. ה-MCU שלנו, ה-word, שזה בעצם המילה, כאילו... איך אני אגיד את זה? בעצם ה-assembly שלנו מקבל לתוכו word, שהכוונה ב-word זה בעצם איזשהו גודל שהוא הגודל ה-native של ה-MCU, זה הגודל שהכי קל לו להתמודד איתו וככה הוא עובד איתו הכי מהר. ה-MCU שלנו הוא 32 bit, אז הגודל שאליו הוא קורא הוא 16 [לא ברור]. יכול להיות גם 32 bit, אבל הוא 16, ואני יודע את זה כי מדדתי, אבל יכול להיות גם מספר אחר, בסדר? אז בואו אני אגיד לכם שזה 16 ונסביר מה הבעיה.

אז יש לי פה uint8. אז יש לי פה שמונה בתים, ואחרי זה ה-compiler אומר "סבבה, אני לא אכניס לשם עוד מידע, ואני ארד שורה כדי לקחת את pressure".

## Structs

איך אנחנו רואים את זה? בואו נדבר על struct. בעצם אנחנו חוזרים ל-struct ואחרי זה נדבר שוב על padding.

אז struct - אם אני רוצה להגדיר structure מסוג מסוים, אז יש לי את `sensor_data`, אוקיי? ואני אומר לו "זה ה-sensor", וזו הדרך לאתחל struct, אוקיי? אז עם סוגריים מסולסלים, אז הערך הראשון זה הערך הראשון פה, הערך השני זה הערך השני פה, והערך השלישי זה הערך השלישי כאן.

יש גם אפשרות לאתחל את כולם לאפסים, אני אראה רק את הדוגמה: בעצם אני עושה ככה, ואני שם פה 0, אז כולם מאופסים. אבל בואו לא נעשה את זה, כי אי אפשר להגדיר משתנה פעמיים.

יש עוד דרך להגדיר structure: אז יש לנו structure מסוג `sensor` שה-structure הזה הוא בלי padding. מה הכוונה שהוא בלי padding? זאת אומרת שבסוף של ה-8 bit הזה הוא לא דוחף לי סתם אפסים, ישר מתחיל ה-pressure, אז הוא יתפוס פחות בתים.

איך אנחנו יודעים כמה בתים תופס כל structure? אז יש לנו איזשהו operator שנקרא `sizeof`. רק שתדעו ש-`sizeof` מחושב בזמן קומפילציה, הוא לא מחושב בזמן ריצה. אז בעצם זו לא פונקציה באמת, פה יופיע מספר. הוא לא נקרא לפונקציה `sizeof` בקומפילציה, ואני חושב שנוכל לראות את זה כשנעשה את ה-disassembly, אז נוכל לראות את זה.

אז בואו אני אעשה Ctrl+S ונעבור על התוכנית. אני רק קצת אגדיל אותה כדי שנוכל לראות באמת מה יש כאן.

אז ככה, הגדרתי שני struct-ים. structure אחד הוא עם padding, הוא עם הוספה של אפסים כאשר אנחנו לא עומדים ב-alignment של ה-MCU. ה-structure השני הוא בלי הוספה של אפסים, זאת אומרת שברגע שמסתיים משתנה אחד מתחיל ישר אחר.

שאלה של מאור: כן, אם אתה לא תשתמש ב-`packed`, אז אתה יכול להעביר את המידע יותר מהר, אבל אתה צריך שזה יהיה מסונכרן בין שני הגדלים. זאת אומרת שאם אני אעביר ל-MCU אחר את ה-structure הזה, והוא יגדיר את אותו structure עם `packed`, אז יכול להיות שכשהוא יקרא את ה-structure הזה – וזה משהו שאוהבים לשאול בשאלות עבודה – אז בעצם יופיע לו במקום של האפסים יופיע לו המידע של pressure, לדוגמה, אוקיי? ואז הוא יקבל pressure לא נכון. אז צריך לדעת להגדיר את זה או הגדרה שהיא קומונלית לשניהם, או שההגדרה תתאים, ששניכם תגדירו באותה צורה את ה-structure.

אוקיי. אחר כך אנחנו נרצה להדפיס. אז איך אני ניגש למשתנה מתוך ה-struct? אז אני יכול להשתמש באופרטור נקודה, והאופרטור נקודה הזה נותן לי את המידע עצמו. אז אנחנו עושים פה `printf`, אוקיי? בואו נקטין את זה שוב כדי שייכנס בעמוד.

אז ככה: ב-`sensor1`, את ה-temperature אנחנו מדפיסים אותה כ-`ld`, זה ה-format specifier שלו. ו-`ld`, למה זה בעצם `ld`? כי שמנו פה `uint32_t`, ו-`uint32_t` זה `int32_t`, ו-`int32_t` זה `long int`, אוקיי? אם הייתי שם פה 64 אז היה `lld`. ו-`lld` הוא `sizeof` מחזיר את זה, אבל `sizeof` מחזיר את זה בתוך ה-compiler של המחשב, ב-compiler של ה-MCU אז הוא מחזיר `d`.

אז יש לנו את ה-temperature, יש לנו את ה-pressure, אוקיי? מה שנחמד ב-structs שאני לא חייב לגשת אליהם ברצף, אני יכול לגשת לכל אחד מהכיוון שלו. אני לא יכול לגשת ל-pressure ואז לזה, בסדר. ויש לי את ה-status, אז אני מדפיס גם את ה-status לפי מה שהתחלתי אותם. אותו דבר גם בשני.

אז ככה ניגשים. בואו נקמפל ונראה את ההדפסה. בהמשך אני מראה לכם איך זה... אנחנו נגיע לזה יותר מאוחר בשיעור, פשוט ככה התקמצנתי עליכם.

אוקיי, אז עדיין אנחנו כותבים לתוך ה-MCU, כי אני רוצה שהקוד הזה ירוץ ב-MCU על ה-core של ה-MCU, בעצם על ה-CPU של ה-MCU. בואו ננקה פה את הכל כדי שאני אוכל להראות לכם דרך נוספת שבה אני דורס את ה-`write`.

אז אוקיי, בואו נחזור שנייה ל-`write`. אם אתם זוכרים, אמרתי שברגע שאני שם את `itm_debug`, אז הוא מודבק לפה, נכון? שזה מה שקורה בתוך ה-`include`. בעצם ה-`include` לוקח את הקובץ header ופשוט מדביק אותו כאן ב-pre-compilation, שאנחנו נגיע לזה עוד רגע.

אז מה שעשיתי זה לקחתי את ה-debug והוספתי לו עוד איזה פונקציה של `write`. מה הפונקציית `write` הזאת עושה? אז אם אני רוצה להגיע לזה, אז אני לוחץ על Ctrl. אז Ctrl ובלחיצה על הפונקציה לוקח אותי ל-implementation של הפונקציה. ויש פה שתי אימפלמנטציות: אחת מהן זו האימפלמנטציה המקורית שמוגדרת עם `__attribute__((weak))`, שזה מה שנותן לנו ה-GCC כדי שנוכל לדרוס אותה. ובעצם זה מה שאני עושה - ככה אני דורס. אז הגדרתי שוב, הגדרתי פונקציה, ונמצא את זה בתוך `itm_debug`, לא כמו מקודם. הפעם גם יש את הפונקציה עצמה וזה מה שהיא עושה.

אז מה יקח ה-compiler? הוא יודע שאם אחד מוגדר עם `weak`, אז הוא לוקח את השני, אוקיי? אז הוא לוקח את זה ויש לנו את ה-`ITM_SendChar`, שזה בדיוק מה שעשינו מקודם, וככה אני לא צריך ללכת לתוך הקוד, אני פשוט מוסיף את זה עם ה-header עצמו. בסדר גמור? אני כבר לא רואה אתכם, כי נעלם לי ה-זה, אבל תגידו לי שהבנתם.

– הבנתי.

תודה מאור. אז בואו עכשיו, אני מקווה שלא איבדתי את הרצף, אז בואו נחזור ל-source. ופה גם כן הפעלתי את ה-debugger, והפעלתי את ה-ITM כמו שעשינו מקודם. אז אם אתם צריכים לעשות את זה, תעשו את זה אצלכם.

אוקיי, ופה נלך לכאן. זה כבר... אני מקליט. שוב, זה בשביל זה, שתראו שהם ב-0, ונעשה שורה-שורה. כשאומרים שורה-שורה אז יש לנו את `Step Over`. `Step Into` זה להיכנס לתוך פונקציה, `Step Over` זה לעבור לשורה הבאה.

אז אנחנו מגדירים את ה-sensor, אוקיי, אז נוכל לראות אותו מוגדר כאן. כרגע ה-sensor מוגדר, ו-`sensor2` עדיין לא מוגדר, אז יש פה מלא זבל. ברגע שאני אגדיר אותו, אז בעצם הדאטה ייכתב לי בפנים, אז יש פה את ה-25, וככה אנחנו יכולים לראות את המשתנים עצמם.

ועכשיו אני אעשה את ההדפסה. אז אני אדפיס את שלושת המשתנים של... שלושת ה-members, זה השם, members של `sensor`, ואני גם אדפיס את ה-`sizeof`. אז אנחנו רואים שה-`sizeof` הוא 8 בתים.

אוקיי, למה 8 בתים? יש לנו 4 בתים ל-`uint32_t`, ויש לנו בית אחד של ה-status, עוד בית של padding שהוא בעצם אפסים ריקים, ויש לנו את הבית הזה של ה-16, ביחד 8 בתים, למרות שאנחנו סופרים פה 5.

ואם אנחנו נעבור עכשיו ואנחנו נדפיס מה שנמצא בתוך זה, אז אנחנו נראה שבעצם יש לנו פה 7 בתים. למה? כי אנחנו בעצם לא עושים את ה-padding הזה. עשינו את ההדפסה וזה.

## Padding & Alignment on a 32-bit MCU

עכשיו, אז עכשיו זה היה ההסבר על ה-padding. אז נעבור עליו מהר: בעצם זה עוד בתים שה-compiler דוחף לנו כדי שנוכל לקרוא מהר יותר. בעצם אם אני לא צריך לבחור באיזה בית אני משתמש, ואנחנו נראה עוד רגע, או יותר מאוחר, האמת שזה יקרה בסוף השיעור, אז אני יכול פשוט לקרוא כל פעם את ה-word אחד, זה בעצם פקודת assembly אחת של `LDR` (Load Word), ואני יכול להמשיך הלאה.

אז למה זה משנה? כי אם אני בעצם קורא את זה הרבה יותר מהר, אני יכול לעבוד מהר יותר, ואני עושה פחות פעולות כדי לעשות אותו דבר, אז זה נותן לי את ה-performance benefit. ואם לא, אז אני יכול להשתמש ביותר זיכרון, אוקיי.

## Bitfields

הלאה, בואו נדבר על bitfields. אוקיי, אני עוצר את זה פה. בוא נמשיך, כי אני לא חושב שיש הדפסות. אבל רק שתדעו שאחר כך אני מעביר לתוך פונקציה שעושה `modify`, אז בעצם אני מעביר לה את ה-struct הזה ואת ה-struct הזה. מה זה האופרטור הזה? האופרטור הזה זה אופרטור address. בעצם הכתובת של ה-sensor כאן, אני מעביר אותה לתוך הפונקציה הבאה, והפונקציה הזאת תדע לשנות אותה ולהחזיר. אז נתתי לכם את ה-זה, בואו נמשיך ונדבר על bitfields.

מה זה bitfield? בואו ניכנס ל-source. אז עכשיו אנחנו בתוך ה-bitfields. אז bitfield זה איזשהו struct שבו אני יכול להגדיר כמה bits כל משתנה, אוקיי? אם מקודם השתמשנו ב-uint-ים, אז עכשיו אני מגדיר משתנה שהוא `uint32_t`, אבל בעצם זה שקר. זה לא לגמרי שקר, אבל אם אני לא משתמש ב-`packed`, אז זה שקר לגמרי.

אוקיי, אז בעצם בתוך bitfield אני משתמש בנקודתיים האלה ומגדיר 3 bits לכל דבר. זאת אומרת שעכשיו מה-struct הזה, ה-3 bits הראשונים הם יהיו ה-`mode`, ה-bit הבא יהיה `enable`, ה-4 bits הבאים הם יהיו סתם שמורים, בעצם לא משנה, איזשהו ערך שהוא לא רלוונטי לנו, לא קריטי, הרבה פעמים יש שמורים כאלה. ויהיה לנו עוד 8 bits של `counter`, ו-16 bits של `unused`, אוקיי?

אז בעצם רשום פה 32, אבל מה שקובע כאן זה בעצם הנקודתיים האלה. בסדר? אז בואו נפתח את המחשבון. נעשה כזה דבר: יש לנו 3 bits, אז יכול להיות לי עד 7 של `mode`, אוקיי? שזה 2 בחזקת 3, שזה 8 mode-ים.

אחר כך יש לי bit אחד של `enable`. בואו נגדיל את זה שנייה. יש לי bit אחד...

נשמרים, לא אכפת לנו מהם. ואז יש לנו פה שמונה ביטים של counter, אוקיי? אז עד 255 ספירות, ועוד 16 ביטים שהם unused. אז זה לדוגמה ה-ControlReg שלנו, סתם נתתי לזה שם. אוקיי, אז הוא 32 ביט בסך הכול.

איך אני ניגש? אז אם אני לוקח את הרגיסטר הזה, ואני מגדיר לו כתובת בזיכרון, את זה שאלת יובל, אני מגדיר לו כתובת בזיכרון, אז אני יכול באמצעותה לגשת ישירות לערך שם בצורה נורמלית ולא עם ה-bit fields האלה. ואז הקומפיילר עצמו הוא יחליט איך ייראו הפעולות באסמבלי. אז זו דרך יותר מהירה לעשות את זה.

סבבה, זה bitfield. ועם bitfield הרבה פעמים כדאי לדבר על union. מה זה union? union זה בעצם איזשהו משתנה שהגודל שלו הוא הגודל המקסימלי בין כל הערכים שאני שם פה. אז אם זה 32 וזה 32, אז הגודל של ה-union הזה שנקרא `DataConverter_t` הוא 32.

אז בואו נדבר שנייה על מה ששאלת. יש לי שתי אפשרויות להגדיר struct: יש לי אפשרות אחת להגדיר struct ואת השם כמו שעשינו מקודם, ויש לי אפשרות אחרת לעשות `typedef`. `typedef` בעצם אומר שה-type הזה שהגדרתי, שה-type הזה שנקרא struct עם השם נגיד... בואו ננסה לעשות את הקונבנציה הנכונה, שעושים את זה ככה: `ControlReg_`, אז כל פעם במקום לעשות `struct ControlReg_` אני יכול להשתמש ב-`ControlReg_t`. `t` זה קיצור של type, אוקיי?

אז זה בדיוק מה שעשיתי כאן. אותו דבר אגב אפשר לעשות עם union: בעצם union שנקרא `DataConverter`, אז יש בו או בתוך הזיכרון הזה שמוגדר, אז יש או את ה-`ControlReg_t` או את ה-`uint32_t`. אז אלה האפשרויות, ובעצם אם אני רוצה לגשת למידע, אז המידע נמצא גם פה וגם פה.

אז אם אני רוצה לגשת אליו בצורה של `ControlReg`, אז אני ניגש בצורה הזאת: אז `register`, אופרטור של נקודה, `bitfield_data` שזה האחד הזה, ואז אחר כך אני ניגש ל-`mode` שמכאן ואני יכול להכניס בתוכו 5.

אם אני רוצה לקרוא, ופה אנחנו קצת נוגעים בזה, אז אני יכול לעשות את זה בשתי צורות: או שאני לוקח את ה-bitfield הזה וממיר אותו ל-pointer - שאנחנו נדבר על פוינטרים בשיעור הבא אז אל תתרגשו - ממירים אותו ל-pointer ואז אנחנו קוראים אותו והוא יטפוס. אז אנחנו נקמפל את זה. או שאני פשוט יכול לקחת `reg` ובעצם להשתמש בייצוג בזיכרון שהוא unsigned. אז שניהם קיימים על אותו המקום. אני בעצם כותב לפה, אני כותב לפה.

מה היה קורה אם הייתי עושה פה `uint64_t`? אז בעצם הגודל של ה-DataConverter הזה היה הופך להיות 64, כי הוא הגודל הגדול ביותר. וב-32 ביט הראשונים הם היו משותפים בין שניהם. ואחר כך היה פה 64 שהיית יכול לגשת אליו רק דרך ה-`uint_data`. אוקיי? זה בגדול. אנחנו נראה בזיכרון איך זה נראה, אבל בואו נמשיך הלאה.

אז בואו אני אעבור שנייה על המצגת כדי לראות שלא פספסנו כלום. אז בעצם bitfield נותן לי אפשרות לגשת לביטים ספציפיים בתוך structure. מאוד נוח, ככה אני יכול לכתוב את זה ואז אני לא צריך להשתמש ב-masking.

הרבה פעמים משתמשים בזה כי אני רוצה לגשת לרגיסטרים, אז דיברנו כמו שיובל שאל. אז מתי משתמשים בו? כשאני חושב שהקומפיילר יעשה עבודה יותר טובה ממני בביטים. לרוב נכון, ולרוב יש לו סט יכולות יותר גדול ממני. הוא מאוד מהיר ומאוד נוח, אבל אגיד את האמת, אנשים מתעצלים לעשות הגדרות כאלה של לשבת ולהתחיל לכתוב כל רגיסטר, את כל הביטים שלו, את כל ה-members שלו ביט ביט, אז הם משתמשים בזה. אם אני לא משתמש בהכול גם אז בכלל זה נוח. ואז בדיעבד אנחנו הולכים לכתוב הרבה כאלה.

אז זה מאוד נוח. endianness פחות רלוונטי פה, ואם אנחנו משתמשים בכמה MCUs, אז צריך לדבר על ה-bitfield, אבל פחות קריטי.

Union: union בעצם זה ה-type שהוא בעצם משלב את שניהם על אותו מקום בזיכרון. ברגע שאני מגדיר `DataConverter_t`, אני בעצם מגדיר את הזיכרון הגדול ביותר ואני יכול לגשת לשניהם באותה צורה. משתמשים בזה כשאני רוצה לחסוך זיכרון. זו איזושהי צורה לחסוך זיכרון. אני יכול להשתמש גם בפונקציונליות הזאת וגם בפונקציונליות הזאת על אותו סוג של משתנה. עושים את זה הרבה פעמים בניהול פקטים, אבל במקרה שלנו חוסכים פה זיכרון ואנחנו יכולים גם לגשת לזיכרון בשתי צורות שונות, ביותר קלות, והקומפיילר יעשה לנו את העבודה. וואלה נחמד.

אפשר להשתמש בזה על כל הסוגים. הייתי יכול גם לשים פה `float`. אוקיי? ואז היה לי פה עוד איזשהו סוג של `float` והייתי יכול לגשת אליו. אבל בעצם הגודל יהיה הגודל המקסימלי, אז או `uint64_t` או `float`. לדעתי `uint64_t` יותר גדול מ-`float` בתצוגה הזאתי. אוקיי, אז זה ה-bitfield עצמו.

## ארגון פרויקט

ועכשיו בואו נדבר קצת על... רגע, שאלות. יש למישהו שאלות בנושא? לא, יופי.

בואו נדבר קצת על הארגון של פרויקטים בזיכרון. אז בשביל זה אני פותח את הדבר הזה כאן ואנחנו נדבר על מה יש כאן. אז יש לנו כמה תיקיות, חלקן וירטואליות וחלקן לא. אז בואו נגדיל את זה כדי שנוכל לראות. הוא מגדיל רק את זה, הוא לא מגדיל גם פה? בסדר.

קיצור, אז יש לנו את ה-Binaries. בעצם ב-Binaries יש לנו את קובצי ההרצה שבסוף הם מורצים פה. מכיוון שאנחנו עובדים בבעצם single process, יש לנו רק CPU אחד שמריץ, אז לרוב יהיה לנו רק binary אחד. בתוך ה-binary עצמו יש את כל ה-headers ואת כל ה... אנחנו נסתכל על ה-ELF ואנחנו נעשה לו בעצם אנליזה בעוד איזה רגע בערך, ואנחנו נראה בעצם מה נמצא שם בתוך ה-binary עצמו.

אחר כך יש לנו את ה-Includes. יש לנו Includes ויש לנו Inc. אוקיי, אז בגדול זה לא אותו דבר. ה-Includes האלה זה בעצם כל מיני דברים סטנדרטיים שאנחנו מקבלים מה-C library שה-STM נותן לנו. בעצם כל הפונקציונליות של C זה ה-Includes האלה.

ויש לנו את ה-Inc הזה. לרוב כאן - ופה הוא ריק - לרוב כאן דוחפים את קובצי ה-`.h`. אני לא עשיתי את זה, אבל בואו נעשה את זה עכשיו. אז קובץ ה-`.h` עצמו צריך להיות כאן. ואז הקומפיילר מוגדר שאת כל קובצי ה-`.h` הוא לוקח מכאן, ואת קובצי ה-`.c` הוא מדביק אחד לשני ב-pre-compilation, שאנחנו מגיעים לזה עוד רגע.

אחר כך יש לנו את ה-Source, כל הקבצים של הפונקציונליות והקוד, אז הם נמצאים בתוך הקבצים של Source. מחלקים את זה לפי נוחות. בעצם ה-main מריץ. אם אני בעצם מאגד את כל הפונקציונליות של ה-ITM, אני אשים את זה בתוך פונקציה אחת. כל ה-system calls, שזה sys calls, אז אני בתוך אחד. ואת ה-sys memory אני אשים בתוך קובץ אחר. ואני אשתמש ב-headers שלהם כדי לחשוף את הפונקציונליות. או אם אתם זוכרים, דיברנו על `extern`, אז אני יכול גם להשתמש ב-`extern` כדי בעצם לקחת פונקציה משם מקובץ אחר בלי header לדוגמה.

אחר כך יש לנו את ה-Startup. בעצם זה איזשהו קובץ מסוג `.s`, שהוא מגדיר לנו כל מיני הגדרות על ה-CPU עצמו. אז כשנגיע לאינטראפטים אנחנו נראה כאן, הוא מגדיר לנו איזשהו handler של ה-reset, מה קורה כשעושים ריסט. הוא מגדיר לנו העתקה של דאטה. בואו נגיע שנייה ל-interrupt table. אז זה ה-interrupt table. אנחנו עוד לא יודעים מה זה אומר, נגיע לזה עוד איזה שיעור-שניים, אנחנו ממש מהר מתקדמים.

ופה אני יכול לקחת את כל האינטראפטים ואחר כך אם אני רוצה לדרוס אותם, כי הם גם כן מוגדרים ב-weak, ולשים להם פונקציונליות משלי. אז בואו לא ניתן לכם יותר מדי טיזרים, זה הולך להגיע בהמשך. אבל בעצם כל הפונקציונליות הזאתי כתובה כאן. ויש כל מיני סוגים כאלה. אז כל זה נמצאים בתוך ה-startup file, בעצם איזשהו קובץ שמגדיר לי כל מיני פונקציונליות של אינטראפטים שנכתבים לתוך ה-MCU ועוד כל מיני דברים, נגיד מה קורה אחרי ריסט, וכל מיני פונקציונליות של ה-MCU הסטנדרטית. אז משתמשים בקובץ הזה.

אחר כך יש לנו את ה-Debugging. בעצם ה-Debugging זה איזושהי... מקווה שאתם לא שומעים את התנים וזה רק אצלי. אז יש לנו את הקוד הזה, שזה בעצם בנייה של הקוד עצמו עם סימבולים. בעצם אנחנו לוקחים את כל הסימבולים שנמצאים... בעצם סימבולים זה גם הפונקציות שהגדרנו, זה גם המשתנים, זה גם הזיכרון וגם הזיכרון הגלובלי, וזה בעצם מגדיר את כולם, ואנחנו נראה אותם עוד רגע. אם אנחנו נספיק לייצר את ה-obj, אנחנו נשתמש בו כאן.

ואחר כך בסוף יש לנו את ה-linker script. בשיעור הקודם דיברנו על linking, שהוא דינמי וסטטי, אז בגדול אין לנו טעם לעשות linking שהוא לא סטטי ב-MCU. מה ההבדל בין לינק סטטי ללינק דינמי? לינק סטטי, זאת אומרת שאיזושהי ספרייה שאני הולך להשתמש בה, איזושהי פונקציונליות, אפילו קובץ header וזה, שאני הולך להשתמש בהם, איזשהו קובץ אובייקט שאני הולך להשתמש בו - הוא כבר נמצא baked in לתוך ה-ELF שלי, לתוך הקובץ הבינארי שלי. למה שהוא יהיה שם? כי אז אני יכול להשתמש בו בזמן ריצה. זה נקרא לינק סטטי. זאת אומרת שכל אובייקט שאני משתמש בו - ואני מתכוון על אובייקט בצורת קובץ - אז הוא גם כן נמצא בתוך הקובץ הבינארי שלי, הוא פשוט מודבק שם. זה לינק סטטי.

יש לינק דינמי. לינק דינמי יותר רלוונטי לנו כאשר אנחנו עובדים עם כמה פרוססים במקביל. כשאנחנו עובדים עם כמה תוכניות שרצות במקביל, אני יכול לטעון ספרייה אליי בצורה דינמית, ואז הקוד שלה, בעצם לא בדיוק הקוד, יותר ה-אסמבלי שלה, הוא נמצא כבר איפשהו בזיכרון ואני טוען אותו ומשתמש בצורה... זה נקרא lazy loading. בעצם ברגע שאיני צריך את הפונקציה הזאתי, אני הולך אל הקובץ שלה, זה קובץ מסוג `.so` הרבה פעמים, אני הולך אל הקובץ שלה, טוען אליי את הפונקציונליות, מפעיל אותה ואחר כך משחרר.

אנחנו לא עושים dynamic linking, כי אין לנו צורך בזה, אז אין פה אפילו את הפונקציונליות הזאתי ואין על מה לדבר על קובצי `.a` וקובצי `.so`, ש-`.a` זה לסטטי ו-`.so` לדינמי. אז אנחנו נמשיך כאן, אוקיי? נתתי איזושהי הרחבה שתדעו.

אבל אנחנו בעצם את ה-linking עושים באמצעות שני סקריפטים. והסקריפטים האלה: אחד, הוא מסביר איך אני עושה פלאש ל-MCU, בעצם לאן לדחוף כל דבר. אז בעצם בואו ניקח פה איזושהי דוגמה: נגיד את `ITM_SendChar`, אז הוא ישים בכתובת `0x8000280`. אז לתוך הכתובת הזאתי, יהיו ה-instructions שמגדירים את `ITM_SendChar`, בסדר? שזה בגדול זה זה.

אוקיי, אז הוא בעצם אומר לאן בזיכרון, בזיכרון של ה-MCU, תשים את הפקודות האלה. זה מה שאומר ה-script linking הזה, והוא נוצר אוטומטית. ואותו דבר גם ל-RAM, אז בעצם ל-RAM מה שאני עושה memory mapping, שאני ממפה אליי את הזיכרון של ה-MCU כדי שאני אוכל לכתוב אליו, לתוך הבאס, נדבר גם על זה, אז אני משתמש ב-script הזה. זה שזה נראה לכם כמו סינית, סבבה, אין צורך להבין את זה יותר מדי, אבל תדעו שזה מה שהקבצים האלה עושים. נמשיך הלאה.

## קובצי Header מול קובצי Source

אז מה זה header files ו-source files? סליחה שקפצתי, ואיך אנחנו משתמשים בהם?

אז header file, המשמעות שלו בעצם הוא מגדיר לנו איזשהו interface, הוא מגדיר לנו פרוטוטייפים של פונקציות, והוא מגדיר לנו מאקרואים, כל מיני constants - ששכחתי את השם של זה בעברית - ו-data structures. בעצם אני פשוט הולך וכותב עליו... בואו ניפתח את ה-header, בואו ניפתח את ה-header האחרון ששמנו, אז הוא נמצא בתוך ה-Inc.

אז בעצם יש לי פה פרוטוטייפים, אם הייתי מגדיר לתוכו את המאקרואים כמו שעשינו ב-bit manipulation, אז אני יכול לשים אותו פה. ומה שקורה, זה כשאני מקמפל, אז ה-header עצמו...

כל פעם מודבק לתוך ה-C. כל מקום שיש include, נגיד יש include ITM_debug.h, אז כל פעם שאני עושה את זה אז זה מודבק לתוך ה-C, לתוך הקובץ C. אז בעצם אם אנחנו ב-syscalls, אז ב-syscalls יש כאן ITM_debug.h. רגע, אז מה עושה הקומפיילר? הוא אומר: "אה, אוקיי, רשום את זה כאן". עושה כזה דבר. זהו. זה כל מה שעושה header file.

חוץ מזה שיש לנו פה איזשהו guard. זה נקרא ifndef guard. אז ברגע... הוא אומר: "אם לא מוגדר ITM_DEBUG_H, אז תגדיר אותו". ואז בפעם הבאה שנקרא ה-header file הזה, ה-include הזה עוד פעם, אז הוא אומר: "אה, אוקיי, זה מוגדר", וממשיך הלאה. ואז אנחנו לא מגדירים דברים פעמיים. זה בעצם מגן עלינו מלהגדיר משתנים או להגדיר פונקציות פעמיים. אז בגדול, מה שאני יכול לעשות ככה, אני יכול לעשות פה עוד פעם include ITM_debug.h והכול יהיה בסדר.

אז... רגע. למה? פעם ראשונה הוא יבוא, יגיד: "אה, זה לא מוגדר ITM_DEBUG_H? סבבה, אני אגדיר אותו". ידביק את מה שצריך ויסיים. ה-endif וה-ifndef זה בעצם הם ביחד. פעם השנייה הוא יבוא, אוקיי, ויגיד: "רגע, אם לא מוגדר... אה, כן מוגדר!". אז הוא לא נכנס ל-if הזה וזהו, לא קרה כלום. אז בעצם זה מגן עלינו.

אוקיי, אז מה עושים includes? includes פשוט רק מעתיקים את המידע שבתוכם לתוך הקובץ.

מה יש בתוך ה-source file? בעצם יש את האימפלמנטציה של הפונקציונליות שאנחנו משתמשים. בעצם כל הקוד שלי בעצם נמצא בתוך ה-C, חוץ מ-inline functions, אבל בואו לא ניכנס לזה כי אין לנו זמן. אז חוץ מזה, כל הפונקציונליות נמצאת בתוך ה-C והיא משתמשת ב-header files לעוד פונקציונליות.

בעצם ה-header file גם חושף פונקציונליות בתוך ה-C. אז יש לנו בתוך הקובץ C הזה את הפונקציונליות הזאת של ITM_SendChar ושל _write. אז ברגע שיש לי אותם, אז אני יכול לחשוף אותם באמצעות ה-header file בעצם לעוד קבצים. אז ברגע שאני עושה include, אז בעצם ה-ITM_SendChar, בעצם ה-prototype הזה נמצא בעוד קבצים. אז זה header files ו-source files.

## תהליך הבנייה (Embedded Project Build Process)

אז בואו נדבר על ה-build process, מה קורה. בעצם ה-build process שלנו הוא מורכב מכמה שלבים, אוקיי?

יש לנו את ה-preprocessing, או פרי-קומפילציה, או יש לזה עוד איזשהו שם. זה איזשהו שלב מקדים שהוא בעצם עושה את כל הפעולות שהן עם ה-hash הזה, אוקיי? ועוד כמה. כל מקום שיש לנו פה שימוש במאקרו, אז הוא לוקח את המאקרו הזה ובמקומו מדביק את זה. define מגדיר לנו מאקרו, אז הוא לוקח ומדפיס לנו. אז בעצם כל מקום שרשום BIT3, שהוא לא בהערה, אז הוא הולך ומדביק אותו. וכל מקום שיש include, אז הוא לוקח את כל הפונקציונליות הזאת ומדביק לנו אותה פה, כמו שאמרתי מקודם. אז זה הפרי-קומפילציה, אוקיי?

אחר כך יש לנו את הקומפילציה. הוא בעצם לוקח את הקוד שכתוב בשפה, במקרה שלנו ב-C, וממיר אותה לאסמבלי. בעצם לפקודות שראיתם אחר כך.

אחר כך יש לנו את האסמבלי, שזה עוד שלב, שהוא לוקח את כל הפקודות אסמבלי והוא ממיר אותן לשפת מכונה ונותן לנו קובץ מסוג o, שזה קובץ object file.

יש לנו אחר כך את ה-linking, שזה בעצם ההשמה של כל הקבצי o. את כל הקבצי o אני הופך אותם לתוך קובץ ELF, שזה ה-binary בסוף, או סוגים אחרים של binaries, אוקיי?

וב-post-build אני לוקח את ה-ELF ואני דוחף אותם לתוך ה-MCU, שזה ה-flashing.

אז בואו נעבור שנייה ככה מהר. אז בעצם, שוב, preprocessing עושה לי את כל ה-defines, את כל ה-includes ואת כל ה... יש גם אפשרות לעשות ifs בתוך ה-preprocessing. ראינו פה את זה ב-if guard, אוקיי? בואו נחזור ל-ifndef guard. אז אני יכול לעשות ifdef. אז if defined הדבר הזה, אז תעשה את הקוד הזה. אם הוא לא defined, אל תעשה אותו. אז יש לנו גם ifndef. If not defined זה, אז תעשה את זה. אז כל עוד לא מוגדר לנו הדבר הזה, אז הוא יפעיל את זה. אז זה בעצם ה-conditional compilation או ifdefs, זה הצורה שבה משתמשים בהם בעברית.

אחר כך, מה התוצר של זה? התוצר של זה זה קבצי i, והקבצי i האלה, אם למישהו יצא אי פעם לראות, אז זה פשוט הרבה מאוד קבצים מאוד ארוכים ומאוד גדולים, שכל דבר שמוגדר בקובץ הזה פשוט מודבק לשם. אז אתה יכול לכתוב שלושה includes ו-printf, ויצא לך משהו עצום.

אחר כך יש לנו את הקומפילציה. בעצם בקומפילציה אנחנו לוקחים את הקבצי i עצמם ואנחנו הופכים אותם לאסמבלי. אז אנחנו עושים gcc -S, או ה-gcc הספציפי שלנו, gcc -S. רגע, בואו אולי אני אעשה פה איזשהו ניקיון ונוכל לראות את כל הפעולות. אז build. אני אגדיל את זה.

אז בהתחלה אני לוקח את ה-gcc הזה. אני חושב שה-gcc שלנו מדלג על שלבים, אז אנחנו לא עושים את ה-pre-compilation כשלב אחד, אז הוא עושה את הכול ביחד. נכון? כן, אז הוא עושה ישר לתוך קובץ אסמבלי, אוקיי? אז הוא לוקח את הקבצים שלנו וממיר אותם לתוך אסמבלי. startup, זה האסמבלי שלנו. אז הסיומת שלו היא S.

ואת האסמבלי הזה אנחנו רוצים להפוך לקובץ object. אז את הקובץ object הזה אנחנו עושים gcc, שזאת הפקודה, -c, שזה איזשהו flag, ואנחנו אומרים לו אחר כך: "תקח את הקובץ אסמבלי ותמיר אותו לתוך להיות קובץ object", בעצם לקובץ כבר מקומפל, ששם נמצא ה-machine code.

ובסוף אנחנו לוקחים את כל ה-machine codes האלה ביחד ואת כל הסימבולים שלהם, שזה ה-functions והכול, ואנחנו הופכים אותם לתוך להיות קובץ executable, בעצם קובץ הרצה, ה-binary, זה אותו דבר. אז ה-linker script שלנו לוקח אותם ועושה את זה. אז gcc, כל קבצי ה-o files שלנו, כל קבצי ה-object, וה- -T נותן לנו את ה-linker script עצמו שאנחנו משתמשים, ו- -o זה ה-output, זה האפליקציה שאנחנו הולכים להשתמש בה, אז app.elf.

ובסוף, אחר כך יש לנו את ה-post build ELF. אז אנחנו מקבלים את ה-ELF, שזה ה-binary שכבר אפשר להריץ אותו, והוא מורכב מהקוד עצמו, הוא מורכב מה-data, מה-uninitialized data שזה data שהוא עוד לא אותחל או יאותחל בזמן ריצה, ו-metadata שזה ההדרים, שהדרים זה הקבצי header, ה-symbol table שגם נותנים לנו אפשרות ליגשת מבחוץ, ו-debug info.

לא פשוט, זה מעניין.

- למה זה קובץ o אם זה בינארי? לא הבנתי.

- למה זה קובץ o? זה קובץ object. את הקובץ הזה הוא עדיין לא...

- למה זה object כאילו אם זה בינארי? זה נשמע כמו... כמו object כאילו.

- זה השם של הקובץ, למה זה object כאילו... האמת שאני לא סגור על זה.

- אז סתם שם, יענו? זה לא שזה נשמר באיזה...

- זה לא נגיד object כמו של object oriented שבעצם יש לו צורה. זה פשוט קובץ שאפשר לקחת אותו ולעשות לו linking לכל מיני. אז נגיד הוא כן object מהצורה שאם היית כותב את הקוד בצורה מאוד מסודרת, אז היה לך בסופו של דבר object של כל מה שיש שם. היה לך object של ה-main שאתה יכול בסופו של דבר ללנקג' אותו לתוך בינארי. ואז יכולים להיות לך כמה בינארים. פחות רלוונטי בתצורה של מיקרו-קונטרולרים עם CPU אחד, שזה מה שאנחנו עובדים, אבל כן רלוונטי בכללי, כאילו גם בקורס ליונוקס. בתכלס בונה כמה פרוססים, כמה תוכניות, ואתה רוצה להשתמש באובייקט הזה כדי להפוך כמה בינארים. בסדר?

- כן.

## מבנה קובץ ELF וכלי ניתוח

אוקיי, אז מה זה ELF? ELF זה הקובץ בינארי, ואנחנו נראה איך הוא נראה. אז בעצם ELF... יש לנו כמה טולים של GNU שנותנים לנו. יש לנו readelf שנותן לנו לקרוא את הקבצי header שמשתמשים בהם, את ה-sections ואת הסימבולים. יש לנו objdump שבעצם מראה לנו את ה-disassembly עצמו, אנחנו יכולים לראות את האסמבלי בצורה קריאה.

ואיש לנו את הסיבה שאנחנו צריכים את זה. אם אני רוצה לראות את כל הפונקציות ולהבין מה לעזאזל קורה באיזשהו מקום, אז אני יכול להשתמש ב-readelf. אם אני רוצה לראות אם קובץ אותחל בצורה לא נכונה, אז אני יכול להשתמש ב-objdump, אוקיי?

איך אני עושה את זה? אז אנחנו משתמשים בהם בצורה הזאת. readelf, אז אני בעצם לוקח את ה-program עצמו. אז readelf -a את ה-ELF הזה, אז הוא בעצם ייתן לי קובץ תוצר של כל האסמבלי. אם אתם בעצם מקמפלים, אז יצא לכם כבר כאן אותו. בתוך ה-debug, אז יש לנו את הקובץ list. אוקיי? שקובץ list עצמו, זה כל מה שמשתמשים. אז יש לנו את האזור של הטקסט, זה מגדיר את האזורים. ה-bss זה האזור שלא אותחל, ה-data, יש לנו איזשהו array, כל מיני דברים. יש לנו דברים ספציפית של ARM, כל מיני דברים של זה. ואחר כך יש לנו את האסמבלי עצמו. זה איתחול של הגלובליים, זה ה-stack frame dummy, אנחנו נדבר עליו מתישהו לקראת סוף הקורס שנדבר על ה-scheduler. ואז שוב, כבר ראינו את הדוגמה הזאת, אז זה ה-ITM עצמו. אז זה בעצם לקחת את ה-n שווה 1 0x. בעצם אם אני אראה לכם אולי את ה-disassembly של ה-bit manipulation אז זה אולי יותר מעניין.

אז בואו נעשה את ה-objdump ל-structures, נכון? לפני לא מעט זמן, עשיתי לכם איזשהו טיזר ואמרתי לכם שבעצם נקראות יותר פקודות כשאני הולך להשתמש ב-structure שהוא packed ולא packed. אז אני לקחתי את ה-objdump ובגדול הוא נמצא כאן, בואו אני אראה לכם. structures.elf, בואו ניקח את זה כדי שנוכל לקפוץ לשם.

אוקיי, נעשה פה Ctrl+F Ctrl+V. אז יש לנו פה modify, זה בעצם רק המיקום של הפונקציה בזיכרון, היא נמצאת בכתובת הזאת, 08...2dfs. וההגדרה שלה, אז תראו מה אני עושה. אני לוקח את s1->status. למה זה עם חץ? כי בעצם ברגע שאני רוצה לגשת לפוינטר, אני משתמש פה באופרטור חץ ולא בנקודה. אז האופרטור הזה בעצם מה שהוא עושה, אז הוא לוקח את רגיסטר 3 והוא שם בו את הערך 3. כשרוצים לגשת לערך סתמי אז משתמשים ב-hash הזה באסמבלי. ואחר כך אני אומר לו... אני אומנם לא זוכר את כל האסמבלי, אבל אני חושב שזה store byte ואז בעצם הוא לוקח והוא מכניס את זה. אז בואו עכשיו, אחרי שראינו איך זה נראה, אז בואו נראה... בואו נקפוץ לפה כי פה כבר הכנסתי את השורות. אז אם...

`sensor1_data` הוא בעצם ה-struct שהוא עם padding. אז אם אני ניגש ל-status, אז אתם רואים הוא ישר לוקח את הערך שבו אני רוצה לשנות, אני רוצה לשים בו את הערך 3, ועושה את R3 בתוך R0, שזאת אומרת ש-R0 הוא המקום אליו הוא העביר את ה-struct הזה שקיבלת בתוך הפונקציה.

ואם אני רוצה לגשת ל-pressure, ש-pressure נמצא כאן, ש-pressure בעצם הוא ישר אחריו, אז עוד פעם הוא עושה אותו דבר, אוקיי? לוקח את ה-1225 שזה הערך ששיניתי, מעביר לכאן את זה.

מה קורה כאשר אני עושה את אותו דבר, אבל בעצם על struct שהוא packed, שהוא בעצם צפוף? אז בעצם זה, כי הוא בתוך... הוא נמצא ב-alignment, אז זה נראה ככה. אז בעצם לשים 3 נראה אותו דבר, אוקיי. וגם זה נראה בהתחלה אותו דבר.

אבל אז אנחנו בעצם עושים איזשהי פונקציה נוספת, פונקציונליות נוספת שאנחנו עושים... אנחנו בעצם לוקחים את ה-data עצמו ואנחנו מזיזים אותו. אז בעצם אנחנו לוקחים את ה-1025 הזה, ו... האמת שאני לא סגור על ה-assembly, אבל אנחנו לוקחים את זה ואנחנו מזיזים אותו לתוך register אחר. וכדי להכניס אותו לתוך ה-register הזה, אז אנחנו צריכים לעשות לו left shift ב-8. אז אנחנו עושים איזשהו OR ל-byte השני ומזיזים אותו, ואז אנחנו יכולים להשתמש בהכל. אז אתם רואים, יש לנו פי שניים יותר פעולות כדי לעשות את אותו דבר. זה בגדול מה שרציתי להראות לכם.

- "רואים שגם הידע שלך יותר חזק ב-assembly... האמת שאני לא זוכר טוב את של ARM, אבל אני מבין גם מקודם שה-STRB זה store byte. אז בעצם הוא עושה שתי פעולות של store byte כדי לעשות את ה... כי הרי uint16 זה שני בתים."

- "נכון. אז כאילו הבית הראשון נמצא ישר אחרי ה-status, והבית השני נמצא בעצם פה. כאילו, אם אנחנו חושבים על זה ככה, אז בעצם יש לנו עוד שתי פעולות. אה, אוקיי, אז יש store H, זה store... אני לא זוכר. כנראה שזה half word או משהו כזה, והשני זה store byte. כי half word זה 16. בסדר. זה לא היה העיקר. באמת שלרוב אנחנו לא צוללים ככה ל-assembly, אבל זה היה רק כדוגמה חמודה. אולי לפחות לי, כי יש לי איזשהו שגעון מוזר, בשביל להראות שבאמת packed עושה פי שניים יותר פעולות."

## מטלות

על ה-linker script דיברנו. זה בגדול, זה מידע על linker script. וזה היה כל השיעור של היום. בואו, רק רציתי שקצת תתעסקו ב-disassembly, אז בואו נכתוב שנייה assignments.

אז ה-assignments יופיעו... בואו נראה. יש לי פה VS Code פתוח של Bare Metal? כן. אז בתוך lesson 1 נפתח עכשיו file, אוקיי, נעשה assignments. וביחד נפתח איזשהו file, נקרא לו assignments.txt.

אוקיי, נעשה assignment 0:

`0. Compile and build your own Hello MCU.`

בסדר? ואחר כך יש לנו את 1. אז 1 רציתי שתעשו כזה דבר, בעצם תעשו איזשהו loop:

`1. Create a loop that gets bit location from 0 and mask size, and operation, and perform it on your dummy register.`

`Try to use functions, macros, and switches.`

Switch functions. למה? כי switch-ים יותר מהירים. סבבה?

ויש לנו את 2. אז את הדבר הזה, בעצם... אני לא יודע אם תעשו את הדבר הזה, אבל בואו תעשו כזה דבר:

`2. Copy this project to an STM32 project, and read the ELF, .list file.`

`Try to search for your functions in disassembly.`

יכול להיות שדחפתי פה קצת יותר מדי אנגלית.

`3. Go to ChatGPT and ask it about one or more assembly commands to convert it to human language.`

בשפה שלכם כאילו. אם יש פה שגיאות כתיב וזה, אתם יכולים להציע לי תיקונים, אני אתקן את זה.

## עבודה עם Git

בואו עכשיו אני אעשה לזה commit, ודיברנו גם קצת על Git. אז מה יש לנו כאן? אם אני עושה git status, אני רואה שיש לי פה מלא זבל, וזה פחות נוח. אז בואו אני אגלה לכם איזשהו סוד.

- "זה נוצר מה-ST, נכון? מה-IDE..."

- "כן, אני לא יודע מה צריך ומה לא, אז אני עושה commit להכל. כדי שגם לכם יהיה. אבל אם אתם מג'נרטים, אז יכול להיות שאתם משתמשים."

בגדול, את כל lesson 1 אני אכניס, אוקיי? כולל ה-metadata והכל, אתם תשתמשו במה שתירצו, בסדר? ויהיה לכם עוד תיקייה של assignments, סבבה? אז זה lesson 1. את ה-metadata עצמו אני לא הולך להכניס, כי אני חושב שהוא מייצר לכם לבד.

אז "After lesson 1 changes", סליחה, אוקיי, "lesson 1 assignments", וסבבה, commit code. נעשה commit. אי אפשר לעשות מפה, אז בטרמינל צריך לבדוק: git status.

אוקיי, אז אנחנו אחד קדימה. והוספנו את התיקייה של lesson 1, היא מופיעה כאן. עכשיו אנחנו נעשה git push, ואחר כך אתם יכולים לעשות גם git fetch.

כשאתם רוצים לעבוד עם זה, או שאתם רוצים לקחת את זה ל-branch חדש, אז אם אתם על ה-branch הנגיש, אתם יכולים לעשות git pull. אם אתם רוצים לקחת branch חדש, אז תשתמשו בבקשה בפקודה הזאת:

`git fetch --all --prune`

שיביא לי את כל ה-branch-ים האחרים. `--prune` שינקה לכם מידע שאתם לא צריכים מתוך ה-Git.

ואז הוא יביא לכם את הכל. ובמקרה שלכם, אם אתם אמורים להיות על זה, אז git pull פשוט יפתור לכם את הבעיה. שימו לב שאתם נמצאים בתוך ה-Git. איך אתם יודעים שאתם בתוך תיקיית Git? אז מופיע לכם כאן ה-lesson הזה, בסדר? אם הוא לא מופיע, אז אתם כנראה לא בתוך תיקיית Git.

וגם git status יכול להגיד לכם איזה branch אתם, ואם יש לכם דברים למשוך, אוקיי? ואם אתם רוצים לדעת באיזה branch אתם – `git branch`. אז ה-branch שאתם נמצאים בו הוא master.

- "למה ה-prune? למה לעשות את ה-prune?"

- "prune מנקה. ל-Git יש איזשהי תיקייה מוסתרת של metadata. והתיקייה הזאתי בעצם נועדה כדי לעשות indexing לכל המידע שנמצא ב-remote. מה זאת אומרת? שבעצם אם אני רוצה לעבור לאיזשהו branch, אז אני לא תמיד רוצה שכל ה-data... כאילו, הייתי רוצה לדעת איזה branch-ים קיימים ואיזה לא. אז את המידע עצמו אני מקבל מהמחשב שעליו הוא מוגדר, בעצם מה-remote. במקרה שלנו זה GitHub. אבל איזה branch-ים, אני מקבל מה-metadata. ואם אני עושה `--prune`, אז הוא גם יודע להביא לי את המידע וגם יודע למחוק מידע שכבר לא רלוונטי. אז זה נוח וזה מקטין וזה גם גורם ל-Git לעבוד יותר מהר. אז אני תמיד מוסיף את ה-`--prune`, ש-prune זה בעצם ניקוי."

אוקיי, אז היה לנו שיעור מאוד נחמד היום. אני אישית נהניתי, אני מניח שגם אתם. רגע, אני אעצור את ההקלטה שזאת לא תהיה הקלטה של 12 שעות ב-Fathom.