שעון ו-millis ב-CH32V003

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

לוחות פיתוח ל-CH32V003 שתכננתי, וייצרתי בחסות PCBWay
לוחות פיתוח ל-CH32V003 שתכננתי, וייצרתי בחסות PCBWay

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

כמו לכל מיקרו-בקר, גם ל-CH32V003 יש פין אחד שמסוגל להוציא כפלט את תדר שעון המערכת. כאן הפונקציונליות הזו נקראת Microcontroller Clock Output – MCO, והיא "יושבת" על פין PC4. כדי להפעיל אותה צריך לחבר שעון פריפריאלי לפורט C כדי שהוא יקבל חשמל, להגדיר את PC4 בתור פין פלט Multiplexed (ערך "11" בצמד הביטים CNF4 ברגיסטר GPIOC->CFGLR), ואז לכתוב ערך לשלישיית הביטים MCO ברגיסטר RCC->CFGR0 (ביטים מס' 24-26). הערך שנכתוב יקבע אם הפלט יהיה שעון המערכת, המתנד הפנימי, המתנד החיצוני (בהנחה שיש כזה) או פלט PLL. אותנו מעניין כרגע שעון המערכת, שזה "100".

הביטים והרגיסטר להפעלת פלט ה-MCO, מתוך ה-Datasheet
הביטים והרגיסטר להפעלת פלט ה-MCO, מתוך ה-Datasheet (לחצו לתמונה גדולה)

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

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

פלט שעון מהמיקרו-בקר במצב ברירת מחדל
פלט שעון מהמיקרו-בקר במצב ברירת מחדל (לחצו לתמונה גדולה)

דף המוצר וה-Datasheet אומרים שהמיקרו-בקר הזה מסוגל לעבוד גם במהירות שעון של 48MHz, בלי צורך בגביש חיצוני. כדי לעשות זאת נצטרך להפעיל את ה-PLL. בלי להיכנס לפרטים, זהו מין טריק חומרה שמקבל כקלט את אות השעון, ומפיק פלט בתדר שהוא כפולה שלו. בשבב שלנו יש x2 PLL, מה שמסביר את המספר 48. כדי להשתמש בזה בתור שעון המערכת עלינו לבצע שלוש פעולות:

  1. להפעיל את מודול ה-PLL, באמצעות כתיבת "1" לביט 24 ("PLLON") ברגיסטר RCC->CTLR
  2. לחכות להתייצבות של ה-PLL – את זה יבשר לנו ערך "1" שיופיע בביט 25 ("PLLRDY") של אותו רגיסטר.
  3. להגיד למערכת שהשעון הראשי שלה הוא מעכשיו הפלט של ה-PLL, באמצעות כתיבת "10" לזוג הביטים "SW" (ביטים 0-1) ברגיסטר RCC->CFGR0.

אחרי כל זה, נראה בסקופ את הפלט הבא:

פלט שעון מהמיקרו-בקר, עם PLL
פלט שעון מהמיקרו-בקר, עם PLL (לחצו לתמונה גדולה)

באופן די מפתיע, לפחות לעומת המיקרו-בקרים שעבדתי איתם עד היום, אלה למעשה שתי האופציות היחידות עבור שעון המערכת הראשי כשעובדים עם מתנד פנימי. אין Prescaler או מתנד איטי חלופי בשבילו. זאת אומרת, הם קיימים, אבל רק עבור שעוני-משנה ומודולים אחרים. רוצים שהשעון הראשי יהיה איטי יותר? חברו גביש חיצוני (תחום התדרים המותר הוא 4-25MHz, לפני ה-PLL).

אם כך, סגרנו בעצם את הבסיס של עניין השעון. עכשיו צריך להיעזר במידע שיש לנו על התדר שלו כדי לבצע תיזמוני "זמן אמיתי" – ומה טוב ושימושי יותר מאשר פונקציית millis כמו זו שבארדואינו? בגדול, המשימה היא למצוא טיימר מתאים, לכוון את הספירה שלו לערך הנכון, ולהפעיל אותו כך שיעורר פסיקה פעם באלפית שנייה. בפונקציית הפסיקה נוסיף 1 למשתנה גלובלי שסופר את אלפיות השנייה החולפות. אגב, כיוון שליבת ה-CH32V003 היא 32 ביט, קריאה של משתנה בן 32 ביט אמורה להיות "אטומית" ולכן פונקציית millis אמורה להיות פשוטה יותר מאשר בארדואינו, שבו הפעולות הבסיסיות הן על 8 ביטים בלבד.

אחרי חיפוש וקריאה נוספים ב-Reference Manual גיליתי את STK – טיימר/מונה פשוט יחסית, שנבנה במיוחד למשימה הזו של תזמון "גלובלי" (כמו SysTick ב-Cortex-M של ARM). יש לו רגיסטר בקרה שקובע אם ואיך הוא יפעל, רגיסטר מונה, רגיסטר סף, ורגיסטר סטטוס עם ביט שימושי אחד בלבד (ביט מס' 0, "CNTIF") שאומר לנו אם המונה עבר את הסף. בשלב ראשון כתבתי קוד ללא פסיקות, שמגדיר את STK כך שיספור עד 24 מיליון. שימו לב שבסביבת הפיתוח, קבוצת הרגיסטרים נקראת SysTick ולא STK כמו במסמך.

קוד דוגמה לאתחול ושימוש ב-SysTick ללא פסיקות
קוד דוגמה לאתחול ושימוש ב-SysTick ללא פסיקות (לחצו לתמונה גדולה)

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

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

__attribute__((interrupt("WCH-Interrupt-fast")))

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

פונקציית פסיקה ל-SysTick עבור millis
פונקציית פסיקה ל-SysTick עבור millis (לחצו לתמונה גדולה)

שנית, כמו בכל מיקרו-בקר, אנחנו צריכים לאפשר לפסיקה עצמה לפעול. את זה עלינו לעשות בשני מקומות. ברמה הלוקלית, ביט מס' 1 ("STIE") ברגיסטר SysTick->CTLR צריך להיות עם ערך "1". ברמה הגלובלית, ביט 12 ("INTEN12") ברגיסטר PFIC->IENR[0] צריך להיות גם כן "1". איך אני יודע שדווקא ביט 12? זהו האינדקס של הפסיקה הרצויה בטבלת ווקטורי הפסיקות שמופיעה במסמך ה-Reference Manual (טבלה 6-1 / סעיף 6.3, לפחות בגרסה 1.3 של המסמך)

ומאיפה הגיע INER[0]? לא תמצאו אותו במסמך, שם יש רק INER1 ו-INER2. בקובצי ההגדרות שסביבת הפיתוח מוסיפה לפרויקט מיפו מחדש את כל הרגיסטרים של הפסיקות בצורה די מבלבלת, אבל INER1, שבו נמצא הביט שאנחנו מחפשים, ממופה ל-INER[0] אז פשוט נקבל את זה כעובדה בינתיים.

PFIC->IENR[0] = 0x1000; SysTick->SR = 0; SysTick->CMP = 24000; SysTick->CNT = 0; SysTick->CTLR = 0xF;

המספר 24000 הגיע מתדר השעון של ברירת המחדל – 24 מיליון הרץ – חלקי אלף עבור אלפית שנייה אחת.

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



להרשמה
הודע לי על
0 תגובות
Inline Feedbacks
הראה את כל התגובות