פסיקת טיימר בסיסית ב-CH32V003

בפוסט זה נפרט על הקוד וההגדרות הדרושים כדי להפעיל פסיקה מבוססת-טיימר במיקרו-בקר הסיני הזול, תוך שימוש בטיימר כללי (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 נאפס את הדגל של האירוע הלא-נכון (או של שניהם בו-זמנית), התוכנית תיתקע לגמרי, או לא תגיב לאירוע חשוב.

שלד של פונקציית פסיקה עבור TIM2
שלד של פונקציית פסיקה עבור TIM2 (לחצו לתמונה גדולה)

בקוד שבתמונה, המאפיין __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 יאופס על ידי החומרה והטיימר יפסיק לרוץ.

להרשמה
הודע לי על
2 תגובות
מהכי חדשה
מהכי ישנה לפי הצבעות
Inline Feedbacks
הראה את כל התגובות

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