שיעור 1 — מבוא לדרייברים: GPIO
טוען נגן…
תמלול
## הגדרות סביבת עבודה וסנכרון קוד מ-Git
אז שלום. אנחנו היום בשיעור השני של הקורס דרייברים. יש לנו היום שיעור יחסית מעניין, יחסית אינטראקטיבי. אז אני רואה שכבר שלושה מכם נכנסו למצגות, ומישהו כבר לקח את ה-repository המעודכן. בחצי שעה האחרונה אוספתי את ה-assignments לכם, אז אני אראה. אז אני משתף את המסך. אני מצטער, המסך הוא לא רחב כמו בדרך כלל, אבל נעשה שטח, מה שנקרא. אוקיי, תעדכנו אותי שאתם רואים. אני חושב שכבר רואים. יופי.
אז בואו נתחיל. אמרתי שאני אראה קודם כול איך אנחנו לוקחים את השינויים מה-Git. אז יש לנו שתי דרכים: יש לנו אפשרות לעבוד עם VS Code ויש לנו אפשרות לעבוד עם Git Bash כמו שהתקנו. יש גם את Git GUI, תראו פשוט אם אתם יודעים לעבוד עם ה-GUI אז תראו איך אתם עושים את ההשלמות לבד. אז עם ה-Git Bash אני אראה פשוט איך עושים ואז עם ה-VS Code.
מתוך ה-Git Bash, שימו לב שה-path שלי זה איפה שעשיתי את ה-clone אליו, וה-main. אני לא יצרתי branches כי אני חושב שאנחנו כרגע רק עובדים כ-instance אחד של קורס, אז אין צורך ליצור branches. הוא תמיד יהיה איתכם מעודכן.
שוב, ברגע שנעשה `git status`, הוא יעדכן אותנו אם ה-remote, ה-remote שזה ה-instance שנמצא ב-cloud, אם הוא מעודכן. אז `git status`. אוקיי, אז הוא יעדכן שיש לי כל מיני קבצים שלא הכנסתי אותם. יש כל מיני קבצים שאנחנו לא צריכים, שזה קבצי meta-data, שזה המידע על הקבצים, וקבצי settings שאנחנו גם לא צריכים.
אז אם רואים שרשום לנו שאנחנו מאחורה ב-commits, אז הדבר שאנחנו צריכים לעשות זה `git pull`. אוקיי? ברגע שנעשה `git pull`, אם היו שינויים אז הוא מושך את השינויים. אם לא היו שינויים, אז הוא ירשום לנו "already up to date". אוקיי? ככה אנחנו מתעדכנים בשינויים מה-Git Bash.
יש עוד דרך אחת, יותר נוחה. הדרך היא באמצעות VS Code. באמצעות VS Code, זה כבר ה-assignment להיום, אז שנייה אני אסגור את זה. אז אנחנו הולכים, ימני, Open with Code. ופה הוא יפתח לנו את ה-repository עצמו. אוקיי, אנחנו נראה את כל התיקיות שמופיעות. אז זה באמת ה-assignment להיום, יש הרבה, אז מקווה שלא יהיה לנו הפרש גדול, אבל גם שתספיקו לעשות את זה.
אנחנו נותנים ל-source control להתעדכן. נעשה ככה קצת שתוכלו לראות. נותנים ל-source control להתעדכן. ומיפה שוב, אז יש לנו את ה-meta-data שאנחנו לא צריכים ואת הקובץ settings שאנחנו לא צריכים. וכשנרצה למשוך, אז נלחץ על שלוש נקודות פה, Pull, והוא יביא לנו את כל המידע שהשתנה. כרגע הוא מעדכן אותנו שאין לי שום דבר.
עוד דבר שהוא מציע פה, זה אם אנחנו רוצים לעשות periodic `git fetch`. אם בא לכם שזה יתעדכן, למה לא, אבל זה מיותר. זהו, שאלות לגבי זה?
## סקירת נושאי השיעור (Agenda)
יופי, אז אני מקווה שזה ברור לכולם איך אנחנו כל פעם לוקחים את השינויים. שימו לב שיש commits, אתם יכולים להסתכל על מה נוסף בכל commit. יכולים לראות ש-class1 זה ה-commits של השיעור האחרון. אז פה הוספתי את ה-assignments להיום, סבבה, ופה כבר הוספתי את שאר הקבצים של השיעור. אוקיי, מעולה, אז את זה אנחנו סוגרים.
אנחנו היום נעבוד עם ה-repository כאן, אז אני אסגור פה את הקוד שלא יעשה לכם ספוילרים של מה שהולך להיות. סבבה, זה צד אחד. צד שני, אנחנו עם המצגת. אני רואה שחלקכם מחוברים, כל הכבוד. slideshow, בסדר. נעשה את זה ככה.
אז מה אנחנו הולכים לעבור היום? אנחנו נתחיל לדבר קצת על ה-intro ל-drivers וספציפית GPIO basic concepts. נדבר על מה מרכיב GPIO, מה זה GPIO, נסביר על ה-GPIO pin ו-port, איך זה נראה בחומרה מאחורה. נדבר על GPIO modes ו-configurations, איך אנחנו בעצם מגדירים פין GPIO. אחר כך נעשה GPIO programming, נסביר קצת את ה-bit fields. נדבר איך אנחנו מגדירים alternative functions, נדבר על driver development ו-GPIO interrupts.
בשיעור הקודם תכננתי שנשב על איזושהי דוגמה של interrupt, לא עשינו אותה. ברגע שנסיים את השיעור היום, תוכלו לחזור למטלה מהשיעור הקודם ולסיים אותה, ככה שזאת מטלה שדילגנו, תוכלו לסיים אותה, סבבה? ואפילו בצורה טובה יותר.
אוקיי, אז רק שמישהו יעדכן אותי, הייתי שמח שאחד מכם יהיה עם מצלמה שאני אראה לפחות פרצוף שאומר לי "אני איתך". מעבר לזה, תגידו לי רק שאתם רואים כי אין לי פידבק מכם.
- כן, רואים.
יופי.
## מבוא ל-GPIO (General Purpose Input Output)
אז מה זה GPIO? GPIO זה ראשי תיבות של General Purpose Input Output. בגדול זה פינים, אוקיי? זה לא חייב להיות פינים, אבל 99% מהמקרים זה פינים. זה פינים שיוצאים מתוך הרכיב, מתוך המיקרו-קונטרולר שלנו, והם פינים עם שימוש כללי. מה הכוונה שימוש כללי? יש להם כמה פונקציות בסיסיות, שזה input, output. יש גם את ה-alternative function, אוקיי? שזאת הפונקציה הנוספת או הפונקציות הנוספות שיכולות להיות על הפין. אנחנו נסתכל היום ב-datasheet ונראה את הכול.
ויש גם מיקרו-קונטרולרים שכל GPIO יכול להיות analog input. ב-STM זה ככה, אוקיי? ב-STM כל GPIO יכול להיות analog input. אנחנו נדבר היום על GPIO במובן של input ו-output כללי. אוקיי? זאת אומרת, input לפין, output לפין, ואיך אנחנו מגדירים אותו כ-alternative function, אבל עדיין לא נסביר מה זה alternative function.
סבבה. אז מה זה GPIO? זה בעצם ה-peripheral שהוא הבסיסי ביותר, כל דבר שאנחנו עושים, אנחנו עושים עם ה-peripheral הזה, אנחנו בעצם מגדירים איזשהו פין. הפין זה בגדול התקשורת החוצה שלנו שלא RF מתוך המיקרו-קונטרולר, אוקיי? זה הרגליים האלה שבאמת לא מולחמות פה בתמונה ה-AI generated הזאתי, ומהן יוצא output או input, אז זה General Purpose Input Output.
זה בעצם ה-building block של כל ה-peripherals האחרים. זאת אומרת שאם אני אקח דוגמה PWM, שאני אסביר מה זה, זה Pulse Width Modulation, זה איזשהו פולס ברוחב פס קבוע בתדירות קבועה, אז ההגדרה שלו זה בעצם להזיז פין מלמעלה למטה בזמן קבוע לאורך זמן קבוע, אוקיי? אם אתם לא יודעים מה זה, זה בסדר גמור. עוד דוגמה ל-GPIO: רגל תקשורת של I2C, אוקיי? זה איזשהו GPIO. וכולי, כאילו ל-SPI, כל דבר שמזיז פינים מלמעלה למטה.
רגע, משהו פה קרה, אני רואה שה-Zoom קצת משחק איתי, שנייה. אוקיי, בסדר גמור.
## ארכיטקטורת הדרייבר ומבנה ה-API
אז ההבנה הראשונה ב-hardware abstraction לשכבה הזו, זה בעצם ה-GPIO. השיעור הקודם זרקנו הרבה מושגים שיש מכם שיגידו שהם מטורפים להבין, היום אנחנו קצת נעשה את זה בצורה ברורה יותר ונבין מה אנחנו עושים.
אז יש פה איזשהו פרויקט שאתם יכולים לקחת אותו אליכם. אני תוך כדי השיעור אכתוב את הפונקציות, אוקיי? אז מה שעשיתי זה כמו שאמרתי לכם בשיעור הקודם, לקחתי את הדרייבר עצמו, יצרתי enums לקונפיגורציות, יצרתי איזשהו struct לקונפיגורציה. אל תדאגו, אנחנו נעבור על הכול, אני בכוונה עובר על זה בריפרוף.
ויצרתי את ה-external API, שזה בעצם מה שיצא אלינו. אוקיי? אז `gpio_init`, הוא מקבל את ה-structure. Read, write, toggle, `read_port`, ו-`write_port`. אוקיי? אז אנחנו לא נממש את שני אלה, אבל אנחנו נעשה את כל השאר.
ובשיעור הזה אנחנו נתחיל לעשות מימושים של כל הדברים האלה וגם נסביר מאיפה אנחנו מביאים את המידע, ככה שהוא לא מומצא מהאצבע.
אוקיי, אז GPIO זה מאוד קריטי ל-system integration. למה? כי מעבר לפינים של תקשורת יש לנו גם פינים של שליטה, יש לנו פינים של power. למשל אם אנחנו רוצים לעבוד עם ממסר (relay), אנחנו יכולים לשלוט עליו עם פין GPIO. אם אנחנו רוצים לאפס איזשהו רכיב אז יש לנו אפשרות ל-reset pin. אם אנחנו עובדים עם איזושהי תקשורת שהיא באס ויש לנו רק chip select, סתם נותן איזשהו רמז לתקשורת עתידית שנדבר איתה, אז גם זה נעשה עם GPIO.
ברגע שמבינים איך לעבוד עם זה, בעצם מבינים את הכול. ברגע שמבינים את הקונפיגורציות מבינים איך תקשורות עובדות, אוקיי? אז משפט פילוסופיה נחמד: ברגע שנשלוט ב-GPIO, אנחנו יכולים להבין את העקרונות של כל ה-peripherals בכללי, אוקיי? אפילו peripherals של RF. אוקיי, לא נגע בזה, אני אפסיק לזרוק רפרנסים לזה, אבל שתדעו.
נדבר קצת על register manipulation techniques, איך אנחנו כותבים לרגיסטרים. נדבר על hardware abstraction concepts, איך אנחנו בעצם מחביאים חלק מהפונקציונליות ואיך אנחנו חושפים. Error handling pattern ו-resource management principles, איך אנחנו בעצם מנהלים את הזיכרון.
## המושגים GPIO Pin ו-GPIO Port
אוקיי, אז GPIO basic concepts. יש לנו שתי ישויות של GPIO: יש לנו את ה-pin, שזה הבודד, אוקיי? שזה בעצם קו I/O אחד. לדוגמה, ניקח את הקו הזה שאני מצביע, קו I/O אחד, יכול להיות מוגדר בצורה עצמאית, תלוי ב-MCU, לרוב כן. יש כאלה שה-muxing הפנימי לא מאפשר שיהיו כל מיני פונקציות שונות, אבל בואו נניח שכן, אוקיי? אם לא, אז פשוט רשום לנו ב-reference manual. לכל פין יש alternative function משלו ויש להם גם שמות: PA1, PB5, PC13, אלה הם הפינים.
לא רשום לנו עליהם מה זה, מאיפה אנחנו ניקח את המידע? ניקח אותו מתוך הסכמה, בסדר? אז בתוך הסכמה, אני חושב שהיא כבר פתוחה, ואם היא לא פתוחה אז יש לי עוד הזדמנות להראות לכם איפה המסמכים שלכם. כל המסמכים שבקורס, כולל המסמך החדש שזה ה-datasheet, הם נמצאים בתוך ה-source control מתחת ל-docs. אם אני רוצה את הסכמה, אני פשוט לוחץ על המספר הזה, שזה הקובץ של הסכמה, ופה אנחנו רואים את כל הפינים עצמם, אוקיי? אז PA0, כל מקום שיהיה רשום PA0 אז הוא מחובר איתו ביחד, PA1. שימו לב שלכולם יש לא רק מספר אלא גם אות.
האות הזאתי, זה ה-port, אוקיי? ה-port. אז ה-port זה collection של 16 פינים, זה תלוי ביצרן. יכול להיות שזה יהיה collection של 8, יכול להיות שזה collection של 32. זה בדרך כלל יהיה חזקה של 2, אוקיי? אז GPIO port זה collection של 16 פינים ב-STM או 2 בחזקת n לכל אחד אחר. לרוב יהיה להם קונפיגורציה משותפת. לדוגמה, מה משותף ל-PA? לפורט בזה...
אוקיי, אז אמרנו שכל פין יכול להיות מוגדר בצורה עצמאית. אז אם אנחנו כאן ככה מזיזים מהצד, ואנחנו רואים איזשהן הגדרות, אז pin, mode, pull, speed, output, ו-alternate, כל אלה מוגדרים בצורה עצמאית. מה לא יוגדר בצורה עצמאית?
- מה זאת אומרת בצורה עצמאית?
בצורה עצמאית, זאת אומרת שלכל פין תהיה הגדרה משל עצמו. יש הגדרות של כל ה-port, ויש הגדרות של ה-pin.
אוקיי? אז מה שלא יהיה מוגדר בצורה עצמאית, לדוגמה, זה השעון של ה-port, אוקיי? אז יש לנו...
עכשיו, השאלון שהוא בעצם מרפרש לנו את הפורט, הוא לא יהיה מוגדר בצורה עצמאית.
עוד דבר, ואת זה אתם לא יכולתם לדעת כי את זה אנחנו נלמד בהמשך, יש לנו איזשהו קשר בין המספרים של הפינים לבין ה-interrupt. אנחנו נראה את הקשר בהמשך.
אז נחזור. פין מוגדר עצמאית, יש לנו את ההגדרות שלו, אנחנו נעבור על כולם. והפורט - הפורט זה איזשהו collection של פינים עם קונפיגורציה משותפת. לדוגמה, Port A זה PA0 עד PA15. Port B זה PB0 עד PB15. אז יש לנו פה את כל אלה.
ושימו לב שאין לנו את E, F, G, יש לנו ישר H. אני חושב ש-F ו-G הם פנימיים של המערכת, אבל אני לא לגמרי יודע עליהם. כל שאר הפורטים יש אותם ואפשר לראות אותם בתוך הסכמה. רואים את PH0 ו-PH1, יש לנו את כל אלה.
## Behind the Scenes - GPIO Hardware Structure
אנחנו דיברנו קצת על ה-reference manual, עכשיו אני אפתח אותו ואנחנו נסתכל קצת לבפנים. אז RM0383 זה ה-reference manual עצמו.
אנחנו היום עובדים על GPIO-ים, אז General-purpose I/Os (GPIO) זה החלק שלנו.
ובתוך החלק הזה, בתוך ה-GPIO, יש לנו את הסכמה של כל GPIO, איך נראה כל GPIO בתוך ה-MCU. נדבר על הסכמה הזאת.
הסכמה הזאת נראית קצת מבלבלת והיא לא כל כך ברורה, והיא לא כל כך עומדת בצורה שבה אנחנו רגילים לראות סכמות, אז בואו נסביר מה יש פה. הבלוקים המרובעים האלה שרשום ככה על הצד, אלה הרגיסטרים של השליטה, חלק מהם. יש לנו את ה-Bit Set/Reset Register, זה הולך לתוך ה-Output Data Register, ויש לנו את ה-Input Data Register.
מפה יש לנו שני קווים שהולכים לתוך הקו של ה-I/O pin. זה בעצם הפין החיצוני עצמו. עליו יש לנו עוד שתי דיודות. מה שהדיודות האלה עושות, הן מגינות עלינו ממתחים גבוהים. אנחנו פחות בעניין של חשמל.
הדיודות האלה מגינות על ה-MCU, כי זה הולך לתוך ה-MCU, מגינות על ה-MCU שהוא לא יישרף. אם אנחנו רוצים לדעת על ההגנה וכמה, אנחנו יכולים לקרוא ב-datasheet. זה ספציפית לא משהו שאנחנו מקנפגים, זה תמיד שם, אז אנחנו נתעלם מהדיודות.
אחר כך יש לנו עוד שני חלקים שנקראים pull-up ו-pull-down, עוד שני רגיסטרים שאפשר לקנפג. אתם רואים, יש להם ON ו-OFF. מה אלה הרגיסטרים האלה? אנחנו נגיע עוד רגע.
אם אנחנו מסתכלים בחלק העליון, בריבוע העליון, זה הריבוע של ה-input. עליו יש לנו איזשהו TTL Schmitt trigger, שזה איזשהו רכיב שעוזר לנו למנוע רעשים. זה משהו שיש בכל input, ואפשר להגדיר אותו ואפשר לא להגדיר אותו. ואחריו יש לנו את ה-Input Data Register, מה שמגיע לבפנים. זה ה-read.
אפשר גם להסתכל יותר למטה, יש את הסכמה הזאת בכמה אופרציות, אבל זה הכל ביחד. אז זה מה שיוצא לתוך ה-input וממנו אנחנו קוראים data. שימו לב שהפונקציות האלטרנטיביות שלנו גם כן נכנסות פה. את ה-alternate function input אנחנו קוראים אחרי הטריגר של ה-noise reduction כי אנחנו צריכים, ולפני זה יש לנו את האנלוג, שאנלוג יש שיטות של סינון רעשים משלו, אז אנחנו לא נוגעים באנלוג. האנלוג, אתם רואים, זה לפני זה. אז זה ה-input.
ב-output אנחנו נכנסים לכאן. ואז יש לנו פה שני סוויצ'ים, עוד רגע נדבר עליהם. יש לנו את ה-output control, יש לנו את ה-write שאנחנו כותבים לתוכו, וזה עוד איזשהו חלק של alternate function שאנחנו נרצה לכתוב גם לתוכו. כאשר אנחנו נעבוד עם איזשהי פונקציה אלטרנטיבית (אנחנו לא עושים את זה, אנחנו רק מגדירים), כאשר נרצה לעבוד עם איזשהי פונקציה אלטרנטיבית, כל מה שנרצה לכתוב נכתוב לכאן, ספציפית לכאן, וכל מה שנרצה לקרוא נקרא מכאן. אני אומר "מכאן", זה יהיה מרגיסטרים. בסדר? שאלות לגבי זה?
- לא הבנתי כל כך את סדר העבודה פה בסכמה.
הסכמה הזאת זה הפונקציונליות של GPIO בתוך ה-MCU. ככה נראה מה שהוא יכול לעשות. או שיש לך את החלק של ה-input, ה-input driver, או החלק של ה-output.
החלק של ה-input, כל מה שיש לו אחרי החלק הזה זה או כניסה אנלוגית, או כניסה של alternate function, או כניסה ל-read, שה-read עובר דרך ה-Input Data Register. אז כאשר אנחנו נרצה לעבוד עם פין ב-input, אנחנו נעבוד עם החלק הזה, עם ה-Input Data Register. אנחנו נסתכל על הרגיסטרים האלה ואנחנו גם נגדיר אותם.
ב-output אנחנו פשוט נגדיר output, בעצם נכתוב לכאן (זה לאיזשהו רגיסטר), נכתוב אליו data וה-data הזה יגיע החוצה. איך הוא יגיע החוצה? משני החלקים האלה.
אז מה אלה החלקים האלה? רגע, בוא נסביר שנייה על מתחים. יש לנו את ה-VDD. VDD זה מתח ההפעלה של ה-MCU, שזה 3.3V, ויש לנו את ה-VSS שזה האדמה.
מה הקומפוננטות שיש לנו? יש לנו input driver, שזה בעצם אחרי איזשהו רכיב שמסנן לנו רעשים אנחנו קוראים את ה-data. יש לנו שני output driver-ים, שיש לנו בתוכם שני טרנזיסטורים שבאמצעותם אנחנו שולטים על המידע שיש כאן. אם ה-P-MOS הוא סגור, אז יש לנו VDD כאן. איך עובד טרנזיסטור? תלוי ברגל עצמה. בוא נדבר כבר על ה-P-MOS שנייה. ברגע שה-gate הוא low, ברגע שפה אנחנו כתבנו 0, אז מכאן מגיע 1.
- מאיפה מגיע 1?
- מה-VDD, זה זורם לנקודה השחורה ועל כל הנקודות השחורות עד שזה יוצא החוצה. ברגע ששמנו 1 בתוך ה-Output Data של פין ספציפי, אז זה הופך להיות 0 וזה הופך להיות 1, ואז שמנו פה 0, קיצרנו לאדמה. זה רשום כאן.
אז P-MOS זה P-channel MOSFET. זה סוג של טרנזיסטור שהוא מוליך כאשר ה-gate, שזה הרגל הזאת, היא 0 (או low, במקרה שלנו 0). כאשר הרגל פה היא 0, אז מכאן הולך 1 ופה יש 1. אנחנו משתמשים בו כדי למשוך ללמעלה.
וה-N-MOS הוא הפוך ממנו. כאשר ה-gate הוא 1, אז הוא מוליך, ואז אנחנו מוציאים פה 1. הוא בעצם מוציא לנו 1.
זה מה שקורה כאן. זה לא רלוונטי לנו, כי אנחנו מבחינתנו רואים את זה רק כרגיסטרים. אנחנו רואים את זה ב-"שים פה 1, אז הוא ישים לי 1 לוגי, שים פה 0, אז הוא ישים פה 0 לוגי".
- יש משמעות למה זה P-MOS ולמה N-MOS? כאילו למה אחד P-MOS ולמה אחד N-MOS?
- כי הם הפוכים. אתה רואה שיש את העיגול? העיגול הזה אומר לנו NOT, שהוא הפוך מהשני.
- קבוע בכל P-MOS?
- כן. P-MOS ו-N-MOS הם הפוכים.
- הבנתי.
- אפשר לקרוא עליהם, זה פשוט... זה פחות חשמלי, אנחנו יותר כאילו... זה רק כדי להסביר מה קורה שם מאחורה.
יש לנו שתי דיודות, שאינן אלה הדיודות האלה, הן להגנה אלקטרוסטטית (ESD protection). אותה הגנה של... אנחנו הולכים בשטיח ומתחשמלים, אז זה מאוד מסוכן לרכיבים אלקטרוניים, אז זה מגן עלינו.
ולבסוף יש לנו את ה-pull-up ו-pull-down, שאלה רגיסטרים פנימיים. עוד שנייה נסביר את הפונקציונליות שלהם.
## GPIO Operating Modes
### 1. GPIO Input Mode - High Impedance State
יש לנו כמה modes. נעבור על כולם ונסביר איזה חלקים מתוך הסכמה הזאת בעצם פעילים כשאנחנו עובדים.
יש לנו Input Mode במצב של High Impedance State. זאת אומרת שזה מצב שבו הפין בעצם מחובר לאיזשהו רכיב חיצוני, שהוא נותן לו את ה-1 ואת ה-0, ולרכיב החיצוני הזה יש התנגדות מאוד גבוהה, אז אנחנו נותנים לו לקבוע את ה-1 ואת ה-0 לוגי בשבילנו. במקרה הזה, כל מה שרלוונטי לנו זה מכאן עד לפה. כל זה רלוונטי לנו.
מה בעצם קורה במצב כזה? ברגע שנקרא מתוך ה-Input Data Register, אנחנו נקרא את מה שנתן לנו הרכיב החיצוני שהתחבר אלינו. הוא כתב לנו את ה-output שלו 1 או 0 לוגי (3.3V או 0V). 1 או 0 לוגי יכול להיות כל מיני מתחים שונים במיקרוקונטרולרים שונים, במקרה שלנו זה 3.3V, ואנחנו יכולים לקבל עד 5V (מעבר לזה אנחנו יכולים להישרף). אז הוא כתב לנו 0 או 3.3V וזה נכנס אלינו ואנחנו פשוט קוראים את זה.
יש לנו איזשהי סכנה שנקראת floating risk. מה זאת הסכנה הזאת? זו הסכנה שאנחנו נהיה במצב שבו אנחנו לא מחוברים לאיזשהו state ספציפי ואז אנחנו בין states, זאת אומרת שאנחנו in limbo, לא 1 ולא 0. במצב הזה אנחנו יכולים לצרוך הרבה אנרגיה וזה מצב קצת מסוכן. זה מצב שבו אנחנו גם לא בטוחים אם מה שאנחנו קוראים הוא נכון.
כדי להימנע מזה, יש לנו דברים לעשות. אבל מי שאחראי על משיכה או ל-1 או ל-0 (אני אומר משיכה, אבל אנחנו לא מדברים על ה-pull-up-ים האלה) זה מי שנמצא בחוץ בפין, והוא מושך לנו את המתחים ל-1 או 0.
זה ה-mode הזה. זה mode מאוד ספציפי, זה סך הכל לחבר את הפין ולקרוא. לרוב לא משתמשים בו יותר מדי, אבל משתמשים בו אם יש לנו מישהו עם high drive (שזה הגודל של הרגיסטרים מאוד גבוה, יותר מ-1M) והוא מחובר אלינו חיצונית, אז אנחנו משתמשים בזה.
עוד דבר, זה לא יעיל לרעשים כי יכול להיות שאנחנו יכולים לקבל כל מיני קריאות לא נכונות מהחלק החיצוני, אין לנו מה שמגן עלינו מהרעש. רעש אומר קריאות לא נכונות.
Use cases: כאשר אנחנו מחברים device עם output drive מאוד גדול, או כאשר אנחנו מחוברים עם pull-up או pull-down resistors מחוברים. יש לנו פה את אלה הפנימיים ויש אפשרות לחבר כאלה חיצוניים, ואז אנחנו לא נגדיר אותם כאן, אנחנו נגדיר את זה בצורה הזאת. נדבר על מה זה pull-up ו-pull-down, אבל בגדול הבנתם: pull-up מושך אותנו ל-1 לוגי, pull-down מושך אותנו ל-0V. נדבר על זה עוד רגע.
וכאשר אנחנו קוראים איזשהו analog signaling שאנחנו קוראים אותו כ-1 או 0 לדוגמה.
מה הסכנות? Noisy environment, ו-power consumption שהוא יכול להיות מאוד גבוה, כי כאשר השערים (gates) באיזשהו mode שהוא לא 1 או 0, הם יכולים להתנהג בצורה מוזרה ולצרוך הרבה אנרגיה, אז זה פחות מומלץ. זה האפשרות הראשונה.
### 2. GPIO Input Mode - Pull-up/Pull-down
האפשרות השנייה זה קריאה עם pull-up ו-pull-down. ממש בשקופית הבאה אני מסביר מה זה pull-up ו-pull-down. אבל מה יש לנו מבחינת הסכמה הזאת? אם זה ממש מבלבל אתכם, יש פה למטה רק את...
הקריאה עצמה. אולי זה יעזור יותר. זה הקריאה עצמה, אז בואו נתייחס לקו הזה. מה אנחנו... מה בעצם אם אנחנו משתמשים... אנחנו משתמשים בקו הזה עד ל-Read, ואנחנו משתמשים באחד משני ה-Registers.
אוקיי? אז יש לנו את האפשרות לעבוד עם Pull-up Resistor, סליחה, Resistor, אמרתי Register סליחה, Resistor. זה נגד, שהנגד הזה מושך אותנו באופן קבוע ל-1 לוגי. זאת אומרת כאשר לא קיצרו אותנו לאדמה, אנחנו ב-1 לוגי.
אז בעצם אם היינו רוצים לקבל Input ממקור חיצוני, אז הוא יכול רק לקצר לאדמה, נכון? ברגע שהוא מקצר לאדמה פה, אז יש לנו 0V. ברגע שהוא משחרר, אז יש לנו 1 לוגי. ברגע שהוא מקצר יש לנו 0, ברגע שהוא משחרר יש לנו 1 לוגי. וככה אנחנו יכולים לקבל ממנו Input-ים. הוא לא צריך עכשיו לכתוב לנו את המתח שלו, הוא יכול פשוט לקצר ולשחרר, ואנחנו מושכים את עצמנו למעלה.
אותו דבר בכיוון ההפוך. אם אנחנו רוצים להיות משוכים למטה, ורק כאשר אנחנו מקבלים Input אז יש לנו את ה-Pull-down שהוא הפוך.
אז בואו נעבור קצת על ההגדרות. אז ה-Pull-up זה בעצם Resistor פנימי שהוא מושך אותנו אל ה-VDD. ה-Pin נקרא High כאשר הוא לא מחובר, וכאשר הוא לא מקוצר אז הוא יקרא 1, וכאשר הוא מקוצר הוא 0. וה-Device חייב לרדת ל-Low, ל-0 לוגי, כדי לקבל Input.
סבבה? אז אמרנו, נתק - VDD, קצר - 0.
חזרה, יש לנו את ה-Pull-down שהוא ההפך. בעצם ה-Resistor הפנימי מושך אותנו ל-0V. ה-Pin קורא לנו Low כאשר אנחנו עובדים עם External Input, וה-External device חייב להרים אותנו אל הגובה של ה-Pull High. קצת יותר מסוכן, כי הוא צריך להרים אותנו ל-3.3V, יכול להיות שזה בעייתי, אבל לרוב זה בסדר. אנחנו עושים אינטגרציה, מוודאים את החשמל, אינטגרציה חשמלית, ואז הוא מעלה אותנו ל-1 לוגי, שזה 3.3V או כל 1 לוגי אחר, והוא מוריד אותנו כשהוא משחרר. אז זה עבודה עם Pull-down.
מתי אנחנו משתמשים בהם? משתמשים בהם כאשר אנחנו משתמשים ב-Buttons או Switches. בעצם הקצר זה ה-Switch, שאתם יכולים לראות פה אימפלמנטציה בדבר הזה. אז יש לנו פה את ה... זה למשל Pull-down, אוקיי? יש לנו פה נגד חיצוני, שהוא ה-Pull-down. תחשבו שה-Pull-down פה הוא חיצוני. יש לנו פה נגד חיצוני, וברגע שה-User לוחץ על הכפתור, אז יש לנו פה 1 לוגי. ברגע שהוא משחרר, אז יש לנו 0, כי זה מושך אותנו ל-0.
סבבה? זה PA0, זה גם הכפתור בתוך ערכת הפיתוח. סליחה, לא אמרתי, כרגע מכיוון שאני בשטח אין לי ערכת פיתוח. הקוד עצמו עובד, ואתם גם תצטרכו לבדוק את זה. ומקסימום אני אמור להשיג בימים הקרובים, אני אגיע לדירה באזור המסוכן שנקרא המרכז, אני אגיע לדירה ואני אביא את הערכה, ואם צריך לתקן באגים אני אתקן.
סבבה, אז זה בעצם דוגמה לאיך נראה Pull-down חיצוני. אז ה-VDD הוא פנימי, אבל יכול להיות גם איזשהו Input חיצוני, והכפתור מקצר לנו את זה.
וזה Pull-up. אז יש לנו את הרגל, נדמיין שזו רגל של GPIO. יש לנו את הרגל הזאתי. פה יש לנו את ה-Pull-up שהוא מושך אותנו ל-VDD, בגדול זה יהיה איזשהו גודל גדול, איזה 4.7kΩ או כיוצא בזה. וכאן יהיה לנו את הכפתור הזה, וברגע שנלחץ על הכפתור אז פה יהיה 0, וזה יהיה ה-Pull-down. אז זה שני ה-Registers.
אז אני חוזר. אנחנו עובדים עם זה הרבה פעמים עם Buttons או Switch, כפי שהם מוגדרים. אם אנחנו עובדים עם קונפיגורציה של Open Drain... עוד לא יודעים מה זה Open Drain, זה בסדר, זה בשקופית הבאה או אחריה, אז אנחנו בעצם מגדירים את ה-Pull-up Resistor. וברגע שהחלק השני מקצר לאדמה אז יהיה לנו 0, וברגע שהוא משחרר אז יש לנו 1, זה Open Drain.
ו-Default state definition זה הגדרה של Default state כדי למנוע Floating input, מה שהיה לנו בהגדרה הקודמת. היתרונות: Noise immunity, בלי קומפוננטות חיצוניות, בלי עוד נגדים שצריך להרכיב על המעגל, ו-Power efficient. בעצם הנגדים האלה מקטינים את הזרם שזורם, הם מושכים את ה-Output למעלה, ואז הם בעצם מקטינים את הזרם שזורם בינינו לבין מה שמכניס לנו את ה-Input. זה מקטין לנו את הזרם או למעלה או למטה, כי הם בדרך כלל נגדים מאוד גדולים.
שאלות על Pull-up-ים ו-Pull-down-ים, וסליחה, שאלות יותר נכון על GPIO Input?
- **תלמיד:** לי יש שאלה אם אפשר. קודם כול, רציתי למסגר את כל זה, כי בעצם כל המעבר על כל החלק הזה זה בשביל להבין את כל המכניקה החשמלית של מה שקורה כשאנחנו רוצים לעבוד עם ה-GPIOs בעצם, לתת קלט ופלט למעבד, נכון?
- **מרצה:** כרגע אנחנו רק על ה-Input, אבל כן. זה בעצם, זה אפילו לא חשמלי, זה יותר בלוקי.
- **תלמיד:** כן, זה מה שאני אומר. אנחנו רואים פה סכמת בלוקים של המעבד, אבל בגדול כשאנחנו מתעסקים עם זה בסופו של יום, למעשה כל מה שאנחנו עושים זה GPIO Read Write. כן, תלוי ב-Mode וב...
- **מרצה:** וב-Configuration.
- **תלמיד:** כן, והדבר היחיד שאולי משנה בכל מה שאמרת פה מעשית, פרקטית, זה אם אני צריך להוסיף Resistor בשביל Pull-up ו-Pull-down, אם הבנתי נכון.
- **מרצה:** לא לגמרי. זה כן חלק מההגדרות. אני נותן לזה איזשהו רקע ל-Use cases של חיבורים, אוקיי? כי זה בעצם מה שמשנה. בגדול יש כמה סוגים של חיבורים.
- **תלמיד:** אני רק אשאל שאלה אחרונה. הדבר היחיד שאולי מעניין מכל מה שהסברת פה, זה אם יש השפעה על הצורה שאני עובד... כאילו בסופו של דבר כותב ל-Registers והשאר בעצם מטופלים מכנית חשמלית, נכון?
- **מרצה:** נכון.
- **תלמיד:** אוקיי.
אז בגדול, בתוך ה-MCU שלנו יש לנו את הנגדים האלה בפנים. הנגדים האלה מחוברים אם אנחנו מפעילים את ה-Register שקובע אותם. אם אנחנו מחברים אותם, אז יהיה לנו או Pull-up או Pull-down. החיבור עצמו משנה כי אנחנו לא רוצים שיהיה לנו איזשהו Floating state, איזשהו מצב שהוא לא 0 ולא 1, מצב צף נקרא לזה. והמצב הזה יכול לגרום לנו לאיזשהו בלבול, כי אם הוא צף אז האם זה 0, האם זה 1, זה יכול להיות מבלבל ולא רצוי.
אז זו הסיבה שאנחנו מגדירים. יש לנו עוד כל מיני הגדרות שאנחנו מגדירים. אני עכשיו אסביר את הכול, ואז אנחנו נבנה את ההגדרות וכבר נגדיר אותן ל-GPIO ספציפי.
מתי אנחנו עובדים עם Pull-up ו-Pull-down? לפי האפליקציה, יש לנו כמה סוגים של חיבורים. בגדול אין יותר מדי סוגים של חיבורים: יש לנו או חיבורי Pull-up, או חיבורי Pull-down, או חיבור Push-Pull או Open Drain, או בעצם חיבורים של Input, שזה גם עם Pull-up-ים. אז אנחנו נכיר אותם ואחר כך אנחנו גם נקנפג אותם.
## Understanding Pull-up and Pull-down Resistors
אז מה זה Pull-up Resistor? זה Resistor שהוא מחבר את ה-High voltage, את ה-VDD אל קו ספציפי. הוא מושך את הקו לכיוון High voltage כאשר הוא לא פועל, כאשר אין לנו מה שמפעיל אותו. אז אנחנו מדברים גם ב-Input וגם ב-Output.
כאשר אנחנו מוגדרים כ-Input, אז מה שמפעיל אותו זה מה שנכנס אלינו כ-Input. כאשר אנחנו ב-Output, אז מה שמפעיל אותו זה ה-Output Control.
אם אני אתן דוגמה, אם אנחנו נגדיר Pull-up, ואנחנו נגדיר רק את ה-N-MOS, אז ברגע שאנחנו נכניס כאן 1, אז אנחנו נהיה ב-0, וכאשר אנחנו נכניס כאן 0 אז אנחנו נהיה ב-1. אז זו תצורה שנקראת Open Drain. יש לנו גם אפשרות לעבוד בלי זה, עוד שנייה נדבר על זה.
הערכים הם בדרך כלל נגדים מאוד גדולים, 1kΩ עד 100kΩ. ב-STM יש לנו נגד פנימי של 40kΩ.
Pull-down Resistor הוא בעצם ההפך. הוא מקשר אותנו בין ה-Signal line לאדמה, הוא מושך אותנו לקראת ה-Low voltage, שזה ה-0 הלוגי שלנו, כאשר לא כתוב לנו כלום. והוא בעצם אותו דבר כמו ה-Pull-up רק פחות אינטואיטיבי, פשוט הפוך.
במה אנחנו משתמשים? בעצם אין לנו Undefined state. ב-Power management אנחנו מונעים מכל מיני טרנזיסטורים לצרוך הרבה זרם, ויש לנו Default logic level. אנחנו יודעים שב-Default הוא 1 וב-Default הוא 0. זו בעצם קביעה של Default state.
## Pull-up vs Pull-down Selection
מתי אנחנו נבחר ב-Pull-up? כאשר הסיגנל שלנו הוא Active Low, כאשר הסיגנל שלנו הוא 0V. אנחנו נבחר ב-Pull-up כאשר אנחנו מחברים כפתור, או שנחבר איזושהי תקשורת.
לדוגמה, I2C עובדת ב-Open Drain. אנחנו מחברים עם Pull-up, ואנחנו או ה-Slave שלנו, עוד לא הגענו למה זה, כאשר אנחנו או הצד השני בעצם מקצרים את הקו כדי לתקשר. 1-Wire protocol זה משהו דומה. בצורה הזאת נבחר ב-Pull-up.
עוד משהו שנצטרך, שה-Default שלנו יהיה 1 לוגי. לדוגמה אם יש לנו איזשהו Reset pin שרק כאשר הוא ב-0 אז הוא מרוסט, אז 1 זה ה-Default state שלו.
מתי נבחר Pull-down? הפוך, כאשר הסיגנל שלנו הוא Active High. המידע שאנחנו מקבלים מגיע בתצורה של High, וזה איזשהו סנסור שנותן לנו איזושהי קריאה. לדוגמה אני עבדתי עם מדי ספיקה של צינורות, שכל כמות מסוימת של מים היו מעבירים פולס אחד. אז הם היו עם Active High.
כאשר ה-Default שלנו הוא 0, וכאשר אנחנו רוצים לעבוד בסביבה שהיא יותר שקטה, ככה שה-0 שלנו הוא יותר מוחלט מה-1 הלוגי, וה-0 שלנו זה האדמה, אז אנחנו נעבוד עם Pull-down.
מתי עובדים עם Internal מול External Resistor?
אנחנו נעבוד עם Internal כאשר אנחנו לא צריכים איזושהי משיכה מאוד חזקה. הגודל של הנגד זה מה שמושך אותנו למעלה או למטה. אז אנחנו נעבוד עם נגד פנימי ומשיכה חלשה. היתרון של זה שאיננו צריכים עוד רכיבים ושאפשר לקנפג את זה במערכת. החיסרון הוא שזה Fixed value ולרוב הוא חלש.
מתי נעבוד עם Pull-up חיצוני? כאשר מהנדס החומרה הגדיר לנו איזשהו נגד חיצוני, מתוך צורך של Pull יותר גבוה מבחינת אינטגרציה או מבחינה אחרת. היתרונות של זה זה Pull יותר חזק, Value שהוא יותר מותאם אישית, ועובד עם כל מיני סוגים של Voltage level לעומת 40kΩ. החסרונות זה שזה עוד רכיב ועוד מקום ב-PCB, וקונפיגורציה שהיא מוגדרת תמיד עם Pull-up או Pull-down.
זה ה-Input.
## GPIO Output Mode - Push-Pull
אני עכשיו אקפוץ שנייה לחלק של ה-Output, כי זה קצת יותר נקי.
בואו נתעלם מהחלק העליון, ופה יש לנו את ה-Output. מה אנחנו יודעים מתוך ה-Output? באיזה חלקים אנחנו נשתמש ל-Output בכללי, עדיין לא לרכיב הספציפי הזה? יש לנו את הנגדים, יש לנו את הקונפיגורציה, ויש לנו פה את ה-Output Control.
אם אנחנו הולכים ל-Figure 19, אפשר גם להסתכל ב-16, מה שאנחנו משתמשים זה ה-Output Driver וה-Control שפה. אז תחשבו שרשמתי פה Output Driver.
סליחה, Input driver, אוקיי, Input driver אפשר לקרוא גם למה שכתבנו, אבל Output driver זה מה שמגדיר לנו את ה-Output. אז בואו נחשוב שרק החלק הזה זה ה-Output Driver.
## GPIO Output Mode - Push-Pull
הכי חשוב, אתם ראיתם את הקו מ-Output Data Register עד ל-Output Control, עד לשני ה-MOSFETs שלנו, ובסוף אנחנו יוצאים ל-GPIO pin בלי נגדים, בלי נגדי Pull-up ו-Pull-down.
כאשר ה-Logic הוא High, כאשר ה-Control, כתבנו ל-Control 1, אז ה-P-MOS הוא ON, ה-P-MOS הזה שלנו הוא ON, אז יש לנו 1 וולט... וה-N-MOS, שזה ההפוך שלו, הוא OFF. אז הוא לא מושך אותנו לאפס, אז הוצאנו 1 לוגי. כאשר הפוך, אז בעצם אנחנו מוציאים... סליחה, 1 לוגי. כאשר הפוך, אז אנחנו מוציאים 0 לוגי, ישירות לפין. סבבה? ככה אנחנו קובעים גם את האפס וגם את ה-1. זה בעצם נותן לנו שליטה מאוד מהירה. זה גם מאוד מהיר וזה בעצם מונע מאיתנו לקצר את שניהם.
זה הסיבה שהם הפוכים. מי ששאל אותי, אלון נכון? למה הם הפוכים? כי אם שניהם היו מחוברים, אז היה לנו קצר מ-VDD ל-VSS והיינו שורפים, יכול להיות מאוד את הרכיב. אז או זה פועל או זה פועל.
למה אנחנו משתמשים בקונפיגורציה שנקראת Push-Pull? כי היא קונפיגורציה מאוד מהירה, אנחנו שולטים גם על Low to High ו-High to Low יחסית מהר. כתבנו 0 זה מגיע לפה, כתבנו 1 זה מגיע לפה. המהירות נקבעת על ידי איזשהו רגיסטר שנקרא מהירות, אנחנו נדבר עליו. יש לנו דרייב שהוא יחסית מהיר, אנחנו יכולים לתת עד 25mA לכל פין. אם אנחנו צריכים לעבוד עם רכיבים שצריכים לקבל Input של 25mA, אמרתי מיליוולט... 25mA, אז אנחנו נעביר להם לשם. זה בעצם איזשהו סטנדרט, רוב ה-MCUs עובדים עם משהו כזה.
ויש לנו Low output impedance, אנחנו עובדים עם התנגדות מאוד נמוכה בין הקווים, ככה שאנחנו יכולים לעבור מאוד מהיר בין הקווים, בין 1 ל-0, ואנחנו יכולים לשנות את ה-Logic levels מאוד מהיר ולהגיע למהירויות יותר גבוהות. מתי אנחנו נגדיר את זה ככה? כאשר נעבוד עם תקשורת עם Bandwidth גבוה יותר. למה רוחב פס גבוה יותר? רוחב פס גבוה יותר זה בעצם נגזר מהמהירות של הקו אם אנחנו עובדים עם קו אחד. סבבה? אז זה אפשרות אחת, זה Push-Pull, הוא מאוד מהיר.
מתי אנחנו נעבוד איתו? כאשר נרצה לעבוד עם LED, ונרצה לעבוד איתו בצורה מהירה, בלי איזשהו רזיסטור שיעשה לנו איזושהי הגבלה של המהירות, אוקיי? כאשר אנחנו נרצה לעבוד עם Chip Select Line, בעצם כל מיני רגליים שהן... האמת ש-Chip Select... אולי טעיתי פה, ה-Chip Select אני חושב שהוא... טוב, שנגיע ל-SPI נדבר על זה. כאשר אנחנו רוצים לקבוע איזשהו Line של דיבור, 1 או 0, כאשר אנחנו רוצים לעבוד עם תקשורת מאוד מהירה, שאנחנו רוצים לקבוע אותם גם למעלה וגם למטה בצורה מאוד מהירה, ואנחנו לא רוצים שיהיה לנו איזשהי התנגדות על הקו גבוהה, אז אנחנו נעבוד עם Push-Pull. וכאשר אנחנו עובדים עם Enable ו-Disable pins, אז הכל תלוי בקונפיגורציה, ואם אנחנו רוצים לג'נרט סיגנלים של Clock. למה סיגנלים של Clock? כי הם סיגנלים יחסית מהירים.
אז אם אנחנו אי פעם נצטרך לג'נרט סיגנלים של Clock, אתם יכולים לעשות את זה כ-Assignment אם בא לכם, זה ממש נחמד, עשו לי את זה באיזשהו ראיון עבודה בעבר. אם אנחנו רוצים לג'נרט, חלק מאיזה שאלה, אם אתם רוצים לג'נרט איזשהו Clock, אז אנחנו נגדיר את זה ב-Push-Pull ועל זה נג'נרט.
## GPIO Output Mode - Open-Drain
שאלות על Push-Pull?
– כאילו, אם אתה יכול להסביר ב-Use Cases שנתת אולי דברים יותר מהעולם האמיתי שקשורים לכל אחד מה-Use Cases האלה?
– אוקיי, אז ב-SPI, שזו אחת התקשורות שנדבר עליה בשיעורים הבאים, אז אנחנו רוצים להגדיר תקשורת מאוד מהירה, ואנחנו לא יכולים עכשיו שיהיה לנו איזשהי התנגדות מאוד גבוהה על הקו, אז אנחנו צריכים לעבוד עם איזשהו דרייבר, עם איזושהי תקשורת שהיא בלי התנגדות גבוהה, בגלל זה התנגדות שלנו אתם ראיתם היא בערך 25 אוהם. למה אנחנו לא רוצים שתהיה התנגדות? כי אנחנו רוצים שכאשר אנחנו קופצים ל-1 ול-0, זה יהיה יחסית מהיר. פחות אכפת לנו מבחינת הרעשים, SPI הוא מאוד רגיש לרעשים לדוגמה, לא אכפת לנו גם כי אנחנו רוצים להעביר כמה שיותר דאטה כמה שיותר מהר. תחשבו על זה כמו על ה-UDP של הקווים החשמליים. יותר מעניין אותנו לדחוף דאטה. רוצים לדחוף דאטה, אז אנחנו צריכים התנגדות נמוכה על הקו, אז אנחנו נגדיר את זה ל-Push-Pull. מצד אחד אנחנו קובעים 1, את השני 0, והפוך. תקשורת מאוד מהירה.
אז אנחנו נצטרך לעבוד עם Push-Pull. אם אנחנו לא רוצים שיהיה מוגדר איזשהו Pull-up או Pull-down, אז אנחנו נגדיר את זה גם בצורה הזאתי. ואם אנחנו צריכים להעביר איזשהו זרם כלשהו כדי להפעיל עד 25mA, גם אז אנחנו נגדיר עם Push-Pull. אלה ה-Use Cases. ואם אנחנו רוצים לג'נרט סיגנל, זו דוגמה.
קונפיגורציה נוספת, כבר דיברתי עליה כמה פעמים, בואו נעשה קצת סדר. יש לנו את ה-Open-Drain. Open-Drain זה בעצם קונפיגורציה שאנחנו משתמשים ב-Pull-up, אוקיי, אני יודע שרשום פה בלי Pull-up, אבל אנחנו משתמשים ב-Pull-up. או שיש לנו Pull-up חיצוני, זה גם אפשרות, אבל משתמשים באיזשהו Pull-up. ואנחנו משתמשים ב-N-MOS פה, וה-Input אנחנו לא נתייחס אליו. כאשר אנחנו רוצים לתת Output לצד השני, אז כאשר אנחנו לא נותנים איזשהו סיגנל, אנחנו במצב שהוא Floating, אלא אם כן מוגדר... הגדרנו או אצלנו או אצל מי שמקבל את ה-Input מוגדר Pull-up, מה שממושך למעלה. חייב שיהיה מוגדר משהו שממושך למעלה, אבל זה לא חייב בהגדרה של הפין, בהגדרה של המערכת. זאת אומרת, או שאצלי יהיה מוגדר Pull-up, או שיהיה מוגדר חיצונית Pull-up, או שיהיה מוגדר בצד של ה-Input Pull-up, או שיהיה מוגדר בצד של ה-Input בתוך הרכיב Pull-up. בכל אחת מהאפשרויות, חייב שיהיה מוגדר Pull-up, אבל לא חייב שיהיה מוגדר בתוך ה-Open-Drain שיהיה חייב איתו Pull-up. בגלל זה הוא מסומן כאן כ-X. הוא לרוב יהיה Disabled, אלא אם כן צריך אותו.
אז מה שקורה, ברגע ש-Active Low, ברגע שאני שמתי פה 0, אז יש לנו קצר מ-VSS לנוקדה השחורה, נכון? ברגע ששמתי פה 0, אז אני קיצרתי לכאן ל-0. ברגע ששחררתי את זה, אז אנחנו עוברים לעבוד במוד של High Impedance, בעצם אחד מה-Pull-ups שדיברנו יצטרך להיכנס.
– רק עוד פעם, ברגע שאתה שם 0, השורש הוא VDD?
– כן, אבל אנחנו לא מחברים את ה-P-MOS. רשום פה שה-P-MOS הוא לא מחובר, רק ה-N-MOS. אז ברגע שאני שם פה 0, אז כאן אני מנותק ובעצם אני מגיע ל-Pull-up שמחובר. בוא נניח שמחובר Pull-up, זה יעזור לי להסביר. נניח שמחובר Pull-up, אז פה יש 0, אז יש לנו כאן בחוץ 3.3V, וזה ה-Output שאנחנו נותנים מהפין. ברגע שאני שם 1, אז יש לנו VSS, שזה האפס, ויוצא לנו 0. ואז בעצם אנחנו שולטים על רכיב אחר רק באמצעות 0. זה נראה כמו ממושך אותו למטה. הוא לא ממושך למעלה, המשיכה למעלה היא תעשה איפה שהוא על הקו, או שזה באיזשהו Floating State. אם המערכת מצריכה את זה, אז סבבה. לרוב יהיה שם Pull-up.
אז או שיש על הקו איפה שהוא Pull-up, או שתהיה משיכה למטה. אם יהיו כמה Pull-ups, זה גם בסדר, אבל לרוב לא עושים את זה. אז או שיש משיכה למטה או Floating State, ש-Floating State זה בגדול Pull-up. סבבה?
– אפשר להסתכל על זה כמו מתג?
– נכון, זה כמו מתג. נכון.
– מתג, אתה מושך פעם אחת מחבר, פעם אחת מנתק.
– זהו, שאני לא מחבר. אני רק מנתק.
– למה? כשאתה נותן 0 אתה מחבר לאדמה.
– תחשוב על זה כמו נורמלי ON...
– כאתה נותן Output 0, מה אתה עושה? אתה בעצם מחבר לאדמה, נכון? זה ה-Open-Collector.
– נכון. וכשאני לא נותן Output 0, נותן Output 1, אני לא עושה כלום.
– מנתק. אתה מנתק אותך מהקו. אתה נכנס ל-High Impedance.
– אני נכנס ל-Floating, יותר נכון.
– זה לחצן שהוא נורמלי ON. זה לחצן שאו שאתה מנתק את עצמך מהרשת, או שאתה מוריד לאדמה.
– אבל כן, זהו. זה או שאנחנו לא מחוברים, או שאנחנו מקצרים. זה המצב בגדול. וזה חשוב שנגיד את זה ככה, כי למה אנחנו משתמשים בתצורה הזאתי? כי אם אנחנו משתמשים בתצורה הזאתי, המשיכה למעלה יכולה להיות משיכה לאיזשהו ערך, זה לא חייב להיות בתוך הערכים שה-MCU שלנו יכול לקבל. יכול להיות שאני עובד עם Pull-up חיצוני שמחובר ל-12V, ואני רק מושך למטה. אז זה היתרון של Open-Drain. כאשר אני עובד עם Open-Drain, אני לא צריך לסנכרן את רמות המתחים. למה? כי אני רק מושך למטה. מושך למטה ומשחרר. בסדר?
– תלוי ב-Absolute maximum.
– מה הכוונה?
– שאם אתה תיתן נגיד 1 לוגי, מה שמנתק את הזרם, תקבל את כל המתח, 12V עליך, שאתה תפוצץ את ה... נגיד יש יציאות ייעודיות שזאת נגיד Tolerance 5V, ואז זה מאוד קל לעשות את זה ב-I2C שעובד עם 5V במצב הזה.
– אז במצב הזה, אני יכול אפילו לחבר ל-12V, ואני אסביר גם למה. כי ברגע שה-Pull-up הוא לא אצלי, אז המתח הוא לא נמצא... אתה יודע מה? היית חייב לבלבל אותי בחלק הזה?
– תחשוב על הדיודה הזאתי, היא לא תוכל לעמוד ב-12V, בגלל זה זה נראה כמו סיכון. זה נכון, אפשר לעבוד פתאום במתחים שאנחנו לא יכולים לדחוף דרך ה-P-MOS, כאילו ב-Push-Pull, אבל אז צריך לשים לב באיזה Absolute הוא מסוגל לעבוד. ויש נגיד מוצאים שיש להם את ה-Tolerance ל-5V, ואז אתה באמת יכול לעבוד פתאום עם רכיב שהוא נגיד... רכיב שיודע לעשות קומוניקציה של I2C ב-5V על ידי Pull-up ל-5V. ככה אני כאילו מסתכל על זה, אני מדבר כאן בשיתוף.
– אני... זהו, אני אגיד לך שאני עשיתי את זה עם מתחים שמעל הטולרנס, אבל אני לא בדיוק בטוח איך זה עובד, כי יש... אתה יודע מה, אני אבדוק על זה. תמיד מבלבל אותי למה, אבל... המקרה, בוא נגיד ככה, המקרה הכי נפוץ שאני ראיתי זה שאתה רוצה לעבוד ב-I2C שעובד ברמות מתחים שונות, עם ברכיב השני נגיד EEPROM שעובד ב-5V, ואז זה ממש מעולה לעבוד בזה.
זה נכון, המקרה הנפוץ ביותר של Open-Drain זה I2C, נכון. ובבקרה, שאינני נגעתי בבקרה כמה שנים, יש כל מיני מקרים שבהם אתה מתחבר לבקר של 12V לדוגמה, שהוא עם הנגד הפנימי שלו, ולרוב ה-Drive שלו יותר גבוה משלך, ויהיה על הקו 12V, ואתם עדיין לא תישרפו. אתם יודעים מה, אני אחזור אליכם עם ההגיון. בואו נשאיר את זה צף כרגע, בסדר? או שאתה יכול לעבוד עם הטולרנס שלך, או שאתה יכול לעבוד עם כל רמות מתח. בסדר? אני אחזור אליכם לגבי זה.
אוקיי, אז בואו נמשיך כאן. אז שוב, אמרנו, כאשר אנחנו מושכים למטה, כאשר אנחנו שמים פה 1 אנחנו מושכים למטה, כאשר אנחנו משחררים אנחנו במצב צף. לא דיברנו על המתח, זה פחות רלוונטי, אבל או שיש Pull-up פנימי או שיש איזשהו Pull-up בחוץ, וכל מה שאנחנו עושים זה מקצרים לאדמה. ה-Use Cases של זה באמת כמו שאמרנו זה I2C, נדבר בהמשך, קווים של Interrupt אם אנחנו רוצים לעבוד, או איזשהו Level Shifting שאנחנו עובדים איתו. אוקיי, אלה האפשרויות. למה אנחנו בעצם יכולים לעבוד עם Open-Drain כאשר אנחנו עובדים עם קווים משותפים? רשמתי פה, אבל אני חייב להגיד שאני לא חד על זה כי אני לא מהנדס חשמל, אז אם Device A מושך גבוה ו-Device B מושך נמוך, אז הקו יהיה נמוך.
לא עובר זרם ביניהם, ואני חושב... וה-active... וכל device יכול למשוך ל-pull כאשר רק ה... אוקיי.
אפשר לשאול שאלה פיזיקלית רגע?
מה מה?
בתור מהנדס חשמל?
כן, טוב בוא בוא תעזור לי עפרון.
אתה מתאר כאן בדיוק למה עשו את I2C, כי I2C הוא באס כזה ש עובד על כתובות, ואז כדי שכולם יוכלו לעבוד ביחד ואף אחד לא יאלץ לגבוה, אז יש הנהגת pull up אחד, ורק מאלצים אנשים שרוצים להוריד למטה שזה מי שמיועד לתקשר. האחרים פשוט כאילו מתנתקים מהקו ולא מוסיפים לו אילוצים או משהו.
נכון. זה נכון לגבי I2C. כאשר אנחנו מחברים אבל שני...
הם פשוט מיישמים את מה שרשמת כאן, לדעתי.
כן, את זה אני יודע. טוב, בואו... איך שלא חוזרים לזה, אמרנו שנטפל בזה אחר כך, אוקיי? אני אתפוס שיחה עם המהנדס חשמל האהוב עליי. ונשאל אותו איך אפשר לעשות את זה או אם אי אפשר. בסדר?
בואו נמשיך, זה open drain. אז שוב אמרנו, אנחנו בעצם מחברים רק את החלק הזה, מכאן כולל ה-NMOS.
## GPIO Alternate Function Mode
אפשרות נוספת זה ה-Alternate Function Mode, שזה פחות GPIO, יותר הגדרה עצמה של ה-pin. אז שימו לב שיש פה איזושהי דוגמה של pin מסוים, אז יש לו כמה סוגים של alternated functions, אוקיי? את ה-alternate function עצמו אנחנו מגדירים מתוך ה... מתוך הרגיסטרים של ה-alternate function, אנחנו נדבר עליהם עוד רגע.
והאיזה רגיסטרים של... אני חוזר לסכמה הגדולה, כי פה יש לנו את ה-alternate function. אז מה שבעצם מחובר זה ה-input עצמו. אם אנחנו... אם בחרנו אנחנו מקבלים את ה-alternate function input. ה-output גם כן, אוקיי? אם אנחנו צריכים P-MOS ו-N-MOS, תלוי בדרישה. אז שוב, זרקנו I2C להעביר, אז ב-I2C כן נחבר את זה, כי נקנפג את ה-pin להיות... נקנפג את ה-pin להיות open drain, אוקיי? ו-alternate function. אז בעצם זה כל מה שמחובר. שימו לב שה-pull up pull down לא מחוברים אלא אם כן צריך אותם, אוקיי?
מתי אנחנו... מה האופרציות שמשתמשים? כאשר אנחנו רוצים להגדיר פינים... סליחה, כאשר אנחנו נגדיר את ה-pin במוד הזה, אז ה-GPIO control כבר לא ישלוט לנו על ה... כבר לא ישלוט לנו על הפינים עצמם, אלא השליטה תעשה על ידי ה-alternate function... alternate function control שנמצאים כל אחד ב-alternate function שלו, אוקיי? לדוגמה timer, אז timer יעשה, או אם זה איזשהו bus או אם... סליחה, או אם זה איזשהו buffer של data לכל אחד מה... לכל אחד מה-alternate functions. לדוגמה SPI, אם אנחנו עובדים עם SPI אז יש לנו איזשהו buffer שאנחנו דוחפים לתוכו את המידע, וזה מגיע לכאן, וזה בעצם מדלג על כל מה שלא רלוונטי בתוך ה-output control ומגיע החוצה. אז יש לנו פה איזשהו... איזשהו MUX שבאמצעותו אנחנו נשלוט. זה בעצם השרטוט של MUX.
אוקיי. אז גם ה-timing כבר ייעשה מתוך ה-hardware והפרוטוקול עצמו... צריכים קצת להגדיל באמת, כי עין קצת קטנה. והפרוטוקול עצמו הוא מה שיחליט על ה-output שיכול לצאת או ה-input שנכנס, תלוי במה ש... במה שאנחנו עושים. ויש לנו כמה פונקציות, אז ראינו כאן את ה-alternate functions לדוגמה, שיש לנו לאיזה פין ספציפי, אז יש לנו Timer1, Timer2, עוד טיימרים, I2C למיניהם, כל מיני פורטים, SPI גם פורטים, עוד פורטים של USART, UART סנכרוני, פורטים של OTG אני חושב שזה ה-USB וכו', EVENTOUT, SDIO, סטנדרט data input output, כל מיני סוגים. PWM יכול להיות פה, כל מיני וכל מיני באמת דברים. אז כאשר אנחנו נגדיר alternate function, אנחנו נגדיר אותה כאן, סבבה? אז אנחנו נילך לתוך הרגיסטר, אנחנו נקבע את הרגיסטר של ה-mode להיות alternate function, ואז אנחנו נקבע את ה-alternate function עצמו. עוד שנייה אנחנו נעשה את זה.
מוד אחרון, analog connection, באמת נקי, בלי כלום. יש לנו רק את הקו הזה שמגיע ל-analog לקריאה, ואם זה... ואם זה יציאת אנלוג, אז יש לנו את ה... איפה הקו הזה? יש לנו את הקו שמגיע מכאן. אוקיי, אז אם אנחנו עובדים עם A to D זה... analog to digital. כן, אם אנחנו עובדים עם כניסת A to D אז ה-analog... אנחנו מקבלים את ה... מכניסה דיגיטלית כאן ואנחנו קוראים אותה בצורה אנלוגית. והפוך, D to A, אנחנו מכניסים כניסה דיגיטלית והוא מוציא לנו אותה כ-אנלוגי מכאן. אלה הפונקציונליות שלנו, אז אנחנו משתמשים בזה.
לרוב זה יהיה הפונקציה עם ה צריכת חשמל הנמוכה ביותר, כי אין לנו איזשהם זה, אבל זה יכול להשתנות בין MCU ל-MCU. תקראו בתוך ה-datasheet או בתוך ה... או בתוך ה-reference manual של ה-MCU שאתם עובדים איתו, זה לא תמיד יהיה ST, אז שתדעו, זה לא תמיד אנלוגי במקרה הזה. קריאה אנלוגית, אין לנו... אין לנו בעצם סוויצ'ים דיגיטליים ש... כדי למנוע רעשים, אנחנו גם לא רוצים את זה. כי יכול להיות ש מניעת רעשים שלא נעשה באמצעות סינון אנלוגי, אנחנו לא נוגעים בזה כרגע, יכולה להיות לנו בעיה, אלא אם כן הגדרנו בצורה חיצונית איזשהו סינון רעשים, אבל זה צריך להיות בצורה חיצונית, אוקיי?
ויש לנו בעצם פין high impedance, עם מתח מאוד גבוה, שהוא מונע בעצם קריאה שהיא... קריאה שהיא... קריאה שהיא צפה, אוקיי? אז איזה use cases? אז אמרנו, analog to digital input, digital to analog output, חיישנים אנלוגיים וקומפרטור... קומפרטורים, אוקיי? מה הכוונה קומפרטורים? אנחנו יכולים להגדיר איזשהו מתח של כניסה אנלוגית שממנו אנחנו נקבל interrupt. אז ואז אנחנו בעצם שמים פה את ה-pin הזה להיות כקומפרטור, כי analog comparator, ברגע שאנחנו מקבלים, עוברים איזשהו סף אנלוגי, אז אנחנו מקבלים interrupt, כי בעצם יש השוואה, השוואה אנלוגית, analog comparison.
סבבה.
זה המעבר על כל האפשרויות לקנפג זה. אנחנו עוד שנייה נראה גם את ה-pin שבאמצעותו אנחנו עושים את זה. יש לכם פה איזשהו מדריך אני רואה, אבל עוד שנייה נדבר עליו. אנחנו עוד שנייה נדבר על הכול, אז על זה.
## Optimizing I/O Power Consumption
אז איך אנחנו עושים אופטימיזציה של power consumption? אנחנו מבטלים GPIOs שלא רלוונטיים, אנחנו מבטלים להם את השעון. אם זה... אם אנחנו לא יכולים לבטל את השעון כי הוא משותף לכל ה-port, אז ספציפית אנחנו מבטלים ל-pin ספציפי. אנחנו מקנפגים unused pin ל-lowest power mode, במקרה שלנו זה analog input. אנחנו משתמשים ב... סליחה, יש לי עט שנדחף בדרך. אנחנו משתמשים ב-output speed הרלוונטי, נדבר עליו גם. ואנחנו מקטינים את ה-frequency של הבאס, אנחנו מקטינים אותו לכמה שיותר מינימלי.
אוקיי. אז מה ה-output speed ומה זה קובע? Output speed זה בעצם המהירות של ה-toggling של השינוי בין... ב-output, זה רלוונטי רק ל-output. יש לנו בעצם כמה הגדרות כאלה, שימו לב שבכל MCU זה משהו אחר, יכולים להיות כמה הגדרות, יכולים להיות בלי הגדרות, זה ספציפית רלוונטי ל-MCU הזה שאני עובד איתו, ה-STM32F411RE V6, משהו כזה. אני חושב שה-שניים האחרונים לא רלוונטיים. אז low speed בעצם אנחנו יכולים לעשות toggling של עד 2 MHz, שזה אומר 2 מיליון סייקלים בשנייה. אוקיי? והצריכת זרם המקסימלית זה 2 mA, אוקיי? זה אומר שהוא יכול להזין עד 2 mA, שזה יחסית נמוך, אבל לא צריך יותר מזה. אחר כך יש לנו את ה-medium speed, גם יותר מהיר וגם נותן יותר זרם, וגם צורך יותר זרם. high speed, 50 MHz אז זה 20, ו-very high speed ל-100 MHz אם אנחנו צריכים, ולמהירויות כאלה זה בדרך כלל יהיה ל תקשורות. ובסופו של דבר 25 mA drive כאילו, שזה ה-highest power, אז זה גם הצריכת אנרגיה הכי גדולה, אוקיי? milliampere זה יחידות של זרם.
אוקיי. אז עברנו פה על הכול, סבבה.
## GPIO Programming with Bitfields
בואו ניסתכל קצת על איך אנחנו עובדים עם GPIO structure. אני לא יודע אם הגעתי אליו בשיעור הקודם, אבל אנחנו פשוט נדבר עליו עכשיו.
אז יש לנו את ה... את ה-struct הזה, רגע, אני לא מדבר על ה-struct הזה. אז יש לנו את ה... את ה-header file שהכנתי, שה-header file הזה יש לו כל מיני דברים שאנחנו צריכים, הוא לא מושלם. אנחנו לא נראה פה נגיד את כל ה-interrupts, נדבר עליהם דרך אגב. אנחנו כן נראה את כל הרגיסטרים האלה. אוקיי? אז לכל... לכל פריפרל יש לו את הרגיסטרים שמגדירים אותו, בסדר? לדוגמה... לדוגמה, יש לנו את ה-MODER, יש לנו את ה-OTYPER, אוקיי? יש לנו את ה-OSPEEDR, PUPDR, IDR, ODR, BSRR, LCKR, ו-AFR, שזה שני רגיסטרים, אוקיי? כל אחד מהם הוא uint32_t. יש אפשרות אפילו להשקיע יותר ולהפוך כל אחד מהם להיות bitfield. אני לא עשיתי את זה ב... לזה ספציפית, אם אתם ממש רוצים, יש בתוך ה-repository של... יש בתוך ה-repository של ה-bare metal, בתוך common, יש איזשהו אחד כזה שעשיתי, שפה ממש הגדרתי לכל אחד מהם את הכמה ביטים כל... כמה ביטים כל משתנה. מאיפה לקחתי את זה? הלכתי... אוקיי, זה ה... זה ה-repository, אתם יכולים לקחת, אתם יכולים גם להדביק את זה כאן ולהחליף אותם ולעבוד באותה צורה.
סבבה? אז שימו לב שיש לנו GPIO... GPIO MODER, אז הוא בעצם יחליף את זה, אוקיי? וכאן למטה יש כבר את ההגדרה עצמה. ואז אנחנו עובדים את זה מתוך... בתור bitfield. זאת אומרת, איך אנחנו ניגשים ל-bitfield? באמצעות האופרטור נקודה ובתוכו אנחנו שמים את ה-data, במקום לעבוד עם bitwise operators. מה שנוח לכם, זה פשוט הרבה יותר עבודה לכתוב את זה. זה הרבה יותר מהיר לעבוד איתו, אז צריך לעבוד עם bitwise operators, אנחנו נעבוד קצת עם bitwise operators. מאיפה לקחתי את המידע הזה? את המידע הזה לקחתי מה-reference manual...
אפשר להסביר מה עשית פה?
אני עכשיו מראה בדיוק מה עשיתי. לקחתי את המידע הזה מה-reference manual. ה-reference manual זה המסמך שמסביר את כל הפונקציונליות של ה-MCU שלנו, אוקיי? אנחנו ראינו שאנחנו נמצאים בתוך ה-section של ה-GPIO, מתוך ה-section של ה-GPIO יש לנו את ה-GPIO registers, אוקיי? כל ה-GPIO registers זה בעצם ה-description למה שאנחנו עושים. לדוגמה, ה-GPIO port mode register, אז יש לנו לכל אחד... אז כל GPIO port אנחנו מגדירים ביחד באיזשהן הגדרות. אז את כל ה-mode registers של 0 עד 16... 0 עד 15, שזה 16 הפינים, אנחנו מגדירים מכאן, אוקיי?
אז לדוגמה, אם אני... ואנחנו נעשה את זה עוד רגע, אם אני מגדיר את port A, אוקיי, אנחנו ניקח את הכתובת של port A. port A שזה הכתובת של ה-bus הזה plus 0, אז זה הפורט הראשון, אנחנו נגדיר אותו על ה-struct הזה, וממנו אנחנו... מ-offset 0, שזה הראשון פה, אנחנו נגדיר את כל ה-MODER. מה הם... מה הם ה-modes שיש לנו? ראינו אותם: input, output, alternate function mode, ו-analog mode, אוקיי? אז ברגע שאני אשים פה 0, 0 0 כי זה שני ביטים, אנחנו נראה איך עושים את זה, ברגע שאנחנו נשים פה 0 0 בביטים של ה-uint32_t הזה, אז יש לנו פה 32, אז ברגע שנשים בביטים 0 ו-1 0 0, אז אנחנו נעבוד עם input. כאשר אנחנו נשים בביט 1, 0 ובביט 0 נשים 1, אז נהיה output, אני מדבר ספציפית על... על ה-pin PA0. אוקיי, אמרנו port A.
ואם אני ארצה לעבוד עם alternate function, אני אשים פה 1 0, ש-1 0 זה 2 בדצימלי, ואם אני אשים 3, אנחנו נעבוד עם analog mode. אוקיי? אז זה בעצם ה-GPIO mode register. אחר כך יש לנו את ה... את ה-type, אם אנחנו עובדים עם push-pull או open drain, ראינו את שני ה-types האלה. ואת ה-speed, דיברנו על ה-speed, וה-pull up ו-pull down. אז בואו כבר אני... שנייה אני אעיף את זה כדי שנוכל לעבוד עם המצגת, ואז אני רק אסביר.
אז זה ה-structure שאיתו אנחנו עובדים, אוקיי? ואת ה-structure הזה אנחנו בעצם מקבלים לתוך ה-header file שלנו. אז הנה ה-header file שלנו. אוקיי, ה-header file שלנו זה בעצם רק הפונקציונליות שאנחנו משתמשים.
GPIO mode, נכון? מה אמרנו שיש ב-mode? ה-mode הוא או input 0, output 1, alternate 2, analog 3.
אחר כך יש לנו pull, pull-up. אז יש לנו בלי pull... סליחה, בלי pull,
in pull-up,
in pull-down.
אחר כך יש לנו את ה-speed, אז ה-speed הוא רק רלוונטי ל-output.
ויש לנו את ה-output type, שזה עוד רגיסטר, אבל אנחנו מקבלים אותו. וכל זה, שימו לב שכל אלה מגדירים את ה... כל אלה, ועוד ה-alternate function מגדירים את ה-GPIO config, אוקיי?
את ה-GPIO config הזה אני חושף החוצה. למה אני חושף אותו החוצה? כי הוא בתוך ה-header. אם עכשיו מישהו ירצה להשתמש ב-driver שלי, הוא מכיר את ה-structure הזה, אוקיי? הוא מכיר את ה-structure הזה, הוא מגדיר אותו, ואז הוא מעביר אליי אותו. איך הוא מעביר אותו אליי? כמשתנה... סליחה, כארגומנט.
אז אם אנחנו עושים את ה-init, אז הוא מגדיר אותו... מביא לנו אותו כאן, ואנחנו מגדירים את ה-GPIO הזה, אם הצלחנו כמובן, אז אנחנו מגדירים את ה-GPIO הזה בתוך המערכת. אם אנחנו רוצים לקרוא פין, אז הוא מביא לנו את ה-port עצמו, שזה בעצם ה-port, זה מגדיר לנו את ה-port. הוא מביא לנו את ה-port הזה ואת ה-pin, הוא לא מביא לנו את כל ה-structure. זה כל ה-structure, זה רק ה-port שלנו כאן. ואותו דבר גם בהמשך, קריאה וכתיבה, ויש לכם פה גם את השיעורי בית, אנחנו נראה אותם. בסדר?
למה אנחנו משתמשים בתצורה הזאתי ולא בצורה שהיא אבסטרקטית כזאתי, ישירות לעבוד עם MODER, ישירות לעבוד עם OTYPER? אפשר לעבוד עם שניהם, זה פשוט יותר מסודר, וזה לא לוקח הרבה יותר זיכרון, אוקיי? למה זה לא לוקח הרבה יותר זיכרון? סליחה, זה כן לוקח יותר זיכרון, אני מתנצל. כל פעם שאני מגדיר כזה, אני מגדיר את כולם. אם הייתי מגדיר אחד ספציפי, אז הוא היה לוקח לי רק uint32_t. אם אני מגדיר את כולם, אז הוא יראה לי פה את הגודל, אבל בגדול הגודל זה 2, 4, 8, 10, 12 (כי זה שתיים). אז בעצם 12 כפול 4, כי כל אחד מהם זה 4 בתים, אז 12 כפול 4 זה 48 בתים כל פעם לכל אחד כזה שמוגדר. אם אני מגדיר אותו גלובלית, אז הוא מוגדר פעם אחת, בסדר? אם הוא לא, אז בתוך ה-stack. ניהול זיכרון... לא כולנו עושים בזה, אבל אנחנו נדבר קצת על ניהול זיכרון. אוקיי?
אז יש לנו עכשיו את המסמך הזה. אתם יכולים להיכנס לתוך ה-gpio_driver.c, ופה יש לנו את כל הפונקציות. אז יש לנו את הפונקציות האלה, שהן פונקציות עזר, כי הן מוגדרות static. אם אנחנו מגדירים פונקציה static, אז מותר להשתמש בה רק בתוך אותו קובץ C. אוקיי?
אז עכשיו אנחנו נעשה את ה-gpio_init. אוקיי, gpio_init. רגע, משהו חסר פה. אוקיי, ב-gpio_init נמחקה לי ההגדרה, אז תיכף נעשה את זה. נמחקה לנו כזאת... נמחקה לנו כזאת, אז בואו נעשה את זה. אז לפני שאנחנו נעשה אותו עצמו, בואו נעבוד פונקציה-פונקציה.
## Building Our GPIO Driver API
אז נתחיל לבנות את ה-GPIO driver שלנו. יצרנו את ה-enums, אוקיי? יש לנו enums. אני יצרתי לכם כדי קצת לחסוך זמן, אבל הבנתם מאיפה הם מגיעים. בעצם הלכתי ל-Reference Manual פה, ולכל אחד מהם עשיתי. אז לדוגמה speed, אז יש לנו ארבעה speeds. למה ארבעה speeds? ארבע אפשרויות, כי זה שני ביטים. שני ביטים, אז $2^2$ אפשרויות. אם היה לנו שלושה ביטים, $2^3$ אפשרויות, אוקיי? שזה 8. במקרה שלנו יש 4 אפשרויות, 0 עד 3.
אחר כך יצרתי את ה-structure. היה לכם שיעורי בית לעשות משהו כזה לאיזשהי תקשורת כללית, פה אנחנו כבר עושים למשהו מאוד בסיסי, ל-GPIO. לכל אחד מהם יש את ההגדרות שלו.
אחר כך אנחנו מדברים על ה-GPIO Driver API. זה ה-API שאנחנו עובדים איתו: init, read pin, write pin, toggle (שזה בעצם תהפוך את ה-state), read port (בעצם קריאה מ-port ואז אני מחזיר כבר את כל ה-port, אוקיי? מה הכוונה מחזיר את כל ה-port? אז בעצם אני מחזיר את כל ה-port לתוך uint16_t, אוקיי? אז בעצם להחזיר את זה לתוך איזשהו mask של ה-pins, אוקיי? אז אם PA0, אז יהיה 1, אז יהיה שם ב-bit 0 אה 1, וכולי וכולי). write port, אז בעצם אני מקבל איזשהו value ואני כותב אותו לתוך ה-port עצמו. ואחר כך אתם תעשו בשיעורי בית modify ו-deinit, אוקיי? modify ו-deinit זה פשוט פונקציות שהן משלימות את זה. בנוסף, יצרתי פונקציות סטטיות, שהן הפונקציות שיהיו לי פונקציות עזר, שמיקמתי אותן כאן: set output mode, set mode, set pull, ו-set alternate function.
## Register Deep Dive - MODER
אז בואו נתחיל ברגיסטר Deep Dive. אוקיי, זה בעצם... Deep Dive, סליחה. אז יש לנו את ה-mode register, אנחנו רוצים לקבוע את ה-register עצמו. אנחנו בעצם מקבלים... טוב, פה אני כבר אצטרך להזיז את זה, כי זה קצת פחות רלוונטי וזה יותר רלוונטי. זה קצת יותר רלוונטי, אני אזיז את זה כאן.
אוקיי, אז יש לנו את הפונקציה הסטטית הזאתי. אני אוהב את הפונקציות הסטטיות להגדיר למעלה ולמטה לעשות להן את המימוש עצמו, אז נאתיק אותן שוב למטה. אתם יכולים לעשות מה שבא לכם, איך שנוח. פה את זה אני גם אעיף כדי שנוכל לראות את הכל.
אוקיי, אז מה אנחנו מקבלים? סליחה, קודם כל נתחיל עם set_mode, אני אעלה את set_mode למעלה. set_mode. אז יש לנו את ה-mode register. אז אנחנו ניקח את ה-port עצמו, אוקיי? מה הדבר הראשון מה אנחנו צריכים לבדוק? אנחנו צריכים לבדוק שה-pin... אנחנו צריכים לעשות איזשהי בדיקה שהמידע שקיבלנו הוא נכון. אז אנחנו נבדוק:
`if (port == NULL || pin < 0 || pin > 15 || mode < GPIO_MODE_INPUT || mode > GPIO_MODE_ANALOG)`
אז יש לנו בעיה, נכון? קיבלנו דאטה שהוא לא ולידי. ואם יש לנו בעיה, אז מה אנחנו נחזיר? אנחנו נחזיר סטטוס, שהסטטוס הזה אומר invalid param, או GPIO error. פה זה invalid param. אם אנחנו לא מצליחים להגדיר אז GPIO error, ובהמשך אתם יכולים להוסיף עוד שגיאות כדי שיהיה לנו בעצם קוד יותר ברור.
אחר כך, אנחנו לוקחים את ה-port, נכון? קיבלנו port, ומתוך ה-port אנחנו רוצים את ה-mode register. אבל מה הבעיה? מישהו יודע להגיד לי?
אוקיי, אני חושב שעשיתי את זה בכמה שיעורים הקודמים, אבל בואו נעשה את זה שנייה שוב. מה קורה אם אני אכתוב לתוך ה-mode register, לתוך שני הביטים האלה, נניח אני יכול לכתוב רק לשניהם, אם אני אכתוב לתוכם את המספר 1, מה יקרה? אם אני אעשה פה 1. אני אגדיר בעצם את כל זה, נכון?
אפילו... זאת הכוונה שלך?
אני אומר בכללי, אם אני ארצה לכתוב לתוך ה... אני רוצה לכתוב רק לביט הזה, אז אני... סליחה, אני ארצה לכתוב לשני הביטים האלה, ואני ארצה לשים פה את הערך 1. אוקיי, אז איך אני יכול לעשות את זה? או עם AND, או עם OR, נכון?
אתה יכול לעשות את זה עם bitwise OR כאילו.
נכון, אם אני עושה את זה עם bitwise OR, אז אני עושה bitwise OR של 1, אז אני בעצם משנה רק את הביט הזה, נכון? ומה קורה בביט הזה פה?
נשאר כמו שהוא.
ואם היה פה מוגדר לפני זה analog mode, מה יכול לקרות? שלא שיניתי, נכון?
נכון.
אז מה אני רוצה לעשות לפני שאנחנו...
אז אתה עושה bitwise AND על הביט השני.
נכון מאוד. אז קודם אנחנו ננקה, נעשה clear. איך אנחנו עושים bitwise AND? AND שווה, ואז מה אנחנו רוצים? אנחנו רוצים את הגודל של ה-mask שלנו. אז הגודל של ה-mask שלנו זה 3, נכון? 0x3. ואנחנו רוצים להזיז אותו בכמה? ב-2 כפול מספר ה-pin, נכון? וכל זה אנחנו רוצים לעשות ב-NOT. שאלות?
תסביר שוב איך עשית את מה שעשית עכשיו.
אז אני אראה, אני אפתח את ה-calculator. בוא נראה אם הוא נפתח בכפתור. אי אפשר לעשות set ל-bit? אפשר לעשות set ל-bit, יש לנו שתי אפשרויות. יש לנו פה שני ביטים. איך אנחנו עושים set ל-bit? יש לנו שתי אפשרויות: או שכמו שהראיתי פה, אנחנו מגדירים את הכל באמצעות bit-field, ואז מי שעושה לנו את ה-clean זה ה-compiler, אוקיי? זה מאוד נוח, וזה גם מאוד עוזר. יכול להיות שנעבור לזה בהמשך. לשיעור הראשון אני משאיר את זה ככה, אוקיי? אנחנו נעבוד עם הצורה הזאתי.
או שאנחנו עובדים בתצורה שאני הגדרתי כאן, אוקיי? שבה אנחנו פשוט מדברים עם כל register כ-word שלנו, שהוא uint32_t. ואז אם אנחנו עובדים עם איזשהו data bit שהוא יותר גדול מ-1, אנחנו צריכים קודם לנקות. כי אם אנחנו לא ננקה, הכלים שלנו הם או OR, או NOT, או XOR. בסדר? XOR לא רלוונטי לנו כרגע. אז OR ו-NOT. OR משנה את הביט ל-1, אבל הוא לא הופך את האחרים ל-0. אנחנו צריכים קודם להפוך ל-0, ואז לשנות ל-1. קודם לנקות את המידע, ואז לקבוע אותו. בסדר? אז מה עשיתי כאן?
סתם שאלה, אנחנו יכולים לעשות כאילו, נגיד אם זה 01, נכון? אתה רוצה לעבור ל-01...
כן.
אז אי אפשר לעשות פעם ראשונה OR ופעם שנייה AND עם זה?
כן, רק הפוך.
אוקיי, סליחה, הבנתי, סבבה.
לא, לא, זה בסדר. זה נכון, אתה יכול לעשות ככה, זה פחות נוח, כי אז יש לך כל מיני מקרים. אם אני קודם שם 00, מנקה אותו, ואז אני שם את האחדים, סיימתי את זה וזהו. אני אראה פה דוגמה, אוקיי?
אז נגיד אני עכשיו... בוא נעזוב את הראשונים. אוקיי, מה שעשיתי, לקחתי את ה-mask הזה, את ה-0x3, אוקיי? 11, סבבא? והזזתי אותו, בכמה הזזתי אותו? כפול ה-pin. נניח שפה יש לנו סך הכל 8 פינים, כי זה עד 16, התבלבלתי. פה יש לנו שמונה פינים, כל mode מיוצג על ידי שני ביטים.
אוקיי? שני הביטים האלה הם קובעים לי את ה-mode, נכון? אז אני לקחתי 11 ואז אני מזיז אותו 2 כפול המספר של ה-pin. אם זה יהיה pin מספר 0, אז הזזתי אותו 0 פעמים, אז אני כאן. אוקיי? אם זה pin מספר 3, אז אני יכול להזיז אותו 3 פעמים. אם הזזתי אותו 3 פעמים, אז זה פעם אחת, זה פעם שנייה, זה פעם שלישית. זה בכלל לא עוזר לי. אז למה עשיתי 2 כפול? אז זה מספר המקומות, אוקיי? 2. ואז אני מזיז: פעם אחת, שזה ל-mode של 1, פעם שנייה, שזה ל-mode של 2, ופעם שלישית, שזה אם ביקשתי את pin 3.
אז left shift, מזיז לי אותם שמאלה כמות פעמים. אז אם אני רוצה לקבוע... עוד שנייה אני אגיד מה אני עושה אחר כך. אם אני רוצה לקבוע את הפין השלישי, אז אני צריך להזיז 2 כפול 3 פעמים שמאלה את המידע שלי.
ובסוף יש לנו פה את האופרטור הזה של ה-NOT, שמה שהוא עושה, הוא הופך לי את כולם. אז הוא הופך, ואז הערך הזה, למאסק הזה, אני עושה NOT עם מה שכבר היה ב-MODER.
אז אנחנו מדברים בכוונה על מספר 3 שהוא הרביעי, אז כל מקום אחר ישמור את ה-data שלו, ובמקומות האלה אז יהיה 0. כל אלה לא ישתנו, חוץ ממה שסימנתי, ואז אני אנקה אותם. ככה אנחנו עושים clear.
ואנחנו רוצים גם לעשות set בסוף. אז את ה-set אנחנו עושים בצורה מאוד דומה, אפילו מעתיקים את זה, ואנחנו עושים את זה עם OR. אז OR שווה אותו דבר. בסדר?
אז כרגע יהיה פה 0, כרגע יהיה פה 0, ואז נעשה לו... נעשה OR עם מה שנרצה. אם אנחנו נעשה OR עם זה, אז אנחנו שמים רק את ה-bit הזה, רק אותו אנחנו מדליקים. אם אנחנו עושים OR עם זה - אז רק את זה, OR עם זה - זה... לא רלוונטי, אבל אנחנו נשארים במצב הקודם.
סבבה. זה כל מה שצריך לעשות כדי להגדיר את ה-mode. רגע...
זה צריך להיות 0x1, נכון? כדי לעשות את זה ל-01.
זה צריך להיות ה-mode.
ה-mode, אם ה-mode שלנו 1 ואז הזזנו אותו ב-2 כפול ה-pin, אז הוא הגיע או לכאן, או לכאן, או לכאן. תלוי ב-pin עצמו. סבבה?
Clear וזה set. אוקיי? זה GPIO set mode.
אתה יכול שוב להסביר למה כתבת את ה-mode?
כי ה-mode זה מה שאני קובע. אני מקבל mode, שה-mode הוא יהיה... מה יהיה ה-mode? ה-mode יהיה, רגע, אני אלך למקום הנכון, אני אעשה את זה שיהיה יותר ברור. ה-mode יהיה או 0, או 1, או 2, או 3. אוקיי? שזה מתאים ל... שניה, אני אקטין את זה... שזה מתאים ל-0 שזה ה-input, או 1, או 2, או 3. אז זה בעצם מה שאני כותב בסוף. אני קיבלתי את ה-mode, אוקיי? שה-mode הגיע אליי, אם זה input אז זה 0, אם זה output אז 1, אם זה alternate אז 2, ואם זה analog אז 3. סבבה? אז זה ה-mode, שזה מה שאני קובע בסוף. את זה אני רק משתמש כדי לנקות, ואת זה אני משתמש כדי לקבוע.
אז אם ה-mode שלי היה analog, אז היה לי פה 3, אז הזזתי... הלכתי ל-pin... בוא ניקח לדוגמה את pin 4. אז pin 4, אנחנו זזים 4 פעמים כפול 2, אז הגענו ל-8. 4 כפול 2 זה 8. ואז אנחנו עושים OR עם 3. אז OR עם 3 פה, אז יהיה לי 1 ו-1. אז פה ייכתב 1 ו-1 בסוף. שזה מה שרציתי לקבוע.
אוקיי.
אוקיי?
## GPIO Output Type Register (OTYPER)
תקשיבו, זה בסדר, זה לוקח קצת תרגול. אני בכוונה מקשה עליכם קצת בהתחלה כדי שתדעו מה שקורה. בהמשך אנחנו ננסה לעשות את זה עם bitfield, אוקיי? אבל בהתחלה חייבים קצת לסבול.
רק עוד שאלה. סך הכל מובן, רק להבין... יש לי איזה קפיצה אולי שהיא אוטומטית שאני לא מבין אותה. ה-compiler הופך את זה מ-3 דצימלי ל-11 על בסיס 2?
ה-compiler מציג את הכל ב...
בבינארי?
כן. מבחינתו הכל מוכר לו בבינארי.
הבנתי.
בדרך כלל עושים 0x שזה בכלל hex, רק בשביל התצוגה. מבחינתו הכל בינארי. גם mode, אז הוא מקבל את זה כבינארי והוא יעשה את זה פשוט ככה. הוא לא עושה המרות מ-hex, או שכן...
פשוט כתבת 3 בדצימלי.
נכון, נכון. הוא כן עושה את ההמרה הזאת, אבל פה זה בכלל יגיע אליו... פה זה בכלל יגיע אליו כי enum... אתה רואה שנפתח לנו פה ה-enum הזה? אז 3 בעצם יגיע לו לכאן, שזה כ-enum, וזה בעצם יהיה 1 1.
יש לי שאלה, כי אני מכיר את הפונקציות האלה, אתה יודע, אתה עושה GPIO set mode ואז אתה עושה פשוט write. ואני מנסה להבין בהקשר של לכתוב דרייברים או לעבוד עם דרייברים, בדרך כלל הפונקציות האלה מגיעות ממומשות, בגדול, לא? עד כמה אנחנו באמת...
נראה את זה כשנעבוד עם הספרייה של STM או של כל ספק אחר. אבל אנחנו צריכים לדעת מה קורה מתחת לזה, אז אנחנו נממש אותם. אנחנו עושים כרגע מימוש של דרייבר. יכול להיות גם דרייבר שאין לו מימוש, אז מראים איך עושים מימושים. לרוב, חלק מהפונקציות האלה מגיעות ממומשות, נכון.
סבבה. אז זה היה ה-mode. ואחר כך יש לנו את ה-output drive, אוקיי? את ה... סליחה, את ה-Output Type Register.
ה-Output Type Register הוא מאוד דומה. אם אתם רוצים להגיד לי מה קורה מבחינת הבדיקה שלי... מה משתנה בבדיקה?
ה-pins שאני יכול לקבל הם אותם ה-pins. ה-port שאני מקבל, אני בודק שלא קיבלתי port שהוא NULL, שהוא לא מוגדר. אנחנו יכולים להגדיר פוינטרים כ-NULL, ואז הם לא מוגדרים. אז אם קיבלנו port שהוא לא מוגדר, אז אנחנו מחזירים INVALID_PARAM.
ולבסוף, אין לנו יותר mode. מה יש לנו? GPIO otype. מה זה ה-GPIO otype? מה האפשרויות שלו? אתם לא חייבים להגיד לי ספציפית את האפשרויות, אתם יכולים להגיד לי גם מה יכול להיות האפשרות.
נכון, pull-up, pull-down או high-Z, לא?
האפשרויות הן או push-pull או open-drain. זה מה שמוגדר לנו בתוך הזה. אוקיי, אז בעצם התשובה הנכונה היתה מה שבתוך ה-enum. אז אם זה קטן מהמינימום של ה-enum, אז אנחנו... זה לא נכון, ואם זה גדול יותר מזה. אז אלה האפשרויות. נעשה את זה מהר. אם אתם לא יודעים איך לעשות השלמה אוטומטית: Ctrl + Enter. ואנחנו נרשום OTYPE, הוא ייתן לנו open-drain, שזה הנמוך ביותר. אנחנו יכולים גם לוודא ולראות את ה-0... הנה, כבר טעיתי. הנמוך ביותר הוא לא ה-open-drain, הנמוך ביותר הוא ה-push-pull, שזה ה-0. אז אם זה קטן מ-0 אז זה שגיאה, נכון? ואם זה גדול מ-open-drain, אז אנחנו זה...
ולעומת ה-mode, פה זה הרבה יותר קל כי אנחנו לא צריכים לעשות clean, כי אנחנו עובדים רק עם bit בגודל 1.
אז אנחנו נעשה: `port->OTYPER |= (otype << pin);`
כי אנחנו לא צריכים להכפיל. אוקיי, זה הקוד.
ועשינו את זה, אנחנו מחזירים שהצלחנו את הכל, אז: `return GPIO_STATUS_OK;`
אלה שתי הפונקציות האלה.
## GPIO Speed ו-Pull-Up/Pull-Down Registers
אחר כך, אותו דבר אנחנו עושים ל-set pull ול-speed, אוקיי? אז אמרנו מה זה ה-speed. ה-speed זה בעצם המהירות של ה-output של ה-pin. כמה... באיזה מהירות הוא יכול לעבור מ-1 ל-0 ומ-0 ל-1.
וה-pull-down ו-pull-up, זה ה-registers. אז בואו מהר אני אעשה את זה. אנחנו פשוט נעתיק. תדעו, להעתיק קוד זה הכי טוב, למה? כי זה קוד שהוא פחות רלוונטי לטעויות. יכלנו לעשות איזושהי פונקציית בדיקה, אבל זה קצת היה מבלבל.
אז אנחנו ניקח את ה-GPIO pull. pull, אז אנחנו יודעים ש-pull none זה הנמוך, ו-pull-up... ו-pull-down זה הגבוה.
אנחנו שוב מנקים, מה אנחנו מנקים? את ה-register הספציפי. איזה register אנחנו צריכים? אנחנו צריכים את ה-register של ה-pull-up...
קפצתי את ה-pull-up/pull-down register. אוקיי, זה האחד, אותו אנחנו מנקים, וגם אליו אנחנו כותבים את זה, שזה ה-pull.
מאוד פשוט. סבבה, זה הגדרנו פה.
והאחרון, אז אנחנו נעשה את אותו דבר גם מהר ל... לא ל-alternate function... רגע, אין לנו את ה-speed, לא יצרתי את ה-speed? אוקיי, אני משאיר לכם את זה כשיעורי בית, ליצור את ה-speed. לא רלוונטי, אוקיי? אנחנו כרגע לא ניגע בזה, אבל בהמשך ניצור את ה-speed. אז אני עושה לכם פה set speed.
## GPIO Input Data ו-Output Data Registers (IDR/ODR)
עכשיו, מה קצת מעניין? יש לנו את ה-alternate function. רגע שנייה, בואו נמשיך. יש לנו את ה-speed - לא יצרנו אותו, אתם צריכים לעשות אותו. 3, set speed.
את ה-pull-up/pull-down - אני לא עושה את זה כי חבל על הזמן, אני כן רוצה שנתקדם.
ואחר כך יש לנו את ה-Input Data ואת ה-Output Data Register.
אוקיי, אז Input Data ו-Output Data, אז יש לנו... אלה הם כבר פונקציות חיצוניות, אוקיי?
אלה כבר פונקציות חיצוניות, הפונקציות שאנחנו מקבלים אותם מכאן. מה אנחנו הולכים לבדוק? אותו דבר...
אנחנו נבדוק שזה זה, אבל פה אנחנו לא צריכים לבדוק את החלק הזה.
סבבה. ואז אנחנו צריכים להחזיר את הערך. אז return...
אז יש לנו את ה-Input Data Register. ה-Input Data Register אומר מה הערך שיש לי ב-pin הספציפי. אוקיי?
אם אנחנו מדברים על pin 1, אנחנו שוב יכולים ללכת לכאן, אבל זה קצת כבר לא נוח. אז ה-Input Data Register, אז יש לנו את ה-registers האלה. אם בכל אחד מהם יש 1, אז זה ה-1 שאנחנו רוצים. אז אנחנו מקבלים את ה-pin ואנחנו מקבלים ממנו את התשובה.
אז מה אנחנו עושים? אנחנו הולכים שוב ל-port שלנו, אוקיי, ל-port, וממנו אנחנו רוצים ללכת ל-IDR, Input Data Register. ואנחנו רוצים להחזיר ממנו OR עם 0x1, כי זה גודל 1, ומה? 0x1 מופז שמאלה ב-pin.
שאלות לגבי זה? זה קצת הפוך, אבל מקווה שאתם מבינים.
אז פה בעצם יופיע לי 1 בנקודה שבה יש לי 1. אוקיי? אם זה pin מספר 5, אז יופיע פה 1. ועכשיו מי שמשתמש ב-API נותן לי pin 5, אז אני צריך לעשות 1, 5 שמאלה, ליצור לי את המאסק הזה, ולעשות לו OR עם זה כדי לקבל את התשובה. זה הכל.
אני לא כל כך הבנתי את ההזזה הזאת של ה-1.
אוקיי, אני שוב אחזור ל-calculator. אנחנו יוצרים איזושהי מסכה, אוקיי? מה זו המסכה הזאתי? נניח שיש לנו פה רק 8, בסדר? אז אנחנו עכשיו... עכשיו הגיע הלקוח שלי, שזה מי שמשתמש בספרייה שאני יוצר, והוא אומר לי "אני רוצה לקרוא את pin 5". אז אני שם 1 ומזיז אותו 5 פעמים. אז 1, 2, 3, 4, 5. זה ה-pin הזה. אז הזזתי את ה-1 הזה, אוקיי? ה-left shift מזיז לי אותו. אז זה מה שיצרתי, יצרתי פה איזושהי מסכה.
הבנתי, הבנתי.
הבנת הכל או שנמשיך?
איך הגעת לזה שאתה צריך דווקא את 5? סתם כאילו...
כי זה מה שקיבלתי ב-input, זה פראמטר של ה-pin. ואז את ה-1 הזה אני משווה עם כאן. זאת אומרת את ההשוואה אני עושה רק אל מול ה-bit הזה. אני לא רוצה לקבל את המידע של השאר כי זה לא נכון, אני רוצה לקבל רק את הזה. אז איך אני אקבל אותו? אני אעשה OR. אוקיי? אם יש פה 1 ופה יש 1, אז יחזור לי true. סבבה? אם פה יש 0 ופה יש 1, אז יחזור לי false.
או הפוך. אם פה יש 1, סליחה, אם פה יש 1 ופה יש 0, אז זה יחזור ל-false. ואז ככה אני מחזיר את הערך של ה-GPIO.
אוקיי, ואותו דבר בואו נעשה גם את ה-write. או ש את ה-write אנחנו כרגע... אני אשאיר לכם גם לעשות, אם אתם מרגישים בנוח? את ה-write אני אעשה מהר גם כן.
את ה-write, אנחנו משתמשים ב-output... אז Output Data Register, ואנחנו כותבים לתוכו. אז Output Data Register הוא בגודל של ביט אחד, אז OR שווה 1 וזה של ה-pin. עכשיו זה לא בדיוק 1 כי זה ה-state. רגע, נכון? אם אני לא... אז בואו נעשה יותר נכון: if state...
רוני, לא היית צריך את ה-state, כי אם הוא 0 אז אתה מזיז פשוט 0 חמש פעמים לצד, עושה OR איתו ו הכל בסדר, לא?
לא, צודק, אבל אתה צריך לעשות clear כמו שעשינו בהתחלה, לא? אז אני עושה פה clear כמו שאני עכשיו עושה. בדיוק. סבבה? זה בדיוק ה-זה. ובסוף... מה שכחתי להחזיר? return OK, רק במקרה של... אה, לא סליחה, זה לא מחזיר data. סבבה? ו-toggle, אתם רוצים להגיד לי מה עושה toggle?
רק לראות אם הבנתי, ה-NOT לא מובן לי ב-else.
אני הופך את כל שאר הביטים כדי לעשות AND. אז כל שאר הביטים נשארים 1 כדי שאני לא אשנה אותם. הביט הספציפי שאני רוצה לשנות הוא יהיה 0 ואיתו אני עושה את ה-AND, כי AND עם 0 זה...
ואז מה שמשתנה, נכנסת 0 הוא יהפוך להיות 1?
כי כל אלה יש להם סטטוס, נכון? סליחה, יותר נכון כל אלה. לכל אלה יש איזשהו ביט, איזשהו mode. אז יכול להיות שפה יהיה לי 1. אני לא רוצה לשנות אותם, אז אני הופך. ואז יש לי 0 במקום הספציפי. שוב, נתתי דוגמה את 5, פה יהיה 0, כל השאר 1-ים. כל השאר ישאירו את ה-state שלהם אם אני אעשה איתם AND, והוא יהפוך להיות 0. אני מנקה אותו.
אבל ה-state נכנס כ-0.
אם הוא נכנס כ-0, זה יכול להיות גם 1.
למה?
כן, אז בוא שוב, נתתי את הדוגמה הזאתי, אז 5, נכון? אז לקחנו את זה, וזה הנקודה הזאתי, אנחנו פה.
אבל אתה ב-0 שם. אתה הזזת 0 חמש פעמים לצד, ואז כשתעשה NOT, הכל יהפוך להיות 1.
נכון. אוקיי. יפה, אני פספסתי את זה.
אוקיי, עכשיו אנחנו רוצים לעשות toggle. אז toggle אנחנו עושים בצורה...
מה, עוד פעם? אלון? הוא עושה bitwise עם XOR.
נכון. מעולה. read_port ו-write_port גם כן יישארו לכם לשיעורי בית. בואו נעשה את ה-setup.
סבבה. אז הגדרנו את זה. עכשיו בואו נעשה את ה-init. מה אנחנו נרצה לעשות ב-init? נרצה להגדיר את הכל. אז ה-init עצמו, ועוד שנייה גם נגיע ל-alternate function.
אז מתוך ה-init, קודם כל אנחנו נרצה להגדיר את ה-MODER. אוקיי? אז איך אנחנו נעשה את זה? נקרא לפונקציה שלנו `gpio_set_mode`. הוא מקבל את ה-port, ה-port שלו יהיה config, `config->port`, כי זה קיבלנו מה-user. אחר כך את ה-pin, אז `config->pin`. חץ, לא נקודה... `config->pin`. ובסוף `config->mode`. מישהו רוצה להגיד לי מה שכחתי? בדיקה. if `config == NULL`...
סבבה, אז פה הגדרנו את ה-mode. עכשיו מה אנחנו נרצה להגדיר? את ה-output. אוקיי? ושוב, זה וזה מוגדרים באותה צורה. וזהו, אנחנו נגדיר אותו באמצעות הזאתי. סבבה. pull, נכון?
אוקיי. תגידו לי אם אתם עוקבים. ו-alternate function, אנחנו עדיין לא עשינו את זה, אבל נעשה את זה עוד רגע. סבבה. מה עוד חסר לנו? את ה-speed, נכון? אז לא יצרנו פה speed, אני עושה את זה כזה... `gpio_set_speed`.
אוקיי. למה הוא כועס? יש לנו פה סוגריים כפולים. ובסוף return `GPIO_OK`. סבבה. אז בעצם השתמשנו בכל הפונקציות שיצרנו כדי לעשות את זה. מה נשאר לנו? את ה-alternate function, אוקיי? איך אנחנו עושים את ה-alternate function? עוד שנייה נדבר על זה, אבל תגידו לי רק שמובן מה עשינו פה. הגדרנו את ה-pin. מעולה.
## GPIO Alternative Functions
אז מה זה alternate function? לכל... אני חושב שאם זה אנחנו נסיים ואנחנו שוב נגיע ל אינטראפטים בשיעור הבא, זה בסדר גמור.
Alternate functions, מה זה בעצם? לכל pin יש לו עוד פונקציונליות שהוא יכול לעשות, ראינו אותם. אבל יש לו שני רגיסטרים שבהם אנחנו יכולים להשתמש, אז יש לנו את ה-Alternate Function Register 0 שזה הפונקציות... שזה שולט על ה-pin-ים מ-0 עד 7, וה-High מ-8 עד 15. מה זה רמז לנו?
אז מעבר לבדיקה שברכנו אותה, ואין מה לעשות, זה חוסך הרבה מאוד באגים. למה פה זה מספרים? כי פה אנחנו עובדים עם אלה. אוקיי, אז מעבר לבדיקה...
זה 6 שמה? כי יש לך 9 פונקציות, לא 15.
Alternate function יש לנו 8, כן. אז זה 8.
לפי הרשימה שראיתי, כן יש 15, כאילו עד 15, אבל ממה שיש בפועל נראה לי אפשר עד 9, לא?
רגע, יש לנו... אתה יכול להגיע עד... לא, יש לנו סליחה, יש לנו עד 15. כן. למה אין פונקציות אמיתיות עד 15? אה זה בסדר, אבל אנחנו לא יכולים להגביל את זה ככה, כי יכול להיות שיהיה אחד שיהיה לו. אנחנו יכולים להסתכל בדאטה שיט עוד רגע, אנחנו נראה את זה. כרגע אני משאיר את זה 15 כי אמרנו שמה שקובע לנו זה ה... זה כאילו... כל עוד אנחנו לא יודעים, כרגע מה שקובע לנו זה ה-15 שפה, שקיבלנו אותו כאן, אוקיי? אז כרגע זה נשאר 15.
אפשר לעשות עוד עבודה, ללכת לתוך הדאטה שיט, שם מוגדרות כל ה-alternate functions. עכשיו, אני באמת לא הכנתי את הטבלה, אבל בואו נמצא את זה מהר. יש טבלה יותר יפה, הנה. זו הטבלה.
יש כאילו עד 15, אתה עדיין קובע את זה כ-15, אוקיי? אלון?
כן, כן, כן, אני רואה.
אז זה עד 15. בסדר? מה שהראיתי שם זה רק דוגמה, יש פה עוד הרבה פונקציות. אז הדוגמה, אם נרצה להגדיר את PA להיות טיימר מ-Timer Channel 2, אז נצטרך לשים פה את Alternate Function 1 ואז נקבע אותו כאן.
אוקיי, אז עכשיו איך אנחנו בעצם... איך אנחנו בעצם יודעים לאיזה רגיסטר לקבוע? אנחנו משתמשים ב-if. אז if... אני אגלה לכם משהו, אם אנחנו רוצים לעבוד יותר מהר, אז אנחנו נרצה לעבוד עם switch, אוקיי? אז switch. למה switch? זה הרבה יותר עבודה, אני מסכים.
למה? כי switch עושה לנו את ה-branching בצורה הרבה יותר מהירה, את הקפיצה בצורה הרבה יותר מהירה. אז נעשה ככה מהר 0 עד... ב-VS Code זה הרבה יותר מהיר, אבל בואו נעשה את זה ככה: 0, 1, 2, 3, 4, 5, 6, אמרנו עד 7.
ואם זה כל אחד מהם, אז אנחנו נעשה את זה ככה. ואז אנחנו נרצה ללכת ל-port AFR במקום 0, ואותו אנחנו נרצה קודם לנקות, נכון? AND שווה NOT, ואז מה? יש לנו 0x... 15, אני כבר לא זוכר את זה.
נעשה 15 ואחרי זה נהפוך את זה ל-hex, נהיה מקצועיים, אבל כרגע לא. ואת זה אנחנו רוצים להזיז ב... רוצים את ה-alternate function שלנו. ואותו אנחנו נזיז במספר ה-pin-ים, מספר הביטים, כפול ה-pin. pin כפול מספר הביטים, שמספר הביטים שלנו...
4.
4, תודה.
סבבה. ואותו דבר, אז אנחנו... רק שפה, אז כן, 0x... פה נעשה את זה 15, בסדר. אוקיי, 15. ופה אנחנו כן נרצה לעשות את זה עם OR, וככה לקבוע את זה, סבבה?
ואותו דבר לאחרים, אז נעשה את זה שוב זריז. נעשה את זה שוב זריז, ואז אפילו אפשר למחוק את הבדיקה של ה-pin. שנייה, נעשה את זה זריז. אז 8, 9, 10... סבבה. אני מסיר את ה-default כי אנחנו עשינו פה את הבדיקה, אז אין צורך גם ב-default. את זה אני אהפוך להיות 1 ואת זה אני אהפוך להיות 1, וזהו. וכבר קראנו ל-alternate function וכבר עשינו את זה. סבבה. אז עכשיו הגדרנו את זה, מעולה. וסיימנו, אוקיי?
לפני שנגיע ל-MCU interrupts, כי אנחנו לא נספיק, אני רק רוצה שנספיק ככה מהר לראות איך הקוד נראה ברמת האפליקציה, זאת אומרת המשתמש שלו.
אוקיי, אז המשתמש שלו, שזה ה-main, בואו נניח שהוא לא משתמש בזה, אז אנחנו לא ניגע ב-interrupt, אז אנחנו ניגע רק פה. אז אנחנו נגדיר, אתם רואים, נגדיר משתנה שנקרא `gpio_config_t`, שהוא יגדיר איזשהו משתנה שנקרא `led_config`. ככה אנחנו יכולים לאתחל struct ב-C, רק ב-C, ב-C++ זה לא עובד.
את ה-port, אנחנו ניתן לו את ה-port הזה, שה-port זה רק כתובת, אוקיי? זה ה-port של פה אנחנו ניתן לו, סבבה? אז ה-port מסוג `GPIO_TypeDef`, שזה הסוג של ה-port. אוקיי, סליחה, ה-port, והוא מוגדר לכתובת C. בסדר?
אני חוזר אל ה-main. פה אני אשמור, אני חוזר אל ה-main. אז הגדרנו את ה-port, הגדרנו את ה-pin שאנחנו רוצים, זה איזשהו pin שהגדרנו. אנחנו מגדירים אותו להיות output, את ה-mode להיות push-pull, speed low, ו-pull none. שימו לב שה-speed מוגדר, אבל אנחנו לא עשינו לו אימפלמנטציה, אז לא יקרה כלום, אבל לא נורא. ו-pull none, אוקיי?
את זה אני פה מדלג, אוקיי? ואת החלק הזה אני גם מדלג, אז אני אוריד אותו שנייה למטה. בשיעור הבא אנחנו נעשה את החלק הזה. וגם את זה אנחנו לא צריכים.
אוקיי. ואני לוקח את ה-structure הזה, ואני מכניס אותו לכאן. אוקיי? ומפה, אני יכול להשתמש בפונקציונליות האלה. אם היינו עובדים עם interrupt היינו עושים את זה, אבל כרגע יש לנו פה איזשהו loop. הלופ הזה יהיה loop אינסופי, ובלופ הזה אני יכול לעשות כזה דבר: GPIO... set, בואו נעשה set, נכון? `gpio_write_pin`. אז איזה port אנחנו רוצים לתת לו? אנחנו ניקח את `led_config`.
אוקיי, `led_config.port`, אוקיי, ואיזה pin? ה-pin זה אותו... זה ה-pin של ה... זה ה-pin שאנחנו נותנים. אז איזה pin? אנחנו נותנים את pin 13, אבל אנחנו יותר נכון לעשות את זה `led_config.pin`. אוקיי, וה-state, אז נעשה אותו 1.
אוקיי, סתם איזושהי תוכנית שכל X זמן, ופה אנחנו נוסיף sleep, למרות שאין לנו באמת ממש sleep. sleep של 5 שניות. אם אנחנו רוצים לעשות sleep, אז אנחנו עושים ככה, sleep כזה ממש טיפשי:
`for (volatile int i = 0; i < 100000; i++)`
אז זה בעצם סתם ככה ירוץ ולא יעשה כלום.
סבבה. אז ככה אנחנו נקרא. אם אנחנו נרצה להגדיר איזשהו button, איזשהו כפתור, אנחנו יכולים להגדיר את הכפתור הזה כאן, אוקיי? אנחנו לא נגדיר אותו בצורה הזאת, אז אני... אני כן אחזיר את זה למטה. סליחה, כן, אני אחזיר את זה למטה, אני אעתיק רק את מה שאנחנו צריכים.
אנחנו צריכים את זה, אוקיי? ובמקום כל זה אנחנו, סליחה, במקום כל זה אנחנו נגדיר את זה ככה, ואת זה אנחנו נגדיר פה, באותה צורה, סבבה? משהו פה פספסתי, `gpio_config`, `gpio_config`, זה הכפתור, ופה חסר לי כזה. סבבה.
אז הגדרתי, לא הגדרתי את הכול, אבל בסדר, כי אני לא צריך את הכול בשביל input, נכון? זה פונקציות של output. בשביל input אני רק צריך pull-up ואת ה-mode של input. ואז את ה... ואז את זה אני גם כן... פה אני צריך עוד פסיק. ואז את זה אני גם כן... אז אני אעשה כזה `if`.
רגע, קודם כול אני אגדיר אותו. אז את זה אני אכניס לכאן, ופה אני צריך להעביר לו את ה-struct. איך מעבירים struct לפונקציה? מעבירים אותו עם האמפרסנד (&), שזה הכתובת של... הכתובת של ה-struct. אוקיי.
עכשיו אנחנו כבר הגדרנו את ה-GPIO button. סבבה. ומפה אני כבר יכול להגדיר:
`if (gpio_read_pin(button_config.port, button_config.pin))`
ואז אני עושה פה `if`, אז אם החזיר 1, אז זה true. `if (true)`, אז אני יכול לעשות כזה דבר. זה סתם, זה לא כזה רציונלי. אז `gpio_toggle_pin`, נכון? `toggle_pin`. ואז איזה pin אנחנו מזה? ניקח את `led_config`, נגדיר אותו.
סבבה. יש לנו פה תוכנית מאוד נחמדה, וזה כאילו... וזה כל מה שאנחנו צריכים לעשות, וכל פעם שנרצה להשתמש, נוכל להשתמש ב-driver. ה-driver מגיע לנו מכאן, והמשתנים מגיעים לנו מכאן, ואנחנו יכולים לעשות את זה.
בואו נקמפל רק נבדוק, אני חושב שהוא יכעס עלינו קצת על זה. ואפילו זה מתקמפל בפעם אחת? או שלא? זה היה מהר מדי.
מה הבעיה שלו? לא הגדרנו `NULL`. בסדר, בואו נגדיר... מישהו זוכר איפה מוגדר `NULL`? אני מניח שמוגדר ב-`stdio.h`. `#include <stdio.h>`. בגדול `NULL` זה סך הכול... אני חייב להראות לכם מה זה `NULL`, פשוט לא רוצה סתם להגדיר את זה כפול. אז הוא כועס שפה לא מוגדר `NULL`. אם אני אלחץ כאן, אז `stddef.h`. אז `NULL` מוגדר כ-pointer של 0. רק שזו הגדרה של משהו שלא מוגדר.
נעשה Ctrl+B, נבנה את זה שוב. מעולה, עכשיו הוא כועס על דברים אחרים. על מה הוא כועס? הוא כועס ש-`mode` הוא לא declared, כי פה זה `pull`. אוקיי, כולנו פספסנו, זה בסדר. ועוד פעם.
מה עכשיו? `OTYPER`, אז איזה משתנה יש לו? `config.output_type`, בסדר. ופה בטח `config.alternate_function` אין לו. `alternate`, בסדר. זהו, כאילו... אז זה... פה אנחנו לא צריכים להחזיר, אז פה אנחנו נחזיר... נחזור בלי לשנות, רק `return`. אוקיי, אותו דבר גם פה, אנחנו לא צריכים להחזיר שום ערך, אז רק נחזור.
סבבה. וזהו, אני חושב שסיימנו. וזה כן צריך להחזיר `return GPIO_OK`. זהו. פה הכול בנוי.
## מטלת הבית
אוקיי, אז זה היה השיעור הזה. שיעור הבא אנחנו נעשה את ה-MCU interrupts, זה בסדר גמור, זה ממש קצר.
בואו נדבר על ה-assignment שלכם. אוקיי, אתם עדיין איתי, נכון? יופי.
אז ה-assignment שלכם זה בעצם לסיים את ה-GPIO driver. מופיע לכם פה קובץ של assignment. פשוט כל מה ש... סליחה, כמו פעם קודמת, קובץ של assignment, תפתחו אותו איך שבא לכם, הנה הייתי פותח אותו עם VS Code. או זה. ומפה אתם יכולים לעשות ככה, ימני, Open Preview. אם יצרתם את ה-markdown נכון, אז הוא יפתח לכם את זה בצורה יפה כזאת.
אוקיי, הוא מראה לכם איך הייתם אמורים ליצור את הקבצים. איזה פונקציות אתם צריכים לממש. אז אתם צריכים לממש עוד שתי פונקציות שלא עשינו, `enable_clock` ו-`disable_clock`. אוקיי, אנחנו כן צריכים לעשות את זה. אני כן אעשה את זה מהר כי אנחנו לא דיברנו על זה.
אז כדי לעבוד עם GPIO, אנחנו צריכים בעצם לאתחל לו את השעון. אמרתי את זה כמה פעמים ושכחתי. אז בואו נלך ל-`init` ואני פשוט אעשה את זה, ואתם תעשו את זה אחר כך.
אז RCC... ואנחנו רוצים לאתחל את השעון של ה-GPIO. טוב, אני אפתח את זה מהר ואז אנחנו נדבר על זה פעם הבאה. פעם הבאה, כי אני שכחתי כנראה, וזה בסדר גמור. RCC, Reset and clock... RCC, יש לנו כמה שעונים, אנחנו רוצים את ה-registers. ה-register שאנחנו רוצים זה את של ה-GPIO-ים. אז פה אנחנו ממהרים לחפש את ה-GPIO. הנה, GPIO זה, אז אנחנו רוצים את AHB1. אז `RCC->AHB1ENR`, שזה האחד הזה. ה-`AHB1ENR`.
אוקיי. זהו, אז AHB1 זה reset, אנחנו צריכים enable. אז הוא ב-offset של 30. AHB1ENABLE_REGISTER, זה האחד שלנו. ופה אנחנו פשוט צריכים להכניס את הפורט הספציפי. איך אנחנו ניקח את הפורט? נעשה כזה דבר.
`uint8_t port_num = config->port -`
אם אתם לא מבינים, אני אסביר את זה עוד רגע, פחות ה-base שלנו, ה-peripheral base, נכון? לא, פחות ה-base הזה. שזה הבסיס של ה-peripherals, אוקיי, פחות זה. ואת זה אנחנו נחלק ב-4000. חלקי `0x400`.
למה? מה עשיתי? בעצם לקחתי את הכתובת, אוקיי, את הכתובת של ה-port, חיסרתי ממנה את הבסיס, אוקיי, רק שיהיה לי בעצם את אלה, ואז חילקתי את זה, סליחה, ב-400. זאת אומרת שאם אני B, אז יהיה לי 1, אוקיי, אם אני C, אז יהיה לי 2, וכן הלאה. סבבה? זה ברור? בעצם הפכתי את מה שעשיתי כאן. חיסרתי את זה וחילקתי בזה.
אוקיי, אז זה 400. ואת ה-`port_num`, זה מה שאני אעשה איתו enable. אז זה `|= (1 << port_num)`. סבבה? ואז בעצם עשיתי פה enable clock. Enable GPIO clock. אתם צריכים להפוך את זה פשוט לפונקציה. סבבה? לא קשה. תראו שאתם מבינים את זה. זה מה שאתם צריכים לעשות.
טוב, זה עדיין מתקמפל. זה לא מתקמפל, למה זה לא מתקמפל? כן, צריך להפוך את זה לכתובת. אז פה עשיתי, הימרתי לכתובת ועכשיו חיסרתי את זה וחילקתי, בסדר? חילוק של integers. זה `enable_gpio_clock`, אוקיי? וזה הכול.
אז נחזור למטלות, אני אשחרר אתכם. השיעור באמת התארך, אני מתנצל.
אתם צריכים לממש את ה-GPIO clock, אתם צריכים ליצור לו `disable`, אתם צריכים לעשות `write`, `read_port`, בעצם לוודא את כל מה שיש פה לממש, אוקיי? אם אתם רוצים לממש דברים בצורה שונה, אז תדעו שאתם יכולים לקחת את זה, לעשות שילוב ולעשות.
אז פה באמת היו הינטים שרשומים לכל אחד מההגדרות, אז בגלל שזה היה חסר אז פספסתי את זה, סורי. זה הכול.
ו-`gpio_modify`, אתם צריכים לעשות לו modify. שימו לב שב-modify אתם יכולים קודם לבדוק ואז לשנות. לא חייבים לשנות, או שאפשר ישר לשנות. `deinit` זה לאפס להגדרות יצרן, ואת ה-interrupts אל תעשו.
אחר כך אני ארצה שתגדירו לדים, תוכלו להגדיר את זה בצורה הזאתי, ותיצרו איזשהו interface ל-LED. זאת אומרת קחו את ה-LED הזה, תיצרו איזשהו interface שלכם, ותממשו את הפונקציות האלה ותקראו לפונקציות של ה-GPIO בשביל לעשות את זה. סבבה?
אותו דבר `button_interrupt`, אל תעשו עם interrupt, תעשו עם...
## הנחיות לתרגיל הבית
polling, כמו שעשיתי בדוגמה. אני עכשיו אדחוף את זה.
debouncing — אם אתם יודעים איך להגדיר debouncing סבבה, אם לא — נדבר על זה בתחילת השיעור הבא.
והוספה של שגיאות: תעברו על כל מיני שגיאות שאתם רואים, ששלחנו פשוט שגיאה גנרית, ותדחפו שם שגיאות שלכם. זה עוזר ככה להבין מה התקלות, ואנחנו נעשה את זה כמעט בכל אחד. אני אשאיר אתכם לעשות את התקלות שלכם.
איך אתם בעצם יכולים לעשות את זה? תייצרו איזה שהוא MD, תעשו fork והכול שלכם.
אם אתם רוצים עוד בונוסים — תייצרו PWM משלכם. אם אתם לא יודעים מה זה — זה בסדר, תשאלו בקבוצה, אני אכתוב לכם. אם לא, אתם יכולים ליצור multiple button support, LED pattern programming, מה שבא לכם אתם יכולים ליצור עם זה. יש לכם פה ספרייה, ואתם יכולים להוסיף עליה ולהשתמש.
## משאבים וכלים
ה-resources שאתם משתמשים בהם — זה ה-reference manual, ה-user manual שצריך, ובגדול מה שצריך. פה אתם יכולים להשתמש בהם, אתם לא חייבים להשתמש בהכול, בגדול ה-STM32Cube מספיק.
Support — תדברו בקבוצת WhatsApp, אין לנו פורום לקורס ואין לנו office hours.
פה קצת אילושהם דיבאגים, איך אנחנו יכולים לדבג דברים: לא התחלנו את השעון, לא עשינו נכון את ה-mapping, ה-resistor לא נכון וכולי.
## עדכון ה-Branch וסיכום השיעור
אני אדחוף את כל השינויים שעשינו מהשיעור ל-branch נפרד. אתם יכולים לקחת ממנו, בסדר? אני לא אדחוף אותם ל-branch הזה, כי הוא מוכן לשיעור.
אתם יודעים איך לעבור branch, נכון? Git checkout והשם של ה-branch.
שאלות לגבי היום? אנחנו לא סיימנו את הכול, אבל אני חושב שזה היה שיעור מאוד טוב ומאוד אינטראקטיבי. יכולתם לשאול שאלות ולהבין. אני אצור את ה-branch ואני אשלח לכם אותו. אם מישהו רוצה — יכול להתנתק, מי שיש לו עוד שאלות — שישאל.
- היי, שומעים אותי?
- כן, כן, שומעים אותך.
- אין שאלות להיום? זה בסדר.
- נראה לי שסיימנו להיום, נכון?
- כן, אנחנו סיימנו. אני רק דוחף את ה-branch שתוכלו לעבור אליו.
- אה, סבבה, תודה רבה.
- אז אני אראה עוד רגע.
אני לא יודע באיזה מצב אתה, אם עשית clone או לא. אבל אם עשית clone, אז היית כרגע על ה-branch של ה-main. Git checkout - מחזיר אותי ל-branch הקודם, אוקיי?
- כן, כן, אני פשוט לא מכיר את התוכנה הזאת, אז...
- אוקיי, אז תיכנס למצגת, תוריד VS Code...
- בסדר, בסוף שבוע, ואם אני אצטרך עזרה אני ארשום הודעה.
- סבבה. זה נמצא בכל מקרה גם פה. יש לנו פה כמה branches. ה-branch הנוסף נמצא כאן עם השינויים, ואתה יכול לקחת מה שאתה רוצה.
ניפגש בשבוע הבא. תודה רבה לאיראנים שלא הפריעו לנו.
שבוע הבא אנחנו נסיים גם את ה-interrupts, אתם יכולים לקרוא במצגת כדי להתקדם, ואתם יכולים גם להתחיל לקרוא על I2C.
אני צריך להחזיר לכם תשובה לגבי ה-open-drain. אני אחפש קצת ואחזיר לכם, סבבה?
אז להתראות בשבוע הבא. כל הכבוד, אני חושב שהיה שיעור מעולה. להתראות.
- תודה, לילה טוב.
- תודה רבה.
- ביי, לילה טוב.