בפוסט זה נפרט על הקוד וההגדרות הדרושים כדי להפעיל פסיקה מבוססת-טיימר במיקרו-בקר הסיני הזול, תוך שימוש בטיימר כללי (GPTM) במקום הטיימר הפשוט STK – כי אותו כבר ניצלנו ליצירת פונקציית millis…
את הפוסטים הקודמים על CH32V003 כתבתי לפני יותר משנה, וכיוון שאז עוד לא היה לי שימוש מעשי/מסחרי למיקרו-בקרים האלה, לא התקדמתי איתם. לאחרונה החלטתי לשלב אותם (ליתר דיוק, את הדגם המינימלי CH32V003J4M6) בפרויקט ללקוח, והבנתי שכדאי ליצור על הדרך איזושהי "שכבת הפשטת חומרה" (HAL) בסיסית דמוית-ארדואינו שתקל על תכנות של פרויקטים דומים בעתיד. סביבת הפיתוח MounRiver Studio מספקת אמנם HAL, אך זו מסורבלת ומורכבת הרבה יותר מדי לצרכים שלי כרגע.
אם פרויקט דורש תזמונים "גסים", אפשר לעשות אותם בלולאה הראשית בעזרת פונקציה בסגנון millis. כפי שראינו בעבר, טיימר המערכת STK מעולה לצורך זה, אבל הוא לא הטיימר היחיד שעומד לרשותנו. במיקרו-בקרים אלה יש לנו שני טיימרים נוספים של 16 ביט כל אחד: TIM1 מסוג ADTM ה"משוכלל", ו-TIM2 מסוג GPTM , "לשימוש כללי". את שניהם כדאי להכיר אם נרצה תזמונים מהירים ומדויקים, למשל הפקה של אותות PWM, שליטה במנועי סרבו וכדומה.
בואו נתחיל בקטן, ונראה כיצד מפעילים את TIM1 עם פונקציה בסיסית ביותר – "הקפצה" מחזורית של פסיקה (בדומה למה שעשינו כדי למדוד אלפיות שנייה ב-millis). את המשימה הזו אפשר לחלק לשני חלקים: החלק של מדידת הזמן, והחלק של הפסיקה (גם ההפעלה שלה מבחינת הגדרות, וגם הטיפול בה בקוד). אתחיל עם הפסיקה דווקא, כי טעות קטנה שלי בהגדרות בזבזה לי חצי שעה עבודה…
הגדרות לפסיקה (interrupt) של TIM2
כיוון שבאיזשהו שלב נרצה להריץ ISR (פונקציית טיפול בפסיקה), אנחנו חייבים קודם כול לומר למיקרו-בקר שיאפשר את הפסיקה הזו ברמה הגלובלית. זה אומר שצריך למצוא ולשנות את הביט הרלוונטי ברגיסטר המתאים בקבוצת הרגיסטרים FPIC_IENR. במסמך ה-Reference Manual נחפש את טבלה 6.3, "Vector Table of Interrupts and Exceptions". מופיע שם רק דבר אחד שקשור ל-TIM2 – בשורה האחרונה, וקטור מס' 38 שמתואר כ"TIM2 global interrupt". אם כן, צריך לכתוב "1" לביט 38 ב-IENR. כל רגיסטר הוא 32 ביט, אז ביט מס' 38 יהיה למעשה ביט מס' 6 ב-IENR2. ושוב, כמו שגיליתי בקשר ל-STK, סביבת הפיתוח מתחילה לספור את הרגיסטרים האלה דווקא מאפס. לכן, בסביבת הפיתוח, כל הסיפור מסתכם בשורה
PFIC->IENR[1] |= 0x40;
חוץ מהאיפשור הגלובלי הזה, יש לנו גם איפשור לוקלי. ישנם כמה אירועים שונים שיכולים בעיקרון "להקפיץ" את הפסיקה האחת הזאת של TIM2: סף מסוים של המונה הכללי, סף של אחד מתת-הערוצים (שמשמשים בדרך כלל ל-PWM), טריגר חיצוני שמחובר לטיימר, ועוד. כרגע אנחנו מעוניינים רק ב-Overflow של המונה הכללי. כדי שהאירוע הזה יוכל לעורר את הפסיקה, נכתוב "1" לביט 0 (נקרא UIE) ברגיסטר DMAINTENR:
TIM2->DMAINTENR |= 1;
כאשר האירוע יתרחש, ביט 0 (נקרא UIF) ברגיסטר INTFR יהפוך ל-"1". האחריות להחזיר אותו ל-"0" היא עלינו, ואם נשכח לעשות זאת, פונקציית הפסיקה פשוט תרוץ שוב ושוב בלי סוף. הנה שלד לדוגמה של פונקציית פסיקה ל-TIM2. חשוב לזהות ולאפס את הדגל הספציפי שעורר את הפסיקה, בהתאם להגדרות של התוכנית שכתבנו; לדוגמה, אם כתבנו תוכנית שבה שני סוגי אירועים שונים יכולים לגרום לפסיקה, ובתוך ה-ISR נאפס את הדגל של האירוע הלא-נכון (או של שניהם בו-זמנית), התוכנית תיתקע לגמרי, או לא תגיב לאירוע חשוב.
בקוד שבתמונה, המאפיין __attribute__ והסוגריים שאחריו הם "ככה זה" – הדרך שבה כותבים פסיקות בסביבת הפיתוח MounRiver Studio – וגם שם הפונקציה TIM2_IRQHandler לא נתון לבחירתנו אלא מוגדר מראש עבור הפסיקה הספציפית הזאת. אפשר למצוא אותו ברשימה שמופיעה בקובץ startup_ch32v00x.S, שסביבת הפיתוח מוסיפה אוטומטית לכל פרויקט חדש.
הגדרות למדידת זמן
כמו כל אלמנט "פריפראלי" במיקרו-בקר הזה, גם TIM2 לא פועל כברירת מחדל, וכדי להפעיל אותו אנחנו חייבים קודם כול לחבר אותו לשעון המערכת. לשם כך צריך לכתוב "1" לביט 0, שנקרא "TIM2EN", ברגיסטר APB1PCENR של בקרת השעון (ה-RCC):
RCC->APB1PCENR |= 1;
אם התוכנית נעזרת בטיימר כל הזמן, או שלא אכפת לנו מצריכת החשמל הזעירה שלו, אפשר לבצע את החיבור הזה פעם אחת בתחילת הקוד וזהו.
כברירת מחדל, הטיימר סופר מ-0 עד הערך שמופיע ברגיסטר ATRLR, ואז מתחיל מהתחלה. ערך ברירת המחדל של ATRLR הוא 0xFFFF (מלוא 16 הביטים, כלומר 65,535 עשרוני). אם שעון המערכת שלי הוא 24MHz, ואני רוצה שהטיימר יריץ את הפסיקה שלו כל 100 מיליוניות השנייה, אכתוב לרגיסטר הזה את הערך 2400:
TIM2->ATRLR = 2400;
השלב הבא (שנדרש למען הסדר הטוב בכל פעם שאנחנו משנים את ATRLR או את ה-Prescaler של הטיימר, שלא דיברנו עליו כאן) הוא לאתחל את הטיימר בהתאם להגדרות החדשות. לשם כך נכתוב "1" לביט 0 (שנקרא UG) ברגיסטר SWEVGR:
TIM2->SWEVGR |= 1;
ואחרון חביב, נתניע את הטיימר ונגיד לו "רוץ!" באמצעות כתיבת "1" לביט 0 (שנקרא CEN) ברגיסטר CTRL1:
TIM2->CTRL1 |= 1;
בהנחה שעשינו את כל הפעולות הנכונות בהגדרת הפסיקה של הטיימר, פונקציית הפסיקה שכתבנו תרוץ מעכשיו בפרקי הזמן שהגדרנו, ללא התערבות של הקוד הראשי.
אפרופו, ברגיסטר CTRL1 יש ביט מעניין נוסף – ביט מס' 3 שנקרא OPM. אם נכתוב בו "1", הטיימר יכבה את עצמו אוטומטית אחרי שישלים מחזור אחד ויחיד. זאת אומרת, בהמשך לדוגמה למעלה, ככה אוכל לגרום לפסיקה לרוץ פעם אחת בלבד בתום 100 מיליוניות השנייה, וברגע שזה יקרה, הביט CEN יאופס על ידי החומרה והטיימר יפסיק לרוץ.
מגניב מאוד, אני אוהב את הפוסטים הטכניים 🙂
כמה בעצם הממשק למתכנת שונה מזה שבסדרות stm32 השונות והחיקויים שלהן?
חוץ משמות הרגיסטרים שהשתנו קצת, אני חושב שכל המודולים הפריפריאליים
גנובמבוססים על אלה של STM32. עד כדי כך שבמסגרת הלמידה של מודול ב-CH32V003 אני כבר מחפש בגוגל ישר איך עובדים עם המודול הזה ב-STM32…